# Algoritmo de Gillespie para la expresión genética de un circuito genético retroalimentado

Considérese una célula ideal que únicamente posee dos genes interconectados entre sí en la formma que se muestra a continuación

Las ecuaciones diferenciales deterministas que describen la expresión genética del sistema, siguiendo lo postulado por el dogma central de la biología molecular, serán

\begin{equation}
\dot{r}_1 = \beta_1 \left(\frac{p_2^{n_1}}{p_2^{n_1} + k_1^{n_1}}\right) - \gamma_r r_1
\end{equation}

\begin{equation}
\dot{p}_1 = k_{p_1} r_1  - \gamma_p p_1
\end{equation}

\begin{equation}
\dot{r}_2 = \beta_2 \left(\frac{1}{1+ \left(\frac{p_1}{k_2}\right)^{n_2}}\right) - \gamma_r r_2
\end{equation}

\begin{equation}
\dot{p}_2 = k_{p_2} r_2 - \gamma_p p_2
\end{equation}

si asumimos idénticas las tasas de degradación de ambas proteínas.

Importamos los paquetes que utilizaremos

```python
import numpy as np
import matplotlib.pyplot as plt
import progressbar
from tqdm import tqdm

from numba import jit,njit,float64,int32
import numba as nb
```

E inicializamos las constantes.

```python
n1 = 3.5
n2 = 3.5
k1 = 4400
k2 = 8080
γr1 = 1/5
γr2 = 50/251
β1 = 216/125
β2 = 216/125
kp1 = 50
kp2 = 45
γp1 = 1/45
γp2 = 1/45
```

```python
@njit('f8[:](f8[:],f8)')
def Gillespie(trp0,tmax):

    '''
    Gillespie(trp0, tmax)

    Return a ndarray from the given initial state with the last times, number of mRNA and the last number of proteins.

    Parameters
    ----------
    trp0 : array like
        Array like with the initial state of a every single cell. It must be in this order: initial time,
        initial number of mRNA transcribed from gene 1, initial number of mRNA transcribed from gene 2,
        initial number of proteins translated from gene 1 and initial number of mRNA transcribed from gene 2.

    tmax : int
        Maximum time to be run in the simulation.

    Returns
    -------
    out : ndarray
        Array of the last time, number of mRNAs and the last number of proteins obtained after the stochastic
        simulation of a single system. The array is organized following the same input order.

    '''

    t,r1,r2,p1,p2 =trp0

    while t < tmax:
        s1 = β1*((p2**n1)/(k1**n1 + p2**n1))
        s2 = γr1*r1
        s3 = β2/(1 + (p1/k2)**n2)
        s4 = γr2*r2
        s5 = kp1*r1
        s6 = γp1*p1
        s7 = kp2*r2
        s8 = γp2*p2
        st = s1+s2+s3+s4+s5+s6+s7+s8
        τ = (-1/st)*np.log(np.random.rand())
        x = np.random.rand()
        if(x< s1/st):
            r1+=1
        elif(x < (s1+s2)/st):
            r1-=1
        elif(x<(s1+s2+s3)/st):
            r2+=1
        elif(x<(s1+s2+s3+s4)/st):
            r2-=1
        elif(x<(s1+s2+s3+s4+s5)/st):
            p1+=1
        elif(x<(s1+s2+s3+s4+s5+s6)/st):
            p1-=1
        elif(x<(s1+s2+s3+s4+s5+s6+s7)/st):
            p2+=1
        else:
            p2-=1
        t+=τ
    return np.array([t,r1,r2,p1,p2])
```

```python
def Estado_celula(X0,tiempos):

    '''
    Estado_celula(X0,tiempos)

    Returns a matrix with the evolved genetic expression of a single cell.

    Parameters
    ----------
    X0 :ndarray
        Vector with the system's initial state.
        It has as many rows as elements have tiempos and as many columns as evaluated chemical species.
    tiempos : numpy linspace, ndarray, array like
        Vector containing the sampling times to evolve the system.

    Returns
    -------
    out : matrix, 2D ndarray
        2D ndarray with the evolved system data.

    Example
    --------
    >>> x0 = np.array([0.0,0.0,0.0])             #time starts in 0.0, number of mRNA starts in 0.0
                                                 #and number of proteins starts in 0.0, respectively.

    >>> Estado_celula(x0,np.linspace(0,200,10))  #Evolves a single cell with initial conditions x0 and a
                                                 #equispaced sampling time vector with 10 elements from
                                                 #0 to 200. We chose only 10 elements for simplicity, but
                                                 #there are 50 by default.

    array([[0.00000000e+00, 0.00000000e+00, 0.00000000e+00],
    [2.22233941e+01, 6.00000000e+00, 4.58400000e+03],
    [4.44470482e+01, 3.00000000e+00, 7.73200000e+03],
    [6.66697353e+01, 5.00000000e+00, 5.90500000e+03],
    [8.88922698e+01, 4.00000000e+00, 5.80100000e+03],
    [1.11113080e+02, 6.00000000e+00, 7.65800000e+03],
    [1.33335290e+02, 7.00000000e+00, 6.79300000e+03],
    [1.55556091e+02, 7.00000000e+00, 1.00870000e+04],
    [1.77778084e+02, 2.00000000e+00, 9.95200000e+03],
    [2.00003693e+02, 3.00000000e+00, 8.36100000e+03]])

    Note that the first row is the initial condition vector x0.

    '''

    X = np.zeros((len(tiempos),len(X0)))
    X[0] = X0

    for i in range(1,len(tiempos)):
        X[i] = Gillespie(X[i-1],tiempos[i])

    return X
```

```python
x0 = np.array([0.,0.,0.,0.,0.])
```

```python
%%time
una_celula = Estado_celula(x0,np.arange(0,1000,0.01,dtype=float))
```

    CPU times: user 297 ms, sys: 0 ns, total: 297 ms
    Wall time: 300 ms

```python
plt.xlabel('tiempo (s)')
plt.ylabel('número de moléculas de ARNm')
plt.step(una_celula[:,0],una_celula[:,1])
plt.step(una_celula[:,0],una_celula[:,2])
```

```python
plt.xlabel('tiempo (adimensional)')
plt.ylabel('número de proteínas')
plt.step(una_celula[:,0],una_celula[:,3])
plt.step(una_celula[:,0],una_celula[:,4])
plt.legend(["Gen 1", "Gen 2"], loc ="lower right")
```

```python
%%time
tiempos = np.arange(0,800.)
num_cel = 8000 #número de células
celulas = np.array([Estado_celula(x0,tiempos) for i in range(num_cel)])
celulas.shape
```

    CPU times: user 10min 4s, sys: 2.66 s, total: 10min 7s
    Wall time: 10min 18s

    (8000, 800, 5)

```python
celulas_prom = np.mean(celulas,axis=0) #axis = 0 saca el promedio componente a componente de cada célula.
celulas_prom;
```

```python
celulas.shape
```

    (8000, 800, 5)

```python
T,*sols = np.loadtxt("data3.csv",delimiter=",",unpack=True)
sols = np.array(sols)
sols[:,-1]
```

    array([4.79490458e+00, 2.31388905e+00, 1.07868549e+04, 4.68639851e+03])

```python
celulas_prom.shape
```

    (800, 5)

```python
plt.figure(dpi=100)
plt.xlabel('Tiempo (adimensional)')
plt.ylabel('Número de ARNms 1')
plt.plot(celulas_prom[:,0],celulas_prom[:,1],zorder=100)
for i in range(5):
    plt.step(celulas[i,:,0],celulas[i,:,1],c = "gray",alpha=0.1)
#el primero es la célula, el 2do es la fila
#y el tercero, como es 0, es el tiempo.
#Cuando es 2 es proteína. Depende de las especies
#químicas que use.
```

```python
plt.figure(dpi=100)
plt.xlabel('Tiempo (adimensional)')
plt.ylabel('Número de ARNms 2')
plt.plot(celulas_prom[:,0],celulas_prom[:,2],zorder= 100)
for i in range(5):
    plt.plot(celulas[i,:,0],celulas[i,:,2],c = "gray",alpha=0.1)
```

```python
plt.figure(dpi=100)
plt.xlabel('Tiempo (adimensional)')
plt.ylabel('Número de Proteínas 1')
plt.plot(celulas_prom[:,0],celulas_prom[:,3],zorder= 100)
for i in range(10):
    plt.plot(celulas[i,:,0],celulas[i,:,3],c = "gray",alpha=0.1)
```

```python
plt.figure(dpi=100)
plt.xlabel('Tiempo (adimensional)')
plt.ylabel('Número de Proteínas 2')
plt.plot(celulas_prom[:,0],celulas_prom[:,4],zorder= 100)
plt.legend(["Estocástico", "Determinista"])
for i in range(10):
    plt.plot(celulas[i,:,0],celulas[i,:,4],c = "gray",alpha=0.2)
```

```python
ruido = np.std(celulas,axis=0)/celulas_prom
```

    <ipython-input-19-ac6b00529f84>:1: RuntimeWarning: invalid value encountered in true_divide
      ruido = np.std(celulas,axis=0)/celulas_prom

```python
ñ1 = np.std(celulas[:,600:800,1],axis=0).mean(axis=0)
ñ2 = np.std(celulas[:,600:800,2],axis=0).mean(axis=0)
ñ3 = np.std(celulas[:,600:800,3],axis=0).mean(axis=0)
ñ4 = np.std(celulas[:,600:800,4],axis=0).mean(axis=0)
```

```python
mask = celulas_prom[:,0] > 600
```

```python
print("ARNm del gen 1 promedio:", celulas_prom[mask].mean(axis=0)[1])
print("ARNm del gen 2 promedio:", celulas_prom[mask].mean(axis=0)[2])
print("Proteína del gen 1 promedio:", celulas_prom[mask].mean(axis=0)[3])
print("Proteína del gen 2 promedio:", celulas_prom[mask].mean(axis=0)[4])
```

    ARNm del gen 1 promedio: 4.880736874999999
    ARNm del gen 2 promedio: 2.4362412499999997
    Proteína del gen 1 promedio: 10982.148084374996
    Proteína del gen 2 promedio: 4928.565570625002

```python
print("Ruido cuadrado en el ARNm del gen 1:",(np.mean(ruido[mask],axis=0)**2)[1])
print("Ruido cuadrado en el ARNm del gen 2:",(np.mean(ruido[mask],axis=0)**2)[2])
print("Ruido cuadrado en las proteínas del gen 1:",(np.mean(ruido[mask],axis=0)**2)[3])
print("Ruido cuadrado en las proteínas del gen 2:",(np.mean(ruido[mask],axis=0)**2)[4])
```

    Ruido cuadrado en el ARNm del gen 1: 0.2959679961245225
    Ruido cuadrado en el ARNm del gen 2: 0.5728860806780876
    Ruido cuadrado en las proteínas del gen 1: 0.03100253052993909
    Ruido cuadrado en las proteínas del gen 2: 0.056570727702649926

X bloquea a Y y Y activa a X/ABloquea BActiva.ipynb


```python
import sympy
from phasor.utilities.ipynb.displays import *
from phasor.utilities.ipynb.ipy_sympy import *
import scipy.linalg

import numpy.testing as np_test
import declarative as decl
from declarative.bunch import (
    DeepBunch,
)

#import numpy as np

from phasor import system
from phasor import readouts
from phasor import optics
from phasor.optics.nonlinear_crystal import NonlinearCrystal
from phasor.utilities.print import pprint

```

    Populating the interactive namespace from numpy and matplotlib
    Sympy version:  1.0

```python
from phasor.optics.models.KTP_test_stand import KTPTestStand
sys = system.BGSystem()
sys.own.test = KTPTestStand()
LSTARR = sys.test.full_noise_matrix()
```

    OMG:  20
    OMG:  20
             RI        RQ        GI        GQ
    --  -------  --------  --------  --------
    RI  0.49212  0         0         0
    RQ  0        2.0848    0         0.326656
    GI  0        0         0.901251  0
    GQ  0        0.326656  0         1.23746

```python
.3177 * 3.403
```

```python
print(sys.ctree_as_yaml())
```

    adjust_PSD: 2
    environment:
      F_AC:
        F_center_Hz: 0.0
        F_width_Hz: 0
        frequency: {units: hertz, val: 0}
        order: 2
      F_carrier_1064: {order: null}
    include_johnson_noise: true
    sided_spectra: 1
    symbols: {Z_termination: 50, c_m_s: 299792458, h_Js: 6.6260700408e-34, hbar_Js: 1.0545718001e-34,
      i: !!python/complex '1.0j', i2pi: !!python/complex '6.283185307179586j', kB_J_K: 1.380658e-23,
      math: !!python/module:numpy '', pi: 3.141592653589793, temp_K: 299}
    test:
      AC_G:
        noise: {port_set: AC noise}
        port_set: AC
      AC_N: {port_set: AC noise}
      AC_R:
        noise: {port_set: AC noise}
        port_set: AC
      AC_RGI:
        noise: {port_set: AC noise}
        port_set: AC
      AC_RGQ:
        noise: {port_set: AC noise}
        port_set: AC
      DC_G: {port_set: DC}
      DC_R: {port_set: DC}
      PD_G: {include_readouts: false}
      PD_R: {include_readouts: false}
      PSLG:
        phase: {units: degree, val: 90}
        polarization: S
        power: {units: watt, val: 0.0}
      PSLGs:
        phase: {units: degree, val: 90}
        polarization: S
        power: {units: watt, val: 1.0}
      PSLR:
        phase: {units: radian, val: 0}
        polarization: S
        power: {units: watt, val: 1}
      PSLRs:
        phase: {units: radian, val: 0}
        polarization: S
        power: {units: watt, val: 1}
      hPD_G: {include_readouts: false, phase_deg: 0}
      hPD_R: {include_readouts: false, phase_deg: 0}
      include_AC: true
      ktp:
        N_ode: 20
        length: {units: millimeter, val: 10}
        nlg: 0.1
        solution_order: 3
        symplectify: true
      mDC1:
        mirror_H1:
          AOI_deg: 45
          L_hr: 0
          L_t: 0
          R_backscatter: 0
          T_hr: 1
          backscatter_phase: {units: radian, val: 0}
          phase: {units: radian, val: 0}
        mirror_H2:
          AOI_deg: 45
          L_hr: 0
          L_t: 0
          R_backscatter: 0
          T_hr: 0
          backscatter_phase: {units: radian, val: 0}
          phase: {units: radian, val: 0}
      mDC2:
        mirror_H1:
          AOI_deg: 45
          L_hr: 0
          L_t: 0
          R_backscatter: 0
          T_hr: 1
          backscatter_phase: {units: radian, val: 0}
          phase: {units: radian, val: 0}
        mirror_H2:
          AOI_deg: 45
          L_hr: 0
          L_t: 0
          R_backscatter: 0
          T_hr: 0
          backscatter_phase: {units: radian, val: 0}
          phase: {units: radian, val: 0}

```python
ooa = sys.ctree_shadow()
```

```python
ooa.test.ktp.length.val = 10
ooa.debug.solver.delta_V_max = True
print(ooa.test.ktp.length.val)

sys = system.BGSystem(
    ctree = ooa.extractidx('new')
)
sys.own.test = KTPTestStand( )

LSTARR = sys.test.full_noise_matrix()
```

    10
    OMG:  20
    OMG:  20
    DELTA V MAX:  1.0  AT ORDER:  2
    DELTA V MAX:  1.0  AT ORDER:  3
    DELTA V MAX:  0  AT ORDER:  4
             RI        RQ        GI        GQ
    --  -------  --------  --------  --------
    RI  0.49212  0         0         0
    RQ  0        2.0848    0         0.326656
    GI  0        0         0.901251  0
    GQ  0        0.326656  0         1.23746

```python
0 is 0
```

    True

```python

```


```python
import numpy as np
import matplotlib.pyplot as plt
import math
from scipy import interpolate
from scipy import integrate
import time
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from math import sqrt
from math import pi
import matplotlib.font_manager as fm
import matplotlib.ticker as mtick
from matplotlib.ticker import ScalarFormatter
from scipy import optimize
from labellines import labelLine, labelLines
import csv
```

```python
#Import show() function for plotting
%run C:\Users\wbenn/Marshak_Wave_Scripts/Marshak_3T/SelfSimilar_main/show.ipynb
#Import initial conditions
%run C:\Users\wbenn/Marshak_Wave_Scripts/Marshak_3T/SelfSimilar_main/initial_conditions.ipynb
```

## Governing equations
This notebook produces self-similar solutions for the system
$$[1]\qquad \frac{\partial T_e} {\partial t} = \alpha \frac{\partial^2}{\partial x^2} T_e^{n+4} + \frac{\gamma}{C_{\mathrm{ve}}} (T_i - T_e),$$
$$[2]\qquad \frac{\partial T_i}{\partial t} = -\frac{\gamma}{C_{\mathrm{vi}}}(T_i-T_e),$$
where $\alpha = \frac{ac}{3} \frac{4}{n+4} \frac{T_0^{-n}}{\kappa_0 C_\mathrm{ve}}.$
$T_e$ is electron temperature (keV). $T_i$ is ion temperature (keV). $x$ is the spatial variable (cm). $t$ is the time variable (ns). $C_{\mathrm{ve}}$ and $C_{\mathrm{vi}}$ are the electron and ion heat capacities at constant volume respectively $\left(\frac{\mathrm{GJ}}{\mathrm{cm}^{3}\mathrm{keV}}\right)$. $\gamma$ is the electron-ion coupling coefficient $\left(\frac{\mathrm{GJ}}{\mathrm{keV}\mathrm{cm}^3\mathrm{ns}}\right)$. $a = 4 \sigma_\mathrm{SB}/c$ is the radiation constant $\left(\frac{\mathrm{GJ}}{\mathrm{cm}^3\mathrm{keV}^4}\right)$. $c$ is the speed of light $\left(\frac{cm}{ns}\right)$. $\kappa_0$ is a constant (cm$^{-1}$). $n$ is a parameter that governs the dependence of the radiation opacity on the electron temperature.

## Similarity transform
With the transform,
$$\xi = A \frac{x}{\sqrt{t}},$$
$$  \gamma(x,t,T_\mathrm{e}) = \gamma_0 T_\mathrm{e}^{-m} f(x,t),$$
where $A=\frac{1}{\sqrt{2\alpha}}$, $m$ is a parameter that may be either 0 or $\frac{3}{2}$, and $f(x,t)$ is a known function, the governing equations become,
### Time dependent case: $f(x,t) = \frac{1}{2t}$
$$[3]\qquad -\xi \frac{dT_e}{d\xi} = \frac{d^2 T_e^{n+4}}{d\xi^2} +  \frac{\gamma_0}{C_{\mathrm{ve}}}T_{\mathrm{e}}^{-m} (T_i-T_e)$$
$$[4] \qquad-\xi \frac{dT_i}{d\xi} = -\frac{\gamma_0}{ C_{\mathrm{vi}}} T_{\mathrm{e}}^{-m}(T_i-T_e)$$
or
### Space dependent case $f(x,t) = \frac{1}{2A^2x^2}$
$$[4] \qquad -\xi \frac{dT_e}{d\xi} = \frac{d^2 T_e^{n+4}}{d\xi^2} +\frac{\gamma_0}{ C_{\mathrm{ve}}\xi^2} T_{\mathrm{e}}^{-m} (T_i-T_e) $$
$$[5] -\xi \frac{dT_i}{d\xi} = -\frac{\gamma_0}{ C_{\mathrm{vi}} \xi^2} T_{\mathrm{e}}^{-m}(T_i-T_e)$$

## ODE'S to solve:
### Time transform :
$$[1]\qquad -\xi \frac{dT_e}{d\xi} = \frac{d^2 T_e^{n+4}}{d\xi^2} +  \frac{\gamma_0}{C_{\mathrm{ve}}}T_{\mathrm{e}}^{-m} (T_i-T_e)$$
$$[2] \qquad-\xi \frac{dT_i}{d\xi} = -\frac{\gamma_0}{ C_{\mathrm{vi}}} T_{\mathrm{e}}^{-m}(T_i-T_e)$$
### Space transform:

$$[3] \qquad -\xi \frac{dT_e}{d\xi} = \frac{d^2 T_e^{n+4}}{d\xi^2} +\frac{\gamma_0}{ C_{\mathrm{ve}}\xi^2} T_{\mathrm{e}}^{-m} (T_i-T_e) $$
$$[4] -\xi \frac{dT_i}{d\xi} = -\frac{\gamma_0}{ C_{\mathrm{vi}} \xi^2} T_{\mathrm{e}}^{-m}(T_i-T_e)$$
### Derive 1st order ODE for [1]:
\begin{equation}
    -\xi \frac{dT_e}{d\xi} = \frac{d^2 T_e^{n+4}}{d\xi^2} +  \frac{\gamma_0}{C_{\mathrm{ve}}}T_{\mathrm{e}}^{-m} (T_i-T_e)
\end{equation}
Define a new function u:
$$u(\xi) = T'_e(\xi)  $$
$$\frac{d^2 T_e^{n+4}}{d \xi} = (n+4) T_e(\xi)^{n+2} \left((n+3) u^2+T_e(\xi) u'(\xi)\right)$$

[1] becomes:
\begin{equation}
    -\xi u = (n+4) T_e(\xi)^{n+2} \left((n+3) u^2+T_e(\xi) u'(\xi)\right) +  \frac{\gamma_0}{C_{\mathrm{ve}}}T_{\mathrm{e}}^{-m}(T_i-T_e)
\end{equation}
Solving for u':

\begin{equation}
     \frac{-\xi u -  \frac{\gamma_0}{C_{\mathrm{ve}}}T_{\mathrm{e}}^{-m}(T_i-T_e)}{(n+4)T_e^{n+2}}-(n+3)u^2 = T_e(\xi)u'(\xi)
\end{equation}
Simplifying:
\begin{equation}
    u' = -\frac{\frac{\xi u +  \frac{\gamma_0}{C_{\mathrm{ve}}}T_{\mathrm{e}}^{-m}(Ti-T_e)T_e^{-n} }{n+4} + (n+3)u^2T_e^2}{T_e^3}
\end{equation}
### Derive 1st order ODE for [3]:
\begin{equation}
    -\xi \frac{dT_e}{d\xi} = \frac{d^2 T_e^{n+4}}{d\xi^2} +\frac{\gamma_0}{ C_{\mathrm{ve}}\xi^2} T_{\mathrm{e}}^{-m} (T_i-T_e)
\end{equation}
Define $ u' = T_e'$
\begin{equation}
    -\xi u(\xi) = (n+4) T_e(\xi)^{n+2} \left((n+3) u^2(\xi)+T_e(\xi) u'(\xi)\right) + \frac{\gamma_0}{ C_{\mathrm{ve}}\xi^2} T_{\mathrm{e}}^{-m}\left(T_i(\xi)-T_e(\xi)\right)
\end{equation}
Solving for u'
\begin{equation}
    \frac{-\xi u(\xi)- \frac{\gamma_0}{ C_{\mathrm{ve}}\xi^2} T_{\mathrm{e}}^{-m}(T_i(\xi)-T_e(\xi))}{(n+4)T_e(\xi)^{n+2}} - (n+3)u^2(\xi) = T_e(\xi) u'(\xi)
\end{equation}
\begin{equation}
    u'(\xi)= \frac{-\xi u(\xi)Te^{-n} - \frac{\gamma_0}{C_{\mathrm{ve}}\xi^2}Te^{-(n+m)}\left(Ti-Te\right)-(n+3)(n+4)Te^2u^2}       {Ti^3(n+4)}
\end{equation}
### Initial Conditions
### IC for Te for the space and time transforms
\begin{equation}
    T_e(\xi) = \bigg{(}\frac{(n+3)}{(n+4)}\xi_{\mathrm{max}}({\xi_{\mathrm{max}}}-\xi)\bigg{)}^{-(n+3)}
\end{equation}
### Derivative of Te
\begin{equation}
    \frac{dT_e}{d\xi} = -\bigg{(}\frac{(n+3)}{(n+4)}\xi_{\mathrm{max}}\bigg{)}^{1/(n+3)}\bigg{(}\frac{(\xi_{\mathrm{max}}-\xi)}{n+3}\bigg{)}^{(2-n)/(n+3)}
\end{equation}
### Ti initial conditions for time transform
\begin{equation}
T^{(1)}_i(\xi)= \frac{\gamma_0}{\mathrm{Cv_i}}\frac{(n+3)}{(n+4)}\frac{(\xi_{\mathrm{max}}-\xi_{\mathrm{max}})}{\xi_{\mathrm{max}}}T_e^{(1)}(\xi).
\end{equation}
### Ti initial conditions for space transform
\begin{equation}
\begin{split}
Ti^{(1)}=\frac{4\frac{\gamma_0}{\mathrm{C_vi}}(n+3)(-\xi_{\mathrm{max}}+\xi)Te^{(1)}}{(n+4)(2\frac{\gamma_0}{\mathrm{C_vi}}\xi^2-\xi^3-4\frac{\gamma_0}{\mathrm{C_vi}}\xi\xi_{\mathrm{max}}-\xi^2\xi_{\mathrm{max}}+2\frac{\gamma_0}{\mathrm{C_vi}}\xi_{\mathrm{max}}^2-\xi\xi_{\mathrm{max}}-\xi_{\mathrm{max}}^3)}.
\end{split}
\end{equation}

### Definitions of Parameters and variables
$ \xi = A \frac{x}{\sqrt{t}}, \qquad A^2 = \frac{1}{2\alpha}, \qquad \alpha = \frac{ac}{3} \frac{4}{n+4} \frac{T_0^{-n}}{\kappa_0 C_{\mathrm{ve}}}, \qquad  \gamma(x,t,T_{\mathrm{e}}) = \gamma_0 T_{\mathrm{e}}^{-m} f(x,t), \qquad m=0, 3/2$

### Test Problem Parameters
$C_{ve} = 0.1 [GJcm^{-3}keV^{-1}], \qquad C_{vi} = 0.05 [GJcm^{-3}keV^{-1}], \qquad \gamma_0 = [cm^3keV-ns], \qquad \rho = 3.0 [gcm^{-3}], \qquad a = 0.01372 [GJKeV^{-4} cm^{-3}], \qquad  c = 29.9792458 [cm ns^{-1}], \qquad n = 3$

### Define constants

```python
n = 3
T0=1
c=29.998
a=0.01372
kappa0=100
xmax=1.2
rho = 3.0
Cve=0.1*rho
Cvi=.05*rho
#alpha2= a*c*4*T0**(-n)/(3*(n+4)*kappa0*Cve)
alpha= a*c*4/(3*(n+4)*kappa0*Cve*(T0**n)*rho)

A = sqrt(1/(2*alpha))
tol = 1e-12
m=0
gamma0=0.5
ICt = [g(xmax-tol,xmax),gprime(xmax-tol,xmax), h(xmax-tol,xmax)]
ICs = [g(xmax-tol,xmax),gprime(xmax-tol,xmax), f(xmax-tol,xmax)]
print(alpha)
step=1e-4

print(alpha)
```

    0.0008710530370370367
    0.0008710530370370367

### Optimize functions to find ximax

```python
#Time dependent optimize functions
#using BDF to solve time dependent eqs 1,2
opt_fun_BDF_time = lambda xm: integrate.solve_ivp(RHSfun_time, (xm-tol,1e-10), [g(xm-tol,xm),gprime(xm-tol,xm), h(xm-tol,xm)],
                                         method='BDF', max_step = step).y[0][-1] - 1
#Using DOP853 to solve time dependent
opt_fun_DOP_time = lambda xm: integrate.solve_ivp(RHSfun_time, (xm-tol,0), [g(xm-tol,xm),gprime(xm-tol,xm), h(xm-tol,xm)],
                                         method='DOP853', max_step = step).y[0][-1] - 1
#Space dependent(BDF) eqs 2,3
opt_fun_BDF_space = lambda xm: integrate.solve_ivp(RHSfun_space, (xm-tol,1e-10), [g(xm-tol,xm),gprime(xm-tol,xm), f(xm-tol,xm)],
                                         method='BDF', max_step = step).y[0][-1] - 1
```

### Output results for numerical code verification

#### Time dependent case

```python
#Choose parameters
m=3/2
minval = optimize.brentq(opt_fun_BDF_time, .8,1.2)
print(minval)
sol = integrate.solve_ivp(RHSfun_time, (minval-tol,1e-14), ICs, method='BDF', max_step = step)

#choose initial and final times
# 1 shake = 10 ns
tau_i=1e-12
tau_1=1
tau_2=10
tau_3=20

z1=sqrt(tau_i)/A
z2=sqrt(tau_1)/A
z3 =sqrt(tau_2)/A
z4 = sqrt(tau_3)/A
ll = len(sol.t)

TeList=np.ndarray.tolist(sol.y[0])
TiList=np.ndarray.tolist(sol.y[2])

xlistI=np.ndarray.tolist(sol.t*z1)
xlist1=np.ndarray.tolist(sol.t*z2)
xlist2=np.ndarray.tolist(sol.t*z3)
xlist3=np.ndarray.tolist(sol.t*z4)

#plot solution
plt.plot(sol.t,sol.y[0],label="Te")
plt.plot(sol.t,sol.y[2],label = "Ti")
plt.xlabel(r"Normalized Distance $\xi$")
plt.ylabel("Normalized Temperature")
plt.legend()
# show("test")

# Write results to csv files
#Initial condition
with open('g1e-12_m32_gamma0_half_time.csv', 'w', ) as myfile:
    wr = csv.writer(myfile, quoting=csv.QUOTE_NONE, lineterminator = '\n',escapechar='')

    fields = (["position (cm)", "Electron Temp (KeV)", "Ion Temp (KeV)"])
    for i in range(ll):
        wr.writerow([(xlistI[ll-1-i]),(TeList[ll-1-i]), (TiList[ll-1-i])])
#results
with open('g1_m32_gamma0_half_time.csv', 'w', ) as myfile:
    wr = csv.writer(myfile, quoting=csv.QUOTE_NONE, lineterminator = '\n',escapechar='')

    fields = (["position (cm)", "Electron Temp (keV)", "Ion Temp (keV)"])
    for i in range(ll):
        wr.writerow([(xlist1[ll-1-i]),(TeList[ll-1-i]), (TiList[ll-1-i])])
with open('g10_m32_gamma0_half_time.csv', 'w', ) as myfile:
    wr = csv.writer(myfile, quoting=csv.QUOTE_NONE, lineterminator = '\n',escapechar='')

    fields = (["position (cm)", "Electron Temp (keV)", "Ion Temp (keV)"])
    for i in range(ll):
        wr.writerow([(xlist2[ll-1-i]),(TeList[ll-1-i]), (TiList[ll-1-i])])
with open('g20_m32_gamma0_half_time.csv', 'w', ) as myfile:
    wr = csv.writer(myfile, quoting=csv.QUOTE_NONE, lineterminator = '\n',escapechar='')

    fields = (["position (cm)", "Electron Temp (keV)", "Ion Temp (keV)"])
    for i in range(ll):
        wr.writerow([(xlist3[ll-1-i]),(TeList[ll-1-i]), (TiList[ll-1-i])])
```

```python
### Check solution at x,t=0
print(sol.y[0][-1],sol.t[-1])
```

    1.0004139623298154 1e-14

#### Space Dependent case

```python
#Choose parameters
m=3/2
minval = optimize.brentq(opt_fun_BDF_space, .8,1.2)
print(minval)
sol = integrate.solve_ivp(RHSfun_space, (minval-tol,1e-12), ICs, method='BDF', max_step = 1e-4)

#choose initial and final times
# 1 shake = 10 ns
tau_i=1e-12
tau_1=1
tau_2=10
tau_3=20

z1=sqrt(tau_i)/A
z2=sqrt(tau_1)/A
z3 =sqrt(tau_2)/A
z4 = sqrt(tau_3)/A
ll = len(sol.t)

TeList=np.ndarray.tolist(sol.y[0])
TiList=np.ndarray.tolist(sol.y[2])

xlistI=np.ndarray.tolist(sol.t*z1)
xlist1=np.ndarray.tolist(sol.t*z2)
xlist2=np.ndarray.tolist(sol.t*z3)
xlist3=np.ndarray.tolist(sol.t*z4)

#plot solution
plt.plot(sol.t,sol.y[0],label="Te")
plt.plot(sol.t,sol.y[2],label = "Ti")
plt.xlabel(r"Normalized Distance $\xi$")
plt.ylabel("Normalized Temperature")
plt.legend()
# show("test")

# Write results to csv files
#Initial condition
with open('g1e-12_m32_gamma0_half_space.csv', 'w', ) as myfile:
    wr = csv.writer(myfile, quoting=csv.QUOTE_NONE, lineterminator = '\n',escapechar='')

    fields = (["position (cm)", "Electron Temp (KeV)", "Ion Temp (KeV)"])
    for i in range(ll):
        wr.writerow([(xlistI[ll-1-i]),(TeList[ll-1-i]), (TiList[ll-1-i])])
#results
with open('g1_m32_gamma0_half_space.csv', 'w', ) as myfile:
    wr = csv.writer(myfile, quoting=csv.QUOTE_NONE, lineterminator = '\n',escapechar='')

    fields = (["position (cm)", "Electron Temp (KeV)", "Ion Temp (KeV)"])
    for i in range(ll):
        wr.writerow([(xlist1[ll-1-i]),(TeList[ll-1-i]), (TiList[ll-1-i])])
with open('g10_m32_gamma0_half_space.csv', 'w', ) as myfile:
    wr = csv.writer(myfile, quoting=csv.QUOTE_NONE, lineterminator = '\n',escapechar='')

    fields = (["position (cm)", "Electron Temp (KeV)", "Ion Temp (KeV)"])
    for i in range(ll):
        wr.writerow([(xlist2[ll-1-i]),(TeList[ll-1-i]), (TiList[ll-1-i])])
with open('g20_m32_gamma0_half_space.csv', 'w', ) as myfile:
    wr = csv.writer(myfile, quoting=csv.QUOTE_NONE, lineterminator = '\n',escapechar='')

    fields = (["position (cm)", "Electron Temp (KeV)", "Ion Temp (KeV)"])
    for i in range(ll):
        wr.writerow([(xlist3[ll-1-i]),(TeList[ll-1-i]), (TiList[ll-1-i])])
```


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

x, y = sympy.symbols('x y')
```

- $\alpha$ (그리스 문자 알파), a (영어)
- $\beta$ (그리스 문자 베타), b (영어)
- $\gamma$ (그리스 문자 감마), r (영어)
- $\epsilon$ (그리스 문자 엡실론), e (영어)
- $\kappa$ (그리스 문자 카파), k (영어)
- $\nu$ (그리스 문자 뉴), v (영어)
- $\rho$ (그리스 문자 로), p (영어)
- $\sigma$ (그리스 문자 시그마), 6 (숫자)
- $\omega$ (그리스 문자 오메가), w (영어)

## Numpy를 활용한 선형대수 입문 (김도형 박사님 블로그) - datascienceshool.net에 가면 엄청 도움됨!
- 선형대수 (linear Algebra)는 데이터 분석에 필요한 각종 계산을 돕기 위한 학문이다. 데이터 분석을 하려면 수 많은 숫자로 이루어진 데이터를 다루어야 한다. 하나의 데이터가 수십 개에서 수만 개의 숫자로 이루어져 있을 수도 있고, 또 이러한 데이터 수만 개가 하나의 집합을 이루고 있을 수도 있다.
- 선형대수를 사용하면 대량의 데이터를 포함하는 복잡한 계산 과정을 몇 글자 되지 않는 간단한 수식으로 서술할 수 있다. 따라서, 데이터를 다루는 과정을 정확하고 간단하게 서술할 수 있다. 이를 위해 우선 선형대수에서 사용되는 여러 가지 기호와 개념을 익혀보자.

## Data Type
- 선형대수에서 다루는 데이터는 개수나 형태에 따라 크게 **스칼라(scalar), 벡터(vector), 행렬(matrix)**의 세 가지 유형으로 나누어진다. 간단하게 말하자면 스칼라는 숫자 하나로 이루어진 데이터이고, 벡터는 여러 개의 숫자로 이루어진 데이터 레코드(data record)이며, 행렬은 이러한 벡터, 즉 데이터 레코드가 여러 개 있는 데이터 집합이라고 볼 수 있다.

## 스칼라 (Scalar)
- 스칼라는 하나의 숫자만으로 이루어진 데이터를 말한다. 예를 들어 어떤 붓꽃 한 송이의 꽃잎 길이를 측정하면 숫자가 하나 나올 것이다. 이 데이터는 스칼라이다. 스칼라는 보통 x와 같이 알파벳 소문자로 표기하며 실수(real number)인 숫자 중의 하나이므로 실수 집합 R의 원소라는 의미에서 다음처럼 표기한다.
- x $\in$ R

## 벡터 (Vector)
- 벡터는 여러 개의 숫자가 특정한 순서대로 모여 있는 것을 말한다. 사실 대부분의 데이터 레코드는 여러 개의 숫자로 이루어진 경우가 많다. 예를 들어 붓꽃의 종을 알아내기 위해 크기를 측정할 때, 꽃잎의 길이 x1뿐 아니라 꽃잎의 폭 x2, 꽃 받침의 길이 x3, 꽃받침의 폭 x4라는 4개의 숫자를 측정할 수도 있다. 이렇게 측정된 4개의 숫자는 한 송이의 붓 꽃에서 나온 데이터이므로 따로따로 다루기보다는 하나의 쌍으로 묶어 놓는 것이 좋다. 이때 숫자의 순서가 바뀌면 어떤 숫자가 꽃잎의 길이이고 어떤 숫자가 꽃받침의 폭인지 알 수 없으므로 숫자의 순서를 유지하는 것이 중요하다. 이런 데이터 묶음을 선형대수에서는 벡터라고 부른다.
- 붓꽃의 예에서는 4개의 데이터(x1, x2, x3, x4)가 하나로 묶여 있는데, 이를 선형 대수 기호로는 다음 처럼 하나의 문자 x로 표기한다.
- x = $[[x1],[
      x2],
      [x3],
      [x4]]$
- 이 때 벡터는 복수의 가로줄, 즉 행(row)을 가지고 하나의 세로줄, 즉 열(column)을 가지는 형태로 위에서 아래로 내려써서 표기해야 한다.
- 하나의 벡터를 이루는 데이터의 개수를 차원(dimension)이라고 한다. 위에서 예로 든 x는 4개의 실수로 이루어져 있으므로 4차원 실수 벡터라고 한다.

- 벡터의 원소가 되는 스칼라 값음 1, 2 등의 자연수 아래 첨자(sub-script)를 붙여서 원소의 위치를 표시한다. 하지만, 자연수 아래 첨자가 되어 있다고 무조건 스칼라는 아니다. 마찬가지로 벡터가 여러 개 있으면 서로 다른 벡터를 구별하기 위해 벡터 이름에 아래 첨자를 붙일 수도 있다. 따라서 아래 첨자를 가진 알파벳 소문자 기호는 스칼라일 수도 있고 벡터일 수도 있다. 두 경우는 문맥에 따라 구별해야 한다. 때에 따라서는 스칼라가 아닌 벡터라는 점을 강조하기 위해 볼드체 기호 **x**를 사용하기도 한다.

- 예를 들어 어떤 붓꽃 표본 한 송이를 꺽어 측정하였더니, 꽃잎의 길이가 5.1cm, 꽃잎의 폭이 3.5cm, 꽃받침의 길이가 1.5cm, 꽃받침의 폭이 0.2cm 였다면 이 데이터 쌍을 x1이라고 이름 붙이고 다음처럼 나타낼 수도 있다.
- x1 = [5.1\n3.5\n1.4\n0.2]
- x2 = [4.9\n3.0\n1.4\n0.2]
- 만약 이 데이터를 이용하여 붓꽃의 종을 결정하는 예측 문제를 풀고 있다면 이러한 데이터 벡터를 특징벡터(feature vector)이라고 한다. Numpy를 사용하여 벡터를 표현할 떄는 벡터를 열의 개수가 하나인 2차원 배열 객체로 표현하는 것이 올바르다. 예를들어 위의 벡터 x1을 numpy로 표기하면 다음과 같다.

```python
x1 = np.array([[5.1], [3.5], [1.4], [0.2]])
x1
```

    array([[ 5.1],
           [ 3.5],
           [ 1.4],
           [ 0.2]])

- 하지만 Numpy는 1차원 배열 객체도 대부분 벡터로 인정한다. 이 때는 벡터가 마치 하나의 행처럼 표시되어도 실제로는 열이라는 점에 주의한다.

```python
x1 = np.array([5.1, 3.5, 1.4, 0.2])
x1
```

    array([ 5.1,  3.5,  1.4,  0.2])

- 그러나 벡터 데이터를 처리하는 프로그램에 따라서 두 가지 표현법 중 열 표기를 정확하게 요구하는 경우도 있으므로 주의해야 한다. 예를 들어 Scikit-learn 패키지에서 벡터를 요구하는 경우에는 반드시 열의 개수가 1개인 2차원 배열 객체를 넣어야 한다.

#### 연습 문제 1
- Numpy를 사용해서 다음에 제시한 붓꽃 데이터 x2에 대한 벡터 변수 x2를 만든다.
- x2 = [4.9\n3.0\n1.4\n0.2]

```python
x2 = np.array([[4.9], [3.0], [1.4], [0.2]])
x2
```

    array([[ 4.9],
           [ 3. ],
           [ 1.4],
           [ 0.2]])

- 예측 문제의 입력 데이터는 대부분 벡터로 표시한다. 예를 들어 숫자 이미지를 입력받아 어떤 숫자인지 분류하는 문제에서 이미지는 원래 2차원 데이터이지만 1차원 벡터로 변환할 수 있다. 다음은 8 x 8 차원의 숫자 0과 1 이미지 4개를 64차원의 벡터로 펼친 것이다. 같은 숫자에 대한 벡터가 서로 닮았다는 점에 주의하라.

```python
from sklearn.datasets import load_digits

digits = load_digits()
d0 = digits.images[0]
d1 = digits.images[10]
d2 = digits.images[1]
d3 = digits.images[11]

f, ax = plt.subplots(1, 4)
ax[0].imshow(d0, interpolation = 'nearest', cmap=plt.cm.Blues)
ax[0].grid(False)
ax[1].imshow(d1, interpolation = 'nearest', cmap=plt.cm.Blues_r)
ax[1].grid(False)
ax[2].imshow(d2, interpolation = 'nearest', cmap=plt.cm.Blues)
ax[2].grid(False)
ax[3].imshow(d3, interpolation = 'nearest', cmap=plt.cm.Blues_r)
ax[3].grid(False)
plt.show()
```

```python
v0 = d0.reshape(64, 1)
v1 = d1.reshape(64, 1)
v2 = d2.reshape(64, 1)
v3 = d3.reshape(64, 1)

f, ax = plt.subplots(4, 1, figsize=(12,2))
ax[0].imshow(v0.T, interpolation='nearest', cmap=plt.cm.Blues)
ax[0].grid(False); ax[0].set_xticks([]); ax[0].set_yticks([])
ax[1].imshow(v1.T, interpolation='nearest', cmap=plt.cm.Blues)
ax[1].grid(False); ax[0].set_xticks([]); ax[0].set_yticks([])
ax[2].imshow(v2.T, interpolation='nearest', cmap=plt.cm.Blues)
ax[2].grid(False); ax[0].set_xticks([]); ax[0].set_yticks([])
ax[3].imshow(v3.T, interpolation='nearest', cmap=plt.cm.Blues)
ax[3].grid(False); ax[0].set_xticks([]); ax[0].set_yticks([])
plt.tight_layout()
plt.show()
```

## 행렬 (Matrix)

- 행렬은 복수의 차원을 가지는 데이터 레코드가 다시 여러 개 있는 경우의 데이터를 합쳐서 표기한 것이다. 예를 들어 앞서 말한 붓꽃 예에서 6개의 붓꽃에 대해 크기를 측정하였다면 4차원 붓꽃 데이터가 6개가 있다. 즉, 4 X 6 = 24개의 실수 숫자가 있는 것이다.

- 행렬의 우너소 하나하나는x2,3처럼 두 개의 숫자 쌍을 아래첨자(sub-script)로 붙여서 표기한다. 첫 번째 숫자가 행을 뜻하고 두 번째 숫자가 열을 뜻한다. 예를 들어 x2,3는 두 번째 행(위에서 아래로 두번째), 세번째 열(왼쪽에서 오른쪽으로 세번째)의 숫자를 뜻한다. 행/열의 개수가 10보다 적을떄는 쉼표 없이 x23라고 표기할 때도 있다.
- 벡터는 열의 수가 1인 행렬이라고 볼 수 있으므로 벡터를 다른 말로 **열 벡터(column vector)**라고도 한다.
- 데이터를 행렬로 묶어서 표시할 때는 붓꽃 하나에 대한 데이터 레코드, 즉 하나의 벡터가 열이 아닌 행(row)으로 표시한다. 붓꽃의 예에서는 하의 데이터 레코드가 4차원 데이터 였다는 점을 기억하다.

- **하나의 데이터 레코드를 단독으로 벡터로 나타낼 때는 하나의 열(column)** 나타내고 **복수의 데이터 레코드 집합을 행렬로 나타낼 때는 하나의 데이터 레코드가 하나의 행(row)**으로 표기하는 것은 얼핏 보기에는 일관서잉 없어 보이지만, 추후 다른 연산을 할 때 이런 모양이 필요하기 때문이다. 데이터 분석에서 쓰는 일반적인 관례이므로 외워두어야 한다.
- 만약 이 데이터를 이용하여 붓꽃의 종을 결정하는 예측 문제를 풀고 있다면 이 행을 특징 행렬(feature matrix)이라고 하기도 한다.
- 앞에서 예로 들었던 두 송이의 붓꽃 데이터를 하나의 행렬로 합친 Numpy는 아래와같다.

```python
A = np.array([[11,12,13],[21,22,23]])
A
```

    array([[11, 12, 13],
           [21, 22, 23]])

#### 연습 문제 2
- Numpy를 사용해서 다음에 제시한 붓 꽃 데이터 X에 대한 행렬 변수 x를 만든다.

```python
x = np.array([[5.1, 3.5, 1.4, 0.2], [4.9, 3.0, 1.4, 0.2]])
x
```

    array([[ 5.1,  3.5,  1.4,  0.2],
           [ 4.9,  3. ,  1.4,  0.2]])

## 전치 연산 (Transpose)
- 이번에는 이러한 스칼러, 벡터, 행렬 데이터를 변형시키는 방법 즉, 연산(operation)에 대해서 알아보자. **전치(Transpose)** 연산은 행렬에서 가장 기본이 되는 연산으로 행렬의 **행과 열을 바꾸는 연산**을 말한다. 전치 연산은 벡터나 행렬에 T라는 위첨자(super-script)를 붙여서 표기한다. 예를 들어 앞에서 보인 6 x 4 차원의 행렬을 전치 연산하면 4 x 6 차원의 행렬이 된다.
- 전치 연산으로 만든 행렬을 원래 행렬에 대한 전치행렬이라고 한다. (열)벡터 x에 대해 전치 연산을 적용하여 만든 x transpose는 행의 수가 1인 행렬이므로 행 벡터(row vecotr)이라고 한다.

- Numpy에서는 ndarray 객체의 T라는 속성을 이용하여 전치 행렬을 구한다. 이 때 T 메서드(method)가 아닌 속성(attribute)이므로 소괄호 ()를 붙여서 호출하면 안된다.

```python
A.T
```

    array([[11, 21],
           [12, 22],
           [13, 23]])

- 다만 1차원 ndarray는 전치 연산이 정의되지 않는다.

```python
x1
```

    array([ 5.1,  3.5,  1.4,  0.2])

```python
x1.T
```

    array([ 5.1,  3.5,  1.4,  0.2])

#### 연습문제 3
- 1. Numpy를 사용해서 다음에 제시한 붓꽃 데이터 X의 전치행렬 X Transpose를 구한다.
- 2. 전치행렬을 다시 전치한 행렬 (X transpose) of transpose를 구한다. 이 행렬과 원래 행렬X를 비교한다.

```python
X = np.array([[5.1, 3.5, 1.4, 0.2], [4.9, 3.0, 1.4, 0.2]])
X.T
```

    array([[ 5.1,  4.9],
           [ 3.5,  3. ],
           [ 1.4,  1.4],
           [ 0.2,  0.2]])

```python
X.T.T
```

    array([[ 5.1,  3.5,  1.4,  0.2],
           [ 4.9,  3. ,  1.4,  0.2]])

## 특수한 벡터와 행렬
- 몇가지 특수한 벡터와 행렬은 별도의 기호나 이름이 붙는다.

### 영 벡터
- 모든 원소가 0인 N차원 벡터이다.

### 일 벡터
- 모든 원소가 1인 N차원 벡터이다.

### 정방 행렬
- 행의 개수와 열의 개수가 같은 행렬을 정방 행렬(square Matrix)이라고 한다.

### 대각 행렬
- 행렬에서 행과 열이 같은 위치를 주 대각 또는 간단히 대각(diagonal)이라고 한다. 대각 위치에 있지 않는 것들은 비대각이라고 한다. 모든 비대각 요소가 0인 행렬을 대각 행렬이라고 한다.
- 대각 행렬이 되려면 대각 성분이 0이기만 하면 된다. 반드시 정방행렬일 필요는 없다.
- Numpy로 대각 정방 행렬을 생성하려면 diag 명령을 사용한다.

```python
np.diag([1,2,3])
```

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

### 단위행렬 (Identity Matrix)
- 대각 행렬 중에서도 모든 대각 성분의 값이 1인 대각행렬을 단위 행렬이라고 한다. 단위 행렬은 보통 알파벳 대문자 I로 표기한다.
- Numpy로 단위행렬을 생성하려면, identity or eye 명령을 사용한다.

```python
np.identity(5)
```

    array([[ 1.,  0.,  0.,  0.,  0.],
           [ 0.,  1.,  0.,  0.,  0.],
           [ 0.,  0.,  1.,  0.,  0.],
           [ 0.,  0.,  0.,  1.,  0.],
           [ 0.,  0.,  0.,  0.,  1.]])

```python
np.eye(10)
```

    array([[ 1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
           [ 0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
           [ 0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
           [ 0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
           [ 0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
           [ 0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.],
           [ 0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.],
           [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.],
           [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.],
           [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.]])

### 대칭행렬
- 만약 전치 연산을 통해서 얻은 전치 행렬과 원래의 행렬이 같으면 대칭 행렬(symmetric matrix)이라고 한다. 정방 행렬만 대칭 행렬이 될 수 있다.

#### 연습문제 4
1. 영 벡터, 일벡터, 정방행렬, 대각행렬, 단위행렬, 대칭 행렬의 예를 하나씩 만들어 본다.
2. 위의 벡터와 행렬을 Numpy로 나타내 본다.

```python
# 영벡터
zero = np.array([[0], [0], [0], [0]])
zero
```

    array([[0],
           [0],
           [0],
           [0]])

```python
# 일벡터
one = np.array([[1], [1], [1]])
one
```

    array([[1],
           [1],
           [1]])

```python
# 정방행렬
X = np.array([[1,2,3],[4,5,6], [7,8,9]])
X.T
```

    array([[1, 4, 7],
           [2, 5, 8],
           [3, 6, 9]])

```python
# 대각행렬
Y = np.array([[1,0,0], [0,3,0], [0,0,10]])
Y
```

    array([[ 1,  0,  0],
           [ 0,  3,  0],
           [ 0,  0, 10]])

```python
# 단위행렬
np.array([[1,0,0], [0,1,0], [0,0,1]]), np.eye(3)
```

    [[1 0 0]
     [0 1 0]
     [0 0 1]] [[ 1.  0.  0.]
     [ 0.  1.  0.]
     [ 0.  0.  1.]]

```python
# 대칭 행렬
np.array([[1,2,3], [2,1,4], [3,4,1]])
```

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

```python

```


## Learning Process

Here in this section we are going to partly describy and in the remaining to test and evaluate performance of various machine learning models that we selected and adopted to built up learning models for accomplishing supervised machine learning tasks related to classification problems. More precisely, we focused on binary classification problemsa, since the target variables, that is T-OR-D feature, amongst the 12 features from which the dataset is made of, and from which the more or less hundered of records are described, is a binary categorical feature which can assume the two values represented by labels DECK and THROUGH describing in two distinct manner a property about each bridge within the dataset, that property refers to the system used for constructing the bridge surface, that is commonly called deck, for let veicheles or trains or whatevert to cross the rivers that are three distinct rivers: A, M, O. Where A stands for Allegheny river, while M stands for Monongahela river and lastly O stands for Ohio river.

Before describing the fine tuning process applied to the different models accordingly to their own major properties, characteristics and features, we have decided and established to test each model performance by looking at how well all of them is going just exploiting the default setting and runnng cross-validation protocol, in other word also referred to as policy, to check the accuracy level, as ana instance and some other metrics.

To be more detailed we follow the common machine leaning working flow, that requires to split the data set, after having preprocessed it properly and in a suitable manner to meet mahcine learning models needs, into subsets commonly referred  to as training set and test set. Where, the former is exploited to build up a inference model and the latter is used to check model's performance as weel as behavior up on held out sample of istances never seen before and so those examples that the learning model wasn't provided with to learn weights and select right hyper-params to plug in back into model at the end of training procedure, in order to later test the model with the found weights as well as hyper-parameters and if it meet our requirements in terms of performance values reached at test time, then it will be ready for deployments.

As we can see from the image just above what we have to do, following the purposed machine learning scheme or work flow are the subsequent steps:

__(1) Split Dataset into Train and Test sets__:
After having done with preprocessing phase we separate or divide the dataset into training set and test set, where usually the training set is bigger than the test set, and in some cases  the test set, for recording some kind of performance measures, can be made of just a single instance against which the model will be tested and checked.

__(2) Split Train set into smaller Train set and a Validation set__:
Once  have made the first split then we hold out a part for later checking the test set and we focus on training set. In fact once we have selected a machine learning model amongst those we want to adopt to fit a model and compare the results obtained we try to indentify the best fit parameters to setting the model with them. To reach such a goal we can adopt different aproaches to split further the training set into a validation set and a smaller train set in order to emulate before doing test phase a possible beahvior of the trainng model once we think it is ready for the following phase that is test step.
There are several procedure for establishing how to divide training set into two new subsets, that are validation and a little bit smaller train set, where all of them are also connected or reffered to a learning algorithm called cross-validation which consists roughly speaking into testing the model against a smaller portion of training set in order to record and measure the performance of a model before saying we are ready to proceed with test phase. Among the existing Cross-Validation Procedure we adopt and so describe briefly the following:
- **K-fold Cross-Validation**: which is a cross validation protocol exploited so that we split the training set into K-folds, in other words K-subsets all of the same size, a part eventually the last one which will be the remainder set of samples. One at a time the K-fold are left out and the model is firstly trained against K-1 subsets(folds) and then test against the left out fold for recording the performanc. At the end of the k-times we have trained the model we have recorded the performance measures and we so can average the results and understand how model in average is well doing. In oder words we can either assume the mean value as the driving value to assume the model as satisfying our constraint on performance measures or adopt the best result amongst the k-trains as the settings for hyper-params to adopt. This procedure is feasible and suitable if we do not carea botu the fact that, in cases of classification tasks, the categories might be balanced or not in terms of number of instances, as well as, it can be adopted also if we want to show a learning curve and some other performance graphics or schemes as Confusion matrtices and ROC or recall-precision curves. Latly usual values for K are: 5, 10, 20.

- **Leave One Out Cross-Validation**: It is a special case of the K-fold Cross-Validation. In fact, instead of adopting the usual values as 5, 10, 20, for K as the number of possible subsets, we establish to identify each single instance as a possible fold. It's clear that this algorithm requires more time to be completed and does not allow to show up the graphics cited just above, since we do not enough data for confusion matrix and ROC or recall-precision curves.
- **Stratified Cross-Validation**: It is a good compromise when the dataset is still large enough to be exploited for training purposes but we decide to split into K-folds the traning set such that each subset have the same proportion of samples coming from the differn classes. This operation reveals to be necessary or even mandatory when we detect that the dataset does not show the same number of samples, more or less, for each class, in other word when the dataset is unbalanced for a given attribute that is the target attribute. This means that, while trying to mitigate the issue about the unbalanced dataset we thunk as well as hope that this management let the model to fit a model which will not be affected heavily just from the most numerous class, bus still learn how to classify the samples coming from the other less numerous classes, without too mcuh misclassifying errors. As usual also with Stratified Cross-Validation we are able to show same graphics as with plain K-fold Cross-Validation, the difference is that the folds are not randomly sampled from the original training set, but yet are sampled in the same proportion per each class in order to have the same number of samples for each class inside each fold.

We try out all of the three described cross-validation techniques to measure how weel default settings for different models are doing to gaina a base line against wich compare later results coming from fine tuning process carried out exploiting grid search technique for selecting the best combinatin of purposed values for each candidate machine learning technique.

__(3) Grid Search approach__: is the technique adopted when taking into account one at a time all the machine learning algorithm for selecting the best set of hyper-parameters. It consists into defining for each model a grid of possible values for different hyper-parameters that in some sense represent our degree of freedom referred to some of the properties that characterize all the different models. The grid of values might be real number ranging within a more or less interval, or a string value used to trigger a certain feature of a model combined with other related aspects of the machine learning algorithm of the given model. We recall that the standard grid search will proceed until all possible combination of provided values have been test and training with such settings have been carried out. Opposite to classic grid search it is another technique called Random Grid Search, which implies instead to let a model to choose or sample randomly the hyper-parameters within the ranges or intervals related to each hyper-params. The latter technique can be potentially less expensive since we test a reduced number of combination but might be sub-optimal even if the results can be still acceptable and meaningful.

__(4) Metrics and model performance's evaluation tools__: before deploying we have to test our model against a test set, that is a subset created or obtained from overall dataset which was, after preprocessing phase that turns feature values somehow into numbers, devided into two distinct sets generally of different size. The test set evaluation implies exploiting some metrics such as Accuracy, but yet there exist several others that partly are derived from confusion matrix evaluation tool, such as Precsio, Recal F1-score, and the like.

So what we understand is that we can make use of a bouch of metrics but rather than using directly those metrics we can explore model's performance by means of more useful tools such as Confusion Matrix, Roc curve in order to better understanding model's behavior when fed with unobserved new samples, as well as, how to set a threshold for determing when a target variable will suggest us that the classified sample belong to one class or the other. So, here we briefly describe which instruments we exploit to measure model's peformance starting from confusion matrix and moving ahead toward ROC curve.

**Confusion Matrix**: in statistics a confusion matrix it's a grid or matrix of numbers and in the simplest scenario, correspoding to a binary classification task, it aims at showing how well a model was going when applied onto unknow or preiviously unseen data points and  samples, in the following manner. Arbitrary we establish that along the rows we have the fraction of samples that the model has calssified or has assigned to agiven class, and so the rows account for *Predicted valuers*. Vice versa, along the columns we have the total number of samples for each class that all together resemble the so called *Actual Values* as we illustrate in the picture just below:

So, such a table of numbers allows us to measure the fraction of correctly classified examples belonging to the Positive class, also referred to as *True Positive(TP)* or to the Negative class, also named *True Negative(TN)* and at the same time we can derive also the fractions of wrongly classified Positive samples and Negative ones, respectively knwon as *False Positve(FP)* and *False Negative (FN)*. It is clear that looking at the diagonal matrix we can undestand that the larger the value along the diagonal the better the model was able to correctly identify the samples accordingly to their own actual class.
From the four statistics measure depiceted above, that are TP,TN, FP, and FN, by the time have been dirved other useful metrics that can be exploited when the most used and well knwon measurement performance of accuracy is not enough due to the fact as an instance we wanto to analyze deeper our model behavior toward optimization of some goal or because we have to deal with dataset which are not balanced through classes to be predicted and so we wanto some other metrics to ensure the goodness of our solutions.

**Roc Curve**: for the reason cited some lines earlier by exploiting the four basis metrics have been developed other useful tools for ensuring the goodness of a solution, among those tools we decide to adopt the following one, knwon as Roc Curve. It is a curve, whose acronim stands for Receiver Operating Curve, largely employed in the field of *Decison Theory* and aims at finding, siggesting or showing how moedl's performance varywhen we are going to set different thresholds for a simple scenario in which we are going to solve a binary classification problem. Such a curve require to show on the x-axis the fraction of samples corresponding to *False Positve Rate (FPR)* at a different values of the model's hyper-params corresponding to threshold set for classifying items at inference time, as well as on the x-axis the fraction of samples corresponding to *True Positive Rate(TRP)* in order to plot a curve that originates at coordinates (0,0) and terminates at coordinates (1,1), varying in the middle accordingly to the pairs of values recorded at a given threshold for (FPR,TPR) pair. We are also reporting two driving curves that are respectvely the curve related to the *Random Classifier* which corresponds to a classifier that for each instance is just randomly selecting the predicted class, and the *Perfetct Classifier* that always correctly classifyes all the samples. Our goal by analysing such a graphics is to identify the threshold value such that we are near the points on the curve that are not so far from the upper-left corner so that to maximise the TPR as well as minimizing the FPR. Another useful quantity related to Roc Curve is represented by the so called amount Area Under the Curve (AUC) that suggests us how much area under the rco curve whas accounted while varying the threshold for classiffying the test samples, in particular the higher the value the better the classifier is doing varying the threshlds. Lastly we notice that the Random Sample accounts for an AUC equls to 0.5 %, while the Perfect Classifier for 1.0 % so we aim at obtaing a value for AUC that is in at least in the between but that approaches the unity. Here, below is presented an example of Roc Curve example:

Lastly, before moving ahead with describing Machine Learning Models we provide a brief list of other useful metrics that can be exploited if necessary during our analyses:

**Learning Curve**:
learning curves constitute a great tool to diagnose bias and variance in any supervised learning algorithm. It comes in handt when we have to face against the so called **Bias-Variance Trade-Off**. In order to explain what that trade-off implies we have to say briefly what follows:
- In supervised learning, we assume there's a real relationship between feature(s) and target and estimate this unknown relationship with a model. Provided the assumption is true, there really is a model, which we'll call $f$, which describes perfectly the relationship between features and target.
- In practice, $f$ is almost always completely unknown, and we try to estimate it with a model $\hat{f}$. We use a certain training set and get a certain $\hat{f}$. If we use a different training set, we are very likely to get a different $\hat{f}$. As we keep changing training sets, we get different outputs for $\hat{f}$. The amount by which $\hat{f}$ varies as we change training sets is called **variance**.
- While, for most real-life scenarios, however, the true relationship between features and target is complicated and far from linear. Simplifying assumptions give **bias** to a model. The more erroneous the assumptions with respect to the true relationship, the higher the bias, and vice-versa.
- In practice, however, we need to accept a trade-off. We can’t have both low bias and low variance, so we want to aim for something in the middle, knowing that:
\begin{equation}
\begin{cases}
Y = f(X) + \quad irreducible \quad error \\
f(X) = \hat{f(X)} + \quad reducible \quad error \\
Y = \hat{f(X)} + \quad reducible \quad error + \quad irreducible \quad error \\
 \end{cases}
\end{equation}

__(5)Deployment__: The last step for building a machine learning system comprise the deployment of one or more machine learning trained models that fit and satisfy constraints that was intially fixed before doing any kind of analysis. The main goal of deployemnt is to employ such statistics models for predicting, in other words making inference, against new data, unknown obeservatons for which we do not knwo their target class values.

### Pricipal Component Analysis

After having investigate the data points inside the dataset, I move one to another section of my report where I decide to explore examples that made up the entire dataset using a particular technique in the field of statistical analysis that corresponds, precisely, to so called Principal Component Analysis. In fact, the major objective of this section is understand whether it is possible to transform, by means of some kind of linear transformation given by a mathematical calculation, the original data examples into reprojected representation that allows me to retrieve most useful information to be later exploited at training time. So, lets dive a bit whitin what is and which are main concepts, pros and cons about Principal Component Analysis.

Firstly, we know that **Principal Component Analysis**, more shortly PCA, is a statistical procedure that uses an orthogonal transformation to convert a set of observations of possibly correlated variables into a set of values of linearly uncorrelated variables called *principal components*. This transformation is defined in such a way that:
- the first principal component has the largest possible variance (that is, accounts for as much of the variability in the data as possible),
- and each succeeding component in turn has the highest variance possible under the constraint that it is orthogonal to the preceding components.

The resulting vectors, each being a linear combination of the variables and containing n observations, are an uncorrelated orthogonal basis set. PCA is sensitive to the relative scaling of the original variables.

PCA is mostly used as a tool in *exploratory data analysis* and for making predictive models, for that reasons I used such a technique here, before going through the different learning technique for producing my models.

#### Several Different Implementation

From the theory and the filed of research in statistics, we know that out there, there are several different implementation and way of computing principal component analysis, and each adopted technique has different performance as well as numerical stability. The three major derivations are:
- PCA by means of an iterative based procedure of extraing pricipal components one after the other selecting each time the one that account for the most of variance along its own axis, within the remainig subspace to be derived.
- The second possible way of performing PCA is done via calculation of *Covariance Matrix* applied to attributes, that are our independent predictive variables, used to represent data points.
- Lastly, it is used the technique known as *Singular Valued Decomposition* applied to the overall data points within our dataset.

Reading scikit-learn documentation, I discovered that PCA's derivation uses the *LAPACK implementation* of the *full SVD* or a *randomized truncated SVD* by the method of *Halko et al. 2009*, depending on the shape of the input data and the number of components to extract. Therefore I will descrive mainly that way of deriving the method with respect to the others that, instead, will be described more briefly and roughly.

#### PCA's Iterative based Method
Going in order, as depicted briefly above, I start describing PCA obtained by means of iterative based procedure to extract one at a time a new principal componet explointing the data points at hand.

We begin, recalling that, PCA is defined as an orthogonal linear transformation that transforms the data to a new coordinate system such that the greatest variance by some scalar projection of the data comes to lie on the first coordinate (called the first principal component), the second greatest variance on the second coordinate, and so on.

We suppose to deal with a data matrix X, with column-wise zero empirical mean, where each of the n rows represents a different repetition of the experiment, and each of the p columns gives a particular kind of feature.

From a math poitn of view, the transformation is defined by a set of p-dimensional vectors of weights or coefficients $\mathbf {w} _{(k)}=(w_{1},\dots ,w_{p})_{(k)}$ that map each row vector $\mathbf{x}_{(i)}$ of X to a new vector of principal component scores ${\displaystyle \mathbf {t} _{(i)}=(t_{1},\dots ,t_{l})_{(i)}}$, given by: ${\displaystyle {t_{k}}_{(i)}=\mathbf {x} _{(i)}\cdot \mathbf {w} _{(k)}\qquad \mathrm {for} \qquad i=1,\dots ,n\qquad k=1,\dots ,l}$.

In this way all the individual variables ${\displaystyle t_{1},\dots ,t_{l}}$ of t considered over the data set successively inherit the maximum possible variance from X, with each coefficient vector w constrained to be a unit vector.

More precisely, the first component In order to maximize variance has to satisfy the following expression:

${\displaystyle \mathbf {w} _{(1)}={\underset {\Vert \mathbf {w} \Vert =1}{\operatorname {\arg \,max} }}\,\left\{\sum _{i}\left(t_{1}\right)_{(i)}^{2}\right\}={\underset {\Vert \mathbf {w} \Vert =1}{\operatorname {\arg \,max} }}\,\left\{\sum _{i}\left(\mathbf {x} _{(i)}\cdot \mathbf {w} \right)^{2}\right\}}$

So, with $w_{1}$ found, the first principal component of a data vector $x_{1}$ can then be given as a score $t_{1(i)} = x_{1} ⋅ w_{1}$ in the transformed co-ordinates, or as the corresponding vector in the original variables, $(x_{1} ⋅ w_{1})w_{1}$.

The others remainig components are computed as folloes. The kth component can be found by subtracting the first k − 1 principal components from X, as in the following expression:

- ${\displaystyle \mathbf {\hat {X}} _{k}=\mathbf {X} -\sum _{s=1}^{k-1}\mathbf {X} \mathbf {w} _{(s)}\mathbf {w} _{(s)}^{\rm {T}}}$

- and then finding the weight vector which extracts the maximum variance from this new data matrix  ${\mathbf {w}}_{{(k)}}={\underset {\Vert {\mathbf {w}}\Vert =1}{\operatorname {arg\,max}}}\left\{\Vert {\mathbf {{\hat {X}}}}_{{k}}{\mathbf {w}}\Vert ^{2}\right\}={\operatorname {\arg \,max}}\,\left\{{\tfrac {{\mathbf {w}}^{T}{\mathbf {{\hat {X}}}}_{{k}}^{T}{\mathbf {{\hat {X}}}}_{{k}}{\mathbf {w}}}{{\mathbf {w}}^{T}{\mathbf {w}}}}\right\}$

It turns out that:
- from the formulas depicted above me get the remaining eigenvectors of $X^{T}X$, with the maximum values for the quantity in brackets given by their corresponding eigenvalues. Thus the weight vectors are eigenvectors of $X^{T}X$.
- The kth principal component of a data vector $x_(i)$ can therefore be given as a score $t_{k(i)} = x_{(i)} ⋅ w_(k)$ in the transformed co-ordinates, or as the corresponding vector in the space of the original variables, $(x_{(i)} ⋅ w_{(k)}) w_{(k)}$, where $w_{(k)}$ is the kth eigenvector of $X^{T}X$.
- The full principal components decomposition of X can therefore be given as: ${\displaystyle \mathbf {T} =\mathbf {X} \mathbf {W}}$, where W is a p-by-p matrix of weights whose columns are the eigenvectors of $X^{T}X$.

#### Covariance Matrix for PCA analysis

PCA made from covarian matrix computation requires the calculation of sample covariance matrix of the dataset as follows: $\mathbf{Q} \propto \mathbf{X}^T \mathbf{X} = \mathbf{W} \mathbf{\Lambda} \mathbf{W}^T$.

The empirical covariance matrix between the principal components becomes ${\displaystyle \mathbf {W} ^{T}\mathbf {Q} \mathbf {W} \propto \mathbf {W} ^{T}\mathbf {W} \,\mathbf {\Lambda } \,\mathbf {W} ^{T}\mathbf {W} =\mathbf {\Lambda } }$.

#### Singular Value Decomposition for PCA analysis

Finally, the principal components transformation can also be associated with another matrix factorization, the singular value decomposition (SVD) of X, ${\displaystyle \mathbf {X} =\mathbf {U} \mathbf {\Sigma } \mathbf {W} ^{T}}$, where more precisely:
- Σ is an n-by-p rectangular diagonal matrix of positive numbers $σ_{(k)}$, called the singular values of X;
- instead U is an n-by-n matrix, the columns of which are orthogonal unit vectors of length n called the left singular vectors of X;
- Then, W is a p-by-p whose columns are orthogonal unit vectors of length p and called the right singular vectors of X.

factorizingn the matrix ${X^{T}X}$, it can be written  as:

${\begin{aligned}\mathbf {X} ^{T}\mathbf {X} &=\mathbf {W} \mathbf {\Sigma } ^{T}\mathbf {U} ^{T}\mathbf {U} \mathbf {\Sigma } \mathbf {W} ^{T}\\&=\mathbf {W} \mathbf {\Sigma } ^{T}\mathbf {\Sigma } \mathbf {W} ^{T}\\&=\mathbf {W} \mathbf {\hat {\Sigma }} ^{2}\mathbf {W} ^{T}\end{aligned}}$

Where we recall that ${\displaystyle \mathbf {\hat {\Sigma }} }$ is the square diagonal matrix with the singular values of X and the excess zeros chopped off that satisfies ${\displaystyle \mathbf {{\hat {\Sigma }}^{2}} =\mathbf {\Sigma } ^{T}\mathbf {\Sigma } } {\displaystyle \mathbf {{\hat {\Sigma }}^{2}} =\mathbf {\Sigma } ^{T}\mathbf {\Sigma } }$. Comparison with the eigenvector factorization of $X^{T}X$ establishes that the right singular vectors W of X are equivalent to the eigenvectors of $X^{T}X$ , while the singular values $σ_{(k)}$ of X are equal to the square-root of the eigenvalues $λ_{(k)}$ of $X^{T}X$ .

At this point we understand that using the singular value decomposition the score matrix T can be written as:

${\begin{aligned} \mathbf{T} & = \mathbf{X} \mathbf{W} \\ & = \mathbf{U}\mathbf{\Sigma}\mathbf{W}^T \mathbf{W} \\ & = \mathbf{U}\mathbf{\Sigma} \end{aligned}}$

so each column of T is given by one of the left singular vectors of X multiplied by the corresponding singular value. This form is also the polar decomposition of T.

Efficient algorithms exist to calculate the SVD, as in scikit-learn package, of X without having to form the matrix $X^{T}X$, so computing the SVD is now the standard way to calculate a principal components analysis from a data matrix

```python
from utils.all_imports import *;
%matplotlib inline
# Set seed for notebook repeatability
np.random.seed(0)
```

    None

```python
# =========================================================================== #
# READ INPUT DATASET
# =========================================================================== #
dataset_path, dataset_name, column_names, TARGET_COL = get_dataset_location()
estimators_list, estimators_names = get_estimators()

dataset, feature_vs_values = load_brdiges_dataset(dataset_path, dataset_name)
columns_2_avoid = ['ERECTED', 'LENGTH', 'LOCATION']
# Array used for storing graphs
plots_names = list(map(lambda xi: f"{xi}_learning_curve.png", estimators_names))
pca_kernels_list = ['linear', 'poly', 'rbf', 'cosine', 'sigmoid']
cv_list = list(range(10, 1, -1))
```

```python
# Make distinction between Target Variable and Predictors
# --------------------------------------------------------------------------- #
rescaledX, y, columns = prepare_data_for_train(dataset, target_col=TARGET_COL)
```

    Summary about Target Variable {target_col}
    --------------------------------------------------
    2    57
    1    13
    Name: T-OR-D, dtype: int64
    shape features matrix X, after normalizing:  (70, 11)

```python
# sns.pairplot(dataset, hue=TARGET_COL, height=2.5);
```

```python
df = show_table_pc_analysis(X=rescaledX); df.to_csv('cumulative_varation_explained.csv') # df
```

    Cumulative varation explained(percentage) up to given number of pcs:

#### Major Pros & Cons of PCA

```python
try_comparing_various_online_solvers_by_kernel(X=rescaledX, y=y, kernels=None, heldout=[.75, .7, .65, .6, .55, .5, .45, .4, .35, .33, .3, .25, .2], rounds=20, n_classes=-1, n_components=9, estimators=estimators_list, cv=StratifiedKFold(2), verbose=0, show_fig=True, save_fig=False, stratified_flag=True, n_splits=3, random_state=42, gridshape=(3, 2), figsize=(15, 15), title="try comparing various online solvers by kernel", fig_name="try_comparing_various_online_solvers_by_kernel.png")
```

```python
X = copy.deepcopy(rescaledX); n_components=9
learning_curves_by_kernels(estimators_list[:], estimators_names[:], X, y, train_sizes=np.linspace(.1, 1.0, 10), n_components=n_components, pca_kernels_list=pca_kernels_list[0], verbose=0, by_pairs=True, savefigs=True, figs_dest=os.path.join('figures', 'learning_curve', f"Pcs_{n_components}"))
```

What we can learn observig or looking at the bounch of graphics we have displayed just above is that not all of the different machine learning techiniques applied on dataset pre-processed by means of a precise kernel trick adopted for KernelPca show the same behavior, and even some plots suggest us that a machine learning algorithm may seem not suitable for such a small dataset for determining the well-known trend of a learning curve while exploiting a learning technique. To be more precise, we are going to speak roughly and briefly a little bit about each graphics, in the order as they have been shown here before, recalling that we have adopted up to 9 Principal components that account for nearly 95% of comulative explained variance related to the dataset at hand:

- The first general observation or thought that we can explain is that for all the pictures the initial gap between the two curves is most of the time wide, large, in other words, important, more or less about 20% in percentage.

- Speaking about both learning curve obtained from applying Gaussian Niave Bayes, before, and K-Nearest Neighbor, after, what we can say is that the curve related to Training Score is decreasing more or less of 10 point percentage, reaching 90% from the top, while the Cross-alidation curve seems to be constant for the first 6 trials and then to improve a little bit, but for the former model we can see increasing and descreasing that do not allow to fix a trend for measuring a gap between the two curves, instead for the latter model seems that the two curve converge at a certain point once the traingin set is more or less the overall dataset, which means we are going to exploit the most of information.

- Instead, if we focus on Logistic Regression technique and SGD Classifier, we have observed that before a given numebr of trials in both picitures the two curves, that are Trainign and Cross-validation score curves, seem to follow an independent trend increasing and descrising independently, but then the two curves start to follow a decreasing trend so we can hipotize that after that precise trials we may have reached the desired training size.

- The learning curve associated with SVC classifier suggests us that it seems to behave looking at both Traingin and Cross-Validation curve more or less as the previous graphics, but while the Training curve as decreases loosing accuracy score value such a curve tightenes the varaince, instead the Cross-validation curve keeps a wide varaince stretching it while increasing the training set size, meaaning that the models seem to be more unsure about the results.

- The last two graphics are the most problematics and questionable ones, since while enalarging the training size we do not see either improvements or worsening referring to the Training curve, while the Cross-lvalidation curve seems at the very beginning to show a samew behavior as the Training curve, but then after a sequence of trials with increasing training size we get an improvement in terms of accuracy scores. But, since we normally expect to observe a learning curve where Training curve as well as Cross-validation curve respectively decreases and increases their performance at least at a given point where we hope the gap existing between them will be as small as possible wecan think that such those two graphics are nto explaining directly the real issues we face while dealing with Decsion Tree and Radom Forest Classifiers with such a small dataset.

### References
- Metrics and Diagnositc Tools:
    - (__Confusion Matrix__) https://en.wikipedia.org/wiki/Confusion_matrix
    - (__F-1-Sccore, Accuracy, and Precision-Recall__) https://towardsdatascience.com/beyond-accuracy-precision-and-recall-3da06bea9f6c
    - (__Roc Curve__) https://en.wikipedia.org/wiki/Receiver_operating_characteristic
- Statistics:
    - (__Correlation and dependence__) https://en.wikipedia.org/wiki/Correlation_and_dependence

pittsburgh-bridges-data-set-analysis/models-analyses/merge_analyses/Section 1.2 - Learning Process and Pca Analysis.ipynb

pittsburgh-bridges-data-set-analysis/models-analyses/merge_analyses/.ipynb_checkpoints/Section 1.2 - Learning Process and Pca Analysis-checkpoint.ipynb


```python
# 그래프, 수학 기능 추가
# Add graph and math features
import pylab as py
import numpy as np
import numpy.linalg as nl
# 기호 연산 기능 추가
# Add symbolic operation capability
import sympy as sy

```

```python
# 3차원 그래픽
# 3D Graphics
from mpl_toolkits.mplot3d import Axes3D

```

# 3차원 공간의 직선과 평면
Lines and Planes of 3D Spaces

## 3차원 공간의 직선
Lines of 3D Spaces

3차원 공간의 직선을 벡터로 표시해 보자.
Let's describe a line on a 3D space using vectors.

$$
    \mathbf{x}=\mathbf{x_0}+t \cdot \mathbf{d}
$$

여기서 $\mathbf{x_0}$는 직선 위의 점, $\mathbf{d}$는 직선의 방향, $t$는 $-\infty$ 와 $\infty$ 사이에서 변화하는 매개변수이다.
Here, $\mathbf{x_0}$ is a point on the line, $\mathbf{d}$ is the direction of the line, and $t$ is a parameter that changes between $-\infty$ and $\infty$.

예를 들어 $\mathbf{x}_0=(-1, 1, 1)^T$ 을 지나고 방향은 $\mathbf{d}=(1, 1, 1)^T$ 인 직선을 그려보자.

For example, let's plot a line passing $\mathbf{x}_0=(-1, 1, 1)^T$ with direction of $\mathbf{d}=(1, 1, 1)^T$.

```python
x_0 = np.matrix([[-1, 1, 1]]).T
d = np.matrix([[1, 1, 1]]).T
t = np.matrix([np.arange(-10, 10)])
x = (x_0 + d * t).tolist()

fig = py.figure(figsize=(10, 10))
ax = fig.add_subplot(111, projection='3d')

ax.plot(x[0], x[1], x[2], label='line')
ax.plot((0, x_0[0, 0]), (0, x_0[1, 0]), (0, x_0[2, 0]), label='$\\mathbf{x_0}$')
ax.plot((x_0[0, 0], x_0[0, 0]+d[0, 0]),
        (x_0[1, 0], x_0[1, 0]+d[1, 0]),
        (x_0[2, 0], x_0[2, 0]+d[2, 0]),
        label='$\\mathbf{d}$', linewidth=3)

py.legend(loc=0)

```

## 3차원 공간의 평면
Planes of 3D Spaces

(Weisstein, Eric W. "Plane." From MathWorld--A Wolfram Web Resource. http://mathworld.wolfram.com/Plane.html)

3차원 공간의 평면은 다음 조건을 만족하는 점 $\mathbf{x}$의 집합이다.
A 3D plane is a set of points $\mathbf{x}$ satisfying the following condition.

$$
    \mathbf{n} \cdot \left(\mathbf{x} - \mathbf{x}_0 \right) = 0
$$

여기서 $\mathbf{x}_0$은 평면 위의 한 점이고 $\mathbf{n}$은 평면에 수직인 벡터이다.

Here, $\mathbf{x}_0$ is a point on the plane and $\mathbf{n}$ is a vector normal to the plane.

위 내적 값을 시각화 해보자.
Let's visualize the inner product values.

```python
# Normal Direction
n = py.matrix([[2, 1, 0.5]]).T
# A point on the plane
x_0 = py.matrix([[1, -2, 0.5]]).T

# Set x y z ranges
x_array = py.linspace(-5, 5, 10+1)
y_array = py.linspace(-5, 5, 10+1)
z_array = py.linspace(-5, 5, 10+1)

# Combinations of x y z coordinates
x_mesh, y_mesh, z_mesh = py.meshgrid(x_array, y_array, z_array)

x_flat = x_mesh.flatten()
y_flat = y_mesh.flatten()
z_flat = z_mesh.flatten()

# (x - x_0)
x_minus_x0_x_flat = x_flat - x_0[0, 0]
x_minus_x0_y_flat = y_flat - x_0[1, 0]
x_minus_x0_z_flat = z_flat - x_0[2, 0]

# Prepare for the dot product
x_minus_x0_array = py.array(
    [
        x_minus_x0_x_flat,
        x_minus_x0_y_flat,
        x_minus_x0_z_flat,
    ]
)

# n dot (x - x_0)
dot_product = py.array(py.dot(n.T, x_minus_x0_array)).flatten()

# Prepare for the 3D axis
fig = py.figure(figsize=(10, 10))
ax = fig.add_subplot(111, projection='3d')

# Scatter plot
# https://matplotlib.org/gallery/mplot3d/scatter3d.html
# http://jakevdp.github.io/mpl_tutorial/tutorial_pages/tut5.html
scatter = ax.scatter(x_flat,
                     y_flat,
                     z_flat, c=dot_product, cmap='jet')

# Indicate x_0
ax.plot((0, x_0[0, 0]), (0, x_0[1, 0]), (0, x_0[2, 0]), label='$\\mathbf{x_0}$')
# Indicate n
ax.plot((x_0[0, 0], x_0[0, 0]+n[0, 0]),
        (x_0[1, 0], x_0[1, 0]+n[1, 0]),
        (x_0[2, 0], x_0[2, 0]+n[2, 0]),
        label='$\\mathbf{n}$', linewidth=3)

# Legend
py.legend(loc=0)

# Which color of point represent which value?
py.colorbar(scatter)

# Title of the plot
py.title(r'$\mathbf{n} \cdot \left(\mathbf{x} - \mathbf{x}_0 \right)$')

```

내적 값 0인 점의 집합이 해당 평면이다.
The set of points with the inner product value 0 is the plane.

## Final Bell
마지막 종

```python
# stackoverfow.com/a/24634221
import os
os.system("printf '\a'");

```

```python

```


# XIA

## Preamplifier: charge to voltage

The electric charge generated in a solid state detector by an incident photon, is integrated by a reset-type preamplifier to produce an output voltage in which the X-ray events are visible as steps. A capacitor discharged is frequently needed and is visible as a jump.

```python
import numpy as np
import matplotlib.pyplot as plt
from spectrocrunch.utils import units
```

Function that converts X-ray energy to a preamplifier voltage step:

```python
def fanonoise(n,factor):
    # TODO: use proper pdf
    if n<=0:
        return 0
    else:
        m = np.random.poisson(lam=n)
        return n + (m-n)*factor

def energytovoltage(energies,fanofactor=0.115):
    energies = units.Quantity(energies,'keV')
    ehole = units.Quantity(3.8,'eV')
    e = units.Quantity(1,'e')
    ne = (energies/ehole).to('dimensionless').magnitude
    ne = np.asarray(ne)
    # Fano noise
    if fanofactor:
        try:
            ne = units.Quantity([fanonoise(n,fanofactor) for n in ne],'dimensionless')
        except TypeError:
            ne = units.Quantity(fanonoise(ne,fanofactor),'dimensionless')
    cf = units.Quantity(0.002,'pF')
    return (ne*e/cf).to('V')

def voltagetoenergy(voltage):
    voltage = units.Quantity(voltage,'V')
    ehole = units.Quantity(3.8,'eV')
    cf = units.Quantity(0.002,'pF')
    e = units.Quantity(1,'e')
    energies = voltage*(cf/e*ehole)
    return energies.to('keV')
```

Function to simulate preamplifier events based on a Poissonian X-ray source with a particular energy spectrum defined by an expected energy and count rate (assume lines have a Lorentzian energy distribution):

```python
def xrays_sample(avgenergies,avgrates,avgtime):
    tunit = 's'
    eunit = 'keV'
    Vunit = 'V'
    hunit = 'Hz'

    avgrates = units.Quantity(avgrates,hunit)
    avgtime = units.Quantity(avgtime,tunit)
    events = avgtime*avgrates
    events = events.to('dimensionless').magnitude.astype(int)

    # Natural line width (in principle energy dependent)
    avgenergies = units.Quantity(avgenergies,eunit)
    energystd = units.Quantity(4,'eV')

    emag = lambda x: x.to(eunit).magnitude
    hmag = lambda x: x.to(hunit).magnitude
    vmag = lambda x: x.to(Vunit).magnitude
    ev_time = []
    ev_step = []
    for avgrate,avgenergy,n in zip(avgrates,avgenergies,events):
        # Poisson source
        time_periods = np.random.exponential(scale=1.0/hmag(avgrate), size=n)
        ev_time += time_periods.cumsum().tolist()
        # Lorentzian spectral lines
        ev_energy = emag(avgenergy) + np.random.standard_cauchy(size=n)*emag(energystd)
        # Energy to voltage (fano noise)
        ev_volt = energytovoltage(units.Quantity(ev_energy,eunit))
        ev_step += ev_volt.tolist()

    ev_time, ev_step = zip(*sorted(zip(ev_time, ev_step)))
    ev_time = units.Quantity(ev_time,'1/'+hunit).to(tunit)
    ev_step = units.Quantity(ev_step,Vunit)

    return ev_time,ev_step
```

Function to simulate a preamplifier output:

```python
def preamp_sample(ev_time,ev_step,samplefreq):
    samplefreq = units.Quantity(samplefreq,'Hz')
    samplebin = 1./samplefreq
    n = int(np.ceil(((max(ev_time)/samplebin).to('dimensionless').magnitude)))+1
    time = samplebin * np.arange(n)

    # Gaussian electronic noise
    estd = units.Quantity(120/(2*np.sqrt(2*np.log(2))),'eV')
    ampstd = energytovoltage(estd,fanofactor=0)
    ampstd = units.Quantity(np.random.normal(scale=ampstd.magnitude,size=n),ampstd.units)

    # Preamplifier resets
    ampmax = units.Quantity(3,'V')
    steps = ev_step.cumsum()
    nresets = np.ceil((steps/ampmax).to('dimensionless').magnitude)
    steps -= (nresets.astype(int)-1)*ampmax

    # Sample step function
    def pointwise(x):
        ind = np.where(x>=ev_time)[0]
        if ind.size==0:
            return units.quantity_like(0,steps)
        else:
            return steps[ind[-1]]
    ampout = units.asqarray(list(map(pointwise,time)))

    return time,ampout,ampstd
```

Simulate preamplifier output:

```python
avgenergies = units.Quantity([4.,7],'keV')
avgrates = units.Quantity([1.,2],'MHz')
ev_time,ev_step = xrays_sample(avgenergies,avgrates,units.Quantity(300,'us'))
time,ampout,ampstd = preamp_sample(ev_time,ev_step,units.Quantity(50,'MHz'))
signal = ampout+ampstd
```

Plot preamplifier output histogram (without pulse processing, so without electronic noise):

```python
from spectrocrunch.math.distributions import plothistogram
from silx.math.fit.functions import sum_gauss
from silx.math.fit import fittheories
from silx.math.fit.fitmanager import FitManager

#samples = ev_step.to('mV')
samples = voltagetoenergy(ev_step)
y,edges = np.histogram(samples.magnitude,bins=500)

x = plothistogram(y,edges=edges)
binsize = np.median(np.diff(x))

ax = plt.gca()
xlabel = ax.set_xlabel('Energy ({:~})'.format(samples.units))
ylabel = ax.set_ylabel('Counts')

try:
    samples.to('keV')
    bfit = True
except:
    bfit = False

if bfit:
    fit = FitManager()
    fit.setdata(x=x, y=y)
    fit.loadtheories(fittheories)
    fit.settheory('Gaussians')
    fit.setbackground('No Background')
    fit.estimate()
    fit.runfit()
    yfit = fit.gendata()
    lines = plt.plot(x,yfit,color='r')
    #ax = lines[0].axes
    #lim = avgenergies.to(samples.units).magnitude
    #d = lim[-1]-lim[0]
    #ax.set_xlim(lim[0]-0.8*d,lim[-1]+1.2*d)

    params = np.array(fit.get_fitted_parameters())
    params = params.reshape((params.size//3,3))
    height,position,FWHM = params.T
    position = units.quantity_like(position,samples)
    area = height*FWHM*np.sqrt(np.pi/(4*np.log(2)))/binsize
    FWHM = units.quantity_like(FWHM,samples)
    sigma = FWHM/(2*np.sqrt(2*np.log(2)))
    rate = area/max(time)
    print('Expected:')
    for e,r in zip(avgenergies.to('keV'),avgrates.to('MHz')):
        print('{:~}: {:~}'.format(e,r))
    print('Fitted:')
    for e,r,s in zip(position.to('keV'),rate.to('MHz'),sigma.to('eV')):
        print('{:~} (sigma={:~}): {:~}'.format(e,s,r))
```

Plot preamplifier output (signal used for pulse processing):

```python
time = time.to('us')
signal = signal.to('V')
ampout = ampout.to('V')

fig,axs = plt.subplots(1,2,figsize=(10,6))

def xlim(ax,xmin,xmax):
    xmin,xmax = units.quantity_like(xmin,time),units.quantity_like(xmax,time)
    tmp = ax.set_xlim(xmin.magnitude,xmax.magnitude)
    visible_y = signal[(xmin < time) & (time < xmax)]
    if len(visible_y):
        tmp = ax.set_ylim(np.min(visible_y).magnitude,
                          np.max(visible_y).magnitude)

def preamp_plot(o,x,y,marker=None,label=None):
    lines = o.plot(x,y,marker=marker,label=label)
    ax = lines[0].axes
    xlabel = ax.set_xlabel('Time ({:~})'.format(x.units))
    ylabel = ax.set_ylabel('Preamp Output ({:~})'.format(y.units))
    return ax

for i in range(2):
    ax = axs[i]
    tmp = preamp_plot(ax,time,signal,label='signal')
    ax.plot(time,ampout,label='expected')
    ax.legend()

xlim(axs[0],0,30)
xlim(axs[1],8,10)
```

## Digital pulse processor
When detecting events from a random process, a pulse is generated for each event which has a finite time width $\tau$. Either pulses are rejected during this time period (non-extendable dead time) or overlapping pulses are counted as
one pulse (extendable dead time).

### Extendable dead time
Pulses which overlap (in the fast or slow channel) are counted as one. The measured count rate is related to the real count rate as
$$
r_{\text{meas}} = re^{-r\tau}
$$
where $\tau$ the dead time per event.

### Dead time fraction
The dead time fraction refers to the fraction of lost events during measurement
$$
\text{DT} = \frac{n-n_{\text{meas}}}{n}
$$

### Live time
The live time of the measurement is defined as the fraction of the measurement time $t$ in which events are detected
$$
t_{\text{live}} = t(1-\text{DT})
$$
The total dead time would be $t-t_{\text{live}}=t\text{DT}$.

### Other relationships
$$
\begin{align}
\text{DT} &= \frac{r-r_{\text{meas}}}{r} = \frac{t-t_{\text{live}}}{t}\\
1-\text{DT} & = \frac{r_{meas}}{r} = \frac{t_{live}}{t}\\
n_{\text{meas}}&=n(1-\text{DT})\\
r_{\text{meas}}&=r(1-\text{DT})\\
t_{\text{live}}&=te^{-r\tau}\\
\tau&=-\frac{\ln{(1-\text{DT})}}{r}\\
\text{DT}&=1-e^{-r\tau}
\end{align}
$$

### Dual channel
The preamplifier output is filtered by a Trapezoidal filter. This is done twice: fast (X-ray detection, small filter width), slow (energy filter, large filter width). The filter width is given by $2L+G$ where $G$ the gap time and $L$ the peaking time. Peaks in the fast channel are counted to obtain the real count rate (after dividing by the fast channel live time). Peaks in the slow channel are used to determine the X-ray energies. Peaks in the slow channel that appear as multiple peaks in the fast channel are rejected (pile-up rejection). Peak overlap in the fast channel cannot be detected and results in pile-up peaks in the XRF spectrum.

### XIA statistics
The XIA records these statistics for each detector:

 * $\text{NUM}$: detector number
 * $\text{EV} = n_{\text{slow}}$: number slow channel events (after pile-up rejection)
 * $\text{ICR} = r = \frac{n_{\text{fast}}}{t_{\text{fast}}}$: real count rate in hertz
 * $\text{OCR} = r_{\text{slow}} = \frac{n_{\text{slow}}}{t}$: slow channel count rate in hertz
 * $\text{LT} = t_{\text{fast}}$: fast channel live time in milliseconds
 * $\text{DT}$: dead time in percentage

Based on these statistics, the real counts can be derived from the measured counts in the slow channel
$$
n=n_{\text{slow}}\frac{\text{ICR}}{\text{OCR}}
$$

The live time of the slow channel is
$$
t_{\text{slow}} = t\frac{\text{OCR}}{\text{ICR}}
$$

```python
time = time.to('us')
signal = signal.to('V')
ampout = ampout.to('V')

def trapfilter(G,L):
    dt = time[1]-time[0]
    L = max(int(np.ceil(L/dt)),1)
    G = max(int(np.ceil(G/dt)),0)
    print('Filter width: {} samples, {:~}'.format((2*L+G),(2*L+G)*dt))
    return np.concatenate([np.ones(L)/float(L),np.zeros(G),-np.ones(L)/float(L)])

G = units.Quantity(0,'ns')
L = units.Quantity(5,'ns')
wfast = trapfilter(G,L)

G = units.Quantity(0.05,'us')
L = units.Quantity(0.1,'us')
wslow = trapfilter(G,L)

sfast = np.convolve(signal.magnitude,wfast,'same')
sslow = np.convolve(signal.magnitude,wslow,'same')
sfast = units.Quantity(sfast,signal.units)
sslow = units.Quantity(sslow,signal.units)

ax = preamp_plot(plt,time,signal,label='signal')
ax = preamp_plot(plt,time,ampout,label='expected')
xlim(ax,5,8)
ylim = ax.get_ylim()
add = units.quantity_like(ylim[0]+(ylim[1]-ylim[0])*0.1,sfast)
sfast += add
sslow += add
ax = preamp_plot(ax,time,sfast,label='fast',marker='+')
ax = preamp_plot(ax,time,sslow,label='slow',marker='+')
tmp = ax.legend()
```

## XIA EDF output

```python
import matplotlib.pyplot as plt
import numpy as np
from spectrocrunch.io import xiaedf

path = '/data/id21/store/backup_visitor/2018/ev280/id21/fe2streptor2/fe2streptor2_main_root/zap'
radix = 'fe2streptor2_main_root'
num = 1

xiaimage = xiaedf.xiaimage_number(path,radix,num)
xiaimage.dtcor(False)
xiaimage.detectorsum(False)
xiaimage.counter_reldir('.')
```

### DXP statistics

```python
from spectrocrunch.math.distributions import plothistogram
import scipy.optimize

xiaimage.onlyicrocr(False)

stats = xiaimage.stats.astype(float)
Nslow = stats[...,xiaimage.STEVT,0]
Rreal = stats[...,xiaimage.STICR,0]
Rslow = stats[...,xiaimage.STOCR,0]
LTfast = stats[...,xiaimage.STLT,0]/1000.
DTslow = stats[...,xiaimage.STDT,0]/100.

# Dead time fraction
maskDT100 = Rreal==0
with np.errstate(divide='ignore', invalid='ignore'):
    RT = Nslow/Rslow
    DT = (Rreal-Rslow)/Rreal
    LTslow = RT*Rslow/Rreal
DT[maskDT100] = 1
RT[maskDT100] = 0.1
LTslow[maskDT100] = 0
DTfast = 1-LTfast/RT
Nfast = Rreal*LTfast
Rfast = Nfast/RT

np.testing.assert_array_almost_equal(DTslow, DT, decimal=2)

# Dead time per event
with np.errstate(divide='ignore', invalid='ignore'):
    tauslow = -np.log(LTslow/RT)/Rreal
    taufast = -np.log(LTfast/RT)/Rreal
mask = DT<1
tauslow[~mask] = tauslow[mask].max()
mask = np.isfinite(taufast)
taufast[~mask] = taufast[mask].max()

tmp = plothistogram(*np.histogram(DTfast,bins=100),alpha=0.7,label='DTfast')
tmp = plothistogram(*np.histogram(DTslow,bins=100),alpha=0.7,label='DTslow')
ax = plt.gca()
tmp = ax.set_xlabel('Dead time fraction')
tmp = ax.set_ylabel('Number of pixels')
tmp = ax.legend()
#tmp = ax.set_yscale('log')

plt.figure()
tmp = plothistogram(*np.histogram(LTfast,bins=100),alpha=0.7,label='LTfast')
tmp = plothistogram(*np.histogram(LTslow,bins=100),alpha=0.7,label='LTslow')
ax = plt.gca()
tmp = ax.set_xlabel('Time (msec)')
tmp = ax.set_ylabel('Number of pixels')
tmp = ax.legend()

plt.figure()
mask = DTslow < 0.2
tmp = plothistogram(*np.histogram(tauslow[mask]*1e6,bins=100),alpha=0.7,label='Tau slow (DT<20%)')
tmp = plothistogram(*np.histogram(tauslow[~mask]*1e6,bins=100),alpha=0.7,label='Tau slow (DT>=20%)')
tmp = plothistogram(*np.histogram(taufast*1e6,bins=100),label='Tau fast')
ax = plt.gca()
tmp = ax.set_xlabel('Tau (usec)')
tmp = ax.set_ylabel('Number of pixels')
tmp = ax.legend()

plt.figure()
tmp = plt.plot([Rreal.min(), Rreal.max()], [Rreal.min(), Rreal.max()], label='Rreal')
mask = DTslow < 0.8
tmp = plt.plot(Rreal[mask].flatten(), Rreal[mask].flatten(), '.', label='Rreal (DT<80%)')
tmp = plt.plot(Rreal[~mask].flatten(), Rreal[~mask].flatten(), '.', label='Rreal (DT>=80%)')
ax = plt.gca()
tmp = ax.set_xlabel('Real count rate (Hz)')
tmp = ax.set_ylabel('Channel count rate (Hz)')
tmp = ax.legend()

plt.figure()
tmp = plt.plot(Rreal[mask].flatten(), Rfast[mask].flatten(), '.', label='Rfast (DT<80%)')
tmp = plt.plot(Rreal[~mask].flatten(), Rfast[~mask].flatten(), '.', label='Rfast (DT>=80%)')
x = np.linspace(Rreal.min(), Rreal.max(), 100)
y = x*np.exp(-x*taufast.min())
tmp = plt.plot(x, y, label='Rfast calc')
y = x*np.exp(-x*taufast.max())
tmp = plt.plot(x, y, label='Rfast calc')
ax = plt.gca()
tmp = ax.set_xlabel('Real count rate (Hz)')
tmp = ax.set_ylabel('Channel count rate (Hz)')
tmp = ax.legend()

plt.figure()
tmp = plt.plot(Rreal[mask].flatten(), Rslow[mask].flatten(), '.', label='Rslow (DT<80%)')
tmp = plt.plot(Rreal[~mask].flatten(), Rslow[~mask].flatten(), '.', label='Rslow (DT>=80%)')
x = np.linspace(Rreal.min(), Rreal.max(), 100)
y = x*np.exp(-x*tauslow.min())
tmp = plt.plot(x, y, label='Rslow calc')
y = x*np.exp(-x*tauslow.max())
tmp = plt.plot(x, y, label='Rslow calc')
ax = plt.gca()
tmp = ax.set_xlabel('Real count rate (Hz)')
tmp = ax.set_ylabel('Channel count rate (Hz)')
tmp = ax.legend()
```

### XRF spectra

```python
from spectrocrunch.io import spec
from spectrocrunch.instruments.configuration import factory
import spectrocrunch
xiaimage.dtcor(True)
xiaimage.detectorsum(True)
xiaimage.exclude_detectors = ['S0']
mca = xiaimage.data[...,0]

counters = xiaimage.counterbasenames()
h = xiaimage.header(source=counters[0])
instrument = factory('id21')

kwargs = {'energy':instrument.edfheaderkeys['energylabel'],
          'speclabel':instrument.edfheaderkeys['speclabel'],
          'compensationmotors':instrument.compensationmotors,
          'units':instrument.units}
parser = spec.edfheader_parser(**kwargs)
info = parser(h)
```

Sum all channels and detectors:

```python
mcasum = mca.sum(axis=-1)
tmp = plt.imshow(mcasum)
ax0,ax1 = info['axes']
title = '{:~}, {:~}'.format(info['energy'],info['time'])

def imshow(ax,image,ax0,ax1,rlo=0,rhi=1,aspect=None,**kwargs):
    d0 = ax0.stepsize
    d1 = ax1.stepsize
    y0 = ax0.start+d0*0.5
    y1 = ax0.end-d0*0.5
    x0 = ax1.start+d1*0.5
    x1 = ax1.end-d1*0.5
    if aspect:
        aspectratio_data = ((y1-y0)/(x1-x0)).to('dimensionless').magnitude
        aspect = aspect/np.abs(aspectratio_data)
    extent = (x0.magnitude,x1.magnitude,y0.magnitude,y1.magnitude)
    mi,ma = np.nanmin(image),np.nanmax(image)
    d = ma-mi
    mi,ma = mi+rlo*d,mi+rhi*d
    image = (image-mi)/(ma-mi)
    image = np.clip(image,0,1)
    img = ax.imshow(image, interpolation = 'nearest',
                    extent = extent, origin = 'lower',
                    aspect = aspect, **kwargs)
    ax = img.axes
    ax.set_xlabel(str(ax0))
    ax.set_ylabel(str(ax1))
    return ax

ax = imshow(plt,mcasum,ax0,ax1,rhi=0.1)
tmp = ax.set_title(title)
```

Select pixels for statistical analysis:

```python
from skimage.filters import threshold_otsu

img = mcasum.copy()
imgsel = img.copy()
mi = img.min()

if True:
    mask = (DT>0.05) & (DT<0.8)
else:
    img[DT>0.5] = mi
    thres = threshold_otsu(img)
    mask = img >= thres

imgsel[~mask] = np.nan
rgb = np.stack([img,imgsel,img*0],axis=-1)
ax = imshow(plt,rgb,ax0,ax1,rhi=0.1)
tmp = ax.set_title(title)
```

Non-Negative Matrix Factorization of selected spectra:

```python
from sklearn import decomposition
from numpy.random import RandomState
from scipy.sparse import csr_matrix
from spectrocrunch.utils.timing import timeit

nrow,ncol,nchan = mca.shape
nsamples = nrow*ncol
nfeatures = nchan

with timeit('NMF'):
    model = decomposition.NMF(n_components=3, init='random',
                              random_state=RandomState(0))
    #data = csr_matrix(mca.reshape((nsamples,nfeatures)))
    data = csr_matrix(mca[mask,...])
    coefficients = model.fit_transform(data).T
```

    Execution time (NMF): 89.7415919303894

Each XRF spectrum is a linear combination of the NMF components. Plot the coefficients (images) and the components (vectors):

```python
fig,axs = plt.subplots(1,model.n_components,figsize=(15,6))
eigenimage = np.full_like(mcasum,np.nan)
for i,coeff in enumerate(coefficients):
    ax = axs[i]
    eigenimage[mask] = coeff
    ax = imshow(ax,eigenimage,ax0,ax1,rhi=0.3)
    ax.set_title('Comp{}'.format(i+1))

plt.figure(figsize=(15,6))
mcabin = 5e-3
x = np.arange(nchan)*mcabin
for i,eigenvector in enumerate(model.components_):
    plt.plot(x,eigenvector,label='Comp{}'.format(i+1))
ax = plt.gca()
ax.set_yscale("log")
ax.set_ylim(0.001,None)
ax.set_xlabel('Energy (keV)')
tmp = ax.legend()
```