# 최적설계Optimum Design

- 주요 참고 문헌(예제 인용) : 최적설계입문Introduction to Optimum Design, Jasbir S. Arora[1]

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

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

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

sympy.init_printing()
```

## 경사도 벡터Gradient

- 선형회귀에서 회귀계수를 찾아내기 위한 알고리즘의 핵심

- $n$개의 변수 $x_1$, $x_2$, ... , $x_n$에 대한 함수 $f(\mathbf{x})$의 $\mathbf{x}^*$에서의 편미분

$$
c_i = \frac{\partial \, f(\mathbf{x}^*)}{\partial \, x_i}, \quad i=1, \dots, n
$$

- 이를 열벡터로 모으면 경사도 벡터

$$
\triangledown f(\mathbf{x}^*) = \begin{bmatrix}
\dfrac{\partial \, f(\mathbf{x}^*)}{\partial \, x_1} \\
\dfrac{\partial \, f(\mathbf{x}^*)}{\partial \, x_2} \\
\vdots \\
\dfrac{\partial \, f(\mathbf{x}^*)}{\partial \, x_n}
\end{bmatrix} = \begin{bmatrix}
\dfrac{\partial \, f(\mathbf{x}^*)}{\partial \, x_1} &
\dfrac{\partial \, f(\mathbf{x}^*)}{\partial \, x_2} &
\cdots &
\dfrac{\partial \, f(\mathbf{x}^*)}{\partial \, x_n}
\end{bmatrix}^{\text{T}}
$$

- $\triangledown$ 각 성분으로의 미분 연산을 의미하며 'nabla'라고 읽음

- 다음은 2변수, 3변수 함수의 경사도 벡터 $\triangledown f$를 나타냄

## 헤시안Hessian

- 경사도 벡터를 한번 더 미분, 즉 두번 미분

$$
\triangledown^2 f(\mathbf{x}^*) =\frac{\partial^2 f(\mathbf{x}^*)}{\partial \,\mathbf{x} \,  \partial \,\mathbf{x}} =
\begin{bmatrix}
\dfrac{\partial^2 f(\mathbf{x}^*)}{\partial x^{2}_1} & \dfrac{\partial^2 f(\mathbf{x}^*)}{\partial x_1 \partial x_2} & \cdots & \dfrac{\partial^2 f(\mathbf{x}^*)}{\partial x_1 \partial x_n} \\
\dfrac{\partial^2 f(\mathbf{x}^*)}{\partial x_2 \partial x_1} & \dfrac{\partial^2 f(\mathbf{x}^*)}{\partial x_2^2} & \cdots & \dfrac{\partial^2 f(\mathbf{x}^*)}{\partial x_2 \partial x_n}\\
\vdots & \vdots & \ddots & \vdots \\
\dfrac{\partial^2 f(\mathbf{x}^*)}{\partial x_n \partial x_1} & \dfrac{\partial^2 f(\mathbf{x}^*)}{\partial x_n^ \partial x_2} & \cdots & \dfrac{\partial^2 f(\mathbf{x}^*)}{\partial x_n^2}
\end{bmatrix}
$$

- 헤시안은 대칭행렬

- 행렬형식 표현

$$
\mathbf{H} = \left[ \frac{\partial^2 f}{\partial x_i \partial x_j} \right] ; \quad i=1,n, \,  j=1, n
$$

- 예제 : 다음 함수에서 점(1,2)에서의 경사도벡터와 헤시안을 계산

$$
f(\mathbf{x}) = x_1^3 + x_2^3 + 2x_1^2 + 3x_2^2 - x_1 x_2 + 2x_1 + 4x_2
$$

- 1계 편도함수

$$
\frac{\partial f}{\partial x_1} = 3 x_1^2 + 4 x_1 - x_2 + 2 \\[5pt]
\frac{\partial f}{\partial x_2} = 3 x_2^2 + 6 x_2 - x_1 + 4
$$

- $x_1 = 1$, $x_2 = 2$에서의 경사도 벡터

$$
\triangledown f(1,2) = \begin{bmatrix} 7 \\ 27 \end{bmatrix}
$$

- 헤시안을 위해 한번 더 미분

$$
 \begin{array}{lcl}
\dfrac{\partial^2 f}{\partial x_1^2}=6x_1 +4 & &\dfrac{\partial^2 f}{\partial x_1 \partial x_2}=-1\\
\dfrac{\partial^2 f}{\partial x_2 \partial x_1}=-1 & &\dfrac{\partial^2 f}{\partial x_2^2}=6x_2 +6
\end{array}
$$

- $x_1 = 1$, $x_2 = 2$에서의 헤시안

$$
\mathbf{H}(1,2) = \begin{bmatrix} 10 & -1 \\ -1 & 18 \end{bmatrix}
$$

## 테일러 급수Taylor series

- 미분가능한 함수를 어느 점 근처에서 다항식으로 근사하는 방법

- 스코틀랜드의 수학자 그레고리James Gregory가 시초

- 1715년 이후, 영국의 수학자 테일러Brook Taylor에 의해 널리 알려짐[2]

$$
f(x) = f(x^*) + \frac{d f(x^*)}{dx} (x - x^*) + \frac{1}{2} \frac{d^2 f(x^*)}{dx^2}(x - x^*)^2 + R
$$

- 위 식의 의미는 어떤 점 $x^*$이 근처에서 함수 $f(x)$의 값은 위 식처럼 계산될 수 있다는 것

- 약간 다르게 표현 하기 위해 $x-x^* = d$로 두고

$$
f(x^* + d) = f(x^*) + \frac{d f(x^*)}{dx} d + \frac{1}{2} \frac{d^2 f(x^*)}{dx^2}d^2 + R
$$

- 어떤 점 $x^*$에서 $d$만큼 떨어진 곳의 함수값은 $f(x^*)$를 중심으로 위 식처럼 계산될 수 있다.

- 실험으로 확인

```python
#%matplotlib inline
plt.rcParams["figure.figsize"] = (16,5)

import math

def  taylor_e(x, a, n) :
    # x* = a 에서 전개
    # f(x) = e^{a} + e^{a}*(x-a) + e^{a}/2*(x-a)^2 + R
    fx =  np.exp(a)

    # 테일러 시리즈 전개, 각 항의 상수는 일반적으로 1/1!, 1/2!, 1/3!, ..., 1/N!
    for i in range(1, n+1) :
        fx += np.exp(a)/math.factorial(i)*(x-a)**i

    #return np.exp(a) + np.exp(a)*(x-a) + np.exp(a)/2 * (x-a)**2
    return fx

x = np.linspace(0, 5)
y = np.exp(x)

fig, ax = plt.subplots(1,3)

fx1 = taylor_e(x, 3, 1)
fx2 = taylor_e(x, 3, 2)
fx3 = taylor_e(x, 3, 3)

ax[0].plot(x, y , lw=3, label="$e^x$")
ax[0].plot(x, fx1, lw=3, label="Taylor f(x) order 1", color=style_colors[1])
ax[0].legend(fontsize=15)

ax[1].plot(x, y , lw=3, label="$e^x$")
ax[1].plot(x, fx2, lw=3, label="Taylor f(x) order 2", color=style_colors[2])
ax[1].legend(fontsize=15)

ax[2].plot(x, y , lw=3, label="$e^x$")
ax[2].plot(x, fx3, lw=3, label="Taylor f(x) order 3", color=style_colors[3])
ax[2].legend(fontsize=15);

plt.suptitle("Taylor series, order=1,2,3", fontsize=15)
plt.show()
```

### 2변수 이상 테일러 급수

- 2차 항을 각 변수별로 모두 만들어 줌

$$
\begin{align}
& f(x_1, x_2) \\[5pt]
= \, &  f(x_1^*, x_2^*) + \frac{\partial f}{\partial x_1}(x_1 - x_1^*) + \frac{\partial f}{\partial x_2}(x_2 - x_2^*) \\[5pt]
&+ \frac{1}{2} \left[ \frac{\partial^2 f}{\partial x_1^2}(x_1 - x_1^*)^2 + 2 \frac{\partial^2 f}{\partial x_1 \partial x_2} (x_1 - x_1^*)(x_2 - x_2^*) + \frac{\partial^2 f}{\partial x_2^2}(x_2 - x_2^*)^2 \right] \\[5pt]
&+ R
\end{align}
$$

- 시그마 기호로 쓰면

$$
\begin{align}
f(x_1, x_2) &=  f(x_1^*, x_2^*) + \sum_{i=1}^{2} \color{RoyalBlue}{\frac{\partial f}{\partial x_i}}(x_i - x_i^*)  \\[5pt]
&+ \frac{1}{2}  \sum_{i=1}^{2} \sum_{j=1}^{2}  \color{OrangeRed}{\frac{\partial^2 f}{\partial x_i \partial x_j}} (x_i - x_i^*)(x_j - x_j^*) +  R
\end{align}
$$

- 색깔로 표시된 부분은 경사도벡터와 헤시안 행렬의 성분임을 이용하여 더 간략히 쓰면

$$
f(\mathbf{x}) = f(\mathbf{x}^*) + \triangledown f^{\text{T}}(\mathbf{x} - \mathbf{x}^*) + \frac{1}{2}(\mathbf{x}-\mathbf{x}^*)^{\text{T}} \mathbf{H}(\mathbf{x}-\mathbf{x}^*) + R
$$

- 1 변수와 마찬가지로 $\mathbf{x} - \mathbf{x}^* = \mathbf{d}$ 로 두면

$$
f(\mathbf{x}^* + \mathbf{d}) = f(\mathbf{x}^*) +  \triangledown f^{\text{T}} \mathbf{d} + \frac{1}{2} \mathbf{d}^{\text{T}} \mathbf{H} \mathbf{d} + R
$$

- $f(\mathbf{x}^*)$를 이항하면 좌변은 $\Delta f = f(\mathbf{x}^* + \mathbf{d})-f(\mathbf{x}^*)$ 가 되고 이는 $\mathbf{x}^*$에서 $\mathbf{x}$로 이동했을 때 함수값의 변화량이 된다.

$$
\Delta f = \triangledown f^{\text{T}} \mathbf{d} + \frac{1}{2} \mathbf{d}^{\text{T}} \mathbf{H} \mathbf{d} + R
$$

## 경사도벡터의 중요 성질

- 다음 두 경사도벡터의 성질은 경사하강법gradient descent의 근간

### 1. 함수표면의 접면에 수직

### 2. 경사도벡터의 방향은 함수의 최대증가방향

- 위 그림에서 함수값 0.4에서 0.8로 가려고 할때 가장 빠른 경로를 생각

- 자세한 증명은 부록으로 제공

- 방향도함수directional derivative의 개념을 사용하지만 지금까지 배운 편미분과 테일러시리즈의 지식으로 이해 가능

## 경사도, 헤시안을 이용한 최적조건

- 최적해를 위한 필요조건과 충분조건

- 필요조건 : 최적점에서 만족되어야 할 조건

- 필요조건을 만족하지 않으면 최적점이 아님

- 하지만 필요조건이 만족되었다고 최적점을 보장하는 것은 아님

- 따라서 필요조건을 만족하면 후보최적점candidate optimum point

- 후보최적점에 대해 충분조건을 검토하여 최적점을 판단

### 최적화 문제에는

- 다음 두 종류의 문제가 존재

- 본 수업에서는 비제약 최적화 문제만 다룸

#### 비제약 최적설계 문제

- 주어진 목적함수의 값을 무조건 작게 하는 변수 $\mathbf{x}$의 값을 찾는 문제

#### 제약 최적설계 문제

- 주어진 목적함수의 값을 작게하나 지정된 $\mathbf{x}$의 범위 내에서만 값을 찾는 문제

### 비제약 최적설계 문제

- 최적화 시킬 변수 $\mathbf{x}$에 관한 제약이 없는 상태에서 $f(\mathbf{x})$를 최소화

- 제약 문제를 비제약 문제로 환원하는 것이 가능하므로 비제약 문제를 이해하는 것이 중요

- 준비

    - $\mathbf{x}^*$를 지역 최소점으로 가정

    - 최소점으로 부터 약간의 이동 $\mathbf{d} = \mathbf{x}-\mathbf{x}^*$

    - 그에 따른 함수의 변화

    $$
    \Delta f = f(\mathbf{x}) - f(\mathbf{x}^*) \ge 0 \tag{1}
    $$

    - 식 (1)은 임의의 증분 $\mathbf{d}$에 대해 성립 필요

    - $\mathbf{d}$가 매우 작으므로 $\Delta f$ 를 $\mathbf{x}^*$에서 테일러 시리즈로 근사

### 1계 필요조건first order necesssary condition

#### 일변수 함수의 경우

- 최소점이 되기 위해 반드시 만족시켜야 하는 조건

- 최소점으로 가정한 점 $x^*$에서 테일러 시리즈

$$
f(x) = f(x^*) + f'(x^*) \, d + \frac{1}{2} f''(x^*) \, d^2 + R
$$

- $f(x^*)$를 이항하면 변화량 $\Delta f$는

$$
\Delta f = f'(x^*) d + \frac{1}{2} f''(x^*) \, d^2 + R
$$

- $x^*$가 최소라는 가정이 맞다면  $\Delta f$는 음수가 아님

- $d$는 매우 작은 증분이므로 $\Delta f$에서 $f'(x^*) d$가 지배항

- 그 뒤로는 제곱, 세제곱으로 급격히 작아진다.

- 따라서 $\Delta f$를 음이 아니게 하기 위해서 지배항 $f'(x^*) d$가 음이 아니어야 한다!

- $d$의 부호에 상관없이 $\Delta f$를 음이 아니게 하기 위해서

- **지역 최소를 위한 1계 필요조건**

$$
\color{OrangeRed}{f'(x^*) =0} \tag{2}
$$

- 필요조건을 만족시킨다면 그점은

    - 지역 최소
    - 지역 최대
    - 변곡점

- 이런 점을 통틀어 상점staionary point

#### 다변수 함수의 경우

- 똑같은 논리 적용

- 테일러 시리즈

$$
f(\mathbf{x}) = f(\mathbf{x}^*) + \triangledown f\left(\mathbf{x}^*\right)^{\text{T}}\mathbf{d} + \frac{1}{2}\mathbf{d}^{\text{T}} \mathbf{H}\left(\mathbf{x}^*\right)\mathbf{d} + R
$$

- 음이 되면 안되는 함수의 변화량

$$
\Delta f = \triangledown f\left(\mathbf{x}^*\right)^{\text{T}}\mathbf{d} + \frac{1}{2}\mathbf{d}^{\text{T}} \mathbf{H}\left(\mathbf{x}^*\right)\mathbf{d} + R
$$

- 경사도벡터에 대한 필요조건

$$
\color{OrangeRed}{\triangledown f\left(\mathbf{x}^*\right) = \mathbf{0}} \tag{2*}
$$

## 최적설계의 수치해법

- 만약 목적함수가 미분 가능이기는 하지만 미분 가능할 정도로 정식화되지 않았다면?

- 변수의 값에 따른 목적함수의 값만을 계산할 수 있는 상황이라면?

### 수치 알고리즘 일반개념

- 최적화 수치 알고리즘은 일반적으로 다음 단계를 따름

    - 단계 1. 타당성있는 출발점 $\mathbf{x}^{(0)}$ 추정, $k=0$
    - 단계 2. 탐색방향 $\mathbf{d}^{(k)}$를 계산
    - 단계 3. 수렴 검토
    - 단계 4. 양의 이동거리 $\alpha_k$ 계산
    - 단계 5. 새로운 설계 변수 계산 $\mathbf{x}^{(k+1)} = \mathbf{x}^{(k)} + \alpha_k \mathbf{d}^{(k)}$, $k=k+1$ 단계 2로

- 따라서   $\alpha_k$와   $\mathbf{d}^{(k)}$ 계산이 중요

### 경사도 수치계산

- 목적함수를 미분할 수 있는 함수의 형태를 모를 때 함수값만으로 미분계수를 구함

- 이론적으로 극한을 정의를 동원하지만 컴퓨테이션에서는 그냥 종속변수의 작은 변화를 독립변수의 작은 변화로 나눈 것

#### 전방차분법
$$
\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}
$$

#### 후방차분법
$$
\frac{\partial \, f}{\partial \, x_i} \approx \frac{f(x_1, \cdots, x_i, \cdots, x_n)-f(x_1, \cdots, \color{RoyalBlue}{x_i + \Delta x_i} , \cdots, x_n) }{\Delta x_i}
$$

#### 중앙차분법
$$
\frac{\partial \, f}{\partial \, x_i} \approx \frac{f(x_1, \cdots, \color{RoyalBlue}{x_i+\frac{1}{2}\Delta x_i}, \cdots, x_n)-f(x_1, \cdots, \color{RoyalBlue}{x_i - \frac{1}{2} \Delta x_i} , \cdots, x_n) }{\Delta x_i}
$$

#### 경사도 수치계산 실습

$$
f(x) = \frac{4x}{x^2 + 1}
$$

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

sympy.simplify(sympy.diff((4*x)/(x**2 + 1), x))
```

```python
plt.rcParams["figure.figsize"] = (15,7)

def f(x):
    """
    실습용 함수 정의
    """
    return 4*x / (x**2  + 1)

def df_anal(x):
    """
    sympy의 결과로 얻은 도함수
    """
    return (-4*x**2 + 4)  / (x**4 + 2*x**2 + 1)

def df_numer(x):
    """
    수치적으로 도함수의 값을 계산하는 함수
    중앙차분법으로 바꿔보세요.
    """
    h = 0.1
    return (f(x + h * 0.5) - f(x - 0.5 * h)) / h

x = np.linspace(-5, 5, 200)

plt.plot(x, f(x), lw=3, color=style_colors[0], label=r"$f(x) = \frac{4x}{(x^2  + 1)}$")
plt.plot(x, df_anal(x), lw=10, color=style_colors[1], alpha=0.3 , label=r"$\frac{df}{dx}$")
plt.plot(x, df_numer(x), color=style_colors[1], lw=3, label=r"Numerical derivative")
plt.legend(fontsize=20)

plt.suptitle("Numerical derivative", fontsize=20)
plt.show()
```

### 강하단계

- 어떤 방향이 바람직한 $\mathbf{d}^{(k)}$ 방향인가?

- 반복의 목적은 목적함수 $f(\mathbf{x})$의 최소점에 도달하는 것

- 알고리즘의 $k$번째 반복에서 $\mathbf{x}^{(k)}$가 최소점이 아니라면 더 작은 목적함수값을 가지는 다른점  $\mathbf{x}^{(k+1)}$가 있다.

$$
f\left(\mathbf{x}^{(k+1)}\right) < f\left(\mathbf{x}^{(k)}\right)
$$

- 위 식 좌변에 단계 5의 식을 대입($\mathbf{x}^{(k+1)} = \mathbf{x}^{(k)} + \alpha_k \mathbf{d}^{(k)}$)

$$
f\left(\mathbf{x}^{(k)} + \alpha_k \mathbf{d}^{(k)}\right) <  f\left(\mathbf{x}^{(k)}\right) \tag{*}
$$

- $ f\left(\mathbf{x}^{(k)} + \alpha_k \mathbf{d}^{(k)}\right)$는 $f\left(\mathbf{x}^{(k)}\right)$에서 $\alpha_k \mathbf{d}^{(k)}$만큼 변화한 것이므로 테일러 시리즈 전개

$$
f(\mathbf{x}^* + \mathbf{d}) = f(\mathbf{x}^*) +  \triangledown f^{\text{T}} \mathbf{d} + \frac{1}{2} \mathbf{d}^{\text{T}} \mathbf{H} \mathbf{d} + R
$$

- 위 식대로 맞춰서 전개하면 ( $ \mathbf{c}^{(k)}$는 경사도벡터)

$$
 f\left(\mathbf{x}^{(k)} + \alpha_k \mathbf{d}^{(k)}\right)= f\left(\mathbf{x}^{(k)} \right) + \mathbf{c}^{(k)} \cdot \left( \alpha_k \mathbf{d}^{(k)} \right) +R
$$

- 전개한 결과를 $(*)$에 대입

$$
\begin{align}
f\left(\mathbf{x}^{(k)} + \alpha_k \mathbf{d}^{(k)}\right)
&= f\left(\mathbf{x}^{(k)} \right) + \mathbf{c}^{(k)} \cdot \left( \alpha_k \mathbf{d}^{(k)} \right) +R \\[5pt]
&\approx  f\left(\mathbf{x}^{(k)} \right) + \mathbf{c}^{(k)} \cdot \left( \alpha_k \mathbf{d}^{(k)} \right) <  f\left(\mathbf{x}^{(k)}\right)
\end{align}
$$

- 따라서 최종적으로 다음과 같은 부등식을 얻는다.

$$
\begin{align}
\alpha_k \left( \mathbf{c}^{(k)} \cdot \mathbf{d}^{(k)} \right)  &< 0 \\[5pt]
\mathbf{c}^{(k)} \cdot \mathbf{d}^{(k)}  & < 0 \quad \because \alpha_k > 0
\end{align}
$$

- 위 부등식의 의미 : **$\mathbf{x}^{(k)}$를 $\mathbf{x}^{(k+1)}$로 개선시키기 위한 탐색방향 $ \mathbf{d}^{(k)}$는 경사도벡터 $\mathbf{c}^{(k)}$와 90°에서 270° 사이에 있어야 한다.**

- 위 부등식으로 표시되는 강하조건descent condition을 만족하는 방향을 강하방향descent direction

- 강하방향에 기반한 방법을 **강하법** 이라 한다.

### 일차원 최소화

#### 일변수 함수로 환원

- 설계 변화의 바람직한 방향 $\mathbf{d}^{(k)}$를 알 때 설게변수의 업데이트에 대해서 목적함수는

$$
f \left(\mathbf{x}^{(k+1)} \right) = f\left(\mathbf{x}^{(k)} + \alpha \mathbf{d}^{(k)}\right)
$$

- $\alpha$만의 함수로 목적함수가 업데이트

$$
f\left(\mathbf{x}^{(k+1)}\right) = f\left(\mathbf{x}^{(k)} + \alpha \mathbf{d}^{(k)}\right) \equiv \bar{f}(\alpha)
$$

- 즉, 현재 위치 $\mathbf{x}^{(k)}$에서 $\mathbf{d}^{(k)}$방향으로 양수 $\alpha$ 만큼 나아가면서 목적함수 $f$를 줄여나간다.

- 1변수 함수에 대한 필요조건 $\dfrac{d f}{d \alpha}=0$을 도입하면 정확하게(해석적으로) 얼마나 나아갈지 결정할 수 있다.

- $f$는 $\mathbf{x}^{(k+1)}$의 함수인데 $\mathbf{x}^{(k+1)}$은 다시 $\alpha$의 함수이므로 연쇄법칙을 적용

$$
\frac{d f\left(\mathbf{x}^{(k+1)}\right)}{d \alpha} = \frac{\partial f\left(\mathbf{x}^{(k+1)}\right)}{\partial \, \mathbf{x}} \frac{d \, \mathbf{x}^{(k+1)}}{d \alpha} = \triangledown f\left(\mathbf{x}^{(k+1)}\right) \cdot \mathbf{d}^{(k)} = 0
$$

- 따라서 강하방향으로의 이동거리는 아래 식처럼 경사도벡터와 강하방향의 내적이 0 다시말해 두 벡터가 수직이 될때까지 진행

$$
\mathbf{c}^{(k+1)} \cdot \mathbf{d}^{(k)}=0
$$

- 다음 그림은 일차원 최소화 과정을 나타낸 것이다.

```python
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.patches import ConnectionPatch

plt.rcParams["figure.figsize"] = (10, 7)

# 데이터 준비
x1 = x2 = np.arange(-1.0, 1.0, 0.05)
X1, X2 = np.meshgrid(x1, x2)
Z = (1/3)*(X1**2)*(np.exp(X2))-1

s = np.linspace(-0.5, 0.5, 100)
s2 = np.linspace(-0.5, 0.5, 10)
S = np.array([2*s, s])
S2 = np.array([2*s2, s2])
C = (1/3)*(S[0,:]**2)*(np.exp(S[1,:]))-1
C2 = (1/3)*(S2[0,:]**2)*(np.exp(S2[1,:]))-1

fig = plt.figure(figsize=(15,15))

ax1 = fig.add_subplot(121, projection='3d')
ax2 = fig.add_subplot(122)
ax1.set_aspect('equal')
ax2.set_aspect('equal')

#Basic contour plot
levels1 = np.linspace(-1, -0.95, 5)

CS = ax1.contour(X1, X2, Z, zdir='z', levels=np.concatenate((levels1, )),  offset=-1.0, cmap='viridis')
ax1.clabel(CS, CS.levels,   fmt='%1.0f', inline=True, fontsize=10)
ax1.plot_surface(X1, X2, Z,rstride=5, cstride=5, color=style_colors[6], alpha=0.3)
ax1.plot3D(S[0,:], S[1,:], C ,'--', lw=1, color=style_colors[1], alpha=0.8)
ax1.scatter3D(S2[0,1], S2[1,1], C2[1], s=20, alpha=1, color=style_colors[1])

CS = ax2.contour(X1, X2, Z,  zdir='z', levels=levels1, offset=-1.0,  cmap='viridis')
ax2.clabel(CS, CS.levels,  fmt='%0.001f', inline=True, fontsize=10)
ax2.plot(S[0,:], S[1,:], '--', lw=3, color=style_colors[1], alpha=0.8)
ax2.scatter(S2[0,1], S2[1,1],  s=70, alpha=1, color=style_colors[1])
ax2.scatter(0,0,  s=70, alpha=1, color=style_colors[1])
con = ConnectionPatch(xyA=(S2[0,1], S2[1,1]), xyB=(0,0), coordsA="data", coordsB="data", color=style_colors[3], lw=3,
                     arrowstyle="-|>", shrinkA=5, shrinkB=5,
                      mutation_scale=30, fc="w", zorder=2)
ax2.add_artist(con)

ax1.text(S2[0,1], S2[1,1], -0.8,r'$\bar{f}(\alpha=0)$', color=style_colors[1], fontsize=15)
ax2.text(S2[0,1]+0.2, S2[1,1]+0.15, r'$\mathbf{d}^{(k)}$', color=style_colors[1], fontsize=20)
ax2.text(S2[0,1], S2[1,1]-0.1, r'$\mathbf{x}^{(k)}$', color=style_colors[1], fontsize=20)

ax1.set_title("Line search in 3D")
ax2.set_title("Line search in 2D")

ax1.view_init(30,-60)

plt.show()
```

#### 등간격 탐색

- 가장 쉽게 생각할 수 있는 방법 : $f(\alpha=0)$에서 $\alpha$를 등간격 $\delta$만큼 증가시키면서 함수값 계산

- 현재 함수값이 증가하면 전, 전 스탭 함수값을 좌측값, 현재 함수값을 우측값으로 설정하고 $\delta$를 적당히 줄이고 다시 반복

#### 황금분할탐색

- 최소값이 있을 것 같은 구간을 황금비율로 잘라 구간을 계속 좁혀나가는 방법

- 좁혀진 구간을 다시 좁힐 때 이전에 계산해둔 값을 재사용할 수 있어 계산량을 줄임

- 황금비 $\tau= \left(\sqrt{5} -1 \right)/2 = 0.618$로 두고 탐색 구간 $I$ 내부의 두 점을 $\tau I = 0.618I$와 $(1-\tau)I = 0.382I$ 로 잡고 구간 줄이기

```python
def gss(a, b, x, d,  tol=1e-12):
    '''
    https://en.wikipedia.org/wiki/Golden-section_search
    golden section search
    to find the minimum of f on [a,b]
    f: a strictly unimodal function on [a,b]
    a, b : search interval
    x :  current design variables
    d : descent direction
    '''
    gr = (np.sqrt(5) + 1) / 2

    l = b - (b - a) / gr
    r = a + (b - a) / gr

    while abs(l - r) > tol:
        if f_alpha(l, x, d) < f_alpha(r, x, d):
            b = r
        else:
            a = l

        # we recompute both c and d here to avoid loss of precision
        # which may lead to incorrect results or infinite loop
        l = b - (b - a) / gr
        r = a + (b - a) / gr

    return (b + a) / 2
```

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

def func(x):
    """
    최소화 시킬 함수를 정의 한다. 아무 함수나 적당히 바꿔가면서 실험가능
    """
    return  x**3 - x**2 - 4*x + 4

def fa(x_star, alpha):
    x = x_star + alpha

    return  func(x)

def gss_for_demo(a, b, x, func, tol=1e-7):
    '''
    golden section search
    to find the minimum of f on [a,b]
    f: a strictly unimodal function on [a,b]
    a, b : search interval
    x :  current design variables
    '''
    gr = (np.sqrt(5) + 1) / 2

    l = b - (b - a) / gr
    r = a + (b - a) / gr

    hist = []

    while abs(l - r) > tol:
        hist.append((a, l, r, b))
        if func(x, l) < func(x, r):
            b = r
        else:
            a = l

        # we recompute both c and d here to avoid loss of precision
        # which may lead to incorrect results or infinite loop
        l = b - (b - a) / gr
        r = a + (b - a) / gr

    return hist, (b + a) / 2

# 1차원 탐색할 원함수의 범위를 지정한다.
x = np.linspace(-2, 3, 100)
# alpha=0에서 앞쪽 구간 얼마나 탐색할지 구간을 정해주는 상수, 적당히 세팅한다.
B = 3
#탐색을 시작할 스타팅 포인트를 지정한다.
x_star = -0.5

# 1차원 탐색 구간을 만들고
a = np.linspace(0, B, 100)
# 1차원 목적함수의 값을 계산한다.(그림 그리기용)
f = fa(x_star, a)

# 황분분할 탐색을 시도한다.
hist, mini = gss_for_demo(0, B,  x_star, fa)

# 원함수의 1차원 방향의 단면
plt.plot(x, func(x), color=style_colors[0], label='Contour in a descent direction')
plt.plot(x_star, func(x_star), 'o')

# 황금분할탐색에서 사용한 1차원 함수
plt.plot(a, f, color=style_colors[1], lw=3, label=r'$f(\alpha)$')

for i in range(6) :
    plt.plot(hist[i][0], 0+(1*i),  'o', color=style_colors[i])
    plt.plot(hist[i][1], 0+(1*i),  'o', color=style_colors[i])
    plt.plot(hist[i][2], 0+(1*i),  'o', color=style_colors[i])
    plt.plot(hist[i][3], 0+(1*i),  'o', color=style_colors[i])

plt.text(0., f[0], r'$f(\alpha=0)$', fontsize=15)
plt.legend(fontsize=15)
plt.axvline(x=0, color=style_colors[0])
plt.suptitle("Golden section search", fontsize=15)

plt.show()

print("minimum at alpha= {:E} , start at x={}".format(mini, x_star))
```

### 최속강하법steepest descent method

- 최속강하법 알고리즘[1]

    **Step 1**. Estimate a starting design as $\mathbf{x}^{(0)}$. Set the iteration counter $k=0$. Select the convergence parameter $\epsilon$.
    $$
    $$

    **Step 2**.  Calculate the gradient of $f(\mathbf{x})$ at the current point $\mathbf{x}^{(k)}$ as $\mathbf{c}^{(k)} = \triangledown f\left(\mathbf{x}^{(k)}\right )$.
    $$
    $$

    **Step 3**.  Calculate the length of $\mathbf{c}^{(k)} $ as $\lVert \mathbf{c}^{(k)} \rVert$. If $\lVert \mathbf{c}^{(k)} \rVert < \epsilon$ , then stop the iterative process because $\mathbf{x}^* = \mathbf{x}^{(k)}$ is a local minimum point. Otherwise, continue.
    $$
    $$

    **Step 4**.   Let the search direction at the current point $x^{(k)}$ be $\mathbf{d}^{(k)} = - \mathbf{c}^{(k)}$.
    $$
    $$

    **Step 5**. Calculate a step size $\alpha_k$ that minimizes $f(\alpha)=f\left(\mathbf{x}^{(k)}+\alpha \mathbf{d}^{(k)}\right)$ in the direction $\mathbf{d}^{(k)}$. Any one-dimensional search algorithm may be used to determine $\alpha_k$.
    $$
    $$

    **Step 6**. Change the design as follows: set $k=k+1$ and go to Step 2.
    $$
    \mathbf{x}^{(k+1)} =  \mathbf{x}^{(k)} + \alpha_k \mathbf{d}^{(k)}
    $$

https://en.wikipedia.org/wiki/Gradient_descent : public domain

### 공액경사도법conjugate gradient method

- 공액경사도법 알고리즘[1]

    **Step 1**. Estimate a starting design as $\mathbf{x}^{(0)}$. Set the iteration counter $k=0$. Select the convergence parameter $\epsilon$. Calculate
    $$
    \mathbf{d}^{(0)} = - \mathbf{c}^{(0)} = - \triangledown f \left( \mathbf{x}^{(0)} \right)
    $$

    **Step 2**. Compute the gradient of the cost function as
    $$
    \mathbf{c}^{(k)}=\triangledown f\left(\mathbf{x}^{(k)}\right)
    $$

    **Step 3**. Calculate $\lVert \mathbf{c}^{(k)} \rVert$. If  $\lVert \mathbf{c}^{(k)}  \rVert < \epsilon$, then stop; otherwise continue.
    $$
    $$

    **Step 4**.  Calculate the new conjugate direction as
    $$
    \mathbf{d}^{(k)} = - \mathbf{c}^{(k)} + \beta_{k} \mathbf{d}^{(k-1)} \\[5pt]
    \beta_k = \left( \frac{\lVert \mathbf{c}^{(k)} \rVert}{\lVert \mathbf{c}^{(k-1)} \rVert} \right)^2
    $$

    **Step 5**. Compute a step size $\alpha_k = \alpha$ to minimize $f(\alpha) = f\left(\mathbf{x}^{(k)}+\alpha\mathbf{d}^{(k)} \right)$.
    $$
    $$

    **Step 6**. Change the design as follows: set $k=k+1$ and go to Step 2.
    $$
    \mathbf{x}^{(k+1)} =  \mathbf{x}^{(k)} + \alpha_k \mathbf{d}^{(k)}
    $$

- 공액방향은 강하조건을 만족

- 강하조건에 $\mathbf{d}^{(k)}$를 대입

$$
\begin{align}
\mathbf{c}^{(k)} \cdot \mathbf{d}^{(k)} &= \mathbf{c}^{(k)} \cdot \left( - \mathbf{c}^{(k)} + \beta_{k} \mathbf{d}^{(k-1)} \right) \\[5pt]
&= - \underbrace{\mathbf{c}^{(k)} \cdot \mathbf{c}^{(k)}}_{\lvert \mathbf{c}^{(k)} \rvert^2}  +  \beta_{k} \underbrace{\mathbf{c}^{(k)} \cdot  \mathbf{d}^{(k-1)}}_{\text{step size cond.}\, =0} < 0  \\[5pt]
\end{align}
$$

https://en.wikipedia.org/wiki/Conjugate_gradient_method : public domain

### 수치해법 적용 실습

- 다음 함수들에 대해서 최속강하법, 공액경사도법을 적용해 해를 구해본다.

$$
\begin{align}
f_1(x_1, x_2) &= x_1^2 + x_2^2 \\[5pt]
f_2(x_1, x_2) &= 50x_1^2 + x_2^2 \\[5pt]
f_3(x_1, x_2) &= 50(x_2-x_1^2)^2 + (2-x_1)^2
\end{align}
$$

```python
def f_1(x) :
    """
    convex function
    """
    return   x[0]**2 + x[1]**2

def f_2(x):
    return   50*x[0]**2 + x[1]**2

def f_3(x) :
    """
    Rosenbrock function, example 11.8 from Jasbir Aroa
    """
    return 50*(x[1]-x[0]**2)**2 + (2-x[0])**2

F = [f_1, f_2, f_3]

OPTIMS = [(0,0), (0,0), (2,4)]
```

- 각 함수들의 그림

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

x = np.linspace(-6, 6, 200)
y = np.linspace(-6, 6, 200)

X,Y = np.meshgrid(x,y)

Zs = [ f_1([X,Y]),  f_2([X,Y]),  f_3([X,Y])]

# Basic contour plot
levels1 = np.linspace(8, 20, 1)
levels2 = np.linspace(30, 100, 2)
levels3 = np.linspace(200, 2500, 5)

fig = plt.figure()
ax_11 =  fig.add_subplot(231)
ax_12 =  fig.add_subplot(232)
ax_13 =  fig.add_subplot(233)
ax_21 =  fig.add_subplot(234, projection='3d')
ax_22 =  fig.add_subplot(235, projection='3d')
ax_23 =  fig.add_subplot(236, projection='3d')

CS = ax_11.contour(X, Y, Zs[0], levels=np.concatenate((levels1, levels2, levels3)), cmap='viridis')
ax_11.clabel(CS, CS.levels,  fmt='%1.0f', inline=True, fontsize=10)
ax_11.axis('equal')

CS = ax_12.contour(X, Y, Zs[1], levels=np.concatenate((levels1, levels2, levels3)), cmap='viridis')
ax_12.clabel(CS, CS.levels,  fmt='%1.0f', inline=True, fontsize=10)
ax_12.axis('equal')

CS =ax_13.contour(X, Y, Zs[2], levels=np.concatenate((levels1, levels2, levels3)), cmap='viridis')
ax_13.clabel(CS, CS.levels,  fmt='%1.0f', inline=True, fontsize=10)
ax_13.axis('equal')

ax_21.plot_surface(X,Y, Zs[0], rstride=10, cstride=10, cmap='viridis', alpha=0.5)
ax_22.plot_surface(X,Y, Zs[1], rstride=10, cstride=10,  cmap='viridis', alpha=0.5)

ax_23.plot_surface(X,Y, Zs[2], rstride=10, cstride=10,  cmap='viridis', alpha=0.5)
ax_23.view_init(35, -80)

plt.suptitle("Test functions", fontsize=15)

plt.show()
```

#### 코드 실습

- 아래 코드에 빈 부분을 채워서 완성해보세요.

- def f_alpha(alpha, x, d)를 완성하기 위한 수식

$$
 \bar{f}(\alpha) \equiv f\left(\mathbf{x}^{(k)} + \alpha \mathbf{d}^{(k)}\right)
$$

- def grad(x)를 완성하기 위한 수식

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

- 강하방향 계산 부분을 완성하기 위한 수식
    - 최속강하법

    $$\mathbf{d}^{(k)} = - \mathbf{c}^{(k)}$$

    - 공액경사법

     $$
    \mathbf{d}^{(k)} = - \mathbf{c}^{(k)} + \beta_{k} \mathbf{d}^{(k-1)} \\[5pt]
    \beta_k = \left( \frac{\lVert \mathbf{c}^{(k)} \rVert}{\lVert \mathbf{c}^{(k-1)} \rVert} \right)^2
    $$

- 업데이트 부분을 완성하기 위한 수식

$$
    \mathbf{x}^{(k+1)} =  \mathbf{x}^{(k)} + \alpha_k \mathbf{d}^{(k)}
    $$

```python
#######################################################
# 직접 실습을 하기 위한 코드 셀입니다.
# pass로 되어 있는 부부을 위 수식으로 채워넣으세요.
# 제대로 코딩된 버전은 바로 아래 코드셀에 있습니다.
#######################################################

#######################################################
# set current fuction and method
j = 1
CGM = False    # False:최속강하법, True:공액경사도법
#######################################################

f = F[j]
Z = Zs[j]

def f_alpha(alpha, x, d) :
    """
    This is a one-dimensional version of the error function
    given by the parameter alpha
    """
    #########################################################
    # 여기 코드를 적어 완성하세요.
    #########################################################
    #  x + alpha*d
    pass
    #########################################################
    # 여기까지 코드를 적어 완성하세요.
    #########################################################

    return f(x_new)

def grad(x):
    """
    This function computes the gradient of the objective function
    at the point x by finite difference.
    """
    h = 1.0e-6
    g = np.zeros(x.shape[0]).astype('longdouble')

    #########################################################
    # 여기 코드를 적어 완성하세요.
    #########################################################
    for i in range(x.shape[0]) :
        pass
    #########################################################
    # 여기까지 코드를 적어 완성하세요.
    #########################################################

    return g

#################################################################
# initialize
#################################################################
x = np.array([5.,-5.]).astype('longdouble')
B = 4.0             # 초기 황금분할탐색 인터벌
eps = 1.0e-6  # 반복 중단 기준
xs = [x, ]

################################################################
# information
#################################################################
print("INIT POINT : {}, dtype : {}".format(x, x.dtype))
print("METHOD     : {}".format("CGM" if CGM else "SDM"))
print("\n")

print("################################################################")
print("# START OPTIMIZATION")
print("################################################################")

for i in range(15000): # while True :
    # 현재 위치 x에서 경사도벡터 계산
    c = grad(x)

    # 정지조건 점검
    if np.linalg.norm(c) < eps :
        print('break')
        print( "iter:{:4d}, x:{}, cost:{}".format(i, x.T, f(x)) )
        break;

    #########################################################
    # 강하방향을 계산합니다. : 여기 코드를 적어 완성하세요.
    # 강하방향의 변수는 d로 하세요.
    #########################################################
    if i > 1 and CGM == True :
        pass
    else :
        pass
    #########################################################
    # 여기까지 코드를 적어 완성하세요.
    #########################################################

    # 1차원 선탐색
    alpha = gss(0, B, x, d) #황금분할탐색
    B = 2*alpha # 현재 스탭을 기준으로 다음 선탐색 구간을 설정

    # 현 단계의 코스트와 각종 변수들을 저장합니다.
    cost_old = f(x)
    x_old = x.copy()
    c_old = c.copy()
    d_old = d.copy()

    #########################################################
    # 설계변수를 업데이트 합니다. : 여기 코드를 적어 완성하세요.
    #########################################################
    pass
    #########################################################
    # 여기까지 코드를 적어 완성하세요.
    #########################################################

    # 새로운 코스트를 계산
    cost_new = f(x)

    # 목적함수가 증가하지 않았는지 체크
    if cost_new > cost_old :
        print("ERROR")
        print("Iter:{:+2d}, Cost_old:{:.7f}, Cost_new:{:.7f}".format(i, cost_old, cost_new))
        break;

    # 정보 출력
    if i % 50 == 0 :
        print( "Iter:{:04d}, x_1  :{:+.6f},  x_2 : {:+.6f}, |c| : {:#012.6f}, Cost:{:.14f}".format(i, x[0], x[1], np.linalg.norm(c), f(x)) )

    # 애니메이션 생성을 위한 정보 저장, 특정 거리보다 더 많이 전진했으면 저장
    if np.linalg.norm(x-x_old) > 1.0e-3 :
        xs.append(x)

x_hist = np.array(xs)
```

```python
#######################################################
# set current fuction and method
j = 2
CGM = True    # False:최속강하법, True:공액경사도법
#######################################################

f = F[j]
Z = Zs[j]

def f_alpha(alpha, x, d) :
    """
    This is a one-dimensional version of the error function
    given by the parameter alpha
    """
    #########################################################
    # 여기 코드를 적어 완성하세요.
    #########################################################
    x_new = x + alpha * d
    #########################################################
    # 여기까지 코드를 적어 완성하세요.
    #########################################################

    return f(x_new)

def grad(x):
    """
    This function computes the gradient of the objective function
    at the point x by finite difference.
    """
    h = 1.0e-6
    g = np.zeros(x.shape[0]).astype('longdouble')

    #########################################################
    # 여기 코드를 적어 완성하세요.
    #########################################################
    for i in range(x.shape[0]) :
        dx1 = x.copy()
        dx2 = x.copy()

        dx1[i] += h
        dx2[i] -= h

        g[i] = ( f(dx1) - f(dx2) ) / (2*h)
    #########################################################
    # 여기까지 코드를 적어 완성하세요.
    #########################################################

    return g

#################################################################
# initialize
#################################################################
x = np.array([5.,-5.]).astype('longdouble')
B = 4.0             # 초기 황금분할탐색 인터벌
eps = 1.0e-6  # 반복 중단 기준
xs = [x, ]

################################################################
# information
#################################################################
print("INIT POINT : {}, dtype : {}".format(x, x.dtype))
print("METHOD     : {}".format("CGM" if CGM else "SDM"))
print("\n")

print("################################################################")
print("# START OPTIMIZATION")
print("################################################################")

for i in range(15000): # while True :
    # 현재 위치 x에서 경사도벡터 계산
    c = grad(x)

    # 정지조건 점검
    if np.linalg.norm(c) < eps :
        print('break')
        print( "iter:{:4d}, x:{}, cost:{}".format(i, x.T, f(x)) )
        break;

    #########################################################
    # 강하방향을 계산합니다. : 여기 코드를 적어 완성하세요.
    # 강하방향의 변수는 d로 하세요.
    #########################################################
    if i > 1 and CGM == True :
        beta = (np.linalg.norm(c) / np.linalg.norm(c_old))**2
        d = -c + beta*d_old
    else :
        d = -c # for steepest descent method
    #########################################################
    # 여기까지 코드를 적어 완성하세요.
    #########################################################

    # 1차원 선탐색
    alpha = gss(0, B, x, d) #황금분할탐색
    B = 2*alpha # 현재 스탭을 기준으로 다음 선탐색 구간을 설정

    # 현 단계의 코스트와 각종 변수들을 저장합니다.
    cost_old = f(x)
    x_old = x.copy()
    c_old = c.copy()
    d_old = d.copy()

    #########################################################
    # 설계변수를 업데이트 합니다. : 여기 코드를 적어 완성하세요.
    #########################################################
    x = x + alpha * d
    #########################################################
    # 여기까지 코드를 적어 완성하세요.
    #########################################################

    # 새로운 코스트를 계산
    cost_new = f(x)

    # 목적함수가 증가하지 않았는지 체크
    if 0 : #if cost_new > cost_old :
        print("ERROR")
        print("Iter:{:+2d}, Cost_old:{:.7f}, Cost_new:{:.7f}".format(i, cost_old, cost_new))
        break;

    # 정보 출력
    if i % 50 == 0 :
        print( "Iter:{:04d}, x_1  :{:+.6f},  x_2 : {:+.6f}, |c| : {:#012.6f}, Cost:{:.14f}".format(i, x[0], x[1], np.linalg.norm(c), f(x)) )

    # 애니메이션 생성을 위한 정보 저장, 특정 거리보다 더 많이 전진했으면 저장
    if np.linalg.norm(x-x_old) > 1.0e-3 :
        xs.append(x)

x_hist = np.array(xs)
```

```python
#######################################################
# 위 코드 셀에서 수행한 최적화 과정을 그리는 코드
# 실행하면 설계변수의 업데이트 내역이 그려짐
#######################################################

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

CS = plt.contour(X, Y, Z, levels=np.concatenate((levels1, levels2, levels3)), cmap='viridis')
plt.clabel(CS, CS.levels,  fmt='%1.0f', inline=True, fontsize=10)
plt.plot(*OPTIMS[j], 'o', color=style_colors[3])
plt.plot(x_hist[:,0], x_hist[:,1], 'o-', color=style_colors[1], alpha=0.3)
plt.axis('equal')
plt.show()
```

```python
#######################################################
# 설계변수의 업데이트 내역을 애니메이션으로 그림
# azure notebook에서는 에러나기 때문에 로컬 환경에 jupyter를 구성해야함
#######################################################

from matplotlib import animation, rc
from IPython.display import HTML

fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], 'o', color=style_colors[1], alpha=0.5, animated=True)
plt.plot(*OPTIMS[j], 'o', color=style_colors[3])
CS = plt.contour(X, Y, Z, levels=np.concatenate((levels1, levels2, levels3)), cmap='viridis')
plt.clabel(CS, CS.levels,  fmt='%1.0f', inline=True, fontsize=10)

def init():
    ax.set_xlim(-6, 6)
    ax.set_ylim(-6, 6)
    return ln,

def update(frame):
    if len(xdata) > 0 :
        xdata[0] = x_hist[frame,0]
    else :
        xdata.append(x_hist[frame,0])

    if len(ydata) > 0 :
        ydata[0] = x_hist[frame,1]
    else :
        ydata.append(x_hist[frame,1])

    ln.set_data(xdata, ydata)
    return ln,

ani = animation.FuncAnimation(fig, update, frames=np.arange(x_hist.shape[0]), interval=60,
                    init_func=init, blit=True)

# equivalent to rcParams['animation.html'] = 'html5'
rc('animation', html='html5')
HTML(ani.to_html5_video())
```

### 전역적 최적성

#### 볼록집합convex set

- 어떤 집합 $S$내의 임의의 점 $P_1$, $P_2$를 잇는 선분 전체가 $S$내에 있다면 집합 $S$는 볼록집합

- 일반적으로 두점으로 정의되는 선분

$$
\mathbf{x} = \alpha \mathbf{x}^{(2)} + (1-\alpha)\mathbf{x}^{(1)}
$$

- 위 선분 전체가 집합 $S$에 속하면 볼록집합

#### 볼록함수convex function

- 볼록집합 $S$상에 정의된 $n$변수의 함수 $f(\mathbf{x})$는 집합 $S$내의 모든 점에서 함수의 헤시안이 양정 또는 양반정이면 볼록함수

- 1차원 문제에서는 2계 도함수(2번 미분)가 음이 아님

#### 볼록계획문제convex programming problem

- 볼록집합에서 정의된 목적함수가 볼록함수 $\implies$ 볼록계획문제

- 볼록계획문제에서는 지역최소는 곧 전역최소

### 1. 함수표면의 접면에 수직

- 여기서 접면은 개념적 표현

- 2변수 실함수라면 접선이되고, 3변수 실함수라면 접면, 4변수 이상 실함수라면 초 접평면hyper tangent plane이 된다.

-  아래 그림은 3변수 음함수를 나타낸것

#### [증명]

- 위 그림처럼 $f(\mathbf{x})= \text{constant}$인 곡면 위를 지나고 $\mathbf{x}^*$를 통과하는 임의의 곡선 $\mathbf{C}$에 대해서

- 곡선 $\mathbf{C}$가 $s$에 대해서 매개변수화되었다 하자.

- 즉 곡선 $\mathbf{C} = [\, f_1(s) \quad f_2(s) \quad f_3(s)\, ]^{\text{T}}$인 "일변수 벡터함수"이다.

- 이렇게 정의된 곡선 $\mathbf{C}$의 $\mathbf{x}^*$에서 접선벡터는 각 성분을 $s$에 대해 미분한 것이므로

$$
\mathbf{T} = \begin{bmatrix} \dfrac{\partial \, x_1}{\partial \, s} & \dfrac{\partial \, x_2}{\partial \, s} & \cdots & \dfrac{\partial \, x_n}{\partial \, s} \end{bmatrix}^{\text{T}}
$$

- 이제 매개변수 $s$가 미세하게 변할 때 곡면 함수 $f(\mathbf{x})$의 변화를 생각하자.

- 즉, 곡면 함수 $f(\mathbf{x})$를 $s$로 미분하는 것이다.

- 그런데 $s$가 변해감에 따라 곡면 함수 $f(\mathbf{x})$의 값은 변하지 않고 $\text{constant}$로 일정하므로

$$
\frac{\partial f}{\partial s} = 0
$$

- 미분의 연쇄법칙을 적용하면

$$
\frac{\partial f}{\partial s} = \color{RoyalBlue}{\frac{\partial f}{\partial x_1}}\color{OrangeRed}{\frac{\partial x_1}{\partial s}} + \color{RoyalBlue}{\frac{\partial f}{\partial x_2}}\color{OrangeRed}{\frac{\partial x_2}{\partial s}} + \cdots + \color{RoyalBlue}{\frac{\partial f}{\partial x_n}}\color{OrangeRed}{\frac{\partial x_n}{\partial s}} =0
$$

- 위 식을 파란색 부분과 주황색 부분을 따로 모아 벡터형식으로 쓰면

$$
\frac{\partial f}{\partial s} = \begin{bmatrix}\color{RoyalBlue}{\dfrac{\partial f}{\partial x_1}} & \color{RoyalBlue}{\dfrac{\partial f}{\partial x_2}} & \cdots &  \color{RoyalBlue}{\dfrac{\partial f}{\partial x_n}} \end{bmatrix}^{\text{T}} \cdot \begin{bmatrix} \color{OrangeRed}{\dfrac{\partial x_1}{\partial s}} & \color{OrangeRed}{\dfrac{\partial x_2}{\partial s}} & \cdots & \color{OrangeRed}{\dfrac{\partial x_n}{\partial s}}\end{bmatrix} =  0
$$

- 결국 파란부분은 경사도벡터, 주황색 부분은 접선벡터 $\mathbf{T}$가 되고

$$
\color{RoyalBlue}{\mathbf{c}} \cdot \color{OrangeRed}{ \mathbf{T} }= 0
$$

- 경사도벡터 $\mathbf{c}$는 접선과의 내적이 0이므로 접선에 수직이 된다.

### 2. 경사도벡터의 방향은 함수의 최대증가방향

#### [증명]

- 이번에는 $\mathbf{x}^*$에서 접선 방향이 아닌  방향으로의 단위벡터를 $\mathbf{u}$라 하자.

- $\mathbf{x}^*$에서 $\mathbf{u}$방향으로의 변화는 $\mathbf{x}^* + t \, \mathbf{u}$로 표시 가능

- 이제 점 $\mathbf{x}^*$에서 $\mathbf{u}$방향으로 $t$에 대한 미분을 생각해 볼 수 있는데

- 이러한 미분을 방향미분이라 한다. 미분의 정의대로 쓰면

$$
\triangledown_{\mathbf{u}} f = \lim_{t \to 0} \frac{f(\mathbf{x}^* + t \, \mathbf{u}) - f(\mathbf{x^*})}{t} \tag{*}
$$

- 분자의 $f(\mathbf{x}^* + t \, \mathbf{u})$을 테일러 시리즈 전개하면

$$
f( \mathbf{x}^* + t \mathbf{u}) = f(\mathbf{x}^*) + t \left[ u_1 \frac{\partial f}{\partial x_1} + u_2 \frac{\partial f}{\partial x_2} + \cdots + u_n \frac{\partial f}{\partial x_n} \right] + O(t^2)
$$

- 시그마 기호를 도입하고 양변에 $f(\mathbf{x}^*)$를 빼면

$$
f( \mathbf{x}^* + t \mathbf{u} ) - f(\mathbf{x}^*) = t \sum_{i=1}^n u_i \frac{\partial f}{\partial x_i}+ O(t^2)
$$

- 위 결과를 $(*)$ 대입하면

$$
\begin{align}
\triangledown_{\mathbf{u}} f &=  \lim_{t \to 0} \frac{ t \sum_{i=1}^n u_i \frac{\partial f}{\partial x_i}+ O(t^2)}{t} \\[5pt]
&= \sum_{i=1}^n u_i \frac{\partial f}{\partial x_i} =  \mathbf{c} \cdot \mathbf{u}
\end{align}
$$

- 즉, 함수 $f(\mathbf{x})$의 $\mathbf{u}$방향으로의 변화율은 경사도벡터 $\mathbf{c}$와 단위벡터 $\mathbf{u}$의 내적

- 내적의 정의에 의해

$$
 \mathbf{c} \cdot \mathbf{u} = \lvert  \mathbf{c} \rvert \lvert \mathbf{u} \rvert \cos \theta
$$

- 위 변화율이 최대가 되려면 $\theta$가 0° 또는 180°

- 따라서 경사도벡터 방향으로 변화할때 변화율이 가장 크다. 그 변화율은 경사도벡터의 크기이다. ($\because \lvert \mathbf{u} \rvert = 1, \quad \cos \theta = 1$)

## 참고문헌

1.  최적설계입문Introduction to Optimum Design, Jasbir S. Arora

2. http://jjycjnmath.tistory.com/32?category=738760

```python
%%html
<link href='https://fonts.googleapis.com/earlyaccess/nanummyeongjo.css' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/earlyaccess/nanumgothiccoding.css' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/earlyaccess/notosanskr.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;   }
    table { font-family: 'Noto Sans KR' !important;  font-size: 11pt !important; }
    p  { font-family: 'Georgia','Nanum Myeongjo'; font-size: 12pt; line-height: 200%;  text-indent: 10px; }
    li { font-family: 'Georgia','Nanum Myeongjo';font-size: 12pt; line-height: 200%; }
    li > p { text-indent: 0px; }
    /* blockquote > p { font-family: 'Georgia','Nanum Myeongjo' !important; font-size: 12pt !important; } */
    sup { font-family: 'Georgia'; }
    code, pre { font-family: 'Nanum Gothic Coding' !important; font-size: 13pt !important;line-height:130% !important; }
    .code-body { font-family: 'Nanum Gothic Coding'; }
    .ns { font-family: 'Noto Sans KR'; font-size: 15pt;}
    .fig-title { font-family: 'Noto Sans KR'; font-size: 10pt;}
    .summary {font-family: 'Noto Sans KR'; /*font-size: 12pt;*/ line-height: 200%;
              border-left:3px solid #FF0000;padding-left:20px;margin-top:10px; }
</style>
```

<link href='https://fonts.googleapis.com/earlyaccess/nanummyeongjo.css' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/earlyaccess/nanumgothiccoding.css' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/earlyaccess/notosanskr.css' rel='stylesheet' type='text/css'>


# MODEL PROJECT

## 1. Introduction

We start in *section.3* with a classical intertemporal optimization problem in two periods. Our representative consumer exhibits a CRRA utility function and we want to detect his optimizing behaviour given underlying parameters. In *section.4*, instead, we focus on the optimizing behaviour of our representative firm. Its production function satisfies the Inada condition and  exhibits positive but diminishing marginal products. We come up with the optimal conditions to satisfy profit maximization.
In *section.5*, we add complexity to the model making our representative consumer and firm interact under the features of the classical Ramsey model. Here, we rely on the book *Robert J.Barro and Xavier Sala-i-Martin, Economic Growth* for the main results. We find the steady state for the given parameters and we come up with the generical phase diagram showing the dinamics of capital and consumption. Finally we plot sample paths for different initial value of consumption trying to detect the unique saddle path.

## 2. Import packages

```python
import numpy as np
import scipy as sp
import math
from scipy import optimize
import sympy as sm
from scipy import linalg
from sympy import Derivative
from scipy import interpolate
from sympy import symbol,function
import ipywidgets as widgets
from IPython.display import display

#autoreload model when code is run
%load_ext autoreload
%autoreload 2

%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
from matplotlib import cm
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
```

## 3. Consumer optimization problem

Before moving to the Ramsey model, we focus on our representative consumer and his optimization problem. The standard Ramsey model assumes that the household exhibits a CRRA utility function with respect to consumption of the type:

$$u(c)=\frac{c^{1-\theta}-1}{1-\theta}$$

where $\theta$ is the measure of relative risk aversion.
This type of preferences is really useful when $\theta=1$ because in this case the CRRA utility becomes a logarithmic utility.
We create a simple utility maximization problem in two periods ($t=0$ and $t=1$) to detect how these preferences impact the consumer optimal behavior.
Our representative consumer maximizes consumption in the present $c_0$ and in the future $c_1$ taking into consideration his labour income is $w_0$ today and $w_1$ tomorrow. So, for our consumer, the utility maximization problem becomes:

$$max \hspace{2pt} U(c_0,c_1) = u(c_0)+e^{-\rho}u(c_1)$$

under the constraint:

$$c_0+\frac{1}{1+r}{c_1} = w_0 +\frac{1}{1+r}{w_1}$$

where $\rho$ is the subjective discount rate and $r$ the interest rate.

Now, before solving the problem, we assume some initial values for the parameters and we code the utility function and constraint.

```python
#a. Defining initial values for the various parameters
theta = 0.5
w_t= 10      #present labour income
w_t1 = 10     #future labour income
rho = 0.1
r = 0.5

#a. Defining utility function
def utility(ct, theta):
    if theta != 1:
        return (ct**(1-theta)-1)/(1-theta)
    else:
        return np.log(c_t)

#b. Defining utility function for two periods
def inter_util(c_t, c_t1, rho, theta):
    return utility(c_t, theta)+np.exp(-rho)*utility(c_t1, theta)

#c. Defining constraint
def constraint(c_t, c_t1, w_t, w_t1, r):
    return w_t+(1/(1+r))*w_t1-c_t-(1/(1+r))*c_t1
```

To solve the maximization problem we firstly set the lagrangian:

$$L=\max_{c_{0},c_{2}}\bigg(\frac{c_{0}^{1 - \theta} - 1}{1 - \theta}\bigg) + e^{-\rho}\bigg(\frac{c_{1}^{1 - \theta} - 1}{1 - \theta}\bigg) + \lambda\bigg(w_{0} + \frac{1}{1 + r}w_{1} - c_{0} - \frac{1}{1 + r}c_{1}\bigg)$$

and we compute the first order conditions:

$$c_{0}^{-\theta} = \lambda$$
$$e^{-\rho}c_{1}^{-\theta}=\frac{1}{1 + r}\lambda$$

The above reduces to:

$$c_{0}^{-\theta} = e^{-\rho}(1 + r)c_{1}^{-\theta}$$

We transpose the above into code and we solve the optimization problem.

```python
#d. Defining Euler equation
def euler(c_t, c_t1, rho, theta, r):
    return c_t**(-theta)-np.exp(-rho)*(1+r)*(c_t1)**(-theta)

#e. Creating optimization function
def optimalchoice(x, rho, theta, r, w_t, w_t1):
    op = [euler(x[0], x[1], rho, theta, r)]
    op.append(constraint(x[0],x[1], w_t, w_t1, r))
    return op

#f. Applying fsolve function
ct_star, ct1_star = optimize.fsolve(optimalchoice, args=(rho, theta, r, w_t, w_t1), x0=(0.5, 0.5))
utility_star = inter_util(ct_star, ct1_star, rho, theta)

#g. Printing results
print(f'The optimal consumption at the present is: {ct_star:.2f}')
print(f'The optimal consumption tomorrow is: {ct1_star:.2f}')
print(f'Utility from optimal bundle is: {utility_star:.2f}')
```

    The optimal consumption at the present is: 7.48
    The optimal consumption tomorrow is: 13.78
    Utility from optimal bundle is: 8.38

With these particular parameters' values, our household decides to consume more in the future (13.78) than in the present (7.48).
We know construct an interactive graph to detect his optimizing bundle for changes in the various parameters.

```python
def plot_max_problem(w_t, w_t1, theta, rho, r):

    fig1 = plt.figure(figsize=(10,10))
    ax = fig1.add_subplot(1,1,1)

    # Applying fsolve function
    ctstar, ct1star = optimize.fsolve(optimalchoice, args = (rho, theta, r, w_t, w_t1), x0=(0.5, 0.5))
    utility_star = inter_util(ct_star, ct1_star, rho ,theta)

    # Plot the budget constraint
    c_t = np.linspace(0, 30, 100)
    ax.plot(c_t, (1 + r) * (w_t - c_t) + w_t1, color='k')

    # Plot the optimal bundle and converging dashed lines
    ax.hlines(ct1star, 0, ctstar, linestyle='dashed')
    ax.vlines(ctstar, 0, ct1star, linestyle='dashed')
    ax.plot(ctstar, ct1star, marker='o', markersize = 7, color ='red')

    # Add optimal values for c_t and c_t1
    ax.text(0, ct1star, "{:.1f}".format(ct1star), color="red", ha="right", va="center")
    ax.text(ctstar, 0, "{:.1f}".format(ctstar), rotation = 'vertical', color="red", ha='center', va = 'top')

    # Add, axis limits, labels, title
    ax.set_xlim(0, 30)
    ax.set_ylim(0, 30)
    ax.set_xlabel(r'Present Consumption, $C_{t}$', fontsize=14)
    ax.set_ylabel(r'Future Consumption, $C_{t+1}$', fontsize=14)
    ax.set_title(r'Optimal Bundle For CRRA Utility', fontsize=20, pad=15)

    plt.show()

def plot_time():
    widgets.interact(plot_max_problem,
    w_t= widgets.IntSlider(
           description='$w_{t}$',
           min=1,
           max=10,
           step=1,
           value=7,
           continuous_update=False,
    ),

    w_t1 = widgets.IntSlider(
            description="$w_{t+1}$",
            min=1,
            max=10,
            step=1,
            value=7,
            continuous_update=False,
    ),
    theta = widgets.FloatSlider(
            description="$\\theta$",
            min=0.5,
            max=0.9,
            step=0.02,
            value=0.5,
            continuous_update=False,
    ),
    rho = widgets.FloatSlider(
            description="$\\rho$",
            min=0.1,
            max=0.99,
            step=0.05,
            value=0.1,
            continuous_update=False,
    ),
    r = widgets.FloatSlider(
            description="$r$",
            min=0.1,
            max=0.5,
            step=0.1,
            value=0.5,
            continuous_update=False,
    ),
);
```

```python
plot_time()
```

    interactive(children=(IntSlider(value=7, continuous_update=False, description='$w_{t}$', max=10, min=1), IntSl…

We can know study the behaviour of our consumer more in details. All the following changes are ceteris paribus, that is, when we change one particular parameter, the others remain fixed.
First of all, if the measure of risk aversion ($\theta$) increases, then our household will increase present consumption and lower future one. The same happens for an increase in the rate of subjective discount ($\rho$).
Theoretically, if the interest rate ($r$) increases, then present consumption becomes more expensive in comparison to future consumption and we expect consumers to decrease $c_t$ for $c_{t+1}$ (substitution effect). This is actually what happens in the case of our representative household. Futhermore A strong income effect seems not to be present for every value of $r$.
An increase only in the future wage ($w_t$), present wage ($w_{t+1})$ or both leads to some increase in both present and future consumption as the budget constraint move rightwards and our consumer splits the additional income in some way among present and future. The way this more income is divided between the two periods depends on the initial wages as well as the values of the other parameters.
In conclusion it is worth to underline that in the case the subjective rate of discount ($\rho$) equals ($r$), the interest rate, then our consumer with CRRA utility function exactly divides overall income equally between present and future consumption.

## 4. Firm optimization problem

In the Ramsey model the representative firm has a production function that satisfies the neoclassical properties of constant return to scale in $K$ (capital) and $L$ (Labour) and each input exhibits positive but diminishing marginal product.
Moreover, the problem of maximizing the present value of profit reduces to
a problem of maximizing profit in each period without regard to the outcomes in other periods.
The firm profit at any point in time is:

$$\pi = F(K,L) - wL - (r+\delta)K$$

where $F(K,L)$ is the production function, $w$ is the wage rate, $r$ is the interest rate and $0 \leq \delta \leq 1$ the discount rate.
In the Ramsey model, to obtain constant growth in the steady-state, we have to deal with quantities per unit of effective labor and so we will do, assuming that the technology will be $T=1$. From now on, for simplicity, we assume that technology will increase at the rate $x=0$. The production function will be rewritten in intensive form:

$$\hat{y}=f(\hat{k})$$

where $\hat{y}=\frac{Y}{LT}$ and $\hat{k}=\frac{K}{LT}$. The Profit function becomes:

$$\pi= f(\hat{k}) - w - (r+\delta)\hat{k}$$

Specifically, we are going to assume the following production function:

$$\hat{y}=f(\hat{k})=\hat{k}^{\alpha}$$

where $0 \leq \alpha \leq 1$ is the capital share of output and $\hat{k}$ is capital per effective labour.
We are going to check that the marginal product of capital is diminishing ($f''(\hat{k})< 0$), that the Inada conditions are satisfied and only afterwards we will calculate the profit maximization conditions.

```python
#a. Creating parameters
sm.init_printing(use_unicode=True) # for pretty printing
alpha = sm.symbols('alpha')        # capital share

k = sm.symbols('k')                # capital per effective worker

w = sm.symbols('w')                # wage

l = sm.symbols('L')                # labour per effective worker

delta = sm.symbols('delta')        # capital depreciation rate
r = sm.symbols('r')                # interest rate
```

```python
#b. Defining production function
prod = k**alpha
print(f'The production function is:')
display(prod)

#c. Defining profit function
profit = prod - (r+delta)*k - w
print(f'The profit function is:')
display(profit)

#d. Calculating first and second derivatives with respect to k
print(f'The first derivative with respect to k is:')
sol = sm.diff(prod,k)
display(sol)
print(f'The second derivative with respect to k is:')
sol1 = sm.diff(prod,k, k)
display(sol1)

#e. Check Inada conditions
print(f'First Inada condition is:')
inada1 = sm.Limit(sol,k,0)
inada_ = sm.Eq(inada1,sm.oo)
display(inada_)
print(f'Second Inada condition is:')
inada2 = sm.Limit(sol,k, sm.oo,0)
inada_2 = sm.Eq(inada2, 0)
display(inada_2)
```

Clearly, the first derivative is positive and the second derivative is negative confirming decreasing marginal product of capital per effective labour. Even if it should be already clear that the Inada conditions are satisfied, we are going to plot the production function for different values of alpha to check that it is actually concave as concavity satisfies Inada conditions.

```python
#f. Setting different parameters vaues
kvec = np.linspace(0,10,100)
alpha = 0.3
alpha1 = 0.5
alpha2 = 0.7
y = kvec**alpha
y1 = kvec**alpha1
y2 = kvec**alpha2

#g. Plotting the functions
prod_im = plt.figure(figsize = (9,6))
ax = prod_im.add_subplot(1, 1, 1)
ax.plot(kvec,y, 'red')
ax.plot(kvec,y1, 'blue')
ax.plot(kvec,y2, 'green')
ax.set_title('Production Function For Different Alpha Values', fontsize=15, pad=10)
ax.set_ylabel('$\hat{y}$', fontsize=12)
ax.set_xlabel('$\hat{k}$', fontsize=12)
ax.set_xlim(0,10)
ax.set_ylim(0,5)

plt.show()
```

So our production function is confirmed to be concave. Moreover, we can notice that higher levels of alpha lead to higher outcome per effective worker.
We now move to the profit maximization conditions.

```python
#h. Profit maximization with respect to capital per effective worker
print(f'Optimazing the profit with respect to k, we obtain:')
profitk = sm.Eq(sm.diff(profit,k))
display(profitk)
print(f'And the optimal k is:')
profitk1 = sm.solve(profitk,k)
display(profitk1)

#i. Profit maximization with respect to labour per effective worker
print(f'Optimazing the profit with respect to l, we obtain the optimal condition that the wage as to equal:')
profitl = (prod - k - sol)
profitleq = sm.Eq(profitl, w)
display(profitleq)
```

## 5. Ramsey Model

We now make our representative consumer and firm interacting under the classical framework of the Ramsey model. Our optimizing household of *section.3* mantains the same CRRA utility function with respect to consumption but now he maximizes over an infinite horizon taking into account the welfare and resources of his prospective descendants. He expects the size of his future family to grow at the rate $n$, given as exogenous and constant. We normalize the size of his family at time $t= \small{0}$ to 1, so that at the generic time $t$, the size of the family will be $L(t)=e^{nt}$. The utility maximization problem become:

$$U= \int_{0}^{\infty} e^{-(\rho-n)t}\left[\frac{c^{1-\theta}-1}{1-\theta}\right]dt$$

Futhermore the household holds assets ($a(t)$) which pay rate of return $r(t)$ and he supplies inelastically one unit of labor services for the wage rate $w(t)$. We assume a closed economy so that no assets can be traded internationally and we assume a competitive market so that the interest rate, $r(t)$, and the wage rate $w(t)$ are assumed as given.
The total income received is the sum of labor income, $w(t)·L(t)$, and asset income, $r(t)·(Assets)$ and the income that it is not consumed is used to accumulate more assets:

$$\frac{d(Assets)}{dt} = r(Assets)+wL-C$$

In per-capita terms, the household budget constraint becomes:

$$\dot{\alpha} = (r-n)\alpha+w-c$$

We assume that the credit market imposes a constraint on the amount of borrowing to rule out chain-letter possibilities (No Ponzi game condition). The appropriate restriction turns out to be that the present value of assets must be asymptotically non-negative:

$$\lim_{t \to \infty} \bigg\{ \alpha(t)*exp\left[-\int_{0}^{t}[r(v)-n]dv \right] \bigg\} \geq 0$$

Our representative firm maintains the same characteristics depicted in *Section.4*. As before, initial technology is normalized to 1  and for simplicity we assume that it grows at rate $x=0$.
One unit of output can be used to generate one unit of household consumption or one unit of additional capital. Therefore, as long as the economy is not at a corner solution, in which all current output goes into consumption or capital, the price of capital in terms of consumption will be fixed at unity.

We briefly derive the fundamental equations of the model (for complete derivations have a look in *Barro & Martin, Chapter 2* and relative appendix).
Since the economy is closed, assets per adult person, $\hat{a}$, equal capital per worker, $\hat{k}$. Given the consumer budget constraint, the optimizing conditions for $r$ and $w$ found in *Section.4* and $a=k$, we obtain the resource constraint for the economy which determines the evolution of capital per effective labour:

$$\dot{\hat{k}} = \hat{k}^{\alpha} - \hat{c} -(x+n+\delta)\hat{k} \hspace{7pt}(1)$$

The differential equation that determines the evolution of the consumption per effective labour, obtained from the household optimization problem, is instead:

$$\frac{\dot{\hat{c}}}{\hat{c}}=\frac{\hat{c}}{c}-x=\frac{1}{\theta} \big[\alpha \hat{k}^{\alpha-1} -\delta -\rho-\theta x \big] \hspace{7pt}(2)$$

The transversality condition becomes:

$$\lim_{t \to \infty} \bigg\{ \hat{k}*exp\left[-\int_{0}^{t}[\alpha \hat{k}^{\alpha-1} -\delta - n - x]dv \right] \bigg\} = 0$$

The $\dot{k}=0$ locus is determined by equating (1) to 0:

$$\hat{c}= \hat{k}^{\alpha}-(x+n+\delta)\hat{k}$$

Equation (2) implies instead that the $\dot{c}=0$ locus is satisfied when:

$$\alpha \hat{k}^{\alpha-1}=\delta +\rho+\theta x$$

The steady state values for $\hat{c}$ and $\hat{k}$ are determined by setting the expression (1) and (2) to zero. Moreover, the determination of the steady state occurs at the intersection of the vertical line for $\dot{\hat{c}}=0$ and the solid curve for $\dot{\hat{k}}=0$. The two equations for the steady state quantities are:

$$ \hat{k}^{*}=\big(\frac{\delta+\rho+ x\theta}{\alpha}\big)^{\frac{1}{\alpha-1}}$$

$$ \hat{c}^{*}=(k^{*})^{\alpha}-(n+\delta+x)k^{*}$$

and steady-state value for the production per effective labour becomes $\hat{y}^{*}=(\hat{k}^{*})^{\alpha}$.
After some calculations, the transversality condition implies that $\rho>n+(1-\theta)x$. If $\rho$ doesn't satisfy this condition, the household's optimization problem is not well posed and infinite utility can be attained if consumption grows at the rate $x$.
The capital equation for $k_{t+1}$ and the euler equation $\hat{c_{t+1}}$ are obtained from differentiating (1) and (2) respectively:

$$\hat{k}_{t+1}= \big[\hat{k}^{\alpha}+\hat{k}e^{-\delta}-\hat{c}\big]e^{(-(x+n))}$$
$$\hat{c}_{t+1}= e^{-x}\big[e^{-rho}(e^{-\delta}+\alpha \hat{k}_{t+1}^{\alpha-1}\big]^{\frac{1}{\theta}}\hat{c}_t$$

We now start to code the main equation for the model and we find the steady-state values for consumption, capital and production for our given values of $\theta$, $\rho$, $n$, $\delta$, $\alpha$ and $x$.

```python
#a. Parameter values
rho = 0.015          # subjective discount rate
theta = 1.7          # measure of relative risk aversion
x = 0                # growth rate of technology
alpha = 0.3          # capital share of output
n = 0.003            # growth rate of population
delta = 0.03         # discount rate of capital

#b. Transversality condition must be satisfied
print(f'Is the transversality condition satisfied?')
def transversality():
    if  rho > n + (1 - theta)*x:
        print(f'Yes, it is satisfied.')
    else:
        print(f'No, it is not satisfied.')

transversality()
```

    Is the transversality condition satisfied?
    Yes, it is satisfied.

```python
#c. Defining production function
def prod(k_t):
    return k_t**alpha

#d. Defining the consumption euler equation obtained from 1
def cons_t1(c_t, k_t):
    return np.exp(-x) * (np.exp(-rho) * (np.exp(-delta) + alpha * cap_t1(k_t, c_t)**(alpha - 1)))**(1 / theta) * c_t

#e. Defining the capital equation obtain from 2
def cap_t1(k_t,c_t):
    return (prod(k_t) + np.exp(-delta) * k_t - c_t) * np.exp(-(x + n))

```

```python
#f. Steady state capital
def k_star():
    return ((delta + rho + theta*x)/alpha)**(1 / (alpha-1))

#g. Steady state consumption
def c_star():
    return prod(k_star())-(n+delta+x)* k_star()

#h. Steady state production
def y_star():
    return (k_star())**alpha

#i. Values of steady state capital and consumption for our chosen variables
print(f'The steady state capital per effective labour is: {k_star():.1f}')
print(f'The steady state consumption per effective labour is: {c_star():.1f}')
print(f'The steady state production per effective labour is: {y_star():.1f}')
```

    The steady state capital per effective labour is: 15.0
    The steady state consumption per effective labour is: 1.8
    The steady state production per effective labour is: 2.3

So, for the particular values we gave to the parameters, the steady state capital per effective labour is 15.2, consumption 1.8 and production 2.3.

### 5.2 Generical Phase Diagram

The Ramsey model is interesting for its predictions about the path of capital and consumption growth given initial condition $\hat{c}_0$ and $\hat{k}_0$. We now want to plot the generic phase diagram showing the nature of this dynamics.

```python
#a. locus for capital
def locus_cap(k_t):
    return prod(k_t) - k_t*(delta + n + x)

#b. Create a grid of points for plotting
x_vec = np.linspace(0, 500, 7000)

#c. Constructing the generic phase diagram
phase = plt.figure(figsize=(12,7))
plt.xlim(0,135)
plt.ylim(0,4)
plt.grid(False)
plt.xlabel([])

#c.1 Adding the capital and consumption locii
plt.plot(x_vec, locus_cap(x_vec), linestyle ='--', color='black')
plt.axvline(k_star(), linestyle = '-', color = 'red')
plt.plot(k_star(), c_star(), marker='o', markersize = 10, color ='red')

#c.2 Adding arrows to indicate dynamics
plt.arrow((1/2)*k_star(), (1/2)*c_star(), dx=0, dy=0.3, head_width = 2, head_length= 0.2, color = 'green')
plt.arrow((1/2)*k_star(), (1/2)*c_star(), dx=5, dy=0, head_width = 0.15, head_length= 2, color = 'orange')
plt.arrow((1/2)*k_star(), 1.7*c_star(), dx=0, dy=0.3, head_width = 2, head_length= 0.2, color = 'green')
plt.arrow((1/2)*k_star(), 1.7*c_star(), dx=-5, dy=0, head_width = 0.15, head_length= 2, color = 'orange')

plt.arrow(2*k_star(), (1/2)*c_star(), dx=0, dy=-0.3, head_width = 2, head_length = 0.2, color = 'green')
plt.arrow(2*k_star(), (1/2)*c_star(), dx=5, dy=0, head_width = 0.15, head_length = 2, color = 'orange')
plt.arrow(2*k_star(), 2*c_star(), dx=0, dy=-0.3, head_width = 2, head_length = 0.2, color = 'green')
plt.arrow(2*k_star(), 2*c_star(), dx=-5, dy=0, head_width = 0.15, head_length = 2, color = 'orange')

#c.3 Adding axis labels and title
plt.xlabel('Capital ($\hat{k}$)', fontsize = 12)
plt.ylabel('Consumption ($\hat{c}$)', fontsize = 12)
plt.title('Ramsey Model Phase Diagram ', fontsize=15, weight='bold', pad = 10)
plt.show()
```

The dashed black line represents the $\dot{k}=0$ loci and red line the $\dot{c}=0$ loci (the other loci where $\dot{c}=0$ corresponds the the horizontal axis $\hat{c}=0$). The red dot displays, instead, the steady state.
The arrows depict the nature of the dinamics for consumption and capital. So, consumption rises for value of capital $\hat{k}<k^{*}$ (green arrows point upwards) and decreases otherwise (green arrows point downwards). Capital, instead, falls for values of consumption above the dashed loci (yellow arrows point leftwards) and rises for values below (yellow arrows point rightwards).

### 5.3 Plotting different  paths

In the previous subsection we got an overall idea of the dinamics of the systems. Now, we are going to plot the consumption/capital paths for different initial values of consumption. We will see that only one precise value of $c_0$ will lead us to the the steady state and this path is called the saddle path.

```python
#a. Create dictionary for all parameters values
parameters = {'rho':rho, 'theta':theta, 'n':n, 'alpha':alpha, 'x':x, 'delta':delta}

#b. Create a class to make our plotting easier
class Ramsey(object):

    def __init__(self, parameters, k_t=None, c_t=None):

        # current value of state variable, k
        self.c_t            = c_t
        self.k_t            = k_t
        # dictionary of parameter values
        self.par   = parameters
        # dictionary of steady state values
        self.steadystate      = {'k_star':self.k_ss(self.par), 'c_star':self.c_ss(self.par)}

    def k_ss(self, parameters):
        """
        The steady-state level of capital stock per effective worker
        """
        return ((delta + rho + theta*x)/alpha)**(1 / (alpha-1))

    def c_ss(self, parameters):
        """
        The steady-state level of consumption per effective worker
        """
        k_star = self.k_ss(parameters)

        return prod(k_star)-(n+delta+x)* k_star

    def cap_t1(self, k_t, c_t):
        """
        Next period's capital stock per effective worker
        """
        return np.exp(-(x + n)) * (k_t**alpha + np.exp(-delta) * k_t - c_t)

    def cons_t1(self, k_t, c_t):
        """
        Next period's consumption per effective worker
        """
        return np.exp(-x) * (np.exp(-rho) * (np.exp(-delta) + alpha * cap_t1(k_t, c_t)**(alpha - 1)))**(1 / theta) * c_t

    def follow(self):
        """
        Update the state variables
        """
        self.k_t = self.cap_t1(self.k_t, self.c_t)
        self.c_t = self.cons_t1(self.k_t, self.c_t)

    def sample(self, n=None):
        """
        Generate sample path of length n starting from the current state
        """
        path = np.zeros(shape=(n, 2))

        for s in range(n):
            path[s, 0] = self.k_t
            path[s, 1] = self.c_t
            self.follow()

        return path

    def algorithm(self, k0=None, c0=None, eps=1.5e-07):
        """
        Non-linear saddle path for the Ramsey model using the
        'forward shooting' algorithm
        """
        # compute steady state values
        k_star, c_star = self.steadystate['k_star'], self.steadystate['c_star']

        if k0 <= k_star:
            c_low = 0
            c_high = c_star
        else:
            c_low = c_star
            c_high = k0**alpha
        c0 = (c_high + c_low) / 2
        self.k_t, self.c_t = k0, c0

        # Initialize a counter
        col  = 0
        iterations = 0

        # Forward Shooting Algorithm
        while 1:
            self.follow()
            dis = np.abs(((self.k_t - k_star)**2 + (self.c_t - c_star)**2)**1/2)
            col = col + 1
            if k0 <= k_star:
                if self.k_t > k_star:
                    if dis < eps:
                        break
                    else: # initial c_low too low
                        c_low = c0
                        c0 = (c_high + c_low) / 2
                        self.k_t, self.c_t = k0, c0
                        col = 0
                if self.c_t > c_star:
                    if dis < eps:
                        break
                    else: # initial c_high too high
                        c_high = c0
                        c0 = (c_high + c_low) / 2
                        self.k_t, self.c_t = k0, c0
                        col = 0
            else:
                if self.k_t < k_star:
                    if dis < eps:
                        break
                    else: # initial c_low too high
                        c_high = c0
                        c0 = (c_high + c_low) / 2
                        self.k_t, self.c_t = k0, c0
                        col = 0
                if self.c_t < c_star:
                    if dis < eps:
                        break
                    else: # initial c_low too low
                        c_low = c0
                        c0 = (c_high + c_low) / 2
                        self.k_t, self.c_t = k0, c0
                        col = 0

        self.k_t, self.c_t = k0, c0
        solution = self.sample(col)

        return [self.c_t, solution, col, dis]

```

```python

#c. Create a reference to the class Ramsey
ram = Ramsey(parameters)

#d. Initial levels of consumption and production
ram.c_t, ram.k_t = 10, 0.5

#e. Updating the system
ram.follow()
ram.k_t, ram.c_t

#f. Generating a sample path of length 10  and grid
ram.sample(30)
x1_vec = np.linspace(0, 500, 7000)

#g. Given an initial level of capital
k0 = 0.5 * ram.steadystate['k_star']

#h. Different initial conditions for choices of consumption per effective worker
in_consvalues = np.linspace(0.5 * ram.steadystate['c_star'], 1.5 * ram.steadystate['c_star'], num=7)

#i. Creating a new plot
phase1 = plt.figure(figsize=(12,7))

#j Sample path for each value of consumption
for c_t in in_consvalues:
    ram.k_t, ram.c_t = k0, c_t
    tmr = ram.sample(3000)
    plt.plot(tmr[:, 0], tmr[:, 1], linestyle ='dashed')

#k. Adding loci
plt.plot(x1_vec, locus_cap(x1_vec), linestyle = 'solid', color ='black')
plt.axvline(ram.steadystate['k_star'], linestyle = 'solid', color='black' )
plt.plot(ram.steadystate['k_star'], ram.steadystate['c_star'], marker='o', markersize=10, color='red')

#l. Adding saddle path
ram_sol = ram.algorithm(k0, eps=1.5e-04)
plt.plot(ram_sol[1][:, 0], ram_sol[1][:, 1], linestyle = 'solid', color='red')

#m. Adding the line which identify initial level of capital per worker and k0, k** symbol
plt.axvline(k0, linestyle='--', color = 'red')
plt.text(130, 0, "$\hat{k}$**", color="black", ha='center', va = 'top', fontsize=12)
plt.text(k0, 0, "$\hat{k}_0$", color="black", ha='center', va = 'top', fontsize=12)

#n. Adding axis features and title
plt.xlim(0, 130)
plt.ylim(0, 4)
plt.xlabel('Capital ($\hat{k}$)', fontsize = 12)
plt.ylabel('Consumption ($\hat{c}$)', fontsize = 12)
plt.title('Different Paths & Optimal Saddle Path', fontsize=15, weight='bold')

plt.show()
```

The figure shows the dynamics of consumption and capital for different initial values of consumption given a specific initial $k_0$. We can also see the optimal saddle-path (red solid line) which is unique and can be achieved if only that particular $c_0$ is chosen. The saddle-path is the sequence that  leads to the steady state given an initial amount of capital. As we have previously detected in the generic phase diagram, values of $c_0$ too low bring to the condition of full capital and no consumption $\hat{k}^{**}$, meanwhile value of $c_0$ too high bring to the condition of full consumption but any capital.

```python
#g. Given an initial level of capital
k0 = 2 * ram.steadystate['k_star']

#h. Different initial conditions for choices of consumption per effective worker
in_consvalues = np.linspace(1 * ram.steadystate['c_star'], 2 * ram.steadystate['c_star'], num=7)

#i. Creating a new plot
phase1 = plt.figure(figsize=(12,7))

#j Sample path for each value of consumption
for c_t in in_consvalues:
    ram.k_t, ram.c_t = k0, c_t
    tmr = ram.sample(3000)
    plt.plot(tmr[:, 0], tmr[:, 1], linestyle ='dashed')

#k. Adding loci
plt.plot(x1_vec, locus_cap(x1_vec), linestyle = 'solid', color ='black')
plt.axvline(ram.steadystate['k_star'], linestyle = 'solid', color='black' )
plt.plot(ram.steadystate['k_star'], ram.steadystate['c_star'], marker='o', markersize=10, color='red')

#l. Adding saddle path
ram_sol = ram.algorithm(k0, eps=1.5e-04)
plt.plot(ram_sol[1][:, 0], ram_sol[1][:, 1], linestyle = 'solid', color='red')

#m. Adding the line which identify initial level of capital per worker and k0, k** symbol
plt.axvline(k0, linestyle='--', color = 'red')
plt.text(130, 0, "$\hat{k}$**", color="black", ha='center', va = 'top', fontsize=12)
plt.text(k0, 0, "$\hat{k}_0$", color="black", ha='center', va = 'top', fontsize=12)

#n. Adding axis features and title
plt.xlim(0, 130)
plt.ylim(0, 4)
plt.xlabel('Capital ($\hat{k}$)', fontsize = 12)
plt.ylabel('Consumption ($\hat{c}$)', fontsize = 12)
plt.title('Different Paths & Optimal Saddle Path', fontsize=15, weight='bold')

plt.show()
```

We have chosen another $\hat{k}_0$, this time on the right of the steady state. We can see again the optimal saddle-path (red solid line) which is unique and can be achieved if only that particular value of $c_0$ is chosen. Again, Values of $\hat{c}_0$ too low bring to the condition of full capital and no consumption $\hat{k}^{**}$, meanwhile value of $\hat{c}_0$ too high bring to the condition of full consumption but any capital.

## 6. Conclusion

In conclusion, we have seen the optimizing behaviour of our consumer and our firm individually. In the Ramsey model their interaction produced the phase diagram of *Section.5*. In particular the elasticity of intertemporal substitution ($1/\theta$) determines the speed of the adjustment to the steady state and the behavior of the saving rate during the transition. For example, if the elasticity is high then large changes in consumption are not very costly to consumers and, if the real interest rate is high, they will save more. Instead, if the elasticity is low and the real interest rate high, then the consumption smoothing motive is very strong and consumer will save less and consume more.


# Error Propagation using Random Gaussian Numbers

Example calculation of propagating uncertainties, both when adding and multiplying number, and also in the general case. The propagation can be done both analytically (using the error propagation formula) and also using simulation.

The example is based on FIRST doing the error propagation **analytically**, and then verifying it by running a so-called Monte-Carlo (MC) program, which uses random numbers for propagating errors.

## References:
- Barlow: page 48-61
- Bevington: page 36-48

## Author(s), contact(s), and dates:
- Author: Troels C. Petersen (NBI)
- Email:  petersen@nbi.dk
- Date:   18th of November 2021

***

DO THE FOLLOWING ANALYTICAL EXERCISE FIRST!!!

1. A class of students estimate by eye, that the length of the table in Auditorium A is $L = (3.5\pm 0.4)$m, and that the width is $W = (0.8\pm 0.2)$m.

   Assuming that there is no correlation between these two measurements, calculate ANALYTICALLY what the Perimeter (P), area (A), and diagonal (D) length is including (propagated) uncertainties. Repeat the calculation, given that the correlation between length and width is $\rho(L,W) = 0.5$ - not an unreasonable number, given that they are estimated by the same (uncertain) scale.

NOTE: This is a complete standard problem, that you will be asked to solve again and again in the course. For this reason, make sure that you understand how to do it, and become good at doing it reasonably fast.

```python
# Defining the parameters:
mu1   =  3.5
sig1  =  0.4
mu2   =  0.8
sig2  =  0.2
rho12 =  0.5           # Correlation parameter!
```

```python
if not (-1.0 <= rho12 <= 1.0):
    raise ValueError(f"Correlation factor not in interval [-1,1], as it is {rho12:6.2f}")
```

### Note on analytic solutions:

Python includes symbolic algebra in the package *SymPy*, which is both simple and powerful (and in Python). In addition, printing with Latex can also be included (see below), which (in combination) is very nice.

Below is a SymPy and Latex example with the hope that it will wet your appetite.

```python
from IPython.core.display import Latex

def lprint(*args,**kwargs):
    """Pretty print arguments as LaTeX using IPython display system

    Parameters
    ----------
    args : tuple
        What to print (in LaTeX math mode)
    kwargs : dict
        optional keywords to pass to `display`
    """
    display(Latex('$$'+' '.join(args)+'$$'),**kwargs)
```

```python
# Import SymPy:
from sympy import *

# Define variables:
L,W,P,A,D = symbols("L, W, P, A, D")
dL,dW,dP,dA,dD = symbols("sigma_L, sigma_W, sigma_P, sigma_A, sigma_D")

# Perimeter:
# Define relation, and print:
P = 2*L + 2*W
lprint(latex(Eq(symbols('P'),P)))

# Calculate uncertainty and print:
dP = sqrt((P.diff(L) * dL)**2 + (P.diff(W) * dW)**2)
lprint(latex(Eq(symbols('sigma_P'), dP)))

# Turn expression into numerical functions
fP = lambdify((L,W),P)
fdP = lambdify((L,dL,W,dW),dP)

# Define values and their errors
vL, vdL = mu1,sig1
vW, vdW = mu2,sig2

# Numerically evaluate expressions and print
vP = fP(vL,vW)
vdP = fdP(vL,vdL,vW,vdW)
lprint(fr'P = ({vP:.1f} \pm {vdP:.1f})\,\mathrm{{m}}')

# NOTE: Do the above analytical calculation before you continue below! Possibly use SymPy for the differentiations.
```

```python
import numpy as np                                     # Matlab like syntax for linear algebra and functions
import matplotlib.pyplot as plt                        # Plots and figures like you know them from Matlab
import seaborn as sns                                  # Make the plots nicer to look at
from iminuit import Minuit                             # The actual fitting tool, better than scipy's
import sys                                             # Modules to see files and folders in directories
```

```python
sys.path.append('../../../External_Functions')
from ExternalFunctions import Chi2Regression
from ExternalFunctions import nice_string_output, add_text_to_ax # useful functions to print fit results on figure
```

## Error propagation - Simulation

Now we want to try to see, if we can solve the above error propagation problem using simulation. The method is relatively straight forward: You simply take "realistic" values of the input parameters x (here Length (x1) and Width (x2)), calculate the resulting value y (here Perimeter, Area, and Diagonal), and do this many times. The resulting distribution of y should be centered around the value y(x1,x2), and the standard deviation should reflect the uncertainty in y from the uncertainties in the input variables.

This is a much more clumsy of calculating the uncertainty, but comes with the advantage, that if the resulting uncertainty is not Gaussian, then one can actually see this (i.e. it avoids the assumptions used in the usual error propagation formula).

```python
# First we set the parameters of the program:
N_exp = 10000           # Number of "experiments" (i.e. drawing from random distributions)
save_plots = False
r = np.random
r.seed(42)
```

Define parameters for two random numbers (Gaussianly distributed):

```python
mu1   =  3.5
sig1  =  0.4
mu2   =  0.8
sig2  =  0.2
rho12 =  0.5           # Correlation parameter!
```

```python
if not (-1.0 <= rho12 <= 1.0):
    raise ValueError(f"Correlation factor not in interval [-1,1], as it is {rho12:6.2f}")
```

Now we calculate numbers that allows the transform from uncorrelated variables `u` and `v` to correlated random numbers `x1` and `x2` below (see Barlow page 42-44 for method):

```python
# Define the parameters needed for the transformation:
theta = 0.5 * np.arctan( 2.0 * rho12 * sig1 * sig2 / ( np.square(sig1) - np.square(sig2) ) )
sigu = np.sqrt( np.abs( (((sig1*np.cos(theta))**2) - (sig2*np.sin(theta))**2 ) / ( (np.cos(theta))**2 - np.sin(theta)**2) ) )
sigv = np.sqrt( np.abs( (((sig2*np.cos(theta))**2) - (sig1*np.sin(theta))**2 ) / ( (np.cos(theta))**2 - np.sin(theta)**2) ) )

# Produce random numbers with the (possible) correlation:
u = r.normal(0.0, sigu, N_exp)
v = r.normal(0.0, sigv, N_exp)
x1_all = mu1 + np.cos(theta)*u - np.sin(theta)*v
x2_all = mu2 + np.sin(theta)*u + np.cos(theta)*v
x12_all = np.array([x1_all, x2_all])

y_all = x1_all - 2*x2_all         # Silly formula - you have to put this in yourself!
```

Note that the above is nothing more than a matrix multiplication written out! Also note that the absolute value is taken before the square root to avoid `np.sqrt(x)` with `x<0`.

***
Plot both input distribution and resulting 2D-histogram on screen:

```python
fig, ax = plt.subplots(figsize=(14, 8))
counts, xedges, yedges, im = ax.hist2d(x1_all, x2_all, bins=[120, 80], range= [[0.0, 6.0], [-1.0, 3.0]], cmin=1)
ax.plot([0.0, 6.0], [0.0, 0.0], "--k")   # NOTE: This draws a line from [x1, x2], [y1, y2] with dashed line ("--") and in black ("k")
fig.colorbar(im) # ticks=[-1, 0, 1]

ax.set(title='Histogram of lengths (x) and widths (y)',
       xlabel='x',
       ylabel='y',
       aspect='equal', # NOTE: This forces the x- and y-axis to have the SAME scale!!!
      )

d = {'Entries': len(x12_all),
     'Mean x': x1_all.mean(),
     'Mean y': x2_all.mean(),
     'Std x': x1_all.std(ddof=1),
     'Std y': x2_all.std(ddof=1),
    }

text = nice_string_output(d, extra_spacing=2, decimals=3)
add_text_to_ax(0.02, 0.97, text, ax, fontsize=15);

fig.tight_layout()
fig

if save_plots :
    fig.savefig("Dist_2Dgauss.pdf")
```

Now we bin `y_all` and fit it with a Gaussian distribution:

```python
def gaussian(x, N, mu, sigma):
    return N * binwidth / (sigma*np.sqrt(2*np.pi)) * np.exp(-0.5* (x-mu)**2/sigma**2)
```

```python
nbins = 100
xmin, xmax = 0.0, 4.0
binwidth = (xmax-xmin)/nbins

fig2, ax2 = plt.subplots(figsize=(16, 6));
counts, bin_edges, _ = ax2.hist(y_all, nbins, range=(xmin, xmax), histtype='step', linewidth=2)
bin_centers = (bin_edges[1:] + bin_edges[:-1])/2
s_counts = np.sqrt(counts)
```

This is the distribution of "whatever you put into it" (initially x1-2*x2), which shows what output you get and what uncertainty to expect (given by the width - think about this!). We can thus get the result by simply recording the mean and width (SD):

```python
mean = y_all.mean()
std = y_all.std(ddof=1)
print(f"  Mean = {mean:5.3f},    Std = {std:5.3f}")
```

However, we are in principle not even sure, if this distribution is Gaussian, so in order to check this, we draw a Gaussian on top using the above mean and standard deviation values.

```python
xaxis = np.linspace(xmin, xmax, 1000)
yaxis = gaussian(xaxis, N_exp, mean, std)
ax2.plot(xaxis, yaxis, linewidth=2)
fig2.tight_layout()
fig2
```

# An alternative (beyond the scope of this exercise)

An alternative is to fit it, though this is actually a subject later introduced (so consider the below as "just for fun"). Note that in the second line (defining the Minuit object to minimised) we give "reasonable" starting values, which is key to making fitting work (more on that later).

```python
Chi2_object = Chi2Regression(gaussian, bin_centers[counts>0], counts[counts>0], s_counts[counts>0])
Chi2_object.errordef = 1                # Set the error definition (Chi2: 1, LLH: 0.5)
minuit = Minuit(Chi2_object, N=5000, mu=2.0, sigma=0.5)      # Make minuit object and give initial values
minuit.migrad()                         # Perform the actual fit

for name in minuit.parameters:
    print("  Fit value: {0} = {1:.5f} +/- {2:.5f}".format(name, minuit.values[name], minuit.errors[name]))

chi2 = minuit.fval                      # Get the Chi2 value at the minimum
Nvar = 3                                # Number of variables (N, mu, sigma)
Ndof = len(counts[counts>0]) - Nvar     # Number of degrees of freedom

from scipy import stats
chi2_prob =  stats.chi2.sf(chi2, Ndof)  # The chi2 probability given Ndof (degrees of freedom)
print(f"  P(Chi2={chi2:4.2f}, Ndof={Ndof:2d}) = {chi2_prob:6.4f}")
```

And plot the fit on top of the histogram:

```python
yaxis_fit = gaussian(xaxis, *minuit.values[:])
ax2.plot(xaxis, yaxis_fit);
```

```python
d = {'Entries': len(y_all),
     'Mean': y_all.mean(),
     'Std': y_all.std(ddof=1),
     'Chi2': chi2,
     'NDF': Ndof,
     'Prob': chi2_prob,
     'N': [minuit.values['N'], minuit.errors['N']],
     'mu': [minuit.values['mu'], minuit.errors['mu']],
     'sigma': [minuit.values['sigma'], minuit.errors['sigma']],
    }

text = nice_string_output(d, extra_spacing=2, decimals=3)
add_text_to_ax(0.03, 0.96, text, ax2, fontsize=16);

fig2.tight_layout()
fig2
```

```python
if save_plots:
    fig2.savefig("Dist_ErrorProp.pdf")
```

---

# Questions:

0. First solve the problem of obtaining the Perimeter, Area & Diagonal with uncertainty ANALYTICALLY.

1. Now look at the program, and assure yourself that you understand what is going on. Put in the correct expression for y in terms of x1=L and x2=W in order to calculate the circumference, area, and diagonal length, and run the program. Does the output correspond well with the results you expected from your analytical calculations to begin with?

2. Imagine that you wanted to know the central value and uncertainty of y1 and y2 (i.e. two other cases), given the
   same Gaussian PDFs as above for `x1`=$L$ and `x2`=$W$:

     `y1 = log(square(x1*tan(x2))+sqrt((x1-x2)/(cos(x2)+1.0+x1)))`

     `y2 = 1.1+sin(20*x1)`

   Get the central value of y1 and y2, and see if you can quickly differentiate these two with
   respect to `x1` and `x2`, and thus predict what uncertainty to expect for y1 and y2 using
   the error propagation formula. It is (for once) OK to give up on the first expression :-)
   Next, try to estimate the central value and uncertainty using random numbers
   like above - do you trust this result more? And are the distributions Gaussian?

### Advanced questions:

3. Try to generate `x1` and `x2` with non-linear correlation, which yields zero linear correlation,
   and see that despite not having any linear correlation, the result on perimeter, area, and diagonal
   length is still affected.

```python

```


# Some Random SymPy Stuff

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

# for older SymPy versions:
#%load_ext sympy.interactive.ipythonprinting

# for even older IPython/SymPy versions:
#%load_ext sympyprinting
```

```python
x, y = sp.symbols("x y")
```

```python
(sp.pi + x) ** 2
```

```python
expr = ((x + y)**2 * (x + 1))
expr
```

```python
sp.expand(expr)
```

```python
sp.diff(sp.cos(x**2)**2 / (1 + x), x)
```

```python
x = sp.symbols('x', positive=True)
x > 0
```

```python
sp.sqrt(x ** 2)
```

```python
sp.sqrt(y ** 2)
```

```python
sp.exp(sp.I * sp.pi)
```

```python
sp.oo
```

```python
a, b, c = sp.symbols("a, b, c")
```

```python
sp.expand(sp.sin(a + b))
```

```python
sp.expand(sp.sin(a + b), trig=True)
```

```python
sp.simplify(sp.sin(x)**2 + sp.cos(x)**2)
```

```python
sp.factor(x**3 + 6 * x**2 + 11*x + 6)
```

```python
sp.simplify(sp.cos(x) / sp.sin(x))
```

```python
expr1 = 1 / ((a + 1) * (a + 2))
expr1
```

```python
expr1.is_rational_function()
```

    True

```python
sp.apart(expr1)
```

```python
z = sp.symbols('z')
```

```python
f = sp.sin(x * y) + sp.cos(y * z)
f
```

```python
sp.integrate(f, x)
```

```python
sp.integrate(f, y)
```

```python
sp.integrate(sp.exp(-x**2), x)
```

```python
sp.integrate(sp.exp(-x**2), (x, -sp.oo, sp.oo))
```

```python
sp.integrate(x + sp.sinh(x), x)
```

```python
sp.diff(_, x)
```

```python
sp.limit(sp.sin(sp.pi * x) / x, x, 0)
```

```python
4 * sp.atan(1)
```

```python
n = sp.symbols('n')
```

```python
expr = (-1)**n / (2 * n + 1)
expr
```

```python
sp.summation(expr, (n, 0, sp.oo))
```

```python
sp.series(sp.atan(x), x, n=13)
```

```python
expr = 1 / (x + y)
expr
```

```python
expr.series(x)
```

```python
sp.series(sp.exp(x), x)
```

```python
sp.summation(1 / sp.factorial(n), (n, 0, sp.oo))
```

```python
expr = (1 + 1/n) ** n
expr
```

```python
sp.limit(expr, n, sp.oo)
```

```python
sp.pi.evalf(100)
```

```python
sp.solve(x**4 - x**2 - 1, x)
```

```python
sp.solve([x + y - 1, x - y - 1], [x,y])
```

```python
sp.solve([x + y - a, x - y - c], [x,y])
```

```python
sp.symbols('m:q')
```

```python
sp.symbols('r:5')
```

```python
m = sp.symbols('m')
M = sp.Matrix([[1, m], [3, 4]])
M
```

```python
M.inv()
```

```python
A = sp.Matrix(sp.symbols("m:2:2")).reshape(2, 2)
A
```

```python
A[1, 0]
```

```python
b = sp.Matrix(sp.symbols("b:2"))
b
```

```python
A**2
```

```python
A * b
```

```python
A.det()
```

```python
A.inv()
```

from https://github.com/sympy/sympy/wiki/Matrices-eigenvalues

```python
M = sp.Matrix(4, 4, lambda i, j: i * j + 1)
M
```

```python
M.eigenvals()
```

```python
M = sp.Matrix(4, 4, lambda i, j: i * j + 2)
M
```

```python
M.eigenvals()
```

```python
M = sp.Matrix(4, 4, lambda i, j: i * j + 3)
M
```

```python
M.eigenvals()
```

```python
x = sp.symbols('x')
M = sp.Matrix(4, 4, lambda i, j: i * j + x)
M
```

```python
M.eigenvals()
```

from https://groups.google.com/forum/#!topic/sympy/mISLFQcEUIM

```python
f = sp.Piecewise((0, x <= -1), (sp.Rational(1, 2), x < 1), (0, True))
f
```

```python
#sp.integrate(f.subs(x, x - y) * f(x), y)
```

```python
sp.integrate(sp.Heaviside(x + 1) / 2 - sp.Heaviside(x - 1) / 2, (x, -10, -5))
```

```python
q = sp.Piecewise((0, x < 0), (x, x < 1), (0, True))
q
```

```python
q.args
```

from https://groups.google.com/forum/#!topic/sympy/tt8IA3y2sfQ

```python
f = sp.Lambda(x, sp.exp(-x**2))
f
```

```python
conv = sp.Integral(f(x-y)*f(y), (y, -sp.oo, sp.oo))
conv
```

```python
conv.subs(x, 0)
```

```python
l = sp.lambdify(x, sp.Integral(f(x), (x, -sp.oo, sp.oo)))
#l(3) # doesn't work!
l
```

    <function numpy.<lambda>>

```python
l = sp.lambdify(x, sp.Integral(f(x), (x, -sp.oo, sp.oo)), "sympy")
l
```

    <function <lambda>>

```python
l(x)
```

from http://www.mare.ee/indrek/misc/convolution.pdf

```python
# Convolute two "one-piece" functions. Arguments F and G
# are tuples in form (h(x) , a_h, b_h), where h(x) is
# the function and [a_h, b_h) is the range where the functions
# are non-zero.
def convolute_onepiece(x, F, G):
    f, a_f, b_f = F
    g, a_g, b_g = G
    f = sp.S(f)
    g = sp.S(g)
    # make sure ranges are in order, swap values if necessary
    if b_f - a_f > b_g - a_g:
        f, a_f, b_f, g, a_g, b_g = g, a_g, b_g, f, a_f, b_f
    y = sp.Dummy('y')
    i = sp.integrate(f.subs(x, y) * g.subs(x, x-y), y)
    return [
        (i.subs(y, x-a_g) - i.subs(y, a_f), a_f+a_g, b_f+a_g),
        (i.subs(y, b_f) - i.subs(y, a_f), b_f+a_g, a_f+b_g),
        (i.subs(y, b_f) - i.subs(y, x-b_g), a_f+b_g, b_f+b_g) ]
```

```python
# Two "flat" functions, uniform centered PDFs
F = 0.5, -1, 1
G = 0.05, -10, 10
convolute_onepiece(sp.abc.x, F, G)
```

from https://github.com/fperez/sympy/blob/master/sympy/solvers/tests/test_pde.py

```python
from sympy import Derivative as D
```

```python
x, y, z, t = sp.symbols("x y z t")
c = sp.symbols("c", real=True)
Phi = sp.Function('Phi')
F, R, T, X, Y, Z, u = map(sp.Function, 'FRTXYZu')
r, theta, z = sp.symbols('r theta z')
```

```python
eq = sp.Eq(D(F(x, y, z), x) + D(F(x, y, z), y) + D(F(x, y, z), z))
eq
```

```python
sp.pde_separate_mul(eq, F(x, y, z), [Y(y), u(x, z)])
```

```python
sp.pde_separate_mul(eq, F(x, y, z), [X(x), Y(y), Z(z)])
```

```python
# wave equation
wave = sp.Eq(D(u(x, t), t, t), c**2*D(u(x, t), x, x))
wave
```

```python
sp.pde_separate_mul(wave, u(x, t), [X(x), T(t)])
```

```python
# Laplace equation in cylindrical coords
eq = sp.Eq(1/r * D(Phi(r, theta, z), r) + D(Phi(r, theta, z), r, 2) +
           1/r**2 * D(Phi(r, theta, z), theta, 2) + D(Phi(r, theta, z), z, 2))
eq
```

```python
# Separate z
sp.pde_separate_mul(eq, Phi(r, theta, z), [Z(z), u(theta, r)])
```

```python
# Lets use the result to create a new equation...
eq = sp.Eq(_[1], c)
eq
```

```python
# ...and separate theta...
sp.pde_separate_mul(eq, u(theta, r), [T(theta), R(r)])
```

```python
# ...or r...
sp.pde_separate_mul(eq, u(theta, r), [R(r), T(theta)])
```

```python

```

  To the extent possible under law,
  the person who associated CC0
  with this work has waived all copyright and related or neighboring
  rights to this work.


In this notebook, you will implement the forward longitudinal vehicle model. The model accepts throttle inputs and steps through the longitudinal dynamic equations. Once implemented, you will be given a set of inputs that drives over a small road slope to test your model.

The input to the model is a throttle percentage $x_\theta \in [0,1]$ which provides torque to the engine and subsequently accelerates the vehicle for forward motion.

The dynamic equations consist of many stages to convert throttle inputs to wheel speed (engine -> torque converter -> transmission -> wheel). These stages are bundled together in a single inertia term $J_e$ which is used in the following combined engine dynamic equations.

\begin{align}
    J_e \dot{\omega}_e &= T_e - (GR)(r_{eff} F_{load}) \\ m\ddot{x} &= F_x - F_{load}
\end{align}

Where $T_e$ is the engine torque, $GR$ is the gear ratio, $r_{eff}$ is the effective radius, $m$ is the vehicle mass, $x$ is the vehicle position, $F_x$ is the tire force, and $F_{load}$ is the total load force.

The engine torque is computed from the throttle input and the engine angular velocity $\omega_e$ using a simplified quadratic model.

\begin{align}
    T_e = x_{\theta}(a_0 + a_1 \omega_e + a_2 \omega_e^2)
\end{align}

The load forces consist of aerodynamic drag $F_{aero}$, rolling friction $R_x$, and gravitational force $F_g$ from an incline at angle $\alpha$. The aerodynamic drag is a quadratic model and the friction is a linear model.

\begin{align}
    F_{load} &= F_{aero} + R_x + F_g \\
    F_{aero} &= \frac{1}{2} C_a \rho A \dot{x}^2 = c_a \dot{x}^2\\
    R_x &= N(\hat{c}_{r,0} + \hat{c}_{r,1}|\dot{x}| + \hat{c}_{r,2}\dot{x}^2) \approx c_{r,1} \dot{x}\\
    F_g &= mg\sin{\alpha}
\end{align}

Note that the absolute value is ignored for friction since the model is used for only forward motion ($\dot{x} \ge 0$).

The tire force is computed using the engine speed and wheel slip equations.

\begin{align}
    \omega_w &= (GR)\omega_e \\
    s &= \frac{\omega_w r_e - \dot{x}}{\dot{x}}\\
    F_x &= \left\{\begin{array}{lr}
        cs, &  |s| < 1\\
        F_{max}, & \text{otherwise}
        \end{array}\right\}
\end{align}

Where $\omega_w$ is the wheel angular velocity and $s$ is the slip ratio.

We setup the longitudinal model inside a Python class below. The vehicle begins with an initial velocity of 5 m/s and engine speed of 100 rad/s. All the relevant parameters are defined and like the bicycle model, a sampling time of 10ms is used for numerical integration.

```python
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

class Vehicle():
    def __init__(self):

        # ==================================
        #  Parameters
        # ==================================

        #Throttle to engine torque
        self.a_0 = 400
        self.a_1 = 0.1
        self.a_2 = -0.0002

        # Gear ratio, effective radius, mass + inertia
        self.GR = 0.35
        self.r_e = 0.3
        self.J_e = 10
        self.m = 2000
        self.g = 9.81

        # Aerodynamic and friction coefficients
        self.c_a = 1.36
        self.c_r1 = 0.01

        # Tire force
        self.c = 10000
        self.F_max = 10000

        # State variables
        self.x = 0
        self.v = 5
        self.a = 0
        self.w_e = 100
        self.w_e_dot = 0

        self.sample_time = 0.01

    def reset(self):
        # reset state variables
        self.x = 0
        self.v = 5
        self.a = 0
        self.w_e = 100
        self.w_e_dot = 0
```

Implement the combined engine dynamic equations along with the force equations in the cell below. The function $\textit{step}$ takes the throttle $x_\theta$ and incline angle $\alpha$ as inputs and performs numerical integration over one timestep to update the state variables. Hint: Integrate to find the current position, velocity, and engine speed first, then propagate those values into the set of equations.

```python
class Vehicle(Vehicle):
    def step(self, throttle, alpha):
        # ==================================
        #  Implement vehicle model here
        # ==================================
        F_aero = self.c_a * self.v * self.v
        R_x    = self.c_r1 * self.v
        F_g    = self.m * self.g * np.sin(alpha)
        F_load = F_aero + R_x + F_g
        T_e    = throttle * (self.a_0 + self.a_1 * self.w_e + self.a_2 * self.w_e * self.w_e)
        W_w    = self.GR * self.w_e
        r_eff  = self.v / W_w
        s      = (W_w * self.r_e - self.v) / self.v
        cs     = self.c * s

        if abs(s) < 1 :
            F_x = cs
        else:
            F_x = self.F_max

        self.x = self.x + self.v * self.sample_time
        self.v = self.v + self.a * self.sample_time
        self.a = (F_x - F_load) / self.m
        self.w_e = self.w_e + self.w_e_dot * self.sample_time
        self.w_e_dot = (T_e - self.GR * self.r_e * F_load) / self.J_e

        pass
```

Using the model, you can send constant throttle inputs to the vehicle in the cell below. You will observe that the velocity converges to a fixed value based on the throttle input due to the aerodynamic drag and tire force limit. A similar velocity profile can be seen by setting a negative incline angle $\alpha$. In this case, gravity accelerates the vehicle to a terminal velocity where it is balanced by the drag force.

```python
sample_time = 0.01
time_end = 100
model = Vehicle()

t_data = np.arange(0,time_end,sample_time)
v_data = np.zeros_like(t_data)

# throttle percentage between 0 and 1
throttle = 0.2

# incline angle (in radians)
alpha = 0

for i in range(t_data.shape[0]):
    v_data[i] = model.v
    model.step(throttle, alpha)

plt.plot(t_data, v_data)
plt.show()
```

We will now drive the vehicle over a slope as shown in the diagram below.

To climb the slope, a trapezoidal throttle input is provided for the next 20 seconds as shown in the figure below.

The vehicle begins at 20% throttle and gradually increases to 50% throttle. This is maintained for 10 seconds as the vehicle climbs the steeper slope. Afterwards, the vehicle reduces the throttle to 0.

In the cell below, implement the ramp angle profile $\alpha (x)$ and throttle profile $x_\theta (t)$ and step them through the vehicle dynamics. The vehicle position $x(t)$ is saved in the array $\textit{x_data}$. This will be used to grade your solution.

```python
time_end = 20
t_data = np.arange(0,time_end,sample_time)
x_data = np.zeros_like(t_data)

v_data = np.zeros_like(t_data)
throttle_data = np.zeros_like(t_data)
alpha_data = np.zeros_like(t_data)

# reset the states
model.reset()

# ==================================
#  Learner solution begins here
# ==================================
for i in range(t_data.shape[0]):
    if i >=0 and i < 500:
        throttle = 0.2 + 0.0006 * i
    elif i >= 500 and i < 1500:
        throttle = 0.5
    elif i >= 1500 and i < 2000:
        throttle = 2 - 0.001 * i

    if model.x < 60:
        alpha = 0.05
    elif model.x >= 60 and model.x <= 150:
        alpha = 0.1
    elif model.x > 150:
        alpha = 0

    alpha_data[i] = alpha
    throttle_data[i] = throttle
    x_data[i] = model.x
    v_data[i] = model.v
    model.step(throttle,alpha)
# ==================================
#  Learner solution ends here
# ==================================

# Plot x vs t for visualization
plt.plot(t_data, x_data)
plt.show()
```

If you have implemented the vehicle model and inputs correctly, you should see that the vehicle crosses the ramp at ~15s where the throttle input begins to decrease.

The cell below will save the time and vehicle inputs as text file named $\textit{xdata.txt}$. To locate the file, change the end of your web directory to $\textit{/notebooks/Course_1_Module_4/xdata.txt}$

Once you are there, you can download the file and submit to the Coursera grader to complete this assessment.

```python
data = np.vstack([t_data, x_data]).T
np.savetxt('xdata.txt', data, delimiter=', ')
```

Congratulations! You have now completed the assessment! Feel free to test the vehicle model with different inputs in the cell below, and see what trajectories they form. In the next module, you will see the longitudinal model being used for speed control. See you there!

```python
sample_time = 0.01
time_end = 30
model.reset()

t_data = np.arange(0,time_end,sample_time)
x_data = np.zeros_like(t_data)

# ==================================
#  Test various inputs here
# ==================================
for i in range(t_data.shape[0]):

    model.step(0,0)

plt.axis('equal')
plt.plot(x_data, y_data)
plt.show()
```

```python

```

Course 1 - Introduction to Self Driving Cars/Longitudinal_Vehicle_Model.ipynb