# Integration By Parts

In this notebook, we extend our toolbox to evaluate integrals beyond the substitution rule.  We introduce another essential method, called integration by parts.

### 1. Motivation

Consider the following three integrals:
1. $\displaystyle \int \sin(x) \ dx$
2. $\displaystyle \int x \sin(x^2) \ dx$
3. $\displaystyle \int x \sin(x) \ dx$

We readily evaluate the first integral since we recognize that the integrand, $\sin(x)$, is the derivative of $-\cos(x)$.  That is, $$\displaystyle \int \sin(x) \ dx = -\cos(x) + C.$$

We can evaluate the second integral by applying the substitution $u = x^2$ (implying $du = 2 x \ dx$, or $x \ dx = \frac{1}{2} \ du$).  Go ahead, and give it a try.  You should obtain $$\int x \sin(x^2) \ dx = -\frac{1}{2} \cos(x^2) + C.$$

If you wish a refresher on the substitution rule, you may find it helpful to review the first or both of the following videos (from MATH 134):

```python
from IPython.display import YouTubeVideo
YouTubeVideo('4TgQk-X96gs')
```

```python
from IPython.display import YouTubeVideo
YouTubeVideo('Hs-rGwnsL8s')
```

But what about the third integral introduced above?  We do not recognize it as the derivative of a known function.  Further, no substitution will allow us to make progress.  It appears that we are stuck with the methods that we know.  We need a new method to evaluate the third integral.

### 2. Preview: Establishing the Need for a Method that Reverses the Product Rule

After the dust clears, we will see that the third integral will evaluate as follows:
$$\int x \sin(x) \ dx = - x \cos(x) + \sin(x) + C.$$
We can of course check this by differentiating the function on the right-hand side.  Since this function involves a product, $x \cos(x)$, we need to apply the product rule, as follows:
\begin{align}
\frac{d}{dx} \left[ -x \cos(x) + \sin(x) \right]
& = \frac{d}{dx} \left[ -x \cos(x) \right] + \frac{d}{dx} \sin(x) \\
& = \left[ (-x) ( -\sin(x) ) + (-1)( \cos(x) )\right] + \cos(x) \\
& = x \sin(x) - \cos(x) + \cos(x) \\
& = x \sin(x).
\end{align}
Based on the above, it appears that we need a method to reverse the product rule in a systematic way.

### 3. Introduction to Integration By Parts

In the next video, we will introduce you to integration by parts, show how this method reverses the product rule in a systematic way, and work through the example introduced above.

NOTE: The video was created before this notebook.  It also covers the motivation and preview, so you may skip through the first 6 minutes or so and start viewing at 6:09.  We will have this type of repetition for a few more pre-classes, after which the videos will fit seamlessly into the text.

```python
from IPython.display import YouTubeVideo
YouTubeVideo('R30VjQPJK9M')
```

### 4. Summary

Integration by parts is a method to reverse the product rule in the evaluation of integrals
$$ \int f(x) g'(x) \ dx = f(x) g(x) - \int g(x) f'(x) \ dx.$$

In differential form, the 'formula' or pattern for integration by parts is $$\int u \ dv = u \ v - \int v \ du.$$

- In general, choose $u$ and $dv$ such that the integral on the right-hand side is simpler than the original integral on the left-hand side.  In a few cases, it suffices for the integral on the right-hand side to be no more complicated than the original integral on the left-hand side.
- Rule of Thumb: Let $dv$ be the most complicated part of the integrand plus the differential (e.g., $dx$) for which you can find the antiderivative mentally.

### 5. Further Study

Please refer to Section 5.5 in the textbook for additional treatment of this topic.

### 6. Don't Forget

Don't forget to return to eClass to complete the pre-class quiz.

```python

```


# Examproject

In this notebook we will solve all the question in the 2019 exam set for introduction to programming and numerical analysis

```python
#import libraries
import numpy as np
import pandas as pd
from scipy import optimize
from matplotlib import pyplot as plt
import sympy as sm
import time
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm

#import local modules
from examproject.humancapital import HumanCapitalAccumulation
from examproject.as_ad import ASAD
from examproject.exchange_economy import ExchangeEconomy
```

## Human capital accumulation

In this first question we consider a simply two period consumer problem with human capital accumulation

### Question 1

We save the parameters given in the first question

```python
rho = 2
beta = 0.96
gamma = 0.1
w = 2
b = 1
Delta = 0.1

h_vec = np.linspace(0.1,1.5,100)
```

We call an instance of the class HumanCapitalAccumulation with the parameters given as above

```python
#call instance of class
question1 = HumanCapitalAccumulation(rho,beta,gamma, w, b, Delta, h_vec)

#call solution method for period2 and save data to dataframe
data1 = question1.solution(question1.period2)
```

```python
data1.plot(0,[2,3,4])
plt.grid(True)
plt.ylabel("u")
plt.title("Utility")

data1.plot(0,1)
plt.grid(True)
plt.ylabel("l")
plt.title("Labour supply")

data1[data1["unemp u"]<data1["work u"]].head(1)
```

If the consumer has 0.5667 or above human capital, then working in the second period providers higher utility

### Question 2

```python
#call solution method for period1 from class
data2 = question1.solution(question1.period1)
data2.head()
```

      h
      best l
      best u
      work u
      unemp u

      0
      0.100000
      0
      -1.96
      -5.613391
      -1.96

      1
      0.114141
      0
      -1.96
      -4.988852
      -1.96

      2
      0.128283
      0
      -1.96
      -4.501010
      -1.96

      3
      0.142424
      0
      -1.96
      -4.109180
      -1.96

      4
      0.156566
      0
      -1.96
      -3.787372
      -1.96

```python
#create h,u plot
data2.plot(0,[2,3,4])
plt.grid(True)
plt.ylabel("u")
plt.title("Utility")

#create h,l plot
data2.plot(0,1)
plt.grid(True)
plt.ylabel("l")
plt.title("Labour supply")

data2[data2["unemp u"]<data2["work u"]].head(1)
```

With human capital accumulation the workers attains higher expected utility for working at lower levels of human capital. The breakpoint for period 1 is 0.355 human capital

### Question 3

```python
#solving consumer problem for only period1
data3 = question1.solution(question1.utility)
```

```python
#slice data2 and data3 for entries in which working is optimal
slice1 = data2.loc[data2["best l"]==1]
slice2 = data3.loc[data3["best l"]==1]

#finding difference between slices
diff = slice1[~slice1["h"].isin(slice2["h"])]
```

```python
print(diff)
```

               h  best l    best u    work u  unemp u
    18  0.354545       1 -1.948004 -1.948004    -1.96
    19  0.368687       1 -1.890505 -1.890505    -1.96
    20  0.382828       1 -1.837070 -1.837070    -1.96
    21  0.396970       1 -1.787270 -1.787270    -1.96
    22  0.411111       1 -1.740733 -1.740733    -1.96
    23  0.425253       1 -1.697140 -1.697140    -1.96
    24  0.439394       1 -1.656210 -1.656210    -1.96
    25  0.453535       1 -1.617697 -1.617697    -1.96
    26  0.467677       1 -1.581387 -1.581387    -1.96
    27  0.481818       1 -1.547089 -1.547089    -1.96
    28  0.495960       1 -1.514633 -1.514633    -1.96
    29  0.510101       1 -1.483870 -1.483870    -1.96
    30  0.524242       1 -1.454666 -1.454666    -1.96
    31  0.538384       1 -1.426899 -1.426899    -1.96
    32  0.552525       1 -1.400463 -1.400463    -1.96

```python
#create figure for utility in period 1 without utility from period 2
fig = plt.figure(dpi=100)
ax = fig.add_subplot(1,1,1)
ax.plot(data3["h"],data3["best u"], label = "empl", color ="b")
ax.plot(data3["h"],data3["unemp u"], label = "unempl", color ="g")
ax.set_title("without period 2")
ax.set_xlabel("h_t")
ax.set_ylabel("u_t")
ax.grid(True)
ax.legend()

#create figure for utility in period 1 with utility from period 2
fig1 = plt.figure(dpi=100)
ax2 = fig1.add_subplot(1,1,1)
ax2.plot(data2["h"],data2["best u"], label = "empl", color="b")
ax2.plot(diff["h"],diff["best u"], label = "b>wh in p1", color="orange")
ax2.plot(data2["h"],data2["unemp u"], label = "unempl", color="g")
ax2.set_title("with period 2")
ax2.set_xlabel("h_t")
ax2.set_ylabel("u_t")
ax2.grid(True)
ax2.legend()
```

As seen from out plots there exists levels of humancapital, where the potential wage $h_t*w$ is lower than the benefits (marked with orange on lower plot). However, due to human capital accumulation, it becomes optimal to work due to the expected utility for period 2 being higher.

## AS-AD Model

In this question we solve and simulate an AS-AD model

### Question 1

We save the parameters given in the assignment and assign sympy symbols to variables

```python
#parameters
par = {}
par["alpha"] = 5.76
par["h"] = 0.5
par["b"] = 0.5
par["phi"] = 0
par["gamma"]=0.075

#call pretty printing
sm.init_printing(use_unicode=True)

#make symbols
pi_t = sm.symbols('pi_t')
pi_t1 = sm.symbols('pi_t-1')
alpha = sm.symbols("alpha")
h = sm.symbols("h")
b = sm.symbols("b")
phi = sm.symbols("phi")
gamma = sm.symbols("gamma")
v_t = sm.symbols('v_t')
y_t = sm.symbols('y_t')
y_t1 = sm.symbols('y_t-1')
s_t = sm.symbols('s_t')
s_t1 = sm.symbols('s_t-1')
```

We define the equilibrium condition and solve for the optimal $y_t$

```python
adsras = sm.Eq(1/(h*alpha)*(v_t-(1+b*alpha)*y_t), pi_t1 + gamma*y_t - phi*gamma*y_t1 + s_t - phi*s_t1)
adsras
```

```python
y_sol = sm.solve(adsras, y_t)
y_sol
```

We use the optimal $y_t$ to find the optimal $\pi_t$

```python
admod = 1/(h*alpha)*(v_t-(1+b*alpha)*y_sol[0])
admod
```

y_sol and admod gives us the optimal output gap and inflation gap in equilibrium expressed symbolically

### Question 2

We transfrom our symbolical solutions into working python functions with the lambdify. We use these functions to find the outputgap and inflation gap in equilibrium and after the demand shock

```python
#transforming sympy func to python func
_y_sol = sm.lambdify((y_t1, pi_t1, s_t, s_t1 ,v_t, alpha, gamma, h, phi, b), y_sol[0])
_admod = sm.lambdify((y_t1, pi_t1, s_t, s_t1 ,v_t, alpha, gamma, h, phi, b), admod)

#define y_sol
def y_sol(y_t1, pi_t1, s_t, s_t1 ,v_t):
    return _y_sol(y_t1, pi_t1, s_t, s_t1 ,v_t, alpha = par["alpha"], h = par["h"], b = par["b"], phi = par["phi"], gamma = par["gamma"])

#define pi_sol
def pi_sol(y_t1, pi_t1, s_t, s_t1 ,v_t):
    return _admod(y_t1, pi_t1, s_t, s_t1 ,v_t, alpha = par["alpha"], h = par["h"], b = par["b"], phi = par["phi"], gamma = par["gamma"])
```

```python
#solves equilibrium
y_equil = y_sol(y_t1 = 0, pi_t1 = 0, s_t = 0, s_t1 = 0, v_t = 0)
pi_equil = pi_sol(y_t1 = 0, pi_t1 = 0, s_t = 0, s_t1 = 0, v_t = 0)

#print results
print(f"y in equilibrium:",y_equil)
print(f"pi in equilibrium:",pi_equil)
```

    y in equilibrium: 0.0
    pi in equilibrium: 0.0

```python
#solve shock values
y_equil2 = y_sol(y_t1 = 0, pi_t1 = 0, s_t = 0, s_t1 = 0, v_t = 0.1)
pi_equil2 = pi_sol(y_t1 = 0, pi_t1 = 0, s_t = 0, s_t1 = 0, v_t = 0.1)

#print results
print(f"y after shock:",y_equil2)
print(f"pi after shock:",pi_equil2)
```

    y after shock: 0.0244140625
    pi after shock: 0.001831054687500002

Further we add a graph showing the shift in the AD curve

```python
#setup parameters
gamma = par["gamma"]
phi = par["phi"]
h = par["h"]
b = par["b"]
alpha = par["alpha"]
pi_t1 = 0
y_t1 = 0
s_t = 0
s_t1 = 0
v_t = 0

#define y vector
y_vec = np.linspace(-0.4, 0.4, 100)

#call instance of ASAD class with parameters given as above
asad1 = ASAD(gamma, phi, h, b, alpha)

#call sras method from ASAD class
sras1 = asad1.sras(pi_t1, y_vec, y_t1, s_t, s_t1)

#call ad method from ASAD class
ad1 = asad1.ad(v_t, y_vec)
ad2 = asad1.ad(0.1, y_vec)

#create plot with the data
fig = plt.figure(figsize=(12,8))
ax = fig.add_subplot(1,1,1)
ax.plot(y_vec,sras1, label="SRAS")
ax.plot(y_vec,ad1, label="AD0")
ax.plot(y_vec,ad2, label="AD1")
ax.grid(True)
ax.set_xlabel("outputgap")
ax.set_ylabel("inflationgap")
ax.set_title("AS-AD Curve")
ax.legend()
```

We see a slight shift upwards for the AD curve resulting in higher inflation and output gap

### Question 3

By use of the ar_simulation method we will simulate the development of the economy after the shock. The shocks are defined as autoregressive processes

```python
delta = 0.80
omega = 0.15

#call ar_simulation method from ASAD class
ardata = asad1.ar_simulation(100, omega, delta, phi = asad1.phi)
ardata.head()
```

      iteration
      pi
      y
      s
      v
      c
      x

      0
      0.0
      0.000000
      0.000000
      0.0
      0.0000
      0.0
      0.0

      1
      1.0
      0.001831
      0.024414
      0.0
      0.1000
      0.0
      0.1

      2
      2.0
      0.003199
      0.018244
      0.0
      0.0800
      0.0
      0.0

      3
      3.0
      0.004202
      0.013375
      0.0
      0.0640
      0.0
      0.0

      4
      4.0
      0.004918
      0.009545
      0.0
      0.0512
      0.0
      0.0

We plot the output gap and inflation gap as a function of time.

```python
fig = plt.figure(figsize = (10,6))
ax = fig.add_subplot(1,1,1)
ax.plot(ardata["iteration"], ardata["pi"])
ax.plot(ardata["iteration"], ardata["y"])
ax.set_xlabel("iteration")
ax.set_ylabel("pi and y")
ax.axhline(y=0,color="k")
ax.legend()
ax.grid(True)
```

We see a spike in the output gap over the first couple of iterations of the simulation. It then converges on a negative value before converging on the equilibrium. Inflationgap doesn't reach the same peak as the outputgap. The chance is slower, and it never becomes negative.

### Question 4

We simulate the stochastic process withe the stoch_simulation method from the ASAD class

```python
#call stoch_simulation method
stochdata = asad1.stoch_simulation(N = 1000, omega = omega, delta = delta, sigmax = 3.492, sigmac = 0.2, seed = 404, phi=asad1.phi)
stochdata.head(5)
```

      iteration
      pi
      y
      s
      v
      c
      x

      0
      0.0
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000

      1
      1.0
      0.001831
      0.024414
      0.000000
      0.100000
      0.000000
      0.100000

      2
      2.0
      0.012862
      0.985219
      -0.062861
      3.859693
      -0.062861
      3.779693

      3
      3.0
      0.031249
      -1.113500
      0.101899
      -4.230382
      0.111329
      -7.318137

      4
      4.0
      -0.228226
      -3.105331
      -0.026575
      -12.705975
      -0.041860
      -9.321669

From the data we plot the output gap and inflation gap as a function of time. We further create a plot to show the stochastic demand and supply shocks

```python
#plotting output and inflation
fig1 = plt.figure(figsize = (10,6))
ax1 = fig1.add_subplot(1,1,1)
ax1.plot(stochdata["iteration"], stochdata["y"])
ax1.plot(stochdata["iteration"], stochdata["pi"])
ax1.set_xlabel("iteration")
ax1.set_ylabel("pi and y")
ax1.legend()
ax1.grid(True)

#plotting the stochastic processes
stochdata.plot(0,[6,5])
```

From the simulated data we calculate several statistics about our stochastic model

```python
#create arrays
yarray = np.array(stochdata["y"])
piarray = np.array(stochdata["pi"])

#print statistics
print(f"y variance is {np.var(yarray):.4f}")
print(f"pi variance is {np.var(piarray):.4f}")
print(f"y-pi corr is {(np.corrcoef(yarray, piarray)[1,0]):.4f}")
print(f"y autocorr is {(np.corrcoef(yarray[1:], yarray[:-1])[1,0]):.4f}")
print(f"pi autocorr is {(np.corrcoef(piarray[1:], piarray[:-1])[1,0]):.4f}")
```

    y variance is 2.0500
    pi variance is 1.2927
    y-pi corr is -0.1353
    y autocorr is 0.7682
    pi autocorr is 0.9813

As seen from the plots and the statistics output is more volatile than inflation. The variance is significantly higher while being less autocorrelated. There's a slight negative correlation between inflation and output

### Question 5

In this question we examine the correlation between inflation and output as a function of the parameter $\phi$

```python
#define correlation as function of phi
def corr_phi_func():
    """Calculates the correlation of inflation and output for different values of phi
    Args:
        None

    Returns:
        corrarray (array): array containing correlation data between inflation and output
        phiarray (array): array containing phi data
    """
    #create phi vector
    phi_vec = np.linspace(0.1,1,100)

    #allocate space
    corrarray = np.empty(100)
    phiarray = np.empty(100)

    #loop for every phi value
    for i, phi in enumerate(phi_vec):

        #call instance of ASAD class
        asad2 = ASAD(gamma, phi, h, b, alpha)

        #call stoch_simulation method
        stoch2data = asad2.stoch_simulation(N = 1000, omega = omega, delta = delta, sigmax = 3.492, sigmac = 0.2, seed = 404, phi=asad2.phi)

        #save data to array
        corrarray[i] = np.corrcoef(stoch2data["y"], stoch2data["pi"])[1,0]
        phiarray[i] = phi

    return corrarray, phiarray

#call corr_phi_func and plot data
corrarray, phiarray = corr_phi_func()
plt.plot(phiarray, corrarray)
plt.xlabel("phi")
plt.ylabel("y-pi corr")
plt.grid(True)
plt.title("y-pi corr")
```

The correlation between inflation and output is increasing in in $\phi$

Below we define a function corr_func, which is used to find a $\phi$ that ensures a correlation of 0.31 between inflation and output

```python
def corr_func(phi):
    """Function made specifically for a minimizer function
    Args:
        phi (float): parameter

    Returns
        square difference of output-inflation correlations and correlation target
    """
    #call instance of ASAD class
    asad2 = ASAD(gamma, phi, h, b, alpha)

    #call stoch_simulation method
    stoch2data = asad2.stoch_simulation(N = 1000, omega = omega, delta = delta, sigmax = 3.492, sigmac = 0.2, seed = 404, phi = asad2.phi)

    return (np.corrcoef(stoch2data["y"], stoch2data["pi"])[1,0]-0.31)**2

#call a minimizer to minimize corr_func output
phi_root = optimize.minimize(corr_func,x0=0)
phi_root
```

          fun: 8.552675723015356e-16
     hess_inv: array([[0.03023896]])
          jac: array([8.65147877e-09])
      message: 'Optimization terminated successfully.'
         nfev: 48
          nit: 6
         njev: 16
       status: 0
      success: True
            x: array([0.96166711])

a $\phi$ value of 0.962 secures us a autocorrelation close to 0.31

### Question 6

In this question we try to construct a minimizer to solve our model for the same statistics as given for the US

```python
def corr_func2(vec):
    """Calculates the squared difference between select statistics and minimizer targets

    Args:
        vec (tuple): tuple of the variables phi, sigmax and sigmac

    Returns:
        sqlist (list): squared difference between statistics and the target
    """
    #unpack vec
    phi, sigmax, sigmac = vec

    #call instance of ASAD class
    asad2 = ASAD(gamma, phi, h, b, alpha)

    #call stoch_simulation method
    stoch2data = asad2.stoch_simulation(N = 1000, omega = omega, delta = delta, sigmax = sigmax, sigmac = sigmac, seed = 404, phi=phi)

    #create arrays from data
    yarray = np.array(stoch2data["y"])
    piarray = np.array(stoch2data["pi"])

    #calculate squared difference of statistics and targets
    sq_y_var = (np.var(yarray)-1.64)**2
    sq_pi_var = (np.var(piarray)-0.21)**2
    sq_ypi_corr = (np.corrcoef(yarray, piarray)[1,0]-0.31)**2
    sq_y_auto = (np.corrcoef(yarray[1:], yarray[:-1])[1,0]-0.84)**2
    sq_pi_auto = (np.corrcoef(piarray[1:], piarray[:-1])[1,0]-0.48)**2

    #save all to list
    sqlist = [sq_y_var, sq_pi_var, sq_ypi_corr, sq_y_auto, sq_pi_auto]

    return sqlist

def corr_min(vec):
    """sums the output of corr_func2

    Args:
        vec (tuple): tuple of the variables phi, sigmax and sigmac

    Returns:
        sqsum (float): sum of sqlist
    """
    #save list
    list = corr_func2(vec)

    #sum list
    sqsum = sum(list)

    return sqsum

#call a minimizer to minimize the output of corr_min function based on the vec tuple
phi_root = optimize.minimize(corr_min, x0 = [0,3.492,0.2], bounds = ((0,1), (10**-6,10**6), (10**-6,10**6)))
phi_root
```

          fun: 0.02357832638636935
     hess_inv: <3x3 LbfgsInvHessProduct with dtype=float64>
          jac: array([-3.62800068e-06, -2.98372438e-08, -3.75047216e-07])
      message: b'CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL'
         nfev: 92
          nit: 14
       status: 0
      success: True
            x: array([0.9717686 , 3.02301921, 0.21947609])

```python
#save minimizer parameters
phi_sol, sigmax_sol, sigmac_sol = phi_root.x

#call new stoch_simulation with new parameters
stochdata = asad1.stoch_simulation(N = 1000, omega = omega, delta = delta, sigmax = sigmax_sol, sigmac = sigmac_sol, seed = 404, phi=phi_sol)

#create arrays
yarray1 = np.array(stochdata["y"])
piarray1 = np.array(stochdata["pi"])

#calculate statistics
print(f"y variance is {np.var(yarray1):.4f}")
print(f"pi variance is {np.var(piarray1):.4f}")
print(f"y-pi corr is {(np.corrcoef(yarray1, piarray1)[1,0]):.4f}")
print(f"y autocorr is {(np.corrcoef(yarray1[1:], yarray1[:-1])[1,0]):.4f}")
print(f"pi autocorr is {(np.corrcoef(piarray1[1:], piarray1[:-1])[1,0]):.4f}")
```

    y variance is 1.6460
    pi variance is 0.0718
    y-pi corr is 0.2732
    y autocorr is 0.7845
    pi autocorr is 0.4845

Our minimizer gives us the parameters $\phi = 0.972$, sigmax = 3.023 and sigmac = 0.219.
Our estimate is somewhat close on some statistics compared to the US, however we still fall short on the variance for inflation, and have minor faults on output autocorrelation and inflation/output correlation.

We do not know for certain how precisely our models predict the US economy. The deviations can therefore be the result of a misspecified or ill used model.

## Exchange economy

In this question we solve a simple exchange economy

### Question 1

We save the parameters and other info given in the problem text

```python
# a. parameters
N = 50000
mu = np.array([3,2,1])
Sigma = np.array([[0.25, 0, 0], [0, 0.25, 0], [0, 0, 0.25]])
gamma = 0.8
zeta = 1

# b. random draws
seed = 1986
np.random.seed(seed)

# preferences
alphas = np.exp(np.random.multivariate_normal(mu, Sigma, size=N))
betas = alphas/np.reshape(np.sum(alphas,axis=1),(N,1))

# endowments
e1 = np.random.exponential(zeta,size=N)
e2 = np.random.exponential(zeta,size=N)
e3 = np.random.exponential(zeta,size=N)

#create list from endowments
elist = [e1,e2,e3]
```

Based on this data we create 3 histograms for the different budget shares for each good.

```python
#create figure
fig, axs = plt.subplots(1,3, figsize=(14,6))
fig.suptitle('Budget shares')

#define yaxis
ylims = (0,1700)

#create plot for good 1
axs[0].hist(betas[:,0], bins=150)
axs[0].set_title("beta_1")
axs[0].set_ylim(ylims)

#create plot for good 2
axs[1].hist(betas[:,1], bins=150)
axs[1].set_title("beta_2")
axs[1].set_ylim(ylims)

#create plot for good 3
axs[2].hist(betas[:,2], bins=150)
axs[2].set_title("beta_3")
axs[2].set_ylim(ylims)

plt.show()
```

```python
#print stats for the 3 goods
for i in range(3):
    print(f"mean for beta_{str(i+1)} is {np.mean(betas[:,i]):.4f}")
    print(f"variance for beta_{str(i+1)} is {np.var(betas[:,i]):.4f}")
```

    mean for beta_1 is 0.6423
    variance for beta_1 is 0.0182
    mean for beta_2 is 0.2581
    variance for beta_2 is 0.0142
    mean for beta_3 is 0.0996
    variance for beta_3 is 0.0033

Good one have the largest budget shares of the three goods with a mean of 0.64, the variance is also the highest. Good 3 has the lowest budget share and variance, while good 2 is in between good 1 and 3

### Question 2

Through our ExchangeEconomy class we create 3 arrays containing the data needed to make the excess demand plots

```python
#define price vector
price_vec = np.linspace(0.5,10,50)

#create grid
price1, price2 = np.meshgrid(price_vec, price_vec)

def plot3d():
    """calculates excess demand for the 3 goods
    Args:
        None

    Returns:
        array1 (array): excess demand for good 1
        array2 (array): excess demand for good 2
        array3 (array): excess demand for good 3
    """

    #allocate space
    array1 = np.empty((len(price_vec), len(price_vec)))
    array2 = np.empty((len(price_vec), len(price_vec)))
    array3 = np.empty((len(price_vec), len(price_vec)))

    for i, p1 in enumerate(price_vec):
        for j,p2 in enumerate(price_vec):

            #call instance of ExchangeEconomy class
            a = ExchangeEconomy(betas, elist, N, p1, p2)

            #call excess_demand() method
            z = a.excess_demand()

            #save data to arrays
            array1[i,j] = z[0]
            array2[i,j] = z[1]
            array3[i,j] = z[2]

    return array1, array2, array3

#call plot3d function and time it
%time array1,array2,array3 = plot3d()
```

    Wall time: 5.77 s

```python
#create the figure
fig = plt.figure(figsize=(14,8))
fig.suptitle('Excess Demand')

#create 3d plot for good 1
ax1 = fig.add_subplot(131,projection='3d')
cs1 = ax1.plot_surface(price1,price2,array1, cmap=cm.jet)
ax1.invert_xaxis()
ax1.set_xlabel("p1")
ax1.set_ylabel("p2")

#create 3d plot for good 2
ax2 = fig.add_subplot(132,projection='3d')
cs2 = ax2.plot_surface(price1,price2,array2, cmap=cm.jet)
ax2.invert_xaxis()
ax2.set_xlabel("p1")
ax2.set_ylabel("p2")

#create 3d plot for good 3
ax3 = fig.add_subplot(133,projection='3d')
cs3 = ax3.plot_surface(price1,price2,array3, cmap=cm.jet)
ax3.invert_xaxis()
ax3.set_xlabel("p1")
ax3.set_ylabel("p2")
```

The excess demand for good 1 is falling in p2 and rising in p1. The opposite is true for good 2. Good 3 is our numeraire and rises in both p1 and p2 as expected

### Question 3

To find the walras equlibrium prices we call the method walras, which uses the algorithm described in the problem text to numerically find the prices that minimizes the excess demand

```python
#we call an instance of the class ExchangeEconomy
b = ExchangeEconomy(betas, elist, N, 2, 2)

#we call the method walras and time it
%time z = b.walras(kappa=0.15, epsilon=10**-5)
print(z)
```

    Wall time: 20.5 s
    (6.490055134537612, 2.6166939963966915)

```python
#unpack z
p1, p2 = z

#call new instance with optimal p1 and p2
c = ExchangeEconomy(betas, elist, N, p1, p2, gamma)

#call excess_demand func
c.excess_demand()
```

    array([ 9.99628537e-06,  3.72918294e-06, -7.46345759e-05])

We get sufficient low excess demand functions with the prices given by the walras method

### Question 4

Once again we make of our class ExchangeEconomy and one of its methods called utility, which calculates the utility for a given equilibrium

```python
#call utility method
utilarray = c.utility()

#make histogram
plt.hist(utilarray,200)
plt.show()

#print stats
print(f"Mean is {np.mean(utilarray):.3f}")
print(f"Variance is {np.var(utilarray):.3f}")
```

Mean utility is a bit higher than 1. The distribution is also skewed to the right as seen in the plot.

### Question 5

We chose to use the mean of the endowments as the fixed and equal value for this question. We define the new parameters and call a new instance of our class ExchangeEconomy

```python
#define new endowments
e1new = np.zeros(N)+np.mean(e1)
e2new = np.zeros(N)+np.mean(e2)
e3new = np.zeros(N)+np.mean(e3)

#create new list of endowments
newelist = [e1new, e2new, e3new]

#call instance of ExchanceEconomy
e = ExchangeEconomy(betas, newelist, N, 2, 2, gamma)

#solve the walras equilibrium with the walras method
newp1, newp2 = e.walras(0.15, 10**-5)
```

```python
#call instance of class with new equilibrium prices
f = ExchangeEconomy(betas, newelist, N, newp1, newp2, gamma)

#calculate utility in equilibrium
util = f.utility()

#create histogram
plt.hist(util, 200)
plt.show

#print stats
print(f"Mean is {np.mean(util):.3f}")
print(f"Variance is {np.var(util):.3f}")
```

In the new more equal economy the mean is slightly higher while the variance is a lot lower. The change in endowments lead to a more fair distribution of utitility.

We further examine the effect of the parameter gamma by creating the function gammafind

```python
def gammafind():
    """calculates mean and variance for the utility based on the parameter gamma
    Args:
        None

    Returns:
        Data (dataframe): dataframe containing mean, var and gamma data
    """

    #allocate space
    gammalist = []
    meanlist = []
    varlist = []

    for i in range(100):

        #call instance of ExchangeEconomy
        g = ExchangeEconomy(betas, newelist, N, newp1, newp2, (i+1)/100)

        #calculate utility
        util = g.utility()

        #save data to lists
        gammalist.append((i+1)/100)
        meanlist.append(np.mean(util))
        varlist.append(np.var(util))

    #create dataframe from lists
    data = pd.DataFrame()
    data["gamma"]=gammalist
    data["mean"]=meanlist
    data["var"]=varlist

    return data

#call instance of gamma and time it
%time data = gammafind()
```

    Wall time: 16.6 s

```python
#plot data from gammafind

data.plot(0,1)
plt.grid(True)
plt.title("Mean of utility")
plt.ylabel("mean")

data.plot(0,2)
plt.grid(True)
plt.title("Variance of utility")
plt.ylabel("var")
```

Both mean and variance is increasing in gamma meaning higher gamma values results in higher average mean but also larger spread, which could be seen as a sort of inequality


```python
#Credits : Mahmoud Asem @Asem000  Septemeber 2021
```

```python
#Imports
import jax
import jax.numpy as jnp
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib as mpl
!pip install optax
import optax
```

    Collecting optax
      Downloading optax-0.0.9-py3-none-any.whl (118 kB)
         |████████████████████████████████| 118 kB 8.5 MB/s eta 0:00:01
    Collecting chex>=0.0.4
      Downloading chex-0.0.8-py3-none-any.whl (57 kB)
         |████████████████████████████████| 57 kB 6.1 MB/s  eta 0:00:01
    Requirement already satisfied: jaxlib>=0.1.37 in /usr/local/lib/python3.7/dist-packages (from optax) (0.1.70+cuda110)
    Requirement already satisfied: jax>=0.1.55 in /usr/local/lib/python3.7/dist-packages (from optax) (0.2.19)
    Requirement already satisfied: absl-py>=0.7.1 in /usr/local/lib/python3.7/dist-packages (from optax) (0.12.0)
    Requirement already satisfied: numpy>=1.18.0 in /usr/local/lib/python3.7/dist-packages (from optax) (1.19.5)
    Requirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from absl-py>=0.7.1->optax) (1.15.0)
    Requirement already satisfied: dm-tree>=0.1.5 in /usr/local/lib/python3.7/dist-packages (from chex>=0.0.4->optax) (0.1.6)
    Requirement already satisfied: toolz>=0.9.0 in /usr/local/lib/python3.7/dist-packages (from chex>=0.0.4->optax) (0.11.1)
    Requirement already satisfied: opt-einsum in /usr/local/lib/python3.7/dist-packages (from jax>=0.1.55->optax) (3.3.0)
    Requirement already satisfied: flatbuffers<3.0,>=1.12 in /usr/local/lib/python3.7/dist-packages (from jaxlib>=0.1.37->optax) (1.12)
    Requirement already satisfied: scipy in /usr/local/lib/python3.7/dist-packages (from jaxlib>=0.1.37->optax) (1.4.1)
    Installing collected packages: chex, optax
    Successfully installed chex-0.0.8 optax-0.0.9

```python
import sympy as sp
```

### Generate a a differential equation and its solution using SymPy

```python
t= sp.symbols('t')
f = sp.Function('y')
diffeq = sp.Eq(f(t).diff(t,t) + f(t).diff(t)-t*sp.cos(2*sp.pi*t),0)
sol = sp.simplify(sp.dsolve(diffeq,ics={f(0):1,f(t).diff(t).subs(t,0):10}).rhs)
```

```python
diffeq
```

$\displaystyle - t \cos{\left(2 \pi t \right)} + \frac{d}{d t} y{\left(t \right)} + \frac{d^{2}}{d t^{2}} y{\left(t \right)} = 0$

```python
sp.Eq(f(t).diff(t).subs(t,0),10)
```

$\displaystyle \left. \frac{d}{d t} y{\left(t \right)} \right|_{\substack{ t=0 }} = 10$

```python
sp.Eq(f(t).subs(t,0),1)
```

$\displaystyle y{\left(0 \right)} = 1$

```python
sp.Eq(f(t),sol)
```

$\displaystyle y{\left(t \right)} = \frac{\left(2 \pi t e^{t} \sin{\left(2 \pi t \right)} + 8 \pi^{3} t e^{t} \sin{\left(2 \pi t \right)} - 16 \pi^{4} t e^{t} \cos{\left(2 \pi t \right)} - 4 \pi^{2} t e^{t} \cos{\left(2 \pi t \right)} + 16 \pi^{3} e^{t} \sin{\left(2 \pi t \right)} + e^{t} \cos{\left(2 \pi t \right)} + 12 \pi^{2} e^{t} \cos{\left(2 \pi t \right)} - e^{t} + 36 \pi^{2} e^{t} + 336 \pi^{4} e^{t} + 704 \pi^{6} e^{t} - 640 \pi^{6} - 304 \pi^{4} - 44 \pi^{2}\right) e^{- t}}{4 \pi^{2} \left(1 + 8 \pi^{2} + 16 \pi^{4}\right)}$

```python
#verify solution
sp.simplify(-t*sp.cos(sp.pi*2*t)+sol.diff(t)+sol.diff(t,t))
```

$\displaystyle 0$

### Constructing the MLP

```python
N_b = 1
N_c = 100

tmin,tmax=0. ,jnp.pi

'''boundary conditions'''

# U[0] = 1
t_0 = jnp.ones([N_b,1],dtype='float32')*0.
ic_0 = jnp.ones_like(t_0)
IC_0 = jnp.concatenate([t_0,ic_0],axis=1)

# U_t[0] = 10
t_b1 = jnp.zeros([N_b,1])
bc_1 = jnp.ones_like(t_b1) * 10
BC_1 = jnp.concatenate([t_b1,bc_1],axis=1)

conds = [IC_0,BC_1]

#collocation points

key=jax.random.PRNGKey(0)

t_c = jax.random.uniform(key,minval=tmin,maxval=tmax,shape=(N_c,1))
colloc = t_c

def ODE_loss(t,u):
  u_t=lambda t:jax.grad(lambda t:jnp.sum(u(t)))(t)
  u_tt=lambda t:jax.grad(lambda t : jnp.sum(u_t(t)))(t)
  return -t*jnp.cos(2*jnp.pi*t) + u_t(t) + u_tt(t)
```

```python
def init_params(layers):
  keys = jax.random.split(jax.random.PRNGKey(0),len(layers)-1)
  params = list()
  for key,n_in,n_out in zip(keys,layers[:-1],layers[1:]):
    lb, ub = -(1 / jnp.sqrt(n_in)), (1 / jnp.sqrt(n_in)) # xavier initialization lower and upper bound
    W = lb + (ub-lb) * jax.random.uniform(key,shape=(n_in,n_out))
    B = jax.random.uniform(key,shape=(n_out,))
    params.append({'W':W,'B':B})
  return params

def fwd(params,t):
  X = jnp.concatenate([t],axis=1)
  *hidden,last = params
  for layer in hidden :
    X = jax.nn.tanh(X@layer['W']+layer['B'])
  return X@last['W'] + last['B']

@jax.jit
def MSE(true,pred):
  return jnp.mean((true-pred)**2)

def loss_fun(params,colloc,conds):
  t_c =colloc[:,[0]]
  ufunc = lambda t : fwd(params,t)
  ufunc_t=lambda t:jax.grad(lambda t:jnp.sum(ufunc(t)))(t)
  loss =jnp.mean(ODE_loss(t_c,ufunc) **2)

  t_ic,u_ic = conds[0][:,[0]],conds[0][:,[1]]
  loss += MSE(u_ic,ufunc(t_ic))

  t_bc,u_bc = conds[1][:,[0]],conds[1][:,[1]]
  loss += MSE(u_bc,ufunc_t(t_bc))

  return  loss

@jax.jit
def update(opt_state,params,colloc,conds):
  # Get the gradient w.r.t to MLP params
  grads=jax.jit(jax.grad(loss_fun,0))(params,colloc,conds)

  #Update params
  updates, opt_state = optimizer.update(grads, opt_state)
  params = optax.apply_updates(params, updates)

  #Update params
  # return jax.tree_multimap(lambda params,grads : params-LR*grads, params,grads)
  return opt_state,params

```

```python
# construct the MLP of  6 hidden layers of 8 neurons for each layer
params = init_params([1] + [20]*4+[1])
```

```python
lr = optax.piecewise_constant_schedule(1e-3,{10_000:5e-3,30_000:1e-3,50_000:5e-4,70_000:1e-4})
optimizer = optax.adam(lr)
opt_state = optimizer.init(params)
```

```python
%%time
epochs = 10_000
for _ in range(epochs):
  opt_state,params = update(opt_state,params,colloc,conds)

  # print loss and epoch info
  if _ %(100) ==0:
    print(f'Epoch={_}\tloss={loss_fun(params,colloc,conds):.3e}')
```

    Epoch=0	loss=1.026e+02
    Epoch=100	loss=1.500e+01
    Epoch=200	loss=7.508e+00
    Epoch=300	loss=5.177e+00
    Epoch=400	loss=3.458e+00
    Epoch=500	loss=2.615e+00
    Epoch=600	loss=2.391e+00
    Epoch=700	loss=2.254e+00
    Epoch=800	loss=2.133e+00
    Epoch=900	loss=2.013e+00
    Epoch=1000	loss=1.855e+00
    Epoch=1100	loss=1.549e+00
    Epoch=1200	loss=1.018e+00
    Epoch=1300	loss=9.915e-01
    Epoch=1400	loss=9.756e-01
    Epoch=1500	loss=9.618e-01
    Epoch=1600	loss=9.480e-01
    Epoch=1700	loss=9.327e-01
    Epoch=1800	loss=9.145e-01
    Epoch=1900	loss=8.902e-01
    Epoch=2000	loss=7.379e-01
    Epoch=2100	loss=3.372e-01
    Epoch=2200	loss=1.753e-02
    Epoch=2300	loss=4.962e-03
    Epoch=2400	loss=3.304e-03
    Epoch=2500	loss=2.670e-03
    Epoch=2600	loss=2.238e-03
    Epoch=2700	loss=1.883e-03
    Epoch=2800	loss=1.577e-03
    Epoch=2900	loss=1.311e-03
    Epoch=3000	loss=1.082e-03
    Epoch=3100	loss=8.856e-04
    Epoch=3200	loss=7.198e-04
    Epoch=3300	loss=5.825e-04
    Epoch=3400	loss=4.711e-04
    Epoch=3500	loss=6.700e-04
    Epoch=3600	loss=3.161e-04
    Epoch=3700	loss=3.296e-04
    Epoch=3800	loss=2.278e-04
    Epoch=3900	loss=2.032e-04
    Epoch=4000	loss=1.785e-04
    Epoch=4100	loss=1.624e-04
    Epoch=4200	loss=1.494e-04
    Epoch=4300	loss=1.377e-04
    Epoch=4400	loss=2.545e-04
    Epoch=4500	loss=1.195e-04
    Epoch=4600	loss=2.162e-03
    Epoch=4700	loss=1.041e-04
    Epoch=4800	loss=1.321e-04
    Epoch=4900	loss=9.035e-05
    Epoch=5000	loss=8.385e-05
    Epoch=5100	loss=8.181e-05
    Epoch=5200	loss=7.205e-05
    Epoch=5300	loss=2.472e-03
    Epoch=5400	loss=6.137e-05
    Epoch=5500	loss=6.695e-05
    Epoch=5600	loss=5.197e-05
    Epoch=5700	loss=4.714e-05
    Epoch=5800	loss=7.266e-05
    Epoch=5900	loss=3.922e-05
    Epoch=6000	loss=1.470e-03
    Epoch=6100	loss=3.234e-05
    Epoch=6200	loss=1.919e-03
    Epoch=6300	loss=2.676e-05
    Epoch=6400	loss=2.389e-05
    Epoch=6500	loss=2.301e-05
    Epoch=6600	loss=1.962e-05
    Epoch=6700	loss=3.187e-04
    Epoch=6800	loss=1.618e-05
    Epoch=6900	loss=2.400e-03
    Epoch=7000	loss=1.355e-05
    Epoch=7100	loss=2.970e-04
    Epoch=7200	loss=1.146e-05
    Epoch=7300	loss=1.040e-05
    Epoch=7400	loss=3.527e-05
    Epoch=7500	loss=9.057e-06
    Epoch=7600	loss=4.930e-04
    Epoch=7700	loss=3.244e-04
    Epoch=7800	loss=1.300e-03
    Epoch=7900	loss=7.630e-06
    Epoch=8000	loss=1.313e-05
    Epoch=8100	loss=1.451e-05
    Epoch=8200	loss=7.152e-06
    Epoch=8300	loss=6.462e-05
    Epoch=8400	loss=5.616e-03
    Epoch=8500	loss=6.044e-06
    Epoch=8600	loss=7.831e-05
    Epoch=8700	loss=5.532e-06
    Epoch=8800	loss=8.422e-05
    Epoch=8900	loss=1.461e-03
    Epoch=9000	loss=5.241e-06
    Epoch=9100	loss=2.537e-04
    Epoch=9200	loss=4.977e-06
    Epoch=9300	loss=4.646e-05
    Epoch=9400	loss=4.768e-06
    Epoch=9500	loss=1.605e-05
    Epoch=9600	loss=4.656e-06
    Epoch=9700	loss=3.441e-03
    Epoch=9800	loss=4.752e-06
    Epoch=9900	loss=6.980e-03
    CPU times: user 17.7 s, sys: 139 ms, total: 17.8 s
    Wall time: 17.7 s

```python
lam_sol= sp.lambdify(t,sol)

dT = 1e-3
Tf = jnp.pi
T = np.arange(0,Tf+dT,dT)

sym_sol =np.array([lam_sol(i) for i in T])

plt.plot(T,sym_sol,'--r',label='sympy solution')
plt.plot(T,fwd(params,T.reshape(-1,1))[:,0],'--k',label='NN solution')
plt.legend()
```


# TensorBoard Usage -- Updated

Looks like I need to relearn Tensorboard since my old code is all deprecated :/
Tutorial link: [link](https://www.tensorflow.org/get_started/summaries_and_tensorboard)

1. Create the TensorFlow graph that you'd like to collect summary data from, and decide which nodes you would like to annotate with summary operations.
```python
def variable_summaries(var):
  """Attach a lot of summaries to a Tensor (for TensorBoard visualization)."""
  with tf.name_scope('summaries'):
    mean = tf.reduce_mean(var)
    tf.summary.scalar('mean', mean)
    with tf.name_scope('stddev'):
      stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
    tf.summary.scalar('stddev', stddev)
    tf.summary.scalar('max', tf.reduce_max(var))
    tf.summary.scalar('min', tf.reduce_min(var))
    tf.summary.histogram('histogram', var)
```

2. To generate summaries, we need to run all of these summary nodes. Combine them into a single op that generates all the summary data.
```python
merged = tf.summary.merge_all()
```

3. Run the merged summary op, which will generate a serialized Summary protobuf object with all of your summary data at a given step. Finally, to write this summary data to disk, pass the summary protobuf to a tf.summary.FileWriter.
```python
train_writer = tf.summary.FileWriter(FLAGS.summaries_dir + '/train',
                                      sess.graph)
test_writer = tf.summary.FileWriter(FLAGS.summaries_dir + '/test')
tf.global_variables_initializer().run()
```

4. During training . . .
```python
summary, acc = sess.run([merged, accuracy], feed_dict=feed_dict(False))
    test_writer.add_summary(summary, i)
```

5. Launching tensorboard after training.
```
tensorboard --logdir=path/to/log-directory
```

Description of below code (docstring):

*A simple MNIST classifier which displays summaries in TensorBoard. This is an unimpressive MNIST model, but it is a good example of using tf.name\_scope to make a graph legible in the TensorBoard graph explorer, and of naming summary tags so that they are grouped meaningfully in TensorBoard. It demonstrates the functionality of every TensorBoard dashboard.*

```python
import argparse
import sys
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

```

```python
def train():
    mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)

    sess = tf.InteractiveSession()
    # Create a multilayer model.

    # Input placeholders
    with tf.name_scope('input'):
        x = tf.placeholder(tf.float32, [None, 784], name='x-input')
        y_ = tf.placeholder(tf.float32, [None, 10], name='y-input')

    with tf.name_scope('input_reshape'):
        image_shaped_input = tf.reshape(x, [-1, 28, 28, 1])
        tf.summary.image('input', image_shaped_input, 10)

    # We can't initialize these variables to 0 - the network will get stuck.
    def weight_variable(shape):
        """Create a weight variable with appropriate initialization."""
        initial = tf.truncated_normal(shape, stddev=0.1)
        return tf.Variable(initial)

    def bias_variable(shape):
        """Create a bias variable with appropriate initialization."""
        initial = tf.constant(0.1, shape=shape)
        return tf.Variable(initial)

    def variable_summaries(var):
        """Attach a lot of summaries to a Tensor (for TensorBoard visualization)."""
        with tf.name_scope('summaries'):
            mean = tf.reduce_mean(var)
            tf.summary.scalar('mean', mean)
            with tf.name_scope('stddev'):
                stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
            tf.summary.scalar('stddev', stddev)
            tf.summary.scalar('max', tf.reduce_max(var))
            tf.summary.scalar('min', tf.reduce_min(var))
            tf.summary.histogram('histogram', var)

    def nn_layer(input_tensor, input_dim, output_dim, layer_name, act=tf.nn.relu):
        """Reusable code for making a simple neural net layer.

        It does a matrix multiply, bias add, and then uses relu to nonlinearize.
        It also sets up name scoping so that the resultant graph is easy to read,
        and adds a number of summary ops.
        """
        # Adding a name scope ensures logical grouping of the layers in the graph.
        with tf.name_scope(layer_name):
            # This Variable will hold the state of the weights for the layer
            with tf.name_scope('weights'):
                weights = weight_variable([input_dim, output_dim])
                variable_summaries(weights)
            with tf.name_scope('biases'):
                biases = bias_variable([output_dim])
                variable_summaries(biases)
            with tf.name_scope('Wx_plus_b'):
                preactivate = tf.matmul(input_tensor, weights) + biases
                tf.summary.histogram('pre_activations', preactivate)
            activations = act(preactivate, name='activation')
            tf.summary.histogram('activations', activations)
            return activations

    hidden1 = nn_layer(x, 784, 500, 'layer1')

    with tf.name_scope('dropout'):
        keep_prob = tf.placeholder(tf.float32)
        tf.summary.scalar('dropout_keep_probability', keep_prob)
        dropped = tf.nn.dropout(hidden1, keep_prob)

    # Do not apply softmax activation yet, see below.
    y = nn_layer(dropped, 500, 10, 'layer2', act=tf.identity)

    with tf.name_scope('cross_entropy'):
        # The raw formulation of cross-entropy,
        #
        # tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(tf.softmax(y)),
        #                               reduction_indices=[1]))
        #
        # can be numerically unstable.
        #
        # So here we use tf.nn.softmax_cross_entropy_with_logits on the
        # raw outputs of the nn_layer above, and then average across
        # the batch.
        diff = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y)
        with tf.name_scope('total'):
            cross_entropy = tf.reduce_mean(diff)
    tf.summary.scalar('cross_entropy', cross_entropy)

    with tf.name_scope('train'):
        train_step = tf.train.AdamOptimizer(FLAGS.learning_rate).minimize(cross_entropy)

    with tf.name_scope('accuracy'):
        with tf.name_scope('correct_prediction'):
            correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
        with tf.name_scope('accuracy'):
            accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    tf.summary.scalar('accuracy', accuracy)

    # Merge all the summaries and write them out to /tmp/tensorflow/mnist/logs/mnist_with_summaries (by default)
    merged = tf.summary.merge_all()
    train_writer = tf.summary.FileWriter(FLAGS.log_dir + '/train', sess.graph)
    test_writer = tf.summary.FileWriter(FLAGS.log_dir + '/test')
    tf.global_variables_initializer().run()

    # Train the model, and also write summaries.
    # Every 10th step, measure test-set accuracy, and write test summaries
    # All other steps, run train_step on training data, & add training summaries

    def feed_dict(train):
        """Make a TensorFlow feed_dict: maps data onto Tensor placeholders."""
        if train or FLAGS.fake_data:
            xs, ys = mnist.train.next_batch(100, fake_data=FLAGS.fake_data)
            k = FLAGS.dropout
        else:
            xs, ys = mnist.test.images, mnist.test.labels
            k = 1.0
        return {x: xs, y_: ys, keep_prob: k}

    for i in range(FLAGS.max_steps):
        if i % 10 == 0:  # Record summaries and test-set accuracy
            summary, acc = sess.run([merged, accuracy], feed_dict=feed_dict(False))
            test_writer.add_summary(summary, i)
            print('Accuracy at step %s: %s' % (i, acc))
        else:  # Record train set summaries, and train
            if i % 100 == 99:  # Record execution stats
                run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
                run_metadata = tf.RunMetadata()
                summary, _ = sess.run([merged, train_step],
                                      feed_dict=feed_dict(True),
                                      options=run_options,
                                      run_metadata=run_metadata)
                train_writer.add_run_metadata(run_metadata, 'step%03d' % i)
                train_writer.add_summary(summary, i)
                print('Adding run metadata for', i)
            else:  # Record a summary
                summary, _ = sess.run([merged, train_step], feed_dict=feed_dict(True))
                train_writer.add_summary(summary, i)
    train_writer.close()
    test_writer.close()
```

```python
def main(_):
    if tf.gfile.Exists(FLAGS.log_dir):
        tf.gfile.DeleteRecursively(FLAGS.log_dir)
    tf.gfile.MakeDirs(FLAGS.log_dir)
    train()

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--fake_data', nargs='?', const=True, type=bool,
              default=False,
              help='If true, uses fake data for unit testing.')
    parser.add_argument('--max_steps', type=int, default=1000,
              help='Number of steps to run trainer.')
    parser.add_argument('--learning_rate', type=float, default=0.001,
              help='Initial learning rate')
    parser.add_argument('--dropout', type=float, default=0.9,
              help='Keep probability for training dropout.')
    parser.add_argument('--data_dir', type=str, default='/tmp/tensorflow/mnist/input_data',
                      help='Directory for storing input data')
    parser.add_argument('--log_dir', type=str, default='/tmp/tensorflow/mnist/logs/mnist_with_summaries',help='Summaries log directory')
    FLAGS, unparsed = parser.parse_known_args()
    tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)
```

# Trying to save the Graph Again

[Tutorial Link](https://www.tensorflow.org/programmers_guide/meta_graph)

In order for a Python object to be serialized to and from MetaGraphDef, the Python class must implement to_proto() and from_proto() methods, and register them with the system using register_proto_function:

```python
class Variable:

    def to_proto(self, export_scope=None):
        """Converts a `Variable` to a `VariableDef` protocol buffer.

        Args:
        export_scope: Optional `string`. Name scope to remove.

        Returns:
        A `VariableDef` protocol buffer, or `None` if the `Variable` is not
        in the specified name scope.
        """
        if (export_scope is None or self._variable.name.startswith(export_scope)):
            var_def                  = variable_pb2.VariableDef()
            var_def.variable_name    = ops.strip_name_scope(self._variable.name, export_scope)
            var_def.initializer_name = ops.strip_name_scope(self.initializer.name, export_scope)
            var_def.snapshot_name    = ops.strip_name_scope(self._snapshot.name, export_scope)
            if self._save_slice_info:
                var_def.save_slice_info_def.MergeFrom(self._save_slice_info.to_proto(export_scope=export_scope))
            return var_def
        else:
            return None

    @staticmethod
    def from_proto(variable_def, import_scope=None):
        """Returns a `Variable` object created from `variable_def`."""
        return Variable(variable_def=variable_def, import_scope=import_scope)

ops.register_proto_function(ops.GraphKeys.GLOBAL_VARIABLES,
                            proto_type=variable_pb2.VariableDef,
                            to_proto=Variable.to_proto,
                            from_proto=Variable.from_proto)
```

## Exporting a MetaGraph

The function signature, for reference:
```python
def export_meta_graph(filename=None, collection_list=None, as_text=False):
  """Writes `MetaGraphDef` to save_path/filename.

  Args:
    filename: Optional meta_graph filename including the path.
    collection_list: List of string keys to collect.
    as_text: If `True`, writes the meta_graph as an ASCII proto.

  Returns:
    A `MetaGraphDef` proto.
  """
```

You should store all variables you wanna get letter in ```collection```. Or, if you don't specify one, it will save everything (yeah do that).

How to export the default running graph:

```python
logits = tf.matmul(hidden2, weights) + biases
tf.add_to_collection("logits", logits)

init_all_op = tf.global_variables_initializer()

with tf.Session() as sess:
    # Initializes all the variables.
    sess.run(init_all_op)
    # Runs to logit.
    sess.run(logits)
    chatbot.saver.save(sess, 'my-save-dir/my-model-10000')
    # Generates MetaGraphDef.
    chatbot.saver.export_meta_graph('my-save-dir/my-model-10000.meta')

```

## Importing the MetaGraph and Resuming Training

#### Case: Import and Extend Graph

```python
with tf.Session() as sess:
    new_saver = tf.train.import_meta_graph('my-save-dir/my-model-10000.meta')
    new_saver.restore(sess, 'my-save-dir/my-model-10000')

    # Adds loss and train.
    labels = tf.constant(0, tf.int32, shape=[100], name="labels")
    batch_size = tf.size(labels)
    labels = tf.expand_dims(labels, 1)
    indices = tf.expand_dims(tf.range(0, batch_size), 1)
    concated = tf.concat([indices, labels], 1)
    onehot_labels = tf.sparse_to_dense(concated, tf.stack([batch_size, 10]), 1.0, 0.0)

    # Getting the logits variable back that we saved.
    logits = tf.get_collection("logits")[0]
    cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=onehot_labels, logits=logits, name="xentropy")
    loss = tf.reduce_mean(cross_entropy, name="xentropy_mean")

    tf.summary.scalar('loss', loss)
    # Creates the gradient descent optimizer with the given learning rate.
    optimizer = tf.train.GradientDescentOptimizer(0.01)

    # Runs train_op.
    train_op = optimizer.minimize(loss)
    sess.run(train_op)

```

#### Case: Retrieve Hyperparameters

```python
filename = ".".join([tf.latest_checkpoint(train_dir), "meta"])
tf.train.import_meta_graph(filename)
hparams = tf.get_collection("hparams")
```

# Sharing Variables

My version of TensorFlow's tutorial by the same name, but in English. The (sub)section names are identical; I compress the content.

## The Problem

**Reusing the same model on multiple inputs**: Let's say we make a function ```my_model(inputs)``` that creates a bunch of tf.Variable() objects and returns the final output tensor variable. Each time we call my_model(next_inputs), it is going to create a whole new model with new variables. This bad.

**How to fix?**: Rather than requiring classes to create a model and manage the variables, TensorFlow provides a *Variable scope* mechanism for sharing named variables while constructing a graph.

## Variable Scope

The mechanism consists of 2 main functions:
1. ```tf.get_variable(name, shape, initializer)``` creates or returns a variable with a given name.
2. ```tf.variable_scope(scope_name)``` manages namespaces **for names passed to tf.get_variable()**.

__Difference between name scope and variable scope__: [Here is a link](http://stackoverflow.com/questions/35919020/whats-the-difference-of-name-scope-and-a-variable-scope-in-tensorflow) to the best explanation I could find. The following is the main part:

"However, name scope is ignored by tf.get_variable. We can see that in the following example:

```python
with tf.name_scope("my_scope"):
    v1 = tf.get_variable("var1", [1], dtype=tf.float32)
    v2 = tf.Variable(1, name="var2", dtype=tf.float32)
    a = tf.add(v1, v2)

print(v1.name)  # var1:0
print(v2.name)  # my_scope/var2:0
print(a.name)   # my_scope/Add:0
```

The only way to place a variable accessed using tf.get_variable in a scope is to use variable scope, as in the following example:

```python
with tf.variable_scope("my_scope"):
    v1 = tf.get_variable("var1", [1], dtype=tf.float32)
    v2 = tf.Variable(1, name="var2", dtype=tf.float32)
    a = tf.add(v1, v2)

print(v1.name)  # my_scope/var1:0
print(v2.name)  # my_scope/var2:0
print(a.name)   # my_scope/Add:0
```

# RNN Documentation

## RNNCell

### Interface Specifications

It's extremely helpful to read the [source code for RNNCell](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/ops/rnn_cell_impl.py). Let's see a condensed list of the important methods and descriptions.

* __\_\_call\_\___: Run this RNN cell on inputs, starting from the given state.

```python
def __call__(self, inputs, state, scope=None):
  """Args:
      inputs: `2-D` tensor with shape `[batch_size x input_size]`.
      state: if `self.state_size` is an integer, this should be a `2-D Tensor`
        with shape `[batch_size x self.state_size]`.  Otherwise, if
        `self.state_size` is a tuple of integers, this should be a tuple
        with shapes `[batch_size x s] for s in self.state_size`.
      scope: VariableScope for the created subgraph; defaults to class name.
    Returns:
      A pair containing:
      - Output: A `2-D` tensor with shape `[batch_size x self.output_size]`.
      - New state: Either a single `2-D` tensor, or a tuple of tensors matching
        the arity and shapes of `state`.
    """
```

* __state\_size and output\_size__:

```python
  @property
  def state_size(self):
    """size(s) of state(s) used by this cell.
    It can be represented by an Integer, a TensorShape or a tuple of Integers
    or TensorShapes.
    """

  @property
  def output_size(self):
    """Integer or TensorShape: size of outputs produced by this cell."""
```

* __zero\_state__:
```python
  def zero_state(self, batch_size, dtype):
    """Return zero-filled state tensor(s).
    Args:
      batch_size: int, float, or unit Tensor representing the batch size.
      dtype: the data type to use for the state.
    Returns:
      If `state_size` is an int or TensorShape, then the return value is a
      `N-D` tensor of shape `[batch_size x state_size]` filled with zeros.
      If `state_size` is a nested list or tuple, then the return value is
      a nested list or tuple (of the same structure) of `2-D` tensors with
      the shapes `[batch_size x s]` for each s in `state_size`.
    """
    with ops.name_scope(type(self).__name__ + "ZeroState", values=[batch_size]):
      state_size = self.state_size
      return _zero_state_tensors(state_size, batch_size, dtype)
```

## Decoder Class -- Currently undocumented . . .

Here I'm going to give myself an overview of this strange new set of files in tf.contrib.seq2seq (master branch) that is not on the website.

### Basic Decoder

# Miscellaneous

## Review: logits terminology

1.  Logistic function:
$$\begin{align}
p(X) &= \frac{1}{1 + e^{-\beta^TX}}  \\
&= \frac{e^{ \beta^TX}}{1 + e^{ \beta^TX}}
\end{align}$$

2. Odds:
$$
\frac{p(X)}{1 - p(X)} = e^{\beta^T X}
$$

3. log-odds, "logit":
$$
\log\left(\frac{p(X)}{1 - p(X)} \right) = \beta^T X
$$

## Random Facts

* __tf.Variable vs tf.placeholder__: "The difference is that with tf.Variable you have to provide an initial value when you declare it. With tf.placeholder you don't have to provide an initial value and you can specify it at run time with the feed_dict argument inside Session.run" [Source](http://stackoverflow.com/questions/36693740/whats-the-difference-between-tf-placeholder-and-tf-variable)

## Embedding (All Related Topics)

### Embedding Lookup

Goal: Figure out how embedding lookup works. Ideally i'd like to show, as a toy example, an integer being mapped to its binary string. For example, with embed size of 5
```python
embed([2, 19]) --> [[0, 0, 0, 1, 0], [1, 0, 0, 1, 1]]
```

```python
import tensorflow as tf
import numpy as np

input_size = (1, 2) # arbitrary
embed_size = 5
vocab_size = 2**embed_size
input_array = np.random.randint(vocab_size, size=input_size)
expected_embedding = ['{0:b}'.format(inp) for inp in input_array.flatten()]
print("Input array:\n", input_array)
print("Expected embedding:\n", expected_embedding)
```

```python
tf.reset_default_graph()
sess = None
with tf.Session() as sess:
    params   = tf.get_variable('embed_tensor',
                               shape=[vocab_size, embed_size],
                               initializer=tf.contrib.layers.xavier_initializer())
    inputs    = tf.convert_to_tensor(input_array)
    embedding = tf.nn.embedding_lookup(params, inputs)

    sess.run(tf.global_variables_initializer())
    emb = sess.run(embedding)
    print(emb)

sess = None
tf.reset_default_graph()
```

    [[[ 0.30149388 -0.099668    0.14693063 -0.39335564  0.34846574]
      [-0.0505532  -0.21680753 -0.3851763  -0.1110653   0.0726169 ]]]

### Embedding Visualizer

```python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse
import sys
import os
import numpy as np
from tensorflow.contrib.tensorboard.plugins import projector
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
FLAGS = None
```

```python
def generate_embeddings():
    # Import data
    mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True, fake_data=FLAGS.fake_data)
    sess = tf.InteractiveSession()
    # Input set for Embedded TensorBoard visualization
    # Performed with cpu to conserve memory and processing power
    with tf.device("/cpu:0"):
        embedding = tf.Variable(tf.stack(mnist.test.images[:FLAGS.max_steps], axis=0),
                                trainable=False, name='embedding')
    tf.global_variables_initializer().run()
    saver = tf.train.Saver()
    writer = tf.summary.FileWriter(FLAGS.log_dir + '/projector', sess.graph)
    # Add embedding tensorboard visualization.
    config = projector.ProjectorConfig()
    embed= config.embeddings.add()
    embed.tensor_name = 'embedding:0'
    embed.metadata_path = os.path.join(FLAGS.log_dir + '/projector/metadata.tsv')
    embed.sprite.image_path = os.path.join(FLAGS.data_dir + '/mnist_10k_sprite.png')
    # Specify the width and height of a single thumbnail.
    embed.sprite.single_image_dim.extend([28, 28])
    projector.visualize_embeddings(writer, config)
    saver.save(sess, os.path.join(
        FLAGS.log_dir, 'projector/a_model.ckpt'), global_step=FLAGS.max_steps)

def generate_metadata_file():
    # Import data
    mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True, fake_data=FLAGS.fake_data)
    def save_metadata(file):
        with open(file, 'w') as f:
            for i in range(FLAGS.max_steps):
                c = np.nonzero(mnist.test.labels[::1])[1:][0][i]
                f.write('{}\n'.format(c))
    save_metadata(FLAGS.log_dir + '/projector/metadata.tsv')
```

```python
def main(_):
    if tf.gfile.Exists(FLAGS.log_dir + '/projector'):
        tf.gfile.DeleteRecursively(FLAGS.log_dir + '/projector')
        tf.gfile.MkDir(FLAGS.log_dir + '/projector')
    tf.gfile.MakeDirs(FLAGS.log_dir  + '/projector') # fix the directory to be created
    generate_metadata_file()
    generate_embeddings()

if __name__ == '__main__':
    BASE = '/home/brandon/mnist-tensorboard-embeddings/'
    parser = argparse.ArgumentParser()
    parser.add_argument('--fake_data', nargs='?', const=True, type=bool,
                        default=False, help='If true, uses fake data for unit testing.')
    parser.add_argument('--max_steps', type=int, default=10000, help='Number of steps to run trainer.')
    parser.add_argument('--data_dir', type=str, default=BASE+'mnist_data', help='Directory for storing input data')
    parser.add_argument('--log_dir', type=str, default=BASE+'logs', help='Summaries log directory')
    FLAGS, unparsed = parser.parse_known_args()
    tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)
```

#### YES: TensorBoard Summit Tutorial Code

```python
import os
import tensorflow as tf
import urllib
import urllib.request

LOGDIR = '/tmp/mnist_tutorial/'
GIST_URL = 'https://gist.githubusercontent.com/dandelionmane/4f02ab8f1451e276fea1f165a20336f1/raw/                            dfb8ee95b010480d56a73f324aca480b3820c180'

### MNIST EMBEDDINGS ###
mnist = tf.contrib.learn.datasets.mnist.read_data_sets(train_dir=LOGDIR + 'data', one_hot=True)
### Get a sprite and labels file for the embedding projector ###
urllib.request.urlretrieve(GIST_URL + 'labels_1024.tsv', LOGDIR + 'labels_1024.tsv')
urllib.request.urlretrieve(GIST_URL + 'sprite_1024.png', LOGDIR + 'sprite_1024.png')
```

    Extracting /tmp/mnist_tutorial/data/train-images-idx3-ubyte.gz
    Extracting /tmp/mnist_tutorial/data/train-labels-idx1-ubyte.gz
    Extracting /tmp/mnist_tutorial/data/t10k-images-idx3-ubyte.gz
    Extracting /tmp/mnist_tutorial/data/t10k-labels-idx1-ubyte.gz

    ('/tmp/mnist_tutorial/sprite_1024.png',
     <http.client.HTTPMessage at 0x7f59ff6014e0>)

```python
def conv_layer(input, size_in, size_out, name="conv"):
    with tf.name_scope(name):
        w = tf.Variable(tf.truncated_normal([5, 5, size_in, size_out], stddev=0.1), name="W")
        b = tf.Variable(tf.constant(0.1, shape=[size_out]), name="B")
        conv = tf.nn.conv2d(input, w, strides=[1, 1, 1, 1], padding="SAME")
        act = tf.nn.relu(conv + b)
        tf.summary.histogram("weights", w)
        tf.summary.histogram("biases", b)
        tf.summary.histogram("activations", act)
        return tf.nn.max_pool(act, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")

def fc_layer(input, size_in, size_out, name="fc"):
    with tf.name_scope(name):
        w = tf.Variable(tf.truncated_normal([size_in, size_out], stddev=0.1), name="W")
        b = tf.Variable(tf.constant(0.1, shape=[size_out]), name="B")
        act = tf.nn.relu(tf.matmul(input, w) + b)
        tf.summary.histogram("weights", w)
        tf.summary.histogram("biases", b)
        tf.summary.histogram("activations", act)
        return act
```

```python
def mnist_model(learning_rate, use_two_conv, use_two_fc, hparam):
    tf.reset_default_graph()
    sess = tf.Session()
    # Setup placeholders, and reshape the data
    x = tf.placeholder(tf.float32, shape=[None, 784], name="x")
    x_image = tf.reshape(x, [-1, 28, 28, 1])
    tf.summary.image('input', x_image, 3)
    y = tf.placeholder(tf.float32, shape=[None, 10], name="labels")
    if use_two_conv:
        conv1 = conv_layer(x_image, 1, 32, "conv1")
        conv_out = conv_layer(conv1, 32, 64, "conv2")
    else:
        conv1 = conv_layer(x_image, 1, 64, "conv")
        conv_out = tf.nn.max_pool(conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
    flattened = tf.reshape(conv_out, [-1, 7 * 7 * 64])
    if use_two_fc:
        fc1 = fc_layer(flattened, 7 * 7 * 64, 1024, "fc1")
        embedding_input = fc1
        embedding_size = 1024
        logits = fc_layer(fc1, 1024, 10, "fc2")
    else:
        embedding_input = flattened
        embedding_size = 7*7*64
        logits = fc_layer(flattened, 7*7*64, 10, "fc")
    with tf.name_scope("xent"):
        xent = tf.reduce_mean(
        tf.nn.softmax_cross_entropy_with_logits(
            logits=logits, labels=y), name="xent")
        tf.summary.scalar("xent", xent)
    with tf.name_scope("train"):
        train_step = tf.train.AdamOptimizer(learning_rate).minimize(xent)
    with tf.name_scope("accuracy"):
        correct_prediction = tf.equal(tf.argmax(logits, 1), tf.argmax(y, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
        tf.summary.scalar("accuracy", accuracy)
    summ = tf.summary.merge_all()

    # _________ EMBEDDING VARIABLE/TENSOR CREATED _________
    embedding = tf.Variable(tf.zeros([1024, embedding_size]), name="test_embedding")
    assignment = embedding.assign(embedding_input)

    # _________ SAVER CREATED _________
    saver = tf.train.Saver()

    # _________ INIT GLOBAL VARS IS RUN  _________
    sess.run(tf.global_variables_initializer())

    # _________ WRITER IS CREATED  _________
    writer = tf.summary.FileWriter(LOGDIR + hparam)
    writer.add_graph(sess.graph)

    # ================================================================
    # _________ PROJECTORCONFIG CONSTRUCTOR  _________
    config = tf.contrib.tensorboard.plugins.projector.ProjectorConfig()
    embedding_config = config.embeddings.add()
    embedding_config.tensor_name = embedding.name
    embedding_config.sprite.image_path = LOGDIR + 'sprite_1024.png'
    embedding_config.metadata_path = LOGDIR + 'labels_1024.tsv'
    # Specify the width and height of a single thumbnail.
    embedding_config.sprite.single_image_dim.extend([28, 28])
    tf.contrib.tensorboard.plugins.projector.visualize_embeddings(writer, config)
    # ================================================================

    for i in range(2001):
        batch = mnist.train.next_batch(100)
        if i % 5 == 0:
            [train_accuracy, s] = sess.run([accuracy, summ], feed_dict={x: batch[0], y: batch[1]})
            writer.add_summary(s, i)
        if i % 500 == 0:
            sess.run(assignment, feed_dict={x: mnist.test.images[:1024], y: mnist.test.labels[:1024]})
            saver.save(sess, os.path.join(LOGDIR, "model.ckpt"), i)
        sess.run(train_step, feed_dict={x: batch[0], y: batch[1]})
```

```python
def make_hparam_string(learning_rate, use_two_fc, use_two_conv):
    conv_param = "conv=2" if use_two_conv else "conv=1"
    fc_param = "fc=2" if use_two_fc else "fc=1"
    return "lr_%.0E,%s,%s" % (learning_rate, conv_param, fc_param)

def main():
    # You can try adding some more learning rates
    for learning_rate in [1E-4]:
        # Include "False" as a value to try different model architectures
        for use_two_fc in [True]:
            for use_two_conv in [True]:
                # Construct a hyperparameter string for each one (example: "lr_1E-3,fc=2,conv=2)
                hparam = make_hparam_string(learning_rate, use_two_fc, use_two_conv)
                print('Starting run for %s' % hparam)

                # Actually run with the new settings
                mnist_model(learning_rate, use_two_fc, use_two_conv, hparam)
```

### Misc. Ops Testing

tf concat with -1 as an argument? No documentation on it so guess we'll have to try it out.

```python
import tensorflow as tf
import numpy as np

a = tf.convert_to_tensor(np.arange(20).reshape(5, 4))
concat = tf.concat([[-1, 1], tf.shape(a)], 0)
```

```python
# Config (hopefully) fixes GPU not releasing memory issue in jupyter.
config = tf.ConfigProto()
config.gpu_options.allow_growth=True
with tf.Session(config=config) as sess:
    print(sess.run(concat))
```

    [-1  1  5  4]

```python

```

```python

```


# University of Applied Sciences Munich
## Kalman Filter Tutorial

---
(c) Lukas Köstler (lkskstlr@gmail.com)

IF YOU ARE DOING THE EXERCISE DO NOT READ THIS! TRY TO FIND A GOOD SOLUTION WITHOUT KNOWING THE PROBLEM PERFECTLY.

After you have run out of ideas, you can see how I created the data and then adapt your Kalman-Filter to fit this perfectly.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

## Physics
I use a basketball throw straight into the air with drag [1], [2]:

\begin{equation}
\ddot{x} = - g - \lambda \, \dot{x} \, \vert \dot{x} \vert
\end{equation}
with

\begin{align}
\text{time:} && t && s \\
\text{height:} && x = x(t) && m \\
\text{gravitational acc.:} && g = 9.81 && \frac{m}{s^2} \\
\text{lambda:} && \lambda = \frac{C_d \rho A}{2 m_{ball}} = \frac{0.54 \cdot 1.18 \cdot 0.75^2}{4  \pi \cdot 2 \cdot 0.6} \approx 0.024 && \frac{kg \,m^2}{m^3 \,kg} = \frac{1}{m} \\
\text{drag coefficient:} && C_d = 0.54 && 1 \\
\text{density (air)} && \rho = 1.18 && \frac{kg}{m^3} \\
\text{crossectional area} && A = \frac{0.75^2}{4 \pi} && m^2 \\
\text{mass ball} && m_{ball} = 0.6 && kg
\end{align}

So
\begin{equation}
\ddot{x} \approx - 9.81 - 0.024 \, \dot{x} \, \vert \dot{x} \vert
\end{equation}

[1]: http://www.uphysicsc.com/2011-GM-B-379.PDF
[2]: https://en.wikipedia.org/wiki/Drag_(physics)

```python
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (6, 3)

import numpy as np
from scipy.integrate import quad
%matplotlib notebook
```

#### Sources
+ http://www.uphysicsc.com/2011-GM-B-379.PDF

```python
# Constants and ODE solution
v0 = 120.0 / 3.6 # initial velocity upwards
_lambda = 0.54*1.18*0.75**2 / (4*np.pi*2*0.6) # coefficient
g = 9.81 # gravitational acceleration

# Upwards velocity
def v_t_up(t):
    return - np.sqrt(g / _lambda) * np.tan(np.sqrt(g*_lambda)*t - np.arctan(np.sqrt(_lambda/g)*v0))

# Downwards velocity
def v_t_down(t):
    return - np.sqrt(g / _lambda) * np.tanh(np.sqrt(g*_lambda)*t)
# Downwards delta x (sign omitted, i.e. > 0)
def delta_x_t_down(t):
    return 1.0 / _lambda * np.log(np.cosh(np.sqrt(g*_lambda)*t))

# Point of zero velocity (upwards -> downwards)
t1 = np.arctan(np.sqrt(_lambda/g)*v0) / np.sqrt(g*_lambda)
# Height at point of return
h1, error_h1 = quad(lambda t: v_t_up(t), 0, t1)
# Time for downwards movement
dt2 = np.arccosh(np.exp(h1*_lambda)) / np.sqrt(g*_lambda)
```

```python
# Generate data
dt = 0.1 # sampling rate
T = t1+dt2
N = int(np.ceil(T/dt))

fac = 7

tt = np.array(range(N+1))*dt
tt_h = np.array(range(fac*N+1))*dt/fac

xx = np.zeros_like(tt)
xx_h = np.zeros_like(tt_h)

vv = np.zeros_like(tt)
vv_h = np.zeros_like(tt_h)

# first
vv_h[0] = v_t_up(0.0)

h1_num = np.NAN
for k in range(1, len(tt_h)):
    if tt_h[k] < t1:
        _dx, _e = quad(v_t_up, tt_h[k-1], tt_h[k])

        xx_h[k] = xx_h[k-1] + _dx
        vv_h[k] = v_t_up(tt_h[k])
    elif tt_h[k-1] <= t1 <= tt_h[k]:
        _dx1, _e1 = quad(v_t_up, tt_h[k-1], t1)

        h1_num = xx_h[k-1] + _dx1

        xx_h[k] = h1_num - delta_x_t_down(tt_h[k]-t1)
        vv_h[k] = v_t_down(tt_h[k]-t1)

    elif t1 < tt_h[k]:
        xx_h[k] = h1_num - delta_x_t_down(tt_h[k]-t1)
        vv_h[k] = v_t_down(tt_h[k]-t1)
    else:
        print("ERROR")
```

```python
# Subsample
ind = range(0, fac*N+1, fac)

tt = tt_h[ind]
xx = xx_h[ind]
vv = vv_h[ind]
```

```python
# Noise
np.random.seed(0)

sigma_v_base = 1.0
sigma_v_range = 0.2

sigma_x_base = 1.5
sigma_x_range = 0.02

sigmas_v = sigma_v_base + sigma_v_range * np.abs(vv)
sigmas_x = sigma_x_base + sigma_x_range * np.abs(xx)

xx_n = xx + np.random.randn(len(xx))*sigmas_x
vv_n = vv + np.random.randn(len(vv))*sigmas_v

xx_n[11] = xx[11] + 15.126734
xx_n[40] = xx[40] + 15.156423

np.savetxt('time.csv', tt)
np.savetxt('measurements.csv', xx_n)
np.savetxt('velocity.csv', vv_n)
np.savetxt('groundtruth.csv', xx)
```

```python
# Plot
plt.figure()
plt.plot(tt_h, xx_h, '--')
plt.plot(tt, xx_n, '+-')
plt.grid('on')
plt.show()
```

    <IPython.core.display.Javascript object>

```python
plt.figure()
plt.plot(tt, sigmas_x, label="$\sigma_x$")
plt.plot(tt, sigmas_v, label="$\sigma_v$")
plt.show()
```

    <IPython.core.display.Javascript object>

```python
plt.figure()
plt.plot(tt_h, vv_h, tt, vv_n)
plt.show()
```

    <IPython.core.display.Javascript object>

```python
plt.figure()
plt.plot(tt[0:-1], np.diff(xx), tt[0:-1], vv[0:-1]*dt + 0.5*g*dt**2)
```

    <IPython.core.display.Javascript object>

    [<matplotlib.lines.Line2D at 0x109b14518>,
     <matplotlib.lines.Line2D at 0x109b142b0>]

```python
plt.figure()
plt.plot(tt[0:-1], np.abs(vv[0:-1]*dt-np.diff(xx)), label="no quad approx")
plt.plot(tt[0:-1], np.abs(vv[0:-1]*dt-0.5*g*dt**2-np.diff(xx)), label="quad approx")
```

    <IPython.core.display.Javascript object>

    [<matplotlib.lines.Line2D at 0x10ae30668>]

```python
plt.figure()
plt.plot(tt_h[0:-1], vv_h[0:-1]*(dt/fac)-np.diff(xx_h))
```

    <IPython.core.display.Javascript object>

    [<matplotlib.lines.Line2D at 0x10a0c2320>]

```python
np.sqrt( np.mean( (vv[0:-1]*dt-np.diff(xx))**2 ) )
```

    0.063363947970518447

```python
np.
```

    array([  0.        ,   3.16120312,   6.00982966,   8.58559556,
            10.9200373 ,  13.0385682 ,  14.96192158,  16.70718745,
            18.28857234,  19.7179658 ,  21.00536944,  22.15922588,
            23.18667382,  24.09374765,  24.88553454,  25.56629884,
            26.13958045,  26.60827258,  26.97468256,  27.24057867,
            27.40722489,  27.47540528,  27.4454549 ,  27.31759055,
            27.09240496,  26.77093319,  26.35463385,  25.84536308,
            25.24534223,  24.55712059,  23.78353419,  22.92766225,
            21.99278236,  20.98232583,  19.89983412,  18.74891739,
            17.53321593,  16.25636497,  14.92196332,  13.53354602,
            12.09456104,  10.60834996,   9.07813253,   7.50699473,
             5.89788016,   4.2535843 ,   2.57675137,   0.86987341,  -0.86470875])


# 運動学的方程式の導出 （オイラーパラメータ）

吉田勝俊（宇都宮大学）

## 参考情報
- [SymPyで代数演算してみる - Qiita](https://qiita.com/zawawahoge/items/1be137a8147902a5e6cb)
- [Matrices (linear algebra) — SymPy 1.6.2 documentation](https://docs.sympy.org/latest/modules/matrices/matrices.html)

```python
import sympy as sym #数式処理ライブラリ
sym.init_printing() #数式を綺麗に表示する設定
```

## Sympy 変数・関数

```python
om1, om2, om3 = sym.symbols('\omega_1 \omega_2 \omega_3')
q0, q1, q2, q3 = sym.symbols('q0 q1 q2 q3', Real=True, positive=True)
dq0, dq1, dq2, dq3 = sym.symbols('\dot{q}_0 \dot{q}_1 \dot{q}_2 \dot{q}_3', Real=True, positive=True)
```

```python
q = sym.Matrix([q0,q1,q2,q3]) #オイラーパラメータ
dq = sym.Matrix([dq0,dq1,dq2,dq3]) #その時間微分

display(q)
display(dq)
```

#### テスト

```python
q.dot(dq)
```

## クォータニオンによる回転行列

```python
Rot = sym.Matrix([
    [ 1 -2*q2**2 -2*q3**2,    2*q1*q2 -2*q0*q3, 2*q0*q2 +2*q1*q3],
    [ 2*q1*q2 +2*q0*q3, 1 -2*q1**2 -2*q3**2,   -2*q0*q1 +2*q2*q3],
    [-2*q0*q2 +2*q1*q3, 2*q0*q1 +2*q2*q3, 1 -2*q1**2 -2*q2**2]
])
Rot
```

### 回転行列の時間微分

```python
def sympy_dt_param(expr, param_vec, dparam_vec):
    """
    パラメータを時間の関数として時間微分する関数
        param_vec: パラメータベクトル sympy.Matrix([sympyのシンボル達])
        dparam_vec: パラメータベクトルの微分 sympy.Matrix([sympyのシンボル達])
    """
    dim = len(param_vec)
    result = expr

    # 仮の時間変数
    t = sym.symbols('t')

    # パラメータを時間関数で置換
    func_vec = []
    for i in range(dim):
        lab = 'param' + str(i) #仮の変数表示名
        fun = sym.symbols(lab, cls=sym.Function, Real=True)
        func_vec.append( fun(t) )

    func_vec = sym.Matrix(func_vec) #sympy型へ変換

    for i in range(dim):
        result = result.replace(param_vec[i],func_vec[i])

    # 時間微分を実行
    result = result.diff()
    dfunc_vec = func_vec.diff()

    # 時間関数を元のパラメータに戻す
    for i in range(dim):
        result = result.replace(dfunc_vec[i], dparam_vec[i])
        result = result.replace(func_vec[i], param_vec[i])

    return result
```

回転行列を実際に時間微分する

```python
dRot = sympy_dt_param(Rot, q, dq)
dRot
```

### 回転行列の逆行列

```python
invRot = Rot.transpose() #回転行列なので転置で求まる
invRot
```

### 角速度ベクトルの外積行列 $[\boldsymbol{\omega}\times]:=R^{-1}\dot{R}$

```python
OMcross = sym.simplify(invRot*dRot)
```

### 外積行列 $[\boldsymbol{\omega}\times]:=R^{-1}\dot{R}$ から角速度ベクトル $\boldsymbol{\omega}$ を抽出

```python
OMvec = sym.Matrix([OMcross[2,1],OMcross[0,2],OMcross[1,0]])
OMvec
```

### 行列表示 $\boldsymbol{\omega}=\Omega \dot{\boldsymbol{q}}$ の表現行列 $\Omega$

- 線形変換のヤコビ行列＝表現行列なので，ヤコビ行列を求める関数 [jacobian(X)](https://docs.sympy.org/latest/modules/matrices/matrices.html#sympy.matrices.matrices.MatrixCalculus.jacobian) を流用しました．
- $q_0$, $q_1$, $q_2$, $q_3$ が拘束条件下 $q_0^2+q_1^2+q_2^2+q_3^2=1$ で独立でなく，sympy の `simplify` のクセと相まって，以下の簡約化は危うい均衡状態にあります．ちょっと変えると，式形が変わります（数学的には等価だが）．

```python
tmp = OMvec
tmp = sym.Matrix([
    tmp[0].replace(q1, sym.sqrt(1-(q0**2 +q2**2 +q3**2))),
    tmp[1].replace(q2, sym.sqrt(1-(q0**2 +q1**2 +q3**2))),
    tmp[2].replace(q3, sym.sqrt(1-(q0**2 +q1**2 +q2**2))),
])
tmp = tmp.jacobian(dq)
tmp = sym.simplify(tmp)
tmp = tmp.replace(1, (q0**2 +q1**2 +q2**2 +q3**2))
tmp = sym.MutableMatrix(tmp)
# 綺麗にならない成分を微調整
tmp2 = tmp[0,1]
tmp2 = tmp2.replace(q0, sym.sqrt(1-(q1**2 +q2**2 +q3**2)))
tmp2 = sym.simplify(tmp2)
tmp2 = tmp2.replace(1, (q0**2 +q1**2 +q2**2 +q3**2))
tmp[0,1] = tmp2
# 綺麗にならない成分を微調整
tmp2 = tmp[1,2]
tmp2 = tmp2.replace(q0, sym.sqrt(1-(q1**2 +q2**2 +q3**2)))
tmp2 = sym.simplify(tmp2)
tmp2 = tmp2.replace(1, (q0**2 +q1**2 +q2**2 +q3**2))
tmp[1,2] = tmp2
# 綺麗にならない成分を微調整
tmp2 = tmp[2,3]
tmp2 = tmp2.replace(q0, sym.sqrt(1-(q1**2 +q2**2 +q3**2)))
tmp2 = sym.simplify(tmp2)
tmp2 = tmp2.replace(1, (q0**2 +q1**2 +q2**2 +q3**2))
tmp[2,3] = tmp2
OMmat = tmp
OMmat
```

### LaTeXソースの出力

```python
print(sym.latex(OMmat))
```

### $\Omega(\boldsymbol{q},\dot{\boldsymbol{q}})$ を $\dot{\boldsymbol{q}}$ について解き，運動学的方程式 $\dot{\boldsymbol{q}}=K(\boldsymbol{q},\boldsymbol{\omega})$ を導く

#### $\dot{\boldsymbol{q}}$ について解く
（注）若干，計算に時間が掛かる場合があります．

```python
var = dq
oms = sym.Matrix([om1, om2, om3])
equation1 = OMmat*var - oms
equation2 = q.dot(dq)
equations = equation1.row_insert(3, sym.Matrix([equation2]))
result = sym.solve(equations, var)
result
```

#### 解から$K(\boldsymbol{q},\boldsymbol{\omega})$を作る

```python
tmp = sym.Matrix([result[dq[i]] for i in range(4)])
tmp = tmp.replace(q0, sym.sqrt(1-q1**2-q2**2-q3**2))
tmp = sym.simplify(tmp)
tmp = tmp.replace(1, q0**2+q1**2+q2**2+q3**2)
Kfunc = tmp
Kfunc
```

### LaTeXソースの出力

```python
print(sym.latex(2*Kfunc))
```

## 運動学的方程式 $\dot{\boldsymbol{q}}=K(\boldsymbol{q},\boldsymbol{\omega})$ の行列表示

- 線形変換 $K(\boldsymbol{q},\boldsymbol{\omega})$ のヤコビ行列＝表現行列なので，ヤコビ行列を求める関数 [jacobian(X)](https://docs.sympy.org/latest/modules/matrices/matrices.html#sympy.matrices.matrices.MatrixCalculus.jacobian) を流用しました．

### $\dot{\boldsymbol{q}} = K_{\omega}\boldsymbol{q}$ 型の表現行列

```python
Kω = Kfunc.jacobian(q)
Kω
```

### $\dot{\boldsymbol{q}} = K_{\boldsymbol{q}}\boldsymbol{\omega}$ 型の表現行列

```python
Kq = Kfunc.jacobian(oms)
Kq
```

```python

```


# Método dos Mínimos Quadrados (MMQ)

## License

All content can be freely used and adapted under the terms of the
[Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/).

## Imports

Coloque **todos** os `import` na célula abaixo. Não se esqueça do `%matplotlib inline` para que os gráficos apareçam no notebook.

```python
import numpy as np
```

## IMPORTANTE

Agora que vocês sabem técnicas de programação defensiva, eu espero que todo o código que vocês fizerem abaixo utilizem essas técnicas. Crie docstrings para suas funções, cheque as entradas (quando for possível) e cheque as saídas. **Não esqueçam dos comentários**.

## Fabricando dados para teste

Para saber se nosso código está funcionando, precisamos fabricar alguns dados com parâmetros conhecidos. Vamos gerar dados que seguem a equação da reta:

$$
d_i = a x_i + b
$$

**IMPORTANTE**: Eu vou usar a biblioteca numpy as gerar os dados abaixo.
Vocês **não podem** utilizar o numpy para calcular a sua solução.
Uso do numpy deve ser conquistado com ~~sangue~~esforço.
O código abaixo serve como exemplo do que vocês poderão fazer ao utilizar o Python no seu trabalho (fora da aula).

```python
a = 10
b = 50
N = 50
# Vou utilizar a função linspace do numpy para facilitar a vida
# Essa função cria N valores igualmente espaçados entre dois números (5 e 50)
x = np.linspace(5, 50, N)
# Agora podemos usar os valores de x, a e b acima para simular dados observados
dados_obs = a*x + b
# Vamos adicionar erro aleatório aos dados para ficar mais interessante
# O erro seguirá uma distribuição normal com os seguintes parâmetros
media_erro = 0
std_erro = 20
# A linha abaixo faz com que os valores aleatórios não sejam verdadeiramente aleatórios
# veja https://en.wikipedia.org/wiki/Pseudorandom_number_generator
np.random.seed(42)
# Gera a lista de numéros aleatórios
erro = np.random.normal(loc=media_erro, scale=std_erro, size=len(dados_obs))
# Agora podemos adicionar o erro aos dados observados
dados_obs += erro
```

Utilize a célula abaixo para gerar um gráfico de círculos pretos (`ok`) de seus dados.

```python

```

## Forma matricial da equação da reta e a matriz Jacobiana

Temos uma equação da reta para cada valor de $x_i$:

$$
\begin{align}
d_1 &= ax_1 + b \\
d_2 &= ax_2 + b \\
\vdots \\
d_N &= ax_N + b \\
\end{align}
$$

Esse sistema pode ser escrito de forma matricial com os parâmetros sendo $a$ e $b$:

$$
\begin{bmatrix}
d_1 \\ d_2 \\ \vdots \\ d_N
\end{bmatrix} =
\begin{bmatrix}
x_1 & 1 \\
x_2 & 1 \\
\vdots & \vdots \\
x_N & 1
\end{bmatrix}
\begin{bmatrix}
a \\ b
\end{bmatrix}
$$

$$
\bar{d} = \bar{\bar{A}}\bar{p}
$$

## Tarefa

Faça uma função chamada `jacobiana` que calcule e retorne a matrix Jacobiana ($\bar{\bar{A}}$).

**Para pensar**: o que essa função deve receber como argumento? (**Dica**: ela só precisa de 1)

```python

```

```python

```

```python

```

### Resultado esperado

A célula abaixo testa a sua Jacobiana contra uma produzida pelo numpy.

```python
assert np.allclose(jacobiana(x), np.transpose([x, np.ones_like(x)]))
```

```python

```

```python

```

## Tarefa

Calcule dados preditos para o vetor de parâmetros definido abaixo **utilizando a forma matricial da equação**. Guarde o resultado em uma variável chamada `preditos`.

Faça um gráfico dos dados observados (gerados acima) como pontos pretos e os dados preditos que você calculou como uma linha vermelha.

**Dica**: utilize as funções que você criou na aula passada.

```python
p = [5, 15]
```

```python

```

```python

```

```python

```

### Resultado esperado

A célula abaixo testa seus resultados contra um calculado com o numpy.

```python
assert np.allclose(preditos, np.dot(jacobiana(x), p))
```

```python

```

```python

```

O gráfico deve ser parecido com o abaixo:

## Sistema de equações normais

A solução de mínimos quadrados é o vetor $\bar{p}$ que resolve o sistema linear abaixo (chamado de sistema de equações normais):

$$
\bar{\bar{A}}^T\bar{\bar{A}}\bar{p} = \bar{\bar{A}}^T\bar{d}^o
$$

Para resolver esse sistema, precisamos primeiramente calcular a matriz do sistema $\bar{\bar{A}}^T\bar{\bar{A}}$ e o vetor do lado direito $\bar{\bar{A}}^T\bar{d}^o$.

## Tarefa

Faça uma função chamada `eqnormais_sistema` que calcule e retorne a matriz $\bar{\bar{A}}^T\bar{\bar{A}}$ dada a matriz Jacobiana.

Utilize as funções criadas na aula anterior.

**Dica**: É possível saber quantas linhas e colunas o sistema deve conter. Cheque se o seu resulatdo possui esse número.

```python

```

```python

```

```python

```

### Resultado esperado

A célula abaixo testa seus resultados contra um calculado com o numpy.

```python
assert np.allclose(eqnormais_sistema(jacobiana(x)), np.transpose(jacobiana(x)).dot(jacobiana(x)))
```

```python

```

```python

```

## Tarefa

Faça uma função chamada `eqnormais_lado_direito` que calcule e retorne o vetor do lado direito do sistema de equações normais.

**Dicas**:

* Essa função deve receber 2 argumentos.
* O que essa função deve retornar é um vetor ou matriz?
* É possível saber o número de elementos que o resultado deve conter. Cheque esse número.

```python

```

```python

```

```python

```

### Resultado esperado

A célula abaixo testa seus resultados contra um calculado com o numpy.

```python
assert np.allclose(eqnormais_lado_direito(jacobiana(x), dados_obs), np.transpose(jacobiana(x)).dot(dados_obs))
```

```python

```

```python

```

## Solução de mínimos quadrados

Agora que temos o sistema de equações normais, podemos resolvê-lo numericamente para encontrar os valores de $a$ e $b$ que produzem a reta que melhor ajusta nossos dados.

## Tarefa

Faça uma função chamada `elim_gauss` que resolve um sistema de equações utilizando a eliminação de Gauss. Essa função deve receber como argumento a matriz do sistema e o vetor lado direito e retornar o vetor de solução.

**Dicas**:

* Cheque o número de elementos na matriz e no vetor.
* A matriz deve ser quadrada.

```python

```

```python

```

```python

```

### Resultado esperado

A célula abaixo testa seus resultados contra um calculado com o numpy.

```python
np.random.seed(42)
A_teste = np.random.uniform(10, 50, size=(21, 21))
x_teste = np.random.uniform(5, 20, size=21)
y_teste = A_teste.dot(x_teste)
assert np.allclose(elim_gauss(A_teste, y_teste), x_teste)
```

```python

```

```python

```

## Tarefa

Faça uma função `ajuste_reta` que recebe um vetor de valores de x e um de dados observados e retorna a solução de minimos quadrados $\bar{p}$ (vetor com os valores de $a$ e $b$ estimados).

Aplique essa função nos dados observados simulados acima. Cheque se a solução bate com o valor esperado (você pode fazer isso usando um `assert`).

Faça um gráfico dos dados observados (pontos pretos) pelos dados preditos pela solução que você obteve agora (linha vermelha). O gráfico deve conter uma legenda. A legenda para os dados preditos deve ser da forma "y = 234x + 244" (trocando os números pelos valores que você estimou).

**Dica**:r

* Quantos elementos deve ter o vetor retornado?
* Para inserir números em uma string (texto): `"y = {}".format(123.1)"` $\to$ `"y = 123.1"`
* Para formatar os números que você quer inserir numa string: `"y = {:.4f}".format(123.242524536362446353436335)` $\to$  `"y = 123.2425"`

```python

```

```python

```

```python

```

### Resultado esperado

Os valores estimados para $\bar{p}$ devem ser aproximadamente:

    [9.742296, 52.57738183]

O gráfico deve ser parecido com o abaixo:

## Tarefa Bônus

Podemos utilizar o  método dos mínimos quadrados para ajustar qualquer equação que seja linear com relação as parâmetros ($a$ e $b$ no caso da reta). Isso quer dizer que podemos ajustar uma parábola:

$$
d_i = ax_i^2 + bx + c
$$

Dessa vez, os parâmetros que queremos estimar são $a$, $b$ e $c$. Note que agora temos 3 parâmetros, não 2. Por isso, a Jacobiana terá 3 colunas ao invés de 2.

Faça ao menos as seguintes funções:

* `jacobiana_parabola`: calcula e retorna a matriz Jacobiana para o caso da parábola. Deve receber como argumento somente o vetor de coordenadas x.
* `ajuste_parabola`: calcula a solução de mínimos quadrados para o caso de uma parábola. Deve receber como argumento o vetor de coordenadas x e o vetor de dados. Deve retornar o vetor de parâmetros $\bar{p}$ estimado (contem os valores de $a$, $b$ e $c$)

Teste suas funções com os dados gerados abaixo. Note que estamos usando o mesmo vetor x. Gere gráficos dos dados fabricados e também dos dados preditos pela estimativa (como os que foram feitos acima).

O que acontece se você tentar ajustar uma reta aos dados da parábola? E se tentar ajustar uma parábola aos dados da reta?

**Dicas**:

* Você precisa criar outras funções para montar o sistema de equações normais e calcular a solução do sistema?

```python
a_par, b_par, c_par = 2, 20, 200
dados_parabola = a_par*x**2 + b_par*x + c_par + erro
```

```python

```

```python

```

```python

```

```python

```

### Resultado esperado

Os gráficos que você deve gerar deverão ser parecidos com os abaixo: