Python is an easy to learn, yet powerful programming language. In general, programming languages can be divided into low-level and high-level languages.
In a low-level language you have to tell the computer very detailed and specific what to do. These very specific commands can be executed by your computer quickly, because you tell it exactly what to do.
A high-level programming language builds on top of a low-level language. You as programmer no longer have to deal with this low-level stuff. This makes programming very easy and comfortable. However, in the end your code will probably be slower compared to a code written in a low-level language.
As a beginner, you probably don't care about execution speed too much. A high-level language like python is perfect to get started. However, please don't get the impression: "Python is just something for beginners." Python is a widespread programming language used in basically all fields of computation such as science, industry or web applications.
You are currently looking at a jupyter notebook. These notebooks are one of the possibilities how to write and execute python code. A jupyter notebook consists of separate code cells. A cell can be executed either by clicking on the play button in the tool bar on top or by pressing shift + enter.
Execute the cell below!
In [ ]:
1 + 1
As you can see you can use python like a simple calculator. You can use the operators +, -, \*, / and parentheses () just like you would expect for a normal calculator. The python symbol to calculate powers is \*\*.
Use the next cell to calculate the square of 5-3 in python!
In [ ]:
For more complex calculations it is convenient to store numbers as variables. In principle, any combination of letters, numbers, and underscores which is not starting with a number can be used as a variable name. For readability purposes, it is recommended to use lowercase words separated by underscores as variable names. The = sign is used to assign a value to a variable.
Execute the following example!
In [ ]:
my_first_variable = 10
my_second_variable = 5.34
my_first_variable + my_second_variable
Let's say you want to calculate the gravitational force of some object with a mass of 4 kg.
Assign 9.81 to a variable called acceleration!
Assign 4 to a variable called mass!
Multiply both variables to calculate the gravitational force!
In [ ]:
Note, that the = is similar to the = in math, but it is not quite the same. Python always evaluates code on the right of the equal sign and assigns it to the variable on the left. So we can do:
In [ ]:
x = 5 + 2
But we can't do:
In [ ]:
5 + 2 = x
One thing we can do in python which does not make sense as a mathematical expression is shown in the following example:
In [ ]:
x = 5
x = x + 1
x
Here, we took the value of x, added 1 to it and then assigned the result back to the same name x. As a shortcut for such an operation we can use the += operator:
In [ ]:
x = 5
x += 1
x
The same also works for +=, -=, *= and /=.
Assign 3 to a variable called x!
Multiply x with 2 and assign the result to x!
Return the result by typing x!
In [ ]:
Variables can also hold data types other than numbers. For example they can store text. In programming a text piece is called a string. To tell python that something is a string and for example not a variable name, it has to be enclosed in quotes.
Execute the following example!
In [ ]:
my_first_string = 'Kartoffe'
my_second_string = 'lauf'
my_first_string + my_second_string * 2
Strings are not the only type of text in our code. Sometimes you want to add additional information to a human reader of your code but you don't want python to evaluate it. We can use the # sign to tell python that everything behind this sign in the same line is a comment and should not be evaluated. For example:
In [ ]:
5 + 7 # here we add 5 and 7
# 3 * 5 this calulation is not evaluated because it is inside the comment.
Obviously, it is not necessary to comment on every simple statement like the one above but some comments can help to make your code more readable.
In [ ]:
input_a = 5
input_b = 9
# This is the complicated calculation we want to performe for both variables:
intermediate_result = (input_a + 1) * 10 * 20 / 50
intermediate_result *= 50 - 40 / 3
result_of_a = intermediate_result / 59 * 2
# Here, I just copied the code above
# and replaced input_a with input_b and result_of_a with result_of_b
intermediate_result = (input_b + 1) * 10 * 20 / 50
intermediate_result *= 50 - 40 / 3
result_of_b = intermediate_result / 59 * 2
This code is perfectly valid. However, if you code like this your code will soon be very messy. Whenever you start copying parts of your code, there is always a better way to structure your code. In the example above we could clean up the code by defining a function which does this complicated calculation for us. A function is typically something that: takes some input, does something with the input, and then returns something. For example:
In [ ]:
def square(number):
squared = number * number
return squared
So lets go through that step by step.
Now that we have defined this function, we can use it like this:
In [ ]:
square(10)
Now we can also rewrite our "complicated calculation" example nicely. Instead of copying the code, we define a function which does this complicated calculation. Then, we can use this function whenever we need to performe such a calculation.
In [ ]:
def complicated_calculation(input_number):
intermediate_result = (input_number + 1) * 10 * 20 / 50
intermediate_result *= 50 - 40 / 3
end_result = intermediate_result / 59 * 2
return end_result
input_a = 5
input_b = 9
result_of_a = complicated_calculation(input_a)
result_of_b = complicated_calculation(input_b)
I hope you aggree that using a function in this example makes the code clearer and easier to read. Now it is your turn!
Define a function called gravitational_force wich takes an input argument mass and returns the gravitational force.
In [ ]:
# Define your function here:
# If you defined the function correctly the following statment should
# return the gravitational force acting on a mass of 4 kg
gravitational_force(4)
So far, our functions had exactly one input argument and returned one output. It is also possible to have zero or multiple inputs and or outputs. For example:
In [ ]:
def add(a, b):
return a + b
def return_5_anyway(): # note that you still need brackets even when your function takes no input
return 5
return_5_anyway() # also wenn we call a function we allways have to use the brackets
Python already comes with many built-in functions. You can find a complete list here. One very useful function is the print function. It allows you to print stuff to the output. For example:
In [ ]:
calculation_result = 13 * 5
print(calculation_result)
# or maybe even nicer:
print('the calculation result is:', calculation_result)
We can get access to even more useful functions by importing modules. For example, we can import the math module containing all kinds of mathematical functionality. After the import we can access functions in the module by typing name_of_the_module.function . For example:
In [ ]:
import math
print(math.log(5))
There are some modules like "math" which are present in every python installation and countless modules which can be installed additionally.
In [ ]:
def complicated_calculation(input_number):
intermediate_result = (input_number + 1) * 10 * 20 / 50
intermediate_result *= 50 - 40 / 3
end_result = intermediate_result / 59 * 2
return end_result
input_1 = 5
input_2 = 9
input_3 = 3
input_4 = 2
input_5 = 7
input_6 = 8
result_of_1 = complicated_calculation(input_1)
print(result_of_1)
result_of_2 = complicated_calculation(input_2)
print(result_of_2)
result_of_3 = complicated_calculation(input_3)
print(result_of_3)
result_of_4 = complicated_calculation(input_4)
print(result_of_4)
result_of_5 = complicated_calculation(input_5)
print(result_of_5)
result_of_6 = complicated_calculation(input_6)
print(result_of_6)
Luckily we already banned this complicated calculation into the body of a function, so we don't have to deal with it each time we do the calculation. Still, the code is pretty long and contains lots of repetitions.
Lists can help use to make this more efficient. Instead of creating several variables input_1, input_2, input_3 ... it is more convenient to store all the values in a list:
In [ ]:
inputs = [5, 9, 3, 2, 7, 8]
A list is defined with square brackets and list elements are separated by commas.
Whenever we want to access an element of a list we can do so by typing the list name and the index of the element in square brackets.
In [ ]:
inputs = [5, 9, 3, 2, 7, 8]
print(inputs[3])
print(inputs[0])
print(inputs[-1]) # returns the last element of the list
It is important to note that python starts counting with 0. So the first element of a list has the index 0 and not index 1.
Print the fifth element of the inputs list! (it should be a 7)
In [ ]:
inputs = [5, 9, 3, 2, 7, 8]
print(# your code goes here)
It is also possible to get slices of the complete list. The general notation is [begin:end:step]. For example:
In [ ]:
inputs = [5, 9, 3, 2, 7, 8]
print(inputs[:4]) # the first four list elements
print(inputs[1:4]) # elements from index 1 to (not including) index 4
print(inputs[::2]) # every second element
Ok. Now we have conveniently stored all our input values in a list. But how do we perform our "complicated calculation" with all these inputs? The easiest solution is to use a for loop. have a look at the following example:
In [ ]:
inputs = [5, 9, 3, 2, 7, 8]
for number in inputs:
print(number)
Let's go through that step by step:
Using a list and a for loop we can rewrite our "complicated calculation" example as follows:
In [ ]:
def complicated_calculation(input_number):
intermediate_result = (input_number + 1) * 10 * 20 / 50
intermediate_result *= 50 - 40 / 3
end_result = intermediate_result / 59 * 2
return end_result
inputs = [5, 9, 3, 2, 7, 8]
for number in inputs:
result = complicated_calculation(number)
print(result)
Now it's your turn again.
In [ ]:
Let's say we want to write a game where some kind of creatures interact with each other. During the game we have to keep track of different properties of each creature and each creature should have certain functions it can perform. To store all these properties and and functions in one place we can create an object. Have a look at the following code:
In [ ]:
# First, we have to define what properties and functions our object should have.
# Our object will later be created as an instance of the class we define here.
class Creature:
# In the __init__ function we define what happens when an instance of this class is created.
# Note that each function in an object gets the object itself as first input argument.
def __init__(self, initial_health, start_position):
self.health = initial_health # Here, we set a property of our object called health to initial_health.
self.position = start_position
# Here we define some functions (also called methods) our object should have.
def move(self, new_position):
self.position = new_position
def attack(self, target):
target.health -= 5
# Now, that we have defined what a creature is we can create instances of the creature class.
# Here, we create a creature object called bob and one called eve.
# The following statement calls the __init__ function of the Creature class
# and passes an initial_health and a start_position.
# The start position is passed as a list containing an x and a y coordinate.
bob = Creature(100, [5, 3])
eve = Creature(120, [4, 3])
# the object properties and functions can be accessed by object_name.property:
print('Bob\'s health is:', bob.health)
print('Eve\'s health is:', eve.health)
eve.attack(bob)
print('after Eve\'s attack Bob\'s health is:', bob.health)
Obviously, this is not a finished game yet but hopefully it's clear how objects can help to write code for such an example.
Now your task is to define a class yourself.
In [ ]:
# Define your class here
# Create an instance called tennis_ball here
# If you have done everything correctly the following should print
# the position, velocity and radius of the tennis ball.
print('the position of the tennis ball is:', tennis_ball.position)
print('the velocity of the tennis ball is:', tennis_ball.velocity)
print('the radius of the tennis ball is:' , tennis_ball.radius)
Okay, now the properties of the ball are set. It has a position represented by a list with an x and y coordinate. You can also think of this list as a vector. So far, you have set the x value to 0 and the y value to 1. This means, the ball is floating in a height of 1 (meter). The ball's velocity is also a vector which, so far, only contains zeros.
A ball floating in a height of 1 meter is not very realistic. The ball should be able to move! We assume that there is no friction. The only thing that changes the velocity of the ball is the gravitational force. To make the ball move you have to:
In [ ]:
class Ball:
def __init__(self, start_position, start_velocity, radius):
self.position = start_position
self.velocity = start_velocity
self.radius = radius
# so for the move function doesn't change anything. Fix it!
def move(self, time_step):
self.position[0] += 0
self.position[1] += 0
self.velocity[0] += 0
self.velocity[1] += 0
tennis_ball = Ball([0, 1], [0, 0], 0.033)
time_step = 0.04
for step in range(1, 15): # range is a built-in function which returns all number is a given range
tennis_ball.move(time_step)
print('time:', step*time_step, 'position:', tennis_ball.position)
If you fixed the code correctly, it should take the ball roughly 0.45 s to fall from a height of 1 meter before it reaches y = 0. Then, the ball continues to fall to negative y values. Again, this is not a very realistic behaviour of our ball. At y = 0 there is the floor of our room. It should perform an elastic collision and bounce up again. We somehow have to adjust our behaviour when our ball hits the floor.
In [ ]:
print(5 > 1)
print(5 < 3)
We can also check if things are equal == or not equal !=
In [ ]:
print(5 == 5) # note that we use == as = is already used for assignments in python
print(5 != 3)
Such booleans can be used to adjust the behaviour of our code in certain conditions by using the __if__-statement. All code in the body of an __if__-statement is only executed when the condition of the if statement evaluates to True. Have look at the following example:
In [ ]:
if 5 > 1:
# this is executed because 5 > 1 is True
print('5 > 1 is True')
if 5 < 3:
# this is not executed because 5 < 3 is False
print('I can write what I want. It will not be printed')
Knowing about conditions we can now update our tennis_ball code to make it bounce. Before updating the velocity, check whether the position has a negative y component. If that is the case invert the sign of velocity's y-component.
In [ ]:
class Ball:
def __init__(self, start_position, start_velocity, radius):
self.position = start_position
self.velocity = start_velocity
self.radius = radius
def move(self, time_step):
self.position[0] += self.velocity[0] * time_step
self.position[1] += self.velocity[1] * time_step
# fix the move function here to make the ball bounce
self.velocity[0] += 0
self.velocity[1] -= 9.81 * time_step
tennis_ball = Ball([0, 1], [0.2, 0], 0.033)
time_step = 0.04
number_of_steps = 150
trajectory = [] # this is an empty list
for step in range(number_of_steps):
tennis_ball.move(time_step)
trajectory.append(tennis_ball.position[:]) # append adds something at the end of a list
# don't worry about the code below too much it just crates an animation
# of your calculated positions.
from animation import animate
%matplotlib notebook
ani = animate(trajectory, time_step*100/0.04)
Probably you have noticed that the ball reaches smaller and smaller heights. It somehow looses energy although we have not included any friction. What is the reason for that?
Change the timestep to 0.004 and see how the behaviour changes!
(Hint: if you reduce the time_step you might want to increase the number_of_steps accordingly)
Great! You have just done your first own tennis ball computer simulations! And from simulating tennis balls to simulating atoms and molecules it is realy not a big step.