```python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import sympy

sympy.init_printing()

style_name = 'bmh' #bmh
mpl.style.use(style_name)
np.set_printoptions(precision=2, linewidth =150)

style = plt.style.library[style_name]
style_colors = [ c['color'] for c in style['axes.prop_cycle'] ]
```

### 1. 다음 중 함수인것과 아닌것을 구별 해보세요.

```python
plt.rcParams["figure.figsize"] = (17, 5)

x   = np.linspace(-5, 5, 100)
x21 = np.linspace(0, 1, 10)
x22 = np.linspace(1, 4, 100)
an  = np.linspace(0, 2 * np.pi, 100)

y = x**2
y22 = -(x22-2)**2 + 1

fig = plt.figure()
ax1 = fig.add_subplot(1, 4, 1)
ax2 = fig.add_subplot(1, 4, 2)
ax3 = fig.add_subplot(1, 4, 3)
ax4 = fig.add_subplot(1, 4, 4)

ax1.plot(y, x, color=style_colors[1], lw=3)
ax1.set_xlabel('x')
ax1.set_ylabel('y')

ax2.plot(x21, np.zeros_like(x21), color=style_colors[1], lw=3)
ax2.plot(x22, np.abs(y22), color=style_colors[1], lw=3)
ax2.set_xlabel('x')
ax2.set_ylabel('y')

ax3.plot(3 * np.cos(an), 3 * np.sin(an), color=style_colors[1], lw=3)
ax3.set_xlabel('x')
ax4.set_ylabel('y')

ax4.plot(an, np.sin(an), color=style_colors[1], lw=3)
ax4.set_xlabel('x')
ax4.set_ylabel('y')

plt.show()
```

- 1번 : 함수 아님, 독립변수 하나에 값이 2개 대응

- 2번 : 함수

- 3번 : 함수 아님, 독립변수 하나에 값이 2개 대응

- 4번 : 함수

### 2. 곱의 미분

$$
y = (2x^2 -2)(3x - x^3) \quad x \in [-2,2]
$$

- 다항식 미분 공식과 곱의 미분법을 써서 도함수를 구해보고 SymPy 사용하여 결과를 확인하고, 함수 그래프와 도함수의 그래프를 matplotlib를 사용하여 그려보세요.

- 곱의 미분을 이용한 미분 과정

$$
\begin{align}
\frac{dy}{dx} &=(2x^2 -2)'(3x - x^3) + (2x^2 -2)(3x - x^3)' \\[5pt]
&= (4x)(3x - x^3) + (2x^2 -2)(3-3x^2) \\[5pt]
&= 12x^2 -4x^4 + 6x^2 - 6x^4 - 6 + 6x^2 \\[5pt]
&= -10x^4 + 24x^2 -6
\end{align}
$$

- 아래 sympy 코드에서 pass로 표시된 부분을 적절한 표현으로 완성하시오.

```python
x = sympy.Symbol('x')

# diff 함수안에 함수를 표현하는 알맞은 코드를 적는다.
# diff함수 사용법 : sympy.diff( 함수표현, 미분할 변수 )
# pass 가 있는 위치에 알맞은 표현을 써넣는다.
sympy.simplify( sympy.diff((2*x**2 - 2)*(3*x - x**3) , x) )
```

- 아래 함수의 그래프와 도함수의 그래프를 그리는 코드에서 pass로 표시된 부분을 적절한 표현으로 완성하시오.

```python
plt.rcParams["figure.figsize"] = (5,10)

x = np.linspace(-2, 2, 100)

# 주어진 함수 y와 도함수 dy를 알맞게 초기화 한다.
# 변수는 위에서 생성한 x로 쓴다.
y  = (2*x**2 - 2)*(3*x - x**3) # 여기 주어진 함수 y의 알맞은 표현을 쓴다.
dy = -10*x**4 + 24*x**2 - 6    # 여기 주어진 함수의 도함수의 알맞은 표현을 쓴다.

fig = plt.figure()

ax1 = fig.add_subplot(2,1,1)
ax2 = fig.add_subplot(2,1,2)

ax1.plot(x, y, lw=3)
ax1.set_title(r"$f(x) = (2x^2 -2)(3x - x^3)$")

ax2.plot(x, dy, lw=3, color=style_colors[1])
ax2.set_title(r"Derivative of $f(x)$")
plt.show()
```

### 3. 분수함수 미분

$$
y = \frac{1}{x - 2}
$$

분수함수 미분을 이용하여 각각 편도함수를 구해보고 SymPy 사용하여 결과를 확인하세요.

- 분수함수 미분을 이용한 미분과정

$$
\frac{d }{dx} \left( \frac{1}{x - 2} \right) = \frac{1' \times (x-2) - 1 \times (x-2)'}{(x - 2)^2} = \frac{-1}{(x - 2)^{2} }
$$

- 합성함수 미분을 이용한 미분 과정

$$
\frac{d }{dx} \left( \frac{1}{x - 2} \right) = \frac{d }{dx}  (x - 2)^{-1} = -(x - 2)^{-2} \frac{d}{dx} (x - 2) = \frac{-1}{(x - 2)^{2} }
$$

- 아래 sympy 코드에서 pass로 표시된 부분을 적절한 표현으로 완성하시오.

```python
x = sympy.Symbol('x')

# diff 함수안에 함수를 표현하는 알맞은 코드를 적는다.
# diff함수 사용법 : sympy.diff( 함수표현, 미분할 변수 )
# pass 가 있는 위치에 알맞은 표현을 써넣는다.
sympy.diff(1 / (x - 2), x)
```

### 4. 다변수함수 미분

$$
f(x,y) = \frac{1}{2} x^2 + xy^3
$$

다항식 미분 공식을 써서 편도함수를 구해보고 SymPy 사용하여 결과를 확인하세요. 함수의 그래프와 각 편도함수의 그래프를 matplotlib를 사용하여 그려보세요.

- 편도함수

$$
\begin{align}
& \frac{\partial}{\partial x} f(x,y) = x + y^3 \\[5pt]
& \frac{\partial}{\partial y} f(x,y) = 3xy^2
\end{align}
$$

- 아래 sympy 코드에서 pass로 표시된 부분을 적절한 표현으로 완성하시오.

```python
x = sympy.Symbol('x')
y = sympy.Symbol('y')

# diff 함수안에 함수를 표현하는 알맞은 코드를 적는다.
# diff함수 사용법 : sympy.diff( 함수표현, 미분할 변수 )
# pass 가 있는 위치에 알맞은 표현을 써넣는다.
sympy.diff((1/2)*x**2 + x*y**3, x)
```

```python
# diff 함수안에 함수를 표현하는 알맞은 코드를 적는다.
# diff함수 사용법 : sympy.diff( 함수표현, 미분할 변수 )
# pass 가 있는 위치에 알맞은 표현을 써넣는다.
sympy.diff((1/2)*x**2 + x*y**3, y)
```

- 아래 함수의 그래프와 도함수의 그래프를 그리는 코드에서 pass로 표시된 부분을 적절한 표현으로 완성하시오.

```python
plt.rcParams["figure.figsize"] = (17, 5)

from mpl_toolkits import mplot3d

x = np.linspace(-3, 3, 150)
xx, yy = np.meshgrid(x, x)

# 주어진 함수 f와 도함수 dfx, dfy를 알맞게 초기화 한다.
# 2변수 함수이기 때문에 변수는 위에서 생성한 메쉬그리드 변수 xx, yy로 쓴다.
f = (1/2)*xx**2 + xx*yy**3  # 여기 주어진 함수 f의 알맞은 표현을 쓴다.
dfx = xx + yy**3            # 여기 주어진 함수 dfx의 알맞은 표현을 쓴다.
dfy = 3*xx*yy**2            # 여기 주어진 함수 dfy의 알맞은 표현을 쓴다.

fig = plt.figure()
ax1 = fig.add_subplot(1, 3, 1, projection='3d')
ax2 = fig.add_subplot(1, 3, 2, projection='3d')
ax3 = fig.add_subplot(1, 3, 3, projection='3d')

ax1.plot_surface(xx, yy, f, rstride=10, cstride=10, cmap=plt.cm.viridis, edgecolor="k")
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_title(r'$\frac{1}{2} x^2 + xy^3$', fontsize=20)

ax2.plot_surface(xx, yy, dfx, rstride=10, cstride=10, cmap=plt.cm.viridis, edgecolor="k")
ax2.set_xlabel('x')
ax2.set_ylabel('y')
ax2.set_title(r'$\frac{\partial}{\partial x}\left( \frac{1}{2} x^2 + xy^3 \right)$', fontsize=20)

ax3.plot_surface(xx, yy, dfy, rstride=10, cstride=10, cmap=plt.cm.viridis, edgecolor="k")
ax3.set_xlabel('x')
ax3.set_ylabel('y')
ax3.set_title(r'$\frac{\partial}{\partial y}\left( \frac{1}{2} x^2 + xy^3 \right)$', fontsize=20)

plt.show()
```

### 5. 자동미분

다음 함수에 대해서 sympy를 사용하여 도함수를 구하고, 수치 미분과 자동 미분의 결과를 비교해보세요.

$$
y = \frac{x}{1+x}, \quad x \ge 0
$$

- 아래 sympy 코드에서 pass로 표시된 부분을 적절한 표현으로 완성하시오.

```python
x = sympy.Symbol('x')

# diff 함수안에 함수를 표현하는 알맞은 코드를 적는다.
# diff함수 사용법 : sympy.diff( 함수표현, 미분할 변수 )
# pass 가 있는 위치에 알맞은 표현을 써넣는다.
sympy.simplify(sympy.diff(x / (1+x), x))
```

- 다음은 주어진 함수와 위 셀에서 sympy로 미분한 도함수의 그래프를 그린 것이다.
- 아래 함수의 그래프와 도함수의 그래프를 그리는 코드에서 pass로 표시된 부분을 적절한 표현으로 완성하시오.

```python
plt.rcParams["figure.figsize"] = (10, 5)

# 미분할 위치를 정하는 변수
x0 = 1

x = np.linspace(0, 3, 100)

# 주어진 함수 y와 도함수 dy를 함수 형태로 초기화 한다.
def y(x):
    """주어진 함수를 계산하는 함수"""
    return x / ((1 + x))      # 여기 주어진 함수 y의 알맞은 표현을 쓴다.

def dy(x):
    """주어진 함수 y의 도함수를 계산하는 함수"""
    return 1 / ( ((1+x)**2))  # 여기 주어진 함수 dy의 알맞은 표현을 쓴다.

plt.plot(x, y(x), label='y')
plt.plot(x, dy(x), label='dy')
plt.legend(fontsize=15)

dy0 = dy(x0)
plt.plot(x0, dy0, '.', markersize=15, color=style_colors[3])
plt.plot(x0, dy0, 'o', fillstyle='none', markersize=15, color=style_colors[3])
plt.text(x0, dy0+0.05, "derivative at x=1 : {:.2f}".format(dy0), fontsize=15)
plt.show()
```

- 그래프가 제대로 그려졌다면 $x=1$일때 도함수의 값이 0.25로 그려진다.

#### 수치미분

- 아래 코드는 주이전 함수를 전방 차분으로 수치미분하기 위한 코드이다.

$$
\frac{\partial \, f}{\partial \, x_i} \approx \frac{f(x_1, \cdots, \color{RoyalBlue}{x_i + \Delta x_i} , \cdots, x_n) - f(x_1, \cdots, x_i, \cdots, x_n)}{\Delta x_i}
$$

- sympy로 계산한 미분계수 0.25 근처 값이 출력됨을 확인할 수 있다.

```python
def numer_deriv(f, x, eps=0.01):
    """
    f   : 미분할 함수
    x   : 미분계수를 구할 독립변수 값
    eps : 차분을 구하기 위한 미소변량
    """
    diff = ( f(x+eps) - f(x) ) / eps

    return diff

print( "derivative at x=1 : {:.2f}".format(numer_deriv(y, x0)) )
```

    derivative at x=1 : 0.25

#### 자동미분

- 아래 코드는 주어진 함수를 자동미분하기 위해 필요한 게이트를 클래스로 구한한 코드이다.

```python
###############################################################
# 주어진 함수를 그래프로 만들기 위해 필요한 게이트 클래스 구현
# ctrl+enter를 눌러서 셀을 한번 실행시켜야 함
###############################################################

class Multiply :
    """
    https://github.com/WegraLee/deep-learning-from-scratch
    """
    def __init__(self):
        self.x = None
        self.y = None

    def __repr__(self):
        return "Mutiply"

    def forward(self, x, y):
        self.x = x
        self.y = y

        return x * y

    def backward(self, dout):
        dx = dout * self.y  # x와 y를 바꾼다.
        dy = dout * self.x

        return dx, dy

class Add :
    """
    https://github.com/WegraLee/deep-learning-from-scratch
    """
    def __init__(self):
        pass

    def __repr__(self):
        return "Add"

    def forward(self, x, y):
        return x + y

    def backward(self, dout):
        dx = dout * 1
        dy = dout * 1

        return dx, dy

class Inverse :
    def __init__(self):
        self.x = None

    def forward(self, x) :
        self.x = x

        return 1./x

    def backward(self, dout):
        return -(1 / self.x**2)*dout
```

```python
###############################################################
# 주어진 함수의 그래프를 그리기 위한 코드
###############################################################

from graphviz import Digraph

dot = Digraph(comment='The Round Table')
dot.attr(rankdir='LR', size='8,5')
dot.attr('node', shape='circle')

dot.node('A', 'x'); dot.node('B', 'x');
dot.node('O', '1'); dot.node('P', '+');
dot.node('D', '/'); dot.node('M', '*'); dot.node('Y', 'y');

dot.edge('B', 'P');            dot.edge('O', 'P');
dot.edge('P', 'D', label='a'); dot.edge('D', 'M', label='b');
dot.edge('A', 'M');            dot.edge('M', 'Y', label='y')

dot
```

- 주어진 함수의 그래프 표현은 위와 같고 이를 순전파, 역전파 시키기 위한 함수는 아래와 같다.

- 순전파를 위해 a, b, y 순서로 진행시킨다.

```python
a_fwd = a.forward(x, 1)
b_fwd = b.forward(a_fwd)
forward = y.forward(x, b_fwd)
```

- 순전파 결과를 이용해서 역전파 시킨다. y에 대해 1을 넘기면서 역전파를 시작한다.

```python
y_bak = y.backward(1)
```

- a와 b를 순서대로 역전파시킬 때 인자로 어떤 값을 넘겨야 하는지 pass부분을 채워 완성하시오.

- 결과는 다음처럼 출력됨 : Func. val. by forward pass = 0.50, Derivative by backward pass = 0.25

```python
# 아래 함수에서 pass로 된 부분에 적당한 코드를 채워넣으세요.

def autodiff(x) :
    # 각 게이트를 정의 한다.
    # 위 그래프 그림에서 a는 x와 1을 입력으로 받는 더하기(Add) 함수
    a = Add()

    # b는 입력에 역수를 취하는 역수(Inverse) 함수
    b = Inverse()

    #y 는 x와 b를 입력으로 받는 곱하기(Multiply) 함수
    y = Multiply()

    ######################################################################
    #forward
    # 게이트 순서대로 전진한다.
    a_fwd = a.forward(x, 1)
    b_fwd = b.forward(a_fwd)
    forward = y.forward(x, b_fwd)
    ######################################################################

    ######################################################################
    #backward
    # 게이트의 역순으로 후진한다.
    y_bak = y.backward(1)

    # b함수와 a함수 순으로 후진할때 각각 backward함수에 어떤 인자를
    # 넘겨야 하는가?
    b_bak = b.backward(y_bak[1])
    a_bak = a.backward(b_bak)
    ######################################################################

    # x 노드에 해당하는 backward 결과를 다 더한다.
    backward = y_bak[0] + a_bak[0]

    return forward, backward

print("Func. val. by forward pass = {:.2f}, Derivative by backward pass = {:.2f}".format(*autodiff(x0)) )
```

    Func. val. by forward pass = 0.50, Derivative by backward pass = 0.25

```python
%%html
<link href='https://fonts.googleapis.com/earlyaccess/notosanskr.css' rel='stylesheet' type='text/css'>
<!--https://github.com/kattergil/NotoSerifKR-Web/stargazers-->
<link href='https://cdn.rawgit.com/kattergil/NotoSerifKR-Web/5e08423b/stylesheet/NotoSerif-Web.css' rel='stylesheet' type='text/css'>
<!--https://github.com/Joungkyun/font-d2coding-->
<link href="http://cdn.jsdelivr.net/gh/joungkyun/font-d2coding/d2coding.css" rel="stylesheet" type="text/css">
<style>
    h1     { font-family: 'Noto Sans KR' !important; color:#348ABD !important;   }
    h2     { font-family: 'Noto Sans KR' !important; color:#467821 !important;   }
    h3, h4 { font-family: 'Noto Sans KR' !important; color:#A60628 !important;   }
    p:not(.navbar-text) { font-family: 'Noto Serif KR', 'Nanum Myeongjo'; font-size: 12pt; line-height: 200%;  text-indent: 10px; }
    li:not(.dropdown):not(.p-TabBar-tab):not(.p-MenuBar-item):not(.jp-DirListing-item):not(.p-CommandPalette-header):not(.p-CommandPalette-item):not(.jp-RunningSessions-item)
            { font-family: 'Noto Serif KR', 'Nanum Myeongjo'; font-size: 12pt; line-height: 200%; }
    table  { font-family: 'Noto Sans KR' !important;  font-size: 11pt !important; }
    li > p  { text-indent: 0px; }
    li > ul { margin-top: 0px !important; }
    sup { font-family: 'Noto Sans KR'; font-size: 9pt; }
    code, pre  { font-family: D2Coding, 'D2 coding' !important; font-size: 12pt !important; line-height: 130% !important;}
    .code-body { font-family: D2Coding, 'D2 coding' !important; font-size: 12pt !important;}
    .ns        { font-family: 'Noto Sans KR'; font-size: 15pt;}
    .summary   {
                   font-family: 'Georgia'; font-size: 12pt; line-height: 200%;
                   border-left:3px solid #FF0000;
                   padding-left:20px;
                   margin-top:10px;
                   margin-left:15px;
               }
    .green { color:#467821 !important; }
    .comment { font-family: 'Noto Sans KR'; font-size: 10pt; }
</style>
```

<link href='https://fonts.googleapis.com/earlyaccess/notosanskr.css' rel='stylesheet' type='text/css'>
<!--https://github.com/kattergil/NotoSerifKR-Web/stargazers-->
<link href='https://cdn.rawgit.com/kattergil/NotoSerifKR-Web/5e08423b/stylesheet/NotoSerif-Web.css' rel='stylesheet' type='text/css'>
<!--https://github.com/Joungkyun/font-d2coding-->
<link href="http://cdn.jsdelivr.net/gh/joungkyun/font-d2coding/d2coding.css" rel="stylesheet" type="text/css">
<style>
    h1     { font-family: 'Noto Sans KR' !important; color:#348ABD !important;   }
    h2     { font-family: 'Noto Sans KR' !important; color:#467821 !important;   }
    h3, h4 { font-family: 'Noto Sans KR' !important; color:#A60628 !important;   }
    p:not(.navbar-text) { font-family: 'Noto Serif KR', 'Nanum Myeongjo'; font-size: 12pt; line-height: 200%;  text-indent: 10px; }
    li:not(.dropdown):not(.p-TabBar-tab):not(.p-MenuBar-item):not(.jp-DirListing-item):not(.p-CommandPalette-header):not(.p-CommandPalette-item):not(.jp-RunningSessions-item)
            { font-family: 'Noto Serif KR', 'Nanum Myeongjo'; font-size: 12pt; line-height: 200%; }
    table  { font-family: 'Noto Sans KR' !important;  font-size: 11pt !important; }
    li > p  { text-indent: 0px; }
    li > ul { margin-top: 0px !important; }
    sup { font-family: 'Noto Sans KR'; font-size: 9pt; }
    code, pre  { font-family: D2Coding, 'D2 coding' !important; font-size: 12pt !important; line-height: 130% !important;}
    .code-body { font-family: D2Coding, 'D2 coding' !important; font-size: 12pt !important;}
    .ns        { font-family: 'Noto Sans KR'; font-size: 15pt;}
    .summary   {
                   font-family: 'Georgia'; font-size: 12pt; line-height: 200%;
                   border-left:3px solid #FF0000;
                   padding-left:20px;
                   margin-top:10px;
                   margin-left:15px;
               }
    .green { color:#467821 !important; }
    .comment { font-family: 'Noto Sans KR'; font-size: 10pt; }
</style>


# The `Problem` class

An analytical problem is a problem where the mathematical formulation of the various objectives is known, as opposed to a data-driven problem, where one may need to train surrogate models to proceed with optimization.

The `Problem` class is the way to define optimization problems in the DESDEO framework. Once defined, the same `Problem` class instance can be used to solve optimization problems using various EAs from the `desdeo-emo` package, or the more traditional methods from the `desdeo-mcdm` package.

This notebook will help you understand how to instantiate a analytical problem object from scratch. The notebook will also go over other abstractions, namely classes for defining the decision variables, objectives, and the constraints, and will go over the functionalities provided by the abstractions.

## Multiobjective Optimization Problem

Let's say that we have the following minimization problem:

\begin{equation}
\begin{aligned}
& \underset{\mathbf x}{\text{min}}
& & y_1, y_2, y_3\\
& & & y_1 = x_1 + x_2 + x_3 \\
& & & y_2 = x_1 * x_2 * x_3  \\
& & & y_3 = x_1 * x_2 + x_3 \\
& \text{s.t.} & &  -2 \leq x_1 \leq 5 \\
& & &  -1 \leq  x_2 \leq 10 \\
& & &  -0 \leq x_3 \leq 3 \\
& & &  x_1 + x_2 + x_3 \leq 10 \\
& & &  \mathbf{x} \; \in S, \\
\end{aligned}
\end{equation}

## Variables

Before instantiating the problem instance, we have to create object to define each of the variables, objectives, and constraints.

The variable objects can be created with the `desdeo_problem.Variable.Variable` class.
This object stores the information related to the variable (such as, lower bound, upper bound, and an initial value).
This information is used by the methods whenever required (such as when setting box constraints on searching algorithms or recombination operators) and for displaying results to the decision maker.
Use this class to create variable objects, one variable at a time.

To define multiple `Variable` instances easily, use the `desdeo_problem.Variable.variable_builder` function.
The function takes in all the necessary information for all the variables at once, and returns a List of `Variable` instances, one for each decision variable.

Use the `help()` function to know more about any function/class in the desdeo framework.

```python
from desdeo_problem.Variable import variable_builder

help(variable_builder)
```

    Help on function variable_builder in module desdeo_problem.Variable:

    variable_builder(names: List[str], initial_values: Union[List[float], numpy.ndarray], lower_bounds: Union[List[float], numpy.ndarray] = None, upper_bounds: Union[List[float], numpy.ndarray] = None) -> List[desdeo_problem.Variable.Variable]
        Automatically build all variable objects.

        Args:
            names (List[str]): Names of the variables
            initial_values (np.ndarray): Initial values taken by the variables.
            lower_bounds (Union[List[float], np.ndarray], optional): Lower bounds of the
                variables. If None, it defaults to negative infinity. Defaults to None.
            upper_bounds (Union[List[float], np.ndarray], optional): Upper bounds of the
                variables. If None, it defaults to positive infinity. Defaults to None.

        Raises:
            VariableError: Lengths of the input arrays are different.

        Returns:
            List[Variable]: List of variable objects

Let's build the `Variable` objects

```python
var_names = ["a", "b", "c"]  #  Make sure that the variable names are meaningful to you.

initial_values = [1, 1, 1]
lower_bounds = [-2, -1, 0]
upper_bounds = [5, 10, 3]

variables = variable_builder(var_names, initial_values, lower_bounds, upper_bounds)
```

```python
print("Type of \"variables\": ", type(variables))
print("Length of \"variables\": ", len(variables))
print("Type of the contents of \"variables\": ", type(variables[0]))
```

    Type of "variables":  <class 'list'>
    Length of "variables":  3
    Type of the contents of "variables":  <class 'desdeo_problem.Variable.Variable'>

## Objectives

Objectives are defined using tha various objective classes found within the module `desdeo_problem.Objective`. To define an objective class instance, one needs to pass the following:

1. Objective name/s (Required): Name of the objective (or list of names, for multiple objective). This information will be used when displaying results to the user. Hence, these names must be understandable to the user.

2. Evaluator (Required for analytical/simulation based objectives): An evaluator is a python `Callable` which takes in the decision variables as it's input and returns the corresponding objective values. This python function can be used to connect to simulators outside the DESDEO framework.

3. Lower bound (Not required): A lower bound for the objective. This information can be used to generate approximate ideal/nadir point during optimization.

4. Upper bound (Not required): An upper bound for the objective. This information can be used to generate approximate ideal/nadir point during optimization.

5. maximize (Not required): This is a boolean value that determines whether an objective is to be maximized or minimized. This is `False` by default (i.e. the objective is minimized).

The DESDEO framework has the following classification for objectives, based on the kind of evaluater to be used:

1. "Scalar" objectives: If an evaluator/simulator evaluates only one objective, the objective is defined as a Scalar objective. Use the `desdeo_problem.Objective._ScalarObjective` class to handle such cases.

2. "Vector" objectives: If an evaluator evaluates and returns more than one objective at once, the set of objectives is defined as Vector objective. Use the `desdeo_problem.Objective.VectorObjective` class to handle such cases.

**Note**:`_ScalarObjective` will be depreciated in the future, and all of it's functionality will be handled by the `VectorObjective` class, which will be renamed to, simply, `Objective`.

To define a problem instance, the objectives may be defined as all Scalar objectives, all Vector objectives, or a mix of both, depending upon the case.

Let's see how to define and use both kinds of Objective classes:

```python
from desdeo_problem.Objective import _ScalarObjective, VectorObjective

import numpy as np
```

Define the evaluators for the objectives. These evaluators should be python functions that take in the decision variable values and give out the objective value/s. The arguments of these evaluators are **2-D Numpy arrays**.

```python
def obj1_2(x):  #  This is a "simulator" that returns more than one objective at a time. Hence, use VectorObjective
    y1 = x[:, 0] + x[:, 1] + x[:, 2]
    y2 = x[:, 0] * x[:, 1] * x[:, 2]
    return (y1, y2)

def obj3(x):  #  This is a "simulator" that returns only one objective at a time. Hence, use ScalarObjective
    y3 = x[:, 0] * x[:, 1] + x[:, 2]
    return y3
```

Define the objectives. For this, you need the names of the objectives, and the evaluators defined above. If an evaluator returns multiple objective values, use the `VectorObjective` class to define those objectives. If an evaluator returns objective values for only one objective, either `VectorObjective` or `ScalarObjective` can be used.

If using `VectorObjective`, names should be provided in a list.

Additionaly, bounds of the objective values can also be provided.

```python
f1_2 = VectorObjective(["y1", "y2"], obj1_2)
f3 = _ScalarObjective("y3", obj3, maximize=True)  # Note: f3 = VectorObjective(["y3"], obj3) will also work.
```

## Constraints

Constraint may depend on the decision variable values, as well as the objective function.

The constraint should be defined so, that when evaluated, it should return a positive value, if the constraint is adhered to, and a negative, if the constraint is breached.

```python
from desdeo_problem.Constraint import ScalarConstraint

const_func = lambda x, y: 10 - (x[:, 0] + x[:, 1] + x[:, 2])

# Args: name, number of variables, number of objectives, callable

cons1 = ScalarConstraint("c_1", 3, 3, const_func)
```

## Creating the Problem object

Now that we have all the building blocks, we can create the problem object, using the `desdeo_problem.Problem.MOProblem` class.

Provide objectives, variables and constraints in lists.

```python
from desdeo_problem.Problem import MOProblem

prob = MOProblem(objectives=[f1_2, f3], variables=variables, constraints=[cons1])
```

The problem class provides abstractions such as the `evaluate` method. The method evaluates all the objective and constraint values for a given set of decision variables (in a numpy array), using the evaluators.

The abstraction also provides methods such as `train` and `surrogate_evaluate` for data driven problem. These will be tackled in the next notebook.

The output is a NamedTuple object. It contains the following elements:

1. `objectives`: Contains the objective values

2. `fitness`: Contains the fitness values. Fitness is either equal to the objective value, or equal to (-1 * objective value), depending upon whether the objective is to be minimized or maximized respectively. The optimization methods in the DESDEO framework internally use this value, rather than the values contained in output.objectives

3. `constraints`: Contains constraint violation values.
4. `uncertainity`: Contains the quantification of "uncertainity" of the evaluation

All of these values can be accessed in different ways, as shown below.

**Note**: Input as list of lists is not supported

```python
data = np.asarray([[1, -1, 0], [5, 5, 2]])
res= prob.evaluate(data)
```

```python
print(res)
# Note the sign reversal in the third objective and third fitness values because of maximization.
```

    Evaluation Results Object
    Objective values are:
    [[ 0. 12. -1.]
     [ 0. 50. 27.]]
    Constraint violation values are:
    [[10.]
     [-2.]]
    Fitness values are:
    [[  0.  12.   1.]
     [  0.  50. -27.]]
    Uncertainity values are:
    [[nan nan nan]
     [nan nan nan]]

```python
print("The objective values for the given set of decision variables are: \n", res.objectives)
print("The constraint violation for the given set of decision variables are:\n", res.constraints)
```

    The objective values for the given set of decision variables are:
     [[ 0. 12. -1.]
     [ 0. 50. 27.]]
    The constraint violation for the given set of decision variables are:
     [[10.]
     [-2.]]

```python
res
```

    EvaluationResults(objectives=array([[ 0., 12., -1.],
           [ 0., 50., 27.]]), fitness=array([[  0.,  12.,   1.],
           [  0.,  50., -27.]]), constraints=array([[10.],
           [-2.]]), uncertainity=array([[nan, nan, nan],
           [nan, nan, nan]]))


# Series Expansion Approximations

This notebook lets you play around with different ways of approximating analytic functions in a given interval.  The three ways are

1. Legendre series
2. Taylor series
3. Fourier series

You have previously seen Taylor series and Fourier series in your first-year courses, but the Legendre polynomials are new in this course on differential equations.

Taylor series are defined by
\begin{equation}
    T_f(x) = \sum_{k=0}^\infty \frac{f^{(k)}(a)}{k!} {(x-a)}^k
    \quad\text{where}\quad
    f^{(k)}(a) = \left.\frac{\mathrm{d}^k f(x)}{\mathrm{d}x^k}\right\lvert_{x=a}
\end{equation}
and Fourier series on the interval $[-1, 1)$ are defined by
\begin{equation}
    F_f(x) = \frac12 a_{f,0} + \sum_{k=1}^\infty \biggl(a_{f,k}\cos(n\pi x) + b_{f,k}\sin(n\pi x)\biggr),
\end{equation}
where the coefficients are
\begin{equation}
    a_{f,k} = \int_{-1}^1 f(x) \cos(k \pi x)\,\mathrm{d}x \quad\text{and}\quad b_{f,k} = \int_{-1}^1 f(x) \sin(k \pi x)\,\mathrm{d}x.
\end{equation}

The Legendre series is a similar approximation to the Fourier series, but using the Legendre polynomials $P_n$ instead of the trigonometric basis used in Fourier.  This is a series of the form
\begin{equation}
    L_f(x) = \sum_{k=0}^\infty \ell_k P_k(x),
\end{equation}
where you have considered how to calculate the coefficients $\ell_k$ elsewhere in this seminar.

## Initialisation code

These few cells produce some functions for finding (to a good approximation) the various series that we're interested in.  You don't need to understand the details of how any of this works, but feel free to ask.  The Legendre series and Fourier series calculations are done in the same way they are described above, but the Taylor series ones are not.

**You need to run all the cells in this section for anything to work.**

```python
%matplotlib inline
from matplotlib import pyplot
import matplotlib
import scipy.special
import scipy.integrate
import numpy as np
import numpy.polynomial
import ipywidgets
```

```python
def legendre_series(f, n):
    r"""
    Calculate the terms of the Legendre series expansion of the function
    ..math:`f(x)` with the first ..math:`n_terms` terms.  This will be the
    terms up to but _excluding_ the coefficient of ..math:`P_n(x)`.

    The resultant object can be called like a function to return the value of
    the approximation at values of ..math:`x`.
    """
    if n < 1:
        raise ValueError("'n' must be at least 1.")
    def integrand(x, k):
        return scipy.special.eval_legendre(k, x)*f(x)
    # Approximate the inner product integral for each of the polynomials,
    # including the normalisation factor.  `scipy.integrate.quad` performs
    # numerical integration (also called 'quadrature') until a particular
    # precision goal is reached.
    return np.polynomial.legendre.Legendre(np.array([
        scipy.integrate.quad(integrand, -1, 1, args=(k,))[0] * (k + 0.5)
        for k in range(n)
    ]))

def taylor_coefficient(f, k, a=15):
    r"""
    Calculate the ..math:`k`th coefficient in the Taylor expansion of
    ..math:`f(x)` around the point ..math:`x_0 = 0`.  The first term is
    ..math:`k = 0`, as this is the zeroth-order term.

    ``a`` is a precision factor, and should probably just be left as-is.
    """
    if k == 0:
        return f(0)
    # The standard way of defining Taylor series with derivatives and
    # factorials doesn't play nicely with numerical methods.  This method is
    # based on contour integration (magic).
    scale = np.exp(-a/k)
    return np.exp(a)/k * sum(
        (-1)**n * np.imag(f(scale * np.exp(1j*np.pi*(0.5-n)/k)))
        for n in range(1, k+1)
    )

def taylor_series(f, n, a=15):
    r"""
    Calculate the first ..math:`n` terms of the Taylor series expansion of
    ..math:`f(x)` around the point ..math:`x_0 = 0` up to but excluding the
    term ..math:`x^n`.

    The resultant object can be called like a function to return the value of
    the approximation at values of ..math:`x`.
    """
    if n < 1:
        raise ValueError("'n' must be at least 1.")
    return np.polynomial.Polynomial([
        taylor_coefficient(f, k, a)
        for k in range(n)
    ])

class fourier_series:
    r"""
    Calculate the first ..math:`n` terms of the Fourier series expansion of
    ..math:`f(x)` when mapped to the period ..math:`[-1, 1)`.

    The terms are "numbered" in the order
    ..math::
        a_0, b_1, a_1, b_2, a_2, \dotsc
    This is by analogy to Taylor series; the first term is the constant, then
    the lowest-order odd term, the next-lowest even term, and so on.

    The resultant object can be called like a function to return the value of
    the approximation at values of ..math:`x`.
    """
    def __init__(self, f, n):
        if n < 1:
            raise ValueError("'n' must be at least 1.")
        self._n_a = (n + 1) // 2
        self._n_b = n - self._n_a
        self.a = np.empty((self._n_a,), dtype=np.float64)
        # To keep the labelling clear I store the `b[0] = 0` too.
        self.b = np.empty((self._n_b + 1,), dtype=np.float64)
        self.a[0] = 0.5 * scipy.integrate.quad(f, -1, 1)[0]
        self.b[0] = 0
        def cosint(x, k): return f(x) * np.cos(k*np.pi*x)
        def sinint(x, k): return f(x) * np.sin(k*np.pi*x)
        for k in range(1, self._n_a):
            self.a[k] = scipy.integrate.quad(cosint, -1, 1, args=(k,))[0]
        for k in range(1, self._n_b):
            self.b[k] = scipy.integrate.quad(sinint, -1, 1, args=(k,))[0]

    def __call__(self, xs):
        out = np.zeros_like(xs)
        for k in range(self._n_a):
            out += self.a[k] * np.cos(k*np.pi * xs)
        for k in range(1, self._n_b):
            out += self.b[k] * np.sin(k*np.pi * xs)
        return out

```

```python
def series_plot(f, terms):
    r"""
    Plot a function ``f`` and its Legendre-, Taylor- and Fourier-series
    approximations of the given order on the interval ..math:`[-1, 1)`.
    """
    xs = np.linspace(-1, 1, 301)
    cm = matplotlib.cm.viridis
    _, axes = pyplot.subplots()
    fs = np.array([f(x) for x in xs])
    axes.plot(xs, fs, label="Exact", color='black', linewidth=2, dashes=(5, 5))
    axes.plot(xs, legendre_series(f, terms)(xs), label="Legendre", color=cm(0))
    axes.plot(xs, taylor_series(f, terms)(xs), label="Taylor", color=cm(0.4))
    axes.plot(xs, fourier_series(f, terms)(xs), label="Fourier", color=cm(0.8))
    axes.set_xlim((xs[0] - 0.03*(xs[-1]-xs[0]), xs[-1] + 0.03*(xs[-1]-xs[0])))
    # Base the y axis on the actual function rather than all the data, so the
    # Taylor series doesn't blow out the entire scale when it's wrong, and it
    # stays the same no matter the number of terms used in the plot.
    mn, mx = np.min(fs), np.max(fs)
    axes.set_ylim((mn - 0.1*(mx-mn), mx + 0.1*(mx-mn)))
    axes.legend()
```

## Investigating series behaviour at low orders

I have defined some mathematical functions in Python which are hopefully quite clear from the code.  Below them, I have plotted the function, and the series expansions of the function with a controllable `terms` parameter.  Feel free to copy-paste to see what happens when you use your own functions.

Think about these questions, and then discuss them with your group.

1. Which series would you call the "most accurate"?  Why?  Does it depend on the function?
2. What sorts of functions are the different expansions best at approximating?  Which are they bad at?
3. The Legendre series often seems to "give up" in the middle of some shapes at low orders (_e.g._ sinusoids).  Why is this, and are there any things the series is still useful for?
4. The Taylor series almost invariably has the largest pointwise error.  Why is this?  What is the Taylor series useful for?

### Approximating a polynomial

$f(x) = c_0 + c_1x + c_2x^2 + \dotsb + c_{12}x^{12}$ for arbitrary constants $\{c_n\}$.

```python
def high_order_polynomial(x):
    return np.polynomial.Polynomial([
        -0.0372875, 0.674885, 1.34898, -12.652, -7.15369, 57.7268,
        8.73373, -104.258, 10.0257, 79.9955, -21.4594, -21.5587, 8.38861,
    ])(x)

ipywidgets.interact(
    lambda terms: series_plot(high_order_polynomial, terms),
    terms=(1, 15, 1),
);
```

    interactive(children=(IntSlider(value=8, description='terms', max=15, min=1), Output()), _dom_classes=('widget…

### Approximating a logistic function

$f(x) = {\bigl(1 + e^{-5x}\bigr)}^{-1}$, which makes a sigmoid ("s") shape, asymptotically tending to $0$ and $1$.

```python
def logistic(x):
    return 1 / (1 + np.exp(-5*x))

ipywidgets.interact(
    lambda terms: series_plot(logistic, terms),
    terms=(1, 12, 1),
);
```

    interactive(children=(IntSlider(value=6, description='terms', max=12, min=1), Output()), _dom_classes=('widget…

### Approximating a Lorentzian function

$f(x) = {\bigl(\varGamma\pi + {(x/\varGamma)}^2\bigr)}^{-1}$.  This sort of shape is common when talking about linewidths of atomic transitions and lasers, and is a valid probability distribution.  Unlike Gaussians, they can be quite tricky to work with, because no integer statistical moments of them (other than the zeroth order) exist—the series only work here because the domain is limited to $[-1, 1]$ rather than being infinite.

```python
def lorentzian(x, c=0.2):
    return 1 / (c*np.pi + (x/c)**2)

ipywidgets.interact(
    lambda terms: series_plot(lorentzian, terms),
    terms=(1, 12, 1),
);
```

    interactive(children=(IntSlider(value=6, description='terms', max=12, min=1), Output()), _dom_classes=('widget…


# Conservation of energy

```python
dtl        = [0.01, 0.005, 0.0025, 0.00125];
iterationl = [500, 1000, 2000, 4000];

En = [[] for i in range(0, len(dtl))];
```

```python
import matplotlib.pyplot as plt;
import numpy as np;
import numpy.linalg as linalg;
import math;

from sympy import *;
from sympy.utilities import *;
from ipywidgets import *;

def dostuf(index):
    # Symbol for symbolic expression
    x = Symbol('x');

    # Lagrange polynomial
    def lagrange(p, i):
        width = 1.0/p;
        this = i * width;
        # Numerator
        num = 1;
        for j in range(0, p + 1):
            if(j != i):
                num = Mul(num, x - (j*width));
        # Denominator
        denom = 1;
        for j in range(0, p + 1):
            if(j != i):
                denom = Mul(denom, this - (j*width));
        denom = Pow(denom, -1);
        return Mul(num, denom);

    degree = 1;
    phi = [];
    for i in range(0, degree + 1):
        phi += [lagrange(degree, i)];

    # Constant
    c = 1;

    # Delta time
    dt = dtl[index];

    # Amount of iterations
    iterations = iterationl[index];

    # Stepsize
    stepSize = 1000;

    # Time
    t = 0;

    # Elements
    elements = [];#[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];

    for i in range(0, 10):
        elements += [i*0.1];

    elementCount = len(elements) - 1;

    # Extremal nodes
    minNode = min(elements);
    maxNode = max(elements);

    # Generate nodes
    nodeCount = 1 + (degree * elementCount);

    nodes = [];
    for i in range(0, elementCount):
        elementWidth = elements[i + 1] - elements[i];
        partWidth = elementWidth/degree;
        for p in range(0, degree):
            nodes += [elements[i] + (partWidth * p)];
    nodes += [maxNode];

    # Time coefficient matrix
    T = np.zeros((nodeCount, nodeCount));

    def integ(phi1, phi2, h):
        product = Mul(phi1, phi2);
        return h*integrate(product, (x, 0, 1));

    for i in range(0, elementCount):
        elementWidth = elements[i + 1] - elements[i];
        for n in range(0, degree + 1):
            for m in range(0, degree + 1):
                T[(degree * i) + n, (degree * i) + m] += integ(phi[n], phi[m], elementWidth);

    Tc = T.copy();

    for i in range(0,nodeCount):
        T[0,i]           = 0;
        T[nodeCount-1,i] = 0;
    T[0,0] = 1;
    T[nodeCount-1,nodeCount-1] = 1;

    #Time coefficient matrix

    S = np.zeros((nodeCount, nodeCount));

    def diffInteg(phi1, phi2, h, trans, invtrans):
        # Transform local to global
        gPhi1 = phi1.subs(x, invtrans);
        gPhi2 = phi2.subs(x, invtrans);
        # Take global derivative
        derGPhi1 = (diff(gPhi1, x));
        derGPhi2 = (diff(gPhi2, x));
        # Transform global to local
        derPhi1 = derGPhi1.subs(x, trans);
        derPhi2 = derGPhi2.subs(x, trans);
        product = Mul(derPhi1, derPhi2);
        return h*integrate(product, (x,0,1));

    for i in range(0, elementCount):
        elementWidth = elements[i + 1] - elements[i];
        transform = (elementWidth * x) + (elements[i]);
        inversetransform = (x - elements[i])/elementWidth;
        for n in range(0, degree + 1):
            for m in range(0, degree + 1):
                S[(degree * i) + n, (degree * i) + m] += diffInteg(phi[n], phi[m], elementWidth, transform, inversetransform);

    Sc = S.copy();
    for i in range(0,nodeCount):
        S[0,i]           = 0;
        S[nodeCount-1,i] = 0;

        # A single time step
    def iteration(v, vDer):
        vNew = v + dt*vDer;
        q = -c*c*S@v;
        r = linalg.solve(T, q);
        vDerNew = vDer + dt*r;
        return (vNew, vDerNew);

    # Evaluation
    def ev(u, pos):
        # Find element the pos(ition) is in
        for i in range(0, elementCount):
            if(elements[i] <= pos <= elements[i+1]):
                elementWidth = elements[i + 1] - elements[i];
                transformedPos = (pos - elements[i])/elementWidth;
                # Calculate linear sum
                sum = 0;
                for p in range(0, degree+1):
                    sum += u[(i*degree) + p]*phi[p].subs(x, transformedPos);
                return sum;
        return 0;

    gaussA = 1;
    gaussB = 0.25;
    gaussC = 0.05;
    # The real solution
    def realU(x, t):
        modx = x % 0.5;
        return gaussA*math.exp( -( (modx-gaussB)*(modx-gaussB) )/(gaussC*gaussC) );

    def energy(u, uDer):
        su = 0;
        for i in range(0, nodeCount):
            for j in range(0, nodeCount):
                su += Tc[i][j] * uDer[i] * uDer[j];
                su += Sc[i][j] * c * c * u[i] * u[j];
        return su;

    # Set initial value
    u    = np.zeros((nodeCount, 1));
    uDer = np.zeros((nodeCount, 1));

    k = 2*math.pi;

    for i in range(0, nodeCount):
        coord   = nodes[i];
        u[i]    = realU(coord, 0);
        uDer[i] = 0;

    data = [];
    data.append(u);
    En[index].append(energy(u, uDer));

    bigspacing = np.linspace(0.0, 2.0, 100);

    uNew    = np.zeros((nodeCount, 1));
    uDerNew = np.zeros((nodeCount, 1));

    for i in range(0, iterations):
        uNew, uDerNew = iteration(u,uDer);
        u = uNew;
        uDer = uDerNew;
        data.append(u);
        En[index].append(energy(u, uDer));
        t += dt;

    '''
    if(i != 0):
        return;
    spacing = np.linspace(0.0, 1.0, 100);
    evaluation = np.linspace(0.0, 1.0, 100);
    for i in range(0, 100):
        evaluation[i] = ev(data[0], evaluation[i]);
    plt.plot(spacing, evaluation, "b-");
    plt.ylabel("U(x, 0)");
    plt.xlabel("x");
    '''
    #plt.plot(data[i]);

for i in range(0, len(dtl)):
    dostuf(i);

```

```python
plt.yscale('log')
plt.plot(np.linspace(0.0, 5.0, 500), En[0][:500], label="dt= " + str(dtl[0]));
plt.plot(np.linspace(0.0, 5.0, 1000), En[1][:1000], label="dt= " + str(dtl[1]));
plt.plot(np.linspace(0.0, 5.0, 2000), En[2][:2000], label="dt= " + str(dtl[2]));
plt.plot(np.linspace(0.0, 5.0, 4000), En[3][:4000], label="dt= " + str(dtl[3]));
plt.legend(bbox_to_anchor=(0.5, 1.05, 0., 0.1), loc=3,
       ncol=2, mode="expand", borderaxespad=0.);
plt.ylabel("E(U, t)");
plt.xlabel("t");
```

```python
plt.yscale('linear')
plt.plot(np.linspace(0.0, 0.1, 10), En[0][:10], label="dt= " + str(dtl[0]));
plt.plot(np.linspace(0.0, 0.1, 20), En[1][:20], label="dt= " + str(dtl[1]));
plt.plot(np.linspace(0.0, 0.1, 40), En[2][:40], label="dt= " + str(dtl[2]));
plt.plot(np.linspace(0.0, 0.1, 80), En[3][:80], label="dt= " + str(dtl[3]));
plt.legend(bbox_to_anchor=(0.5, 1.05, 0., 0.1), loc=3,
       ncol=2, mode="expand", borderaxespad=0.);
plt.ylabel("E(U, t)");
plt.xlabel("t");
```

```python

```

```python

```

```python

```


## Some standard rocket launch calculations for ksp code development
* Uses numerical methods (Runge Kutta)

```python
import math as ma
import cmath as cma
import numpy as np
from sympy import *
import pandas as pd
import matplotlib.pyplot as plt
from scipy.integrate import RK45 as rk45
```

```python
# definitions
d2r      = ma.pi/180         # deg to rad
g        = 9.81              # use ksp constant
r_kerbin = 600000            # use in-game value
rho_SL   = 1.14              # Sea level atmo density
h_atmo   = 75000             # atmospheric height [m]
cd       = 0.5               # (constant) drag coeficient (calculable in ksp)
m0       = 300000            # lift-off mass (wet mass)[kg]
m1       = 15000             # empty-mass (dry mass) [kg]
mrat     = m0/m1             # fuel mass ratio (wet mass/ dry mass) -- (obtain in-game)
mburn    = m0/mrat           # burnout mass
Rd       = 2.5               # rocket diameter [m] (need to look into how this translates to ksp)
A        = ma.pi/4*Rd**2     # rocket radial surface area
T2W      = 1.5               # thrust to weight ratio
Isp      = 285               # Engine Isp (combined)
Rthrust  = T2W*m0*g          # rocket thrustr calculation [N]
mdot     = Rthrust/Isp/g     # propellant mass flow rate [kg/s]
mprop    = m0 - m1           # propellant mass
hpitch   = 100               # elevation to begin pitch over [m]
tburn    = mprop/mdot        # burn time [s]
t0       = 0                 # Initial time for the numerical integration
tf       = tburn             # Final time for the numerical integration
tspan    = np.arange(t0,tf)  # Range of integration
```

```python
# initial conditions
v0 = 0
x0 = 0
h0 = 0
vD0 = 0
vG0 = 0
gamma0 = 90*d2r
```

```python
# initial conditions vector
f0 = np.array([v0,gamma0,x0,h0,vD0,vG0])
# TODO incomplete
```


# Asset replacement model

**Randall Romero Aguilar, PhD**

This demo is based on the original Matlab demo accompanying the  Computational Economics and Finance 2001 textbook by Mario Miranda and Paul Fackler.

Original (Matlab) CompEcon file: **demddp02.m**

Running this file requires the Python version of CompEcon. This can be installed with pip by running

    !pip install compecon --upgrade

Last updated: 2021-Oct-01

## About

At the beginning of each year, a manufacturer must decide whether to continue to operate an aging physical asset or replace it with a new one. An asset that is $a$ years old yields a profit contribution $p(a)$ up to $n$ years, at which point the asset becomes unsafe and must be
replaced by law. The cost of a new asset is $c$. What replacement policy maximizes profits?

This is an infinite horizon, deterministic model with time $t$ measured in years.

## Initial tasks

```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from compecon import DDPmodel
```

## Model Parameters

Assume a maximum asset age of 5 years, asset replacement cost $c = 75$, and annual discount factor $\delta = 0.9$.

```python
maxage  = 5
repcost = 75
delta   = 0.9
```

### State Space

The state variable $a \in \{1, 2, 3, \dots, n\}$ is the age of the asset in years.

```python
S = np.arange(1, 1 + maxage)  # machine age
n = S.size                    # number of states
```

### Action Space

The action variable $x \in \{\text{keep, replace}\}$ is the hold-replacement decision.

```python
X = np.array(['keep', 'replace'])     # list of actions
m = len(X)                  # number of actions
```

### Reward Function

The reward function is
\begin{equation}
f(a, x) = \begin{cases} p(a), &x = \text{keep} \\
    p(0) - c, &x = \text{replace}
\end{cases}
\end{equation}

Assuming a profit contribution $p(a) = 50 − 2.5a − 2.5a^2$ that is a function of the asset age $a$ in years:

```python
f = np.zeros((m, n))
f[0] = 50 - 2.5 * S - 2.5 * S ** 2
f[1] = 50 - repcost
f[0, -1] = -np.inf
```

### State Transition Function

The state transition function is
\begin{equation}
g(a, x) = \begin{cases} a + 1, &x = \text{keep} \\
    1, &x = \text{replace}
\end{cases}
\end{equation}

```python
g = np.zeros_like(f)
g[0] = np.arange(1, n + 1)
g[0, -1] = n - 1  # adjust last state so it doesn't go out of bounds
```

## Model Structure

The value of an asset of age a satisfies the Bellman equation
\begin{equation}
V(a) = \max\{p(a) + \delta V(a + 1),\quad p(0) − c + \delta V(1)\}
\end{equation}

where we set $p(n) = -\infty$ to enforce replacement of an asset of age $n$. The Bellman equation asserts that if the manufacturer keeps an asset of age $a$, he earns $p(a)$ over the coming year and begins the subsequent year with an asset that is one year older and worth $V(a+1)$; if he replaces the asset, however, he starts the year with a new asset, earns $p(0)−c$ over the year, and begins the subsequent year with an asset that is one year old and worth $V(1)$. Actually, our language is a little loose here. The value $V(a)$ measures not only the current and future net earnings of an asset of age $a$, but also the net earnings of all future assets that replace it.

To solve and simulate this model, use the CompEcon class ```DDPmodel```.

```python
model = DDPmodel(f, g, delta)
model.solve()
```

    A deterministic discrete state, discrete action, dynamic model.
    There are 2 possible actions over 5 possible states

```python
solution = pd.DataFrame({
    'Age of Machine': S,
    'Action': X[model.policy],
    'Value': model.value}).set_index('Age of Machine')

solution['Action'] = solution['Action'].astype('category')
```

## Analysis

### Plot Optimal Value
This Figure gives the value of the firm at
the beginning of the period as a function of the asset’s age.

```python
ax = solution['Value'].plot();
ax.set(title='Optimal Value Function', ylabel='Value', xticks=S);
```

### Plot Optimal Policy
This Figure gives the optimal action as a function of the asset’s age.

```python
ax = solution['Action'].cat.codes.plot(marker='o')
ax.set(title='Optimal Replacement Policy', yticks=[0, 1], yticklabels=X, xticks=S);
```

### Simulate Model
The path was computed by performing a deterministic simulation of 12 years in duration using the ```simulate()``` method.

```python
sinit, nyrs = S.min() - 1, 12
t = np.arange(1 + nyrs)
spath, xpath = model.simulate(sinit, nyrs)
```

```python
simul = pd.DataFrame({
    'Year': t,
    'Age of Machine': S[spath],
    'Action': X[xpath]}).set_index('Year')

simul['Action'] = pd.Categorical(X[xpath], categories=X)
simul
```

      Age of Machine
      Action

      Year

      0
      1
      keep

      1
      2
      keep

      2
      3
      keep

      3
      4
      replace

      4
      1
      keep

      5
      2
      keep

      6
      3
      keep

      7
      4
      replace

      8
      1
      keep

      9
      2
      keep

      10
      3
      keep

      11
      4
      replace

      12
      1
      keep

### Plot State and Action Paths
Next Figure gives the age of the asset along the optimal path. As can be seen in this figure, the asset is replaced every four years.

```python
fig, axs = plt.subplots(2, 1, sharex=True)
simul['Age of Machine'].plot(ax=axs[0])
axs[0].set(title='Age Path');

simul['Action'].cat.codes.plot(marker='o', ax=axs[1])
axs[1].set(title='Replacement Path', yticks=[0, 1], yticklabels=X);
```

_build/jupyter_execute/notebooks/ddp/02 Asset replacement model.ipynb


# Sistemas mecánicos

El objetivo de esta práctica es analizar sistemas mecánicos a traves de las herramientas matemáticas de la ingeniería de control.

Empecemos modelando el siguiente sistema mecánico:

Si dibujamos nuestro diagrama de cuerpo libre y escribimos la sumatoria de fuerzas en $x$, obtendremos:

$$
\sum F_x = F - F_R - F_A = ma
$$

en donde $F$ es la fuerza aplicada hacia la derecha, $F_R$ es la fuerza de reacción en el resorte y $F_A$ es la fuerza de reacción en el amortiguador.

Si ahora tomamos en cuenta que:

$$
\begin{align}
F_R &= k x \\
F_A &= c v = c\dot{x} \\
ma  &= m \ddot{x}
\end{align}
$$

podemos escribir esta sumatoria de fuerzas como:

$$
F - kx - c\dot{x} = m \ddot{x}
$$

y sacando la transformada de Laplace, y factorizando terminos comúnes:

$$
F(s) = X(s)\left[ ms^2 + cs + k \right]
$$

Por lo que, cuando consideramos a $F(s)$ como la entrada de nuestro sistema y a $X(s)$ como la salida de nuestro sistema, podemos obtener la función de transferencia:

$$
\frac{X(s)}{F(s)} = \frac{1}{ms^2 + cs + k}
$$

y simular el comportamiento de este:

```python
from control import tf, step_response, root_locus
from numpy import linspace
from matplotlib.pyplot import plot
```

```python
m = 1200/4
c = 1500
k = 15000
G = tf([1], [m, c, k])
```

```python
ts = linspace(0, 10, 500)
t, y = step_response(G, ts)
```

```python
plot(t, y);
```

Sin embargo, tenemos que revisar una cosa. Los datos ingresados a este sistema se obtuvieron de valores comerciales para la suspensión de un automovil tipo sedán; sin embargo la función ```step_response``` simula el comportamiento del sistema para una entrada unitaria (en este caso $1N$), por lo que para que esta simulación tenga relevancia tenemos que amplificar esta entrada.

Se propone una entrada de $1100N$ que corresponse al peso de un hombre pesado, lo que esperaremos entonces es un movimiento como el que pasa cuando un hombre pesado se sube a un sedán:

```python
ts = linspace(0, 10, 500)
t, y = step_response(1100*G, ts)
```

```python
plot(t, y);
```

Y ahora obtenemos una simulación con el comportamiento que esperariamos de la suspensión de un coche cuando un hombre pesado se sube en el; la suspensión deja de moverse despues de unos $3s$ y se estabiliza en un valor de aproximadamente $0.07m$, es decir $7cm$ despues de haberse comprimido casi $10cm$  y regresar unas $3$ o $4$ veces.

---
## Ejercicio

* Define un sistema ```G1``` con un amortiguador con constante $c=0\frac{Ns}{m}$, un resorte con constante $k=100\frac{N}{m}$ y una masa de $10kg$.

```python
# ESCRIBE TU CODIGO AQUI
raise NotImplementedError
```

```python
from numpy.testing import assert_allclose

assert_allclose(G1.dcgain(), [0.01], 2)
assert_allclose(G1.pole(), [0.+3.16j,  0.-3.16j], 2)
assert_allclose(G1.zero(), [], 2)
```

```python
G1.zero()
```

* Simula el comportamiento de este sistema para una fuerza aplicada de $5N$ del tiempo $0s$ al tiempo $15s$.

```python
# ESCRIBE TU CODIGO AQUI
raise NotImplementedError
plot(t, y);
```

```python
from nose.tools import assert_almost_equal, assert_equal

assert_equal(ts[0], 0)
assert_equal(ts[-1], 15)
assert_almost_equal(max(y), 0.02, 4)
assert_almost_equal(min(y), 0.0, 4)
```

* Define un sistema ```G2``` con un amortiguador con constante $c=10\frac{Ns}{m}$, un resorte con constante $k=0\frac{N}{m}$ y una masa de $10kg$.

```python
# ESCRIBE TU CODIGO AQUI
raise NotImplementedError
```

```python
from numpy.testing import assert_allclose
from numpy import inf

assert_allclose(G2.dcgain(), [inf])
assert_allclose(G2.pole(), [-1, 0])
assert_allclose(G2.zero(), [], 2)
```

* Simula el comportamiento de este sistema para una fuerza aplicada de $5N$ del tiempo $0s$ al tiempo $20s$.

```python
# ESCRIBE TU CODIGO AQUI
raise NotImplementedError
plot(t, y);
```

```python
from nose.tools import assert_almost_equal, assert_equal

assert_equal(ts[0], 0)
assert_equal(ts[-1], 20)
assert_almost_equal(max(y), 1.9, 4)
assert_almost_equal(min(y), 0.0, 4)
```

---

Una vez que hemos comprobado la manera de simular estos sistemas mecánicos, podemos pasar al siguiente paso, predecir su comportamiento a partir de la pura función de transferencia.

La función de transferencia tiene varias caracteristicas de las que no hemos hablado, por ejemplo los polos del sistema:

```python
G1 = tf([1], [1, 1, 1])
G1.pole()
```

Estos polos son obtenidos al resolver la ecuación formada al igualar el denominador de la función de transferencia a $0$.

> Este denominador es llamado **Polinomio caracteristico del sistema** ya que es el que determina el comportamiento del sistema.

Si graficamos estos polos tendremos:

```python
rs, ks = root_locus(G1)
```

Esta gráfica se le conoce como **Lugar geométrico de las raíces**, y en ella las cruces representan a los polos que obtuvimos, las lineas que parten de estas cruces representan el movimiento de estos polos bajo relalimentación, lo cual vermos en la próxima práctica.

En esta gráfica podemos notar que las raices son complejas, y que su parte real es negativa; esta última característica es la que nos inidca que el comportamiento de este sistema será estable; para corroborar esto podemos simular y gráficar su comportamiento:

```python
ts = linspace(0, 15, 500)
t, y = step_response(G1, ts)

plot(t, y);
```

Por otro lado, si creamos una función de transferencia que tenga polos con parte real $0$, nos dará un comportamiento **Criticamente estable**.

```python
G2 = tf([1], [1, 0, 1])
G2.pole()
```

```python
rs, ks = root_locus(G2)
```

```python
ts = linspace(0, 15, 500)
t, y = step_response(G2, ts)

plot(t, y);
```

O una función de transferencia con polos con parte real positiva, su comportamiento será inestable:

```python
G3 = tf([1], [1, -1, 1])
G3.pole()
```

```python
rs, ks = root_locus(G3)
```

```python
ts = linspace(0, 15, 500)
t, y = step_response(G3, ts)

plot(t, y);
```

Despues de esto, podemos preguntarnos si en lugar de esperar que el comportamiento de nuestro sistema sea el adecuado, podemos construir una función con el comportamiento deseado, y la respuesta es si; podemos por ejemplo utilizar dos raices copmletamente reales para obtener un comportamiento subamortiguado:

$$
G_4 = \frac{1}{s+1} \cdot \frac{1}{s+3} = \frac{1}{s^2 + 4s + 3}
$$

```python
G4 = tf([1], [1, 1])*tf([1], [1, 3])
G4.pole()
```

```python
rs, ks = root_locus(G4)
```

```python
ts = linspace(0, 15, 500)
t, y = step_response(G4, ts)

plot(t, y);
```

O incluso asegurarnos que el comportamiento sea inestable:

```python
G5 = tf([1], [1, -1])*tf([1], [1, 3])
G5.pole()
```

```python
rs, ks = root_locus(G5)
```

```python
ts = linspace(0, 15, 500)
t, y = step_response(G5, ts)

plot(t, y);
```

---

## Ejercicio

* Define una función de transferencia ```G3```, con un comportamiento inestable.

```python
# ESCRIBE TU CODIGO AQUI
raise NotImplementedError
```

```python

```

```python
assert not all([polo.real<0 for polo in G3.pole()])
```

* Define una función de transferencia ```G4```, con un comportamiento estable.

```python
# ESCRIBE TU CODIGO AQUI
raise NotImplementedError
```

```python
assert all([polo.real<0 for polo in G4.pole()])
```

* Define una función de transferencia ```G5```, con un comportamiento criticamente estable.

```python
# ESCRIBE TU CODIGO AQUI
raise NotImplementedError
```

```python
assert any([polo.real==0 for polo in G5.pole()])
```

* Define una función de transferencia ```G6```, con un comportamiento sobreamortiguado.

```python
# ESCRIBE TU CODIGO AQUI
raise NotImplementedError
```

```python
from numpy import pi, angle
assert all([pi - pi/4 < angle(polo) < pi + pi/4 for polo in G6.pole()])
```

* Define una función de transferencia ```G7```, con un comportamiento subamortiguado.
> Sugerencia: Utiliza como base la función de transferencia del sistema masa - resorte - amortiguador.

```python
# ESCRIBE TU CODIGO AQUI
raise NotImplementedError
```

```python
assert not all([pi - pi/4 < angle(polo) < pi + pi/4 for polo in G7.pole()])
```

---


# Solving Inverted Pendulum with SVP

This notebook shows how to use the proposed structured value-based planning (SVP) approach to generate the state-action $Q$-value function for the classic inverted pendulum problem. The correctness of the solution is verified by trajectory simulations.

## Problem definition

The goal is to balance the inverted pendulum on the upright equilibrium position.
The physical dynamics of the system is described by the angle and the angular speed, i.e., $(\theta, \dot{\theta})$. Denote $\tau$ as the time interval between decisions, $u$ as the torque input on the pendulum, the dynamics can be written as

\begin{align}
    & \theta := \theta + \dot{\theta}~\tau,\\
    & \dot{\theta} := \dot{\theta} + \left( \sin{\theta} - \dot{\theta} + u \right)\tau.
\end{align}

A reward function that penalizes control effort while favoring an upright pendulum is used as

\begin{equation}
    r(\theta,u) = - 0.1u^2 + \exp{\left(\cos{\theta}-1 \right)}.
\end{equation}

In the simulation, the state space is $(-\pi, \pi]$ for $\theta$ and $[-10,10]$ for $\dot{\theta}$. We limit the input torque in $[-1,1]$ and set $\tau=0.3$.
We discretize each dimension of the state space into 50 values, and action space into 1000 values, which forms an $Q$-value function  a matrix of dimension $2500\times 1000$.

## Structured Value-based Planning (SVP)

The proposed structured value-based planning (SVP) approach is based on the $Q$-value iteration. At the $t$-th iteration, instead of a full pass over all state-action pairs:
- SVP first randomly selects a subset $\Omega$ of the state-action pairs. In particular, each state-action pair in $\mathcal{S}\times\mathcal{A}$ is observed (i.e., included in $\Omega$) independently with probability $p$.
- For each selected $(s,a)$, the intermediate $\hat{Q}(s,a)$ is computed based on the $Q$-value iteration:
    \begin{equation*}\hat{Q}(s,a) \leftarrow \sum_{s'} P(s'|s,a) \left( r(s,a) + \gamma \max_{a'} Q^{(t)}(s',a') \right),\quad\forall\:(s,a)\in\Omega.
    				\end{equation*}
- The current iteration then ends by reconstructing the full $Q$ matrix with matrix estimation, from the set of observations in $\Omega$. That is, $Q^{(t+1)}=\textrm{ME}\big(\{\hat{Q}(s,a)\}_{(s,a)\in\Omega}\big).$

Overall, each iteration reduces the computation cost by roughly $1-p$.

Through SVP, we can compute the final state-action $Q$-value function.
To obtain the optimal policy for state $s$, we compute

\begin{align*}
    \pi^{\star} \left(s\right) = \mbox{argmax}_{a \in \mathcal{A}} Q^{\star}\left(s, a\right).
\end{align*}

### Generate state-action value function with SVP

```julia
push!(LOAD_PATH, ".")
using MDPs, InvertedPendulum, Printf, LinearAlgebra
mdp = MDP(state_space(), action_space(), transition, reward)
__init__()
policy = value_iteration(mdp, true, "../data/qip_otf_0.4.csv", true)
print("")  # suppress output
```

### Visualize policy as a heat map

```julia
viz_policy(mdp, policy, "SVP policy (40% observed)", true, "ip/policy_ip_0.4.tex")
```

### Verify correctness
Simulate and visualize trajectory from initial state `[angle, speed]`.

```julia
ss, as = simulate(mdp, policy, [-0.5, 0.0])
viz_trajectory(ss, as, "SVP policy trajectory (40% observed)", "SVP policy input (40% observed)", true, "ip/traj_ip_0.4.tex")

```

```julia
nsim = 2000
deviation = 0
for sim = 1:nsim
    state = [rand(PMIN:0.001 * (PMAX - PMIN):PMAX),
             rand(VMIN:0.001 * (VMAX - VMIN):VMAX)]
    traj, _ = simulate(mdp, policy, copy(state))
    deviation += vecnorm(traj[51:end, 1])
end # for sim
deviation /= nsim
@printf("average deviation: %.3f\n", deviation)
```

```julia

```


### Chapter 3 - Exercise 7

> Gil Miranda (https://github.com/gil-miranda)

Queremos provar que, para a regressão linear `simples`, vale a igualdade

$$
R^2 = r^2 = Corr(X,Y)^2
$$

Onde:
$$
\begin{align}
Corr(X,Y) = \frac{\sum^n_{i=1}(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum^n_{i=1}(x_i - \bar{x})^2} \sqrt{\sum^n_{i=1}(y_i - \bar{y})^2}}
\tag{1}
\end{align}
$$

$$
\begin{equation*}
R^2 = \frac{TSS - RSS}{TSS}
\tag{2}
\end{equation*}
$$

$$
\begin{align*}
RSS = \sum^n_{i=1}(y_i-\hat{y}_i)^2 \quad TSS = \sum^n_{i=1}(y_i-\hat{y}_i)^2
\end{align*}
$$

Para o exercício, podemos assumir, sem perda de generalidade que $\bar{x} = \bar{y} = 0$, apenas para efeitos de simplificação das contas.

Vamos relembrar o estimador de minimos quadrados para os parâmetros $\beta_0$ e $\beta_1$ e também o estimador $\hat{y}$

$$
\begin{equation}
\hat{y} = \hat{\beta}_0 + \hat{\beta}_1 x \tag{3}
\end{equation}
$$

$$
\begin{equation*}
\hat{\beta}_0 = \bar{y} - \hat{\beta}_1 \bar{x}
\end{equation*}
$$

$$
\begin{equation*}
\hat{\beta}_1 = \frac{\sum^n_{i = 1} (x_i - \bar{x})(y_i - \bar{y})}{\sum^n_{i = 1} (x_i - \bar{x})}
\end{equation*}
$$
Que, tomando a hipotese de simplificação temos
$$
\begin{equation*}
\hat{\beta}_0 = 0
\end{equation*}
$$

$$
\begin{equation}
\hat{\beta}_1 = \frac{\sum^n_{i = 1} x_iy_i}{\sum^n_{i = 1}x_i} \tag{4}
\end{equation}
$$

---
Temos agora todas as ferramentas para resolver o exercício
$$
\newcommand{\equalexpl}[1]{\underset{\substack{\uparrow\\ \\ \mathrlap{\text{\hspace{-1em}#1}}}}{=}}
\begin{align*}
\frac{\displaystyle\sum^n_{i=1}y_i^2 - \displaystyle\sum^n_{i=1}(y_i - \hat{y}_i)^2}{\displaystyle\sum^n_{i=1}y_i^2} &\equalexpl{substituindo (3)} \frac{\displaystyle\sum^n_{i=1}y_i^2 - \displaystyle\sum^n_{i=1}(y_i - \hat{\beta}_1x_i)^2}{\displaystyle\sum^n_{i=1}y_i^2}\\
&\equalexpl{expandindo o quadrado} \frac{\cancel{\displaystyle\sum^n_{i=1}y_i^2} - \cancel{\displaystyle\sum^n_{i=1}y_i^2} + 2\hat{\beta}_1\displaystyle\sum^n_{i=1}x_iy_i - \beta_1^2\displaystyle\sum^n_{i=1} x_i^2 }{\displaystyle\sum^n_{i=1}y_i^2}\\
&\equalexpl{substituindo (4)} \frac{\cancel{2} \frac{\left(\displaystyle\sum^n_{i=1}x_iy_i\right)^2}{\displaystyle\sum^n_{i=1}x_i^2}  - \frac{\cancel{\left(\displaystyle\sum^n_{i=1}x_iy_i\right)^2}}{\left(\displaystyle\sum^n_{i=1}x_i^2\right)^{\bcancel{2}}}\bcancel{\displaystyle\sum^n_{i=1} x_i^2} }{\displaystyle\sum^n_{i=1}y_i^2}\\
&= \frac{\left(\displaystyle\sum^n_{i=1}x_iy_i\right)^2}{\displaystyle\sum^n_{i=1}y_i^2 \displaystyle\sum^n_{i=1}x_i^2}\\
&= Corr(X,Y)^2\\
&\therefore R^2 = r^2
\end{align*}\\

$$

---
Podemos chegar no mesmo resultado de uma forma mais fácil, e sem precisar assumir $\bar{x} = \bar{y} = 0$, para isso vamos olhar a fórmula de $R^2$ de outra maneira

$$
\begin{equation}
Var(X) = \frac{1}{n-1} \sum^n_{i=1} (X_i - \mathbb{E}[X])^2 \tag{5}
\end{equation}
$$

$$
\begin{equation}
Cov(X,Y) = \frac{1}{n-1} \sum^n_{i=1} (X_i - \mathbb{E}[X])(Y_i - \mathbb{E}[Y]) \tag{6}
\end{equation}
$$

Tomamos $\mathbb{E}[X] = \bar{X}$, média amostral.

Segue da identidade
$$TSS = ESS + RSS$$
Com $ESS  = \sum^n_{i=1} (\hat{y} - \bar{y})^2$

Que
$$
\newcommand{\equalexpl}[1]{\underset{\substack{\uparrow\\ \\ \mathrlap{\text{\hspace{-1em}#1}}}}{=}}
\begin{align*}
R^2 &= \frac{ESS}{TSS}\\
&= \frac{\sum^n_{i=1} (\hat{y} - \bar{y})^2}{\sum^n_{i=1} (y - \bar{y})^2}\\
&= \frac{\sum^n_{i=1} (\hat{y} - \bar{y})^2}{\sum^n_{i=1} (y - \bar{y})^2} \cdot \frac{(n-1)^{-1}}{(n-1)^{-1}}\\
&\equalexpl{substituindo (5)} \frac{Var(\hat{y})}{Var(y)} \tag{7}
\end{align*}
$$

Vamos também manipular a equação $(3)$
$$
\begin{align*}
\hat{\beta}_1 &= \frac{\sum^n_{i = 1} (x_i - \bar{x})(y_i - \bar{y})}{\sum^n_{i = 1} (x_i - \bar{x})}\\
&= \frac{\sum^n_{i = 1} (x_i - \bar{x})(y_i - \bar{y})}{\sum^n_{i = 1} (x_i - \bar{x})}\cdot \frac{(n-1)^{-1}}{(n-1)^{-1}}\\
&= \frac{Cov(x,y)}{Var(x)} \tag{8}
\end{align*}
$$

E a equação $(1)$
$$
\begin{align*}
Corr(x,y)^2 &= \frac{\left(\sum^n_{i=1}(x_i - \bar{x})(y_i - \bar{y})\right)^2}{\sum^n_{i=1}(x_i - \bar{x})^2 \sum^n_{i=1}(y_i - \bar{y})^2}\\
&= \frac{\left(\sum^n_{i=1}(x_i - \bar{x})(y_i - \bar{y})\right)^2}{\sum^n_{i=1}(x_i - \bar{x})^2 \sum^n_{i=1}(y_i - \bar{y})^2} \cdot \frac{(n-1)^{-1}}{(n-1)^{-1}}\\
&= \frac{Cov(x,y)^2}{Var(x)Var(y)}
\tag{9}
\end{align*}
$$

Agora podemos utilizar as propriedades da variância para encontrar o resultado esperado
$$
\newcommand{\equalexpl}[1]{\underset{\substack{\uparrow\\ \\ \mathrlap{\text{\hspace{-1em}#1}}}}{=}}
\begin{align*}
R^2 &= \frac{Var\left(\hat{y}\right)}{Var(y)}\\
&\equalexpl{substituindo (3)} \frac{Var(\hat{\beta}_0 + \hat{\beta}_1x)}{Var(y)}\\
&= \frac{Var(\hat{\beta}_1x)}{Var(y)}\\
&= \hat{\beta}_1^2\frac{Var(x)}{Var(y)}\\
&\equalexpl{substituindo (8)} \left(\frac{Cov(x,y)}{Var(x)}\right)^2 \cdot \frac{Var(x)}{Var(y)}\\
&= \frac{Cov(x,y)^2}{Var(x)\cdot Var(y)}\\
&\equalexpl{substituindo (9)} Corr(x,y)^2 = r^2\\
&\therefore R^2 = r^2
\end{align*}
$$


```python
import heapq, random, itertools, operator, functools, math
import matplotlib.pyplot as plt
from sympy import IndexedBase
```

```python
def take(iterable, n):
    for _ in range(n):
        yield next(iterable)
```

```python
a = IndexedBase('a')
actions = [a[i] for i in range(10)]
actions
```

    [a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]]

```python
q_star = { a: random.gauss(0, 1) for a in actions }
```

```python
def rewarder(q_star, process, *, stationarity):

    reward = None
    Q = {a: v for a, v in q_star.items()}

    while True:

        action, q, n, t, by = process.send(reward)
        qs = Q[action]
        reward = random.gauss(qs, 1)
        yield action, q, qs, by, reward

        Q = {a:stationarity(a, q) for a, q in Q.items()}
```

```python
def k_armed_bandit(actions, epsilon, init=lambda action: 0, *, update,measure):

    Q = [(-q, -q, 0, i, a) for i, a in enumerate(actions) for q in [init(a)]]
    heapq.heapify(Q)

    for t in itertools.count():

        by = None
        if random.random() > epsilon:
            _, q, n, i, a = heapq.heappop(Q)
            by = 'e' # exploiting flag
        else:
            j = random.randrange(0, len(Q))
            _, q, n, i, a = Q.pop(j)
            heapq.heapify(Q) # takes linear time to maintain the heap invariant.
            by = 's' # search flag

        q = -q # to restore "max-heap" behavior
        n_next = n + 1
        tup = (a, q, n_next, t, by)
        reward = yield tup

        q_updated = update(*(tup + (reward,)))

        q_measured = measure(a, q_updated, n_next, t, by, reward)

        heapq.heappush(Q, (-q_measured, -q_updated, n_next, i, a))
```

```python
def optimistic_k_armed_bandit(init):
    return functools.partial(k_armed_bandit, init=init, measure=lambda *args: args[1])
```

```python
def sample_average(action, q, n, t, by, reward):
    return q + ((reward - q)/n)
```

```python
def exponential_recency_weighted_average(alpha):
    return lambda action, q, n, t, by, reward: q + alpha*(reward - q)
```

```python
def upper_confidence_bound(c):
    def M(action, q, n, t, by, reward):
        #return q if by == 's' else (q + c * math.sqrt(math.log(t+1)/n))
        return q + c * math.sqrt(math.log(t+1)/n)
    return functools.partial(k_armed_bandit, measure=M)

```

```python
def unbiased_exponential_recency_weighted_average(alpha):
    o = 0.0

    def unbiased(action, q, n, t, by, reward):
        nonlocal o
        o = o + alpha*(1-o)
        step_size = alpha / o
        return q + step_size*(reward-q)

    return unbiased
```

```python
def stationary(a, q):
    return q
```

```python
def gauss_error_non_stationary(sd):
    return lambda a, q: q + random.gauss(0, sd)
```

```python
def avg(*args):
    return sum(args)/len(args)
```

```python
R = rewarder(q_star,
             process=k_armed_bandit(actions, epsilon=0.1, update=sample_average),
             stationarity=stationary)
```

```python
plt.plot(list(take(map(lambda tup: tup[-1], R), 1000)));
```

```python
R = rewarder(q_star,
             process=optimistic_k_armed_bandit(actions, epsilon=0.1, update=sample_average),
             stationarity=stationary)
```

```python
plt.plot(list(take(map(lambda tup: tup[-1], R), 1000)));
```

```python
list(take(R, 100))
```

    [(a[6], 1.1603025623309136, 1.193593242594897, 'e', 2.025184908199433),
     (a[6], 1.1612809360253349, 1.193593242594897, 'e', -0.21065852210900782),
     (a[6], 1.159730721948347, 1.193593242594897, 'e', 0.675522184564056),
     (a[6], 1.1591842111838049, 1.193593242594897, 'e', 0.8977523121521089),
     (a[6], 1.158889473980838, 1.193593242594897, 'e', 1.9767313536305577),
     (a[6], 1.1598104670885516, 1.193593242594897, 'e', 1.766221427556978),
     (a[6], 1.1604925941531956, 1.193593242594897, 'e', 0.34780046938366393),
     (a[6], 1.1595794569343534, 1.193593242594897, 'e', 1.672830331969025),
     (a[6], 1.1601554960758065, 1.193593242594897, 'e', 1.569281577967653),
     (a[6], 1.160614157602591, 1.193593242594897, 'e', 1.8530315605901768),
     (a[6], 1.1613895410325883, 1.193593242594897, 'e', 1.7222081976080654),
     (a[6], 1.1620168549661178, 1.193593242594897, 'e', -0.193119465329463),
     (a[9], -0.615337335996176, -0.9771139250377112, 's', 0.8023605771727995),
     (a[6], 1.1605027361724916, 1.193593242594897, 'e', 2.6951733735731103),
     (a[6], 1.1622155382231618, 1.193593242594897, 'e', -0.28979397728632783),
     (a[8], 0.35322148755635174, -0.04772748946425353, 's', 0.9433843741567965),
     (a[6], 1.160596798518023, 1.193593242594897, 'e', 2.0521884364906615),
     (a[6], 1.1615896622574136, 1.193593242594897, 'e', -0.4660699719313368),
     (a[4], 0.5936802439722204, 0.45480487318915475, 's', 2.4146912653771375),
     (a[6], 1.159779139861208, 1.193593242594897, 'e', 1.8075136936612934),
     (a[6], 1.1604988449209859, 1.193593242594897, 'e', 1.5704248881683394),
     (a[6], 1.1609538127825256, 1.193593242594897, 'e', 2.353302875897825),
     (a[6], 1.1622757075309904, 1.193593242594897, 'e', 0.3503334743887244),
     (a[6], 1.161376546696946, 1.193593242594897, 'e', 1.2107634731436832),
     (a[6], 1.1614311782527498, 1.193593242594897, 'e', 1.6656530821025486),
     (a[8], 0.4024017281063888, -0.04772748946425353, 's', -1.868831152777455),
     (a[6], 1.161988329527722, 1.193593242594897, 'e', 2.0333898299494706),
     (a[6], 1.162950141338342, 1.193593242594897, 'e', 0.11839303579375593),
     (a[6], 1.161798479700476, 1.193593242594897, 'e', -1.8962651674898896),
     (a[6], 1.1584305681947595, 1.193593242594897, 'e', 1.3034144071777678),
     (a[6], 1.1585900663674582, 1.193593242594897, 'e', 0.9748899061095406),
     (a[6], 1.1583881980594826, 1.193593242594897, 'e', 1.7100087969204512),
     (a[6], 1.1589937091449503, 1.193593242594897, 'e', 1.1101548371118695),
     (a[6], 1.1589401577501772, 1.193593242594897, 'e', 1.8394093259559638),
     (a[6], 1.159685468996843, 1.193593242594897, 'e', 2.903737099188391),
     (a[6], 1.161593621765105, 1.193593242594897, 'e', 0.7012078001729257),
     (a[6], 1.1610904678617255, 1.193593242594897, 'e', 0.4736681710424683),
     (a[6], 1.160340006838997, 1.193593242594897, 'e', 0.011730249424934502),
     (a[6], 1.1590874334939436, 1.193593242594897, 'e', 0.2678269940960176),
     (a[6], 1.1581165615556017, 1.193593242594897, 'e', 1.8990432156111754),
     (a[6], 1.158922792952833, 1.193593242594897, 'e', 1.7292611817496786),
     (a[6], 1.1595427259841338, 1.193593242594897, 'e', 1.8274827032679581),
     (a[6], 1.16026795940138, 1.193593242594897, 'e', 2.1968530257373846),
     (a[6], 1.1613922382151936, 1.193593242594897, 'e', 1.9921244151366566),
     (a[6], 1.1622922730764302, 1.193593242594897, 'e', 1.9315599845318014),
     (a[3], 0.4086068402978509, -0.18464862541815075, 's', 0.2682063117291206),
     (a[6], 1.1631248138896937, 1.193593242594897, 'e', 1.3264542629810228),
     (a[6], 1.1633013862670898, 1.193593242594897, 'e', 2.422825211831761),
     (a[6], 1.1646615631845463, 1.193593242594897, 'e', 0.5520499488262622),
     (a[6], 1.1640007092316247, 1.193593242594897, 'e', 1.2640627405443623),
     (a[6], 1.1641085346964013, 1.193593242594897, 'e', 2.139322328725425),
     (a[6], 1.1651582804380902, 1.193593242594897, 'e', 1.573017143283555),
     (a[6], 1.1655968383551283, 1.193593242594897, 'e', 0.33294642676810826),
     (a[6], 1.1647024770107812, 1.193593242594897, 'e', 1.9388847328253396),
     (a[6], 1.1655331446672346, 1.193593242594897, 'e', 2.0081599946594824),
     (a[6], 1.1664362816983087, 1.193593242594897, 's', -0.2747498198432776),
     (a[6], 1.1648932558936602, 1.193593242594897, 'e', 2.100305608963199),
     (a[6], 1.1658936969129858, 1.193593242594897, 'e', 0.03880434516172304),
     (a[4], 0.7960148019061001, 0.45480487318915475, 's', -1.591514239013345),
     (a[6], 1.1646895416226533, 1.193593242594897, 'e', 0.8715750455831057),
     (a[6], 1.1643767193216505, 1.193593242594897, 'e', -0.8712973853609443),
     (a[6], 1.1622064910650594, 1.193593242594897, 'e', 0.4337678565252925),
     (a[6], 1.1614307310708742, 1.193593242594897, 'e', 1.7987004329363825),
     (a[6], 1.1621086775622205, 1.193593242594897, 'e', 0.8667545223666073),
     (a[6], 1.161794804921205, 1.193593242594897, 'e', 2.537706356651567),
     (a[6], 1.163255432895441, 1.193593242594897, 'e', 0.24907906045497674),
     (a[6], 1.162285998778325, 1.193593242594897, 'e', 1.4017977481484936),
     (a[6], 1.1625397188518103, 1.193593242594897, 'e', 1.9081919041078803),
     (a[6], 1.1633287687832983, 1.193593242594897, 'e', 1.1175374570429837),
     (a[6], 1.1632803635911837, 1.193593242594897, 'e', 1.094589961820757),
     (a[6], 1.1632078288480259, 1.193593242594897, 'e', 1.1396230779852294),
     (a[6], 1.1631829504188458, 1.193593242594897, 'e', 2.403990380100221),
     (a[6], 1.1644904398073403, 1.193593242594897, 'e', 3.012090896404409),
     (a[6], 1.166435282393232, 1.193593242594897, 'e', 2.1117503786507217),
     (a[8], 0.22769150649993927, -0.04772748946425353, 's', 0.21298961723248394),
     (a[6], 1.167429304576468, 1.193593242594897, 'e', 2.2869630081490913),
     (a[6], 1.1686052853575317, 1.193593242594897, 'e', -0.7086582172693914),
     (a[0], 0.5927298681049354, 0.5372002781187555, 's', 1.2051168455339007),
     (a[6], 1.166635439079854, 1.193593242594897, 'e', 0.49918791760907166),
     (a[6], 1.165935808554203, 1.193593242594897, 'e', 0.7170872215145974),
     (a[6], 1.1654658100337427, 1.193593242594897, 'e', 1.7838679676768545),
     (a[6], 1.1661126742153778, 1.193593242594897, 'e', 3.323032523014701),
     (a[6], 1.1683665089581148, 1.193593242594897, 'e', 2.138286703026523),
     (a[6], 1.169378951749418, 1.193593242594897, 'e', 2.5425946916059767),
     (a[5], -0.2552336317232545, -0.3843761983908249, 's', 0.8963851082559475),
     (a[6], 1.170810876399946, 1.193593242594897, 'e', 3.642707809127349),
     (a[6], 1.1733857690382037, 1.193593242594897, 'e', 0.9057096847823911),
     (a[6], 1.1731072299286762, 1.193593242594897, 'e', 1.064122953938474),
     (a[6], 1.1729939406604952, 1.193593242594897, 'e', 1.5689396582069695),
     (a[6], 1.1734050992456941, 1.193593242594897, 'e', 1.2865335336689416),
     (a[6], 1.173522452393436, 1.193593242594897, 'e', 1.7178944973894388),
     (a[6], 1.1740865685022401, 1.193593242594897, 'e', 1.3851117504614479),
     (a[6], 1.174305021071556, 1.193593242594897, 'e', 1.2071445551116937),
     (a[6], 1.1743389812929006, 1.193593242594897, 'e', 1.8921519118895445),
     (a[6], 1.1750805235765749, 1.193593242594897, 'e', 1.4482236055542461),
     (a[6], 1.1753624049821245, 1.193593242594897, 'e', 2.169091374940939),
     (a[6], 1.1763868678377523, 1.193593242594897, 'e', 0.7268126679790468),
     (a[4], 0.5572618978141556, 0.45480487318915475, 's', 0.9250279823182626),
     (a[6], 1.1759238666020584, 1.193593242594897, 'e', 0.3114109849927026),
     (a[6], 1.1750344500571928, 1.193593242594897, 'e', 0.7186801106782018)]

```python
R = rewarder(q_star,
             process=k_armed_bandit(actions,
                                    epsilon=0.1,
                                    update=exponential_recency_weighted_average(alpha=0.1)),
             stationarity=gauss_error_non_stationary(sd=0.01))
```

```python
plt.plot(list(take(map(lambda tup: tup[-1], R), 1000)));
```

```python
R = rewarder(q_star,
             process=k_armed_bandit(actions,
                                    epsilon=0.1,
                                    update=unbiased_exponential_recency_weighted_average(alpha=0.1)),
             stationarity=gauss_error_non_stationary(sd=0.01))
```

```python
plt.plot(list(take(map(lambda tup: tup[-1], R), 1000)));
```

```python
def benchmark(confs, nprocesses=2000, length=10000, filename=None):
    plt.ylim(-0.5, 2)
    avgs = []
    for conf in confs:
        process = conf['process']
        runs = [map(lambda tup: tup[-1],
                    rewarder(q_star,
                             process= process(actions,
                                              epsilon=conf['epsilon'],
                                              update=conf['update']),
                             stationarity=conf['stationarity']))
                for i in range(nprocesses)]
        R = list(take(map(avg, *runs), length))
        avgs.append(R)
        plt.plot(R, label=conf['label'])

    ret = plt.legend()

    if filename:
        plt.savefig(filename)

    return ret, avgs
```

```python
confs = [
    {
        'epsilon':0.1,
        'update':sample_average,
        'stationarity':stationary,
        'label': 'stationary, eps=0.1, sample average'
    },
    {
        'epsilon':0.1,
        'update':exponential_recency_weighted_average(alpha=0.1),
        'stationarity':stationary,
        'label': 'stationary, eps=0.1, ERW=0.1 average'
    },
    {
        'epsilon':0.1,
        'update':unbiased_exponential_recency_weighted_average(alpha=0.1),
        'stationarity':stationary,
        'label': 'stationary, eps=0.1, ERW=0.1 unbiased average'
    },
    {
        'epsilon':0.1,
        'update':sample_average,
        'stationarity':gauss_error_non_stationary(sd=0.01),
        'label': 'non stationary with gaussian errs, eps=0.1, sample average'
    },
    {
        'epsilon':0.1,
        'update':exponential_recency_weighted_average(alpha=0.1),
        'stationarity':gauss_error_non_stationary(sd=0.01),
        'label': 'non stationary with gaussian errs, eps=0.1, ERW=0.1 average'
    },
    {
        'epsilon':0.1,
        'update':unbiased_exponential_recency_weighted_average(alpha=0.1),
        'stationarity':gauss_error_non_stationary(sd=0.01),
        'label': 'non stationary with gaussian errs, eps=0.1, ERW=0.1 unbiased average'
    },
]
# bench
fig, avgs = benchmark(confs, filename='non-stationary-benchmark.svg', length=10_000)
fig
```

```python
confs = [
    {
        'epsilon':0,
        'update':sample_average,
        'stationarity':stationary,
        'label': 'greedy, eps=0.0, sample average, stationary'
    },
    {
        'epsilon':0.01,
        'update':sample_average,
        'stationarity':stationary,
        'label': 'greedy, eps=0.01, sample average, stationary'
    },
    {
        'epsilon':0.1,
        'update':sample_average,
        'stationarity':stationary,
        'label': 'greedy, eps=0.1, sample average, stationary'
    },
    {
        'epsilon':0.5,
        'update':sample_average,
        'stationarity':stationary,
        'label': 'greedy, eps=0.5, sample average, stationary'
    },
]

# bench
fig, avgs = benchmark(confs, filename='stationary-benchmark.svg', length=10_000)
fig;
```

```python
confs = [
    {
        'epsilon':0,
        'update':sample_average,
        'stationarity':stationary,
        'label': 'greedy, eps=0.0, sample average, stationary'
    },
    {
        'epsilon':0.01,
        'update':sample_average,
        'stationarity':stationary,
        'label': 'greedy, eps=0.01, sample average, stationary'
    },
    {
        'epsilon':0.1,
        'update':sample_average,
        'stationarity':stationary,
        'label': 'greedy, eps=0.1, sample average, stationary'
    },
    {
        'epsilon':0.5,
        'update':sample_average,
        'stationarity':stationary,
        'label': 'greedy, eps=0.5, sample average, stationary'
    },
]

# bench
fig, avgs = benchmark(confs, filename='stationary-benchmark.svg', length=10_000)
fig;
```

```python
confs = [
    {
        'process': functools.partial(k_armed_bandit, measure=lambda *args: args[1]),
        'epsilon':0.1,
        'update':sample_average,
        'stationarity':stationary,
        'label': 'greedy realistic, eps=0.0, sample average, stationary'
    },
    {
        'process': optimistic_k_armed_bandit(init=lambda action: 100),
        'epsilon':0.1,
        'update':sample_average,
        'stationarity':stationary,
        'label': 'greedy optimistic, eps=0.01, sample average, stationary'
    },
    {
        'process': upper_confidence_bound(c=2),
        'epsilon':0,
        'update':sample_average,
        'stationarity':stationary,
        'label': 'upper confidence bound, eps=0.0, sample average, stationary'
    },
]

# bench
fig, avgs = benchmark(confs, filename='stationary-benchmark.svg', nprocesses=2000, length=500)
fig;
```

```python

```


```python
import kwant
import semicon

import numpy as np
import scipy.linalg as la
import scipy.sparse.linalg as sla

import matplotlib.pyplot as plt
%matplotlib inline
```

# Peierl's substitution

```python
# Define model and system

hamiltonian = '(k_x**2 + k_y**2) * sigma_0 + g * B_z * sigma_z'
vector_potential = '[-B_z * y, 0, 0]'

shape = lambda site: -10 <= site.pos[1] <= 10

# We cannot make use of ``kwant.continuum.discretize`` because we need
# to alter output of symbolic discretization in order to include
# Peierl's substitution

tb_ham, coords = kwant.continuum.discretize_symbolic(hamiltonian, 'xy')
tb_ham = semicon.peierls.apply(tb_ham, coords, A=vector_potential)
template = kwant.continuum.build_discretized(tb_ham, coords)

# Build the system in standard and usual way

syst = kwant.Builder(kwant.TranslationalSymmetry([1, 0]))
syst.fill(template, shape, (0, 0));
syst = syst.finalized()

# Let's look into the band structure

p = dict(t=1, phi_0=2*np.pi, B_z=.15, exp=np.exp, alpha=.25, g=.15)
kwant.plotter.bands(syst, params=p, show=False, fig_size=(12, 6));
plt.ylim(-.1, 2);
```

# compare with $ k_i \rightarrow k_i + \frac{e}{h} A_i $

```python
import sympy
sympy.init_printing()
```

```python
kwant.continuum.discretize_symbolic("(k_x + 2 * pi * (e/h) * A_x)**2")[0]
```

```python
hamiltonian = 'k_x**2'
vector_potential = '[A_x, 0, 0]'

tb_ham, coords = kwant.continuum.discretize_symbolic(hamiltonian, 'x')
tb_ham = semicon.peierls.apply(tb_ham, coords, A=vector_potential)
tb_ham
```


# Lecture 8:  Chemical Kinetics
Today's lecture will provide a rapid tour through chemical kinetics.  The final result is that for a system consisting of $N$ species undergoing $M$ **irreversible**, **elementary** reactions of the form
\begin{align}
  \sum_{i=1}^{N}{\nu_{ij}^{\prime}\mathcal{S}_{i}} \longrightarrow
  \sum_{i=1}^{N}{\nu_{ij}^{\prime\prime}\mathcal{S}_{i}}, \qquad j = 1, \ldots, M
\end{align}
the rate of change of specie $i$ (the reaction rate) can be written as
\begin{align}
  f_{i} = \sum_{j=1}^{M}{\nu_{ij}\omega_{j}}, \qquad i = 1, \ldots, N
\end{align}
where the progress rate for each reaction is given by
\begin{align}
  \omega_{j} = k_{j}\prod_{i=1}^{N}{x_{i}^{\nu_{ij}^{\prime}}}, \qquad j = 1, \ldots, M
\end{align}
and $k_{j}$ is the forward reaction rate coefficient.

We will discuss the meaning of these expressions and each term as well as the physical units.  We will also go through a few different forms for the reaction rate coefficients

Your responsibility for the final project is to write a library that will return the reaction rate to a user once the user specifies the reaction set.

A few notes:

| Symbol | Meaning |
|:--------:|:-------:|
| $\mathcal{S}_{i}$ | Chemical symbol of specie $i$ |
| $\nu_{ij}^{\prime}$ | Stoichiometric coefficients of reactants |
| $\nu_{ij}^{\prime\prime}$ | Stoichiometric coefficients of products |
| $N$                       | Number of species in system |
| $M$                       | Number of elementary reactions |
| $f_{i}$                   | Rate of consumption or formation of specie $i$ (reaction rate) |
| $\omega_{j}$              | Progress rate of reaction $j$ |
| $x_{i}$                   | Concentration of specie $i$ |
| $k_{j}$                   | Reaction rate coefficient for reaction $j$ |

```python

```


```python
import matplotlib.pyplot as plt
import numpy as np
x0 = np.linspace(0,8,100)
fig = plt.figure()
ax = fig.add_subplot()
ax.set_aspect('equal')
ax.set_xlim(xmin=0, xmax= 10)
ax.set_ylim(ymin=0, ymax= 10)
ax.quiver(0,0,7,7,scale=1,units='xy')
ax.text(7,7,'v',fontsize=16)
ax.quiver(0,0,3,8,scale=1,units='xy')
ax.text(3,8.3,'u',fontsize=16)
ax.quiver(0,0,5.5,5.5,scale=1,units='xy',color='r')
ax.text(3,5,r'$u_{\parallel_v}$',fontsize=16)
ax.quiver(5.5,5.5,3-(5.5),8-(5.5),scale=1,units='xy',color='r')
ax.text(4.3,7,r'$u_{\perp_v}$',fontsize=16)

ax.quiver(0,0,8,3,scale=1,units='xy')
ax.text(8,3,r"u'",fontsize=16)
ax.quiver(11/2,11/2,8-11/2,3-11/2,scale=1,units='xy',color='magenta')
ax.text(6.5,4.5,r'$-u_{\perp_v}$',fontsize=16)

import clifford.g2 as g2
# paru -S python-numba
# pip install clifford
u = 3*g2.e1 + 8*g2.e2
v = 7*g2.e1 + 7*g2.e2
(u|v)*v.inv()
(u^v)*v.inv()
v*u*v.inv()

```

```python

```

    (8.0^e1) + (3.0^e2)

# Projection in $G_{(\mathbb{R}_2)}$
> ## $ \begin{array}{}uv + vu  = 2(u\cdot v) \\
^{Proj_v(u)}_{u=u_{\parallel_v}+u_{\perp_v}}{\huge\color{red}{g}}^{u\cdot v v^{-1}}_{u\cdot v = u_{\parallel_v}v}
\end{array}
\left \{
\begin {array}{}
uv = (u \cdot v) + (u \wedge v)  \\
vu = (u \cdot v) - (u \wedge v) \\
u_{\parallel_v}v = u \cdot v\, \because x \parallel y \iff x \wedge y = 0 \\
uv + vu  = 2(u_{\parallel_v}v) \\
\frac{1}{2}(uv + vu) = u_{\parallel_v}v \\
u \cdot v = u_{\parallel_v}v \\
u \cdot v v^{-1} = u_{\parallel_v} \quad \because v^{-1} = \frac{v}{||v||^2}\\
\frac{u \cdot v}{v \cdot v} v = u_{\parallel_v} \quad \because v^2 = v \cdot v ,\quad v \parallel v\\
\end{array}\right.$

> ### $ u = u_{\parallel_v} + u_{\perp_v}$
> ### $ uv = u_{\parallel_v}v + u_{\perp_v}v$
> ### $ uv = u_{\parallel_v}v - vu_{\perp_v}$
> ### $ uv = u_{\parallel_v}v - v(u - u_{\parallel_v})$
> ### $ uv = u_{\parallel_v}v - vu + vu_{\parallel_v}$
> ### $ uv = u_{\parallel_v}v - vu + u_{\parallel_v}v$
> ### $ uv + vu = u_{\parallel_v}v + u_{\parallel_v}v$
> ### $ uv + vu = 2u_{\parallel_v}v$
> ### $ \frac{1}{2}(uv + vu) = u_{\parallel_v}v$
> ### $ u \cdot v = u_{\parallel_v}v$

> ### $
^{P_j(\alpha x + \beta y)}_{P_j(\alpha x) + P_j(\beta y)}{\huge\color{red}{p_{roj}}}^{\alpha P_j(x)+\beta P_j(y)}_{}
$

# Rejection in $G_{(\mathbb{R}_2)}$
> ## $\begin{array}{}
uv - vu = 2(u \wedge v)\\
u \wedge v = \frac{1}{2}(uv - vu)\\
u_{\perp_v}v = u_{\perp_v} \wedge v \\
^{Rej_v(u)}_{u=u_{\parallel_v}+u_{\perp_v}}{\huge\color{red}{g}}^{u\wedge v v^{-1}}_{u\wedge v = u_{\perp_v}v}
\end{array}
\left \{
\begin {array}{}
uv = (u \cdot v) + (u \wedge v)  \\
vu = (u \cdot v) - (u \wedge v) \\
u_{\perp_v}v = u \wedge v\, \because u \perp v \iff u \cdot v = 0 \\
uv - vu  = 2(u_{\perp_v}v) \\
\frac{1}{2}(uv - vu) = u_{\perp_v}v \\
u \wedge v = u_{\perp_v}v \\
u \wedge v v^{-1} = u_{\perp_v} \quad \because v^{-1} = \frac{v}{||v||^2}\\
\frac{u \wedge v}{v \cdot v} v = u_{\perp_v} \quad \because v^2 = v \cdot v ,\quad v \parallel v\\
\end{array}\right.$

# Reflection in $G_{(\mathbb{R}_2)}$
> # $ ^{Ref_v(u)}_{u=u_{\parallel_v} + u_{\perp_v}}{\Large\color{red}{g}}^{vuv^{-1}}_{u'=u_{\parallel_v}-u_{\perp_v}}$

> ## $ u_{\parallel_v} - u_{\perp_v} =
u \cdot v v^{-1} - u \wedge v v^{-1} = (u \cdot v - u \wedge v ) v^{-1} = (v \cdot u + v \wedge u)v^{-1} = vuv^{-1}$

> ## $ u' = u_{\parallel_v} - u_{\perp_v} \\
vu' = vu_{\parallel_v} - vu_{\perp_v} \\
vu' = u_{\parallel_v}v + u_{\perp_v}v \\
vu' = uv\\
v^{-1}vu' = v^{-1}uv\\
u' = v^{-1}uv $
> # $ ^{Ref_v(u)}_{u=u_{\parallel_v} + u_{\perp_v}}{\Large\color{red}{g}}^{v^{-1}uv}_{u'=u_{\parallel_v}-u_{\perp_v}}$

```python
import clifford.g2 as g2
u = g2.e1 + 4*g2.e2
v = 3*g2.e1 + g2.e2

#u.inv()*v.inv() == (u*v).inv()
# (uv)^{-1}
v.inv()*u*v == v*u*v.inv()
```

    0.04118 - (0.06471^e12)

# Rotation in $G_{\mathbb{R}_2}$
> ## $
^{Rot_v(\theta)}_{I=e_1e_2}{\Large\color{red}{g}}^{ve^{I\theta}}_{e^{-I\theta}v}
$
>> ### need bivector I
>>> ### $ \color{red}{Bivector} \left \{ \begin{array}{}
e_1e_2 \\ e_1 \wedge e_2 \\ uv = u \cdot v + u \wedge v \\
\Large\therefore\, {^{Dref_{\hat{v}\hat{w}}(u)}_{}{\Large\color{red}{g}}^{(w^{-1})(v^{-1})u(v)(w)}_{(wv)^{-1}u(vw)}}
\end{array}\right .$

```python
v*w
```

    0.5403 + (0.5403^e1) + (0.84147^e12)

```python
# import clifford as cl
import clifford.g3 as g3
import sympy as sm
import numpy as np
v = np.sin(np.pi/2)*np.cos(0)*g3.e1 + np.sin(np.pi/2)*np.sin(0)*g3.e2 + np.cos(np.pi/2)*g3.e3
w = np.sin(2)*np.cos(1)*g3.e1 + np.sin(2)*np.sin(1)*g3.e2 + np.cos(2)*g3.e3
#w = (1/np.sqrt(2))*g3.e1 + (1/np.sqrt(2))*g3.e2
u = g3.e1 + g3.e3
(v*w).inv()*u*(v*w)
```

    -(0.10836^e1) + (1.38865^e2) + (0.24474^e3)

```python
w*v*u*v*w
```

    -(0.10836^e1) + (1.38865^e2) + (0.24474^e3)

```python

```

    (1.0^e1)

```python
(v*w).mag2()
```

    1.0

```python

```


```python
# sp3: Spherical Coordinates in R^3

# Make SymPy available to this program:
import sympy
from sympy import *

# Make GAlgebra available to this program:
from galgebra.ga import *
from galgebra.mv import *
from galgebra.printer import Fmt, GaPrinter, Format
    # Fmt:       sets the way that a multivector's basis expansion is output.
    # GaPrinter: makes GA output a little more readable.
    # Format:    turns on latex printer.
from galgebra.gprinter import gFormat, gprint
gFormat()
```

```python
# sp3: Geometric algebra for R^3 using spherical coordinates
# Mathematics convention: r, phi (colatitude), theta (longitude), in right handed order.

sp3coords = (r, phi, theta) = symbols('r phi theta')
sp3 = Ga('e', g=None, coords=sp3coords, \
    X=[r*sin(phi)*cos(theta), r*sin(phi)*sin(theta),r*cos(phi)], norm=True)
       # g = None. Instead, the spherical coordinate parameterization X is used.
       # "\" is Python's line continuation character
(er, ephi, etheta) = sp3.mv()

grad = sp3.grad
from galgebra.dop import *
pdr     = Pdop(r)
pdphi   = Pdop(phi)
pdtheta = Pdop(theta)
```

```python
sp3.grad
```

```python

```


# Projectile using function

- **Maximum Height**
\begin{equation}
H=\frac{{u^2}{\sin^2\theta}}{2g}
\end{equation}

- **Horizontal Range**
\begin{equation}
R=\frac{{u^2}{\sin2\theta}}{g}
\end{equation}

![img][image.png](attachment:image.png)

- **Time of Flight**
\begin{equation}
T=\frac{{2u}{\sin\theta}}{g}
\end{equation}

```python
import numpy as np                     # from numpy import* not required to write np
import pandas as pd                    # from pandas import*
import matplotlib.pyplot as plt        # from matplotlib import*
%matplotlib inline
```

```python
def projectile(u,angle):
    g =9.80                                     # acceleration due to gravity
    RA = u**2*(np.sin(2*np.pi*angle/180))/g     # horizontal range
    MH = u**2*(np.sin(np.pi*angle/180)**2)/(2*g)# maximum height
    TF = 2*u*np.sin(np.pi*angle/180)/g          # Time of flight
    return(RA,MH,TF)
```

```python
p = projectile(40,60)
p
```

    (141.39190265868388, 61.22448979591835, 7.069595132934192)

```python
Angle = []   # list of angle
RA = []      # list of  range
MH = []      # list of max height
TF = []      #list of time of flight
for i  in range(0,90+1,5):
    p = projectile(40,i)
    Angle.append(i)
    RA.append(p[0])   #add element in list RA
    MH.append(p[1])    #add element in list MH
    TF.append(p[2])    #add element in list TF
```

```python
#RA,MH,TF
```

```python
#mkdir plot
```

    A subdirectory or file plot already exists.

```python
#Multiplots/Subplots
plt.figure(figsize=[10,4])
plt.subplot(1,2,1)
plt.plot(Angle,RA,'rs--' ,label='Range')
plt.plot(Angle,MH ,'g-o', label='Max height')
plt.xlabel('Angle(deg)')
plt.ylabel('Distance(m)')
plt.title('Projectile Motion')
plt.legend()
plt.subplot(1,2,2)
plt.plot(Angle,TF,'k^:' ,label='Time of flight')
plt.xlabel('Angle(deg)')
plt.ylabel('Time(sec)')
plt.title('Projectile Motion')
plt.legend()
plt.savefig('plot/projective.png')
#plt.savefig('projective.eps')
#plt.show()
```

```python
data={}    # dictionary
data.update({"Angle":Angle,"Range":RA ,"Max.Height": MH,"Time of flight":TF})
#data
```

```python
df = pd.DataFrame(data)
df.head()
```

      Angle
      Range
      Max.Height
      Time of flight

      0
      0
      0.000000
      0.000000
      0.000000

      1
      5
      28.350723
      0.620092
      0.711475

      2
      10
      55.840023
      2.461526
      1.417536

      3
      15
      81.632653
      5.468351
      2.112809

      4
      20
      104.944916
      9.549206
      2.792001

```python
# mkdir data
```

```python
df.to_csv("data/projectile.csv")  #save data in csv format in excel
```

### Projectile in parabolic equation

- **velocity**
\begin{equation}
V=\sqrt{U_x^2+U_y^2} \\
U_x=u\sin\theta\\
U_y= u \sin\theta-gt\\
\alpha = tan^{-1}\frac{U_y}{U_x}
\end{equation}
- **Displacement**
\begin{equation}
x =U cos \theta .{t}\\
y = {x}  tan\theta- (\frac{g}{2U^2 cos^2\theta})x^2
\end{equation}

```python
import numpy as np                     # from numpy import* not required to write np
import pandas as pd                    # from pandas import*
import matplotlib.pyplot as plt        # from matplotlib import*
%matplotlib inline
import seaborn as sns
sns.set()
#---thoery part---
u=100
g=9.8
theta1 = 30*np.pi/180
theta2 = 45*np.pi/180
theta3 = 60*np.pi/180

for i in np.arange(0,1,0.01): # using loop for time
    t = i
    ux1=u*np.sin(theta1)
    #uy1 = u*np.sin(theta1) - g*t
    #x1 = u*t*np.cos(theta1)
x= np.arange(0,1500,50)
A1 = 2*u**2 *(np.cos(theta1))**2
y1 = x*np.tan(theta1)-(g/A1)*x**2
#print(y1)
A2 = 2*u**2 *(np.cos(theta2))**2
y2 = x*np.tan(theta2)-(g/A2)*x**2
#print(y2)
A3 = 2*u**2 *(np.cos(theta3))**2
y3 = x*np.tan(theta3)-(g/A3)*x**2
#print(y3)
```

```python
p={"range30":y1,\
   "range45":y2,\
   "range60":y3}
#p
```

```python
df=pd.DataFrame(p)
df.head()
```

      range30
      range45
      range60

      0
      0.000000
      0.00
      0.000000

      1
      27.234180
      47.55
      81.702540

      2
      51.201694
      90.20
      153.605081

      3
      71.902540
      127.95
      215.707621

      4
      89.336721
      160.80
      268.010162

```python
import matplotlib.pyplot as plt
u=50
g=9.8
thetas=[30,45,60]
fig, ax = plt.subplots(1, figsize=(8, 6))

colors=["red","blue","green"]
marker=["o","*",'s']
for i,theta in enumerate(thetas):
    T=2*u*np.sin(np.deg2rad(theta))/g
    t=np.arange(0,T,0.1)
    x=u*np.cos(np.deg2rad(theta))*t
    A = 2*u**2 *(np.cos(np.deg2rad(theta)))**2
    y = x*np.tan(np.deg2rad(theta))-(g/A)*x**2
    ax.plot(x, y, color=colors[i],marker=marker[i], label="angle:"+str(theta))
    ax.legend(loc=0,title="legend",frameon=False)

ax.set_title("Projectile at different angles",fontsize=15)
ax.set_xlabel("Range(meter)")
ax.set_ylabel("Height(meter)")

# plt.show()
```

```python
P={"x1":x , "y1":y}
dfy=pd.DataFrame(P)
dfy
```

      x1
      y1

      0
      0.0
      0.000000

      1
      2.5
      4.281127

      2
      5.0
      8.464254

      3
      7.5
      12.549381

      4
      10.0
      16.536508

      ...
      ...
      ...

      84
      210.0
      17.986670

      85
      212.5
      14.035797

      86
      215.0
      9.986924

      87
      217.5
      5.840051

      88
      220.0
      1.595178

89 rows × 2 columns

```python

```


# Initialization

```python
%matplotlib inline
%config InlineBackend.figure_format = 'svg'

import scqubits as qubit
import scqubits.utils.plotting as plot

import numpy as np
```

# Transmon qubit

The transmon qubit and the Cooper pair box are described by the Hamiltonian
\begin{equation}
H_\text{CPB}=4E_\text{C}(\hat{n}-n_g)^2+\frac{1}{2}E_\text{J}\sum_n(|n\rangle\langle n+1|+\text{h.c.}),
\end{equation}
expressed in the charge basis. Here,  $E_C$ is the charging energy, $E_J$ the Josephson energy, and $n_g$ the offset charge. Internal representation of the Hamiltonian proceeds via the charge basis with charge-number cutoff specified by `ncut`, which must be chosen sufficiently large for convergence.

An instance of the transmon qubit is initialized as follows:

```python
CPB = qubit.Transmon(
    EJ=30.02,
    EC=1.2,
    ng=0.3,
    ncut=31
    )
```

The energy eigenvalues of the transmon Hamiltonian for the given set of model parameters are obtained by calling the `eigenvals()` method. The optional parameter `evals_count` specifies the sought number of eigenenergies.

```python
CPB.eigenvals(evals_count=12)
```

    array([-21.84381856,  -6.17518551,   8.01366695,  20.04897106,
            30.54312385,  38.7071573 ,  54.55482909,  67.49323244,
            90.05182723, 107.1140667 , 135.67852225, 156.68219246])

To plot eigenenergies as a function of one of the qubit parameters (`EJ`, `EC`, or `ng`), we generate an array of values for the desired parameter and call the method `plot_evals_vs_paramvals`:

```python
ng_list = np.linspace(-2, 2, 220)
CPB.plot_evals_vs_paramvals('ng', ng_list, evals_count=6, subtract_ground=False);
```

    HBox(children=(FloatProgress(value=0.0, max=220.0), HTML(value='')))

```python
CPB.ng = 0.3
ej_vals = CPB.EJ * np.cos(np.linspace(-np.pi/2, np.pi/2, 40))
CPB.plot_evals_vs_paramvals('EJ', ej_vals, evals_count=4, subtract_ground=False);
```

    HBox(children=(FloatProgress(value=0.0, max=40.0), HTML(value='')))

```python
CPB.plot_n_wavefunction(esys=None, which=1, mode='real');
```

```python
CPB.plot_wavefunction(esys=None, which=[0,2,6], mode='real');
```

```python
CPB.plot_phi_wavefunction(esys=None, which=1, mode='abs_sqr');
```

### Charge matrix elements

```python
CPB.EJ  = 30
CPB.ncut = 80
nmat = CPB.matrixelement_table('n_operator', evals_count=10)
plot.print_matrix(abs(nmat));
```

```python
CPB.plot_matrixelements('n_operator', evals_count=10);
```

```python
fig, ax = CPB.plot_matelem_vs_paramvals('n_operator', 'ng', ng_list, select_elems=4, filename='./data/test');
```

    HBox(children=(FloatProgress(value=0.0, max=220.0), HTML(value='')))

```python

```