```python
#Importing standard packages
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
from scipy import interpolate
import sympy as sm
from sympy.plotting import plot
from sympy import lambdify
from scipy import linalg
from scipy import optimize
from scipy.optimize import brute
from scipy import interpolate
import time
import datetime
from IPython import display

#Importing custom functions
from examproject import *
from examproject.functions import *

#set format for output
sm.init_printing(use_unicode=True)

# Hvad gør disse?
%load_ext autoreload
%autoreload 2

np.seterr(all='print')
init_time=time.time()
```

Note that parts of the scripts takes a while to compute, so one should run all cells and start from the top.

# 1. Human capital accumulation

We are given the below model described in the exam problem.
____________________________________________________________________________________________

Consider a worker living in **two periods**, $t \in \{1,2\}$.

In each period she decides whether to **work ($l_t = 1$) or not ($l_t = 0$)**.

She can *not* borrow or save and thus **consumes all of her income** in each period.

If she **works** her **consumption** becomes:

$$c_t = w h_t l_t\,\,\text{if}\,\,l_t=1$$

where $w$ is **the wage rate** and $h_t$ is her **human capital**.

If she does **not work** her consumption becomes:

$$c_t = b\,\,\text{if}\,\,l_t=0$$

where $b$ is the **unemployment benefits**.

Her **utility of consumption** is:

$$ \frac{c_t^{1-\rho}}{1-\rho} $$

Her **disutility of working** is:

$$ \gamma l_t $$

From period 1 to period 2, she **accumulates human capital** according to:

$$ h_2 = h_1 + l_1 +
\begin{cases}
0 & \text{with prob. }0.5 \\
\Delta & \text{with prob. }0.5
\end{cases} \\
$$

where $\Delta$ is a **stochastic experience gain**.

In the **second period** the worker thus solves:

$$
\begin{eqnarray*}
v_{2}(h_{2}) & = &\max_{l_{2}} \frac{c_2^{1-\rho}}{1-\rho} - \gamma l_2
\\ & \text{s.t.} & \\
c_{2}& = & w h_2 l_2 \\
l_{2}& \in &\{0,1\}
\end{eqnarray*}
$$

In the **first period** the worker thus solves:

$$
\begin{eqnarray*}
v_{1}(h_{1}) &=& \max_{l_{1}} \frac{c_1^{1-\rho}}{1-\rho} - \gamma l_1 + \beta\mathbb{E}_{1}\left[v_2(h_2)\right]
\\ & \text{s.t.} & \\
c_1 &=& w h_1 l_1 \\
h_2 &=& h_1 + l_1 + \begin{cases}
0 & \text{with prob. }0.5\\
\Delta & \text{with prob. }0.5
\end{cases}\\
l_{1} &\in& \{0,1\}\\
\end{eqnarray*}
$$

where $\beta$ is the **discount factor** and $\mathbb{E}_{1}\left[v_2(h_2)\right]$ is the **expected value of living in period two**.

The **parameters** of the model are:

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

The **relevant levels of human capital** are:

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

_________________________________________________________________________________

## Solving the problem in period 2

In the module `functions.py` we have defines functions that:
* Compute consumption
* Computes combined utility of consumption and disutility of labour
* Computes utility in period 2
* Computes utility in period 1
* Solves for period 2
* Solves for period 1

In the module `visuals.py` there is a functon for plotting utility, labour and consumption as a function of human capital.
Using those, we now solve for period 2 and plot.

```python
# Computing vectors of human cpaital, utility, labour and consumption in period 2
# that serves for equlibrium
h2_vec,v2_vec,l2_vec,c2_vec=solve_period_2(w,rho,b,gamma,h_vec)
# Plotting
maximfigure(h2_vec,l2_vec,c2_vec,v2_vec,2)
```

The above tells us, that if the human capital in period 2 is above 0.65, the worker is willing to supply labour as the wage recieved is higher than the unemployment benefit.

## Solving for period 1

Using the defined functions, we now solve in period 1.

```python
# Computing vectors of human cpaital, utility, labour and consumption in period 1
# that serves for equlibrium
h1_vec,v1_vec,l1_vec,c1_vec=solve(w,rho,b,gamma,beta,Delta,h_vec)
# Plotting
maximfigure(h1_vec,l1_vec,c1_vec,v1_vec,1)
```

The above shows, that even for $h_1 \approx 0.4$, the worker choose to supply labour,
where the that threshold was $\approx 0.55$ in period 2.
But as the worker choose to supply labour in period 1, her human capital immediately rises with $l_1=1$ and $E[\Delta]=0.05$.

## Will the worker never work if her potential wage income is lower than the unemployment benefits she can get?

First, we'll show that even for a wage lower than the unemployment benefit, the worker will choose to supply labour. We'll set the wage to marginally lower than $b$

```python
# Computing vectors of human cpaital, utility, labour and consumption in period 2
# that serves for equlibrium, where wage is below benefits.
h1_vec,v1_vec,l1_vec,c1_vec=solve(b-0.0001,rho,b,gamma,beta,Delta,h_vec)
maximfigure(h1_vec,l1_vec,c1_vec,v1_vec,1)
```

If $h_1$ is high enough (around 0.8), the worker will choose to work. The reason for this, is the accumulation of human capital. Even though the worker recieves less than benefits in period 1, which reduces her potential consumption in period 1, the gain in experience leads to a higher effective wage in period 2, and as the worker is forward-looking, although while discounting future utility, this leads to willingly reducing consumption in period 1.
To show this, will solve the same problem as above, but without accumulation of capital.

```python
# Computing vectors of human cpaital, utility, labour and consumption in period 2
# that serves for equlibrium, where wage is below benefits and there is no accumulation
# of human capital.
h1_vec_no,v1_vec_no,l1_vec_no,c1_vec_no=solve(b-0.0001,rho,b,gamma,beta,Delta,h_vec,accum=False)
plt.plot(h1_vec,v1_vec,label='Utility')
plt.plot(h1_vec,v1_vec_no,label='Utility, if no accum.')
plt.grid()
plt.xlabel('$h_1$')
plt.ylabel('Utility')
plt.title('Utility')
plt.legend();
```

Above, we have plotted $v_1$ with and without accumulation of human capital.
Even if there is no accumulation, at some point, it will be more beneficial to supply labour.
But with accumulation, the total expected utility of both periods, $v_1$, is larger than without accumulation when $h_1 \gtrapprox 0.8 $, which is the same level of human capital, where the worker chose to supply labour despite of the income in period 1 being lower than benefits.

# AS-AD model

We are given the model described below

__________________________________________________________________

Consider the following **AS-AD model**. The **goods market equilibrium** is given by

$$ y_{t} = -\alpha r_{t} + v_{t} $$

where $y_{t}$ is the **output gap**, $r_{t}$ is the **ex ante real interest** and $v_{t}$ is a **demand disturbance**.

The central bank's **Taylor rule** is

$$ i_{t} = \pi_{t+1}^{e} + h \pi_{t} + b y_{t}$$

where $i_{t}$ is the **nominal interest rate**, $\pi_{t}$ is the **inflation gap**, and $\pi_{t+1}^{e}$ is the **expected inflation gap**.

The **ex ante real interest rate** is given by

$$ r_{t} = i_{t} - \pi_{t+1}^{e} $$

Together, the above implies that the **AD-curve** is

$$ \pi_{t} = \frac{1}{h\alpha}\left[v_{t} - (1+b\alpha)y_{t}\right]$$

Further, assume that the **short-run supply curve (SRAS)** is given by

$$ \pi_{t} = \pi_{t}^{e} + \gamma y_{t} + s_{t}$$

where $s_t$ is a **supply disturbance**.

**Inflation expectations are adaptive** and given by

$$ \pi_{t}^{e} = \phi\pi_{t-1}^{e} + (1-\phi)\pi_{t-1}$$

Together, this implies that the **SRAS-curve** can also be written as

$$ \pi_{t} = \pi_{t-1} + \gamma y_{t} - \phi\gamma y_{t-1} + s_{t} - \phi s_{t-1} $$

The **parameters** of the model are:

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

__________________________________________________________________

## Equilibrium values of $y_t$ and $\pi_t$

Defining variables.

```python
global i,pi,pi_l,epi,epi_l,epi_pl,r,y,y_l,v,v_l,s,s_l,alpha,h,b,phi,gamma

i = sm.symbols('i_t')            # Nominal interest rate
pi =sm.symbols('\pi_t')          # Inflation gap
pi_l =sm.symbols('pi_{t-1}')    # Inflation gap, lagged
epi =sm.symbols('pi_t')         # Expected inflation gap
epi_l =sm.symbols('pi_{t-1}')   # Expected inflation gap, lagged
epi_pl =sm.symbols('pi_{t-1}')  # Expected inflation gap, future
r = sm.symbols('r_t')            # Real interest rate
y =sm.symbols('y_t')             # Output gap
y_l =sm.symbols('y_{t-1}')       # Output gap, lagged
v = sm.symbols('v_t')            # Demand disturbance
v_l = sm.symbols('v_{t-1}')      # Demand disturbance, lagged
s = sm.symbols('s_t')            # Supply disturbance
s_l = sm.symbols('s_{t-1}')      # Supply disturbance, lagged
alpha = sm.symbols('alpha')
h = sm.symbols('h')
b = sm.symbols('b')
phi = sm.symbols('phi')
gamma = sm.symbols('gamma')
```

Defining the AD equation using `sympy`.

```python
AD=sm.Eq(pi,1/(h*alpha)*(v-(1+b*alpha)*y))
AD
```

Defining the SRAS equation

```python
SRAS=sm.Eq(pi,pi_l+gamma*y-phi*gamma*y_l+s-phi*s_l)
SRAS
```

Solving with respect to output gap and inflation gap.

```python
y_pi = sm.solve([AD,SRAS],[y,pi])
y_pi
```

## Illustrating equilibrium and a demand shock to it.

First we'll substitute in the values of the lagged output and inflation gap and the shocks.

Solving for equilibrium

```python
# Defining initial values.
par['v_t']=0
par['pi_{t-1}']=0
par['y_{t-1}']=0
par['v_t']=0
par['s_t']=0
par['s_{t-1}']=0

# Substituting in the equations and solving.
AD_eq=AD.subs(par)       #Implementing values set in parameter-dictionary in AD
SRAS_eq=SRAS.subs(par)   #Implementing values set in parameter-dictionary in SRAS
AD_eq, SRAS_eq
```

```python
eq_y_pi_eq = sm.solve([AD_eq,SRAS_eq],[y,pi])    #Solving for the initial equilibrium
eq_y_pi_eq
```

Solving for equilibrium after shock

```python
# Adding to the existing parameters.
par['v_t']=0.1
AD_sh=AD.subs(par)       #Implementing demand shock in AD
SRAS_sh=SRAS.subs(par)   #Implementing demand shock in SRAS
AD_sh, SRAS_sh
```

```python
eq_y_pi_sh = sm.solve([AD_sh,SRAS_sh],[y,pi]) #Solving for the shock
eq_y_pi_sh
```

Plotting results

```python
plot1=sm.plot(AD_eq.rhs, AD_sh.rhs, SRAS_eq.rhs,xlim=[-0.1,0.1], ylim=[-0.1,0.1],
                ylabel="$\pi_t$",xlabel="$y_t$",legend=True,aspect_ratio=(20,20),show=False)
plot1[1].line_color='b'
plot1[2].line_color='r'
plot1[0].label='AD'
plot1[1].label='AD, demand shock'
plot1[2].label='SRAS'
plot1.show()
```

The positive demand shock increases the demand for money and thus raises prices and thus inflation, that is, demand-pull inflation.

## Plotting impulse response functions

We start be initializing the equilibrium. Then we implement a persistent shock, and hereafter we find each equlibirum and fetch $y_t$ and $\pi_t$ and set the lagged variables for the next period.
The function `single_shock()` can be found the modules.

```python
single_shock(100,v_shock=0.1)
```

The demand shock raises output and thus increase inflation. The central bank, cf. their Taylor-rule, responds to this by raising the nominal interest rate. This entails that the output falls below the initial value as the central bank sets the interest high in order to reduce inflation, thus boom-bust shock.

## Stochastic shocks

We'll now let the demand and supply shock be stochastic and normally distributed. The function below is defined in the modules, and the output is the evolutions of output and inflation.

```python
y_bc, pi_bc = stochproc(1000,v_on=True,s_on=True)
```

The above shows the business cycles of inflation and output.
We'll now calculate different statistics of the above variables.

```python
statistics(y_bc,pi_bc)
```

    1. Variance of output
    1.9924598447871917
    2. Variance of inflation
    1.1842428723853964
    3. Correlation between output and inflation
    -0.1280566415980595
    4. Auto-correlation of output
    0.7820846187538315
    5. Auto-correlation of inflation
    0.981286025199083

## 2.5. Finding the value of $\phi$ that entails a correlation of 0.31 of output and inflation

We start by plotting the correlation as a function of $\phi$ utilizing the `stochproc()` from `functions.py`, as we can alter the value of $\phi$ in the function and supress the plot. This will take a while, so be patient.

```python
corrs_y_pi=[]                            #Creating empty list
phi_list=np.linspace(0,1,20).tolist()    #Creating a lineary spaced list of phis
for phi in np.linspace(0,1,20):
    y_bc, pi_bc = stochproc(1000,v_on=True,s_on=True,phis=phi,plot=False)  # Computing inflation and output process for each phi while supressing the plot output
    corr=np.corrcoef(y_bc, pi_bc)[1,0]                           # Computing the correlation coefficient.
    corrs_y_pi.append(corr)                                      # Appending to the list of corr.coeff.

```

```python
#Plotting phi with corr.coeff.
plt.plot(phi_list,corrs_y_pi)
plt.xlabel('$\phi$')
plt.ylabel('$Corr(\pi_t , y_t)$')
plt.title('$\phi$ and $Corr(\pi_t , y_t)$');
```

We'll define a function that returns the absolute value of difference of 0.31 and correlation as a function of $\phi$, and use that to optimize the value of $\phi$ in order to obtain the correct correlation coefficient.

Note that as we can visually see in the above plot, we know that the optimal $\phi \in (0.8,1)$ approximately, so we'll spare the user for some time, by including the this interval as bounds in the optimization.

 Note
For some reason, the cell below often fails and reports an issue of the type of variables in the function. The solution to this, is to reimport the custom functions from our modules. We've tried to find out what the issue is exactly, but without success. So the custom functions are reimported first.

```python
from examproject import *
from examproject.functions import *

obj=lambda phis: corr(phis)                                                 #Setting the objective function from the modul 'functions
phi_res=optimize.minimize_scalar(obj,0.9,method='bounded',bounds=[0.8,1]) #Optimizing
```

```python
print('Optimal phi:')
print(phi_res.x)
print('Correlation coefficent obtained, wait please:')
diff,corrcoef=corr(phi_res.x,corr_out=True)
print(corrcoef)
```

    Optimal phi:
    0.8886632821118373
    Correlation coefficent obtained, wait please:
    0.30999896257915516

So the value of $\phi$ such that the correlation between inflation and output is approx. 0.889 and the obtaind corr.coeff. is very close to 0.31.

##  Matching the US economy

We will now attempt to find the optimal combination of $\phi$, $\sigma ^x$ and $\sigma ^c$ that matches the statistics of inflation and output with the US economy.

Note that we are using functions from the module `functions.py`. First we use the function `us_econ(phi,sigmax,sigmac)`, that generates the statistics of inflation and output and compute the difference from the US economy of each statistic. The function then sums all of the differences. This is what we seek to minimize.
As we must use multiple argument to obtain the US-alike statistics, we use a list of of the variables as an argument to the function. So the objective function still only takes one argument, as needed in order to use it as an objective function with `scipy.optimize`.

But then we must also use a list of initial values and a list of bounds.

We have already tried altering the parameters one by one and at least we have found a local minimum. We use that as an initial guess.
Moreover, in order to spare time, we are using rather small ranges as bounds for the standard deviations.

Note that we timed the optimization process and it took approx. 15 minutes. Therefore, we have 'turned off' the function by putting the code block in a `while False:` and saved the results in seperate cells, so the user do not have to wait for it to finish. If one where to try it out, they should just alter the code to `while True:`
Arguably, there could more efficient methods to this problem, but we've have chosen to go with this one.

```python
while False:
    # Bounds for each item of the argument-list
    boundphi=(0,1)
    boundsigmax=(0,10)
    boundsigmac=(0,10)
    # Collecting bounds in one list.
    bounds=(boundphi,boundsigmax,boundsigmac)

    # Initial guess for each item of the argument-list
    init_stat=(0.9418,3.114,0.316)
    obj=lambda stat: us_econ(stat)                #Setting the objective function from the modul 'functions
    starttime=time.time()                        #Starting a 'stop watch'
    result = optimize.minimize(obj,init_stat,method='SLSQP',bounds=bounds,options={'disp':True}) # Optimizing
    endtime=time.time()                         #Stopping a 'stop watch'
    print('Time elapsed: '+str(endtid-starttime))  #Printing time elapsed
    print(result)                               #Printing results from optimization
    print(result.x)
```
Optimization terminated successfully.    (Exit mode 0)
            Current function value: 0.18571940419298055
            Iterations: 12
            Function evaluations: 99
            Gradient evaluations: 12
Time elapsed: 924.923330783844
     fun: 0.18571940419298055
     jac: array([-6.80936548, -1.11769019,  0.09201169])
 message: 'Optimization terminated successfully.'
    nfev: 99
     nit: 12
    njev: 12
  status: 0
 success: True
       x: array([0.95217138, 3.0970445 , 0.24371336])
[0.95217138, 3.0970445 , 0.24371336]
Above is the results from optimization.
The optimizing parameters are $\phi=0.952$, $\sigma_x=3.097$ and $\sigma_c=0.244$.
Below we save the optimizing array of parameters and put them in the simulation function while surpressing the plot and calculating the statistics.

```python
saved_result=np.array([0.95217138, 3.0970445 , 0.24371336])
```

```python
y_bc, pi_bc = stochproc(1000,v_on=True,s_on=True,plot=False,phis=saved_result[0],sigmax=saved_result[1],sigmac=saved_result[2])
statistics(y_bc,pi_bc)
```

    1. Variance of output
    1.6378866967276513
    2. Variance of inflation
    0.08166912291038368
    3. Correlation between output and inflation
    0.3092132875784681
    4. Auto-correlation of output
    0.7855115090476144
    5. Auto-correlation of inflation
    0.47999999105308583

$var(y_t)$, $corr(y_t\pi_t)$ and $corr(\pi_t\pi_{t-1})$ matches the US economy very well. The other statistics do not. However, the summed difference from the US economy is only 0.18 as visible from the output of the optimization.

# Exchange economy

We are given the model described below

___________________________________________________________

Consider an **exchange economy** with

1. 3 goods, $(x_1,x_2,x_3)$
2. $N$ consumers indexed by \\( j \in \{1,2,\dots,N\} \\)
3. Preferences are Cobb-Douglas with log-normally distributed coefficients

    $$ \begin{eqnarray*}
    u^{j}(x_{1},x_{2},x_{3}) &=&
    \left(x_{1}^{\beta_{1}^{j}}x_{2}^{\beta_{2}^{j}}x_{3}^{\beta_{3}^{j}}\right)^{\gamma}\\
     &  & \,\,\,\beta_{i}^{j}=\frac{\alpha_{i}^{j}}{\alpha_{1}^{j}+\alpha_{2}^{j}+\alpha_{3}^{j}} \\
     &  & \,\,\,\boldsymbol{\alpha}^{j}=(\alpha_{1}^{j},\alpha_{2}^{j},\alpha_{3}^{j}) \\
     &  & \,\,\,\log(\boldsymbol{\alpha}^j) \sim \mathcal{N}(\mu,\Sigma) \\
    \end{eqnarray*} $$

4. Endowments are exponentially distributed,

$$
\begin{eqnarray*}
\boldsymbol{e}^{j} &=& (e_{1}^{j},e_{2}^{j},e_{3}^{j}) \\
 &  & e_i^j \sim f, f(z;\zeta) =  1/\zeta \exp(-z/\zeta)
\end{eqnarray*}
$$

Let $p_3 = 1$ be the **numeraire**. The implied **demand functions** are:

$$
\begin{eqnarray*}
x_{i}^{\star j}(p_{1},p_{2},\boldsymbol{e}^{j})&=&\beta^{j}_i\frac{I^j}{p_{i}} \\
\end{eqnarray*}
$$

where consumer $j$'s income is

$$I^j = p_1 e_1^j + p_2 e_2^j +p_3 e_3^j$$

The **parameters** and **random preferences and endowments** are given by:

```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)
```

___________________________________________________________

## Illustrating consumers' budget shares of each good

As the utility function is a Cobb-Douglas, and therefore $\gamma$ represents a monotonic transformation of the preferences, $\beta$ represents the budget shares. Below we will then plot each $\beta^j_1$, $\beta^j_2$ and $\beta^j_3$.

```python
# Initializing lists of betas for each good
beta1=[]
beta2=[]
beta3=[]
for x in [1,2,3]: #Looping over goods
    for j in range(N): #Looping over consumers
        #betax is appended by j consumers x good beta coefficient.
        globals()['beta%s' % x].append(betas[j][x-1])
```

```python
# Plotting
fig=plt.figure(figsize=(12,8))
for axs in [1,2,3]:
    ax = fig.add_subplot(1,3,axs)
    ax.hist(globals()['beta%s' % axs],bins=100);
    ax.grid()
    ax.set_xlabel('$beta$')
    ax.set_ylabel('Frequency')
    ax.set_title('Good '+str(axs))
fig.tight_layout()
```

## Plotting the excess demands functions

We start by defining the demand functions for each good as functions. These are already given in the assignment. The excess demand is equal to the sum of demand for each good minus the sum of the initial endowment of each good.
Having defined the demand functions, we can now define the excess demand functions.  This is all done in the module `functions.py`.These functions enables us to plot the excess demand for each good as a function of the $p_1$ and $p_2$ combination.

```python
# We start by creating a mesh of the combination of prices.
p1_vec = np.linspace(0.1,10,200)
p2_vec = np.linspace(0.1,10,200)
p1_grid,p2_grid = np.meshgrid(p1_vec,p2_vec,indexing='ij')
# Initializing the grid of excess demand as a function of prices
ex1_grid = np.empty((200,200))
ex2_grid = np.empty((200,200))
ex3_grid = np.empty((200,200))

# Computing excess demand for each good as a function of prices
for i,p1 in enumerate(p1_vec):
    for j,p2 in enumerate(p2_vec):
        ex1_grid[i,j] = ex_demand_good1(betas,p1,p2,e1,e2,e3)
        ex2_grid[i,j] = ex_demand_good2(betas,p1,p2,e1,e2,e3)
        ex3_grid[i,j] = ex_demand_good3(betas,p1,p2,e1,e2,e3)
```

We've now generated the grids for the plots, so wewe will now plot.

```python
# Plotting
fig = plt.figure(figsize=(15,5))
ax = fig.add_subplot(1,3,1,projection='3d')
ax.plot_wireframe(p1_grid,p2_grid,ex1_grid, rstride = 10, cstride = 10)
ax.invert_xaxis()
ax.set_title('Excess demand of good 1')
ax.set_xlabel('$p_1$')
ax.set_ylabel('$p_2$')
ax.set_zlabel('$z_1$')
ax.xaxis.pane.fill = False
ax.yaxis.pane.fill = False
ax.zaxis.pane.fill = False

ax1 = fig.add_subplot(1,3,2,projection='3d')
ax1.plot_wireframe(p1_grid,p2_grid,ex2_grid, rstride = 10, cstride = 10)
ax1.invert_xaxis()
ax1.set_title('Excess demand of good 2')
ax1.set_xlabel('$p_1$')
ax1.set_ylabel('$p_2$')
ax1.set_zlabel('$z_3$')
ax1.xaxis.pane.fill = False
ax1.yaxis.pane.fill = False
ax1.zaxis.pane.fill = False

ax2 = fig.add_subplot(1,3,3,projection='3d')
ax2.plot_wireframe(p1_grid,p2_grid,ex3_grid, rstride = 10, cstride = 10)
ax2.invert_xaxis()
ax2.set_title('Excess demand of good 3')
ax2.set_xlabel('$p_1$')
ax2.set_ylabel('$p_2$')
ax2.set_zlabel('$z_2$')
ax2.xaxis.pane.fill = False
ax2.yaxis.pane.fill = False
ax2.zaxis.pane.fill = False
```

We see that as $p_1$ goes towards zero and especially when good 1 is relatively cheap over good 2, the excess demand for good 1 rises.
The same goes for good 2, when $p_2$ falls.
When the prices for both good 1 and good 2 rises, good 3 becomes relatively cheap and its demand rises.

## Finding the Walras-equilibrium

We will define a function, wher the user can give an intial guess of prices that entail equilibirum in the markets of good 1 and good 2.
We will use a tâtonnement process, which is done by `walras_taton()` from the module `functions.py`

```python
# Computing prices in the Walrasian equilibirum
p1_eq,p2_eq = walras_taton(5,5,betas,e1,e2,e3,N,epsilon=0.0001,kappa=0.2,max_iters=10000)
```

    Completed.
    The Walras-equilibrium prices are:
     p1: 6.490055255000671
     p2: 2.616694041336358

    Excess demand in market 1: -9.997473534895107e-05

    Excess demand in market 2: -3.72963331756182e-05

    Excess demand in market 3: 0.0007464346781489439

Acoording to Walras' Law, the sum of excess demand in all n markets is equal to zero. Therefore, if n-1 markets is in equlibrium and there exists no excess demand, the last market will also clear. Therefore, if will suffice to check for no excess demand in the first to markets, which is wwhat we have done above. We can perform a check by computing the excess demand in all markets, using the above prices.

The excess demand is very close to zero.

## Plotting the distribution of utility

We start by defining a function that calculates the utility based on the given utility function, `utility()` and then use `utility_plot_stat()`, which itself uses `utility()`, both defined in the module `functions.pu` to compute the vector of utlity and then plot it in a histogram.

```python
# Computing utility in the Walrasian equilibrium and plotting.
utility_plot_stat(betas,p1_eq,p2_eq,e1,e2,e3,gamma)
```

## The Walras equilibrium for equal endowments

We start by creating new vectors of endowments, where the given endowments are distributed equally.

```python
# Creating new endowments, where each consumers endowment of each good
    #is equal to the mean of the initial endowment of that good.
e1_eqe = np.zeros(N)+np.mean(e1)
e2_eqe = np.zeros(N)+np.mean(e2)
e3_eqe = np.zeros(N)+np.mean(e3)
```

We will now find the prices that entail a Walras equilibrium using `walras_taton()` from `functions.py`

```python
# Computing the prices in the Walrasian equlibrium given equal endowments.
p1_eq_eqe,p2_eq_eqe = walras_taton(5,5,betas,e1_eqe,e2_eqe,e3_eqe,N,epsilon=0.0001,kappa=0.2,max_iters=10000)
```

    Completed.
    The Walras-equilibrium prices are:
     p1: 6.485991169468596
     p2: 2.6172457122234225

    Excess demand in market 1: -9.99098047032021e-05

    Excess demand in market 2: -3.732368350028992e-05

    Excess demand in market 3: 0.0007456994062522426

We will now calculate the utility and plot the distribution and print the statistics using `utility_plot_stat()` from `functions.py`.

```python
# Computing utility in the Walrasian equilibrium and plotting.
utility_plot_stat(betas,p1_eq_eqe,p2_eq_eqe,e1_eqe,e2_eqe,e3_eqe,gamma)
```

Now that the endowments are equal across consumers the utility will be more evenly distributed as the variance of their preferences for each good is small. Such that the exchange within the economy is small. A more equal initital endowment then decreases the variance of the utility.

If we were to raise $\gamma$ the variance will be higher, as the effect on utility from exchange will be as diversified across consumers as for a low $\gamma$ but the small differences in utility caused by preferences will be magnified.

Below we generate a animated histogram (with percent and not frequency) across different values of $\gamma$. Run the cell below in order to rerun it.

```python
# Creating a list of gammas
gammas_list=[0.5]
i=0
while i<=14:
    gammas_list.append(gammas_list[-1]+0.5)
    i+=1

# Plotting the utility distribution through the list of gammas
for gammas in gammas_list:
    time.sleep(0.5)
    display.display(utility_plot_stat(betas,p1_eq_eqe,p2_eq_eqe,e1_eqe,e2_eqe,e3_eqe,gammas,perc=True));
    display.clear_output(wait=True);
```

Raising $\gamma$ had the effect as expected. When $\gamma$ is raised the difference in preferences entails a higher difference in utility. But the utility has very low variance from the start stemming from the equal initial endowment, so the difference in utility is solely caused by preferences, and these difference is higher for a higher $\gamma$.

Below we plot the effect on variance and mean of utility from $\gamma$ for both initial and equal endowments.

```python
# Creating a new list of gammas
gammas_list=[0.2]
i=0
while i<=10:
    gammas_list.append(gammas_list[-1]+0.1)
    i+=1

# Initializing lists of mean and variance of both distriutions of utility.
mean_init=[]
mean_equal=[]
var_init=[]
var_equal=[]
# Computing the statistics  through gammas using the utility-computing function in the module
for gammas in gammas_list:
    mean_init.append(np.mean(utilityexch(betas,p1_eq,p2_eq,e1,e2,e3,gammas)))
    mean_equal.append(np.mean(utilityexch(betas,p1_eq_eqe,p2_eq_eqe,e1_eqe,e2_eqe,e3_eqe,gammas)))
    var_init.append(np.var(utilityexch(betas,p1_eq,p2_eq,e1,e2,e3,gammas)))
    var_equal.append(np.var(utilityexch(betas,p1_eq_eqe,p2_eq_eqe,e1_eqe,e2_eqe,e3_eqe,gammas)))

# Plotting
fig = plt.figure(figsize=(10,5))
ax = fig.add_subplot(1,2,1)
l1=ax.plot(gammas_list,mean_init, label='Mean')[0]
l2=ax.plot(gammas_list,var_init, label='Variance')[0]
ax.set_title('Effect of gamma on utility, initial endowments')
ax.set_xlabel('$\gamma$')
ax.set_ylim((0,1.5))
ax1 = fig.add_subplot(1,2,2)
ax1.plot(gammas_list,mean_equal, label='Mean')
ax1.plot(gammas_list,var_equal, label='Variance')
ax1.set_title('Effect of gamma on utility, equal endowments')
ax1.set_xlabel('$\gamma$')
ax1.set_ylim((0,1.5))
fig.legend([l1,l2], ['Mean','Variance'], loc = (0.375, 0), ncol=2 )
```

Visible, having equal endowments means that there is less exchange and the difference in utility is small as its only caused by the small differences in preferences. So although a higher $\gamma$ magnifies the effect of theses differences, the change is minimal.

However, when using the initial endowments, the effect on the variance is much higher. Here the difference in utility post-exchange is both caused by difference in preferences but also the initial wealth of each consumer given their initial endowments. And these differences are magnified by a higher $\gamma$.

```python
finish_time=time.time()
print('Time elapsed for this notebook to run: '+str(datetime.timedelta(seconds=round(finish_time-init_time))))
```

    Time elapsed for this notebook to run: 0:13:21


# Predicting Noise (complaints)
#### Author: Sung Hoon Yang @ NYU

*Below is the abstract of the paper for which the prediction was conducted*

Noise in New York City is increasingly unbearable as evidenced by a growing number of noise complaints, while responses to noise complaints and therefore enforcement of The Noise Code has been hampered due to inability to handle sheer volumes. This capstone project aims to provide a data-driven optimization approach to improve the New York City Department of Environmental Protection’s (DEP) current scheduling process to better address noise complaints. To accomplish this, we will use machine learning to predict noise complaints and qualities that lead to violations for scheduling and routing optimization. Based on our discussion with DEP sponsors and preliminary analysis of 311 complaints, we suspect that our model will improve DEP metrics and that construction-related permits will be of highest variable importance to predicting and determining the validity of noise complaints. The implications of this analysis will allow DEP inspectors to improve time to complaint resolution, vanquish their backlog of complaints, and increase their issuance of violations.

Using publicly available data, we will build a neural network model to predict daily noise complaints per spatial bin as demarcated by Neighborhood Tabulation Areas. In this particular model, I will build a model that predicts the number of daily complaints that pertain to DEP's overseeing per 29 polygons of Manhattan. Below is the complete list of features, followed by that of spatial bins as demarcated by NTA shapefile.

### Features
* **wknd**: Weekend flag
* **holiday**: Holiday flag
* **hours_to_resolution_stdz**: Hours to Resolution standardized annually
* **AWND**: Average daily wind speed (tenths of meters per second)
* **PRCP**: Precipitation (tenths of mm)
* **SNOW**: Snowfall (mm)
* **SNWD**: Snow depth (mm)
* **TMAX**: Maximum temperature (tenths of degrees C)
* **WDF5**: Direction of fastest 5-second wind (degrees)
* **WSF5**: Fastest 5-second wind speed (tenths of meters per second)
* **d-1_cnt**: Complaint count of D-1
* **d-2_cnt**: Complaint count of D-2
* **d-3_cnt**: Complaint count of D-3
* **d-4_cnt**: Complaint count of D-4
* **d-5_cnt**: Complaint count of D-5
* **d-6_cnt**: Complaint count of D-6
* **d-7_cnt**: Complaint count of D-7
* **d-8_cnt**: Complaint count of D-8
* **d-9_cnt**: Complaint count of D-9
* **d-10_cnt**: Complaint count of D-10
* **d-11_cnt**: Complaint count of D-11
* **d-12_cnt**: Complaint count of D-12
* **d-13_cnt**: Complaint count of D-13
* **d-14_cnt**: Complaint count of D-14
* **d-15_cnt**: Complaint count of D-15
* **d-16_cnt**: Complaint count of D-16
* **d-17_cnt**: Complaint count of D-17
* **d-18_cnt**: Complaint count of D-18
* **d-19_cnt**: Complaint count of D-19
* **d-20_cnt**: Complaint count of D-20
* **d-21_cnt**: Complaint count of D-21
* **d-22_cnt**: Complaint count of D-22
* **d-23_cnt**: Complaint count of D-23
* **d-24_cnt**: Complaint count of D-24
* **d-25_cnt**: Complaint count of D-25
* **d-26_cnt**: Complaint count of D-26
* **d-27_cnt**: Complaint count of D-27
* **d-28_cnt**: Complaint count of D-28
* **ahv_open_cnt**: after hour variance open count
* **WT01**: unknown weather feature (omitted from data dictionary, but included)
* **WT02**: unknown weather feature (omitted from data dictionary, but included)
* **WT04**: unknown weather feature (omitted from data dictionary, but included)
* **WT08**: unknown weather feature (omitted from data dictionary, but included)

### Spatial Bins

* **MN01**:	Marble Hill-Inwood
* **MN03**:	Central Harlem North-Polo Grounds
* **MN04**:	Hamilton Heights
* **MN06**:	Manhattanville
* **MN09**:	Morningside Heights
* **MN11**:	Central Harlem South
* **MN12**:	Upper West Side
* **MN13**:	Hudson Yards-Chelsea-Flatiron-Union Square
* **MN14**:	Lincoln Square
* **MN15**:	Clinton
* **MN17**:	Midtown-Midtown South
* **MN19**:	Turtle Bay-East Midtown
* **MN20**:	Murray Hill-Kips Bay
* **MN21**:	Gramercy
* **MN22**:	East Village
* **MN23**:	West Village
* **MN24**:	SoHo-TriBeCa-Civic Center-Little Italy
* **MN25**:	Battery Park City-Lower Manhattan
* **MN27**:	Chinatown
* **MN28**:	Lower East Side
* **MN31**:	Lenox Hill-Roosevelt Island
* **MN32**:	Yorkville
* **MN33**:	East Harlem South
* **MN34**:	East Harlem North
* **MN35**:	Washington Heights North
* **MN36**:	Washington Heights South
* **MN40**:	Upper East Side-Carnegie Hill
* **MN50**:	Stuyvesant Town-Cooper Village
* **MN99**:	park-cemetery-etc-Manhattan

```python
#torch
import torch
device = 'cuda' if torch.cuda.is_available() else 'cpu'
if device == 'cuda':
    torch.set_default_tensor_type('torch.cuda.FloatTensor')
import gc
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader
from torch.utils.data import ConcatDataset
#wrangling
import numpy as np
import pandas as pd
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1000)
from itertools import product
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import normalize
from sklearn.decomposition import NMF
from sklearn.model_selection import train_test_split
import scipy
#dt
import datetime
from dateutil.relativedelta import relativedelta
#user setting
import sys
sys.path.insert(0, './analysis/311/duke')
# precipitation data
from prep_dta import _2010, _2011, _2012, _2013, _2014, _2015, _2016, _2017, _2018
from american_holidays import american_holidays as _american_holidays_str
#viz
import matplotlib
from matplotlib import pyplot as plt
%matplotlib inline
```

```python
spatial_dict = {'MN01':'Marble Hill-Inwood','MN03':'Central Harlem North-Polo Grounds','MN04':'Hamilton Heights','MN06':'Manhattanville','MN09':'Morningside Heights','MN11':'Central Harlem South','MN12':'Upper West Side','MN13':'Hudson Yards-Chelsea-Flatiron-Union Square','MN14':'Lincoln Square','MN15':'Clinton','MN17':'Midtown-Midtown South','MN19':'Turtle Bay-East Midtown','MN20':'Murray Hill-Kips Bay','MN21':'Gramercy','MN22':'East Village','MN23':'West Village','MN24':'SoHo-TriBeCa-Civic Center-Little Italy','MN25':'Battery Park City-Lower Manhattan','MN27':'Chinatown','MN28':'Lower East Side','MN31':'Lenox Hill-Roosevelt Island','MN32':'Yorkville','MN33':'East Harlem South','MN34':'East Harlem North','MN35':'Washington Heights North','MN36':'Washington Heights South','MN40':'Upper East Side-Carnegie Hill','MN50':'Stuyvesant Town-Cooper Village','MN99':'park-cemetery-etc-Manhattan',}
```

```python
features_stdz = pd.read_csv('./features_stdz.csv')
features_stdz.head()
```

```python
targets_cpu = np.loadtxt('./targets.txt')
```

```python
targets_cpu[0]
```

### Inspect Hessian

I am using MSELoss. Below shows that covariance matrix is essentially the Hessian of the Loss function.

$$
\begin{align}
\text{MSE} &= \frac{1}{N}||W\vec{x} - \vec{y}||^2 \quad \hat{y} :=W\vec{x}\\
\nabla_{W} \text{MSE} &= \frac{2}{N} ||W\vec{x} - \vec{y}||\vec{x}^T \\
\nabla_{W}^2 \text{MSE} &= \frac{1}{N} 2\vec{x}\vec{x}^T \\
\end{align}
$$

Let us examine the covariance matrix of the features.

```python
fig = plt.figure(figsize=(8, 8))
plt.clf()
plt.imshow(features_stdz.corr())
```

If the covariance matrix diverges from Identity, some eigenvectors may have a small magnitude, collapsing the corresponding axis of the Loss space. But, it's okay if you use `SGD` with a small learning rate, because you cannot examine the entire Hypothesis space. If repeated predictions result in similar minimization, we can be confident that it is close tothe global minimum.

```python
# Measure sizes
features = np.array(features_stdz)
S = 29
T = int(features.shape[0] / S)
Fe=features.shape[1]
H=Fe*S//4
batch_size=1
num_epochs = 40

features_nn = features.reshape(T, S*Fe)
```

```python
X_train, X_test, y_train, y_test = train_test_split(
    features_nn
    , targets_cpu
    , test_size=0.2
    , shuffle=False
)

X_train.shape, X_test.shape, y_train.shape, y_test.shape
```

```python
dev_cnt = torch.cuda.device_count()

train_ds = DataLoader(
    torch.from_numpy(np.concatenate((X_train, y_train), axis=1))
    , batch_size=batch_size
    , drop_last=True
    , shuffle=True
    , num_workers=dev_cnt*6
    , pin_memory=True
)

test_ds = DataLoader(
    torch.from_numpy(np.concatenate((X_test, y_test), axis=1))
    , batch_size=batch_size
    , drop_last=True
    , num_workers=dev_cnt*6
    , pin_memory=True
)
```

```python
# Here we define our model as a class
class ManhattanModel(nn.Module):
    def __init__(self, input_dim, feature_dim, hidden_dim, output_dim, num_layers, batch_size):
        """
        The model uses LSTM model as both Encoder/Decoder for this undercomplete Autoencoder model.
        * Batch normalization is used for all linear layers.
        * The autoencoder compresses the representation to hidden_dim/4,
            and then recovers the dimensionality back to hidden_dim
        * Softmax layer is used to output pseudo-probability density of complaint volume
            of each spatial bin on each day.
        """
        super(ManhattanModel, self).__init__()
        self.input_dim = input_dim
        self.feature_dim = feature_dim
        self.hidden_dim = hidden_dim
        self.output_dim = output_dim
        self.batch_size = batch_size
        self.num_layers = num_layers
        self.lstm1 = nn.LSTM(
            input_dim*feature_dim
            , self.hidden_dim
            , self.num_layers
            , dropout=0.1
            , batch_first=True
        )
        self.batchnorm1d_1 = nn.BatchNorm1d(batch_size)
        self.linear_1 = nn.Linear(self.hidden_dim, int(self.hidden_dim/2))
        self.batchnorm1d_2 = nn.BatchNorm1d(batch_size)
        self.linear_2 = nn.Linear(int(self.hidden_dim/2), int(self.hidden_dim/4))
        self.lstm2 = nn.LSTM(
            int(self.hidden_dim/4)
            , self.hidden_dim
            , self.num_layers
            , dropout=0.1
            , batch_first=True
        )
        self.batchnorm1d_3 = nn.BatchNorm1d(batch_size)
        self.linear_3 = nn.Linear(self.hidden_dim, int(self.hidden_dim/2))
        self.batchnorm1d_4 = nn.BatchNorm1d(batch_size)
        self.linear_4 = nn.Linear(int(self.hidden_dim/2), output_dim)

    def init_hidden(self):
        # This is what we'll initialise our hidden state as
        return (torch.zeros(self.num_layers, self.batch_size, self.hidden_dim),
                torch.zeros(self.num_layers, self.batch_size, self.hidden_dim))

    def forward(self, x, h1, h2):
        if h1 is None:
            x, h1 = self.lstm1(x)
        else:
            x, h1 = self.lstm1(x, h1)
        x = self.batchnorm1d_1(x)
        x = self.linear_1(x)
        x = F.relu(x)
        x = self.batchnorm1d_2(x)
        x = self.linear_2(x)
        x = F.relu(x)
        if h2 is None:
            x, h2 = self.lstm2(x)
        else:
            x, h2 = self.lstm2(x, h2)
        x = self.linear_3(x)
        x = self.batchnorm1d_3(x)
        x = F.relu(x)
        x = self.batchnorm1d_4(x)
        x = self.linear_4(x)
        x = F.relu(x)
        x = F.softmax(x, dim=-1)
        return x, h1, h2

```

```python
gc.collect()
torch.cuda.empty_cache()

model_s = ManhattanModel(S, Fe, H, S, 1, batch_size)
# model_s = ManhattanDenseNet(S, Fe, H, S, 2, batch_size)

model = nn.DataParallel(model_s, device_ids=range(dev_cnt)) #I want to use all available GPUs anyhow
# model = model.cuda()
model.to(device)
# Observe that all parameters are being optimized
optimizer_ft = optim.SGD(model.parameters(), lr=1e-3, momentum=0.9)

# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=8, gamma=0.1)

loss_fn = torch.nn.MSELoss()
optimiser = torch.optim.SGD(model.parameters(), lr=1e-3, weight_decay=1e-5)
losses = []

```

```python
d = torch.load('/scratch/shy256/capstone/cs_lstm_L1_AEDE_SM.pth')

model.load_state_dict(d)

model.eval()
losses = []
preds = []
targets = []
c_1, c_2 = None, None

with torch.no_grad():
    for batch_idx, data in enumerate(test_ds):
        X_test_bf = data[:, :Fe*S].reshape(1, batch_size, Fe*S).type(torch.cuda.FloatTensor)
        y_test_bf = data[:, Fe*S:].type(torch.cuda.FloatTensor)
        output, c_1, c_2 = model(X_test_bf, c_1, c_2)
        collapsed_size = torch.tensor(y_test_bf.shape).cumprod(dim=0)[-1].item()
        pred = output.view(collapsed_size)
        preds.extend(pred)
        target = F.softmax(y_test_bf.view(collapsed_size))
        targets.extend(target)
        loss = loss_fn(
            pred.squeeze()
            , target.squeeze()
        )
        losses.append(loss.item())
        loss_t = np.mean(losses[-1:])
#         print(
#             '[test] [b]:%s - [loss]:%s' \
#             % (batch_idx, str(loss_t))
#         )
```

```python
target_vals_orig = np.array(targets).reshape(len(targets) // 29, 29)
pred_vals_orig = np.array(preds).reshape(len(targets) // 29, 29)

```

```python
target_vals = target_vals_orig.copy()
pred_vals = pred_vals_orig.copy()

```

```python
import matplotlib.dates as mdates

plt.clf()
dts = pd.date_range(
    datetime.datetime.strptime("2017-08-27", '%Y-%m-%d') - relativedelta(days=len(losses) - 1)
    , datetime.datetime.strptime("2017-08-27", '%Y-%m-%d')
)
dts_str = [datetime.datetime.strftime(dt, '%Y-%m-%d') for dt in dts]
mn_nms = ["MN14" , "MN31" , "MN32" , "MN12" , "MN20" , "MN23" , "MN24" , "MN01" , "MN03" , "MN09" , "MN13" , "MN22" , "MN25" , "MN27" , "MN36" , "MN17" , "MN19" , "MN21" , "MN28" , "MN33" , "MN15" , "MN35" , "MN40" , "MN11" , "MN04" , "MN34" , "MN06" , "MN99" , "MN50"]

fig, axs = plt.subplots(
    S * 2 + 2, 1, figsize=(16, 80)
)
# plt.subplots_adjust(bottom=0.9, top=1)
axs[0].plot(dts, target_vals.sum(axis=1), label='target')
axs[0].plot(dts, pred_vals.sum(axis=1), label='prediction')
tot_avg = target_vals.sum(axis=1).astype(np.float64).mean()
tot_std = target_vals.sum(axis=1).astype(np.float64).std()
axs[0].set_ylabel(f'Total\nm:{tot_avg:.1f}\nstdev:{tot_std:.1f}')
# axs[1].plot(dts, losses)

tot_acc = np.array(torch.Tensor((pred_vals.sum(axis=1) / target_vals.sum(axis=1)).astype(np.float64)).cpu())
axs[1].plot(
    dts, tot_acc * 100, color='g', label='score'
)
axs[1].set_ylim(0, 100)
axs[1].set_ylabel('Score')
axs[1].bar(dts, tot_acc * 100, 1.1, color='g')
axs[1].bar(dts, 100-tot_acc*100, 1.1, bottom=tot_acc * 100, color='r')

losses = np.array(losses)
loss_avg = losses.mean()
loss_std = losses.std()
axs[1].set_ylabel('Score')

for i in range(0, S):
    if i == 15:
        break
    # replace zero with the mean to make the prediction accuracy graph look better
    index_both_zero = list(set(np.where(target_vals[:,i].astype(np.float64) == 0)[0]) & set(np.where(pred_vals[:,i].astype(np.float64) < 5e-1)[0]))
    np.put(target_vals[:,i], index_both_zero, [1] * len(index_both_zero))
    np.put(pred_vals[:,i], index_both_zero, [1] * len(index_both_zero))
    # if target of pred is zero and the other is not, assign 0.5 to coerce to accuracy
    target_vals[:,i][target_vals[:,i] <= 8e-1] = 1e-5
    pred_vals[:,i][pred_vals[:,i] <= 8e-1] = 1e-5
    denom = np.max(np.array([target_vals[:,i], pred_vals[:,i]]), axis=0)
    numer = np.min(np.array([target_vals[:,i], pred_vals[:,i]]), axis=0)
    acc = np.array(torch.Tensor((numer / denom).astype(np.float64)).cpu())
    t, p, nm = target_vals[:,i].astype(np.float64), pred_vals[:,i].astype(np.float64), mn_nms[i]
    i *= 2
    i += 2
    axs[i].plot(
        dts, t, 'r-'
        , dts, p, 'b-'
    )
    cnt_avg = t.mean()
    pred_cnt_avg = p.mean()
    cnt_std = t.std()
    pred_cnt_std = p.std()
    axs[i].set_ylabel(spatial_dict[nm].replace(' ', '\n') + f'\nm:{cnt_avg:.1f}/{pred_cnt_avg:.1f}\nstdev:{cnt_std:.1f}/{pred_cnt_std:.1f}')
    acc[~np.isfinite(acc)] = 0
    axs[i + 1].plot(
        dts, acc * 100, color='g', label='score'
    )
    axs[i + 1].set_ylim(0, 100)
    axs[i + 1].set_ylabel('Score')
    axs[i + 1].bar(dts, acc * 100, 1.1, color='g')
    axs[i + 1].bar(dts, 100-acc*100, 1.1, bottom=acc * 100, color='r')
plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.25,
                    wspace=0.05)
plt.savefig('./test_result_b1-shuf_manhattan_dn.png')
# plt.show()
```

```python
target_vals = target_vals_orig.copy()
pred_vals = pred_vals_orig.copy()

```

```python
# see if I can do better by using the aggregate ranking
static_ranking = np.flip(
    np.array(list([spatial_dict[k] for k in mn_nms]))[np.argsort(target_vals.sum(axis=0).astype(np.float64))], 0)[:15]
static_ranking
```

```python
# Check the frequentists of the Model's Dynamic Ranking
a = []
[a.extend(list(frozenset(np.flip(np.array(list([spatial_dict[k] for k in mn_nms]))[np.argsort(target_vals[II].astype(np.float64))], 0)[:15]))) for II in range(0, 200)]

pd.Series(a).unique()

```

```python
A = []
static_ranking_arr = []
for i in range(406):
    v = len(list(set(np.flip(np.array(list([spatial_dict[k] for k in mn_nms]))[np.argsort(target_vals[i].astype(np.float64))], 0)[:15]) & \
set(np.flip(np.array(list([spatial_dict[k] for k in mn_nms]))[np.argsort(pred_vals[i].astype(np.float64))], 0)[:15]))) / 15
    A.append(v)
    v = len(list(set(np.flip(np.array(list(spatial_dict.values()))[np.argsort(target_vals[i].astype(np.float64))], 0)[:15]) & \
set(static_ranking))) / 15
    static_ranking_arr.append(v)

fig, axs = plt.subplots(1, 2, figsize=(16, 4))
A = np.array(A)
axs[0].plot(A, color='#afeeee')
model_mean = np.array(A).mean()
axs[0].set_title(f'Prediction with Model\nmean accuracy: {model_mean:.3f}')
axs[0].set_ylim(0, 1)
axs[0].set_xlabel('Days')
axs[0].set_ylabel('Score')
axs[0].bar(np.arange(A.size), A, 1.1, color='#afeeee')
axs[0].bar(np.arange(A.size), 1-A, 1.1, bottom=A, color='magenta')

static_ranking_arr = np.array(static_ranking_arr)
axs[1].plot(static_ranking_arr, color='#afeeee')
static_ranking_mean = np.array(static_ranking_arr).mean()
axs[1].set_title(f'Prediction with Static Ranking\nmean accuracy: {static_ranking_mean:.3f}')
axs[1].set_ylim(0, 1)
axs[1].set_xlabel('Days')
axs[1].set_ylabel('Score')
axs[1].bar(np.arange(static_ranking_arr.size), static_ranking_arr, 1.1, color='#afeeee')
axs[1].bar(np.arange(static_ranking_arr.size), 1-static_ranking_arr, 1.1, bottom=static_ranking_arr, color='magenta')
```

# Bibliography

Menne, M.J., I. Durre, R.S. Vose, B.E. Gleason, and T.G. Houston, 2012:  An overview
of the Global Historical Climatology Network-Daily Database.  Journal of Atmospheric
and Oceanic Technology, 29, 897-910, doi:10.1175/JTECH-D-11-00103.1.


```python
# 그래프, 수학 기능 추가
# Add graph and math features
import pylab as py
import numpy as np
import numpy.linalg as nl
# 기호 연산 기능 추가
# Add symbolic operation capability
import sympy as sy

```

```python
sy.init_printing()

```

# 2차 적분
Second Order Numerical Integral

다시 반지름 $r$, 면적 $a$ 인 반원을 생각해 보자.

Again, let's think about a half circle with radius & area of $r$ & $a$ respectively.

```python
r, a = sy.symbols('r, a', nonnegative=True)

```

```python
eq_circle_area = sy.Eq(sy.pi * r * r , 2 * a)

```

```python
eq_circle_area

```

이러한 원의 반지름의 제곱을 생각해 보자.

Let's think about the square of the radius of such circle.

```python
r_square_sol = sy.solve(eq_circle_area, r**2)[0]

```

```python
r_square_sol

```

이 결과를 이용해서 함수로 반원을 구현해 보자.

Using this result, let's implement the half circle in a function.

```python
def half_circle(x, half_circle_area=1.0):
    return py.sqrt(np.abs(2 * half_circle_area / py.pi - x**2))

```

$a=1$인 경우 $r$ 값을 계산해 보면 다음과 같다.

We can also find the value of $r$ such that $a=1$ as follows.

```python
r = float(sy.N(r_square_sol.subs(a, 1.0))) ** 0.5

```

```python
r

```

```python
type(r)

```

이 함수를 이용하여 반원을 그려 보자.

Using this function, let's plot a half circle.

```python
x_array = py.linspace(-r, r)
y_plus = half_circle(x_array)

py.fill_between(x_array, y_plus)

py.axis('equal')
py.grid(True)

```

이번에는 3 지점에서의 함수값을 이용하는 심슨규칙을 이용해서 구해 보기로 하자.

This time, let's integrate by the Simpson's rule using function values at three points.

## 심슨 규칙
Simpson's Rule

마찬가지로 일정 간격으로 $x$ 좌표를 나누어 보자.

Same as before, let's divide $x$ coordinates in a constant interval.

```python
d = r * 2.0

n = 10

x_interval = d / n

# 도 단위의 theta = 180, 179, ..., 0
# theta in deg = 180, 179, ..., 0
theta_deg_array = py.arange(180, -0.5, -1)
# theta 를 radian 단위로 바꿈
# theta in radians
theta_rad_array = py.deg2rad(theta_deg_array)

x_array = r * py.cos(theta_rad_array)
y_plus = half_circle(x_array)

x_array_bar = py.arange(-r, r+x_interval*0.1, x_interval)
y_array_bar = half_circle(x_array_bar)
x_interval = x_array_bar[1]-x_array_bar[0]

py.plot(x_array, y_plus)
py.plot(x_array_bar, y_array_bar, '.')

py.axis('equal')
py.grid(True)

```

마지막 두 구간을 생각해 보기로 하자.

Let's just think about the last two segments.

```python
d = r * 2.0

n = 10

x_interval = d / n

# 도 단위의 theta = 180, 179, ..., 0
# theta in deg = 180, 179, ..., 0
theta_deg_array = py.arange(180, -0.5, -1)
# theta 를 radian 단위로 바꿈
# theta in radians
theta_rad_array = py.deg2rad(theta_deg_array)

x_array = r * py.cos(theta_rad_array)
y_plus = half_circle(x_array)

x_array_bar = py.arange(-r, r+x_interval*0.1, x_interval)
y_array_bar = half_circle(x_array_bar)
x_interval = x_array_bar[1]-x_array_bar[0]

# 마지막 두 구간에 해당하는 x y 값을 선택
# Choose x y values of the last two intervals
x_last_two_array = x_array[x_array_bar[-3] < x_array]
y_last_two_array = y_plus[x_array_bar[-3] < x_array]

py.plot(x_array, y_plus, alpha=0.5)
py.plot(x_array_bar, y_array_bar, '.')
py.fill_between(x_last_two_array, y_last_two_array)

py.axis('equal')
py.grid(True)

```

해당 넓이를 구하기 위해, 이 세 점을 지나는 2차 다항식을 찾아서 적분할 수 있을 것이다

To get the area, we would be able to find a second order polynomal passing through these three points and integrate.

문제를 좀 더 쉽게 만들기 위해 해당 면적을 원점 주위로 평행 이동 시켜 보자.

To make the problem simpler, let's translate the area around the origin.

```python
d = r * 2.0

n = 10

x_interval = d / n

# 도 단위의 theta = 180, 179, ..., 0
# theta in deg = 180, 179, ..., 0
theta_deg_array = py.arange(180, -0.5, -1)
# theta 를 radian 단위로 바꿈
# theta in radians
theta_rad_array = py.deg2rad(theta_deg_array)

x_array = r * py.cos(theta_rad_array)
y_plus = half_circle(x_array)

x_array_bar = py.arange(-r, r+x_interval*0.1, x_interval)
y_array_bar = half_circle(x_array_bar)
x_interval = x_array_bar[1]-x_array_bar[0]

# 마지막 두 구간에 해당하는 x y 값을 선택
# Choose x y values of the last two intervals
x_last_two_array = x_array[x_array_bar[-3] < x_array]
y_last_two_array = y_plus[x_array_bar[-3] < x_array]

py.plot(x_array, y_plus, alpha=0.0)
py.plot(x_array_bar[-3:], y_array_bar[-3:], '.')

# 마지막 두 구간을 표시
# Indicate last two intervals
py.fill_between(x_last_two_array, y_last_two_array)

# 평행이동한 면적
# Translated Area
py.plot(x_array_bar[-3:]-x_array_bar[-2], y_array_bar[-3:], '.')
py.fill_between(x_last_two_array-x_array_bar[-2], y_last_two_array)

# x 좌표 표시
# Indicate x coordinates
py.text(-x_interval, -0.1, '$-\Delta x$', horizontalalignment='center')
py.text(x_interval, -0.1, '$+\Delta x$', horizontalalignment='center')

# y 좌표 표시
# Indicate x coordinates
py.text(-x_interval, y_array_bar[-3], '$y_0$', horizontalalignment='center', verticalalignment='bottom')
py.text(          0, y_array_bar[-2], '$y_1$', horizontalalignment='center', verticalalignment='bottom')
py.text(+x_interval, y_array_bar[-1], '$y_2$', horizontalalignment='center', verticalalignment='bottom')

py.axis('equal')
py.grid(True)

```

$$
y=a_0 x^2 + a_1 x + a_2
$$

원래 위치의 면적과 평행이동한 면적은 같다.
The translate area and the original area are equivalent.

평행이동한 면적의 세 점을 살펴 보자.
Let's take a look at the three points of the translated area.

$$
\begin{align}
    p_0&=\left(-\Delta x, y_0\right) \\
    p_1&=\left(0, y_1\right) \\
    p_2&=\left(\Delta x, y_2\right)
\end{align}
$$

```python
delta_x, y_m, y_0, y_p = sy.symbols('Delta_x, y_0, y_1, y_2', real=True)

```

```python
points = (-delta_x, y_m), (0, y_0), (delta_x, y_p)

```

```python
points

```

2차 다항식은 다음과 같은 형태를 가진다.

A second order polynomial would take following form.

```python
a0, a1, a2, x = sy.symbols('a0, a1, a2, x', real=True)
f = a0 * x**2 + a1 * x + a2

```

```python
f

```

위 세 점을 모두 지나는 2차 곡선을 생각해 보자.
Let's think about a second order polynomal passing all three points above.

$$
\begin{align}
    y_0&=a_0 \left(-\Delta x\right)^2 + a_1 \left(-\Delta x\right) + a_2 \\
    y_1&=a_2 \\
    y_2&=a_0 \left(\Delta x\right)^2 + a_1 \left(\Delta x\right) + a_2
\end{align}
$$

```python
eq_points = [sy.Eq(p[-1], f.subs(x, p[0])) for p in points]

```

```python
eq_points

```

계수 $a_i$에 관하여 풀어 보자.
Let's try to solve for the coefficients $a_i$.

```python
a_sol = sy.solve(eq_points, (a0, a1, a2))

```

```python
a_sol

```

## 2차 다항식의 정적분
Definite Integral of a Second Order Polynomial

이제 $f(x)$를 $x$에 관하여 $-\Delta x$ 부터 $\Delta x$까지 적분해 보자.
Now let's integrate $f(x)$ about $x$ from $-\Delta x$ to $\Delta x$.

```python
integral = sy.integrate(f, (x, -delta_x, delta_x))

```

```python
integral

```

계수를 대입하고 정리해 보자.
Let's substitute the coefficients and simplfy.

```python
simpson = sy.simplify(integral.subs(a_sol))

```

```python
simpson

```

예를 들어 C 언어 코드로는 다음과 같이 가능하다
For example, in C programming language, following expression would be possible.

```python
sy.ccode(simpson)

```

## 심슨 규칙 구현
Implementing Simpson's Rule

한번에 두 구간의 면적을 계산한다.

In one iteration, calculate the area of two intervals.

$$
    Area = F_0 + F_2 + \ldots + F_{n-2}
$$

$$
    F_k = \frac{\Delta x}{6}\left[f(x_k)+4 \cdot f(x_{k+1}) + f(x_{k+2})\right]
$$

```python
def num_int_2(f, xi, xe, delta_x):
    """
    f : function to indegrate f(x)
    xi : start of integration
    xe : end of integration
    delta_x : integration interval
    """

    # delta_x 값이 너무 작은 경우
    # if delta_x is too small
    if 1e-7 > abs(delta_x):
        raise ValueError(f'delta_x(delta_x:g) too small')

    n_interval = int((xe - xi)/delta_x)

    # 구간의 갯수를 항상 짝수로 한다.
    # Always use even number of intervals

    if n_interval % 2:

        n_interval += 1
        delta_x = (xe - xi) / n_interval

    x_array = py.arange(xi, xe+delta_x*0.1, delta_x)
    delta_x_third = delta_x / 3.0

    integration_result = 0.0
    xp = x_array[0]
    y0 = f(xp)

    for i in range(1, n_interval, 2):
        x1 = x_array[i]
        x2 = x_array[i+1]

        y1 = f(x1)
        y2 = f(x2)

        area_i = delta_x_third * (y0 + 4*y1 + y2)
        xp, y0 = x2, y2
        integration_result += area_i

    return integration_result

```

```python
n = 10
result = num_int_2(half_circle, -r, r, 2*r/n)
print('result =', result)

```

```python
n = 100
result = num_int_2(half_circle, -r, r, 2*r/n)
print('result =', result)

```

```python
n = 100
result = num_int_2(half_circle, -r, r, 2*r/(2**8))
print('result =', result)

```

도전 과제 1 : 넓이 1인 반원의 예로 0차, 1차 적분과의 오차를 비교하시오.
Using the example of half circle with area 1, compare errors with zeroth and first order integrations.

도전 과제 2 : 긴 지름 4, 짧은 지름 2인 타원의 면적의 절반을 심슨법으로 계산하시오.
Try this 2 : Calculate the half of area of an ellipse with long diameter 4 and short diameter 2 using the Simpson's rule.

참고문헌 : https://ko.wikipedia.org/wiki/%ED%83%80%EC%9B%90

```python
y, a, b = sy.symbols('y, a, b', nonnegative=True)

```

```python
ellipse_4_2_eq = sy.Eq((x**2)/a**2 + (y**2)/b**2, 1)
ellipse_4_2_eq

```

```python
sol_ellipse = sy.solve(ellipse_4_2_eq, y)
sol_ellipse

```

```python
def ellipse(x, a=2, b=1):
    return (b / a) * py.sqrt(a**2 - x**2)

```

```python
x_array_ellipse = py.linspace(-2.0, 2.0, 2**8)
py.plot(x_array_ellipse, ellipse(x_array_ellipse))

x_array_circle = py.linspace(-r, r, 2**8)
py.plot(x_array_circle, half_circle(x_array_circle))
py.grid(True)
py.axis('equal')
py.show()

```

```python
delta_x = 2**-8
delta_x

```

```python
result = num_int_2(ellipse, -2, 2, 2**-8)
print('result =', result)

```

```python
print(f"a b pi/2 = {2*1*py.pi/2}")

```

```python
result = num_int_2(half_circle, -r, r, (2*r)/(2**8))
print('result =', result)

```

```python
result = num_int_2(half_circle, -r, r, (2*r)/(100))
print('result =', result)

```

## Final Bell
마지막 종

```python
# stackoverfow.com/a/24634221
import os
os.system("printf '\a'");

```

```python

```


# Configuraciones para el Grupo de Estudio

## Contenidos
- ¿Por qué jupyter notebooks?
- Bash
- ¿Que es un *kernel*?
- Instalación
- Deberes

## Python y proyecto Jupyter

- Necesitamos llevar un registro del avance de cada integrante.
- Lenguaje de programación interpretado de alto nivel.
- Jupyter notebooks: son fáciles de usar
- `Necesitamos que todos tengan una versión de Python con jupyter lab`

## ¿Cómo funciona Jupyter?

- Es un derivado del proyecto `iPython`, que ofrece una interfaz interactiva para programadores.
- Tiene formato `.ipynb`
- Es posible usar otros lenguajes de programación diferentes a Python.
- Permite al usuario configurar cómo se visualiza su código mediante `Markdown`.
- Ahora una demostración

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

# constantes
pi = math.pi; h = 6.626e-34; kB = 1.380e-23; c = 3.0e+8;
Temps = [9940.00, 8500.00, 7500.00, 6627.00, 5810.93, 4231.15, 3000.00, 2973.15, 288.15]
labels = ['Sirius', 'White star', 'Yellow-white star', 'Polaris', 'Sol', 'HfC', 'Bombilla', 'TaN', 'Atmósfera ']
colors = ['r','g','#FF9633','c','m','#eeefff','y','b','k']

# arreglo de frecuencias
freq = np.arange(0.25e14,3e15,0.25e14)

# funcion spectral energy density (SED)
def SED(f, T):
    energyDensity = ( 8*pi*h*(np.power(f, 3.0))/(c**3) ) / (np.exp((h/kB)*f/T) - 1)
    return energyDensity

# Calculo de SED para temperaturas
for i in range(len(Temps)):
    r = SED(freq,Temps[i])
    plt.plot(freq*1e-12,r,color=colors[i],label=labels[i])

plt.legend(); plt.xlabel('frequency ( THz )'); plt.ylabel('SED_frequency ( J $m^{-3}$ $Hz^{-1}$ )')
plt.xlim(0.25e2,2.5e3); plt.show()
```

### Permite escribir expresiones matemáticas complejas

Es posible escribir código en $\LaTeX$ si es necesario

\begin{align}
  \frac{\partial u(\lambda, T)}{\partial \lambda} &= \frac{\partial}{\partial \lambda} \left( \frac{C_{1}}{\lambda^{5}}\left(\frac{1}{e^{C_{2}/T\lambda} -1}\right) \right) \\
  0 &= \left(\frac{-5}{e^{C_{2}/T\lambda} -1}\frac{1}{\lambda^{6}}\right) + \left( \frac{C_{2}e^{C_{2}/T\lambda}}{T\lambda^{7}} \right)\left(\frac{1}{e^{C_{2}/T\lambda} -1}\right)^{2} \\
  0 &= \frac{-\lambda T5}{C_{2}} + \frac{e^{C_{2}/T\lambda}}{e^{C_{2}/T\lambda} -1} \\
  0 &= -5 + \left(\frac{C_{2}}{\lambda T}\right) \left(\frac{e^{C_{2}/T\lambda}}{e^{C_{2}/T\lambda} -1}\right)
\end{align}

## ¿Cómo es que usa un lenguaje diferente a Python?

- Un kernel es una especie de `motor computacional` que ejecuta el código dentro de un archivo `.ipynb`.
- Los kernels hay para varios lengajes de programación, como R, Bash, C++, julia.

## ¿Por qué Bash?

- Bash es un lenguaje de scripting que se comunica con la shell e históricamente ha ayudado a científicos a llevarse mejor con la bioinformática.

## ¿Dónde encontramos las instrucciones para instalar Python?

- Es posible hacerlo de varias maneras: `Anaconda` y el `intérprete oficial` desde https://www.python.org/downloads/
- Usaremos el intérprete de `Anaconda`: es más fácil la instalación si no te acostumbras a usar la línea de comandos.
- Si ustedes ya están familiarizados con Python y no desean instalar el intérprete de `Anaconda` pueden usar `pip` desde https://pypi.org/project/bash_kernel/

## Deberes
- Creamos una carpeta en `google Drive` donde harán subirán los archivos `.ipynb` y una conversión a HTML, u otro tipo de archivo dependiendo de la sesión.
- Vamos a tener un quiz cada semana, que les enviaremos por el servidor de Discord del grupo de estudio.
- El deber para la siguiente semana:
    1. Instalar Ubuntu si aún no lo poseen usando cualquiera de las alternativas presentadas.
    2. Instalar Anaconda, jupyter lab y el kernel de bash.

Se deben enviar un documento word o pdf con capturas de pantalla que compruebe esto.
Si tienen algún problema, usen por favor los foros de `Discord` y nos ayudamos entre todos.