# Frequentist Inference Case Study - Part B

## Learning objectives

Welcome to Part B of the Frequentist inference case study! The purpose of this case study is to help you apply the concepts associated with Frequentist inference in Python. In particular, you'll practice writing Python code to apply the following statistical concepts:
* the _z_-statistic
* the _t_-statistic
* the difference and relationship between the two
* the Central Limit Theorem, including its assumptions and consequences
* how to estimate the population mean and standard deviation from a sample
* the concept of a sampling distribution of a test statistic, particularly for the mean
* how to combine these concepts to calculate a confidence interval

In the previous notebook, we used only data from a known normal distribution. **You'll now tackle real data, rather than simulated data, and answer some relevant real-world business problems using the data.**

## Hospital medical charges

Imagine that a hospital has hired you as their data scientist. An administrator is working on the hospital's business operations plan and needs you to help them answer some business questions.

In this assignment notebook, you're going to use frequentist statistical inference on a data sample to answer the questions:
* has the hospital's revenue stream fallen below a key threshold?
* are patients with insurance really charged different amounts than those without?

Answering that last question with a frequentist approach makes some assumptions, and requires some knowledge, about the two groups.

We are going to use some data on medical charges obtained from [Kaggle](https://www.kaggle.com/easonlai/sample-insurance-claim-prediction-dataset).

For the purposes of this exercise, assume the observations are the result of random sampling from our single hospital. Recall that in the previous assignment, we introduced the Central Limit Theorem (CLT), and its consequence that the distributions of sample statistics approach a normal distribution as $n$ increases. The amazing thing about this is that it applies to the sampling distributions of statistics that have been calculated from even highly non-normal distributions of data! Recall, also, that hypothesis testing is very much based on making inferences about such sample statistics. You're going to rely heavily on the CLT to apply frequentist (parametric) tests to answer the questions in this notebook.

```python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import t
from numpy.random import seed
medical = pd.read_csv('data/insurance2.csv')
```

```python
import scipy.stats as stats

```

```python
medical.shape
```

    (1338, 8)

```python
medical.head()
```

      age
      sex
      bmi
      children
      smoker
      region
      charges
      insuranceclaim

      0
      19
      0
      27.900
      0
      1
      3
      16884.92400
      1

      1
      18
      1
      33.770
      1
      0
      2
      1725.55230
      1

      2
      28
      1
      33.000
      3
      0
      2
      4449.46200
      0

      3
      33
      1
      22.705
      0
      0
      1
      21984.47061
      0

      4
      32
      1
      28.880
      0
      0
      1
      3866.85520
      1

__Q1:__ Plot the histogram of charges and calculate the mean and standard deviation. Comment on the appropriateness of these statistics for the data.

__A:__ Distribution is not *normal*. It looks exponential. If exponential the mean and standard deviation should be the same, they are close but not same which may suggest that the distribution is not perfectly exponential

```python
mean_est = np.mean(medical.charges)
std_est = np.std(medical.charges)
print("Mean = ", round(mean_est,2))
print("Standard Deviation = ", round(std_est,2))
```

    Mean =  13270.42
    Standard Deviation =  12105.48

```python
_ = plt.hist(medical.charges, bins=30)
_ = plt.xlabel('charge amount (USD)')
_ = plt.ylabel('number of patients')
_ = plt.title('Distribution of medical charges in patient population')
_ = plt.axvline(mean_est, color='r')
#_ = plt.axvline(mean+std, color='r', linestyle='--')
#_ = plt.axvline(mean-std, color='r', linestyle='--')
#_ = plt.axvline(mean+2*std, color='r', linestyle='-.')
#_ = plt.axvline(mean-2*std, color='r', linestyle='-.')
```

__Q2:__ The administrator is concerned that the actual average charge has fallen below 12,000, threatening the hospital's operational model. On the assumption that these data represent a random sample of charges, how would you justify that these data allow you to answer that question? And what would be the most appropriate frequentist test, of the ones discussed so far, to apply?

__A:__ By leveraging the central limit theorem we could calculate the mean of the population. The result shows that the mean is around $13300

```python
def charges_sampler(n):
    return np.random.choice(medical.charges, n)
```

```python
distribution_of_mean = []
```

```python
seed(47)
# take your samples here
size=50
for i in range(1000) :
    distribution_of_mean.append(np.mean(charges_sampler(size)))
```

```python
mean_of_distribution_of_mean = np.mean(distribution_of_mean)
pop_mean = mean_of_distribution_of_mean #because of Central Limit Theorem
print("pop_mean = ", round(pop_mean,2))
```

    pop_mean =  13280.98

```python
_ = plt.hist(distribution_of_mean, bins=10)
_ = plt.axvline(pop_mean, color='r')
_ = plt.xlabel('mean height (cm)')
_ = plt.ylabel('numbe of people')
_ = plt.title('Sampling distribution')
```

```python
z_score = stats.norm.ppf(0.025)
```

__Q3:__ Given the nature of the administrator's concern, what is the appropriate confidence interval in this case? A ***one-sided*** or ***two-sided*** interval? (Refresh your understanding of this concept on p. 399 of the *AoS*). Calculate the critical value and the relevant 95% confidence interval for the mean, and comment on whether the administrator should be concerned.

__A:__ A 95% confidence interval is appropriate. A one-sided interval is sufficient since we are only interested if the mean charge is below $12000 (H_0). We can do a one sample, one sided T-test. Which is not available in the standard scipy.stats functions. So we will do a one sample two-sided test and consider only the relevant confidence level to test the null hypotesys that the mean is < 12000

```python
t_val, p_val = stats.ttest_1samp(medical.charges, 12000)
print("t-value : ",t_val)
print("p-value : ",p_val)
```

    t-value :  3.8373517196585314
    p-value :  0.000130171652092872

```python
print("T-value is large and p-value is less than 0.05 so the Null Hypotesis that the means of charges is below $12000 is rejected")
```

    T-value is large and p-value is less than 0.05 so the Null Hypotesis that the means of charges is below $12000 is rejected

The administrator then wants to know whether people with insurance really are charged a different amount to those without.

__Q4:__ State the null and alternative hypothesis here. Use the _t_-test for the difference between means, where the pooled standard deviation of the two groups is given by:
\begin{equation}
s_p = \sqrt{\frac{(n_0 - 1)s^2_0 + (n_1 - 1)s^2_1}{n_0 + n_1 - 2}}
\end{equation}

and the *t*-test statistic is then given by:

\begin{equation}
t = \frac{\bar{x}_0 - \bar{x}_1}{s_p \sqrt{1/n_0 + 1/n_1}}.
\end{equation}

(If you need some reminding of the general definition of ***t-statistic***, check out the definition on p. 404 of *AoS*).

What assumption about the variances of the two groups are we making here?

__A:__ Null Hypothesis : the mean of charges is the *same* between people with insurance and withouth insurance.

Alternative Hypothesis : the mean of charges is *different* between people with insurance and withouth insurance.

We make the following assumptions when conducting a t-test:
1. Scale of meassured data is continuos (not discrete)
2. Data is a **random sample**, representative of the population
3. Data is described by **Normal distribution**   (IS IT??? it's exponentially distributed)
4. **Homogeneity of Variance** --> Standard deviations are similar (THER ARE NOT!!)

```python
s_ins = np.std(medical[(medical.insuranceclaim == 1)].charges)
s_no_ins = np.std(medical[(medical.insuranceclaim == 0)].charges)
n_ins = len(medical[(medical.insuranceclaim == 1)].charges)
n_no_ins = len(medical[(medical.insuranceclaim == 0)].charges)
mean_ins = np.mean(medical[(medical.insuranceclaim == 1)].charges)
mean_no_ins = np.mean(medical[(medical.insuranceclaim == 0)].charges)
s_ins, s_no_ins, n_ins, n_no_ins, mean_ins, mean_no_ins
```

    (14036.956250260417,
     6440.699841605235,
     783,
     555,
     16423.928276537677,
     8821.421892306305)

```python
s_p = np.sqrt(((n_ins-1)*(s_ins**2)+(n_no_ins-1)*(s_no_ins**2))/(n_ins + n_no_ins -2))
#print(s_p)
```

```python
t_val2 = (mean_ins - mean_no_ins)/(s_p*np.sqrt((1/n_ins)+(1/n_no_ins)))
print(t_val2)
```

    11.901306943555388

```python
p_val2 = t.cdf(-t_val2, (n_no_ins-1))*2
print(p_val2)
```

    3.0484411272853534e-29

```python
print("T-value is large and p-value is much less than 0.05 so the Null Hypotesis that the means of charges is equal is rejected. This suggests that the alternative hypotesis is true: there is a difference between the charge to patients with insurance and without insurance.")
```

    T-value is large and p-value is much less than 0.05 so the Null Hypotesis that the means of charges is equal is rejected. This suggests that the alternative hypotesis is true: there is a difference between the charge to patients with insurance and without insurance.

__Q5:__ Perform this hypothesis test both manually, using the above formulae, and then using the appropriate function from [scipy.stats](https://docs.scipy.org/doc/scipy/reference/stats.html#statistical-tests) (hint, you're looking for a function to perform a _t_-test on two independent samples). For the manual approach, calculate the value of the test statistic and then its probability (the p-value). Verify you get the same results from both.

__A:__

```python
t_val3, p_val3 = stats.ttest_ind(medical[(medical.insuranceclaim == 1)].charges, medical[(medical.insuranceclaim == 0)].charges)
print("t-value : ",t_val3)
print("p-value : ",p_val3)
```

    t-value :  11.893299030876712
    p-value :  4.461230231620717e-31

```python
print("T-value is large and p-value is much less than 0.05 so the Null Hypotesis that the means of charges is equal is rejected. This suggests that the alternative hypotesis is true: there is a difference between the charge to patients with insurance and without insurance.")
```

    T-value is large and p-value is much less than 0.05 so the Null Hypotesis that the means of charges is equal is rejected. This suggests that the alternative hypotesis is true: there is a difference between the charge to patients with insurance and without insurance.

```python

```

```python

```

Congratulations! Hopefully you got the exact same numerical results. This shows that you correctly calculated the numbers by hand. Secondly, you used the correct function and saw that it's much easier to use. All you need to do is pass your data to it.

__Q6:__ Conceptual question: look through the documentation for statistical test functions in scipy.stats. You'll see the above _t_-test for a sample, but can you see an equivalent one for performing a *z*-test from a sample? Comment on your answer.

__A:__ z-score and t-score are the same for samples larger than 30.  https://www.statisticshowto.com/probability-and-statistics/hypothesis-testing/t-score-vs-z-score/

## Learning outcomes

Having completed this project notebook, you now have good hands-on experience:
* using the central limit theorem to help you apply frequentist techniques to answer questions that pertain to very non-normally distributed data from the real world
* performing inference using such data to answer business questions
* forming a hypothesis and framing the null and alternative hypotheses
* testing this using a _t_-test

Frequentist_Inference/Frequentist Inference Case Study - Part B (2).ipynb


# Challenge problem: neutrino oscillations

This notebook is meant to get you started towards implementing the neutrino oscillation procedure detailed in http://arxiv.org/abs/1904.10559. The paper shows the details for both 2- and 3-flavour oscillations, but we'll focus here on just two, $\nu_e$ and $\nu_\mu$. Specifically, we will work on reproducing Figure 2, where we will plot the 'survival' probability of the electron neutrino.

### Derivations and important physical constants

Recall from the lectures that I showed an expression for the probability of $\nu_e$ turning into a $\nu_\mu$:
\begin{equation}
P_{\nu_e \rightarrow \nu_\mu} = |\nu_\mu(t)|^2 = \left[ \sin(2\theta) \sin \left( \frac{E_2 - E_1}{2\hbar}t \right) \right]^2
\end{equation}

Oftentimes this is expressed in terms of travel distance $L$ and a neutrino energy $E$, using the correspondence $E_i = |p|^2 c^2 + m_i^2 c^4$ and making some approximations, such as $L \approx ct$ for particles travelling at close to the speed of light:
\begin{eqnarray}
P_{\nu_e \rightarrow \nu_\mu} =  |\nu_\mu(t)|^2 &=& \left[ \sin(2\theta) \sin \left( \frac{(m_{\nu_\mu}^2  - m^2_{\nu_e})}{2\hbar} \frac{L}{E} \right) \right]^2 \\
&=&  \left[ \sin(2\theta) \sin \left( \frac{\Delta m^2}{2\hbar c} \frac{L}{E} \right) \right]^2
\end{eqnarray}
where $\Delta m^2$ is shorthand for $m_{\nu_\mu}^2  - m^2_{\nu_e}$.
For a full derivation of this, see, for examples,  Griffith's book _Introduction to Elementary Particles_. I'm just going to take it as a given.

**Task 1.** Track down the relevant constants you need: $\theta$, $\Delta m^2$ for electron and muon neutrinos, and $L$. You'll have to be really careful with units here, making sure you don't pick up (or lose) any rogue factors of $c$, etc. The physical neutrino parameters can be found online; the value of $L$ they used was from the KamLAND experiment (https://arxiv.org/pdf/hep-ex/0212021.pdf).

```python
θ = 0.5867 # rad, Found on Wikipedia discussion of neutrino oscillation and PMNS matrix

# Thanks to the authors Carlos and Benjamin for helping me out with this part!
Δm_sq = 7.37e-5  # expressed in (eV / c^2)^2
hbar_c = 1.97e-10 # expressed in eV * km
L = 180 # km, from the experimental paper

gev_conversion_factor = (1e-9)**2  / 1e-9

# This gives us a prefactor with units such that we can input an energy in GeV
prefactor = (Δm_sq * L / (2 * hbar_c)) * gev_conversion_factor
```

**Task 2.** Set up the required registers.

For two-flavour oscillation, we only need to use a single qubit. So, we just need one quantum register with one qubit, and a classical register with a classical output bit.

```python
import numpy as np

from qiskit import QuantumRegister, ClassicalRegister
from qiskit import QuantumCircuit
from qiskit import execute, BasicAer

import matplotlib.pyplot as plt

import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
```

```python
q = QuantumRegister(1)
c = ClassicalRegister(1)
```

**Task 3.** Write a function that will apply a circuit to your registers that (a) creates the initial superposition, (b) applies the evolution operator for a neutrino of specified energy, (c) measures the output.

The way this is done in the paper is actually by using $|0\rangle$ and $|1\rangle$ to represent the flavour basis (instead of using them to represent the mass basis). This way our measurements are made in the flavour basis as well. So make sure you are applying the unitary basis transformations in the "right" direction.

```python
def oscillate(E, q, c):
    circuit = QuantumCircuit(q, c)

    # First we convert from flavour basis to mass basis
    circuit.u3(2*θ, 0, 0, q[0])

    # Next we apply the evolution operator
    circuit.u1(prefactor / E, q[0])

    # Then we undo the basis change
    circuit.u3(-2*θ, 0, 0, q[0])

    circuit.measure(q, c)

    return circuit
```

**Task 4.** Simulate your circuit for varying energies, and plot the results.

```python
energy_range = list(np.linspace(2, 12, 200))
probs = []
n_shots = 10000

for E in energy_range:
    neutrino_circuit = oscillate(E / 1000, q, c)

    backend = BasicAer.get_backend('qasm_simulator') # the device to run on
    result = execute(neutrino_circuit, backend, shots=n_shots).result()
    counts = result.get_counts(neutrino_circuit)

    # Extract the probabilities
    if '0' in counts.keys():
        p_0 = counts['0'] / n_shots
        p_1 = 1 - p_0
    else:
        p_1 = counts['1'] / n_shots
        p_0 = 1 - p_1

    probs.append(p_0)
```


# Transfinite Interpolation

In this work, we intend to study the usage of *transfinite interpolation*.

```python
# Magics
%matplotlib inline
```

```python
# Imports
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
```

```python
def F_arc(ksi,eta):
    #angle = ksi*np.pi/2
    angle = ksi*2.0*np.pi

    x = 4*ksi*eta*(1-ksi)*(1-eta) + (1+eta/np.sqrt(2))*np.cos(angle)
    y = (1+eta/np.sqrt(2))*np.sin(angle)

    return np.array([x,y])

def F_square(ksi, eta, length=1.0, center=[0,0], n_points=10):
    s = np.linspace(0,length,n_points)
    t = np.linspace(0,length,n_points)
    X,Y = np.meshgrid(s,t)

def F_circle(ksi, eta, r=1.0, tol=1.0e-12, center=[0,0]):
    x0, y0 = center

    if np.abs(ksi) < tol:
        x = x0 + r*np.cos(-eta*np.pi/2 + np.pi/2)
        y = y0 + r*np.sin(-eta*np.pi/2 + np.pi/2)
        return np.array([x,y])  # F(0,eta)

    if np.abs(ksi - 1) < tol:
        x = x0 + r*np.cos(eta*np.pi/2 + np.pi)
        y = y0 + r*np.sin(eta*np.pi/2 + np.pi)
        return np.array([x,y])   # F(1,eta)

    if np.abs(eta) < tol:
        x = x0 + r*np.cos(ksi*np.pi/2 + np.pi/2)
        y = y0 + r*np.sin(ksi*np.pi/2 + np.pi/2)
        return np.array([x,y])   # F(ksi,0)

    if np.abs(eta - 1) < tol:
        x = x0 + r*np.cos(-ksi*np.pi/2 + 2*np.pi)
        y = y0 + r*np.sin(-ksi*np.pi/2 + 2*np.pi)
        return np.array([x,y])   # F(ksi,1)

    print ("ERROR: Did not enter if.")
    assert False

def F_wing(ksi, eta, tol=1.0e-12):
    if np.abs(ksi) < tol:
        x = 4.0
        y = -2*eta
        return np.array([x,y])  # F(0,eta)

    if np.abs(ksi - 1) < tol:
        x = 4.0
        y = 2*eta
        return np.array([x,y])   # F(1,eta)

    if np.abs(eta) < tol:
        if ksi >= 0.0 and ksi < 0.33:
            x = 4.0 - 11.5*ksi + 7.5*ksi**2
            y = -1.125*ksi + 1.125*ksi**2
        elif ksi >= 0.33 and ksi < 0.66:
            x = 9.0 - 36*ksi + 36*ksi**2
            y = -0.75 + 1.5*ksi
        else:
            assert ksi >= 0.66 and ksi <= 1.0
            x = -3.5*ksi + 7.5*ksi**2
            y = 1.125*ksi - 1.125*ksi**2
        return np.array([x,y])   # F(ksi,0)

    if np.abs(eta - 1) < tol:
        x = 4.0 - 20*ksi + 20*ksi**2
        y = -2 + 4*ksi
        return np.array([x,y])   # F(ksi,1)

    print ("ERROR: Did not enter if.")
    assert False

def F_ellipse(ksi, eta, a=1.0, b=20.0, tol=1.0e-12, center=[0,0]):
    x0, y0 = center

    if np.abs(ksi) < tol:
        x = x0 + a*np.cos(-eta*np.pi/2 + np.pi/2)
        y = y0 + b*np.sin(-eta*np.pi/2 + np.pi/2)
        return np.array([x,y])  # F(0,eta)

    if np.abs(ksi - 1) < tol:
        x = x0 + a*np.cos(eta*np.pi/2 + np.pi)
        y = y0 + b*np.sin(eta*np.pi/2 + np.pi)
        return np.array([x,y])   # F(1,eta)

    if np.abs(eta) < tol:
        x = x0 + a*np.cos(ksi*np.pi/2 + np.pi/2)
        y = y0 + b*np.sin(ksi*np.pi/2 + np.pi/2)
        return np.array([x,y])   # F(ksi,0)

    if np.abs(eta - 1) < tol:
        x = x0 + a*np.cos(-ksi*np.pi/2 + 2*np.pi)
        y = y0 + b*np.sin(-ksi*np.pi/2 + 2*np.pi)
        return np.array([x,y])   # F(ksi,1)

    print ("ERROR: Did not enter if.")
    assert False

def F_ellipse_arc(ksi, eta, a=3.0, b=1.0, tol=1.0e-12, center=[0,0]):
    x0, y0 = center

    if np.abs(ksi) < tol:
        x = ksi * a
        y = eta * b
        return np.array([x,y])  # F(0,eta)

    if np.abs(ksi - 1) < tol:
        x = x0 + a*np.cos(eta*np.pi/4)
        y = y0 + b*np.sin(eta*np.pi/4)
        return np.array([x,y])   # F(1,eta)

    if np.abs(eta) < tol:
        x = ksi * a
        y = eta * b
        return np.array([x,y])   # F(ksi,0)

    if np.abs(eta - 1) < tol:
        x = x0 + a*np.cos(-ksi*np.pi/4 + np.pi/2)
        y = y0 + b*np.sin(-ksi*np.pi/4 + np.pi/2)
        return np.array([x,y])   # F(ksi,1)

    print ("ERROR: Did not enter if.")
    assert False
```

A tranfinite interpolation is defined by

$$
\begin{align}
\vec{S}(\xi, \eta) &= (1-\xi) \vec{F} (0,\eta) + \xi\vec{F}(1,\eta) + (1-\eta) \vec{F} (\xi,0) + \eta \vec{F}(\xi,1) \\
                   &- \left[(1-\xi)(1-\eta) \vec{F}(0,0) + (1-\xi)\eta \vec{F}(0,1) + \xi(1-\eta)\vec{F}(1,0) + \xi\eta \vec{F}(1,1)\right]
\end{align}
$$
Or in python code

```python
def TranfiniteInterpolation(ksi, eta, F):
    return (1-ksi)*F(0,eta) + ksi*F(1,eta) + (1-eta)*F(ksi,0) + eta*F(ksi,1) \
    - (1-ksi)*(1-eta)*F(0,0) - (1-ksi)*eta*F(0,1) - ksi*(1-eta)*F(1,0) - ksi*eta*F(1,1)
```

The method below will use the shape functions in a unit square in $[0,1]\times[0,1]$ to interpolate the `node_values` in the $(\xi,\eta)$ position.

```python
def InterpolateQuad(ksi, eta, node_values):
    shape_func = np.array([
        (1 - ksi)*(1 - eta),
        ksi*(1 - eta),
        ksi*eta,
        eta*(1 - ksi)
        ])

    value = 0.0

    for node_value, N in zip(node_values, shape_func):
        value += N*node_value

    return value
```

Now let's generate a square domain from $(\xi,\eta)\rightarrow[0,1]\times[0,1]$.

```python
# First choose which map you want, e,g, arc, wing or circle
#F = F_arc
#F = F_wing
#F = F_circle
F = F_ellipse_arc
n_points = 5

n_points_ksi = n_points
n_points_eta = n_points

s = np.linspace(0,1,n_points_ksi)
t = np.linspace(0,1,n_points_eta)
X,Y = np.meshgrid(s,t)

P = np.array([F(0,0),F(0,1),F(1,0),F(1,1)])

scat = []
Z = np.zeros((n_points_ksi, n_points_eta))
X_mapped, Y_mapped = np.zeros((n_points_ksi, n_points_eta)), np.zeros((n_points_ksi, n_points_eta))

I = np.eye(4,4)
N1, N2, N3, N4 = I[0:4]

node_values = N1

for i, ksi in enumerate(s):
    for j, eta in enumerate(t):
        x,y = TranfiniteInterpolation(ksi, eta, F)
        scat.append([x, y])
        X_mapped[i,j] = x
        Y_mapped[i,j] = y
        Z[i,j] = InterpolateQuad(ksi, eta, node_values)

scat = np.array(scat)
```

Let's view the results:

```python
# Plot results
plt.close(1)
fig = plt.figure(1)

plt.subplot(221)
plt.scatter(X, Y, s=1)
plt.axis('equal')

plt.subplot(223)
#plt.scatter(F(X,Y)[0],F(X,Y)[1], s=1)
plt.scatter(scat[:,0], scat[:,1], s=1)
plt.axis('equal')

ax = fig.add_subplot(222, projection='3d')
surf = ax.plot_surface(X, Y, Z.T, cmap='jet', rstride=3, cstride=3)
ax.tick_params(axis='both', which='major', labelsize=6)
ax.axis('equal')

ax = fig.add_subplot(224, projection='3d')
surf = ax.plot_surface(X_mapped, Y_mapped , 0 * Z, cmap='jet', rstride=1, cstride=1)
#surf = ax.plot_surface(X_mapped, Y_mapped , Z, cmap='jet', rstride=3, cstride=3)
ax.tick_params(axis='both', which='major', labelsize=6)
ax.axis('equal')

plt.show()
```

```python
# Plot results
plt.close(1)
fig = plt.figure(1)

plt.subplot(221)
plt.scatter(X, Y, s=1)
plt.axis('equal')

plt.subplot(223)
#plt.scatter(F(X,Y)[0],F(X,Y)[1], s=1)
plt.scatter(scat[:,0], scat[:,1], s=1)
plt.axis('equal')

ax = fig.add_subplot(222, projection='3d')
surf = ax.plot_surface(X, Y , (0 * Z).T, cmap='pink_r', rstride=1, cstride=1)
ax.tick_params(axis='both', which='major', labelsize=6)
ax.axis('equal')
ax.view_init(elev=90, azim=-90)
ax.set_axis_off()

ax = fig.add_subplot(224, projection='3d')
surf = ax.plot_surface(X_mapped, Y_mapped , (0 * Z), cmap='pink_r', rstride=1, cstride=1)
ax.tick_params(axis='both', which='major', labelsize=6)
ax.axis('equal')
ax.view_init(elev=90, azim=-90)
ax.set_axis_off()
plt.subplots_adjust(bottom=0.3)
plt.show()
```

$$
\begin{align}
\vec{S}(\xi, \eta) &= (1-\xi) \vec{F} (0,\eta) + \xi\vec{F}(1,\eta) + (1-\eta) \vec{F} (\xi,0) + \eta \vec{F}(\xi,1) \\
                   &- \left[(1-\xi)(1-\eta) \vec{F}(0,0) + (1-\xi)\eta \vec{F}(0,1) + \xi(1-\eta)\vec{F}(1,0) + \xi\eta \vec{F}(1,1)\right]
\end{align}
$$

```python
def Blend_1D1( r, s, x00, x01, x10, x11, xr0, xr1, x0s, x1s):
    '''
    Extends scalar data along the boundary into a square.

    Diagram:

    01-----r1-----11
     |      .      |
     |      .      |
    0s.....rs.....1s
     |      .      |
     |      .      |
    00-----r0-----10

    Reference:
    William Gordon, Charles A Hall,
    Construction of Curvilinear Coordinate Systems and Application to
    Mesh Generation,
    International Journal of Numerical Methods in Engineering,
    Volume 7, pages 461-477, 1973.

    Joe Thompson, Bharat Soni, Nigel Weatherill,
    Handbook of Grid Generation,
    CRC Press,
    1999.

    :param double r,s
        The coordinates where an interpolated value is desired.
    :param double X00, X01, X10, X11
        The data values at the corners.
    :param double XR0, XR1, X0S, X1S
        The data values at points along the edges.

    :return the interpolated data value at (R,S).
    '''
    return (
    - ( 1.0 - r ) * ( 1.0 - s ) * x00
    + ( 1.0 - r )               * x0s
    - ( 1.0 - r ) *         s   * x01
    +               ( 1.0 - s ) * xr0
    +                       s   * xr1
    -         r   * ( 1.0 - s ) * x10
    +         r                 * x1s
    -         r   *         s   * x11
    )
```

Now let's try to define a **3D** transfinite interpolation:

$$
\begin{align}
\vec{S}(\xi,\eta,\zeta) =
  (1-\xi)(1-\eta)(1-\zeta)&\vec{F}(0,0,0)       &-(1-\xi)(1-\eta) &\vec{F}(0,0,\zeta)   &+(1-\xi) (1-\eta)   \zeta &\vec{F}(0,0,1)&\\
- (1-\xi)        (1-\zeta)&\vec{F}(0,\eta,0)    &+(1-\xi          &\vec{F}(0,\eta,\zeta)&-(1-\xi)            \zeta &\vec{F}(0,\eta,1)&\\
+ (1-\xi)   \eta (1-\zeta)&\vec{F}(0,1,0)       &-(1-\xi)   \eta  &\vec{F}(0,1,\zeta)   &+(1-\xi)    \eta    \zeta &\vec{F}(0,1,1)&\\
-        (1-\eta)(1-\zeta)&\vec{F}(\xi,0,0)     &+       (1-\eta) &\vec{F}(\xi,0,\zeta) &-        (1-\eta)   \zeta &\vec{F}(\xi,0,1)&\\
+                (1-\zeta)&\vec{F}(\xi,\eta,0)  &+           \zeta&\vec{F}(\xi,\eta,1)  &-           \eta (1-\zeta)&\vec{F}(\xi,1,0)&\\
+           \eta          &\vec{F}(\xi,1,\zeta) &-           \eta\zeta&\vec{F}(\xi,1,1) &+   \xi  (1-\eta)(1-\zeta)&\vec{F}(1,0,0)&\\
-    \xi (1-\eta)         &\vec{F}(1,0,\zeta)   &+   \xi (1-\eta)\zeta&\vec{F}(1,0,1)   &-   \xi          (1-\zeta)&\vec{F}(1,\eta,0)&\\
+    \xi                  &\vec{F}(1,\eta,\zeta)&-   \xi         \zeta&\vec{F}(1,\eta,1)&+   \xi     \eta (1-\zeta)&\vec{F}(1,1,0)&\\
-    \xi    \eta          &\vec{F}(1,1,\zeta)   &+   \xi    \eta \zeta&\vec{F}(1,1,1)&
\end{align}
$$

```python
def Blend_2D1(
    r, s, t,
    x000, x001, x010, x011, x100, x101, x110, x111,
    xr00, xr01, xr10, xr11, x0s0, x0s1, x1s0, x1s1,
    x00t, x01t, x10t, x11t, x0st, x1st, xr0t, xr1t, xrs0, xrs1
    ):
    '''
    BLEND_2D1 extends scalar data along the surface into a cube.

    Diagram:

    010-----r10-----110    011-----r11-----111
      |       .       |      |       .       |
      |       .       |      |       .       |
    0s0.....rs0.....1s0    0s1.....rs1.....1s1     S
      |       .       |      |       .       |     |
      |       .       |      |       .       |     |
    000-----r00-----100    001-----r01-----101     +----R
       BOTTOM                      TOP

    011-----0s1-----001    111-----1s1-----101
      |       .       |      |       .       |
      |       .       |      |       .       |
    01t.....0st.....00t    11t.....1st.....10t          T
      |       .       |      |       .       |          |
      |       .       |      |       .       |          |
    010-----0s0-----000    110-----1s0-----100     S----+
       LEFT                       RIGHT

    001-----r01-----101    011-----r11-----111
      |       .       |      |       .       |
      |       .       |      |       .       |
    00t.....r0t.....100    01t.....r1t.....11t     T
      |       .       |      |       .       |     |
      |       .       |      |       .       |     |
    000-----r00-----100    010-----r10-----110     +----R
       FRONT                       BACK

    Reference:

    William Gordon, Charles A Hall,
    Construction of Curvilinear Coordinate Systems and Application to
    Mesh Generation,
    International Journal of Numerical Methods in Engineering,
    Volume 7, pages 461-477, 1973.

    Joe Thompson, Bharat Soni, Nigel Weatherill,
    Handbook of Grid Generation,
    CRC Press,
    1999.

    :param double R, S, T
        The coordinates where an interpolated value is desired.

    :param double X000, X001, X010, X011, X100, X101, X110, X111
        The data values at the corners.

    :param double XR00, XR01, XR10, XR11, X0S0, X0S1, X1S0, X1S1, X00T, X01T, X10T, X11T
        The data values at points along the edges.

    Input, double X0ST, X1ST, XR0T, XR1T, XRS0, XRS1, the data values
    at points on the faces.

    :return double
        The interpolated data value at (R,S,T).
    '''

    return (
       + ( 1.0 - r ) * ( 1.0 - s ) * ( 1.0 - t ) * x000
       - ( 1.0 - r ) * ( 1.0 - s )               * x00t
       + ( 1.0 - r ) * ( 1.0 - s ) *         t   * x001
       - ( 1.0 - r )               * ( 1.0 - t ) * x0s0
       + ( 1.0 - r )                             * x0st
       - ( 1.0 - r )               *         t   * x0s1
       + ( 1.0 - r ) *         s   * ( 1.0 - t ) * x010
       - ( 1.0 - r ) *         s                 * x01t
       + ( 1.0 - r ) *         s   *         t   * x011
       -               ( 1.0 - s ) * ( 1.0 - t ) * xr00
       +               ( 1.0 - s )               * xr0t
       -               ( 1.0 - s ) *         t   * xr01
       +                             ( 1.0 - t ) * xrs0
       +                                     t   * xrs1
       -                       s   * ( 1.0 - t ) * xr10
       +                       s                 * xr1t
       -                       s   *         t   * xr11
       +         r   * ( 1.0 - s ) * ( 1.0 - t ) * x100
       -         r   * ( 1.0 - s )               * x10t
       +         r   * ( 1.0 - s ) *         t   * x101
       -         r                 * ( 1.0 - t ) * x1s0
       +         r                               * x1st
       -         r                 *         t   * x1s1
       +         r   *         s   * ( 1.0 - t ) * x110
       -         r   *         s                 * x11t
       +         r   *         s   *         t   * x111
    )
```

```python
def Blend_rst_1DN(r, s, t, bound_rst):
    '''
    Extends vector data on edges into a cube.

    Diagram:

    010-----r10-----110    011-----r11-----111
      |       .       |      |       .       |
      |       .       |      |       .       |
    0s0.....rs0.....1s0    0s1.....rs1.....1s1     S
      |       .       |      |       .       |     |
      |       .       |      |       .       |     |
    000-----r00-----100    001-----r01-----101     +----R
       BOTTOM                      TOP

    011-----0s1-----001    111-----1s1-----101
      |       .       |      |       .       |
      |       .       |      |       .       |
    01t.....0st.....00t    11t.....1st.....10t          T
      |       .       |      |       .       |          |
      |       .       |      |       .       |          |
    010-----0s0-----000    110-----1s0-----100     S----+
       LEFT                       RIGHT

    001-----r01-----101    011-----r11-----111
      |       .       |      |       .       |
      |       .       |      |       .       |
    00t.....r0t.....100    01t.....r1t.....11t     T
      |       .       |      |       .       |     |
      |       .       |      |       .       |     |
    000-----r00-----100    010-----r10-----110     +----R
       FRONT                       BACK

    Reference:

    William Gordon, Charles A Hall,
    Construction of Curvilinear Coordinate Systems and Application to
    Mesh Generation,
    International Journal of Numerical Methods in Engineering,
    Volume 7, pages 461-477, 1973.

    Joe Thompson, Bharat Soni, Nigel Weatherill,
    Handbook of Grid Generation,
    CRC Press,
    1999.

    :param double r, s, t
        The (r,s,t) coordinates of the point to be evaluated.

    :return double X(n)
        The interpolated value at the point (r,s,t).

    :param method bound_rst
        A subroutine which is given (r,s,t) coordinates, it returns the value
        at that point. "bound_rst" will only be called for "edges", that is,
        for values (r,s,t) where at least two of r, s and t are either 0.0 or
        1.0. "bound_rst" has the form:

            x = bound_rst(r, s, t)
    '''
    #  Get the I-th coordinate component at the corners.
    x000 = bound_rst(0.0, 0.0, 0.0)
    x001 = bound_rst(0.0, 0.0, 1.0)
    x010 = bound_rst(0.0, 1.0, 0.0)
    x011 = bound_rst(0.0, 1.0, 1.0)
    x100 = bound_rst(1.0, 0.0, 0.0)
    x101 = bound_rst(1.0, 0.0, 1.0)
    x110 = bound_rst(1.0, 1.0, 0.0)
    x111 = bound_rst(1.0, 1.0, 1.0)

    #  Get the I-th coordinate component at the edges.
    xr00 = bound_rst(r, 0.0, 0.0)
    xr01 = bound_rst(r, 0.0, 1.0)
    xr10 = bound_rst(r, 1.0, 0.0)
    xr11 = bound_rst(r, 1.0, 1.0)

    x0s0 = bound_rst(0.0, s, 0.0)
    x0s1 = bound_rst(0.0, s, 1.0)
    x1s0 = bound_rst(1.0, s, 0.0)
    x1s1 = bound_rst(1.0, s, 1.0)

    x00t = bound_rst(0.0, 0.0, t)
    x01t = bound_rst(0.0, 1.0, t)
    x10t = bound_rst(1.0, 0.0, t)
    x11t = bound_rst(1.0, 1.0, t)

    #  Interpolate the I-th component on the faces.
    x0st = Blend_1D1(s, t, x000, x001, x010, x011, x0s0, x0s1, x00t, x01t)
    x1st = Blend_1D1(s, t, x100, x101, x110, x111, x1s0, x1s1, x10t, x11t)
    xr0t = Blend_1D1(r, t, x000, x001, x100, x101, xr00, xr01, x00t, x10t)
    xr1t = Blend_1D1(r, t, x010, x011, x110, x111, xr10, xr11, x01t, x11t)
    xrs0 = Blend_1D1(r, s, x000, x010, x100, x110, xr00, xr10, x0s0, x1s0)
    xrs1 = Blend_1D1(r, s, x001, x011, x101, x111, xr01, xr11, x0s1, x1s1)

    #  Interpolate the I-th coordinate component of the interior point.
    return Blend_2D1(
        r, s, t, x000, x001, x010, x011, x100, x101,
        x110, x111, xr00, xr01, xr10, xr11, x0s0, x0s1, x1s0, x1s1,
        x00t, x01t, x10t, x11t, x0st, x1st, xr0t, xr1t, xrs0, xrs1
        )
```

```python
# view edges
def CalculateEdges(n_points, edge_function):
    scat = []
    for zeta in [0,1]:
        for eta in [0,1]:
            for ksi in np.linspace(0,1,n_points):
                x,y,z = Blend_rst_1DN(ksi, eta, zeta, edge_function)
                scat.append([x,y,z])
    for zeta in np.linspace(0,1,n_points):
        for eta in [0,1]:
            for ksi in [0,1]:
                x,y,z = Blend_rst_1DN(ksi, eta, zeta, edge_function)
                scat.append([x,y,z])
    for zeta in [0,1]:
        for eta in np.linspace(0,1,n_points):
            for ksi in [0,1]:
                x,y,z = Blend_rst_1DN(ksi, eta, zeta, edge_function)
                scat.append([x,y,z])
    return np.array(scat)

# view faces
def CalculateFaces(n_points, edge_function):
    scat = []
    for zeta in [0,1]:
        for eta in np.linspace(0,1,n_points):
            for ksi in np.linspace(0,1,n_points):
                x,y,z = Blend_rst_1DN(ksi, eta, zeta, edge_function)
                scat.append([x,y,z])
    for zeta in np.linspace(0,1,n_points):
        for eta in [0,1]:
            for ksi in np.linspace(0,1,n_points):
                x,y,z = Blend_rst_1DN(ksi, eta, zeta, edge_function)
                scat.append([x,y,z])
    for zeta in np.linspace(0,1,n_points):
        for eta in np.linspace(0,1,n_points):
            for ksi in [0,1]:
                x,y,z = Blend_rst_1DN(ksi, eta, zeta, edge_function)
                scat.append([x,y,z])
    return np.array(scat)

# view all
def CalculateBody(n_points, edge_function):
    scat = []
    for zeta in np.linspace(0,1,n_points):
        for eta in np.linspace(0,1,n_points):
            for ksi in np.linspace(0,1,n_points):
                x,y,z = Blend_rst_1DN(ksi, eta, zeta, edge_function)
                scat.append([x,y,z])
    return np.array(scat)

```

The function below returns the edges of a round hexahedron. The full object will be achieved through the usage of transfinite interpolation using the "edge function" of the round hexahedron.

```python
def F_roundhexa(ksi, eta, zeta, r = 0.3, tol=1.0e-12, center=[0.5,0.5,0.5]):
    x0, y0, z0 = center
    H = 0.5
    L = 0.9
    C1 = np.append(F_circle(0,0, r=r, center=[x0,y0]), H)
    C2 = np.append(F_circle(0,1, r=r, center=[x0,y0]), H)
    C3 = np.append(F_circle(1,0, r=r, center=[x0,y0]), H)
    C4 = np.append(F_circle(1,1, r=r, center=[x0,y0]), H)

    S1 = np.append(F_circle(0,0, r=L, center=[x0,y0]), 0.0)
    S2 = np.append(F_circle(0,1, r=L, center=[x0,y0]), 0.0)
    S3 = np.append(F_circle(1,0, r=L, center=[x0,y0]), 0.0)
    S4 = np.append(F_circle(1,1, r=L, center=[x0,y0]), 0.0)

    if np.abs(zeta-1) < tol:
        # not sure why I had to invert eta, ksi
        # it should be ksi, eta (something to do with base vectors?)
        x,y = F_circle(eta, ksi, r=r, tol=tol, center=[x0,y0])
        z = zeta*H
        return np.array([x,y,z])

    if np.abs(zeta) < tol:
        P1 = F_circle(0,0, r=L, center=[x0,y0])
        P2 = F_circle(0,1, r=L, center=[x0,y0])
        P3 = F_circle(1,0, r=L, center=[x0,y0])
        P4 = F_circle(1,1, r=L, center=[x0,y0])
        node_values=np.array([P2,P1,P3,P4])
        x, y = InterpolateQuad(ksi, eta, node_values)
        z = zeta*H
        return np.array([x,y,z])

    if np.abs(ksi-1) < tol:
        if np.abs(eta-1) < tol:
            x,y,z = (C4 - S4)*zeta + S4
            return np.array([x,y,z])

        if np.abs(eta) < tol:
            x,y,z = (C2 - S2)*zeta + S2
            return np.array([x,y,z])

    if np.abs(ksi) < tol:
        if np.abs(eta-1) < tol:
            x,y,z = (C3 - S3)*zeta + S3
            return np.array([x,y,z])

        if np.abs(eta) < tol:
            x,y,z = (C1 - S1)*zeta + S1
            return np.array([x,y,z])

    print ("ERROR: Did not enter if.")
    assert False
```

```python
# From Lucci Paulo Dissertation:
def RoundHexa(ksi, eta, zeta):
    x = 0.125*(-5.2*ksi*zeta + 2.8*ksi + 1.697*(ksi*zeta + ksi)*np.cos(0.785*eta) + 1.697*(zeta + 1.0)*np.sin(0.785*ksi))
    y = 0.125*(-5.2*eta*zeta + 2.8*eta + 1.697*(eta*zeta + eta)*np.cos(0.785*ksi) + 1.697*(zeta + 1.0)*np.sin(0.785*eta))
    z = zeta

    return np.array([x,y,z])

def ComputeReferenceModel(n_points):
    scat = []
    for zeta in np.linspace(-1,1,n_points):
        for eta in np.linspace(-1,1,n_points):
            for ksi in np.linspace(-1,1,n_points):
                x,y,z = RoundHexa(ksi, eta, zeta)
                scat.append([x,y,z])
    return np.array(scat)
```

```python
scat = CalculateBody(n_points=10, edge_function=F_roundhexa)
scat_ref = ComputeReferenceModel(n_points=10)

# Plot results
plt.close(1)
fig = plt.figure(1)

ax = fig.add_subplot(211, projection='3d')
surf = ax.scatter(scat[:,0], scat[:,1], scat[:,2])
ax.tick_params(axis='both', which='major', labelsize=8)

ax = fig.add_subplot(212, projection='3d')
surf = ax.scatter(scat_ref[:,0], scat_ref[:,1], scat_ref[:,2])
ax.tick_params(axis='both', which='major', labelsize=8)

plt.show()
```

```python

```


```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use('classic')
%matplotlib inline
```

# Homework 5

**Instructions:** Complete the notebook below. Download the completed notebook in HTML format. Upload assignment using Canvas.

**Due:** Feb. 9 at **2pm.**

## Exercise: AR(1) Process Simulation

For each of the AR(1) processes defined below:

1. Compute $y$ for $201$ periods (i.e., $t=0,\ldots, 200$).
2. Construct a well-labeled plot of $y$ for each simulation.
2. Print the mean and standard deviation of the stimulated process.

You may use the function from the Notebook for Lecture 10, write your own, or take a more brute force approach.

```python
# Define a function for computing the first-order difference equation. CELL NOT PROVIDED
def ar1_sim(rho,sigma,y0,T,):
    '''Function for simulating an AR(1) process

        y[t] = rho*y[t-1] + epsilon[t]

    Args:
        rho (float):    Autoregressive coefficient on y[t-1]
        sigma (float):  Standard deviation of epsilon
        y0 (float):     Initial value of the process
        T (int):        Number of periods to simulate including initial period

     Returns:
         NumPy ndarray
     '''

    y = np.zeros(T)
    epsilon = np.random.normal(scale=sigma,size=T)
    y[0] = y0
    for t in range(T-1):
        y[t+1] = rho*y[t]+epsilon[t+1]

    return y

# Create variable 'T' equal to number of simulation periods
T = 201

# Set NumPy RNG seed to 126 st that simulations look the same each time I run the Notebook
np.random.seed(126)
```

1. $y_{t} = 0.33 y_{t-1} + \epsilon_t, \; \; \; \; \epsilon \sim \mathcal{N}(0,1), \; \; \; \; y_0 = 0$

```python
rho=0.33
sigma = 1
y0 = 0

y = ar1_sim(rho,sigma,y0,T)

plt.plot(y,lw=3)
plt.title('AR(1): $\\rho='+str(rho)+'$ and $\\sigma='+str(sigma)+'$')
plt.xlabel('$t$',fontsize=15)
plt.ylabel('$y_t$',fontsize=15)
plt.grid()
```

```python
# print mean of y
print('mean of y:              ',round(y.mean(),4))

# Print standard deviation of y
print('standard deviation of y: ',round(y.std(),4))
```

    mean of y:               -0.0743
    standard deviation of y:  1.0322

2. $y_{t} = y_{t-1} + \epsilon_t, \; \; \; \; \epsilon \sim \mathcal{N}(0,1), \; \; \; \; y_0 = 0$

```python
rho=1
sigma = 1
y0 = 0

y = ar1_sim(rho,sigma,y0,T)

plt.plot(y,lw=3)
plt.title('AR(1): $\\rho='+str(rho)+'$ and $\\sigma='+str(sigma)+'$')
plt.xlabel('$t$',fontsize=15)
plt.ylabel('$y_t$',fontsize=15)
plt.grid()
```

```python
# print mean of y
print('mean of y:              ',round(y.mean(),4))

# Print standard deviation of y
print('standard deviation of y:',round(y.std(),4))
```

    mean of y:               -3.0308
    standard deviation of y: 3.8289

3. $y_{t} = -0.95 y_{t-1} + \epsilon_t, \; \; \; \; \epsilon \sim \mathcal{N}(0,1), \; \; \; \; y_0 = 0$

```python
rho=-0.95
sigma = 1
y0 = 0

y = ar1_sim(rho,sigma,y0,T)

plt.plot(y,lw=3)
plt.title('AR(1): $\\rho='+str(rho)+'$ and $\\sigma='+str(sigma)+'$')
plt.xlabel('$t$',fontsize=15)
plt.ylabel('$y_t$',fontsize=15)
plt.grid()
```

```python
# print mean of y
print('mean of y:              ',round(y.mean(),4))

# Print standard deviation of y
print('standard deviation of y: ',round(y.std(),4))
```

    mean of y:               -0.0131
    standard deviation of y:  3.1956

4. $y_{t} = 0.75 y_{t-1} + \epsilon_t , \; \; \; \; \epsilon \sim \mathcal{N}(0,4), \; \; \; \; y_0 = 20$

```python
rho=0.75
sigma = 2
y0 = 20

y = ar1_sim(rho,sigma,y0,T)

plt.plot(y,lw=3)
plt.title('AR(1): $\\rho='+str(rho)+'$ and $\\sigma='+str(sigma)+'$')
plt.xlabel('$t$',fontsize=15)
plt.ylabel('$y_t$',fontsize=15)
plt.grid()
```

```python
# print mean of y
print('mean of y:              ',round(y.mean(),4))

# Print standard deviation of y
print('standard deviation of y:',round(y.std(),4))
```

    mean of y:               0.8346
    standard deviation of y: 3.6313

5. $y_{t} = 0.9 y_{t-1} + \epsilon_t , \; \; \; \; \epsilon \sim \mathcal{N}(0,3), \; \; \; \; y_0 = 0$

```python
rho=0.9
sigma = np.sqrt(3)
y0 = 0

y = ar1_sim(rho,sigma,y0,T)

plt.plot(y,lw=3)
plt.title('AR(1): $\\rho='+str(rho)+'$ and $\\sigma='+str(round(sigma,4))+'$')
plt.xlabel('$t$',fontsize=15)
plt.ylabel('$y_t$',fontsize=15)
plt.grid()
```

```python
# print mean of y
print('mean of y:              ',round(y.mean(),4))

# Print standard deviation of y
print('standard deviation of y: ',round(y.std(),4))
```

    mean of y:               2.8525
    standard deviation of y:  4.5849

## Exercise: Stochastic Growth

We've seen in class that total factory productivity (TFP) in the US fluctuates over the business cycle around a long-term growth trend. That is, total factor productivity is characterized by *stochastic growth*. In this problem, you will simulate a model of TFP with stochastic growth.

### Background

Suppose that the trend component of TFP $A^{trend}$ grows smoothly at the constant rate $g$:

\begin{align}
A^{trend}_t & = (1+g)A_{t-1}^{trend}, \tag{1}
\end{align}

where $A_0$ is given. The log-deviations of actual TFP $A$ from trend TFP are determined according to an AR(1) process:

\begin{align}
\log\left(A_t/A^{trend}_t\right) & = \rho \log \left(A_{t-1} / A^{trend}_{t-1}\right) + \epsilon_t, \tag{2}
\end{align}

where $A_0$ is given and $\epsilon_t \sim \mathcal{N}(0,\sigma^2)$. Solve quation (2) for $A_t$ to get an expression for $A_t$ in terms of $A_{t-1}$, $A^{trend}_{t}$, $A^{trend}_{t-1}$, and $\epsilon_t$:

\begin{align}
A_t & = A^{trend}_t \displaystyle e^{\rho \log \left(A_{t-1} / A^{trend}_{t-1}\right) + \epsilon_t} \tag{3}
\end{align}

### Simulation

To simulate this model:

1. Specify values for $A_0$ and $A^{trend}_0$.
2. For $t = 0, 1, \ldots... T$, compute $A^{trend}_{t+1}$ using equation (1) and then compute $A_{t+1}$ using equation (3)

Use the following prameter values for this simulation

| $$A^{trend}_0$$ | $$A_0$$ | $$g$$  | $$\rho$$ | $$\sigma$$ | $$T$$ |
|-----------------|---------|--------|----------|------------|-------|
| 1               | 1       | 0.02   | 0.7      | 0.025      | 101   |

In the follwing cell, simulate $A^{trend}$ and $A$ using the parameter values provided.

```python
# Simulate the stochastic growth model of TFP
T = 101
g = 0.02
rho = 0.7
sigma = 0.025

epsilon = np.random.normal(scale=sigma,size=T)
a_trend = np.ones(T)*1
a = np.ones(T)*1

for t in range(T-1):
    a_trend[t+1] = (1+g)*a_trend[t]
    a[t+1] = a_trend[t+1]*np.exp(rho*np.log(a[t]/a_trend[t])+epsilon[t])
```

Contruct a well-labeled plot of the simulated values of $A$ and $A^{trend}$

```python
# Construct a plot of simulated TFP with its trend with:
#    1. Actual line: blue with lw=1, alpha=0.7, label = 'actual'
#    2. Trend line:  red with lw=3, alpha=0.25, label = 'trend'
plt.plot(a_trend,'r',lw=3,alpha=0.25,label='Trend')
plt.plot(a,label='Actual')
plt.title('Simulated TFP with Stochastic Growth')
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
plt.grid()
```

Contruct a well-labeled plot of the simulated values of $\log \left(A/A^{trend}\right)$

```python
# Construct a plot of simulated log deviation of TFP from trend
plt.plot(np.log(a/a_trend),lw=3)
plt.title('Simulated $\log A_t/A^{trend}$')
plt.grid()
```

Compute the mean and standard deviation of $\log \left(A/A^{trend}\right)$

```python
# print mean of log(A/A trend)
print('mean of log(A/A trend):              ',round((np.log(a/a_trend)).mean(),4))

# Print standard deviation of log(A/A trend)
print('standard deviation of log(A/A trend): ',round((np.log(a/a_trend)).std(),4))
```

    mean of log(A/A trend):               -0.0192
    standard deviation of log(A/A trend):  0.03

## Exercise: Questions for Prescott (1986) Reading

The following questions are about Edward Prescott's 1986 article "Theory  Ahead of Business Cycle Measurement" from the Fall 1986 issue of the Minneapolis Fed's *Quarterly Review* (link to article: [https://www.minneapolisfed.org/research/qr/qr1042.pdf](https://www.minneapolisfed.org/research/qr/qr1042.pdf)). The article is a summary of the research agenda of the author, his main collaborator, Finn Kydland, and others. That agenda entailed incorporating stochastic growth of TFP into the neoclassical growth model (essentially a Solow model but with the saving rate determined as a consequence a of utility maximization problem) as a way of modeling business cycle fluctuations. This line of research would later be called *real business cycle* theory.

The below questions are specifically about:
* Pages 9-11 (including the table on page 12)
* The final paragraph on page 21

**Question:** On page 10, Prescott writes that the "models constructed within this theoretical framework are necessarily highly abstract. Consequently they are necessarily false." What does Kydland mean by this? In what sense are abstract models necessarily false?

**Answer**

All theoretical models are approximations of reality and therefore will not relfect the complexity of the natural environment. But that doesn't mean that abstract models can't be useful. In fact, abstraction helps us understand complicated phenomena by emphasizing only the most relevant aspects of the issue. <!-- answer -->

**Question:** On page 10, Prescott lists two reasons for why he doesn't like the term *business cycle*. What is the first reason and what does it mean?

**Answer**

The first reason that Prescott doesn't like the term *business cycle* is that it implies that short-term fluctuations are the result of economic forces that are *independent* of long-term growth. In Prescott's view, short-term fluctuations and long-term growth *are a consequence of the same process*: stochastic TFP growth. That is, TFP grows, but at a random and unpredictable rate.<!-- answer -->

**Question:** Table 1 on page 12 shows that household consumption measures in the US fluctuate less than investment measures. What is the intuition for why consumption varies less than investment?

**Answer**

People like to smooth their consumption over time as much as they can. When houseohlds have higher-than-average income, they save most for the future and so investment rises more than consumption when incomes are high. And when household incomes are lower-than-average, households primarily reduce saving and investment falls more than consumption when incomes are high.<!-- answer -->

**Question:** In the last paragraph of page 21, to what does Prescott attribute economic fluctuations? Can you think of any reasons why this statement was, and still is, controversial?

**Answer**

"Economic fluctuations are the optimal responses to uncertainty in the rate of technological change." The statement is controversial for several reasons. <!-- answer -->
* First, the argument attributes *all* economic fluctuations to TFP variation and disregards other plausible candidates like, for example, exogenous variation in monetary policy. <!-- answer -->
* Second, Prescott's claim that fluctuations are *optimal* implies that the economy is *always* doing the best it can with what it has: When unemployment is high, it's because TFP fell relative to trend and households decided to take more leisure. <!-- answer -->
* Third, the statement implies that, even in principle, policymakers cannot improve the welfare of the public by stabilizing the business cycle. <!-- answer -->


# Custom Functions for NRPy+

## Author: Patrick Nelson

## Introduction

Using SymPy, NRPy+ is able to provide a lot of different functions out of the box, covering many different use cases. However, there are some more obscure functions that we can't access; we may also want to use a different method than the SymPy function uses or use data from multiple different points.

The code below first tells SymPy that `nrpyMyFunc` should be treated as a SymPy function. This name is what should be used in the python code. Then, a dictionary is imported from `outputC` so we can add an entry to it. The key for the entry should be identical to the function; the entry (`myFunc_Ccode` in the example below) should be identical to the name of the function in the C code.
```python
import sympy as sp

nrpyMyFunc = sp.Function('nrpyMyFunc')
from outputC import custom_functions_for_SymPy_ccode
custom_functions_for_SymPy_ccode["nrpyMyFunc"] = "myFunc_Ccode"
```

The above method is not restricted to functions; macros can be used as well. Additionally, SymPy interprets the argument of the function quite generously; the argument of the SymPy function can be more SymPy code, a string, or even nothing (possibly more!). Consider the following examples; the Python code will be on the left and the resulting C code on the right:
* `x = nrpyMyFunc(y)` $\rightarrow$ `x = myFunc_Ccode(y)`
    * Here, the Python symbol y can be any SymPy expression; the appropriate corresponding expression will be substituted in the resulting C code, and CSE will be applied as well.
* `x = nrpyMyFunc()` $\rightarrow$ `x = myFunc_Ccode()`
* `x = nrpyMyFunc` $\rightarrow$ `x = myFunc_Ccode`
* `x = nrpyMyFunc("SOME_GF")` $\rightarrow$ `x = myFunc_Ccode(SOME_GF)`

As you can see, we have considerable flexibility in how the C code turns out.

### Obscure functions

Most functions that we may need to use (such as trigonometric or logarithmic functions) are already included in SymPy. However, there are myriad other types of functions that can show up in various problems, such as the dilogarithm function, which is needed for the [Split Monopole initial data in `GiRaFFE_NRPy`](Tutorial-GiRaFFEfood_NRPy-Split_Monopole.ipynb). In this case, we were able to use a preexisting library, but this is not necessary in general; it is just as possible to add a function that you have written yourself.

### Changing functionality

Sometimes, SymPy may have the function you need, but implemented in a way that is not ideal. This was the case when we wrote [Tutorial-Min_Max_and_Piecewise_Expressions](../Tutorial-Min_Max_and_Piecewise_Expressions.ipynb). The SymPy implementation `sp.Abs` assumed a complex input (even when we specified that the input was real), resulting in a function that was much more computationally expensive than it needed to be and introducing errors into the produced C code. We solved this by adding `nrpyAbs` to the dictionary, pointing to the basic C implementation of the function.

### Multiple gridpoints

Operations like interpolation are critical to some applications (`GiRaFFE_NRPy` in particular depends on interpolation for its staggered grids). However, the only context in which data is read from multiple gridpoints in NRPy+ is finite-difference derivatives. For the interpolation of metric gridfunctions required in `GiRaFFE_NRPy`, we handwrote an entire function to replicate a macro in the original `GiRaFFE`, requiring several extra gridfunctions of storage compared to the original code. An alternative would be to port the macro to the `GiRaFFE_NRPy` C code and use the above method to allow SymPy-generated kernels to use it.

A basic interpolator could be written as follows:
```C
#define A0 1.0
#define A1 1.0
#define METRIC   auxevol_gfs[IDX4S(GF_TO_INTERP, i0,i1,i2)]
#define METRICp1 auxevol_gfs[IDX4S(GF_TO_INTERP, i0+(flux_dirn==0),i1+(flux_dirn==1),i2+(flux_dirn==2))]
#define METRIC_INTERPED(GF_TO_INTERP) (A0*(METRIC) + A1*(METRICp1))
```
This computes the average of a values of the some gridfunction `GF_TO_INTERP` at the current point and the next one in a given `flux_dirn`, which is a basic interpolation of the value at the cell face.

Naturally, one must take care to use the correct gridfunction macros as defined in the C code. A function could be written to automate this because SymPy expressions can be easily cast to strings and NRPy+'s gridfunction `#define`s are straightforwrard to generate. For instance, the following could be used:

```python
import os,sys
nrpy_dir_path = os.path.join("..")
if nrpy_dir_path not in sys.path:
    sys.path.append(nrpy_dir_path)

from outputC import outputC,custom_functions_for_SymPy_ccode # NRPy+: Core C code output module
import sympy as sp               # SymPy: The Python computer algebra package upon which NRPy+ depends
import grid as gri               # NRPy+: Functions having to do with numerical grids
import indexedexp as ixp         # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support

gammaDD = ixp.register_gridfunctions_for_single_rank2("AUXEVOL","gammaDD","sym01")
alpha = gri.register_gridfunctions("AUXEVOL","alpha")

nrpyMetricInterp = sp.Function('nrpyMetricInterp')

def gf_id_of(gridfunction):
    return (str(gridfunction).upper() + "GF")

custom_functions_for_SymPy_ccode["nrpyMetricInterp"] = "METRIC_INTERPED"

x = nrpyMetricInterp(gf_id_of(alpha)) * nrpyMetricInterp(gf_id_of(gammaDD[0][0]))
outputC(x,"x")
```

    /*
     *  Original SymPy expression:
     *  "x = nrpyMetricInterp(ALPHAGF)*nrpyMetricInterp(GAMMADD00GF)"
     */
    {
      x = METRIC_INTERPED(ALPHAGF)*METRIC_INTERPED(GAMMADD00GF);
    }


## Gráfica de T de rocío

$f(\psi)=\sum_{i=1}^{c}\frac{z_i[1-k_i]}{1+\psi[k_i-1]}$

Fracciones del problema realizado en clase:

$z_{n-Butano}=0.2$

$z_{n-Pentano}=0.25$

$z_{n-Hexano}=0.25$

$z_{n-Heptano}=0.3$

Constantes del problema realizado en clase:

$K_{n-Butano}=4$

$K_{n-Pentano}=1.9$

$K_{n-Hexano}=1$

$K_{n-Heptano}=0.5$

Desarrollando la expresión:

$f(\psi)=\frac{z_1[1-k_1]}{1+\psi[k_1-1]} + \frac{z_2[1-k_2]}{1+\psi[k_2-1]} + \frac{z_3[1-k_3]}{1+\psi[k_3-1]} + \frac{z_4[1-k_4]}{1+\psi[k_4-1]}$

```python
# Librerias
import numpy as np
from numpy import poly1d,polyfit
import matplotlib.pyplot as plt
from sympy import Symbol

# Para imprimir en formato LaTex
from sympy.interactive import printing
printing.init_printing(use_latex=True)
```

## Derivada de la función

```python
psi = Symbol('psi')

z_1 = Symbol('z_1')
z_2 = Symbol('z_2')
z_3 = Symbol('z_3')
z_4 = Symbol('z_4')

k_1 = Symbol('k_1')
k_2 = Symbol('k_2')
k_3 = Symbol('k_3')
k_4 = Symbol('k_4')

f_psi= ((z_1*(1-k_1)/(1+psi*(k_1-1))) + (z_2*(1-k_2)/(1+psi*(k_2-1))) + (z_3*(1-k_3)/(1+psi*(k_3-1))) + (z_4*(1-k_4)/(1+psi*(k_4-1))))
dpsi = f_psi.diff(psi)

print('Función:')
display(f_psi)

print('Derivada:')
display(dpsi)

```

## Funciones propuestas

```python
def Rachford_Rice_4(z,k,Li,Ls,p):
    psi = np.arange(Li,Ls+p,p)
    f_psi = []
    for value in psi:
        f_psi.append((z[0]*(1-k[0])/(1+value*(k[0]-1))) + (z[1]*(1-k[1])/(1+value*(k[1]-1))) + (z[2]*(1-k[2])/(1+value*(k[2]-1))) + (z[3]*(1-k[3])/(1+value*(k[3]-1))) )

    return psi,f_psi

def find_root_interval(given):
    i = 0
    for value in given:
        if value >=0:
            P_1 = i
            P_0 = i-1
        else:
            i += 1
    return P_0,P_1

def interp(x_1,x_2,y_1,y_2,y_n):
    x_n = x_1 -((x_1-x_2)*(y_1-y_n)/(y_1-y_2))
    return x_n , y_n
```

## Gráfica problema 1 21/01/2020

```python
# Diseño de la gráfica por forma vectorizada
psi = np.arange(0,1.1,.1)
f_psia= ((z[0]*(1-k[0])/(1+psi*(k[0]-1))) + (z[1]*(1-k[1])/(1+psi*(k[1]-1))) + (z[2]*(1-k[2])/(1+psi*(k[2]-1))) + (z[3]*(1-k[3])/(1+psi*(k[3]-1))))

plt.plot(psi,f_psia,marker='*')
plt.grid()
plt.xlabel('$\psi$')
plt.ylabel('$f(\psi$)').set_rotation(0)
plt.axvline(x=1,color='r',ls='dotted')
plt.axvline(x=0,color='r',ls='dotted')
# Prueba con el resultado de la sumatoria
plt.axhline(y=0.0315,color='green',ls='dotted',label='Diferencial al valor 0')
plt.legend()
plt.show()
```

```python
# Diseño de la gráfica por medio de Sumatoria
z = [0.2,0.25,0.25,0.3]
k = [4, 1.9, 1, 0.5]

[psi,f_psi] = Rachford_Rice_4(z,k,0,1,.1)

p2 = 0.0315
plt.plot(psi,f_psi, marker='*')
plt.grid()
plt.xlabel('$\psi$')
plt.ylabel('$f(\psi$)').set_rotation(0)
plt.axvline(x=1,color='r',ls='dotted')
plt.axvline(x=0,color='r',ls='dotted')
# Prueba con el resultado de la sumatoria
plt.axhline(y=0.0315,color='green',ls='dotted',label='Diferencial al valor 0')
plt.legend()
plt.show()
```

## Gráfica problema 2 22/01/2019

```python
Z_p=[.2,.25,.25,.3]
K_p=[3.4,1.5,.7,.35]

[psi,f_psi] = Rachford_Rice_4(Z_p,K_p,0,1,.1)

plt.figure(figsize=(10,5))
plt.title("Gráfica en el intervalo de 0 a 1")
plt.plot(psi,f_psi,marker='.')
plt.grid()
plt.xlabel('$\psi$')
plt.ylabel('$f(\psi$)').set_rotation(0)
plt.axvline(x=1,color='r',ls='dotted')
plt.axvline(x=0,color='r',ls='dotted')
plt.show()

plt.figure(figsize=(10,5))
[psi,f_psi] = Rachford_Rice_4(Z_p,K_p,-10,10,1)

plt.plot(psi,f_psi,marker='.')
plt.grid()
plt.xlabel('$\psi$')
plt.ylabel('$f(\psi$)').set_rotation(0)
plt.title("Gráfica en el intervalo de -10 a 10")
plt.axvline(x=1,color='r',ls='dotted')
plt.axvline(x=0,color='r',ls='dotted')
plt.show()

```

## Problema gráfica 3 27/01/2020

```python
# Diseño de la gráfica por forma vectorizada considerando tres mezclas
psi = np.arange(0,1.1,.1)
k =[2.5,1.1,0.53]
z = [.25,.4,.35]
f_psia= ((z[0]*(1-k[0])/(1+psi*(k[0]-1))) + (z[1]*(1-k[1])/(1+psi*(k[1]-1))) + (z[2]*(1-k[2])/(1+psi*(k[2]-1))))

# Obtencion del polinomio
Poly = poly1d(polyfit(psi,f_psia, deg=3), variable='x')
print(Poly)

# Evaluacion del polinomio en el rango de nuestra psi
y = Poly(psi)

# Encontrando las raices de nuestro polinomio
[min,max]=find_root_interval(f_psia)
[x_n,y_n] = interp(psi[6],psi[7],f_psia[6],f_psia[7],0)
print(f"La Raíz es:[{x_n},{y_n}]")

# Dimensiones de la grafica
plt.figure(figsize=(15,10))

# Grafica del polinomio
plt.plot(psi,y,marker=".",label=f"{Poly}")
plt.grid()

# Grafica para los valores de psi y F(psi)
plt.plot(psi,f_psia,marker='*',color="#00C72E",label="Función sumatoria")

# Muestra del punto donde y = 0
plt.plot(x_n,y_n,marker="X",color="red",label=f"Raíz = {x_n}")

# Prueba con el resultado de la sumatoria
plt.axhline(y=0.0,color='#434F66')

# Lineas Verticales y horizontales
plt.axvline(x=1,color='r',ls='dotted')
plt.axvline(x=0,color='r',ls='dotted')
plt.axvline(x=x_n,color='b',ls='dotted')

# Formato de gráfica
plt.title("$f (\psi)$ vs $\psi$",fontsize=20)
plt.grid(True)
plt.xlabel('$\psi$', fontsize=15)
plt.ylabel('$f(\psi$)', fontsize=15).set_rotation(0)
plt.legend()
plt.show()
```

```python
# Diseño de la gráfica por forma vectorizada considerando tres mezclas
psi = np.arange(0,1.1,.1)
k =[2.2,1.1,0.5]
z = [.25,.4,.35]
f_psia= ((z[0]*(1-k[0])/(1+psi*(k[0]-1))) + (z[1]*(1-k[1])/(1+psi*(k[1]-1))) + (z[2]*(1-k[2])/(1+psi*(k[2]-1))))

# Obtencion del polinomio
Poly = poly1d(polyfit(psi,f_psia, deg=3), variable='x')
print(Poly)

# Evaluacion del polinomio en el rango de nuestra psi
y = Poly(psi)

# Encontrando las raices de nuestro polinomio
[min,max]=find_root_interval(f_psia)
[x_n,y_n] = interp(psi[6],psi[7],f_psia[6],f_psia[7],0)
print(f"La Raíz es:[{x_n},{y_n}]")

# Dimensiones de la grafica
plt.figure(figsize=(15,10))

# Grafica del polinomio
plt.plot(psi,y,marker=".",label=f"{Poly}")
plt.grid()

# Grafica para los valores de psi y F(psi)
plt.plot(psi,f_psia,marker='*',color="#00C72E",label="Función sumatoria")

# Muestra del punto donde y = 0
plt.plot(x_n,y_n,marker="X",color="red",label=f"Raíz = {x_n}")

# Prueba con el resultado de la sumatoria
plt.axhline(y=0.0,color='#434F66')

# Lineas Verticales y horizontales
plt.axvline(x=1,color='r',ls='dotted')
plt.axvline(x=0,color='r',ls='dotted')
plt.axvline(x=x_n,color='b',ls='dotted')

# Formato de gráfica
plt.title("$f (\psi)$ vs $\psi$",fontsize=20)
plt.grid(True)
plt.xlabel('$\psi$', fontsize=15)
plt.ylabel('$f(\psi$)', fontsize=15).set_rotation(0)
plt.legend()
plt.show()
```

## Problema 4.23 gráfica 4 28/01/2020

```python
# Diseño de la gráfica por forma vectorizada considerando tres mezclas
psi = np.arange(0,1.1,.1)
k =[4.2,1.5,.62,.21]
z = [.25,.25,.25,.25]
f_psia= ((z[0]*(1-k[0])/(1+psi*(k[0]-1))) + (z[1]*(1-k[1])/(1+psi*(k[1]-1))) + (z[2]*(1-k[2])/(1+psi*(k[2]-1))) + (z[3]*(1-k[3])/(1+psi*(k[3]-1))))

# Obtencion del polinomio
Poly = poly1d(polyfit(psi,f_psia, deg=4), variable='x')
print(Poly)

# Evaluacion del polinomio en el rango de nuestra psi
y = Poly(psi)

# Encontrando las raices de nuestro polinomio
[min,max]=find_root_interval(f_psia)
[x_n,y_n] = interp(psi[min],psi[max],f_psia[min],f_psia[max],0)
print(f"Valores : {min},{max}")
'''
AQUÍ HAY UN ERROR IMPORTANTE ---> ¡SOLUCIONADO!
    Los valores de la funcion interp están alterados para ser siempre la posición 6 y 7
    entonces se debe de solucionar utilizando los valones maximos y mínimos mostrados en
    en la función find_root_interval
'''
print(f"La Raíz es:[{x_n},{y_n}]")

# Dimensiones de la grafica
plt.figure(figsize=(15,10))

# Grafica del polinomio
plt.plot(psi,y,marker=".",label=f"{Poly}")
plt.grid()

# Grafica para los valores de psi y F(psi)
plt.plot(psi,f_psia,marker='*',color="#00C72E",label="Función sumatoria")

# Muestra del punto donde y = 0
plt.plot(x_n,y_n,marker="X",color="red",label=f"Raíz = {x_n}")

# Prueba con el resultado de la sumatoria
plt.axhline(y=0.0,color='#434F66')

# Lineas Verticales y horizontales
plt.axvline(x=1,color='r',ls='dotted')
plt.axvline(x=0,color='r',ls='dotted')
plt.axvline(x=x_n,color='b',ls='dotted')

# Formato de gráfica
plt.title("$f (\psi)$ vs $\psi$",fontsize=20)
plt.grid(True)
plt.xlabel('$\psi$', fontsize=15)
plt.ylabel('$f(\psi$)', fontsize=15).set_rotation(0)
plt.legend()
plt.show()
```

# Prueba funciones para determinar las fracciones de los componentes

```python
z = [0.25,0.25,0.25,0.25]
psi_0 = 0.476
k = [4.2,1.5,0.62,0.21]

def fracc(z,psi_0,k):
    i = len()
    Xn =  z/(1+psi_0(k-1))
    return Xn

fracc(z,psi_0,k)
```

```python
%run function_zoo

Pr = interp_y(20,40,7.02,4.34,25)
print(Pr)

upsilon = interp_y(20,40,1.006,0.658,25)
print(upsilon)

# pendiente de añadir al zoo
def fracc(z_i,Psi_c,k_i):
    x_i = []
    y_i = []
    i = 0

    for element in z_i:
        x_i.append(z_i[i]/(1+Psi_c*(k_i[i]-1)))
        y_i.append(k_i[i]*x_i[i])
        i += 1
    return x_i,y_i
```

    (25, 6.35)
    (25, 0.919)

```python
# Diseño de la gráfica por forma vectorizada considerando tres mezclas
psi = np.arange(0,1.1,.1)
k =[4,1.3,.5,.2]
z = [.25,.25,.25,.25]
f_psia= ((z[0]*(1-k[0])/(1+psi*(k[0]-1))) + (z[1]*(1-k[1])/(1+psi*(k[1]-1))) + (z[2]*(1-k[2])/(1+psi*(k[2]-1))) + (z[3]*(1-k[3])/(1+psi*(k[3]-1))))

# Obtencion del polinomio
Poly = poly1d(polyfit(psi,f_psia, deg=4), variable='x')
print(Poly)

# Evaluacion del polinomio en el rango de nuestra psi
y = Poly(psi)

# Encontrando las raices de nuestro polinomio
[min,max]=find_root_interval(f_psia)
[x_n,y_n] = interp(psi[min],psi[max],f_psia[min],f_psia[max],0)
print(f"Valores : {min},{max}")
'''
AQUÍ HAY UN ERROR IMPORTANTE ---> ¡SOLUCIONADO!
    Los valores de la funcion interp están alterados para ser siempre la posición 6 y 7
    entonces se debe de solucionar utilizando los valones maximos y mínimos mostrados en
    en la función find_root_interval
'''
print(f"La Raíz es:[{x_n},{y_n}]")

[a,b] = fracc(z,x_n,k)

print(f"x_i = {a}")
print(f"y_i = {b}")

# Dimensiones de la grafica
plt.figure(figsize=(15,10))

# Grafica del polinomio
plt.plot(psi,y,marker=".",label=f"{Poly}")
plt.grid()

# Grafica para los valores de psi y F(psi)
plt.plot(psi,f_psia,marker='*',color="#00C72E",label="Función sumatoria")

# Muestra del punto donde y = 0
plt.plot(x_n,y_n,marker="X",color="red",label=f"Raíz = {x_n}")

# Prueba con el resultado de la sumatoria
plt.axhline(y=0.0,color='#434F66')

# Lineas Verticales y horizontales
plt.axvline(x=1,color='r',ls='dotted')
plt.axvline(x=0,color='r',ls='dotted')
plt.axvline(x=x_n,color='b',ls='dotted')

# Formato de gráfica
plt.title("$f (\psi)$ vs $\psi$",fontsize=20)
plt.grid(True)
plt.xlabel('$\psi$', fontsize=15)
plt.ylabel('$f(\psi$)', fontsize=15).set_rotation(0)
plt.legend()
plt.show()
```

```python
# Diseño de la gráfica por forma vectorizada considerando tres mezclas
psi = np.arange(0,1.1,.1)
k =[3.8,1.4,.5,.158]
z = [.25,.25,.25,.25]
f_psia= ((z[0]*(1-k[0])/(1+psi*(k[0]-1))) + (z[1]*(1-k[1])/(1+psi*(k[1]-1))) + (z[2]*(1-k[2])/(1+psi*(k[2]-1))) + (z[3]*(1-k[3])/(1+psi*(k[3]-1))))

# Obtencion del polinomio
Poly = poly1d(polyfit(psi,f_psia, deg=4), variable='x')
print(Poly)

# Evaluacion del polinomio en el rango de nuestra psi
y = Poly(psi)

# Encontrando las raices de nuestro polinomio
[min,max]=find_root_interval(f_psia)
[x_n,y_n] = interp(psi[min],psi[max],f_psia[min],f_psia[max],0)
print(f"Valores : {min},{max}")
'''
AQUÍ HAY UN ERROR IMPORTANTE ---> ¡SOLUCIONADO!
    Los valores de la funcion interp están alterados para ser siempre la posición 6 y 7
    entonces se debe de solucionar utilizando los valones maximos y mínimos mostrados en
    en la función find_root_interval
'''
print(f"La Raíz es:[{x_n},{y_n}]")

[a,b] = fracc(z,x_n,k)

print(f"x_i = {a}")
print(f"y_i = {b}")

# Dimensiones de la grafica
plt.figure(figsize=(15,10))

# Grafica del polinomio
plt.plot(psi,y,marker=".",label=f"{Poly}")
plt.grid()

# Grafica para los valores de psi y F(psi)
plt.plot(psi,f_psia,marker='*',color="#00C72E",label="Función sumatoria")

# Muestra del punto donde y = 0
plt.plot(x_n,y_n,marker="X",color="red",label=f"Raíz = {x_n}")

# Prueba con el resultado de la sumatoria
plt.axhline(y=0.0,color='#434F66')

# Lineas Verticales y horizontales
plt.axvline(x=1,color='r',ls='dotted')
plt.axvline(x=0,color='r',ls='dotted')
plt.axvline(x=x_n,color='b',ls='dotted')

# Formato de gráfica
plt.title("$f (\psi)$ vs $\psi$",fontsize=20)
plt.grid(True)
plt.xlabel('$\psi$', fontsize=15)
plt.ylabel('$f(\psi$)', fontsize=15).set_rotation(0)
plt.legend()
plt.show()
```


### VQE（Variational quantum eigensolver）
パラメータ付き量子回路で変分的に基底状態を求めましょう。

### 必要なライブラリをインポート

```python
from sympy import *
from sympy.physics.quantum import *
from sympy.physics.quantum.qubit import Qubit,QubitBra,measure_all,measure_partial
from sympy.physics.quantum.gate import X,Y,Z,H,CNOT,SWAP,CPHASE,CGateS
from sympy.physics.quantum.gate import IdentityGate as _I
from sympy.physics.quantum.gate import UGate as U

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

```python
import scipy.optimize
import scipy.linalg
import numpy as np
import sys
```

### 変分量子回路で利用するためのゲートを定義

```python
def Rxi(n,t): return U(n,represent(cos(t)*_I(1)*_I(0)-I*sin(t)*X(1)*_I(0),nqubits=2))
def Rix(n,t): return U(n,represent(cos(t)*_I(1)*_I(0)-I*sin(t)*_I(1)*X(0),nqubits=2))
def Rzi(n,t): return U(n,represent(cos(t)*_I(1)*_I(0)-I*sin(t)*Z(1)*_I(0),nqubits=2))
def Riz(n,t): return U(n,represent(cos(t)*_I(1)*_I(0)-I*sin(t)*_I(1)*Z(0),nqubits=2))

print(Rxi((0,1),pi/4).get_target_matrix())
print(Rix((0,1),pi/4).get_target_matrix())
print(Rzi((0,1),pi/4).get_target_matrix())
print(Riz((0,1),pi/4).get_target_matrix())
```

### ハミルトニアンの定義

$ H = \frac{1}{2} \left( S_{ii} \mathbb{1} + S_{ix} \sigma_{x}^{1} + S_{iz} \sigma_{z}^{1} + S_{xi} \sigma_{x}^{0}  + S_{zi} \sigma_{z}^{0}
+ S_{xx} \sigma_{x}^{0} \sigma_{x}^{1} + S_{xz} \sigma_{x}^{0} \sigma_{z}^{1} + S_{zx} \sigma_{z}^{0} \sigma_{x}^{1}
+ S_{zz} \sigma_{z}^{0} \sigma_{z}^{1} \right) $

```python
Sii,Six,Sxi,Sxx,Szz,Siz,Szi,Sxz,Szx = symbols('Sii Six Sxi Sxx Szz Siz Szi Sxz Szx')
Hamiltonian = (   Sii *_I(0) *_I(1)
                + Six *_I(0) * X(1)
                + Siz *_I(0) * Z(1)
                + Sxi * X(0) *_I(1)
                + Szi * Z(0) *_I(1)
                + Sxx * X(0) * X(1)
                + Sxz * X(0) * Z(1)
                + Szx * Z(0) * X(1)
                + Szz * Z(0) * Z(1) )/2
h = represent(Hamiltonian,nqubits=2)
Hamiltonian
```

$ HeH^+ $ 分子のハミルトニアンの数値設定（パラメータはNature Communication 2014 より)

```python
H_valued = h.subs([
    (Sii,-3.8505),
    (Six,-0.2288),
    (Sxi,-0.2288),
    (Siz,-1.0466),
    (Szi,-1.0466),
    (Sxx, 0.2613),
    (Sxz, 0.2288),
    (Szx, 0.2288),
    (Szi,-1.0466),
    (Szz,0.2356)])
H_valued
```

### 答えを事前に計算
このような小さいサイズのMatrixであれば、厳密対角化も計算で、固有値は求められます。

```python
# sympy は数値計算が苦手なので対角化は時間がかかります.
# ↓ sympy で提供されている対角化
# P, D = H_valued.diagonalize()
# ↓ sympy で提供されている固有値, 固有ベクトルの求め方
E = H_valued.eigenvects()
M = np.argmin([re(E[i][0]) for i in range(len(E))])
pprint(E[M])
print(re(E[M][0]))
```

```python
# ↓数値計算は, 固有値を求めるのが得意な numpy でも試します.
l, p = np.linalg.eig( np.array( H_valued.tolist(), dtype=np.complex128 ))
v = np.transpose(p)
mini = np.argmin(l)
E_answer = l[mini]
#print(p)
#print(l)
print(np.array([v[mini]]))
print(E_answer)
```

```python
def dice(n): return [np.random.rand() for i in range(n)]
def vqe_trial(phi):
    global count
    global f
    count += 1
    trial_func = Rxi((0,1), phi[0]).get_target_matrix() \
        * Riz((0,1), phi[1]).get_target_matrix() \
        * represent(CNOT(0,1),nqubits=2) \
        * Riz((0,1), phi[2]).get_target_matrix() \
        * Rix((0,1), phi[3]).get_target_matrix() \
        * Rzi((0,1), phi[4]).get_target_matrix() \
        * Rxi((0,1), phi[5]).get_target_matrix()
    trial_func_dag = Dagger(trial_func)
    trial = trial_func_dag * H_valued * trial_func
    pr = -1*abs(((qapply(trial).tolist())[0])[0])   # *(-1) をつけなければなりません
    # print(pr)
    f.write(str(count)+ ' ' +str(pr)+ ' ' +str(pr/E_answer)+ '\n')
    f.flush()
    return pr
```

```python
count = 0
f = sys.stdout
for i in range(10):
    vqe_trial(dice(6))
```

```python
count = 0
f = open('result_VQE.txt', 'w')
res = scipy.optimize.minimize(vqe_trial,dice(6),options={"maxiter": 100},method='Powell')
f.close()
```

```python
#pprint(res)
print(res["fun"])
```

```python
dat = np.loadtxt("result_VQE.txt")
plt.plot(dat[:,0],dat[:,1])
plt.plot(dat[:,0],dat[:,2])
```


# Examen Módulo 1 - Optimización. Tipo de examen 1.
Tipo de exámen https://drive.google.com/file/d/19rcQyjtzkfcHItyROkIsgT3OXOh-fIUS/view

Lea cuidadosamente las siguientes **indicaciones** antes de comenzar el examen:
- Para resolver el examen edite este mismo archivo y renómbrelo de la siguiente manera: *Examen3_ApellidoNombre*, donde *ApellidoNombre* corresponde a su apellido paterno con la inicial en mayúscula, seguido de su primer nombre con la inicial en mayúscula **sin acentos**. Por ejemplo, en mi caso el nombre del archivo sería *Examen3_JimenezEsteban*.
- Resuelva los puntos en el espacio provisto. Si requiere agregar más celdas para código o escritura, hágalo.
- Recuerde que también se está evaluando su capacidad de interpretar los resultados. Escriba sus interpretaciones/conclusiones en celdas utilizando *Markdown*.
- El formato de presentación de todo el examen en general debe ser adecuado. Use tamaños de letra, colores, etiquetas, etcétera.
- No se resuelven dudas de ningún tipo por los profesores de la asignatura. Por favor, absténgase de preguntar.
- Revise bien el tipo de examen. Sólo se le calificará si realiza el tipo de examen asignado.

## Primer punto. Optimización de funciones escalares usando `sympy` (30 puntos).

**Enunciado.** Calcular **las dimensiones** del rectángulo de área máxima que puede inscribirse dentro de un semicírculo de radio 2.

Note que **las dimensiones** son base $b = 2x$ y altura $h = y$. Ver la siguiente figura.

```python
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
```

```python
x = np.linspace(-2, 2, 100)
y = np.sqrt(4 - x**2)

xr = [-1.5, -1.5, 1.5, 1.5, -1.5]
yr = [0, np.sqrt(4 - 1.5**2), np.sqrt(4 - 1.5**2), 0, 0]

xx = [0, 1.5]
yx = [0, 0]

xy = [1.5, 1.5]
yy = [0, np.sqrt(4 - 1.5**2)]

plt.figure(figsize = (12, 6))
plt.plot(x, y, 'b', label = 'semicírculo')
plt.plot(xr, yr, 'r', label = 'rectángulo')
plt.plot(xx, yx, 'k')
plt.plot(xy, yy, 'g')
plt.text(0.7, 0.1, '$x$', fontsize = 16)
plt.text(1.6, 0.6, '$y$', fontsize = 16, color = 'g')
plt.legend(loc = 'best')
plt.show()
```

**Punto 1 (10 puntos).** Encuentre el área del rectángulo en función de la variable $x$, $A_R(x)$. También provea el dominio de la función para que tenga sentido.

Realice este punto usando fórmulas en LaTeX, en celdas Markdown.

**Solución punto 1.**

El área del rectángulo $A_R$ está dada por

$$A_R = bh$$

Pero como $b = 2x$ y $h=y$ entonces

$$A_R = 2xy$$

La variable $y$ se puede escribir como función de $x$ usando el teorema de pitágoras

$$y = \sqrt{4-x^2}$$

Reemplazando finalmente se tiene que

$$A_R(x) = 2x\sqrt{4-x^2}$$

y el dominio de la función está dado por

$$Dom_{A_R(x)} = [0,2]$$

**Punto 2 (10 puntos).** Usando `sympy` maximice la función $A_R(x)$ en su dominio.

**Solución punto 2.**

```python
import sympy as sym
```

```python
sym.var('x')
```

    x

```python
def AR(x):
    return 2*x*(4-x**2)**(1/2)
```

```python
dAR = sym.diff(AR(x),x)
dAR
```

    -2.0*x**2*(-x**2 + 4)**(-0.5) + 2*(-x**2 + 4)**0.5

```python
xc = sym.solve(dAR,x)
xc
```

    [-1.41421356237310, 1.41421356237310]

```python
AR(0), AR(1.4142), AR(2)
```

    (0.0, 3.9999999992642556, 0.0)

El área máximo del rectángulo es $4$ cuando $x = 1.4142$

**Punto 3 (10 puntos).** ¿Cuáles son las dimensiones $b$ y $h$ del rectángulo resultante?

**Solución punto 3.**

Por definición tenemos que $b = 2x$

```python
b = 2*xc[1]
b
```

    2.82842712474619

Para h se tiene que

```python
h = (4-xc[1]**2)**(1/2)
h
```

    1.41421356237309

## Segundo punto. Programación lineal (35 puntos).

**Enunciado.** Considere un restaurante que abre los siete días de la semana. Con base en la experiencia, el mínimo número de trabajadores necesarios en cada día de la semana es:

```python
import pandas as pd
```

```python
df = pd.DataFrame(columns=['Lunes', 'Martes', 'Miercoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'], index = ['Num_trabajadores'])
df.loc['Num_trabajadores', :] = [14, 13, 15, 16, 19, 18, 11]
df
```

      Lunes
      Martes
      Miercoles
      Jueves
      Viernes
      Sábado
      Domingo

      Num_trabajadores
      14
      13
      15
      16
      19
      18
      11

Cada trabajador trabaja cinco días consecutivos, y luego descansa dos, repitiendo este patrón indefinidamente. Se debe minimizar la cantidad de trabajadores totales en el restaurante.

*Definición de variables*: enumeramos los días del uno al siete (Lunes - 1, Martes - 2, ..., Domingo - 7). Sea $x_i$ el número de trabajadores que empieza su turno de cinco días el día $i$.

**Punto 1 (20 puntos).** Escriba la función a minimizar junto con las restricciones, explicando detalladamente cada paso (usando fórmulas en LaTeX, en celdas Markdown). Además escriba el problema en la forma
\begin{equation}
\begin{array}{ll}
\min_{\boldsymbol{x}} & \boldsymbol{c}^T\boldsymbol{x} \\
\text{s. a. }        & \boldsymbol{A}_{eq}\boldsymbol{x}=\boldsymbol{b}_{eq} \\
                     & \boldsymbol{A}\boldsymbol{x}\leq\boldsymbol{b},
\end{array}
\end{equation}

proporcionando $\boldsymbol{c}$, $\boldsymbol{A}$ y $\boldsymbol{b}$ ($\boldsymbol{A}_{eq}$ y $\boldsymbol{b}_{eq}$, de ser necesario) como arreglos de `NumPy` (no es necesario escribir en LaTeX el problema en la forma indicada, solo proporcionar las matrices como arreglos de `NumPy`).

**Solución punto 1.**

La función a minimiar viene dada por

$$\min_{x_1,\cdots,x_7} = x_1+x_2+x_3+x_4+x_5+x_6+x_7$$

y las restricciones están dadas por la información dada en la tabla

$$x_1+x_4+x_5+x_6+x_7 \geq 14$$
$$x_1+x_2+x_5+x_6+x_7 \geq 13$$
$$x_1+x_2+x_3+x_6+x_7 \geq 15$$
$$x_1+x_2+x_3+x_4+x_7 \geq 16$$

Así se escribe menor o igual $\leq$

```python
A = np.array([[-1,0,0,-1,-1,-1,-1],
             [-1,-1,0,0,-1,-1,-1],
             [-1,-1,-1,0,0,-1,-1],
             [-1,-1,-1,-1,0,0,-1],
             [-1,-1,-1,-1,-1,0,0],
             [0,-1,-1,-1,-1,-1,0],
             [0,0,-1,-1,-1,-1,-1]])
b = np.array([-14,-13,-15,-16,-19,-18,-11])
c = np.array([1,1,1,1,1,1,1])
```

**Punto 2 (15 puntos).** Resuelva el problema utilizando la función `linprog`. ¿Cuántos trabajadores son necesarios en total?

```python
import scipy.optimize as opt
```

```python
resultado = opt.linprog(c,A_ub=A,b_ub=b)
resultado
```

         fun: 22.0
     message: 'Optimization terminated successfully.'
         nit: 7
       slack: array([0., 4., 0., 0., 0., 0., 0.])
      status: 0
     success: True
           x: array([4., 7., 1., 4., 3., 3., 0.])

**Conclusión** En total es necesario 22 trabajadores

## Tercer punto. Ajuste de curvas (35 puntos).

**Enunciado**. El archivo `population_data.csv` contiene información histórica anual de México relativa a la población, nacimientos y muertes desde el año 1950 hasta el año 2015. La primer columna corresponde a los años, la segunda a la población, la tercera a los nacimientos y la última a los decesos.

Tomado de: https://en.wikipedia.org/wiki/Demographics_of_Mexico.

**Punto 1 (10 puntos).** Importar los datos en un DataFrame de pandas.

**Solución punto 1.**

```python
import pandas as pd
```

```python
file_name = 'population_data.csv'
data = pd.read_csv(file_name)
data
```

      Year
      Poblacion
      Nacimientos
      Decesos

      0
      1950
      28296
      1174947
      418430

      1
      1951
      29110
      1183788
      458238

      2
      1952
      29980
      1195209
      408823

      3
      1953
      30904
      1261775
      446127

      4
      1954
      31880
      1339837
      378752

      5
      1955
      32906
      1377917
      407522

      6
      1956
      33978
      1427722
      368740

      7
      1957
      35095
      1485202
      414545

      8
      1958
      36253
      1447578
      404529

      9
      1959
      37448
      1589606
      396924

      10
      1960
      38677
      1608174
      402545

      11
      1961
      39939
      1647006
      388857

      12
      1962
      41234
      1705481
      403046

      13
      1963
      42564
      1756624
      412834

      14
      1964
      43931
      1849408
      408275

      15
      1965
      45339
      1888171
      404163

      16
      1966
      46784
      1954340
      424141

      17
      1967
      48264
      1981363
      420298

      18
      1968
      49788
      2058251
      452910

      19
      1969
      51361
      2037561
      458886

      20
      1970
      52988
      2132630
      485656

      21
      1971
      54669
      2231399
      458323

      22
      1972
      56396
      2346002
      476206

      23
      1973
      58156
      2572287
      458915

      24
      1974
      59931
      2522580
      433104

      25
      1975
      61708
      2254497
      435888

      26
      1976
      63486
      2366305
      455660

      27
      1977
      65261
      2379327
      450454

      28
      1978
      67013
      2346862
      418381

      29
      1979
      68715
      2274267
      428217

      ...
      ...
      ...
      ...
      ...

      36
      1986
      79410
      2577045
      400079

      37
      1987
      80999
      2794390
      400280

      38
      1988
      82635
      2622031
      412987

      39
      1989
      84327
      2620262
      423304

      40
      1990
      86077
      2735312
      422803

      41
      1991
      87890
      2756447
      411131

      42
      1992
      89758
      2797397
      409814

      43
      1993
      91654
      2839686
      416335

      44
      1994
      93542
      2904389
      419074

      45
      1995
      95393
      2750444
      430278

      46
      1996
      97202
      2707718
      436321

      47
      1997
      98969
      2698425
      440437

      48
      1998
      100679
      2668429
      444665

      49
      1999
      102317
      2769089
      443950

      50
      2000
      103874
      2798339
      437667

      51
      2001
      105340
      2767610
      443127

      52
      2002
      106724
      2699084
      459687

      53
      2003
      108056
      2655894
      472140

      54
      2004
      109382
      2625056
      473417

      55
      2005
      110732
      2567906
      495240

      56
      2006
      112117
      2505939
      494471

      57
      2007
      113530
      2655083
      514420

      58
      2008
      114968
      2636110
      539530

      59
      2009
      116423
      2577214
      564673

      60
      2010
      114255
      2643908
      592018

      61
      2011
      115683
      2586287
      590693

      62
      2012
      117054
      2498880
      602354

      63
      2013
      118395
      2478889
      623599

      64
      2014
      119713
      2463420
      633641

      65
      2015
      121005
      2353596
      655694

66 rows × 4 columns

**Punto 2 (15 puntos).** Usando los años como variable independiente $x$ y la población como variable dependiente $y$, ajustar polinomios de grado 1 hasta grado 3.

Mostrar en un solo gráfico los datos de población contra los años, y los polinomios ajustados.

Graficar el error cuadrático acumulado contra el número de términos. ¿Cuál es el polinomio que mejor se ajusta?

**Solución punto 2.**

```python
x = data.iloc[:,0].values/1e4
y = data.iloc[:,1].values/1e6
```

```python
def fun_obj(beta,x,y,n):
    return np.sum((y-beta.dot([x**i for i in range(n+1)]))**2)
```

```python
b01 = np.zeros(2)
b02 = np.zeros(3)
b03 = np.zeros(4)

ajuste1 = opt.minimize(fun_obj,b01,args=(x,y,1))
ajuste2 = opt.minimize(fun_obj,b02,args=(x,y,2))
ajuste3 = opt.minimize(fun_obj,b03,args=(x,y,3))
```

```python
ajuste1
```

          fun: 0.00021213719532778825
     hess_inv: array([[  82.05135041, -413.83997302],
           [-413.83997302, 2087.46518549]])
          jac: array([-2.91038305e-11,  4.74756234e-10])
      message: 'Optimization terminated successfully.'
         nfev: 40
          nit: 4
         njev: 10
       status: 0
      success: True
            x: array([-2.98667517, 15.44006213])

```python
ajuste2
```

          fun: 0.00021184693390075774
     hess_inv: array([[  71.09616365, -331.9392197 , -134.37019151],
           [-331.9392197 , 1549.9880993 ,  627.22604845],
           [-134.37019151,  627.22604845,  254.97873378]])
          jac: array([-1.49520929e-09,  8.96943675e-09, -2.17132765e-08])
      message: 'Optimization terminated successfully.'
         nfev: 50
          nit: 4
         njev: 10
       status: 0
      success: True
            x: array([-2.7750674 , 13.30509035,  5.38458905])

```python
ajuste3
```

          fun: 0.00021180513419868788
     hess_inv: array([[  69.98919553, -325.33303931, -131.70727417,  -39.43795797],
           [-325.33303931, 1512.46111693,  612.08665328,  183.2994303 ],
           [-131.70727417,  612.08665328,  248.87118009,   74.22895227],
           [ -39.43795797,  183.2994303 ,   74.22895227,   23.22902079]])
          jac: array([-1.49339030e-09,  8.00537237e-09, -2.07892299e-08, -1.60671334e-08])
      message: 'Optimization terminated successfully.'
         nfev: 60
          nit: 4
         njev: 10
       status: 0
      success: True
            x: array([-2.75280674, 13.14309729,  5.31948189,  1.59271595])

```python
xpoints = x*1e4
ypoints = y*1e6
```

```python
yhat1 = (ajuste1.x[0] + ajuste1.x[1]*xpoints/1e4)*1e6
yhat2 = (ajuste2.x[0] + ajuste2.x[1]*xpoints/1e4 + ajuste2.x[2]*(xpoints/(1e4))**2)*1e6
yhat3 = (ajuste3.x[0] + ajuste3.x[1]*xpoints/1e4 + ajuste3.x[2]*(xpoints/(1e4))**2 + ajuste3.x[3]*(xpoints/(1e4))**3)*1e6
```

```python
import matplotlib.pyplot as plt
```

```python
plt.figure(figsize=(8,6))
plt.plot(xpoints,ypoints,'r*',ms = 6,label='Puntos')
plt.plot(xpoints,yhat1,'b',ms = 6,label='Ajuste 1')
plt.plot(xpoints,yhat2,'k',ms = 6,label='Ajuste 2')
plt.plot(xpoints,yhat3,'g',ms = 6,label='Ajuste 3')
plt.grid()
plt.legend(loc='best')
plt.xlabel('Años')
plt.ylabel('Población en miles de personas')
```

```python
error = [ajuste1.fun,ajuste2.fun,ajuste3.fun]
plt.figure(figsize=(8,6))
plt.plot(range(1,4),error,'ro',ms=5)
plt.xlabel('Orden del polinomio')
plt.ylabel('Error')
```

*Conclusión*

Según la gráfica anterior, el polinomio que mejor se ajusta es el de tercer orden.

**Punto 3 (10 puntos).** Con los polinomios ajustados en el punto anterior, estime la población en el año 2016 y compare con el dato real 127,5 millones.

¿Cuál polinomio estima mejor este dato? ¿Es el polinomio que mejor se ajusta? Concluya.

**Solución punto 3.**

```python
yhat1 = (ajuste1.x[0] + ajuste1.x[1]*2016/1e4)*1e6
yhat2 = (ajuste2.x[0] + ajuste2.x[1]*2016/1e4 + ajuste2.x[2]*(2016/(1e4))**2)*1e6
yhat3 = (ajuste3.x[0] + ajuste3.x[1]*2016/1e4 + ajuste3.x[2]*(2016/(1e4))**2 + ajuste3.x[3]*(2016/(1e4))**3)*1e6

yhat1,yhat2,yhat3,127.5
```

    (126041.35218515822, 126082.29872048562, 126089.01624851953, 127.5)

*Conclusión*. El polinomio que mejor estima este dato es el polinomio de grado tres.