<p align="center">

</p>

## Interactive Trend Modeling for Spatial Estimation Demonstration

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

### The Interactive Workflow

Here's a interactive demonstration of:

* trend fitting
* spatial estimation with a trend model vs. a stationary mean

#### Trend Modeling

Trend modeling is the modeling of local features, based on data and interpretation, that are deemed certain (known).  The trend is substracted from the data, leaving a residual that is modeled stochastically with uncertainty (treated as unknown).

* geostatistical spatial estimation methods will make an assumption concerning stationarity
    * in the presence of significant nonstationarity we can not rely on spatial estimates based on data + spatial continuity model
* if we observe a trend, we should model the trend.
    * then model the residuals stochastically

Steps:

1. model trend consistent with data and intepretation at all locations within the area of itnerest, integrate all available information and expertise.

\begin{equation}
m(\bf{u}_\beta), \, \forall \, \beta \in \, AOI
\end{equation}

2. substract trend from data at the $n$ data locations to formulate a residual at the data locations.

\begin{equation}
y(\bf{u}_{\alpha}) = z(\bf{u}_{\alpha}) - m(\bf{u}_{\alpha}), \, \forall \, \alpha = 1, \ldots, n
\end{equation}

3. characterize the statistical behavoir of the residual $y(\bf{u}_{\alpha})$ integrating any information sources and interpretations.  For example the global cumulative distribution function and a measure of spatial continuity shown here.

\begin{equation}
F_y(y) \quad \gamma_y(\bf{h})
\end{equation}

4. model the residual at all locations with $L$ multiple realizations.

\begin{equation}
Y^\ell(\bf{u}_\beta),  \, \forall \, \beta \, \in \, AOI; \, \ell = 1, \ldots, L
\end{equation}

5. add the trend back in to the stochastic residual realizations to calculate the multiple realizations, $L$, of the property of interest based on the composite model of known deterministic trend, $m(\bf{u}_\alpha)$ and unknown stochastic residual, $y(\bf{u}_\alpha)$

\begin{equation}
Z^\ell(\bf{u}_\beta) = Y^\ell(\bf{u}_\beta) + m(\bf{u}_\beta),  \, \forall \, \beta \in \, AOI; \, \ell = 1, \ldots, L
\end{equation}

6. check the model, including quantification of the proportion of variance treated as known (trend) and unknown (residual).

\begin{equation}
\sigma^2_{Z} = \sigma^2_{Y} + \sigma^2_{m} + 2 \cdot C_{Y,m}
\end{equation}

given $C_{Y,m} \to 0$:

\begin{equation}
\sigma^2_{Z} = \sigma^2_{Y} + \sigma^2_{m}
\end{equation}

I can now describe the proportion of variance allocated to known and unknown components as follows:

\begin{equation}
Prop_{Known} = \frac{\sigma^2_{m}}{\sigma^2_{Y} + \sigma^2_{m}} \quad Prop_{Unknown} = \frac{\sigma^2_{Y}}{\sigma^2_{Y} + \sigma^2_{m}}
\end{equation}

I provide some practical, data-driven methods for trend model, but I should indicate that:

1. trend modeling is very important in reservoir modeling as it has large impact on local model accuracy and on the undertainty model
2. trend modeling is used in almost every subsurface model, unless the data is dense enough to impose local trends
3. trend modeling includes a high degree of expert judgement combined with the integration of various information sources

We limit ourselves to simple data-driven methods, but acknowledge much more is needed. In fact, trend modeling requires a high degree of knowledge concerning local geoscience and engineering data and knowledge.

Now I add some additional details on spatial estimation, kriging and variograms to support use of this interactive demonstration. See my lectures on these topics at [My YouTube Channel](www.youtube.com/GeostatsGuyLectures).

#### Spatial Estimation

Consider the case of making an estimate at some unsampled location, $𝑧(\bf{u}_0)$, where $z$ is the property of interest (e.g. porosity etc.) and $𝐮_0$ is a location vector describing the unsampled location.

How would you do this given data, $𝑧(\bf{𝐮}_1)$, $𝑧(\bf{𝐮}_2)$, and $𝑧(\bf{𝐮}_3)$?

It would be natural to use a set of linear weights to formulate the estimator given the available data.

\begin{equation}
z^{*}(\bf{u}) = \sum^{n}_{\alpha = 1} \lambda_{\alpha} z(\bf{u}_{\alpha})
\end{equation}

We could add an unbiasedness constraint to impose the sum of the weights equal to one.  What we will do is assign the remainder of the weight (one minus the sum of weights) to the global average; therefore, if we have no informative data we will estimate with the global average of the property of interest.

\begin{equation}
z^{*}(\bf{u}) = \sum^{n}_{\alpha = 1} \lambda_{\alpha} z(\bf{u}_{\alpha}) + \left(1-\sum^{n}_{\alpha = 1} \lambda_{\alpha} \right) \overline{z}
\end{equation}

We will make a stationarity assumption, so let's assume that we are working with residuals, $y$.

\begin{equation}
y^{*}(\bf{u}) = z^{*}(\bf{u}) - \overline{z}(\bf{u})
\end{equation}

If we substitute this form into our estimator the estimator simplifies, since the mean of the residual is zero.

\begin{equation}
y^{*}(\bf{u}) = \sum^{n}_{\alpha = 1} \lambda_{\alpha} y(\bf{u}_{\alpha})
\end{equation}

while satisfying the unbaisedness constraint.

#### Kriging

Now the next question is what weights should we use?

We could use equal weighting, $\lambda = \frac{1}{n}$, and the estimator would be the average of the local data applied for the spatial estimate. This would not be very informative.

We could assign weights considering the spatial context of the data and the estimate:

* **spatial continuity** as quantified by the variogram (and covariance function)
* **redundancy** the degree of spatial continuity between all of the available data with themselves
* **closeness** the degree of spatial continuity between the avaiable data and the estimation location

The kriging approach accomplishes this, calculating the best linear unbiased weights for the local data to estimate at the unknown location.  The derivation of the kriging system and the resulting linear set of equations is available in the lecture notes.  Furthermore kriging provides a measure of the accuracy of the estimate!  This is the kriging estimation variance (sometimes just called the kriging variance).

\begin{equation}
\sigma^{2}_{E}(\bf{u}) = C(0) - \sum^{n}_{\alpha = 1} \lambda_{\alpha} C(\bf{u}_0 - \bf{u}_{\alpha})
\end{equation}

What is 'best' about this estimate? Kriging estimates are best in that they minimize the above estimation variance.

#### Properties of Kriging

Here are some important properties of kriging:

* **Exact interpolator** - kriging estimates with the data values at the data locations
* **Kriging variance** can be calculated before getting the sample information, as the kriging estimation variance is not dependent on the values of the data nor the kriging estimate, i.e. the kriging estimator is homoscedastic.
* **Spatial context** - kriging takes into account, furthermore to the statements on spatial continuity, closeness and redundancy we can state that kriging accounts for the configuration of the data and structural continuity of the variable being estimated.
* **Scale** - kriging may be generalized to account for the support volume of the data and estimate. We will cover this later.
* **Multivariate** - kriging may be generalized to account for multiple secondary data in the spatial estimate with the cokriging system. We will cover this later.
* **Smoothing effect** of kriging can be forecast. We will use this to build stochastic simulations later.

#### Spatial Continuity

**Spatial Continuity** is the correlation between values over distance.

* No spatial continuity – no correlation between values over distance, random values at each location in space regardless of separation distance.

* Homogenous phenomenon have perfect spatial continuity, since all values as the same (or very similar) they are correlated.

We need a statistic to quantify spatial continuity! A convenient method is the Semivariogram.

#### The Semivariogram

Function of difference over distance.

* The expected (average) squared difference between values separated by a lag distance vector (distance and direction), $h$:

\begin{equation}
\gamma(\bf{h}) = \frac{1}{2 N(\bf{h})} \sum^{N(\bf{h})}_{\alpha=1} (z(\bf{u}_\alpha) - z(\bf{u}_\alpha + \bf{h}))^2
\end{equation}

where $z(\bf{u}_\alpha)$ and $z(\bf{u}_\alpha + \bf{h})$ are the spatial sample values at tail and head locations of the lag vector respectively.

* Calculated over a suite of lag distances to obtain a continuous function.

* the $\frac{1}{2}$ term converts a variogram into a semivariogram, but in practice the term variogram is used instead of semivariogram.
* We prefer the semivariogram because it relates directly to the covariance function, $C_x(\bf{h})$ and univariate variance, $\sigma^2_x$:

\begin{equation}
C_x(\bf{h}) = \sigma^2_x - \gamma(\bf{h})
\end{equation}

Note the correlogram is related to the covariance function as:

\begin{equation}
\rho_x(\bf{h}) = \frac{C_x(\bf{h})}{\sigma^2_x}
\end{equation}

The correlogram provides of function of the $\bf{h}-\bf{h}$ scatter plot correlation vs. lag offset $\bf{h}$.

\begin{equation}
-1.0 \le \rho_x(\bf{h}) \le 1.0
\end{equation}

#### 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.

There are examples 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.

#### Load the required libraries

The following code loads the required libraries.

```python
import os                                                   # to set current working directory
import numpy as np                                          # arrays and matrix math
import scipy                                                # hermite polynomials
import scipy.stats as st                                    # statistical methods
import pandas as pd                                         # DataFrames
import matplotlib.pyplot as plt                             # for plotting
from astropy.convolution import Gaussian1DKernel            # 1D kernel
from astropy.convolution import convolve                    # sparse data convolution
import geostatspy.geostats as geostats                      # variogram calculation
import geostatspy.GSLIB as GSLIB                            # variogram models
from ipywidgets import interactive                          # widgets and interactivity
from ipywidgets import widgets
from ipywidgets import Layout
from ipywidgets import Label
from ipywidgets import VBox, HBox
import numpy.linalg as linalg                               # for linear algebra
import scipy.spatial as sp                                  # for fast nearest neighbor search
from numba import jit                                       # for numerical speed up
import math                                                 # for trig functions etc.
```

#### Declare the required functions

All required functions have been added to the [GeostatsPy](https://pypi.org/project/geostatspy/) package.

#### 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:/PGE337")                                      # set the working directory
```

#### Loading Tabular Data and Set Up the Estimation Grid

Here's the command to load our comma delimited data file in to a Pandas' DataFrame object. I also make exhaustive estimation grids and assign the data to the grid.

```python
#df = pd.read_csv("1D_Porosity.csv")                        # read a .csv file in as a DataFrame
df = pd.read_csv(r"https://raw.githubusercontent.com/GeostatsGuy/GeoDataSets/master/1D_Porosity.csv") # load the data from Dr. Pyrcz's github repository
df.head()                                                   # preview the data
nc = 201; csiz = 10 / (nc-1); cmn = 0
depth_values = np.linspace(0.0,10.0,nc)
Npor_resid = np.zeros(len(df)); Npor_trend = np.zeros(len(df))
Npor_values = np.full(nc,np.NaN)
for idata, Npor in enumerate(df['Nporosity']):
    idepth =  min(int((df['Depth'].values[idata] - cmn) / csiz), nc - 1)
    Npor_values[idepth] = Npor
Y = np.full(len(df),cmn)
df['Y'] = Y
```

#### Interactive Trend Modeling Dashboard

Let's build an interactive display to fit a trend model and provide diagnostic plots.

* data and trend model together
* variograms of data, trend and residual
* variance components of trend, residual and covariance

```python
import warnings; warnings.simplefilter('ignore')

# dashboard: number of simulation locations and variogram parameters
style = {'description_width': 'initial'}
l = widgets.Text(value='                                              Trend Modeling, Michael Pyrcz, Associate Professor, The University of Texas at Austin',layout=Layout(width='950px', height='30px'))
nwin = widgets.IntSlider(min = 1, max = 101, value = 5, step = 2, description = 'Trend Moving Window Size in Cells',orientation='horizontal',
                          layout=Layout(width='800px', height='40px'),continuous_update = False,style = style)

uik = widgets.VBox([l,nwin],)

def f_make_trend(nwin): # function to take parameters, make sample and plot

    gauss_1D_kernel = Gaussian1DKernel(nwin)                        # calculate trend model
    trend = convolve(Npor_values, gauss_1D_kernel,fill_value=0.0)

    for idata, Npor in enumerate(df['Nporosity']):                  # calculate residuals at data
        idepth =  min(int((df['Depth'].values[idata] - cmn) / csiz), nc - 1)
        Npor_resid[idata] = df['Nporosity'].values[idata] - trend[idepth]
        Npor_trend[idata] = trend[idepth]

    df['Nporosity_resid'] = Npor_resid
    df['Nporosity_trend'] = Npor_trend

    var_trend = np.var(df['Nporosity_trend'].values); var_resid = np.var(df['Nporosity_resid'].values)
    cov_tr = np.cov(df['Nporosity_trend'].values,df['Nporosity_resid'].values)[1,0]
    var_total = var_trend + var_resid + 2* cov_tr

    ptrend = var_trend / var_total; presid = var_resid / var_total; pcov = 2*cov_tr / var_total

    plt.subplot(131)                                                # plot dataset and trend
    plt.scatter(df['Depth'],df['Nporosity'], label='Data', color = 'red', alpha = 1.0, edgecolor = 'black')
    plt.plot(depth_values,trend, label='Trend Model', color = 'black', alpha = 0.4)
    for idata, Npor in enumerate(df['Nporosity']):
        plt.plot([df['Depth'].values[idata],df['Depth'].values[idata]],[df['Nporosity_trend'].values[idata],Npor],alpha=0.4,color='blue')

    plt.title('1D Dataset and Trend Model')
    plt.xlabel('Z (m)'); plt.ylabel('NPorosity'); plt.xlim([0,10]); plt.ylim([-2.5,2.5])
    plt.legend(); plt.grid()

    plt.subplot(132)                                                # plot variograms
    nlags = 20
    lags,var,npairs = geostats.gam_1D(df['Nporosity'].values,-999,999,0.25,1,nlags,0)
    plt.scatter(lags,var,color='red',edgecolor='black',alpha=0.4,label='Data');
    trend_lags,trend_var,trend_npairs = geostats.gam_1D(trend,-999,999,csiz,1,nlags*10,0)
    plt.scatter(trend_lags,trend_var,color='black',edgecolor='black',alpha=0.4,label='Trend Model');
    resid_lags,resid_var,resid_npairs = geostats.gam_1D(df['Nporosity_resid'].values,-999,999,0.25,1,nlags,0)
    plt.scatter(resid_lags,resid_var,color='blue',edgecolor='black',alpha=0.4,label='Residual');

    plt.plot([0,1500],[1.0,1.0],color='red',linestyle = "--"); plt.xlim([0,nlags*0.25]); plt.ylim([0,2.0]); plt.xlabel('Lag Distance ($h$)'); plt.ylabel('$\gamma$')
    plt.plot([0,1500],[var_trend,var_trend],color='black',linestyle = "--"); plt.xlim([0,nlags*0.25]); plt.ylim([0,2.0]); plt.xlabel('Lag Distance ($h$)'); plt.ylabel('$\gamma$')
    plt.plot([0,1500],[var_resid,var_resid],color='blue',linestyle = "--"); plt.xlim([0,nlags*0.25]); plt.ylim([0,2.0]); plt.xlabel('Lag Distance ($h$)'); plt.ylabel('$\gamma$')
    plt.legend(loc='upper right'); plt.title('Data, Trend and Residual Variograms')

    plt.subplot(133)                                                # plot the pie chart
    labels = ['Trend','Residual', 'Covariance']
    plt.pie([ptrend, presid, pcov],radius = 1, autopct='%1.1f%%',
            colors = ['#ff9999','#66b3ff','#edf23a'], explode = [.02,.02,0.02], wedgeprops = {"edgecolor":"k",'linewidth': 1})
    plt.title('Variance Decomposition of Trend and Residual')
    plt.legend(labels,loc='lower left')

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

# connect the function to make the samples and plot to the widgets
interactive_plot = widgets.interactive_output(f_make_trend, {'nwin':nwin,})
interactive_plot.clear_output(wait = True)               # reduce flickering by delaying plot updating
```

### Interactive Trend Fitting Demostration

* select the width of the convolution window and evaluate the resulting trend model

#### 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) | [GeostatsPy](https://github.com/GeostatsGuy/GeostatsPy)

### The Inputs

Select the variogram model and the data locations:

* **Trend Moving Window Size in Cells**: the size of the Gaussian kernel for the convolution-based trend model.

```python
display(uik, interactive_plot)                            # display the interactive plot
```

    VBox(children=(Text(value='                                              Trend Modeling, Michael Pyrcz, Associ…

    Output(outputs=({'output_type': 'display_data', 'data': {'text/plain': '<Figure size 432x288 with 3 Axes>', 'i…

#### Reload the Data and Add Gaps

To explore the impact of interpolation and extrapolation in spaital estimation, let's remove data to creat gaps in data coverage.

```python
#df2 = pd.read_csv("1D_Porosity.csv")                        # read a .csv file in as a DataFrame
df2 = pd.read_csv(r"https://raw.githubusercontent.com/GeostatsGuy/GeoDataSets/master/1D_Porosity.csv") # load the data from Dr. Pyrcz's github repository
df2 = df2.drop(labels = range(3,9), axis = 0); df2 = df2.drop(labels = range(20,27), axis = 0)
df2 = df2.sample(frac = 0.7,random_state = 13)
nc = 201; csiz = 10 / (nc-1); cmn = 0

depth_values2 = np.linspace(0.0,10.0,nc)
Npor_resid2 = np.zeros(len(df2)); Npor_trend2 = np.zeros(len(df2))
Npor_values2 = np.full(nc,np.NaN)
for idata, Npor in enumerate(df2['Nporosity']):
    idepth =  min(int((df2['Depth'].values[idata] - cmn) / csiz), nc - 1)
    Npor_values2[idepth] = Npor

Y = np.full(len(df2),cmn)
df2['Y'] = Y
```

#### Interactive Spatial Estimation Without and With Trend Dashboard

Let's build an interactive display to fit a trend model and calculate spatial estimates.

```python
from IPython.utils import io
import warnings; warnings.simplefilter('ignore')

# dashboard: number of simulation locations and variogram parameters
style = {'description_width': 'initial'}
l = widgets.Text(value='                                              Trend Modeling for Spatial Estimation, Michael Pyrcz, Associate Professor, The University of Texas at Austin',layout=Layout(width='950px', height='30px'))
nwin = widgets.IntSlider(min = 1, max = 101, value = 5, step = 2, description = 'Trend Moving Window Size in Cells',orientation='horizontal',
                          layout=Layout(width='800px', height='40px'),continuous_update = False,style = style)

vrange = widgets.IntSlider(min = 1, max = 100, value = 5, step = 1, description = 'Variogram Range in Cells',orientation='horizontal',
                          layout=Layout(width='800px', height='40px'),continuous_update = False,style = style)

uika = widgets.HBox([nwin,vrange],)
uik = widgets.VBox([l,uika],)

def f_make_model(nwin,vrange): # function to take parameters, make sample and plot
    gauss_1D_kernel = Gaussian1DKernel(nwin)                        # calculate trend model
    trend2 = convolve(Npor_values, gauss_1D_kernel)
    for idata, Npor in enumerate(df2['Nporosity']):                  # calculate residuals at data
        idepth =  min(int((df2['Depth'].values[idata] - cmn) / csiz), nc - 1)
        Npor_resid2[idata] = df2['Nporosity'].values[idata] - trend2[idepth]
        Npor_trend2[idata] = trend2[idepth]

    df2['Nporosity_resid'] = Npor_resid2
    df2['Nporosity_trend'] = Npor_trend2

    vario = GSLIB.make_variogram(nug=0.0,nst=1,it1=1,cc1=1.0,azi1=0,hmaj1=vrange*csiz,hmin1=vrange*csiz)
    with io.capture_output() as captured:                          # supress function print output
        est, kvar = geostats.kb2d(df2,'Depth','Y','Nporosity_resid',-999.9,999.9,nc,cmn,csiz,1,cmn,csiz,1,1,0,10,10,0,0,vario)
    total_est = est + trend2

    with io.capture_output() as captured:                          # supress function print output
        no_trend_est, no_trend_kvar = geostats.kb2d(df2,'Depth','Y','Nporosity',-999.9,999.9,nc,cmn,csiz,1,cmn,csiz,1,1,0,10,10,0,0,vario)

    plt.subplot(121)
    plt.plot(depth_values2,no_trend_est[0], color = "Red", alpha = 0.4, label = "Estimate with Stationary Mean")
    plt.scatter(df2['Depth'],df2['Nporosity'], label='Data', color = 'red', alpha = 1.0, edgecolor = 'black')
    plt.title('Simple Kriging with Stationary Mean')
    plt.xlabel('Z (m)'); plt.ylabel('Standardized Porosity'); plt.xlim([0,10]); plt.ylim([-2.5,2.5])
    plt.legend(); plt.grid()

    plt.subplot(122)
    plt.plot(depth_values2,total_est[0],color = "red",alpha = 0.4, label = "Estimate with Trend Model")
    plt.plot(depth_values2,trend2, color = "black",alpha = 1.0,linestyle = "--", label = "Trend")
    plt.scatter(df2['Depth'],df2['Nporosity'], label='Data', color = 'red', alpha = 1.0, edgecolor = 'black')
    plt.title('Simple Kriging with Trend Model and Residual')
    plt.xlabel('Z (m)'); plt.ylabel('Standardized Porosity'); plt.xlim([0,10]); plt.ylim([-2.5,2.5])
    plt.legend(); plt.grid()

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

# connect the function to make the samples and plot to the widgets
interactive_plot2 = widgets.interactive_output(f_make_model, {'nwin':nwin,'vrange':vrange,})
interactive_plot2.clear_output(wait = True)               # reduce flickering by delaying plot updating
```

### Interactive Spatial Estimation Without and With Trend Demostration

* select the width of the convolution window and variogram range and evaluate the kriging estimates with stationary mean and trend model

#### 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) | [GeostatsPy](https://github.com/GeostatsGuy/GeostatsPy)

### The Inputs

Select the variogram model and the data locations:

* **Trend Moving Window Size in Cells**: the size of the Gaussian kernel for the convolution-based trend model.
* **Variogram Range in Cells**: the range of the spherical variogram model in grid cells.

```python
display(uik, interactive_plot2)                            # display the interactive plot
```

    VBox(children=(Text(value='                                              Trend Modeling for Spatial Estimation…

    Output(outputs=({'output_type': 'display_data', 'data': {'text/plain': '<Figure size 432x288 with 2 Axes>', 'i…

#### Comments

This was an interactive demonstration of trend modeling with spatial prediction for spatial data analytics. Much more could be done, I have other demonstrations on the basics of working with DataFrames, ndarrays, univariate statistics, plotting data, declustering, data transformations and many other workflows available at https://github.com/GeostatsGuy/PythonNumericalDemos and https://github.com/GeostatsGuy/GeostatsPy.

#### 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

#### 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

```


```python
import time
print(time.ctime())
```

    Wed Jun 21 19:42:28 2017

This notebook is an example, related to a german journal paper. Please contact the pycartan authors if you are interested in an english translation.

Bei diesem IPython-Notebook handelt es sich um eine Beispiel-Rechnung als Ergänzung zum Aufsatz "Über die Existenz einer separierten Koordinatendarstellung für unteraktuierte mechanische Systeme", eingereicht zur Veröffentlichung bei der Zeitschrift *at - Automatisierungstechnik*.

Der enthaltene Pythoncode hängt u.a. von folgenden Modulen ab:

* [`symbtools`](https://github.com/TUD-RST/symbtools)
  (Grundlegende Funktionalität zum symbolischen Rechnen in der Regelungstheorie)

* [`pycartan`](https://github.com/TUD-RST/pycartan)
  (Funktionalität zum Rechnen mit Differentialformen)

* [`displaytools`](https://github.com/cknoll/displaytools)
 (IPython-Erweiterung, welche spezielle Kommentare (z.B. `##:`) einführt um das Ergebnis einer Zuweisung anzeigen zu lassen.)

Für weitere Informationen siehe auch:
https://raw.githubusercontent.com/cknoll/beispiele/master/readme.md

---

[//]: # (custom_config_start)
$$
\newcommand{\col}{\mathrm{col}}
\newcommand{\opspan}{\operatorname{span}}
\newcommand{\bs}{\boldsymbol}
\newcommand{\mb}{\mathbf}
\newcommand{\nx}{n_{\boldsymbol x}}
\newcommand{\Ddt}{\left(\tfrac{d}{dt}\right)}
$$

[//]: # (custom_config_end)

**Betrachtetes System**: unteraktuierten Viergelenkmanipulator mit Stellkräften am Endeffektor.

**Betrachtete Frage**:
Existiert eine separierte Koordinatendarstellung (Aufspaltung in aktuierte und nicht aktuierte Koordinaten)

```python
%load_ext displaytools3

import sympy as sp
from sympy import sin, cos, pi
from sympy.interactive import printing
import symbtools as st
import symbtools.modeltools as mt
from symbtools.modeltools import Rz # Rotationsmatrix

printing.init_printing(1)
```

```python
# generrischer Code für unteraktuierte Systeme

t = sp.Symbol('t')
np = 4
nq = 0
n = np + nq
pp = st.symb_vector("p1:{0}".format(np+1))
qq = st.symb_vector("q1:{0}".format(nq+1))

aa = st.symb_vector("a1:{0}".format(nq+1))

ttheta = st.row_stack(pp, qq) ##:T
tthetad = st.time_deriv(ttheta, ttheta) ##:T
tthetadd = st.time_deriv(ttheta, ttheta, order=2) ##:T
st.make_global(ttheta, tthetad)
```

```python
params = sp.symbols('s1, s2, s3, s4, m1, m2, m3, m4, J1, J2, J3, J4, l1, l2, l3, l4, kappa')
st.make_global(params)

# Hilfsterme
#QQ = st.symb_vector("Q1, Q2, Q3, Q4")

# Kräfte am Endeffektor
#tau1, tau2 = ttau = st.symb_vector("tau1, tau2")
```

### Bestimmung von $\bs B(\bs \theta)$

```python
Rz(p1) # Rotationsmatirx veranschaulichen
```

```python

Fx, Fy = FF = st.symb_vector('F_t, F_n')

FF4 = sp.Matrix([Fx, Fy])

Q4 = l4 * FF4[1]

FF3 = Rz(p4)[:, 0]*FF4[0]
Q3 = l3 * FF3[1]

FF2 = Rz(p3)[:, 0]*FF3[0]
Q2 = l2 * FF2[1]

FF1 = Rz(p2)[:, 0]*FF2[0]
Q1 = l1 * FF1[1]

```

```python
QQ = sp.Matrix([Q1, Q2, Q3, Q4]) ##:
B = QQ.jacobian(FF) ##:T
```

```python
import pycartan as pc

w1, w2 = ww = pc.VectorDifferentialForm(1, ttheta, coeff=B.T)
```

```python
res1 = w1.d^w1^w2
res2 = w2.d^w1^w2
```

```python
c = res1.coeff[0]##:
```

```python
cs = c.simplify() ##:

```

```python
res2
```

    (0)dp1^dp2^dp3^dp4


# Data Analysis of P2P market as MAD problem
We evaluate the results of the simulation of a prosumer participating in a fully decentralized P2P market. This work is modelled as Multi-armed Bandit problem, a classic control problem that we can apply RL techniques.

This analysis starts by looking to the results presented in the Informs talk as ''Prosumer choice formulatedas Multi Armed Bandit problem - RL application''.

This notebook analyses the simulation saved on the `/results` folder of the GitHub repo ([link here])(https://github.com/tabsa/P2P_market_MAD).

```python
# Import packages
import numpy as np
import pandas as pd
import pickle as pkl
from pathlib import Path
import matplotlib.pyplot as plt
import seaborn as sns
```

Start with upload the simulations done for the Informs talk that shows the first results with this work:

```python
# Read pickle file
wok_dir = Path.cwd() / 'results/'
filename = 'sim_results_fixed_target_15_batch_improve.pkl'
path_file = wok_dir / filename
data = pkl.load(open(path_file, 'rb')) # Read file
# Plot parameters
plt_colmap = plt.get_cmap("tab10", 15)
sns.set_style("whitegrid")

```

Deploy functions for plotting. Each function will be used to plot a different result out of the simulation:

```python
## Define Plot functions

# Plot the mean reward per episode and RL_agent (You can have cumulative option as well)
def plot_reward_per_episode(df_score, y_feat, plt_label, plt_marker, size=(10,8), y_sum=False):
    # Get the plot parameters
    no_RL_agents = len(df_score) # Number of agents
    no_episodes = df_score[0].shape[0]
    plt.figure(figsize=size)
    x = np.arange(0, no_episodes) # Episode range
    for i in range(no_RL_agents):
        y = df_score[i][y_feat]
        y = np.cumsum(y) if y_sum else y # Cumulative plot
        plt.plot(x, y, label=plt_label[i], marker=plt_marker[i], linestyle='--')

# Plot the Q-action value per episode and RL_agent
def plot_Q_val_distribution(df_score, theta, plt_label, n_bins=20, size=(10,8)):
    from statsmodels.distributions.empirical_distribution import ECDF
    # Get the plot parameters
    no_RL_agents = len(df_score)
    no_episodes = df_score[0].shape[0]
    fig, axis = plt.subplots(1, 2, figsize=size, sharex=True)
    x = np.arange(0, no_episodes)
    for i in range(no_RL_agents):
        x = df_score[i][theta]
        axis[0].hist(x, density=True, bins=n_bins, label=plt_label[i])
        ecdf = ECDF(x)
        axis[1].plot(ecdf.x, ecdf.y, label=plt_label[i])
    return fig, axis

# Plot the policy per agent per episode
def plot_action_choice(policy, target, epi_id, y_lim, size=(10,8)):
    # Plot parameters
    no_steps = policy.shape[1]
    fig, axis = plt.subplots(3, 1, figsize=size, sharex=True)
    x = np.arange(0, no_steps) # Steps of the episode (0 ... N)
    y1 = policy[0,:,epi_id].astype(int) # Partner selected per x-value (n_step)
    target = target * np.ones(no_steps) # Array of energy_target for all n_steps of the episode
    # Subplot 1
    axis[0].scatter(x, y1, cmap=plt_colmap, c=y1, marker='.', alpha=1)
    axis[0].set_yticks(np.arange(0, y_lim, step=2))
    # Subplot 2
    axis[1].plot(x, policy[4, :, epi_id], color='green', label='Total reward')
    axis[1].plot(x, policy[3, :, epi_id], color='green', alpha=0.5, linestyle='--', label='Reward_per_step')
    axis[1].set_yticks(np.linspace(0, 1, 6))
    # Subplot 3
    axis[2].bar(x, policy[1, :, epi_id])
    axis[2].plot(x, policy[2, :, epi_id], color='cyan', marker='o', label='state_n')
    axis[2].plot(x, target, color='red', linestyle='--', label='E_target')
    axis[2].set_yticks(np.linspace(0, target.max(), 4))
    return fig, axis

# Plot the optimal estimator Q_arm_j
def plot_final_probability(data, reward, agents, p_j, plt_label, rd_pct=90, width=0.15, size=(10,8)):
    # Plot parameters
    no_RL_agents = len(data)
    no_offers = data[0].shape[1]
    x = np.arange(no_offers)
    # Initialize plot
    fig, ax = plt.subplots(figsize=size)
    ax.bar(x, p_j, width, label='True probability') # Always plot the True probability
    # Options selected by the user --> idx is the list of RL_agents
    k=1
    for i in agents:
        mean_rd = reward[i]['mean_rd'].to_numpy()
        reward_bound = np.percentile(mean_rd, rd_pct)
        epi_rg = np.where(mean_rd >= reward_bound)[0]
        Q_opt_arm_j = data[i][epi_rg,:,0].mean(axis=0)
        ax.bar(x + width*k, Q_opt_arm_j, width, label=plt_label[i])
        k += 1

    ax.set_xticks(x)
    return fig, ax

```

## Total reward $R_{Total}$ evolution per episode
$R^i_{Total}$ is mean reward, given by
\begin{equation}
    R^i_{Total} = \frac{1}{N_i} \sum_n R^i_n
\end{equation}

where $N_i$ is the total number of steps per episode $i$ and $R^i_n$ is the reward per step $n$ (also called reward signal). In fact, $R^i_{Total}$ is constantly updated every step $n$ of each episode $i$.

Here, we plot the $R^i_{Total}$ on the final step $n$ for all episodes with the following conditions:
 - The same Energy_target = 15 kWh for all episodes $i$.
 - Non-existing correlation, i.e. episodes are independent from each other
 - For the first 20 episodes, all startegies adopt a 'Random policy' for learning the Q-value $Q_n(j)$ of each partner $j$
 - **Goal:** Evaluate the learning process of each policy (Random, Epsilon-greedy, Thompson-Sampler). If the *Epsilon-greedy* and *Thompson-Sampler* beat **constantly** the *Random policy*
 - You can disable the cumulative plot in the function `plot_reward_per_episode` by switching the last parameter from `False` to `True`

```python
no_epi = data['simulation']['episodes']
label = data['agents']['id']
marker = ['o', '*', '^']
ax_label = ['Episode #', 'Total reward']
ax_size = 18
plot_reward_per_episode(data['outcome'], 'mean_rd', label, marker, (15,8))
# Legend and labels for the plot
plt.legend(fontsize=ax_size)
plt.ylabel(ax_label[1], fontsize=ax_size)
plt.xticks(fontsize=ax_size)
plt.yticks(fontsize=ax_size)
plt.xlabel(ax_label[0], fontsize=ax_size)
plt.title(f'Cumulative reward across {no_epi} episodes', fontsize=20)
plt.show()
```

**Takeaways:** Both strategies outperform the *Random policy*, but the *Epsilon-greedy* reached out the best results.

## Regret $G_{Total}$ per episode
Here we plot the estimated mean regret, if you remember the formulate. The regret is calculated as
\begin{equation}
    G^i_{Total} = \frac{1}{N_i} \sum_n\left[ \max(Q^i_n(j) - Q^i_n(j) \right]
\end{equation}

where $Q^i_n(j)$ is Q-value of partner/arm $j$ per step $n$. Q_n is a vector with the updated Action-value function per partner $j$, so we update it every step $n$. We use $Q_n$ because we are unaware of the **Probability Distribution**, Bernoulli $B(1, p_j)$ of each offer (partner $j$).

```python
no_epi = data['simulation']['episodes']
label = data['agents']['id']
marker = ['o', '*', '^']
ax_label = ['Episode #', 'Mean reward']
ax_size = 18
plot_reward_per_episode(data['outcome'], 'mean_regret', label, marker, (15,8))
# Legend and labels for the plot
plt.legend(fontsize=ax_size)
plt.ylabel(ax_label[1], fontsize=ax_size)
plt.xticks(fontsize=ax_size)
plt.yticks(fontsize=ax_size)
plt.xlabel(ax_label[0], fontsize=ax_size)
plt.title(f'Regret across {no_epi} episodes', fontsize=20)
plt.show()
```

**Takeaways:** The regret in both strategies is low afgter the initial learning phase. However, we have approx. 0.3 of regret which it is not ideal for our simulations.

## Empirical distribution and Histogram of the $R_{Total}$ and $G_{Total}$
Here we want to find some trend from the empirical CDF and histogram of the results for all episodes, se once again we plot the $R^i_{Total}$ and $G^i _{Total}$.

```python
# Show the histogram and CDF of Q-action value, since it is probability estimator
theta_feat = ['mean_rd', 'mean_regret']
ax_label = ['Q-action estimator', 'Regret estimator', 'Frequency', 'Cumulative Probability']
subplot_title = ['Histogram', 'CDF (quantile)']
for theta in theta_feat:
    fg, ax = plot_Q_val_distribution(data['outcome'], theta, label, size=(15,8)) # Only plotting 'Random' and 'Epsilon-greedy'
    # Legend and labels for the plot
    [ax[i].legend(fontsize=ax_size) for i in range(len(ax))]
    x_id_label = 0 if theta == theta_feat[0] else 1
    [ax[i].set_xlabel(xlabel=ax_label[x_id_label], fontsize=ax_size) for i in range(len(ax))]
    [ax[i].set_ylabel(ylabel=ax_label[i+2], fontsize=ax_size) for i in range(len(ax))]
    [ax[i].tick_params(axis='x', labelsize=ax_size) for i in range(len(ax))]
    [ax[i].tick_params(axis='y', labelsize=ax_size) for i in range(len(ax))]
    # Set title
    [ax[i].set_title(subplot_title[i], fontsize=18) for i in range(len(ax))]
    fg.suptitle(f'{theta}: Empirical distribution function across {no_epi} episodes', fontsize=24)
    plt.show()

```

## Analyse the Optimal policy $\pi^{\ast}(a_n)$
Now we dive into the optimal solution (also called policy) found by each strategy. It is only important to have a look to the policy on the last episodes, because we expect each strategy converges to its optimal in the last episodes.

You can analyse the policy by playing around with the following parameters:
 - `ag_id`: id of the RL_agent with a strategy (0: Random; 1: Epsilon-greedy; 2: Thompson-Sampler)
 - `epi_id`: episode id and please only have a look from eq epi_id = $[89 - 99]$
 - By playing with these two parameters below, you can see the performance of each strategy
 - **Remember:** Python indexes are $n-1$ and start from $0$, so our episodes start from $0$ and stop to $99$.

```python
# Result data structure
pol_sol = data['policy_sol'] # Optimal policy per agent
e_target = data['simulation']['target'] # Energy target per episode (array)
ag_label = data['agents']['id'] # Agent type (Random, Epsilon-greedy, Thompson-Sampler)
no_offers = data['simulation']['environment'].no_offers # No of offers to trade with
no_epi = data['simulation']['episodes'] # Episodes #
# Plot parameters
ag_id = 1 # Agent type (0, 1, 2...)
epi_id = 90 # Episode id
ax_label = ['Step #', 'Partner j', 'Reward','Energy (kWh)']
ax_size = 16
fig, ax = plot_action_choice(pol_sol[ag_id], e_target[epi_id], epi_id, no_offers, (12,8)) # Make the plot
# Set labels of each subplot
[ax[i].set_xlabel(xlabel=ax_label[0], fontsize=ax_size) for i in range(len(ax))]
[ax[i].set_ylabel(ylabel=ax_label[i+1], fontsize=ax_size) for i in range(len(ax))]
[ax[i].tick_params(axis='x', labelsize=ax_size) for i in range(len(ax))]
[ax[i].tick_params(axis='y', labelsize=ax_size) for i in range(len(ax))]
# Set legend of Subplots 2 and 3
[ax[i].legend(fontsize=ax_size) for i in np.arange(1, len(ax))]
# Set title
#[ax[i].set_title(subplot_title[i], fontsize=18) for i in range(len(ax))]
fig.suptitle(f'{ag_label[ag_id]}: Solution found on episode {epi_id}', fontsize=18)
plt.show()
```

## Retrieve the estimator $Q^{\ast}_j$ from the optimal policy
Please remember that $Q^{\ast}_j$ is the estimator of probability $p_j = \text{Pr}(X=1)$ from the Bernoulli distribution $B(1, p_j)$. So $p_j$ is the probability of success and over episodes $i$ the Q-value of each partner $j$ estimates the true value of $p_j$, $Q^i_j = \hat{p}_j$, where $\hat{p}_j$ is the estimator.

If we assume to play a large number of episodes $i$, with the same assumptions mentioned before, $Q^i_j$ ends up converges towards its optimal value $Q^{\ast}_j = p_j$.

Here, we compute the $Q^i_j$ for the last 10 episodes:

```python
arm_info = data['simulation']['environment'].offers_info # 2-D array with info per arm (col dim: 0-Energy_j,
                                                         # 1-price_j, 2-Probability_j)
true_p_j = arm_info[:,2] # True probability p_j
poli_distr = data['policy_dist'] # List with Q_arm_j per agent. Each list element is a 3-D array (epi x arms_j x feat)
outcome = data['outcome']
ag_id = [1, 2] # List of RL_agents to analyse
label = data['agents']['id']
# Call the plot function
fig, ax = plot_final_probability(poli_distr, outcome, ag_id, true_p_j, label, width=0.2,size=(12,8))
# Set labels and legend
ax.set_xlabel(xlabel='Partner j', fontsize=ax_size)
ax.set_ylabel(ylabel='Probability p_j', fontsize=ax_size)
ax.tick_params(axis='x', labelsize=ax_size)
ax.tick_params(axis='y', labelsize=ax_size)
ax.legend(fontsize=ax_size)
# Set title
fig.suptitle('Optimal estimator Q(arm_j) per RL_agent', fontsize=20)
plt.show()

```

```python
ag_id = 0
df_score = data['outcome']

end_step = df_score[ag_id]['final_step'].to_numpy()
ag_pol_sol = pol_sol[ag_id]
epi = data['validation']['episodes']

for e in range(epi):
    aux = ag_pol_sol[0, 0:end_step[e], e] if e == 0 else np.hstack((aux, ag_pol_sol[0, 0:end_step[e], e]))

plt.hist(aux, density=False, bins=15)
```

```python

```

```python

```


```python
%matplotlib inline
```

```python
import matplotlib.pyplot as plt
import pandas as pd               # Python data science library
#import numpy as np
#import re
#import sys

from modelclass import model
#import modelpattern as pt
#import modelmanipulation as mp    # Module for model text processing
#from modelmanipulation import explode

```

# ModelFlow, A library to manage and solve Models
Sandbjerg november 2021

Ib Hansen

Ib.hansen.iv@gmail.com

Work done at Danmarks Nationalbank, ECB and Hansen Ølkonometri.

**Problem**:

Stress-test model for banks

Complicated and slow **Excel** workbook

Difficult to maintain and change

**Solution**:

Separate specification (at a high level of abstraction) and solution code.

Python comes **batteries included**. Data management, text processing, visualization ...

**Implementation** of **minimum workable toolkit**

A **transpiler**: Takes a model in a **domain specific** Business logic language:

Create **solver** and **utility** functions using Python libraries.

**Refactor and refine**

Larger models, Faster transpiler, Newton and Gauss solvers, Logical structure, Derivatives, Visualization, Front ends ...

## Refine and refactor
To suit the needs of the different models thrown at the toolkit.

**Specify very large (or small) models** as concise and intuitive equations. 1 million equation and more can be handled.

**Agile model development** Model are specified at a high level of abstraction and are processed fast. Experiments with model specification are agile and fast.

**Onboarding models and combining from different sources**. Recycling and combining models specified in different ways: Excel, Latex, Dynare, Python or other languages.

**A rich set of analytical tools for model and result analytic** helps to understand the model and its results.

**The user can extend and modify the tools** to her or his needs. All code is in Python and the core is quite small.

## What is a Model in ModelFlow
ModelFlow is created to handle models. The term [**model**](https://en.wikipedia.org/wiki/Model) can mean a lot of different concepts.

The scope of models handled by ModelFlow is **discrete** models which is the same for each time frame, can be formulated as **mathematical equations** and *can* have **lagged** and **leaded** variables.

This allows the system to handle quite a large range of models.

A model with:

 - $\textbf n$ number of endogeneous variables
 - $\textbf k$ number of exogeneous variables
 - $\textbf u$ max lead of endogeneous variables
 - $\textbf r$ max lag of endogeneous variables
 - $\textbf s$ max lag of exogeneous variables
 - $t$ time frame (year, quarter, day second or another another unit)

can be written in two ways, **normalized** or **un-normalized** form

### Normalized model
Each endogenous variable is on the left hand side one time - and only one time.

\begin{eqnarray}
y_t^1 & = & f^1(y_{t+u}^1...,y_{t+u}^n...,y_t^2...,y_{t}^n...y_{t-r}^1...,y_{t-r}^n,x_t^1...x_{t}^k,...x_{t-s}^1...,x_{t-s}^k) \\
y_t^2 & = & f^2(y_{t+u}^1...,y_{t+u}^n...,y_t^1...,y_{t}^n...y_{t-r}^1...,y_{t-r}^n,x_t^1...x_{t}^k,...x_{t-s}^1...,x_{t-s}^k) \\
\vdots \\
y_t^n & = & f^n(y_{t+u}^1...,y_{t+u}^n...,y_t^1...,y_{t}^{n-1}...y_{t-r}^1...,y_{t-r}^n,x_t^1...x_{t}^r,x..._{t-s}^1...,x_{t-s}^k)
\end{eqnarray}

Written in matrix notation where  $\textbf{y}_t$ and $\textbf{x}_t$ are vectors of endogenous/exogenous  variables for time t

\begin{eqnarray}
\textbf{y}_t & = & \textbf{F}(\textbf{y}_{t+u} \cdots \textbf{y}_t \cdots \textbf{y}_{t-r},\textbf{x}_t \cdots \textbf{x}_{t-s})
\end{eqnarray}

ModelFlow allows variable to be scalars, matrices, arrays or pandas dataframes.

### Un-normalized form
Some models can not easy be specified as normalized formulas. Especially   models with equilibrium  conditions can more suitable be specified in the more generalized un-normalized form.

Written in matrix notation like before:

\begin{eqnarray}
\textbf{0}& = & \textbf{F}(\textbf{y}_{t+u} \cdots \textbf{y}_t \cdots \textbf{y}_{t-r},\textbf{x}_t \cdots \textbf{x}_{t-s})
\end{eqnarray}

The number of endogenous variables and equations should still be the same.

#### Model solution
For a normalized model:

\begin{eqnarray}
\textbf{y}_t & = & \textbf{F}(\textbf{y}_{t+u} \cdots \textbf{y}_t \cdots \textbf{y}_{t-r},\textbf{x}_t \cdots \textbf{x}_{t-r})
\end{eqnarray}

a solution is  $\textbf{y}_t^*$ so that:

\begin{eqnarray}
\textbf{y}_t^* & = & \textbf{F}(\textbf{y}_{t+u} \cdots \textbf{y}_t^* \cdots \textbf{y}_{t-r},\textbf{x}_t \cdots \textbf{x}_{t-r})
\end{eqnarray}

For the un-normalized model:
\begin{eqnarray}
\textbf{0}& = & \textbf{F}(\textbf{y}_{t+u} \cdots \textbf{y}_t \cdots \textbf{y}_{t-r},\textbf{x}_t \cdots \textbf{x}_{t-s})
\end{eqnarray}

a solution $\textbf{y}_t^*$ is

\begin{eqnarray}
\textbf{0} & = & \textbf{G}(\textbf{y}_{t+u} \cdots \textbf{y}_t^* \cdots \textbf{y}_{t-r},\textbf{x}_t \cdots \textbf{x}_{t-r})
\end{eqnarray}

Some models can have more than one solution. In this case the solution can depend on the starting point of the solution algorithm.

## Model derivatives

Both for solving and for analyzing the causal structure of a model it can be useful to define different matrices of derivatives for a model $\textbf F()$ like this:

\begin{align}
\textbf{A}_t & = & \frac{\partial \textbf{F}}{\partial \textbf{y}_t^T}  \hphantom{\hspace{5 mm} i=1, \cdots , r}
 &\hspace{1 mm}\mbox{Derivatives with respect to current endogeneous variables} \\ \\
\textbf{E}_t^i & = & \frac{\partial \textbf{F}}{\partial \textbf{y}_{t-i}^T } \hspace{5 mm} i=1, \cdots , r  &\hspace{1 mm}\mbox{  Derivatives with respect to lagged endogeneous variables  } \\  \\
\textbf{D}_t^j & = & \frac{\partial \textbf{F}}{\partial \textbf{y}_{t+j}^T } \hspace{5 mm} j=1, \cdots , u  &\hspace{1 mm}\mbox{  Derivatives with respect to leaded endogeneous variables  } \\  \\
\textbf{F}_t^k & = & \frac{\partial \textbf{F}}{\partial \textbf{x}_{t-i} ^T} \hspace{5 mm} k=0, \cdots , s  &\hspace{1 mm}\mbox{  Derivatives with respect to current and lagged exogeneous variables  }\\
\end{align}

For un-normalized models the derivative matrices are just the dervatives of $\textbf G$ instead of $\textbf F$

## Model solutions

There are numerous methods to solve models (systems) as mentioned above. ModelFlow can apply 3 different types of model solution methods:

 1. If the model has **no contemporaneous feedback**, the equations can be sorted
 [Topological](https://en.wikipedia.org/wiki/Topological_sorting) and then the equations can be calculated in the topological order. This is the same as a spreadsheet would do.
 2. If the model has **contemporaneous feedback** model is solved with an iterative method. Here variants of well known solution methods are used:
     1. [Gauss-Seidle](https://en.wikipedia.org/wiki/Gauss%E2%80%93Seidel_method) (**Gauss**) which can handle large systems, is fairly robust and don't need the calculation of derivatives
     2. [Newthon-Raphson](https://en.wikipedia.org/wiki/Newton%27s_method) (**Newton**) which requires the calculation of derivatives and solving of a large linear system but typically converges in fewer iterations.

Nearly all of the models solved by ModelFlow don't contain leaded endogenous variables. Therefor they can be solved one period at a time. For large sparse nonlinear models Gauss works fine. It solves a model quite fast and we don't need the additional handiwork of handling derivatives and large linear systems that Newton methods require. Moreover many models in question do not have smooth derivatives. The order in which the equation are calculated can have a large impact on the convergence speed.

For some models the Newton algorithm works better. Some models are not able to converge with Gauss-Seidle other models are just faster using Newton. Also the ordering of equations does not matter for the convergence speed.

However some models like FRB/US and other with **rational expectations** or **model consistent expectations** contains leaded endogenous variables. Such models typical has to be solved as one system for for all projection periods. In this case, the Gauss variation [Fair-Taylor](https://fairmodel.econ.yale.edu/rayfair/pdf/1983A.PDF) or Stacked-Newton Method.  The **stacked Newton** methods can be used in all cases, but if not needed, it will usually use more memory and be slower.

| Model  | No contemporaneous feedback | Contemporaneous feedback | Leaded variables |
| --- | --- | --- |--- |
|Normalized | Calculate | Gauss or
 Newton  | Fair Taylor or
 Stacked Newton|
|Un-normalized | Newton | Newton | Stacked Newton |

# Implementation of solving algorithms in Python
Solving a model entails a number of steps:

1. Specification of the model
2. Create a dependency graph.
2. Establish a solve order and separate the the model into smaller sub-models
2. Create a python function which can evaluating $f_i(y_1^{k},\cdots,y_{i-1}^{k},y_{i+1}^{k-1},\cdots,y_{n}^{k-1},z)$
3. If needed, create a python function which can evaluate the Jacobimatrices: $\bf{A,E,D}$  or $\bf{\bar A,\bar E,\bar D}$
3. Apply a solve function using the elements above to the data.

### Normalized model

#### Calculation, No contemporaneous feedback

In systems with no lags each period can be solved in succession
The equations has to be evaluated in a logical (topological sorted) order.

Let $z$ be all predetermined values: all exogenous variable and lagged endogenous variable.

Order the $n$ endogeneous variables in topological order.

For each time period we can find a solution by

for $i$ = 1 to $n$

>$y_{i}^{k} = f_i(y_1^{k},\cdots,y_{i-1}^{k},y_{i+1}^{k-1},\cdots,y_{n}^{k-1},z)$

#### The Gauss-Seidel algorithm. Normalized  models with contemporaneous feedback
The Gauss-Seidel algorithm is quite straight forward. It basically iterate over the formulas, until convergence.

let:

$z$ be all predetermined values: all exogenous variable and lagged endogenous variable.

$n$ be the number of endogenous variables.

$\alpha$ dampening factor which can be applyed to selected equations

For each time period we can find a solution by doing Gauss-Seidel iterations:

for $k = 1$ to convergence

>for $i$ = 1 to $n$
>
>>$y_{i}^{k} = (1-\alpha) * y_{i}^{{k-1}} + \alpha f_i(y_1^{k},\cdots,y_{i-1}^{k},y_{i+1}^{k-1},\cdots,y_{n}^{k-1},z)$

#### The Newton-Raphson algorithme. Models with contemporaneous feedback

Let $\bf z$ be a vector all predetermined values: all exogenous variable and lagged endogenous variable.

For each time period we can find a solution by doing Newton-Raphson iterations:

for $k = 1$ to convergence

>$\bf y = \bf {F(y^{k-1},z) }$
>
>$\bf y^{k} =  \bf y - \alpha \times  \bf{(A-I)}^{-1} \times ( \bf {y - y^{k-1} })$

The expression: $\bf{(A-I)}^{-1}\times  ( \bf {y - y^{k-1} })$ is the same as the solution to:

$\bf {y- y^{k-1} } = \bf (A-I) \times \bf x$

This problem can be solved much more efficient than performing $\bf{(A-I)}^{-1}\times  ( \bf {y - y^{k-1} })$

The Scipy library provides a number of solvers to this linear set of equations. There are both solvers using  factorization and iterative methods, and there are solvers for dense and sparce matrices. All linear solvers  can easily be incorporated into ModelFlows Newton-Raphson nonlinear solver.

#### Stacked Newton-Raphson all  periods in one go. Models with both leaded and lagged endogeneous variable

If the model has leaded endogenous variables it can in general not be solved one time period at a time. We have to solve the model for all time frames as one large model.

$$\bf{\bar A} =\begin{bmatrix}
		\bf{A_1}   & \bf{D_1^1} & \bf{D_1^2} & \bf{0}     &\bf{0}      &\bf{0}      &\bf{0}      &\bf{0}  \\
        \bf{E_2^1} & \bf{A_2}   & \bf{D_2^1} & \bf{D_2^2} &\bf{0}      &\bf{0}      &\bf{0}      &\bf{0} \\
        \bf{E_3^2} & \bf{E_3^1} & \bf{A_3}   & \bf{D_3^1} & \bf{D_3^2} &\bf{0}      &\bf{0}      &\bf{0} \\
        \bf{E_4^3} & \bf{E_4^2} & \bf{E_4^1} & \bf{A_4}   & \bf{D_4^1} & \bf{D_4^2} &\bf{0}      & \bf{0} \\
        \bf{0}     & \bf{E_5^3} & \bf{E_5^2} & \bf{E_5^1} & \bf{A_5}   & \bf{D_5^1} & \bf{D_5^2} &\bf{0}\\
        \bf{0}     & \bf{0}     & \bf{E_6^3} & \bf{E_6^2} & \bf{E_6^1} & \bf{A_6}   & \bf{D_6^1} & \bf{D_6^2}\\
        \bf{0}     & \bf{0}     & \bf{0}     & \bf{E_7^3} & \bf{E_7^2} & \bf{E_7^1} & \bf{A_7}   & \bf{D_7^1} \\
        \bf{0}     & \bf{0}     & \bf{0}     & \bf{0}     & \bf{E_8^3} & \bf{E_8^2} & \bf{E_8^1} & \bf{A_8} \\
\end{bmatrix} \bar y = \begin{bmatrix}\bf{y_1}\\\bf{y_2}\\\bf{y_3}\\ \bf{y_4} \\\bf{y_5} \\\bf{y_6} \\ \bf{y_7} \\ \bf{y_8} \end{bmatrix} \bar F = \begin{bmatrix}\bf{F}\\\bf{F}\\\bf{F}\\ \bf{F} \\\bf{F} \\\bf{F} \\ \bf{F} \\ \bf{F} \end{bmatrix}$$

Now the solution algorithme looks like this.

Again let $\bf z$ be a vector all predetermined values: all exogenous variable and lagged endogenous variable.

>for $k = 1$ to convergence

>>$\bf{\bar y} = \bf {\bar F(\bar y^{k-1},\bar z) }$

>>$\bf {\bar y^{k}} =  \bf{\bar y} - \alpha \times \bf{(\bar A-I)}^{-1}\times ( \bf {\bar y - \bar y^{k-1} })$

Notice that the model $\bf F$ is the same for all time periods.

However, as time can be an exogenous variable the result of $\bf{F}$ can depend on time. This allows
us to specify terminal conditions.

The update frequency of $\bf{\bar A}$ and $\alpha$ and the value of $\alpha$ can be set to manage the speed and stability of the algorithm.

We solve the problem: $$( \bf {\bar y - \bar y^{k-1} }) = \bf{(\bar A-I)}\times \bf x $$ instead of inverting  $\bf{A}$.

Python gives access to very efficient sparse libraries. The [Scipy library](https://scipy.org/scipylib/index.html) utilizes the [Intel® Math Kernel Library](https://software.intel.com/en-us/mkl). Any of the available routines for solving linear systems can easily be incorporated.

## Create a model instance which calculates the Jacobi matrices.
The derivatives of all formulas with respect to all endogenous variables are needed.

First step is to specifying a model in the business logic language which calculate all the non-zero elements

In ModelFlow this can be done by **symbolic**, by **numerical differentiation** or by a combination.

The formula for calculating  $\dfrac{\partial{numerator }}{{\partial denominator(-lag)}}$ is written as:

\< numerator  \>\_\_p\_\_\< denominator >\_\_lag\_\_\< lag\> = derivative expression

Just another instance of a ModelFlow model class.

### A small Solow model to show the construction of the Jacobi matrix.
An example can be helpful

First a small model is defined - in this case a solow growth model:

```python
fsolow = '''\
Y         = a * k**alfa * l **(1-alfa)
C         = (1-SAVING_RATIO)  * Y
I         = Y - C
diff(K)   = I-depreciates_rate * K(-1)
diff(l)   = labor_growth * L(-1)
K_i= K/L '''
msolow = model.from_eq(fsolow)
```

```python
print(msolow.equations)
```

    FRML <> Y         = A * K**ALFA * L **(1-ALFA)  $
    FRML <> C         = (1-SAVING_RATIO)  * Y  $
    FRML <> I         = Y - C  $
    FRML <> K=K(-1)+(I-DEPRECIATES_RATE * K(-1))$
    FRML <> L=L(-1)+(LABOR_GROWTH * L(-1))$
    FRML <> K_I= K/L  $

### Create some data and solve the model

```python
N = 100
df = pd.DataFrame({'L':[100]*N,'K':[100]*N})
df.loc[:,'ALFA'] = 0.5
df.loc[:,'A'] = 1.
df.loc[:,'DEPRECIATES_RATE'] = 0.05
df.loc[:,'LABOR_GROWTH'] = 0.01
df.loc[:,'SAVING_RATIO'] = 0.05
msolow(df,max_iterations=100,first_test=10,silent=1);
```

### Create an differentiation instance of the model
Use symbolic differentiation when possible else use numerical differentiation.

```python
from modelnewton import newton_diff
msolow.smpl(3,5);  # we only want a few years
```

### Symbolic differentiation

```python
newton = newton_diff(msolow)
print(newton.diff_model.equations)
```

    FRML  <> C__p__Y___lag___0 = 1-SAVING_RATIO   $
    FRML  <> I__p__C___lag___0 = -1   $
    FRML  <> I__p__Y___lag___0 = 1   $
    FRML  <> K__p__I___lag___0 = 1   $
    FRML  <> K__p__K___lag___1 = 1-DEPRECIATES_RATE   $
    FRML  <> K_I__p__K___lag___0 = 1/L   $
    FRML  <> K_I__p__L___lag___0 = -K/L**2   $
    FRML  <> L__p__L___lag___1 = LABOR_GROWTH+1   $
    FRML  <> Y__p__K___lag___0 = A*ALFA*K**ALFA*L**(1-ALFA)/K   $
    FRML  <> Y__p__L___lag___0 = A*K**ALFA*L**(1-ALFA)*(1-ALFA)/L   $

### Numerical differentiation

```python
newton2 = newton_diff(msolow,forcenum=1)
print(newton2.diff_model.equations)
```

    FRML  <> C__p__Y___lag___0 = (((1-SAVING_RATIO)*(Y+0.0025))-((1-SAVING_RATIO)*(Y-0.0025)))/0.005   $
    FRML  <> I__p__C___lag___0 = ((Y-(C+0.0025))-(Y-(C-0.0025)))/0.005   $
    FRML  <> I__p__Y___lag___0 = (((Y+0.0025)-C)-((Y-0.0025)-C))/0.005   $
    FRML  <> K__p__I___lag___0 = ((K(-1)+((I+0.0025)-DEPRECIATES_RATE*K(-1)))-(K(-1)+((I-0.0025)-DEPRECIATES_RATE*K(-1))))/0.005   $
    FRML  <> K__p__K___lag___1 = (((K(-1)+0.0025)+(I-DEPRECIATES_RATE*(K(-1)+0.0025)))-((K(-1)-0.0025)+(I-DEPRECIATES_RATE*(K(-1)-0.0025))))/0.005   $
    FRML  <> K_I__p__K___lag___0 = (((K+0.0025)/L)-((K-0.0025)/L))/0.005   $
    FRML  <> K_I__p__L___lag___0 = ((K/(L+0.0025))-(K/(L-0.0025)))/0.005   $
    FRML  <> L__p__L___lag___1 = (((L(-1)+0.0025)+(LABOR_GROWTH*(L(-1)+0.0025)))-((L(-1)-0.0025)+(LABOR_GROWTH*(L(-1)-0.0025))))/0.005   $
    FRML  <> Y__p__K___lag___0 = ((A*(K+0.0025)**ALFA*L**(1-ALFA))-(A*(K-0.0025)**ALFA*L**(1-ALFA)))/0.005   $
    FRML  <> Y__p__L___lag___0 = ((A*K**ALFA*(L+0.0025)**(1-ALFA))-(A*K**ALFA*(L-0.0025)**(1-ALFA)))/0.005   $

### Display the full stacked matrix
To make the sparcity clear all zero values are shown as blank

```python
stacked_df = newton.get_diff_df_tot()
stacked_df.applymap(lambda x:f'{x:,.2f}' if x != 0.0 else ' ')
```

      per
      3
      4
      5

      var
      C
      I
      K
      K_I
      L
      Y
      C
      I
      K
      K_I
      L
      Y
      C
      I
      K
      K_I
      L
      Y

      per
      var

      3
      C
      -1.00

      0.95

      I
      -1.00
      -1.00

      1.00

      K

      1.00
      -1.00

      K_I

      0.01
      -1.00
      -0.01

      L

      -1.00

      Y

      0.51

      0.49
      -1.00

      4
      C

      -1.00

      0.95

      I

      -1.00
      -1.00

      1.00

      K

      0.95

      1.00
      -1.00

      K_I

      0.01
      -1.00
      -0.01

      L

      1.01

      -1.00

      Y

      0.51

      0.49
      -1.00

      5
      C

      -1.00

      0.95

      I

      -1.00
      -1.00

      1.00

      K

      0.95

      1.00
      -1.00

      K_I

      0.01
      -1.00
      -0.01

      L

      1.01

      -1.00

      Y

      0.51

      0.49
      -1.00

## Speeding up solving through Just In Time compilation (Numba)
Python is an interpreted language. So slow

**Numba** is a Just In time Compiler.

Experience with a Danish model (1700 equations) shows a speedup from 5 million floating point
operations per second (MFlops) to 800 MFlops. But compilation takes time.

Also experiments with the **Cython** library has been performed. This library will translate the Python code to C++ code. Then a C++ compiler can compile the code and the run time will be improved a lot.

Also matrices can be used. This will force the use of the highly optimized routines in the
Numpy library.

## Specification of model in Business Logic Language
The Business logic Language is a Python like language, where each function $f_i$ from above is specified as:

```
FRML <options> <left hand side> = <right hand side> $ ...
```

The ```<left hand side>``` should not contain transformations, but can be a tuple which match the ```<right hand side>```. A  \$ separates each formular.

Time is implicit, so $var_t$ is written as ```var```, while $var_{t-1}$ is written as ```var(-1)``` and $var_{t+1}$ is written as ```var(+1)```. Case does not matter. everything is eventual made into upper case.

It is important to be able to create short and expressive models, therefor. Stress test models should be able to handle many bank and sectors without repeating text. So on top of the **Business logic language**. there is a **Macro Business Logic language**. The primary goal of this is to allow (conditional) looping and normalization of formulas.

The user can specify any conforming python function on the right hand side

# Onboarding a model
**Python has incredible strong tools both for interacting with other systems like Excel and Matlab. Some of the sources, from which models has been recycled are:

- Latex
    - Model written in Latex - with some rules to allow text processing.
- Eviews
- Excel
    - Calculation model from Excel workbook
    - Grabbing coefficients from excel workbooks
- Matlab
    - Wrapping matlab models into python functions, which can be used in ModelFlow
    - Grabbing coefficients from matlab .mat files.
- Aremos models
- TSP models

Grabbing models and transforming them to Business logic language usually requires a tailor-made Python program. However in the ModelFlow folder there are different examples of such grabbing.

## The model structure

The logical structure of a model is useful for several reasons.

The structure of **contemporaneous endogenous variable** is used to establish the calculation sequence and identify simultaneous systems (strong graphcomponents).

The structure of a model can be seen as a directed graph. All variables are node in the graph.
If a variable $b$ is on the right side of the formula defining variable $a$ there is an edge from $b$ to $a$.

### First we define the nodes (vertices) of the dependency graph.

The set of nodes is the set of relevant variables. Actually we want to look
at **two dependency graphs**: one containing *all variables*,
and one only containing *endogenous contemporaneous variable* (the $y^j_t$'s). So we define two sets S and E:

**All endogenous, exogenous, contemporaneous and lagged variables**

$S=\{y^j_{t-i}|j=1..n,i=1..r   \} \cup \{x^j_{t-i}|j=1..k,i=1..s   \}$

**Contemporaneous endogenous variables**

$E=\{y^j_{t}|j=1..n    \}$

Naturally:
$E \subseteq S$

### Then we define the edges of the dependency graph.
Again two sets are relevant:

**From all variables to contemporaneous endogenous variables**

$T = \{(a,b) | a \in E, b \in S\}$ a is on the right side of b

### And we can construct useful dependency graphs
The we can define a graph TG which defines the data dependency of the model:

$TG = (S,T)$ The graph defined by nodes S and edges T.

TG can be used when exploring the dependencies in the model. This is useful for the user when drilling down the results.

However for preparing the solution a smaller graph has to be used. When solving the model for a specific period both exogenous and lagged endogenous variables are predetermined. Therefor we define the the dependency graph for contemporaneous endogenous variables:

$TE = (E,T_e)$ The graph defined by nodes $S$ and edges $T_e$.

TE is used to determine if the model is simultaneous or not.

If the model is not simultaneous, then TE have no cycles, that is, it is a
Directed Acyclical Graph (DAG). Then we can find an order in which the formulas can
be calculated. This is called a topological order.

The topological order is a linear ordering of nodes (vertices) such that for every edge (v,u), node v comes before u in the ordering.

A topological order is created by doing a topological sort of TE.

If TE, the dependency graph associated with F is **not** a Directed Acyclical Graph (A DAG).
Then F has contemporaneous feedback and is simultaneous. Or - in Excel speak - the
model has circular references. And we need to use an iterative methods to solve the model. Sometime a model contains several simultaneous blocks. Then each block is a strong element of the graph. Each formula which is not part of a simultaneous bloc is in itself a strong element.

A condensed graph where each strong element is condensed to a node is a DAG.
So the condensed graph have a topological order. This can be used when solving the model.

The dependency graphs are constructed, analyzed and manipulated through the **Networkx** Python library.

####  Total dependency graphs
This shows $TG$ mentioned above.

```python
msolow.drawmodel(title='Total graph',all=0)
```

### The dependency graph for contemporaneous endogenous variables (TE)

```python
msolow.drawendo(title='Contemporaneous endo')
```

#### And the adjacency matrix of the graph
The graph can also be represented as a adjacency matrix. This is a is a square matrix A. $A_{i,j}$  is one when there is an edge from node i to node j, and zero when there is no edge.

If the graph is a DAG the adjacency matrix, and the elements are in a topological order, is a lover triangular matrix.

```python
a = msolow.plotadjacency(size=(8,8))
```

## Solution ordering

### For normalized models:
For a model **without contemporaneous feedback**, the topoligical sorted order is then used as calculating order.

For a model **with contemporaneous feedback** and no leaded variables, ModelFlow divides a model into three parts. A recursive **prolog** model, a recursive **epilog** model, the rest is the simultaneous **core** model. Inside the core model the ordering of the equations are preserved.  It may be that the core model contains several strong componens, which each could be solved as a simultanous system, however it is solved as one simultanous system.

Only the core model is solved as a simultaneous system. The prolog model is calculated once before the solving og the simultaneous system, the epilog model is calculated once after the solution of the simultanous system.
For most models this significantly reduce the computational burden of solving the model.

For a model with leaded variables where the model is stacked. All  equations are created equal.

```python
# The preorder
print(f'The prolog variables {msolow.preorder}')
print(f'The core   variables {msolow.coreorder}')
print(f'The epilog variables {msolow.epiorder}')
```

    The prolog variables ['L']
    The core   variables ['Y', 'C', 'I', 'K']
    The epilog variables ['K_I']

# Some Model manipulation capabilities

## Model inversion aka Target/instruments or Goal Seek
In ordet to answer questions like:

- How much capital has to be injected in order to maintain a certain GDP level in a stressed scenario?
- How much loans has to be shredded by the banks in order to maintain a minimum level of capital (slim to fit)?
- How much capital has to be injected in order to keep all bank above a certain capital threshold ?
- What probability of transmission result in infected 2 weeks later

The model instance is capable to **"invert"** a model. To use the terminology of Tinbergen(1955) that is to calculate the value of some exogenous variables - **the instruments** which is required in order to achieve a certain target value for some endogenous variables - **the targets**.

To use the terminology of Excel it is a goal/seek functionality with multiple cells as goals and multiple cells as targets.

The problem can be thought as follows: From the generic description of a model: $\textbf{y}_t= \textbf{F}(\textbf{x}_{t})$. Here $\textbf{x}_{t}$ are all predetermined variables - lagged endogenous and all exogenous variables.

It can be useful to allow a **delay**, when finding the instruments. In this case we want to look at  $\textbf{y}_t= \textbf{F}(\textbf{x}_{t-delay})$

Think of a condensed model ($\textbf{G}$) with a few endogenous variables($\bar{\textbf{y}}_t$):
the targets and a few exogenous variables($\bar{\textbf{x}}_{t-delay}$): the instrument variables. All the rest of the predetermined variables are fixed:
$\bar{\textbf{y}}_t= \textbf{G}(\bar{\textbf{x}}_{t-delay})$

If we invert G we have a model where  instruments are functions of targets:
$\bar{\textbf{x}_{t-delay}}= \textbf{G}^{-1}(\bar{\textbf{y}_{t}})$. Then all we have to do is to find
$\textbf{G}^{-1}(\bar{\textbf{y}_{t}})$

The approximated Jacobi matrix of $\textbf{G}$ :
$\textbf{J}_t \approx \frac{\Delta \textbf{G} }{\Delta \bar{\textbf{x}}_{t-delay}}$ is used to find the instruments

### And how to solve for the instruments
For most models $\bar{\textbf{x}}_{t-delay}= \textbf{G}^{-1}(\bar{\textbf{y}_{t}})$ do not have a nice close form solution. However it can be solved numerically. We  turn to Newton–Raphson method.

So $\bar{\textbf{x}}_{t-delay}= \textbf{G}^{-1}(\bar{\textbf{y}_{t}^*})$ will be found using :

for $k$ = 1 to convergence

>$\bar{\textbf{x}}_{t-delay,end}^k= \bar{\textbf{x}}_{t-delay,end}^{k-1}+ \textbf{J}^{-1}_t \times (\bar{\textbf{y}_{t}^*}-
\bar{\textbf{y}_{t}}^{k-1})$

>$\bar{\textbf{y}}_t^{k}= \textbf{G}(\bar{\textbf{x}}_{t-delay}^{k})$

convergence: $\mid\bar{\textbf{y}_{t}^*}-
\bar{\textbf{y}_{t}}
\mid\leq \epsilon$

Now we just need to find:

$\textbf{J}_t = \frac{\partial \textbf{G} }{\partial \bar{\textbf{x}}_{t-delay}}$

A number of differentiation methods can be used (symbolic, automated or numerical). ModelFlow uses numerical differentiation, as it is quite simple and fast.

$\textbf{J}_t \approx \frac{\Delta \textbf{G} }{\Delta \bar{\textbf{x}}_{t-delay}}$

That means that we should run the model one time for each instrument, and record the
effect on each of the targets, then we have $\textbf{J}_t$

In order for $\textbf{J}_t$ to be invertible there has to be the same number of targets and instruments.

However, each instrument can be a basket of exogenous variable. They will be adjusted in fixed proportions. This can be useful for instance when using bank leverage as instruments. Then the leverage instrument can consist of several loan types.

You will notice that the level of $\bar{\textbf{x}}$ is updated (by  $\textbf{J}^{-1}_t \times (\bar{\textbf{y}_{t}^*}-
\bar{\textbf{y}_{t}}^{k-1})$) in all periods from $t-delay$ to $end$, where $end$ is the last timeframe in the dataframe. This is useful for many applications including calibration of disease spreading models and in economic models, where the instruments are level variable (i.e. not change variables). If this is not suitable, it can be changed in a future release.

The target/instrument functionality is implemented in the python class `targets_instruments` specified in **ModelFlows** `modelinvert` module.

### An example
The workflow is as follow:

1.  Define the targets
2.  Define the instruments
3.  Create a target_instrument class istance
4. Solve the problem

Step one is to define the targets. This is done by creating a dataframe where the target values are set.

```python
msolow.basedf
```

      L
      K
      ALFA
      A
      DEPRECIATES_RATE
      LABOR_GROWTH
      SAVING_RATIO
      Y
      C
      I
      K_I

      0
      100.000000
      100.000000
      0.5
      1.0
      0.05
      0.01
      0.05
      0.000000
      0.000000
      0.000000
      0.000000

      1
      101.000000
      100.025580
      0.5
      1.0
      0.05
      0.01
      0.05
      100.511609
      95.486029
      5.025580
      0.990352

      2
      102.010000
      100.076226
      0.5
      1.0
      0.05
      0.01
      0.05
      101.038487
      95.986562
      5.051924
      0.981043

      3
      103.030100
      100.151443
      0.5
      1.0
      0.05
      0.01
      0.05
      101.580575
      96.501546
      5.079029
      0.972060

      4
      104.060401
      100.250762
      0.5
      1.0
      0.05
      0.01
      0.05
      102.137821
      97.030930
      5.106891
      0.963390

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

      95
      257.353755
      185.913822
      0.5
      1.0
      0.05
      0.01
      0.05
      218.736417
      207.799596
      10.936821
      0.722406

      96
      259.927293
      187.661027
      0.5
      1.0
      0.05
      0.01
      0.05
      220.857924
      209.815028
      11.042896
      0.721975

      97
      262.526565
      189.428077
      0.5
      1.0
      0.05
      0.01
      0.05
      223.002024
      211.851922
      11.150101
      0.721558

      98
      265.151831
      191.215119
      0.5
      1.0
      0.05
      0.01
      0.05
      225.168912
      213.910466
      11.258446
      0.721153

      99
      267.803349
      193.022302
      0.5
      1.0
      0.05
      0.01
      0.05
      227.358789
      215.990850
      11.367939
      0.720761

100 rows × 11 columns

**Define Targets**

```python
target = msolow.basedf.loc[50:,['L','K']]+[30,10]
target.head()
```

      L
      K

      50
      194.463182
      135.971544

      51
      196.107814
      136.933236

      52
      197.768892
      137.911105

      53
      199.446581
      138.905161

      54
      201.141047
      139.915414

Then we have to provide the instruments. This is **a list of list of tuples**.
-  Each element in the outer list is an instrument.
-  Each element in the inner list is an instrument variable
-  Each element of the tuple contains a variable name and the associated impulse $\Delta$.

The $\Delta variable$ is used in the numerical differentiation. Also if one instrument contains several variables, the proportion of each variable will be determined by the relative $\Delta  variable$.

For this experiment the inner list only contains one variable.

**Define Instruments**

```python
instruments = [ [('LABOR_GROWTH',0.001)] , [('DEPRECIATES_RATE',0.001)]]
```

**Run the experiment**

For models which are relative linear we don't need to update  𝐉𝑡  for each iteration and time frame. As our small toy model is nonlinear, the jacobi matrix has to be updated frequently. This is controlled by the nonlin=True option below.

```python
result = msolow.invert(msolow.lastdf,target,instruments,nonlin=True)
```

    Finding instruments :   0%|          | 0/50

And do the result match the target?

```python
(result-target).loc[50:,['L','K']].plot();
```

So we got results for the target variable very close to the target values.

```python
msolow.smpl(90,100)  # we only want a few years

msolow.basedf.Y
```

    0       0.000000
    1     100.511609
    2     101.038487
    3     101.580575
    4     102.137821
             ...
    95    218.736417
    96    220.857924
    97    223.002024
    98    225.168912
    99    227.358789
    Name: Y, Length: 100, dtype: float64

```python
msolow.lastdf.Y
```

    0       0.000000
    1     100.511609
    2     101.038487
    3     101.580575
    4     102.137821
             ...
    95    237.269232
    96    239.389730
    97    241.532869
    98    243.698846
    99    245.887858
    Name: Y, Length: 100, dtype: float64

### Shortfall targets
Above the target for each target variable is a certain values. Sometime we we need targets being above a certain shortfall value. In this case an instrument should be used to make the achieve the target threshold only if the target is belove the target. This is activated by an option:**shortfall=True**.

This feature can be useful calculating the amount of deleverage needed for banks to achieve a certain threshold of capital.

## Attribution / Explanation

Experience shows that it is useful to be able to explain the difference between the result from two runs.
The first level of understanding the difference is to look at selected formulas and find out, how much each input variables accounts for.
The second level of understanding the difference is to look at the attribution of the
exogenous variables to the results of the model.

If we have:

$y = f(a,b)$

and we have two solutions where the variables differs by $\Delta y, \Delta a, \Delta b$

How much of $\Delta y$ can be explained by $\Delta a$ and $\Delta b$ ?

Analytical the attributions $\Omega a$ and $\Omega b$ can  be calculated like this:

$\Delta y = \underbrace{\Delta a \dfrac{\partial {f}}{\partial{a}}(a,b)}_{\Omega a} +
\underbrace{\Delta b \dfrac{\partial {f}}{\partial{b}}(a,b)}_{\Omega b}+Residual$

ModelFlow will do a numerical approximation of $\Omega a$ and $\Omega b$. This is done by looking at the two runs of the model:

\begin{eqnarray}
y_0&=&f(a_{0},b_{0}) \\
y_1&=&f(a_0+\Delta a,b_{0}+ \Delta b)
\end{eqnarray}

So $\Omega a$ and $\Omega b$ can be determined:

\begin{eqnarray}
\Omega f_a&=&f(a_1,b_1 )-f(a_1-\Delta a,b_1) \\
\Omega f_b&=&f(a_1,b_1 )-f(a_1,b_1-\Delta  b)
\end{eqnarray}

And:

\begin{eqnarray}
residual = \Omega f_a + \Omega f_b -(y_1 - y_0)
\end{eqnarray}
If the model is fairly linear, the residual will be small.

### Formula attribution
Attribution analysis on the formula level is performed by the method **.dekomp**.

### Model Attribution

At the model level we start by finding which exogenous variables have changed between two runs.

## Python functions can be incorporated
### A mean variance problem

If we look at a fairly general mean variance optimization problem which has been adopted to banks it looks like this:

\begin{eqnarray}
\mathbf x & &\mbox{Position in each asset(+)/liability(-) type}\\
\mathbf x & &\mbox{Position in each asset(+)/liability(-) type}\\
\mathbf \Sigma & &\mbox{Covariance matrix} \\
\mathbf r & &\mbox{Return vector}\\
\lambda & &\mbox{Risk aversion}\\
\mathbf{riskweights}& &\text{Vector of risk weights, liabilities has riskweight = 0}\\
Capital& &\mbox{Max of sum of risk weighted assets}\\
\mathbf{lcrweights}& &\text{Vector of LCR weights, liabilities has lcrweight = 0}\\
LCR& &\text{Min of sum of lcr weighted assets}\\
\mathbf{leverageweight}&&\text{Vector of leverage weights, liabilities has leverageweight = 0}\\
Equity&&\mbox{Max sum of leverage weighted positions}\\
Budget&&\mbox{initial sum of the positions}\\
\end{eqnarray}

\begin{eqnarray}
\mbox{minimize:}  & \lambda \mathbf x^T \mathbf \Sigma \mathbf x - (1-\lambda) \mathbf r^T \mathbf x & \mbox{If }\lambda \mbox{ = 1 minimize risk, if } \lambda\mbox{ = 0 maximize return }\\
\mbox{subject to:} & \mathbf x \succeq \mathbf{x^{min}} &\mbox{Minimum positions}\\
                   & \mathbf x \preceq \mathbf{x^{max}} &\mbox{Maximum positions}\\
                   & \mathbf{riskweights}^T\mathbf x \leq Capital &\mbox{Risk weighted assets <= capital}\\
                   & \mathbf{lcrweights}^T\mathbf x \geq LCR &\mbox{lcr weighted assets >= LCR target}\\
                   & \mathbf{leverageweight}^T\mathbf x \leq equity &\mbox{leverage weighted assets <= equity}\\
                   & \mathbf 1^T\mathbf x = Budget & \mbox{Sum of positions = B}
\end{eqnarray}

### The mean variance problem in the business language language

Wrap optimizing in the CVX library into a Python function:
In the business logic language this problem can be specified like this:

```
positions =  mv_opt(msigma,return,riskaversion, budget,
            [[risk_weights] , [-lcr_weights] , [leverage_weights]],
              [capital, -lcr , equity] ,min_position,max_position)
```

Where the arguments are appropriately dimensioned CVX matrices and vectors.

For a more elaborate example there is an special notebook on the subject of optimization.

Also it should be mentioned that there is an expansion of the basic problem taking transaction cost into account.

## Stability
Jacobi matrices can be used to evaluate the stability properties of the model. To do this we first look at a linearized version of the model. We are interested in the effect of shocks to the system. Will shocks be
damped or will they be amplified.

## Live models
**Showtime**

# Summary
ModelFlow allows easy implementation of models in Python, Which is a powerful and agile language. ModelFlow leverage on the rich ecosystem of Python in order to:

-  Separates the specification of a model and the code which solves the model. So the user can concentrate on the economic and not the implementation of the model.
-  Can include user specified Python function in the model definition.
-  Can solve very large m,odels
-  Can solve simultaneous models.
-  Keeps tab on the dependencies of the formulas. This allows for easy Tracing of results.
-  Can perform model inversion (goal seek) with multiple targets and instruments
-  Can attribute changes in results to input variables. Both for individual formulas and the complete model
-  Can include optimizing behavior

The purpose of this notebook has been to give a broad introduction to model management using ModelFlow.
Using the tool requires some knowledge of python. The required knowledge depends on the complexity of the model. So ModelFlow can be used in Python training.

To get more in-depth knowledge there is a Sphinx based documentation of the library. There you can find the
calling conventions and documentation of all elements.

All suggestions and recommendations are welcome

# Literature:

Aho, Lam, Sethi, Ullman (2006), Compilers: Principles, Techniques, and
Tools (2nd Edition), Addison-Wesley

Berndsen, Ron (1995), [Causal ordering in economic
models](https://kundoc.com/pdf-causal-ordering-in-economic-models-.html),
Decision Support Systems 15 (1995) 157-165

Danmarks Nationalbank (2004), [MONA -- a quarterly model of Danish
economy](http://www.nationalbanken.dk/da/publikationer/Documents/2003/11/mona_web.pdf)

Denning, Peter J. (2006), [The Locality
Principle](http://denninginstitute.com/pjd/PUBS/locality_2006.pdf),
Chapter in *Communication Networks and Systems* (J Barria, Ed.).
Imperial College Press

Gilli, Manfred (1992), [Causal Ordering and Beyond, International
Economic Review, Vol. 33, No. 4 (Nov., 1992), pp.
957-971](http://www.jstor.org/stable/2527152?seq=1#page_scan_tab_contents)

McKinney, Wes (2011),[pandas: a Foundational Python Library for Data Analysis and
Statistics,] Presented at
PyHPC2011](http://www.scribd.com/doc/71048089/pandas-a-Foundational-Python-Library-for-Data-Analysis-and-Statistics)

Numba (2015) documentation,
[http://numba.pydata.org/numba-doc/0.20.0/user/index.html](http://numba.pydata.org/numba-doc/0.20.0/user/index.html%20)

Pauletto, G. (1997), [Computational Solution of Large-Scale
Macroeconometric
Models](http://link.springer.com/book/10.1007%2F978-1-4757-2631-2),
ISBN 9781441947789

E. Petersen, Christian & A. Sims, Christopher. (1987). Computer Simulation of Large-Scale Econometric Models: Project Link. International Journal of High Performance Computing Applications (http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.853.6387&rep=rep1&type=pdf)

Tinberger, Jan (1956), Economic policy: Principles and Design,
Amsterdam,

# Footnotes
$^1$: The author has been able to draw on experience creating software
    for solving the macroeconomic models ADAM in Hansen Econometric.

$^2$: In this work a number of staff in Danmarks Nationalbank made
    significant contributions: Jens Boldt and Jacob Ejsing to the
    program. Rasmus Tommerup and Lindis Oma by being the first to
    implement a stress test model in the system.

$^3$: In ECB Marco Gross, Mathias Sydow and many other collegues has been of great help.

$^4$: The system has benefited from discussions with participants in meetings at: IMF, Bank of Japan, Bank of England, FED Board, Oxford University, Banque de France, Single Resolution Board

$^5$: Ast stands for: [Abstract Syntax
    Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree)

$^6$: Re stands for[: Regular
    expression](https://en.wikipedia.org/wiki/Regular_expression)

```python

```