# Cooling Crust Demonstration, INT Workshop 16-2b
Edward Brown
20 June 2016

```python
%matplotlib inline
from __future__ import print_function, division
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
import scipy.constants as sc

# class to read in dStar output files
from reader import dStarCrustReader, dStarReader

# plot format
charsize=14
major_ticklength=0.6*charsize
major_tickwidth=0.9
minor_ticklength=0.3*charsize
minor_tickwidth=0.7
rc('mathtext',**{'fontset':'stixsans'})
rc('font',**{'size':charsize,'sans-serif':'Bitstream Vera Sans'})
rc('axes',**{'titlesize':charsize,'labelsize':charsize})
rc('xtick',**{'major.size':major_ticklength,
              'major.width':major_tickwidth,'minor.size':minor_ticklength,
              'minor.width':minor_tickwidth,'labelsize':charsize})
rc('ytick',**{'major.size':major_ticklength,'major.width':major_tickwidth,
              'minor.size':minor_ticklength,'minor.width':minor_tickwidth,'labelsize':charsize})
```

## Auxilliary Functions
First, we define some functions: a routine to compute limits for a logarithmic axis; a routine to compute the thermal time
\begin{equation}
\newcommand{\Teff}{T_{\mathrm{eff}}^\infty}
\tau = \frac{1}{4}\left[ \int \left(\frac{\rho C}{K}\right)^{1/2}\mathrm{d}r\right]^2;
\end{equation}
and a routine to compute the radius from the area.

```python
def thermal_time(c):
    '''
    Returns an array tau containing the thermal diffusion time to the surface to the location
    of each crust zone.

    c: of type dStarCrustReader
    '''
    sc.day
    P = c.crust.pressure[0,:]
    rho = c.crust.density[0,:]
    g = c.crust.gravity[0,:]
    K = c.crust.K[0,:]
    Cp = c.crust.cp[0,:]
    kernel = np.sqrt(rho*Cp/K)/rho/g
    dP = np.zeros_like(kernel)
    dP[1:] = P[1:]-P[0:-1]
    dP[0] = P[0]
    tau = np.zeros_like(kernel)
    tau[0] = 0.25*(kernel[0]*dP[0])**2 / sc.day
    for i in range(1,len(tau)):
        tau[i] = 0.25*(np.dot(kernel[0:i],dP[0:i]))**2 / sc.day
    return tau

def radius(area):
    return np.sqrt(area/4.0/np.pi)*1.0e-5
```

Next we define functions to make common plots: the surface effective temperature $\Teff$ during cooling and theh temperature in the crust during outburst.

```python
MeVfm3 = 10.0*sc.eV*1.0e6/sc.fermi**3

def loglimits(x,delta=0.05):
    '''
    Returns two limiting values that enclose the array x with a padding
    of delta (in log-space).

    x: array-like
    '''
    lx = np.log10(x)
    lmin,lmax = lx.min(),lx.max()
    d = delta*(lmax-lmin)
    return 10.0**(lmin-d),10.0**(lmax+d)

def grayscale(n,light_to_dark=True,scale=0.9):
    '''
    returns an array of grayscale values (length `n`) ranging from
    `scale` to 0; if `light_to_dark=False`, then the order is reversed.
    '''
    if light_to_dark:
        return [str(scale*(n-i-1)/(n-1)) for i in range(n)]
    else:
        return [str(scale*i/(n-1)) for i in range(n)]

def cooling_plot(h,ePhi):
    # store cooling times
    t = h.data.time
    quiescent = np.where(t > 0.0)
    t = t[quiescent]
    Teff = 10.0**h.data.lg_Teff[quiescent]*sc.k/sc.eV*ePhi

    plt.xlabel(r'$t$ [d]')
    plt.ylabel(r'$T_{\mathrm{eff}}^\infty$ [K]')
    plt.xlim(loglimits(t))
    plt.semilogx(t,Teff,color='k')
    plt.tight_layout()

def outburst_plot(c):
    tout = np.where(c.times <= 0.0)[0]
    clrs = grayscale(len(tout))
    T = c.crust.temperature[tout,:]*1.0e-9
    plt.xlim(loglimits(P))
    plt.xlabel(r'$P\;[\mathrm{MeV\,fm^{-3}}]$')
    plt.ylabel(r'$T$ [GK]')
    for i in range(T.shape[0]):
        plt.semilogx(P,T[i,:],color=clrs[i])
    plt.tight_layout()
```

## An impure crust
As a first case, we set the impurity parameter $Q=100$.  Of course, for such a large $Q$ the meaning of impurity breaks down; it is closer to the mark to say that the lattice is very disordered (amporphous?) in this case.  As a result, the thermal conductivity is low and the thermal diffusion is slow.

```python
c = dStarCrustReader('LOGS-Q100-H0',dt=10)
crust = c.crust
R = radius(crust.area[0,:])
P = crust.pressure[0,:]
drip = np.where(crust.Xn[0,:] > 0.0)
Pnuc = P/MeVfm3
ePhi = crust.ePhi[0,0]
```

Before analyzing the cooling, we plot the radius as a function of pressure and highlight the inner crust.

```python
xmin,xmax = Pnuc.min(),Pnuc.max()
dx = 0.05*(np.log10(xmax/xmin))
xmin,xmax = 10.0**(np.log10(xmin)-dx),10.0**(np.log10(xmax)+dx)
ymin,ymax = 11.0,11.85
plt.xlim(xmin,xmax)
plt.ylim(ymin,ymax)
plt.semilogx(Pnuc,R,color='k',label='outer crust')
plt.semilogx(Pnuc[drip],R[drip],linewidth=4,color='0.4',solid_capstyle='round',
             label='inner crust')
plt.xlabel(r'$P\;[\mathrm{MeV\,fm^{-3}}]$')
plt.ylabel(r'$R = \sqrt{\mathcal{A}/(4\pi)}$ [km]')
plt.legend(loc='lower left',frameon=False,fontsize='small')
plt.tight_layout()
plt.savefig('radius.pdf',format='pdf',facecolor='none',edgecolor='none')
```

Now we read in the history file and plot the observed surface effective temperature $\Teff$.

```python
h = dStarReader('LOGS-Q100-H0/history.data')
cooling_plot(h,ePhi)
plt.savefig('cooling-Q100-H0.pdf',format='pdf',facecolor='none',edgecolor='none')
```

The cooling timescale is indeed long, $> 1000\,\mathrm{d}$.  Let us check the thermal time.

```python
tau = thermal_time(c)
P = c.crust.pressure[0,:]/MeVfm3
plt.xlim(loglimits(P))
plt.xlabel(r'$P\;[\mathrm{MeV\,fm^{-3}}]$')
plt.ylabel(r'$\tau$ [d]')
plt.loglog(P,tau,color='k')
plt.tight_layout()
plt.savefig('tau-Q100-H0.pdf',format='pdf',facecolor='none',edgecolor='none')
```

Finally, let us plot the temperature evolution through the outburst.

```python
outburst_plot(c)
plt.savefig('profile-Q100-H0.pdf',format='pdf',facecolor='none',edgecolor='none')
tout = np.where(c.times <= 0)[0][0]
T0 = c.crust.temperature[tout,:]*1.0e-9
P0 = P
```

## A more pure crust
Now let us set $Q = 4$ and look at the cooling surface temperature.

```python
h4 = dStarReader('LOGS-Q4-H0/history.data')
cooling_plot(h4,ePhi)
plt.savefig('cooling-Q4-H0.pdf',format='pdf',facecolor='none',edgecolor='none')
```

Let's compare the thermal diffusion time with that for the high-impurity crust.

```python
c4 = dStarCrustReader('LOGS-Q4-H0')
P = c4.crust.pressure[0,:]/MeVfm3
plt.xlim(loglimits(P))
plt.xlabel(r'$P\;[\mathrm{MeV\,fm^{-3}}]$')
plt.ylabel(r'$\tau$ [d]')
plt.loglog(P,thermal_time(c4),color='k')
plt.loglog(P,thermal_time(c),linestyle=':',color='k')
plt.tight_layout()
plt.savefig('tau-Q4-H0.pdf',format='pdf',facecolor='none',edgecolor='none')
```

As expected from the evolution of the surface temperature, the thermal diffusion time is an order of magnitude less in this case.  Because the thermal diffusion time is now less than the duration of the outburst, the crust has time to thermally relax; compare the profile of temperature in crust with that for the high-impurity case.

```python
outburst_plot(c4)
plt.savefig('profile-Q4-H0.pdf',format='pdf',facecolor='none',edgecolor='none')
```

This explains why $\Teff$ is so constant during the first 100 days: the outer layers of the star are already thermally relaxed, so the flux is unchanging until the inner crust has time to evolve.  To see this, let's plot the temperature as a function of $\tau$.

```python
tout = np.where(c4.times == 0.0)[0][0]
indcs = [tout,tout+1,tout+3,tout+6,tout+8]
Ts = c4.crust.temperature[indcs,:]*1.0e-9
clrs = grayscale(len(indcs))
times = c4.times[indcs]
plt.xlim(loglimits(tau,delta=0.2))
plt.xlabel(r'$\tau$ [d]')
plt.ylabel(r'$T$ [GK]')
for i in range(len(indcs)):
    plt.semilogx(tau,Ts[i,:],color=clrs[i])
    if i > len(indcs)-3:
        tstr = r'$t = {0:0.0f}$ d'.format(times[i])
        plt.annotate(s=tstr,xy=(tau[0],Ts[i,0]),verticalalignment='center',
                     horizontalalignment='right',color='k',fontsize='small')
plt.tight_layout()
plt.savefig('Ttau-Q4-H0.pdf',format='pdf',facecolor='none',edgecolor='none')
```

To get $\Teff$ to cool at early times, we need the temperature to decrease with depth in the outer crust.  We achieve this by adding a heat source, of strength $L = 1.7\,\mathrm{MeV}\times \mathrm{d}\dot{M}/\mathrm{d}t$.

```python
h17 = dStarReader('LOGS-Q4-H1.7/history.data')
cooling_plot(h17,ePhi)
plt.savefig('cooling-Q4-H1.7.pdf',format='pdf',facecolor='none',edgecolor='none')
```

```python
c17 = dStarCrustReader('LOGS-Q4-H1.7')
outburst_plot(c17)
plt.savefig('profile-Q4-H1.7.pdf',format='pdf',facecolor='none',edgecolor='none')
```

At a time $t$ after the end of the outburst, the crust has cooled down to a depth where $\tau \approx t$.  The evolution of the surface temperature $\Teff$ is therefore mapping out the crust temperature as a function of depth.

```python
tout = np.where(c17.times == 0.0)[0][0]
indcs = [tout,tout+1,tout+3,tout+6,tout+8,tout+12,tout+16]
Ts = c17.crust.temperature[indcs,:]*1.0e-9
clrs = grayscale(len(indcs))
times = c17.times[indcs]
plt.xlim(loglimits(tau))
plt.xlabel(r'$\tau$ [d]')
plt.ylabel(r'$T$ [GK]')
for i in range(len(indcs)):
    plt.semilogx(tau,Ts[i,:],color=clrs[i])
    if i > len(indcs)-5:
        tstr = r'$t = {0:0.0f}$ d'.format(times[i])
        plt.annotate(s=tstr,xy=(1.0,Ts[i,0]),verticalalignment='bottom',
                     color='k',fontsize='small')
plt.tight_layout()
plt.savefig('Ttau-Q4-H1.7.pdf',format='pdf',facecolor='none',edgecolor='none')
```

```python

```


```python
%matplotlib inline
```

# The heat equation in 2D

$$
\renewcommand{\DdQq}[2]{{\mathrm D}_{#1}{\mathrm Q}_{#2}}
\renewcommand{\drondt}{\partial_t}
\renewcommand{\drondx}{\partial_x}
\renewcommand{\drondtt}{\partial_{tt}}
\renewcommand{\drondxx}{\partial_{xx}}
\renewcommand{\drondyy}{\partial_{yy}}
\renewcommand{\dx}{\Delta x}
\renewcommand{\dt}{\Delta t}
\renewcommand{\grandO}{{\mathcal O}}
\renewcommand{\density}[2]{\,f_{#1}^{#2}}
\renewcommand{\fk}[1]{\density{#1}{\vphantom{\star}}}
\renewcommand{\fks}[1]{\density{#1}{\star}}
\renewcommand{\moment}[2]{\,m_{#1}^{#2}}
\renewcommand{\mk}[1]{\moment{#1}{\vphantom{\star}}}
\renewcommand{\mke}[1]{\moment{#1}{e}}
\renewcommand{\mks}[1]{\moment{#1}{\star}}
$$

In this tutorial, we test a very classical lattice Boltzmann scheme $\DdQq{2}{5}$ on the heat equation.

The problem reads
$$
\begin{gathered} \drondt u = \mu (\drondxx+\drondyy) u, \quad t>0, \quad (x, y)\in(0,1)^2,\\ u(0) = u(1) = 0, \end{gathered}
$$

where $\mu$ is a constant scalar.

## The scheme $\DdQq{2}{5}$

The numerical simulation of this equation by a lattice Boltzmann scheme consists in the approximatation of the solution on discret points of $(0,1)^2$ at discret instants.

To simulate this system of equations, we use the $\DdQq{2}{5}$ scheme given by

* five velocities $v_0=(0,0)$, $v_1=(1,0)$, $v_2=(0,1)$, $v_3=(-1,0)$, and $v_4=(0,-1)$ with associated distribution functions $\fk{i}$, $0\leq i\leq 4$,
* a space step $\dx$ and a time step $\dt$, the ration $\lambda=\dx/\dt$ is called the scheme velocity,
* five moments
  $$ \mk{0}=\sum_{i=0}^{4} \fk{i}, \quad \mk{1}= \sum_{i=0}^{4} v_{ix} \fk{i}, \quad \mk{2}= \sum_{i=0}^{4} v_{iy} \fk{i}, \quad \mk{3}= \frac{1}{2} \sum_{i=0}^{5} (v_{ix}^2+v_{iy}^2) \fk{i}, \quad \mk{4}= \frac{1}{2} \sum_{i=0}^{5} (v_{ix}^2-v_{iy}^2) \fk{i},$$

  and their equilibrium values $\mke{k}$, $0\leq k\leq 4$.
* two relaxation parameters $s_1$ and $s_2$ lying in $[0,2]$ ($s_1$ for the odd moments and $s_2$ for the odd ones).

In order to use the formalism of the package pylbm, we introduce the five polynomials that define the moments: $P_0 = 1$, $P_1=X$, $P_2=Y$, $P_3=(X^2+Y^2)/2$, and $P_4=(X^2-Y^2)/2$, such that
$$
\mk{k} = \sum_{i=0}^4 P_k(v_{ix}, v_{iy}) \fk{i}.
$$

The transformation $(\fk{0}, \fk{1}, \fk{2}, \fk{3}, \fk{4})\mapsto(\mk{0},\mk{1}, \mk{2}, \mk{3}, \mk{4})$ is invertible if, and only if, the polynomials $(P_0,P_1,P_2,P_3,P_4)$ is a free set over the stencil of velocities.

The lattice Boltzmann method consists to compute the distribution functions $\fk{i}$, $0\leq i\leq 4$ in each point of the lattice $x$ and at each time $t^n=n\dt$.
A step of the scheme can be read as a splitting between the relaxation phase and the transport phase:

* relaxation:
$$
    \begin{aligned}\mks{1}(t,x,y)&=(1-s_1)\mk{1}(t,x,y)+s_1\mke{1}(t,x,y),\\ \mks{2}(t,x,y)&=(1-s_1)\mk{2}(t,x,y)+s_1\mke{2}(t,x,y),\\ \mks{3}(t,x,y)&=(1-s_2)\mk{3}(t,x,y)+s_2\mke{3}(t,x,y),\\ \mks{4}(t,x,y)&=(1-s_2)\mk{4}(t,x,y)+s_2\mke{4}(t,x,y).\end{aligned}
$$

* m2f:
$$
    \begin{aligned}\fks{0}(t,x,y)&\;=\mk{0}(t,x,y)-2\mks{3}(t,x,y), \\ \fks{1}(t,x,y)&\;=\tfrac{1}{2}(\phantom{-}\mks{1}(t,x,y)+\mks{3}(t,x,y)+\mks{4}(t,x,y)), \\ \fks{2}(t,x,y)&\;=\tfrac{1}{2}(\phantom{-}\mks{2}(t,x,y)+\mks{3}(t,x,y)-\mks{4}(t,x,y)), \\ \fks{3}(t,x,y)&\;=\tfrac{1}{2}(-\mks{1}(t,x,y)+\mks{3}(t,x,y)+\mks{4}(t,x,y)), \\ \fks{4}(t,x,y)&\;=\tfrac{1}{2}(-\mks{2}(t,x,y)+\mks{3}(t,x,y)-\mks{4}(t,x,y)).\end{aligned}
$$

* transport:
$$
    \begin{aligned} \fk{0}(t+\dt, x,y)&\;=\fks{0}(t,x,y), \\ \fk{1}(t+\dt, x,y)&\;=\fks{1}(t,x-\dx,y), \\ \fk{2}(t+\dt, x,y)&\;=\fks{2}(t,x,y-\dx), \\ \fk{3}(t+\dt, x,y)&\;=\fks{3}(t,x+\dx,y), \\ \fk{4}(t+\dt, x,y)&\;=\fks{4}(t,x,y+\dx). \end{aligned}
$$

* f2m:
$$
    \begin{aligned}\mk{0}(t+\dt,x,y)&\;=\fk{0}(t+\dt,x,y)+\fk{1}(t+\dt,x,y)+\fk{2}(t+\dt,x,y)\\&\;\phantom{=}+\fk{3}(t+\dt,x,y)+\fk{4}(t+\dt,x,y), \\ \mk{1}(t+\dt,x,y)&\;=\fk{1}(t+\dt,x,y)-\fk{3}(t+\dt,x,y), \\ \mk{2}(t+\dt,x,y)&\;=\fk{2}(t+\dt,x,y)-\fk{4}(t+\dt,x,y), \\ \mk{3}(t+\dt,x,y)&\;=\tfrac{1}{2}(\fk{1}(t+\dt,x,y)+\fk{2}(t+\dt,x,y)+\fk{3}(t+\dt,x,y)+\fk{4}(t+\dt,x,y)), \\ \mk{4}(t+\dt,x,y)&\;=\tfrac{1}{2}(\fk{1}(t+\dt,x,y)-\fk{2}(t+\dt,x,y)+\fk{3}(t+\dt,x,y)-\fk{4}(t+\dt,x,y)).\end{aligned}
$$

The moment of order $0$, $\mk{0}$, being conserved during the relaxation phase,
a diffusive scaling $\dt=\dx^2$, yields to the following equivalent equation
$$
\drondt\mk{0} = \bigl(\tfrac{1}{s_1}-\tfrac{1}{2}\bigr) \bigl(\drondxx(\mke{3}+\mke{4})+\drondyy(\mke{3}-\mke{4})\bigr) + \grandO(\dx^2),
$$

if $\mke{1}=0$.
In order to be consistent with the heat equation, the following choice is done:
$$
\mke{3}=\tfrac{1}{2}u, \qquad \mke{4}=0, \qquad s_1 = \frac{2}{1+4\mu}, \qquad s_2=1.
$$

## Using pylbm

pylbm uses Python dictionary to describe the simulation. In the following, we will build this dictionary step by step.

### The geometry

In pylbm, the geometry is defined by a box and a label for the boundaries. We define here a square $(0, 1)^2$.

```python
import pylbm
import numpy as np
import pylab as plt
xmin, xmax, ymin, ymax = 0., 1., 0., 1.
dico_geom = {
    'box': {'x': [xmin, xmax],
            'y': [ymin, ymax],
            'label': 0
           },
}
geom = pylbm.Geometry(dico_geom)
print(geom)
geom.visualize(viewlabel=True);
```

### The stencil

pylbm provides a class stencil that is used to define the discret velocities of the scheme. In this example, the stencil is composed by the velocities $v_0=(0,0)$, $v_1=(1,0)$, $v_2=(-1,0)$, $v_3=(0,1)$, and $v_4=(0,-1)$ numbered by $[0,1,2,3,4]$.

```python
dico_sten = {
    'dim':2,
    'schemes': [
        {'velocities': list(range(5))}
    ],
}
sten = pylbm.Stencil(dico_sten)
print(sten)
sten.visualize();
```

### The domain

In order to build the domain of the simulation, the dictionary should contain the space step $\dx$ and the stencils of the velocities (one for each scheme).

We construct a domain with $N=10$ points in space.

```python
N = 10
dx = (xmax-xmin)/N
dico_dom = {
    'box': {'x': [xmin, xmax],
            'y': [ymin, ymax],
            'label': 0
           },
    'space_step': dx,
    'schemes': [
        {'velocities': list(range(5)),}
    ],
}
dom = pylbm.Domain(dico_dom)
print(dom)
dom.visualize(view_distance=True);
```

### The scheme

In pylbm, a simulation can be performed by using several coupled schemes. In this example, a single scheme is used and defined through a list of one single dictionary. This dictionary should contain:

* 'velocities': a list of the velocities
* 'conserved_moments': a list of the conserved moments as sympy variables
* 'polynomials': a list of the polynomials that define the moments
* 'equilibrium': a list of the equilibrium value of all the moments
* 'relaxation_parameters': a list of the relaxation parameters ($0$ for the conserved moments)
* 'init': a dictionary to initialize the conserved moments

(see the documentation for more details)

The scheme velocity could be taken to $1/\dx$ and the inital value of $u$ to

$$ u(t=0,x) = \sin(\pi x)\sin(\pi y).$$

```python
import sympy as sp

def solution(x, y, t):
    return np.sin(np.pi*x)*np.sin(np.pi*y)*np.exp(-2*np.pi**2*mu*t)

# parameters
mu = 1.
la = 1./dx
s1 = 2./(1+4*mu)
s2 = 1.
u, X, Y, LA = sp.symbols('u, X, Y, LA')

dico_sch = {
    'dim': 2,
    'scheme_velocity': la,
    'schemes': [
        {
            'velocities': list(range(5)),
            'conserved_moments': u,
            'polynomials': [1, X/LA, Y/LA, (X**2+Y**2)/(2*LA**2), (X**2-Y**2)/(2*LA**2)],
            'equilibrium': [u, 0., 0., .5*u, 0.],
            'relaxation_parameters': [0., s1, s1, s2, s2],
        }
    ],
    'parameters': {LA: la},
}

sch = pylbm.Scheme(dico_sch)
print(sch)
```

    +--------------------+
    | Scheme information |
    +--------------------+
        - spatial dimension: 2
        - number of schemes: 1
        - number of velocities: 5
        - conserved moments: [u]

        +----------+
        | Scheme 0 |
        +----------+
            - velocities
                (0: 0, 0)
                (1: 1, 0)
                (2: 0, 1)
                (3: -1, 0)
                (4: 0, -1)

            - polynomials

                ⎡   1   ⎤
                ⎢       ⎥
                ⎢  X    ⎥
                ⎢  ──   ⎥
                ⎢  LA   ⎥
                ⎢       ⎥
                ⎢  Y    ⎥
                ⎢  ──   ⎥
                ⎢  LA   ⎥
                ⎢       ⎥
                ⎢ 2    2⎥
                ⎢X  + Y ⎥
                ⎢───────⎥
                ⎢     2 ⎥
                ⎢ 2⋅LA  ⎥
                ⎢       ⎥
                ⎢ 2    2⎥
                ⎢X  - Y ⎥
                ⎢───────⎥
                ⎢     2 ⎥
                ⎣ 2⋅LA  ⎦

            - equilibria

                ⎡  u  ⎤
                ⎢     ⎥
                ⎢ 0.0 ⎥
                ⎢     ⎥
                ⎢ 0.0 ⎥
                ⎢     ⎥
                ⎢0.5⋅u⎥
                ⎢     ⎥
                ⎣ 0.0 ⎦

            - relaxation parameters

                ⎡0.0⎤
                ⎢   ⎥
                ⎢0.4⎥
                ⎢   ⎥
                ⎢0.4⎥
                ⎢   ⎥
                ⎢1.0⎥
                ⎢   ⎥
                ⎣1.0⎦

        - moments matrices

            ⎡1   1    1     1     1  ⎤
            ⎢                        ⎥
            ⎢   10         -10       ⎥
            ⎢0  ──    0    ────   0  ⎥
            ⎢   LA          LA       ⎥
            ⎢                        ⎥
            ⎢         10         -10 ⎥
            ⎢0   0    ──    0    ────⎥
            ⎢         LA          LA ⎥
            ⎢                        ⎥
            ⎢    50   50    50    50 ⎥
            ⎢0  ───  ───   ───   ─── ⎥
            ⎢     2    2     2     2 ⎥
            ⎢   LA   LA    LA    LA  ⎥
            ⎢                        ⎥
            ⎢    50  -50    50   -50 ⎥
            ⎢0  ───  ────  ───   ────⎥
            ⎢     2    2     2     2 ⎥
            ⎣   LA   LA    LA    LA  ⎦

        - inverse of moments matrices

            ⎡                  2        ⎤
            ⎢               -LA         ⎥
            ⎢1   0     0    ─────    0  ⎥
            ⎢                 50        ⎥
            ⎢                           ⎥
            ⎢                  2      2 ⎥
            ⎢    LA          LA     LA  ⎥
            ⎢0   ──    0     ───    ─── ⎥
            ⎢    20          200    200 ⎥
            ⎢                           ⎥
            ⎢                  2      2 ⎥
            ⎢          LA    LA    -LA  ⎥
            ⎢0   0     ──    ───   ─────⎥
            ⎢          20    200    200 ⎥
            ⎢                           ⎥
            ⎢                  2      2 ⎥
            ⎢   -LA          LA     LA  ⎥
            ⎢0  ────   0     ───    ─── ⎥
            ⎢    20          200    200 ⎥
            ⎢                           ⎥
            ⎢                  2      2 ⎥
            ⎢         -LA    LA    -LA  ⎥
            ⎢0   0    ────   ───   ─────⎥
            ⎣          20    200    200 ⎦

### The simulation

A simulation is built by defining a correct dictionary.

We combine the previous dictionaries to build a simulation. In order to impose the homogeneous Dirichlet conditions in $x=0$, $x=1$, $y=0$, and $y=1$, the dictionary should contain the key 'boundary_conditions' (we use pylbm.bc.Anti_bounce_back function).

```python
dico = {
    'box': {'x': [xmin, xmax],
            'y': [ymin, ymax],
            'label': 0},
    'space_step': dx,
    'scheme_velocity': la,
    'schemes': [
        {
            'velocities': list(range(5)),
            'conserved_moments': u,
            'polynomials': [1, X/LA, Y/LA, (X**2+Y**2)/(2*LA**2), (X**2-Y**2)/(2*LA**2)],
            'equilibrium': [u, 0., 0., .5*u, 0.],
            'relaxation_parameters': [0., s1, s1, s2, s2],
        }
    ],
    'init': {u: (solution, (0.,))},
    'boundary_conditions': {
        0: {'method': {0: pylbm.bc.AntiBounceBack,}},
    },
    'parameters': {LA: la},
}

sol = pylbm.Simulation(dico)
print(sol)
```

    +------------------------+
    | Simulation information |
    +------------------------+

        +--------------------+
        | Domain information |
        +--------------------+
            - spatial dimension: 2
            - space step: 0.1
            - with halo:
                bounds of the box: [-0.05 -0.05] x [1.05 1.05]
                number of points: [12, 12]
            - without halo:
                bounds of the box: [0.05 0.05] x [0.95 0.95]
                number of points: [10, 10]

            +----------------------+
            | Geometry information |
            +----------------------+
                - spatial dimension: 2
                - bounds of the box: [0. 1.] x [0. 1.]
                - labels: [0, 0, 0, 0]

        +--------------------+
        | Scheme information |
        +--------------------+
            - spatial dimension: 2
            - number of schemes: 1
            - number of velocities: 5
            - conserved moments: [u]

            +----------+
            | Scheme 0 |
            +----------+
                - velocities
                    (0: 0, 0)
                    (1: 1, 0)
                    (2: 0, 1)
                    (3: -1, 0)
                    (4: 0, -1)

                - polynomials

                    ⎡   1   ⎤
                    ⎢       ⎥
                    ⎢  X    ⎥
                    ⎢  ──   ⎥
                    ⎢  LA   ⎥
                    ⎢       ⎥
                    ⎢  Y    ⎥
                    ⎢  ──   ⎥
                    ⎢  LA   ⎥
                    ⎢       ⎥
                    ⎢ 2    2⎥
                    ⎢X  + Y ⎥
                    ⎢───────⎥
                    ⎢     2 ⎥
                    ⎢ 2⋅LA  ⎥
                    ⎢       ⎥
                    ⎢ 2    2⎥
                    ⎢X  - Y ⎥
                    ⎢───────⎥
                    ⎢     2 ⎥
                    ⎣ 2⋅LA  ⎦

                - equilibria

                    ⎡  u  ⎤
                    ⎢     ⎥
                    ⎢ 0.0 ⎥
                    ⎢     ⎥
                    ⎢ 0.0 ⎥
                    ⎢     ⎥
                    ⎢0.5⋅u⎥
                    ⎢     ⎥
                    ⎣ 0.0 ⎦

                - relaxation parameters

                    ⎡0.0⎤
                    ⎢   ⎥
                    ⎢0.4⎥
                    ⎢   ⎥
                    ⎢0.4⎥
                    ⎢   ⎥
                    ⎢1.0⎥
                    ⎢   ⎥
                    ⎣1.0⎦

            - moments matrices

                ⎡1   1    1     1     1  ⎤
                ⎢                        ⎥
                ⎢   10         -10       ⎥
                ⎢0  ──    0    ────   0  ⎥
                ⎢   LA          LA       ⎥
                ⎢                        ⎥
                ⎢         10         -10 ⎥
                ⎢0   0    ──    0    ────⎥
                ⎢         LA          LA ⎥
                ⎢                        ⎥
                ⎢    50   50    50    50 ⎥
                ⎢0  ───  ───   ───   ─── ⎥
                ⎢     2    2     2     2 ⎥
                ⎢   LA   LA    LA    LA  ⎥
                ⎢                        ⎥
                ⎢    50  -50    50   -50 ⎥
                ⎢0  ───  ────  ───   ────⎥
                ⎢     2    2     2     2 ⎥
                ⎣   LA   LA    LA    LA  ⎦

            - inverse of moments matrices

                ⎡                  2        ⎤
                ⎢               -LA         ⎥
                ⎢1   0     0    ─────    0  ⎥
                ⎢                 50        ⎥
                ⎢                           ⎥
                ⎢                  2      2 ⎥
                ⎢    LA          LA     LA  ⎥
                ⎢0   ──    0     ───    ─── ⎥
                ⎢    20          200    200 ⎥
                ⎢                           ⎥
                ⎢                  2      2 ⎥
                ⎢          LA    LA    -LA  ⎥
                ⎢0   0     ──    ───   ─────⎥
                ⎢          20    200    200 ⎥
                ⎢                           ⎥
                ⎢                  2      2 ⎥
                ⎢   -LA          LA     LA  ⎥
                ⎢0  ────   0     ───    ─── ⎥
                ⎢    20          200    200 ⎥
                ⎢                           ⎥
                ⎢                  2      2 ⎥
                ⎢         -LA    LA    -LA  ⎥
                ⎢0   0    ────   ───   ─────⎥
                ⎣          20    200    200 ⎦

### Run a simulation

Once the simulation is initialized, one time step can be performed by using the function one_time_step.

We compute the solution of the heat equation at $t=0.1$. On the same graphic, we plot the initial condition, the exact solution and the numerical solution.

```python
import numpy as np
import sympy as sp
import pylab as plt
%matplotlib inline
from mpl_toolkits.axes_grid1 import make_axes_locatable
import pylbm

u, X, Y = sp.symbols('u, X, Y')

def solution(x, y, t, k, l):
    return np.sin(k*np.pi*x)*np.sin(l*np.pi*y)*np.exp(-(k**2+l**2)*np.pi**2*mu*t)

def plot(i, j, z, title):
    im = axarr[i,j].imshow(z)
    divider = make_axes_locatable(axarr[i, j])
    cax = divider.append_axes("right", size="20%", pad=0.05)
    cbar = plt.colorbar(im, cax=cax, format='%6.0e')
    axarr[i, j].xaxis.set_visible(False)
    axarr[i, j].yaxis.set_visible(False)
    axarr[i, j].set_title(title)

# parameters
xmin, xmax, ymin, ymax = 0., 1., 0., 1.
N = 128
mu = 1.
Tf = .1
dx = (xmax-xmin)/N # spatial step
la = 1./dx
s1 = 2./(1+4*mu)
s2 = 1.
k, l = 1, 1 # number of the wave

dico = {
    'box': {'x':[xmin, xmax],
            'y':[ymin, ymax],
            'label': 0},
    'space_step': dx,
    'scheme_velocity': la,
    'schemes':[
        {
            'velocities': list(range(5)),
            'conserved_moments': u,
            'polynomials': [1, X/LA, Y/LA, (X**2+Y**2)/(2*LA**2), (X**2-Y**2)/(2*LA**2)],
            'equilibrium': [u, 0., 0., .5*u, 0.],
            'relaxation_parameters': [0., s1, s1, s2, s2],
        }
    ],
    'init': {u: (solution, (0., k, l))},
    'boundary_conditions': {
        0: {'method': {0: pylbm.bc.AntiBounceBack,}},
    },
    'generator': 'cython',
    'parameters': {LA: la},
}

sol = pylbm.Simulation(dico)
x = sol.domain.x
y = sol.domain.y

f, axarr = plt.subplots(2, 2)
f.suptitle('Heat equation', fontsize=20)

plot(0, 0, sol.m[u].copy(), 'initial')

while sol.t < Tf:
    sol.one_time_step()

sol.f2m()
z = sol.m[u]
ze = solution(x[:, np.newaxis], y[np.newaxis, :], sol.t, k, l)
plot(1, 0, z, 'final')
plot(0, 1, ze, 'exact')
plot(1, 1, z-ze, 'error')

plt.show()
```


「 Autograd（自動微分）」
===============================================================
【原題】Autograd: Automatic Differentiation

【原著】[Soumith Chintala](http://soumith.ch/)

【元URL】https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html

【翻訳】電通国際情報サービスISID AIトランスフォーメーションセンター　徳原　光

【日付】2020年10月27日

【チュトーリアル概要】

PyTorchによるニューラルネットワークの学習において、重要な概念でありパッケージでもあるautogradの機能、そしてその動作内容について解説します。

---

Autograd: 自動微分
=================
PyTorchによるニューラルネットワーク構築の土台となっているのが、autograd（自動微分）パッケージです。

このパッケージの概要をざっくりと確認し、本チュートリアルシリーズで初めてとなるニューラルネットワークの訓練を体験しましょう。

autogradパッケージはTensorの操作に対する自動微分機能を提供します。

Tensor操作に基づく自動微分はdefine-by-runフレームワークであり、ユーザーが実行したコードに対応して誤差逆伝搬が定義され、すべてのイテレーションの計算で異なる結果を生み出します。

ここからは、いくつかの実例を通してこの機能を簡単に見ていきましょう。

<br>

（日本語訳注：define-by-runはデータをニューラルネットワークに流しながら、モデルの構築を行っていく手法を指します。一方でdefine-and-runは先に誤差逆伝搬の形を実行する前に構築します。）

テンソル（Tensor）
==================
``torch.Tensor``はパッケージの中心的なクラスです。

``.requires_grad``属性が``True``に設定された場合、autogradパッケージによってすべての操作が追跡され、演算が終了した際は``.backward()``を呼び出すことで、すべての操作に対する勾配が自動的に計算されます。

このTensorに対する勾配は``.grad``属性に蓄積されていきます。

追跡履歴からTensorを切り離して、追跡を停止する場合は、 ``.detach()``を呼び出します。

これにより、その後の演算ではこのこのTensorは追跡されないように設定可能です。

``with torch.no_grad():``でコードをブロックにまとめることで、追跡履歴（とメモリの利用）を省略することもできます。

これはモデルを評価する際、``requires_grad=True``により学習可能なパラメータを持っているが、勾配の計算は必要ない場合に特に有効です。

そしてもう一つ、自動微分の実行に非常に重要なクラスに``Function``があります。

``Tensor``と``Function``は 相互に接続し、非巡回グラフで完全な計算履歴を記録しています。

各Tensorは、そのTensorを作成した``Function`` を参照する``.grad_fn``属性を持ちます（ユーザーが直接定義したテンソルの場合は``grad_fn is None``となります）。

導関数を算出する場合は、``Tensor``が持つ関数``.backward()``を呼び出します。

``Tensor``がスカラー（すなわち、要素数が1つだけ）の場合、``.backward()``に引数を指定する必要はありません。しかし、テンソルが複数要素を持つ場合は、Tensorと同じ大きさのTensorを``gradient``の引数に指定する必要があります。

```python
 %matplotlib inline
```

```python
import torch
```

Tensorを作成し、``requires_grad=True``と指定することによって演算を追跡してみましょう。

```python
x = torch.ones(2, 2, requires_grad=True)
print(x)
```

    tensor([[1., 1.],
            [1., 1.]], requires_grad=True)

Tensorの計算を実行：

```python
y = x + 2
print(y)
```

    tensor([[3., 3.],
            [3., 3.]], grad_fn=<AddBackward0>)

``y`` は計算結果であり、 計算履歴として``grad_fn``を持っています。

```python
print(y.grad_fn)
```

    <AddBackward0 object at 0x7f7f3e698518>

さらに、``y``を用いた計算を実行します：

```python
z = y * y * 3
out = z.mean()

print(z, out)
```

    tensor([[27., 27.],
            [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)

``.requires_grad_( ... )`` によって既存のTensorの ``requires_grad``フラグを変更することができます。

テンソルの作成時に引数で何も指定していない場合は、``requires_grad``はデフォルト値として ``False`` に設定されています。

```python
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
```

    False
    True
    <SumBackward0 object at 0x7f7ef1826320>

勾配（Gradients）
==================
では、誤差逆伝搬を実行してみましょう。

変数``out``はスカラーの値を持っているため, ``out.backward()`` は``out.backward(torch.tensor(1.))``と同じ結果になります。

```python
out.backward()
```

```python
print(out)
```

    tensor(27., grad_fn=<MeanBackward0>)

勾配 d(out)/dxを表示。

```python
print(x.grad)
```

    tensor([[4.5000, 4.5000],
            [4.5000, 4.5000]])

結果としてすべての要素が ``4.5``の行列を得たのではないでしょうか。

この出力テンソルを“$o$”と記載します。
テンソル“$o$”を計算すると、

$o = \frac{1}{4}\sum_i z_i$

$z_i = 3(x_i+2)^2$

そして、

 $z_i\bigr\rvert_{x_i=1} = 27$.

なので、

$\frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i+2)$

$\frac{\partial o}{\partial x_i}\bigr\rvert_{x_i=1} = \frac{9}{2} = 4.5$

となります。

数学的には、ベクトル関数 $\vec{y}=f(\vec{x})$において、 $\vec{x}$に関する$\vec{y}$ の勾配はヤコビアンと呼ばれています。

\begin{align}J=\left(\begin{array}{ccc}
   \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\
   \vdots & \ddots & \vdots\\
   \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}}
   \end{array}\right)\end{align}

一般的に、 ``torch.autograd`` はベクトルのヤコビアンの積を算出する計算エンジンになります。

これは、$v=\left(\begin{array}{cccc} v_{1} & v_{2} & \cdots & v_{m}\end{array}\right)^{T}$というベクトルに対して、行列積$v^{T}\cdot J$を計算することを意味します。

 もし、 $v$ がスカラー関数の勾配 $l=g\left(\vec{y}\right)$であった場合、
$v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T}$と表されます。

そして連鎖律により、ベクトルのヤコビアンの積は$\vec{x}$に関する$l$の勾配となるのです。

\begin{align}J^{T}\cdot v=\left(\begin{array}{ccc}
   \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\
   \vdots & \ddots & \vdots\\
   \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}}
   \end{array}\right)\left(\begin{array}{c}
   \frac{\partial l}{\partial y_{1}}\\
   \vdots\\
   \frac{\partial l}{\partial y_{m}}
   \end{array}\right)=\left(\begin{array}{c}
   \frac{\partial l}{\partial x_{1}}\\
   \vdots\\
   \frac{\partial l}{\partial x_{n}}
   \end{array}\right)\end{align}

( $v^{T}\cdot J$ は転置の公式から、 $J^{T}\cdot v$を計算して得られる列ベクトル、と同じ成分の行ベクトルを与えることに注意してください)

このベクトルのヤコビアンの積の性質は、スカラ量ではない出力を持つモデルに対して、外部から異なる勾配を追加して計算する際に、非常に有効に利用できます。

ベクトルのヤコビアンの積の例を見てみましょう

```python
x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y)
```

    tensor([-100.0453, -496.9997,  898.9301], grad_fn=<MulBackward0>)

（日本語訳注：関数norm()はノルム≒距離を与えます、引数なしのnorm()は2乗ノルムです。
各要素を2乗して足し算し、そのルートを計算します）

よって上記では、乱数で発生させたxを2倍、4倍、8倍、・・・とyの3要素の2乗和の平均のルートが1000を超えるまで増加させています。

以下の計算結果を参照）

```python
# 日本語訳注
torch.sqrt(y[0]*y[0] + y[1]*y[1] + y[2]*y[2])
```

    tensor(1032.0334, grad_fn=<SqrtBackward>)

この場合、 ``y`` はスカラ量ではありません。 ``torch.autograd``
では直接ヤコビアンの全要素を計算することはできませんが、ベクトルとヤコビアンの積を計算するだけの場合には、簡単に引数として``backward``にベクトルを与えることで勾配が算出できます。

```python
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)

print(x.grad)
```

    tensor([5.1200e+01, 5.1200e+02, 5.1200e-02])

```python
# 日本語訳注
print("x：", x)
print("y：", y)
scale = y/x
print("倍数：", scale)

# このセルの出力から何倍して、yを求めたのか分かります。倍数:　の出力部分です。
# このセルでは変数sclaeで表しています。
# y = scale * x なので、yのxに関する勾配は scaleです。
# yの勾配をvに対して計算した結果、xに対して溜まる勾配値がx.gradです。
# その値x.grad（上記のセルの出力結果）は、
# scaleにv＝[0.1, 1, 0.0001]がかけ算された値となっているはずです。

```

    x： tensor([-0.1954, -0.9707,  1.7557], requires_grad=True)
    y： tensor([-100.0453, -496.9997,  898.9301], grad_fn=<MulBackward0>)
    倍数： tensor([512., 512., 512.], grad_fn=<DivBackward0>)

``with torch.no_grad():``でコードをまとめることで、``.requires_grad=True``となっているTensorの追跡履歴からautogradを停止することができます。

```python
print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
	print((x ** 2).requires_grad)
```

    True
    True
    False

要素が同じTensorを作成した際も、勾配が必要ない場合は``.detach()``を用いることも可能です。

（日本語訳注：以下のセルの.eq()はTensorとして値が同じであればTrueを返します。いまは、xとyの要素は3つあるので、その3つに対して求めた結果を.all()でまとめて求めています　[詳細](https://pytorch.org/docs/stable/generated/torch.eq.html)）

```python
print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())
```

    True
    False
    tensor(True)

**補足:**

``autograd.Function`` のドキュメントは
https://pytorch.org/docs/stable/autograd.html#function
を参照してください。


```python
import numpy as np
import matplotlib.pyplot as plt
from sympy import *
```

```python
t = 0
x = np.linspace(-4,4,100)
```

```python
y = np.exp(-np.power(x-3*t,2))*np.sin(3*np.pi*(x-t))
```

```python

```


# Viscosity Coefficients

## Prelude
In this notebook we will attempt to calculate the viscosity of the Yukawa OCP.

The YAML input file can be found at [input_file](https://raw.githubusercontent.com/murillo-group/sarkas/master/docs/examples/YOCP/input_files/yocp_viscosity.yaml) and this notebook at [notebook](https://raw.githubusercontent.com/murillo-group/sarkas/master/docs/examples/YOCP/YOCP_viscosity.ipynb).

```python
# Import the usual libraries
%pylab
%matplotlib inline

import os
plt.style.use('MSUstyle')

# Import sarkas
from sarkas.processes import Simulation, PostProcess, PreProcess

# Create the file path to the YAML input file
input_file_name = os.path.join('input_files', 'yocp_viscosity.yaml')
```

    Using matplotlib backend: Qt5Agg
    Populating the interactive namespace from numpy and matplotlib

```python
# pre = PreProcess(input_file_name)
# pre.setup(read_yaml=True)
# pre.run()
```

```python
# sim = Simulation(input_file_name)
# sim.setup(read_yaml=True)
# sim.run()
```

```python
postproc = PostProcess(input_file_name)
postproc.setup(read_yaml=True)
postproc.parameters.verbose = True
postproc.therm.setup(postproc.parameters)
postproc.therm.temp_energy_plot(postproc)
```

## Pair Distribution Function

The first observable to calculate is always the RDF.

```python
rdf = postproc.rdf
rdf.setup(postproc.parameters, no_slices=1 )
rdf.parse()

```

```python
rdf.plot(scaling = rdf.a_ws,
         y = ('C-C RDF', 'Mean'),
         xlabel = r'$r /a$')
```

```python
from sarkas.tools.transport import TransportCoefficients
from sarkas.tools.observables import PressureTensor
```

## Pressure Tensor

The viscosity is obtained from the autocorrelation function of the Pressure Tensor $\overleftrightarrow{\mathcal P}$ whose elements are

\begin{equation}
\mathcal P_{\alpha\gamma}(t) = \frac{1}{V} \sum_{i}^{N} \left [ m_i v^{\alpha}_{i} v^{\gamma}_{i} -  \sum_{j > i} \frac{r_{ij}^{\alpha} r_{ij}^{\gamma} }{r_{ij}} \frac{d}{dr}\phi(r) \right ],
\end{equation}

where $r_{ij}^{\alpha}$ is the $\alpha$ component of the distance between particles $i$ and $j$. The first term is the kinetic term and the second term is the virial term, but it is often referred to as the potential contribution. The virial is calculated during the simulation phase and saved together with particles corrdinates.

In order to check that our code are correct, let's verify some laws.

The pressure of the system is calculated from $\mathcal P(t)= \frac1{3} {\rm Tr} \overleftrightarrow{\mathcal P}(t)$ and also from

\begin{equation}
P = \frac{n}{\beta} - \frac{2\pi}{3} n^2 \int_0^{\infty} dr \, r^3 \frac{d\phi(r)}{dr} g(r)
\end{equation}

where $g(r)$ is the pair distribution function that we have already calculated.

Let's calculate the Pressure tensor and the pressure $\mathcal P$.

```python
pt = PressureTensor()
pt.setup(postproc.parameters, no_slices = 2)
# pt.compute()
pt.parse()
```

As usual the data is saved in several dataframes. In this case we have 4 dataframes

* A dataframe for the values of each of the elements of the pressure tensor for each of the slices, `pt.dataframe_slices`
* A dataframe for the mean and std values of each of the elements of the pressure tensor, `pt.dataframe`
* A dataframe for the ACF of each pair $\langle \mathcal P_{\alpha\beta}(t)\mathcal P_{\mu\nu}(0) \rangle$ for each slice, `pt.dataframe_acf_slices`
* A dataframe for the mean and std of the ACF of each pair $\langle \mathcal P_{\alpha\beta}(t)\mathcal P_{\mu\nu}(0) \rangle$, `pt.dataframe_acf`

Let's look at `pt.dataframe` and at its columns

```python
pt.dataframe
```

      Time
      Pressure
      Delta Pressure
      Pressure Tensor Kinetic xx
      Pressure Tensor Potential xx
      Pressure Tensor xx
      ...
      Pressure Tensor Potential zy
      Pressure Tensor zy
      Pressure Tensor Kinetic zz
      Pressure Tensor Potential zz
      Pressure Tensor zz

      NaN
      Mean
      Std
      Mean
      Std
      Mean
      Std
      Mean
      Std
      Mean
      ...
      Mean
      Std
      Mean
      Std
      Mean
      Std
      Mean
      Std
      Mean
      Std

      0
      0.000000e+00
      1.481050e+13
      4.475378e+09
      9.640046e+08
      4.624121e+09
      7.093304e+11
      1.803769e+10
      1.407423e+13
      3.070777e+10
      1.478356e+13
      ...
      -1.349294e+10
      9.080726e+08
      1.513634e+09
      6.403260e+09
      7.178245e+11
      2.283417e+09
      1.412591e+13
      1.183573e+10
      1.484373e+13
      9.552317e+09

      1
      6.250000e-17
      1.481062e+13
      4.360531e+09
      1.077576e+09
      4.509274e+09
      7.092128e+11
      1.788308e+10
      1.407437e+13
      3.043084e+10
      1.478358e+13
      ...
      -1.359784e+10
      7.774131e+08
      1.428573e+09
      6.230626e+09
      7.176454e+11
      1.632009e+09
      1.412631e+13
      1.180888e+10
      1.484395e+13
      1.017687e+10

      2
      1.250000e-16
      1.481073e+13
      4.238994e+09
      1.189024e+09
      4.387736e+09
      7.091114e+11
      1.771433e+10
      1.407450e+13
      3.012222e+10
      1.478361e+13
      ...
      -1.368992e+10
      6.259734e+08
      1.336832e+09
      6.048696e+09
      7.174492e+11
      9.777352e+08
      1.412669e+13
      1.176571e+10
      1.484414e+13
      1.078798e+10

      3
      1.875000e-16
      1.481084e+13
      4.112669e+09
      1.300868e+09
      4.261411e+09
      7.090262e+11
      1.753208e+10
      1.407463e+13
      2.978323e+10
      1.478365e+13
      ...
      -1.377276e+10
      4.507040e+08
      1.235023e+09
      5.853759e+09
      7.172359e+11
      3.218677e+08
      1.412707e+13
      1.170957e+10
      1.484430e+13
      1.138770e+10

      4
      2.500000e-16
      1.481095e+13
      3.982834e+09
      1.412491e+09
      4.131577e+09
      7.089578e+11
      1.733693e+10
      1.407475e+13
      2.941981e+10
      1.478371e+13
      ...
      -1.384260e+10
      2.570485e+08
      1.127174e+09
      5.650556e+09
      7.170051e+11
      3.343385e+08
      1.412743e+13
      1.164139e+10
      1.484443e+13
      1.197573e+10

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      149995
      9.374688e-12
      1.481016e+13
      4.676448e+09
      6.174698e+08
      4.527706e+09
      7.118162e+11
      2.094178e+10
      1.409873e+13
      3.950421e+09
      1.481055e+13
      ...
      -9.425949e+09
      6.014705e+09
      -5.177773e+09
      2.743918e+09
      7.148230e+11
      2.812314e+08
      1.410559e+13
      3.738486e+10
      1.482042e+13
      3.710363e+10

      149996
      9.374750e-12
      1.481018e+13
      4.712473e+09
      6.394380e+08
      4.563731e+09
      7.118652e+11
      2.120674e+10
      1.409889e+13
      4.123326e+09
      1.481076e+13
      ...
      -9.394952e+09
      6.262046e+09
      -5.058588e+09
      2.647215e+09
      7.150820e+11
      1.719736e+08
      1.410561e+13
      3.806962e+10
      1.482069e+13
      3.789764e+10

      149997
      9.374812e-12
      1.481020e+13
      4.744467e+09
      6.613843e+08
      4.595724e+09
      7.119236e+11
      2.144853e+10
      1.409906e+13
      4.318283e+09
      1.481098e+13
      ...
      -9.343740e+09
      6.501897e+09
      -4.937013e+09
      2.541221e+09
      7.153249e+11
      5.141538e+07
      1.410562e+13
      3.872970e+10
      1.482094e+13
      3.867828e+10

      149998
      9.374875e-12
      1.481023e+13
      4.768360e+09
      6.851347e+08
      4.619617e+09
      7.119910e+11
      2.166620e+10
      1.409922e+13
      4.541414e+09
      1.481121e+13
      ...
      -9.272190e+09
      6.732523e+09
      -4.812951e+09
      2.428042e+09
      7.155505e+11
      8.013261e+07
      1.410562e+13
      3.935915e+10
      1.482117e+13
      3.943928e+10

      149999
      9.374937e-12
      1.481025e+13
      4.787700e+09
      7.087402e+08
      4.638958e+09
      7.120667e+11
      2.185886e+10
      1.409938e+13
      4.789867e+09
      1.481144e+13
      ...
      -9.179610e+09
      6.954353e+09
      -4.685684e+09
      2.307675e+09
      7.157575e+11
      2.223411e+08
      1.410561e+13
      3.996308e+10
      1.482137e+13
      4.018542e+10

150000 rows × 59 columns

Note that the Pressure $\mathcal P(t)$ is readily calculated and provided as a column of the dataframe.

Note also that there is a multitude of columns. This is because in dense plasmas it is useful to know the contribution of both the kinetic term and potential term separately, as such the columns of each dataframe contain the kinetic, the potential, and the total value of each $\mathcal P_{\alpha\beta}$ and their ACFs.

Let's plot the Pressure as a function of time

```python
# Let's plot it
p_id = pt.total_num_density / postproc.therm.beta
ax = pt.plot(
    scaling = (1e-12, p_id),
    y = ("Pressure", "Mean"),
    xlabel = "Time [ps]",
    ylabel = r"$ \beta P(t)/n$"
       )
ax.plot(pt.dataframe['Time']*1e12, pt.dataframe[('Pressure','Mean')].expanding().mean()/p_id )
ax.legend(['Pressure', 'Moving avg'])
```

## Pressure from RDF

Let's now calculate the pressure from the integral of the RDF. This is obtained from the method `compute_from_rdf` of the `Thermodynamics` object.

Looking at the documentation of this [method](:meth:`sarkas.tool.observables.Thermodynamics.compute_from_rdf`) we notice that it returns five values:
the Hartree and correlational terms between species :math:`A` and :math:`B` and the ideal pressure $n k_B T$.

The total pressure is given from the sum of the three terms and should be equal to the

$$ P = n k_BT + P_{\rm Hartree} + P_{\rm Corr} = {\operatorname {Mean} } \left \{ \mathcal P(t) \right \} $$

```python
nkT, _, _, p_h, p_c = postproc.therm.compute_from_rdf(rdf, postproc.potential)

P_rdf = nkT + p_h + p_c
P_trace = pt.dataframe[("Pressure", "Mean")].mean()

print("The relative difference between the two methods is = {:.2f} %".format((P_rdf[0] - P_trace)*100/P_rdf[0] ) )
```

    The relative difference between the two methods is = 0.03 %

It seems that we have done a good job!

### Sum rule

Let's now check that we have calculated the ACF correctly. The equal time ACFs of the elements of $\overleftrightarrow{\mathcal P}(t)$ obey the following sum rules

$$
\mathcal J_{zzzz}(0) = \frac 13 \sum_{\alpha}\left \langle \mathcal P_{\alpha\alpha}(0)\mathcal P_{\alpha\alpha}(0) \right \rangle  =  \frac{n}{\beta^2} \left [ 3 + \frac{2\beta}{15} I_1 + \frac \beta5 I_2 \right ] ,
$$
$$
\mathcal J_{zzxx}(0) = \frac 16 \sum_{\alpha} \sum_{\beta\neq\alpha} \left \langle \mathcal P_{\alpha\alpha}(0)\mathcal P_{\beta\beta}(0) \right \rangle = \frac{n}{\beta^2} \left [ 1 - \frac{2\beta}{5} I_1 + \frac \beta{15} I_2 \right ] ,
$$
$$
\mathcal J_{xyxy}(0) = \frac 16 \sum_{\alpha}\sum_{\beta \neq \alpha} \left \langle \mathcal P_{\alpha\beta}(0)\mathcal P_{\alpha\beta}(0) \right \rangle = \frac{n}{\beta^2} \left [ 1 + \frac{4\beta}{15} I_1 + \frac \beta{15} I_2 \right ] ,
$$

where

$$
I_1 = 2\pi n \int dr \, g(r) r^3 \frac{d\phi}{dr}, \quad I_2 = 2\pi n \int dr\, g(r) r^4 \frac{d^2\phi}{dr^2}.
$$

Notice that all three equal time ACF satisfy

$$ \mathcal J_{zzzz}(0) - \mathcal J_{zzxx}(0) = 2 \mathcal J_{xyxy}(0) .$$

Let's look at the dataframe of the ACF first

```python
pt.dataframe_acf
```

      Time
      Pressure ACF
      Delta Pressure ACF
      Pressure Tensor Kinetic ACF xxxx
      Pressure Tensor Potential ACF xxxx
      Pressure Tensor Kin-Pot ACF xxxx
      ...
      Pressure Tensor Kinetic ACF zzzz
      Pressure Tensor Potential ACF zzzz
      Pressure Tensor Kin-Pot ACF zzzz
      Pressure Tensor Pot-Kin ACF zzzz
      Pressure Tensor ACF zzzz

      NaN
      Mean
      Std
      Mean
      Std
      Mean
      Std
      Mean
      Std
      Mean
      ...
      Mean
      Std
      Mean
      Std
      Mean
      Std
      Mean
      Std
      Mean
      Std

      0
      0.000000e+00
      2.193225e+26
      4.406888e+21
      7.042465e+18
      1.273591e+18
      5.099171e+23
      2.827243e+20
      1.987219e+26
      3.916734e+22
      1.006467e+25
      ...
      5.096344e+23
      8.893208e+20
      1.986865e+26
      1.015599e+22
      1.006103e+25
      8.459061e+21
      1.006103e+25
      8.459061e+21
      2.193182e+26
      7.651456e+21

      1
      6.250000e-17
      2.193225e+26
      4.406887e+21
      7.039566e+18
      1.273605e+18
      5.099170e+23
      2.827093e+20
      1.987219e+26
      3.917093e+22
      1.006467e+25
      ...
      5.096344e+23
      8.893161e+20
      1.986865e+26
      1.015870e+22
      1.006103e+25
      8.458738e+21
      1.006103e+25
      8.459131e+21
      2.193182e+26
      7.648481e+21

      2
      1.250000e-16
      2.193225e+26
      4.406876e+21
      7.030938e+18
      1.273617e+18
      5.099169e+23
      2.826976e+20
      1.987219e+26
      3.917447e+22
      1.006467e+25
      ...
      5.096342e+23
      8.893180e+20
      1.986865e+26
      1.016138e+22
      1.006103e+25
      8.458483e+21
      1.006103e+25
      8.459218e+21
      2.193182e+26
      7.645642e+21

      3
      1.875000e-16
      2.193225e+26
      4.406855e+21
      7.016597e+18
      1.273627e+18
      5.099167e+23
      2.826892e+20
      1.987219e+26
      3.917796e+22
      1.006467e+25
      ...
      5.096339e+23
      8.893263e+20
      1.986865e+26
      1.016401e+22
      1.006103e+25
      8.458297e+21
      1.006103e+25
      8.459323e+21
      2.193182e+26
      7.642939e+21

      4
      2.500000e-16
      2.193225e+26
      4.406825e+21
      6.996570e+18
      1.273635e+18
      5.099164e+23
      2.826843e+20
      1.987219e+26
      3.918140e+22
      1.006467e+25
      ...
      5.096335e+23
      8.893410e+20
      1.986865e+26
      1.016659e+22
      1.006103e+25
      8.458181e+21
      1.006103e+25
      8.459442e+21
      2.193182e+26
      7.640372e+21

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      149995
      9.374688e-12
      2.193499e+26
      7.464009e+21
      -9.260680e+18
      2.564085e+18
      5.046615e+23
      2.590839e+21
      1.984372e+26
      4.854286e+23
      1.002041e+25
      ...
      5.131844e+23
      6.688760e+20
      1.992652e+26
      3.809483e+23
      1.010492e+25
      8.977493e+21
      1.011984e+25
      4.153429e+22
      2.200032e+26
      4.141740e+23

      149996
      9.374750e-12
      2.193492e+26
      6.760933e+21
      -9.466666e+18
      2.228395e+18
      5.047103e+23
      2.612042e+21
      1.984374e+26
      4.891900e+23
      1.002078e+25
      ...
      5.133477e+23
      9.467156e+20
      1.992626e+26
      3.851990e+23
      1.010650e+25
      8.148359e+21
      1.012135e+25
      4.639596e+22
      2.200038e+26
      4.243933e+23

      149997
      9.374812e-12
      2.193486e+26
      6.052459e+21
      -9.666614e+18
      1.888858e+18
      5.047653e+23
      2.630856e+21
      1.984376e+26
      4.929383e+23
      1.002119e+25
      ...
      5.135030e+23
      1.227036e+21
      1.992600e+26
      3.893844e+23
      1.010799e+25
      7.263669e+21
      1.012278e+25
      5.124653e+22
      2.200042e+26
      4.345943e+23

      149998
      9.374875e-12
      2.193479e+26
      5.334349e+21
      -9.857902e+18
      1.544056e+18
      5.048264e+23
      2.647229e+21
      1.984378e+26
      4.966713e+23
      1.002164e+25
      ...
      5.136500e+23
      1.509553e+21
      1.992572e+26
      3.934963e+23
      1.010940e+25
      6.324277e+21
      1.012413e+25
      5.608089e+22
      2.200044e+26
      4.447625e+23

      149999
      9.374937e-12
      2.193472e+26
      4.626795e+21
      -1.004232e+19
      1.194677e+18
      5.048934e+23
      2.661116e+21
      1.984380e+26
      5.003741e+23
      1.002213e+25
      ...
      5.137886e+23
      1.793975e+21
      1.992544e+26
      3.975644e+23
      1.011072e+25
      5.330746e+21
      1.012540e+25
      6.089548e+22
      2.200043e+26
      4.549231e+23

150000 rows × 815 columns

Notice that in this case we have many more columns since now we have the ACF of the kinetic-kinetic, kinetic-potential, potential-kinetic, potential-potential, and the total ACF of each pair of elements.

Let's verify the sum rules.

```python
# Diagonal terms
column_zzzz = [
    ('Pressure Tensor ACF xxxx', 'Mean'),
     ('Pressure Tensor ACF yyyy', 'Mean'),
     ('Pressure Tensor ACF zzzz', 'Mean'),
]
J_zzzz_0 = pt.dataframe_acf[column_zzzz].iloc[0].mean()

# Cross-Diagonal Terms
column_zzxx = [
    ('Pressure Tensor ACF xxyy', 'Mean'),
    ('Pressure Tensor ACF xxzz', 'Mean'),
    ('Pressure Tensor ACF yyxx', 'Mean'),
    ('Pressure Tensor ACF yyzz', 'Mean'),
    ('Pressure Tensor ACF zzxx', 'Mean'),
    ('Pressure Tensor ACF zzyy', 'Mean'),
]
J_zzxx_0 = pt.dataframe_acf[column_zzxx].iloc[0].mean()

# Cross Off Diagonal terms
column_xyxy = [
    ('Pressure Tensor ACF xyxy', 'Mean'),
    ('Pressure Tensor ACF xzxz', 'Mean'),
    ('Pressure Tensor ACF yxyx', 'Mean'),
    ('Pressure Tensor ACF yzyz', 'Mean'),
    ('Pressure Tensor ACF zxzx', 'Mean'),
    ('Pressure Tensor ACF zyzy', 'Mean'),
]
J_xyxy_0 = pt.dataframe_acf[column_xyxy].iloc[0].mean()

# The units of J's are [Density *  Energy]^2

print('The isotropy condition : (J_zzzz_0 - J_zzxx_0 )/( 2*J_xyxy_0 ) = {:.4f}'.format( (J_zzzz_0 - J_zzxx_0)/(2.0 * J_xyxy_0)  ))
```

    The isotropy condition : (J_zzzz_0 - J_zzxx_0 )/( 2*J_xyxy_0 ) = 0.9814

Not exactly 1 but pretty close.

Let's now verify the sum rules. These are calculated from the `pt.sum_rule` method

```python
# These sigmas have units of n^2
sigma_zzzz, sigma_zzxx, sigma_xyxy  = pt.sum_rule(postproc.therm.beta, rdf, postproc.potential)

```

```python
"{:.4e}, {:.4e}".format(J_zzzz_0, nkT**2)
```

    '2.1932e+26, 5.0955e+23'

## Viscosity

The shear viscosity is calculated from the Green-Kubo relation

\begin{equation}
\eta = \frac{\beta V}{6} \sum_{\alpha} \sum_{\gamma \neq \alpha} \int_0^{\infty} dt \, \left \langle \mathcal P_{\alpha\gamma}(t) \mathcal P_{\alpha\gamma}(0) \right \rangle,
\end{equation}

where $\beta = 1/k_B T$, $\alpha,\gamma = {x, y, z}$ and $\mathcal J_{\alpha\gamma}(t)$ is the autocorrelation function of the $\alpha,\gamma$ element of the

The bulk viscosity is given by a similar relation

\begin{equation}
\eta_V = \beta V \int_0^{\infty}dt \,  \left \langle \delta \mathcal P(t) \delta \mathcal P(0) \right \rangle,
\end{equation}

where

\begin{equation}
\delta \mathcal P(t) = \mathcal P(t) - \left \langle \mathcal P  \right \rangle
\end{equation}

is the deviation of the scalar pressure.

```python
tc = TransportCoefficients(params = postproc.parameters, no_slices = 2)
```

```python
tc.parse(observable = pt, tc_name = "Viscosities")
# tc.viscosities(pt,plot = True)
```

    Data saved in:
     Simulations/yocp_kappa2/PostProcessing/TransportCoefficients/Production/Viscosities_yocp_kappa2.h5
    Simulations/yocp_kappa2/PostProcessing/TransportCoefficients/Production/Viscosities_slices_yocp_kappa2.h5

    No. of slices = 2
    No. dumps per slice = 30000
    Time interval of autocorrelation function = 9.3750e-12 [s] ~ 7205 w_p T

```python
acf_str = "Delta Pressure ACF"
acf_avg = pt.dataframe_acf[("Delta Pressure ACF", "Mean")]
acf_std = pt.dataframe_acf[("Delta Pressure ACF", "Std")]

pq = "Bulk Viscosity"
tc_avg = tc.viscosity_df[(pq, "Mean")]
tc_std = tc.viscosity_df[(pq, "Std")]
```

```python
fig, axes = tc.plot_tc(
    time = tc.viscosity_df["Time"].iloc[:,0].to_numpy(),
    acf_data=np.column_stack((acf_avg, acf_std)),
    tc_data=np.column_stack((tc_avg, tc_std)),
    acf_name=acf_str,
    tc_name="Bulk Viscosity",
    figname="{}_Plot.png".format("Bulk Viscosity"),
    show=False
)
axes[0].set(ylim = (-1, 1.05))
axes[1].set(ylim = (-0.5, 1000 ) )
```

```python
pq = "Shear Viscosity"
tc_avg = tc.viscosity_df[(pq, "Mean")]
tc_std = tc.viscosity_df[(pq, "Std")]

rescale = pt.total_plasma_frequency * pt.a_ws**2 * pt.species_masses[0] * pt.total_num_density * 0.0654
fig, ax = plt.subplots(1,1)
ax.plot(tc.viscosity_df["Time"].iloc[:,0].to_numpy()*1e12,
       tc_avg / rescale,
       label = r'$\mu$')

ax.fill_between(
    tc.viscosity_df["Time"].iloc[:,0].to_numpy()*1e12,
    (tc_avg - tc_std) / rescale,
    (tc_avg + tc_std) / rescale,
    alpha = 0.2)

ax.plot(tc.viscosity_df["Time"].iloc[:,0].to_numpy()*1e12,
       tc_avg.expanding().mean()/rescale,
       label = r'Moving avg')
ax.set(xlabel = r'Time difference $\tau$ [ps]')
```

I am missing a factor of 2 somewhere and can't figure out where.

To be continued.