 INF-495 - Modelado Computacional Aplicado

 Prof. Claudio Torres, Ph.D.

 Version: 1.02

## Textbook: Computational Mathematical Modeling, An Integrated Approach Across Scales by Daniela Calvetti and Erkki Somersalo.

# Chapter 2

```python
import numpy as np
import scipy.sparse.linalg as sp
import sympy as sym
from scipy.linalg import toeplitz

import ipywidgets as widgets
from ipywidgets import IntSlider

import matplotlib.pyplot as plt
%matplotlib inline
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter

plt.style.use('ggplot')

import matplotlib as mpl
mpl.rcParams['font.size'] = 14
mpl.rcParams['axes.labelsize'] = 20
mpl.rcParams['xtick.labelsize'] = 14
mpl.rcParams['ytick.labelsize'] = 14
sym.init_printing()

from scipy.integrate import solve_ivp
from ipywidgets import interact
```

## 2.1 Simple compartment models

### Example 1:

```python
# Example 1, numerically.
def f_example_1(t,y,c,k1,k2):
    X = y[0]
    Y = y[1]

    Xp = c(t)-k1*X
    Yp = k1*X-k2*Y

    return np.array([Xp,Yp])

# Initial condition
y0 = np.array([500, 600])
a  = 1200
t1 = 12*60
t2 = 19*60
tau = 30
c  = lambda t: a*(np.exp(-((np.mod(t,24*60)-t1)**2)/(2*tau**2)) \
                +np.exp(-((np.mod(t,24*60)-t2)**2)/(2*tau**2))) \
                /(np.sqrt(2*np.pi*tau**2))

T_digest = 80
T_metabolic = 240
k1 = 1/T_digest
k2 = 1/T_metabolic

T = 2*24*60

# time where we want your solution
t = np.linspace(0, T, 100)
sol = solve_ivp(f_example_1, [0,T], y0, t_eval=t, args=(c,k1,k2))

plt.figure()
plt.plot(t, sol.y[0], 'b', label='X(t): Energy in gastric channel')
plt.plot(t, sol.y[1], 'r', label='Y(t): Energy in blood')
plt.legend(loc='right', bbox_to_anchor=(2, 0.5))
plt.xlabel('t')
plt.ylabel('kcal')
plt.grid(True)
plt.show()
```

```python
print(t1,t2)
```

    720 1140

```python
plt.figure()
T = 5*24*60
t = np.linspace(0, T, 1000)
plt.plot(t,c(t),'b')
plt.grid(True)
plt.plot()
```

## 2.1.1.- Simple population models

### Adding competition for resources: Logistic growth

```python
# Example 1, numerically.
def f_spm_logistic_growth(t,y,r,K):
    X = y
    Xp = r*(1-X/K)*X
    return Xp

# Initial condition
r = 1
K = 1000

T = 10

# time where we want your solution
t = np.linspace(0, T, 100)

plt.figure()
for y0 in np.linspace(0,2000,20):
    sol = solve_ivp(f_spm_logistic_growth, [0,T], (y0,), t_eval=t, args=(r,K))
    plt.plot(t, sol.y[0], 'b')
plt.xlabel('t')
plt.ylabel('population size')
plt.grid(True)
plt.show()
```

### Logistic growth with harvesting

```python
def f_logistic_growth_harvesting(t,y,r,K,h):
    X = y
    Xp = r*(1-X/K)*X-h
    return Xp

# Initial condition
r = 1
K = 1000
h = 100
print(K*r/4,h)

T = 8

Xplus = K/2*(1+np.sqrt(1-4*h/(K*r)))
Xminus = K/2*(1-np.sqrt(1-4*h/(K*r)))
print(Xplus,Xminus)

# time where we want your solution
t = np.linspace(0, T, 100)

plt.figure()
N=20
y0s = np.zeros(N+1)
y0s[0] = Xminus-0.5
y0s[1:] = np.linspace(Xminus,1200,N)
for y0 in y0s:
    sol = solve_ivp(f_logistic_growth_harvesting, [0,T], (y0,), t_eval=t, args=(r,K,h))
    plt.plot(t, sol.y[0], 'b')

plt.plot(t, sol.y[0]*0+Xplus, 'r')
plt.plot(t, sol.y[0]*0+Xminus, 'r')
plt.plot(t, sol.y[0]*0, 'r--')
plt.xlabel('t')
plt.ylabel('population size')
plt.grid(True)
plt.show()
```

```python
plt.figure()
f = lambda X: -(r/K)*X**2+r*X-h
Xs = np.linspace(0,Xplus+200,100)
plt.plot(Xs, f(Xs), 'b')
plt.plot(Xs, f(Xs)*0, 'r')
plt.axvline(x=Xminus,color='k',label='$X_-$')
plt.axvline(x=Xplus,color='g',label='$X_+$')
plt.xlabel('X')
plt.legend(loc='right', bbox_to_anchor=(1.3, 0.5))
plt.grid(True)
plt.show()
```

### Models with delay

### Example 2

Differential equations with delay are not currently supported by Scipy, fortunately there are the following options:

- For Python: pydelay, http://pydelay.sourceforge.net
- For R: "PBSddesolve: Solver for delay differential equations", https://github.com/pbs-software/pbs-ddesolve
- For Python using PBSddesolve: https://github.com/hensing/PyDDE
- (*) For Python: https://github.com/Zulko/ddeint

```python
# See: https://github.com/Zulko/ddeint
from ddeint import ddeint

r = 2         # Growth rate
K = 100       # Carrying capacity
tau = 1       # Lag of crowding
X_hist = 50   # Constant history
T = 30        # Time simulation

def values_before_zero(t):
    return X_hist

def f_delay1(Y, t):
    return r*(1-Y(t - tau)/K)*Y(t)

t = np.linspace(0, T, 1000)
sol = ddeint(f_delay1, values_before_zero, t)

fig, ax = plt.subplots(1, figsize=(4, 4))
ax.plot(t, sol)
plt.show()
```

```python
r = 1.4       # Growth rate
K = 150       # Carrying capacity
tau1 = 1      # Lag of birth
tau2 = 5      # Lag of crowding
X_hist = 100  # Constant history
T = 300       # Time simulation
beta = 0.5    # Rate of death

def values_before_zero(t):
    return X_hist

def f_delay2(Y, t):
    return r*Y(t-tau1)-beta*Y(t)-(r/K)*Y(t - tau2)*Y(t)

t = np.linspace(0, T, 2000)
sol = ddeint(f_delay2, values_before_zero, t)

fig, ax = plt.subplots(1, figsize=(4, 4))
ax.plot(t, sol)
plt.show()
```

## 2.2.- Interacting population models

## 2.2.1.- Predator-prey models

### Example 3

```python
# Example 1, numerically.
def f_example_3(t,y,eta,gamma,eps):
    X = y[0]
    Y = y[1]

    Xp = eta*X*(1-Y/(eta/gamma))
    Yp = -eps*Y*(1-X/(eps/delta))

    return np.array([Xp,Yp])

# Initial condition

eta = 8-1/2
eps = 1/5
gamma = 1/100
delta = eps*gamma/(20*eta)

X_eq = eps/delta
Y_eq = eta/gamma

y0 = np.array([0.9*X_eq, 1.2*Y_eq])

T = 15

# time where we want your solution
t = np.linspace(0, T, 100)
sol = solve_ivp(f_example_3,[0,T], y0, t_eval=t, args=(eta,gamma,eps))

X_output = sol.y[0]
Y_output = sol.y[1]

fig, ax1 = plt.subplots()
color = 'tab:blue'
ax1.set_xlabel('time [years]')
ax1.set_ylabel('Prey population', color=color)
ax1.plot(t, X_output, color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx()  # instantiate a second axes that shares the same x-axis
color = 'tab:green'
ax2.set_ylabel('Predator population', color=color)  # we already handled the x-label with ax1
ax2.plot(t, Y_output, color=color)
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout()  # otherwise the right y-label is slightly clipped
plt.show()

plt.figure()
plt.plot(t,X_output,'b')
plt.plot(t,Y_output,'r')
plt.grid(True)
plt.show()
```

```python
plt.figure()
plt.plot(X_output,Y_output,'k')
plt.xlabel('prey')
plt.ylabel('predator')
plt.plot(X_eq,Y_eq,'b.',markersize=20)
plt.plot(y0[0],y0[1],'r.',markersize=20)
plt.text(X_eq,Y_eq,'  equilibrium state')
plt.text(y0[0],y0[1],'  initial state')
plt.show()
```

```python
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot(X_output, Y_output, t,'b')
ax.view_init(elev=40,azim=230)
plt.xlabel('prey')
plt.ylabel('predator')
ax.set_zlabel('t')
plt.show()
```

## 2.2.2.- SIR models in epidemiology

```python
# Example 1, numerically.
def f_SIR(t,y,alpha,beta):
    S = y[0]
    I = y[1]
    R = y[2]

    Sp = -alpha*I*S
    Ip = alpha*I*S-beta*I
    Rp = beta*I

    return np.array([Sp,Ip,Rp])

# Initial condition
N = 1e6
alpha = 1e-6
beta = 1/3

S0 = 9e5
I0 = 1e5
y0 = np.array([S0, I0, N-S0-I0])

T = 20

# time where we want your solution
t = np.linspace(0, T, 100)
sol = solve_ivp(f_SIR, [0,T], y0, t_eval=t, args=(alpha,beta))

plt.figure()
plt.plot(t, sol.y[0], 'b', label='S(t)')
plt.plot(t, sol.y[1], 'r', label='I(t)')
plt.plot(t, sol.y[2], 'g', label='R(t)')
plt.legend(loc='right', bbox_to_anchor=(1.3, 0.5))
plt.xlabel('t')
plt.ylabel('number of individuals')
plt.grid(True)
plt.show()
```

## 2.2.3.- Endemic diseases

```python
# Example 1, numerically.
def f_SIR_endemic(t,y,alpha,beta,sigma,N):
    S = y[0]
    I = y[1]
    R = y[2]

    Sp = -alpha*I*S+sigma*N-sigma*S
    Ip = alpha*I*S-beta*I-sigma*I
    Rp = beta*I-sigma*R

    return np.array([Sp,Ip,Rp])

# Initial condition
N = 1e6
alpha = 1e-6
beta = 1/3
sigma = 1/50

S0 = 9e5
I0 = 1e5
y0 = np.array([S0, I0, N-S0-I0])

T = 150

# time where we want your solution
t = np.linspace(0, T, 100)
sol = solve_ivp(f_SIR_endemic, [0,T], y0, t_eval=t, args=(alpha,beta,sigma,N))

plt.figure()
plt.plot(t, sol.y[0], 'b', label='S(t)')
plt.plot(t, sol.y[1], 'r', label='I(t)')
plt.plot(t, sol.y[2], 'g', label='R(t)')
plt.legend(loc='right', bbox_to_anchor=(2, 0.5))
plt.xlabel('t')
plt.ylabel('number of individuals')
plt.grid(True)
plt.show()
```

# 2.2 Subcompartment models

 Example 5

```python
# Volumnes
Vol_ch = 50./1000
Vol_t  = 0.3/1000

# Initial conditions in chamber
Lac_ch_0 = 0.3
Glc_ch_0 = 1.6
O2_ch_0  = 0.6
CO2_ch_0 = 0.1
x0 = np.array([Lac_ch_0,Glc_ch_0,O2_ch_0,CO2_ch_0])

# Initial conditions in tissue
Lac_t_0  = 0.8
Glc_t_0  = 0.5
O2_t_0   = 0.14
CO2_t_0  = 0.2
ATP_t_0  = 6.2
ADP_t_0  = 0.8
y0 = np.array([Lac_t_0,Glc_t_0,O2_t_0,CO2_t_0,ATP_t_0,ADP_t_0])

# Maximum rates
T1 = 5e-5
T2 = 3e-5
V1 = 1e-4
V2 = 1e-4

# Affinities
M1 = 0.5
M2 = 0.5
K1 = 0.5
K2 = 0.22
mu1 = 0.1
mu2 = 0.1

#Fick's constants
lambda3 = 3e-3
lambda4 = 8e-3

# Constants defining the activity
t0  = 3. # min
tau = 10./60 # min

# Activity levels
a0      = 1e-5
delta_a = 1e-2

def f_example_5(t,z):
    x = z[:4]
    y = z[4:]

    Phi = np.zeros(4)
    Phi[0] = T1*((x[0]/(x[0]+M1))-(y[0]/(y[0]+M1)))
    Phi[1] = T2*((x[1]/(x[1]+M1))-(y[1]/(y[1]+M1)))
    Phi[2] = lambda3*(x[2]-y[2])
    Phi[3] = lambda4*(x[3]-y[3])

    p = y[5]/y[4]
    Psi1 = V1*(p/(p+mu1))*(y[1]/(y[1]+K1))
    rho1 = np.array([2.,-1,0,0,2,-2])*Psi1

    Psi2 = V2*(p/(p+mu2))*(y[1]*y[2]/(y[1]*y[2]+K2**2))
    rho2 = np.array([0.,-1,-6,6,32,-32])*Psi2

    a = a0+delta_a*(np.heaviside(t-(t0), 1)-np.heaviside(t-(t0+tau), 1))
    rho3 = np.array([0.,0,0,0,-1,1])*a

    dxdt = -Phi/Vol_ch

    dydt = np.zeros(6)
    dydt[:4] = Phi
    dydt += rho1+rho2+rho3
    dydt /=Vol_t
    return np.concatenate((dxdt,dydt), axis=0)

# Initial condition
z0 = np.concatenate((x0,y0), axis=0)

T = 10
# time where we want your solution
t = np.linspace(0, T, 100)

sol = solve_ivp(f_example_5, [0,T], z0, t_eval=t)

plt.figure(figsize=(10,10))

plt.subplot(221)
plt.plot(t, sol.y[4], 'b-', label='Lac_t(t)')
plt.plot(t, sol.y[5], 'b--', label='Glc_t(t)')
plt.legend(loc='best')
plt.axvline(x=t0,color='k',alpha=0.5)
plt.axvline(x=t0+tau,color='k',alpha=0.5)
plt.xlabel('t [min]')
plt.ylabel('concentration [mmol/l]')
plt.ylim([0.2,1])
plt.grid(True)

plt.subplot(222)
plt.plot(t, sol.y[6], 'b-', label='O2_t(t)')
plt.plot(t, sol.y[7], 'b--', label='CO2_t(t)')
plt.legend(loc='best')
plt.axvline(x=t0,color='k',alpha=0.5)
plt.axvline(x=t0+tau,color='k',alpha=0.5)
plt.xlabel('t [min]')
#plt.ylabel('concentration [mmol/l]')
plt.ylim([0,0.8])
plt.grid(True)

plt.subplot(223)
plt.plot(t, sol.y[8], 'b-', label='ATP_t(t)')
plt.plot(t, sol.y[9], 'b--', label='ADP_t(t)')
plt.legend(loc='best')
plt.axvline(x=t0,color='k',alpha=0.5)
plt.axvline(x=t0+tau,color='k',alpha=0.5)
plt.xlabel('t [min]')
plt.ylabel('concentration [mmol/l]')
plt.ylim([0,8])
plt.grid(True)

plt.subplot(224)
plt.plot(t, sol.y[2], 'b-', label='O2_Ch(t)')
plt.legend(loc='best')
plt.axvline(x=t0,color='k',alpha=0.5)
plt.axvline(x=t0+tau,color='k',alpha=0.5)
plt.xlabel('t [min]')
#plt.ylabel('concentration [mmol/l]')
plt.ylim([0.58,0.61])
plt.grid(True)

plt.tight_layout()
plt.show()
```

```python

```

SC4/Chapter 2 - Compartment models.ipynb


## First Assignment

#### 1) Apply the appropriate string methods to the **x** variable (as '.upper') to change it exactly to: "$Dichlorodiphenyltrichloroethane$".

```python
x = "DiClOrod  IFeNi  lTRicLOr oETaNo  DiChlorod iPHeny lTrichL oroEThaNe"
```

```python
y = x.replace(' ','')
print(y[27:].capitalize())
```

    Dichlorodiphenyltrichloroethane

#### 2) Assign respectively the values: 'word', 15, 3.14 and 'list' to variables A, B, C and D in a single line of code. Then, print them in that same order on a single line separated by a space, using only one print statement.

```python
A, B, C, D = 'word', 15, 3.14, 'list'
print(f'{A} {B} {C} {D}')
```

    word 15 3.14 list

#### 3) Use the **input()** function to receive an input in the form **'68.4 1.71'**, that is, two floating point numbers in a line separated by space. Then, assign these numbers to the variables **w** and **h** respectively, which represent an individual's weight and height (hint: take a look at the '.split()' method). With this data, calculate the individual's Body Mass Index (BMI) from the following relationship:

\begin{equation}
BMI = \dfrac{weight}{height^2}
\end{equation}

```python
weight, height = input("Enter weight and height, seperated by space: ").split()
BMI = float(weight)/float(height)**2
print(BMI)

```

#### This value can also be classified according to ranges of values, following to the table below. Use conditional structures to classify and print the classification assigned to the individual.

<\center>

(source: https://healthtravelguide.com/bmi-calculator/)

```python
if BMI < 18.5:
    print("Underweight")
elif BMI <= 24.9:
    print("Normal weight")
elif BMI <= 29.9:
    print("Pre-obesity")
elif BMI <= 34.9:
    print("Obesity class I")
elif BMI <= 39.9:
    print("Obesity class II")
else:
    print("Obesity class III")
```

    Obesity class III

#### 4) Receive an integer as an input and, using a loop, calculate the factorial of this number, that is, the product of all the integers from one to the number provided.

```python
number = int(input("Insert an integer:"))
fact = 1
for i in range(1, number+1, 1):
    fact = fact * i
print(fact)
```

    Insert an integer:14
    87178291200

#### 5) Using a while loop and the input function, read an indefinite number of integers until the number read is -1. Present the sum of all these numbers in the form of a print, excluding the -1 read at the end.

```python
sum_input, number_input = 0, 0

while number_input != -1:
    sum_input += number_input
    number_input = int(input("Enter an integer: "))
print(f"Sum of input = {sum_input}")
```

    Enter an integer: 4
    Enter an integer: 6
    Enter an integer: -1
    Sum of input = 10

#### 6) Read the **first name** of an employee, his **amount of hours worked** and the **salary per hour** in a single line separated by commas. Next, calculate the **total salary** for this employee and show it to two decimal places.

```python
name, hours, salary = input("Insert name of the employee, amount of hours worked and salary per hour, separated by commas:").split(",")
totalSalary = float(hours)*float(salary)
print(f"{name} earns {round(totalSalary,2)}")
```

    Insert name of the employee, amount of hours worked and salary per hour, separated by commas:Heinz, 23, 193.440021
    Heinz earns 4449.12

#### 7) Read three floating point values **A**, **B** and **C** respectively. Then calculate itens a, b, c, d and e:

```python
x, y, z = input("Enter the values for A, B, C separated by comma").split(",")
A, B, C = float(x), float(y), float(z)

```

    a) the area of the triangle rectangle with A as the base and C as the height.

```python
print("Area of the triangle:", A*C/2)
```

    Area of the triangle: 5.0

    b) the area of the circle of radius C. (pi = 3.14159)

```python
print("Area of the circle:", 3.14159*C**2)
```

    Area of the circle: 78.53975

    c) the area of the trapezoid that has A and B for bases and C for height.

```python
print("Area of the trapezoid:", (A+B)*C/2)
```

    d) the area of the square that has side B.

```python
print("Area of the square:", B**2)
```

    Area of the square: 9.0

    e) the area of the rectangle that has sides A and B.

```python
print("Area of the rectangle:", A*B)
```

    Area of the rectangle: 6.0

#### 8) Read **the values a, b and c** and calculate the **roots of the second degree equation** $ax^{2}+bx+c=0$ using [this formula](https://en.wikipedia.org/wiki/Quadratic_equation). If it is not possible to calculate the roots, display the message **“There are no real roots”**.

```python
a, b, c = [float(x) for x in input("Enter the values of a, b and c, separated by commas: ").split(",")]
root  = (b**2) - (4*a*c)
if   root < 0:
     print("There are no real roots")
elif root == 0:
        x = -b/(2*a)
        print(f"Root: {x}")
else:
        x1 = (-b+root**2)/(2*a)
        x2 = (-b-root**2)/(2*a)
        print(f"Roots: {x1}, {x2}")

```

    Enter the values of a, b and c, separated by commas: 2, 6666, 1
    Roots: 493629481513409.5, -493629481516742.5

#### 9) Read four floating point numerical values corresponding to the coordinates of two geographical coordinates in the cartesian plane. Each point will come in a line with its coordinates separated by space. Then calculate and show the distance between these two points.

(obs: $d=\sqrt{(x_1-x_2)^2 + (y_1-y_2)^2}$)

```python
x1, x2 = [float(a) for a in input("Enter coordinates x1 and x2, separated by comma ").split(",")]
y1, y2 = [float(a) for a in input("Enter coordinates y1 and y2: separated by comma ").split(",")]
d = ((x1-x2)**2+(y1-y2)**2)**0.5
print(f"d = {d}")
```

    Enter coordinates x1 and x2, separated by comma 2, 6
    Enter coordinates y1 and y2: separated by comma 6, 2
    d = 5.656854249492381

#### 10) Read **two floating point numbers** on a line that represent **coordinates of a cartesian point**. With this, use **conditional structures** to determine if you are at the origin, printing the message **'origin'**; in one of the axes, printing **'x axis'** or **'y axis'**; or in one of the four quadrants, printing **'q1'**, **'q2**', **'q3'** or **'q4'**.

```python
x, y = [float(x) for x in input("Insert a float for x, y: ").split(",")]

if x == 0 and y == 0:
    print('Origin')
elif x == 0:
    print('y axis')
elif y == 0:
    print('x axis')
elif x > 0 and y > 0:
    print('q1')
elif x > 0:
    print('q4')
elif y > 0:
    print('q2')
else:
    print('q3')
```

#### 11) Read an integer that represents a phone code for international dialing.
#### Then, inform to which country the code belongs to, considering the generated table below:
(You just need to consider the first 10 entries)

```python
import pandas as pd
df = pd.read_html('https://en.wikipedia.org/wiki/Telephone_numbers_in_Europe')[1]
df = df.iloc[:,:2]
df.head(20)
```

      Country
      Country calling code

      0
      Austria
      43

      1
      Belgium
      32

      2
      Bulgaria
      359

      3
      Croatia
      385

      4
      Cyprus
      357

      5
      Czech Republic
      420

      6
      Denmark
      45

      7
      Estonia
      372

      8
      Finland
      358

      9
      France
      33

      10
      Germany
      49

      11
      Greece
      30

      12
      Hungary
      36

      13
      Iceland
      354

      14
      Ireland
      353

      15
      Italy
      39

      16
      Latvia
      371

      17
      Liechtenstein
      423

      18
      Lithuania
      370

      19
      Luxembourg
      352

```python
country_code = int(input("Enter the country calling code:"))
code_dict = {43:'Austria',
             32:'Belgium',
             359:'Bulgaria',
             385:'Croatia',
             357:'Cyprus',
             420:'Czech Republic',
             45:'Denmark',
             372:'Estonia',
             359:'Finland',
             33:'France',
            }
if country_code in code_dict.keys():
    print(code_dict[country_code])
else:
    print('Cannot find country.')
```

    Enter the country calling code:33
    France

#### 12) Write a piece of code that reads 6 numbers in a row. Next, show the number of positive values entered. On the next line, print the average of the values to one decimal place.

```python
numbers = [float(x) for x in input("Enter six values separated by commas: ").split(",")]

pos, total = 0, 0
for i in numbers:
    total += i
    if i > 0:
        pos += 1
print(f"{pos} positive numbers")
print(f"Average = {round(total/6 , 1)}")
```

    Enter six values separated by commas: 5, 2, 5, 7, 2, 0
    5 positive numbers
    Average = 3.5

#### 13) Read an integer **N**. Then print the **square of each of the even values**, from 1 to N, including N, if applicable, arranged one per line.

```python
N = int(input("Enter integer: "))
for x in range(1,N+1,1):
    if x%2==0:
        print(x**2)
```

    Enter integer: 10
    4
    16
    36
    64
    100

#### 14) Using **input()**, read an integer and print its classification as **'even / odd'** and **'positive / negative'** . The two classes for the number must be printed on the same line separated by a space. In the case of zero, print only **'null'**.

```python
number = int(input("Enter an integer:"))
class_even = "even" if number%2 == 0 else "odd"
class_pos = "positive" if number == abs(number) else "negative"
if number == 0:
    print("null")
else:
    print(class_even, class_pos)
```

    Enter an integer:-15
    odd negative

## Challenge
#### 15) Ordering problems are recurrent in the history of programming. Over time, several algorithms have been developed to fulfill this function. The simplest of these algorithms is the [**Bubble Sort**](https://en.wikipedia.org/wiki/Bubble_sort), which is based on comparisons of elements two by two in a loop of passes through the elements. Your mission, if you decide to accept it, will be to input six whole numbers ramdonly ordered. Then implement the **Bubble Sort** principle to order these six numbers **using only loops and conditionals**.
#### At the end, print the six numbers in ascending order on a single line separated by spaces.

```python

```


###### Content under Creative Commons Attribution license CC-BY 4.0, code under MIT license (c)2014 L.A. Barba, G.F. Forsyth.

# Coding Assignment: Rocket

The equations of motion for a rocket in purely vertical flight are given by

$$
\begin{align}
\frac{dh}{dt} &= v\\
(m_s+m_p) \frac{dv}{dt}& = -(m_s+m_p)g + \dot{m}_pv_e - \frac{1}{2}\rho v|v|AC_D
\end{align}
$$

$h$ is the altitude of the rocket

$m_s = 50kg$ is the weight of the rocket shell

$g = 9.81 \frac{m}{s^2}$

$\rho = 1.091 \frac{kg}{m^3}$ is the average air density (assumed constant throughout flight)

$A = \pi r^2$ is the maximum cross sectional area of the rocket, where $r = 0.5 m$

$v_e = 325 \frac{m}{s}$ is the exhaust speed

$C_D = 0.15 $ is the drag coefficient

$m_{po} = 100 kg$ at time $t = 0$ is the initial weight of the rocket propellant

The mass of the remaining propellant is given by:

$$m_p = m_{po} - \int^t_0 \dot{m}_p d\tau$$

where $\dot{m}_p$ is the time-varying burn rate given by the following figure:

Propellant Burn Rate

that is,

$$
\begin{equation}
    \dot{m}_p \left( t \right) =
    \begin{cases}
        20 & \quad \text{if} \quad t < 5 \\
        0 & \quad \text{otherwise}
    \end{cases}
\end{equation}
$$

Using Euler's method with a time-step size of $\Delta t=0.1s$, create a Python script to calculate the altitude and velocity of the rocket from launch until crash down.

## Assessment:

To check your answers, you can register for [MAE 6286: Practical Numerical Methods with Python](https://openedx.seas.gwu.edu/courses/course-v1:MAE+MAE6286+2017/about).

1. At time $t=3.2s$, what is the mass (in kg) of rocket propellant remaining in the rocket?

2. What is the maximum speed of the rocket in $\frac{m}{s}$?
    At what time does this occur (in seconds)?
    What is the altitude at this time (in meters)?

3. What is the rocket's maximum altitude during flight (in meters)? At what time (in seconds) does this occur?

4. At what time (in seconds) does the rocket impact the ground? What is the velocity of the rocket (in $\frac{m}{s}$) at time of impact?

## Derivation of the rocket equations

In case you are kind of confused about the rocket equations, here we show how to get to them.

Newton's second law states that the acceleration of the vehicle times its mass is equal to all the forces acting on it. Therefore,

\begin{equation}
(m_s + m_p)\frac{d\bf{v}}{dt}=\sum {\bf F}.
\end{equation}
In the above formula we have assumed that the propellant inside the rocket and the rocket move at the same velocity (in other words, their relative velocity is negligible).

Two of the external forces acting on the rocket are,

\begin{align}
{\bf F}_g&= (m_s+m_p)\bf{g} \quad (\rm{Gravity}),\\
{\bf F}_d&= - \frac{1}{2} \rho_a \mathbf{v} |\mathbf{v}| A  C_D \quad (\rm{Drag}).
\end{align}

We also need to consider the force resulting from the ejection of the propellant. During an interval $dt$, the engine of the rocket ejects downwards a mass of propellant given by $\dot m_p dt$. Relative to the rocket, the speed of the ejected burning gas is assumed constant and equal to $v_e$ (the exhaust speed). The momentum variation induced on the exhaust gas by the engine during that interval is therefore, $d{\bf p}_{gas} = \dot m_p {\bf v}_e dt$. Again using Newton's second law we conclude that the force applied by the rocket on the gas is,

\begin{align}
{\bf F}_{rocket\rightarrow gas} = \frac{d{\bf p}_{gas}}{dt} = \dot m_p {\bf v}_e
\end{align}

Using Newton's third law (|action| = |reaction|), the force exerted by the exhaust gas on the rocket is then,

\begin{align}
{\bf F}_{gas\rightarrow rocket} = -{\bf F}_{rocket\rightarrow gas} = -\dot m_p {\bf v}_e
\end{align}

If we collect all the forces acting on the rocket we finally have:

\begin{align}
(m_s + m_p)\frac{d\bf{v}}{dt}=(m_s+m_p){\bf g}- \frac{1}{2} \rho_a \mathbf{v} |v| A  C_D -\dot m_p {\bf v}_e
\end{align}

---

###### The cell below loads the style of the notebook.

```python
from IPython.core.display import HTML
css_file = '../../styles/numericalmoocstyle.css'
HTML(open(css_file, 'r').read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nixie+One' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'>
<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 750px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1000px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
    margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Alegreya Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:600px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Nixie One', serif;
    font-style:regular;
    font-weight: 400;
    font-size: 45pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}

.text_cell_render h2 {
    font-family: 'Nixie One', serif;
    font-weight: 400;
    font-size: 30pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Nixie One', serif;
    margin-top:16px;
    font-size: 22pt;
    font-weight: 600;
    margin-bottom: 3px;
    font-style: regular;
    color: rgb(102,102,0);
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Nixie One', serif;
    font-size: 14pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Nixie One', sans-serif;
    font-weight: 400;
    font-size: 16pt;
    color: rgb(163,0,0);
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.8em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
    font-family: "Source Code Pro";
    font-size: 90%;
}

.alert-box {
    padding:10px 10px 10px 36px;
    margin:5px;
}

.success {
    color:#666600;
    background:rgb(240,242,229);
}
</style>

```python
import numpy as np
from matplotlib import pyplot
%matplotlib inline
```

#### Create the time grid

```python

T = 100.0  # length of the time-interval
dt = 0.1  # time-step size
N = int(T / dt) + 1  # number of time steps
t = np.linspace(0.0, T, num=N)  # time grid
```

#### Set given values/coefficients

```python
h_o=0 #initial height [m]
m_s=50 #weight of rocket [kg]
g=9.81 #gravitational force [m/s^2]
rho=1.091 #density of air [kg/m^3]
r=0.5 #max radius of rocket [m]
A=np.pi*r**2 #xc Area of rocket [m^2]
v_e = 325 #exhaust speed [m/s]
C_D = 0.15 #drag coeff
m_po=100 #initial (t=0) weight of propellent
```

#### Mass equation

```python
m_p_dot=np.zeros_like(t)
for i in range(len(m_p_dot)):
    if (t[i]<=5):
        m_p_dot[i]=20
    else:
        m_p_dot[i]=0
m_p=np.zeros_like(t)
for i in range(len(m_p)):
    if (t[i]<=5):
        m_p[i]=m_po-(t[i]*20)
    else:
        m_p[i]=0
mass=[]
for i in range(len(t)):
    if (t[i]<=5):
        mass.append(m_s+100-t[i]*m_p_dot[i])
    else:
        mass.append(50)
```

```python
pyplot.plot(t[:100],mass[:100])
pyplot.xlabel('time: first 10 seconds [s]')
pyplot.ylabel('Weight of total rocket and fuel [kg]');
```

#### Define Euler function

```python
def eulerstep(state,rhs,dt):
    '''Update a state to the next time increment using Euler's method

    Arguments:
    state : array of dependant vars (independant var is time)
    rhs   : function that computes the rigth hand side of a diff eq
    dt    : float, time increment

    Returns:
    next_state: array, updates state after one time step
    '''

    next_state = state + rhs(state)*dt

    return next_state
```

#### Write equation of motion

```python
def rocket(state):
    '''Compute rhe right hand side of the string mass diff eq

    Arguments:
    state    : array of state variables, (h,v)

    Returns  :
    derivs   : arrray of derivatives (v,e.o.m.)
    '''
    for i in range(len(m_p_dot)):
        if(t[i]<=5):
            derivs =np.array([state[1], -g+(m_p_dot[i]*v_e)/(m_s+m_p[i])-(0.5/(m_s+m_p[i]))*rho*state[1]*abs(state[1])*A*C_D])
        else:
            derivs=np.array([state[1],-g-(0.5/m_s)*rho*state[1]*abs(state[1])*A*C_D])

    return derivs
```

#### Initialise state of rocket array

```python
sol=np.zeros([N,2])#numerical solution [h,v]
sol[0,0]=h_o #initial hieght = 0m
sol[0,1]=v_e #initial velocity =exhaust speed [m/s]
```

```python
for i in range(N-1):
    sol[i+1] = eulerstep(sol[i],rocket,dt)
#finding the numerical solutions using euler step and right hand side of rocket
```

```python
print(sol)
```

    [[    0.           325.        ]
     [   32.5          310.4429755 ]
     [   63.54429755   297.07487772]
     ...
     [-6127.7854987    -87.36380815]
     [-6136.52187952   -87.36380815]
     [-6145.25826033   -87.36380816]]

```python
pyplot.plot(t,sol[:,0])
```

```python

```


# The Discrete Fourier Transform

*This Jupyter notebook is part of a [collection of notebooks](../index.ipynb) in the bachelors module Signals and Systems, Comunications Engineering, Universität Rostock. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Definition

The [discrete Fourier transform](https://en.wikipedia.org/wiki/Discrete_Fourier_transform) (DFT) is defined as

\begin{equation}
X[\mu] = \sum_{k=0}^{N-1} x[k] \; e^{-j \mu \frac{2 \pi}{N} k}
\end{equation}

where $N \in \mathbb{N}$ denotes the length of the DFT and $\mu \in \mathbb{Z}$ the discrete frequency. The DFT maps a discrete signal $x[k]$ to its discrete spectrum $X[\mu] = \text{DFT}_N \{ x[k] \}$. Since its kernel $e^{-j \mu \frac{2 \pi}{N} k}$ is periodic with period $N$ with respect to the discrete frequency $\mu$, the spectrum $X[\mu]$ is periodic with period $N$

\begin{equation}
X[\mu] = X[\mu + \nu N]
\end{equation}

for $\nu \in \mathbb{Z}$. It follows from the periodicity that the unique range for $\mu = 0,1, \dots, N-1$. For $k = 0,1, \dots, N-1$ the inverse discrete Fourier transform (IDFT) is consequently defined as

\begin{equation}
x[k] = \frac{1}{N} \sum_{\mu=0}^{N-1} X[\mu] \; e^{j \mu \frac{2 \pi}{N} k}
\end{equation}

The kernel $e^{j \mu \frac{2 \pi}{N} k}$ of the IDFT is periodic with respect to the sample index $k$. It follows that $x[k] = \text{IDFT}_M \{ X[\mu] \}$ is periodic

\begin{equation}
x[k] = x[k + \nu N]
\end{equation}

Although $k$ and $\mu$ can take any integer value it can be concluded from the periodicity, that the DFT is only unique for $k, \mu = 0, 1, \dots N-1$. As shown later, the periodicity of the DFT and IDFT has wide-ranging consequences for the properties and theorems of the DFT.

The kernel of the DFT is often represented in terms of the primitive $N$-th [root of unity](https://en.wikipedia.org/wiki/Root_of_unity) abbreviated by $w_N = e^{-j \frac{2 \pi}{N}}$

\begin{equation}
X[\mu] = \sum_{k=0}^{N-1} x[k] \; w_N^{\mu k}
\end{equation}

The kernel of the IDFT is given by reversing the sign of the exponent of $w_N$. Other common definitions of the DFT and IDFT may be found in the literature. They differ with respect to the sign of the exponent and the normalization factor $\frac{1}{N}$. As a consequence, the properties, theorems and transforms may differ from the ones given here.

### Link to the Discrete-Time Fourier Transform

The [discrete-time Fourier transform](../discrete_time_fourier_transform/definition.ipynb) (DTFT) $X(e^{j \Omega}) = \mathcal{F}_* \{ x[k] \}$ of a causal signal of finite length $x[k] = 0$ for $k \in \mathbb{Z} \setminus \{0,1,\dots, N-1 \}$ is continuous with respect to the normalized angular frequency $\Omega$. The DFT $X[\mu]$ of the same signal is discrete. In order to derive the relation between both transforms, the definition of the DTFT is compared to the definition of the DFT. This reveals that both are linked by

\begin{equation}
X[\mu] = X(e^{j \Omega}) \big\rvert_{\Omega = \mu \frac{2 \pi}{N}}
\end{equation}

The DFT $X[\mu]$ of a finite-length discrete signal $x[k]$ can be derived by sampling its DTFT $X(e^{j \Omega})$ equidistantly at $\Omega = \mu \frac{2 \pi}{N}$.

The questions arises if the DFT $X[\mu]$ fully represents a discrete signal $x[k]$ of finite length? In order to elaborate on this, the DTFT of a discrete signal is investigated. The [DTFT of a discrete signal is periodic](../discrete_time_fourier_transform/definition.ipynb#Definition) with period $2 \pi$. This holds also for a discrete signal $x[k]$ of finite length. This is depicted below

The [spectrum of a periodic signal](../periodic_signals/spectrum.ipynb) consists of a series of weighted Dirac impulses. It can be described fully by the weights of the Dirac impulses. The same holds also for the spectrum $X_\text{p} (e^{j \Omega})$ of the periodic continuation $x_\text{p}[k] = \sum_{\nu = - \infty}^{\infty} x[k - \nu N]$ of the finite length signal $x[k]$. The weights of the Dirac pulses are given by equidistant sampling of the spectrum of one period $X(e^{j \Omega})$ at $\Omega = \mu \frac{2 \pi}{N}$. This is depicted below

It can be concluded from above reasoning that the DFT of a causal finite-length discrete signal contains all information on the signal.

### Interpretation of the Spectrum

Inspection of the definition of the inverse DFT reveals, that a discrete signal $x[k]$ of finite length can be represented as a superposition of weighted harmonic exponential signals $z^k$ with the complex frequencies $z = e^{j \mu \frac{2 \pi}{N}}$ for $\mu = 0, 1, \dots, N-1$. The corresponding normalized angular frequencies of the exponential signal are given as $\Omega = \mu \frac{2 \pi}{N}$. In the following, the complex frequencies $z$ are illustrated in the $z$-plane for the case of an even ($N=10$) and odd length ($N=11$) of the DFT

The distribution of the complex frequencies in the $z$-plane is different for an even or odd length of the DFT. Only in the case of an even length, the normalized frequency $\Omega = \pi$ is included. For a further interpretetation of the discrete frequency $\mu$, the link between the DFT and the DTFT is used. The DFT $X[\mu]$ can be derived from the DTFT by sampling $X(e^{j \Omega})$ at $\Omega = \mu \frac{2 \pi}{N}$. The DTFT $X(e^{j \Omega})$ is sampled in the range of $0 \leq \Omega < 2 \pi$. Since the DTFT is periodic with a period of $2 \pi$, it can be conluded that the DTFT $X(e^{j \Omega})$ for $\pi < \Omega < 2 \pi$ is equal to the DTFT for $-\pi < \Omega < 0$. It follows, that the DFT $X[\mu]$ for $\frac{N}{2} < \mu \leq (N-1)$ is equal to the sampled DTFT for [negative normalized angular frequencies](https://en.wikipedia.org/wiki/Negative_frequency) $-\pi < \Omega < 0$. The resulting normalized frequency axis of the DFT is illustrated for the case of an even length ($N=10$)

For a discrete signal which has been derived by sampling from a continuous signal $x[k] = x(k T)$, the normalized angular frequency $\Omega$ can be related to the frequency $f$ by $\Omega = 2 \pi \frac{f}{f_\text{s}}$, where $T$ and $f_\text{s} = \frac{1}{T}$ denote the sampling interval and frequency, respectively. The lowest axis illustrates the resulting frequency axis. It can be concluded that the first half of the spectrum represents the complex values of $X[\mu]$ for increasing positive frequencies, while the second half represents the complex values for decreasing negative frequencies. This structure has to be kept in mind when interpreting a DFT.

The DFT of a discrete signal $x[k] \in \mathbb{C}$ is in general complex valued, $X[\mu] \in \mathbb{C}$. It is commonly illustrated by its real and imaginary part

\begin{equation}
X[\mu] = \Re \{ X[\mu] \} + j \cdot \Im \{ X[\mu] \}
\end{equation}

or by its magnitude and phase

\begin{equation}
X[\mu] = |X[\mu]| \cdot e^{j \varphi[\mu]}
\end{equation}

The magnitude spectrum $|X[\mu]|$ provides insights into the superposition of a discrete signal in terms of the level of its harmonic contributions. It is therefore a valuable tool for the analysis of signals and systems.

### Matrix/Vector Representation

Above definition of the DFT is given in terms of a summation over the samples $x[k]$ for $k=0, 1, \dots, N-1$. The summation has to be evaluated for $\mu = 0, 1, \dots, N-1$ in order to yield the DFT $X[\mu] = \text{DFT}_N \{ x[k] \}$. The calculation can be represented in terms of a matrix/vector multiplication. First the $N$ samples of the signal $x[k]$ are combined into the $N \times 1$ vector

\begin{equation}
\mathbf{x} = \left[ x[0], x[1], \dots, x[N-1] \right]^T
\end{equation}

In the same manner, the $N$ samples of the spectrum $X[\mu]$ are combined  into the $N \times 1$ vector

\begin{equation}
\mathbf{X} = \left[ X[0], X[1], \dots, X[N-1] \right]^T
\end{equation}

The computation of all $N$ values of $X[\mu]$ from all $N$ values of $x[k]$ can now be expressed as matrix/vector product

\begin{equation}
\mathbf{X} = \mathbf{F} \mathbf{x}
\end{equation}

where the $N \times N$ [DFT matrix](https://en.wikipedia.org/wiki/DFT_matrix) is defined as

\begin{equation}
\mathbf{F} = \left[ \begin{array}{ccccc}
1 & 1 & 1 & \dots & 1 \\
1 & w_N & w_N^2 & \dots & w_N^{N-1} \\
1 & w_N^2 & w_N^6 & \dots & w_N^{2 (N-1)} \\
\vdots & \vdots & \vdots & \ddots & \vdots \\
1 & w^{N-1} & w^{2(N-1)} & \dots & w^{(N-1)(N-1)}
\end{array} \right]
\end{equation}

Above DFT matrix is, besides the scaling factor $N$, [unitary](https://en.wikipedia.org/wiki/Unitary_matrix)

\begin{equation}
\mathbf{F} \, \mathbf{F}^H = N \cdot \mathbf{I}
\end{equation}

where $(\cdot)^H$ denotes the [Hermitian](https://en.wikipedia.org/wiki/Hermitian_adjoint) (conjugate transpose) of a matrix and $\mathbf{I}$ the $N \times N$ [identity matrix](https://en.wikipedia.org/wiki/Identity_matrix). Multiplying the left- and right-hand side of above matrix/vector representation of the DFT from the left by $\frac{1}{N} \mathbf{F}^H$ yields the representation of the inverse DFT as

\begin{equation}
\mathbf{x} = \frac{1}{N} \cdot \mathbf{F}^H \mathbf{X}
\end{equation}

The matrix/vector representation of the DFT is useful for the discussion of its properties and its implementation. Functions for the computation of the DFT matrix are provided in many toolkits for numerical mathematics. In the following the Python function [`scipy.linalg.dft`](http://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.dft.html) is used. The real part of $w_N^\mu$ for $\mu=0, 1, \dots, N-1$ is plotted for illustration of the kernel of the DFT. The application of the matrix/vector representation is shown in the following section.

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from scipy.linalg import dft

F = dft(8)

plt.figure(figsize=(10, 10))
for n in range(8):
    ax = plt.subplot(4, 2, n+1)
    ax.stem(np.real(F[:, n]))
    plt.title('$\mu = %d$'%n)
    plt.ylabel(r'$\Re \{ e^{-j \mu \frac{2 \pi}{N} k} \}$')
    plt.xlabel('$k$')
plt.tight_layout()
```

### Transformation of the Dirac Impulse

The $\text{DFT}_N \{ \delta[k] \}$ of the [Dirac impulse](../discrete_signals/standard_signals.ipynb#Dirac-Impulse) is derived by introducing $\delta[k]$ into the definition of the DFT and exploiting the sifting property of the Dirac impulse

\begin{equation}
\text{DFT}_N \{ \delta[k] \} = \sum_{k = 0}^{N-1} \delta[k] \, e^{-j \mu \frac{2 \pi}{N} k} = 1
\end{equation}

The transform of the Dirac impulse is equal to one. Hence, all discrete frequencies $\mu$ are present with equal weight. This is an important property in the theory of discrete signals and systems, since the Dirac impulse is used to characterize linear time-invariant (LTI) systems by their [impulse response](../discrete_systems/impulse_response.ipynb) $h[k] = \mathcal{H} \{ \delta[k] \}$.

**Exercise**

* Derive the $\text{DFT}_N \{ \delta[k - \kappa] \}$ of the shifted Dirac impulse for $0 \leq \kappa < N$.

### Transformation of the Exponential Signal

The DFT $X[\mu] = \text{DFT}_N \{ e^{j \Omega_0 k} \}$ of the [harmonic exponential signal](../discrete_signals/standard_signals.ipynb#Complex-Exponential-Signal) with normalized frequency $\Omega_0$ is derived by introducing it into the definition of the DFT. This results in

\begin{equation}
X[\mu] = \sum_{k=0}^{N-1} e^{j \Omega_0 k} \; e^{-j \mu \frac{2 \pi}{N} k}
= \sum_{k=0}^{N-1} e^{j k (\Omega_0 - \mu \frac{2 \pi}{N})}
= \frac{1 - e^{j (\Omega_0 - \mu \frac{2 \pi}{N}) N}}{1 - e^{j (\Omega_0 - \mu \frac{2 \pi}{N})}}
= e^{-j (\Omega_0 - \mu \frac{2 \pi}{N} ) \frac{N-1}{2} } \cdot \frac{\sin \left( \frac{N (\Omega_0 - \mu \frac{2\pi}{N})}{2} \right)}{\sin \left( \frac{ \Omega_0 - \mu \frac{2\pi}{N}}{2} \right)}
\end{equation}

The second last equality has been derived by noting that the sum constitutes a [finite geometrical series](https://en.wikipedia.org/wiki/Geometric_series) with the common ratio $e^{-j (\Omega - \mu \frac{2 \pi}{N})}$.

Unlike the [DTFT of the exponential signal](../discrete_time_fourier_transform/definition.ipynb#Transformation-of-the-Exponential-Signal), its DFT is in general not given by a series of shifted Dirac impulses. In order to gain further insight, let's consider an harmonic exponential signal which is periodic with period $N$. The lowest normalized frequency such a periodic signal can have is $\Omega_\text{min} =\frac{2 \pi}{N}$. Note that an exponential signal whose frequency is a multiple of $\Omega_\text{min}$ is also periodic with period $N$. It can be concluded that the normalized frequency of an harmonic exponential signal with period $N$ is given as $\Omega_0 = M \cdot \frac{2 \pi}{N}$ for $M = 0,1,\dots, N-1$. Introducing this specific choice for $\Omega_0$ into the DFT above yields

\begin{equation}
\text{DFT}_N \{ e^{j M \cdot \frac{2 \pi}{N} k} \} = N \cdot \delta[\mu - M]
\end{equation}

since

\begin{equation}
\frac{\sin \left(\pi (M-\mu) \right)}{\sin \left(\frac{\pi}{N} (M - \mu) \right)} = \begin{cases}
N & \text{for } M=\mu \\
0 & \text{otherwise}
\end{cases}
\end{equation}

Note that [L'Hôpital's rule](https://en.wikipedia.org/wiki/L'H%C3%B4pital's_rule) was applied for $M=\mu$. The DFT of an harmonic exponential signal which is periodic with period $N$ is given as a Dirac impulse. The case of an aperiodic exponential signal is discussed in the following example.

**Example**

In the following example the DFT of an harmonic exponential signal $e^{j \Omega_0 k}$ with $\Omega_0 = M \cdot \frac{2 \pi}{N}$ is computed by the matrix/vector representation of the DFT. The signal is periodic with period $N$ for $M \in \mathbb{Z}$ and aperiodic for $M \notin \mathbb{Z}$. The latter case is considered first, the former in the exercises below.

```python
N = 16
M = 3.33
W0 = M * 2*np.pi/N

F = dft(N)
k = np.arange(N)
x = np.exp(1j*W0*k)
X = np.matmul(F, x)
X
```

    array([ 1.31266387+5.28685729e-01j,  1.63083183+1.06667837e+00j,
            2.38037882+2.33409311e+00j,  7.49352177+1.09799457e+01j,
           -2.57047932-6.03735081e+00j, -0.54711115-2.61602209e+00j,
           -0.01688123-1.71945225e+00j,  0.24191786-1.28184690e+00j,
            0.40512534-1.00587811e+00j,  0.52515992-8.02910730e-01j,
            0.62378131-6.36151064e-01j,  0.71244674-4.86226008e-01j,
            0.79885258-3.40121704e-01j,  0.88992974-1.86118644e-01j,
            0.99422729-9.76111721e-03j,  1.12563462+2.12436536e-01j])

The real and imaginary part of the signal $x[k]$ as well as its DFT $X[\mu]$ are plotted for illustration

```python
plt.figure(figsize=(10, 8))

plt.subplot(2, 2, 1)
plt.stem(x.real)
plt.xlabel('$k$')
plt.ylabel('$\Re \{ x[k] \}$')
plt.ylim([-1.1, 1.1])

plt.subplot(2, 2, 2)
plt.stem(x.imag)
plt.xlabel('$k$')
plt.ylabel('$\Im \{ x[k] \}$')
plt.ylim([-1.1, 1.1])

plt.subplot(2, 2, 3)
plt.stem(X.real)
plt.xlabel('$\mu$')
plt.ylabel('$\Re \{ X[\mu] \}$')
plt.ylim([-N, N])

plt.subplot(2, 2, 4)
plt.stem(X.imag)
plt.xlabel('$\mu$')
plt.ylabel('$\Im \{ X[\mu] \}$')
plt.ylim([-N, N]);

plt.tight_layout()
```

**Exercise**

* Rerun the example with different normalized frequencies $\Omega_0 = M \frac{2 \pi}{N}$ resulting in periodic or aperiodic harmonic exponential signals.
* Where is the maximum of the real and imaginary part, and the absolute value $|X[\mu]|$ located in case of an aperiodic signal?

It should become obvious that depending on the chosen frequency a more or less 'smeared' Dirac impulse can be observed. This effect is known as [leakage effect](https://en.wikipedia.org/wiki/Spectral_leakage) of the DFT, since the Dirac impulse leaks into the neighboring bins.

**Example**

In order to facilitate the interpretation of a spectrum, many libraries for signal processing provide functionality to compute the values of the frequency axis for a sampled signal. This is illustrated for the spectrum $X[\mu]$ of the exponential signal from the previous example. The magnitude spectrum $|X[\mu]|$ is plotted for a sampling frequency of $f_\text{s} = 48000$ Hz.

```python
fs = 48000
f = np.fft.fftfreq(N, 1/fs)

plt.stem(f, abs(X))
plt.xlabel('$f[\mu]$ in Hz')
plt.ylabel(r'$|X[\mu]|$')
plt.xlim([-fs/2, fs/2]);
```

**Copyright**

This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources). Feel free to use the notebook for your own purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Sascha Spors, Continuous- and Discrete-Time Signals and Systems - Theory and Computational Examples*.


# Optimization

> Marcos Duarte
> Laboratory of Biomechanics and Motor Control ([http://demotu.org/](http://demotu.org/))
> Federal University of ABC, Brazil

If there occur some changes in nature, the amount of action necessary for this change must be as small as possible.

Maupertuis (sec XVIII)

**Optimization is the process of finding the best value from possible alternatives with regards to a certain criteria** ([Wikipedia](http://en.wikipedia.org/wiki/Mathematical_optimization)).

Typically, such best value is the value that maximizes or minimizes the criteria. In this context, to solve a (mathematical) optimization problem is to find the maximum or minimum (a.k.a., a stationary point) of a function (and we can use maximum or minimum interchangeably because the maximum of a function is the minimum of the negative of that function).
To solve an optimization problem, we first have to model the problem and define the objective, the variables, and the constraints of the problem. In optimization, these terms are usually defined as:

1. Objective function (or also, cost, loss, utility, or fitness function): a function describing what we want to optimize.
2. Design variable(s): variables that will be manipulated to optimize the cost function.
3. Constraint functions: a set of constraints, equalities or inequalities that constrains the possible solutions to possible values of the design variables (candidate solutions or feasible solutions or feasible set).

A feasible solution that minimizes (or maximizes) the objective function is called an optimal solution.

The optimization problem is the calculation of the minimum or maximum values of an objective function over a set of **unknown** possible values of the design variables.
Even in case of a finite number of possible values of the objective function and design variables (e.g., after discretization and a manual or a grid search), in general the evaluation of the objective function is computationally expensive and should be avoided.
Of note, even if there is no other option, a random search is in fact more efficient than a manual or a grid search! See [Bergstra, Bengio (2012)](http://jmlr.csail.mit.edu/papers/volume13/bergstra12a/bergstra12a.pdf).

A typical problem of optimization: [Knapsack problem](https://en.wikipedia.org/wiki/Knapsack_problem).

Read more about that in [Introduction to Optimization](http://neos-guide.org/content/optimization-introduction) from the  [NEOS Guide](http://neos-guide.org/).

## Some jargon in mathematical optimization

 - **Linear versus nonlinear optimization**: linear optimization refers to when the objective function and the constraints are linear mathematical functions. When the objective function is linear, an optimal solution is always found at the constraint boundaries and a local optimum is also a global optimum. See [Wikipedia 1](https://en.wikipedia.org/wiki/Linear_programming) and [Wikipedia 2](https://en.wikipedia.org/wiki/Nonlinear_programming).
 - **Constrained versus unconstrained optimization**: in constrained optimization there are no constraints.
 - **Convex optimization**: the field of optimization that deals with finding the minimum of convex functions (or the maximum of concave functions) over a convex constraint set. The convexity of a function facilitates the optimization because a local minimum must be a global minimum and first-order conditions (the first derivatives) are sufficient conditions for finding the optimal solution. Note that although convex optimization is a particular case of nonlinear optimization, it is a relatively simple optimization problem, with robust and mature methods of solution. See [Wikipedia](https://en.wikipedia.org/wiki/Convex_optimization).
 - **Multivariate optimization**: optimization of a function of several variables.
 - **Multimodal optimization**: optimization of a function with several local minima to find the multiple (locally) optimal solutions, as opposed to a single best solution.
 - **Multi-objective optimization**: optimization involving more than one objective function to be optimized simultaneously.
 - **Optimal control**: finding a control law for a given system such that a certain optimality criterion is achieved. See [Wikipedia](https://en.wikipedia.org/wiki/Optimal_control).
 - **Quadratic programming**: optimization of a quadratic function subject to linear constraints. See [Wikipedia](https://en.wikipedia.org/wiki/Quadratic_programming).
 - **Simplex algorithm**: linear optimization algorithm that begins at a starting vertex and moves along the edges of the polytope (the feasible region) until it reaches the vertex of the optimum solution. See [Wikipedia](https://en.wikipedia.org/wiki/Simplex_algorithm).

## Maxima and minima

In mathematics, the maximum and minimum of a function are the largest and smallest values that the function takes at a point either within a neighborhood (local) or on the function entire domain  (global) ([Wikipedia](http://en.wikipedia.org/wiki/Maxima_and_minima)).

For a function of one variable, if the maximum or minimum of a function is not at the limits of the domain and if at least the first and second derivatives of the function exist, a maximum and minimum can be found as the point where the first derivative of the function is zero. If the second derivative on that point is positive, then it's a minimum, if it is negative, it's a maximum.

Figure. Maxima and minima of a function of one variable.

 - Note that the requirement that the second derivative on the extremum to be positive for a minimum or negative for a maximum is sufficient but not a necessary condition. For instance, the function $f(x)=x^4$ has an extremum in $x=0$ since $f'(x)=4x^3$ and $f'(0)=0$, but its second derivative at $x=0$ is also zero: $f''(x)=12x^2;\: f''(0)=0$. In fact, the requirement is that the first non-zero derivative on that point should be positive for a minimum or negative for a maximum: $f''''(0)=24$; the extremum is a minimum.

Let's now apply optimization to solve a problem with a univariate function.

```python
# import Python libraries
import numpy as np
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
import sympy as sym
from sympy.plotting import plot
import pandas as pd
from IPython.display import display
from IPython.core.display import Math
```

### Example 1: Maximum volume of a cardboard box

We want to make a box from a square cardboard with side $a$ such that its volume should be maximum.
What is the optimal distance where the square cardboard should be cut and folded to make a box with maximum volume?

Figure. A box to be made from a cardboard such that its volume should be maximum. Where we should cut?

If the distance where to cut and fold the cardboard is $b$, see figure above, the volume of the box will be:

\begin{equation}
\begin{array}{l l}
V(b) = b(a-2b)(a-2b) \\
\\
V(b) = a^2b - 4ab^2 + 4b^3
\end{array}
\label{}
\end{equation}

In the context of optimization:
**The expression for $V$ is the cost function, $b$ is the design variable, and the constraint is that feasible values of $b$ are in the interval $]0, \dfrac{a}{2}[$, i.e., $b>0$ and $b<\dfrac{a}{2}$.**

The first and second derivatives of $V$ w.r.t. $b$ are:

\begin{equation}
\begin{array}{l l}
\dfrac{\mathrm{d}V}{\mathrm{d}b} = a^2 - 8ab + 12b^2 \\
\\
\dfrac{\mathrm{d}^2 V}{\mathrm{d}b^2} = - 8a + 24b
\end{array}
\label{}
\end{equation}

We have to find the values for $b$ where the first derivative of $V$ is zero (the extrema) and then use the expression for the second derivative of $V$ to find whether each of these extrema is a minimum (positive value) or a maximum (negative value).
Let's use Sympy for that:

```python
a, b = sym.symbols('a b')
V = b*(a - 2*b)*(a - 2*b)
Vdiff = sym.expand(sym.diff(V, b))
roots = sym.solve(Vdiff, b)
display(Math(sym.latex('Roots:') + sym.latex(roots)))
```

$$Roots:\left [ \frac{a}{6}, \quad \frac{a}{2}\right ]$$

Discarding the solution $b=\dfrac{a}{2}$ (where $V=0$, which is a minimum), $b=\dfrac{a}{6}$ results in the maximum volume.
We can check that by plotting the volume of the cardboard box for $a=1$ and $b: [0,\:0.5]$:

```python
plot(V.subs({a: 1}), (b, 0, .5), xlabel='b', ylabel='V')
display(Math(sym.latex('V_{a=1}^{max}(b=%s)=%s'
                       %(roots[0].evalf(n=4, subs={a: 1}), V.evalf(n=3, subs={a: 1, b: roots[0]})))))
```

 - Note that although the problem above is a case of nonlinear constrained optimization, because the objective function is univariate, well-conditioned and the constraints are linear inequalities, the optimization is simple. Unfortunately, this is seldom the case.

## Curve fitting as an optimization problem

Curve fitting is the process of fitting a model, expressed in terms of a mathematical function, that depends on adjustable parameters to a series of data points and once adjusted, that curve has the best fit to the data points.

The general approach to the fitting procedure involves the definition of a merit function that measures the agreement between data and model. The model parameters are then adjusted to yield the best-fit parameters as a problem of minimization (an optimization problem, where the merit function is the cost function).

A classical solution, termed least-squares fitting, is to find the best fit by minimizing the sum of the squared differences between data points and the model function (the sum of squared residuals as the merit function).

For more on curve fitting see the video below and the notebook [Curve fitting](http://nbviewer.jupyter.org/github/demotu/BMC/blob/master/notebooks/CurveFitting.ipynb).

```python
from IPython.display import YouTubeVideo
YouTubeVideo('Rxp7o7_RxII', width=480, height=360, rel=0)
```

## Gradient descent

Gradient descent is a first-order iterative optimization algorithm for finding the minimum of a function ([Wikipedia](https://en.wikipedia.org/wiki/Gradient_descent)).
In the gradient descent algorithm, a local minimum of a function is found starting from an initial point and taking steps proportional to the negative of the derivative of the function (gradient) at the current point and we evaluate if the current point is lower than then the previous point until a local minimum in reached (hopefully).

It follows that, if

\begin{equation}
x_{n+1} = x_n - \gamma \nabla f(x)
\label{}
\end{equation}

for $\gamma$ small enough, then $f(x_{n}) \geq f(x_{n+1})$.

This process is repeated iteratively until the step size (which is proportional to the gradient!) is below a required precision (hopefully the sequence $x_{n}$ converges to the desired local minimum).

### Example 2: Minimum of a function by gradient descent

From https://en.wikipedia.org/wiki/Gradient_descent:
Calculate the minimum of $f(x)=x^4-3x^3+2$.

```python
# From https://en.wikipedia.org/wiki/Gradient_descent
# The local minimum of $f(x)=x^4-3x^3+2$ is at x=9/4

cur_x = 6               # The algorithm starts at x=6
gamma = 0.01            # step size multiplier
precision = 0.00001
step_size = 1           # initial step size
max_iters = 10000       # maximum number of iterations
iters = 0               # iteration counter

f  = lambda x: x**4 - 3*x**3 + 2  # lambda function for f(x)
df = lambda x: 4*x**3 - 9*x**2    # lambda function for the gradient of f(x)

while (step_size > precision) & (iters < max_iters):
    prev_x = cur_x
    cur_x -= gamma*df(prev_x)
    step_size = abs(cur_x - prev_x)
    iters+=1

print('True local minimum at {} with function value {}.'.format(9/4, f(9/4)))
print('Local minimum by gradient descent at {} with function value {}.'.format(cur_x, f(cur_x)))
```

    True local minimum at 2.25 with function value -6.54296875.
    Local minimum by gradient descent at 2.2499646074278457 with function value -6.542968737317345.

The gradient descent optimization algorithm is one of the methods for optimization implemented in the [scipy.optimize.minimize](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html#scipy.optimize.minimize) function, see [minimize(method='CG')](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-cg.html), which deals with minimization of a scalar function of one or more variables using the conjugate gradient algorithm.
Let's run the example above but using the scipy function:

```python
from scipy.optimize import minimize
import numpy as np

minimize(f, 6, args=(), method='CG', jac=None, tol=precision, callback=None,
         options={'gtol': 1e-05, 'norm': np.inf, 'eps': 1.4901161193847656e-08,
                  'maxiter': max_iters, 'disp': True, 'return_all': False})
```

    Optimization terminated successfully.
             Current function value: -6.542969
             Iterations: 3
             Function evaluations: 49
             Gradient evaluations: 15

         fun: array([-6.54296875])
         jac: array([0.])
     message: 'Optimization terminated successfully.'
        nfev: 49
         nit: 3
        njev: 15
      status: 0
     success: True
           x: array([2.24999999])

## Multivariate optimization

When there is more than one design variable (the cost function depends on more than one variable), it's a multivariate optimization. The general idea of finding minimum and maximum values where the derivatives are zero still holds for a multivariate function. The second derivative of a multivariate function can be described by the Hessian matrix:

\begin{equation}
\mathbf{H} = \begin{bmatrix}{\dfrac  {\partial ^{2}f}{\partial x_{1}^{2}}}&{\dfrac  {\partial ^{2}f}{\partial x_{1}\,\partial x_{2}}}&\cdots &{\dfrac  {\partial ^{2}f}{\partial x_{1}\,\partial x_{n}}}\\[2.2ex]{\dfrac  {\partial ^{2}f}{\partial x_{2}\,\partial x_{1}}}&{\dfrac  {\partial ^{2}f}{\partial x_{2}^{2}}}&\cdots &{\dfrac  {\partial ^{2}f}{\partial x_{2}\,\partial x_{n}}}\\[2.2ex]\vdots &\vdots &\ddots &\vdots \\[2.2ex]{\dfrac  {\partial ^{2}f}{\partial x_{n}\,\partial x_{1}}}&{\dfrac  {\partial ^{2}f}{\partial x_{n}\,\partial x_{2}}}&\cdots &{\dfrac  {\partial ^{2}f}{\partial x_{n}^{2}}}
\end{bmatrix}
\label{}
\end{equation}

Let's see now a classical problem in biomechanics where optimization is useful and there is more than one design variable.

## The distribution problem in biomechanics

Using the inverse dynamics approach in biomechanics, we can determine the net force and torque acting on a joint if we know the external forces on the segments and the kinematics and inertial properties of the segments. But with this approach we are unable to determine the individual muscles forces that  created such torque, as expressed in the following equation:

\begin{equation}
M_{total} = M_1 + M_2 + \dots + M_n = r_1F_1 + r_2F_2 + \dots + r_nF_n
\label{}
\end{equation}

where $r_i$ is the moment arm of the force $F_i$ that generates a torque $M_i$, a parcel of the (known) total torque $M_{total}$.

Even if we know the moment arm of each muscle (e.g., from cadaveric data or from image analysis), the equation above has $n$ unknowns. Because there is more than one muscle that potentially created such torque, there are more unknowns than equations, and the problem is undetermined. So, the problem is how to find how the torque is distributed among the muscles of that joint.

One solution is to consider that we (biological systems) optimize our effort in order to minimize energy expenditure, stresses on our tissues, fatigue, etc. The principle of least action, stated in the opening of this text, is an allusion that optimization might be ubiquitous in nature. With this rationale, let's solve the distribution problem in biomechanics using optimization and find the minimum force of each muscle necessary to complete a given task.

The following cost functions have been proposed to solve the distribution problem in biomechanics:

\begin{equation}
\begin{array}{l l}
\displaystyle\sum_{i=1}^N F_i \quad &\text{e.g., Seireg and Arkivar (1973)}
\\
\displaystyle\sum_{i=1}^N F_i^2 \quad &
\\
\displaystyle\sum_{i=1}^N \left(\dfrac{F_i}{pcsa_i}\right)^2 \quad &\text{e.g., Crowninshield and Brand (1981)}
\\
\displaystyle\sum_{i=1}^N \left(\dfrac{F_i}{M_{max,i}}\right)^3 \quad &\text{e.g., Herzog (1987)}
\end{array}
\label{}
\end{equation}

Where $pcsa_i$ is the physiological cross-sectional area of muscle $i$ and $M_{max,i}$ is the maximum torque muscle $i$ can produce.
Each muscle force $F_i$ is a design variable and the following constraints must be satisfied:

\begin{equation}
\begin{array}{l l}
0 \leq F_i \leq F_{max}
\\
\displaystyle\sum_{i=1}^N r_i \times F_i = M
\end{array}
\label{}
\end{equation}

Let's apply this concept to solve a distribution problem in biomechanics.

### Muscle force estimation

Consider the following main flexors of the elbow joint (see figure below): biceps long head, biceps short head, and brachialis. Suppose that the elbow net joint torque determined using inverse dynamics is 20 Nm (flexor). How much each of these muscles contributed to the net torque?

Figure. A view in OpenSim of the arm26 model showing three elbow flexors (Biceps long and short heads and Brachialis).

For the optimization, we will need experimental data for the moment arm, maximum moment, and *pcsa* of each muscle. Let's import these data from the OpenSim arm26 model:

```python
# time elbow_flexion BIClong BICshort BRA
r_ef = np.loadtxt('./../data/r_elbowflexors.mot', skiprows=7)
f_ef = np.loadtxt('./../data/f_elbowflexors.mot', skiprows=7)
```

The maximum isometric force of these muscles are defined in the arm26 model as: Biceps long head: 624.3 N, Biceps short head: 435.56 N, and Brachialis: 987.26 N. Let's compute the mamimum torques that each muscle could produce considering a static situation at the different elbow flexion angles:

```python
m_ef = r_ef*1
m_ef[:, 2:] = r_ef[:, 2:]*f_ef[:, 2:]
```

And let's visualize these data:

```python
labels = ['Biceps long head', 'Biceps short head', 'Brachialis']
fig, ax = plt.subplots(nrows=1, ncols=3, sharex=True, figsize=(10, 4))
ax[0].plot(r_ef[:, 1], r_ef[:, 2:])
#ax[0].set_xlabel('Elbow angle $(\,^o)$')
ax[0].set_title('Moment arm (m)')
ax[1].plot(f_ef[:, 1], f_ef[:, 2:])
ax[1].set_xlabel('Elbow angle $(\,^o)$', fontsize=16)
ax[1].set_title('Maximum force (N)')
ax[2].plot(m_ef[:, 1], m_ef[:, 2:])
#ax[2].set_xlabel('Elbow angle $(\,^o)$')
ax[2].set_title('Maximum torque (Nm)')
ax[2].legend(labels, loc='best', framealpha=.5)
ax[2].set_xlim(np.min(r_ef[:, 1]), np.max(r_ef[:, 1]))
plt.tight_layout()
plt.show()
```

These data don't have the *pcsa* value of each muscle. We will estimate the *pcsa* considering that the amount of maximum muscle force generated per area is constant and equal to 50N/cm$^2$. Consequently, the *pcsa* (in cm$^2$) for each muscle is:

```python
a_ef = np.array([624.3, 435.56, 987.26])/50  # 50 N/cm2
print(a_ef)
```

    [12.486   8.7112 19.7452]

### Static versus dynamic optimization

In the context of biomechanics, we can solve the distribution problem separately for each angle (instant) of the elbow; we will refer to that as static optimization. However, there is no guarantee that when we analyze all these solutions across the range of angles, they will be the best solution overall. One reason is that static optimization ignores the time history of the muscle force. Dynamic optimization refers to the optimization over a period of time. For such, we will need to input a cost function spanning the entire period of time at once. Dynamic optimization usually has a higher computational cost than static optimization. The term 'Dynamic optimization' used in Biomechanics is not related to the broader method known as '[dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming)' in mathematical optimization or computer programming.

Figure. Static and dynamic optimization approaches in Biomechanics. Figure taken from Robertson et al. (2013).

The title 'Static and dynamic optimization solutions for gait are practically equivalent' of an article published by Anderson and Pandy (2001), where they studied normal walking (full gait cycle equals to 1.12 s), summarizes the results of such comparison. The authors reported that the dynamic solution required 1000 times more computation time than the static solution.

For now, we will solve the present problem using static optimization.

### Solution of the optimization problem

For the present case, we are dealing with a problem of minimization, multidimensional (function of several variables), nonlinear, constrained, and we can't assume that the cost function is convex. Numerical optimization is hardly a simple task. There are many different algorithms and public and commercial software for performing optimization. For instance, look at [NEOS Server](http://www.neos-server.org/neos/), a free internet-based service for solving numerical optimization problems.
We will solve the present problem using the [scipy.optimize](http://docs.scipy.org/doc/scipy/reference/optimize.html#module-scipy.optimize) package which provides several optimization algorithms. We will use the function `minimize`:

```python
scipy.optimize.minimize(fun, x0, args=(), method=None, jac=None, hess=None, hessp=None, bounds=None, constraints=(), tol=None, callback=None, options=None)
"""Minimization of scalar function of one or more variables."""
```

Now, let's write Python functions for each cost function:

```python
from scipy.optimize import minimize
```

```python
def cf_f1(x):
    """Cost function: sum of forces."""
    return x[0] + x[1] + x[2]

def cf_f2(x):
    """Cost function: sum of forces squared."""
    return x[0]**2 + x[1]**2 + x[2]**2

def cf_fpcsa2(x, a):
    """Cost function: sum of squared muscle stresses."""
    return (x[0]/a[0])**2 + (x[1]/a[1])**2 + (x[2]/a[2])**2

def cf_fmmax3(x, m):
    """Cost function: sum of cubic forces normalized by moments."""
    return (x[0]/m[0])**3 + (x[1]/m[1])**3 + (x[2]/m[2])**3
```

Let's also define the Jacobian for each cost function (which is an optional parameter for the optimization):

```python
def cf_f1d(x):
    """Derivative of cost function: sum of forces."""
    dfdx0 = 1
    dfdx1 = 1
    dfdx2 = 1
    return np.array([dfdx0, dfdx1, dfdx2])

def cf_f2d(x):
    """Derivative of cost function: sum of forces squared."""
    dfdx0 = 2*x[0]
    dfdx1 = 2*x[1]
    dfdx2 = 2*x[2]
    return np.array([dfdx0, dfdx1, dfdx2])

def cf_fpcsa2d(x, a):
    """Derivative of cost function: sum of squared muscle stresses."""
    dfdx0 = 2*x[0]/a[0]**2
    dfdx1 = 2*x[1]/a[1]**2
    dfdx2 = 2*x[2]/a[2]**2
    return np.array([dfdx0, dfdx1, dfdx2])

def cf_fmmax3d(x, m):
    """Derivative of cost function: sum of cubic forces normalized by moments."""
    dfdx0 = 3*x[0]**2/m[0]**3
    dfdx1 = 3*x[1]**2/m[1]**3
    dfdx2 = 3*x[2]**2/m[2]**3
    return np.array([dfdx0, dfdx1, dfdx2])
```

Let's define initial values:

```python
M = 20  # desired torque at the elbow
iang = 69  # which will give the closest value to 90 degrees
r  = r_ef[iang, 2:]
f0 = f_ef[iang, 2:]
a  = a_ef
m  = m_ef[iang, 2:]
x0 = f_ef[iang, 2:]/10  # far from the correct answer for the sum of torques
print('M =', M)
print('x0 =', x0)
print('r * x0 =', np.sum(r*x0))
```

    M = 20
    x0 = [57.51311369 36.29974032 89.6470056 ]
    r * x0 = 6.622004446068322

Inequality constraints (such as boundaries in our problem) can be entered with the parameter `bounds` to the `minimize` function:

```python
bnds = ((0, f0[0]), (0, f0[1]), (0, f0[2]))
```

Equality constraints (such as the sum of torques should equals the desired torque in our problem), as well as inequality constraints, can be entered with the parameter `constraints` to the `minimize` function (and we can also opt to enter the Jacobian of these constraints):

```python
# use this in combination with the parameter bounds:
cons = ({'type': 'eq',
         'fun' : lambda x, r, f0, M: np.array([r[0]*x[0] + r[1]*x[1] + r[2]*x[2] - M]),
         'jac' : lambda x, r, f0, M: np.array([r[0], r[1], r[2]]), 'args': (r, f0, M)})
```

```python
# to enter everything as constraints:
cons = ({'type': 'eq',
         'fun' : lambda x, r, f0, M: np.array([r[0]*x[0] + r[1]*x[1] + r[2]*x[2] - M]),
         'jac' : lambda x, r, f0, M: np.array([r[0], r[1], r[2]]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[0]-x[0],
         'jac' : lambda x, r, f0, M: np.array([-1, 0, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[1]-x[1],
         'jac' : lambda x, r, f0, M: np.array([0, -1, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[2]-x[2],
         'jac' : lambda x, r, f0, M: np.array([0, 0, -1]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[0],
         'jac' : lambda x, r, f0, M: np.array([1, 0, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[1],
         'jac' : lambda x, r, f0, M: np.array([0, 1, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[2],
         'jac' : lambda x, r, f0, M: np.array([0, 0, 1]), 'args': (r, f0, M)})
```

Although more verbose, if all the Jacobians of the constraints are also informed, this alternative seems better than informing bounds for the optimization process (less error in the final result and less iterations).

Given the characteristics of the problem, if we use the function `minimize` we are limited to the SLSQP (Sequential Least SQuares Programming) solver.

Finally, let's run the optimization for the four different cost functions and find the optimal muscle forces:

```python
f1r = minimize(fun=cf_f1, x0=x0, args=(), jac=cf_f1d,
               constraints=cons, method='SLSQP',
               options={'disp': True})
```

    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 409.59266009953336
                Iterations: 7
                Function evaluations: 7
                Gradient evaluations: 7

```python
f2r = minimize(fun=cf_f2, x0=x0, args=(), jac=cf_f2d,
               constraints=cons, method='SLSQP',
               options={'disp': True})
```

    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 75657.38479127169
                Iterations: 5
                Function evaluations: 6
                Gradient evaluations: 5

```python
fpcsa2r = minimize(fun=cf_fpcsa2, x0=x0, args=(a,), jac=cf_fpcsa2d,
                   constraints=cons, method='SLSQP',
                   options={'disp': True})
```

    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 529.9639777695737
                Iterations: 11
                Function evaluations: 11
                Gradient evaluations: 11

```python
fmmax3r = minimize(fun=cf_fmmax3, x0=x0, args=(m,), jac=cf_fmmax3d,
                   constraints=cons, method='SLSQP',
                   options={'disp': True})
```

    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 1075.1388931664733
                Iterations: 12
                Function evaluations: 12
                Gradient evaluations: 12

Let's compare the results for the different cost functions:

```python
dat = np.vstack((np.around(r*100,1), np.around(a,1), np.around(f0,0), np.around(m,1)))
opt = np.around(np.vstack((f1r.x, f2r.x, fpcsa2r.x, fmmax3r.x)), 1)
er = ['-', '-', '-', '-',
      np.sum(r*f1r.x)-M, np.sum(r*f2r.x)-M, np.sum(r*fpcsa2r.x)-M, np.sum(r*fmmax3r.x)-M]
data = np.vstack((np.vstack((dat, opt)).T, er)).T

rows = ['$\text{Moment arm}\;[cm]$', '$pcsa\;[cm^2]$', '$F_{max}\;[N]$', '$M_{max}\;[Nm]$',
        '$\sum F_i$', '$\sum F_i^2$', '$\sum(F_i/pcsa_i)^2$', '$\sum(F_i/M_{max,i})^3$']
cols = ['Biceps long head', 'Biceps short head', 'Brachialis', 'Error in M']
df = pd.DataFrame(data, index=rows, columns=cols)
print('\nComparison of different cost functions for solving the distribution problem')
df
```

    Comparison of different cost functions for solving the distribution problem

      Biceps long head
      Biceps short head
      Brachialis
      Error in M

      $\text{Moment arm}\;[cm]$
      4.9
      4.9
      2.3
      -

      $pcsa\;[cm^2]$
      12.5
      8.7
      19.7
      -

      $F_{max}\;[N]$
      575.0
      363.0
      896.0
      -

      $M_{max}\;[Nm]$
      28.1
      17.7
      20.4
      -

      $\sum F_i$
      215.4
      194.2
      0.0
      0.0

      $\sum F_i^2$
      184.7
      184.7
      86.1
      0.0

      $\sum(F_i/pcsa_i)^2$
      201.7
      98.2
      235.2
      0.0

      $\sum(F_i/M_{max,i})^3$
      241.1
      120.9
      102.0
      -3.552713678800501e-15

## Comments

The results show that the estimations for the muscle forces depend on the cost function used in the optimization. Which one is correct? This is a difficult question and it's dependent on the goal of the actual task being modeled. Glitsch and Baumann (1997) investigated the effect of different cost functions on the optimization of walking and running and the predicted muscles forces were compared with the electromyographic activity of the corresponding muscles of the lower limb. They found that, among the analyzed cost functions, the minimization of the sum of squared muscle stresses resulted in the best similarity with the actual electromyographic activity.

In general, one should always test different algorithms and different initial values before settling for the solution found. Downey (2011), Kitchin (2013), and Kiusalaas (2013) present more examples on numerical optimization. The [NEOS Guide](http://neos-guide.org/) is a valuable source of information on this topic and [OpenOpt](http://openopt.org/) is a good alternative software for numerical optimization in Python.

## Exercises

1. Find the extrema in the function $f(x)=x^3-7.5x^2+18x-10$ analytically and determine if they are minimum or maximum.
2. Find the minimum in the $f(x)=x^3-7.5x^2+18x-10$ using the gradient descent algorithm.
2. Regarding the distribution problem for the elbow muscles presented in this text:
    a. Test different initial values for the optimization.
    b. Test other values for the elbow angle where the results are likely to change.

3. In an experiment to estimate forces of the elbow flexors, through inverse dynamics it was found an elbow flexor moment of 10 Nm.
Consider the following data for maximum force (F0), moment arm (r), and pcsa (A) of the brachialis, brachioradialis, and biceps brachii muscles: F0 (N): 1000, 250, 700; r (cm): 2, 5, 4; A (cm$^2$): 33, 8, 23, respectively (data from Robertson et al. (2013)).
    a. Use static optimization to estimate the muscle forces.
    b. Test the robustness of the results using different initial values for the muscle forces.
    c. Compare the results for different cost functions.

## References

- Bergstra B, Bengio Y (2012) [Random Search for Hyper-Parameter Optimization](http://jmlr.csail.mit.edu/papers/volume13/bergstra12a/bergstra12a.pdf). Journal of Machine Learning Research, 13, 281-305.
- Crowninshield RD, Brand RA (1981) [A physiologically based criterion of muscle force prediction in locomotion](http://www.ncbi.nlm.nih.gov/pubmed/7334039). Journal of Biomechanics, 14, 793–801.
- Downey AB (2014) [Physical Modeling in MATLAB](http://greenteapress.com/wp/physical-modeling-in-matlab-2e/). 2nd edition. Green Tea Press.
- Herzog W (1987) [Individual muscle force estimations using a non-linear optimal design](http://www.ncbi.nlm.nih.gov/pubmed/3682873). J Neurosci Methods, 21, 167-179.
- Glitsch U, Baumann W (1997) [The three-dimensional determination of internal loads in the lower extremity](http://www.ncbi.nlm.nih.gov/pubmed/9456380). Journal of Biomechanics, 30, 1123–1131.
- Kitchin J (2013) [pycse - Python Computations in Science and Engineering](http://kitchingroup.cheme.cmu.edu/pycse/).
- Kiusalaas (2013) [Numerical methods in engineering with Python 3](http://books.google.com.br/books?id=aJkXoxxoCoUC). 3rd edition. Cambridge University Press.
- Nigg BM and Herzog W (2006) [Biomechanics of the Musculo-skeletal System](https://books.google.com.br/books?id=hOIeAQAAIAAJ&dq=editions:ISBN0470017678). 3rd Edition. Wiley.
- Robertson G, Caldwell G, Hamill J, Kamen G (2013) [Research Methods in Biomechanics](http://books.google.com.br/books?id=gRn8AAAAQBAJ). 2nd Edition. Human Kinetics.
- Seireg A, Arvikar RJ (1973) [A mathematical model for evaluation of forces in lower extremeties of the musculo-skeletal system](http://www.ncbi.nlm.nih.gov/pubmed/4706941). Journal of Biomechanics, 6,  313–322, IN19–IN20, 323–326.


#  Particle on a sphere

The Hamiltonial $H$ for a particle on a sphere is given by

\begin{align}
H &= \frac{p^2}{2m} = \frac{L^2}{2 I}
\end{align}

## Exercise 1
$\mathbf{L} = \mathbf{r} \times \mathbf{p}$ is the angular momentum operator
where $\mathbf{r} = (x, y, z)$ is the positional operator and $\mathbf{p} = (p_x, p_y, p_z)$ is the momentum operator

Show that:

\begin{align}
L_x &= - i \hbar \left(y \frac{\partial}{\partial z} - z \frac{\partial}{\partial y} \right) \\
L_y &= - i \hbar \left(z \frac{\partial}{\partial x} - x \frac{\partial}{\partial z} \right) \\
L_z &= - i \hbar \left(x \frac{\partial}{\partial y} - y \frac{\partial}{\partial x} \right)
\end{align}

Hint: $p_x = -i\frac{\partial}{\partial x}$

Hint 2: To do this exercise you need to do a cross product

## Exercise 2

Find the commutator relations $[L_x, L_y]$ and $[L^2, L_z]$.

Does $L_x$ and $L_y$ commute?

Does $L^2$ and $L_z$ commute?

Hint: $[a, b] = ab - ba$

## Exercise 3

The particle on a sphere is given by:

\begin{align}
L^2 Y_{m_l}^l (\varphi, \theta) = \hbar^2 l(l+1) Y_{m_l}^l (\varphi, \theta) \\
L_z Y_{m_l}^l (\varphi, \theta) = \hbar m_l Y_{m_l}^l (\varphi, \theta)
\end{align}

where $l = 0, 1, 2, ...$ and $m_l = -l, -l+1,...,0,..., l-1, l$

Make a surface plot of a sphere where the particle can be located

## Exercise 4

Find the analytic expressions for $Y_{m_l}^l (\varphi, \theta)$, for instance in the lecture notes on explain everything (code ATEHWGWY).

 Make python functions for $L = 0$, $L = 1$ and $L = 2$ with all combination of $m_l$

```python
def Y_10(phi, theta):

def Y_11(phi, theta):

def Y_1m1(phi, theta):

# or more generally using sph_harm from scipy.special

from scipy.special import sph_harm

```

## Exercise 5
The parametrization for the probability densities are:

\begin{align}
x &= \cos \theta \sin \phi |Y_{m_l}^l (\varphi, \theta)|^2 \\
y &= \sin \theta \sin \phi |Y_{m_l}^l (\varphi, \theta)|^2 \\
z &= \cos \phi |Y_{m_l}^l (\varphi, \theta)|^2
\end{align}

Give an explaination to why the parametrization looks like this.

Plot the probability density of $|Y_{m_l}^l (\varphi, \theta)|^2$ for $L = 0$, $L = 1$
and $L=2$ for $m_l = -l, -l+1,...,0,..., l-1, l$.

Try to plot them with the sphere from exercise 3, here it is a good idea to add the keyword $alpha = 0.3$ for the sphere plot.

## Exercise 6

The formulas for  $Y_{m_l}^l (\varphi, \theta)$ for $l=1$ and $m_l=-1,0,1$ represent the three $p$ orbitals. It is easy to identify $p_z$ as the function $Y_{0}^1$, whereas $p_x$ and $p_y$ must be obtained as combinations of $Y_{-1}^1$ and $Y_{1}^1$.

1. Make the appropriate linear conbinations to obtain the $p_x$ and $p_y$ orbitals. Hint: $p_x$ and $p_y$ are real functions
2. Compute the parametric surface for the probability density of $p_x$ and $p_y$ as in exercise 5
3. Plot $p_x$ and $p_y$

Advanced (not mandatory): repeat the points above for $d$ and $f$ orbitals.

problem_candidates/Particle on a sphere exercises.ipynb