## Subsurface Data Analytics

### Multivariate Analysis for Subsurface Data Analytics in Python

#### Michael Pyrcz, Associate Professor, University of Texas at Austin

##### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)

### PGE 383 Exercise: Multivariate Analysis for Subsurface Data Analytics in Python

Here's a simple workflow, demonstration of multivariate analysis for subsurface modeling workflows. This should help you get started with building subsurface models that integrate uncertainty in the sample statistics.

#### Bivariate Analysis

Understand and quantify the relationship between two variables

* example: relationship between porosity and permeability
* how can we use this relationship?

What would be the impact if we ignore this relationship and simply modeled porosity and permeability independently?

* no relationship beyond constraints at data locations
* independent away from data
* nonphysical results, unrealistic uncertainty models

#### Bivariate Statistics

Pearson’s Product‐Moment Correlation Coefficient
* Provides a measure of the degree of linear relationship.
* We refer to it as the 'correlation coefficient'

Let's review the sample variance of variable $x$. Of course, I'm truncating our notation as $x$ is a set of samples a locations in our modeling space, $x(\bf{u_\alpha}), \, \forall \, \alpha = 0, 1, \dots, n - 1$.

\begin{equation}
\sigma^2_{x}  = \frac{\sum_{i=1}^{n} (x_i - \overline{x})^2}{(n-1)}
\end{equation}

We can expand the the squared term and replace on of them with $y$, another variable in addition to $x$.

\begin{equation}
C_{xy}  = \frac{\sum_{i=1}^{n} (x_i - \overline{x})(y_i - \overline{y})}{(n-1)}
\end{equation}

We now have a measure that represents the manner in which variables $x$ and $y$ co-vary or vary together.  We can standardized the covariance by the product of the standard deviations of $x$ and $y$ to calculate the correlation coefficent.

\begin{equation}
\rho_{xy}  = \frac{\sum_{i=1}^{n} (x_i - \overline{x})(y_i - \overline{y})}{(n-1)\sigma_x \sigma_y}, \, -1.0 \le \rho_{xy} \le 1.0
\end{equation}

In summary we can state that the correlation coefficient is related to the covariance as:

\begin{equation}
\rho_{xy}  = \frac{C_{xy}}{\sigma_x \sigma_y}
\end{equation}

The Person's correlation coefficient is quite sensitive to outliers and depature from linear behavoir (in the bivariate sense).  We have an altenrative known as the Spearman's rank correlations coefficient.

\begin{equation}
\rho_{R_x R_y}  = \frac{\sum_{i=1}^{n} (R_{x_i} - \overline{R_x})(R_{y_i} - \overline{R_y})}{(n-1)\sigma_{R_x} \sigma_{R_y}}, \, -1.0 \le \rho_{xy} \le 1.0
\end{equation}

The rank correlation applies the rank transform to the data prior to calculating the correlation coefficent.  To calculate the rank transform simply replace the data values with the rank $R_x = 1,\dots,n$, where $n$ is the maximum value and $1$ is the minimum value.

\begin{equation}
x_\alpha, \, \forall \alpha = 1,\dots, n, \, | \, x_i \ge x_j \, \forall \, i \gt j
\end{equation}

\begin{equation}
R_{x_i} = i
\end{equation}

The corelation coefficients provide useful metrics to quantify relationships between two variables at a time. We can also consider bivariate scatter plots and matrix scatter plots to visualize multivariate data. In general, current practical subsurface modeling is bivariate, two variables at a time.

#### Multivariate Statistics

See lecture on Multivariate Statistics, including the concepts of joint, conditional and marginal probability.

#### Objective

In the PGE 383: Stochastic Subsurface Modeling class I want to provide hands-on experience with building subsurface modeling workflows. Python provides an excellent vehicle to accomplish this. I have coded a package called GeostatsPy with GSLIB: Geostatistical Library (Deutsch and Journel, 1998) functionality that provides basic building blocks for building subsurface modeling workflows.

The objective is to remove the hurdles of subsurface modeling workflow construction by providing building blocks and sufficient examples. This is not a coding class per se, but we need the ability to 'script' workflows working with numerical methods.

#### Getting Started

Here's the steps to get setup in Python with the GeostatsPy package:

1. Install Anaconda 3 on your machine (https://www.anaconda.com/download/).
2. From Anaconda Navigator (within Anaconda3 group), go to the environment tab, click on base (root) green arrow and open a terminal.
3. In the terminal type: pip install geostatspy.
4. Open Jupyter and in the top block get started by copy and pasting the code block below from this Jupyter Notebook to start using the geostatspy functionality.

You will need to copy the data file to your working directory.  They are available here:

* Tabular data - sample_data_MV_biased.csv at https://git.io/fhgu0.

There are exampled below with these functions. You can go here to see a list of the available functions, https://git.io/fh4eX, other example workflows and source code.

```python
import geostatspy.GSLIB as GSLIB          # GSLIB utilies, visualization and wrapper
import geostatspy.geostats as geostats    # GSLIB methods convert to Python
```

We will also need some standard packages. These should have been installed with Anaconda 3.

```python
import numpy as np                        # ndarrys for gridded data
import pandas as pd                       # DataFrames for tabular data
import os                                 # set working directory, run executables
import matplotlib.pyplot as plt           # for plotting
from scipy import stats                   # summary statistics
import math                               # trig etc.
import scipy.signal as signal             # kernel for moving window calculation
import random
import seaborn as sns
```

#### Set the working directory

I always like to do this so I don't lose files and to simplify subsequent read and writes (avoid including the full address each time).

```python
os.chdir("d:/PGE383")                     # set the working directory
```

#### Loading Tabular Data

Here's the command to load our comma delimited data file in to a Pandas' DataFrame object.

```python
df = pd.read_csv('sample_data_MV_biased.csv') # load our data table (wrong name!)
```

Visualizing the DataFrame would be useful and we already learned about these methods in this demo (https://git.io/fNgRW).

We can preview the DataFrame by printing a slice or by utilizing the 'head' DataFrame member function (with a nice and clean format, see below). With the slice we could look at any subset of the data table and with the head command, add parameter 'n=13' to see the first 13 rows of the dataset.

```python
print(df.iloc[0:5,:])                   # display first 4 samples in the table as a preview
df.head(n=13)                           # we could also use this command for a table preview
```

       Unnamed: 0    X    Y  Facies  Porosity      Perm           AI
    0         259  190   19       0  0.041122  0.120862  6367.442357
    1         501  270   49       0  0.043853  0.094627  6906.176965
    2         273  270  219       0  0.062169  0.408779  6244.965113
    3         159  380  259       0  0.064295  0.322764  7113.679894
    4          96  870  449       0  0.065537  0.672005  5672.957304

      Unnamed: 0
      X
      Y
      Facies
      Porosity
      Perm
      AI

      0
      259
      190
      19
      0
      0.041122
      0.120862
      6367.442357

      1
      501
      270
      49
      0
      0.043853
      0.094627
      6906.176965

      2
      273
      270
      219
      0
      0.062169
      0.408779
      6244.965113

      3
      159
      380
      259
      0
      0.064295
      0.322764
      7113.679894

      4
      96
      870
      449
      0
      0.065537
      0.672005
      5672.957304

      5
      233
      260
      69
      0
      0.066231
      0.424679
      6885.780670

      6
      35
      400
      100
      0
      0.066615
      0.156520
      6026.595761

      7
      464
      780
      629
      0
      0.066703
      0.265882
      4713.977913

      8
      533
      510
      339
      0
      0.066970
      0.412301
      6518.084345

      9
      459
      490
      19
      0
      0.067607
      0.648172
      5297.559776

      10
      578
      310
      219
      0
      0.069584
      0.434025
      6949.553872

      11
      441
      450
      219
      0
      0.071233
      0.300430
      5442.438599

      12
      25
      300
      200
      0
      0.073276
      0.523248
      6969.149686

#### Summary Statistics for Tabular Data

The table includes X and Y coordinates (meters), Facies 1 and 0 (1 is sandstone and 0 interbedded sand and mudstone), Porosity (fraction), and permeability as Perm (mDarcy).

There are a lot of efficient methods to calculate summary statistics from tabular data in DataFrames. The describe command provides count, mean, minimum, maximum, and quartiles all in a nice data table. We use transpose just to flip the table so that features are on the rows and the statistics are on the columns.

```python
df.describe().transpose()
```

      count
      mean
      std
      min
      25%
      50%
      75%
      max

      Unnamed: 0
      367.0
      292.926431
      169.167110
      0.000000
      150.000000
      295.000000
      440.000000
      586.000000

      X
      367.0
      500.108992
      289.978309
      0.000000
      240.000000
      500.000000
      765.000000
      990.000000

      Y
      367.0
      520.403270
      277.752407
      9.000000
      269.000000
      539.000000
      769.000000
      999.000000

      Facies
      367.0
      0.596730
      0.491224
      0.000000
      0.000000
      1.000000
      1.000000
      1.000000

      Porosity
      367.0
      0.126874
      0.030546
      0.041122
      0.103398
      0.125616
      0.148323
      0.210258

      Perm
      367.0
      83.313352
      224.350475
      0.094627
      2.288119
      10.294907
      50.219914
      1991.097723

      AI
      367.0
      4790.458884
      975.582304
      1981.177309
      4108.843701
      4712.673154
      5466.957467
      7561.250336

#### Visualizing Tabular Data with Location Maps

It is natural to set the x and y coordinate and feature ranges manually. e.g. do you want your color bar to go from 0.05887 to 0.24230 exactly? Also, let's pick a color map for display. I heard that plasma is known to be friendly to the color blind as the color and intensity vary together (hope I got that right, it was an interesting Twitter conversation started by Matt Hall from Agile if I recall correctly). We will assume a study area of 0 to 1,000m in x and y and omit any data outside this area.

```python
xmin = 0.0; xmax = 1000.0               # range of x values
ymin = 0.0; ymax = 1000.0               # range of y values
pormin = 0.05; pormax = 0.25;           # range of porosity values
permmin = 0.01; permmax = 2000.0         # range of permeability values
AImin = 2000.0; AImax = 8000.0          # range of AI values
nx = 100; ny = 100; csize = 10.0
cmap = plt.cm.plasma                    # color map
```

Let's try out locmap. This is a reimplementation of GSLIB's locmap program that uses matplotlib. I hope you find it simpler than matplotlib, if you want to get more advanced and build custom plots lock at the source. If you improve it, send me the new code.

Now we can populate the plotting parameters and visualize the porosity data.

```python
plt.subplot(221)
GSLIB.locmap_st(df,'X','Y','Facies',xmin,xmax,ymin,ymax,0,1,'Well Data - Porosity','X(m)','Y(m)','Facies (0-shale, 1-sand)',cmap)

plt.subplot(222)
GSLIB.locmap_st(df,'X','Y','Porosity',xmin,xmax,ymin,ymax,pormin,pormax,'Well Data - Porosity','X(m)','Y(m)','Porosity (fraction)',cmap)

plt.subplot(223)
GSLIB.locmap_st(df,'X','Y','Perm',xmin,xmax,ymin,ymax,permmin,permmax,'Well Data - Permeability','X(m)','Y(m)','Permeability (md)',cmap)

plt.subplot(224)
GSLIB.locmap_st(df,'X','Y','AI',xmin,xmax,ymin,ymax,AImin,AImax,'Well Data - Acoustic Impedance','X(m)','Y(m)','Acoustic Impedance (m/s x g/cm^3)',cmap)

plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=3.2, wspace=0.2, hspace=0.2)
plt.show()
```

#### Bivariate Analysis

Let's start with some simple bivariate plotting and calculations. Firsty some scatter plots.

```python
plt.subplot(121)
plt.plot(df['Porosity'].values,df['Perm'].values, 'o', label='', markerfacecolor='red', markeredgecolor='black', alpha=0.2)
plt.title('Well Data Permeability vs. Porostiy')
plt.xlabel('Porosity (fraction)')
plt.ylabel('Permeability (mD)')
#plt.legend()

plt.subplot(122)
plt.plot(df['AI'].values,df['Porosity'].values, 'o', label='', markerfacecolor='red', markeredgecolor='black', alpha=0.2)
plt.title('Well Data Porostiy vs. Acoustic Impedance')
plt.ylabel('Porosity (fraction)')
plt.xlabel('Acoustic Impedance (m/s x g/cm^3)')

plt.subplots_adjust(left=0.0, bottom=0.0, right=2.2, top=1.2, wspace=0.2, hspace=0.2)
plt.show()
```

#### Correlation and Covariance

It is straight forward to calculat the covariance and correlation from the pairs of data in our dataset. Here's the covariance.  Notice that the matrix is symmetrical?  Makes sense, as the $C_{Por,Perm} = C_{Perm,Por}$.  Also, note that the diagonal values ($C_{i,j}$ where $i=j$) equal to the variance.  We check porosity by calculating the variance.

```python
print(df.iloc[:,3:7].cov())                    # the covariance matrix for columns 3,4,5 and 6 and all rows
print('The variance of porosity is ' + str(round(np.var(df['Porosity'].values),6)))
```

                  Facies   Porosity          Perm             AI
    Facies      0.241301   0.011339     32.169444    -249.227081
    Porosity    0.011339   0.000933      3.937189     -17.250550
    Perm       32.169444   3.937189  50333.135522  -47685.009733
    AI       -249.227081 -17.250550 -47685.009733  951760.832084
    The variance of porosity is 0.00093

Here's the correlation coefficient.

```python
df.iloc[:,3:7].corr()
```

      Facies
      Porosity
      Perm
      AI

      Facies
      1.000000
      0.755721
      0.291902
      -0.520058

      Porosity
      0.755721
      1.000000
      0.574529
      -0.578884

      Perm
      0.291902
      0.574529
      1.000000
      -0.217867

      AI
      -0.520058
      -0.578884
      -0.217867
      1.000000

#### Matrix Scatter Plots

If we have 3 or more variables to consider then matrix scatter plot offer an efficient method to display the multivariate relationships, 2 variables at a time.  Once can identify:

1. the range, envelope of the paired data
2. homoscedastic and heteroscedastic behavoirs
3. non-linear features

Here's the seaborn package matrix scatter plot function, pairplot. Let's color the results by facies.

```python
sns.pairplot(df, hue='Facies',vars=['Porosity','Perm','AI'],markers='o')
```

#### Pair Grid Plot

For maximum control of the resulting plot we can work with pair grid plots from seaborn.

* we can independently set lower, upper and diagonal components of the plot

We demostrate this with all facies pooled together for:

* scatter plots for upper

* histograms for diagonal

* kernel density plots for the lower

```python
pairgrid = sns.PairGrid(df,vars=['Porosity','Perm','AI'], size = 4)
pairgrid = pairgrid.map_upper(plt.scatter, color = 'red', edgecolor = 'black', alpha = 0.2, s = 10)
pairgrid = pairgrid.map_diag(plt.hist, bins = 20, color = 'red',alpha = 0.2, edgecolor = 'k')# Map a density plot to the lower triangle
pairgrid = pairgrid.map_lower(sns.kdeplot, cmap = plt.cm.inferno,
                              shade = False, shade_lowest = False, alpha = 1.0, n_levels = 10)
pairgrid.add_legend()
plt.subplots_adjust(left=0.0, bottom=0.0, right=0.6, top=0.6, wspace=0.2, hspace=0.2)
```

#### Joint, Conditional and Marginals

We can use kernel density estimation to estimate the joint probabilities density function (pdf) for the paired data, a 2D pdf! We could use this to estimate any required joint, marginal and conditional probability (care must be taken with normalization). Let's use the seaborn package 'kdeplot' function to estimate the joint pdf for porosity and acoustic impedance.

```python
ax = sns.kdeplot(df['AI'].values,df['Porosity'].values, shade=True, n_levels = 10,cmap=cmap,cbar= True, shade_lowest = False)
ax.set_xlabel('Acoustic Impedance (m/s x g/cm^3)'); ax.set_ylabel('Porosity (fraction)'); ax.set_title('Porosity vs. Acoustic Impedance')
```

I think is it useful to visualize the joint pdfs with the marginal pdfs on a single plot. We can use seaborn's 'jointplot' to accomplish this.

```python
ax = sns.jointplot('AI','Porosity', df, kind='kde',shade = False, n_levels = 10,cmap=cmap, shade_lowest = True);
```

The correlation coefficient and the p-value of the correlation coefficient (significant if < $\alpha/2$ or > $1-\alpha/2$).

#### Calculating Conditional Statistics

Of course, we could just calculate the conditional statistics by-hand. We need to select some bins over the variable that we will condition to. Let's calculate conditional statistical of porosity given acoustic impedance. We will select 9 equal spaced bins.

```python
AI_bins = np.linspace(2000,8000,10)            # set the bin boundaries and then the centroids for plotting
AI_centroids = np.linspace((AI_bins[0]+AI_bins[1])*0.5,(AI_bins[8]+AI_bins[9])*0.5,9)
print(AI_bins)                                 # check the boundaries
print(AI_centroids)                            # check the centroids
df['AI_bins'] = pd.cut(df['AI'], AI_bins,labels = AI_centroids) # cut on bondaries and lable with centroids
df.head()                                      # check the new column in the DataFrame
```

    [2000.         2666.66666667 3333.33333333 4000.         4666.66666667
     5333.33333333 6000.         6666.66666667 7333.33333333 8000.        ]
    [2333.33333333 3000.         3666.66666667 4333.33333333 5000.
     5666.66666667 6333.33333333 7000.         7666.66666667]

      Unnamed: 0
      X
      Y
      Facies
      Porosity
      Perm
      AI
      AI_bins

      0
      259
      190
      19
      0
      0.041122
      0.120862
      6367.442357
      6333.333333

      1
      501
      270
      49
      0
      0.043853
      0.094627
      6906.176965
      7000.000000

      2
      273
      270
      219
      0
      0.062169
      0.408779
      6244.965113
      6333.333333

      3
      159
      380
      259
      0
      0.064295
      0.322764
      7113.679894
      7000.000000

      4
      96
      870
      449
      0
      0.065537
      0.672005
      5672.957304
      5666.666667

Now we can use the 'groupby' function built-in to Pandas' DataFrames to extract subsets of porosity values in each bin from the DataFrame and then to calculate the conditional statistics: expectation, P90 and P10. Let's plot the result.

```python
cond_exp = df.groupby('AI_bins')['Porosity'].mean()
cond_P90 = df.groupby('AI_bins')['Porosity'].quantile(.9)
cond_P10 = df.groupby('AI_bins')['Porosity'].quantile(.1)

plt.subplot(111)
plt.plot(AI_centroids,cond_exp,color='black')
plt.plot(AI_centroids,cond_P90,'r--',color='black',linewidth = 1.0)
plt.plot(AI_centroids,cond_P10,'r--',color='black',linewidth = 1.0)

plt.xlabel('Acoustic Impedance (m/s x g/cm^3)')
plt.ylabel('Porosity (fraction) | Acoustic Impedance')
t = plt.title('Porosity Conditional to Accoustic Impedance')
plt.ylim(pormin,pormax)
plt.xlim(AImin,AImax)
plt.text(3200, .10, 'P10')
plt.text(3200, .15, 'Expectation')
plt.text(3200, .19, 'P90')

plt.grid(True)

plt.subplots_adjust(left=0.0, bottom=0.0, right=1.2, top=1.2, wspace=0.2, hspace=0.2)
plt.show()
```

Does acoustic impedance provide information about porosity?

Yes, clearly the conditional statistics vary over acoustic impedance, knowing the acoustic impedance reduces the uncertainty about porosity.

#### Comments

This was a basic demonstration of multivariate analysis. A lot more could be done, for example, there are methods that reduce the dimensionality, and remove dependency to allow for independent variable modeling workflows etc.

I have other demonstrations on the basics of working with DataFrames, ndarrays, univariate statistics, plotting data, declustering, data transformations, trend modeling and many other workflows available at https://github.com/GeostatsGuy/PythonNumericalDemos and https://github.com/GeostatsGuy/GeostatsPy.

I hope this was helpful,

*Michael*

#### The Author:

### Michael Pyrcz, Associate Professor, University of Texas at Austin
*Novel Data Analytics, Geostatistics and Machine Learning Subsurface Solutions*

With over 17 years of experience in subsurface consulting, research and development, Michael has returned to academia driven by his passion for teaching and enthusiasm for enhancing engineers' and geoscientists' impact in subsurface resource development.

For more about Michael check out these links:

#### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)

#### Want to Work Together?

I hope this content is helpful to those that want to learn more about subsurface modeling, data analytics and machine learning. Students and working professionals are welcome to participate.

* Want to invite me to visit your company for training, mentoring, project review, workflow design and / or consulting? I'd be happy to drop by and work with you!

* Interested in partnering, supporting my graduate student research or my Subsurface Data Analytics and Machine Learning consortium (co-PIs including Profs. Foster, Torres-Verdin and van Oort)? My research combines data analytics, stochastic modeling and machine learning theory with practice to develop novel methods and workflows to add value. We are solving challenging subsurface problems!

* I can be reached at mpyrcz@austin.utexas.edu.

I'm always happy to discuss,

*Michael*

Michael Pyrcz, Ph.D., P.Eng. Associate Professor The Hildebrand Department of Petroleum and Geosystems Engineering, Bureau of Economic Geology, The Jackson School of Geosciences, The University of Texas at Austin

I have other demonstrations on the basics of working with DataFrames, ndarrays, univariate statistics, plotting data, declustering, data transformations, trend modeling and many other workflows available at https://github.com/GeostatsGuy/PythonNumericalDemos and https://github.com/GeostatsGuy/GeostatsPy.

I hope this was helpful,

*Michael*

Michael Pyrcz, Ph.D., P.Eng. Associate Professor The Hildebrand Department of Petroleum and Geosystems Engineering, Bureau of Economic Geology, The Jackson School of Geosciences, The University of Texas at Austin

#### More Resources Available at: [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)

```python

```


## Model definition

```julia
using MomentClosure, Catalyst
```

```julia
# using Catalyst

#= Notes:
  -`\rightarrow` indicates mass-action reactions
    for which the propensity functions are constructed accordingly
    to the law of mass action.
  - the system-size parameter Ω must be included manually
=#

rn = @reaction_network begin
  (c₁/Ω^2), 2X + Y → 3X
  (c₂), X → Y
  (c₃*Ω, c₄), 0 ↔ X
end c₁ c₂ c₃ c₄ Ω
```

\begin{align}
\require{mhchem}
\ce{ 2 X + Y &->[c{_1} \mathrm{inv}\left( \Omega^{2} \right)] 3 X}\\
\ce{ X &->[c{_2}] Y}\\
\ce{ \varnothing &<=>[c{_3} \Omega][c{_4}] X}
\end{align}

```julia
# net stoichiometry matrix
get_S_mat(rn)
```

    2×4 Array{Int64,2}:
      1  -1  1  -1
     -1   1  0   0

```julia
# vector of propensity functions
propensities(rn, combinatoric_ratelaw=false)
```

    4-element Array{SymbolicUtils.Mul{Real,Int64,Dict{Any,Number},Nothing},1}:
     c₁*X(t)*Y(t)*(Ω^-2)*(X(t) - 1)
     c₂*X(t)
     c₃*Ω
     c₄*X(t)

```julia
# using MomentClosure's ReactionSystemMod

@parameters t, c₁, c₂, c₃, c₄, Ω
@variables X(t), Y(t)

# stoichiometric matrix
S_mat = [ 1 -1  1 -1;
         -1  1  0  0]

# propensity functions
a = [c₁*X*Y*(X-1)/Ω^2, c₂*X, c₃*Ω, c₄*X]

rn2 = ReactionSystemMod(t, [X, Y], [c₁, c₂, c₃, c₄, Ω], a, S_mat)
```

    ReactionSystemMod(t, Term{Real,Nothing}[X(t), Y(t)], Sym{ModelingToolkit.Parameter{Real},Nothing}[c₁, c₂, c₃, c₄, Ω], SymbolicUtils.Mul{Real,Int64,Dict{Any,Number},Nothing}[c₁*X(t)*Y(t)*(Ω^-2)*(X(t) - 1), c₂*X(t), c₃*Ω, c₄*X(t)], [1 -1 1 -1; -1 1 0 0])

## Generating moment equations

```julia
# raw moment equations
raw_eqs = generate_raw_moment_eqs(rn, 2, combinatoric_ratelaw=false)
```

    MomentClosure.RawMomentEquations(ODESystem(Equation[Differential(t)(μ₁₀(t)) ~ c₃*Ω + c₁*μ₂₁(t)*(Ω^-2) - (c₂*μ₁₀(t)) - (c₄*μ₁₀(t)) - (c₁*μ₁₁(t)*(Ω^-2)), Differential(t)(μ₀₁(t)) ~ c₂*μ₁₀(t) + c₁*μ₁₁(t)*(Ω^-2) - (c₁*μ₂₁(t)*(Ω^-2)), Differential(t)(μ₂₀(t)) ~ c₂*μ₁₀(t) + c₃*Ω + c₄*μ₁₀(t) + 2.0c₁*μ₃₁(t)*(Ω^-2) + 2.0c₃*Ω*μ₁₀(t) - (2.0c₂*μ₂₀(t)) - (2.0c₄*μ₂₀(t)) - (c₁*μ₁₁(t)*(Ω^-2)) - (c₁*μ₂₁(t)*(Ω^-2)), Differential(t)(μ₁₁(t)) ~ c₂*μ₂₀(t) + c₁*μ₁₁(t)*(Ω^-2) + c₁*μ₂₂(t)*(Ω^-2) + c₃*Ω*μ₀₁(t) - (c₂*μ₁₀(t)) - (c₂*μ₁₁(t)) - (c₄*μ₁₁(t)) - (c₁*μ₁₂(t)*(Ω^-2)) - (c₁*μ₃₁(t)*(Ω^-2)), Differential(t)(μ₀₂(t)) ~ c₂*μ₁₀(t) + c₁*μ₂₁(t)*(Ω^-2) + 2.0c₂*μ₁₁(t) + 2.0c₁*μ₁₂(t)*(Ω^-2) - (c₁*μ₁₁(t)*(Ω^-2)) - (2.0c₁*μ₂₂(t)*(Ω^-2))], t, Term{Real,Nothing}[μ₁₀(t), μ₀₁(t), μ₂₀(t), μ₁₁(t), μ₀₂(t), μ₂₁(t), μ₁₂(t), μ₃₁(t), μ₂₂(t)], Sym{ModelingToolkit.Parameter{Real},Nothing}[c₁, c₂, c₃, c₄, Ω], Equation[], Base.RefValue{Array{Num,1}}(Num[]), Base.RefValue{Any}(Array{Num}(undef,0,0)), Base.RefValue{Array{Num,2}}(Array{Num}(undef,0,0)), Base.RefValue{Array{Num,2}}(Array{Num}(undef,0,0)), Symbol("##ODESystem#295"), ODESystem[], Dict{Any,Any}(), nothing, Any[]), Dict{Any,Any}((0, 0) => 1,(3, 0) => μ₃₀(t),(2, 2) => μ₂₂(t),(2, 1) => μ₂₁(t),(1, 0) => μ₁₀(t),(3, 1) => μ₃₁(t),(2, 0) => μ₂₀(t),(0, 1) => μ₀₁(t),(1, 3) => μ₁₃(t),(4, 0) => μ₄₀(t)…), 2, 2, 4, [(0, 0), (1, 0), (0, 1), (2, 0), (1, 1), (0, 2), (3, 0), (2, 1), (1, 2), (0, 3), (4, 0), (3, 1), (2, 2), (1, 3), (0, 4)], [(2, 0), (1, 1), (0, 2)], [(3, 0), (2, 1), (1, 2), (0, 3), (4, 0), (3, 1), (2, 2), (1, 3), (0, 4)], [(1, 0), (0, 1)])

```julia
using Latexify
latexify(raw_eqs)
```

\begin{align*}
\frac{d\mu{_{10}}}{dt} =& c{_3} \Omega + c{_1} \mu{_{21}} \Omega^{-2} - c{_2} \mu{_{10}} - c{_4} \mu{_{10}} - c{_1} \mu{_{11}} \Omega^{-2} \\
\frac{d\mu{_{01}}}{dt} =& c{_2} \mu{_{10}} + c{_1} \mu{_{11}} \Omega^{-2} - c{_1} \mu{_{21}} \Omega^{-2} \\
\frac{d\mu{_{20}}}{dt} =& c{_2} \mu{_{10}} + c{_3} \Omega + c{_4} \mu{_{10}} + 2 c{_1} \mu{_{31}} \Omega^{-2} + 2 c{_3} \Omega \mu{_{10}} - 2 c{_2} \mu{_{20}} - 2 c{_4} \mu{_{20}} - c{_1} \mu{_{11}} \Omega^{-2} - c{_1} \mu{_{21}} \Omega^{-2} \\
\frac{d\mu{_{11}}}{dt} =& c{_2} \mu{_{20}} + c{_1} \mu{_{11}} \Omega^{-2} + c{_1} \mu{_{22}} \Omega^{-2} + c{_3} \Omega \mu{_{01}} - c{_2} \mu{_{10}} - c{_2} \mu{_{11}} - c{_4} \mu{_{11}} - c{_1} \mu{_{12}} \Omega^{-2} - c{_1} \mu{_{31}} \Omega^{-2} \\
\frac{d\mu{_{02}}}{dt} =& c{_2} \mu{_{10}} + c{_1} \mu{_{21}} \Omega^{-2} + 2 c{_2} \mu{_{11}} + 2 c{_1} \mu{_{12}} \Omega^{-2} - c{_1} \mu{_{11}} \Omega^{-2} - 2 c{_1} \mu{_{22}} \Omega^{-2}
\end{align*}

```julia
# central moment equations
central_eqs = generate_central_moment_eqs(rn, 2, combinatoric_ratelaw=false)
latexify(central_eqs)
```

\begin{align*}
\frac{d\mu{_{10}}}{dt} =& c{_3} \Omega + c{_1} M{_{21}} \Omega^{-2} + c{_1} M{_{20}} \mu{_{01}} \Omega^{-2} + c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} + 2 c{_1} M{_{11}} \mu{_{10}} \Omega^{-2} - c{_2} \mu{_{10}} - c{_4} \mu{_{10}} - c{_1} M{_{11}} \Omega^{-2} - c{_1} \mu{_{01}} \mu{_{10}} \Omega^{-2} \\
\frac{d\mu{_{01}}}{dt} =& c{_2} \mu{_{10}} + c{_1} M{_{11}} \Omega^{-2} + c{_1} \mu{_{01}} \mu{_{10}} \Omega^{-2} - c{_1} M{_{21}} \Omega^{-2} - 2 c{_1} M{_{11}} \mu{_{10}} \Omega^{-2} - c{_1} M{_{20}} \mu{_{01}} \Omega^{-2} - c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} \\
\frac{dM{_{20}}}{dt} =& c{_2} \mu{_{10}} + c{_3} \Omega + c{_4} \mu{_{10}} + 2 c{_1} M{_{31}} \Omega^{-2} + c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} + 2 c{_1} M{_{11}} \Omega^{-2} \mu{_{10}}^{2} + 4 c{_1} M{_{21}} \mu{_{10}} \Omega^{-2} + 2 c{_1} M{_{30}} \mu{_{01}} \Omega^{-2} + 4 c{_1} M{_{20}} \mu{_{01}} \mu{_{10}} \Omega^{-2} - 2 c{_2} M{_{20}} - 2 c{_4} M{_{20}} - c{_1} M{_{11}} \Omega^{-2} - c{_1} M{_{21}} \Omega^{-2} - c{_1} M{_{20}} \mu{_{01}} \Omega^{-2} - c{_1} \mu{_{01}} \mu{_{10}} \Omega^{-2} \\
\frac{dM{_{11}}}{dt} =& c{_2} M{_{20}} + c{_1} M{_{11}} \Omega^{-2} + c{_1} M{_{22}} \Omega^{-2} + c{_1} M{_{02}} \Omega^{-2} \mu{_{10}}^{2} + c{_1} M{_{21}} \mu{_{01}} \Omega^{-2} + c{_1} \mu{_{01}} \mu{_{10}} \Omega^{-2} + 2 c{_1} M{_{12}} \mu{_{10}} \Omega^{-2} + 2 c{_1} M{_{11}} \mu{_{01}} \mu{_{10}} \Omega^{-2} - c{_2} M{_{11}} - c{_2} \mu{_{10}} - c{_4} M{_{11}} - c{_1} M{_{12}} \Omega^{-2} - c{_1} M{_{31}} \Omega^{-2} - c{_1} M{_{02}} \mu{_{10}} \Omega^{-2} - c{_1} M{_{11}} \mu{_{01}} \Omega^{-2} - c{_1} M{_{11}} \mu{_{10}} \Omega^{-2} - c{_1} M{_{11}} \Omega^{-2} \mu{_{10}}^{2} - 2 c{_1} M{_{21}} \mu{_{10}} \Omega^{-2} - c{_1} M{_{30}} \mu{_{01}} \Omega^{-2} - c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} - 2 c{_1} M{_{20}} \mu{_{01}} \mu{_{10}} \Omega^{-2} \\
\frac{dM{_{02}}}{dt} =& c{_2} \mu{_{10}} + c{_1} M{_{21}} \Omega^{-2} + 2 c{_2} M{_{11}} + 2 c{_1} M{_{12}} \Omega^{-2} + c{_1} M{_{20}} \mu{_{01}} \Omega^{-2} + c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} + 2 c{_1} M{_{02}} \mu{_{10}} \Omega^{-2} + 2 c{_1} M{_{11}} \mu{_{01}} \Omega^{-2} + 2 c{_1} M{_{11}} \mu{_{10}} \Omega^{-2} - c{_1} M{_{11}} \Omega^{-2} - 2 c{_1} M{_{22}} \Omega^{-2} - 2 c{_1} M{_{02}} \Omega^{-2} \mu{_{10}}^{2} - 4 c{_1} M{_{12}} \mu{_{10}} \Omega^{-2} - 2 c{_1} M{_{21}} \mu{_{01}} \Omega^{-2} - c{_1} \mu{_{01}} \mu{_{10}} \Omega^{-2} - 4 c{_1} M{_{11}} \mu{_{01}} \mu{_{10}} \Omega^{-2}
\end{align*}

## Performing moment closure

```julia
# normal closure on raw moment equations
closed_raw_eqs = moment_closure(raw_eqs, "normal")
latexify(closed_raw_eqs)
```

\begin{align*}
\frac{d\mu{_{10}}}{dt} =& c{_3} \Omega + c{_1} \mu{_{01}} \mu{_{20}} \Omega^{-2} + 2 c{_1} \mu{_{10}} \mu{_{11}} \Omega^{-2} - c{_2} \mu{_{10}} - c{_4} \mu{_{10}} - c{_1} \mu{_{11}} \Omega^{-2} - 2 c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} \\
\frac{d\mu{_{01}}}{dt} =& c{_2} \mu{_{10}} + c{_1} \mu{_{11}} \Omega^{-2} + 2 c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} - c{_1} \mu{_{01}} \mu{_{20}} \Omega^{-2} - 2 c{_1} \mu{_{10}} \mu{_{11}} \Omega^{-2} \\
\frac{d\mu{_{20}}}{dt} =& c{_2} \mu{_{10}} + c{_3} \Omega + c{_4} \mu{_{10}} + 2 c{_3} \Omega \mu{_{10}} + 2 c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} + 6 c{_1} \mu{_{11}} \mu{_{20}} \Omega^{-2} - 2 c{_2} \mu{_{20}} - 2 c{_4} \mu{_{20}} - c{_1} \mu{_{11}} \Omega^{-2} - c{_1} \mu{_{01}} \mu{_{20}} \Omega^{-2} - 4 c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{3} - 2 c{_1} \mu{_{10}} \mu{_{11}} \Omega^{-2} \\
\frac{d\mu{_{11}}}{dt} =& c{_2} \mu{_{20}} + c{_1} \mu{_{11}} \Omega^{-2} + c{_3} \Omega \mu{_{01}} + c{_1} \mu{_{02}} \mu{_{20}} \Omega^{-2} + 2 c{_1} \Omega^{-2} \mu{_{11}}^{2} + 2 c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{3} + 2 c{_1} \mu{_{10}} \Omega^{-2} \mu{_{01}}^{2} - c{_2} \mu{_{10}} - c{_2} \mu{_{11}} - c{_4} \mu{_{11}} - 2 c{_1} \mu{_{01}} \mu{_{11}} \Omega^{-2} - c{_1} \mu{_{02}} \mu{_{10}} \Omega^{-2} - 3 c{_1} \mu{_{11}} \mu{_{20}} \Omega^{-2} - 2 c{_1} \Omega^{-2} \mu{_{01}}^{2} \mu{_{10}}^{2} \\
\frac{d\mu{_{02}}}{dt} =& c{_2} \mu{_{10}} + 2 c{_2} \mu{_{11}} + c{_1} \mu{_{01}} \mu{_{20}} \Omega^{-2} + 4 c{_1} \mu{_{01}} \mu{_{11}} \Omega^{-2} + 2 c{_1} \mu{_{02}} \mu{_{10}} \Omega^{-2} + 2 c{_1} \mu{_{10}} \mu{_{11}} \Omega^{-2} + 4 c{_1} \Omega^{-2} \mu{_{01}}^{2} \mu{_{10}}^{2} - c{_1} \mu{_{11}} \Omega^{-2} - 4 c{_1} \Omega^{-2} \mu{_{11}}^{2} - 2 c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} - 2 c{_1} \mu{_{02}} \mu{_{20}} \Omega^{-2} - 4 c{_1} \mu{_{10}} \Omega^{-2} \mu{_{01}}^{2}
\end{align*}

```julia
# higher order moment closure functions
latexify(closed_raw_eqs, :closure)
```

\begin{align*}
\mu{_{21}} =& \mu{_{01}} \mu{_{20}} + 2 \mu{_{10}} \mu{_{11}} - 2 \mu{_{01}} \mu{_{10}}^{2} \\
\mu{_{12}} =& \mu{_{02}} \mu{_{10}} + 2 \mu{_{01}} \mu{_{11}} - 2 \mu{_{10}} \mu{_{01}}^{2} \\
\mu{_{31}} =& \mu{_{01}} \mu{_{30}} + 6 \mu{_{01}} \mu{_{10}}^{3} + 3 \mu{_{10}} \mu{_{21}} + 3 \mu{_{11}} \mu{_{20}} - 6 \mu{_{11}} \mu{_{10}}^{2} - 6 \mu{_{01}} \mu{_{10}} \mu{_{20}} \\
\mu{_{22}} =& \mu{_{02}} \mu{_{20}} + 2 \mu{_{11}}^{2} + 2 \mu{_{01}} \mu{_{21}} + 2 \mu{_{10}} \mu{_{12}} + 6 \mu{_{01}}^{2} \mu{_{10}}^{2} - 2 \mu{_{02}} \mu{_{10}}^{2} - 2 \mu{_{20}} \mu{_{01}}^{2} - 8 \mu{_{01}} \mu{_{10}} \mu{_{11}}
\end{align*}

```julia
println(latexify(closed_raw_eqs, :closure))
```

    \begin{align*}
    \mu{_{21}} =& \mu{_{01}} \mu{_{20}} + 2 \mu{_{10}} \mu{_{11}} - 2 \mu{_{01}} \mu{_{10}}^{2} \\
    \mu{_{12}} =& \mu{_{02}} \mu{_{10}} + 2 \mu{_{01}} \mu{_{11}} - 2 \mu{_{10}} \mu{_{01}}^{2} \\
    \mu{_{31}} =& \mu{_{01}} \mu{_{30}} + 6 \mu{_{01}} \mu{_{10}}^{3} + 3 \mu{_{10}} \mu{_{21}} + 3 \mu{_{11}} \mu{_{20}} - 6 \mu{_{11}} \mu{_{10}}^{2} - 6 \mu{_{01}} \mu{_{10}} \mu{_{20}} \\
    \mu{_{22}} =& \mu{_{02}} \mu{_{20}} + 2 \mu{_{11}}^{2} + 2 \mu{_{01}} \mu{_{21}} + 2 \mu{_{10}} \mu{_{12}} + 6 \mu{_{01}}^{2} \mu{_{10}}^{2} - 2 \mu{_{02}} \mu{_{10}}^{2} - 2 \mu{_{20}} \mu{_{01}}^{2} - 8 \mu{_{01}} \mu{_{10}} \mu{_{11}}
    \end{align*}

```julia
# normal closure on central moment equations
closed_central_eqs = moment_closure(central_eqs, "normal")
latexify(closed_central_eqs)
```

\begin{align*}
\frac{d\mu{_{10}}}{dt} =& c{_3} \Omega + c{_1} M{_{20}} \mu{_{01}} \Omega^{-2} + c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} + 2 c{_1} M{_{11}} \mu{_{10}} \Omega^{-2} - c{_2} \mu{_{10}} - c{_4} \mu{_{10}} - c{_1} M{_{11}} \Omega^{-2} - c{_1} \mu{_{01}} \mu{_{10}} \Omega^{-2} \\
\frac{d\mu{_{01}}}{dt} =& c{_2} \mu{_{10}} + c{_1} M{_{11}} \Omega^{-2} + c{_1} \mu{_{01}} \mu{_{10}} \Omega^{-2} - 2 c{_1} M{_{11}} \mu{_{10}} \Omega^{-2} - c{_1} M{_{20}} \mu{_{01}} \Omega^{-2} - c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} \\
\frac{dM{_{20}}}{dt} =& c{_2} \mu{_{10}} + c{_3} \Omega + c{_4} \mu{_{10}} + c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} + 6 c{_1} M{_{11}} M{_{20}} \Omega^{-2} + 2 c{_1} M{_{11}} \Omega^{-2} \mu{_{10}}^{2} + 4 c{_1} M{_{20}} \mu{_{01}} \mu{_{10}} \Omega^{-2} - 2 c{_2} M{_{20}} - 2 c{_4} M{_{20}} - c{_1} M{_{11}} \Omega^{-2} - c{_1} M{_{20}} \mu{_{01}} \Omega^{-2} - c{_1} \mu{_{01}} \mu{_{10}} \Omega^{-2} \\
\frac{dM{_{11}}}{dt} =& c{_2} M{_{20}} + c{_1} M{_{11}} \Omega^{-2} + c{_1} M{_{02}} M{_{20}} \Omega^{-2} + c{_1} M{_{02}} \Omega^{-2} \mu{_{10}}^{2} + c{_1} \mu{_{01}} \mu{_{10}} \Omega^{-2} + 2 c{_1} \Omega^{-2} M{_{11}}^{2} + 2 c{_1} M{_{11}} \mu{_{01}} \mu{_{10}} \Omega^{-2} - c{_2} M{_{11}} - c{_2} \mu{_{10}} - c{_4} M{_{11}} - c{_1} M{_{02}} \mu{_{10}} \Omega^{-2} - 3 c{_1} M{_{11}} M{_{20}} \Omega^{-2} - c{_1} M{_{11}} \mu{_{01}} \Omega^{-2} - c{_1} M{_{11}} \mu{_{10}} \Omega^{-2} - c{_1} M{_{11}} \Omega^{-2} \mu{_{10}}^{2} - c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} - 2 c{_1} M{_{20}} \mu{_{01}} \mu{_{10}} \Omega^{-2} \\
\frac{dM{_{02}}}{dt} =& c{_2} \mu{_{10}} + 2 c{_2} M{_{11}} + c{_1} M{_{20}} \mu{_{01}} \Omega^{-2} + c{_1} \mu{_{01}} \Omega^{-2} \mu{_{10}}^{2} + 2 c{_1} M{_{02}} \mu{_{10}} \Omega^{-2} + 2 c{_1} M{_{11}} \mu{_{01}} \Omega^{-2} + 2 c{_1} M{_{11}} \mu{_{10}} \Omega^{-2} - c{_1} M{_{11}} \Omega^{-2} - 4 c{_1} \Omega^{-2} M{_{11}}^{2} - 2 c{_1} M{_{02}} M{_{20}} \Omega^{-2} - 2 c{_1} M{_{02}} \Omega^{-2} \mu{_{10}}^{2} - c{_1} \mu{_{01}} \mu{_{10}} \Omega^{-2} - 4 c{_1} M{_{11}} \mu{_{01}} \mu{_{10}} \Omega^{-2}
\end{align*}

```julia
latexify(closed_central_eqs, :closure)
```

\begin{align*}
M{_{30}} =& 0 \\
M{_{21}} =& 0 \\
M{_{12}} =& 0 \\
M{_{31}} =& 3 M{_{11}} M{_{20}} \\
M{_{22}} =& M{_{02}} M{_{20}} + 2 M{_{11}}^{2}
\end{align*}

```julia
println(latexify(closed_central_eqs, :closure))
```

    \begin{align*}
    M{_{30}} =& 0 \\
    M{_{21}} =& 0 \\
    M{_{12}} =& 0 \\
    M{_{31}} =& 3 M{_{11}} M{_{20}} \\
    M{_{22}} =& M{_{02}} M{_{20}} + 2 M{_{11}}^{2}
    \end{align*}

## Solving the moment equations

```julia
# check the ordering of parameters in the model
paramsmap(rn)
```

    Dict{Sym{ModelingToolkit.Parameter{Real},Nothing},Int64} with 5 entries:
      Ω  => 5
      c₄ => 4
      c₂ => 2
      c₁ => 1
      c₃ => 3

```julia
# check the ordering of species in the model
speciesmap(rn)
```

    Dict{Term{Real,Nothing},Int64} with 2 entries:
      X(t) => 1
      Y(t) => 2

```julia
# parameter values [c₁, c₂, c₃, c₄, Ω]
p = [0.9, 2, 1, 1, 100]

# initial molecule numbers of species [X, Y]
u₀ = [1, 1]

# deterministic initial conditions
u₀map = deterministic_IC(u₀, closed_raw_eqs)

# time interval to solve one on
tspan = (0., 100.)

# convert the closed raw moment equations into a DifferentialEquations ODEProblem
oprob = ODEProblem(closed_raw_eqs, u₀map, tspan, p)

using OrdinaryDiffEq
# solve using Tsit5() solver
@time sol = solve(oprob, Tsit5(), saveat=0.1);
```

     10.295819 seconds (23.91 M allocations: 1.263 GiB, 20.99% gc time)

```julia
using Plots
plot(sol, vars=(0, [1,2]), lw=2)
```

```julia
# Run SSA
using DiffEqJump

# convert ReactionSystem into JumpSystem
# Note: can directly convert the ReactionSystem to DiscreteProblem
# but then combinatoric_ratelaws cannot be set
jsys = convert(JumpSystem, rn, combinatoric_ratelaws=false)

# create a DiscreteProblem encoding that the molecule numbers are integer-valued
dprob = DiscreteProblem(jsys, u₀, tspan, p) # same parameters as defined earlier

# create a JumpProblem: specify Gillespie's Direct Method as the solver
# and SET save_positions to (false, false) as otherwise time of each
# reaction occurence would be saved (complicating moment estimates)
jprob = JumpProblem(jsys, dprob, Direct(), save_positions=(false, false))

# define an EnsembleProblem to simulate multiple trajectories
ensembleprob  = EnsembleProblem(jprob)

# simulate 10000 SSA trajectories
@time sol_SSA = solve(ensembleprob, SSAStepper(), saveat=0.1, trajectories=10000);
```

     64.843064 seconds (11.63 M allocations: 1.132 GiB, 1.58% gc time)

```julia
using DiffEqBase.EnsembleAnalysis

means_SSA = timeseries_steps_mean(sol_SSA)
plot!(means_SSA, lw=2, labels=["SSA μ₁₀(t)" "SSA μ₀₁(t)"], linestyle=:dash,
      linecolor=[1 2], background_color_legend=nothing, legend=:bottomright)
```


# Cocco, Gomes, & Maenhout (2005)

 # "[Consumption and Portfolio Choice Over the Life Cycle](https://academic.oup.com/rfs/article-abstract/18/2/491/1599892)"

 - Notebook created by [Mateo Velásquez-Giraldo](https://github.com/Mv77) and [Matthew Zahn](https://sites.google.com/view/matthew-v-zahn/matthew-v-zahn).

This notebook uses the [Econ-ARK/HARK](https://github.com/econ-ark/hark) toolkit to describe the main results and reproduce the figures in the linked paper. The main HARK tool used here is the $\texttt{PortfolioConsumerType}$ class. For an introduction to this module, see the [HARK's Documentation](https://hark.readthedocs.io/en/latest/).

Our results using the HARK toolkit resemble those of the original paper, but we have not been able to replicate its results exactly for reasons that we have not been able to track down. We suspect there might be subtleties in the normalization or calibration that we have missed and that could be causing the discrepancies. We would be thankful if a reader (or the original authors) were able to discover the reason for the discrepancies, and could issue a pull request for a revision that reconciles them.

We thank Chris Carroll and Sylvain Catherine for comments and guidance.

```python
# This cell does some preliminary set up

# Packages
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# Import relevenat HARK tools
import HARK.ConsumptionSaving.ConsPortfolioModel as cpm

# This is a jupytext paired notebook that autogenerates BufferStockTheory.py
# which can be executed from a terminal command line via "ipython BufferStockTheory.py"
# But a terminal does not permit inline figures, so we need to test jupyter vs terminal
# Google "how can I check if code is executed in the ipython notebook"
from IPython import get_ipython # In case it was run from python instead of ipython

# If the ipython process contains 'terminal' assume not in a notebook
def in_ipynb():
    try:
        if 'terminal' in str(type(get_ipython())):
            return False
        else:
            return True
    except NameError:
        return False

# Determine whether to make the figures inline (for spyder or jupyter)
# vs whatever is the automatic setting that will apply if run from the terminal
if in_ipynb():
    # %matplotlib inline generates a syntax error when run from the shell
    # so do this instead
    get_ipython().run_line_magic('matplotlib', 'inline')
else:
    get_ipython().run_line_magic('matplotlib', 'auto')
```

### The base model

The authors' aim is to represent the life cycle of a consumer that is exposed to uninsurable labor income risk and how he chooses to allocate his savings between a risky and a safe asset, without the possibility to borrow or short-sell.

##### Dynamic problem

The problem of an agent $i$ of age $t$ in the base model is recursively represented as

\begin{split}
V_{i,t} =& \max_{0\leq C_{i,t} \leq X_{i,t}, \alpha_{i,t}\in[0,1]} U(C_{i,t}) + \delta p_t E_t\{ V_{i,t+1} (X_{i,t+1}) \}\\
&\text{s.t}\\
&X_{i,t+1} = Y_{i,t+1} + (X_{i,t} - C_{i,t})(\alpha_{i,t} R_{t+1} + (1-\alpha_{i,t})\bar{R}_f)
\end{split}

where $C_{i,t}$ is consumption, $\alpha_{i,t}$ is the share of savings allocated to the risky asset, $Y_{i,t}$ is labor income, and $X_{i,t}$ represents wealth. The utility function $U(\cdot)$ is assumed to be CRRA in the base model. Extensions beyond the baseline model include an additively separable bequest motive in the utility function. The discount factor is $\delta$ and $p_t$ is the probability of survival from $t$ to $t+1$. Death is certain at a maximum period $T$.

Note that the consumer cannot borrow or short-sell.

The control variables in the problem are $\{C_{it}, \alpha_{it}\}^T_{t=1}$ and the state variables are $\{t, X_{it}, v_{it} \}^T_{t=1}$. The agent solves for  policy rules as a function of the state variables—$C_{it}(X_{it}, v_{it})$ and $\alpha_{it}(X_{it}, v_{it})$.

#### Labor income

An important driver of the paper's results is the labor income process. It is specified as follows:

\begin{equation}
\log Y_{i,t} = f(t,Z_{i,t}) + v_{i,t} + \epsilon_{i,t}, \quad \text{for } t\leq K.
\end{equation}

where $K$ is the (exogenous) age of retirement, $Z_{i,t}$ are demographic characteristics, $\epsilon_{i,t}\sim \mathcal{N}(0,\sigma^2_\epsilon)$ is a transitory shock, and  $v_{i,t}$ is a permanent component following a random walk

\begin{equation}
v_{i,t} = v_{i,t-1} + u_{i,t} = v_{i,t-1} + \xi_t + \omega_{i,t}
\end{equation}

in which the innovation is decomposed into an aggregate ($\xi_t$) and an idiosyncratic component ($\omega_{i,t}$), both following mean-0 normal distributions.

Post-retirement income is a constant fraction $\lambda$ of permanent income in the last working year $K$.

A crucial aspect of the labor income process is that $f(\cdot,\cdot)$ is calibrated to match income profiles in the PSID, capturing the usual humped shape of income across lifetime.

#### Matching labor income in HARK

In HARK's consumption-saving models, the income process takes the form
\begin{equation}
    \ln Y_t = \ln P_t + \ln \theta_t
\end{equation}
where $P_t$ represents permanent income and $\ln \theta_t \sim N(0,\sigma_\theta)$ transitory shocks to income. Permanent income evolves according to
\begin{equation}
    \ln P_{t+1} = \ln \Gamma_{t+1} +  \ln \psi_{t+1} + \ln P_t
\end{equation}
where $\Gamma_{t+1}$ is a deterministic growth factor, and $\ln \psi_{t+1} \sim N(0,\sigma_\psi)$ a permanent income shock.

To represent the author's assumptions in HARK, we express both income processes as sums of deterministic components and i.i.d shocks
\begin{align}
\text{Cocco et. al} &\quad& \ln Y_{i,t} &=& f(t,Z_{i,t}) + v_{i,0} &+&\sum_{k=1}^t u_{i,k} &+&\qquad \varepsilon_{i,t} \\
\text{HARK}        &\quad& \ln Y_{i,t} &=& \ln P_{i,0} + \sum_{k=1}^t \ln \Gamma_k &+&\sum_{k=1}^t \ln \psi_{i,k} &+& \qquad \ln \theta_{i,t}.
\end{align}

These representations make evident the mapping that we use

|HARK | Cocco et. al |
| :---: | :-----------: |
| $\ln$ $P_{i,0}$ | $f(0,Z_{i,0})$ + $v_{i,0}$ |
| $\ln$ $\Gamma_{t+1}$| $f(t+1$, $Z_{i,t+1})$ - $f(t,Z_{i,t})$|
|$\ln$ $\psi_{i,k}$| $u_{i,k}$|
|$\ln$ $\theta_{i,t}$| $\varepsilon_{i,t}$|

and to achieve a retirement income that is equal to a fraction $\lambda$ of permanent income in the last working period $K$, we simply make $\Gamma_{K+1} = \lambda$ and $\Gamma_{t} = 1$ $\forall t>K+1$.

#### Assets and their returns

There are two assets available for consumers to allocate their savings.

- Bonds: paying a risk-free return $\bar{R}_f$.

- Stocks: paying a stochastic return $R_t = \bar{R}_f + \mu + \eta_t$, where the stochastic component $\eta_t \sim \mathcal{N}(0, \sigma^2_\eta)$ is allowed to be correlated with the aggregate labor income innovation $\xi_t$.

The borrowing and short-selling constraints ensure that agents cannot allocate negative dollars to either of these assets or borrow against future labor income or retirement wealth. Recall $\alpha_{i,t}$ is the proportion of the investor's savings that are invested in the risky asset. The model's constraints imply that $\alpha_{i,t}\in[0,1]$ and wealth is non-negative.

### Calibration—Summary

The paper defines and calibrates several parameters which can be broken down into the following categories:

__1. Preferences and life cycle__

 | Parameter | Description | Code | Value |
 |:---:| ---         | ---  | :---: |
 | $\delta$ | Time Preference Factor | $\texttt{DiscFac}$ | 0.96 |
 | $\gamma$ | Coefficient of Relative Risk Aversion| $\texttt{CRRA}$ | 10 |
 | $p_t$ | Survival Probability | $\texttt{LivPrb}$ | [0.6809,0.99845] |
 | $t_0$ | Starting Age | $\texttt{t_start}$ | 20 |
 | $t_r$ | Retirement Age | $\texttt{t_ret}$ | 65 |
 | $T$ | Maximum Age | $\texttt{t_end}$ | 100 |

__2. Income process and the finanical assets__

 | Parameter | Description | Code | Value|
 |:---:| ---         | ---  | :---: |
 | $f(t,Z_{i,t})$| Average income at each stage of life | $\texttt{det_income}$ | $ \exp($0.530339 + 0.16818 $t$+ (0.0323371/10) $t^2$ + (0.0019704/100) $t^3)$ |
 | $\lambda$ | Last Period Labor Income Share for Retirement | $\texttt{repl_fac}$ | 0.68212 |
 | $\log \Gamma$ | Permanent Income Growth Factor | $\texttt{PermGroFac}$ | $\{\log f_{t+1} - \log f_t\}^{t_r+1}_{t=20}$ |
 | $\mathsf{R}$ | Interest Factor | $\texttt{Rfree}$ | 1.02 |
 | $\mu$ | Average Stock Return | $\texttt{RiskyDstnFunc}$ \& $\texttt{RiskyDrawFunc}$ | 1.06 |
 | $\sigma_\eta$ | Std Dev of Stock Returns | $\texttt{RiskyDstnFunc}$ \& $\texttt{RiskyDrawFunc}$ | 0.157 |

__3. Shocks__

 | Parameter | Description | Code | Value |
 |:---:| ---         | ---  | :---: |
 | $\sigma_v$ | Std Dev of Log Permanent Shock| $\texttt{PermShkStd}$ | 0.102956 |
 | $\sigma_\epsilon$ | Std Dev of Log Transitory Shock| $\texttt{TranShkStd}$ | 0.27166 |

For reference, the authors' source Fortran code that includes these paramerization details is available on [Gomes' personal page](http://faculty.london.edu/fgomes/research.html). Code that solves the model is also available in [Julia](https://github.com/econ-ark/HARK/issues/114#issuecomment-371891418).

### Calibration in our implementation.

We replicate the previously defined parameter values and transition processes for state variables and shocks, and format them into the structures required by HARK.

This is done in the external file [Calibration/params.py](https://github.com/matthew-zahn/CGMPort/blob/master/CGMPort-Shared/Code/Python/Calibration/params.py), which we now import.

```python
# Calibrate the model in line with the information above
import sys,os
# The following code navigates to another directory where a python script with the parameters for the model is saved.
sys.path.append(os.path.realpath('Calibration/'))
# Loading the parameters from the Calibration/params.py script
from params import dict_portfolio, time_params, det_income, age_plot_params
```

All of the model's parameters are contained in the structure <tt>dict_portfolio<tt>, which can now be passed to HARK's <tt>PortfolioConsumerType<tt> to build and solve a representation of our problem.

```python
# Solve the model with the given parameters
agent = cpm.PortfolioConsumerType(**dict_portfolio)
agent.solve()
```

### A note on normalization

The problem as specified above makes the value function homogeneous with respect to permanent labor income. This is convenient as it allows for a re-statement of the problem in variables that are normalized by permanent income or its random components, eliminating a state variable.

The authors report (page 497) taking the normalization $v_{i,t} = 1$. This amounts to defining normalized variables $\tilde{\cdot}$ as the original variable divided by $e^{v_{i,t}-1}$. For instance:

\begin{equation}
\tilde{Y_{i,t}} = \frac{Y_{i,t}}{\exp(v_{i,t}-1)} = \frac{\exp (f(t,Z_{i,t}) + v_{i,t} + \varepsilon_{i,t})}{\exp(v_{i,t}-1)} = \exp( f(t,Z_{i,t}) + 1 + \varepsilon_{i,t} )
\end{equation}

These normalized variables have the convenient interpretation of the state that things would be in if, it weren't for permanent shocks. The author's depictions of policy functions are presented in terms of these normalized variables.

On the other hand, HARK normalizes the problem by total permanent income $P_t = \exp (f(t,Z_{i,t}) + v_{i,t})$ and its solution objects are therefore in terms of normalized variables $\hat{\cdot}$, defined as
\begin{equation}
\hat{X_{i,t}} = \frac{X_{i,t}}{P_{i,t}} = \frac{X_{i,t}}{\exp (f(t,Z_{i,t}) + v_{i,t})}.
\end{equation}

Therefore, to present our results in a way consistent with that of the original authors, we would use the following relationship
\begin{equation}
\tilde{X_{i,t}} = \hat{X_{i,t}} \times \exp (f(t,Z_{i,t})+1)
\end{equation}

However, our results are much more consistent with those of the original authors when we take the normalization $v_{i,t} = 0$, which also make sense since it makes the random-walk multiplicative part of permanent income $\exp{v_{i,t}}=1$. We therefore assume this is a typo, take $v_{i,t} = 0$, document this issue in the \textbf{Puzzles} section below, and use the relationship
\begin{equation}
\tilde{X_{i,t}} = \hat{X_{i,t}} \times \exp (f(t,Z_{i,t})).
\end{equation}

The next cell defines our normalization factor

```python
# Define a normalization factor
norm_factor = det_income*np.exp(0)
```

### Key Results

#### The optimal risky asset share

The plot below shows the policy function for the risky portfolio share as a function of wealth at different ages.

The optimal risky share is decreasing in wealth. The authors argue this is due to the fact that, at low levels of wealth, relatively safe human wealth represents a higher fraction of the consumer's wealth, so he shifts his tradeable wealth towards riskier alternatives.

Analyzing the policy rule by age also shows that the risky share increases from young to middle age, and decreases from middle to old age. This is consistent with the previous interpretation: shares trace the humped shape of labor earnings.

These estimates are different from what is produced in the original paper, which are also reproduced below. Generally, the policy functions do not share the same curvature, which leads to greater reductions in the optimal portfolio share at lower levels of wealth.

```python
# Plot portfolio rule
eevalgrid = np.linspace(0,300,100)
plt.figure()
# In the last period of life you consume everything
# so portfolio choice is irrelevant

# Ages
ages = age_plot_params
age_born = time_params['Age_born']
for a in ages:
    plt.plot(eevalgrid,
             agent.solution[a-age_born].ShareFuncAdj(eevalgrid/norm_factor[a-age_born]),
             label = 'Age = %i' %(a))
plt.xlabel('Wealth')
plt.ylabel('Risky portfolio share')
plt.title('Risky Portfolio Policy Function')
plt.legend()
plt.grid()

if not in_ipynb():
    plt.show(block=False)
    plt.pause(1)
else:
    plt.show(block=True)
```

We present the author's originally reported policy functions for comparison

#### Consumption behavior

The plot below shows the policy function for consumption as a function of wealth at different ages.

At all age levels consumption increases with wealth. The consumption function also appears to shift upwards as life progresses.

Our consumption policy functions again do not match those of the original paper, which are also reproduced below. Consumption also appears to increase with age in our policy functions that does not come through in the results presented in the paper.

```python
# Plot consumption function
plt.figure()
ages = age_plot_params
for a in ages:
    plt.plot(eevalgrid,
             agent.solution[a-age_born].cFuncAdj(eevalgrid/norm_factor[a-age_born])*norm_factor[a-age_born],
             label = 'Age = %i' %(a))
plt.xlabel('Wealth')
plt.ylabel('Consumption')
plt.title('Consumption Policy Function')
plt.legend()
plt.grid()

if not in_ipynb():
    plt.show(block=False)
    plt.pause(1)
else:
    plt.show(block=True)
```

We again present the author's originally reported policy functions for comparison

### Simulations

Using the policy functions obtained from solving the model we present a series of simulations to highlight features of the model.

We first run a few simulations to verify the quality of our calibration.

The figures below show simulated levels of permanent income and risky portfolio shares for 5 agents over their life spans. We can see the model generates a heterogeneous permanent income distribution. Interestingly, all of these agents tend to follow the same general pattern for investing in the risky asset. Early in life, all of their portfolios are invested in the risky asset. This declines as the agent ages and converges to approximately 35% once they reach retirement.

```python
# Set up simulation parameters

# Number of agents and periods in the simulation.
agent.AgentCount = 5 # Number of instances of the class to be simulated.
# Since agents can die, they are replaced by a new agent whenever they do.

# Number of periods to be simulated
agent.T_sim = 80

# Set up the variables we want to keep track of.
agent.track_vars = ['aNrmNow','cNrmNow', 'pLvlNow', 't_age', 'ShareNow','mNrmNow']

# Run the simulations
agent.initializeSim()
agent.simulate()

# Present diagnostic plots.
plt.figure()
plt.plot(agent.history['t_age']+time_params['Age_born'], agent.history['pLvlNow'],'.')
plt.xlabel('Age')
plt.ylabel('Permanent income')
plt.title('Simulated Income Paths')
plt.grid()

if not in_ipynb():
    plt.show(block=False)
    plt.pause(1)
else:
    plt.show(block=True)

plt.figure()
plt.plot(agent.history['t_age']+time_params['Age_born'], agent.history['ShareNow'],'.')
plt.xlabel('Age')
plt.ylabel('Risky share')
plt.title('Simulated Risky Portfolio Shares')
plt.grid()

if not in_ipynb():
    plt.show(block=False)
    plt.pause(1)
else:
    plt.show(block=True)
```

#### The average life cycle patterns

We now increase the number of simulations to examine and compare the behavior of the mean values of variables of interest at different ages, conditional on survival. In each case we present the original plots from the paper for reference.

The plot below illustrates the average dynamics of permanent income, consumption, and market resources across all of the simulated agents. The plot follows the general pattern observed in the original paper. However, our results show that the agents are accumulating significantly more market resources.

```python
# Number of agents and periods in the simulation.
agent.AgentCount = 50 # Number of instances of the class to be simulated.
# Since agents can die, they are replaced by a new agent whenever they do.

# Number of periods to be simulated
agent.T_sim = 80*50

# Run the simulations
agent.initializeSim()
agent.simulate()

raw_data = {'Age': agent.history['t_age'].flatten()+time_params['Age_born'] - 1,
            'pIncome': agent.history['pLvlNow'].flatten(),
            'rShare': agent.history['ShareNow'].flatten(),
            'nrmM': agent.history['mNrmNow'].flatten(),
            'nrmC': agent.history['cNrmNow'].flatten()}

Data = pd.DataFrame(raw_data)
Data['Cons'] = Data.nrmC * Data.pIncome
Data['M'] = Data.nrmM * Data.pIncome

# Find the mean of each variable at every age
AgeMeans = Data.groupby(['Age']).mean().reset_index()
```

```python
plt.figure()
plt.plot(AgeMeans.Age, AgeMeans.pIncome,
         label = 'Income')
plt.plot(AgeMeans.Age, AgeMeans.M,
         label = 'Market resources')
plt.plot(AgeMeans.Age, AgeMeans.Cons,
         label = 'Consumption')
plt.legend()
plt.xlabel('Age')
plt.title('Variable Means Conditional on Survival')
plt.grid()

if not in_ipynb():
    plt.show(block=False)
    plt.pause(1)
else:
    plt.show(block=True)
```

```python
# Find age percentiles
AgePC5 = Data.groupby(['Age']).quantile(0.05).reset_index()
AgePC95 = Data.groupby(['Age']).quantile(0.95).reset_index()

# plot till death - 1
age_1 = time_params['Age_death'] - time_params['Age_born']

plt.figure()
plt.ylim([0, 1.1])
plt.plot(AgeMeans.Age[:age_1], AgeMeans.rShare[:age_1], label = 'Mean')
plt.plot(AgePC5.Age[:age_1], AgePC5.rShare[:age_1], '--r', label='Perc. 5')
plt.plot(AgePC95.Age[:age_1], AgePC95.rShare[:age_1], '--g', label = 'Perc. 95')
plt.legend()

plt.xlabel('Age')
plt.ylabel('Risky Share')
plt.title('Risky Portfolio Share Mean Conditional on Survival')
plt.grid()

if not in_ipynb():
    plt.show(block=False)
    plt.pause(1)
else:
    plt.show(block=True)
```

### Other results in the original paper

#### The welfare implications of different allocation rules

The authors next conduct a welfare analysis of different allocation rules, including popular heuristics. The rules are presented in the next figure.

The utility cost of each policy in terms of constant consumption streams with respect to the authors calculated optimal policy function is reported in the next table.

Interestingly, the "no-income" column corresponds to the usual portfolio choice result of the optimal share being the quotient of excess returns and risk times relative risk aversion, disregarding labor income. The experiment shows this allocation produces substantial welfare losses.

#### Heterogeneity and sensitivity analysis

The authors also considered a number of extensions to the baseline model. These are summarized below along with their main conclusions.

- Labor income risk: Income risk may vary across employment sectors relative to the baseline model. The authors examine extreme cases for industries that have a large standard deviation and temporary income shocks. While some differences appear across sectors, the results are generally in line with the baseline model.
- Disastrous labor income shocks: The authors find that even a small probability of zero labor income lowers the optimal portfolio allocation in stocks, while the qualitative features of the baseline model are preserved.
- Uncertain retirement income: The authors consider two types of uncertainty for retirement income; it is stochastic and correlated with current stock market performance and allowing for disastrous labor income draws before retirement. The first extension has results essentially the same as the baseline case. The second leads to more conservative portfolio allocations but is broadly consistent with the baseline model.
- Endogenous borrowing constraints: The authors add borrowing to their model by building on credit-market imperfections. They find that the average investor borrows about \$5,000 and are in debt for most of their working life. The agents eventually pay off this debt and save for retirement. Relative to the benchmark model, the investor has put less of their money in their portfolio and arrive at retirement with substantially less wealth. These results are particularly pronounced at the lower end of the income distribution relative to the higher end. Additional details are available in the text.
- Bequest motive: The authors introduce a bequest motive into the agent's utility function (i.e., $b>0$). Young investors are more impatient and tend to save less for bequests. As the agent ages, savings increases and is strongest once the agent retires. This leads to effects on the agent's portfolio allocation. Taking a step-back however, these effects are not very large unless $b$ is large.
- Educational attainment: The authors generally find that savings are consistent across education groups. They note that for a given age, the importance of future income is increasing with education level. This implies that riskless asset holdings are larger for these households.
- Risk aversion and intertemporal substitution: Lowering the level of risk aversion in the model leads to changes in the optimal portfolio allocation and wealth accumulation. Less risk-averse investors accumulate less precautionary savings and invest more in risky assets.

### Conclusion

This article provides a dynamic model with accurate lifetime income profiles in which labor income increases risky asset holdings, as it is seen as a closer substitute of risk-free assets. It finds an optimal risky asset share that decreases in wealth and with age, after middle age. The model is also used to show that ignoring labor income for portfolio allocation can generate substantial welfare losses.

### Puzzles and Questions

- Table 4 says stock returns are $0.06$. They might mean that the equity premium $\mu$ is $0.06$.
- The authors report taking the normalization $v_{i,t} = 1$. However the ranges of their results seem more consistent with $v_{i,t} = 0$ so that $\exp (v_{i,t}) = 1$, which also makes more sense for interpretation.

### Robustness Analyses

Given the differences between our results and the original paper, we did a number of checks to ensure our model was behaving consistently with well-established theoretical results. Specifically we checked:
- For an infinitely lived agent with log normal returns, that their optimal portfolio allocation converges to the Campbell-Viceira (2002) approximation to the optimal portfolio share in Merton-Samuelson (1969) model.
- For an infinitely lived agent with no labor income that can only invest in a single risky asset, that their marginal propensity to consumer converges to the theoretical MPC of Merton-Samuelson (1969).
- For an agent facing no labor income risk, that their consumption patterns precisely match the results from a perfect foresight solution.

In all three cases, we verified that our HARK model holds up to these results. More details and specific results are available upon request.

As the HARK toolkit continues to develop, there are additional sensitivities that we can perform to further check the credibility of our results. Specifically, once human wealth is available in the $\texttt{PortfolioConsumerType}$ class, we can perform the following additional checks, which were kindly suggested by Professor Sylvain Catherine:
- Shut down the income risk and remove retirement income. The solution to this new problem are provided by Merton 1971. Basically, you capitalize future earnings as an endowment of risk free asset. Then the equity share should be such that Equity/(Wealth+NPV of Human capital) is the same as the equity share in Merton 1969.
- Adding back the permanent income risk and check if the equity share is consistent with Viceira 2001. Viceira tells you something like this: $\pi = \frac{\mu - r}{\gamma \sigma^2_s} + \left(\frac{\mu - r}{\gamma \sigma^2_s} - \beta_{HC} \right) \frac{HC}{W}$, where $\beta_{HC} = \frac{\text{Cov}(r_{HC},r_s)}{\text{Var}(r_s)}$. In the CGM problem it is easy to compute $\beta_{HC}$ because earnings follow a simple random walk. HC is the NPV of human capital, which you can approximate very well by discounting expected earnings by $r+\beta_{HC}*(rm-r)$.

### Bibliographic entry of the original article

```python
### Bibtex entry
'''
@article{10.1093/rfs/hhi017,
    author = {Cocco, João F. and Gomes, Francisco J. and Maenhout, Pascal J.},
    title = "{Consumption and Portfolio Choice over the Life Cycle}",
    journal = {The Review of Financial Studies},
    volume = {18},
    number = {2},
    pages = {491-533},
    year = {2005},
    month = {02},
    abstract = "{This article solves a realistically calibrated life cycle model of consumption and portfolio choice with non-tradable labor income and borrowing constraints. Since labor income substitutes for riskless asset holdings, the optimal share invested in equities is roughly decreasing over life. We compute a measure of the importance of human capital for investment behavior. We find that ignoring labor income generates large utility costs, while the cost of ignoring only its risk is an order of magnitude smaller, except when we allow for a disastrous labor income shock. Moreover, we study the implications of introducing endogenous borrowing constraints in this incomplete-markets setting.}",
    issn = {0893-9454},
    doi = {10.1093/rfs/hhi017},
    url = {https://doi.org/10.1093/rfs/hhi017},
    eprint = {http://oup.prod.sis.lan/rfs/article-pdf/18/2/491/24421441/hhi017.pdf},
}
'''
```

    '\n@article{10.1093/rfs/hhi017,\n    author = {Cocco, João F. and Gomes, Francisco J. and Maenhout, Pascal J.},\n    title = "{Consumption and Portfolio Choice over the Life Cycle}",\n    journal = {The Review of Financial Studies},\n    volume = {18},\n    number = {2},\n    pages = {491-533},\n    year = {2005},\n    month = {02},\n    abstract = "{This article solves a realistically calibrated life cycle model of consumption and portfolio choice with non-tradable labor income and borrowing constraints. Since labor income substitutes for riskless asset holdings, the optimal share invested in equities is roughly decreasing over life. We compute a measure of the importance of human capital for investment behavior. We find that ignoring labor income generates large utility costs, while the cost of ignoring only its risk is an order of magnitude smaller, except when we allow for a disastrous labor income shock. Moreover, we study the implications of introducing endogenous borrowing constraints in this incomplete-markets setting.}",\n    issn = {0893-9454},\n    doi = {10.1093/rfs/hhi017},\n    url = {https://doi.org/10.1093/rfs/hhi017},\n    eprint = {http://oup.prod.sis.lan/rfs/article-pdf/18/2/491/24421441/hhi017.pdf},\n}\n'


```python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
%matplotlib inline
# IMG_FILEPATH = "../../01_deep-learning-from-scratch/images/04_extensions/"

# matplotlib 폰트설정
#plt.rc('font', family='NanumGothicOTF') # For MacOS
matplotlib.rcParams['axes.unicode_minus'] = False
plt.rc('font', family='NanumGothic') # For Windows
print(plt.rcParams['font.family'])
```

    ['NanumGothic']

# Tanh와 그 도함수

```python
a = np.arange(-5, 5, 0.01)
plt.plot(a, np.tanh(a))
plt.title("Tanh 활성화 함수")
plt.xlabel("$x$")
plt.ylabel("$Tanh(x)$");
# plt.savefig(IMG_FILEPATH + "05_tanh_activation.png")
```

```python
(a[501] - a[500]) / 0.01
```

    0.9999999999999787

```python
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

a = np.arange(-5, 5, 0.01)
plt.plot(a, sigmoid(a));
```

```python
def relu(x):
    return np.array([el if el > 0 else 0 for el in x])

a = np.arange(-5, 5, 0.01)
plt.plot(a, relu(a))
plt.title("ReLU 활성화 함수")
plt.xlabel("X")
plt.ylabel("$ReLU(x)$");
# plt.savefig(IMG_FILEPATH + "04_relu_activation.png")
```

```python
a = np.arange(-5, 5, 0.01)
plt.figure(figsize=(8,6))
plt.plot(a, sigmoid(a) * (1-(sigmoid(a))))
plt.plot(a, 1 - (np.tanh(a) **2))
plt.legend(['$sigmoid(x)$의 도함수',
            '$Tanh(x)$의 도함수'])
plt.title("$sigmoid(x)$와 $Tanh(x)$의 도함수")
plt.xlabel("x");
# plt.savefig(IMG_FILEPATH + "06_sigmoid_tanh_derivative.png")
```

# 교차 엔트오피 / 소프트맥스

$$ \text{CE}(p_i, y_i) = - y_i * \text{log}(p_i) - (1 - y_i) * \text{log}(1-p_i) $$

$$ CE(x, 0) = \sum_{i}{(- y_i * log(p_i) - (1 - y_i) * log(1-p_i))} $$

$$ \begin{align} = &\sum_{i}{(- 0 * log(p_i) - (1 - 0) * log(1-p_i))} \\
=  &\sum_{i}{- log(1-p_i)} \end{align}$$

$$ CE(x, 1) = \sum_{i}{(- y_i * log(p_i) - (1 - y_i) * log(1-p_i))} $$

$$ \begin{align} = &\sum_{i}{(- 1 * log(p_i) - (1 - 1) * log(1-p_i))} \\
=  &\sum_{i}{- log(p_i)} \end{align}$$

$$ \text{Normalize}(\begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix})  = \begin{bmatrix} \frac{x_1}{x_1 + x_2 + x_3} \\
\frac{x_2}{x_1 + x_2 + x_3} \\
\frac{x_3}{x_1 + x_2 + x_3}
\end{bmatrix} $$

$$ \text{Softmax}(\begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix})  = \begin{bmatrix} \frac{e^{x_1}}{e^{x_1} + e^{x_2} + e^{x_3}} \\
\frac{e^{x_2}}{e^{x_1} + e^{x_2} + e^{x_3}} \\
\frac{e^{x_3}}{e^{x_1} + e^{x_2} + e^{x_3}}
\end{bmatrix} $$

$$ S(\begin{bmatrix} x_1 \\ x_2 \\ x_3 \\ \vdots \\ x_n \end{bmatrix})  = \begin{bmatrix} \frac{e^{x_1}}{e^{x_1} + e^{x_2} + e^{x_3} + \ldots + e^{x_n}} \\
\frac{e^{x_2}}{e^{x_1} + e^{x_2} + e^{x_3} + \ldots + e^{x_n}} \\
\frac{e^{x_3}}{e^{x_1} + e^{x_2} + e^{x_3} + \ldots + e^{x_n}} \\
\ldots \\
\frac{e^{x_n}}{e^{x_1} + e^{x_2} + e^{x_3} + \ldots + e^{x_n}}
\end{bmatrix} $$

$$ {SCE}_1 = - y_1 * log(\frac{e^{x_1}}{e^{x_1} + e^{x_2} + e^{x_3}}) - (1 - y_1) * log(1-\frac{e^{x_1}}{e^{x_1} + e^{x_2} + e^{x_3}}) $$

$$ \text{softmax}(\begin{bmatrix} p_1 \\ p_2 \\ p_3 \end{bmatrix}) - \begin{bmatrix} y_1 \\ y_2 \\ y_3 \end{bmatrix} $$

# $y_i = 0$에 대한 로그 손실

```python
x = np.linspace(0.01, 0.99, 99)
y1 = -1.0 * np.log(1 - x)
y2 = (x - 0) ** 2
plt.plot(x, y1);
plt.plot(x, y2);
plt.legend(['교차 엔트로피 손실', '평균제곱오차'])

plt.title("교차 엔트로피 손실과 평균제곱오차($y = 0$)")
plt.xlabel("예측값 ($p$)")
plt.ylabel("손실값");
# plt.savefig(IMG_FILEPATH + "04_Log_loss_vs_MSE_y_eq_0.png")
```

이론적으로는 $y=0$이면 $p$가 1에 가까워질수록 손실도 무한에 가까워진다.

# $y_i = 1$에 대한 로그 손실

```python
x = np.linspace(0.01, 0.99, 99)
y = -1.0 * np.log((x))

plt.plot(x, y);
plt.title("$y = 1$일 때의 로그 손실")
plt.xlabel("예측값 ($p$)")
plt.ylabel("로그 손실");
```

# 소프트맥스 교차 엔트로피 함수의 도함수 유도(2가지인 간단한 경우)

$$ C(p, y) = - y * \text{log}(p) - (1 - y) * \text{log}(1-p) $$

$$
C(p,y)=
\begin{cases}
-log(1-p) & \text{if }  y = 0\\
-log(p) & \text{if }  y = 1
\end{cases}
$$

$$ \text{SCE}(\begin{bmatrix} p_1 \\ p_2 \end{bmatrix}, \begin{bmatrix} y_1 \\ y_2 \end{bmatrix})_1 = - y_1 * log(S(\begin{bmatrix} p_1 \\ p_2 \end{bmatrix})_1) - (1 - y_1) * log(1-S(\begin{bmatrix} p_1 \\ p_2 \end{bmatrix})_1) = - y_1 * log(\frac{e^{p_1}}{e^{p_1} + e^{p_2}}) - (1 - y_1) * log(1-\frac{e^{p_1}}{e^{p_1} + e^{p_2}}) $$

Structure:

$ y_1 = a $, $e^{x_2} = b$

$$ SC(x_1) = - a * log(\frac{e^{x_1}}{e^{x_1} + b}) - (1 - a) * log(1-\frac{e^{x_1}}{e^{x_1} + b}) $$

**몫 규칙:**

$$ f(x) = \frac{g(x)}{h(x)} $$

$$ f'(x) = \frac{g'(x) * h(x) - g(x) * h'(x)}{(h(x))^2} $$

다음과

$$ f(x) = \frac{e^x}{e^x + b} $$

$$ \begin{align} f'(x) =& \frac{e^x * (e^x + b) - (e^x * e^x)}{(e^x + b)^2} \\
=& \frac{e^x * (e^x + b - e^ x)}{(e^x + b)^2} \\
=& \frac{e^x * b}{(e^x + b)^2}\end{align} $$

다음을 만족하면

$$ g(x) = - a * log(f(x)) - (1 - a) * log(1-f(x)) $$

다음이 성립한다.

$$ g'(x) = - a * \frac{f'(x)}{f(x)} - (1 - a) * \frac{-1 * f'(x)}{1-f(x)} $$

먼저 $\frac{f'(x)}{f(x)}$ 를 계산한다.

$$ \begin{align}
\frac{f'(x)}{f(x)} =& \frac{\frac{e^x * b}{(e^x + b)^2}}{\frac{e^x}{e^x + b}} \\\\
=& \frac{-e^x * b}{(e^x + b)^2} * \frac{e^x + b}{e^x}
 \\
=& \frac{b}{e^x + b} \end{align} $$

그리고 아래와 같은 사실을 이용한다.

$$ \frac{b}{e^x + b} = 1 - \frac{e^x}{e^x + b} $$

$ \begin{align}
\frac{-1 * f'(x)}{1 - f(x)} =& \frac{-1 * \frac{e^x * b}{(e^x + b)^2}}{1 - \frac{e^x}{e^x + b}} \\
=& \frac{\frac{-e^x * b}{(e^x + b)^2}}{\frac{b}{e^x + b}} \\
=& \frac{-e^x * b}{(e^x + b)^2} * \frac{e^x + b}{b}
 \\
=& \frac{-e^x}{e^x + b} \end{align}$

마지막으로 모든 식을 합치면

$$ \begin{align} SC'(x) =& - a * \frac{f'(x)}{f(x)} - (1 - a) * \frac{-1 * f'(x)}{1-f(x)} \\
=& -a * \frac{b}{e^x + b} - (1 - a) * \frac{-e^x}{e^x + b} \\
=& -a * \frac{b}{e^x + b} + \frac{e^x}{e^x + b} - a * \frac{-e^x}{e^x + b} \\
=& -a * (1 - \frac{e^x}{e^x + b}) + \frac{e^x}{e^x + b} - a * \frac{-e^x}{e^x + b} \\
=& -a + a * \frac{e^x}{e^x + b} + \frac{e^x}{e^x + b} - a * \frac{-e^x}{e^x + b} \\
=& -a + \frac{e^x}{e^x + b} \\
\end{align} \\
$$

소프트맥스 층에서 역방향으로 전달되는 도함수는 다음과 같이 간단하다.

$$ S - Y = s(\begin{bmatrix} p_1 \\ p_2 \end{bmatrix}) - \begin{bmatrix} y_1 \\ y_2 \end{bmatrix} = \begin{bmatrix} \frac{e^{p_1}}{e^{p_1} + e^{p_2}} - y_1 \\ \frac{e^{p_2}}{e^{p_1} + e^{p_2}} - y_2 \end{bmatrix} $$

그리고 다음과 같은 근거를 볼 때, 이 도함수가 옳음을 알 수 있다.

* softmax 함수의 출력은 항상 0부터 1사이의 값이다.
* $y_i$가 0이면, $ s(x_1) - y_1 $는 양수가 된다. $x_1$를 증가시키면 손실값도 증가하기 때문이다. $y_i = 1$이면 반대가 성립한다.
* Note that this loss function only makes sense because $ s(x_i) $ is always between 0 and 1.
* $ s(x_i) $가 항상 0과 1사이의 값을 갖기 때문에 이 손실함수가 성립한다.

텐서플로에 'softmax_cross_entropy_with_logits'라는 함수가 있는 것도 이 때문이다.

# 안정적인 소프트맥스

$$ log(softmax(x_j)) = log(\frac{e^{x_j}}{\sum_i^n e^{x_j}}) = x_j - logsumexp(X) $$

$$ softmax(x_j) = e^{log(softmax(x_j))} = e^{x - logsumexp(x)} $$

$$ logsumexp(X) = log(\sum_i^n e^{x_i}) = c + log(\sum_i^n e^{x_i - c}) $$

# 원-핫 인코딩

$$ [0, 2, 1] \Rightarrow \begin{bmatrix} 1 & 0 & 0 & \ldots & 0 \\ 0 & 0 & 1 & \ldots & 0 \\ 0 & 1 & 0 & \ldots & 0 \end{bmatrix} $$

# 학습률 감쇠

## 선형 감쇠

$$ \alpha_{t} = \alpha_{start} - (\alpha_{start} - \alpha_{end}) * \frac{t}{N} $$

## 지수적 감쇠

$$ \alpha_{t} = \alpha_{start} * ((\frac{\alpha_{end}}{\alpha_{start}})^\frac{1}{N})^t $$

# 확률적 경사 하강법의 모멘텀 계산식

$ \text{update} = \nabla_t + \mu * \nabla_{t-1} + \mu^2 * \nabla_{t-2} + \ldots $

# 가중치 초기화

## 특징 수를 이용한 초기화

$ f_n = w_{1, n}* x_1 + \ldots + w_{784, n}* x_{784} + b_n $

$ \text{Var}(w_{i,j}) = 1 $

$ \text{Var}(X_1 + X_2) = \text{Var}(X_1) + \text{Var}(X_2) $

```python
n_feat = 784
n_hidden = 256

np.random.seed(190131)
```

```python
a = np.random.randn(1, n_feat)
```

```python
b = np.random.randn(n_feat, n_hidden)
```

```python
out = np.dot(a, b).reshape(n_hidden)
```

```python
fig, ax = plt.subplots(1, 2, figsize=(12, 5))
ax[0].hist(out)
ax[0].set_title("784개 입력을 받는 은닉층의\n 활성화 함수 통과 전 가중합의 분포")
ax[1].hist(np.tanh(out))
ax[1].set_title("784개 입력을 받는 은닉층의\n tanh 활성화 함수를 통과한 출력값의 분포");
# fig.savefig(IMG_FILEPATH + "01_weight_init_activation_illustration.png")
```

```python

```


# Занятие 8
# Прикладная алгебра и численные методы
## Повторение: псевдообратная матрица, ортогонализация Грама-Шмидта, LU, QR.

```
!python -m pip install sympy --upgrade
```

    Requirement already up-to-date: sympy in /usr/local/lib/python3.7/dist-packages (1.7.1)
    Requirement already satisfied, skipping upgrade: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy) (1.2.1)

```
import numpy as np
import sympy
from sympy import S, latex, Matrix, I, simplify, expand, GramSchmidt
import scipy.linalg
import numpy.linalg
from sympy.functions.special.polynomials import chebyshevt, chebyshevu
from numpy.polynomial.chebyshev import chebinterpolate
import matplotlib.pyplot as plt
from google.colab import files
%matplotlib inline
```

```
import sympy
import numpy
sympy.__version__, numpy.__version__
```

    ('1.7.1', '1.19.5')

```
sympy.init_printing(use_unicode=False, wrap_line=False, no_global=True)
```

## Псевдообратная матрица
В numpy псевдообратная матрица находится с помощью np.linalg.pinv
## Пример 1
Найти псевдообратную матрицу к матрице
$$
\left(
\begin{matrix}
1 & 4 & 7 & 10 & 13\\
16 & 19 & 22 & 25 & 28\\
31 & 34 & 37 & 40 & 43\\
46 & 49 & 52 & 55 & 58
\end{matrix}
\right)
$$

```
m = 4
n = 5
A = np.arange(1, 3*m*n + 1, 3).reshape((m, n))
As = Matrix(A)
As_pinv = As.pinv()
display(As, As_pinv, As_pinv.evalf(8))
for item in (np.linalg.pinv(A), scipy.linalg.pinv(A)):
    print('A^+ = {0},\n A = A A^+ A  {1}, A^+ = A^+ A ^+ {2}'.format(item, np.allclose(A, np.dot(A, np.dot(item, A))), np.allclose(item, np.dot(item, np.dot(A, item)))))
```

$\displaystyle \left[\begin{matrix}1 & 4 & 7 & 10 & 13\\16 & 19 & 22 & 25 & 28\\31 & 34 & 37 & 40 & 43\\46 & 49 & 52 & 55 & 58\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}- \frac{3}{50} & - \frac{7}{225} & - \frac{1}{450} & \frac{2}{75}\\- \frac{4}{125} & - \frac{73}{4500} & - \frac{1}{2250} & \frac{23}{1500}\\- \frac{1}{250} & - \frac{1}{750} & \frac{1}{750} & \frac{1}{250}\\\frac{3}{125} & \frac{61}{4500} & \frac{7}{2250} & - \frac{11}{1500}\\\frac{13}{250} & \frac{32}{1125} & \frac{11}{2250} & - \frac{7}{375}\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}-0.06 & -0.031111111 & -0.0022222222 & 0.026666667\\-0.032 & -0.016222222 & -0.00044444444 & 0.015333333\\-0.004 & -0.0013333333 & 0.0013333333 & 0.004\\0.024 & 0.013555556 & 0.0031111111 & -0.0073333333\\0.052 & 0.028444444 & 0.0048888889 & -0.018666667\end{matrix}\right]$

    A^+ = [[-0.06       -0.03111111 -0.00222222  0.02666667]
     [-0.032      -0.01622222 -0.00044444  0.01533333]
     [-0.004      -0.00133333  0.00133333  0.004     ]
     [ 0.024       0.01355556  0.00311111 -0.00733333]
     [ 0.052       0.02844444  0.00488889 -0.01866667]],
     A = A A^+ A  True, A^+ = A^+ A ^+ True
    A^+ = [[-0.06       -0.03111111 -0.00222222  0.02666667]
     [-0.032      -0.01622222 -0.00044444  0.01533333]
     [-0.004      -0.00133333  0.00133333  0.004     ]
     [ 0.024       0.01355556  0.00311111 -0.00733333]
     [ 0.052       0.02844444  0.00488889 -0.01866667]],
     A = A A^+ A  True, A^+ = A^+ A ^+ True

```
latex(As)
```

    '\\left[\\begin{matrix}1 & 4 & 7 & 10 & 13\\\\16 & 19 & 22 & 25 & 28\\\\31 & 34 & 37 & 40 & 43\\\\46 & 49 & 52 & 55 & 58\\end{matrix}\\right]'

### LU разложение
$PA = LU$ для матрицы $A$

$L$ - левая треугольная матрица с единицами на главной диагонали,
$U$ - правая треугольная (трапециевидная) матрица,
$P$ - матрица перестановок.

$A = P^{-1}LU$.
### Пример 2.
Построим  LU разложение для матрицы
$$
M=\left(
\begin{matrix}
0 & 3+ I & 5 - 2I\\
 - I & 2 & 1 - I\\
5 & -1 + 4I & -3
\end{matrix}
\right)
$$

```
M = Matrix([[0, 3 + I, 5 - 2*I], [-I, 2, 1 - I], [5, -1 + 4*I, -3]])
L, U, perm = M.LUdecomposition()
display('L', L, 'U', U, 'LU', simplify(expand(L*U)), 'M', M, 'perm', perm)
```

Произведение матриц LU отличается от исходной матрицы M перестановкой строк. Восстановим матрицу M, применяя перестановки в соответствии с результатом, выдаваемым LUdecomposition:

```
number_of_rows = M.shape[0]
L, U, perm = M.LUdecomposition()
LU = simplify(expand((L*U).permuteBkwd(perm)))
P = sympy.eye(number_of_rows).permuteFwd(perm)
display('L', L, 'U', U, 'perm', perm, 'P', P, 'LU', LU,
        'M', M, 'PM == LU', P*M == simplify(expand(L*U)))
```

### QR разложение
$A = QR$

$Q$ - матрица из ортогональных столбцов, т.е. $Q^HQ = I$, $I$ - единичная матрица,
$R$ - правая треугольная (трапециевидная) матрица.

Ранг матрицы $A$ равен числу столбцов матрицы $Q$.
### Пример 3.
Построим  QR разложение для матрицы

$$
M_3 = \left(
\begin{matrix}
0 & 3  & 5 & 6 \\
 - 1 & 2 & 1 & -7\\
5 & -1  & -3 & 8
\end{matrix}
\right)
$$

```
M3 = Matrix([[0, 3, 5, 6], [-1, 2, 1, -7], [5, -1, -3, 8]])
Q3, R3 = M3.QRdecomposition()
Q3R3 = simplify(expand(Q3*R3))
display('Q3', simplify(expand(Q3)), 'R3', simplify(expand(R3)),
        'Q3R3', Q3R3, 'M3', M3, 'M3 = Q3R3', M3 == Q3R3)
```

    'Q3'

$\displaystyle \left[\begin{matrix}0 & \frac{\sqrt{910}}{35} & \frac{3 \sqrt{35}}{35}\\- \frac{\sqrt{26}}{26} & \frac{3 \sqrt{910}}{182} & - \frac{\sqrt{35}}{7}\\\frac{5 \sqrt{26}}{26} & \frac{3 \sqrt{910}}{910} & - \frac{\sqrt{35}}{35}\end{matrix}\right]$

    'R3'

$\displaystyle \left[\begin{matrix}\sqrt{26} & - \frac{7 \sqrt{26}}{26} & - \frac{8 \sqrt{26}}{13} & \frac{47 \sqrt{26}}{26}\\0 & \frac{3 \sqrt{910}}{26} & \frac{68 \sqrt{910}}{455} & \frac{15 \sqrt{910}}{182}\\0 & 0 & \frac{13 \sqrt{35}}{35} & \frac{9 \sqrt{35}}{7}\end{matrix}\right]$

    'Q3R3'

$\displaystyle \left[\begin{matrix}0 & 3 & 5 & 6\\-1 & 2 & 1 & -7\\5 & -1 & -3 & 8\end{matrix}\right]$

    'M3'

$\displaystyle \left[\begin{matrix}0 & 3 & 5 & 6\\-1 & 2 & 1 & -7\\5 & -1 & -3 & 8\end{matrix}\right]$

    'M3 = Q3R3'

    True

 ## Псевдообратная матрица на основе QR разложения:
 $$
M = QR, \quad M^+ = R^T(R\cdot R^T)^{-1}Q^T
 $$

 ## Пример 4
 Воспользуемся этой формулой для построения псевдообратной матрицы Примера 3:

```
M3pinv = M3.pinv()
M3pinv.simplify()
display(M3pinv, R3.T*(R3*R3.T)**(-1)*Q3.T)
```

$\displaystyle \left[\begin{matrix}\frac{1}{61} & \frac{460}{2013} & \frac{1195}{6039}\\\frac{17}{183} & \frac{431}{2013} & \frac{631}{6039}\\\frac{6}{61} & \frac{5}{671} & - \frac{37}{671}\\\frac{7}{183} & - \frac{76}{671} & - \frac{38}{6039}\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}\frac{1}{61} & \frac{460}{2013} & \frac{1195}{6039}\\\frac{17}{183} & \frac{431}{2013} & \frac{631}{6039}\\\frac{6}{61} & \frac{5}{671} & - \frac{37}{671}\\\frac{7}{183} & - \frac{76}{671} & - \frac{38}{6039}\end{matrix}\right]$

# Процесс ортогонализации Грама--Шмидта

К системе из $n$ векторов применим процесс ортогонализации Грамма-Шмидта
$$
\begin{matrix}
e_1^{new} = e_1\\
e_2^{new} = e_2 - \frac{(e_1, e_2)}{(e_1, e_1)}e_1\\
e_3^{new} = e_3 - \frac{(e_1, e_3)}{(e_1, e_1)}e_1 - \frac{(e_2, e_3)}{(e_2, e_2)}e_2\\
...\\
e_n^{new} = e_n - \frac{(e_1, e_n)}{(e_1, e_1)}e_1 - \frac{(e_2, e_n)}{(e_2, e_2)}e_2 - ... - \frac{(e_{n - 1}, e_n)}{(e_{n - 1}, e_{n - 1})}e_{n - 1}
\end{matrix}
$$
Полученные ортогональные векторы нормализуем с помощью деления на их модули.

## Пример 5
Построить ортонормированную систему векторов на основе системы
$$
\begin{matrix}
e_1 = (1, 2, 0, 1)\\
e_2 = (5, -2, 1, -1)\\
e_3 = (-1, 0, 2, -3)\\
e_4 = (0, -1, 0, 1)
\end{matrix}
$$

```
e1 = Matrix([1, 2, 0, 1])
e2 = Matrix([5, -2, 1, -1])
e3 = Matrix([-1, 0, 2, -3])
e4 = Matrix([0, -1, 0, 1])
g1 = e1
g2 = e2 - g1.dot(e2)/g1.dot(e1)*g1
g3 = e3 - g1.dot(e3)/g1.dot(g1)*g1 - g2.dot(e3)/g2.dot(g2)*g2
g4 = e4 - g1.dot(e4)/g1.dot(g1)*g1 - g2.dot(e4)/g2.dot(g2)*g2 - g3.dot(e4)/g3.dot(g3)*g3
display('normalized', *GramSchmidt([e1, e2, e3, e4], True),
        'not normalized', *GramSchmidt([e1, e2, e3, e4]),
        'g1234', *[sympy.simplify(item) for item in (g1, g2, g3, g4)])
print(*[sympy.simplify(item) for item in (g1.dot(g2), g1.dot(g3), g1.dot(g4), g2.dot(g3), g2.dot(g4), g3.dot(g4))])
```

    'normalized'

$\displaystyle \left[\begin{matrix}\frac{\sqrt{6}}{6}\\\frac{\sqrt{6}}{3}\\0\\\frac{\sqrt{6}}{6}\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}\frac{5 \sqrt{31}}{31}\\- \frac{2 \sqrt{31}}{31}\\\frac{\sqrt{31}}{31}\\- \frac{\sqrt{31}}{31}\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}- \frac{\sqrt{102}}{102}\\\frac{2 \sqrt{102}}{51}\\\frac{\sqrt{102}}{17}\\- \frac{7 \sqrt{102}}{102}\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}- \frac{3 \sqrt{527}}{527}\\- \frac{5 \sqrt{527}}{527}\\\frac{18 \sqrt{527}}{527}\\\frac{13 \sqrt{527}}{527}\end{matrix}\right]$

    'not normalized'

$\displaystyle \left[\begin{matrix}1\\2\\0\\1\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}5\\-2\\1\\-1\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}- \frac{1}{3}\\\frac{4}{3}\\2\\- \frac{7}{3}\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}- \frac{54}{527}\\- \frac{90}{527}\\\frac{324}{527}\\\frac{234}{527}\end{matrix}\right]$

    'g1234'

$\displaystyle \left[\begin{matrix}1\\2\\0\\1\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}5\\-2\\1\\-1\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}- \frac{1}{3}\\\frac{4}{3}\\2\\- \frac{7}{3}\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}- \frac{54}{527}\\- \frac{90}{527}\\\frac{324}{527}\\\frac{234}{527}\end{matrix}\right]$

    0 0 0 0 0 0

## Пример 6
Построим с помощью QR-разложения псевдообратную матрицу для матрицы $A$, столбцы которой $e_1$, $e_2$, $e_3$ и $e_4$ из Примера 5.

Матрицу $Q$ можем сразу составить из нормированных векторов $g_1$, $g_2$, $g_3$ и $g_4$ (можно использовать GramSchmidt([e1, e2, e3, e4], True)).

Обозначим ортонормированные векторы  $f_1$, $f_2$, $f_3$ и $f_4$.

Матрица $R$ имеет вид
$$
R = \left(
\begin{matrix}
(f_1, a_1) & (f_1, a_2) & (f_1, a_3) & (f_1, a_4)\\
0 & (f_2, a_2) & (f_2, a_3) & (f_2, a_4)\\
0 & 0 & (f_3, a_3) & (f_3, a_4)\\
0 & 0 & 0 & (f_4, a_4)\\
\end{matrix}\right)
$$

Вначале составим матрицу $A$, затем получим ортонормированную систему из столбцов $A$, далее на их основе составим матрицу $Q$. Инициализируем матрицу $R$ нулевой матрицей соответствующей размерности, заполним ее в соответствии с формулой. Построим псевдообратную матрицу по формуле   $A^+ = R^T(R\cdot R^T)^{-1}Q^T$.

```
A = Matrix([e1.T, e2.T, e3.T, e4.T]).T
A
```

$\displaystyle \left[\begin{matrix}1 & 5 & -1 & 0\\2 & -2 & 0 & -1\\0 & 1 & 2 & 0\\1 & -1 & -3 & 1\end{matrix}\right]$

```
A = Matrix([e1.T, e2.T, e3.T, e4.T]).T
f = GramSchmidt([e1, e2, e3, e4], True)
Q = Matrix([fk.T for fk in f]).T
R = sympy.zeros(4, 4)
ee = (e1, e2, e3, e4)
for k in range(4):
    for j in range(k, 4):
        R[k, j] = f[k].dot(ee[j])
display('Q', Q, 'R', R, 'A', A, 'QR', Q*R, 'A+', A.pinv(), 'R.T(R*R.T).inv()Q.T', R.T*(R*R.T).inv()*Q.T)

```

    'Q'

$\displaystyle \left[\begin{matrix}\frac{\sqrt{6}}{6} & \frac{5 \sqrt{31}}{31} & - \frac{\sqrt{102}}{102} & - \frac{3 \sqrt{527}}{527}\\\frac{\sqrt{6}}{3} & - \frac{2 \sqrt{31}}{31} & \frac{2 \sqrt{102}}{51} & - \frac{5 \sqrt{527}}{527}\\0 & \frac{\sqrt{31}}{31} & \frac{\sqrt{102}}{17} & \frac{18 \sqrt{527}}{527}\\\frac{\sqrt{6}}{6} & - \frac{\sqrt{31}}{31} & - \frac{7 \sqrt{102}}{102} & \frac{13 \sqrt{527}}{527}\end{matrix}\right]$

    'R'

$\displaystyle \left[\begin{matrix}\sqrt{6} & 0 & - \frac{2 \sqrt{6}}{3} & - \frac{\sqrt{6}}{6}\\0 & \sqrt{31} & 0 & \frac{\sqrt{31}}{31}\\0 & 0 & \frac{\sqrt{102}}{3} & - \frac{11 \sqrt{102}}{102}\\0 & 0 & 0 & \frac{18 \sqrt{527}}{527}\end{matrix}\right]$

    'A'

$\displaystyle \left[\begin{matrix}1 & 5 & -1 & 0\\2 & -2 & 0 & -1\\0 & 1 & 2 & 0\\1 & -1 & -3 & 1\end{matrix}\right]$

    'QR'

$\displaystyle \left[\begin{matrix}1 & 5 & -1 & 0\\2 & -2 & 0 & -1\\0 & 1 & 2 & 0\\1 & -1 & -3 & 1\end{matrix}\right]$

    'A+'

$\displaystyle \left[\begin{matrix}\frac{1}{12} & \frac{11}{36} & \frac{1}{2} & \frac{11}{36}\\\frac{1}{6} & - \frac{1}{18} & 0 & - \frac{1}{18}\\- \frac{1}{12} & \frac{1}{36} & \frac{1}{2} & \frac{1}{36}\\- \frac{1}{6} & - \frac{5}{18} & 1 & \frac{13}{18}\end{matrix}\right]$

    'R.T(R*R.T).inv()Q.T'

$\displaystyle \left[\begin{matrix}\frac{1}{12} & \frac{11}{36} & \frac{1}{2} & \frac{11}{36}\\\frac{1}{6} & - \frac{1}{18} & 0 & - \frac{1}{18}\\- \frac{1}{12} & \frac{1}{36} & \frac{1}{2} & \frac{1}{36}\\- \frac{1}{6} & - \frac{5}{18} & 1 & \frac{13}{18}\end{matrix}\right]$

# Numpy и Scipy

## LU-разложение Scipy
https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.lu.html

$A = P L U$

scipy.linalg.lu(a, permute_l=False, overwrite_a=False, check_finite=True)

## QR-разложение
### Numpy
https://numpy.org/doc/stable/reference/generated/numpy.linalg.qr.html

numpy.linalg.qr(a, mode='reduced')

Пусть K = min(M, N), тогда

‘reduced’ : возвращает q, r с размерностями соответственно (M, K), (K, N) (по умолчанию)

‘complete’ : возвращает q, r с размерностями соответственно (M, M), (M, N)

‘r’ : возвращает r размерности только (K, N)

‘raw’ : возвращает h, tau с размерностями соответственно (N, M), (K,)

Значения ‘reduced’, ‘complete и ‘raw’ появились начиная с версии numpy 1.8. Значение по умолчанию ‘reduced’. Обратите внимание, что array h возвращаемый в режиме ‘raw’ транспонирован для совместимости с Fortran. Режим ‘economic’ не поддерживается больше. Режимы ‘full’ и ‘economic’ могут быть вызваны с использованием только первого символа (для обратной совместимости), но остальные пишутся полностью.
### Scipy
https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.qr.html

scipy.linalg.qr(a, overwrite_a=False, lwork=None, mode='full', pivoting=False, check_finite=True)

При pivoting=False возвращает матрицы $Q$ и $R$. Если pivoting=True, дополнительно возвращается $P$, такая что $A P = Q R$, причем диагональные элементы $R$ образую невозрастающую последовательность.


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

```python
model = semicon.models.ZincBlende(
    components=['foreman', 'zeeman'],
    bands=['gamma_6c'],
    default_databank='winkler',
)
```

```python
model.hamiltonian
```

$$\left[\begin{matrix}\frac{B_{z} g_{c}}{2} \mu_{B} + E_{0} + E_{v} + \frac{\gamma_{0} \hbar^{2} k_{x}^{2}}{2 m_{0}} + \frac{\gamma_{0} \hbar^{2} k_{y}^{2}}{2 m_{0}} + \frac{\gamma_{0} \hbar^{2} k_{z}^{2}}{2 m_{0}} & \frac{g_{c} \mu_{B}}{2} \left(B_{x} - i B_{y}\right)\\\frac{g_{c} \mu_{B}}{2} \left(B_{x} + i B_{y}\right) & - \frac{B_{z} g_{c}}{2} \mu_{B} + E_{0} + E_{v} + \frac{\gamma_{0} \hbar^{2} k_{x}^{2}}{2 m_{0}} + \frac{\gamma_{0} \hbar^{2} k_{y}^{2}}{2 m_{0}} + \frac{\gamma_{0} \hbar^{2} k_{z}^{2}}{2 m_{0}}\end{matrix}\right]$$

```python
model.bands
```

    ['gamma_6c']

```python
model.parameters(material='InAs')
```

    {'E_0': 0.418, 'Delta_0': 0.38, 'P': 0.9197, 'g_c': -14.9, 'gamma_1': 2.6959795187642825, 'gamma_2': -0.5520102406178573, 'gamma_3': 0.24798975938214163, 'kappa': -1.2520102406178584, 'q': 0.39, 'gamma_0': 43.66812227074236, 'E_v': 0, 'm_0': 510998.94609999994, 'phi_0': 4135.667662, 'mu_B': 5.7883818012e-05, 'hbar': 197.3269788}

# continuum dispersion

```python
import kwant
import numpy as np
import scipy.linalg as la

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

```python
def plot(bands, style, new_gamma_0=None):

    model = semicon.models.ZincBlende(
        bands=bands,
        components=('foreman',),
        default_databank='winkler'
    )

    if new_gamma_0 is not None:
        params = model.parameters(material='InAs').renormalize(new_gamma_0=1)
    else:
        params = model.parameters(material='InAs')

    disp = kwant.continuum.lambdify(str(model.hamiltonian), locals=params)
    h_k = lambda kx, ky, kz: disp(k_x=kx, k_y=ky, k_z=kz)

    k = np.linspace(-.5, .5, 101)
    e = np.array([la.eigvalsh(h_k(ki, 0, 0)) for ki in k])

    plt.plot(k, e, style)
    plt.plot([], [], style, label=bands)

plt.figure(figsize=(12, 10))

plot(bands=('gamma_6c',), style='C0')
plot(bands=('gamma_8v', 'gamma_7v'), style='C1')
plot(bands=('gamma_6c', 'gamma_8v', 'gamma_7v'), style='k--')

plt.legend(prop={'size': 18})
```