# Priors: Theano Graph

```python
with gp.G3Model() as model:
    g3 = gp.G3GP(gp.Constant(), gp.SE(x,gp.ARD_L2), gp.Identity(y))
    g3.observed(x_obs,y_obs)
    mu, var, median, I_up, I_down, m1, m2 = g3.compilate_functions(model.vars,x)

model.use_prior()
gp.show_graph(model.logpt)
```

```python
gp.show_graph(model.fastlogp.f)
```

```python
model.use_prior(False)
gp.show_graph(model.logpt)
```

```python
gp.show_graph(model.fastlogp.f)
```

# Transformation
http://localhost:8888/notebooks/GaussianProcesses/BoxCoxGP/sunspots-GP-vs-TGP.ipynb

```python
hidden_var = np.linspace(-10,5,501)
params = {'ARD_L2_log_': np.array(2.5, dtype=np.float32),
         'COV_SE_log_': np.array([ 2, -3 ], dtype=np.float32),
         'Mean_Constant': np.array(3.0, dtype=np.float32),
         'T_Shift': np.array(0.0, dtype=np.float32),
         'T_Power_log_': np.array(-0.4, dtype=np.float32),
       }
trans, dtrans, dist_gp, dist_tgp = g3.compile_distributions(model.point_to_In(params))
params
```

```python
grilla = np.linspace(-5,15,501)
gp.style_normal()
plt.figure(1,(10,10))

desfase = 5

yyy = np.zeros_like(grilla)
i = 0
for xxx in grilla:
    yyy[i] = dist_gp(np.array([xxx]))
    i+=1
plt.plot(grilla,yyy*50-desfase,label='GaussianDist')

plt.plot(trans(grilla),grilla,label='BoxCoxTrans')

yyy = np.zeros_like(grilla)
i = 0
for xxx in trans(grilla):
    yyy[i] = dist_gp(np.array([xxx]))
    i+=1
plt.plot(yyy*30-desfase,grilla,label='BoxCox(Gaussian)')

i = 0
for xxx in grilla:
    yyy[i] = dtrans(np.array([xxx]))
    i+=1
plt.plot((yyy-np.min(yyy))-desfase,grilla,label='Jacobian(BoxCox)')

i = 0
for xxx in grilla:
    yyy[i] = dist_tgp(np.array([xxx]))
    i+=1
plt.plot(yyy*40-desfase,grilla,label='BoxCox-GaussianDist')

#location_gp = grilla[np.argmax(yyy*100)]
#min_gp = np.min(yyy*100)
#max_gp = trans(np.array([location_gp]))
#plt.plot(np.array([location_gp,location_gp]),np.array([min_gp,max_gp]))

gp.plot('Box-Cox Change of Variable','GP','Box-Cox GP',loc=1,ncol=1)
plt.axis([-5,12,-5,12])
gp.save('change_of_variable.pdf')
```

```python
import theano.sandbox.linalg as sT
trans = tt.log(sT.det(tt.jacobian(g3.mapping.inv(value),value)))
ftrans = th.function([value]+model.point_to_In(init),trans,allow_input_downcast=True)
yyy = np.zeros_like(grilla)
i = 0
for xxx in grilla:
    yyy[i] = ftrans(np.array([xxx]))
    i+=1
plt.plot(grilla,yyy)
```

```python
value,dist = gp.marginal_tgp(np.array([0.0]))
fdist = th.function([value]+gp_model.point_to_In(start_bfgs),dist,allow_input_downcast=True)
grilla = np.linspace(-200,200,301)
yyy = np.zeros_like(grilla)
i = 0
for xxx in grilla:
    yyy[i] = fdist(np.array([xxx]))
    i+=1
plt.plot(grilla,yyy)
```

# Plot Cov

```python
ker=LaplaceKernel(noise,h,np.array([l]))
plt.matshow(ker.cov(x_obs[:,np.newaxis]))
plt.matshow(sp.linalg.inv(ker.cov(x_obs[:,np.newaxis])))
plt.matshow(CovCholesky(ker.cov(x_obs[:,np.newaxis])))
plt.matshow(CovCholesky(ker.cov(x_obs[:,np.newaxis])).T)
```

```python
#Graficar Covarianza
plt.figure(1, figsize=(20,6))
Kxx=InfoMatrix(GaussKer,np.expand_dims(np.arange(-2,2,0.1),1))

plt.subplot(121)
v=np.max(np.abs(Kxx))
plt.imshow(Kxx,cmap=cm.seismic,vmax=v,vmin=-v)
plt.colorbar()

#Graficar Precision
plt.subplot(122)
Jxx=np.linalg.solve(Kxx,np.eye(len(Kxx)))
v=np.max(np.abs(Jxx))
plt.imshow(Jxx,cmap=cm.seismic,vmax=v,vmin=-v)
plt.colorbar()

plt.figure(2, figsize=(20,6))
plt.plot(Kxx[len(Kxx)/2,:],'b')
plt.plot(Jxx[len(Jxx)/2,:],'r')

#plt.axis([550,650,-0.1,14])
#show(np.linalg.norm(Jxx.dot(Kxx)- np.eye(len(Kxx))))
```

# GP 2D
http://localhost:8888/notebooks/GaussianProcesses/BoxCoxGP/Presentation-TGP.ipynb

```python
# Parametros grilla
x = np.linspace(0, 50, 51)
y = np.linspace(0,200, 201)

# Parametros GP
l = 30
sigma = 10*np.log(1.5)
gamma_x = 0.0*np.log(2.0/l**2)
gamma_y = 0.8*np.log(2.0/l**2)

x2d, y2d = np.meshgrid(x, y)
xy = np.zeros((len(x)*len(y),2))
for i in range(len(x)):
    for j in range(len(y)):
        xy[i*len(y)+j,:] = x[i],y[j]
x2d = x2d.T
y2d = y2d.T

Kerxy = GaussianKernel(-np.inf,sigma,np.array([gamma_x,gamma_y]))
fxy2d_hidden = Kerxy.sample(xy).reshape((len(x),len(y)))

fig = plt.figure(1,[20,10])
ax = fig.gca(projection='3d')
ax.plot_surface(x2d, y2d, fxy2d_hidden, alpha=0.4,cmap=cm.RdBu_r)
cset = ax.contourf(x2d, y2d, fxy2d_hidden, zdir='z', offset=np.min(fxy2d_hidden), cmap=cm.RdBu_r)
```

# Sympy

```python
%matplotlib inline
import sympy as sy
import seaborn as sb
import matplotlib.pyplot as plt
from IPython.display import display

from sympy import init_printing
from sympy import symbols,diff,log,sign,Abs
from sympy.plotting import plot

init_printing()
plt.rcParams['figure.figsize'] = (20, 10)
#init_session(quiet=True)

x,y = symbols('x,y',real=True)

```

```python
phi = sign(x)*log(Abs(x))
dphi = diff(phi,x)
phi_1 = sy.solve(phi-y,x,domain=sy.S.Reals)
```

```python
display(phi)
display(dphi)
display(phi_1)
```

```python
plot(phi,(x,-2,2))
```

```python
plot(dphi,(x,-2,2))
```

```python

```

```python

```


# Finite Elements Lab 2 Worksheet

##### Georges Limbert

###### nCATS, Faculty of Engineering and the Environment, University of Southampton

```
from IPython.core.display import HTML
css_file = '../ipython_notebook_styles/ngcmstyle.css'
HTML(open(css_file, "r").read())
```

<link href='http://fonts.googleapis.com/css?family=Open+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=Nixie+One' rel='stylesheet' type='text/css'>
<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 1000px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1200px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Open Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:900px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Arvo', serif;
    font-style:regular;
    font-weight: 400;
    font-size: 45pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}
.text_cell_render h2 {
    font-family: 'Arvo', serif;
    font-weight: 400;
    font-size: 30pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Arvo', serif;
    margin-top:16px;
	font-size: 22pt;
    font-weight: 600;
    margin-bottom: 3px;
    font-style: regular;
    color: rgb(102,102,0);
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Arvo', serif;
    font-size: 14pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Arvo', sans-serif;
    font-weight: 400;
    font-size: 16pt;
    color: rgb(163,0,0);
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.8em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
        font-family: "PT Mono";
        font-size: 90%;
}

</style>

## Aims

The aim of this computer laboratory is to gain a practical insight into the fundamental steps required to program a simple finite element code to solve a problem of elasticity. In doing so, you will learn:

* How to recast the PDE problem into a variational form
* To derive the formulation of a 2D finite element
* To create a simple structured mesh generator to mesh a 2D domain
* To create an assembly procedure for the stiffness matrix of a 2D structure
* How to numerically solve the variational problem for a practical example

The numerical environment chosen for this laboratory is [IPython](http://ipython.org).

## Basic problem: linear elasticity

Let's consider a 2D solid body $\Omega $ at rest, subjected to body forces ${\bf{b}}$, prescribed displacements ${\bf{\bar d}}$ and tractions ${\bf{\bar t}}$. The problem of elasticity is defined as follows:

$$
\begin{align}
{\text{div}}\sigma  + {\bf{b}} & = {\bf{0}} & {\bf x}(x,y) &\text{ in } \Omega \\
{\bf{d}}(u,v) &= {\bf{\bar d}}(u,v) & {\bf x}(x,y) &\text{ on } \partial {\Omega
_u} \\
{\bf{t}}({\bf x}) &= {\bf{\bar t}}({\bf x}) & {\bf x}(x,y) &\text{ on }\partial {\Omega_\sigma}
\end{align}
$$

$\sigma $ is the symmetric Cauchy stress tensor.

### Question 1

Apply integration by parts to the PDE ${\text{div}}\sigma  + {\bf{b}} = {\bf{0}}$
with the divergence theorem (relation between the flow (flux) of a vector field
though a surface and the behavior inside the surface) recast it into its
variational form. You will also take into account the conditions about the
**test function** $\upsilon$ on the boundary where
${\bf{d}}$ is prescribed.

## Constituitive relationship

To relate stress and strain one must define what is called a
**constitutive relationship**. In other branches of physics other types of
relationships would be used (e.g. relation between strain and temperature).

In the case of isotropic linear elasticity (Hooke's elasticity) the Cauchy
stress tensor is related to the strain tensor $\varepsilon $ through the
following relationship:

$$
\begin{equation}
\sigma = \mathbb{C} : {\bf \varepsilon}
\end{equation}
$$

where $\mathbb{C}$ is the elasticity tensor.

Equation (2) is a **tensor equation** (valid in any coordinate system). If
one decides to use a Cartesian coordinate system (2) can be written in index
notation as:

$$
\begin{equation}
{\left( {\bf \sigma}  \right)_{ij}} = {\sigma _{ij}} = \left( \mathbb{C} : {\bf \varepsilon}
\right)_{ij} = \mathbb{C}_{ijkl} \varepsilon_{kl} \quad \left\{
{{\text{2D}}:i,j,k,l:1 \dots 2; \quad {\text{3D}}:i,j,k,l:1 \dots 3} \right\}
\end{equation}
$$

Please note that we use the following convention: bold characters represent
vectors, matrices or tensors.

Because the Cauchy, strain and elasticity tensors are symmetric and the material
isotropic one can simplify the tensorial constitutive equation into a matrix
equation involving vectors and asecond-order matrix.

$$
\begin{equation}
\left\{ {\bf \sigma}  \right\} = \left\{ \begin{array}{l}
{\sigma _{xx}}\\
{\sigma _{yy}}\\
{\sigma _{xy}}
\end{array} \right\} = \underbrace {\frac{E}{{1 - {\nu ^2}}}\left[
{\begin{array}{*{20}{c}}
1&\nu &0\\
\nu &1&0\\
0&0&{\frac{{1 - \nu }}{2}}
\end{array}} \right]}_{\left[ \mathbb{C} \right]}\underbrace {\left\{ \begin{array}{l}
{\varepsilon _{xx}}\\
{\varepsilon _{yy}}\\
2{\varepsilon _{xy}}
\end{array} \right\}}_{\left\{ {\bf \varepsilon} \right\}}
\end{equation}
$$

$\left\{ {E,\nu } \right\}$ are material properties, respectively the Young's
modulus and Poisson's ratio which characterise the stiffness and compressibility
of the material.

Let’s consider a 4-noded bi-linear interpolation element (Figure 1).

The shape functions chosen here are simple linear Lagrange polynomials but other
forms could be used (quadratic Lagrange, Hermite, NURBS, etc):

\begin{equation}
{N_i} = \frac{1}{4}(1 + {\xi_i} \xi )(1 + {\eta _i}\eta )
\quad {\xi _i},{\eta _i} =  - 1,1
\end{equation}

There are as many shape functions as nodes in the element, i.e. 4:

\begin{equation}
\left\{
\begin{array}{rl}
N_1 &= \frac{1}{4}\left( {1 - \xi } \right)\left( {1 - \eta } \right)\\
N_2 &= \frac{1}{4}\left( {1 + \xi } \right)\left( {1 - \eta } \right)\\
N_3 &= \frac{1}{4}\left( {1 + \xi } \right)\left( {1 + \eta } \right)\\
N_4 &= \frac{1}{4}\left( {1 - \xi } \right)\left( {1 + \eta } \right)
\end{array}
\right\}
\end{equation}

We are using an **isoparametric formulation** which means that the
**position vector** and **displacement vector** are interpolated using
the same order of shape (or interpolation) functions.

#### Interpolation of the position vector (discretisation)

$$
\begin{equation}
{\bf{x}}\left( {{\xi ^e},t} \right) = {{\bf{x}}_1}(t){N_1}\left( {{\xi ^e}}
\right) + {{\bf{x}}_2}(t){N_2}\left( {{\xi ^e}} \right) +
{{\bf{x}}_3}(t){N_3}\left( {{\xi ^e}} \right) + {{\bf{x}}_4}(t){N_4}\left( {{\xi
^e}} \right)
\end{equation}
$$

where

$$
\begin{equation}
{{\bf{x}}_A}(t) = {\rm{position}}\,(node\,A) = \left\{ \begin{array}{l}
{x_A}\\
{y_A}
\end{array} \right\}
\end{equation}
$$

$$
\begin{equation}
{\bf{x}}\left( {{\xi ^e},t} \right) = {\left\{ {\begin{array}{*{20}{c}}
{x(t)}\\
{y(t)}
\end{array}} \right\}_{node\,1}}{N_1}\left( {{\xi ^e}} \right) + {\left\{
{\begin{array}{*{20}{c}}
{x(t)}\\
{y(t)}
\end{array}} \right\}_{node\,2}}{N_2}\left( {{\xi ^e}} \right) + {\left\{
{\begin{array}{*{20}{c}}
{x(t)}\\
{y(t)}
\end{array}} \right\}_{node\,3}}{N_3}\left( {{\xi ^e}} \right) + {\left\{
{\begin{array}{*{20}{c}}
{x(t)}\\
{y(t)}
\end{array}} \right\}_{node\,4}}{N_4}\left( {{\xi ^e}} \right)
\end{equation}
$$

#### Interpolation of the displacement vector (discretisation)

$$
\begin{equation}
{\bf{d}}\left( {{\xi ^e},t} \right) = {{\bf{d}}_1}(t){N_1}\left( {{\xi ^e}}
\right) + {{\bf{d}}_2}(t){N_2}\left( {{\xi ^e}} \right) +
{{\bf{d}}_3}(t){N_3}\left( {{\xi ^e}} \right) + {{\bf{d}}_4}(t){N_4}\left( {{\xi
^e}} \right)
\end{equation}
$$

where

$$
\begin{equation}
{{\bf{d}}_A}(t) = {\rm{displacement}}\,(node\,A) = \left\{ \begin{array}{l}
{u_A}\\
{v_A}
\end{array} \right\}
\end{equation}
$$

## Degrees of freedom

We can arrange all the degrees of freedom of the element in one vector:

$$
\begin{equation}
{\bf{d}} = {\{ {u_1},{v_1},{u_2},{v_2},{u_3},{v_3},{u_4},{v_4}\} ^T}
\end{equation}
$$

The discretised versions of the position and displacement vector can be
rewritten in matrix form:

$$
\begin{equation}
{\bf{x}}\left( {{\xi ^e},t} \right) = {N_I}\left( {{\xi ^e}}
\right){{\bf{x}}_I}(t) = {{\bf{N}}^T}\left( {{\xi ^e}} \right){{\bf{x}}_I}(t)
\end{equation}
$$

$$
\begin{equation}
{\bf{d}}\left( {{\xi ^e},t} \right) = {N_I}\left( {{\xi ^e}}
\right){{\bf{d}}_I}(t) = {{\bf{N}}^T}\left( {{\xi ^e}} \right){{\bf{d}}_I}(t)
\end{equation}
$$

where

$$
\begin{equation}
{{\bf{N}}^T}\left( {{\xi ^e}} \right) = \left\{ {{N_1}({\xi ^e}),{N_2}({\xi
^e}),{N_3}({\xi ^e}),{N_4}({\xi ^e})} \right\}
\end{equation}
$$

The test function used for the formulation of the weak form is also discretised:

$$
\begin{equation}
\upsilon \left( {{\xi ^e},t} \right) = {N_I}\left( {{\xi ^e}} \right){\upsilon
_I}(t) = {{\bf{N}}^T}\left( {{\xi ^e}} \right){\upsilon _I}(t)
\end{equation}
$$

### Question 2

Inject the discretised version of the displacement into the weak form established in Question 1.
You will have to express the strain matrix using the ${\bf B}$ matrix (derivatives of shape functions).
You should obtain 3 integral matrix/vector forms: the **element stiffness matrix**, the **nodal load vector due to body forces** and the **nodal load vector due to boundary forces**.

## Integration

The integral expressions obtained in **Question 2** will have to be
numerically integrated using, in our case, Gauss integration. We have integrals
defined over the element domain ${\Omega ^e}$ and its boundary $\partial {\Omega
^e}$.

There are well known integration formulas over parametric domains that are exact
for polynomials like the Lagrange polynomials used in the shape functions. The
integral of a function is transformed into the sum of **weighted values
that the function takes at specific points**, the so-called **integration
points** or **Gauss points**.

$$
\begin{equation}
\int\limits_ {f(\xi )\,\,} d \xi = {w_i}f({\xi _i})
\end{equation}
$$

These integration formulas apply to the **parent element** (Figure 1) which is the element in the parametric domain $ = \{ \{  - 1,1\} ,\{ - 1,1\} \} $ so it is necessary to map back the parent element domain to the current (deformed) domain. This is a simple change of variables:

$$
\begin{equation}
d{\Omega _e} = dx\,dy = \det ({\bf{J}})\,d\xi\, d\eta
\end{equation}
$$

$$
\begin{equation}
\int\limits_{{\Omega^{e}}} {f({\bf{x}})\,\,} d\Omega  = \int\limits_
{f({\bf{x}}({\xi ^e}))\,\underbrace {\det \left( {\frac{{\partial
{\bf{x}}}}{{\partial {\xi ^e}}}} \right)}_{{J_{{\xi ^e}}} = \det ({\bf{J}})}\,} d \xi
= {w_i}{J_{{\xi ^e}}}({\xi ^e}_i)f({\xi ^e}_i)
\end{equation}
$$

Here, we will use a 2 points Gauss integration rule: i.e. 4 integration points for the quadrilateral element:

$$
\begin{align}
{{\bf{G}}_1} &= \left\{ { - \frac{1}{{\sqrt 3 }}, - \frac{1}{{\sqrt 3 }}}
\right\} & {w_1} &= 1\\
{{\bf{G}}_2} &= \left\{ {\frac{1}{{\sqrt 3 }}, - \frac{1}{{\sqrt 3 }}} \right\} &
{w_2} &= 1\\
{{\bf{G}}_3} &= \left\{ {\frac{1}{{\sqrt 3 }},\frac{1}{{\sqrt 3 }}} \right\} &
{w_3} &= 1\\
{{\bf{G}}_4} &= \left\{ { - \frac{1}{{\sqrt 3 }},\frac{1}{{\sqrt 3 }}} \right\} &
{w_4} &= 1
\end{align}
$$

### Question 3

1. Inject the integration formula into the integral matrix equations obtained in Question 2.
2. Write a Python programme to:

   1. calculate the stiffness matrix of the element and nodal force vector;
   2. generate a structured grid over a 2D rectangle domain (mesh generator);
   3. specify a uniform force at each node of one side of the rectangle domain;
   4. specify a uniform displacement at each node of one side of the rectangle domain;
   5. assemble the global stiffness matrix and force vector of the whole rectangular structure.

### Question 4

Using the Python programme from question 3, solve the following problem.

Consider a 2D rectangular solid domain (length $L_x = 1m$; height $L_y = 0.5m$) with material properties $\{ E = 210{\rm{ GPa}};\,\,\,\nu  = 0.3\} $ and sides labelled (counter-clockwise, starting from the left vertical side) as A, B, C and D.

The boundary conditions are as follows:

\begin{equation}
  u({\text{side}}\,{\text{A}}) = v({\text{side}}\,{\text{B}}) = 0
\end{equation}

The load condition is:

\begin{equation}
{\bf{F}}({\text{side C}})[Newton] = \left\{ \begin{array}{l}
{F_x}({\text{side C}})\\
{F_y}({\text{side C}})
\end{array} \right\} = \left\{ \begin{array}{l}
{10^6}\\
0
\end{array} \right\}
\end{equation}

**The boundary and loading conditions are homogeneous and therefore do not
introduce shear coupling. The Cauchy stress tensor has only one non-null
component, ${\sigma _{xx}}$.**

**NB:** the total load of 10$^{6}$ N must be uniformly distributed over all
the nodes of side C.

1. Solve the boundary-value problem of elasticity described above to calculate
the displacement of faces D and C. **You will consider 2 mesh sizes:** 1 for
which the whole structure is meshed *with one element only* and one size
of your choice.
2. Compare the solutions to the exact analytical solutions given by ${\sigma
_{xx}} = {F_x}/{L_y}$.

The strains can be calculated as

$$
\begin{align}
{\varepsilon _{xx}} = {\sigma _{xx}}/E =
{F_x}/E{L_y} &=  - {\varepsilon _{yy}}/\nu \\  {\varepsilon _{xy}} &= 0
\end{align}
$$

and the displacements as

$$
\begin{align}
  u &= {\varepsilon _{xx}}{L_x} \\ v &= {\varepsilon _{yy}}{L_y}.
\end{align}
$$

Do the results surprise you? Why?

FEEG6016 Simulation and Modelling/2014/Finite Elements Lab 2.ipynb


# Car Rental 1

## Objective and Prerequisites

Boost your modeling skills with this example, which will teach you how you can use mathematical optimization to figure out how many cars a car rental company should own and where they should be located every day to maximize weekly profits.

This model is example 25 from the fifth edition of Model Building in Mathematical Programming by H. Paul Williams on pages 284-286 and 340-342.

This example is at the intermediate level, where we assume that you know Python and the Gurobi Python API and that you have some knowledge of building mathematical optimization models.

**Download the Repository**

You can download the repository containing this and other examples by clicking [here](https://github.com/Gurobi/modeling-examples/archive/master.zip).

**Gurobi License**

In order to run this Jupyter Notebook properly, you must have a Gurobi license. If you do not have one, you can request an [evaluation license](https://www.gurobi.com/downloads/request-an-evaluation-license/?utm_source=3PW&utm_medium=OT&utm_campaign=WW-MU-MUI-OR-O_LEA-PR_NO-Q3_FY20_WW_JPME_CAR_RENTAL1_COM_EVAL_GitHub&utm_term=Car_Rental1&utm_content=C_JPM) as a *commercial user*, or download a [free license](https://www.gurobi.com/academia/academic-program-and-licenses/?utm_source=3PW&utm_medium=OT&utm_campaign=WW-MU-EDU-OR-O_LEA-PR_NO-Q3_FY20_WW_JPME_CAR_RENTAL1_ACADEMIC_EVAL_GitHub&utm_term=Car_Rental1&utm_content=C_JPM) as an *academic user*.

---
## Problem Description

A small car rental company (which rents only one type of car) has depots in Glasgow, Manchester, Birmingham and Plymouth. There is an estimated demand for each day of the week except Sunday (when the company is closed). These estimates are given in the following table. It is not necessary to meet all demand.

Cars can be rented for one, two or three days and returned to either the
depot from which they were rented or another depot at the start of the next morning. For
example, a 2-day rental on Thursday means that the car has to be returned on
Saturday morning; a 3-day rental on Friday means that the car has to be returned
on Tuesday morning. A 1-day rental on Saturday means that the car has to be
returned on Monday morning, and a 2-day rental on Tuesday morning.

The rental period is independent of the origin and destination. From historical
data, the company knows the distribution of rental periods: 55% of cars are hired
for one day, 20% for two days, and 25% for three days. The current estimates
of percentages of cars rented from each depot and returned to a given depot
(independent of the day) are given in the following table.

The company's marginal cost of renting out a car (‘wear and tear’, administration, etc.) is estimated as follows:

| Days rented | Marginal cost |
| --- | --- |
| 1-day | $\$ 20$ |
| 2-day | $\$ 25$ |
| 3-day | $\$ 30$ |

The ‘opportunity cost’ (interest on capital, storage, servicing, etc.) of owning
a car is $\$ 15$ per week.

It is possible to transfer undamaged cars from one depot to another depot,
irrespective of distance. Cars cannot be rented out during the day in which they
are transferred. The costs in USD, per car, of transfer are given in the following table.

Ten percent of cars returned by customers are damaged. When this happens,
the customer is charged an excess of $\$ 100$  (irrespective of the amount of damage
that the company completely covers by its insurance). In addition, the car has
to be transferred to a repair depot, where it will be repaired the following day.
The cost of transferring a damaged car is the same as transferring an undamaged
one (except when the repair depot is the current depot,
in which case the cost would be $\$0$). The transfer of a damaged car takes a day, unless it is already at a repair depot.
Having arrived at a repair depot, all types of repair (or replacement) take a day.
Only two of the depots have repair capacity. The (cars/day) capacity at each repair depot is as follows:

| Repair depot | Capacity |
| --- | --- |
| Manchester | 12 |
| Birmingham | 20 |

Having been repaired, the car is available for rent at the depot the next day
or may be transferred to another depot (taking a day). Thus, a car that is returned
damaged on a Wednesday morning is transferred to a repair depot (if not the
current depot) on Wednesday, repaired on Thursday, and is available for rent
at the repair depot on Friday morning.
The rental price depends on the number of days for which the car is rented
and whether it is returned to the same depot or not. The prices (in USD) are given in the following table.

We assume the following at the beginning of each day:
1. Customers return cars that are due that day.
2. Damaged cars are sent to the repair depot.
3. Cars that were transferred from other depots arrive.
4. Transfers are sent out.
5. Cars are rented out.
6. If it is a repair depot, then the repaired cars are available for rental.

The goal is to determine the numbers of cars the car rental company should own and where should they be located at the start of each week day in order to maximize weekly profit. The company wants a ‘steady state’ solution
in which the same expected number of cars will be located at the same depot on
the same day of subsequent weeks.

---
## Model Formulation

$d,d2 \in \text{Depots}=\{\text{Glasgow}, \text{Manchester}, \text{Birmingham},  \text{Plymouth}\}$

$\text{NRD}=\{\text{Glasgow}, \text{Plymouth}\}$: Depots without repair capacity.

$\text{RD}=\{\text{Manchester}, \text{Birmingham}\}$: Depots with repair capacity.

$t \in \text{Days}=\{\text{Monday},\text{Tuesday},\text{Wednesday},\text{Thursday},\text{Friday},\text{Saturday}\}$

$r \in \text{RentDays}=\{1,2,3\}$: Number of days rented.

### Parameters

$\text{demand}_{d,t} \in \mathbb{R}^+$: Estimated rental demand at depot $d$ on day $t$.

$\text{pctDepot}_{d,d2} \in \mathbb{R}^+$: Proportion of cars rented at depot $d$ to be returned to depot $d2$.

$\text{cstTransfer}_{d,d2} \in \mathbb{R}^+$: Transfer cost of a car from depot $d$ to depot $d2$.

$\text{pctRent}_{r} \in \mathbb{R}^+$: Proportion of cars rented for $r$ days.

$\text{capRepair}_{d} \in \mathbb{R}^+$: Repair capacity of depot $d$.

$\text{cstSameDepot}_{r} \in \mathbb{R}^+$: Rental cost for $r$ days with return to same depot.

$\text{cstOtherDepot}_{r} \in \mathbb{R}^+$: Rental cost for $r$ days with return to other depot.

$\text{marginalCost}_{r} \in \mathbb{R}^+$: Marginal cost to company of  $r$ days rental of a car.

$\text{pctUndamaged } \in [0,1]$: Percent of cars returned by customers that are undamaged.

$\text{pctDamaged  } \in [0,1]$: Percent of cars returned by customers that are damaged.

$\text{cstOwn} \in \mathbb{R}^+$: Cost of owning a car.

$\text{damagedFee} = 10$: Damaged car fee. Ten percent of the cars are damaged and the fee for a damaged car is $\$100$.

### Decision Variables

$\text{xOwned} \in \mathbb{R}^+$: Total number of cars owned.

$\text{xUndamaged}_{d,t} \in \mathbb{R}^+$: Number of undamaged cars available at depot $d$ at the beginning of day $t$.

$\text{xDamaged}_{d,t} \in \mathbb{R}^+$: Number of damaged cars available at depot $d$ at the beginning of day $t$.

$\text{xRented}_{d,t} \in \mathbb{R}^+$: number of cars rented  out from depot $d$ at the beginning of day $t$.

$\text{xUDleft}_{d,t} \in \mathbb{R}^+$: Number of undamaged cars available at depot $d$ at the beginning of day $t$.

$\text{xDleft}_{d,t} \in \mathbb{R}^+$: Number of damaged cars left in depo $d$ at the end of day  $t$.

$\text{xUDtransfer}_{d,d2,t} \in \mathbb{R}^+$: Number of undamaged cars  at depot $d$ at the beginning of day $t$, to be transferred to depot $d2$.

$\text{xDtransfer}_{d,d2,t} \in \mathbb{R}^+$: Number of damaged cars at depot $d$ at the beginning of day $t$, to be transferred to depot $d2$.

$\text{xRepaired}_{d,t} \in \mathbb{R}^+$: Number of damaged cars to be repaired at depot $d$ during day $t$.

### Objective function
The objective is to maximize profit.

\begin{equation}
\sum_{d \in \text{Depots}}
\sum_{t \in \text{Days}}
\sum_{r \in \text{RentDays}}
\text{pctDepot}_{d,d}*\text{pctRent}_{r}*(\text{cstSameDepot}_{r} - \text{marginalCost}_{r} + \text{damagedFee})*\text{xRented}_{d,t}
\end{equation}

\begin{equation}
+ \sum_{d \in \text{Depots}} \sum_{d2 \in \text{Depots}}
\sum_{t \in \text{Days}}
\sum_{r \in \text{RentDays}}
\text{pctDepot}_{d,d2}*\text{pctRent}_{r}*(\text{cstOtherDepot}_{r} - \text{marginalCost}_{r} + \text{damagedFee})*\text{xRented}_{d,t}
\end{equation}

\begin{equation}
- \sum_{d \in \text{Depots}} \sum_{d2 \in \text{Depots}}
\sum_{t \in \text{Days}} \text{cstTransfer}_{d,d2}*(\text{xUDtransfer}_{d,d2,t} + \text{xDtransfer}_{d,d2,t} )
- \text{cstOwn}*\text{xOwned}
\end{equation}

### Constraints

**Undamaged cars at a non-repair depot**

Number of undamaged cars available at a non-repair depot $d$ at the beginning of day $t$.

\begin{equation}
\sum_{d2 \in \text{Depots}}
\sum_{r \in \text{RentDays}} \text{pctUndamaged}*\text{pctDepot}_{d2,d}*\text{pctRent}_{r}*\text{xRented}_{d2,(t-r)mod(6)}
\end{equation}

\begin{equation}
+ \sum_{d2 \in \text{Depots}} \text{xUDtransfer}_{d2,d,(t-1)mod(6)} + \text{xUDleft}_{d,(t-1)mod(6)} = \text{xUndamaged}_{d,t}
\quad \forall d \in NRD, t \in Days
\end{equation}

Demand of undamaged cars at the non-repair depot $d$ during day $t$.

\begin{equation}
\text{xUndamaged}_{d,t} = \text{xRented}_{d,t} +
\sum_{d2 \in \text{Depots}} \text{xUDtransfer}_{d,d2,t} + \text{xUDleft}_{d,t}
\quad \forall d \in NRD, t \in Days
\end{equation}

**Undamaged cars at a repair depot**

Number of undamaged cars available at a repair depot $d$ at the beginning of day $t$.

\begin{equation}
\sum_{d2 \in \text{Depots}}
\sum_{r \in \text{RentDays}} \text{pctUndamaged}*\text{pctDepot}_{d2,d}*\text{pctRent}_{r}*\text{xRented}_{d2,(t-r)mod(6)}
\end{equation}

\begin{equation}
+ \sum_{d2 \in \text{Depots}} \text{xUDtransfer}_{d2,d,(t-1)mod(6)}
+ \text{xRepaired}_{d,(t-1)mod(6)}  + \text{xUDleft}_{d,(t-1)mod(6)} = \text{xUndamaged}_{d,t}
\quad \forall d \in NRD, t \in Days
\end{equation}

Demand of undamaged cars at the repair depot $d$ during day $t$.

\begin{equation}
\text{xUndamaged}_{d,t} = \text{xRented}_{d,t} +
\sum_{d2 \in \text{Depots}} \text{xUDtransfer}_{d,d2,t} + \text{xUDleft}_{d,t}
\quad \forall d \in NRD, t \in Days
\end{equation}

**Damaged cars at a non-repair depot**

Number of damaged cars available at a non-repair depot $d$ at the beginning of day $t$.

\begin{equation}
\sum_{d2 \in \text{Depots}}
\sum_{r \in \text{RentDays}} \text{pctDamaged}*\text{pctDepot}_{d2,d}*\text{pctRent}_{r}*\text{xRented}_{d2,(t-r)mod(6)}
\end{equation}

\begin{equation}
+ \text{xDleft}_{d,(t-1)mod(6)} = \text{xDamaged}_{d,t} \quad \forall d \in NRD, t \in Days
\end{equation}

Demand of undamaged cars at the non-repair depot $d$ during day $t$.

\begin{equation}
\text{xDamaged}_{d,t} =
\sum_{d2 \in \text{Depots} \cap RD} \text{xDtransfer}_{d,d2,t} + \text{xDleft}_{d,t}
\quad \forall d \in NRD, t \in Days
\end{equation}

**Damaged cars at a repair depot**

Number of damaged cars available at a repair depot $d$ at the beginning of day $t$.

\begin{equation}
\sum_{d2 \in \text{Depots}}
\sum_{r \in \text{RentDays}} \text{pctDamaged}*\text{pctDepot}_{d2,d}*\text{pctRent}_{r}*\text{xRented}_{d2,(t-r)mod(6)}
\end{equation}

\begin{equation}
+ \sum_{d2 \in \text{Depots}} \text{xDtransfer}_{d2,d,(t-1)mod(6)}
+ \text{xDleft}_{d,(t-1)mod(6)} = \text{xdamaged}_{d,t}
\quad \forall d \in RD, t \in Days
\end{equation}

Demand of undamaged cars at the non-repair depot $d$ during day $t$.

\begin{equation}
\text{xDamaged}_{d,t} = \text{xRepaired}_{d,t} +
\sum_{d2 \in \text{Depots} \cap RD} \text{xDtransfer}_{d,d2,t} + \text{xDleft}_{d,t}
\quad \forall d \in RD, t \in Days
\end{equation}

**Depot Capacity**

Repair capacity of depot $d$ for each day $t$.

\begin{equation}
\text{xRepaired}_{d,t} \leq \text{capRepair}_{d}
\quad \forall d \in Depots, t \in Days
\end{equation}

**Depot Demand**

Demand at depot $d$ for each day $t$.

\begin{equation}
\text{xRented}_{d,t} \leq \text{demand}_{d,t}
\quad \forall d \in Depots, t \in Days
\end{equation}

**Number of cars**

Total number of cars owned equals number of cars rented out from all depots on Monday for 3 days, plus those on Tuesday for 2 or 3 days, plus all damaged and undamaged cars in depots at the beginning of Wednesday.
Rationale: Let’s pick a day (Wednesday), count the cars undamaged and damaged that were returned to the depots and that are available on Wednesday morning. Let’s count the cars that have been rented and have not been returned: Cars rented on Monday for 3 days, and cars rented on Tuesday for 2 or 3 days.

\begin{equation}
\sum_{d \in \text{Depots}} (0.25*\text{xRented}_{d,0} + 0.45*\text{xRented}_{d,1} + \text{xUndamaged}_{d,2}  + \text{xdamaged}_{d,2} ) = \text{xOwned}
\end{equation}

---
## Python Implementation

We import the Gurobi Python Module and other Python libraries.

```python
import numpy as np
import pandas as pd
from itertools import product

import gurobipy as gp
from gurobipy import GRB

# tested with Python 3.7.0 & Gurobi 9.0
```

## Input data

We define all the input data for the model.

```python
# list of depots and working days of a week

depots = ['Glasgow','Manchester','Birmingham','Plymouth']
NRD = ['Glasgow','Plymouth'] # Non-repair depot
RD =['Manchester','Birmingham'] # Repair depot

days = [0,1,2,3,4,5] # Monday = 0, Tuesday = 1, ...  Saturday = 5
rentDays = [1,2,3]

d2w, demand = gp.multidict({
    ('Glasgow',0): 100,
    ('Glasgow',1): 150,
    ('Glasgow',2): 135,
    ('Glasgow',3): 83,
    ('Glasgow',4): 120,
    ('Glasgow',5): 230,
    ('Manchester',0): 250,
    ('Manchester',1): 143,
    ('Manchester',2): 80,
    ('Manchester',3): 225,
    ('Manchester',4): 210,
    ('Manchester',5): 98,
    ('Birmingham',0): 95,
    ('Birmingham',1): 195,
    ('Birmingham',2): 242,
    ('Birmingham',3): 111,
    ('Birmingham',4): 70,
    ('Birmingham',5): 124,
    ('Plymouth',0): 160,
    ('Plymouth',1): 99,
    ('Plymouth',2): 55,
    ('Plymouth',3): 96,
    ('Plymouth',4): 115,
    ('Plymouth',5): 80
})

#repairCap
depots, capacity = gp.multidict({
    ('Glasgow'): 0,
    ('Manchester'): 12,
    ('Birmingham'): 20,
    ('Plymouth'): 0
})

# Create a dictionary to capture
# pctRent: percentage of cars rented for r days
# cstMarginal: marginal cost for renting a car for r days
# prcSameD: price of renting a car r days and returning to same depot
# prcOtherD: price of renting a car r days and returning to another depot
rentDays, pctRent, costMarginal, priceSameD, priceOtherD = gp.multidict({
    (1): [0.55,20,50,70],
    (2): [0.20,25,70,100],
    (3): [0.25,30,120,150]
})

# Cost of owing a car per week.
cstOwn = 15

# Proportional damaged car fee
damagedFee = 10

# Create a dictionary to capture the proportion of cars rented at depot d to be returned to depot d2
d2d, pctFromToD = gp.multidict({
    ('Glasgow','Glasgow'): 0.6,
    ('Glasgow','Manchester'): 0.2,
    ('Glasgow','Birmingham'): 0.1,
    ('Glasgow','Plymouth'): 0.1,
    ('Manchester','Glasgow'): 0.15,
    ('Manchester','Manchester'): 0.55,
    ('Manchester','Birmingham'): 0.25,
    ('Manchester','Plymouth'): 0.05,
    ('Birmingham','Glasgow'): 0.15,
    ('Birmingham','Manchester'): 0.2,
    ('Birmingham','Birmingham'): 0.54,
    ('Birmingham','Plymouth'): 0.11,
    ('Plymouth','Glasgow'): 0.08,
    ('Plymouth','Manchester'): 0.12,
    ('Plymouth','Birmingham'): 0.27,
    ('Plymouth','Plymouth'): 0.53
})

# Create a dictionary to capture the transfer costs  of cars
d2d, cstFromToD = gp.multidict({
    ('Glasgow','Glasgow'): 0.001,
    ('Glasgow','Manchester'): 20,
    ('Glasgow','Birmingham'): 30,
    ('Glasgow','Plymouth'): 50,
    ('Manchester','Glasgow'): 20,
    ('Manchester','Manchester'): 0.001,
    ('Manchester','Birmingham'): 15,
    ('Manchester','Plymouth'): 35,
    ('Birmingham','Glasgow'): 30,
    ('Birmingham','Manchester'): 15,
    ('Birmingham','Birmingham'): 0.001,
    ('Birmingham','Plymouth'): 25,
    ('Plymouth','Glasgow'): 50,
    ('Plymouth','Manchester'): 35,
    ('Plymouth','Birmingham'): 25,
    ('Plymouth','Plymouth'): 0.001
})

# Proportion of undamaged and damaged cars returned
pctUndamaged = 0.9
pctDamaged = 0.1

```

### Preprocessing
We prepare the data structures to build the linear programming model.

```python
# Build a list of tuples (depot, depot2) such that d != d2
list_d2notd = []

for d,d2 in d2d:
    if (d != d2):
        tp = d,d2
        list_d2notd.append(tp)

d2notd = gp.tuplelist(list_d2notd)

# Build a list of tuples (depot, depot2, day)
list_dd2t = []

for d,d2 in d2notd:
    for t in days:
        tp = d,d2,t
        list_dd2t.append(tp)

dd2t = gp.tuplelist(list_dd2t)

# Build a list of tuples (depot, rent_day)
list_dr = []

for d in depots:
    for r in rentDays:
        tp = d,r
        list_dr.append(tp)

dr = gp.tuplelist(list_dr)

# Build a list of tuples (depot, day, rent_days )
list_dtr = []

for d in depots:
    for t in days:
            for r in rentDays:
                tp = d,t,r
                list_dtr.append(tp)

dtr = gp.tuplelist(list_dtr)

# Build a list of tuples (depot, depot2, day, rent_days)
list_dd2tr = []

for d,d2 in d2notd:
    for t in days:
        for r in rentDays:
            tp = d,d2,t,r
            list_dd2tr.append(tp)

dd2tr = gp.tuplelist(list_dd2tr)
```

## Model Deployment
We create a model and the variables. The main decision variables are the number of cars to own
and where should they be located at the start of each day of a week to maximize weekly profits.

```python
model = gp.Model('RentalCar1')

# Number of cars owned
n = model.addVar(name="cars")

# Number of undamaged cars
nu = model.addVars(d2w, name="UDcars")

# Number of damaged cars
nd = model.addVars(d2w, name="Dcars")

# Number of cars hired (rented) cannot exceed their demand
tr = model.addVars(d2w, ub=demand, name="Hcars")
#for d,t in d2w:
    #tr[d,t].lb = 1

# End inventory of undamaged cars
eu = model.addVars(d2w, name="EUDcars")

# End inventory of damaged cars
ed = model.addVars(d2w, name="EDcars")

# Number of undamaged cars transferred
tu = model.addVars(dd2t, name="TUDcars")

# Number of damaged cars transferred
td = model.addVars(dd2t, name="TDcars")

# Number of damaged cars repaired
rp = model.addVars(d2w, name="RPcars")

# Number of damaged cars repaired cannot exceed depot capacity
for d,t in d2w:
    rp[d,t].ub = capacity[d] #repair capacity
```

    Using license file c:\gurobi\gurobi.lic

### Constraints
The number of undamaged cars available at a non-repair depot d at the beginning of  day t should be equal to the demand of undamaged cars at the non-repair depot d during day t.

```python
# Undamaged cars into a non-repair depot constraints (left hand side of balance equation -availability)

UDcarsNRD_L = model.addConstrs((gp.quicksum(pctUndamaged*pctFromToD[d2,d]*pctRent[r]*tr[d2,(t-r)%6 ] for d2,r in dr )
                              + gp.quicksum(tu.select('*',d,(t-1)%6)  )
                              + eu[d,(t-1)%6 ] == nu[d,t] for d in NRD for t in days ),
                             name="UDcarsNRD_L")

# Undamaged cars out of a non-repair depot constraints (right hand side of balance equation -requirements)

UDcarsNRD_R = model.addConstrs((tr[d,t]
                                + gp.quicksum(tu.select(d,'*',t ))
                                + eu[d,t] == nu[d,t] for d in NRD for t in days ), name='UDcarsNRD_R' )
```

The number of undamaged cars available at a repair depot d at the beginning of  day t should be equal to the demand of undamaged cars at the repair depot d during day t.

```python
# Undamaged cars into a repair depot constraints (left hand side of balance equation -availability)

UDcarsRD_L = model.addConstrs((gp.quicksum(pctUndamaged*pctFromToD[d2,d]*pctRent[r]*tr[d2,(t-r)%6 ] for d2,r in dr )
                              + gp.quicksum(tu.select('*',d,(t-1)%6)  ) + rp[d, (t-1)%6 ]
                              + eu[d,(t-1)%6 ] == nu[d,t] for d in RD for t in days ),
                             name="UDcarsRD_L")

# Undamaged cars out of a repair depot constraints (right hand side of balance equation -requirements)

UDcarsRD_R = model.addConstrs((tr[d,t]
                                + gp.quicksum(tu.select(d,'*',t ) )
                                + eu[d,t] == nu[d,t] for d in RD for t in days ), name='UDcarsRD_R' )
```

The number of damaged cars available at a non-repair depot d at the beginning of  day t should be equal to the demand of damaged cars at the non-repair depot d during day t.

```python
# Damaged cars into a non-repair depot constraints (left hand side of balance equation -availability)

DcarsNRD_L = model.addConstrs((gp.quicksum(pctDamaged*pctFromToD[d2,d]*pctRent[r]*tr[d2,(t-r)%6 ] for d2,r in dr )
                              + ed[d,(t-1)%6 ] == nd[d,t] for d in NRD for t in days ),
                             name="DcarsNRD_L")

# Damaged cars out of a non-repair depot constraints (right hand side of balance equation -requirements)

DcarsNRD_R = model.addConstrs(( gp.quicksum(td[d,d2,t] for d2 in RD )
                                + ed[d,t] == nd[d,t] for d in NRD for t in days ), name='DcarsNRD_R' )
```

The number of damaged cars available at a repair depot d at the beginning of  day t should be equal to the demand of damaged cars at the repair depot d during day t.

```python
# Damaged cars into a repair depot constraints (left hand side of balance equation -availability)

DcarsRD_L = model.addConstrs((gp.quicksum(pctDamaged*pctFromToD[d2,d]*pctRent[r]*tr[d2,(t-r)%6 ] for d2,r in dr )
                              + gp.quicksum(td[d2,d,(t-1)%6 ] for d2, dd in d2notd if (dd == d))
                              + ed[d,(t-1)%6 ] == nd[d,t] for d in RD for t in days ),
                             name="DcarsRD_L")

# Damaged cars out of a repair depot constraints (right hand side of balance equation -requirements)

DcarsND_R = model.addConstrs((rp[d,t] + gp.quicksum(td[d,d2,t ] for d2 in NRD )
                                + ed[d,t] == nd[d,t] for d in RD for t in days ), name='DcarsND_R' )
```

Total number of cars equals the number of cars rented out from all depots on Monday for 3 days, plus those on Tuesday for 2 or 3 days, plus all damaged and undamaged cars in depots at the beginning of Wednesday.

```python
# Total number of cars owned constraint
# Note: 25% of cars are rented for 3 days, and 20% + 25% = 45% of the cars are rented for 2-days or 3-days

carsConstr = model.addConstr((gp.quicksum(0.25*tr[d,0] + 0.45*tr[d,1] + nu[d,2] + nd[d,2] for d in depots )
                              == n ),name='carsConstr')
```

The objective function is to maximize profit.

```python
# Maximize profit objective function

model.setObjective((
    gp.quicksum(pctFromToD[d,d]*pctRent[r]*(priceSameD[r] - costMarginal[r] + damagedFee)*tr[d,t] for d,t,r in dtr )
    + gp.quicksum(pctFromToD[d,d2]*pctRent[r]*(priceOtherD[r]-costMarginal[r]+damagedFee)*tr[d,t] for d,d2,t,r in dd2tr)
    - gp.quicksum(cstFromToD[d,d2]*tu[d,d2,t] for d,d2,t in dd2t)
    - gp.quicksum(cstFromToD[d,d2]*td[d,d2,t] for d,d2,t in dd2t) - cstOwn*n ), GRB.MAXIMIZE)
```

```python
# Verify model formulation

model.write('CarRental1.lp')

# Run optimization engine

model.optimize()
```

    Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (win64)
    Thread count: 4 physical cores, 8 logical processors, using up to 8 threads
    Optimize a model with 97 rows, 289 columns and 1061 nonzeros
    Model fingerprint: 0x7ddb81fe
    Coefficient statistics:
      Matrix range     [1e-03, 1e+00]
      Objective range  [2e+01, 7e+01]
      Bounds range     [1e+01, 3e+02]
      RHS range        [0e+00, 0e+00]
    Presolve removed 49 rows and 85 columns
    Presolve time: 0.01s
    Presolved: 48 rows, 204 columns, 936 nonzeros

    Iteration    Objective       Primal Inf.    Dual Inf.      Time
           0    2.1102412e+05   3.626166e+02   0.000000e+00      0s
          69    1.2116021e+05   0.000000e+00   0.000000e+00      0s

    Solved in 69 iterations and 0.01 seconds
    Optimal objective  1.211602072e+05

---
## Analysis

```python
# Output report

# Total number of cars owned
print(f"The optimal number of cars to be owned is: {round(n.x)}.")

# Optimal profit
print(f"The optimal profit is: {'${:,.2f}'.format(round(model.objVal,2))}.")

```

    The optimal number of cars to be owned is: 617.
    The optimal profit is: $121,160.21.

```python
# Create a list to translate the number label of each day to the actual name of the day
dayname = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']

# Number of undamaged cars in depot at the beginning of each day.
print("\n\n_________________________________________________________________________________")
print(f"Estimated number of undamaged cars in depot at the beginning of each day: ")
print("_________________________________________________________________________________")

undamaged_cars = pd.DataFrame(columns=['Day','Glasgow','Manchester','Birmingham','Plymouth'])
for t in days:
    undamaged_cars = undamaged_cars.append({"Day": dayname[t], "Glasgow": round(nu['Glasgow',t].x), "Manchester":  round(nu['Manchester',t].x), 'Birmingham':  round(nu['Birmingham',t].x),'Plymouth': round(nu['Plymouth',t].x)   }, ignore_index=True)
undamaged_cars.index=[''] * len(undamaged_cars)
undamaged_cars
```

    _________________________________________________________________________________
    Estimated number of undamaged cars in depot at the beginning of each day:
    _________________________________________________________________________________

      Day
      Glasgow
      Manchester
      Birmingham
      Plymouth

      Monday
      68
      98
      146
      41

      Tuesday
      66
      95
      155
      40

      Wednesday
      70
      100
      123
      43

      Thursday
      68
      114
      116
      42

      Friday
      70
      102
      124
      43

      Saturday
      67
      95
      158
      40

```python
# Number of Damaged cars in depot at the beginning of each day.
print("_________________________________________________________________________________")
print(f"Estimated number of damaged cars in depot at the beginning of each day: ")
print("_________________________________________________________________________________")

damaged_cars = pd.DataFrame(columns=['Day','Glasgow','Manchester','Birmingham','Plymouth'])
for t in days:
    damaged_cars = damaged_cars.append({"Day": dayname[t], "Glasgow": round(nd['Glasgow',t].x), "Manchester":  round(nd['Manchester',t].x), 'Birmingham':  round(nd['Birmingham',t].x),'Plymouth': round(nd['Plymouth',t].x)   }, ignore_index=True)
damaged_cars.index=[''] * len(damaged_cars)
damaged_cars
```

    _________________________________________________________________________________
    Estimated number of damaged cars in depot at the beginning of each day:
    _________________________________________________________________________________

      Day
      Glasgow
      Manchester
      Birmingham
      Plymouth

      Monday
      8
      12
      20
      6

      Tuesday
      7
      12
      20
      4

      Wednesday
      8
      13
      20
      5

      Thursday
      8
      12
      21
      5

      Friday
      8
      12
      20
      7

      Saturday
      7
      12
      22
      4

```python
# Undamaged car rented out from each depot and day.
print("_________________________________________________________________________________")
print(f"Estimated number of undamaged cars rented out from each depot and day: ")
print("_________________________________________________________________________________")

rentedOut = {}

for d in depots:
    for t in days:
        count = 0
        for d2 in depots:
            for r in rentDays:
                #print(f"Depot {d}, day {t}: cars rented out {tr[d,t].x}")
                count += pctUndamaged*pctFromToD[d,d2]*pctRent[r]*tr[d,t].x
        rentedOut[d,t] = round(count)

#print(rentedOut)

rentout_cars = pd.DataFrame(columns=['Day','Glasgow','Manchester','Birmingham','Plymouth'])
for t in days:
    rentout_cars = rentout_cars.append({"Day": dayname[t], "Glasgow": round(rentedOut['Glasgow',t]), "Manchester":  round(rentedOut['Manchester',t]), 'Birmingham':  round(rentedOut['Birmingham',t]),'Plymouth': round(rentedOut['Plymouth',t])   }, ignore_index=True)
rentout_cars.index=[''] * len(rentout_cars)
rentout_cars
```

    _________________________________________________________________________________
    Estimated number of undamaged cars rented out from each depot and day:
    _________________________________________________________________________________

      Day
      Glasgow
      Manchester
      Birmingham
      Plymouth

      Monday
      61
      89
      86
      37

      Tuesday
      59
      85
      140
      36

      Wednesday
      63
      72
      111
      39

      Thursday
      62
      103
      100
      38

      Friday
      63
      92
      63
      39

      Saturday
      60
      85
      112
      36

---
## References

H. Paul Williams, Model Building in Mathematical Programming, fifth edition.

Copyright © 2020 Gurobi Optimization, LLC

```python

```


# Calculation of the Feasible Joint Stiffness

A framework for computing the joint stiffness using inverse methods that account
for the musculoskeletal redundancy effects is presented. The process is
separated into five steps:

1. Perform inverse kinematics (IK) and static optimization (SO) using OpenSim to
generate the required data for the next steps

2. Extract the symbolic representation of the muscle moment arm from the OpenSim
model, where higher order derivatives are required for the calculation of the
stiffness

3. Calculate the feasible muscle forces that satisfy both the action and the
physiological constraints of the muscles

4. Calculate the feasible joint stiffness

```python
# notebook general configuration
%load_ext autoreload
%autoreload 2

import os
import pickle
import numpy as np
import sympy as sp
from IPython.display import display
sp.interactive.printing.init_printing()
from tqdm import tqdm
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (10.0, 6.0)
%matplotlib inline
```

    The autoreload extension is already loaded. To reload it, use:
      %reload_ext autoreload

## Subject Data

Please provide the following information for the subject under study:

- **.osim** the scaled OpenSim model (after scaling the model using a static
    pose)

- **.trc** the marker trajectories of the movement (based on OpenSim file
    requirements)

- **.mot** the ground reaction forces (based on the OpenSim file format)

- **.xml** describing how to apply the ground reaction forces (based on OpenSim
  force requirements)

- **.xml** file with the reserve actuator for the SO (based on OpenSim requirements)

- **heel strike** and **toe off** events (used for annotation of the results,
  and are not so important)

The following calculations are performed for the *Gait10dof18musc* dataset.

```python
# time events of toe off and heel strike
heel_strike_right = [0.65, 1.85]
toe_off_right = [0.15, 1.4]
heel_strike_left = [0.0, 1.25]
toe_off_left = [0.8, 2]

# required files and directories
subject_dir = os.getcwd() + '/../dataset/Gait10dof18musc/'
model_file = subject_dir + 'subject01.osim'
trc_file = subject_dir + 'subject01_walk.trc'
grf_file = subject_dir + 'subject01_walk_grf.mot'
grf_xml_file = subject_dir + 'subject01_walk_grf.xml'
reserve_actuators_file = subject_dir + 'reserve_actuators.xml'
results_dir = os.getcwd() + '/notebook_results/'
figures_dir = os.getcwd() + '/results/'

if not (os.path.isfile(model_file) and
        os.path.isfile(trc_file) and
        os.path.isfile(grf_file) and
        os.path.isfile(grf_xml_file) and
        os.path.isfile(reserve_actuators_file)):
    raise RuntimeError('required files do not exist')

if not (os.path.isdir(results_dir) and
        os.path.isdir(figures_dir)):
    raise RuntimeError('required folders do not exist')
```

## Step 1: Perform Required OpenSim Analyses

In order to perform the feasible muscle force calculations, the kinematics and
kinetics that satisfy the experimental measured motion and ground reaction
forces must be calculated. The following functions make use of the OpenSim IK
and SO analyses.

```python
from opensim_utils import perform_ik, perform_so, plot_sto

# perform OpenSim inverse kinematics
ik_file = perform_ik(model_file, trc_file, results_dir)
plot_sto(ik_file, 4, save=True)

# perform OpenSim static optimization
(so_force_file, so_activation_file) = perform_so(model_file, ik_file, grf_file,
                                                 grf_xml_file, reserve_actuators_file,
                                                 results_dir)
plot_sto(so_force_file, 4, save=False)

# store file names so that they can be loaded without running this section
pickle.dump((ik_file, so_force_file), file(results_dir + 'opensim_files.dat', 'w'))
```

```python
# get file names from step 1
(ik_file, so_force_file) = pickle.load(file(results_dir + 'opensim_files.dat', 'r'))
```

## Step 2: Extract Symbolic Moment Arm

Recall that in the definition of joint stiffness the muscle moment arm must be
differentiated with respect to the generalized coordinates. Unfortunately,
OpenSim lacks the analytical means for evaluating higher order derivatives, as
it computes the muscle moment arm numerically from the input joint
configuration. In order to derive a symbolic representation, multivariate
polynomial fitting was performed on samples of the muscle moment arm at
different configurations. To reduce the complexity and improve the robustness of
the fit, we determined the coordinates affecting each element in the moment arm
matrix, by identifying the DoFs spanned by each muscle.

```python
# since this section of code may be called independently after step 1 ensure
# that cells 2, 5 and 9 are evaluated
from IPython.display import Javascript
Javascript("Jupyter.notebook.execute_cells([2, 5, 9])")
```

```python
from analytic_moment_arm import calculate_spanning_muscle_coordinates,\
    calculate_moment_arm_symbolically

# calculate the coordinates that are spanned by each muscle (improves
# robustness)
calculate_spanning_muscle_coordinates(model_file, results_dir)

# calculate the symbolic representation of the muscle moment arm matrix
calculate_moment_arm_symbolically(model_file, results_dir)
```

      0%|          | 0/18 [00:00<?, ?it/s]

    Calculating...

    /usr/lib/python2.7/site-packages/multipolyfit/core.py:66: FutureWarning: `rcond` parameter will change to the default of machine precision times ``max(M, N)`` where M and N are the input matrix dimensions.
    To use the future default and silence this warning we advise to pass `rcond=None`, to keep using the old, explicitly pass `rcond=-1`.
      beta = linalg.lstsq(A, y)[0]
    100%|██████████| 18/18 [00:01<00:00, 15.94it/s]

```python
from analytic_moment_arm import visualize_moment_arm

# get files from previous step (to avoid recalculation)
R = pickle.load(file(results_dir + 'R.dat', 'r'))
sampling_dict = pickle.load(file(results_dir + 'sampling_dict.dat', 'r'))
model_coordinates = pickle.load(file(results_dir + 'model_coordinates.dat', 'r'))
model_muscles = pickle.load(file(results_dir + 'model_muscles.dat', 'r'))

# visualize 2D moment arm R(q1)
muscle = 'vasti_r'
coordinates = sampling_dict[muscle]['coordinates']
visualize_moment_arm(coordinates[0], muscle, coordinates[0], sampling_dict,
                     model_coordinates, model_muscles, R, figures_dir)

# visualize 3D moment arm R(q1, q2)
muscle = 'hamstrings_r'
coordinates = sampling_dict[muscle]['coordinates']
visualize_moment_arm(coordinates[0], muscle, coordinates, sampling_dict,
                     model_coordinates, model_muscles, R, figures_dir)
```

## Step 3: Calculate the Feasible Muscle Forces

The feasible muscle forces are calculated below. Initially, the moment arm and
maximum muscle force quantities are computed for each instance of the
movement. Then the following inequality is formed assuming a linear muscle model

\begin{equation}\label{equ:linear-muscle-null-space-inequality}
  \begin{gathered}
    f_m = f_{max} \circ a_m = f_m^{\parallel} +
    N_{R} f_{m0},\; 0 \preceq a_m \preceq 1
    \rightarrow \\
    \begin{bmatrix}
      - N_{R} \\
      \hdashline
      N_{R}
    \end{bmatrix}
    f_{m0} \preceq
    \begin{bmatrix}
      f_m^{\parallel} \\
      \hdashline
      f_{max} - f_m^{\parallel}
    \end{bmatrix} \\
    Z f_{m0} \preceq \beta
  \end{gathered}
\end{equation}

where $a_m \in \Re^{m}$ represents a vector of muscle activations, $f_{max} \in
\Re^{m}$ a vector specifying the maximum muscle forces, $\circ$ the Hadamard
(elementwise) product, $f_m^{\parallel}$ the particular muscle force solution
that satisfies the action, $N_{R}$ the moment arm null space and $f_{m0}$ the
null space forces.

The next step is to sample the inequality $Z f_{m0} \leq \beta$. This is the
bottleneck of the analysis. The *convex_bounded_vertex_enumeration* uses the
lsr method, which is a vertex enumeration algorithm for finding the vertices
of a polytope in $O(v m^3)$.

```python
# since this section of code may be called independently after step 1 ensure
# that cells 2, 5 and 9 are evaluated
from IPython.display import Javascript
Javascript("Jupyter.notebook.execute_cells([2, 5, 9])")
```

```python
from feasible_muscle_forces import calculate_feasible_muscle_forces

# calculate the feasible muscle forces that satisfy the motion and physiological
# muscle constraints
calculate_feasible_muscle_forces(model_file, ik_file, so_force_file, results_dir)
```

    100%|██████████| 151/151 [00:02<00:00, 64.00it/s]
    100%|██████████| 151/151 [00:00<00:00, 4950.83it/s]
      0%|          | 0/151 [00:00<?, ?it/s]

    Collecting data ...
    Calculating null space ...

    100%|██████████| 151/151 [43:43<00:00, 17.38s/it]

## Step 4: Calculate the Feasible Joint Stiffness

In the following section, we will introduce a method for calculating the
feasible muscle forces that satisfy the motion and the physiological muscle
constraints. As the muscles are the main actors of the system, it is important
to examine the effect of muscle redundancy on the calculation of limbs'
stiffness.

The muscle stiffness is defined as

\begin{equation}\label{equ:muscle-stiffness}
  K_m = \frac{\partial f_m}{\partial l_{m}},\;  K_m \in \Re^{m \times m}
\end{equation}

where $f_m \in \Re^{m}$ represents the muscle forces, $l_{m} \in \Re^{m}$ the
musculotendon lengths and $m$ the number of muscles. The joint stiffness is
defined as

\begin{equation}\label{equ:joint-stiffness}
  K_j = \frac{\partial \tau}{\partial q},\; K_j \in \Re^{n \times n}
\end{equation}

where $\tau \in \Re^{n}$, $q \in \Re^{n}$ are the generalized forces and
coordinates, respectively and $n$ the DoFs of the system. Finally, the task
stiffness is defined as

\begin{equation}\label{equ:task-stiffness}
  K_t = \frac{\partial f_t}{\partial x_t},\; K_t \in \Re^{d \times d}
\end{equation}

where $f_t \in \Re^{d}$ denotes the forces, $x_t \in \Re^{d}$ the positions and
$d$ the DoFs of the task.

The derivation starts with a model for computing the muscle stiffness matrix
$K_m$. The two most adopted approaches are to either use the force-length
characteristics of the muscle model or to approximate it using the definition of
the short range stiffness, where the latter is shown to explain most of the
variance in the experimental measurements. The short range stiffness is
proportional to the force developed by the muscle ($f_m$)

\begin{equation}\label{equ:short-range-stiffness}
  k_{s} = \gamma \frac{f_m}{l_m^o}
\end{equation}

where $\gamma = 23.4$ is an experimentally determined constant and $l_m^o$ the
optimal muscle length. This definition will be used to populate the diagonal
elements of the muscle stiffness matrix, whereas inter-muscle coupling
(non-diagonal elements) will be assumed zero since it is difficult to measure
and model in practice.

The joint stiffness is related to the muscle stiffness through the following
relationship

\begin{equation}\label{equ:joint-muscle-stiffness}
    K_j = -\frac{\partial R^T}{\partial q} \bullet_2 f_m - R^T K_m R
\end{equation}

where the first term captures the varying effect of the muscle moment arm ($R
\in \Re^{m \times n}$), while the second term maps the muscle space stiffness to
joint space. The notation $\bullet_2$ denotes a product of a rank-3 tensor
($\frac{\partial R^T}{\partial q} \in \Re^{n \times m \times n}$, a 3D matrix)
and a rank-1 tensor ($f_m \in \Re^{m}$, a vector), where the index $2$ specifies
that the tensor dimensional reduction (by summation) is performed across the
second dimension, resulting in a reduced rank-2 tensor of dimensions $n \times
n$.

In a similar manner, the task stiffness is related to the muscle stiffness
through the following relationship

\begin{equation}\label{equ:task-muscle-stiffness}
  K_t  = -J_t^{+T} \left(\frac{\partial J_t^T}{\partial q} \bullet_2
    f_t + \frac{\partial R^T}{\partial q} \bullet_2 f_m + R^T
    K_m R\right) J_t^{+}
\end{equation}

where the task Jacobian matrix ($J_t \in \Re^{d \times n}$) describes the
mapping from joint to task space ($\Re^{n} \rightarrow \Re^{d}$), $+$ stands for
the Moore-Penrose pseudoinverse and $+T$ the transposed pseudoinverse operator.

Algorithm for calculating the feasible joint stiffness:

**Step 1:** Calculate the feasible muscle forces $f_m^{\oplus}$ that satisfy the
task and the physiological muscle constraints

**Step 2:** Calculate the muscle stiffness matrix $K_m$ using the short range
stiffness model

\begin{equation*}\label{equ:short-range-stiffness-2}
  k_s = \gamma \frac{f_m}{l_m^o},\; \gamma = 23.4
\end{equation*}

**Step 3:** Calculate the joint stiffness $K_j$

\begin{equation*}
    K_j = -\frac{\partial R^T}{\partial q} \bullet_2 f_m - R^T K_m R
\end{equation*}

```python
# since this section of code may be called independently after step 3 ensure
# that cells 2, 5 and 9 are evaluated
from IPython.display import Javascript
Javascript("Jupyter.notebook.execute_cells([2, 5, 9])")
```

```python
from feasible_joint_stiffness import calculate_feasible_joint_stiffness

# calculate feasible joint stiffness
calculate_feasible_joint_stiffness(model_file, ik_file, results_dir)
```

    Initialization ...

      0%|          | 0/151 [00:00<?, ?it/s]

    Calculating feasible joint stiffness ...

    100%|██████████| 151/151 [06:47<00:00,  2.70s/it]

```python
from feasible_joint_stiffness import visualize_feasible_joint_stiffness

# visualize feasible joint stiffness
visualize_feasible_joint_stiffness(results_dir, figures_dir)
```

This figure depicts the feasible joint stiffness of the hip, knee and ankle
joints during walking with the heel strike and toe-off events annotated
accordingly. These results confirm experimental measurements and furthermore
present similarities in the outline of the minimum stiffness predicted by our
method. Notably, the hip stiffness range is gradually decreasing between heel
strike and toe-off, because the flexor muscles are preparing for the swing phase
and the capacity to increase the joint stiffness reaches its lowest value before
the toe-off event. A similar pattern is observed at the knee joint, which
undergoes a flexion and a subsequent extension during the swing phase. We
observe that the capacity of the muscles to modulate the ankle stiffness is not
decreased and the range is gradually shifted upwards in the region between the
heel strike and toe-off events. The increase in the minimum possible values of
the ankle stiffness is attributed to the counterbalance of the ground reaction
forces by the ankle plantar flexion muscles. As muscle effort is spent by these
muscles, one would expect a lower maximum bound, which is not the case
here. This could be contributed to the fact that the musculoskeletal system is
asymmetric, i.e., the plantar flexion muscles can induce larger magnitudes of
moment at the ankle joint in comparison to the dorsiflexion muscles. We can
conclude that the contribution of the ground reaction forces results in an
increase of the ankle stiffness.


```python
from IPython.core.display import HTML
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'>

<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Philosopher', sans-serif;
    font-weight: 400;
    font-size: 2.2em;
    line-height: 100%;
    color: rgb(0, 80, 120);
    margin-bottom: 0.1em;
    margin-top: 0.1em;
    display: block;
}
.text_cell_render h2 {
    font-family: 'Philosopher', serif;
    font-weight: 400;
    font-size: 1.9em;
    line-height: 100%;
    color: rgb(245,179,64);
    margin-bottom: 0.1em;
    margin-top: 0.1em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Philosopher', serif;
    margin-top:12px;
    margin-bottom: 3px;
    font-style: italic;
    color: rgb(94,127,192);
}

.text_cell_render h4 {
    font-family: 'Philosopher', serif;
}

.text_cell_render h5 {
    font-family: 'Alegreya Sans', sans-serif;
    font-weight: 300;
    font-size: 16pt;
    color: grey;
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.1em;
    display: block;
}

.text_cell_render h6 {
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 10pt;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
        font-family: "PT Mono";
        font-size: 100%;
}

</style>

# Geometrical view

## Preamble

## System of linear equations

Most courses and textbooks introduce linear algebra to solve systems of linear equations.  _Linear equations_ have independent variables that are each of power one and not transcendental.  Example are shown in (1) below.

$$ \begin{align} {2x}-{y}&={3} \\ -{x} + 2{y} &= {0} \end{align} \tag{1} $$

Solving systems of linear equations attempts at finding numerical solutions for the variables taht solve all the equations in the linear system.  A solution to (1) is shown in (2) below.

$$\begin{align}2 &\left( 2 \right) - 1 \left( 1 \right) = 3 \\ - &\left( 2 \right) + 2 \left( 1 \right) = 0\end{align}\tag{2}$$

We can create an _augmented matrix_ from the system in (1).  To do this, we create a matrix of coefficients, including those from both sides of the equations.  We store the matrix as a computer variable called `A_augm`.

```python
from sympy import Matrix
```

```python
A_augm = Matrix([[2, -1, 3], [-1, 2, 0]]) # Note the placement of ()'s and []'s
A_augm # A_augm is a computer variable that contains the matrix
```

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

Because `A_augm` was created using the `Matrix()` function from `sympy`, it is an instance of this type.  The `type()` indicates it more specificically as a _mutable, dense matrix_.  Mutable means that we can change the values of the elements (coefficients in this case) and dense because the value of each position in the matrix is stored.  (when there are many zeros, the a sparse matrix can be created, that only stores the values that are not zero and their positions.)

```python
# We can ask python what type of computer variable A_augm holds
type(A_augm)  # We see that it is a mutable dense matrix
```

    sympy.matrices.dense.MutableDenseMatrix

We can also create a _matrix of coefficients_.  It includes only ethe left-hand side coefficients.

```python
A = Matrix([[2, -1], [-1, 2]])  # Matrix of coefficients
A
```

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

The variables (unknowns) can be expressed as a column vector, saved as the computer variable `x_vect` below.  First, we specify the variable `x` and `y` as mathematical variables using the `symbols()` function.

```python
from sympy import symbols
```

```python
x, y, z = symbols('x y z') # Creating symbolic mathematical variables as opposed to computer variables
# These symbols can no longer be used as computer variable names
```

```python
x_vect = Matrix([x, y])
x_vect
```

$\displaystyle \left[\begin{matrix}x\\y\end{matrix}\right]$

The same goes for the column vector of solutions, saved as the computer variable `b_vect`.

```python
b_vect = Matrix([3, 0])
b_vect
```

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

In (3) below we see the matrix times the column vector results in the vector of solutions.  The (row, column) sizes are also shown.

$$ \begin{align} A_{2 \times 2} \quad \underline{x}_{2 \times 1} = \underline{b}_{2 \times 1} \\ \\ \begin{bmatrix} 2 && -1 \\ -1 && 2 \end{bmatrix} \begin{bmatrix} x \\ y\end{bmatrix} = \begin{bmatrix} 3 \\ 0 \end{bmatrix} \end{align} \tag{3}$$

The `Eq()` function is used below to do the matrix times the vector on the left-hand side (resulting in a column vector) and the solution vector on the right.  It gives a visual idea of how we use linear algebra to solve these systems.

```python
from sympy import Eq
```

```python
Eq(A * x_vect, b_vect) # From Ax = b
# The Eq function takes the arguments left-hand-side (LHS), right-handside (RHS) of the equation
```

$\displaystyle \left[\begin{matrix}2 x - y\\- x + 2 y\end{matrix}\right] = \left[\begin{matrix}3\\0\end{matrix}\right]$

## The row picture

Each linear equation above is an equation of a straight line.  This represents the _row picture_ of a linear system.  The solutions to both equations is at the point where they cross.  Have a look at the plot below. Don't be too concerned about the code for plotting.  It does not form part of this series of notebooks.

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

```python
x_vals = np.linspace(-3, 3, 100) # Create 100 values between -3 and 3
# Note that we cannot use the computer variable x, because it has been reserved above as a mathematical variable in
# the symbols function

plt.figure(figsize = (10,8)) # Create a graph of size 10 by 8
plt.plot(x_vals, 2 * x_vals - 3) # Plot every single value created above with 2 times that values
# Taken from the first equation which was y = 2x or f(x) = 2x
# The plot takes the arguments (code between parentheses) of x,y
plt.plot(x_vals, ((x_vals / 2))) # Also plot the second equation
plt.show; # Draw the plot on screen
```

We note that the two line cross at the point $\left( 2, 1 \right)$.  It should be clear that there are three possible outcomes for a system with two variables and two equations.  Either the lines cross with a single solution, or they do not cross at all (parallel lines), with no solutions, or they coincide, with infinitely many solutions.

## The column picture

We can view the same system as a vector problem.  Here the coefficients of each separate variable as a column vector.  This problem now becomes a linear combination of vectors.  We are thus asking how many (scalar multiplication) of one vector plus how many of the other vector will equal the vector on the right hand side.  This should be clear from (4) below.

$$ x\begin{bmatrix} 2 \\ -1 \end{bmatrix}+y\begin{bmatrix} -1 \\ 2 \end{bmatrix}=\begin{bmatrix} 3 \\ 0 \end{bmatrix} \tag{4}$$

As before, we create separate computer variables and show that the end-result is the same.

```python
x_var_vect = Matrix([2, -1])
x_var_vect
```

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

```python
y_var_vect = Matrix([-1, 2])
y_var_vect
```

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

```python
Eq(x * x_var_vect + y * y_var_vect, b_vect)
```

$\displaystyle \left[\begin{matrix}2 x - y\\- x + 2 y\end{matrix}\right] = \left[\begin{matrix}3\\0\end{matrix}\right]$

In the image below, we see the two vectors (blue and green) and the resultant vector (orange).

```python
ax = plt.axes()

ax.arrow(0.0, 0.0, 2.0, -1.0,
        length_includes_head=True,
        head_width=0.5,
         head_length=0.7,
         fc='lightblue',
         ec='black')
ax.arrow(0.0, 0.0, -1.0, 2.0,
        length_includes_head=True,
        head_width=0.5,
        head_length=0.7,
        fc='lightgreen',
        ec='black')
ax.arrow(0.0, 0.0, 3.0, 0.0,
        length_includes_head=True,
        head_width=0.5,
         head_length=0.7,
         fc='orange',
         ec='black')

plt.grid()

plt.xlim(-5,5)
plt.ylim(-5,5)

plt.title('Vector view',fontsize=10)

plt.show();
```

We know that it takes twice the blue vector and one time the green vector to give us the orange vector.

```python
ax = plt.axes()

ax.arrow(0.0, 0.0, 2.0, -1.0,
        length_includes_head=True,
        head_width=0.5,
         head_length=0.7,
         fc='lightblue',
         ec='black')
ax.arrow(2.0, -1.0, 2.0, -1.0,
        length_includes_head=True,
        head_width=0.5,
         head_length=0.7,
         fc='lightblue',
         ec='black')

ax.arrow(4.0, -2.0, -1.0, 2.0,
        length_includes_head=True,
        head_width=0.5,
        head_length=0.7,
        fc='lightgreen',
        ec='black')
ax.arrow(0.0, 0.0, 3.0, 0.0,
        length_includes_head=True,
        head_width=0.5,
         head_length=0.7,
         fc='orange',
         ec='black')

plt.grid()

plt.xlim(-5,5)
plt.ylim(-5,5)

plt.title('Vector view',fontsize=10)

plt.show();
```

It should be very clear that the two vectors are special in that linear combinations of them will allow us to fill a plane in $\mathbb{R}^{2}$ (in which they both lie).

It should also be clear to see that these two vectors are not linear combinations of each other.  If this were so, they are linearly independent and linear combinations of them fill only a line in $\mathbb{R}$.

We can imagine that the $xy$-plane is filled with (all the points are created by) vectors, i.e. I can find any coordinate by drawing a vector to it.  All these vectors together can be called a _set_ and they fill $\mathbb{R}^{2}$.  Later we will learn about _basis vectors_ and how they _span_ a space.

## The 3-space picture

To end this section, we take a look at the three equation with three variables.

$$ \begin{align} {3x}+{2y}-{z}&=2 \\ {x}-{2y}-{z}&=3 \\ {2x}+{y}-{z}&={1} \end{align} \tag{5}$$

We can create an augmented matrix from the coefficents, stored in the computer variable `A_augm` below.

```python
A_augm = Matrix([[3, 2, -1, 2], [1, -2, -1, 3], [2, 1, -1, 1]])
A_augm
```

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

The `.rref()` method will quickly solve the linear system for use.

```python
A_augm.rref()
```

    (Matrix([
     [1, 0, 0,  5/2],
     [0, 1, 0, -3/2],
     [0, 0, 1,  5/2]]), (0, 1, 2))

The solution is where three planes in $\mathbb{R}^{3}$ _cross_.  We can once again imagine that the planes (each equation) may never cross or lie on-top of each other.  Below is an image of three planes crossing at one point.

```python
from sympy.plotting import plot3d
```

```python
plot3d((3*x+2*y-2, (x,-5,5), (y,-5,5)),
      (x-2*y-3, (x,-5,5), (y,-5,5)),
      (2*x+y-1, (x,-5,5), (y,-5,5)));
```

As before, we can view this problem as a column picture.

## Conclusion

We have seen how linear systems can be viewed as rows or columns and how they can be seen as matrices and vectors.

```python

```

Mathematics/Linear Algebra/0.0 MIT-18.06 - Jupyter/Lecture_1(part-1)_Geometric_of_Linear_Equations.ipynb