Federal University of Rio Grande do Sul (UFRGS)
Postgraduate Program in Civil Engineering (PPGEC)
# MRPy - [M]ultivariate [R]andom [P]rocesses with P[y]thon

_A Python Class for Stochastic Dynamic Analysis of Structural Systems_
---
_Prof. Marcelo M. Rocha, Dr.techn._ [(ORCID)](https://orcid.org/0000-0001-5640-1020)
_Porto Alegre, RS, Brazil_

---
## Table of contents

1. [Basic concept](#concept)

2. [Constructors using external information (class methods)](#external)
   2.1.  [Directly from ```ndarray```](#ndarray)
   2.2.  [Reading from file](#from_file)
   2.3.  [Simulate from spectral density](#from_spectrum)
   2.4.  [Simulate from autocovariance](#from_autocov)
   2.5.  [Simulate from pseudo-spectrum](#from_pseudo)

3. [Constructors through modification (object methods)](#modifiers)
   3.1.  [Remove mean value](#zero_mean)
   3.2.  [Weighted superposition](#superpose)
   3.3.  [Double length with trailing mean values](#double)
   3.4.  [Extract segment](#extract)
   3.5.  [Apply amplitude envelope](#envelope)
   3.6.  [Moving average](#mov_average)
   3.7.  [Bandpass and bandstop filters](#filtered)
   3.8.  [Kanai/Tajimi filter for seismic acceleration](#Kanai)
   3.9.  [Integration and diferentiation in frequency domain](#calculus)
   3.10. [Numerical solution of the dynamic equilibrium equation](#sdof)
   3.11. [Free system response by random decrement](#random_decrement)
   3.12. [Fit theoretical free response equation](#fit_decay)

4. [Constructors from conceptual properties](#special)
   4.1.  [Zero process](#zero_process)
   4.2.  [Dirac's Delta function (unit impulse)](#dirac)
   4.3.  [Heavisides function (unit step)](#heaviside)
   4.4.  [White noise](#white_noise)
   4.5.  [Pink noise](#pink_noise)

5. [Methods for time series analysis](#properties)
   5.1.  [Spectral density estimation (periodogram)](#periodogram)
   5.2.  [Autocovariance and autocorrelation function](#autocov)
   5.3.  [Pseudo-spectrum estimation](#pseudo)
   5.4.  [Peak factor by Davenport's formula](#Davenport)
   5.5.  [Peak factor by splitmax method](#splitmax)

6. [Further methods](#extras)
   6.1.  [Plot axis generation](#axis)
   6.2.  [Pre-visualization plots](#plots)
   6.3.  [Save to file](#to_file)
   6.4.  [Periodogram to autocovariance conversions](#Sx2Cx)
   6.5.  [Wind speed turbulence spectra](#wind_spectra)
   6.6.  [UBC pseudo spectrum for seismic acceleration](#UBC_spectrum)

7. [Examples of application](#examples)
   7.1. [Modal analysis of structural systems](#modal)
   7.2. [Wind turbulence response by NBR-6123](#NBR6123)
   7.3. [Seismic response by UBC-1997](#UBC1997)

___

```python
import numpy as np
import matplotlib.pyplot as plt
from   MRPy import MRPy
```

## 1. Basic concept

The ```MRPy``` class inherits from ```numpy.ndarray``` with the inclusion of a time attribute, which is optionally ```fs```, the sampling frequency, or ```Td```, the time span. Only one of these attribute must be specified at object construction, while the other will be automatically calculated through the relation:

\begin{equation}
 N = T_{\rm d} \; f_{\rm s}
\end{equation}

The series length ```N``` must be an even integer.

To better illustrate the concept, the basic construction sintax is exemplified below by 3 simulations of a Gaussian white noise, with sampling frequency 512Hz:

```python
arry = np.random.randn(3, 8193)
X    = MRPy(arry, fs=512)
```

The input array contains any number of time series disposed as rows or columns, but internally the constructor will identify the largest dimension and force time series to be disposed as rows. This means that the provided array will be eventually transposed and the object content is always a matrix in the form:

\begin{equation}
{\mathbf X}(t) =
\begin{bmatrix}
 X_1(t_1)    &  X_1(t_2)    &  X_1(t_3)    & \cdots  &  X_1(t_N)    \\
 X_2(t_1)    &  X_2(t_2)    &  X_2(t_3)    & \cdots  &  X_2(t_N)    \\
 X_3(t_1)    &  X_3(t_2)    &  X_3(t_3)    & \cdots  &  X_3(t_N)    \\
\cdots       & \cdots       & \cdots       & \cdots  & \cdots       \\
 X_{NX}(t_1) &  X_{NX}(t_2) &  X_{NX}(t_3) & \cdots  &  X_{NX}(t_N) \\
\end{bmatrix}
\end{equation}

From this definition, five attributes are internally assigned to the ```MRPy``` object ```X```:

```python
X.printAttrib()
```

     fs = 512.0Hz
     Td = 16.0s

     NX = 3
     N  = 8192
     M  = 4097

where ```NX``` is the number of time series (number of matrix rows) and ```  M = N//2 + 1  ``` is the (odd) half length of each series in ```X```, corresponding to the length of spectral density and autocovariance estimators. These attributes are kept ready to be used by class methods presented in this notebook.

If the series length ```N```  (number of columns) is not even, it will be truncated by 1 at construction, so _it is strongly recommended to provide only series with even lengths to avoid unintended truncation_.

There are two main reasons for disposing the time series as rows in the ```MRPy``` array:

1. In the context of Structural Dynamics, both load and the response vectors are column vectors. Hence, ```MRPy``` arrays can undergo matrix operations as they are (for instance, they can be pre-multiplied by the modal shape matrix), each column being the load or the response at a given time step. Indeed, time series in MRPy instances are meant to be associated with system degrees of freedom or vibration modes.

2. Iteration over time series is straightforward with commands
   ```for row in X:```
   or
   ```for kX, row in enumerate(X):```
   which are frequently used along class methods code.

The inheritance from ```ndarray``` also ensures that many useful methods are kept available:

```python
for kx, x in enumerate(X):
    print('{0}: mean = {1:7.4f},  std = {2:7.4f},  min = {3:7.4f},  max = {4:7.4f}'.format(
           kx, x.mean(), x.std(), x.min(), x.max()))
```

    0: mean = -0.0035,  std =  1.0031,  min = -3.9671,  max =  3.7256
    1: mean = -0.0200,  std =  1.0002,  min = -3.6985,  max =  3.8203
    2: mean =  0.0034,  std =  1.0065,  min = -4.2584,  max =  3.7806

There is a quick visualization method available, which works for ```NX``` not larger than 12:

```python
h0 = X.plot_time(fig=0, figsize=(6, 8), axis_t=[0, X.Td, -5, 5])
```

There is also a quick visualization method in frequency domain (see section [2.3 Simulate from spectral density](#from_spectrum)):

```python
h1 = X.plot_freq(fig=1, figsize=(8, 8), axis_f=[0, X.fs/2, 0, 0.05])
```

The next sections present the available methods for creating, modifying and operating with
```MRPy``` instances.

## 2. Constructors using external information (class methods)

### 2.1. Directly from ```ndarray```

The example provided in the previous section illustrates how a ```MRPy``` instance can be directly created from a ```ndarray``` instance. The same can be accomplished by specifying the time span instead of the sampling frequency.

Let's present another example, this time by combining some noise to a sinus function:

```python
Td   =  16.      # signal duration (seconds)
N    =  2048     # number of sampling points

c0   =  1.0      # signal mean value
s0   =  0.3      # amplitude of harmonic part
f0   =  1.0      # frequency of harmonic part (Hz)
n0   =  1.0      # amplitude of gaussian noise

t    =  np.linspace(0, Td, N)
arry =  c0 + s0*np.sin(2*np.pi*f0*t) + n0*np.random.randn(N)

Y    =  MRPy(arry, Td=Td)

h2   =  Y.plot_time(fig=2, figsize=(8, 3), axis_t=[0, Y.Td, -4, 6])
h3   =  Y.plot_freq(fig=3, figsize=(8, 3), axis_f=[0, 10.0,  0, 1])
```

### 2.2. Reading from file

The method ```from_file()``` reads a ```MRPy``` object from a specified file. Some formats are implemented and others may be included to expand possibilities. The default file format is ```form='mrpy'```, which is a gnu zipped (```gzip```) file with a pickled object dumping (```pickle```). Files with this format can be generated as explained in section [5.2. Save to file](#to_file).

The other possibilities for parameter ```form``` are:

1. ```'excel'```: reads an Excel file (.xlsx) containing a ```pandas``` data frame in a sheet named ```MRPy```. The ```index``` attribute (first column) correspond to time instants and the further columns are the time series. Column headers are disregarded. This format allow easy communication with Excel.

2. ```'columns'```: reads a tab delimited ASCII file (.txt), where first column corresponds to time instants and following columns are the time series.

3. ```'invh'```: reads a comma delimited ASCII file (.csv), where first column corresponds to time instants (in seconds) while the last three columns correspond to accelerations in axis $x$, $y$ and $z$ measured with the cell phone app "iNVH" by Bosch. This is the format is used in classroom examples.

4. ```'MPU6050'```: reads a gnu zipped comma delimited ASCII file (.csv.gz), where first column corresponds to time instants (in seconds), the following three columns correspond to accelerations in axis $x$, $y$ and $z$, and the last three columns correspond to angular velocities in axis $x$, $y$ and $z$. This is the format used by a Raspberry Pi data logger reading a MPU6050 sensor that this author has developed for vibration monitoring.

File extensions must not be included in the file name. ASCII files are read with ```numpy.genfromtxt()```. No matter the specified format, the data will undergo resampling to ensure even sampling frequency. This means that all these formats allows for _slightly_ irregular sampling rates. The series are resampled through linear interpolation (with ```scipy``` function ```interp1d```) while its length an total time span are preserved. The resampling function is also available as a class method ```resampling()```:

```python
ti = np.arange(0,1,0.05) + 0.04*np.random.rand(20)  # irregular time steps!
ti = ti - ti[0]
Ei = np.random.randn(20)                            # simulation

E  = MRPy.resampling(ti, Ei);                       # resampling

h4 = E.plot_time(fig=4, figsize=(8, 3), axis_t=[0, 1, -3, 3])       # resampled
h4 = plt.plot(ti, Ei, 'ro', ti, Ei, 'r:', lw=1)                      # original
```

Below is an example by reading a file generated with the accelerations recorded by the cell phone app "iNVH" from Bosch:

```python
C  = MRPy.from_file('resources/iNVH001', form='invh');
C.printAttrib()

C.plot_time(fig=5, figsize=(8, 8), axis_t=[0, C.Td, -20, 20]);
```

File samples for all four available formats are included in the ```resources/``` folder that comes along with this notebook.

### 2.3 Simulate from spectral density

The power spectral density, $S_{X}(\omega)$, of a random process is related to the autocovariance function, $C_{X}(\tau)$, as a Fourier transform pair:

$$ S_{X}(\omega) = \frac{1}{2\pi} \int_{-\infty}^{+\infty}{C_{X}(\tau) e^{-i\omega \tau} \: d\tau} $$

$$ C_{X}(\tau) = \int_{-\infty}^{+\infty}{S_{X}(\omega) e^{i\omega \tau} \: d\omega} $$

By the definition used herein, the integral of the (one-sided) spectral density is the mean square value of the signal:

$$ \int_0^\infty {S_X(\omega) \: d\omega} = \sigma_X^2 + \mu_X^2 $$

The same can be obtained by setting $\tau = 0$ in the definition of $C_{X}(\tau)$ above:

$$  C_{X}(0) = \int_0^\infty {S_X(\omega) \: d\omega} = \sigma_X^2 + \mu_X^2 $$

As an example, let us simulate a wind speed turbulence, $v(t)$, according to Harris' spectral density, $S_V(f)$. In section [6.5. Wind speed turbulence spectra](#wind_spectra) a utility method is described that returns the following spectral density, but for now let us do it explicitly.

$$ \frac{f S_V(f)}{\sigma_V^2} = \frac{0.6 Y}{\left( 2 + Y^2 \right)^{5/6}}$$
with:
\begin{align*}
 Y          &= 1800 f \;/\; \bar{V}_{10}     \\
 \sigma_V^2 &= 6.66 \; c_{\rm as} \; \bar{V}_{10}^2
\end{align*}

where $\sigma_V^2$ is the wind speed variance, $c_{\rm as}$ is the surface drag coefficient and $\bar{V}_{10}$ is the mean wind speed at 10m height.

Firstly, the frequency axis is generated:

```python
cas =  6.5e-3                      # NBR-6123 category II
V10 =  20.                         # mean speed at 10m (m/s)
sV2 =  6.66*cas*(V10**2)           # wind speed variance

fs  =  64.                         # samplig rate
N   =  8192                        # length of sample
M   =  N//2 + 1                    # length of periodogram
df  =  fs/M                        # frequency step

f   =  np.linspace(0, fs/2, M)     # frequency axis
Y   =  1800*f[1:]/V10              # avoiding zero division
```

To simulate a two-dimensional uncorrelated process, the spectral density must be calculated and replicated twice:

```python
SV0       =  np.zeros((2, M))
SV0[0,1:] =  0.6*sV2*Y/((2 + Y**2)**(5/6))/f[1:]
SV0[1,1:] =  SV0[0,1:]  # (replicating)

plt.figure(6, figsize=(8, 3))
plt.grid(True)

h6  = plt.loglog(f[1:], SV0[0,1:])
```

Once the spectral density is defined, a ```MRPy``` instance can be simulated from it. Due to ```ndarray``` inheritance, the mean value can be directly added to the time series:

```python
V  =  MRPy.from_periodogram(SV0, fs) + V10
V.printAttrib()

h7 =  V.plot_time(fig=7, figsize=(8, 6), axis_t=[0, V.Td,  -40,  40])
```

It must be emphasized that the time series simulated above are _uncorrelated_. Simulation of correlated multi-variate random processes shall be implemented in a near future.

To check wether the simulated series indeed present the intended spectra, the periodogram estimation method can be used.
The _periodogram_ is the most basic estimator of the (one-sided) spectral density of a time series. It makes use of FFT (_Fast Fourier Transform_, ```numpy.fft```) and a time scaling factor that depends on the sampling rate, ```fs```.

```python
SV1, fs = V.periodogram()

plt.figure(8, figsize=(8, 3))
plt.grid(True)

h8  = plt.loglog(f[1:], SV0[0,1:], 'b:', lw=1)    # specified
h8  = plt.loglog(f[1:], SV1[0,1:], 'r:', lw=1)    # simulated
h8  = plt.loglog(f[1:], SV1[1,1:], 'g:', lw=1)    # simulated
```

It can be finally confirmed that both simulated time series have _exactly_ the specified spectral density.

There is also a quick autocovariance visualization method available:

```python

```

### 2.4 Simulate from autocovariance

The autocovariance, $C_X(\tau)$, may be explicitly provided as a ```ndarray```, or through a conversion from a spectral density, $S_X(\omega)$, using the class tool ```Sx2Cx()```. Let us illustrate this by using the same spectral density specified in the previous section.

```python
CV0, Tmax = MRPy.Sx2Cx(SV0, fs)
tau       = np.linspace(0, Tmax, M)

plt.figure(9, figsize=(8, 3))
plt.grid(True)

h9  = plt.plot(tau, CV0[0,:])
```

Conversely, there is also a class method to go from autocovariance to spectral density:

```python
SV2, fs  = MRPy.Cx2Sx(CV0, Tmax)
f        = np.linspace(0, fs/2, M)

plt.figure(10, figsize=(8, 3))
plt.grid(True)

h10 = plt.loglog(f[1:], SV2[0,1:], 'b:', lw=1)
h10 = plt.loglog(f[1:], SV0[0,1:], 'r:', lw=1)
```

Once the autocovariance function is specified, simulation may be carried on as:

```python
V   =  MRPy.from_autocov(CV0, Tmax) + V10
V.printAttrib()

h11 =  V.plot_time(fig=11, figsize=(8, 6), axis_t=[0, V.Td,  -40,  40])
```

To check wether the simulated series indeed present the intended autocovariance, the ```autocov()``` estimation method can be used. This method will call the periodogram estimator and carry out the due conversions.

```python
CV1, Tmax = V.autocov()

plt.figure(12, figsize=(8, 3))
plt.grid(True)

h12 = plt.plot(tau, CV0[0,:], 'b:', lw=1)     # specified
h12 = plt.plot(tau, CV1[0,:], 'r:', lw=1)     # estimated
h12 = plt.plot(tau, CV1[1,:], 'g:', lw=1)     # estimated
```

And it can be also confirmed that both simulated time series have _exactly_ the specified autocovariance function.

For any MRPy instance there is a quick method for checking the autocovariance function available. The plot, however, presents the _autocorrelation function_, which corresponds to the autocovariance scaled such that is value at origin (the variance) is normalized to unity.

```python
h13 = V.plot_corr(fig=13, figsize=(8, 6), axis_T=[0, Tmax, -0.2, 1.2])
```

### 2.5 Simulate from pseudo-spectrum

A pseudo spectrum is a function of the vibration period, $T$, of a one degree of freedom system. The spectrum corresponds to the system peak displacement response resulting from a given ground acceleration. The _pseudo-spectrum_ is the peak acceleration response calculated by multiplying the peak displacement by the squared frequency.
It is important to observe that, although the word _spectrum_ is used, this is not the same concept behind the spectral density definition (energy distribution across frequencies).

As an example, firstly a seismic acceleration record is read from file:

```python
Acc = MRPy.from_file('resources/earthquake', form='columns');
Acc.printAttrib()

h14 = Acc.plot_time(fig=14, figsize=(8, 3), axis_t=[0, Acc.Td, -0.5, 0.5])
```

Then the displacement response spectrum is estimated by the ```pseudo()``` object method. This method may take some time to compute, for the 1 d.o.f. system dynamic response must be evaluated for _each_ frequency step!

```python
Su, Tmax = Acc.pseudo(zeta=0.05)         # system viscous damping must be specified, 5% is default
T        = np.linspace(0, Tmax, Acc.M)

plt.figure(15, figsize=(8, 3))
h15 = plt.plot(T, Su[0,:])
plt.grid(True)
```

There is also a method for quickly plotting this displacement spectrum:

```python
h16 = Acc.plot_pseudo(fig=16, figsize=(8, 3), axis_T=[0, Tmax, -0.01, 0.1])
```

This is, however, a peak displacement spectrum. To convert it to peak acceleration (pseudo-spectrum) is must be multiplied by the squared frequency:

```python
Sa       = np.zeros(Su.shape)
Sa[0,1:] = Su[0,1:]*(2*np.pi/T[1:])**2

plt.figure(17, figsize=(8, 3))
h17 = plt.plot(T, Sa[0,:])
plt.grid(True)

```

This estimated acceleration response spectrum corresponds to one single ground acceleration record. A collection of records lead to normalized spectra as provided by design codes. In section [6.6. UBC pseudo-spectrum for seismic acceleration](#UBC_spectrum) a utility method is described that returns a design spectrum from UBC code, but for now let us use the spectrum estimated above.

To simulate a ground acceleration sample from the specified pseudo-acceleration spectrum the ```from_pseudo()``` method is called.

```python
T1 = 3.    # end of "attack" time
T2 = 8.    # end of "sustain" time
T0 = 8.    # time constan of the exponential amplitude decay

A0 = MRPy.from_pseudo(Sa, Tmax, (T1, T2, T0), zeta=0.05)
A0.printAttrib()

h18 = A0.plot_time(fig=18, figsize=(8, 3), axis_t=[0, A0.Td, -0.5, 0.5])
```

The specified acceleration pseudo-spectrum can be finally verified:

```python
Su0, Tmax0 = A0.pseudo()
Sa0        = np.zeros(Su0.shape)
Sa0[0,1:]  = Su0[0,1:]*(2*np.pi/T[1:])**2

plt.figure(19, figsize=(8, 3))
h19 = plt.plot(T, Sa0[0,:], 'b', lw=1)
h19 = plt.plot(T, Sa [0,:], 'r', lw=1)
plt.grid(True)

```

---
## 3.  Constructors through modification (object methods)

### 3.1 Remove mean value

```python

```

### 3.2 Extract a segment

```python

```

### 3.3.  Double length with trailing mean values

```python

```

### 3.4.  Extract segment

```python

```

### 3.5.  Apply amplitude envelope

```python

```

### 3.6.  Moving average

```python

```

### 3.7.  Bandpass and bandstop filters

```python

```

### 3.8.  Kanai/Tajimi filter for seismic acceleration

```python

```

### 3.9.  Integration and diferentiation in frequency domain

A time signal frequently needs to be integrated or diferentiated with respect to time. For instance, signal may be generated by sensor like accelerometers, what means that to yield the (fluctuating part of) displacement it must be twice integrated.

There are some issues with integrating a noisy signal, as the cumulative sum of random errors yields a _random walk_.

Let us demonstrate it with:

```python

```

Observe that, despite noise mean value being zero, there are large excursions with growing amplitude along time. The explanation is simple: the variance of the integrated process is the sum of variances of all previous samples, what means it is non-stationary and monotonically increasing.

To face this problem, the URP module provides frequency domain integral and derivative functions that allow the definition of a frequency band to be retained, while signal energy outside this band if fully disregarded. This means that whenever integrating a signal, the lower bound of this band may be specified as high enough to get rid of random walking.

To exemplify the bandpass integration, we firstly integrate with the full signal band available:

```python

```

... and now the band is restricted:

```python

```

By comparing the two results (which corresponds to the _same_ signal simulation) it is possible to see that the low frequency content can be arbitrarily removed without further affecting the signal. The most suitable value for the high pass frequency (lower bound of given frequency range) must be chosen in order to get rid of drift without attenuating relevant signal information.

On the other hand, whenever differentiating a signal, the upper bound may be specified as low enough to get rid of high frequency noise intrinsic to numerical derivatives.

To demonstrate this funcionallity, let us differentiate back the (high-pass) integrated signal of the previous section, but initially with the full available band:

```python

```

It can be observed that the original signal was _apparently_ fully recovered, but we know that the low frequency content has been removed as observed by a non-zero mean square error.

Now we repeat the same operation, but getting rid of some undesirable high frequency signal content. Let us say that the upper bound of the available frequency range will be only 1/4 of the available:

```python

```

and we finally check the integral of this signal:

```python

```

from which it can be observed that now the integral not only has no drifting problem but is also smoothed out due to the previous low pass differentiation. Obviously the same integral could have been obtained from applying both lower and upper bounds at the same time.

### 3.10. Numerical solution of the dynamic equilibrium equation

```python

```

### 3.11. Free system response by random decrement

```python

```

### 3.12. Fit theoretical free response equation

```python

```

---
## 4. Constructors from idealized properties

### 4.1.  Zero time series

```python
Z   = MRPy.zeros(NX=1, N=1024, fs=1024)
#f01 = Z.plot_time()

```

### 4.2.  Dirac's Delta function (unit impulse)

```python
D   = MRPy.Dirac(NX=1, N=1024, t0=5, Td=10)
f02 = D.plot_time()

```

### 4.3. Heaviside's function (unit step)

```python
H   = MRPy.Heaviside(NX=1, N=1024, t0=5, Td=10)
f03 = H.plot_time()

```

### 4.4.  White noise

```python
W   = MRPy.white_noise(NX=2, N=1000, Td=10)
f04 = plt.plot(W[0], W[1], 'b.')

```

### 4.5.  Pink noise

```python

```

---
## 5. Further methods for time series analysis

### 5.3.  Pseudo-spectrum estimation

### 5.4.  Peak factor by Davenport's formula

```python

```

### 5.5.  Peak factor by splitmax method

```python

```

---
## 6. General utilities

### 6.1.  Plot axis generation

```python

```

### 6.2.  Pre-visualization plots

```python

```

### 6.3.  Save to file

```python
X.to_file('myfile')
Y = MRPy.from_file('myfile')

X.printAttrib()
Y.printAttrib()
```

     fs = 512.0Hz
     Td = 16.0s

     NX = 3
     N  = 8192
     M  = 4097

     fs = 512.0Hz
     Td = 16.0s

     NX = 3
     N  = 8192
     M  = 4097

```python
X.to_file('myfile', form='excel')
Y = MRPy.from_file('myfile', form='excel')

X.printAttrib()
Y.printAttrib()
```

     fs = 512.0Hz
     Td = 16.0s

     NX = 3
     N  = 8192
     M  = 4097

     fs = 512.0Hz
     Td = 16.0s

     NX = 3
     N  = 8192
     M  = 4097

### 6.4.  Periodogram to autocovariance conversions

```python

```

### 6.5.  Wind speed turbulence spectra

```python

```

### 6.6. UBC pseudo-spectrum for seismic acceleration

As provided by UBC, the seismic acceleration pseudo spectrum depends on only two parameters: $C_a$ and $C_v$, as can be seen in the diagram below:

```python

```

---
## 7. Examples of application

###   7.1. Modal analysis of structural systems

```python

```

###   7.2. Wind turbulence response by NBR-6123

```python

```

###   7.3. Seismic response by UBC-1997

```python

```

---


# ***Introduction to Radar Using Python and MATLAB***
## Andy Harrison - Copyright (C) 2019 Artech House
<br/>

# Right Circular Cone Radar Cross Section
***

Referring to Section 7.4.1.6, the geometry of the right circular cone is given in Figure 7.11. For axial incidence, the radar cross section is independent of polarization, and is written as (Equation 7.48)

$$
    \sigma = \frac{\lambda^2}{\pi}\frac{\left(\dfrac{ka\sin(\pi/n)}{n}\right)^2}{\Big( \cos(\pi/n) - \cos(3\pi/n)\Big)^2} \hspace{0.5in} \text{(m}^2\text{)},
$$

Equation 7.48 is for first-order diffraction only, which is valid when $ka \gg 1$.  Taking double diffraction into account, a more accurate expression for the axial radar cross section is (Equation 7.49)

\begin{align}
    \sigma = \frac{\lambda^2}{\pi}\left(\dfrac{ka\sin(\pi/n)}{n}\right)^2\, \Bigg| &\dfrac{1}{\big( \cos(\pi/n) - \cos(3\pi/n)\big)^2}  \nonumber \\[7pt]
    &+ \frac{\sin(\pi/n)\, \exp\Big(j(2ka - \pi/4)\Big)}{n\sqrt{\pi k a}\, \big( \cos(\pi/n) - \cos(3\pi/2n)\big)^2} \Bigg|^2 \hspace{0.15in} \text{(m}^2\text{)}.\nonumber
\end{align}

If the angle of the incident energy is normal to the generator of the cone, $\theta_i = 90^\text{o} - \alpha$, then the geometrical theory of diffraction equations are no longer valid.  Instead, an expression based on the asymptotic expansion of the physical optics equation is used.  This is written as

\begin{equation}\label{eq:rcs_cone_normal}
    \sigma = \frac{8\lambda^2 \pi}{9 \sin^2\alpha\cos\alpha} \left(\frac{a}{\lambda}\right)^3 \hspace{0.5in} \text{(m}^2\text{)}.
\end{equation}

The other special case is when the cone is viewed from the base, $\theta_i = 180^o$.  For this case, the physical optics expression for a circular disc is used; see Table 7.1, and repeated here as

\begin{equation}\label{eq:rcs_cone_base}
    \sigma = \frac{\lambda^2(ka)^4}{4\pi} \hspace{0.5in} \text{(m}^2\text{)}.
\end{equation}

For all other incident angles, the radar cross section depends on the polarization of the incident energy, and is given by the following equations

\begin{align}\label{eq:rcs_cone_arbitrary_1}
    \sigma = &\frac{\lambda^2ka}{4\pi^2\sin\theta_i}\left(\frac{\sin(\pi/n)}{n}\right)^2 \times  \Bigg| \exp\Big[-j(2ka\sin\theta_i - \frac{\pi}{4})\Big] \Big[\Big(\cos\frac{\pi}{n} - 1\Big)^{-1} \nonumber \\[7pt] &\pm \Big(\cos\frac{\pi}{n} - \cos\frac{3\pi - 2\theta_i}{n} \Big)^{-1} \Big]  + \exp\Big[j(2ka\sin\theta_i - \frac{\pi}{4})\Big] \Big[\Big(\cos\frac{\pi}{n} - 1\Big)^{-1} \nonumber \\[7pt]
    & \pm \Big(\cos\frac{\pi}{n} - \cos\frac{3\pi + 2\theta_i}{n} \Big)^{-1} \Big] \Bigg|^2, \, \, 0 < \theta_i < \alpha;
\end{align}

\begin{align}\label{eq:rcs_cone_arbitrary_2}
    \sigma = \frac{\lambda^2ka}{4\pi^2\sin\theta_i}\left(\frac{\sin(\pi/n)}{n}\right)^2 &\Bigg[\Big(\cos\frac{\pi}{n} - 1\Big)^{-1} \pm \nonumber \\[7pt]
    & \Big(\cos\frac{\pi}{n} - \cos\frac{3\pi - 2\theta_i}{n} \Big)^{-1} \Bigg]^2, \, \,   \alpha < \theta_i < \pi/2;
\end{align}

\begin{align}\label{eq:rcs_cone_arbitrary_3}
    \sigma = &\frac{\lambda^2ka}{4\pi^2\sin\theta_i}\left(\frac{\sin(\pi/n)}{n}\right)^2 \times \Bigg| \exp\Big[-j(2ka\sin\theta_i - \frac{\pi}{4})\Big] \Big[\Big(\cos\frac{\pi}{n} - 1\Big)^{-1} \nonumber \\[7pt]
    &  \pm \Big(\cos\frac{\pi}{n} - \cos\frac{3\pi - 2\theta_i}{n} \Big)^{-1} \Big]  + \exp\Big[j(2ka\sin\theta_i - \frac{\pi}{4})\Big] \Big[\Big(\cos\frac{\pi}{n} - 1\Big)^{-1} \nonumber \\[7pt]
    &  \pm \Big(\cos\frac{\pi}{n} - \cos\frac{2\theta_i-\pi}{n} \Big)^{-1} \Big] \Bigg|^2, \, \, \pi/2 < \theta_i < \pi.
\end{align}

The positive sign is used for horizontal polarization and the negative sign is used for vertical polarization.
***

Begin by getting the library path

```python
import lib_path
```

Set the operating frequency (Hz), the cone half angle (radians), and the base radius (m)

```python
from numpy import radians

frequency = 1e9

cone_half_angle = radians(15.0)

base_radius = 1.4
```

Set up the incident angles (radians) using the `linspace` routine from `scipy`

```python
from numpy import linspace

from scipy.constants import pi

incident_angle = linspace(0, pi, 1801)
```

Calculate the radar cross section (m^2) for the right circular cone

```python
from Libs.rcs.right_circular_cone import radar_cross_section

from numpy import array

rcs = array([radar_cross_section(frequency, cone_half_angle, base_radius, ia) for ia in incident_angle])
```

Display the radar cross section (dBsm) for the right circular cone

```python
from matplotlib import pyplot as plt

from numpy import log10, degrees

# Set the figure size

plt.rcParams["figure.figsize"] = (15, 10)

# Display the results

plt.plot(degrees(incident_angle), 10 * log10(rcs[:, 0]), '', label='VV')

plt.plot(degrees(incident_angle), 10 * log10(rcs[:, 1]), '--', label='HH')

# Set the plot title and labels

plt.title('RCS vs Incident Angle', size=14)

plt.ylabel('RCS (dBsm)', size=12)

plt.xlabel('Incident Angle (deg)', size=12)

# Set the tick label size

plt.tick_params(labelsize=12)

# Turn on the grid

plt.grid(linestyle=':', linewidth=0.5)

# Set the legend

plt.legend(loc='upper right', prop={'size': 10})
```

```python

```


---
layout: page
title: Teorema Central do Limite
nav_order: 7
---

[](https://colab.research.google.com/github/icd-ufmg/icd-ufmg.github.io/blob/master/_lessons/07-tcl.ipynb)

# Teorema Central do Limite
{: .no_toc .mb-2 }

O teorema base para os nossos testes de hipóteses
{: .fs-6 .fw-300 }

{: .no_toc .text-delta }
Resultados Esperados

1. Revisar conceitos de Probabilidade ligados a distribuição normal
1. Revisar o teorema central do limite
1. Entendimento do teorema central do limite
1. Simular médias de qualquer distribuição
1. Mostrar como a distribuição de média segue uma normal

---
**Sumário**
1. TOC
{:toc}
---

```python
# -*- coding: utf8

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
```

```python
plt.style.use('seaborn-colorblind')

plt.rcParams['figure.figsize']  = (16, 10)
plt.rcParams['axes.labelsize']  = 20
plt.rcParams['axes.titlesize']  = 20
plt.rcParams['legend.fontsize'] = 20
plt.rcParams['xtick.labelsize'] = 20
plt.rcParams['ytick.labelsize'] = 20
plt.rcParams['lines.linewidth'] = 4
```

```python
plt.ion()
```

```python
def despine(ax=None):
    if ax is None:
        ax = plt.gca()
    # Hide the right and top spines
    ax.spines['right'].set_visible(False)
    ax.spines['top'].set_visible(False)

    # Only show ticks on the left and bottom spines
    ax.yaxis.set_ticks_position('left')
    ax.xaxis.set_ticks_position('bottom')
```

## Introdução

Uma razão pela qual a distribuição normal é tão útil é o teorema central do limite, que diz (em essência) que uma variável aleatória definida como a média (ou soma) de um grande número de variáveis aleatórias independentes e identicamente distribuídas é aproximadamente distribuída normalmente. Em outras palavras, a distribuição amostral de médias segue uma normal.

Em detalhes, se $X_1, ..., X_n$ são variáveis aleatórias. Em particular, todas as VAs foram amostradas de uma mesma população com média $\mu$ (finita), desvio padrão $\sigma$ (finito). Além do mais, a geração de cada VA é independente da outra, sendo toas identicamente distribuídas. Quando $n$ é grande, então

$$\frac{1}{n}(X_1 + \cdots + X_n)$$

é aproximadamente distribuído por uma Normal com média $\mu$ e desvio padrão $\sigma/\sqrt{n}$. De forma equivalente (mas muitas vezes mais útil),

$$Z = \frac{(X_1 + \cdots + X_n) - \mu }{\sigma / \sqrt{n}}$$

é aproximadamente uma normal com média 0 e desvio padrão 1.

$$Z \sim Normal(0, 1).$$

### Como transformar VAs?

Lemebre-se da aula passada que sabemos estimar:

$$\bar{x} \approx \mu$$

e

$$s^2 \approx \sigma$$

Além do mais, sabemos que a variância do estimador da média é:

$$Var(\hat{\mu}) = \frac{\sigma^2}{n}$$

Assim:

\begin{align}
\bar{X} \sim Normal(\mu, \frac{\sigma^2}{n}) \\
\bar{X}- \mu \sim Normal(0, \frac{\sigma^2}{n}) \\
\frac{\bar{X}- \mu}{\sigma / \sqrt{n}} \sim Normal(0, 1) \\
\end{align}

## Exemplo das Moedas

Considere o caso de uma moeda sendo jogada para cima. Agora, escolhe um número `n` (tamanho da amostra), e gere amostras da mesma. Ou seja, jogue uma moeda para cima `n` vezes. Por fim, some quantas vezes a moeda cai em `cara` (ou `coroa`). Isto é uma soma para uma amostra de tamanho `n`.

O processo de geração destes dados é bem capturado por uma distribuição Binomial. Variáveis aleatórias binomiais, que possuem dois parâmetros $n$ e $p$. A distribuição binomial é útil para contar o número de sucessos $n$ dada uma probabilidade $p$. Por exemplo, quantas vezes ($n$) uma moeda ($p$) gera um o valor cara. Formalmente, uma variável aleatória Binomial($n, p$) é simplesmente a soma de $n$ variáveis aleatórias independentes de Bernoulli($p$), cada uma delas igual a $1$ com probabilidade $p$ e $0$ com probabilidade $1 - p$.

Ao gerar um valor de uma Binomial estamos falando "Jogue uma moeda para cima n vezes e conte quantas caras!". No caso abaixo, jogue uma moeda para cima 5 vezes e conte quantas caras!

```python
num_caras = np.random.binomial(5, 0.5)
num_caras
```

    4

Vamos repetir o processo várias! Jogue uma moeda para cima 5 vezes, pare, respire, depois jogue mais 5. Por aí vai. Note que temos a contagem para cada experimento de tamanho 5.

```python
np.random.binomial(5, 0.5, size=10)
```

    array([3, 2, 3, 1, 1, 3, 2, 3, 2, 1])

Agora, vamos ver o gráfico de tal experimento!

```python
num_caras_a_cada_5 = np.random.binomial(5, 0.5, size=10000)
plt.hist(num_caras_a_cada_5, bins=[-0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5], edgecolor='k')
plt.xlabel('Jogadas de moedas')
plt.ylabel('Número de caras')
plt.title('1000 jogadas de moedas em grupos de 5')
despine()
```

Mesma coisa com 10 moedas

```python
num_caras_a_cada_5 = np.random.binomial(10, 0.5, size=1000000)
ticks = np.linspace(-0.5, 10.5, num=12)
print(ticks)
plt.hist(num_caras_a_cada_5, bins=ticks, edgecolor='k')
plt.xlabel('Jogadas de moedas')
plt.ylabel('Número de caras')
plt.title('1000 jogadas de moedas em grupos de 5')
despine()
```

Lembrando da sua aula de probabilidade, uma população que segue uma variável Binomial($n, p$) tem média $\mu = np$ e desvio padrão $\sigma = \sqrt{np(1 - p)}$. Se plotarmos ambos, você pode facilmente ver a semelhança. Obseve o plot abaixo da PDF com os parâmetros que listamos (média, desvio padrão).

```python
import scipy.stats as ss

mean = 10 * 0.5
std = np.sqrt(10 * 0.5 *(1 - 0.5))
x = np.linspace(1, 11, 1000)
y = ss.distributions.norm.pdf(loc=mean, scale=std, x=x)
plt.xlim(0, 10)
plt.plot(x, y)
plt.xlabel('Número caras - x')
plt.ylabel('P(X = x)')
despine()
```

```python
mean = 10 * 0.5
std = np.sqrt(10 * 0.5 *(1 - 0.5))
x = np.linspace(1, 11, 1000)
y = ss.distributions.norm.pdf(loc=mean, scale=std, x=x)
plt.xlim(0, 10)
plt.plot(x, y, label='Aproximação Normal')

num_caras_a_cada_5 = np.random.binomial(10, 0.5, size=1000000)
ticks = np.linspace(-0.5, 10.5, num=12)
plt.hist(num_caras_a_cada_5, bins=ticks, edgecolor='k', label='Dados',
         density=True)
plt.plot(x, y)
plt.xlabel('Jogadas de moedas')
plt.ylabel('Número de caras')
plt.title('1000 jogadas de moedas em grupos de 5')
plt.legend()
despine()
```

## Exemplo com Dados Sintéticos de Matrículas

Para exemplificar com dados, considere o exemplo abaixo onde geramos uma distribuição sintética de 25 mil alunos da UFMG. A distribuição captura o número de matéria que um aluno se matrícula no ano. Note que diferente da moeda, que gera apenas cara ou cora, cada aluno pode se matricular entre [min, max] matérias. No exemplo, vamos suport que todo aluno precisa se matricular em pelo menos uma matéria `min=1` e pode ser matricular em no máximo 10 matérias `max=10`. Agora, vamos suport que cada número de matéria tem probabilidade $p_i$. Ou seja, a chance se matricular em uma matéria é $p_1$ e por aí vai.

Dados deste tipo são modelados por distribuições multinomiais. Generalizando a Binomial, uma Multinomial conta a quantidade de sucessos (matrículas) em cada $p_i$. A mesma é definida por $n > 0$, número de amostras ou matrículas, e $p_1, \ldots, p_k$ probabilidade de se matrícular em $i$ matérias. A pmf de uma multinomial é dada por:

$$P(X = x) = \frac{n!}{x_1!\cdots x_k!} p_1^{x_1} \cdots p_k^{x_k}$$

Inicialmente observe os valores de $p_i$.

```python
num_materias = np.arange(10) + 1
prob_materias = np.array([6, 7, 16, 25, 25, 25, 10, 12, 2, 11])
prob_materias = prob_materias / prob_materias.sum()
plt.bar(num_materias, prob_materias, edgecolor='k')
plt.xlabel('Número de Matérias no Semestre')
plt.ylabel('Fração de Alunos')
plt.title('Distribuição do número de matérias no semestre')
despine()
```

Agora vamos responder a pergunta: **Quantas matérias, em média, um aluno se matrícula?!**. Note que a nossa pergunta aqui é **em média!!**. Então, vamos considerar que temos 25 mil discente na ufmg. Para cada um destes alunos, vamos amostrar de $p_i$ o número de matérias que tal aluno está matrículado no atual semestre.

```python
amostras = 25000
mats = np.arange(10) + 1
print(mats)

dados = []
for i in range(25000):
    n_mat = np.random.choice(mats, p=prob_materias)
    dados.append(n_mat)
dados = np.array(dados)
dados
```

    [ 1  2  3  4  5  6  7  8  9 10]

    array([ 6,  5,  2, ..., 10,  8,  8])

Agora vamos responder nossa pergunta. **Quantas matérias, em média, um aluno se matrícula?!**. Para tirar uma média precisamos de uma amostra. Vamos definir amostras de tamanho 100. Então, vamos amostrar 100 alunos, **com repetição**, dos nossos 25 mil alunos.

```python
n_amostra = 100
soma = 0
for i in range(n_amostra):
    aluno = np.random.randint(0, len(dados))
    num_mat = dados[aluno]
    soma += num_mat
media = soma / n_amostra
print(media)
```

    5.47

Vamos repetir o processo algumas vezes. Tipo, 10000 vezes.

```python
n_amostra = 100
medias = []
for _ in range(10000):
    soma = 0
    for i in range(n_amostra):
        aluno = np.random.randint(0, len(dados))
        num_mat = dados[aluno]
        soma += num_mat
    media = soma / n_amostra
    medias.append(media)
medias = np.array(medias)
```

Agora vamos ver os resultados!

```python
plt.hist(medias, bins=20, edgecolor='k')
plt.ylabel('P(X = x)')
plt.xlabel('Média das matérias - x')
plt.title('CLT na Prática')
despine()
```

Agora, vamos comparar com a nossa Normal, para isto podemos usar a média das médias e o desvio padrão das médias.

```python
mean = np.mean(medias)
# ddof=1 faz dividir por n-1
std = np.std(medias, ddof=1)

# pegue 1000 números entre o minimo e o max
x = np.linspace(np.min(medias), np.max(medias), 1000)
y = ss.distributions.norm.pdf(loc=mean, scale=std, x=x)

plt.plot(x, y, label='Aproximação Normal')

plt.hist(medias, bins=20, edgecolor='k', density=True)
plt.ylabel('P(X = x)')
plt.xlabel('Média das matérias - x')
plt.title('CLT na Prática')
despine()
```

## Com Dados

```python
df = pd.read_csv('https://media.githubusercontent.com/media/icd-ufmg/material/master/aulas/03-Tabelas-e-Tipos-de-Dados/nba_salaries.csv')
df.head()
```

      PLAYER
      POSITION
      TEAM
      SALARY

      0
      Paul Millsap
      PF
      Atlanta Hawks
      18.671659

      1
      Al Horford
      C
      Atlanta Hawks
      12.000000

      2
      Tiago Splitter
      C
      Atlanta Hawks
      9.756250

      3
      Jeff Teague
      PG
      Atlanta Hawks
      8.000000

      4
      Kyle Korver
      SG
      Atlanta Hawks
      5.746479

```python
df['SALARY'].sort_values().plot.hist(bins=20, edgecolor='k')
plt.xlabel('Salário na NBA')
plt.ylabel('Número de Linhas')
despine()
```

```python
N = 10000
data = df['SALARY']
medias = []
for i in range(N):
    mean = np.random.choice(data, 100).mean()
    medias.append(mean)
```

```python
mean = np.mean(medias)
# ddof=1 faz dividir por n-1
std = np.std(medias, ddof=1)

# pegue 1000 números entre o minimo e o max
x = np.linspace(np.min(medias), np.max(medias), 1000)
y = ss.distributions.norm.pdf(loc=mean, scale=std, x=x)

plt.plot(x, y, label='Aproximação Normal')

plt.hist(medias, bins=20, edgecolor='k', density=True)
plt.ylabel('P(X = x)')
plt.xlabel('Salário da NBA - x')
plt.title('CLT na Prática')
despine()
```

## Condições para o TCL

Existem algumas condições para garantir que o TCL  seja válido.

1. Dados independentes e identicamente distribuídos.
1. Variância finita.
1. Pelo menos umas 30 amostras

Observe do wikipedia que uma distribuição Pareto(1) tem variância infinita. Quebramos nossa condição. Olhe que o plot abaixo não parece em nada com uma Normal.

https://en.wikipedia.org/wiki/Pareto_distribution

```python
data = []
for _ in range(10000):
    m = np.random.pareto(1, size=100).mean()
    data.append(m)
```

```python
plt.hist(data, bins=100, edgecolor='k')
despine()
```

Podemos quebrar também com amostras muito pequenas, tipo na Beta(3, 2, size=2) abaixo.

Observe como é muito perto de uma Normal mas tem um certo viés para a direita.

```python
data = []
for _ in range(10000):
    m = np.random.beta(3, 2, size=2).mean()
    data.append(m)
plt.hist(data, edgecolor='k')
despine()
```

```python
mean = np.mean(data)
# ddof=1 faz dividir por n-1
std = np.std(data, ddof=1)

# pegue 1000 números entre o minimo e o max
x = np.linspace(np.min(data), np.max(data), 1000)
y = ss.distributions.norm.pdf(loc=mean, scale=std, x=x)

plt.plot(x, y, label='Aproximação Normal')

plt.hist(data, bins=20, edgecolor='k', density=True)
plt.ylabel('P(X = x)')
plt.title('CLT na Prática')
despine()
```


## Subsurface Data Analytics

## Interactive Demonstration of Ridge Regression and Intro to Hyperparameter Tuning

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

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

In the Fall of 2019 my students requested a demonstration to show the value of ridge regression. I wrote this interactive demonstration to show cases in which the use of regularization coefficient, a hyperparameter, that reduces the model flexibilty / sensivity to training data (reduces model variance) improves the prediction accuracy.

### PGE 383 Exercise: Interactive Demonstration of Ridge Regression and Intro to Hyperparameter Tuning

Let's start by introducing linear regression, expanding to ridge regression with a regularization coefficient and then explain the 2 interactive demonstrations in this notebook (we had a 2 for 1 sale this week!).

#### Linear Regression

Linear regression for prediction.  Here are some key aspects of linear regression:

**Parametric Model**

* the fit model is a simple weighted linear additive model based on all the available features, $x_1,\ldots,x_m$.

* the parametric model takes the form of:

\begin{equation}
y = \sum_{\alpha = 1}^m b_{\alpha} x_{\alpha} + b_0
\end{equation}

**Least Squares**

* least squares optimization is applied to select the model parameters, $b_1,\ldots,b_m,b_0$

* we minize the error, residual sum of squares (RSS) over the training data:

\begin{equation}
RSS = \sum_{i=1}^n (y_i - (\sum_{\alpha = 1}^m b_{\alpha} x_{\alpha} + b_0))^2
\end{equation}

* this could be simplified as the sum of square error over the training data,

\begin{equation}
\sum_{i=1}^n (\Delta y_i)^2
\end{equation}

**Assumptions**

* **Error-free** - predictor variables are error free, not random variables
* **Linearity** - response is linear combination of feature(s)
* **Constant Variance** - error in response is constant over predictor(s) value
* **Independence of Error** - error in response are uncorrelated with each other
* **No multicollinearity** - none of the features are redundant with other features

#### Other Resources

This is a tutorial / demonstration of **Linear Regression**.  In $Python$, the $SciPy$ package, specifically the $Stats$ functions (https://docs.scipy.org/doc/scipy/reference/stats.html) provide excellent tools for efficient use of statistics.
I have previously provided this example in R and posted it on GitHub:

1. R https://github.com/GeostatsGuy/geostatsr/blob/master/linear_regression_demo_v2.R
2. Rmd with docs https://github.com/GeostatsGuy/geostatsr/blob/master/linear_regression_demo_v2.Rmd
3. knit as an HTML document(https://github.com/GeostatsGuy/geostatsr/blob/master/linear_regression_demo_v2.html)

#### Ridge Regression

With ridge regression we add a hyperparameter, $\lambda$, to our minimization, with a shrinkage penalty term.

\begin{equation}
\sum_{i=1}^n (y_i - (\sum_{\alpha = 1}^m b_{\alpha} x_{\alpha} + b_0))^2 + \lambda \sum_{j=1}^m b_{\alpha}^2
\end{equation}

As a result ridge regression has 2 criteria:

* set the model parameters to minimize the error with training data

* shrink the estimates of the slope parameters towards zero

Note: the intercept is not affected by lambda.

The $\lambda$ is a hyperparameter that controls the degree of fit of the model and may be related to the model variance and bias trade-off.

* for $\lambda \rightarrow 0$ the solution approaches linear regression, there is no bias (relative to a linear model fit), but the variance is high

* as $\lambda$ increases the model variance decreases and the model bias increases

* for $\lambda \rightarrow \infty$ the coefficients approach 0.0 and the model approaches the global mean

#### Train / Test Split

The available data is split into training and testing subsets.

* in general 15-30% of the data is withheld from training to apply as testing data

* testing data selection should be fair, the same difficulty of predictions (offset/different from the training dat

#### Machine Learning Model Training

The training data is applied to train the model parameters such that the model minimizes mismatch with the training data

* it is common to use **mean square error** (known as a **L2 norm**) as a loss function summarizing the model mismatch

* **miminizing the loss function** for simple models an anlytical solution may be available, but for most machine this requires an iterative optimization method to find the best model parameters

This process is repeated over a range of model complexities specified by hyperparameters.

#### Machine Learning Model Tuning

The withheld testing data is retrieved and loss function (usually the **L2 norm** again) is calculated to summarize the error over the testing data

* this is repeated over over the range of specified hypparameters

* the model complexity / hyperparameters that minimize the loss function / error summary in testing is selected

This is known are model hypparameter tuning.

#### Machine Learning Model Overfit

More model complexity/flexibility than can be justified with the available data, data accuracy, frequency and coverage

* Model explains “idiosyncrasies” of the data, capturing data noise/error in the model

* High accuracy in training, but low accuracy in testing / real-world use away from training data cases – poor ability of the model to generalize

#### The Interactive Demonstrations

Here's a simple workflow, demonstration of predictive machine learning model training and testing for overfit.  We use a:

* simple polynomial model

* 1 preditor feature and 1 response feature

for an high interpretability model/ simple illustration.

#### Workflow Goals

Learn the basics of machine learning training, tuning for model generalization while avoiding model overfit. We use the very simple case of ridge regression that introduces a hyperparameter to linear regression.

We consider 2 examples:

1. **A linear model + noise** to make a random dataset with 1 predictor feature and 1 response feature. You can make the model, by-hand set the lambda hyperparameter and observe the impact. The code actually runs many lambda values so you can explore accurate and inacturate (over and underfit models).

2. **A loaded multivariate subsurface dataset** with random resampling to explore uncertainty in your result. Like above you can set the lambda hyperparameter by-hand and observe the model accuracy in train and test over a range model flexibility. In this case, since the model is quite multidimensional, you can see the cross validation plot instead of the visualization of the model.

#### Getting Started

You will need to copy the following data files to your working directory.  They are available [here](https://github.com/GeostatsGuy/GeoDataSets):

* [unconv_MV.csv](https://github.com/GeostatsGuy/GeoDataSets/blob/master/unconv_MV.csv)

The dataset is available in this repository, https://github.com/GeostatsGuy/GeoDataSets.

* download this file to your working directory

#### Import Required Packages

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

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

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

```python
%matplotlib inline
import os                                               # to set current working directory
import sys                                              # supress output to screen for interactive variogram modeling
import io
import numpy as np                                      # arrays and matrix math
import pandas as pd                                     # DataFrames
import matplotlib.pyplot as plt                         # plotting
from sklearn.model_selection import train_test_split    # train and test split
from sklearn.metrics import mean_squared_error          # model error calculation
from sklearn.linear_model import Ridge                  # ridge regression
import scipy                                            # kernel density estimator for PDF plot
from matplotlib.pyplot import cm                        # color maps
from ipywidgets import interactive                      # widgets and interactivity
from ipywidgets import widgets
from ipywidgets import Layout
from ipywidgets import Label
from ipywidgets import VBox, HBox
```

If you get a package import error, you may have to first install some of these packages. This can usually be accomplished by opening up a command window on Windows and then typing 'python -m pip install [package-name]'. More assistance is available with the respective package docs.

### Demonstration 1, Simple Linear Model + Noise

Let's build the code and dashboard in one block for concisenss. I have other examples to cover basics if you need that.

#### Build the Interactive Dashboard

The following code:

* makes a random dataset, change the random number seed and number of data for a different dataset
* loops over polygonal fits of 1st-12th order, loops over mulitple realizations and calculates the average MSE and P10 and P90 vs. order
* calculates a specific model example
* plots the example model with training and testing data, the error distributions and the MSE envelopes vs. complexity

```python
import warnings
warnings.filterwarnings('ignore')

text_trap = io.StringIO()
sys.stdout = text_trap

l = widgets.Text(value='                            Machine Learning Ridge Regression Hyperparameter Tuning Demo, Prof. Michael Pyrcz, The University of Texas at Austin',
                 layout=Layout(width='950px', height='30px'))

n = widgets.IntSlider(min=5, max = 200, value=30, step = 1, description = 'n',orientation='horizontal', style = {'description_width': 'initial'}, continuous_update=False)
split = widgets.FloatSlider(min=0.05, max = .95, value=0.20, step = 0.05, description = 'Test %',orientation='horizontal',style = {'description_width': 'initial'}, continuous_update=False)
std = widgets.FloatSlider(min=0, max = 200, value=0, step = 1.0, description = 'Noise StDev',orientation='horizontal',style = {'description_width': 'initial'}, continuous_update=False)
lam = widgets.FloatLogSlider(min=-5.0, max = 5.0, value=1,base=10,step = 0.2,description = 'Regularization',orientation='horizontal', style = {'description_width': 'initial'}, continuous_update=False)

ui = widgets.HBox([n,split,std,lam],)
ui2 = widgets.VBox([l,ui],)

def run_plot(n,split,std,lam):
    seed = 13014; nreal = 20; slope = 20.0; intercept = 50.0
    np.random.seed(seed) # seed the random number generator
    lam_min = 1.0E-5;lam_max = 1.0e5

    # make the datastet
    X_seq = np.linspace(0.0,100,100)
    X = np.random.rand(n)*20
    y = X*slope + intercept # assume linear + error
    y = y + np.random.normal(loc = 0.0,scale=std,size=n) # add noise

    # calculate the MSE train and test over a range of complexity over multiple realizations of test/train split
    clam_list = np.logspace(-5,5,20,base=10.0)
    #print(clam_list)
    cmse_train = np.zeros([len(clam_list),nreal]); cmse_test = np.zeros([len(clam_list),nreal])
    cmse_truth = np.zeros([len(clam_list),nreal])
    for j in range(0,nreal):
        for i, clam in enumerate(clam_list):
            #print('lam' + str(clam))
            X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=split, random_state=seed+j)
            n_train = len(X_train); n_test = len(X_test)
            ridge_reg = Ridge(alpha=clam)
            #print(X_train.reshape(n_train,1))
            ridge_reg.fit(X_train.reshape(n_train,1),y_train)
            #print('here')
            y_pred_train = ridge_reg.predict(X_train.reshape(n_train,1))
            y_pred_test = ridge_reg.predict(X_test.reshape(n_test,1))
            y_pred_truth = ridge_reg.predict(X_seq.reshape(len(X_seq),1))
            y_truth = X_seq*slope + intercept
            cmse_train[i,j] = mean_squared_error(y_train, y_pred_train)
            cmse_test[i,j] = mean_squared_error(y_test, y_pred_test)
            cmse_truth[i,j] = mean_squared_error(y_truth, y_pred_truth)
    # summarize over the realizations
    cmse_train_avg = cmse_train.mean(axis=1)
    cmse_test_avg = cmse_test.mean(axis=1)
    cmse_truth_avg = cmse_truth.mean(axis=1)
    cmse_train_high = np.percentile(cmse_train,q=90,axis=1)
    cmse_train_low = np.percentile(cmse_train,q=10,axis=1)
    cmse_test_high = np.percentile(cmse_test,q=90,axis=1)
    cmse_test_low = np.percentile(cmse_test,q=10,axis=1)
    cmse_truth_high = np.percentile(cmse_truth,q=90,axis=1)
    cmse_truth_low = np.percentile(cmse_truth,q=10,axis=1)

#     cmse_train_high = np.amax(cmse_train,axis=1)
#     cmse_train_low = np.amin(cmse_train,axis=1)
#     cmse_test_high = np.amax(cmse_test,axis=1)
#     cmse_test_low = np.amin(cmse_test,axis=1)

    # build the one model example to show
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=split, random_state=seed)
    n_train = len(X_train); n_test = len(X_test)
    ridge_reg = Ridge(alpha=lam)
    ridge_reg.fit(X_train.reshape(n_train,1),y_train)
    y_pred_train = ridge_reg.predict(X_train.reshape(n_train,1))
    y_pred_test = ridge_reg.predict(X_test.reshape(n_test,1))
    y_pred_truth = ridge_reg.predict(X_seq.reshape(len(X_seq),1))

    # calculate error
    error_seq = np.linspace(-100.0,100.0,100)
    error_train = y_pred_train - y_train
    error_test = y_pred_test - y_test
    error_truth = X_seq*slope + intercept - y_truth
    mse_train = mean_squared_error(y_train,y_pred_train)
    mse_test = mean_squared_error(y_test,y_pred_test)
    mse_truth = mean_squared_error(y_truth,y_pred_truth)

    error_train_std = np.std(error_train)
    error_test_std = np.std(error_test)
    error_truth_std = np.std(error_truth)

    #kde_error_train = scipy.stats.gaussian_kde(error_train)
    #kde_error_test = scipy.stats.gaussian_kde(error_test)

    plt.subplot(131)
    plt.plot(X_seq, ridge_reg.predict(X_seq.reshape(len(X_seq),1)), color="black")
    plt.plot(X_seq, X_seq*slope+intercept, color="black",alpha = 0.2)
    plt.title("Ridge Regression Model, Lambda = "+str(round(lam,2)))
    plt.scatter(X_train,y_train,c ="red",alpha=0.2,edgecolors="black")
    plt.scatter(X_test,y_test,c ="blue",alpha=0.2,edgecolors="black")
    plt.ylim([0,500]); plt.xlim([0,20]); plt.grid()
    plt.xlabel('Porosity (%)'); plt.ylabel('Permeability (mD)')

    plt.subplot(132)
    plt.hist(error_train, facecolor='red',bins=np.linspace(-200.0,200.0,10),alpha=0.2,density=True,edgecolor='black',label='Train')
    plt.hist(error_test, facecolor='blue',bins=np.linspace(-200.0,200.0,10),alpha=0.2,density=True,edgecolor='black',label='Test')
    #plt.plot(error_seq,kde_error_train(error_seq),lw=2,label='Train',c='red')
    #plt.plot(error_seq,kde_error_test(error_seq),lw=2,label='Test',c='blue')
    #plt.xlim([-55.0,55.0]);
    plt.ylim([0,0.1])
    plt.xlabel('Model Error'); plt.ylabel('Frequency'); plt.title('Training and Testing Error, Lambda = '+str((round(lam,2))))
    plt.legend(loc='upper left')
    plt.grid(True)

    plt.subplot(133); ax = plt.gca()
    plt.plot(clam_list,cmse_train_avg,lw=2,label='Train',c='red')
    ax.fill_between(clam_list,cmse_train_high,cmse_train_low,facecolor='red',alpha=0.05)

    plt.plot(clam_list,cmse_test_avg,lw=2,label='Test',c='blue')
    ax.fill_between(clam_list,cmse_test_high,cmse_test_low,facecolor='blue',alpha=0.05)

    plt.plot(clam_list,cmse_truth_avg,lw=2,label='Truth',c='black')
    ax.fill_between(clam_list,cmse_truth_high,cmse_truth_low,facecolor='black',alpha=0.05)

    plt.xscale('log'); plt.xlim([lam_max,lam_min]); plt.yscale('log'); #plt.ylim([10,10000])
    plt.xlabel('Model Flexibility by Regularization, Lambda'); plt.ylabel('Mean Square Error'); plt.title('Training and Testing Error vs. Model Complexity')
    plt.legend(loc='upper left')
    plt.grid(True)

    plt.plot([lam,lam],[1.0e-5,1.0e5],c = 'black',linewidth=3,alpha = 0.8)

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

# connect the function to make the samples and plot to the widgets
interactive_plot = widgets.interactive_output(run_plot, {'n':n,'split':split,'std':std,'lam':lam})
interactive_plot.clear_output(wait = True)               # reduce flickering by delaying plot updating

```

### Interactive Machine Learning Ridge Regression Hyperparameter Tuning Demonstation

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

Change the number of sample data, train/test split and the data noise and observe hyperparameter tuning! Change the regularization hyperparameter, lambda, to observe a specific model example.  Based on a simple, linear truth model + noise.

### The Inputs

* **n** - number of data
* **Test %** - percentage of sample data withheld as testing data
* **Noise StDev** - standard deviation of random Gaussian error added to the data
* **Regularization Hyperparameter** - the lambda coefficient, weight in loss the function on minimization of the  model parameters

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

    VBox(children=(Text(value='                            Machine Learning Ridge Regression Hyperparameter Tuning…

    Output()

#### Observations

What did we learn?

* regularization reduces the model sensitivity to the data, this results in reduced model variance
* regularization is helpful with sparse data with noise

When the noise is removed from the dataset, regularization increase / flexibility and sensitivity reduction reduces the quality of the model.

Let's check out a more extreme example to really see this effect.

* we can enhance data sparcity by increasing the problem dimensionality
* we will use more realistic, noisy data

### Demonstration 2: Multivariate, Realistic Dataset

We have to load data, set set the directory to the location that you have placed this dataset.

* [unconv_MV.csv](https://github.com/GeostatsGuy/GeoDataSets/blob/master/unconv_MV.csv)

The dataset is available in this repository, https://github.com/GeostatsGuy/GeoDataSets.

* download this file to your working directory

#### Set the working directory

I always like to do this so I don't lose files and to simplify subsequent read and writes (avoid including the full address each time).  Also, in this case make sure to place the required (see below) data file in this working directory.

```python
os.chdir("d:\PGE337")                                       # set the working directory
```

#### Load the data

The following loads the data into a DataFrame and then previews the first 5 samples.

```python
df_mv = pd.read_csv("unconv_MV.csv")
df_mv.head()
```

      WellIndex
      Por
      LogPerm
      AI
      Brittle
      TOC
      VR
      Production

      0
      1
      15.91
      1.67
      3.06
      14.05
      1.36
      1.85
      177.381958

      1
      2
      15.34
      1.65
      2.60
      31.88
      1.37
      1.79
      1479.767778

      2
      3
      20.45
      2.02
      3.13
      63.67
      1.79
      2.53
      4421.221583

      3
      4
      11.95
      1.14
      3.90
      58.81
      0.40
      2.03
      1488.317629

      4
      5
      19.53
      1.83
      2.57
      43.75
      1.40
      2.11
      5261.094919

#### Check the data

We should do much more data investigation. I do this in most of my workflows, but for this one, let's just calculate the summary statistics and call it a day!

```python
df_mv.describe().transpose()
```

      count
      mean
      std
      min
      25%
      50%
      75%
      max

      WellIndex
      1000.0
      500.500000
      288.819436
      1.000000
      250.75000
      500.50000
      750.250000
      1000.00000

      Por
      1000.0
      14.950460
      3.029634
      5.400000
      12.85750
      14.98500
      17.080000
      24.65000

      LogPerm
      1000.0
      1.398880
      0.405966
      0.120000
      1.13000
      1.39000
      1.680000
      2.58000

      AI
      1000.0
      2.982610
      0.577629
      0.960000
      2.57750
      3.01000
      3.360000
      4.70000

      Brittle
      1000.0
      49.719480
      15.077006
      -10.500000
      39.72250
      49.68000
      59.170000
      93.47000

      TOC
      1000.0
      1.003810
      0.504978
      -0.260000
      0.64000
      0.99500
      1.360000
      2.71000

      VR
      1000.0
      1.991170
      0.308194
      0.900000
      1.81000
      2.00000
      2.172500
      2.90000

      Production
      1000.0
      2247.295809
      1464.256312
      2.713535
      1191.36956
      1976.48782
      3023.594214
      12568.64413

#### Build the Interactive Dashboard

The following code:

* makes a random sample of the dataset
* loops over ridge regression fits varying the lambda between 1.0e-5 to 1.0e5, loops over mulitple realizations and calculates the average MSE and P10 and P90 vs. lambda.
* calculates specified model cross validation
* plots the example model with training and testing data, the error distributions and the MSE envelopes vs. complexity/inverse of lambda

```python
import warnings
warnings.filterwarnings('ignore')

text_trap = io.StringIO()
sys.stdout = text_trap

l = widgets.Text(value='                          Machine Learning Ridge Regression Hyperparameter Tuning Demo, Prof. Michael Pyrcz, The University of Texas at Austin',
                 layout=Layout(width='950px', height='30px'))

n = widgets.IntSlider(min=5, max = 200, value=30, step = 1, description = 'n',orientation='horizontal', style = {'description_width': 'initial'}, continuous_update=False)
split = widgets.FloatSlider(min=0.05, max = .95, value=0.20, step = 0.05, description = 'Test %',orientation='horizontal',style = {'description_width': 'initial'}, continuous_update=False)
lam = widgets.FloatLogSlider(min=-5.0, max = 5.0, value=1,base=10,step = 0.2,description = 'Regularization',orientation='horizontal', style = {'description_width': 'initial'}, continuous_update=False)

ui = widgets.HBox([n,split,lam],)
ui2 = widgets.VBox([l,ui],)

def run_plot(n,split,lam):
    seed = 13014; nreal = 20; slope = 20.0; intercept = 50.0
    np.random.seed(seed) # seed the random number generator
    lam_min = 1.0E-5;lam_max = 1.0e5

    df_sample_mv = df_mv.sample(n=n)
    df_X_mv = df_sample_mv[['Por','LogPerm','AI','Brittle','TOC','VR']]
    df_y_mv = df_sample_mv[['Production']]

    # calculate the MSE train and test over a range of complexity over multiple realizations of test/train split
    clam_list = np.logspace(-5,5,20,base=10.0)
    #print(clam_list)
    cmse_train = np.zeros([len(clam_list),nreal]); cmse_test = np.zeros([len(clam_list),nreal])
    for j in range(0,nreal):
        for i, clam in enumerate(clam_list):
            #print('lam' + str(clam))
            df_X_mv_train, df_X_mv_test, df_y_mv_train, df_y_mv_test = train_test_split(df_X_mv, df_y_mv, test_size=split, random_state=seed+j)
            n_train = len(df_X_mv_train); n_test = len(df_X_mv_test)
            ridge_reg = Ridge(alpha=clam)
            #print(X_train.reshape(n_train,1))
            ridge_reg.fit(df_X_mv_train.values,df_y_mv_train.values)
            #print('here')
            y_pred_train = ridge_reg.predict(df_X_mv_train.values)
            y_pred_test = ridge_reg.predict(df_X_mv_test.values)
            cmse_train[i,j] = mean_squared_error(df_y_mv_train.values, y_pred_train)
            cmse_test[i,j] = mean_squared_error(df_y_mv_test.values, y_pred_test)
    # summarize over the realizations
    cmse_train_avg = cmse_train.mean(axis=1)
    cmse_test_avg = cmse_test.mean(axis=1)
    cmse_train_high = np.percentile(cmse_train,q=90,axis=1)
    cmse_train_low = np.percentile(cmse_train,q=10,axis=1)
    cmse_test_high = np.percentile(cmse_test,q=90,axis=1)
    cmse_test_low = np.percentile(cmse_test,q=10,axis=1)

#     cmse_train_high = np.amax(cmse_train,axis=1)
#     cmse_train_low = np.amin(cmse_train,axis=1)
#     cmse_test_high = np.amax(cmse_test,axis=1)
#     cmse_test_low = np.amin(cmse_test,axis=1)

    # build the one model example to show

    df_X_mv_train, df_X_mv_test, df_y_mv_train, df_y_mv_test = train_test_split(df_X_mv, df_y_mv, test_size=split, random_state=seed+j)
    n_train = len(df_X_mv_train); n_test = len(df_X_mv_test)
    ridge_reg = Ridge(alpha=lam)
    ridge_reg.fit(df_X_mv_train.values,df_y_mv_train.values)
    y_pred_train = ridge_reg.predict(df_X_mv_train.values)
    y_pred_test = ridge_reg.predict(df_X_mv_test.values)

    # calculate error
    error_seq = np.linspace(-100.0,100.0,100)
    error_train = y_pred_train - df_y_mv_train.values
    error_test = y_pred_test - df_y_mv_test.values
    mse_train = mean_squared_error(df_y_mv_train.values,y_pred_train)
    mse_test = mean_squared_error(df_y_mv_test.values,y_pred_test)

    error_train_std = np.std(error_train)
    error_test_std = np.std(error_test)

    #kde_error_train = scipy.stats.gaussian_kde(error_train)
    #kde_error_test = scipy.stats.gaussian_kde(error_test)

    plt.subplot(131)
    plt.plot([0,8000],[0,8000],c = 'black',linewidth=3,alpha = 0.4)
    plt.scatter(df_y_mv_train.values,y_pred_train,color="red",edgecolors='black',label='Train',alpha=0.2)
    plt.scatter(df_y_mv_test.values,y_pred_test,color="blue",edgecolors='black',label='Test',alpha=0.2)
    plt.title("Cross Validation, Lambda = "+str(round(lam,2)))
    plt.ylim([0,8000]); plt.xlim([0,8000]); plt.grid(); plt.legend(loc = 'upper left')
    plt.xlabel('True Production (MCFPD)'); plt.ylabel('Estimated Production (MCFPD)')

    plt.subplot(132)
    plt.hist(error_train, facecolor='red',bins=np.linspace(-2000.0,2000.0,20),alpha=0.2,density=False,edgecolor='black',label='Train')
    plt.hist(error_test, facecolor='blue',bins=np.linspace(-2000.0,2000.0,20),alpha=0.2,density=False,edgecolor='black',label='Test')
    #plt.plot(error_seq,kde_error_train(error_seq),lw=2,label='Train',c='red')
    #plt.plot(error_seq,kde_error_test(error_seq),lw=2,label='Test',c='blue')
    #plt.xlim([-55.0,55.0]);
    plt.ylim([0,10])
    plt.xlabel('Model Error'); plt.ylabel('Frequency'); plt.title('Training and Testing Error, Lambda = '+str((round(lam,2))))
    plt.legend(loc='upper left')
    plt.grid(True)

    plt.subplot(133); ax = plt.gca()
    plt.plot(clam_list,cmse_train_avg,lw=2,label='Train',c='red')
    ax.fill_between(clam_list,cmse_train_high,cmse_train_low,facecolor='red',alpha=0.05)

    plt.plot(clam_list,cmse_test_avg,lw=2,label='Test',c='blue')
    ax.fill_between(clam_list,cmse_test_high,cmse_test_low,facecolor='blue',alpha=0.05)

    plt.xscale('log'); plt.xlim([lam_max,lam_min]); plt.yscale('log'); plt.ylim([1.0e5,1.0e7])
    plt.xlabel('Model Flexibility by Regularization, Lambda'); plt.ylabel('Mean Square Error'); plt.title('Training and Testing Error vs. Model Complexity')
    plt.legend(loc='upper left')
    plt.grid(True)

    plt.plot([lam,lam],[1.0e5,1.0e7],c = 'black',linewidth=3,alpha = 0.8)

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

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

```

### Interactive Machine Learning Ridge Regression Hyperparameter Tuning Demonstation

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

Change the number of sample data, train/test split and the data noise and observe hyperparameter tuning! Change the regularization hyperparameter, lambda, to observe a specific model example. Based on a more complicated (6 predictor features, some non-linearity and sampling error).

### The Inputs

* **n** - number of data
* **Test %** - percentage of sample data withheld as testing data
* **Noise StDev** - standard deviation of random Gaussian error added to the data
* **Regularization Hyperparameter** - the lambda coefficient, weight in loss the function on minimization of the  model parameters

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

    VBox(children=(Text(value='                          Machine Learning Ridge Regression Hyperparameter Tuning D…

    Output()

#### Comments

This was a basic demonstration of machine learning model training and tuning, with ridge regression. I have many other demonstrations and even basics of working with DataFrames, ndarrays, univariate statistics, plotting data, declustering, data transformations and many other workflows available at https://github.com/GeostatsGuy/PythonNumericalDemos and https://github.com/GeostatsGuy/GeostatsPy.

#### The Author:

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

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

For more about Michael check out these links:

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

#### Want to Work Together?

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

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

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

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

I'm always happy to discuss,

*Michael*

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

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

```python

```


# Tensorflow MNIST Beginners Tutorial

This notebook follows the [Tensorflow Beginners tutorial](https://www.tensorflow.org/tutorials/mnist/beginners/).

## Setup
First we import the libraries we'll use.

```python
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
```

## Load MNIST Data
Then we set some constants and initialize the mnist dataset.

```python
FLAGS = 'MNIST_data/'

mnist = input_data.read_data_sets(FLAGS, one_hot=True)
```

    Extracting MNIST_data/train-images-idx3-ubyte.gz
    Extracting MNIST_data/train-labels-idx1-ubyte.gz
    Extracting MNIST_data/t10k-images-idx3-ubyte.gz
    Extracting MNIST_data/t10k-labels-idx1-ubyte.gz

## The Softmax Regression

Then we start defining the Tensorflow variables, placeholders, loss function and train step. And we finally initialize them and start a session that will run our code.

Our evidence is defined as:

\begin{equation}
evidence_i=\sum_jW_{i,j}x_i+b_i
\end{equation}

where $W_i$ is the weights and $b_i$ is the bias for class $i$, and $j$ is an index for summing over the pixels in our input image $x$. We then convert the evidence tallies into our predicted probabilities $y$ using the "softmax" function:

\begin{equation}
y=softmax(evidence)
\end{equation}

Here softmax is serving as an "activation" or "link" function, shaping the output of our linear function into the form we want -- in this case, a probability distribution over 10 cases. You can think of it as converting tallies of evidence into probabilities of our input being in each class. It's defined as:

\begin{equation}
softmax(x)=normalize(exp(x))
\end{equation}

If you expand that equation out, you get:

\begin{equation}
softmax(x)_i=\frac{exp(x_i)}{\sum_jexp(x_j)}
\end{equation}

More compactly, we can just write:

\begin{equation}
y=softmax(Wx+b)
\end{equation}

## Implementing the Regression

```python
x = tf.placeholder(tf.float32, (None, 784))
```

`x` isn't a specific value. It's a `placeholder`, a value that we'll input when we ask TensorFlow to run a computation. We want to be able to input any number of MNIST images, each flattened into a 784-dimensional vector. We represent this as a 2-D tensor of floating-point numbers, with a shape `[None, 784]`. (Here `None` means that a dimension can be of any length.)

We also need the weights and biases for our model. We could imagine treating these like additional inputs, but TensorFlow has an even better way to handle it: `Variable`. A `Variable` is a modifiable tensor that lives in TensorFlow's graph of interacting operations. It can be used and even modified by the computation. For machine learning applications, one generally has the model parameters be `Variable`s.

```python
W = tf.Variable(tf.zeros((784, 10)))
b = tf.Variable(tf.zeros([10]))
```

We create these `Variable`s by giving `tf.Variable` the initial value of the `Variable`: in this case, we initialize both `W` and `b` as tensors full of zeros. Since we are going to learn `W` and `b`, it doesn't matter very much what they initially are.

Notice that `W` has a shape of `[784, 10]` because we want to multiply the 784-dimensional image vectors by it to produce 10-dimensional vectors of evidence for the difference classes. `b` has a shape of `[10]` so we can add it to the output.

Now we define our model:

```python
y = tf.nn.softmax(tf.matmul(x, W) + b)
```

First, we multiply `x` by `W` with the expression `tf.matmul(x, W)`. This is flipped from when we multiplied them in our equation, where we had $Wx$, as a small trick to deal with `x` being a 2D tensor with multiple inputs. We then add `b`, and finally apply `tf.nn.softmax`.

## Training
In order to train our model, we need to define what it means for the model to be good. Well, actually, in machine learning we typically define what it means for a model to be bad. We call this the cost, or the loss, and it represents how far off our model is from our desired outcome. We try to minimize that error, and the smaller the error margin, the better our model is.

One very common, very nice function to determine the loss of a model is called "cross-entropy." Cross-entropy arises from thinking about information compressing codes in information theory but it winds up being an important idea in lots of areas, from gambling to machine learning. It's defined as:

\begin{equation}
H_{y′}(y)=-\sum_iy′_ilog(y_i)
\end{equation}
Where $y$ is our predicted probability distribution, and $y′$ is the true distribution (the one-hot vector with the digit labels). In some rough sense, the cross-entropy is measuring how inefficient our predictions are for describing the truth. Going into more detail about cross-entropy is beyond the scope of this tutorial, but it's well worth [understanding](http://colah.github.io/posts/2015-09-Visual-Information/).

To implement cross-entropy we need to first add a new placeholder to input the correct answers and then we can implement the cross-entropy function, $-\sum{y′log(y)}$:

```python
y_ = tf.placeholder(tf.float32, (None, 10))
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y),
                                              reduction_indices=[1]))
```

First, `tf.log` computes the logarithm of each element of `y`. Next, we multiply each element of `y_` with the corresponding element of `tf.log(y)`. Then `tf.reduce_sum` adds the elements in the second dimension of `y`, due to the `reduction_indices=[1]` parameter. Finally, `tf.reduce_mean` computes the mean over all the examples in the batch.

Now that we know what we want our model to do, it's very easy to have TensorFlow train it to do so. Because TensorFlow knows the entire graph of your computations, it can automatically use the [backpropagation algorithm](http://colah.github.io/posts/2015-08-Backprop/) to efficiently determine how your variables affect the loss you ask it to minimize. Then it can apply your choice of optimization algorithm to modify the variables and reduce the loss.

```python
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
```

In this case, we ask TensorFlow to minimize `cross_entropy` using the [gradient descent algorithm](https://www.wikiwand.com/en/Gradient_descent) with a learning rate of 0.5. Gradient descent is a simple procedure, where TensorFlow simply shifts each variable a little bit in the direction that reduces the cost. But TensorFlow also provides [many other optimization algorithms](https://www.tensorflow.org/api_docs/python/train/#optimizers): using one is as simple as tweaking one line.

Now we have our model set up to train, we have to create an operation to initialize the variables we created:

```python
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
```

Now we run our code in batches of 100 samples randomly for 1000 times.

> Using small batches of random data is called stochastic training -- in this case, stochastic gradient descent. Ideally, we'd like to use all our data for every step of training because that would give us a better sense of what we should be doing, but that's expensive. So, instead, we use a different subset every time. Doing this is cheap and has much of the same benefit.
> - Tensorflow

```python
for i in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print("Accuracy: {}".format(
    sess.run(accuracy,
             feed_dict={x: mnist.test.images, y_: mnist.test.labels})))
```

    Accuracy: 0.9123000502586365


## 5. Linear ensemble filtering Lorenz-96 problem with localization

In this notebook, we apply the stochastic ensemble Kalman filter to the Lorenz-96 problem.

To regularize the inference problem, we use a localization radius `L` to cut-off long-range correlations and improve the conditioning of the covariance matrix. We refer readers to Asch et al. [2] for further details.

[1] Evensen, G., 1994. Sequential data assimilation with a nonlinear quasi‐geostrophic model using Monte Carlo methods to forecast error statistics. Journal of Geophysical Research: Oceans, 99(C5), pp.10143-10162.

[2] Asch, M., Bocquet, M. and Nodet, M., 2016. Data assimilation: methods, algorithms, and applications. Society for Industrial and Applied Mathematics.

[3] Bishop, C.H., Etherton, B.J. and Majumdar, S.J., 2001. Adaptive sampling with the ensemble transform Kalman filter. Part I: Theoretical aspects. Monthly weather review, 129(3), pp.420-436.

[4] Lorenz, E.N., 1963. Deterministic nonperiodic flow. Journal of atmospheric sciences, 20(2), pp.130-141.

[5] Spantini, A., Baptista, R. and Marzouk, Y., 2019. Coupling techniques for nonlinear ensemble filtering. arXiv preprint arXiv:1907.00389.

[6] Evensen, G. (2003). The ensemble Kalman filter: Theoretical formulation and practical implementation. Ocean dynamics, 53(4), 343-367.

[7] Tsitouras, C. (2011). Runge–Kutta pairs of order 5 (4) satisfying only the first column simplifying assumption. Computers & Mathematics with Applications, 62(2), 770-775.

```julia
using Revise
using LinearAlgebra
using TransportBasedInference
using Statistics
using Distributions
```

Load some packages to make nice figures

```julia
using Plots
default(tickfont = font("CMU Serif", 9),
        titlefont = font("CMU Serif", 14),
        guidefont = font("CMU Serif", 12),
        legendfont = font("CMU Serif", 10),
        grid = false)
pyplot()

using ColorSchemes
using LaTeXStrings
```

The Lorenz-96 model  is a famous problem used in data assimilation and weather prediction. It was derived from first principles as a one-dimensional model for the response of the mid-latitude atmosphere to forcing input. For certain forcing input, it can exhibit a chaotic behavior: sensitivity to initial conditions, strong mixing... The forty-dimensional state $\boldsymbol{x} = (x_1, \ldots, x_{40})$ at time $t$ is governed by the following set of ordinary differential equations:
\begin{equation}
\label{eqn:lorenz96}
\frac{\mathrm{d} x_i}{\mathrm{d} t} = (x_{i+1} - x_{i-2}) x_{i-1} - x_{i} + F, \quad \mbox{for } i = 1, \ldots, 40,
\end{equation}
with periodic boundary conditions, setting the forcing input $F=8.0$ leads to chaos. We reproduce the hard configuration case of Spantini et al. [5]. We integrate this system of ODEs with time step $\Delta t_{dyn} = 0.05$. We observe the state with $\Delta t_{obs}=0.4$, every two components ($x_1, x_3, x_5, \ldots, x_{39})$. The large time interval between two assimilation makes the problem particularly challenging and enhances the non-Gaussianity. The initial distribution $\pi_{\mathsf{X}_0}$ is the standard Gaussian. We assume that there is no process noise. The measurement noise has a Gaussian distribution with zero mean and covariance $\theta^2\boldsymbol{I}_{20}$ where $\theta^2 = 0.5$.

### Simple twin-experiment

Define the dimension of the state and observation vectors

```julia
Nx = 40
Ny = 20
```

    20

Define the time steps $\Delta t_{dyn}, \Delta t_{obs}$  of the dynamical and observation models. Observations from the truth are assimilated every $\Delta t_{obs}$.

```julia
Δtdyn = 0.01
Δtobs = 0.4
```

    0.4

Define the time span of interest

```julia
t0 = 0.0
tf = 1000.0
Tf = ceil(Int64, (tf-t0)/Δtobs)
```

    2500

 Define the distribution for the initial condition $\pi_{\mathsf{X}_0}$

```julia
π0 = MvNormal(zeros(Nx), Matrix(1.0*I, Nx, Nx))
```

    FullNormal(
    dim: 40
    μ: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0  …  0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    Σ: [1.0 0.0 … 0.0 0.0; 0.0 1.0 … 0.0 0.0; … ; 0.0 0.0 … 1.0 0.0; 0.0 0.0 … 0.0 1.0]
    )

We construct the state-space representation `F` of the system composed of the deterministic part of the dynamical and observation models.

The dynamical model is provided by the right hand side of the ODE to solve. For a system of ODEs, we will prefer an in-place syntax `f(du, u, p, t)`, where `p` are parameters of the model.
We rely on `OrdinaryDiffEq` to integrate the dynamical system with the Tsitouras 5/4 Runge-Kutta method adaptive time marching [7].

We assume that the state is observed every two components.

```julia
h(x, t) = x[1:2:end]
h(x, idx, t) = x[idx]

F = StateSpace(lorenz96!, h)
```

    StateSpace(TransportBasedInference.lorenz96!, h)

`ϵx` defines the additive process noise applied between the forecast step and the analysis step. The process noise is applied before to sample form the likelihood.

`ϵy` defines the additive observation noise.

We assume that the noises are Gaussian.

```julia
σx = 1e-6
σy = 0.5

ϵx = AdditiveInflation(Nx, zeros(Nx), σx)
ϵy = AdditiveInflation(Ny, zeros(Ny), σy)
```

    AdditiveInflation(20, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.25 0.0 … 0.0 0.0; 0.0 0.25 … 0.0 0.0; … ; 0.0 0.0 … 0.25 0.0; 0.0 0.0 … 0.0 0.25], [0.5 0.0 … 0.0 0.0; 0.0 0.5 … 0.0 0.0; … ; 0.0 0.0 … 0.5 0.0; 0.0 0.0 … 0.0 0.5])

```julia
model = Model(Nx, Ny, Δtdyn, Δtobs, ϵx, ϵy, π0, 0, 0, 0, F);
```

Set initial condition of the true system

```julia
x0 = rand(model.π0);
```

Run dynamics and generate data

```julia
@time data = generate_lorenz96(model, x0, Tf);
```

      0.051517 seconds (181.26 k allocations: 38.968 MiB, 9.42% compilation time)

Define a stochastic ensemble Kalman filter without localization

```julia
senkf = StochEnKF(model.ϵy, model.Δtdyn, model.Δtobs)
```

    Stochastic EnKF  with filtered = false

Define a stochastic ensemble Kalman filter with localization

Initialize the ensemble matrix `X` $\in \mathbb{R}^{(N_y + N_x) \times N_e}$.

```julia
Ne = 40#ensemble size
X = zeros(model.Ny + model.Nx, Ne)

# Generate the initial conditions for the state.
viewstate(X, model.Ny, model.Nx) .= rand(model.π0, Ne);
```

```julia
Δ = 2
yidx = 1:Δ:model.Nx
idx = vcat(collect(1:length(yidx))', collect(yidx)')
β = 1.0
Lrad = 6

senkfloc = SeqStochEnKF(x->x, F.h, MultiplicativeInflation(β), model.ϵy, Ny, Nx, model.Δtdyn, model.Δtobs,
                    idx, zeros(Nx+1, Ne), false, true)

# Create Localization structure
Gxy(i,j) = periodicmetric!(i,yidx[j], model.Nx)
Gyy(i,j) = periodicmetric!(yidx[i],yidx[j], model.Nx)
Loc = Localization(Lrad, Gxy, Gyy)
```

Apply the sequential filter over the time window

The function `seqassim` provides a friendly API to experiment with the different ensemble filters, the tuning of the different inflation parameters...

Without localization

```julia
@time Xsenkf = seqassim(F, data, Tf, model.ϵx, senkf, deepcopy(X), model.Ny, model.Nx, t0);
```

    Progress: 100%|█████████████████████████████████████████| Time: 0:00:03

      3.297220 seconds (12.17 M allocations: 1.952 GiB, 16.02% gc time, 2.02% compilation time)

RMSE without localization

```julia
mean(map(i->norm(data.xt[:,i]-mean(Xsenkf[i+1]; dims = 2))/sqrt(Nx), 1:Tf))
```

    4.56591354022421

```julia
plot(map(i->norm(data.xt[:,i]-mean(Xsenkf[i+1]; dims = 2))/sqrt(Nx), 1:Tf))
```

`mean_hist` stacked together the ensemble mean over the assimilation window.

```julia
# Plot the first component of the state over time
nb = 1
ne = size(Xsenkf,1)-1
Δ = 10
plt = plot(xlim = (-Inf, Inf), ylim = (-Inf, Inf), xlabel = L"t", ylabel = L"x_1")
plot!(plt, data.tt[nb:Δ:ne], data.xt[1,nb:Δ:ne], linewidth =  3, color = :teal, label = "True")
plot!(plt, data.tt[nb:Δ:ne], mean_hist(Xsenkf)[1,1+nb:Δ:1+ne], linewidth = 3, grid = false,
     color = :orangered2, linestyle = :dash, label = "sEnKF")
scatter!(plt, data.tt[nb:Δ:ne], data.yt[1,nb:Δ:ne], linewidth = 3, color = :grey, markersize = 5, alpha = 0.5, label = "Observation")
plt
```

```julia
nb = 100
ne = size(Xsenkf,1)-1
Δ = 1

plt = plot(layout = grid(3,1), xlabel = L"t", ylabel = "State index")
contourf!(plt[1,1], data.xt[:,nb:Δ:ne], color = :plasma)
contourf!(plt[2,1], mean_hist(Xsenkf)[:,1+nb:Δ:1+ne], color = :plasma)
contourf!(plt[3,1], abs.(mean_hist(Xsenkf)[:,1+nb:Δ:1+ne] .- data.xt[:,nb:Δ:ne]), color = :plasma)
```

Localization can easily be added as a regularization of the analysis step as follows

```julia
@time Xsenkfloc = seqassim(F, data, Tf, model.ϵx, senkfloc, deepcopy(X), model.Ny, model.Nx, t0, Loc);
```

     10.630150 seconds (98.96 M allocations: 11.003 GiB, 11.64% gc time, 0.28% compilation time)

RMSE with localization

```julia
mean(map(i->norm(data.xt[:,i]-mean(Xsenkfloc[i+1]; dims = 2))/sqrt(Nx), 1:Tf))
```

    0.9821230972705552

```julia
# Plot the first component of the state over time
nb = 1
ne = size(Xsenkf,1)-1
Δ = 10
plt = plot(xlim = (-Inf, Inf), ylim = (-Inf, Inf), xlabel = L"t", ylabel = L"x_1")
plot!(plt, data.tt[nb:Δ:ne], data.xt[1,nb:Δ:ne], linewidth =  3, color = :teal, label = "True")
plot!(plt, data.tt[nb:Δ:ne], mean_hist(Xsenkfloc)[1,1+nb:Δ:1+ne], linewidth = 3, grid = false,
     color = :orangered2, linestyle = :dash, label = "sEnKF")
scatter!(plt, data.tt[nb:Δ:ne], data.yt[1,nb:Δ:ne], linewidth = 3, color = :grey, markersize = 5, alpha = 0.5, label = "Observation")
plt
```

```julia
nb = 100
ne = size(Xsenkfloc,1)-1
Δ = 1

plt = plot(layout = grid(3,1), xlabel = L"t", ylabel = "State index")
contourf!(plt[1,1], data.xt[:,nb:Δ:ne], color = :plasma)
contourf!(plt[2,1], mean_hist(Xsenkfloc)[:,1+nb:Δ:1+ne], color = :plasma)
contourf!(plt[3,1], abs.(mean_hist(Xsenkfloc)[:,1+nb:Δ:1+ne] .- data.xt[:,nb:Δ:ne]), color = :plasma)
```


```python
import sys
sys.path.append('..')
import src.HamiltonianTerms as ham
from src.MatrixToPauliString import *
from src.BinaryEncodings import *
import numpy as np
import sympy as sp
import math

dim = 16
nqubits=4
hmat = ham.number_op(dim) + 0.5*np.identity(dim)

mtoqc = MatrixToPauliString(np.asarray(hmat))
mtoqc.convert(standard_encode)
```

```python
print(sp.expand(mtoqc.pauli_strings))
```

```python
print(mtoqc.pauli_strings_as_list())
```

    defaultdict(<class 'complex'>, {'IIII': (8+0j), 'ZIII': (-0.5+0j), 'IZII': (-1+0j), 'IIZI': (-2+0j), 'IIIZ': (-4+0j)})

```python
from qiskit.aqua.operators import I,X,Y,Z,Zero,PauliTrotterEvolution
from qiskit.circuit import Parameter

char_to_op={'I': I, 'X': X, 'Y': Y, 'Z': Z}
```

```python
expr = 0
for pstring, coef in mtoqc.pauli_strings_as_list().items():
    term = char_to_op[pstring[0]]
    for char in pstring[1:]:
        term = term^char_to_op[char]
    expr += coef.real*term
```

```python
evo_time = Parameter('t')
evolution_op = (evo_time*expr).exp_i()
print(evolution_op)
```

    e^(-i*1.0*t * SummedOp([
      16.0 * IIII,
      -1.0 * ZIII,
      -2.0 * IZII,
      -4.0 * IIZI,
      -8.0 * IIIZ
    ]))

```python
print(evolution_op @ Zero)
```

    ComposedOp([
      e^(-i*1.0*t * SummedOp([
        16.0 * IIII,
        -1.0 * ZIII,
        -2.0 * IZII,
        -4.0 * IIZI,
        -8.0 * IIIZ
      ])),
      DictStateFn({'0000': 1})
    ])

```python
fixed_time = evolution_op.bind_parameters({evo_time: 1})
```

```python
print(fixed_time @ Zero)
```

    ComposedOp([
      e^(-i*SummedOp([
        16.0 * IIII,
        -1.0 * ZIII,
        -2.0 * IZII,
        -4.0 * IIZI,
        -8.0 * IIIZ
      ])),
      DictStateFn({'0000': 1})
    ])

```python
trotterized = PauliTrotterEvolution(trotter_mode='suzuki').convert(fixed_time)
print(trotterized)
```

    global phase: -3.4336
         ┌────────┐┌────────┐
    q_0: ┤ RZ(-8) ├┤ RZ(-8) ├
         ├────────┤├────────┤
    q_1: ┤ RZ(-4) ├┤ RZ(-4) ├
         ├────────┤├────────┤
    q_2: ┤ RZ(-2) ├┤ RZ(-2) ├
         ├────────┤├────────┤
    q_3: ┤ RZ(-1) ├┤ RZ(-1) ├
         └────────┘└────────┘

```python
from qiskit import QuantumCircuit, transpile
```

```python
qc = QuantumCircuit(nqubits, nqubits)
```

```python
qc.append(trotterized, [i for i in range(nqubits)])
```

    <qiskit.circuit.instructionset.InstructionSet at 0x7f06d9df6b80>

```python
qc.draw('mpl')
```

```python
transpile(qc, basis_gates = ['cx', 'u1', 'u2', 'u3', 'H', 'X', 'Y', 'Z']).draw('mpl')
```

```python
from qiskit import (
    execute,
    Aer)
from qiskit.providers.aer.noise import NoiseModel
from qiskit import IBMQ
from qiskit.visualization import plot_histogram

provider = IBMQ.load_account()
```

```python
noise_backend = provider.get_backend('ibmq_16_melbourne')
noise_model = NoiseModel.from_backend(noise_backend)
```

```python
simulator = Aer.get_backend('qasm_simulator')

qc.measure([0,1,2],[0,1,2])
```

```python
job = execute(qc, simulator, shots=1000, noise_model=noise_model)
result=job.result()
counts = result.get_counts(qc)
plot_histogram(counts)
```

```python

```


# Assignment

## Deadline: March 31st 2016

## Instructions

* Write a report of all the problems that you have solved and include the code that you used.

* You can use any packages or tools that you see most fit for the purpose.

* Your work will be evaluated based on your report and the tools used.

## Problem 1

A window is being built and the bottom is a rectangle and the top is a semicircle. If there is 12 m of framing materials what must the dimensions of the window be to make the window area as big as possible?

Model the decision problem as an optimization problem and solve it with a method of your choosing. Analyse the result.

## Problem 2

The 10-dimensional Robsenbrock function (one of the variants) is defined as
$$
f(\mathbf{x}) = \sum_{i=1}^{9} 100 (x_{i+1} - x_i^2 )^2 + (1-x_i)^2
$$
for $x\in\mathbb R^{10}$.

Compare at least two different optimization method's performance in minimizing this function over $\mathbb R^{10}$. You can decide the method of comparison as the one that makes most sense to you.

## Problem 3

The problem will be available at http://mhartikainen.pythonanywhere.com/evaluate/. The call http://mhartikainen.pythonanywhere.com/evaluate/1.0/2.0/3.0/4.0 evaluates the optimization problem for variable values $x = (1.0, 2.0,3.0,4.0)$.

The format of the problem is
$$
\begin{align}
\min \ &f(x)\\
\text{s.t. }&g_1(x) \geq 0\\
        &h_1(x) = 0\\
        &h_2(x) = 0\\
        &x\in \mathbb R^4.
\end{align}
$$
Calling the function will result in a json file, which will tell you all the information about the problem that you have available.

Solve the optimization problem, when you can make function calls only using a RESTful API (only the get method implemented). Use the tools and optimization method of your choosing. Analyse the results.

## Problem 4

Study biobjective optimization problem
$$
\begin{align}
\min \ &(\|x-(1,0)\|,\|x-(0,1)\|\\
\text{s.t. }&x\in \mathbb R^2.
\end{align}
$$
Try to generate an evenly spread representation of the Pareto front. Plot the results in both the decision and objective spaces.