# Notebook contents:

This notebook contains a lecture. The code for generating plots are found at the of the notebook. Links below.

- [presentation](#Session-1b:)
- [code for plots](#Code-for-plots)

# Session 11:
## Machine learning introduction

*Andreas Bjerre-Nielsen*

## Taking stock

*What have we learned until now?*
- Fundamental data types, functions, containes, loops
- Pandas: DataFrame, Series - these contain basic datatypes
    - A lot of powerful tools in methods/functions
    - E.g. groupby, join/merge
- Scraping: API, HTML, and a lot more

## Some coding advice

- *How do I extract an object from my function?*
    - Print or return?
- Solving complex problems:
    - Solve one thing at a time, start with the most essential that you can do now
- Is joining datasets difficult?
    - Check out [the pandas documentation on merging](https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html)
    - Or [the guide available from Jake Van der Plas](https://jakevdp.github.io/PythonDataScienceHandbook/03.07-merge-and-join.html)

```python
def my_fct(a):
    b = a + 1
    return (b)

c = my_fct(2)
print(c)
```

    3

## Agenda

1. [Math and stats review](#Math-review)
1. [Why machine learing](#Why-machine-learning)
1. [What is machine learning](#Machine-learning-overview)
1. Classification models
    1. [the perceptron](#The-perceptron-model)
    1. [beyond the perceptron](#Beyond-the-perceptron)

## Math review

Vector: 1-d dimensional array of numbers
\begin{align}\boldsymbol{x}=[x_0,x_1,x_2,..]\end{align}

Matrix: 2-d dimensional array of numbers
\begin{eqnarray}\boldsymbol{X}=[
[&x_{00}&,x_{01}&,x_{02}&,..&],\\
[&x_{10}&,x_{11}&,x_{12}&,..&],\\
[&x_{20}&,x_{21}&,x_{22}&,..&],\\
[&...   &,...   &,...   &,..&]]
\end{eqnarray}

## Function fitting
*What does (supervised) machine learning do?*

Suppose we have some data $y$ we want to model/predict from input $x$.

The aim is to find a function $f$ such that the distance between actual values $y$ and predicted values $f(x)$ are minimized.

*What are some examples?*

- Linear form: $y=x\beta$.
- Logistic form: $y=g(x\beta)$

where $x^T\beta=\beta_0+x_1\beta_1+x_2\beta_2+...+x_n\beta_n$ (vector dot product)

# Why machine learning

## Value of modelling
*Why are models useful?*

Models are pursued with differens aims. Suppose we have a linear model, $y=x\beta+\epsilon$.

- Social science:
    - They teach us something about the world.
    - We want to estimate $\hat{\beta}$ and distribution
- Data science:
    - To make optimal future decisions and precise predictions, i.e. $\hat{y}$.

## Model fragility (1)
*What is a polynomial regression?*

- Fitting a curve with an *n-dimenstional polynomial*
- Can fit any "regular" curve ~ Taylor Series Approximation.

## Model fragility (2)
*Suppose we build models of the size of the Danish population, how do polynomial fits perform?*
- We estimate model with data from 1769-1975.

```python
f_pop1
```

## Model fragility (3)
*Which model performs best when we extend the forecasting period from 1975 to now?*

```python
f_pop2
```

## Model fragility (4)
*What happens if we extend the prediction period until 2050? See the fifth order.*

```python
f_pop3
```

## Model fragility (5)
*What trade off do we face in modelling?*

- Making a model that is too simple and does not capture enough of data (`underfitting`)
- Making a model with great fit on estimation data, but poor out-of-sample prediction (`overfitting`)

The goal of machine learning is to find models that minimize these two problems simultaneously.

## Learning ML

- During lectures copy code for see what it does - ***listen*** to me. Write own notes.
- After lecture > understand code details
- Learn with your group - VERY IMPORTANT!

# Machine learning overview

## Machine learning outline for this course

ML: short for machine learning

- Problems: ***supervised*** vs unsupervised
- Linear supervised ML models
    - classification and regression
    - regularization
    - **getting hands dirty with implementing solver**
- Fundamental concepts of ML
    - overfitting, underfitting, model validation
    - model selection and hyperparameters
- Emphasize differences and synergies between ML and statistics
- Brief intro of non-linear models

## What is machine learning
*Can you define machine learning, i.e. ML?*

- Supervised learning
  - Models designed to infer a relationship between input and **labeled** data.
  - We define the `target` as labels in data we wish to model.
      - Example: population as a function of year.
- Unsupervised learning
  - Find patterns and relationships from **unlabeled** data.
  - This may involve clustering, dimensionality reduction and more.
  - Not part of the course.

## Why machine learning
*How might this be useful for social scientists?*

Supervised machine learning is important (elaborated in Lecture 14):
- Improve estimation by validating models (not only theory)
- We can generate new data (impute missing)
- Better predictive models
- Use in hybrid models that leverage machine learning for causal estimation
    - (e.g. causal forest, neural instrumentation)

## Supervised ML problems
*How can we categorize a supervised ML model?*

Suppose we have model $y=g(X\beta)$

We distinguish by type of the `target` variable `y`:
- **regression**: predict a numeric value
- **classification**: distinguish between target categories (non-numeric data)

## Supervised ML problems (2)
*Which one is classification, which one is regression?*

```python
f_identify_question
```

## Supervised ML problems (3)

```python
f_identify_answer
```

## Regression models
*What are examples of regressions models?*

- Example of targets: income, life expectancy, education length (years)

*What is the underlying data of the target, $y$?*

- target is `continuous`

## Classification models
*What are examples of classification models?*

Target, `y`, are categories
- Examples of target: kind of education (linguistics, math), mode of transportation
    - sometimes known as `factor` in statistics
- (work for `str`, `bool`,  `int`, `float` which are then interpreted as categories)

## Example of supervised ML
*Classification or regression?*

We load the titanic data. We select variables and make dummy variables from categorical. We split into target and features.

Target is: ...?

```python
import numpy as np
import pandas as pd
import seaborn as sns

titanic = sns.load_dataset('titanic')
cols = ['survived','class', 'sex', 'sibsp', 'age', 'alone']
titanic_sub = pd.get_dummies(titanic[cols].dropna(), drop_first=True).astype(np.int64)

X = titanic_sub.drop('survived', axis=1)
y = titanic_sub.survived
```

## Definitions

ML lingo and econometric equivalents (in italic)

- `feature` vector, $\textbf{x}_i$, i.e a row of input variables
  - = explanatory *variables* in econometrics
- `weight` vector, $\textbf{w}$, i.e model parameters
  - = *coefficients* in econometrics where denoted $\beta$
- `bias` term, $w_0$, i.e. the model intercept
  - = the *constant* variable in denoted $\beta_0$

# The perceptron model

## The articifial neuron

A real neuron maps stimulus (input) to output.

[Research estimates](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5063692) there are 55–70 billion neurons in the brain.

## The articifial neuron (2)
We are interested in making a decision rule that takes arbitrary input and outputs either positive or negative.

Mathematically we define this map as $\phi: \mathbb{R}^p\rightarrow\{-1, 1\}$.

\begin{align}
\phi(z_i)=\begin{cases}
\hfill1, & z_i>0\\
-1, & z_i\le0
\end{cases}
\end{align}

- `net-input`, $z_i = \underset{~vector\,form}{\underbrace{\boldsymbol{w}^{T}\boldsymbol{x}_i}} = \underset{~expanded\,form}{\underbrace{1\cdot w_0+w_1x_{i,1}+...+w_kx_{i,k}}}$

- `unit step function`, $\phi$, checks if value exceeds threshold

## The articifial neuron (3)
Quiz: what are the input dimensions of the neuron, what is the output dimension?

- Input is the p-dimensional space, $\mathbb{R}^p$.
- Output is binary, either $-1$ or $1$.

## The articifial neuron (4)
*The unit step function (left) and the decision boundary (right)*

## The articifial neuron (5)
*When does the articial neuron work?*

If the two target types are linearly separable:

## The perceptron learning rule (1)
*How do we estimate the model parameters?*

1. initialize the weight with small random number
1. for each training observation, i=1,..,n
  1. compute predicted target, $\hat{y}_i$
  1. update weights $\hat{w}$

## The perceptron learning rule (2)
*How do we compute the predicted target $\hat{y}$?*

We apply a transformation on the net-input :
- single observation, expanded notation:
\begin{align*}
\hat{y}_i= \phi(z_i),\quad z_i=w_0+w_1x_{i,1}+...+w_kx_{i,k}
\end{align*}

- single observation, vector notation:
\begin{align*}
\hat{y}_i= \phi(z_i),\quad z_i=\boldsymbol{w}^{T}\boldsymbol{x}_i
\end{align*}

- multiple observations, matrix notation:
\begin{align*}
\hat{\boldsymbol{y}}= & \phi(\boldsymbol{z}),\quad\boldsymbol{z}=\boldsymbol{X}\boldsymbol{w}
\end{align*}

## The perceptron learning rule (3)
*How do we update weights?*

Weights are updated as follows:
\begin{align*}
w&=w+\Delta w\\
\Delta w&=\eta\cdot(y_i-\phi(z_i))\cdot \textbf{x}_{i}\end{align*}

where $\eta$ is the learning rate, and the first order derivative is:

$$\frac{\partial SSE}{\partial w}=- \textbf{X}^T\textbf{e}$$

## The perceptron learning rule (4)

The computation process

## Implementation in Python (1)
*Let's set some values of input and output*

```python
X = np.random.normal(size=(3, 2)) # feature matrix
y = np.array([1, -1, 1]) # target vector
w = np.random.normal(size=(3)) # weight vector
print('X:\n',X)
print('y:',y)
print('w:',w)
```

    X:
     [[ 1.72431863 -2.03686152]
     [ 1.28450109 -0.40514301]
     [-0.61794548  0.80683709]]
    y: [ 1 -1  1]
    w: [1.26991801 0.53081782 1.16030887]

## Implementation in Python (2)
*How do we compute the errors vectorized?*

```python
# compute net-input
z = w[0] + X.dot(w[1:]) # (w[0]: bias, w[1:]: other weights, X: features)

# unit step-function
positive = z>0 # compute prediction (boolean)
y_hat = np.where(positive, 1, -1)  # convert prediction

# compute errors
e = y - y_hat # compute errors
SSE = e.T.dot(e)
```

## Implementation in Python (3)
*How do we compute the updated weights?*

```python
# learning rate
eta = 0.001

# update weights
update_vars = eta*X.T.dot(e)
update_bias = eta*e.sum()/2
```

## Working with the perceptron (1)
We load the iris data.

```python
iris = sns.load_dataset('iris').iloc[:100] # drop virginica

X = iris.iloc[:, [0, 2]].values # keep petal_length and sepal_length
y = np.where(iris.species=='setosa', 1, -1) # convert to 1, -1

sns.scatterplot(iris.sepal_length, iris.petal_length, hue=iris.species)
```

## Working with the perceptron (2)
*How do we fit the perceptron model?* [perceptron definition](#Code-from-Raschka-2017)

```python
# initialize the perceptron
clf = Perceptron(n_iter=10)
# clf: short for classifier (classification model),
# n_iter: number of times to run through all observations

# fit the perceptron (estimate the model)
# runs 10 iterations of updating the model
clf.fit(X,y)
```

    <ch02.Perceptron at 0x241692d76a0>

## Working with the perceptron (3)
*How can we evaluate the model??*

```python
print('Number of errors: %i' % sum(clf.predict(X)!=y))

# we plot the decisions
plot_decision_regions(X,y,clf)
```

## Working with the perceptron (4)
*How does the model performance change??*

```python
f,ax = plt.subplots(figsize=(12, 4))
ax.set_xticks(range(11))
ax.plot(range(1, len(clf.errors_) + 1), clf.errors_, marker='o')
ax.set_xlabel('Number of iterations')
ax.set_ylabel('Number of errors')
```

# Model validation

## Model validation
*How can we see how our model generalizes?*

We can simulate out-of-sample prediction. How?

- Idea: Use some of our sample for model evaluation.
- Implementation - divide data randomly into two subsets:
    - `training data` for estimation;
    - `test data` for evaluation.
- Note: does not work for time series.

## Model validation (2)
We revert to titanic, `y`: survived, `X`: everything else

```python
print(titanic_sub.head(3))
```

       survived  sibsp  age  alone  class_Second  class_Third  sex_male
    0         0      1   22      0             0            1         1
    1         1      1   38      0             0            0         0
    2         1      0   26      1             0            1         0

We split the data into test and training samples

```python
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.5, random_state=0)
```

# Beyond the perceptron

## Motivation
*What might we change about the perceptron?*

1. Change from updating errors that are binary to continuous
2. Use more than one observation a time for updating

## The activation function (1)
*What else might we use to update errors?*

- The most simple is **no transformation** of the net-input, i.e. $\phi(z_i)=z_i$.

- When we change this from perceptron we call it Adaptive Linear Neuron (**Adaline**).

## The activation function (2)
*How is this different from the Perceptron?*

## The activation function (3)
*Which activation functions can be used?*

- Linear
- Logistic (Sigmoid)
- Unit step, sign

See page 450 in Python for Machine Learning.

## The activation function (4)
*How do Adaline and Logistic regression differ?*

## A new objective (1)
*The update rule in perceptron seems ad hoc, is there a more general way?*

- Yes, we minimize the sum of squared errors (SSE). The SSE for Adaline is:
\begin{align}SSE&=\boldsymbol{e}^{T}\boldsymbol{e}=e_1^2+..+e_n^2\\\boldsymbol{e}&=\textbf{y}-\textbf{X}\textbf{w}\end{align}

*Doesn't the above look strangely familiar?*

- Yes, it is the same objective as OLS. The difference:
    - OLS computes the exact solution with system of equations from first order conditions.
    - We make an approximate solution.

## A new objective (2)
*So how the hell do we make the approximate solution?*

- Two general classes:
  -  We approximate the first order derivative ~ gradient descent (GD)
  -  We approximate both first and second order derivative ~ quasi Newton

- We take gradient descent - much simpler (often faster)

## A new objective (3)
*How does a gradient descent look?*

An algorithm that finds the direction where expected differences are largest. Attempt of satisfying first order condition (FOC).

## A new objective (4)
*What is the first order derivative of SSE wrt. weights in Adaline?*

\begin{align}\frac{\partial SSE}{\partial w}=\textbf{X}^T\textbf{e},\end{align}

*How do we update with GD in Adaline?*

  - Idea: take small steps to approximate the solution.

  - $\Delta w=\eta\textbf{X}^T\textbf{e}=\eta\cdot\textbf{X}^T(\textbf{y}-\hat{\textbf{y}})$

## A new objective (5)
The gradient descent algorithm we just learned uses the whole data.

- Often known as batch gradient descent.

*What might be a smart way of changing (batch) gradient descent?*

- We only use a subset of the data. Two variants:
    - *stochastic gradient descent* (SGD): uses random subset of observations
    - *mini batch*: uses deterministic subset of observations (loop whole dataset)

- Idea: we converge faster by computing update for subset of data
    - Note: we may need a million repetitions.

## Applying logistic regression
*How difficult is it to use `LogisticRegression`?*

Very easy:

```python
from sklearn.linear_model import LogisticRegression

# estimate model on train data, evaluate on test data
clf = LogisticRegression() # note try default values
# solver='lbfgs'
clf.fit(X_train, y_train) # model training
y_hat = clf.predict(X_test)
accuracy = (y_hat==y_test).mean() # model testing
print('Model accuracy is:', np.round(accuracy,3))
```

    Model accuracy is: 1.0

# The end
[Return to agenda](#Agenda)

# Code for plots

```python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import requests
import seaborn as sns

plt.style.use('ggplot')
%matplotlib inline

SMALL_SIZE = 16
MEDIUM_SIZE = 18
BIGGER_SIZE = 20

plt.rc('font', size=SMALL_SIZE)          # controls default text sizes
plt.rc('axes', titlesize=SMALL_SIZE)     # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE)    # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_SIZE)    # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE)    # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE)    # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE)  # fontsize of the figure title

plt.rcParams['figure.figsize'] = 10, 4 # set default size of plots
```

### Population plots

```python
%run pop_plots.ipynb
```

### Plots of ML types

```python
%run ../ML_plots.ipynb
```

### Plots from book

```python
import requests
import os
base_url = 'https://raw.githubusercontent.com/rasbt/python-machine-learning-book-2nd-edition/master/code/ch02/'

for filename in ('ch02.py', 'iris.data', 'iris.names.txt'):
    if not os.path.exists(filename):
        response = requests.get(base_url+filename)
        with open(filename,'wb') as f:
            f.write(response.text.encode('utf-8'))

from ch02 import Perceptron, AdalineGD, AdalineSGD, plot_decision_regions
```


```python
#remove cell visibility
from IPython.display import HTML
tag = HTML('''
Toggle cell visibility <a href="javascript:code_toggle()">here</a>.''')
display(tag)
```

Toggle cell visibility here.

## Krmiljenje horizontalnega pomika lunarne sonde

Ta primer prikazuje načrtovanje regulatorja za lateralno pozicijo lunarne sonde, izhajajoč iz dinamičnih enačb sistema.

Sistem je predstavljen na zgornji sliki; navpični spust sistema je upočasnjen z vertikalnim potiskom, ki deluje na sistem s konstantno silo $F$. Horizontalno gibanje dosežemo z rahlim nagibom sonde za kot $\theta$; nagib povzroči lateralno silo, ki je približno enaka $F\theta$. Nagib dosežemo z navorom $T$, ki ga povzročajo vodljive pogonske rakete (maksimalni navor znaša 500 Nm). Nagibni kot more biti znotraj intervala vrednosti $\pm15$, da se prepreči nevarno povečanje hitrosti navpičnega spusta. Merjeni veličini sta lateralni pomik in hitrost, zračni upor zanemarimo, ostali sistemski parametri pa so prikazani v spodnji tabeli:

| Parameter |                       Vrednost |
|-----------|-------------------------------:|
|$m$        |                        1000 kg |
|$J$        |           1000 kg$\text{m}^2$ |
|$F$        |                         1500 N |

Cilj krmilnega sistema je regulacija horizontalnega pomika $z$ ob izpolnjevanju naslednjih zahtev:
1. Maksimalni prenihaj znaša 30%.
2. Čas ustalitve krajši od 15 s (dosežena vrednost izhoda naj se razlikuje od tiste v stacionarnem stanju za 5%).
3. Kot $\theta$ naj bo ves čas znotraj omenjenega intervala vrednosti, ki zagotavlja maksimalno lateralne spremembo v velikost 10 m.
4. Brez odstopka v stacionarnem stanju v odzivu na koračno funkcijo.

Enačbi sistema sta:

\begin{cases}
J\ddot{\theta}=T \\
m\ddot{z}=F\theta
\end{cases}
in ob upoštevanju vektorja stanj $\textbf{x}=[x_1,x_2,x_3,x_4]^T=[z,\dot{z},\theta,\dot{\theta}]^T$ ter vhoda $u=T$, ju lahko preoblikujemo v obliko prostora stanj:

\begin{cases}
\dot{\textbf{x}}=\underbrace{\begin{bmatrix}0&1&0&0 \\ 0&0&F/m&0 \\ 0&0&0&1 \\ 0&0&0&0\end{bmatrix}}_{A}\textbf{x}+\underbrace{\begin{bmatrix}0\\0\\0\\1/J\end{bmatrix}}_{B}u \\ \\
\textbf{y}=\underbrace{\begin{bmatrix}1&0&0&0 \\ 0&1&0&0\end{bmatrix}}_{C}\textbf{x}.
\end{cases}

### Načrtovanje krmilnika
Za dosego ničelnega odstopka v stacionarnem stanju, sistem razširimo z novo spremenljivko stanj $\dot{x_5}=y_1-y_d$, v kateri $y_1$ predstavlja izmerjeno lateralen pomik, $y_d$ pa zahtevan pomik. Razširjen sistem lahko torej zapišemo kot:

\begin{cases}
\dot{\textbf{x}_a}=\underbrace{\begin{bmatrix}0&1&0&0&0 \\ 0&0&F/m&0&0 \\ 0&0&0&1&0 \\ 0&0&0&0&0 \\ 1&0&0&0&0 \end{bmatrix}}_{A_a}\textbf{x}_a+\underbrace{\begin{bmatrix} 0&0\\0&0\\0&0\\1/J&0\\0&-1 \end{bmatrix}}_{B_a}\underbrace{\begin{bmatrix} u\\y_d \end{bmatrix}}_{u_a} \\ \\
\textbf{y}_a=\underbrace{\begin{bmatrix}1&0&0&0&0\\0&1&0&0&0\\0&0&0&0&1\end{bmatrix}}_{C_a}\textbf{x}_a
\end{cases}

Sistem je vodljiv glede na prvi stolpec matrike $B_a$, zato lahko uporabimo metodo razporejanja polov. Z namenom ohranitve spoznavnosti sistema, je v matriki $C$ dodana vrstica, ker je novo stanje $x_5$ znano.

Matrika ojačanj $K_a$, ki ustreza vsem zahtevam je:
$$
K_a=\begin{bmatrix}2225.0&6244.0&13861.0&5275.0&316.0\end{bmatrix}
$$
Poli matrike $(A_a-B_aK_a)$ so tako razporejeni v $-0.28$, $-2.24+2.23i$, $-2.24-2.23i$, $-0.26+0.32i$ in $-0.26-0.32i$.

### Načrtovanje spoznavalnika
Sistem je spoznaven, in ker merimo tri spremenljivke stanj sistema, lahko načrtujemo poenostavljen spoznavalnik stanj (za $\theta$ in $\dot{\theta}$), ki ima naslednjo strukturo:
$$
\dot{\hat{\textbf{v}}}=(A_{11}+L_aA_{21})\hat{\textbf{v}}+(A_{12}+L_aA_{22}-A_{11}L_a-L_aA_{21}L_a)\textbf{y}_a+(B_1+L_aB_2)u_a,
$$
pri čemer velja
$$
T^{-1}A_aT=\begin{bmatrix}A_{11}&A_{12} \\ A_{21}&A_{22}\end{bmatrix},
\quad T^{-1}B_a=\begin{bmatrix}B_1 \\ B_2\end{bmatrix},
\quad \overline{\textbf{x}_a}=T^{-1}\textbf{x}_a=\begin{bmatrix}V \\ C\end{bmatrix}\textbf{x}_a,
\quad V=\begin{bmatrix}0&0&1&0&0 \\ 0&0&0&1&0\end{bmatrix},
\quad \hat{\textbf{x}_a}=\begin{bmatrix}\hat{\textbf{v}}-L_a\textbf{y}_a \\ \textbf{y}_a\end{bmatrix}.
$$

Izbiro lastnih vrednosti spoznavalnika izvedemo tako, da dinamika napake ocene stanj konvergira hitreje kot pa dinamika sistema, določena z zgornjimi zahtevami. Lastni vrednosti matrike $A_{11}+L_aA_{21}$ sta $\lambda_i=-10$rad/s, $i=1,2$ z $$ L_a=\begin{bmatrix}0&-\frac{40}{3}&0 \\ 0&-\frac{200}{3}&0\end{bmatrix} $$

### Kako upravljati s tem interaktivnim primerom?
Opazuj delovanje sistema z načrtovanim regulatorjem in direktno spreminjaj krmilnik in spoznavalnik. Simulacija se vedno začne z začetno napako spoznavalnika.

```python
#Preparatory Cell

%matplotlib notebook
import control as ctrl
import numpy
import sympy as sym
from IPython.display import display, Markdown
import ipywidgets as widgets
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib.patches as patches
import matplotlib.transforms as transforms
import matplotlib.lines as lines

#print a matrix latex-like
def bmatrix(a):
     """Returns a LaTeX bmatrix - by Damir Arbula (ICCT project)

     :a: numpy array
     :returns: LaTeX bmatrix as a string
     """
     if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
     lines = str(a).replace('[', '').replace(']', '').splitlines()
     rv = [r'\begin{bmatrix}']
     rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
     rv +=  [r'\end{bmatrix}']
     return '\n'.join(rv)

# Display formatted matrix:
def vmatrix(a):
    if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
    lines = str(a).replace('[', '').replace(']', '').splitlines()
    rv = [r'\begin{vmatrix}']
    rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
    rv +=  [r'\end{vmatrix}']
    return '\n'.join(rv)

#matrixWidget is a matrix looking widget built with a VBox of HBox(es) that returns a numPy array as value !
class matrixWidget(widgets.VBox):
    def updateM(self,change):
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.M_[irow,icol] = self.children[irow].children[icol].value
                #print(self.M_[irow,icol])
        self.value = self.M_

    def dummychangecallback(self,change):
        pass

    def __init__(self,n,m):
        self.n = n
        self.m = m
        self.M_ = numpy.matrix(numpy.zeros((self.n,self.m)))
        self.value = self.M_
        widgets.VBox.__init__(self,
                             children = [
                                 widgets.HBox(children =
                                              [widgets.FloatText(value=0.0, layout=widgets.Layout(width='90px')) for i in range(m)]
                                             )
                                 for j in range(n)
                             ])

        #fill in widgets and tell interact to call updateM each time a children changes value
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].value = self.M_[irow,icol]
                self.children[irow].children[icol].observe(self.updateM, names='value')
        #value = Unicode('example@example.com', help="The email value.").tag(sync=True)
        self.observe(self.updateM, names='value', type= 'All')

    def setM(self, newM):
        #disable callbacks, change values, and reenable
        self.unobserve(self.updateM, names='value', type= 'All')
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].unobserve(self.updateM, names='value')
        self.M_ = newM
        self.value = self.M_
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].value = self.M_[irow,icol]
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].observe(self.updateM, names='value')
        self.observe(self.updateM, names='value', type= 'All')

                #self.children[irow].children[icol].observe(self.updateM, names='value')

#overlaod class for state space systems that DO NOT remove "useless" states (what "professor" of automatic control would do this?)
class sss(ctrl.StateSpace):
    def __init__(self,*args):
        #call base class init constructor
        ctrl.StateSpace.__init__(self,*args)
    #disable function below in base class
    def _remove_useless_states(self):
        pass
```

```python
# Define matrixes

A = numpy.matrix('0 1 0 0; 0 0 1.5 0; 0 0 0 1; 0 0 0 0')
B = numpy.matrix('0;0;0;0.001')
C = numpy.matrix('1 0 0 0; 0 1 0 0')
Aa = numpy.matrix('0 1 0 0 0; 0 0 1.5 0 0; 0 0 0 1 0; 0 0 0 0 0; 1 0 0 0 0')
Ba = numpy.matrix('0 0;0 0;0 0;0.001 0;0 -1')
Ca = numpy.matrix('1 0 0 0 0; 0 1 0 0 0; 0 0 0 0 1')
Ka1 = numpy.matrix('[2225.0, 6244.0, 13861.0, 5275.0, 316.0') #318.9333 835 2012.5 2000 59.6
Ta = (numpy.matrix('0 0 1 0 0; 0 0 0 1 0; 1 0 0 0 0;0 1 0 0 0; 0 0 0 0 1'))**(-1)
Ar = Ta**(-1)*Aa*Ta
Br = Ta**(-1)*Ba
A11 = Ar[0:2,0:2]
A12 = Ar[0:2,2:5]
A21 = Ar[2:5,0:2]
A22 = Ar[2:5,2:5]
B1 = Br[0:2,:]
B2 = Br[2:5,:]
La1 = numpy.matrix([[0, -4*10/3, 0],[0, -3/8*(-4*10/3)**2, 0]])
X0a = numpy.matrix('0;0;0;0;0;0;0;0;0;0;0.002;0.002;0;0;0;0;0;0.002;0.002;0;0;0;0;0')
# X0a = numpy.matrix('0;0;0;0;0')
# V0 = numpy.matrix('0;0')
```

```python
# Define matrixes widget
Kaw = matrixWidget(1,5)
Law = matrixWidget(2,3)
eig1 = matrixWidget(1,1)
eig2 = matrixWidget(2,1)
eig3 = matrixWidget(2,1)
eig4 = matrixWidget(1,1)
eig5 = matrixWidget(1,1)
eig1o = matrixWidget(1,1)
eig2o = matrixWidget(2,1)

Ydw = widgets.FloatSlider(
                         value=10,
                         min=0,
                         max=10.0,
                         step=0.1,
                         description='$y_d$:',
                         disabled=False,
                         continuous_update=False,
                         orientation='horizontal',
                         readout=True,
                         readout_format='.1f',
                        )

# Init matrix widgets
Kaw.setM(Ka1)
Law.setM(La1)
#[-0.6,-0.5-0.35j,-0.5+0.35j,-0.2-0.6j,-0.2+0.6j]
eig1.setM(numpy.matrix([-0.28]))
eig2.setM(numpy.matrix([[-2.24],[-2.23]]))
eig3.setM(numpy.matrix([[-0.26],[-0.32]]))
eig4.setM(numpy.matrix([-1]))
eig5.setM(numpy.matrix([-1]))
eig1o.setM(numpy.matrix([-10]))
eig2o.setM(numpy.matrix([[-10],[0]]))
```

```python
# Support functions
# Simulation function
def simulation(Aa, Baa, Ca, A11, A12, A21, A22, B1, B2, La, Ka, Ta):
    Aa, Baa, Ca = sym.Matrix(Aa), sym.Matrix(Baa), sym.Matrix(Ca)
    A11, A12, A21, A22 = sym.Matrix(A11), sym.Matrix(A12), sym.Matrix(A21), sym.Matrix(A22)
    B1, B2 = sym.Matrix(B1), sym.Matrix(B2)
    La, Ka = sym.Matrix(La), sym.Matrix(Ka)
    Ta = sym.Matrix(Ta)
    sysS = sss(Aa, Baa, Ca, sym.zeros(3,2))
    sysX = sss(Aa, Baa, sym.eye(5), sym.zeros(5,2))
    sysO1 = sss((A11+La*A21), (B1+La*B2).row_join(A12+La*A22-A11*La-La*A21*La), sym.eye(2), sym.zeros(2,5))
    sysO2 = ctrl.append(sysO1, sysS)
    sysO3 = ctrl.connect(sysO2, [[3, 3], [4, 4], [5, 5]], [1, 2, 6, 7], [1, 2, 3, 4, 5])
    sysO = sss(sysO3.A,
               sysO3.B*sym.eye(2).col_join(sym.eye(2)),
               Ta*(sym.eye(2).row_join(-La)).col_join(sym.zeros(3, 2).row_join(sym.eye(3)))*sysO3.C,
               sym.zeros(5,2))
    sysU = sss(sysO.A, sysO.B, -Ka*sysO.C, sym.zeros(1,2))
    sysT = ctrl.append(sysS, sysX, sysO, sysU)
    sysT1 = ctrl.connect(sysT, [[1, 14], [3, 14], [5, 14], [7, 14]], [2, 4, 6, 8], [i for i in range(1, 15)])
    sys = sss(sysT1.A, sysT1.B*sym.Matrix([1, 1, 1, 1]), sysT1.C, sym.zeros(14, 1))
    return sys

# check functions
def eigen_choice(selc,selo):
    if selc == 'brez kompleksnih lastnih vrednosti':
        eig2.children[1].children[0].disabled = True
        eig3.children[1].children[0].disabled = True
        eig3.children[0].children[0].disabled = False
        eig4.children[0].children[0].disabled = False
        eig5.children[0].children[0].disabled = False
        eigc = 0
    if selc == 'dve kompleksni lastni vrednosti':
        eig2.children[1].children[0].disabled = False
        eig3.children[1].children[0].disabled = True
        eig3.children[0].children[0].disabled = True
        eig4.children[0].children[0].disabled = False
        eig5.children[0].children[0].disabled = False
        eigc = 2
    if selc == 'štiri kompleksne lastne vrednosti':
        eig2.children[1].children[0].disabled = False
        eig3.children[1].children[0].disabled = False
        eig3.children[0].children[0].disabled = False
        eig4.children[0].children[0].disabled = True
        eig5.children[0].children[0].disabled = True
        eigc = 4
    if selo == 'brez kompleksnih lastnih vrednosti':
        eig1o.children[0].children[0].disabled = False
        eig2o.children[1].children[0].disabled = True
        eigo = 0
    if selo == 'dve kompleksni lastni vrednosti':
        eig1o.children[0].children[0].disabled = True
        eig2o.children[1].children[0].disabled = False
        eigo = 2
    return (eigc, eigo)

def method_choice(selm):
    if selm == 'Nastavi Ka in La':
        method = 1
        selc.disabled = True
        selo.disabled = True
    if selm == 'Nastavi lastne vrednosti':
        method = 2
        selc.disabled = False
        selo.disabled = False
    return method

# Animation functions
def fun_animation(index):
    global Ydw, yout, T
    yd = Ydw.value
    frame = 1

    linez.set_data(T[0:index*frame],yd*yout[0][0:index*frame])
    linezv.set_data(T[0:index*frame],yd*yout[1][0:index*frame])
    lined.set_data(T,[yd for i in range(0,len(T))])
    lineu.set_data(T[0:index*frame],yd*yout[13][0:index*frame])
    linelimu1.set_data(T,[500 for j in range(0,len(T))])
    linelimu2.set_data(T,[-500 for j in range(0,len(T))])
    linethetaest.set_data(T[0:index*frame],yd*yout[10][0:index*frame]*180/numpy.pi)
    linetheta.set_data(T[0:index*frame],yd*yout[5][0:index*frame]*180/numpy.pi)

    rotation_transform.clear().translate(yd*yout[0][index*frame]*numpy.cos(float(yd*yout[6][index*frame])), yd*yout[0][index*frame]*numpy.sin(float(yd*yout[6][index*frame]))).rotate(float(-yd*yout[6][index*frame]))

    return (linez,linezv,lined,lineu,linelimu1,linelimu2,linethetaest,linetheta)

def anim_init():
    linez.set_data([], [])
    linezv.set_data([], [])
    lined.set_data([], [])
    lineu.set_data([], [])
    linelimu1.set_data([], [])
    linelimu2.set_data([], [])
    linethetaest.set_data([], [])
    linetheta.set_data([], [])
    return (linez,linezv,lined,lineu,linelimu1,linelimu2,linethetaest,linetheta)

```

```python
# Main cell
# Data
global yd, T, yout
yd = 10.
T = []
yout = []

# Figures
fig = plt.figure(num='Simulacija krmiljenja horizontalnega pomika lunarne sonde')
fig.set_size_inches((9.8, 6))
fig.set_tight_layout(True)

ax0 = fig.add_subplot(221)
ax0.set_title('Lunarna sonda')
ax0.set_xlim(-12,12)
ax0.set_ylim(-4,4)
ax0.grid()
# ax0.axis('off')

ax1 = fig.add_subplot(222)
linez = ax1.plot([],[])[0]
linezv = ax1.plot([],[])[0]
lined = ax1.plot([],[])[0]
ax1.set_title('Lateralni pomik in hitrost')
ax1.set_xlabel('$t$ [s]')
ax1.set_ylabel('y [m], $\dot y$ [m/s]')
ax1.set_xlim([0,17])
ax1.axvline(x=0,color='black',linewidth=0.8)
ax1.axhline(y=0,color='black',linewidth=0.8)
ax1.grid()
ax1.legend(['Lateralni pomik','Lateralna hitrost','Zahtevana vrednost'])

ax2 = fig.add_subplot(223)
lineu = ax2.plot([],[])[0]
linelimu1 = ax2.plot([],[],'r')[0]
linelimu2 = ax2.plot([],[],'r')[0]
ax2.set_title('Vhodni navor T')
ax2.set_xlabel('$t$ [s]')
ax2.set_ylabel('$T$ [Nm]')
ax2.set_xlim([0,17])
ax2.axvline(x=0,color='black',linewidth=0.8)
ax2.axhline(y=0,color='black',linewidth=0.8)
ax2.grid()
ax2.legend(['T','Limit'])

ax3 = fig.add_subplot(224)
linethetaest = ax3.plot([],[])[0]
linetheta = ax3.plot([],[])[0]
ax3.set_title(r'$\theta_{est}$ vs $\theta$')
ax3.set_xlabel('$t$ [s]')
ax3.set_ylabel(r'$\theta$ [deg]')
ax3.axvline(x=0,color='black',linewidth=0.8)
ax3.axhline(y=0,color='black',linewidth=0.8)
ax3.set_xlim([0,17])
ax3.grid()

# Patches
rotation_transform = transforms.Affine2D()
circle = patches.Circle((0, 0.6), fill=True, radius=0.5, ec='black', fc='gray', lw=1, zorder=20,
                        transform=rotation_transform + ax0.transData)
rect = patches.Rectangle((-1, -0.4), 2, 0.5, fill=True, ec='black', fc='gray', lw=1, zorder=20,
                         transform=rotation_transform + ax0.transData)
poly = patches.Polygon(numpy.stack(([-0.25, -0.15, 0.15, 0.25], [-0.8, -0.4, -0.4, -0.8])).T,
                       closed=True, fill=True, ec='black', fc='black', lw=1, zorder=20,
                       transform=rotation_transform + ax0.transData)
lleg = patches.Rectangle((-1, -1.2), 0.05, 1, angle=-15, fill=True, ec='black', fc='black', lw=1, zorder=10,
                         transform=rotation_transform + ax0.transData)
rleg = patches.Rectangle((1, -1.2), 0.05, 1, angle=15, fill=True, ec='black', fc='black', lw=1, zorder=10,
                         transform=rotation_transform + ax0.transData)
lfoot = patches.Rectangle((-1.1, -1.2), 0.2, 0.05, fill=True, ec='black', fc='black', lw=1, zorder=20,
                         transform=rotation_transform + ax0.transData)
rfoot = patches.Rectangle((0.9, -1.2), 0.2, 0.05, fill=True, ec='black', fc='black', lw=1, zorder=20,
                         transform=rotation_transform + ax0.transData)
ax0.add_patch(circle)
ax0.add_patch(rect)
ax0.add_patch(poly)
ax0.add_patch(lleg)
ax0.add_patch(rleg)
ax0.add_patch(lfoot)
ax0.add_patch(rfoot)
plt.show()

# Functions
def main_function(Ka,La,Ydw,eig1,eig2,eig3,eig4,eig5,eig1o,eig2o,selm,selc,selo,DW):
    global T, yout, yd, Aa, Ba, A11, A21
    method = method_choice(selm)
    eigc, eigo = eigen_choice(selc,selo)
    yd = Ydw
    ax1.set_ylim([-0.1*yd,yd*1.5])
    ax2.set_ylim([-51*yd,51*yd])
    ax3.set_ylim([-15,15])

    if method == 1: #Setted matrix gain
        sol = numpy.linalg.eig((Aa-Ba[:,0]*Ka))
        print('Lastne vrednosti Aa so: '+str(round(sol[0][0],3))+', '+str(round(sol[0][1],3))+', '+str(round(sol[0][2],3))+', '+str(round(sol[0][3],3))+' in '+str(round(sol[0][4],3)))
        sol = numpy.linalg.eig(A11+La*A21)
        print('Lastni vrednosti A11+La*A21 sta: '+str(round(sol[0][0],3))+' in '+str(round(sol[0][1],3)))
        sys = simulation(Aa, Ba, Ca, A11, A12, A21, A22, B1, B2, La, Ka, Ta)
        T = numpy.linspace(0, 17, 100)
        T, yout = ctrl.step_response(sys, T, X0a)
    if method == 2: #Setted eigenvalues
        if eigc == 0:
            Ka = ctrl.acker(Aa, Ba[:,0], [eig1[0,0], eig2[0,0], eig3[0,0], eig4[0,0], eig5[0,0]])
            Kaw.setM(Ka)
        if eigc == 2:
            Ka = ctrl.acker(Aa, Ba[:,0], [eig1[0,0], numpy.complex(eig2[0,0],eig2[1,0]), numpy.complex(eig2[0,0],-eig2[1,0]), eig4[0,0], eig5[0,0]])
            Kaw.setM(Ka)
        if eigc == 4:
            Ka = ctrl.acker(Aa, Ba[:,0], [eig1[0,0], numpy.complex(eig2[0,0],eig2[1,0]), numpy.complex(eig2[0,0],-eig2[1,0]), numpy.complex(eig3[0,0],eig3[1,0]), numpy.complex(eig3[0,0],-eig3[1,0])])
            Kaw.setM(Ka)
        if eigo == 0:
            La = numpy.matrix([[0, 2*eig1o[0,0]/3 + 2*eig2o[0,0]/3, 0], [0, -2*eig1o[0,0]*eig2o[0,0]/3, 0]])
            Law.setM(La)
        if eigo == 2:
            La = numpy.matrix([[0, 2*numpy.complex(eig2o[0,0],eig2o[1,0])/3 + 2*numpy.complex(eig2o[0,0],-eig2o[1,0])/3, 0], [0, -2*numpy.complex(eig2o[0,0],eig2o[1,0])*numpy.complex(eig2o[0,0],-eig2o[1,0])/3, 0]])
            Law.setM(La)
        sol = numpy.linalg.eig((Aa-Ba[:,0]*Ka))
        print('Lastne vrednosti Aa so: '+str(round(sol[0][0],3))+', '+str(round(sol[0][1],3))+', '+str(round(sol[0][2],3))+', '+str(round(sol[0][3],3))+' in '+str(round(sol[0][4],3)))
        sol = numpy.linalg.eig(A11+La*A21)
        print('Lastni vrednosti A11+La*A21 sta: '+str(round(sol[0][0],3))+' in '+str(round(sol[0][1],3)))
        sys = simulation(Aa, Ba, Ca, A11, A12, A21, A22, B1, B2, La, Ka, Ta)
        T = numpy.linspace(0, 17, 100)
        T, yout = ctrl.step_response(sys, T, X0a)

ani = animation.FuncAnimation(fig, fun_animation, init_func=anim_init, frames=100, repeat=True, interval=170, blit=True)

#create dummy widget
DW = widgets.FloatText(layout=widgets.Layout(width='0px', height='0px'))

#create button widget
START = widgets.Button(
    description='Test',
    disabled=False,
    button_style='', # 'success', 'info', 'warning', 'danger' or ''
    tooltip='Test',
    icon='check'
)

def on_start_button_clicked(b):
    #This is a workaround to have intreactive_output call the callback:
    #   force the value of the dummy widget to change
    if DW.value> 0 :
        DW.value = -1
    else:
        DW.value = 1
    pass
START.on_click(on_start_button_clicked)

# Define type of method
selm = widgets.Dropdown(
    options= ['Nastavi Ka in La', 'Nastavi lastne vrednosti'],
    value= 'Nastavi Ka in La',
    description='',
    disabled=False
)

# Define the number of complex eigenvalues for the controller
selc = widgets.Dropdown(
    options= ['brez kompleksnih lastnih vrednosti', 'dve kompleksni lastni vrednosti', 'štiri kompleksne lastne vrednosti'],
    value= 'štiri kompleksne lastne vrednosti',
    description='Aa:',
    disabled=False
)

# Define the number of complex eigenvalues for the observer
selo = widgets.Dropdown(
    options= ['brez kompleksnih lastnih vrednosti', 'dve kompleksni lastni vrednosti'],
    value= 'brez kompleksnih lastnih vrednosti',
    description='Aobs:',
    disabled=False
)

alltogether = widgets.VBox([
    widgets.HBox([
        selm,
        selc,
        selo
    ]),
    widgets.Label('',border=3),
    widgets.HBox([
        widgets.Label('Ka:',border=3),
        Kaw,
        widgets.Label('',border=3),
        widgets.Label('',border=3),
        widgets.Label('La:',border=3),
        Law
    ]),
    widgets.Label('',border=3),
    widgets.HBox([
        widgets.Label('Aa\'s eigs:',border=3),
        eig1, eig2, eig3, eig4, eig5,
        widgets.Label('',border=3),
        widgets.Label('',border=3),
        widgets.Label('Aobs\'s eigs:',border=3),
        eig1o, eig2o
    ]),
    widgets.Label('',border=3),
    widgets.HBox([
        Ydw,
        widgets.Label('',border=3),
        widgets.Label('',border=3),
        widgets.Label('',border=3),
        START
    ])
])

out = widgets.interactive_output(main_function,{'Ka':Kaw, 'La':Law, 'Ydw':Ydw, 'eig1':eig1, 'eig2':eig2,
                                                'eig3':eig3, 'eig4':eig4, 'eig5':eig5,
                                                'eig1o':eig1o, 'eig2o':eig2o,
                                                'selm':selm, 'selc':selc, 'selo':selo, 'DW':DW})
display(out, alltogether)
```

    <IPython.core.display.Javascript object>

    Output()

    VBox(children=(HBox(children=(Dropdown(options=('Nastavi Ka in La', 'Nastavi lastne vrednosti'), value='Nastav…

```python

```


##### Copyright 2020 The OpenFermion Developers

```python
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
```

# FQE vs OpenFermion vs Cirq: Diagonal Coulomb Operators

    View on QuantumAI

    Run in Google Colab

    View source on GitHub

    Download notebook

Special routines are available for evolving under a diagonal Coulomb operator.  This notebook describes how to use these built in routines and how they work.

```python
try:
    import fqe
except ImportError:
    !pip install fqe --quiet
```

```python
from itertools import product
import fqe
from fqe.hamiltonians.diagonal_coulomb import DiagonalCoulomb

import numpy as np

import openfermion as of

from scipy.linalg import expm
```

```python
#Utility function
def uncompress_tei(tei_mat, notation='chemistry'):
    """
    uncompress chemist notation integrals

    tei_tensor[i, k, j, l] = tei_mat[(i, j), (k, l)]
    [1, 1, 2, 2] = [1, 1, 2, 2] = [1, 1, 2, 2]  = [1, 1, 2, 2]
    [i, j, k, l] = [k, l, i, j] = [j, i, l, k]* = [l, k, j, i]*

    For real we also have swap of i <> j and k <> l
    [j, i, k, l] = [l, k, i, j] = [i, j, l, k] = [k, l, j, i]

    tei_mat[(i, j), (k, l)] = int dr1 int dr2 phi_i(dr1) phi_j(dr1) O(r12) phi_k(dr1) phi_l(dr1)

    Physics notation is the notation that is used in FQE.

    Args:
        tei_mat: compressed two electron integral matrix

    Returns:
        uncompressed 4-electron integral tensor. No antisymmetry.
    """
    if notation not in ['chemistry', 'physics']:
        return ValueError("notation can be [chemistry, physics]")

    norbs = int(0.5 * (np.sqrt(8 * tei_mat.shape[0] + 1) - 1))
    basis = {}
    cnt = 0
    for i, j in product(range(norbs), repeat=2):
        if i >= j:
            basis[(i, j)] = cnt
            cnt += 1

    tei_tensor = np.zeros((norbs, norbs, norbs, norbs))
    for i, j, k, l in product(range(norbs), repeat=4):
        if i >= j and k >= l:
            tei_tensor[i, j, k, l] = tei_mat[basis[(i, j)], basis[(k, l)]]
            tei_tensor[k, l, i, j] = tei_mat[basis[(i, j)], basis[(k, l)]]
            tei_tensor[j, i, l, k] = tei_mat[basis[(i, j)], basis[(k, l)]]
            tei_tensor[l, k, j, i] = tei_mat[basis[(i, j)], basis[(k, l)]]

            tei_tensor[j, i, k, l] = tei_mat[basis[(i, j)], basis[(k, l)]]
            tei_tensor[l, k, i, j] = tei_mat[basis[(i, j)], basis[(k, l)]]
            tei_tensor[i, j, l, k] = tei_mat[basis[(i, j)], basis[(k, l)]]
            tei_tensor[k, l, j, i] = tei_mat[basis[(i, j)], basis[(k, l)]]

    if notation == 'chemistry':
        return tei_tensor
    elif notation == 'physics':
        return np.asarray(tei_tensor.transpose(0, 2, 1, 3), order='C')

    return tei_tensor

```

The first example we will perform is diagonal Coulomb evolution on the Hartree-Fock state.  The diagonal Coulomb operator is defined as

\begin{align}
V = \sum_{\alpha, \beta \in \{\uparrow, \downarrow\}}\sum_{p,q} V_{pq,pq}n_{p,\alpha}n_{q,\beta}
\end{align}

The number of free parpameters are $\mathcal{O}(N^{2})$ where $N$ is the rank of the spatial basis. The `DiagonalCoulomb` Hamiltonian takes either a generic 4-index tensor or the $N \times N$ matrix defining $V$.  If the 4-index tensor is given the $N \times N$ matrix is constructed along with the diagonal correction.  If the goal is to just evolve under $V$ it is recommended the user input the $N \times N$ matrix directly.

All the terms in $V$ commute and thus we can evolve under $V$ exactly by counting the accumulated phase on each bitstring.

To start out let's define a Hartree-Fock wavefunction for 4-orbitals and 2-electrons $S_{z} =0$.

```python
norbs = 4
tedim = norbs * (norbs + 1) // 2
if (norbs // 2) % 2 == 0:
    n_elec = norbs // 2
else:
    n_elec = (norbs // 2) + 1
sz = 0
fqe_wfn = fqe.Wavefunction([[n_elec, sz, norbs]])
fci_data = fqe_wfn.sector((n_elec, sz))
fci_graph = fci_data.get_fcigraph()
hf_wf = np.zeros((fci_data.lena(), fci_data.lenb()), dtype=np.complex128)
hf_wf[0, 0] = 1  # right most bit is zero orbital.
fqe_wfn.set_wfn(strategy='from_data',
                raw_data={(n_elec, sz): hf_wf})
fqe_wfn.print_wfn()
```

Now we can define a random 2-electron operator $V$.  To define $V$ we need a $4 \times 4$ matrix.  We will generate this matrix by making a full random two-electron integral matrix and then just take the diagonal elements

```python
tei_compressed = np.random.randn(tedim**2).reshape((tedim, tedim))
tei_compressed = 0.5 * (tei_compressed + tei_compressed.T)
tei_tensor = uncompress_tei(tei_compressed, notation='physics')

diagonal_coulomb = of.FermionOperator()
diagonal_coulomb_mat = np.zeros((norbs, norbs))
for i, j in product(range(norbs), repeat=2):
    diagonal_coulomb_mat[i, j] = tei_tensor[i, j, i, j]
    for sigma, tau in product(range(2), repeat=2):
        diagonal_coulomb += of.FermionOperator(
            ((2 * i + sigma, 1), (2 * i + sigma, 0), (2 * j + tau, 1),
             (2 * j + tau, 0)), coefficient=diagonal_coulomb_mat[i, j])

dc_ham = DiagonalCoulomb(diagonal_coulomb_mat)

```

Evolution under $V$ can be computed by looking at each bitstring, seeing if $n_{p\alpha}n_{q\beta}$ is non-zero and then phasing that string by $V_{pq}$.  For the Hartree-Fock state we can easily calculate this phase accumulation.  The alpha and beta bitstrings are "0001" and "0001".

```python
alpha_occs = [list(range(fci_graph.nalpha()))]
beta_occs = [list(range(fci_graph.nbeta()))]
occs = alpha_occs[0] + beta_occs[0]
diag_ele = 0.
for ind in occs:
    for jnd in occs:
        diag_ele += diagonal_coulomb_mat[ind, jnd]
evolved_phase = np.exp(-1j * diag_ele)
print(evolved_phase)

# evolve FQE wavefunction
evolved_hf_wfn = fqe_wfn.time_evolve(1, dc_ham)

# check they the accumulated phase is equivalent!
assert np.isclose(evolved_hf_wfn.get_coeff((n_elec, sz))[0, 0], evolved_phase)

```

We can now try this out for more than 2 electrons.  Let's reinitialize a wavefunction on 6-orbitals with 4-electrons $S_{z} = 0$ to a random state.

```python
norbs = 6
tedim = norbs * (norbs + 1) // 2
if (norbs // 2) % 2 == 0:
    n_elec = norbs // 2
else:
    n_elec = (norbs // 2) + 1
sz = 0
fqe_wfn = fqe.Wavefunction([[n_elec, sz, norbs]])
fqe_wfn.set_wfn(strategy='random')
inital_coeffs = fqe_wfn.get_coeff((n_elec, sz)).copy()
print("Random initial wavefunction")
fqe_wfn.print_wfn()
```

We need to build our Diagoanl Coulomb operator For this bigger system.

```python
tei_compressed = np.random.randn(tedim**2).reshape((tedim, tedim))
tei_compressed = 0.5 * (tei_compressed + tei_compressed.T)
tei_tensor = uncompress_tei(tei_compressed, notation='physics')

diagonal_coulomb = of.FermionOperator()
diagonal_coulomb_mat = np.zeros((norbs, norbs))
for i, j in product(range(norbs), repeat=2):
    diagonal_coulomb_mat[i, j] = tei_tensor[i, j, i, j]
    for sigma, tau in product(range(2), repeat=2):
        diagonal_coulomb += of.FermionOperator(
            ((2 * i + sigma, 1), (2 * i + sigma, 0), (2 * j + tau, 1),
             (2 * j + tau, 0)), coefficient=diagonal_coulomb_mat[i, j])

dc_ham = DiagonalCoulomb(diagonal_coulomb_mat)

```

Now we can convert our wavefunction to a cirq wavefunction, evolve under the diagonal_coulomb operator we constructed and then compare the outputs.

```python
cirq_wfn = fqe.to_cirq(fqe_wfn).reshape((-1, 1))
final_cirq_wfn = expm(-1j * of.get_sparse_operator(diagonal_coulomb)) @ cirq_wfn
# recover a fqe wavefunction
from_cirq_wfn = fqe.from_cirq(final_cirq_wfn.flatten(), 1.0E-8)

```

```python
fqe_wfn = fqe_wfn.time_evolve(1, dc_ham)
print("Evolved wavefunction")
fqe_wfn.print_wfn()
```

```python
print("From Cirq Evolution")
from_cirq_wfn.print_wfn()
assert np.allclose(from_cirq_wfn.get_coeff((n_elec, sz)),
                   fqe_wfn.get_coeff((n_elec, sz)))
print("Wavefunctions are equivalent")
```

Finally, we can compare against evolving each term of $V$ individually.

```python
fqe_wfn = fqe.Wavefunction([[n_elec, sz, norbs]])
fqe_wfn.set_wfn(strategy='from_data',
                raw_data={(n_elec, sz): inital_coeffs})
for term, coeff in diagonal_coulomb.terms.items():
    op = of.FermionOperator(term, coefficient=coeff)
    fqe_wfn = fqe_wfn.time_evolve(1, op)

assert np.allclose(from_cirq_wfn.get_coeff((n_elec, sz)),
               fqe_wfn.get_coeff((n_elec, sz)))
print("Individual term evolution is equivalent")
```

```python

```


```python
from IPython.core.display import HTML, Image
css_file = 'style.css'
HTML(open(css_file, 'r').read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Philosopher:400,700,400italic,700italic' rel='stylesheet' type='text/css'>

```python
from sympy import init_printing, Matrix, symbols, eye
from warnings import filterwarnings
```

```python
init_printing(use_latex = 'mathjax')
filterwarnings('ignore')
```

```python
lamda = symbols('lamda') # Note that lambda is a reserved word in python, so we use lamda (without the b)
```

# Eigenvalues and eigenvectors

## What are eigenvectors?

* A Matrix is a mathematical object that acts on a (column) vector, resulting in a new vector, i.e. A**x**=**b**
* An eigenvector is the resulting vector that is parallel to **x** (some multiple of **x**)
$$ {A}\underline{x}=\lambda \underline{x} $$

* The eigenvectors with an eigenvalue of zero are the vectors in the nullspace
* If A is singular (takes some non-zero vector into 0) then λ=0

## What are the eigenvectors and eigenvalues for projection matrices?

* A projection matrix P projects some vector (**b**) onto a subspace (in 3-space we are talking about a plane through the origin)
* P**b** is not in the same direction as **b**
* A vector **x** that is already in the subspace will result in P**x**=**x**, so λ=1
* Another good **x** would be one perpendicular to the subspace, i.e. P**x**=0**x**, so λ=0

## What are the eigenvectors and eigenvalues for permutation matrices?

* A permutation matrix such as the one below changes the order of the elements in a (column) vector
$$ \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix} $$
* A good example of a vector that would remain in the same direction after multiplication by the permutation matrix above would the following vector
$$ \begin{bmatrix} 1 \\ 1 \end{bmatrix} $$
* The eigenvalue would just be λ=1
* The next (eigen)vector would also work
$$ \begin{bmatrix} -1 \\ 1 \end{bmatrix}  $$
* It would have an eigenvalue of λ=-1

## The trace and the determinant

* The trace is the sum of the values down the main diagonal of a square matrix
* Note how this is the same as the sum of the eigenvalues (look at the permutation matrix above and its eigenvalues)
* The determinant of A is the product of the eigenvalues

## How to solve A**x**=λ**x**

$$ A\underline { x } =\lambda \underline { x } \\ \left( A-\lambda I \right) \underline { x } =\underline { 0 }  $$

* The only solution to this equation is for A-λI to be singular and therefor have a determinant of zero
$$ \left|{A}-\lambda{I}\right|=0 $$

* This is called the characteristic (or eigenvalue) equation
* There will be *n* λ's for a *n*×*n* matrix(some of which may be of equal value)

```python
A = Matrix([[3, 1], [1, 3]])
I = eye(2)
A, I # Printing A and the 2-by-2 identity matrix to the screen
```

$$\begin{pmatrix}\left[\begin{matrix}3 & 1\\1 & 3\end{matrix}\right], & \left[\begin{matrix}1 & 0\\0 & 1\end{matrix}\right]\end{pmatrix}$$

```python
(A - lamda * I) # Printing A minus lambda times the identity matrix to the screen
```

$$\left[\begin{matrix}- \lambda + 3 & 1\\1 & - \lambda + 3\end{matrix}\right]$$

* This will have the following determinant

```python
(A - lamda * I).det()
```

$$\lambda^{2} - 6 \lambda + 8$$

* For this 2×2 matrix the absolute value of the -6 is the trace of A and the 8 is the determinant of A

```python
((A - lamda * I).det()).factor()
```

$$\left(\lambda - 4\right) \left(\lambda - 2\right)$$

* I now have two eigenvalues of 2 and 4

* In python we could also use the .*eigenvals()* statement

```python
A.eigenvals() # There is one value of 2 and one value of 4
```

$$\begin{Bmatrix}2 : 1, & 4 : 1\end{Bmatrix}$$

* The eigenvectors are calculated by substituting the two values of λ into the original equation
$$ \left( {A}-\lambda{I} \right)\underline{x}=\underline{0} $$

```python
A.eigenvects()
```

$$\begin{bmatrix}\begin{pmatrix}2, & 1, & \begin{bmatrix}\left[\begin{matrix}-1\\1\end{matrix}\right]\end{bmatrix}\end{pmatrix}, & \begin{pmatrix}4, & 1, & \begin{bmatrix}\left[\begin{matrix}1\\1\end{matrix}\right]\end{bmatrix}\end{pmatrix}\end{bmatrix}$$

* The results above is interpreted as follows
    * The first eigenvalue has one eigenvector and the second eigenvalue also has a single eigenvector

* Note the similarity between the eigenvectors of the two examples above
* It is easy to see that adding a constant multiple of the identity matrix to another matrix (above we added 3I to the initial matrix) doesn't change the eigenvectors; it does add that constant to the eigenvalues though (we went from -1 and 1 to 2 and 4)
$$ A\underline { x } =\lambda \underline { x } \\ \therefore \quad \left( A+cI  \right) \underline { x } =\left( \lambda +c \right) \underline { x }  $$

* If we add another matrix to A (not a constant multiple of I) or even multiply them, then the influence on the original eigenvalues and eigenvectors of A is NOT so predictable (as above)

## The eigenvalues and eigenvectors of a rotation matrix

* Consider this rotation matrix that rotates a vector by 90o (it is orthogonal)
    * Think about it, though: what vector can come out parallel to itself after a 90o rotation?

```python
Q = Matrix([[0, -1], [1, 0]])
Q
```

$$\left[\begin{matrix}0 & -1\\1 & 0\end{matrix}\right]$$

* From the trace and determinant above we know that we will have the following equation
$$ {\lambda}^{2}-{0}{\lambda}+{1}={0} \\ {\lambda}^{2}=-{1} $$

```python
Q.eigenvals()
```

$$\begin{Bmatrix}- i : 1, & i : 1\end{Bmatrix}$$

```python
Q.eigenvects()
```

$$\begin{bmatrix}\begin{pmatrix}- i, & 1, & \begin{bmatrix}\left[\begin{matrix}- i\\1\end{matrix}\right]\end{bmatrix}\end{pmatrix}, & \begin{pmatrix}i, & 1, & \begin{bmatrix}\left[\begin{matrix}i\\1\end{matrix}\right]\end{bmatrix}\end{pmatrix}\end{bmatrix}$$

* Note how the eigenvalues are complex conjugates
* Symmetric matrices will only have real eigenvalues
* An *anti*-symmetric matrix (where the transpose is the original matrix times the scalar -1, as our example above) will only have complex eigenvalues
* Matrices in between can have a mix of these

## Eigenvalues and eigenvectors of an upper triangular matrix

* Compute the eigenvalues and eigenvectors of the following matrix (note it is upper triangular)

```python
A = Matrix([[3, 1], [0, 3]])
A
```

$$\left[\begin{matrix}3 & 1\\0 & 3\end{matrix}\right]$$

```python
A.eigenvals()
```

$$\begin{Bmatrix}3 : 2\end{Bmatrix}$$

* We have two eigenvalues, both equal to 3

```python
A.eigenvects()
```

$$\begin{bmatrix}\begin{pmatrix}3, & 2, & \begin{bmatrix}\left[\begin{matrix}1\\0\end{matrix}\right]\end{bmatrix}\end{pmatrix}\end{bmatrix}$$

* This is a degenerate matrix; it does not have independent eigenvectors

* Look at this upper triangular matrix

```python
A = Matrix([[3, 1, 1], [0, 3, 4], [0, 0, 3]])
A
```

$$\left[\begin{matrix}3 & 1 & 1\\0 & 3 & 4\\0 & 0 & 3\end{matrix}\right]$$

```python
A.eigenvals()
```

$$\begin{Bmatrix}3 : 3\end{Bmatrix}$$

```python
A.eigenvects()
```

$$\begin{bmatrix}\begin{pmatrix}3, & 3, & \begin{bmatrix}\left[\begin{matrix}1\\0\\0\end{matrix}\right]\end{bmatrix}\end{pmatrix}\end{bmatrix}$$

## Example problems

### Example problem 1

* Find the eigenvalues and eigenvectors of the square of the following matrix as well as the inverse of the matrix minus the identity matrix
$$ {A}=\begin{bmatrix} 1 & 2 & 3 \\ 0 & 1 & -2 \\ 0 & 1 & 4 \end{bmatrix} $$

#### Solution

* Notice the following
$$ A\underline { x } =\lambda \underline { x } \\ { A }^{ 2 }\underline { x } =A\left( A\underline { x }  \right) =A\left( \lambda \underline { x }  \right) =\lambda \left( A\underline { x }  \right) ={ \lambda  }^{ 2 }\underline { x }  $$
* Once we know the eigenvalues for A we than simply square them to get the eigenvalues of the matrix squared

* Similarly for the inverse of the matrix we have the following (for a non-zero λ, which is fine as A must be invertible for this problem)
$$ { A }^{ -1 }\underline { x } ={ A }^{ -1 }\frac { A\underline { x }  }{ \lambda  } ={ A }^{ -1 }A\frac { 1 }{ \lambda  } \underline { x } =\frac { 1 }{ \lambda  } \underline {  x} $$

```python
A = Matrix([[1, 2, 3], [0, 1, -2], [0, 1, 4]])
A
```

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

```python
A.eigenvals()
```

$$\begin{Bmatrix}1 : 1, & 2 : 1, & 3 : 1\end{Bmatrix}$$

```python
A.eigenvects()
```

$$\begin{bmatrix}\begin{pmatrix}1, & 1, & \begin{bmatrix}\left[\begin{matrix}1\\0\\0\end{matrix}\right]\end{bmatrix}\end{pmatrix}, & \begin{pmatrix}2, & 1, & \begin{bmatrix}\left[\begin{matrix}-1\\-2\\1\end{matrix}\right]\end{bmatrix}\end{pmatrix}, & \begin{pmatrix}3, & 1, & \begin{bmatrix}\left[\begin{matrix}\frac{1}{2}\\-1\\1\end{matrix}\right]\end{bmatrix}\end{pmatrix}\end{bmatrix}$$

* From this it is clear that the eigenvalues of A2 will be 1, 4, and 9 and for A-1 would be a 1, a half and a third

```python
(A ** 2).eigenvals()
```

$$\begin{Bmatrix}1 : 1, & 4 : 1, & 9 : 1\end{Bmatrix}$$

```python
(A.inv()).eigenvals()
```

$$\begin{Bmatrix}\frac{1}{3} : 1, & \frac{1}{2} : 1, & 1 : 1\end{Bmatrix}$$

* The eigenvectors will be as follows (exactly the same)

```python
(A ** 2).eigenvects()
```

$$\begin{bmatrix}\begin{pmatrix}1, & 1, & \begin{bmatrix}\left[\begin{matrix}1\\0\\0\end{matrix}\right]\end{bmatrix}\end{pmatrix}, & \begin{pmatrix}4, & 1, & \begin{bmatrix}\left[\begin{matrix}-1\\-2\\1\end{matrix}\right]\end{bmatrix}\end{pmatrix}, & \begin{pmatrix}9, & 1, & \begin{bmatrix}\left[\begin{matrix}\frac{1}{2}\\-1\\1\end{matrix}\right]\end{bmatrix}\end{pmatrix}\end{bmatrix}$$

```python
(A.inv()).eigenvects()
```

$$\begin{bmatrix}\begin{pmatrix}\frac{1}{3}, & 1, & \begin{bmatrix}\left[\begin{matrix}\frac{1}{2}\\-1\\1\end{matrix}\right]\end{bmatrix}\end{pmatrix}, & \begin{pmatrix}\frac{1}{2}, & 1, & \begin{bmatrix}\left[\begin{matrix}-1\\-2\\1\end{matrix}\right]\end{bmatrix}\end{pmatrix}, & \begin{pmatrix}1, & 1, & \begin{bmatrix}\left[\begin{matrix}1\\0\\0\end{matrix}\right]\end{bmatrix}\end{pmatrix}\end{bmatrix}$$

```python

```

Mathematics/Linear Algebra/0.0 MIT-18.06 - Jupyter/Lecture_21_Eigenvalues_and_eigenvectors.ipynb


Modelos de inventarios

Santiago Pérez Angarita

Este cuadernillo explorará los **Modelos de inventario** cuya demanda tiene un comportamiento determinístico o estocástico. Se reconocerán algunas funciones básicas de **Python** que tendrán una aplicación posterior en los modelos anteriormente mencionados.

## ¿Qué es un inventario?

Cuando hablamos de **inventarios**, nos referirimos a mercancías, materiales, piezas, entre otro tipo de bienes puestos en reserva para uso o ventas futuras. Algunas de las razones para que una empresa tenga un inventario se relacionan con la posibilidad de hacer una buena planeación a futuro de sus activos o como provisión ante comportamientos fluctuantes o de incertidumbre. Es por esto que, vamos a considerar dos preguntas en las políticas de inventarios: i) **¿Qué tanto debe ser renovado el inventario?** y ii)  **¿Cuándo debe ser renovado el inventario?**, Vale aclarar que estás preguntas tendrán una respuesta conforme al comportamiento que tenga la demanda dentro del modelo generado.

En este cuadernillo se van a considerar dos tipos de modelos de inventario: i) Los determinísticos, que son aquellos cuya demanda es conocida y, ii) Los estocásticos en donde la demanda es una variable aleatoria para cualquier periodo. De acuerdo con ello, el siguiente diagrama resume algunos de los modelos a considerar durante este curso de acuerdo al comportamiento de la demanda:

Ahora bien, antes de continuar con el desarrollo de cada uno de los modelos anteriormente mencionados, se hace necesario hablar de algunos de los costos asociados a un inventario, como lo son: i) Los **costos de retención**, que corresponden a aquellos costos asociados al mantenimiento del inventario tales como: seguros, intereses, depreciación, transporte, impuestos, hurtos, daños, etc.)  y  ii) **los costos de ordenar**, que cubre los fletes, despachos, órdenes de compra, servicio telefónico etc.

### Cantidad económica de pedido

**Situación 1** (*Tomada del libro : Producción y operaciones aplicadas a las Pyme, autor: Carlos José Bello Pérez*)

La empresa Jean & Jean, fabricante de ropa informal, tiene previsto un programa anual de producción de 40 000 unidades de jeans para dama, la información proveniente del departamento  de registro y costos es la siguiente:

**Costo unitario**

|Tela| 107 metros/unidad | \$ 3600/metro  |
|--------|--------|---------|
|Mano de obra| 16 minutos/unidad | \$ 16.11/minutos |
|Gastos generales| \$ 17440000/40000un. | |

**Costo de pedido**

|Hora máquinas| 8 horas / pedido | \$ 746 / hora |
|---:|---:|---:|
|Mano de obra preparación| 18 hora/ pedido | \$ 966.6 / hora|

**Carga gastos generales y administrativos**

Horas máquinas  + Mano de obra

**Costo de almacenamiento en porcentaje del inventario promedio**

|Seguros| 0.4|
|----|----|
|Intereses|2.3|
|Depreciación|1.6|
|Transporte|0.6|
|Impuestos|1.8|
|Manejo y distribución|1.2|
|Obsolescencia|0.5|
|Pérdida|0.2|
|Equipos|0.8|
|Espacio|2.5|

¿Cuál es número de "corridas" de producción que generan el menor costo posible?

¿Cuál es el menor costo posible?

```python
#Gasto tela x unidad
tela_x_unidad=1.07*3600
mano_obra_x_unidad=16*16.11
gastos_generales_x_unidad=17440000/4000
print("Costo unitario de la tela es:",tela_x_unidad)
print("Costo unitario de la mano de obra es:",mano_obra_x_unidad)
print("Costo unitario de los gastos generales",gastos_generales_x_unidad)
```

    Costo unitario de la tela es: 3852.0
    Costo unitario de la mano de obra es: 257.76
    Costo unitario de los gastos generales 4360.0

```python
#Costo de preparación
Maquinas_x_pedido=8*746
Mano_de_obra_x_pedido=18*966.6
print("Costo por pedido de la maquinaria:",Maquinas_x_pedido)
print("Costo por pedido mano de obra:",Mano_de_obra_x_pedido)
```

    Costo por pedido de la maquinaria: 5968
    Costo por pedido mano de obra: 17398.8

```python
#Gasto general y administrativo
gastos_generales_x_pedido=Maquinas_x_pedido+Mano_de_obra_x_pedido
print("gastos generales por pedido:",gastos_generales_x_pedido)
```

    gastos generales por pedido: 23366.8

```python
print ("Total costo unitario",tela_x_unidad+gastos_generales_x_unidad)
print("Total costo por pedido",Maquinas_x_pedido+Mano_de_obra_x_pedido+gastos_generales_x_pedido)
```

    Total costo unitario 8212.0
    Total costo por pedido 46733.6

```python
#Costos de almacenamiento

p=0.4+2.3+1.6+0.6+1.8+1.2+0.5+0.2+0.8+2.5
print("Costo de almacenamiento (% del inventario)",round(p,1))
```

    Costo de almacenamiento (% del inventario) 11.9

$$C_t=Cu*S+Cp*(S/q0)+(P*Cu*q0/2)$$

```python
from sympy import *
x=Symbol("x")
Costo=4545.76*40000+46733.6*(40000/x)+(0.119*4545.76*x/2)
```

```python
plot(Costo,(x,500,40000))
```

```python
Costo.subs(x,40000)
```

$\displaystyle 192696042.4$

Antes de hallar las respuestas de la **situación 1** es importante interpretar el comportamiento del modelo de cantidad económica como un diagrama que depende del tiempo y del tamaño del inventario:

De igual forma, se identifican las siguientes ecuaciones que responden no solo a las preguntas de **¿Qué tanto debe ser renovado el inventario?** y **¿Cuándo debe ser renovado el inventario?**, sino también los costos y tiempos asociados al **modelo de cantidad económica de pedido**

**¿Cuánto? Cantidad económica de pedido que minimiza costos**

$Q^*=\sqrt{\dfrac{2*S*Cp}{p*Cu}}$ \\

**Costo de mantener una unidad en el inventario**

$C_m=T_r * C_u$ \\

**Costo total= Costo de retención + Costo de ordenar**

$C_{total}=\dfrac{1}{2} Q\cdot C_m + \dfrac{D}{Q} \cdot C_p$

**¿Cuándo? Punto de reorden**

$P_o=S\cdot /t_E$ (Ecuación de más fácil comprensión en el cuaderno del profesor)

Tiempo de ciclo

$T=\dfrac{DH \cdot Q^*}{D}$

```python
Cantidad_óptima_x_pedido=sqrt((2*40000*46733.6)/(0.119*4545.76))
```

```python
Cantidad_óptima_x_pedido
```

$\displaystyle 2628.95335110281$

```python
Cantidad_pedidos_anuales=N(40000/Cantidad_óptima_x_pedido)
Cantidad_pedidos_anuales
#N para que haga calculo
```

$\displaystyle 15.2151805901084$

```python
Periodo_entre_pedidos=N(365/Cantidad_pedidos_anuales)
Periodo_entre_pedidos
#Cada cuanto se debe renovar inventario
```

$\displaystyle 23.9891993288131$

```python
Costo.subs(x,2629)
```

$\displaystyle 183252520.327476$

**CALCULADORA EOQ**

```python
D=input("Por favor digite el valor de la demanda anual:" )
C_p=input("Por favor digite el costo de pedido del modelo:" )
C_u=input("Por favor digite el costo unitario:" )
Tr=input("Por favor digite la tasa de retención:" )
t_E=input("POr favor digite el tiempo de espera:" )
D, C_p, C_u, Tr, t_E = float(D), float(C_p), float(C_u), float(Tr), float(t_E) #Hago esto(float) porque las variables pueden tener decimales
C_m=Tr*C_u # Esta fórmula calcula el costo de mantener una unidad
Q=((2*D*C_p)/(C_m))**(1/2)
C_t=((1/2)*Q*C_m)+((D/Q)*C_p)+C_u*D
T=(365*Q/D)
print("La cantidad de económica de pedido es:", Q)
print("El costo mínimo es: ",C_t)
print(T)
```

    Por favor digite el valor de la demanda anual:1
    Por favor digite el costo de pedido del modelo:1
    Por favor digite el costo unitario:1
    Por favor digite la tasa de retención:1
    POr favor digite el tiempo de espera:1
    La cantidad de económica de pedido es: 1.4142135623730951
    El costo mínimo es:  2.414213562373095
    516.1879502661798

### Tamaño de lote de producción

**Situación 2** (*Tomada del libro : Producción y operaciones aplicadas a las Pyme, autor: Carlos José Bello Pérez*)

La siguiente información se obtuvo de la compañía Snaptools

| | |
|-----|-----|
|Consumo estimado para el año 2005 | 105000 unidades |
|Tasa de producción para el año 2005 | 250000 unidades|
|Costo unitario promedio| \$ 26.75/ unidad |
|Costo alquiler bodega | \$ 2.5 / unidad|
|Costo preparación pedido| \$ 37.4 / pedido|
|Seguros inventarios |10\% costo inventario promedio|
|Costo promedio recepción | \$30.06 /pedido|
|Gastos generales y administrativos oficina compras| \$ 70.16/pedido|
|Pérdidas de producto|2\% del consumo total|
|Sobre costo (intereses| 8\% del costo unitario|

Si hay un tiempo de espera de cinco(5) días para la recepción y 234 días laborales por año ¿Cuál es el tamaño de lote de producción?

Antes de hallar las respuestas de la **situación 2** es importante interpretar el comportamiento del modelo de tamaño de lote de producción como un diagrama que depende del tiempo y del tamaño del inventario:

Igual que en el modelo anterior, se identifican las siguientes ecuaciones que responden no solo a las preguntas de ¿Qué tanto debe ser renovado el inventario? y ¿Cuándo debe ser renovado el inventario?, sino también los costos y tiempos asociados al **modelo de tamaño de lote de producción.**

**Inventario máximo**

I=(p-d)*t

**Inventario máximo**

$Inv_{max}=\left(1-\dfrac{D}{P}\right) \cdot Q$

**Duración fase de producción**

$t =\dfrac{Q}{p}$

**¿Cuánto? Tamaño de lote de producción económico**

 $Q^* =\sqrt{\dfrac{2 D  C_p}{\left(1-\dfrac{D}{P}\right)  C_m }}$

**Inventario máximo**

$Inv_{max}=\left(1-\dfrac{D}{P}\right) \cdot Q$

**Inventario promedio**

$Inv_{prom}=\dfrac{1}{2} \cdot \left(1-\dfrac{D}{P}\right) \cdot Q$

**¿Cuánto? Tamaño de lote de producción económico**

 $Q^* =\sqrt{\dfrac{2 D  C_p}{\left(1-\dfrac{D}{P}\right)  C_m }}$

**¿Cuándo? Punto de reorden**

$P_o=d\cdot t_E$

**Costo total**

$C_T=\dfrac{1}{2} \cdot \left(1-\dfrac{D}{P}\right) Q  C_m + \dfrac{D}{Q} C_p$

**Tiempo de ciclo**

$T = \dfrac{DH \cdot Q}{D}$

```python
D=input("Digite la demanda anual del modelo:" )
P=input("Digite la producción anual del modelo:" )
C_p=input("Digite el costo de pedido:"  )
TR=input("Digite la tasa de retención:" )
C_u=input("Digite el costo unitario:" )
DH=input("¿Cuántos días laboran al año?:" )
D, P, C_p, TR, C_u, DH = float(D), float(P), float(C_p), float(TR), float(C_u), float(DH)
C_m=TR*C_u
Q=((2*D*C_p)/((1-(D/P))*C_m))**(1/2)
C_t=(1/2)*(1-(D/P))*Q*C_m +(D/Q)*C_p
Inv_max=(1-(D/P))*Q
T=(DH*Q)/D
t=Q/(P/DH)
print("El tamaño de lote producción es: ",Q)
print("El costo mínimo es:", C_t)
print("El inventario máximo es:" ,Inv_max)
print("El tiempo de ciclo es", T, "días")
print("La duración de la fase de producción es:", t)
```

    Digite la demanda anual del modelo:6000
    Digite la producción anual del modelo:24000
    Digite el costo de pedido:150
    Digite la tasa de retención:0.12
    Digite el costo unitario:10
    ¿Cuántos días laboran al año?:120
    El tamaño de lote producción es:  1414.213562373095
    El costo mínimo es: 1272.7922061357854
    El inventario máximo es: 1060.6601717798212
    El tiempo de ciclo es 28.284271247461906 días
    La duración de la fase de producción es: 7.0710678118654755

```python
(105000/234)*5
```

    2243.5897435897436

### Faltantes planeados

**Situación 3**

Suponga que el Jefe de inventarios de la empresa Jean & Jean de la **Situación 1**, considera necesario incluir algunos de sus Jeans como pedidos en espera, con el fin de reducir algunos de los gastos de la compañía. Tenga en cuenta la siguiente información:

|Costo de ordenar en espera| \$ 1622 / unidad  |
|----|----|
|Días laborales por año | 234 días |

Si se espera que no más del 15\% de unidades de Jeans sean pedidos en espera ¿Usted considera que se reducirían algunos de los gatos de Jean & Jean?

Antes de hallar las respuestas de la **situación 3** es importante interpretar el comportamiento del modelo de faltantes planeados como un diagrama que depende del tiempo y del tamaño del inventario:

De igual forma, se identifican las siguientes ecuaciones que responden a la cantidades óptimas de pedidos en espera y cantidades óptima de pedido, también los costos y tiempos asociados al modelo de faltantes planeados.

**Inventario promedio**

$Inv_{Prom}=\dfrac{(Q-S)^2}{2Q}$

**Días en los que el inventario está disponible**

$t_1=\dfrac{Q-S}{d}$ días

**Días en los que se agotan las existencias**

$t_2=\dfrac{S}{d}$ días

**Número de pedidos por año**

$N_P=\dfrac{D}{Q}$

**Pedidos promedios en espera**

$S_{Prom} = \dfrac{S^2}{2Q}$

**Cantidad óptima de pedido**

 $Q^* =\sqrt{\dfrac{2 D  C_p}{C_m} \left( \dfrac{C_m + C_E}{C_E} \right)}$

**Pedidos en espera óptimos**

$S^* =Q^* \left(\dfrac{C_m}{C_m + C_E} \right)$

**Costo total por año**

$C_{total}=\dfrac{(Q-S)^2}{2Q} \cdot C_m + \dfrac{D}{Q} \cdot C_p + \dfrac{S^2}{2Q} \cdot C_E $

**CALCULADORA FP**

```python
D=input("Digite la demanda anual del modelo:" )
C_p=input("Digite el costo de ordenar el pedido:"  )
C_E=input("Digite el costo de ordenar en espera:" )
TR=input("Digite la tasa de retención:" )
C_u=input("Digite el costo unitario:" )
DH=input("¿Cuántos días laboran al año?:" )
D, C_p, TR, C_u, DH, C_E = float(D), float(C_p), float(TR), float(C_u), float(DH), float(C_E)
C_m=TR*C_u
Q=(((2*D*C_p)/(C_m))*((C_m +C_E)/C_E))**(1/2)
S=Q*(C_m/(C_m + C_E))
t_1=(Q-S)/(D/DH)
t_2=S/(D/DH)
C_t=((Q-S)**2/(2*Q)) * C_m + (D/Q)*C_p + (S**2/2*Q)*C_E
print("La cantidad óptima de pedido es: ", Q)
print("Pedidos en espera óptimos: ", S)
print("El costo mínimo es:", C_t)
print("El inventario máximo es:" ,Inv_max)
print("Los días en los que el inventario está disponible son:", t_1)
print("Los días en los que se agotan las existencias son:", t_2)
```

    Digite la demanda anual del modelo:1
    Digite el costo de ordenar el pedido:1
    Digite el costo de ordenar en espera:1
    Digite la tasa de retención:1
    Digite el costo unitario:1
    ¿Cuántos días laboran al año?:1
    La cantidad óptima de pedido es:  2.0
    Pedidos en espera óptimos:  1.0
    El costo mínimo es: 1.75
    El inventario máximo es: 1060.6601717798212
    Los días en los que el inventario está disponible son: 1.0
    Los días en los que se agotan las existencias son: 1.0

## Modelos estocásticos

### Periódo único

**Situación 4**

La empresa Jean & Jean de la **Situación 1** solicitó un "satélite" para la creación de un nuevo diseño de jean con motivos **darks** para la época de Halloween, el cual sería vendido en 45000 pesos, y además tendría un **costo de compra** de 35000 pesos. Suponga que, al finalizar la época de Halloween no fue posible vender la totalidad de jeans **darks**, la empresa para recuperar un poco de dinero, decide rebajar el precio de venta de ese pequeño lote de jeans a 30000 pesos.   Con base en la experiencia anterior, la demanda de los clientes en el "*Madrugón*" describe una distribución de probabilidad normal con media de 10000 jeans y una desviación estándar de 150 unidades.

De acuerdo a la situación anterior, ¿Cuál debería ser la cantidad de pedido recomendada?

Antes de responder a la pregunta de la **situación 4**, es importante identificar  las siguientes ecuaciones que respondena la cantidad óptima y a los costos asociados de este modelo.

**Costo de subestimar la demanda**

$C_{SUB}$=Precio regular (unidad) $-$ Costo de compra(unidad)

**Costo de sobreestimar la demanda**

$C_{SOB}=$ Costo de compra(unidad) $-$ Precio de venta (Rescate)

$P(demanda \leq Q^*)= \dfrac{C_{SUB}}{C_{SUB}+ C_{SOB}}$

$Q^*= \mu + \mathbb{z} \cdot \sigma$

Tenga en cuenta que $\mathbb{z}$ es el valor asociado a la probabilidad acumulada $P(demanda \leq Q^*)$ de una distribución normal estándar.

**ACTIVIDAD** : Realice una calculadora como las de los modelos anteriores para dar respuesta a la **Situación 4**

### Punto de reorden

**ACTIVIDAD** : De acuerdo a la bibliografía descrita en el contenido programático del curso de **Modelos 2**, investigue en qué consiste el **modelo de punto de Reorden** y dé tres(3) ejemplos de situaciones que le permitan aplicarlo.

**Pista:** Tenga en cuenta que el diagrama asociado a este modelo es el presentado a continuación

**EJERCICIOS DE PRÁCTICA**

* Hyundai compra un componente utilizado en la fabricación de generadores automotrices directamente con el proveedor. La operación de producción de generadores de Hyundai, la cual funciona a un ritmo constante, requerirá 1000 componentes por mes durante todo el año. Suponga que los costos de pedido son de  75000 pesos , el costo unitario es de 7500 pesos por componente y los costos de retención anuales son de 20\% del valor del inventario. Responda las siguientes preguntas de política de inventario:

 ** ¿Cuál es la cantidad económica de este componente?

 ** ¿Cuáles son los costos de retención y pedido anuales totales asociados con su cantidad recomendada?

 ** Suponga que Hyundai, decidió operar con una política de inventario de pedidos en espera. Se estima que los costos de éstos son de 15000 pesos por unidad por año. Identifique lo siguiente: i) Cantidad de pedido de costo mínimo; ii)Número de pedidos en espera , iii) Inventario máximo, iv) Tiempo de ciclo; v) Costo anual total

* Editorial Carvajal produce libros para el mercado minorista. Se espera que la demanda de un libro actual se dé a una tasa anual constante de 7200 ejemplares. El costo de un ejemplar del libro es de 43500 pesos. El costo de retención está basado en un tasa anual de 18\% y los costos de preparación de la producción son de 450000 pesos por preparación. El equipo con el que se produce el libro tiene un volumen de producción anual de 25000 ejemplares. De acuerdo a la siguiente información encuentre: i) Tamaño del lote de producción de costo mínimo; ii) Número de fases de producción por año; iii) Inventario máximo y iv) Costo anual total

* Un reconocido fabricante de varias marcas de pasta dental utiliza el modelo de tamaño del lote de producción para determinar las cantidades de producción de sus productos. El producto conocido como *Total 12* actualmente se produce en tamaños del lote de producción de 5000 unidades. Debido a una reciente escasez de una materia prima particular, el proveedor de la materia prima anunció que un incremento del costo se transferirá al fabricante de *Total 12*. Las estimaciones actuales son que el nuevo costo de la materia prima incrementará el costo de fabricación de la pasta 25\% por unidad. ¿Cuál será el efecto de este incremento de precio en los tamaños del lote de producción de *Total 12*?

*  Hitachi está considerando la compra de un envío especial de aires acondicionados fabricados en Japón. Cada unidad costará a Hitachi 240000 pesos, y se venderá en 375000 pesos. Hitachi no quiere acarrear un excedente de aires acondicionados hasta el año siguiente. Por tanto, venderá todos los acondicionadores sobrantes a un distribuidor en 150000 pesos por unidad. Asegúrese de que la demanda de acondicionadores de aire sigue una distribución de probabilidad normal con $\mu=20$ y $\delta=8$

```python

```

Cuadernos/Modelos II/Modelos de inventarios.ipynb