by Tony Saad

Assistant Professor of Chemical Engineering

University of Utah

Please press the right arrow on your keyboard to proceed.

This is an easy guide for learning Python. In my opinion, you don't need more than this guide to get started with Python. Once you get familiar with the syntax you can dig deeper on your own.

In fact, my attitude towards programming consists of the following two steps:

- Articulate what you want to accomplish (e.g.
`I need to sort this array`

) - google it! (e.g.
`how to sort an array in Python?`

)

As an engineer, programming languages are fundamental tools in my arsenal to tackle research problems. But they are only a means to an end. Therefore, I am happy to learn programming by mimicking what other professional programmers and software developers do. That's why google is your best friend.

A key reference used for this guide is: Numerical Methods in Engineering with Python 3 by Jaan Kiusalaas.

Python is a modern programming language that is very easy to use. Python promotes productivity and a clear focus on accomplishing the tasks at hand.

In an age of extreme technology and unsurpassed information exchange, it is unacceptable for a modern engineer to **NOT** know a programming language. Python stands among the easiest and most enjoyable programming languages out there.

If there's ever an example of why someone needs to learn programming, here it is:

In [7]:

```
from IPython.display import YouTubeVideo
YouTubeVideo('2o4Mk_CPqRk',width=600, height=300)
```

Out[7]:

Back in the 60s, when our nation needed it the most... It was programming that saved the day! (Thanks to Dr. Sean Smith for the suggestion to include a clip from the movie Hidden Figures)

In [88]:

```
from IPython.display import IFrame
IFrame('https://trinket.io/embed/python3/f144398a16',width="100%",height=300)
```

Out[88]:

Here's why you should learn Python:

- Because your future will likely depend on it. Programming job trends rank python among the top. Moreover, coding Dojo places python Numero Uno.
- Python is
**FREE**! - It is very easy to get started programming with Python
- Python is very forgiving
- There is significant community support around Python
- You can even use Python in a web browser!

My favorite way of obtaining python is to download the Anaconda distribution: http://www.anaconda.com. Once you install it, Python mysteriously resides on your computer.

There are a few ways you can run Python:

- Write your code in a text file with the extension
. Then from the terminal type`py`

`python myPythonCode.py`

- Use one of the editors that ship with python. Anaconda ships with
`Spyder`

. Another famous editor is called `PyCharm` . Using these editors (actually Integrated Development Environments, IDEs), you can write code and execute it on the spot. - Use
. This my favorite approach as it allows you to write Python code from within your browser and mix it with text and equation. Simply open up a terminal (command prompt on windows) and type`Jupyter Notebook`

.`jupyter notebook`

For more information on jupyter notebooks, please see this guide by Prof. James Sutherland.

Python has a bunch of key core functionality including the ability to define variables, strings, lists and other types. Let's get started:

Variables, just like any programming language, can be declared and assigned *on the spot*

In [8]:

```
myVar = 33 # my var is an integer in this case
print(myVar) # this will print the value of myVar
```

In [9]:

```
b = 3.0 * myVar # b is now floating point variable
print(b)
```

Note that Python automatically detects the data type of a variable. This is called dynamic typing.

Strings are a special type of variable that represent text. Simply use single (or double) quotations to define a string variable.

In [10]:

```
str1 = 'This is my first string variable.'
print(str1)
```

In [11]:

```
str2 = "this is also another string"
print(str2)
```

You can concatenate strings together:

In [12]:

```
str3 = str1 + str2
print(str3)
```

and you can add extra strings in between

In [13]:

```
str4 = str1 + " " + str2 + "."
print(str4)
```

Strings represent an **immutable** list of characters - that is, you *cannot* assign values of individual characters - otherwise, you will get an error.

In [14]:

```
str4[2]='b'
```

But you can certainly loop through a string:

In [15]:

```
newstr = 'string'
for val in newstr: # you will learn about this kind of loop later
print(val)
```

A tuple is an **immutable** sequence of **arbitrary** objects separated by commas and enclosed in parentheses.

In [16]:

```
a=(1,'b',2)
```

You can access items in a tuple

In [17]:

```
print(a[2])
```

but cannot modify the contents because they are immutable

In [18]:

```
a[3]=2.0
```

Lists are a like tuples but are **mutable**. They are defined using square brackets with items separated by a comma.

In [19]:

```
myList = [1,2,3,'b']
print(myList)
```

You can modify items in a list

In [20]:

```
myList[3]='a'
print(myList)
```

You can append items to a list

In [21]:

```
myList.append(43)
print(myList)
```

You can also insert items in a list at a specified location

In [22]:

```
myList.insert(3,'inserted item')
print(myList)
```

You can create a list of lists as well

In [23]:

```
b = [[1,2,3],
[5,7,8],
[17,0,9]]
print(b)
```

You can create an empty list with: ` x = [] `

and then you can append things to it.

In [24]:

```
x =[]
x.append(1)
x.append('apple')
print(x)
```

For scientific computing applications, it is recommended to use numpy arrays instead of lists. We will look at numpy arrays a little bit later.

If `b`

is a list, then the assignment `a = b`

does **NOT** create a copy of `b`

. Instead, `a`

**AND** `b`

both point to the same data. So if you change `a`

, `b`

will also change. and vice versa.

In [25]:

```
b=[1,3,5] # create a list called b
print("b = ", b) # b should be [1,3,5]
a = b # a is [1,3,5]
a[2]=1.11 # a is [1,3,1.11] BUT THIS ALSO CHANGES b!
print("a = ", a)
print("b is now also changed to: ", b) # b is [1,3,1.11]
```

If you want to make a copy, you should use: `a = b.copy()`

In [26]:

```
b=[1,3,5] # create a list called b
print("b = ", b) # b should be [1,3,5]
a = b.copy() # a is [1,3,5]
a[2]=1.11 # a is [1,3,1.11] BUT THIS ALSO CHANGES b!
print("a = ", a)
print("b remains unchanged: ", b) # b is [1,3,1.11]
```

You can display variables simply by typing them

In [27]:

```
myList
```

Out[27]:

or by using `print`

In [28]:

```
print(myList)
```

You can also format things using `print`

In [29]:

```
print('This is my list:', myList)
```

Python supports basic math operators. You can use those on almost ANYTHING in python!

Operation | Symbol |
---|---|

Addition | + |

Subtraction | - |

Multiplication | * |

Division | / |

Exponentiation | ** |

Modulus | % |

You can also use augmented assignements

Operation | Meaning |
---|---|

a += b | a = a + b |

a -= b | a = a -b |

a *= b | a = a*b |

a /=b | a = a/b |

a **= b | a = a**b |

a %= b | a = a % b |

Python also supports all basic comparison operators. These work on almost everything in Python - but make sure you understand their meaning!

Symbol | Meaning |
---|---|

< | Less than |

> | Greater than |

<= | Less than or equal to |

>= | Greater than or equal to |

== | Equal to |

!= | Not equal to |

When different data types are compared to each other, they are first converted, when possible, to a common type and then compared. Such is the case for example when comparing an integer and a float.

In [30]:

```
a = 2 # a is an integer
b = 2.1 # b is a float
print(a>b) # converts both numbers to a float
```

When a conversion to a common type is not possible, then in general, objects are considered to be unequal.

In [31]:

```
a=2
b='2.1'
print(a==b)
```

You can use if statements to analyze the condition of a variable during runtime. An if statements looks like:

```
if condition:
do something
elif condition2:
do something else
else:
all other cases
```

Don't forget indentation! For the **condition**, You can use any of the comparison operators introduced earlier.

In [32]:

```
a = 1
if a < 0.0:
print ('negative')
elif a > 0.0:
print ('positive')
else:
print('neither negative nor positive')
```

Loops are cool.

A While loop consists of the following:

```
while condition:
block
```

In [33]:

```
nMax = 3
n = 0
while n < nMax:
print ('n =', n)
n += 1
```

Sometimes, you need to do something special if the condition of the while loop is not satisfied. You can use an else statement in that case:

```
while condition:
block
else:
block
```

One of the greatest features of python is that almost everything is iterable! If you have a collection of things, you can simply use a for loop to go through the list:

```
for item in sequence:
block
```

In [34]:

```
a = [1,4,5,6,7]
for val in a: # iterate or loop through the items in a
print(val)
```

The other way to do this is to loop through a range using the `range`

function:

In [35]:

```
print('i a[i]')
print('------')
for i in range(0,3):
print (i,' ', a[i])
```

The `range`

function returns a sequence of numbers:

```
range(nMin, nMax) = [nMin, nMin + 1, nMin + 2, ..., nMax -1]
```

As usual, you can `break`

and `continue`

for loops.

In [36]:

```
# example of breaking a for loop
seq = [1,3,45,2,4]
for val in seq:
if (val == 2):
print('found value - exiting loop.')
break
print(val)
```

In [37]:

```
# example of continue in a for loop
x = [] # Create an empty list
for i in range(1,100):
if i%7 != 0: continue # If not divisible by 7, skip rest of loop
x.append(i) # Append i to the list
print(x)
```

Functions in Python are defined using the following convention:

```
def function(arg1, arg2, ...):
statements
return return_values
```

where `arg1, arg2, ...`

are the function arguments. Arguments can be any Python objects and even other functions. They can be given defaults values which makes using the argument optional.

Let's now define a function that computes first and second derivatives of any differentiable function. Here's what our function looks like:

In [38]:

```
def derivatives(f,x,h=0.0001): # h has a default value,
df =(f(x+h) - f(x-h))/(2.0*h) # compute first dervative
ddf =(f(x+h) - 2.0*f(x) + f(x-h))/h**2 # compute second derivative
return df,ddf # return first & second derivatives
```

Let's now call `derivatives`

on the inverse tangent function (`atan`

)

In [39]:

```
from math import atan # we need to import atan from the math module - you'll learn this later
df, ddf = derivatives(atan, 1.3) # call the derivatives function on atan at the point x = 1.3
print(df,ddf) # print the results
```

Functions can be documented using docstring.

In [40]:

```
def my_function(x,y,a):
"""The function's documentation goes here. you can add whatever description you like.
Arguments:
x: axial position
y: vertical position
a: acceleration
Todo:
* add error checking
"""
return x*y + a
```

Your documentation will look something like this:

Functions can be defined inline in a python file (just like we did here), or, they can be placed in ** modules**.

Modules consist of a collection of functions and placed together in a module for convenience. Functions in a module can be accessed by *importing* them from the module

```
from module_name import function
```

Modules are simply Python files (.py). The module name is the same as the filename.

There are three ways to access functions in a module:

```
from module_name import *
```

Which loas ALL functions in the module. While this is certainly allowed, it is (1) wasteful since Python will load all functions definitions into memory, and (2) it can lead to ambiguity if other modules have similar function defitions. For example, the `sine`

function is defined in the modules: `math`

, `cmath`

, and `numpy`

.

In [41]:

```
from math import *
print(exp(0.1))
```

```
from module_name import function1, function2,...
```

Imports only the specified functions from the module. This is certainly safer but may lead to conflicts if you're not careful

In [42]:

```
from math import exp, log, sinh
x = 0.23
print(exp(x**2) + log(x) - sinh(x/2))
```

```
import module_name
```

Imports the module which allows you to access function definitions in the module using: `module_name.function1`

etc...
This is by far the least ambiguous path.

You can also nickname a module for easy access

```
import module_name as m
```

Then, you'd access functions in `module_name`

using: `m.function1`

.

In [43]:

```
import cmath as cm # cmath library supports complex numbers
Ï€ = cm.pi # define pi
x = Ï€*1j # complex number
cm.exp(x) # e^(iÏ€) = -1
```

Out[43]:

The contents of a module can be listed using:

```
dir(module_name)
```

In [44]:

```
import math
dir(math)
```

Out[44]:

Another module that you may find useful is the `cmath`

module. It supports the same functions as the `math`

module but allows the use of complex numbers for function arguments.

In [45]:

```
import cmath
dir(cmath)
```

Out[45]:

Numpy is one of the most famous python libraries for scientific computing. `num`

stands for numerical and `py`

stands for python (yeah, Python programmers are obsessed with using `py`

in naming their projects). Numpy is the standard library for numerical computing with python. It provides powerful array functionality that beats python lists. Let's look at some numpy features.

Numpy arrays are similar to Python lists but can be manipulated in fantastic ways by other functions in the numpy library. They are by far the standard in python-based scientific computing. See here for more details.

A numpy array can be created several ways. It can be created from a list

In [46]:

```
import numpy as np # don't forget to import numpy
myList = [[2,4],[2,3]] #
a = np.array(myList)
print(a)
```

It can also be created from a tuple

In [47]:

```
myTuple = (1,2,3)
a = np.array(myTuple)
print(a)
```

You can initialize empty numpy arrays:

In [48]:

```
a = np.empty((2,2)) # 2x2 array
print(a)
```

You can also create arrays of zeros or ones:

In [49]:

```
a = np.zeros((3,3))
b = np.ones((2,2))
print(a)
print(b)
```

One useful numpy function is `arange`

. It works like the standard `range`

function, but returns an array instead of a sequence. It looks like this:

```
arange(start, stop, step)
```

In [50]:

```
a = np.arange(0,10,3) # go from 0 to 10 in steps of 3
print(a)
```

In [51]:

```
for i in np.arange(-2,3,2): # use arange to loop
print(i)
```

Another useful numpy functionality is `linspace`

. With `linspace`

, you can create an array of equally spaced real numbers. This is particularly useful if you're defining a grid. Here's an example

In [52]:

```
x = np.linspace(-1,1,10)
print(x)
```

you can use linspace to compute (and plot) a function

In [53]:

```
%matplotlib inline
import matplotlib.pyplot as plt # you will learn this later
import numpy as np
x = np.linspace(-1,1,100)
y = np.exp(-x**2/0.1)
plt.plot(x,y)
```

Out[53]:

Numpy arrays can be accessed using the bracket operator and are indexed by row and column (by default). If a is an `nxn`

array, then `a[i,j]`

refers to row `i`

and column `j`

.

In [54]:

```
a = np.zeros((2,2)) # create a 2x2 array of zeros
print(a)
a[0,1] = 33 # change element a[0,1]
print(a)
```

On the other hand, `a[i]`

refers to the *entire* ith row

In [55]:

```
a = np.zeros((2,2)) # create a 2x2 array of zeros
a[0] = [1,2]
a[1] = [3,4]
print(a)
```

and `a[:,j]`

refers to the *entire* jth column.

In [56]:

```
a = np.zeros((2,2)) # create a 2x2 array of zeros
a[:,0] = [1,3]
a[:,1] = [2,4]
print(a)
```

The colon in `a[:,0]`

allows you to select a subset of an array. In general is works as: `i:j`

will return the subset array consisting of the elements `a[i]`

to `a[j-1]`

In [57]:

```
a = np.arange(1,10)
print(a)
print(a[1:4]) # return the subset array a[1] to a[3]
```

If no numbers are provided around the colon (i.e. `a[:]`

), then the entire range is returned

In [58]:

```
a = np.arange(1,10)
print(a[:])
```

Here's a more complex example of how you can slice through a 4x4 matrix

In [59]:

```
a = np.zeros((4,4)) # create an array of zeros
print(a)
```

In [60]:

```
# now change row 1, columns 1,2
a[1,1:3] = [1.11,2.22]
print(a)
```

In [61]:

```
#now change column 3, rows 2,3
a[2:4,3]=[3.33,4.44]
print(a)
```

You can operate on arrays using standard mathematical operators as long as the array sizes are consistent with the operation being carried out. All operations are carried out element-wise - that is, applied to each element at a time.

In [62]:

```
a = np.arange(0,10) #[0,1,2,...,9]
b = np.arange(10,20) #[10,11,...,19]
a*b
```

Out[62]:

Mathematical functions provided by numpy will operate on numpy arrays element-wise

In [63]:

```
a = np.arange(0,10) #[0,1,2,...,9]
np.sqrt(a) # use sqrt provided by numpy
```

Out[63]:

However, functions provided by other modules, such as the math module, are not guaranteed to work with numpy arrays

In [64]:

```
import math
a = np.arange(0,10) #[0,1,2,...,9]
math.sqrt(a)
```

In this case, the functions provided by the math module will work on one element at a time, not an entire list, e.g.

```
math.sqrt(a[3])
```

In [66]:

```
import numpy as np
A = np.array([[4,-2,1],
[-2,4,-2],
[1,-2,3]])
d = np.diagonal(A) # main diagonal
ud = np.diagonal(A,1) # first upper diagonal
ld = np.diagonal(A,-1) # first lower diagonal
print('main diagonal = ', d)
print('lower diagonal = ', ld)
print('upper diagonal = ', ud)
```

you can also take the trace

In [67]:

```
tr = np.trace(A)
print(tr)
```

you can create an identity matrix

In [68]:

```
idm = np.identity(3)
print(idm)
```

In [69]:

```
from numpy import *
x = array([7,3])
y = array([2,1])
A = array([[1,2],[3,2]])
B = array([[1,1],[2,2]])
# Dot product
print("dot(x,y) =\n",dot(x,y)) # {x}.{y}
print("dot(A,x) =\n",dot(A,x)) # [A]{x}
print("dot(A,B) =\n",dot(A,B)) # [A][B]
```

There's a shorter way of computing dot products. Instead of using `dot(x,y)`

, you can use `[email protected]`

In [70]:

```
from numpy import *
x = array([7,3])
y = array([2,1])
A = array([[1,2],[3,2]])
B = array([[1,1],[2,2]])
# Dot product
print("[email protected] =\n",x@y) # {x}.{y}
print("[email protected] =\n",A@x) # [A]{x}
print("[email protected] =\n",A@B) # [A][B]
```

This depends on your having Python3 installed. Try it, and if it works, then you're likely using python3.

In [71]:

```
from numpy import *
x = array([7,3])
y = array([2,1])
A = array([[1,2],[3,2]])
B = array([[1,1],[2,2]])
# Inner product
print("inner(x,y) =\n",inner(x,y)) # {x}.{y}
print("inner(A,x) =\n",inner(A,x)) # [A]{x}
print("inner(A,B) =\n",inner(A,B)) # [A][B_transpose]
```

In [72]:

```
from numpy import *
x = array([7,3])
y = array([2,1])
A = array([[1,2],[3,2]])
B = array([[1,1],[2,2]])
# Outer product
print("outer(x,y) =\n",outer(x,y))
print("outer(A,x) =\n",outer(A,x))
print("outer(A,B) =\n",outer(A,B))
```

Numpy ships with a linear algebra module named ** linalg** that contains a collection of routines to aid in the solution and manipulation of systems of linear equations.

In [73]:

```
from numpy import array, dot
from numpy.linalg import inv,solve # import inverse and solve from linalg
A = array([[ 4.0, -2.0, 1.0],
[-2.0, 4.0, -2.0],
[ 1.0, -2.0, 3.0]])
b = array([1.0, 4.0, 2.0])
Ainv = inv(A)
x = solve(A,b) # use built in solve
print(x)
xInv = dot(Ainv,b) # use Ainv . b
print(xInv)
```

or how to plot anything in Python!

Matplotlib is the de-facto standard for plotting in Python. Like numpy, matplotlib is a module and supports plotting all sorts of pythonic things.

Here's a simple example plot:

In [74]:

```
%matplotlib inline
import matplotlib.pyplot as plt # pyplot is the standard plotting library in matplotlib
import numpy as np
x = np.linspace(-1,1,200) # create 200 equally spaced points between -1 and 1
y = np.exp(-x**2/0.01) # gaussian
plt.plot(x,y) # plot y vs x
```

Out[74]:

you will notice that we imported `pyplot`

from `matplotlib`

. Also note that the statement `%matplotlib inline`

is necessary **only** when using jupyter notebooks.

you can label axes and add titles to plots:

In [75]:

```
%matplotlib inline
import matplotlib.pyplot as plt # pyplot is the standard plotting library in matplotlib
import numpy as np
x = np.linspace(-1,1,200) # create 200 equally spaced points between -1 and 1
y = np.exp(-x**2/0.01) # gaussian
plt.title('Gaussian')
plt.xlabel('xlabel', fontsize=18)
plt.ylabel('ylabel', fontsize=16)
plt.plot(x,y) # plot y vs x
```

Out[75]:

you can plot multiple curves

In [76]:

```
%matplotlib inline
import matplotlib.pyplot as plt # pyplot is the standard plotting library in matplotlib
import numpy as np
x = np.linspace(-1,1,200) # create 200 equally spaced points between -1 and 1
y = np.exp(-x**2/0.01) # gaussian
y2 = np.exp(-x**2/0.1)
plt.title('Gaussians')
plt.xlabel('position', fontsize=18)
plt.ylabel('value', fontsize=16)
plt.plot(x,y,x,y2) # plot y vs x and y2 vs x
```

Out[76]:

you can also add markers and line styles

In [77]:

```
%matplotlib inline
import matplotlib.pyplot as plt # pyplot is the standard plotting library in matplotlib
import numpy as np
x = np.linspace(-1,1,200) # create 200 equally spaced points between -1 and 1
y = np.exp(-x**2/0.01) # gaussian
y2 = np.exp(-x**2/0.1)
plt.title('Gaussians')
plt.xlabel('position', fontsize=18)
plt.ylabel('value', fontsize=16)
plt.plot(x,y,'r-*',x,y2,'b-.o') # plot y vs x and y2 vs x
```

Out[77]: