# Profils de succion et drainage latéral dans les barrières capillaires

Pour exécuter une cellule, `Ctrl + Enter`. Pour exécuter une cellule et passer à la suivante, `Maj. + Enter`. Pour exécuter toute la feuille, dans le menu `Cell`, sélectionner `Run All`.

## Charger les librairies

```python
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as plticker
from scipy.integrate import quad
from scipy.interpolate import interp1d
```

## Modèles hydrauliques

Les propriétés hydrauliques des milieux poreux non saturés peuvent être décrite par leurs propriétés de rtention d'eau et de conductivité hydraulique. Le modèle de van Genuchten (1980) sera utilisé.

### Courbe de rétention d'eau, van Genuchten (1980):
\begin{align}
\theta(\psi)  = \theta_{r} + (\theta_{s} - \theta_{r})  (1+(a_{VG}  \psi)^{n_{VG}})^{-m_{VG}} \\
\end{align}

### Fonction de conductivité hydraulique, van Genuchten et al. (1991), basé sur van Genuchten (1980) et Mualem (1976):
\begin{align}
k(\psi) = k_{sat}  \frac {(1-((a_{VG}  \psi)^{n_{VG}m_{VG}})  (1+(a_{VG} \psi)^{n_{VG}})^{-m_{VG}}))^2} { (1+(a_{VG} \psi)^{n_{VG}})^{m_{VG}l_{VG}}}
\end{align}

```python
def vanGenuchten(thR, thS, aVG, nVG, mVG, ksat, psi, lVG=0.5):
    th = thR + (thS - thR) * (1+(aVG * psi) ** nVG) ** (-mVG)
    k = ksat*((1-((aVG*psi)**(nVG*mVG))* \
        ((1+((aVG*psi)**nVG))**(-mVG)))**2) / \
        ((1+((aVG*psi)**nVG))**(mVG*lVG))
    return(pd.DataFrame({'psi':psi, 'theta':th, 'k':k}))
```

Une barrière capillaire inclut une couche de bris capillaire (capillary break layer, CBL) sur laquelle est installée une couche de rétention capillaire (moisture retaining layer, MRL). Définissons les paramètres de van Genuchten pour la CBL et la MRL.

```python
# CBL
cbl_thR = 0.017
cbl_thS = 0.37
cbl_aVG = 3.5 * 9.807 # 9.807 pour exprimer en 1/m des valeurs exprimées en 1/kPa
cbl_nVG = 3.0
cbl_mVG = 1 - 1/cbl_nVG
cbl_lVG = 0.5
cbl_ksat = 2.3e-3 # m/s

# MRL
mrl_thR = 0.1
mrl_thS = 0.4
mrl_aVG = 1.8 * 9.807 # 9.807 pour exprimer en 1/m des valeurs exprimées en 1/kPa
mrl_nVG = 1.3
mrl_mVG = 1 - 1/mrl_nVG
mrl_lVG = 0.5
mrl_ksat = 3e-4 # m/s
```

Nous aurons aussi besoin de vecteurs `psi` pour indiquer à notre fonction `vanGenuchten` sur quelles valeurs de succion (en mètres) les valeurs de teneur en eau et de conductivité hydraulique devraient être calculées.

```python
npoints = 1000
cbl_psi = np.logspace(start = -2, stop = 2, num = npoints, endpoint = True)
mrl_psi = np.logspace(start = -2, stop = 2, num = npoints, endpoint = True)
```

Entrons ces paramètres dans la fonction `vanGenuchten`.

```python
cbl_VG = vanGenuchten(thR=cbl_thR, thS=cbl_thS, aVG=cbl_aVG, nVG=cbl_nVG,
                  mVG=cbl_mVG, ksat=cbl_ksat, psi=cbl_psi, lVG=cbl_lVG)
mrl_VG = vanGenuchten(thR=mrl_thR, thS=mrl_thS, aVG=mrl_aVG, nVG=mrl_nVG,
                  mVG=mrl_mVG, ksat=mrl_ksat, psi=mrl_psi, lVG=mrl_lVG)
```

Comme on l'a demandé dans la fotion `vanGenuchten`, la sortie de la fonction est un tableau (de type `pandas` `DataFrame`). Voyons voir l'entête de `cbl_VG` par exemple.

```python
cbl_VG.head()
```

      k
      psi
      theta

      0
      0.001779
      0.010000
      0.360793

      1
      0.001770
      0.010093
      0.360543

      2
      0.001761
      0.010186
      0.360287

      3
      0.001752
      0.010280
      0.360024

      4
      0.001743
      0.010376
      0.359754

Créons un graphique des courbes de rétention d'eau ainsi que de la fonction de conductivité hydraulique.

```python
fig1, axes = plt.subplots(nrows=2, ncols=1, figsize=(6, 12))

# Courbe de rétention d'eau
## Décorations
axes[0].set_ylabel(r'$\theta (m^3/m^3)$')
axes[0].set_xscale('log')
axes[0].set_xticks([0.1, 1, 10, 100, 1000])
## Graphique
axes[0].plot(cbl_VG.psi * 9.807, cbl_VG.theta, linewidth=2, label="CBL material")
axes[0].plot(mrl_VG.psi * 9.807, mrl_VG.theta, label="MRL material")
axes[0].legend()

# Fonction de conductivité hydraulique
## Décorations
axes[1].set_xlabel(r'$\psi (kPa)$')
axes[1].set_ylabel(r'$k (m/s)$')
axes[1].set_xscale('log')
axes[1].set_xticks([0.1, 1, 10, 100, 1000])
axes[1].set_yscale('log')
axes[1].set_ylim([1e-16, 1e-2])
## Graphique
axes[1].plot(cbl_VG.psi * 9.807, cbl_VG.k, linewidth=2, label="CBL material")
axes[1].plot(mrl_VG.psi * 9.807, mrl_VG.k, label="MRL material")
```

## Profil de succion dans une colonne de matériau poreux soumis à un débit unitaire

Une démonstration similaire a d'abord été publiée par Kisch (1959).

\begin{align}
q = k(\psi) \frac{dh}{dz}
\end{align}

\begin{align}
h = z + p = z - \psi
\end{align}

\begin{align}
q = k(\psi) \frac{dz - d\psi}{dz}
\end{align}

\begin{align}
q = k(\psi) (1 - \frac{d\psi}{dz})
\end{align}

\begin{align}
\frac {q}{k(\psi)} = 1 - \frac{d\psi}{dz}
\end{align}

\begin{align}
\frac{d\psi}{dz} = 1-\frac {q}{k(\psi)}
\end{align}

\begin{align}
dz = \frac{d\psi}{1-\frac {q}{k(\psi)}}
\end{align}

\begin{align}
z(\psi) = \int_{\psi_{min}}^{\psi} \frac{1}{1-\frac {q}{k(\psi)}}d\psi
\end{align}

L'intégrale peut être approximée par:
\begin{align}
z(\psi) = \sum_{i=1}^{n} \frac{\Delta\psi}{1-\frac {q}{k_n(\psi)}}
\end{align}

La fonction `kisch` est une manière parmi d'autres d'encoder cette fonction en Python:

```python
def kisch(thR, thS, aVG, nVG, mVG, ksat, psi, q, lVG=0.5, psi_min=1e-3, z_min=0):
    model_init = vanGenuchten(thR=thR, thS=thS, aVG=aVG, nVG=nVG, mVG=mVG, ksat=ksat, lVG=lVG,
                    psi=psi)
    interp_func = interp1d(np.log10(model_init.k), np.log10(model_init.psi))
    psi_q = 10**interp_func(np.log10(q))
    model_kisch = vanGenuchten(thR=thR, thS=thS, aVG=aVG, nVG=nVG, mVG=mVG, ksat=ksat, lVG=lVG,
                           psi=np.logspace(start = np.log10(psi_min), stop = np.log10(psi_q),
                                    num = model_init.shape[0]))
    delta_psi_kisch = model_kisch.psi.diff().shift(-1)
    z = (delta_psi_kisch / (1 - q/model_kisch.k)).cumsum() + z_min
    return(pd.DataFrame({'psi': model_kisch.psi, 'z':z}))
```

Par exemple, prenons un débit unitaire traversant une colonne du sol de la CBL ayant une succion de 0 kPa à sa base

```python
unit_flow=1e-8
cbl_kisch = kisch(thR=cbl_thR, thS=cbl_thS, aVG=cbl_aVG, nVG=cbl_nVG,
                  mVG=cbl_mVG, ksat=cbl_ksat, psi=cbl_psi,
                  q=unit_flow, lVG=cbl_lVG, z_min=0)
cbl_kisch.head()
```

      psi
      z

      0
      0.001000
      0.000005

      1
      0.001005
      0.000010

      2
      0.001010
      0.000015

      3
      0.001015
      0.000020

      4
      0.001020
      0.000025

```python
plt.plot(cbl_kisch.psi, cbl_kisch.z, '-')
plt.xlabel(r'$\psi (m)$')
plt.ylabel('Élévation (m)')
```

Le graphique montre que la succion augmente linéairement avec l'élévation, puis s'incurve pour converger vers une valeur d'environ 0.15 m.

```python
cbl_kisch.psi.max()
```

    0.1510629552887498

```python
plt.plot(cbl_VG.psi, cbl_VG.k)
plt.xscale('log')
plt.yscale('log')
plt.xlim([1e-2, 1])
plt.ylim([1e-12, 1e-2])
plt.xlabel(r'$\psi (m)$')
plt.ylabel('k (m/s)')
plt.axhline(unit_flow, ls=':')
plt.axvline(cbl_kisch.psi.max(), ls=':')
```

Il est possible de superposer une MRL en spécifiant son élévation et la valeur de succion à sa base (égale à la valeur maximale de succion de la CBL).

```python
cbl_thickness = 0.3
mrl_kisch = kisch(thR=mrl_thR, thS=mrl_thS, aVG=mrl_aVG, nVG=mrl_nVG,
                  mVG=mrl_mVG, ksat=mrl_ksat, psi=mrl_psi,
                  q=unit_flow, lVG=mrl_lVG, psi_min=cbl_kisch.psi.max(), z_min=cbl_thickness)
```

### Graphiques du profil de succion dans une barrière capillaire

```python
cbl_kisch_VG = vanGenuchten(thR=cbl_thR, thS=cbl_thS, aVG=cbl_aVG, nVG=cbl_nVG,
                  mVG=cbl_mVG, ksat=cbl_ksat, psi=cbl_kisch.psi, lVG=cbl_lVG)
cbl_kisch_VG['z'] = cbl_kisch.z
mrl_kisch_VG = vanGenuchten(thR=mrl_thR, thS=mrl_thS, aVG=mrl_aVG, nVG=mrl_nVG,
                  mVG=mrl_mVG, ksat=mrl_ksat, psi=mrl_kisch.psi, lVG=mrl_lVG)
mrl_kisch_VG['z'] = mrl_kisch.z
mrl_kisch_VG.head()
```

      k
      psi
      theta
      z

      0
      7.690200e-07
      0.151063
      0.311172
      0.300259

      1
      7.659661e-07
      0.151318
      0.311089
      0.300518

      2
      7.629234e-07
      0.151574
      0.311005
      0.300777

      3
      7.598920e-07
      0.151830
      0.310921
      0.301037

      4
      7.568717e-07
      0.152086
      0.310838
      0.301297

Pour s'assurer une continuité entre la CBL et la MRL, ajoutons des points à l'interface.

```python
interface_VG = pd.DataFrame({'k':cbl_kisch_VG.k.min(),
                            'psi': cbl_kisch_VG.psi.max(),
                            'theta': cbl_kisch_VG.theta.min(),
                            'z': cbl_thickness},
                           index=['interface'])
```

```python
kisch_VG = pd.concat([cbl_kisch_VG, interface_VG, mrl_kisch_VG]).dropna(axis=0)
```

Posons une épaisseur de MRL pour la limite supérieure du grahique.

```python
mrl_thickness = 0.8
kisch_VG = kisch_VG.loc[kisch_VG.z <= (cbl_thickness + mrl_thickness), :]
```

```python
fig2, axes = plt.subplots(nrows=1, ncols=3, figsize=(16,6))

# Suction profile
axes[0].set_xlabel(r'$\psi (kPa)$')
axes[0].set_ylabel(r'$Elevation (m)$')
axes[0].axhline(cbl_thickness, linestyle=':')
axes[0].plot(kisch_VG.psi * 9.807, kisch_VG.z, linestyle='-')

# WC profile
axes[1].set_xlabel(r'$\theta (kPa)$')
axes[1].yaxis.set_visible(False)
axes[1].axhline(cbl_thickness, ls=':')
axes[1].plot(kisch_VG.theta, kisch_VG.z, linestyle='-')

# k profile
axes[2].set_xlabel(r'$k (m/s)$')
axes[2].yaxis.set_visible(False)
axes[2].set_xscale('log')
axes[2].axhline(cbl_thickness, linestyle=':')
axes[2].plot(kisch_VG.k, kisch_VG.z, linestyle='-')
```

## Drainage latéral dans une barrière capillaire inclinée

Ross (1990) décrit le modèle suivant.

\begin{align}
Q_{max} = \tan(\phi) \int_{\psi_{CBC}}^{\psi_{CRC}} k(\psi) d\psi \\
L = \frac {Q_{max}}{q}
\end{align}

```python
def k_vanGenuchten(x, aVG, nVG, mVG, ksat, lVG = 0.5):
        k = ksat * ((1 - ((aVG * x)**(nVG * mVG)) * \
        ((1 + ((aVG * x)**nVG))**(-mVG)))**2) / \
        ((1 + ((aVG * x)**nVG))**(mVG * lVG))
        return(k)
```

### Capacité et longueur de transfert

```python
pente = 0.25

Qmax = pente * quad(k_vanGenuchten, # fonction
                    cbl_kisch.psi.max(), mrl_kisch.psi.max(), # bornes
                    args=(mrl_aVG, mrl_nVG, mrl_mVG, mrl_ksat, mrl_lVG))[0] # arguments de la fonction
L = Qmax / unit_flow

print ('Le débit de transfert maximal dans la barrière capillaire est de', Qmax, 'm²/s.')
print ('La longueur de transfert maximale dans la barrière capillaire est de', L, 'm.')
```

    Le débit de transfert maximal dans la barrière capillaire est de 1.7927827356255177e-08 m²/s.
    La longueur de transfert maximale dans la barrière capillaire est de 1.7927827356255177 m.

### Épaisseur minimale de la CBL

```python
cbl_kisch.psi.max()
```

    0.1510629552887498

### Épaisseur de la couche de rétention capillaire (CRC ou MRL)

```python
mrl_psi = np.linspace(start = cbl_kisch.psi.max(), stop = mrl_kisch.psi.max(), num = 20, endpoint = True)
mrl_thickness = mrl_psi - cbl_kisch.psi.max()
Qmax_thickness = np.array([])
for i in range(0, len(mrl_psi)):
    Qmax_thickness = np.append(Qmax_thickness, pente * quad(k_vanGenuchten, cbl_kisch.psi.max(), mrl_psi[i], args=(mrl_aVG, mrl_nVG, mrl_mVG, mrl_ksat, mrl_lVG))[0])

L_thickness = Qmax_thickness / unit_flow
Qmax_thickness
```

    array([  0.00000000e+00,   5.25578662e-09,   8.56432869e-09,
             1.07881198e-08,   1.23584607e-08,   1.35109193e-08,
             1.43832318e-08,   1.50604249e-08,   1.55973636e-08,
             1.60307780e-08,   1.63860351e-08,   1.66811266e-08,
             1.69291111e-08,   1.71396597e-08,   1.73200628e-08,
             1.74759044e-08,   1.76115215e-08,   1.77303262e-08,
             1.78350337e-08,   1.79278274e-08])

```python
plt.axhline(L, linestyle=':')
plt.plot(mrl_thickness, L_thickness)
plt.xlabel('MRL thickness (m)')
plt.ylabel('Diversion length (m)')
```


# NRPy+'s Reference Metric Interface

## Author: Zach Etienne
### Formatting improvements courtesy Brandon Clark

### NRPy+ Source Code for this module: [reference_metric.py](../edit/reference_metric.py)

## Introduction:
### Why use a reference metric? Benefits of choosing the best coordinate system for the problem

When solving a partial differential equation on the computer, it is useful to first pick a coordinate system well-suited to the geometry of the problem. For example, if we are modeling a spherically-symmetric star, it would be hugely wasteful to model the star in 3-dimensional Cartesian coordinates ($x$,$y$,$z$). This is because in Cartesian coordinates, we would need to choose high sampling in all three Cartesian directions. If instead we chose to model the star in spherical coordinates  ($r$,$\theta$,$\phi$), so long as the star is centered at $r=0$, we would not need to model the star with more than one point in the $\theta$ and $\phi$ directions!

A similar argument holds for stars that are *nearly* spherically symmetric. Such stars may exhibit density distributions that vary slowly in $\theta$ and $\phi$ directions (e.g., isolated neutron stars or black holes). In these cases the number of points needed to sample the angular directions will still be much smaller than in the radial direction.

Thus choice of an appropriate reference metric may directly mitigate the [Curse of Dimensionality](https://en.wikipedia.org/wiki/Curse_of_dimensionality).

# Table of Contents
$$\label{toc}$$

This module is organized as follow

1. [Step 1](#define_ref_metric): Defining a reference metric, [`reference_metric.py`](../edit/reference_metric.py)
1. [Step 2](#define_geometric): Defining geometric quantities, **`ref_metric__hatted_quantities()`**
1. [Step 3](#prescribed_ref_metric): Prescribed reference metrics in [`reference_metric.py`](../edit/reference_metric.py)
    1. [Step 3.a](#sphericallike): Spherical-like coordinate systems
        1. [Step 3.a.i](#spherical): **`reference_metric::CoordSystem = "Spherical"`**
        1. [Step 3.a.ii](#sinhspherical): **`reference_metric::CoordSystem = "SinhSpherical"`**
        1. [Step 3.a.iii](#sinhsphericalv2): **`reference_metric::CoordSystem = "SinhSphericalv2"`**
    1. [Step 3.b](#cylindricallike): Cylindrical-like coordinate systems
        1. [Step 3.b.i](#cylindrical): **`reference_metric::CoordSystem = "Cylindrical"`**
        1. [Step 3.b.ii](#sinhcylindrical): **`reference_metric::CoordSystem = "SinhCylindrical"`**
        1. [Step 3.b.iii](#sinhcylindricalv2): **`reference_metric::CoordSystem = "SinhCylindricalv2"`**
    1. [Step 3.c](#cartesianlike): Cartesian-like coordinate systems
        1. [Step 3.c.i](#cartesian): **`reference_metric::CoordSystem = "Cartesian"`**
    1. [Step 3.d](#prolatespheroidal): Prolate spheroidal coordinates
        1. [Step 3.d.i](#symtp): **`reference_metric::CoordSystem = "SymTP"`**
        1. [Step 3.d.ii](#sinhsymtp): **`reference_metric::CoordSystem = "SinhSymTP"`**
1. [Step 4](#latex_pdf_output): Output this module to $\LaTeX$-formatted PDF file

# Step 1: Defining a reference metric, [`reference_metric.py`](../edit/reference_metric.py) \[Back to [top](#toc)\]
$$\label{define_ref_metric}$$

***Note that currently only orthogonal reference metrics of dimension 3 or fewer are supported. This can be extended if desired.***

NRPy+ assumes all curvilinear coordinate systems map directly from a uniform, Cartesian numerical grid with coordinates $(x,y,z)$=(`xx[0]`,`xx[1]`,`xx[2]`). Thus when defining reference metrics, all defined coordinate quantities must be in terms of the `xx[]` array. As we will see, this adds a great deal of flexibility

For example,  [**reference_metric.py**](../edit/reference_metric.py) requires that the *orthogonal coordinate scale factors* be defined. As described [here](https://en.wikipedia.org/wiki/Curvilinear_coordinates), the $i$th scale factor is the positive root of the metric element $g_{ii}$. In ordinary spherical coordinates $(r,\theta,\phi)$, with line element $ds^2 = g_{ij} dx^i dx^j = dr^2+ r^2 d \theta^2 + r^2 \sin^2\theta \ d\phi^2$, we would first define
* $r = xx_0$
* $\theta = xx_1$
* $\phi = xx_2$,

so that the scale factors are defined as
* `scalefactor_orthog[0]` = $1$
* `scalefactor_orthog[1]` = $r$
* `scalefactor_orthog[2]` = $r \sin \theta$

Here is the corresponding code:

```python
import sympy as sp
import NRPy_param_funcs as par
import reference_metric as rfm

r = rfm.xx[0]
th = rfm.xx[1]
ph = rfm.xx[2]

rfm.scalefactor_orthog[0] = 1
rfm.scalefactor_orthog[1] = r
rfm.scalefactor_orthog[2] = r*sp.sin(th)

# Notice that the scale factor will be given
#    in terms of the fundamental Cartesian
#    grid variables, and not {r,th,ph}:
print("r*sin(th) = "+str(rfm.scalefactor_orthog[2]))
```

    r*sin(th) = xx0*sin(xx1)

Next suppose we wish to modify our radial coordinate $r(xx_0)$ to be an exponentially increasing function, so that our numerical grid $(xx_0,xx_1,xx_2)$ will map to a spherical grid with radial grid spacing ($\Delta r$) that *increases* with $r$. Generally we will find it useful to define $r(xx_0)$ to be an odd function, so let's choose

$$r(xx_0) = a \sinh(xx_0/s),$$

where $a$ is an overall radial scaling factor, and $s$ denotes the scale (in units of $xx_0$) over which exponential growth will take place. In our implementation below, note that we use the relation

$$\sinh(x) = \frac{e^x - e^{-x}}{2},$$

as SymPy finds it easier to evaluate exponentials than hyperbolic trigonometric functions.

```python
a,s = sp.symbols('a s',positive=True)
xx0_rescaled = rfm.xx[0] / s
r = a*(sp.exp(xx0_rescaled) - sp.exp(-xx0_rescaled))/2

# Must redefine the scalefactors since 'r' has been updated!
rfm.scalefactor_orthog[0] = 1
rfm.scalefactor_orthog[1] = r
rfm.scalefactor_orthog[2] = r*sp.sin(th)

print(rfm.scalefactor_orthog[2])
```

    a*(exp(xx0/s) - exp(-xx0/s))*sin(xx1)/2

Often we will find it useful to also define the appropriate mappings from (`xx[0]`,`xx[1]`,`xx[2]`) to Cartesian coordinates (for plotting purposes) and ordinary spherical coordinates (e.g., in case initial data when solving a PDE are naturally written in spherical coordinates). For this purpose, reference_metric.py also declares lists **`xxCart[]`** and **`xxSph[]`**, which in this case are defined as

```python
rfm.xxSph[0] = r
rfm.xxSph[1] = th
rfm.xxSph[2] = ph

rfm.xxCart[0] = r*sp.sin(th)*sp.cos(ph)
rfm.xxCart[1] = r*sp.sin(th)*sp.sin(ph)
rfm.xxCart[2] = r*sp.cos(th)

# Here we show off SymPy's pretty_print()
#   and simplify() functions. Nice, no?
sp.pretty_print(sp.simplify(rfm.xxCart[0]))
```

                            ⎛xx₀⎞
    a⋅sin(xx₁)⋅cos(xx₂)⋅sinh⎜───⎟
                            ⎝ s ⎠

# Step 2: Define geometric quantities, `ref_metric__hatted_quantities()` \[Back to [top](#toc)\]
$$\label{define_geometric}$$

Once `scalefactor_orthog[]` has been defined, the function **`ref_metric__hatted_quantities()`** within [reference_metric.py](../edit/reference_metric.py) can be called to define a number of geometric quantities useful for solving PDEs in curvilinear coordinate systems.

Adopting the notation of [Baumgarte, Montero, Cordero-Carrión, and Müller, PRD 87, 044026 (2012)](https://arxiv.org/abs/1211.6632), geometric quantities related to the reference metric are named "hatted" quantities, . For example, the reference metric is defined as $\hat{g}_{ij}$=`ghatDD[i][j]`:

```python
rfm.ref_metric__hatted_quantities()

sp.pretty_print(sp.Matrix(sp.simplify(rfm.ghatDD)))
```

    ⎡1           0                         0              ⎤
    ⎢                                                     ⎥
    ⎢                     2                               ⎥
    ⎢      ⎛ xx₀    -xx₀ ⎞                                ⎥
    ⎢      ⎜ ───    ─────⎟                                ⎥
    ⎢    2 ⎜  s       s  ⎟                                ⎥
    ⎢   a ⋅⎝ℯ    - ℯ     ⎠                                ⎥
    ⎢0  ───────────────────                0              ⎥
    ⎢            4                                        ⎥
    ⎢                                                     ⎥
    ⎢                                          2          ⎥
    ⎢                           ⎛ xx₀    -xx₀ ⎞           ⎥
    ⎢                           ⎜ ───    ─────⎟           ⎥
    ⎢                         2 ⎜  s       s  ⎟     2     ⎥
    ⎢                        a ⋅⎝ℯ    - ℯ     ⎠ ⋅sin (xx₁)⎥
    ⎢0           0           ─────────────────────────────⎥
    ⎣                                      4              ⎦

In addition to $\hat{g}_{ij}$, **`ref_metric__hatted_quantities()`** also provides:
* The rescaling "matrix" `ReDD[i][j]`, used for separating singular (due to chosen coordinate system) pieces of smooth rank-2 tensor components from the smooth parts, so that the smooth parts can be used within temporal and spatial differential operators.
* Inverse reference metric: $\hat{g}^{ij}$=`ghatUU[i][j]`.
* Reference metric determinant: $\det\left(\hat{g}_{ij}\right)$=`detgammahat`.
* First and second derivatives of the reference metric: $\hat{g}_{ij,k}$=`ghatDD_dD[i][j][k]`; $\hat{g}_{ij,kl}$=`ghatDD_dDD[i][j][k][l]`
* Christoffel symbols associated with the reference metric, $\hat{\Gamma}^i_{jk}$ = `GammahatUDD[i][j][k]` and their first derivatives $\hat{\Gamma}^i_{jk,l}$ = `GammahatUDD_dD[i][j][k][l]`

For example, the Christoffel symbol $\hat{\Gamma}^{xx_1}_{xx_2 xx_2}=\hat{\Gamma}^1_{22}$ is given by `GammahatUDD[1][2][2]`:

```python
sp.pretty_print(sp.simplify(rfm.GammahatUDD[1][2][2]))
```

    -sin(2⋅xx₁)
    ────────────
         2

Given the trigonometric identity $2\sin(x)\cos(x) = \sin(2x)$, notice that the above expression is equivalent to Eq. 18 of [Baumgarte, Montero, Cordero-Carrión, and Müller, PRD 87, 044026 (2012)](https://arxiv.org/abs/1211.6632). This is expected since the sinh-radial spherical coordinate system is equivalent to ordinary spherical coordinates in the angular components.

# Step 3: Prescribed reference metrics in [`reference_metric.py`](../edit/reference_metric.py) \[Back to [top](#toc)\]
$$\label{prescribed_ref_metric}$$

One need not manually define scale factors or other quantities for reference metrics, as a number of prescribed reference metrics are already defined in [reference_metric.py](../edit/reference_metric.py). These can be accessed by first setting the parameter **reference_metric::CoordSystem** to one of the following, and then calling the function **`rfm.reference_metric()`**.

```python
import NRPy_param_funcs as par
import indexedexp as ixp
import grid as gri

# Step 0a: Initialize parameters
thismodule = __name__
par.initialize_param(par.glb_param("char", thismodule, "CoordSystem", "Spherical"))

# Step 0b: Declare global variables
xx = gri.xx
xxCart = ixp.zerorank1(DIM=4) # Must be set in terms of xx[]s
Cart_to_xx = ixp.zerorank1(DIM=4) # Must be set in terms of xx[]s
Cartx,Carty,Cartz = sp.symbols("Cartx Carty Cartz", real=True)
Cart = [Cartx,Carty,Cartz]
xxSph  = ixp.zerorank1(DIM=4) # Must be set in terms of xx[]s
scalefactor_orthog = ixp.zerorank1(DIM=4) # Must be set in terms of xx[]s
have_already_called_reference_metric_function = False

CoordSystem = par.parval_from_str("reference_metric::CoordSystem")
M_PI,M_SQRT1_2 = par.Cparameters("#define",thismodule,["M_PI","M_SQRT1_2"],"")

global xxmin
global xxmax

global UnitVectors
UnitVectors = ixp.zerorank2(DIM=3)
```

We will find the following plotting function useful for analyzing coordinate systems in which the radial coordinate is rescaled.

```python
def create_r_of_xx0_plots(CoordSystem, r_of_xx0,rprime_of_xx0):
    import matplotlib.pyplot as plt
    plt.clf()
    Nr = 20
    dxx0 = 1.0 / float(Nr)
    xx0s    = []
    rs      = []
    deltars = []
    rprimes = []
    for i in range(Nr):
        xx0 = (float(i) + 0.5)*dxx0
        xx0s.append(xx0)
        rs.append(     sp.sympify(str(r_of_xx0     ).replace("xx0",str(xx0))))
        rprimes.append(sp.sympify(str(rprime_of_xx0).replace("xx0",str(xx0))))
        if i>0:
            deltars.append(sp.log(rs[i]-rs[i-1],10))
        else:
            deltars.append(sp.log(2*rs[0],10))

    # fig, ax = plt.subplots()
    fig = plt.figure(figsize=(12,12)) # 8 in x 8 in

    ax = fig.add_subplot(221)
    ax.set_title('$r(xx_0)$ for '+CoordSystem,fontsize='x-large')
    ax.set_xlabel('$xx_0$',fontsize='x-large')
    ax.set_ylabel('$r(xx_0)$',fontsize='x-large')
    ax.plot(xx0s, rs, 'k.', label='Spacing between\nadjacent gridpoints')
    # legend = ax.legend(loc='lower right', shadow=True, fontsize='x-large')
    # legend.get_frame().set_facecolor('C1')

    ax = fig.add_subplot(222)
    ax.set_title('Grid spacing for '+CoordSystem,fontsize='x-large')
    ax.set_xlabel('$xx_0$',fontsize='x-large')
    ax.set_ylabel('$\log_{10}(\Delta r)$',fontsize='x-large')
    ax.plot(xx0s, deltars, 'k.', label='Spacing between\nadjacent gridpoints\nin $r(xx_0)$ plot')
    legend = ax.legend(loc='lower right', shadow=True, fontsize='x-large')
    legend.get_frame().set_facecolor('C1')

    ax = fig.add_subplot(223)
    ax.set_title('$r\'(xx_0)$ for '+CoordSystem,fontsize='x-large')
    ax.set_xlabel('$xx_0$',fontsize='x-large')
    ax.set_ylabel('$r\'(xx_0)$',fontsize='x-large')
    ax.plot(xx0s, rprimes, 'k.', label='Nr=96')
    # legend = ax.legend(loc='upper left', shadow=True, fontsize='x-large')
    # legend.get_frame().set_facecolor('C1')

    plt.tight_layout(pad=2)
    plt.show()
```

## Step 3.a: Spherical-like coordinate systems \[Back to [top](#toc)\]
$$\label{sphericallike}$$

### Step 3.a.i: **`reference_metric::CoordSystem = "Spherical"`** \[Back to [top](#toc)\]
$$\label{spherical}$$

Standard spherical coordinates, with $(r,\theta,\phi)=(xx_0,xx_1,xx_2)$

```python
if CoordSystem == "Spherical":
    # Adding assumption real=True can help simplify expressions involving xx[0] & xx[1] below.
    xx[0] = sp.symbols("xx0", real=True)
    xx[1] = sp.symbols("xx1", real=True)

    RMAX = par.Cparameters("REAL", thismodule, ["RMAX"],10.0)
    xxmin = [sp.sympify(0), sp.sympify(0), -M_PI]
    xxmax = [         RMAX,          M_PI,  M_PI]

    r  = xx[0]
    th = xx[1]
    ph = xx[2]

    Cart_to_xx[0] = sp.sqrt(Cartx ** 2 + Carty ** 2 + Cartz ** 2)
    Cart_to_xx[1] = sp.acos(Cartz / Cart_to_xx[0])
    Cart_to_xx[2] = sp.atan2(Carty, Cartx)

    xxSph[0] = r
    xxSph[1] = th
    xxSph[2] = ph

    # Now define xCart, yCart, and zCart in terms of x0,xx[1],xx[2].
    # Note that the relation between r and x0 is not necessarily trivial in SinhSpherical coordinates. See above.
    xxCart[0] = xxSph[0]*sp.sin(xxSph[1])*sp.cos(xxSph[2])
    xxCart[1] = xxSph[0]*sp.sin(xxSph[1])*sp.sin(xxSph[2])
    xxCart[2] = xxSph[0]*sp.cos(xxSph[1])

    scalefactor_orthog[0] = sp.diff(xxSph[0],xx[0])
    scalefactor_orthog[1] = xxSph[0]
    scalefactor_orthog[2] = xxSph[0]*sp.sin(xxSph[1])

    # Set the unit vectors
    UnitVectors = [[ sp.sin(xxSph[1])*sp.cos(xxSph[2]), sp.sin(xxSph[1])*sp.sin(xxSph[2]),  sp.cos(xxSph[1])],
                   [ sp.cos(xxSph[1])*sp.cos(xxSph[2]), sp.cos(xxSph[1])*sp.sin(xxSph[2]), -sp.sin(xxSph[1])],
                   [                 -sp.sin(xxSph[2]),                  sp.cos(xxSph[2]),  sp.sympify(0)   ]]
```

Now let's analyze $r(xx_0)$ for **"Spherical"** coordinates.

```python
%matplotlib inline

import sympy as sp

import reference_metric as rfm
import NRPy_param_funcs as par
CoordSystem = "Spherical"
par.set_parval_from_str("reference_metric::CoordSystem",CoordSystem)
rfm.reference_metric()

RMAX     = 10.0
r_of_xx0      = sp.sympify(str(rfm.xxSph[0]                   ).replace("RMAX",str(RMAX)))
rprime_of_xx0 = sp.sympify(str(sp.diff(rfm.xxSph[0],rfm.xx[0])).replace("RMAX",str(RMAX)))

create_r_of_xx0_plots(CoordSystem, r_of_xx0,rprime_of_xx0)
```

### Step 3.a.ii: **`reference_metric::CoordSystem = "SinhSpherical"`** \[Back to [top](#toc)\]
$$\label{sinhspherical}$$

Spherical coordinates, but with $$r(xx_0) = \text{AMPL} \frac{\sinh\left(\frac{xx_0}{\text{SINHW}}\right)}{\sinh\left(\frac{1}{\text{SINHW}}\right)}.$$

SinhSpherical uses two parameters: `AMPL` and `SINHW`. `AMPL` sets the outer boundary distance; and `SINHW` sets the focusing of the coordinate points near $r=0$, where a small `SINHW` ($\sim 0.125$) will greatly focus the points near $r=0$ and a large `SINHW` will look more like an ordinary spherical polar coordinate system.

```python
if CoordSystem == "SinhSpherical":
    xxmin = [sp.sympify(0), sp.sympify(0), -M_PI]
    xxmax = [sp.sympify(1),          M_PI,  M_PI]

    AMPL, SINHW = par.Cparameters("REAL",thismodule,["AMPL","SINHW"],[10.0,0.2])
    # Set SinhSpherical radial coordinate by default; overwrite later if CoordSystem == "SinhSphericalv2".
    r = AMPL * (sp.exp(xx[0] / SINHW) - sp.exp(-xx[0] / SINHW)) / \
               (sp.exp(1 / SINHW) - sp.exp(-1 / SINHW))
    th = xx[1]
    ph = xx[2]

    Cart_to_xx[0] = SINHW*sp.asinh(sp.sqrt(Cartx ** 2 + Carty ** 2 + Cartz ** 2)*sp.sinh(1/SINHW)/AMPL)
    Cart_to_xx[1] = sp.acos(Cartz / sp.sqrt(Cartx ** 2 + Carty ** 2 + Cartz ** 2))
    Cart_to_xx[2] = sp.atan2(Carty, Cartx)

    xxSph[0] = r
    xxSph[1] = th
    xxSph[2] = ph

    # Now define xCart, yCart, and zCart in terms of x0,xx[1],xx[2].
    # Note that the relation between r and x0 is not necessarily trivial in SinhSpherical coordinates. See above.
    xxCart[0] = xxSph[0]*sp.sin(xxSph[1])*sp.cos(xxSph[2])
    xxCart[1] = xxSph[0]*sp.sin(xxSph[1])*sp.sin(xxSph[2])
    xxCart[2] = xxSph[0]*sp.cos(xxSph[1])

    scalefactor_orthog[0] = sp.diff(xxSph[0],xx[0])
    scalefactor_orthog[1] = xxSph[0]
    scalefactor_orthog[2] = xxSph[0]*sp.sin(xxSph[1])

    # Set the unit vectors
    UnitVectors = [[ sp.sin(xxSph[1])*sp.cos(xxSph[2]), sp.sin(xxSph[1])*sp.sin(xxSph[2]),  sp.cos(xxSph[1])],
                   [ sp.cos(xxSph[1])*sp.cos(xxSph[2]), sp.cos(xxSph[1])*sp.sin(xxSph[2]), -sp.sin(xxSph[1])],
                   [                 -sp.sin(xxSph[2]),                  sp.cos(xxSph[2]),  sp.sympify(0)   ]]
```

Now we explore $r(xx_0)$ for `SinhSpherical` assuming `AMPL=10.0` and `SINHW=0.2`:

```python
%matplotlib inline

import sympy as sp

import reference_metric as rfm
import NRPy_param_funcs as par
CoordSystem = "SinhSpherical"
par.set_parval_from_str("reference_metric::CoordSystem",CoordSystem)
rfm.reference_metric()

AMPL     = 10.0
SINHW    = 0.2
r_of_xx0      = sp.sympify(str(rfm.xxSph[0]                   ).replace("AMPL",str(AMPL)).replace("SINHW",str(SINHW)))
rprime_of_xx0 = sp.sympify(str(sp.diff(rfm.xxSph[0],rfm.xx[0])).replace("AMPL",str(AMPL)).replace("SINHW",str(SINHW)))

create_r_of_xx0_plots(CoordSystem, r_of_xx0,rprime_of_xx0)
```

### Step 3.a.iii: **`reference_metric::CoordSystem = "SinhSphericalv2"`** \[Back to [top](#toc)\]
$$\label{sinhsphericalv2}$$

The same as SinhSpherical coordinates, but with an additional `AMPL*const_dr*xx_0` term:
$$r(xx_0) = \text{AMPL} \left[\text{const_dr}\ xx_0 + \frac{\sinh\left(\frac{xx_0}{\text{SINHW}}\right)}{\sinh\left(\frac{1}{\text{SINHW}}\right)}\right].$$

```python
if CoordSystem == "SinhSphericalv2":
    # SinhSphericalv2 adds the parameter "const_dr", which allows for a region near xx[0]=0 to have
    # constant radial resolution of const_dr, provided the sinh() term does not dominate near xx[0]=0.
    xxmin = [sp.sympify(0), sp.sympify(0), -M_PI]
    xxmax = [sp.sympify(1),          M_PI,  M_PI]

    AMPL, SINHW = par.Cparameters("REAL",thismodule,["AMPL","SINHW"],[10.0,0.2])
    const_dr = par.Cparameters("REAL",thismodule,["const_dr"],0.0625)

    r = AMPL*( const_dr*xx[0] + (sp.exp(xx[0] / SINHW) - sp.exp(-xx[0] / SINHW)) /
               (sp.exp(1 / SINHW) - sp.exp(-1 / SINHW)) )
    th = xx[1]
    ph = xx[2]

    # NO CLOSED-FORM EXPRESSION FOR RADIAL INVERSION.
    # Cart_to_xx[0] = "NewtonRaphson"
    # Cart_to_xx[1] = sp.acos(Cartz / sp.sqrt(Cartx ** 2 + Carty ** 2 + Cartz ** 2))
    # Cart_to_xx[2] = sp.atan2(Carty, Cartx)

    xxSph[0] = r
    xxSph[1] = th
    xxSph[2] = ph

    # Now define xCart, yCart, and zCart in terms of x0,xx[1],xx[2].
    # Note that the relation between r and x0 is not necessarily trivial in SinhSpherical coordinates. See above.
    xxCart[0] = xxSph[0]*sp.sin(xxSph[1])*sp.cos(xxSph[2])
    xxCart[1] = xxSph[0]*sp.sin(xxSph[1])*sp.sin(xxSph[2])
    xxCart[2] = xxSph[0]*sp.cos(xxSph[1])

    scalefactor_orthog[0] = sp.diff(xxSph[0],xx[0])
    scalefactor_orthog[1] = xxSph[0]
    scalefactor_orthog[2] = xxSph[0]*sp.sin(xxSph[1])

    # Set the unit vectors
    UnitVectors = [[ sp.sin(xxSph[1])*sp.cos(xxSph[2]), sp.sin(xxSph[1])*sp.sin(xxSph[2]),  sp.cos(xxSph[1])],
                   [ sp.cos(xxSph[1])*sp.cos(xxSph[2]), sp.cos(xxSph[1])*sp.sin(xxSph[2]), -sp.sin(xxSph[1])],
                   [                 -sp.sin(xxSph[2]),                  sp.cos(xxSph[2]),  sp.sympify(0)   ]]
```

Now we explore $r(xx_0)$ for `SinhSphericalv2` assuming `AMPL=10.0`, `SINHW=0.2`, and `const_dr=0.05`. Notice that the `const_dr` term significantly increases the grid spacing near $xx_0=0$ relative to `SinhSpherical` coordinates.

```python
%matplotlib inline

import sympy as sp

import reference_metric as rfm
import NRPy_param_funcs as par
CoordSystem = "SinhSphericalv2"
par.set_parval_from_str("reference_metric::CoordSystem",CoordSystem)
rfm.reference_metric()

AMPL     = 10.0
SINHW    = 0.2
const_dr = 0.05
r_of_xx0      = sp.sympify(str(rfm.xxSph[0]                   ).replace("AMPL",str(AMPL)).replace("SINHW",str(SINHW)).replace("const_dr",str(const_dr)))
rprime_of_xx0 = sp.sympify(str(sp.diff(rfm.xxSph[0],rfm.xx[0])).replace("AMPL",str(AMPL)).replace("SINHW",str(SINHW)).replace("const_dr",str(const_dr)))

create_r_of_xx0_plots(CoordSystem, r_of_xx0,rprime_of_xx0)
```

## Step 3.b: Cylindrical-like coordinate systems \[Back to [top](#toc)\]
$$\label{cylindricallike}$$

### Step 3.b.i: **`reference_metric::CoordSystem = "Cylindrical"`** \[Back to [top](#toc)\]
$$\label{cylindrical}$$

Standard cylindrical coordinates, with $(\rho,\phi,z)=(xx_0,xx_1,xx_2)$

```python
if CoordSystem == "Cylindrical":
    # Assuming the cylindrical radial coordinate
    #   is positive makes nice simplifications of
    #   unit vectors possible.
    xx[0] = sp.symbols("xx0", real=True)

    RHOMAX,ZMIN,ZMAX = par.Cparameters("REAL",thismodule,["RHOMAX","ZMIN","ZMAX"],[10.0,-10.0,10.0])
    xxmin = [sp.sympify(0), -M_PI, ZMIN]
    xxmax = [       RHOMAX,  M_PI, ZMAX]

    RHOCYL = xx[0]
    PHICYL = xx[1]
    ZCYL   = xx[2]

    Cart_to_xx[0] = sp.sqrt(Cartx ** 2 + Carty ** 2)
    Cart_to_xx[1] = sp.atan2(Carty, Cartx)
    Cart_to_xx[2] = Cartz

    xxCart[0] = RHOCYL*sp.cos(PHICYL)
    xxCart[1] = RHOCYL*sp.sin(PHICYL)
    xxCart[2] = ZCYL

    xxSph[0] = sp.sqrt(RHOCYL**2 + ZCYL**2)
    xxSph[1] = sp.acos(ZCYL / xxSph[0])
    xxSph[2] = PHICYL

    scalefactor_orthog[0] = sp.diff(RHOCYL,xx[0])
    scalefactor_orthog[1] = RHOCYL
    scalefactor_orthog[2] = sp.diff(ZCYL,xx[2])

    # Set the unit vectors
    UnitVectors = [[ sp.cos(PHICYL), sp.sin(PHICYL), sp.sympify(0)],
                   [-sp.sin(PHICYL), sp.cos(PHICYL), sp.sympify(0)],
                   [ sp.sympify(0),  sp.sympify(0),  sp.sympify(1)]]
```

Next let's plot **"Cylindrical"** coordinates.

```python
%matplotlib inline

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

R = np.linspace(0, 2, 24)
h = 2
u = np.linspace(0,  2*np.pi, 24)

x = np.outer(R, np.cos(u))
y = np.outer(R, np.sin(u))
z = h * np.outer(np.ones(np.size(u)), np.ones(np.size(u)))

r = np.arange(0,2,0.25)
theta = 2*np.pi*r*0

fig = plt.figure(figsize=(12,12)) # 8 in x 8 in
fig, (ax1, ax2) = plt.subplots(1, 2)

ax1 = plt.axes(projection='polar')

ax1.set_rmax(2)

ax1.set_rgrids(r,labels=[])

thetas = np.linspace(0,360,24, endpoint=True)
ax1.set_thetagrids(thetas,labels=[])

# ax.grid(True)
ax1.grid(True,linewidth='1.0')
ax1.set_title("Top Down View")
plt.show()

ax2 = plt.axes(projection='3d', xticklabels=[], yticklabels=[], zticklabels=[])
#ax2.plot_surface(x,y,z, alpha=.75, cmap = 'viridis') # z in case of disk which is parallel to XY plane is constant and you can directly use h

x=np.linspace(-2, 2, 100)
z=np.linspace(-2, 2, 100)
Xc, Zc=np.meshgrid(x, z)
Yc = np.sqrt(4-Xc**2)

rstride = 10
cstride = 10
ax2.plot_surface(Xc, Yc, Zc, alpha=1.0, rstride=rstride, cstride=cstride, cmap = 'viridis')
ax2.plot_surface(Xc, -Yc, Zc, alpha=1.0, rstride=rstride, cstride=cstride, cmap = 'viridis')
ax2.set_title("Standard Cylindrical Grid in 3D")
ax2.grid(False)
plt.axis('off')

plt.show()
```

### Step 3.b.ii" **`reference_metric::CoordSystem = "SinhCylindrical"`** \[Back to [top](#toc)\]
$$\label{sinhcylindrical}$$

Cylindrical coordinates, but with
$$\rho(xx_0) = \text{AMPLRHO} \frac{\sinh\left(\frac{xx_0}{\text{SINHWRHO}}\right)}{\sinh\left(\frac{1}{\text{SINHWRHO}}\right)}$$
and
$$z(xx_2) = \text{AMPLZ} \frac{\sinh\left(\frac{xx_2}{\text{SINHWZ}}\right)}{\sinh\left(\frac{1}{\text{SINHWZ}}\right)}$$

```python
if CoordSystem == "SinhCylindrical":
    # Assuming the cylindrical radial coordinate
    #   is positive makes nice simplifications of
    #   unit vectors possible.
    xx[0] = sp.symbols("xx0", real=True)

    xxmin = [sp.sympify(0), -M_PI, sp.sympify(-1)]
    xxmax = [sp.sympify(1),  M_PI, sp.sympify(+1)]

    AMPLRHO, SINHWRHO, AMPLZ, SINHWZ = par.Cparameters("REAL",thismodule,
                                                       ["AMPLRHO","SINHWRHO","AMPLZ","SINHWZ"],
                                                       [     10.0,       0.2,   10.0,    0.2])

    # Set SinhCylindrical radial & z coordinates by default; overwrite later if CoordSystem == "SinhCylindricalv2".
    RHOCYL = AMPLRHO * (sp.exp(xx[0] / SINHWRHO) - sp.exp(-xx[0] / SINHWRHO)) / (sp.exp(1 / SINHWRHO) - sp.exp(-1 / SINHWRHO))
    # phi coordinate remains unchanged.
    PHICYL = xx[1]
    ZCYL   = AMPLZ   * (sp.exp(xx[2] / SINHWZ)   - sp.exp(-xx[2] / SINHWZ))   / (sp.exp(1 / SINHWZ)   - sp.exp(-1 / SINHWZ))
    Cart_to_xx[0] = SINHWRHO*sp.asinh(sp.sqrt(Cartx ** 2 + Carty ** 2)*sp.sinh(1/SINHWRHO)/AMPLRHO)
    Cart_to_xx[1] = sp.atan2(Carty, Cartx)
    Cart_to_xx[2] = SINHWZ*sp.asinh(Cartz*sp.sinh(1/SINHWZ)/AMPLZ)

    xxCart[0] = RHOCYL*sp.cos(PHICYL)
    xxCart[1] = RHOCYL*sp.sin(PHICYL)
    xxCart[2] = ZCYL

    xxSph[0] = sp.sqrt(RHOCYL**2 + ZCYL**2)
    xxSph[1] = sp.acos(ZCYL / xxSph[0])
    xxSph[2] = PHICYL

    scalefactor_orthog[0] = sp.diff(RHOCYL,xx[0])
    scalefactor_orthog[1] = RHOCYL
    scalefactor_orthog[2] = sp.diff(ZCYL,xx[2])

    # Set the unit vectors
    UnitVectors = [[ sp.cos(PHICYL), sp.sin(PHICYL), sp.sympify(0)],
                   [-sp.sin(PHICYL), sp.cos(PHICYL), sp.sympify(0)],
                   [ sp.sympify(0),  sp.sympify(0),  sp.sympify(1)]]
```

Next let's plot **"SinhCylindrical"** coordinates.

```python
fig=plt.figure()

plt.clf()

fig = plt.figure()

ax = plt.subplot(1,1,1, projection='polar')

ax.set_rmax(2)

Nr = 20
xx0s = np.linspace(0,2,Nr, endpoint=True) + 1.0/(2.0*Nr)

rs = []
AMPLRHO = 1.0
SINHW = 0.4
for i in range(Nr):
    rs.append(AMPLRHO * (np.exp(xx0s[i] / SINHW) - np.exp(-xx0s[i] / SINHW)) / \
                        (np.exp(1.0 / SINHW) - np.exp(-1.0 / SINHW)))

ax.set_rgrids(rs,labels=[])

thetas = np.linspace(0,360,25, endpoint=True)
ax.set_thetagrids(thetas,labels=[])

# ax.grid(True)
ax.grid(True,linewidth='1.0')

plt.show()

```

### Step 3.b.iii: **`reference_metric::CoordSystem = "SinhCylindricalv2"`** \[Back to [top](#toc)\]
$$\label{sinhcylindricalv2}$$

Cylindrical coordinates, but with
$$\rho(xx_0) = \text{AMPLRHO} \left[\text{const_drho}\ xx_0 + \frac{\sinh\left(\frac{xx_0}{\text{SINHWRHO}}\right)}{\sinh\left(\frac{1}{\text{SINHWRHO}}\right)}\right]$$
and
$$z(xx_2) = \text{AMPLZ} \left[\text{const_dz}\ xx_2 + \frac{\sinh\left(\frac{xx_2}{\text{SINHWZ}}\right)}{\sinh\left(\frac{1}{\text{SINHWZ}}\right)}\right]$$

```python
if CoordSystem == "SinhCylindricalv2":
    # Assuming the cylindrical radial coordinate
    #   is positive makes nice simplifications of
    #   unit vectors possible.
    xx[0] = sp.symbols("xx0", real=True)

    # SinhCylindricalv2 adds the parameters "const_drho", "const_dz", which allows for regions near xx[0]=0
    # and xx[2]=0 to have constant rho and z resolution of const_drho and const_dz, provided the sinh() terms
    # do not dominate near xx[0]=0 and xx[2]=0.
    xxmin = [sp.sympify(0), -M_PI, sp.sympify(-1)]
    xxmax = [sp.sympify(1),  M_PI, sp.sympify(+1)]
    AMPLRHO, SINHWRHO, AMPLZ, SINHWZ = par.Cparameters("REAL",thismodule,
                                                       ["AMPLRHO","SINHWRHO","AMPLZ","SINHWZ"],
                                                       [     10.0,       0.2,   10.0,    0.2])
    const_drho, const_dz = par.Cparameters("REAL",thismodule,["const_drho","const_dz"],[0.0625,0.0625])

    RHOCYL = AMPLRHO * ( const_drho*xx[0] + (sp.exp(xx[0] / SINHWRHO) - sp.exp(-xx[0] / SINHWRHO)) / (sp.exp(1 / SINHWRHO) - sp.exp(-1 / SINHWRHO)) )
    PHICYL = xx[1]
    ZCYL   = AMPLZ   * ( const_dz  *xx[2] + (sp.exp(xx[2] / SINHWZ  ) - sp.exp(-xx[2] / SINHWZ  )) / (sp.exp(1 / SINHWZ  ) - sp.exp(-1 / SINHWZ  )) )

    # NO CLOSED-FORM EXPRESSION FOR RADIAL OR Z INVERSION.
    # Cart_to_xx[0] = "NewtonRaphson"
    # Cart_to_xx[1] = sp.atan2(Carty, Cartx)
    # Cart_to_xx[2] = "NewtonRaphson"

    xxCart[0] = RHOCYL*sp.cos(PHICYL)
    xxCart[1] = RHOCYL*sp.sin(PHICYL)
    xxCart[2] = ZCYL

    xxSph[0] = sp.sqrt(RHOCYL**2 + ZCYL**2)
    xxSph[1] = sp.acos(ZCYL / xxSph[0])
    xxSph[2] = PHICYL

    scalefactor_orthog[0] = sp.diff(RHOCYL,xx[0])
    scalefactor_orthog[1] = RHOCYL
    scalefactor_orthog[2] = sp.diff(ZCYL,xx[2])

    # Set the unit vectors
    UnitVectors = [[ sp.cos(PHICYL), sp.sin(PHICYL), sp.sympify(0)],
                   [-sp.sin(PHICYL), sp.cos(PHICYL), sp.sympify(0)],
                   [ sp.sympify(0),  sp.sympify(0),  sp.sympify(1)]]
```

For example, let's set up **`SinhCylindricalv2`** coordinates and output the Christoffel symbol $\hat{\Gamma}^{xx_2}_{xx_2 xx_2}$, or more simply $\hat{\Gamma}^2_{22}$:

```python
par.set_parval_from_str("reference_metric::CoordSystem","SinhCylindricalv2")

rfm.reference_metric()

sp.pretty_print(sp.simplify(rfm.GammahatUDD[2][2][2]))
```

                             ⎛ 2⋅xx₂     ⎞    1
                             ⎜ ──────    ⎟  ──────
                             ⎜ SINHWZ    ⎟  SINHWZ
                            -⎝ℯ       - 1⎠⋅ℯ
    ────────────────────────────────────────────────────────────────────────
           ⎛                  ⎛   2       ⎞   xx₂     ⎛ 2⋅xx₂     ⎞    1   ⎞
           ⎜                  ⎜ ──────    ⎟  ──────   ⎜ ──────    ⎟  ──────⎟
           ⎜                  ⎜ SINHWZ    ⎟  SINHWZ   ⎜ SINHWZ    ⎟  SINHWZ⎟
    SINHWZ⋅⎝- SINHWZ⋅const_dz⋅⎝ℯ       - 1⎠⋅ℯ       - ⎝ℯ       + 1⎠⋅ℯ      ⎠

As we will soon see, defining these "hatted" quantities will be quite useful when expressing hyperbolic ([wave-equation](https://en.wikipedia.org/wiki/Wave_equation)-like) PDEs in non-Cartesian coordinate systems.

## Step 3.c: Cartesian-like coordinate systems \[Back to [top](#toc)\]
$$\label{cartesianlike}$$

### Step 3.c.i: **`reference_metric::CoordSystem = "Cartesian"`** \[Back to [top](#toc)\]
$$\label{cartesian}$$

Standard Cartesian coordinates, with $(x,y,z)=$ `(xx0,xx1,xx2)`

```python
if CoordSystem == "Cartesian":
    xmin, xmax, ymin, ymax, zmin, zmax = par.Cparameters("REAL",thismodule,
                                                         ["xmin","xmax","ymin","ymax","zmin","zmax"],
                                                         [ -10.0,  10.0, -10.0,  10.0, -10.0,  10.0])
    xxmin = ["xmin", "ymin", "zmin"]
    xxmax = ["xmax", "ymax", "zmax"]

    xxCart[0] = xx[0]
    xxCart[1] = xx[1]
    xxCart[2] = xx[2]

    xxSph[0] = sp.sqrt(xx[0] ** 2 + xx[1] ** 2 + xx[2] ** 2)
    xxSph[1] = sp.acos(xx[2] / xxSph[0])
    xxSph[2] = sp.atan2(xx[1], xx[0])

    Cart_to_xx[0] = Cartx
    Cart_to_xx[1] = Carty
    Cart_to_xx[2] = Cartz

    scalefactor_orthog[0] = sp.sympify(1)
    scalefactor_orthog[1] = sp.sympify(1)
    scalefactor_orthog[2] = sp.sympify(1)

    # Set the transpose of the matrix of unit vectors
    UnitVectors = [[sp.sympify(1), sp.sympify(0), sp.sympify(0)],
                   [sp.sympify(0), sp.sympify(1), sp.sympify(0)],
                   [sp.sympify(0), sp.sympify(0), sp.sympify(1)]]
```

```python
%matplotlib inline

import numpy as np
import matplotlib.pyplot as plt

plt.clf()

fig = plt.figure()
ax = fig.gca()
Nx = 16
ax.set_xticks(np.arange(0, 1., 1./Nx))
ax.set_yticks(np.arange(0, 1., 1./Nx))
# plt.scatter(x, y)
ax.set_aspect('equal')

plt.grid()

# plt.savefig("Cartgrid.png",dpi=300)
plt.show()
# plt.close(fig)
```

## Step 3.d: [Prolate spheroidal](https://en.wikipedia.org/wiki/Prolate_spheroidal_coordinates)-like coordinate systems \[Back to [top](#toc)\]
$$\label{prolatespheroidal}$$

### Step 3.d.i: **`reference_metric::CoordSystem = "SymTP"`** \[Back to [top](#toc)\]
$$\label{symtp}$$

Symmetric TwoPuncture coordinates, with $(\rho,\phi,z)=(xx_0\sin(xx_1), xx_2, \sqrt{xx_0^2 + \text{bScale}^2}\cos(xx_1))$

```python
if CoordSystem == "SymTP":

    var1, var2= sp.symbols('var1 var2',real=True)
    bScale, AW, AMAX, RHOMAX, ZMIN, ZMAX = par.Cparameters("REAL",thismodule,
                                                           ["bScale","AW","AMAX","RHOMAX","ZMIN","ZMAX"],
                                                           [0.5,     0.2,   10.0,    10.0, -10.0,  10.0])

    # Assuming xx0, xx1, and bScale
    #   are positive makes nice simplifications of
    #   unit vectors possible.
    xx[0],xx[1] = sp.symbols("xx0 xx1", real=True)

    xxmin = [sp.sympify(0), sp.sympify(0),-M_PI]
    xxmax = [         AMAX,          M_PI, M_PI]

    AA = xx[0]

    if CoordSystem == "SinhSymTP":
        AA = (sp.exp(xx[0]/AW)-sp.exp(-xx[0]/AW))/2

    var1 = sp.sqrt(AA**2 + (bScale * sp.sin(xx[1]))**2)
    var2 = sp.sqrt(AA**2 + bScale**2)

    RHOSYMTP = AA*sp.sin(xx[1])
    PHSYMTP = xx[2]
    ZSYMTP = var2*sp.cos(xx[1])

    xxCart[0] = AA  *sp.sin(xx[1])*sp.cos(xx[2])
    xxCart[1] = AA  *sp.sin(xx[1])*sp.sin(xx[2])
    xxCart[2] = ZSYMTP

    xxSph[0] = sp.sqrt(RHOSYMTP**2 + ZSYMTP**2)
    xxSph[1] = sp.acos(ZSYMTP / xxSph[0])
    xxSph[2] = PHSYMTP

    rSph  = sp.sqrt(Cartx ** 2 + Carty ** 2 + Cartz ** 2)
    thSph = sp.acos(Cartz / rSph)
    phSph = sp.atan2(Carty, Cartx)

    # Mathematica script to compute Cart_to_xx[]
    #             AA = x1;
    #             var2 = Sqrt[AA^2 + bScale^2];
    #             RHOSYMTP = AA*Sin[x2];
    #             ZSYMTP = var2*Cos[x2];
    #             Solve[{rSph == Sqrt[RHOSYMTP^2 + ZSYMTP^2],
    #                    thSph == ArcCos[ZSYMTP/Sqrt[RHOSYMTP^2 + ZSYMTP^2]],
    #                    phSph == x3},
    #                   {x1, x2, x3}]
    Cart_to_xx[0] = sp.sqrt(-bScale**2 + rSph**2 +
                            sp.sqrt(bScale**4 + 2*bScale**2*rSph**2 + rSph**4 -
                                    4*bScale**2*rSph**2*sp.cos(thSph)**2))*M_SQRT1_2 # M_SQRT1_2 = 1/sqrt(2); define this way for UnitTesting

    # The sign() function in the following expression ensures the correct root is taken.
    Cart_to_xx[1] = sp.acos(sp.sign(Cartz)*(
                              sp.sqrt(1 + rSph**2/bScale**2 -
                                      sp.sqrt(bScale**4 + 2*bScale**2*rSph**2 + rSph**4 -
                                              4*bScale**2*rSph**2*sp.cos(thSph)**2)/bScale**2)*M_SQRT1_2)) # M_SQRT1_2 = 1/sqrt(2); define this way for UnitTesting

    Cart_to_xx[2] = phSph
```

### Step 3.d.ii: **`reference_metric::CoordSystem = "SinhSymTP"`** \[Back to [top](#toc)\]
$$\label{sinhsymtp}$$

Symmetric TwoPuncture coordinates, but with $$xx_0 \to \sinh(xx_0/\text{AW})$$

```python
if CoordSystem == "SinhSymTP":

    var1, var2= sp.symbols('var1 var2',real=True)
    bScale, AW, AMAX, RHOMAX, ZMIN, ZMAX = par.Cparameters("REAL",thismodule,
                                                           ["bScale","AW","AMAX","RHOMAX","ZMIN","ZMAX"],
                                                           [0.5,     0.2,   10.0,    10.0, -10.0,  10.0])

    # Assuming xx0, xx1, and bScale
    #   are positive makes nice simplifications of
    #   unit vectors possible.
    xx[0],xx[1] = sp.symbols("xx0 xx1", real=True)

    xxmin = [sp.sympify(0), sp.sympify(0),-M_PI]
    xxmax = [         AMAX,          M_PI, M_PI]

    AA = xx[0]

    if CoordSystem == "SinhSymTP":
        # With xxmax[0] == AMAX, sinh(xx0/AMAX) will evaluate to a number between 0 and 1.
        #   Similarly, sinh(xx0/(AMAX*SINHWAA)) / sinh(1/SINHWAA) will also evaluate to a number between 0 and 1.
        #   Then AA = AMAX*sinh(xx0/(AMAX*SINHWAA)) / sinh(1/SINHWAA) will evaluate to a number between 0 and AMAX.
        AA = AMAX * (sp.exp(xx[0] / (AMAX*SINHWAA)) - sp.exp(-xx[0] / (AMAX*SINHWAA))) / (sp.exp(1 / SINHWAA) - sp.exp(-1 / AMAX))

    var1 = sp.sqrt(AA**2 + (bScale * sp.sin(xx[1]))**2)
    var2 = sp.sqrt(AA**2 + bScale**2)

    RHOSYMTP = AA*sp.sin(xx[1])
    PHSYMTP = xx[2]
    ZSYMTP = var2*sp.cos(xx[1])

    xxCart[0] = AA  *sp.sin(xx[1])*sp.cos(xx[2])
    xxCart[1] = AA  *sp.sin(xx[1])*sp.sin(xx[2])
    xxCart[2] = ZSYMTP

    xxSph[0] = sp.sqrt(RHOSYMTP**2 + ZSYMTP**2)
    xxSph[1] = sp.acos(ZSYMTP / xxSph[0])
    xxSph[2] = PHSYMTP

    scalefactor_orthog[0] = sp.diff(AA,xx[0]) * var1 / var2
    scalefactor_orthog[1] = var1
    scalefactor_orthog[2] = AA * sp.sin(xx[1])

    # Set the transpose of the matrix of unit vectors
    UnitVectors = [[sp.sin(xx[1]) * sp.cos(xx[2]) * var2 / var1,
                    sp.sin(xx[1]) * sp.sin(xx[2]) * var2 / var1,
                    AA * sp.cos(xx[1]) / var1],
                   [AA * sp.cos(xx[1]) * sp.cos(xx[2]) / var1,
                    AA * sp.cos(xx[1]) * sp.sin(xx[2]) / var1,
                        -sp.sin(xx[1]) * var2 / var1],
                   [-sp.sin(xx[2]), sp.cos(xx[2]), sp.sympify(0)]]
```

# Step 4: Output this module to $\LaTeX$-formatted PDF file \[Back to [top](#toc)\]
$$\label{latex_pdf_output}$$

The following code cell converts this Jupyter notebook into a proper, clickable $\LaTeX$-formatted PDF file. After the cell is successfully run, the generated PDF may be found in the root NRPy+ tutorial directory, with filename
[Tutorial-Reference_Metric.pdf](Tutorial-Reference_Metric.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.)

```python
!jupyter nbconvert --to latex --template latex_nrpy_style.tplx Tutorial-Reference_Metric.ipynb
!pdflatex -interaction=batchmode Tutorial-Reference_Metric.tex
!pdflatex -interaction=batchmode Tutorial-Reference_Metric.tex
!pdflatex -interaction=batchmode Tutorial-Reference_Metric.tex
!rm -f Tut*.out Tut*.aux Tut*.log
```

    [NbConvertApp] Converting notebook Tutorial-Reference_Metric.ipynb to latex
    [NbConvertApp] Support files will be in Tutorial-Reference_Metric_files/
    [NbConvertApp] Making directory Tutorial-Reference_Metric_files
    [NbConvertApp] Making directory Tutorial-Reference_Metric_files
    [NbConvertApp] Making directory Tutorial-Reference_Metric_files
    [NbConvertApp] Making directory Tutorial-Reference_Metric_files
    [NbConvertApp] Making directory Tutorial-Reference_Metric_files
    [NbConvertApp] Making directory Tutorial-Reference_Metric_files
    [NbConvertApp] Making directory Tutorial-Reference_Metric_files
    [NbConvertApp] Writing 132807 bytes to Tutorial-Reference_Metric.tex
    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode
    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode
    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode


---

Graphical exploratory data analysis [Iris Dataset]

---

### Clone the repository

```
! git clone https://github.com/mohd-faizy/CAREER-TRACK-Data-Scientist-with-Python.git
```

    Cloning into 'CAREER-TRACK-Data-Scientist-with-Python'...
    remote: Enumerating objects: 44, done.
    remote: Counting objects: 100% (44/44), done.
    remote: Compressing objects: 100% (35/35), done.
    remote: Total 1901 (delta 21), reused 28 (delta 9), pack-reused 1857
    Receiving objects: 100% (1901/1901), 200.33 MiB | 32.17 MiB/s, done.
    Resolving deltas: 100% (673/673), done.
    Checking out files: 100% (821/821), done.

### Change the current working directory

```
# import os module
import os

# to specified path
os.chdir('/content/CAREER-TRACK-Data-Scientist-with-Python/22_Statistical-Thinking-in-Python-[Part -1]/_datasets')

# varify the path using getcwd()
cwd = os.getcwd()

# print the current directory
print("Current working directory is:", cwd)
```

    Current working directory is: /content/CAREER-TRACK-Data-Scientist-with-Python/22_Statistical-Thinking-in-Python-[Part -1]/_datasets

```
ls
```

    2008_all_states.csv    belmont.csv  michelson_speed_of_light.csv
    2008_swing_states.csv  iris.csv

## __1 Exploratory data analysis (EDA)__

__John Tukey's Comments on EDA -:__ "Often times **EDA** is too time consuming, so it is better to jump right in and do our **hypothesis** tests."

That statement is pretty absurd. If we don't have time to do EDA, we really don't have time to do **hypothesis** tests. And we should always do **EDA first**.

__“Exploratory data analysis can never be the whole story, but nothing else can serve as the foundation stone.”__ — _John Tukey_

### Plotting a **histogram** of iris data

For the exercises in this section, you will use a classic data set collected by botanist Edward Anderson and made famous by Ronald Fisher, one of the most prolific statisticians in history. Anderson carefully measured the anatomical properties of samples of three different species of iris,**Iris setosa, Iris versicolor, and Iris virginica.**

The full data set is available [as part of scikit-learn](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_iris.html). Here, we will work with his measurements of petal length.

Plot a histogram of the petal lengths of his 50 samples of Iris versicolor using **matplotlib/seaborn's** default settings. Recall that to specify the default seaborn style, you can use `sns.set()`, where `sns` is the alias that `seaborn` is imported as.

The subset of the data set containing the *Iris versicolor *petal lengths in units of centimeters (cm) is stored in the NumPy array `versicolor_petal_length`.

Plotting the histograms by using the __pandas library__ and indexing the __DataFrame__ to extract the desired column. Here, however, we only need to use the provided __NumPy array__.

$\color{red}{\textbf{NOTE:}}$

>Assigning plotting statements (except for `plt.show()`) to the __dummy variable__ `_`. __This is done to prevent unnecessary output from being displayed. It is not required for our solutions to these exercises, however it is good practice to use it.__

__Alternatively__, if you are working in an interactive environment such as a __Jupyter notebook__, you could use a `;` after our plotting statements __to achieve the same effect__.

```
# Import necessary modules
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# loading the data into the pandas dataframe
iris = pd.read_csv('iris.csv')
iris.head()
```

      sepal length (cm)
      sepal width (cm)
      petal length (cm)
      petal width (cm)
      species

      0
      5.1
      3.5
      1.4
      0.2
      setosa

      1
      4.9
      3.0
      1.4
      0.2
      setosa

      2
      4.7
      3.2
      1.3
      0.2
      setosa

      3
      4.6
      3.1
      1.5
      0.2
      setosa

      4
      5.0
      3.6
      1.4
      0.2
      setosa

```
iris.isna().sum()
```

    sepal length (cm)    0
    sepal width (cm)     0
    petal length (cm)    0
    petal width (cm)     0
    species              0
    dtype: int64

```
print(iris.columns )
print(iris.species.value_counts())
print(iris.shape)
print(iris.species.value_counts().sum())
```

    Index(['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)',
           'petal width (cm)', 'species'],
          dtype='object')
    versicolor    50
    virginica     50
    setosa        50
    Name: species, dtype: int64
    (150, 5)
    150

```
iris.describe()
```

      sepal length (cm)
      sepal width (cm)
      petal length (cm)
      petal width (cm)

      count
      150.000000
      150.000000
      150.000000
      150.000000

      mean
      5.843333
      3.054000
      3.758667
      1.198667

      std
      0.828066
      0.433594
      1.764420
      0.763161

      min
      4.300000
      2.000000
      1.000000
      0.100000

      25%
      5.100000
      2.800000
      1.600000
      0.300000

      50%
      5.800000
      3.000000
      4.350000
      1.300000

      75%
      6.400000
      3.300000
      5.100000
      1.800000

      max
      7.900000
      4.400000
      6.900000
      2.500000

```python
versicolor_petal_length = df[df['species'] == 'Versicolor']['petal length (cm)']
setosa_petal_length = df[df['species'] == 'Setosa']['petal length (cm)']
virginica_petal_length = df[df['species'] == 'Virginica']['petal length (cm)']

```

```
# Display records only with species
versicolor = iris.loc[iris["species"] == "versicolor"]
virginica = iris.loc[iris["species"] == "virginica"]
setosa = iris.loc[iris["species"] == "setosa"]

print(versicolor.head())
print(virginica.head())
print(setosa.head())
```

        sepal length (cm)  sepal width (cm)  ...  petal width (cm)     species
    50                7.0               3.2  ...               1.4  versicolor
    51                6.4               3.2  ...               1.5  versicolor
    52                6.9               3.1  ...               1.5  versicolor
    53                5.5               2.3  ...               1.3  versicolor
    54                6.5               2.8  ...               1.5  versicolor

    [5 rows x 5 columns]
         sepal length (cm)  sepal width (cm)  ...  petal width (cm)    species
    100                6.3               3.3  ...               2.5  virginica
    101                5.8               2.7  ...               1.9  virginica
    102                7.1               3.0  ...               2.1  virginica
    103                6.3               2.9  ...               1.8  virginica
    104                6.5               3.0  ...               2.2  virginica

    [5 rows x 5 columns]
       sepal length (cm)  sepal width (cm)  ...  petal width (cm)  species
    0                5.1               3.5  ...               0.2   setosa
    1                4.9               3.0  ...               0.2   setosa
    2                4.7               3.2  ...               0.2   setosa
    3                4.6               3.1  ...               0.2   setosa
    4                5.0               3.6  ...               0.2   setosa

    [5 rows x 5 columns]

```
print(versicolor['petal length (cm)'].shape)
print(versicolor['petal length (cm)'].sum())
print(versicolor['petal length (cm)'].value_counts())
print(versicolor['petal length (cm)'].describe())
print(np.mean(versicolor['petal length (cm)']))
print(np.median(versicolor['petal length (cm)']))
```

    (50,)
    212.99999999999997
    4.5    7
    4.7    5
    4.0    5
    4.2    4
    4.4    4
    3.9    3
    4.1    3
    4.6    3
    4.8    2
    4.3    2
    4.9    2
    3.5    2
    3.3    2
    5.1    1
    3.8    1
    3.7    1
    3.6    1
    3.0    1
    5.0    1
    Name: petal length (cm), dtype: int64
    count    50.000000
    mean      4.260000
    std       0.469911
    min       3.000000
    25%       4.000000
    50%       4.350000
    75%       4.600000
    max       5.100000
    Name: petal length (cm), dtype: float64
    4.26
    4.35

```
# Calculating sum, mean and mode of a particular column
sum_data = iris['sepal length (cm)'].sum()
mean_data = iris['sepal length (cm)'].mean()
median_data = iris['sepal length (cm)'].median()

print("Sum:",sum_data, "\nMean:", mean_data, "\nMedian:",median_data)
```

    Sum: 876.5
    Mean: 5.843333333333335
    Median: 5.8

```
# Set default Seaborn style
sns.set()

# display records for versicolor petal length
versicolor_petal_length = versicolor['petal length (cm)']

# Plot histogram of versicolor petal lengths
versicolor_petal_length.plot(kind='hist')

# Label axes & Show histogram
plt.xlabel('petal length (cm)')
plt.ylabel('count')
plt.show()
```

```
# using the dummy varible
# Set default Seaborn style
sns.set()

# Plot histogram of versicolor petal lengths
_ = plt.hist(versicolor_petal_length)

# Label axes & Show histogram
plt.xlabel('petal length (cm)')
plt.ylabel('count')
plt.show()
```

### **Adjusting** the number of **bins** in a histogram

The histogram above has made had 10 `bins`. This is the default of matplotlib.

>The `"square root rule"` is a **commonly-used rule of thumb for choosing number of bins**: choose the number of bins to be the square root of the number of samples. Plot the histogram of **Iris versicolor petal** lengths again, this time using the square root rule for the number of bins. You specify the number of `bins` using the bins keyword argument of `plt.hist()`.

The plotting utilities are already imported and the seaborn defaults already set. The variable you defined in the last exercise, `versicolor_petal_length`, is already in your namespace.

```
# Import numpy
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# versicolor_petal_length to numpy array
versicolor_petal_length = np.array(versicolor_petal_length)
print('type of data {}'.format(type(versicolor_petal_length)))

# Compute number of data points: n_data
n_data = len(versicolor_petal_length)

# Number of bins is the square root of number of data points: n_bins
n_bins = np.sqrt(n_data)

# Convert number of bins to integer: n_bins
n_bins = int(n_bins)

# Plot the histogram
_ = plt.hist(versicolor_petal_length, bins=n_bins)

# Label axes
_ = plt.xlabel('petal length (cm)')
_ = plt.ylabel('count')

# Show histogram
plt.show()
```

```
import pylab

# data.hist calls data.plot
# pandas.DataFrame.plot() returns a matplotlib axis
iris.hist(
    column=['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)',
       'petal width (cm)', 'species'],figsize=(12, 10)
    #,sharey=True, sharex=True
    )
pylab.suptitle("Analyzing distribution for the series", fontsize="xx-large")

```

### Creating **bee swarm plot** with Seaborn's

```
plt.figure(figsize=(12, 7))

# Create bee swarm plot with Seaborn's default settings
sns.swarmplot(x='species', y='petal length (cm)', data=iris)

# Label the axes
plt.xlabel('species')
plt.ylabel('petal length (cm)')

# Show the plot
plt.show()
```

__Interpreting a bee swarm plot__

What conclusions could we draw from the above bee swarm plot of iris petal lengths?

>**virginica petals** tend to be the **longest**, and **setosa petals** tend to be the **shortest** of the three species.

### Computing the **ECDF**

__ECDFs are among the most important plots in statistical analysis.__

- Define a function with the signature `ecdf(data)`. Within the function definition,
    - Compute the number of data points, `n`, using the `len()` function.

    - The `x-values` are the sorted data. Use the `np.sort()` function to perform the sorting.

    - The  data of the ECDF go from `1/n` to `1` in equally spaced increments. You can construct this using `np.arange()`. Remember, however, that the end value in `np.arange()` is not inclusive. Therefore, `np.arange()` will need to go from `1` to `n+1`. Be sure to divide this by `n`.

    - The function returns the values `x` and `y`.

```
import numpy as np

def ecdf(data):
    """Compute ECDF for a one-dimensional array of measurements."""

    # Number of data points: n
    n = len(data)

    # x-data for the ECDF: x
    x = np.sort(data)

    # y-data for the ECDF: y
    y = np.arange(1, n+1) / n

    return x, y
```

### Plotting the **ECDF** for Iris dataset

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

versicolor_pl = versicolor['petal length (cm)']
print('type of data {}'.format(type(versicolor_pl)))
versicolor_petal_length = np.array(versicolor_pl)
print('type of data {}'.format(type(versicolor_petal_length)))

def ecdf(data):
    """Compute ECDF for a one-dimensional array of measurements."""

    # Number of data points: n
    n = len(data)

    # x-data for the ECDF: x
    x = np.sort(data)

    # y-data for the ECDF: y
    y = np.arange(1, n+1) / n

    return x, y

# Compute ECDF for versicolor data: x_vers, y_vers
x_vers, y_vers = ecdf(versicolor_petal_length)

plt.figure(figsize=(12, 7))

# Generate plot
_ = plt.plot(x_vers, y_vers, marker = '.', linestyle = 'none')

# Make the margins nice
plt.margins(0.02)

# Label the axes
_ = plt.xlabel('length')
_ = plt.ylabel('ECDF')

# Display the plot
plt.show()
```

### Comparison of **ECDFs**

ECDFs also allow us to compare two or more distributions (though plots get cluttered if we have too many).

Here, we will plot ECDFs for the petal lengths of all three iris species. we have already wrote a function to generate ECDFs so we can put it to good use!

To overlay all three ECDFs on the same plot, we can use `plt.plot()` three times, once for each ECDF. Remember to include `marker='.'` and `linestyle='none'` as arguments inside `plt.plot()`.

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

# display records for versicolor petal length(Pandas Series)
setosa_pl = setosa['petal length (cm)']
versicolor_pl = versicolor['petal length (cm)']
virginica_pl = virginica['petal length (cm)']

# Changing the data from pandas.series to numpy array
setosa_petal_length = np.array(setosa_pl)
versicolor_petal_length = np.array(versicolor_pl)
virginica_petal_length = np.array(virginica_pl)

# ECDF function
def ecdf(data):
    """Compute ECDF for a one-dimensional array of measurements."""

    # Number of data points: n
    n = len(data)

    # x-data for the ECDF: x
    x = np.sort(data)

    # y-data for the ECDF: y
    y = np.arange(1, n+1) / n

    return x, y

# Compute ECDFs
x_set, y_set = ecdf(setosa_petal_length)
x_vers, y_vers = ecdf(versicolor_petal_length)
x_virg, y_virg = ecdf(virginica_petal_length)

plt.figure(figsize=(12, 7))

# Plot all ECDFs on the same plot
_ = plt.plot(x_set, y_set, marker = 'o', linestyle = 'none')
_ = plt.plot(x_vers, y_vers, marker = 'o', linestyle = 'none')
_ = plt.plot(x_virg, y_virg, marker = 'o', linestyle = 'none')

# Make nice margins
plt.margins(0.02)

# Annotate the plot
plt.legend(('setosa', 'versicolor', 'virginica'), loc='lower right')
_ = plt.xlabel('petal length (cm)')
_ = plt.ylabel('ECDF')

# Display the plot
plt.show()
```

The **ECDFs** expose clear differences among the species. Setosa is much shorter, also with less absolute variability in petal length than versicolor and virginica

## __2 Summary Statistics: The sample Mean and Median__

### Computing **mean** & **median**

The mean of all measurements gives an indication of the typical magnitude of a measurement. It is computed using `np.mean()`.

```
print(iris.columns )
```

    Index(['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)',
           'petal width (cm)', 'species'],
          dtype='object')

```
# Compute the mean: mean_length_vers
mean_length_vers = np.mean(versicolor_petal_length)
median__vers = np.median(versicolor_petal_length)

# Print the result with some nice formatting
print('versicolor mean petal length = {} cm'.format(mean_length_vers))
print('versicolor median petal length = {} cm'.format(median__vers))
```

    versicolor mean petal length = 4.26 cm
    versicolor median petal length = 4.35 cm

### Computing **percentiles**

```
# Specify array of percentiles: percentiles
percentiles = np.array([2.5, 25, 50, 75, 97.5])

# Compute percentiles: ptiles_vers
ptiles_vers = np.percentile(versicolor_petal_length, percentiles)

# Print the result
print(ptiles_vers)
```

    [3.3    4.     4.35   4.6    4.9775]

### Comparing **percentiles** to **ECDF**

```
# importing modules
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# Display records only with species
versicolor = iris.loc[iris["species"] == "versicolor"]
versicolor_pl = versicolor['petal length (cm)']   # pandas.core.series.Series
versicolor_petal_length = np.array(versicolor_pl) # Converting it to numpy.ndarray

# Computing the ECDF
def ecdf(data):
    """Compute ECDF for a one-dimensional array of measurements."""

    # Number of data points: n
    n = len(data)

    # x-data for the ECDF: x
    x = np.sort(data)

    # y-data for the ECDF: y
    y = np.arange(1, n+1) / n

    return x, y

# Compute ECDF for versicolor data: x_vers, y_vers
x_vers, y_vers = ecdf(versicolor_petal_length)

# Set default Seaborn style
sns.set()

# figure size
plt.figure(figsize=(12, 7))

# Generate plot
_ = plt.plot(x_vers, y_vers,
             marker = 'o', color='tab:blue',
             linestyle = 'none', alpha=0.5,
             label='ECDF')

# Overlay percentiles as red diamonds.
_ = plt.plot(ptiles_vers,
             percentiles / 100,
             marker='D', color='tab:red',
             linestyle='none', label='percentiles point')

# Make the margins nice
plt.margins(0.02)

# Label the axes
_ = plt.xlabel('length')
_ = plt.ylabel('ECDF')
_ = plt.legend()

# Display the plot
plt.show()
```

### **Box** and **whisker plot**
**bold text**
Making a box plot for the petal lengths is unnecessary because the iris data set is not too large and the bee swarm plot works fine. However, it is always good to get some practice. Make a box plot of the iris petal lengths. You have a pandas DataFrame, df, which contains the petal length data, in your namespace. Inspect the data frame df in the IPython shell using ```df.head()``` to make sure you know what the pertinent columns are.

For your reference, the code used to produce the box plot in the video is provided below:
```python
_ = sns.boxplot(x='east_west', y='dem_share', data=df_all_states)
_ = plt.xlabel('region')
_ = plt.ylabel('percent of vote for Obama')
```
In the IPython Shell, you can use ```sns.boxplot?``` or ```help(sns.boxplot)``` for more details on how to make box plots using seaborn.

```
# Figure size
plt.figure(figsize=(12, 10))

# Create box plot with Seaborn`s default settings
_ = sns.boxplot(x='species', y='petal length (cm)', data=iris)

# Label the axes
_ = plt.xlabel('species')
_ = plt.ylabel('petal length (cm)')
```

```
# Combined Box and swarmplot
# Figure size
plt.figure(figsize=(12, 10))

# Create bee swarm plot with Seaborn's default settings
_ = sns.boxplot(x='species', y='petal length (cm)', data=iris, whis=np.inf)
_ = sns.swarmplot(x='species', y='petal length (cm)', data=iris, color=".2")

# Label the axes
_ = plt.xlabel('species')
_ = plt.ylabel('petal length (cm)')

# Show the plot
plt.show()
```

### **Variance** and **standard deviation**

### Computing the **variance**

>__Variance__ is the mean **squared distance** of the data from their **mean**. Informally, a measure of the ***spread of data***.

$$ variance = \dfrac{1}{n}\sum^{n}_{i=1}(x_i - \bar{x})^2 $$

```
# Array of differences to mean: differences
differences = np.array(versicolor_petal_length - np.mean(versicolor_petal_length))

# Square the differences: diff_sq
diff_sq = differences ** 2

# Compute the mean square difference: variance_explicit
variance_explicit = np.mean(diff_sq)

# Compute the variance using NumPy: variance_np
variance_np = np.var(versicolor_petal_length)

# Print the results
print('Variance Explict {0:.4f}'.format(variance_explicit))
print('Variance Numpy {0:.4f}'.format(variance_np))
```

    Variance Explict 0.2164
    Variance Numpy 0.2164

### The **standard deviation** and the **variance**

>__Standard Deviation__  is a measure that is used to **quantify** the amount of ***variation*** or ***dispersion*** of a set of data values.

$$ Standard Deviation = \sqrt{\dfrac{\sum(x_i - \bar{x})^2}{n - 1}} $$

```
# Compute the variance: variance
variance = np.var(versicolor_petal_length)

# Print the square root of the variance
print('Variance {0:.4f}'.format(np.sqrt(variance)))

# Print the standard deviation
print('Standard Deviation {0:.4f}'.format(np.std(versicolor_petal_length)))
```

    Variance 0.4652
    Standard Deviation 0.4652

### **Covariance** and the **Pearson correlation coefficient**

```
versicolor = iris.loc[iris["species"] == "versicolor"]
versicolor_petal_length = versicolor['petal length (cm)']
versicolor_petal_width = versicolor['petal width (cm)']

print('versicolor petal length mean {0:.2f}'.format(versicolor_petal_length.mean()))
print('versicolor petal width mean {0:.2f}'.format(versicolor_petal_width.mean()))

# Set default Seaborn style
sns.set()

# figure size
plt.figure(figsize=(12, 7))

# vertical mean line
plt.axvline(versicolor_petal_length.mean(), color='red',
            linestyle='dashed', linewidth=1,
            label='versicolor petal length mean')

# horizontal mean line
plt.axhline(versicolor_petal_width.mean(), color='k',
            linestyle='dashed', linewidth=1,
            label='versicolor petal width mean')

# Make a scatter plot
_ = plt.plot(versicolor_petal_length, versicolor_petal_width,
             marker='o', linestyle='none')

# Set margins
_ = plt.margins(0.02)

# Label the axes
_ = plt.xlabel('versicolor petal length')
_ = plt.ylabel('versicolor petal width')
_ = plt.title('plot how I.versicolor petal length and width are related')
_ = plt.legend()

# Show the result
plt.show()
```

#### __Covariance__

it is the measure of how the two quantities vary together.

- Covariance is a measure of the relationship between two random variables and to what extent, they change together. Or we can say, in other words, it defines the changes between the two variables, such that change in one variable is equal to change in another variable.

- If the covariance for any two variables is positive, that means, both the variables move in the same direction. Here, the variables show similar behaviour.

$$ covariance = \dfrac{1}{n}\sum^{n}_{i=1}(x_i - \bar{x})(y_i - \bar{y})$$

### Variance and covariance by looking

Consider four scatter plots of $x-y$ data, appearing to the right. Which has, respectively,

- the highest variance in the variable $x$,
- the highest covariance,
- negative covariance?

$\color{red}{\textbf{Answer:}}$ __(d, c, b)__

#### Computing the covariance

The covariance may be computed using the Numpy function `np.cov()`. For example, we have two sets of data `x` and `y`, `np.cov(x, y)` returns a `2D` array where entries `[0,1]` and `[1,0]` are the covariances.

Entry `[0,0]` is the **variance** of the data in x, and entry `[1,1]` is the variance of the data in y.

This `2D` output array is called the __covariance matrix__, since it organizes the self- and covariance.

```
versicolor = iris.loc[iris["species"] == "versicolor"]
versicolor_petal_length = versicolor['petal length (cm)']
versicolor_petal_width = versicolor['petal width (cm)']

# Compute the covariance matrix: covariance_matrix
covariance_matrix = np.cov(versicolor_petal_length, versicolor_petal_width)

# Print covariance matrix
print(covariance_matrix)

# Extract covariance of length and width of petals: petal_cov
petal_cov = covariance_matrix[0, 1]

# Print the length/width covariance
print(petal_cov)
```

    [[0.22081633 0.07310204]
     [0.07310204 0.03910612]]
    0.07310204081632653

```
# 3D scatter plot
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(12, 7))
ax = fig.add_subplot(111, projection='3d')

ax.scatter(iris['petal width (cm)'],
           iris['petal length (cm)'],
           iris['sepal length (cm)'],
           color='tab:red')

ax.set_xlabel('PetalWidthCm')
ax.set_ylabel('PetalLengthCm')
ax.set_zlabel('SepalLengthCm')

plt.tight_layout(pad=0.5)
plt.show()
```

```

```

#### __Pearson correlation coefficient__

We can compute the **covariance** using built-in NumPy functions. However, if we want to have a more **generally applicable measure** of **how two variables depend on each other**, we want it to be **dimensionless**, that is to not have any units.

So, we can divide the covariance by the ***standard deviations of the x and y variables***. This is called the Pearson correlation coefficient, usually denoted by the Greek letter rho.

#### ___Why is the Pearson correlation coefficient important?___

It helps in knowing **how strong the relationship between the two variables** is. Not only the presence or the absence of the correlation between the two variables is indicated using the Pearson Correlation Coefficient but it also determines the **exact extent to which those variables are correlated**.

The **Pearson coefficient correlation** has a **high statistical significance**. It looks at the relationship between two variables. It seeks to draw a line through the data of two variables to show their relationship. The relationship of the variables is measured with the help Pearson correlation coefficient calculator.

$$ \begin{align} \rho &= \text{Pearson correlation} = \dfrac{\text{covariance}}{(\text{std of x})(\text{std of y})} \\ &= \dfrac{\text{variability due to codependence}}{\text{independent variability}} \end{align}$$

__Pandas__

Example
```python
>>> x.corr(y)                     # Pearson's r
0.7586402890911867
>>> y.corr(x)
0.7586402890911869
>>> x.corr(y, method='spearman')  # Spearman's rho
0.9757575757575757
>>> x.corr(y, method='kendall')   # Kendall's tau
```

__Scipy__

Example
```python
>>> import numpy as np
>>> import scipy.stats
>>> x = np.arange(10, 20)
>>> y = np.array([2, 1, 4, 5, 8, 12, 18, 25, 96, 48])
>>> scipy.stats.pearsonr(x, y)    # Pearson's r
(0.7586402890911869, 0.010964341301680832)
>>> scipy.stats.spearmanr(x, y)   # Spearman's rho
SpearmanrResult(correlation=0.9757575757575757, pvalue=1.4675461874042197e-06)
>>> scipy.stats.kendalltau(x, y)  # Kendall's tau
KendalltauResult(correlation=0.911111111111111, pvalue=2.9761904761904762e-05)

```

|Pearson’s r Value|Correlation Between x and y|
|-----------------|---------------------------|
|equal to 1|perfect positive linear relationship|
|greater than 0|positive correlation|
|equal to 0|independent|
|less than 0|negative correlation|
|equal to -1|perfect negative linear relationship|

>In short, a **larger absolute value of r** indicates **stronger correlation**, closer to a linear function. A **smaller absolute value of r** indicates weaker correlation.

#### __Computing the Pearson correlation coefficient__

The **Pearson correlation coefficient**, also called the ***Pearson r***, is often **easier to interpret** than **Covariance**.

It is computed using the `np.corrcoef()` function. Like `np.cov()`, it **takes two arrays** as arguments and returns a 2D array. Entries `[0,0]` and `[1,1]` are necessarily equal to `1` (**think why?**), and the value we are after is entry `[0,1]`.

In this exercise, we will write a function, `pearson_r(x, y)` that takes in **two arrays** and **returns the Pearson correlation coefficient**. You will then use this function to compute it for the petal lengths and widths of I. versicolor.

Again, we include the scatter plot you generated in a previous exercise to remind you how the petal width and length are related.

__Instructions__

- Define a function with signature `pearson_r(x, y)`.

    - Use `np.corrcoef()` to compute the correlation matrix of x and y (pass them to np.corrcoef() in that order).

    - The function returns entry `[0,1]` of the correlation matrix.

- Compute the Pearson correlation between the data in the arrays `versicolor_petal_length` and `versicolor_petal_width`. Assign the result to r.

- Print the result.

```
def pearson_r(x, y):
    """Compute Pearson correlation coefficient between two arrays

    Args:
        x: arrays
        y: arrays

    returns:
        r: int
    """
    # Compute correlation matrix: corr_mat
    corr_mat = np.corrcoef(x, y)

    # Return entry[0, 1]
    return corr_mat[0, 1]

# Compute Pearson correlation coefficient for I. versicolor: r
r = pearson_r(versicolor_petal_length, versicolor_petal_width)

# Print the result
print(r)
```

    0.7866680885228169


```python
import numpy as np
```

```python
%%markdown
# Recursion - Examples
## Factorial
\begin{align}
!n &= !(n-1).n \\
!0 &= 1
\end{align}
```

# Recursion - Examples
## Factorial
\begin{align}
!n &= !(n-1).n \\
!0 &= 1
\end{align}

```python
def factorial (n):
    if (n >= 1):
        return (n-1)*n
    else:
        return 1

```

```python
n = 4
print ("Factorial of ", n, factorial(n))
```

    Factorial of  4 12

```python
%%markdown
## Determinant of a matrix
* [Laplace's formula](http://en.wikipedia.org/wiki/Determinant#Laplace's_formula_and_the_adjugate_matrix)
\begin{align}
\det(A)=\sum _{j=1}^{n}(-1)^{i+j}a_{i,j}M_{i,j} \text{ for a fixed } i
\end{align}
* With $i = 1$
\begin{align}
\det(A)=\sum _{j=1}^{n}(-1)^{j+1}a_{1,j}M_{1,j}
\end{align}
```

## Determinant of a matrix
* [Laplace's formula](http://en.wikipedia.org/wiki/Determinant#Laplace's_formula_and_the_adjugate_matrix)
\begin{align}
\det(A)=\sum _{j=1}^{n}(-1)^{i+j}a_{i,j}M_{i,j} \text{ for a fixed } i
\end{align}
* With $i = 1$
\begin{align}
\det(A)=\sum _{j=1}^{n}(-1)^{j+1}a_{1,j}M_{1,j}
\end{align}

```python
%%markdown
A = \begin{bmatrix}
 -2 & 2 & -3 \\
 -1 & 1 &  3 \\
  2 & 0 & -1
\end{bmatrix}

along the second column ($j = 2$ and the sum runs over $i$) is given by,
\begin{align}
det(A) &= (-1)^{1+2} \cdot 2 \cdot \begin{vmatrix}-1&3 \\ 2&-1\end{vmatrix} + (-1)^{2+2} \cdot 1 \cdot \begin{vmatrix} -2&-3 \\ 2&-1 \end{vmatrix} + (-1)^{3+2} \cdot 0 \cdot \begin{vmatrix} -2&-3\\-1&3\end{vmatrix} \\
 &= (-2) \cdot ((-1) \cdot (-1) - 2 \cdot 3) + 1 \cdot ((-2) \cdot (-1)-2 \cdot (-3)) \\
 &= (-2) \cdot (-5)+8=18.
\end{align}

along the first column ($j = 1$ and the sum runs over $i$) is given by,
\begin{align}
det(A) &= (-1)^{1+1} \cdot -2 \cdot \begin{vmatrix} 1&3 \\ 0&-1\end{vmatrix} + (-1)^{2+1} \cdot -1 \cdot \begin{vmatrix} 2&-3 \\ 0&-1 \end{vmatrix} + (-1)^{3+1} \cdot 2 \cdot \begin{vmatrix} 2&-3\\1&3\end{vmatrix} \\
 &= -2 \cdot (1 \cdot (-1)) + 1 \cdot (2 \cdot (-1)) + 2 \cdot (2 \cdot 3 - 1 \cdot (-3)) \\
 &= -2 \cdot (-1) + 1 \cdot (-2 ) + 2 \cdot (6 + 3) \\
 &= 2 -2 + 2 \cdot 9 = 18.
\end{align}
```

A = \begin{bmatrix}
 -2 & 2 & -3 \\
 -1 & 1 &  3 \\
  2 & 0 & -1
\end{bmatrix}

along the second column ($j = 2$ and the sum runs over $i$) is given by,
\begin{align}
det(A) &= (-1)^{1+2} \cdot 2 \cdot \begin{vmatrix}-1&3 \\ 2&-1\end{vmatrix} + (-1)^{2+2} \cdot 1 \cdot \begin{vmatrix} -2&-3 \\ 2&-1 \end{vmatrix} + (-1)^{3+2} \cdot 0 \cdot \begin{vmatrix} -2&-3\\-1&3\end{vmatrix} \\
 &= (-2) \cdot ((-1) \cdot (-1) - 2 \cdot 3) + 1 \cdot ((-2) \cdot (-1)-2 \cdot (-3)) \\
 &= (-2) \cdot (-5)+8=18.
\end{align}

along the first column ($j = 1$ and the sum runs over $i$) is given by,
\begin{align}
det(A) &= (-1)^{1+1} \cdot -2 \cdot \begin{vmatrix} 1&3 \\ 0&-1\end{vmatrix} + (-1)^{2+1} \cdot -1 \cdot \begin{vmatrix} 2&-3 \\ 0&-1 \end{vmatrix} + (-1)^{3+1} \cdot 2 \cdot \begin{vmatrix} 2&-3\\1&3\end{vmatrix} \\
 &= -2 \cdot (1 \cdot (-1)) + 1 \cdot (2 \cdot (-1)) + 2 \cdot (2 \cdot 3 - 1 \cdot (-3)) \\
 &= -2 \cdot (-1) + 1 \cdot (-2 ) + 2 \cdot (6 + 3) \\
 &= 2 -2 + 2 \cdot 9 = 18.
\end{align}

```python
def extractMij(A, i, verbose = False):
    """Extract the minor M_ij of A, where we take j=0"""
    n = len(A)
    M0 = A[:, np.r_[1:n]]
    Mi0 = None

    if (i == 0):
        Mi0 = M0[list(range(1, n)), :]
    else:
        Mi0 = M0[list(range(0, i)) + list(range(i+1, n)), :]
    return Mi0

```

```python
def determinant(A, verbose = False):
    """Calculate the determinant of the A matrix, according to the Lagrange algorithm.
       Note that the matrix indices in classical litterature (including in the
       reference document, ie Wikipedia) are usually ranging between 1 and n,
       where as the indices in NumPy are ranging between 0 and n-1.
       Hence, the variable is here called jm1 (standing for j minus -1).
       We select the first column (jm1=0), and go through the rows."""
    n = len(A)
    det = 0

    if (verbose == True):
        print("A (size = %s): \n" % n, str(A))
        print("Determinant of A: ", determinant(A))

    if (n == 2):
        det = (A[0, 0] * A[1, 1] - A[1, 0] * A[0, 1])
    else:
        for im1 in range(0, n):
            Mij = extractMij(A, im1)
            aij = A[im1, 0]
            det += (-1)**im1 * aij * determinant(Mij)

            if (verbose == True):
                print("(-1)**(i+j), aij, det(Mij) for (i, j) = (%s, 1): " % i, (-1)**i, aij, determinant(Mij))
                print("Mij for (i, j) = (%s, 1): \n" % i, str(Mij))
    return det
```

```python
A = np.matrix([[-2, 2, -3], [-1, 1, 3], [2, 0, -1]])
determinant(A, verbose=True)
```

    A (size = 3):
     [[-2  2 -3]
     [-1  1  3]
     [ 2  0 -1]]
    Determinant of A:  18
    (-1)**(i+j), aij, det(Mij) for (i, j) = (2, 1):  1 -2 -1
    Mij for (i, j) = (2, 1):
     [[ 1  3]
     [ 0 -1]]
    (-1)**(i+j), aij, det(Mij) for (i, j) = (2, 1):  1 -1 -2
    Mij for (i, j) = (2, 1):
     [[ 2 -3]
     [ 0 -1]]
    (-1)**(i+j), aij, det(Mij) for (i, j) = (2, 1):  1 2 9
    Mij for (i, j) = (2, 1):
     [[ 2 -3]
     [ 1  3]]

    18

```python
A = np.random.randint(1, 10, size=(5, 5))
determinant(A)
```

    5469

```python

```


# 最小二乗法に基づいたオドメトリのキャリブレーション

## 条件
対向二輪型ロボット

## 数式

左右車輪の角速度から、ロボット中心の速度と角速度を計算

$$
\begin{equation}
	\left[
		\begin{array}{c}
			\upsilon \\
			\omega
		\end{array}
	\right]
	= \boldsymbol{C}\left[
		\begin{array}{c}
			\omega_{R} \\
			\omega_{L}
		\end{array}
	\right]
\end{equation}
$$

推定したいオドメトリのパラメータ

$$
\begin{equation}
	\small
	\boldsymbol{C}
	= \left[
		\begin{array}{cc}
			\displaystyle \frac{r_{R}}{2} &  \displaystyle \frac{r_{L}}{2} \\
			\displaystyle \frac{r_{R}}{b} & \displaystyle -\frac{r_{L}}{b}
			%r_{R}/2 &  r_{L}/2 \\
			%r_{R}/b & -r_{L}/b
		\end{array}
	\right]
\end{equation}
$$

最小二乗法を用いてパラメータ推定１

\begin{equation}
	\left[
		\begin{array}{c}
			c_{2,1} \\
			c_{2,2}
		\end{array}
	\right]
	= (\boldsymbol{{\it \overline{\Phi}^{\mathrm{T}}_{\theta}}}
	\boldsymbol{{\it \overline{\Phi}_{\theta}}})^{-1}
	\boldsymbol{{\it \overline{\Phi}^{\mathrm{T}}_{\theta}}}
	\left[
		\begin{array}{c}
			\theta_{N,1} - \theta_{0,1} \\
			\vdots \\
			\theta_{N,P} - \theta_{0,P}
		\end{array}
	\right]
\end{equation}

\begin{equation}
	\boldsymbol{{\it \Phi}} = T\left[
		\begin{array}{cc}
			\displaystyle \sum_{i=0}^{N-1}\omega_{R,i} & \displaystyle \sum_{i=0}^{N-1}\omega_{L,i} \\
		\end{array}
	\right]
\end{equation}

\begin{equation}
\boldsymbol{{\it \overline{\Phi}_{\theta}}} = \left[
		\begin{array}{c}
			\boldsymbol{{\it \Phi_{1}}} \\
			\vdots \\
			\boldsymbol{{\it \Phi_{P}}}
		\end{array}
	\right]
\end{equation}

最小二乗法を用いてパラメータ推定２

\begin{equation}
	\left[
		\begin{array}{c}
			c_{1,1} \\
			c_{1,2}
		\end{array}
	\right]
	= (\boldsymbol{{\it \overline{\Phi}^{\mathrm{T}}_{xy}}}
	\boldsymbol{{\it \overline{\Phi}_{xy}}})^{-1}
	\boldsymbol{{\it \overline{\Phi}^{\mathrm{T}}_{xy}}}
	\left[
		\begin{array}{c}
			x_{N,1} - x_{0,1} \\
			\vdots \\
			x_{N,P} - x_{0,P}
		\end{array}
	\right]
\end{equation}

\begin{equation}
	\boldsymbol{{\it \Phi}}
	= T\left[
		\begin{array}{cc}
		\displaystyle \sum_{i=0}^{N-1}\omega_{R,i}\cos({\theta_{i} + T\omega_{i}/2})
		& \displaystyle \sum_{i=0}^{N-1}\omega_{L,i}\cos({\theta_{i} + T\omega_{i}/2}) \\
		\displaystyle \sum_{i=0}^{N-1}\omega_{R,i}\sin({\theta_{i} + T\omega_{i}/2})
		& \displaystyle \sum_{i=0}^{N-1}\omega_{L,i}\sin({\theta_{i} + T\omega_{i}/2})
		\end{array}
	\right]
\end{equation}

\begin{equation}
\boldsymbol{{\it \overline{\Phi}_{xy}}} = \left[
		\begin{array}{c}
			\boldsymbol{{\it \Phi_{1}}} \\
			\vdots \\
			\boldsymbol{{\it \Phi_{P}}}
		\end{array}
	\right]
\end{equation}

```python
# 描画のために必要になるもの
import matplotlib
matplotlib.use('nbagg')
import matplotlib.animation as anm
import matplotlib.pyplot as plt
import matplotlib.patches as patches

# 計算で必要になるもの
import numpy as np
import math
from scipy.stats import expon, norm
```

```python
# 世界座標系におけるオブジェクト（ロボットやランドマーク）の管理を行うクラス
class World:
    def __init__(self, time_span, time_interval, debug=False):
        self.objects = [] # オブジェクトを入れる配列
        self.debug = debug # デバッグ用フラグ
        self.time_span = time_span # シミュレーション時間[s]
        self.time_interval = time_interval # サンプリング間隔

    # 配列にオブジェクトを追加する関数
    def append(self, obj):
        self.objects.append(obj)

    # 図の設定を行い、オブジェクトを描画する関数
    def draw(self):
        fig = plt.figure(figsize=(8,8))
        ax = fig.add_subplot(111)
        ax.set_aspect('equal')
        ax.set_xlim(-5,5)
        ax.set_ylim(-5,5)
        ax.set_xlabel("X", fontsize=20)
        ax.set_ylabel("Y", fontsize=20)

        # オブジェクトリスト
        elems = []

        #debug:Trueならばエラーメッセージが出力される
        if self.debug: # アニメーションなしで処理
            for i in range(1000):
                self.one_step(i, elems, ax)
        else: # アニメーションありで処理, frames:アニメのフレーム数（何回更新するか）、interval:フレーム間の時間
            self.ani = anm.FuncAnimation(fig, self.one_step, fargs=(elems, ax), frames=int(self.time_span/self.time_interval)+1,
                                         interval=int(self.time_interval*1000), repeat=False)
            plt.show()

    # アニメーションの1ステップの処理、iはステップ番号、elemsはオブジェクトリスト、axはサブプロット
    def one_step(self, i, elems, ax):
        # elemsの中身をすべて破棄
        while elems:
            elems.pop().remove()
        # 時間テキストを追加
        time_str = "t = %.2f[s]" % (self.time_interval*i)
        elems.append(ax.text(-4.4, 4.5, time_str, fontsize=10))
        # オブジェクトを描画
        for obj in self.objects:
            obj.draw(ax, elems)
            if hasattr(obj, "one_step"):
                obj.one_step(1.0)
```

```python
# 標準的なロボットクラス
class IdealRobot:
    # 初期化する関数
    def __init__(self, name="Name", wrl=np.array([[0.0,0.0]]).T, pose=np.array([0.0,0.0,0.0]).T, sensor=None, color="black", isBias=False):
        # ロボットの情報を初期化（位置姿勢、半径、色、エージェント、poseリスト）
        self.pose = pose
        self.r = 0.2
        self.color = color
        self.poses = [pose]
        self.sensor = sensor
        # 左右車輪の角速度からロボットの中心の速度と角速度を求める
        self.name = name
        self.rR = 0.5
        self.rL = 0.5
        self.B = 1.0
        self.wrl = wrl
        self.sum_wrl = np.array([0.0, 0.0])
        self.sum_wrl_xy = np.array([[0.0, 0.0], [0.0, 0.0]])
        self.c = np.array([[self.rR/2.0,self.rL/2.0],[self.rR/self.B,-1.0*self.rL/self.B]])
        self.nw = np.array([0.0, 0.0])
        self.isBias = isBias
        # リスト
        self.sum_wrl_s = []
        self.sum_wrl_xy_s = []
        self.theta_s = []
        self.xy_s = []

    def set_wrl(self, wrl):
        self.wrl = wrl

    def set_isBias(self, isBias):
        self.isBias = isBias

    def init_func(self):
        self.pose = np.array([0.0, -4.0, math.pi/2.0]).T
        self.nw = np.array([0.0, 0.0])
        self.poses = [self.pose]
        self.sum_wrl = np.array([0.0, 0.0])
        self.sum_wrl_xy = np.array([[0.0, 0.0], [0.0, 0.0]])

    # ロボットを描画する関数
    def draw(self, ax, elems):
        # ロボットの情報を用いて円を作成（向きに合わせて直線も引いている）
        x, y, theta = self.pose
        xn = x + self.r * math.cos(theta)
        yn = y + self.r * math.sin(theta)
        elems += ax.plot([x,xn], [y,yn], color=self.color)
        c = patches.Circle(xy=(x,y), radius=self.r, fill=False, color=self.color)
        # 引数のサブプロットへロボットを追加
        elems.append(ax.add_patch(c))

        # ax.plotはリストを返すので+=にする
        self.poses.append(self.pose)
        elems += ax.plot([e[0] for e in self.poses], [e[1] for e in self.poses], linewidth=0.5, color="black")

    # 左右車輪の角速度wrlから速度と角速度nwを計算
    def nu_omega(self):
        self.nw = np.dot(self.c, self.wrl.T)
        nu = self.nw[0]
        omega = self.nw[1]
        if self.isBias:
            nu = nu * 1.1
        return nu, omega

    def one_step(self, time_interval):
        nu, omega = self.nu_omega()
        self.pose = self.state_transition(nu, omega, time_interval, self.pose)
        self.sum_wrl += self.wrl
        self.sum_wrl_xy[0][0] += self.wrl[0] * math.cos(self.pose[2] + 0.1*omega/2.0)
        self.sum_wrl_xy[0][1] += self.wrl[1] * math.cos(self.pose[2] + 0.1*omega/2.0)
        self.sum_wrl_xy[1][0] += self.wrl[0] * math.sin(self.pose[2] + 0.1*omega/2.0)
        self.sum_wrl_xy[1][1] += self.wrl[1] * math.sin(self.pose[2] + 0.1*omega/2.0)

    # @classmethodをつけることで外部から関数だけ呼び出せる
    @classmethod
    def state_transition(cls, nu, omega, time, pose):
        t0 = pose[2]
        if math.fabs(omega) < 1e-10:
            return pose + np.array([nu*math.cos(t0),
                                     nu*math.sin(t0),
                                     omega]) * time
        else:
            return pose + np.array([nu/omega*(math.sin(t0+omega*time) - math.sin(t0)),
                                    nu/omega*(-math.cos(t0+omega*time) + math.cos(t0)),
                                    omega*time])

    def save_data(self):
        self.sum_wrl_s.append(self.sum_wrl*0.1)
        print(self.sum_wrl_s)
        self.sum_wrl_xy_s.append(self.sum_wrl_xy[0]*0.1)
        self.sum_wrl_xy_s.append(self.sum_wrl_xy[1]*0.1)
        self.theta_s.append(np.array([self.poses[-1][2] - self.poses[0][2]]))
        self.xy_s.append(np.array([self.poses[-1][0] - self.poses[0][0]]))
        self.xy_s.append(np.array([self.poses[-1][1] - self.poses[0][1]]))

    def calibration(self):
        Phi_theta = np.array(self.sum_wrl_s)
        a1 = np.dot(Phi_theta.T, Phi_theta)
        a2 = np.linalg.inv(a1)
        a3 = np.dot(a2, Phi_theta.T)
        a4 = np.dot(a3, np.array(self.theta_s))
        self.c[1][0] = a4[0] / 10.0
        self.c[1][1] = a4[1] / 10.0

        Phi_xy = np.array(self.sum_wrl_xy_s)
        b1 = np.dot(Phi_xy.T, Phi_xy)
        b2 = np.linalg.inv(b1)
        b3 = np.dot(b2, Phi_xy.T)
        b4 = np.dot(b3, np.array(self.xy_s))
        self.c[0][0] = b4[0][0] / 10.0
        self.c[0][1] = b4[1][0] / 10.0

        print(self.c)
```

```python
# time_span:シミュレート総時間、time_interval:サンプリング間隔
world = World(5, 0.1)

# オドメトリと実際の移動経路
odom_wrl = np.array([math.pi/14.99, math.pi/15.00])
act_wrl = np.array([math.pi/16.00, math.pi/15.00])
odometry = IdealRobot(name="odom", wrl=odom_wrl, pose=np.array([0.0, -4.0, math.pi/2.0]).T, sensor=None, color="red", isBias=False)
actual = IdealRobot(name="act", wrl=act_wrl, pose=np.array([0.0, -4.0, math.pi/2.0]).T, sensor=None, color="blue", isBias=True)

world.append(odometry)
world.append(actual)

world.draw()
```

    <IPython.core.display.Javascript object>

```python
actual.save_data()

odometry.init_func()
actual.init_func()

odometry.set_wrl(np.array([math.pi/14.99, math.pi/15.00]))
actual.set_wrl(np.array([math.pi/15.9, math.pi/15.00]))

world.draw()
```

    [array([ 1.02101761,  1.08908545])]

    <IPython.core.display.Javascript object>

```python
actual.save_data()

odometry.init_func()
actual.init_func()

odometry.set_wrl(np.array([math.pi/14.99, math.pi/15.00]))
actual.set_wrl(np.array([math.pi/16.10, math.pi/15.00]))

world.draw()
```

    [array([ 1.02101761,  1.08908545]), array([ 1.02743911,  1.08908545])]

    <IPython.core.display.Javascript object>

```python
actual.save_data()
```

    [array([ 1.02101761,  1.08908545]), array([ 1.02743911,  1.08908545]), array([ 1.01467589,  1.08908545])]

```python
actual.calibration()
```

    [[ 0.26709483  0.27224815]
     [ 0.49038462 -0.49038462]]

```python
odometry.init_func()
actual.init_func()
world.draw()
```

    <IPython.core.display.Javascript object>

```python

```