```python
#we may need some code in the ../python directory and/or matplotlib styles
import sys
import os
sys.path.append('../python/')

#set up matplotlib
os.environ['MPLCONFIGDIR'] = '../mplstyles'
print(os.environ['MPLCONFIGDIR'])
import matplotlib as mpl
from matplotlib import pyplot as plt
#got smarter about the mpl config: see mplstyles/ directory
plt.style.use('standard')
print(mpl.__version__)
print(mpl.get_configdir())

#fonts
# Set the font dictionaries (for plot title and axis titles)
title_font = {'fontname':'Arial', 'size':'16', 'color':'black', 'weight':'normal',
              'verticalalignment':'bottom'} # Bottom vertical alignment for more space
axis_font = {'fontname':'Arial', 'size':'32'}
legend_font = {'fontname':'Arial', 'size':'22'}

#fonts global settings
mpl.rc('font',family=legend_font['fontname'])

#set up numpy
import numpy as np
```

    ../mplstyles
    3.0.3
    /home/phys/villaa/analysis/misc/nrFano_paper2019/mplstyles

# Summary

The purpose of this book is to show how the extracted Fano factor, combined with the expected yield with plus multiple-scatter modification, can predict the trend of the EDELWEISS measured ionization yield width. And, how that final predicted width function can be used to extract the systematic deviation due to the finite binning of the EDELWEISS data.

# Extracted Fano Factor Matching

```python
import h5py
filename = 'data/systematic_error_fits.h5'
#remove vars
f = h5py.File(filename,'r')
#for i in f['mcmc/edwdata_sys_error']:
#    print(i)

#save the results for the Edw fit
path='{}/{}/'.format('mcmc','edwdata_sys_error')

Cms = np.asarray(f[path+'Cms'])
slope = np.asarray(f[path+'m'])
a_yield = np.asarray(f[path+'A'])
b_yield = np.asarray(f[path+'B'])
aH = np.asarray(f[path+'aH'])
scale = np.asarray(f[path+'scale'])
samples = np.asarray(f[path+'samples'])
sampsize = np.asarray(f[path+'sampsize'])
xl = np.asarray(f[path+'Er'])
upvec = np.asarray(f[path+'Csig_u'])
dnvec = np.asarray(f[path+'Csig_l'])
Sigtot = np.asarray(f[path+'Sigss'])
Sigss = np.sqrt(Sigtot**2 - (Cms+slope*xl)**2)

print(Cms)
print(samples[0:5,:])
f.close()
```

    0.040112000594491624
    [[1.50381624e-02 4.31804341e-02 2.23413626e-05 1.05046922e+00
      8.18547846e-02 2.04466981e-01]
     [1.51415346e-02 4.35824578e-02 1.31947144e-05 1.04823979e+00
      8.24564001e-02 1.90742152e-01]
     [1.51415346e-02 4.35824578e-02 1.31947144e-05 1.04823979e+00
      8.24564001e-02 1.90742152e-01]
     [1.51415346e-02 4.35824578e-02 1.31947144e-05 1.04823979e+00
      8.24564001e-02 1.90742152e-01]
     [1.51415346e-02 4.35824578e-02 1.31947144e-05 1.04823979e+00
      8.24564001e-02 1.90742152e-01]]

```python
import fano_calc as fc
#recall defaults (filename='test.h5',
#det='GGA3',band='ER',F=0.00001,V=4.0,alpha=(1/10000.0),aH=0.035,Erv=None,sigv=None,erase=False)
E,sig = fc.RWCalc(filename='data/res_calc.h5',band='NR',alpha=(1/18.0),F=999)
#Ebase,sigbase = fc.RWCalc(filename='data/res_calc.h5',band='NR',alpha=(1/18.0),F=000)

import EdwRes as er
#instead I should use Amy's Fit
f = er.get_sig_nuc_func_alt(1.3,1.5,3.1,0.4,2.7,pars={'V':scale*4.0,'eps_eV':3.0,'a':a_yield,'b':b_yield}, \
                            aH=aH,C=0,m=0)
Ebase = np.arange(0.1,200,0.1)
sigbase = f(Ebase)
```

```python
# import data from Edelweiss
import pandas as pds
res_data = pds.read_csv("data/edelweiss_NRwidth_GGA3_data.txt", skiprows=1, \
                       names=['E_recoil', 'sig_NR', 'E_recoil_err', 'sig_NR_err'], \
                       delim_whitespace=True)

resER_data = pds.read_csv("data/edelweiss_ERwidth_GGA3_data.txt", skiprows=1, \
                         names=['E_recoil', 'sig_ER', 'sig_ER_err'], \
                         delim_whitespace=True)

resER_data = resER_data.sort_values(by='E_recoil')

print (res_data.head(10))
E_recoil = res_data["E_recoil"]
sig_NR = res_data["sig_NR"]
sig_NR_err = res_data['sig_NR_err']
E_recoil_ER = resER_data["E_recoil"]
sig_ER = resER_data["sig_ER"]
sig_ER_err = resER_data['sig_ER_err']

#order those things
idx = np.argsort(E_recoil)
E_recoil = E_recoil[idx]
sig_NR = sig_NR[idx]
sig_NR_err = sig_NR_err[idx]

#make a selectable version of the minimum index
minidx = 2
```

       E_recoil    sig_NR  E_recoil_err  sig_NR_err
    0   16.1946  0.062345      0.946176    0.001157
    1   16.4428  0.062345      0.945278    0.001157
    2   44.2627  0.046528      0.992477    0.001543
    3   24.5012  0.059397      0.992477    0.001185
    4   97.7172  0.044847      1.033260    0.002783
    5   58.4014  0.050082      0.991830    0.002288
    6   34.2156  0.053417      1.033260    0.001102

```python
#set up a 1d plot
fig,axes = plt.subplots(1,1,figsize=(9.0,8.0),sharex=True)
ax1 = axes

for aH_em, Cms_em, m_em, scale_em, A_em, B_em in samples[np.random.randint(len(samples), size=100)]:
    ax1.plot(xl, np.sqrt(Sigss**2+(Cms_em+m_em*xl)**2), color="orange", alpha=0.1)

ax1.plot(xl,upvec,color='r',linestyle="--",linewidth=2, \
         label='1$\sigma$ fluct.')
ax1.plot(xl,dnvec,color='r',linestyle="--",linewidth=2, \
         label='')

ax1.plot(xl,np.sqrt(Sigss**2+(Cms+xl*slope)**2),color='g',linestyle="-",linewidth=3, \
         label='(C$_0$={:01.3}; m={:01.2E})'.format(Cms,slope))

ax1.plot(E,sig,color='b',linestyle=":",linewidth=2, \
        label = 'Extracted Fano')

ax1.errorbar(E_recoil[minidx::],sig_NR[minidx::], yerr=sig_NR_err[minidx::], \
         color='k', marker='o', markersize=4,linestyle='none',label='NR Edw. Measurement', linewidth=2)

ymin = 0.04
ymax = 0.1

ax1.set_yscale('linear')
#ax1.set_yscale('log')
ax1.set_xlim(10, 200)
ax1.set_ylim(ymin,ymax)
ax1.set_xlabel(r'recoil energy [keV]',**axis_font)
ax1.set_ylabel('ionization yield width',**axis_font)
ax1.grid(True)
ax1.yaxis.grid(True,which='minor',linestyle='--')
ax1.legend(loc=1,prop={'size':22})
#ax1.legend(bbox_to_anchor=(1.04,1),borderaxespad=0,prop={'size':22})

for axis in ['top','bottom','left','right']:
  ax1.spines[axis].set_linewidth(2)

plt.tight_layout()
#plt.savefig('figures/figure.png')
plt.show()
```

# Binning Systematic

energy bin|interval (keV)
:-|:-
0|\[5,10)
1|\[10,20)
2|\[20,30)
3|\[30,40)
4|\[40,50)
5|\[50,70)
6|\[70,150)

```python
bins = [5, 10, 20, 30, 40, 50, 70,150]
bins = np.asarray(bins)
```

In order to evaluate the systematic we can approximate that the distribution in ionization yield at each energy is normal. That is it follows the probability distribution function:

\begin{equation}
P(Q|E_r) = \frac{1}{\sqrt{2\pi\sigma_Q(E_r)^2}} \exp{\left[\frac{(Q-\bar{Q})^2}{2\sigma_Q(E_r)^2}\right]},
\end{equation}

Is valid for a fixed energy, $E_r$.

Using the law of conditional probability we can then simply obtain the joint distribution $P(Q,E_r)$ and integrate over the recoil energy bin. We get the following:

\begin{equation}
P_i(Q) = \frac{\int_{a_i}^{b_i} dE_r P(Q|E_r)P(E_r)}{N},
\end{equation}

where N is a normalization factor, $P_i(Q)$ is the yield probability distribution over the bin $i$, and $P(E_r)$ is the probability of getting a given recoil energy (approximated throughout this analysis as an exponential with decay coefficient $\alpha$ = (1/18.0) keV$^{-1}$).

With this function the _measured_ yield width over a finite bin can be calculated quite simply:

\begin{equation}
\begin{aligned}
\langle Q \rangle &= \int_{-\infty}^{\infty} dQ Q P(Q) \\
\langle Q^2 \rangle &= \int_{-\infty}^{\infty} dQ Q^2 P(Q) \\
\sigma_{Qi} &= \sqrt{\langle Q^2 \rangle - \langle Q \rangle^2}
\end{aligned}
\end{equation}

```python
#let's find the corrections for the best fit points
import binning_sys as bsys

print(bins)
delta = lambda C,m: bsys.getBinSys(C,m,bins=bins)[1]
deltav = np.vectorize(delta)

del110 = deltav(Cms,slope)
del110 = np.array(del110)
print(del110)
#print(np.shape(deltav(Candm[:,0],Candm[:,1])))
#del110 = deltav(Candm[:,0],Candm[:,1])
#print(delta(Candm[0,0],Candm[0,1]))
```

    [  5  10  20  30  40  50  70 150]
    here
    here
    [0.95090318 0.93151709 0.97537397 0.98806036 0.99327094 0.98675077
     0.97495657]

```python
#let's make a callable out of the predicted single-scatter resolution
import scipy.interpolate as inter

sigQnr_c = inter.InterpolatedUnivariateSpline(Ebase, sigbase, k=3)
sigQnr_c_v = np.vectorize(sigQnr_c)
```

```python
def log_likelihood(theta, x, y, yerr,sigfunc):
    Cms, m = theta
    model = np.sqrt(sigfunc(x)**2 + (Cms+m*x)**2)
    #sigma2 = yerr**2 + model**2*np.exp(2*log_f)
    sigma2 = yerr**2
    return -0.5*np.sum((y-model)**2/sigma2 + np.log(sigma2))
```

```python
from scipy.optimize import minimize
np.random.seed(42)
nll = lambda *args: -log_likelihood(*args)
initial = np.array([0.0207, 4.73e-5]) + 0.001*np.random.randn(2)
soln = minimize(nll, initial, args=(E_recoil[minidx::], sig_NR[minidx::], sig_NR_err[minidx::],sigQnr_c_v))
Cms_ml, m_ml = soln.x

#lmfout = lmf.minimize(residual_const, params, args=(E_recoil[2::], sig_NR[2::], sig_NR_err[2::],sigQnr_c_v))
print("Maximum likelihood estimates:")
print("m = {0:.4f}".format(Cms_ml))
print("b = {0:.3e}".format(m_ml))

```

    Maximum likelihood estimates:
    m = 0.0401
    b = 4.113e-05

```python
#do correction to data points
E_edw = E_recoil
sig_edw = sig_NR*del110
sig_edw_err = sig_NR_err*del110
print(bins)
print(del110)

print(E_edw)
print(sig_NR)
print(sig_edw)
print(sig_edw_err)

nll = lambda *args: -log_likelihood(*args)
initial = np.array([0.0207, 4.73e-5]) + 0.001*np.random.randn(2)
soln = minimize(nll, initial, args=(E_edw[minidx::], sig_edw[minidx::], sig_edw_err[minidx::],sigQnr_c_v))
Cms_ml, m_ml = soln.x

#lmfout = lmf.minimize(residual_const, params, args=(E_recoil[2::], sig_NR[2::], sig_NR_err[2::],sigQnr_c_v))
print("Maximum likelihood estimates:")
print("m = {0:.4f}".format(Cms_ml))
print("b = {0:.3e}".format(m_ml))
```

    [  5  10  20  30  40  50  70 150]
    [0.95090318 0.93151709 0.97537397 0.98806036 0.99327094 0.98675077
     0.97495657]
    0    16.1946
    1    16.4428
    3    24.5012
    6    34.2156
    2    44.2627
    5    58.4014
    4    97.7172
    Name: E_recoil, dtype: float64
    0    0.062345
    1    0.062345
    3    0.059397
    6    0.053417
    2    0.046528
    5    0.050082
    4    0.044847
    Name: sig_NR, dtype: float64
    0    0.059284
    1    0.058076
    3    0.057934
    6    0.052779
    2    0.046215
    5    0.049419
    4    0.043724
    Name: sig_NR, dtype: float64
    0    0.001101
    1    0.001078
    3    0.001156
    6    0.001089
    2    0.001533
    5    0.002257
    4    0.002714
    Name: sig_NR_err, dtype: float64
    Maximum likelihood estimates:
    m = 0.0386
    b = 5.175e-05

```python
#set up a 1d plot
fig,axes = plt.subplots(1,1,figsize=(9.0,8.0),sharex=True)
ax1 = axes

for aH_em, Cms_em, m_em, scale_em, A_em, B_em in samples[np.random.randint(len(samples), size=100)]:
    ax1.plot(xl, np.sqrt(Sigss**2+(Cms_em+m_em*xl)**2), color="orange", alpha=0.1)

ax1.plot(xl,upvec,color='r',linestyle="--",linewidth=2, \
         label='1$\sigma$ fluct.')
ax1.plot(xl,dnvec,color='r',linestyle="--",linewidth=2, \
         label='')

ax1.plot(xl,np.sqrt(Sigss**2+(Cms+xl*slope)**2),color='g',linestyle="-",linewidth=3, \
         label='(C$_0$={:01.3}; m={:01.2E})'.format(Cms,slope))

ax1.plot(xl,np.sqrt(Sigss**2+(Cms_ml+xl*m_ml)**2),color='b',linestyle="--",linewidth=3, \
         label='bc (C$_0$={:01.3}; m={:01.2E})'.format(Cms_ml,m_ml))

ax1.plot(E,sig,color='b',linestyle=":",linewidth=2, \
        label = 'Extracted Fano')

ax1.errorbar(E_recoil[minidx::],sig_edw[minidx::], yerr=sig_edw_err[minidx::], \
         color='k', marker='o', markersize=4,linestyle='none',label='NR Edw. Measurement', linewidth=2)

ymin = 0.04
ymax = 0.1

ax1.set_yscale('linear')
#ax1.set_yscale('log')
ax1.set_xlim(10, 200)
ax1.set_ylim(ymin,ymax)
ax1.set_xlabel(r'recoil energy [keV]',**axis_font)
ax1.set_ylabel('ionization yield width',**axis_font)
ax1.grid(True)
ax1.yaxis.grid(True,which='minor',linestyle='--')
ax1.legend(loc=1,prop={'size':22})
#ax1.legend(bbox_to_anchor=(1.04,1),borderaxespad=0,prop={'size':22})

for axis in ['top','bottom','left','right']:
  ax1.spines[axis].set_linewidth(2)

plt.tight_layout()
#plt.savefig('figures/figure.png')
plt.show()
```

```python
import h5py
filename = 'data/test_bs_6par.h5'
#remove vars
f = h5py.File(filename,'r')

#save the results for the Edw fit
path='{}/{}/'.format('mcmc','edwdata_binsys')

bins_binsys = np.asarray(f[path+'bins_binsys'])
binsys = np.asarray(f[path+'binsys'])

#print(binsys)
Nsamp = np.shape(binsys)[0]
print(Nsamp)
f.close()
```

    100

```python
#set up a 1d plot
fig,axes = plt.subplots(1,1,figsize=(9.0,8.0),sharex=True)
ax1 = axes

for di in binsys:
    #print(di)
    soln = minimize(nll, initial, args=(E_edw[minidx::], sig_edw[minidx::]*di[minidx::],  \
                                        sig_edw_err[minidx::]*di[minidx::],sigQnr_c_v))
    Cms_mli, m_mli = soln.x
    #print(Cms_mli,m_mli)
    ax1.plot(xl, np.sqrt(Sigss**2+(Cms_mli+m_mli*xl)**2), color="blue", alpha=0.1)

ax1.plot(xl,upvec,color='r',linestyle="--",linewidth=2, \
         label='1$\sigma$ fluct.')
ax1.plot(xl,dnvec,color='r',linestyle="--",linewidth=2, \
         label='')

ax1.plot(xl,np.sqrt(Sigss**2+(Cms+xl*slope)**2),color='g',linestyle="-",linewidth=3, \
         label='(C$_0$={:01.3}; m={:01.2E})'.format(Cms,slope))

ax1.plot(xl,np.sqrt(Sigss**2+(Cms_ml+xl*m_ml)**2),color='b',linestyle="--",linewidth=3, \
         label='bc (C$_0$={:01.3}; m={:01.2E})'.format(Cms_ml,m_ml))

ax1.plot(E,sig,color='b',linestyle=":",linewidth=2, \
        label = 'Extracted Fano')

ax1.errorbar(E_recoil[minidx::],sig_edw[minidx::], yerr=sig_edw_err[minidx::], \
         color='k', marker='o', markersize=4,linestyle='none',label='NR Edw. Measurement', linewidth=2)

ymin = 0.04
ymax = 0.1

ax1.set_yscale('linear')
#ax1.set_yscale('log')
ax1.set_xlim(10, 200)
ax1.set_ylim(ymin,ymax)
ax1.set_xlabel(r'recoil energy [keV]',**axis_font)
ax1.set_ylabel('ionization yield width',**axis_font)
ax1.grid(True)
ax1.yaxis.grid(True,which='minor',linestyle='--')
ax1.legend(loc=1,prop={'size':22})
#ax1.legend(bbox_to_anchor=(1.04,1),borderaxespad=0,prop={'size':22})

for axis in ['top','bottom','left','right']:
  ax1.spines[axis].set_linewidth(2)

plt.tight_layout()
#plt.savefig('figures/figure.png')
plt.show()
```

```python

```


# Week 3
## Introduction to Solid State

```python
import numpy as np
import matplotlib.pyplot as plt
import os
import subprocess
from polypy.read import History
from polypy.msd import MSD
from polypy import plotting

def get_diffusion(file, atom):

    with open(file) as f:
        y = False
        for line in f:
            if str("atom        D ") in line:
                y = True
            if y == True and str(atom) in line:
                d = line.split()
                break
    return d
```

# Background#

Now that you are familiar with molecular dynamics, you are now going to use it to tackle some real world problems.

The transport properties of a material determine many properties that are utilised for modern technological applications. For example, solid oxide fuel cell (SOFCs), which are an alternative to batteries, materials are dependent on the movement of charge carriers through the solid electrolyte. Another example are nuclear fuel materials which oxidise and fall apart - this corrosive behaviour is dependent on the diffusion of oxygen into the lattice.

Due to the importance of the transport properties of these materials, scientists and engineers spend large amounts of their time tring to optomise these properties using different stoichiometries, introducing defects and by using different syntheisis techniques.

# Aim and Objectives #

The **Aim** of the next **five weeks** is to **investigate** the transport properties of a simple fluorite material - CaF$_2$.

The **first objective** is to **investigate** how the transport properties of CaF$_2$ are affected by temperature

The **second objective** is to **investigate** how the transport properties of CaF$_2$ are affected by structural defects (Schottky and Frenkel)

The **third objective** is to **investigate** how the transport properties of CaF$_2$ are affected by chemcial dopants (e.g. different cations)

A rough breakdown looks as follows:

**Week 3**
-  Molecular dynamics simulations of stoichiomteric CaF$_2$

**Week 4**
-  Molecular dynamics simulations of CaF$_2$ containing Schottky defects

**Week 5**
- Molecular dynamics simulations of CaF$_2$ containing Frenkel defects

**Week 6**
-  Molecular dynamics simulations of CaF$_2$ containing various dopants

**Week 7**
-  Molecular dynamics simulations of CaF$_2$ containing various dopants

By these **five weeks** you will be able to:

- **Perform** molecular dynamics simulations at different temperatures
- **Manipulate** the input files
- **Adjust** the ensemble for the simulation
- **Examine** the volume and energy of different simulations
- **Apply** VMD to visualize the simulation cell and evaluate radial distribution - coefficients

The **Aim** of this **week** (week 3) is to **investigate** the temperature-dependence of the transport properties of a simple fluorite material CaF$_2$ using molecular dynamics (MD).

The **first objective** is to **familiarise** yourself with the molecular simulation software package DL_POLY

The **second objective** is to **complete** a tutorial which demonstrates how to calculate diffusion coefficients

The **third objective** is to is to **complete** a tutorial which demonstrates how to **calculate** the activation energy barrier of F diffusion

## Introduction to DL_POLY

DL_POLY is a molecular dynamics (MD) program maintained by Daresbury laboratories. In contrast to pylj, DL_POLY is a three-dimensional MD code that is used worldwide by computational scientists for molecular simulation, but it should be noted that the theory is exactly the same and any understanding gained from pylj is completely applicable to DL_POLY.

For the next five weeks you will use DL_POLY to run short MD simulations on CaF$_2$. You first need to understand the input files required for DL_POLY.

**CONTROL**

This is the file that contains all of the simulation parameters, e.g. simulation temperature, pressure, number of steps etc.

**CONFIG**

This is the file that contains the structure - i.e. the atomic coordinates of each atom.

**FIELD**

This is the file that contains the force field or potential model e.g. Lennard-Jones.

# Exercise 1: Setting Up an MD Simulation#

First, we will use METADISE to produce DL_POLY input files.

Contained within the folder Input/ you will find a file called input.txt.

This is the main file that you will interact with over the next five weeks and is the input file for METADISE which generates the 3 DL_POLY input files: FIELD, CONTROL and CONFIG.

Essentially it is easier to meddle with input.txt than it is to meddle with the 3 DL_POLY files everytime you want to change something.

To run METADISE we will use the subprocess python module.

To use subprocess - specify what program you want to run and the file that you want to run it in, you will need to ensure the file path is correct.

To **generate** the 3 DL_POLY input files: FIELD, CONTROL and CONFIG, **run** the cell below:

#### It is essential that the codes that were downloaded from [here](https://people.bath.ac.uk/chsscp/teach/adv.bho/progs.zip) are in the Codes/ folder in the parent directory, or this following cell will crash.

```python
subprocess.call('../Codes/metadise.exe', cwd='Input/')
os.rename('Input/control_o0001.dlp', 'Input/CONTROL')
os.rename('Input/config__o0001.dlp', 'Input/CONFIG')
os.rename('Input/field___o0001.dlp', 'Input/FIELD')
```

Now you should have a CONFIG, CONTROL and FIELD file within the Input/ directory.

In theory you could just call the DL_POLY program in this directory and your simulation would run.

However, we need to tweak the CONTROL file in order to set up our desired simulation.

1. **Make** a new subdirectory in the week 3 directory named "Example/" and copy CONFIG, CONTROL and FIELD to that subdirectory.

2. Now **edit** the CONTROL file to change the following:

Temperature 300 ---> Temperature 1500
Steps 5001 ---> Steps 40000
ensemble nve ---> ensemble npt hoover 0.1 0.5
trajectory nstraj= 1    istraj=   250 keytrj=0 ---> trajectory nstraj= 0   istraj= 100  keytrj=0

3.Now your simulation is ready, **check** the structure before you run the simulation. You can view the CONFIG file in three dimensions using the VESTA program

It is always good to **check** your structure before (CONFIG) and after (REVCON) the simulation.
You can view the CONFIG and REVCON files in three dimensions using the VESTA program. VESTA can generate nice pictures which will look very good in a lab report.

    Figure 1. Fluorite CaF$_2$ unit cell visualised in VESTA.

# Exercise 2: Running an MD Simulation

Now we have DL_POLY input files, we will run an MD simulation using DL_POLY.

1. **Run** DL_POLY from within the notebook use the command below

Keep in mind that this simulation will take 20 or so minutes so be patient.

If you are not comfortable with running things through this notebook then you can copy and paste the dlpoly_classic.exe executable into the Example/ sub directory and then **double click** the .exe file

```python
subprocess.call('../Codes/dlpoly_classic.exe', cwd='Example/')
```

# Exercise 3: Inspecting an MD Simulation

Now we have run an MD simulation using DL_POLY we can analyse the data using the VESTA

Once DL_POLY has completed you will find several files relating to your simulaton.

 **HISTORY**

This file contains the configuration of your system at each step during the simulation, known as a _trajectory_. You can view this as a movie using VMD

 **STATIS**

Contains the statistics at each step of the simulation.

 **OUTPUT**

Contains various properties of the simulation.

 **REVCON**

This is the configuration at the end of the simulation.  Can be viewed in VESTA. **Check** to see how it has changed, compare it to the CONFIG file.

# Exercise 4: Analysing the Diffusion Properties

Now we have inspected the final structure from the simulation, we can calculate the diffusion coefficient.

## Mean Squared Displacements - Calculating Diffusion Coefficients

As we have seen molecules in liquds, gases and solids do not stay in the same place and move constantly. Think about a drop of dye in a glass of water, as time passes the dye distributes throughout the water. This process is called diffusion and is common throughout nature.

Using the dye as an example, the motion of a dye molecule is not simple. As it moves it is jostled by collisions with other molecules, preventing it from moving in a straight path. If the path is examined in close detail, it will be seen to be a good approximation to a _random walk_.

In mathmatics, a random walk is a series of steps, each taken in a random direction. This was analysed by Albert Einstein in a study of _Brownian motion_ and he showed that the mean square of the distance travelled by a particle following a random walk is proportional to the time elapsed, as given by:
\begin{align}
\Big \langle r^2 \big \rangle & = 6 D_t + C
\end{align}

where $\Big \langle r^2 \big \rangle$ is the mean squared distance, t is time, D is the diffusion rate and C is a constant.

## What is the Mean Squared Displacement?

Going back to the example of the dye in water, lets assume for the sake of simplicity that we are in one dimension. Each step can either be forwards or backwards and we cannot predict which.
From a given starting position, what distance is our dye molecule likely to travel after 1000 steps? This can be determined simply by adding together the steps, taking into account the fact that steps backwards subtract from the total, while steps forward add to the total. Since both forward and backward steps are equally probable, we come to the surprising conclusion that the probable distance travelled sums up to zero.

By adding the square of the distance we will always be adding positive numbers to our total which now increases linearly with time. Based upon equation 1 it should now be clear that a plot of $\Big \langle r^2 \big \rangle$ vs time with produce a line, the gradient of which is equal to 6D. Giving us direct access to the diffusion coefficient of the system.

Lets try explore this with an example.

1. **Run** a short DL_POLY simulation on the input files provided.

You will run a small MSD program called MSD.py to analyse your simulation results.

First you need to **read** in the data. The HISTORY file contains a list of the atomic coordiantes held by the atoms during the simulation.

2.**Run** the cell below to read the HISTORY file into the Jupyter Notebook

```python
## Provide the path to the simulation and the atom that you want data for.
data = History("Example/HISTORY", "F")
```

data is a class object containing information about the trajectory.
More information can be found here https://polypy.readthedocs.io/en/latest/reading_data.html and here https://github.com/symmy596/Polypy/blob/master/polypy/read.py .

The next step is to calculate the MSD.

3.**Run** the cell below to calculate the MSD of the chosen atom throughout the course of the simulation

```python
# Run the MSD calculation

f_msd = MSD(data.trajectory, sweeps=2)

output = f_msd.msd()
```

The MSD calculation function returns an object with imformation about the MSD calculation.

More information and a full tutorial on this functionality can be found here https://polypy.readthedocs.io/en/latest/msd_tutorial.html

4.**Run** the cell below to give plots of the MSD which have a nice linear relationship.

```python
ax = plotting.msd_plot(output)
plt.show()
```

```python
print("Three Dimensional Diffusion Coefficient", output.xyz_diffusion_coefficient())
print("One Dimensional Diffusion Coefficient in X", output.x_diffusion_coefficient())
print("One Dimensional Diffusion Coefficient in Y", output.y_diffusion_coefficient())
print("One Dimensional Diffusion Coefficient in Z", output.z_diffusion_coefficient())
```

# Exercise 5: The Effect of Simulation Length

Now we have calculated the diffusion coefficient, we can investigate the influence of simulation length on the diffusion coefficient.

It is important to consider the length of your simulation (the number of steps).

1. **Create** a new folder called "Example_2/"
2. **Copy** the CONFIG, FIELD and CONTROL files from your previous simulation
3. **Change** the number of steps to 10000
4. **Rerun** the simulation by **running** the cell below

```python
subprocess.call('../Codes/dlpoly_classic.exe', cwd='Example_2/')
```

5.**Run** the cell below to calculate and plot the MSD of the chosen atom throughout the course of the simulation

```python
data = History("Example_2/HISTORY", "F")

# Run the MSD calculation
f_msd = MSD(data.trajectory, sweeps=2)

output = f_msd.msd()
ax = plotting.msd_plot(output)
plt.show()
```

```python
print("Three Dimensional Diffusion Coefficient", output.xyz_diffusion_coefficient())
print("One Dimensional Diffusion Coefficient in X", output.x_diffusion_coefficient())
print("One Dimensional Diffusion Coefficient in Y", output.y_diffusion_coefficient())
print("One Dimensional Diffusion Coefficient in Z", output.z_diffusion_coefficient())
```

You will hopefully see that your MSD plot has become considerably less linear. This shows that your simulation has not run long enough and your results will be unrealiable.

You will hopefully also see a change to the value of your diffusion coefficient.
**The length of your simulation is something that you should keep in mind for the next 5 weeks.**

# Exercise 6: Calculating the Activation Energy

Now we have investigated the influence of simulation length on the diffusion coefficient, we can calculate the activation energy for F diffusion by applying the Arrhenius equation.

To apply the Arrhensius equation, diffusion coefficients from a range of temperatures are required.

Common sense and chemical intuition suggest that the higher the temperature, the faster a given chemical reaction will proceed. Quantitatively, this relationship between the rate a reaction proceeds and the temperature is determined by the Arrhenius Equation.

At higher temperatures, the probability that two molecules will collide is higher. This higher collision rate results in a higher kinetic energy, which has an effect on the activation energy of the reaction. The activation energy is the amount of energy required to ensure that a reaction happens.

\begin{align}
k = A * e^{(-Ea / RT)}
\end{align}

where k is the rate coefficient, A is a constant, Ea is the activation energy, R is the universal gas constant, and T is the temperature (in kelvin).

# Exercise 7: Putting it All Together

Using what you have learned through the tutorials above, your task this week is to calculate the activation energy of F diffusion in CaF$_2$.

1. You will need to **select** a temperature range and carry out simulations at different temperatures within that range.

#### Questions to answer:

- In what temperature range is CaF$_2$ completely solid i.e. no diffusion?
- In what range is fluorine essentially liquid i.e. fluorine diffusion with no calcium diffusion?
- What is the melting temperature of CaF$_2$?
- Plot an Arrhenius plot and determine the activation energies in temperature range - You will need to rearange the equation.

You are encouraged to split the work up within your group and to learn how to view the simulation "movie" using VMD (Ask a demonstrator). VMD is a fantastic program that allows you to visualise your simulation, included below is a video showing a short snippet of an MD simulation of CaF$_2$. A single F atom has been highlighted to show that diffusion is occuring.

```python
%%HTML
<div align="middle">
</div>
```

Furthermore, VMD can also be used to generate images showing the entire trajectory of the simulation, e.g.

    Figure 2. A figure showing all positions occupied by F during an MD simulation at 1500 K. F positions are shown in orange and Ca atoms are shown in green.

To save you time you can use the function declared at the start of this notebook to pull out a diffusion coefficient directly from the simulation output file. MSD.py is a small code to allow visualisation of the MSD plot but it is not neccesary every time you want the diffusion coefficient.

It is up to you how you organise/create your directories but it is reccomended that you start a new notebook.

Use the commands/functions used in this notebook to:
1. **Generate** your input files
2. **Run** DL_POLY
3. **Extract** the diffusion coefficient of F diffusion

Then write your own code to:

4. **Generate** an Arrhenius plot
5. **Calculate** the activation energies of F diffusion

If you finish early then feel free to start the week 4 exercises.


**Notas para contenedor de docker:**

Comando de docker para ejecución de la nota de forma local:

nota: cambiar `dir_montar` por la ruta de directorio que se desea mapear a `/datos` dentro del contenedor de docker.

```
dir_montar=<ruta completa de mi máquina a mi directorio>#aquí colocar la ruta al directorio a montar, por ejemplo:
#dir_montar=/Users/erick/midirectorio.
```

Ejecutar:

```
$docker run --rm -v $dir_montar:/datos --name jupyterlab_prope_r_kernel_tidyverse -p 8888:8888 -d palmoreck/jupyterlab_prope_r_kernel_tidyverse:3.0.16

```

Ir a `localhost:8888` y escribir el password para jupyterlab: `qwerty`

Detener el contenedor de docker:

```
docker stop jupyterlab_prope_r_kernel_tidyverse
```

Documentación de la imagen de docker `palmoreck/jupyterlab_prope_r_kernel_tidyverse:3.0.16` en [liga](https://github.com/palmoreck/dockerfiles/tree/master/jupyterlab/prope_r_kernel_tidyverse).

---

Para ejecución de la nota usar:

[docker](https://www.docker.com/) (instalación de forma **local** con [Get docker](https://docs.docker.com/install/)) y ejecutar comandos que están al inicio de la nota de forma **local**.

O bien dar click en alguno de los botones siguientes:

[](https://mybinder.org/v2/gh/palmoreck/dockerfiles-for-binder/jupyterlab_prope_r_kernel_tidyerse?urlpath=lab/tree/Propedeutico/Python/clases/3_algebra_lineal/0_definiciones_generales.ipynb) esta opción crea una máquina individual en un servidor de Google, clona el repositorio y permite la ejecución de los notebooks de jupyter.

[](https://repl.it/languages/python3) esta opción no clona el repositorio, no ejecuta los notebooks de jupyter pero permite ejecución de instrucciones de Python de forma colaborativa con [repl.it](https://repl.it/). Al dar click se crearán nuevos ***repl*** debajo de sus users de ***repl.it***.

## Lo siguiente está basado en el capítulo 2 y apéndice del libro de texto de J. Kiusalaas "Numerical Methods in Engineering with Python 3" y el libro de "Álgebra Lineal" de S. Grossman y J. Flores.

**Se sugiere haber revisado la sección 1.5 del libro de texto de J. Kiusalaas "Numerical Methods in Engineering with Python 3": uso de numpy**

**Nota: los ejercicios solicitados en esta nota deben realizarlos creando notebooks de jupyterlab.**

# Vectores

## Vector renglón de $n$ componentes

Un vector de $n$ componentes se define como un conjunto **ordenado** de $n$ números escritos de la siguiente forma: $(x_1, x_2, \dots, x_n)$

## Vector columna  de $n$ componentes

Un vector columna de $n$ componentes es un conjunto **ordenado** de $n$ números escritos de la siguiente forma:
$\left(
\begin{array}{c}
x_1\\
x_2\\
\vdots\\
x_n
\end{array}
\right)$, $x_k$ se denomina la **k-ésima** componente del vector y puede estar en algún conjunto de números como $\mathbb{R}$ o $\mathbb{C}$. Típicamente si no se especifica el conjunto se supone $\mathbb{R}$.

Los vectores renglón se encuentran en el conjunto $\mathbb{R}^{1xn}$ y los vectores columna se encuentran en el conjunto $\mathbb{R}^{nx1}$ o simplemente $\mathbb{R}^n$.

---

**Observación**

En *NumPy* un *array* se considera tanto columna o renglón y dependiendo del uso se toma uno u otro.

---

Si quisiéramos forzar a un manejo de los *NumPy array*'s como renglones o columnas podríamos utilizar lo siguiente.

```python
import numpy as np
```

```python
v = np.array([1,2,3,4,5])
```

```python
v.reshape(1,5)
```

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

```python
v.reshape(1,5).shape
```

    (1, 5)

```python
v.reshape(5,1)
```

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

```python
v.reshape(5,1).shape
```

    (5, 1)

# Matrices

El arreglo de coeficientes: $$A=\begin{bmatrix} a_{11} & a_{12} & \cdots& a_{1n} \\ a_{21} & a_{22} & \cdots& a_{2n} \\ &&\vdots \\ a_{m1} & a_{m2} & \cdots& a_{mn} \end{bmatrix}$$

se le nombra matriz de $mxn$. En caso que se tenga un sistema de la forma $Ax=b$ con $b$ vector de constantes conocidas, se le nombra matriz de coeficientes del sistema y al vector $b$ se le nombra lado derecho; la matriz de coeficientes aumentada con el lado derecho se le nombra matriz aumentada: $$ [A|b] = \left [\begin{array}{cccc|c} a_{11} & a_{12} & \cdots& a_{1n} & b_1 \\ a_{21} & a_{22} & \cdots& a_{2n} & b_2 \\ && \vdots& & \\ a_{m1} & a_{m2} & \cdots& a_{mn} & b_m \end{array} \right ]$$

---

**Definiciones**

Formalmente un escalar es un número real o complejo y una matriz es un arreglo rectangular de escalares y se escribe $A \in \mathbb{R}^{mxn}$ o $A \in \mathbb{C}^{mxn}$ para denotar que $A$ es un arreglo de dos dimensiones de números reales o de números complejos respectivamente y $A$ tiene dimensiones $mxn$: $m$ renglones y $n$ columnas.

Una matriz se nombra cuadrada si $m=n$ y rectangular si $m \neq n$.

Una submatriz de una matriz $A$ es un arreglo que se forma al eliminar cualquier combinación de renglones y columnas de $A$. Por ejemplo, $B = \begin{bmatrix} 2 &4 \\ -3& 7\end{bmatrix}$ es una submatriz de $A = \begin{bmatrix} 2 &1& 3& 4 \\ 8& 6& 5& -9\\ -3& 8& 3& 7 \end{bmatrix}$.

El símbolo $A_{i*}$ es utilizado para denotar el $i$-ésimo renglón de $A$ y $A_{*j}$ la $j$-ésima columna de A. Por ejemplo $A_{2*} = \begin{bmatrix} 8& 6& 5& -9 \end{bmatrix}$ y $A_{*2} = \begin{bmatrix} 1 \\6\\8 \end{bmatrix}$ en la definición anterior.

---

# Operaciones en el álgebra lineal con *NumPy*

## Producto escalar-vector, suma y punto entre vectores

```python
v1 = np.array([6,-3,4])
v2 = np.array([4,5,0])
scalar = -1/2
```

```python
scalar*v1
```

    array([-3. ,  1.5, -2. ])

```python
v1.dot(v2)
```

    9

```python
v1+v2
```

    array([10,  2,  4])

## Producto matriz vector *point wise*

```python
A = np.array([[2,5,0],
              [3,6,6],
              [-6,4,-1],
              [5,4,9]])
A
```

    array([[ 2,  5,  0],
           [ 3,  6,  6],
           [-6,  4, -1],
           [ 5,  4,  9]])

```python
v = np.array([-2,1,4])
v
```

    array([-2,  1,  4])

```python
A*v
```

    array([[ -4,   5,   0],
           [ -6,   6,  24],
           [ 12,   4,  -4],
           [-10,   4,  36]])

## Producto matriz-vector

```python
A = np.array([[2,5,0],
              [3,6,6],
              [-6,4,-1],
              [5,4,9]])
A
```

    array([[ 2,  5,  0],
           [ 3,  6,  6],
           [-6,  4, -1],
           [ 5,  4,  9]])

```python
v = np.array([-2,1,4])
v
```

    array([-2,  1,  4])

```python
A.dot(v)
```

    array([ 1, 24, 12, 30])

```python
A@v
```

    array([ 1, 24, 12, 30])

```python
v = np.array([7,0,-3,2])
v
```

    array([ 7,  0, -3,  2])

```python
v@A
```

    array([42, 31, 21])

---

**Observación**

En este último ejemplo que no fue necesario indicar que tenemos un vector renglón para realizar por la izquierda la multiplicación: $vA$.

---

## Suma y producto matriz-matriz *point wise*

```python
A = np.array([[2,5,0],
              [3,6,6],
              [-6,4,-1],
              [5,4,9]])
A
```

    array([[ 2,  5,  0],
           [ 3,  6,  6],
           [-6,  4, -1],
           [ 5,  4,  9]])

```python
B = np.array([[2,-2,3],
              [1,-1,5],
              [0,-2,1],
              [0,0,-3]])
B
```

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

```python
A+B
```

    array([[ 4,  3,  3],
           [ 4,  5, 11],
           [-6,  2,  0],
           [ 5,  4,  6]])

```python
A*B
```

    array([[  4, -10,   0],
           [  3,  -6,  30],
           [  0,  -8,  -1],
           [  0,   0, -27]])

## Producto matriz-matriz

```python
A = np.array([[2,5,0],
              [3,6,6],
              [-6,4,-1],
              [5,4,9]])
A
```

    array([[ 2,  5,  0],
           [ 3,  6,  6],
           [-6,  4, -1],
           [ 5,  4,  9]])

```python
B = np.array([[2,-2,3],
              [1,-1,5],
              [0,-2,1]])
B
```

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

```python
A@B
```

    array([[  9,  -9,  31],
           [ 12, -24,  45],
           [ -8,  10,   1],
           [ 14, -32,  44]])

Ver:

* [numpy-for-matlab-users](https://www.numpy.org/devdocs/user/numpy-for-matlab-users.html)
* [Quickstart tutorial](https://docs.scipy.org/doc/numpy/user/quickstart.html)

## Clase `Matrix` en *SymPy*

```python
import sympy
```

Usamos la clase `Matrix` para representar vectores y matrices de forma simbólica:

```python
sympy.Matrix([1,2]) #vector columna de tamaño 2 x 1
```

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

```python
sympy.Matrix([[1,2]]) #matriz de tamaño 1 x 2
```

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

```python
sympy.Matrix([[1,2],
             [3,4]])
```

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

```python
l = [[1,2], [3,4]]
sympy.Matrix(l)
```

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

## Matrices con entradas simbólicas

```python
a, b, c, d, e, f = sympy.symbols("a, b, c, d, e, f")
```

```python
M = sympy.Matrix([[a,b],
                  [c,d]])
```

```python
M
```

$\displaystyle \left[\begin{matrix}a & b\\c & d\end{matrix}\right]$

```python
v = sympy.Matrix([e, f])
```

```python
v
```

$\displaystyle \left[\begin{matrix}e\\f\end{matrix}\right]$

```python
v.rows
```

    2

```python
v.cols
```

    1

Una forma para crear vectores en la que no enlistamos a las variables.

```python
values = sympy.symbols("g, h")
```

```python
v = sympy.Matrix(values)
```

```python
v
```

$\displaystyle \left[\begin{matrix}g\\h\end{matrix}\right]$

## Acceder a elementos

```python
v1, v2, w1, w2 = sympy.symbols("v1, v2, w1, w2")
```

```python
v = sympy.Matrix([v1, v2])
w = sympy.Matrix([w1, w2])
```

```python
v
```

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

```python
v[0]
```

$\displaystyle v_{1}$

```python
v[1]
```

$\displaystyle v_{2}$

```python
M
```

$\displaystyle \left[\begin{matrix}a & b\\c & d\end{matrix}\right]$

```python
M[0]
```

$\displaystyle a$

```python
M[1]
```

$\displaystyle b$

Otra forma más natural al álgebra lineal.

```python
M[0,0]
```

$\displaystyle a$

```python
M[0,1]
```

$\displaystyle b$

```python
M[1,0]
```

$\displaystyle c$

```python
M[1,1]
```

$\displaystyle d$

Primer renglón, segunda columna de una matriz.

```python
M
```

$\displaystyle \left[\begin{matrix}a & b\\c & d\end{matrix}\right]$

```python
M.row(0)
```

$\displaystyle \left[\begin{matrix}a & b\end{matrix}\right]$

```python
M.col(1)
```

$\displaystyle \left[\begin{matrix}b\\d\end{matrix}\right]$

```python
M.rows
```

    2

```python
M.cols
```

    2

## Algunas matrices comunes

```python
sympy.eye(3)
```

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

```python
sympy.zeros(4)
```

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

```python
sympy.ones(5)
```

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

# Operaciones en el álgebra lineal con *SymPy*

## Producto escalar-vector

```python
alpha = sympy.Symbol("alpha")
```

```python
alpha*v
```

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

## Sumas entre vectores

```python
v + w
```

$\displaystyle \left[\begin{matrix}v_{1} + w_{1}\\v_{2} + w_{2}\end{matrix}\right]$

## Producto punto

```python
v.dot(w)
```

$\displaystyle v_{1} w_{1} + v_{2} w_{2}$

## Producto Matriz-vector

```python
M*v
```

$\displaystyle \left[\begin{matrix}a v_{1} + b v_{2}\\c v_{1} + d v_{2}\end{matrix}\right]$

## Producto Matriz-Matriz

```python
M*M
```

$\displaystyle \left[\begin{matrix}a^{2} + b c & a b + b d\\a c + c d & b c + d^{2}\end{matrix}\right]$

## Clase [MatrixSymbol](https://docs.sympy.org/latest/modules/matrices/expressions.html)

```python
M = sympy.MatrixSymbol('M', 2, 2)
```

```python
M
```

$\displaystyle M$

```python
sympy.Matrix(M)
```

$\displaystyle \left[\begin{array}{cc}M_{0, 0} & M_{0, 1}\\M_{1, 0} & M_{1, 1}\end{array}\right]$

```python
M.rows
```

$\displaystyle 2$

```python
M.cols
```

$\displaystyle 2$

```python
sympy.Matrix(M[0,:])
```

$\displaystyle \left[\begin{array}{cc}M_{0, 0} & M_{0, 1}\end{array}\right]$

```python
sympy.Matrix(M[1,:])
```

$\displaystyle \left[\begin{array}{cc}M_{1, 0} & M_{1, 1}\end{array}\right]$

```python
M*v
```

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

```python
(M*v)[0,0]
```

$\displaystyle v_{1} M_{0, 0} + v_{2} M_{0, 1}$

```python
(M*v)[1,0]
```

$\displaystyle v_{1} M_{1, 0} + v_{2} M_{1, 1}$

# Para más sobre operaciones vectoriales, matriciales y propiedades y definiciones de matrices especiales:

* **Capítulo 2** del libro de "Álgebra Lineal" de S. Grossman y J. Flores hasta sección 2.3. De esta sección hasta la 2.7 se trabajan las definiciones de matriz identidad, inversa, simétrica y triangular inferior-superior. En el capítulo 3 se trabaja con la definición del determinante de una matriz y sus propiedades.

* **Capítulo 3** del libro "Matrix Analysis and Applied Linear Algebra"de Carl D. Meyer.

# Espacios vectoriales

**Lo siguiente está basado en el capítulo 5 del libro "Álgebra Lineal" de S. Grossman y J. Flores.**

Como se vio arriba, las operaciones binarias de (suma, producto por un escalar): $(+,\cdot)$ se encuentran definidas para el conjunto $\mathbb{R}^n$ que es el conjunto de tuplas ordenadas de tamaño $n$ y tales operaciones están bien definidas pues obtenemos elementos del mismo conjunto. También, tales operaciones están bien definidas para el conjunto $\mathbb{R}^{mxn}$, el cual es el conjunto de matrices de tamaño $mxn$.

Para aquellos conjuntos $V$ a los que se les define una (suma, producto por un escalar), $(+,\cdot)$, y el escalar está definido en un conjunto $K$ llamado campo, y cumplen con las siguientes propiedades se les llama *espacios vectoriales* y estos en general son distintos a $\mathbb{R}^n$ o $\mathbb{R}^{mxn}$:

1) Si $x \in V$, $y \in V$ entonces $x+y \in V$ (cerradura bajo la suma).

2) $\forall x,y,z \in V$ se cumple: $(x+y)+z=x+(y+z)$ (ley asociativa de la suma de vectores).

3) $\exists 0 \in V$ tal que para todo $x \in V$, $x+0=0+x=x$ (el $0$ se llama vector cero).

4) Si $x \in V$, $\exists -x \in V$ tal que $x+(-x)=0$ ($-x$ se llama inverso aditivo de $x$ y $0$ es el elemento descrito por la propiedad 3).

5) Si $x,y \in V$, entonces $x+y=y+x$ (ley conmutativa de la suma de vectores).

6) Si $x \in V$, $\alpha \in K$ entonces $\alpha \cdot x \in V$ (cerradura bajo la multiplicación por un escalar).

7) Si $x, y \in V$, $\alpha \in K$ entonces $\alpha \cdot (x+y)=\alpha \cdot x + \alpha \cdot y$ (primera ley distributiva).

8) Si $x \in V$, $\alpha, \beta \in K$ entonces $(\alpha + \beta)\cdot x = \alpha \cdot x + \beta \cdot x$ (segunda ley distributiva).

9) Si $x \in V$, $\alpha, \beta \in K$ entonces $\alpha(\beta \cdot x) = (\alpha \beta)\cdot x$ (ley asociativa de la multiplicación por escalares).

10) $\forall x \in V,$ $\exists 1 \in K$ tal que: $1\cdot x = x$.

## Ejemplos de espacios vectoriales

* $\mathbb{R}^n, \mathbb{R}^{mxn}, \mathbb{C}^n, \mathbb{C}^{mxn}$.

* $V=\{0\}$.

* $V=\{ (x,y) \in \mathbb{R}^2 : y = mx, m \in \mathbb{R}, x \in \mathbb{R}\}$.

* $V=\{ (x,y,z) \in \mathbb{R}^3 : ax+by+cz=0, a \in \mathbb{R}, b \in \mathbb{R}, c\in \mathbb{R} \}$.

* $V = \{ a_0 + a_1x + a_2x^2 + \dots a_{n-1}x^{n-1} + a_nx^n : a_0, a_1, \dots, a_n \in \mathbb{R}\}$.

* $V = \mathcal{C}([0,1])$.

---

**Observación**

Los elementos de un espacio vectorial se les nombra *vectores*, por ejemplo un elemento del conjunto $V = \mathcal{C}([0,1])$ es $f(x)=x^2$ y refiriéndose a este elemento, rigurosamente es un *vector* (aunque sea una función).

---

Ver:

* Fin de la sección $5.1$ y sección $5.2$ del libro de Álgebra Lineal de S. Grossman y J. Flores, para tema de subespacios vectoriales.

# Combinación lineal, espacio generado y bases

Para las siguientes definiciones se considera el espacio vectorial $V=\mathbb{R}^n$. Un vector en este espacio es $x=\left(
\begin{array}{c}
x_1\\
x_2\\
\vdots\\
x_n
\end{array}
\right)$.

## Combinación lineal

Se nombra combinación lineal a la siguiente suma ponderada: $\displaystyle \sum_{i=1}^n a_ix_i$ con $x_1,\dots,x_n \in \mathbb{R}^n$ y $a_1,\dots,a_n \in \mathbb{R}$.

Por ejemplo, en $\mathbb{R}^3$ el vector:
$\left(\begin{array}{c}
-7\\
7\\
7
\end{array}
\right)$ es una combinación lineal de:
$\left(\begin{array}{c}
-1\\
2\\
4
\end{array}
\right)$ y
$\left(\begin{array}{c}
5\\
-3\\
1
\end{array}
\right)$ pues:

$$
\begin{array}{l}
\left( \begin{array}{c}
-7\\
7\\
7
\end{array}
\right) = 2\left(
\begin{array}{c}
-1\\
2\\
4
\end{array}
\right)
-\left(
\begin{array}{c}
5\\
-3\\
1
\end{array}
\right)
\end{array}
$$

y otro ejemplo:
$-2\left(\begin{array}{c}
3\\
0\\
\end{array}
\right)
+4\left(\begin{array}{c}
0\\
1\\
\end{array}
\right)$ es una combinación lineal de
$\left(\begin{array}{c}
3\\
0\\
\end{array}
\right)$ y
$\left(\begin{array}{c}
0\\
1\\
\end{array}
\right)$.

## Conjunto generador

Los vectores $x_1, x_2, \dots,x_n$ generan a $\mathbb{R}^n$ si todo vector en $\mathbb{R}^n$ se puede escribir como una combinación lineal de los mismos. Esto es, si $x \in \mathbb{R}^n$ entonces $x = \displaystyle \sum_{i=1}^n \alpha_i x_i$ con $\alpha_i \in \mathbb{R}$.

Por ejemplo, los $n$ vectores canónicos que tienen un $1$ en la posición $i$:

$$e_i = \left(\begin{array}{c}
0\\
\vdots\\
0\\
1\\
0\\
\vdots\\
0
\end{array}
\right) \leftarrow \text{posición i}$$

son un conjunto generador del espacio $\mathbb{R}^n$. Por ejemplo, en $\mathbb{R}^3$:

$$
\begin{array}{l}
\left( \begin{array}{c}
-7\\
7\\
7
\end{array}
\right) = -7\left(
\begin{array}{c}
1\\
0\\
0
\end{array}
\right)
+7\left(
\begin{array}{c}
0\\
1\\
0
\end{array}
\right)
7\left(
\begin{array}{c}
0\\
0\\
1
\end{array}
\right)
\end{array}
$$

### Espacio generado

Es el conjunto formado por combinaciones lineales de un conjunto vectores. Por ejemplo, si nuestro conjunto de vectores es $\{x_1,x_2\}$ entonces el espacio generado es: $$\text{gen}\{x_1,x_2\}=<x_1,x_2>=\{\alpha_1 x_1 + \alpha_2 x_2 \}.$$

---

**Observación**

Este espacio generado es un subespacio vectorial de $\mathbb{R}^n$.

---

## Independencia/dependencia lineal

El conjunto $\{x_1, x_2, \dots, x_k\}$ para $k \in \mathbb{N}$, es un conjunto con dependencia lineal (o conjunto linealmente dependiente o los vectores $x_1,x_2,\dots, x_k$ son linealmente dependientes) si algún vector en tal conjunto se escribe como combinación lineal de los restantes. Por ejemplo, los vectores:

$$
\left( \begin{array}{c}
-7\\
7\\
7
\end{array}
\right), \left(
\begin{array}{c}
-1\\
2\\
4
\end{array}
\right), \left(
\begin{array}{c}
5\\
-3\\
1
\end{array}
\right)
$$

son **linealmente dependientes** como se vio arriba.

Si el conjunto $\{x_1, x_2, \dots, x_k\}$ no es linealmente dependiente entonces es **linealmente independiente**.

También se utiliza para la definición de independencia lineal que la ecuación: $$\alpha_1 x_1 + \alpha_2 x_2 + \dots + \alpha_k x_k = 0$$ con incógnitas $\alpha_i$'s tiene solución única dada por $\alpha_1 = \alpha_2 = \dots = \alpha_k = 0$.

Como ejemplos de vectores linealmente independientes se tienen:

* $e_1, e_2, \dots, e_k$ con k un número natural.

* $\left( \begin{array}{c}
1\\
-2\\
3
\end{array}
\right), \left(
\begin{array}{c}
2\\
-2\\
0
\end{array}
\right), \left(
\begin{array}{c}
0\\
1\\
7
\end{array}
\right)
$

---

**Observación**

en el espacio $\mathbb{R}^n$ sólo se pueden tener a lo más $n$ vectores linealmente independientes. Entonces los vectores:
$\left( \begin{array}{c}
2\\
-3\\
4
\end{array}
\right), \left(
\begin{array}{c}
4\\
7\\
-6
\end{array}
\right), \left(
\begin{array}{c}
18\\
-11\\
4
\end{array}
\right),
\left(
\begin{array}{c}
2\\
-7\\
3
\end{array}
\right)
$ son linealmente dependientes en $\mathbb{R}^3$ pues constituyen un conjunto de $4$ vectores de $3$ elementos cada uno. Y esto equivale a escribir que $Ax=0$ tiene solución $x$ diferente de cero con $A \in \mathbb{R}^{3x4}$ formada por las columnas de los vectores anteriores.

---

## Base

Los conjuntos de vectores en $\mathbb{R}^n$ que son linealmente independientes y su espacio generado es $\mathbb{R}^n$ se les nombra base. Por ejemplo:

* $e_1, e_2, \dots, e_k \in \mathbb{R}^k$ con $k \in \mathbb{N}$ son base de $\mathbb{R}^k$  y si consideramos $e_1, e_2, \dots, e_n \in \mathbb{R}^n$ entonces estos últimos son base del espacio $\mathbb{R}^n$.

* $\left( \begin{array}{c}
1\\
-2\\
3
\end{array}
\right), \left(
\begin{array}{c}
2\\
-2\\
0
\end{array}
\right), \left(
\begin{array}{c}
0\\
1\\
7
\end{array}
\right)
$ son base del espacio $\mathbb{R}^3$.

---

**Comentarios**

* El conjunto $\{e_1, e_2, \dots, e_n\}$ es nombrada *base canónica* de $\mathbb{R}^n$.

* Si un espacio vectorial tiene una base con un número finito de elementos, entonces se le nombra espacio de **dimensión finita**, en otro caso es un espacio vectorial de **dimensión infinita**.

* De las ventajas que existen al trabajar con bases es que cualquier elemento en el espacio vectorial de dimensión finita se puede representar como una combinación lineal de los vectores en la base.

* Si $V=\{0\}$ entonces típicamente se considera que $V$ tiene dimensión cero.

---

# Vectores ortogonales

Los vectores $x, y \in \mathbb{R}^n$ son ortogonales si $x^Ty=0$. El conjunto $\{x_1,x_2,\dots,x_k\} \in \mathbb{R}^n$ es ortogonal si $x_i^Tx_j = 0$ para $i \neq j$ y si además $x_i^Tx_i = 1$ (norma igual a uno) entonces se nombra conjunto ortonormal.

Norma 2 o Euclidiana de un vector: si $x \in \mathbb{R}^n$ entonces la norma 2 o Euclidiana de $x$, denotada por $||x||_2$ se define como: $||x||_2 = \sqrt{x^Tx}$.

## Matriz ortogonal

Una matriz $A \in \mathbb{R}^{nxn}$ se nombra ortogonal si sus columnas son ortogonales y cada columna es ortonormal. Tal propiedad se puede escribir de forma matricial como $A^TA = I$ donde: $I$ es la matriz identidad.

Como ejemplos de matrices ortogonales en $\mathbb{R}^{nxn}$ se tienen las matrices de rotación y reflexión.

### Matriz de rotación en sentido contrario a las manecillas del reloj en el plano

Está dada por: $$R_{\theta} = \begin{bmatrix} cos(\theta) & -sen(\theta) \\ sen(\theta)& cos(\theta)\end{bmatrix}$$

---

**(Tarea)Ejercicio**

Usar esta matriz de rotación para rotar un ángulo de $\frac{\pi}{3}$ en sentido contrario a las manecillas del reloj al vector $\left(\begin{array}{c}
-3\\
4\\
\end{array}
\right)$.

---

Imports:

```python
import matplotlib.pyplot as plt
import math
```

Definición de $v$:

```python
v = np.array([-3,4])
```

Definición de matriz $R_\theta$ con $\theta = \pi/3$:

```python
theta = math.pi/3
R_theta = np.array([[math.cos(theta), -math.sin(theta)],
                    [math.sin(theta), math.cos(theta)]])
```

Realizamos la rotación para obtener $v_{\text{rot}}$:

```python
v_rot = R_theta@v
```

```python
v_rot
```

    array([-4.96410162, -0.59807621])

Realizamos la gráfica:

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

```python
A
```

    array([[ 0,  0],
           [-3,  4]])

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

```python
A2
```

    array([[ 0.        ,  0.        ],
           [-4.96410162, -0.59807621]])

```python
plt.plot(A[:,0], A[:,1], '-',
         A2[:,0], A2[:,1],'r-',
         )
plt.legend(['Vector', 'Vector con rotación'])
plt.title("Hacemos rotación de $\pi/3$")
plt.show()
```

### Matriz de reflexión de Householder

Está dada por: $$R_{H} = I - 2 \frac{uu^T}{u^Tu}$$ con $u \in \mathbb{R}^n -\{0\}$ y realiza la reflexión respecto al conjunto de vectores ortogonales a $u$.

---

**(Tarea)Ejercicio**

Reflejar al vector $\left(\begin{array}{c}
1\\
1\\
\end{array}
\right)$ utilizando al vector $\left(\begin{array}{c}
-\frac{4}{3}\\
\frac{2}{3}\\
\end{array}
\right)$ para construir a $R_{H}$.

---

## Base ortogonal, ortonormal

Un conjunto de vectores que forma una base de un espacio vectorial que además es un conjunto ortogonal se le nombra base ortogonal. Si adicionalmente cada vector tiene norma igual a 1 entonces es una base ortonormal.

# Espacio nulo, espacio nulo izquierdo, imagen de una matriz, espacio renglón y espacio columna

En lo siguiente se considera $A \in \mathbb{R}^{mxn}$.

## Espacio nulo

$\mathcal{N}(A) = \{x \in \mathbb{R}^n : Ax =0\}$

---

**Observación**

Es un subespacio de $\mathbb{R}^n$.

---

**La dimensión de $\mathcal{N}(A)$ se le nombra nulidad.** Si $\mathcal{N}(A)$ sólo contiene al vector $0$, esto es: $\mathcal{N}(A) = \{0\}$ entonces la nulidad de $\mathcal{N}(A)$ es igual a 0.

## Espacio nulo izquierdo

$\mathcal{N}(A^T) = \{x \in \mathbb{R}^m : A^Tx =0\}$

---

**Observación**

Es un subespacio de $\mathbb{R}^m$.

---

## Imagen de una matriz

$Im(A) = \{y \in \mathbb{R}^m: y=Ax \text{ para alguna } x \in \mathbb{R}^n\}$

---

**Observación**

Es un subespacio de $\mathbb{R}^m$.

---

**La dimensión de $Im(A)$ se le nombra $rank$**.

## Espacio renglón y espacio columna de una matriz

El espacio renglón es el conjunto generado por los renglones de $A$ y el espacio columna es el conjunto generado por las columnas de $A$.

---

**Observación**

Se prueba que el espacio renglón y el espacio columna son iguales a la imagen de la matriz en cuestión y la dimensión de los mismos es igual al *rank*.

---

## Espacio nulo con *SymPy*

```python
M = sympy.Matrix([[1, 2, 3, 0, 0],
                  [4, 10, 0, 0, 1]])
```

```python
M
```

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

```python
sympy.pprint(M.nullspace())
```

    ⎡⎡-15⎤  ⎡0⎤  ⎡ 1  ⎤⎤
    ⎢⎢   ⎥  ⎢ ⎥  ⎢    ⎥⎥
    ⎢⎢ 6 ⎥  ⎢0⎥  ⎢-1/2⎥⎥
    ⎢⎢   ⎥  ⎢ ⎥  ⎢    ⎥⎥
    ⎢⎢ 1 ⎥, ⎢0⎥, ⎢ 0  ⎥⎥
    ⎢⎢   ⎥  ⎢ ⎥  ⎢    ⎥⎥
    ⎢⎢ 0 ⎥  ⎢1⎥  ⎢ 0  ⎥⎥
    ⎢⎢   ⎥  ⎢ ⎥  ⎢    ⎥⎥
    ⎣⎣ 0 ⎦  ⎣0⎦  ⎣ 1  ⎦⎦

## Espacio columna con *SymPy*

```python
M = sympy.Matrix([[1, 1, 2],
                  [2 ,1 , 3],
                  [3 , 1, 4]])
```

```python
sympy.pprint(M.columnspace())
```

    ⎡⎡1⎤  ⎡1⎤⎤
    ⎢⎢ ⎥  ⎢ ⎥⎥
    ⎢⎢2⎥, ⎢1⎥⎥
    ⎢⎢ ⎥  ⎢ ⎥⎥
    ⎣⎣3⎦  ⎣1⎦⎦

# Transformaciones lineales

Sean $V$ y $W$ espacios vectoriales con campo en $\mathbb{R}$ entonces una transformación lineal $T$ de $V$ en $W$, denotada por $T: V \rightarrow W$, es una función que asigna a cada vector $v \in V$ un vector único $T(v) \in W$ y que satisface para cada $u,v \in V$ y cada escalar $\alpha \in \mathbb{R}$:

$$
T(u+v)=T(u)+T(v)
$$

$$
T(\alpha v)=\alpha T(v)
$$

## Ejemplos

* $T\left(\begin{array}{c}
x\\
y\\
\end{array}
\right) = \left(\begin{array}{c}
x+y\\
x-y\\
3y
\end{array}
\right)$

* $T\left(\begin{array}{c}
x\\
y\\
\end{array}
\right)= R_{\theta}\left(\begin{array}{c}
x\\
y\\
\end{array}
\right)
$ con $R_{\theta}$ matriz de rotación se conoce como transformación de rotación.

* $T\left(\begin{array}{c}
x\\
y\\
\end{array}
\right)= \left(\begin{array}{c}
-x\\
y\\
\end{array}
\right)
$ la cual es una transformación de reflexión respecto al eje vertical en el plano.

## Núcleo e imagen de las transformaciones lineales

Se consideran $V, W$ espacios vectoriales y $T: V \rightarrow W$ una transformación lineal.

### Núcleo o kernel

Se denota por $nu(T)$ y está dado por $nu(T) = \{v \in V : T(v) = 0\}$.

---

**Observación**

Este conjunto no es vacío pues $T(0) = 0$ por lo que $0 \in nu(T)$. Además es un subespacio vectorial de $V$ y su dimensión se nombra *nulidad* de $T$.

---

### Imagen

Se denota por $im(T)$ y está dado por $im(T) = \{w \in W : w = T(v) \text{ para alguna } v \in V\}$.

---

**Observación**

Es un subespacio vectorial de $W$ y su dimensión se nombra *rank* de T.

---

### Representación matricial de una transformación lineal

Sea $T: \mathbb{R}^n \rightarrow \mathbb{R}^m$ una transformación lineal. Existe entonces una matriz única de $mxn$, $A$ tal que: $T(x) = Ax \quad \forall x \in \mathbb{R}^n$.

---

**Observación**

La matriz $A$ del resultado depende de las bases elegidas en $\mathbb{R}^n, \mathbb{R}^m$.

---

**(Tarea)Ejercicio**

Para reforzar esta serie de definiciones deben elegir dos ejercicios por cada definición anterior del libro "Álgebra Lineal" de S. Grossman y J. Flores, capítulos 5 y 7 y utilizar *NumPy* y *SymPy* para resolver tales ejercicios.

---

## Referencias

* [SymPy](https://www.sympy.org/en/index.html) y [Numerical Python by Robert Johansson, Apress](https://www.apress.com/gp/book/9781484242452)


# Slides from FYS4411/9411 Definitions of the many-body problem
**Spring 2015**

### Quantum Many-particle Methods
* Large-scale diagonalization (Iterative methods, Lanczo's method, dimensionalities $10^{10}$ states)

* Coupled cluster theory, favoured method in quantum chemistry, molecular and atomic physics. Applications to ab initio calculations in nuclear physics as well for large nuclei.

* Perturbative many-body methods

* Density functional theories/Mean-field theory and Hartree-Fock theory (covered partly also in FYS-MENA4111)

* Monte-Carlo methods (Only in FYS4411, Computational quantum mechanics)

* Green's function theories (depending on interest)

* and other. The physics of the system hints at which many-body methods to use.

### Selected Texts and Many-body theory
* Blaizot and Ripka, *Quantum Theory of Finite systems*, MIT press 1986

* Negele and Orland, *Quantum Many-Particle Systems*, Addison-Wesley, 1987.

* Fetter and Walecka, *Quantum Theory of Many-Particle Systems*, McGraw-Hill, 1971.

* Helgaker, Jorgensen and Olsen, *Molecular Electronic Structure Theory*, Wiley, 2001.

* Mattuck, *Guide to Feynman Diagrams in the Many-Body Problem*, Dover, 1971.

* Dickhoff and Van Neck, *Many-Body Theory Exposed*, World Scientific, 2006.

### Definitions
An operator is defined as $\hat{O}$ throughout. Unless otherwise specified the number of particles is
always $N$ and $d$ is the dimension of the system.  In nuclear physics
we normally define the total number of particles to be $A=N+Z$, where
$N$ is total number of neutrons and $Z$ the total number of
protons. In case of other baryons such isobars $\Delta$ or various
hyperons such as $\Lambda$ or $\Sigma$, one needs to add their
definitions.  Hereafter, $N$ is reserved for the total number of
particles, unless otherwise specificied.

### Definitions
The quantum numbers of a single-particle state in coordinate space are
defined by the variable

$$
x=({\bf r},\sigma),
$$

where

$$
{\bf r}\in {\mathbb{R}}^{d},
$$

with $d=1,2,3$ represents the spatial coordinates and $\sigma$ is the eigenspin of the particle. For fermions with eigenspin $1/2$ this means that

$$
x\in {\mathbb{R}}^{d}\oplus (\frac{1}{2}),
$$

and the integral $\int dx = \sum_{\sigma}\int d^dr = \sum_{\sigma}\int d{\bf r}$,
and

$$
\int d^Nx= \int dx_1\int dx_2\dots\int dx_N.
$$

### Definitions
The quantum mechanical wave function of a given state with quantum numbers $\lambda$ (encompassing all quantum numbers needed to specify the system), ignoring time, is

$$
\Psi_{\lambda}=\Psi_{\lambda}(x_1,x_2,\dots,x_N),
$$

with $x_i=({\bf r}_i,\sigma_i)$ and the projection of $\sigma_i$ takes the values
$\{-1/2,+1/2\}$ for particles with spin $1/2$.
We will hereafter always refer to $\Psi_{\lambda}$ as the exact wave function, and if the ground state is not degenerate we label it as

$$
\Psi_0=\Psi_0(x_1,x_2,\dots,x_N).
$$

### Definitions
Since the solution $\Psi_{\lambda}$ seldomly can be found in closed form, approximations are sought. Here we define an approximative wave function or an ansatz to the exact wave function as

$$
\Phi_{\lambda}=\Phi_{\lambda}(x_1,x_2,\dots,x_N),
$$

with

$$
\Phi_0=\Phi_0(x_1,x_2,\dots,x_N),
$$

being the ansatz to the ground state.

### Definitions
The wave function $\Psi_{\lambda}$ is sought in the Hilbert space of either symmetric or anti-symmetric $N$-body functions, namely

$$
\Psi_{\lambda}\in {\cal H}_N:= {\cal H}_1\oplus{\cal H}_1\oplus\dots\oplus{\cal H}_1,
$$

where the single-particle Hilbert space $\hat{H}_1$ is the space of square integrable functions over
$\in {\mathbb{R}}^{d}\oplus (\sigma)$
resulting in

$$
{\cal H}_1:= L^2(\mathbb{R}^{d}\oplus (\sigma)).
$$

### Definitions
Our Hamiltonian is invariant under the permutation (interchange) of two particles.
Since we deal with fermions however, the total wave function is antisymmetric.
Let $\hat{P}$ be an operator which interchanges two particles.
Due to the symmetries we have ascribed to our Hamiltonian, this operator commutes with the total Hamiltonian,

$$
[\hat{H},\hat{P}] = 0,
$$

meaning that $\Psi_{\lambda}(x_1, x_2, \dots , x_N)$ is an eigenfunction of
$\hat{P}$ as well, that is

$$
\hat{P}_{ij}\Psi_{\lambda}(x_1, x_2, \dots,x_i,\dots,x_j,\dots,x_N)=
\beta\Psi_{\lambda}(x_1, x_2, \dots,x_j,\dots,x_i,\dots,x_N),
$$

where $\beta$ is the eigenvalue of $\hat{P}$. We have introduced the suffix $ij$ in order to indicate that we permute particles $i$ and $j$.
The Pauli principle tells us that the total wave function for a system of fermions
has to be antisymmetric, resulting in the eigenvalue $\beta = -1$.

### Definitions and notations
The Schrodinger equation reads

$$
\begin{equation}
\hat{H}(x_1, x_2, \dots , x_N) \Psi_{\lambda}(x_1, x_2, \dots , x_N) =
E_\lambda  \Psi_\lambda(x_1, x_2, \dots , x_N), \label{eq:basicSE1} \tag{1}
\end{equation}
$$

where the vector $x_i$ represents the coordinates (spatial and spin) of particle $i$, $\lambda$ stands  for all the quantum
numbers needed to classify a given $N$-particle state and $\Psi_{\lambda}$ is the pertaining eigenfunction.  Throughout this course,
$\Psi$ refers to the exact eigenfunction, unless otherwise stated.

### Definitions and notations
We write the Hamilton operator, or Hamiltonian,  in a generic way

$$
\hat{H} = \hat{T} + \hat{V}
$$

where $\hat{T}$  represents the kinetic energy of the system

$$
\hat{T} = \sum_{i=1}^N \frac{\mathbf{p}_i^2}{2m_i} = \sum_{i=1}^N \left( -\frac{\hbar^2}{2m_i} \mathbf{\nabla_i}^2 \right) =
		\sum_{i=1}^N t(x_i)
$$

while the operator $\hat{V}$ for the potential energy is given by

$$
\begin{equation}
	\hat{V} = \sum_{i=1}^N \hat{u}_{\mathrm{ext}}(x_i) + \sum_{ji=1}^N v(x_i,x_j)+\sum_{ijk=1}^Nv(x_i,x_j,x_k)+\dots
\label{eq:firstv} \tag{2}
\end{equation}
$$

Hereafter we use natural units, viz. $\hbar=c=e=1$, with $e$ the elementary charge and $c$ the speed of light. This means that momenta and masses
have dimension energy.

### Definitions and notations
If one does quantum chemistry, after having introduced the  Born-Oppenheimer approximation which effectively freezes out the nucleonic degrees of freedom, the Hamiltonian for $N=n_e$ electrons takes the following form

$$
\hat{H} = \sum_{i=1}^{n_e} t(x_i) - \sum_{i=1}^{n_e} k\frac{Z}{r_i} + \sum_{i < j}^{n_e} \frac{k}{r_{ij}},
$$

with $k=1.44$ eVnm

### Definitions and notations
We can rewrite this as

$$
\begin{equation}
    \hat{H} = \hat{H}_0 + \hat{H}_I
    = \sum_{i=1}^{n_e}\hat{h}_0(x_i) + \sum_{i < j}^{n_e}\frac{1}{r_{ij}},
\label{H1H2} \tag{3}
\end{equation}
$$

where  we have defined

$$
r_{ij}=| {\bf r}_i-{\bf r}_j|,
$$

and

$$
\begin{equation}
  \hat{h}_0(x_i) =  \hat{t}(x_i) - \frac{Z}{x_i}.
\label{hi} \tag{4}
\end{equation}
$$

The first term of Eq. [(3)](#H1H2), $H_0$, is the sum of the $N$
*one-body* Hamiltonians $\hat{h}_0$. Each individual
Hamiltonian $\hat{h}_0$ contains the kinetic energy operator of an
electron and its potential energy due to the attraction of the
nucleus. The second term, $H_I$, is the sum of the $n_e(n_e-1)/2$
two-body interactions between each pair of electrons. Note that the double sum carries a restriction $i < j$.

### Definitions and notations
The potential energy term due to the attraction of the nucleus defines the onebody field $u_i=u_{\mathrm{ext}}(x_i)$ of Eq. [(2)](#eq:firstv).
We have moved this term into the $\hat{H}_0$ part of the Hamiltonian, instead of keeping  it in $\hat{V}$ as in  Eq. [(2)](#eq:firstv).
The reason is that we will hereafter treat $\hat{H}_0$ as our non-interacting  Hamiltonian. For a many-body wavefunction $\Phi_{\lambda}$ defined by an
appropriate single-particle basis, we may solve exactly the non-interacting eigenvalue problem

$$
\hat{H}_0\Phi_{\lambda}= w_{\lambda}\Phi_{\lambda},
$$

with $w_{\lambda}$ being the non-interacting energy. This energy is defined by the sum over single-particle energies to be defined below.
For atoms the single-particle energies could be the hydrogen-like single-particle energies corrected for the charge $Z$. For nuclei and quantum
dots, these energies could be given by the harmonic oscillator in three and two dimensions, respectively.

### Definitions and notations
We will assume that the interacting part of the Hamiltonian
can be approximated by a two-body interaction.
This means that our Hamiltonian is written as

$$
\begin{equation}
    \hat{H} = \hat{H}_0 + \hat{H}_I
    = \sum_{i=1}^N \hat{h}_0(x_i) + \sum_{i < j}^N V(r_{ij}),
\label{Hnuclei} \tag{5}
\end{equation}
$$

with

$$
\begin{equation}
  H_0=\sum_{i=1}^N \hat{h}_0(x_i) =  \sum_{i=1}^N\left(\hat{t}(x_i) + \hat{u}_{\mathrm{ext}}(x_i)\right).
\label{hinuclei} \tag{6}
\end{equation}
$$

The onebody part $u_{\mathrm{ext}}(x_i)$ is normally approximated by a harmonic oscillator potential or the Coulomb interaction an electron feels from the nucleus. However, other potentials are fully possible, such as
one derived from the self-consistent solution of the Hartree-Fock equations.

### Definitions and notations
Our Hamiltonian is invariant under the permutation (interchange) of two particles. % (exercise here, prove it)
Since we deal with fermions however, the total wave function is antisymmetric.
Let $\hat{P}$ be an operator which interchanges two particles.
Due to the symmetries we have ascribed to our Hamiltonian, this operator commutes with the total Hamiltonian,

$$
[\hat{H},\hat{P}] = 0,
$$

meaning that $\Psi_{\lambda}(x_1, x_2, \dots , x_N)$ is an eigenfunction of
$\hat{P}$ as well, that is

$$
\hat{P}_{ij}\Psi_{\lambda}(x_1, x_2, \dots,x_i,\dots,x_j,\dots,x_N)=
\beta\Psi_{\lambda}(x_1, x_2, \dots,x_i,\dots,x_j,\dots,x_N),
$$

where $\beta$ is the eigenvalue of $\hat{P}$. We have introduced the suffix $ij$ in order to indicate that we permute particles $i$ and $j$.
The Pauli principle tells us that the total wave function for a system of fermions
has to be antisymmetric, resulting in the eigenvalue $\beta = -1$.

### Definitions and notations
In our case we assume that  we can approximate the exact eigenfunction with a Slater determinant

$$
\begin{equation}
   \Phi(x_1, x_2,\dots ,x_N,\alpha,\beta,\dots, \sigma)=\frac{1}{\sqrt{N!}}
\left| \begin{array}{ccccc} \psi_{\alpha}(x_1)& \psi_{\alpha}(x_2)& \dots & \dots & \psi_{\alpha}(x_N)\\
                            \psi_{\beta}(x_1)&\psi_{\beta}(x_2)& \dots & \dots & \psi_{\beta}(x_N)\\
                            \dots & \dots & \dots & \dots & \dots \\
                            \dots & \dots & \dots & \dots & \dots \\
                     \psi_{\sigma}(x_1)&\psi_{\sigma}(x_2)& \dots & \dots & \psi_{\sigma}(x_N)\end{array} \right|, \label{eq:HartreeFockDet} \tag{7}
\end{equation}
$$

where  $x_i$  stand for the coordinates and spin values of a particle $i$ and $\alpha,\beta,\dots, \gamma$
are quantum numbers needed to describe remaining quantum numbers.

### Definitions and notations
The single-particle function $\psi_{\alpha}(x_i)$  are eigenfunctions of the onebody
Hamiltonian $h_i$, that is

$$
\hat{h}_0(x_i)=\hat{t}(x_i) + \hat{u}_{\mathrm{ext}}(x_i),
$$

with eigenvalues

$$
\hat{h}_0(x_i) \psi_{\alpha}(x_i)=\left(\hat{t}(x_i) + \hat{u}_{\mathrm{ext}}(x_i)\right)\psi_{\alpha}(x_i)=\varepsilon_{\alpha}\psi_{\alpha}(x_i).
$$

The energies $\varepsilon_{\alpha}$ are the so-called non-interacting single-particle energies, or unperturbed energies.
The total energy is in this case the sum over all  single-particle energies, if no two-body or more complicated
many-body interactions are present.

### Definitions and notations
Let us denote the ground state energy by $E_0$. According to the
variational principle we have

$$
E_0 \le E[\Phi] = \int \Phi^*\hat{H}\Phi d\mathbf{\tau}
$$

where $\Phi$ is a trial function which we assume to be normalized

$$
\int \Phi^*\Phi d\mathbf{\tau} = 1,
$$

where we have used the shorthand $d\mathbf{\tau}=d\mathbf{r}_1d\mathbf{r}_2\dots d\mathbf{r}_N$.

### Definitions and notations
In the Hartree-Fock method the trial function is the Slater
determinant of Eq. [(7)](#eq:HartreeFockDet) which can be rewritten as

$$
\Phi(x_1,x_2,\dots,x_N,\alpha,\beta,\dots,\nu) = \frac{1}{\sqrt{N!}}\sum_{P} (-)^P\hat{P}\psi_{\alpha}(x_1)
    \psi_{\beta}(x_2)\dots\psi_{\nu}(x_N)=\sqrt{N!}\hat{A}\Phi_H,
$$

where we have introduced the antisymmetrization operator $\hat{A}$ defined by the
summation over all possible permutations of two particles.

### Definitions and notations
It is defined as

$$
\begin{equation}
  \hat{A} = \frac{1}{N!}\sum_{p} (-)^p\hat{P},
\label{antiSymmetryOperator} \tag{8}
\end{equation}
$$

with $p$ standing for the number of permutations. We have introduced for later use the so-called
Hartree-function, defined by the simple product of all possible single-particle functions

$$
\Phi_H(x_1,x_2,\dots,x_N,\alpha,\beta,\dots,\nu) =
  \psi_{\alpha}(x_1)
    \psi_{\beta}(x_2)\dots\psi_{\nu}(x_N).
$$

### Definitions and notations
Both $\hat{H}_0$ and $\hat{H}_I$ are invariant under all possible permutations of any two particles
and hence commute with $\hat{A}$

$$
\begin{equation}
  [H_0,\hat{A}] = [H_I,\hat{A}] = 0. \label{commutionAntiSym} \tag{9}
\end{equation}
$$

Furthermore, $\hat{A}$ satisfies

$$
\begin{equation}
  \hat{A}^2 = \hat{A},  \label{AntiSymSquared} \tag{10}
\end{equation}
$$

since every permutation of the Slater
determinant reproduces it.

### Definitions and notations
The expectation value of $\hat{H}_0$

$$
\int \Phi^*\hat{H}_0\Phi d\mathbf{\tau}
  = N! \int \Phi_H^*\hat{A}\hat{H}_0\hat{A}\Phi_H d\mathbf{\tau}
$$

is readily reduced to

$$
\int \Phi^*\hat{H}_0\Phi d\mathbf{\tau}
  = N! \int \Phi_H^*\hat{H}_0\hat{A}\Phi_H d\mathbf{\tau},
$$

where we have used Eqs. [(9)](#commutionAntiSym) and
[(10)](#AntiSymSquared). The next step is to replace the antisymmetrization
operator by its definition and to
replace $\hat{H}_0$ with the sum of one-body operators

$$
\int \Phi^*\hat{H}_0\Phi  d\mathbf{\tau}
  = \sum_{i=1}^N \sum_{p} (-)^p\int
  \Phi_H^*\hat{h}_0\hat{P}\Phi_H d\mathbf{\tau}.
$$

### Definitions and notations
The integral vanishes if two or more particles are permuted in only one
of the Hartree-functions $\Phi_H$ because the individual single-particle wave functions are
orthogonal. We obtain then

$$
\int \Phi^*\hat{H}_0\Phi  d\mathbf{\tau}= \sum_{i=1}^N \int \Phi_H^*\hat{h}_0\Phi_H  d\mathbf{\tau}.
$$

Orthogonality of the single-particle functions allows us to further simplify the integral, and we
arrive at the following expression for the expectation values of the
sum of one-body Hamiltonians

$$
\begin{equation}
  \int \Phi^*\hat{H}_0\Phi  d\mathbf{\tau}
  = \sum_{\mu=1}^N \int \psi_{\mu}^*(\mathbf{r})\hat{h}_0\psi_{\mu}(\mathbf{r})
  d\mathbf{r}.
  \label{H1Expectation} \tag{11}
\end{equation}
$$

### Definitions and notations
We introduce the following shorthand for the above integral

$$
\langle \mu | \hat{h}_0 | \mu \rangle = \int \psi_{\mu}^*(\mathbf{r})\hat{h}_0\psi_{\mu}(\mathbf{r}),
$$

and rewrite Eq. [(11)](#H1Expectation) as

$$
\begin{equation}
  \int \Phi^*\hat{H}_0\Phi  d\mathbf{\tau}
  = \sum_{\mu=1}^N \langle \mu | \hat{h}_0 | \mu \rangle.
  \label{H1Expectation1} \tag{12}
\end{equation}
$$

### Definitions and notations
The expectation value of the two-body part of the Hamiltonian is obtained in a
similar manner. We have

$$
\int \Phi^*\hat{H}_I\Phi d\mathbf{\tau}
  = N! \int \Phi_H^*\hat{A}\hat{H}_I\hat{A}\Phi_H d\mathbf{\tau},
$$

which reduces to

$$
\int \Phi^*\hat{H}_I\Phi d\mathbf{\tau}
  = \sum_{i\le j=1}^N \sum_{p} (-)^p\int
  \Phi_H^*V(r_{ij})\hat{P}\Phi_H d\mathbf{\tau},
$$

by following the same arguments as for the one-body
Hamiltonian.

### Definitions and notations
Because of the dependence on the inter-particle distance $r_{ij}$,  permutations of
any two particles no longer vanish, and we get

$$
\int \Phi^*\hat{H}_I\Phi d\mathbf{\tau}
  = \sum_{i < j=1}^N \int
  \Phi_H^*V(r_{ij})(1-P_{ij})\Phi_H d\mathbf{\tau}.
$$

where $P_{ij}$ is the permutation operator that interchanges
particle $i$ and particle $j$. Again we use the assumption that the single-particle wave functions
are orthogonal.

### Definitions and notations
We obtain

$$
\begin{equation}
\begin{split}
  \int \Phi^*\hat{H}_I\Phi d\mathbf{\tau}
  = \frac{1}{2}\sum_{\mu=1}^N\sum_{\nu=1}^N
    &\left[ \int \psi_{\mu}^*(x_i)\psi_{\nu}^*(x_j)V(r_{ij})\psi_{\mu}(x_i)\psi_{\nu}(x_j)
    dx_ix_j \right.\\
  &\left.
  - \int \psi_{\mu}^*(x_i)\psi_{\nu}^*(x_j)
  V(r_{ij})\psi_{\nu}(x_i)\psi_{\mu}(x_j)
  dx_ix_j
  \right]. \label{H2Expectation} \tag{13}
\end{split}
\end{equation}
$$

The first term is the so-called direct term. It is frequently also called the  Hartree term,
while the second is due to the Pauli principle and is called
the exchange term or just the Fock term.
The factor  $1/2$ is introduced because we now run over
all pairs twice.

### Definitions and notations
The last equation allows us to  introduce some further definitions.
The single-particle wave functions $\psi_{\mu}(x)$, defined by the quantum numbers $\mu$ and $x$
are defined as the overlap

$$
\psi_{\alpha}(x)  = \langle x | \alpha \rangle .
$$

### Definitions and notations
We introduce the following shorthands for the above two integrals

$$
\langle \mu\nu|\hat{v}|\mu\nu\rangle =  \int \psi_{\mu}^*(x_i)\psi_{\nu}^*(x_j)V(r_{ij})\psi_{\mu}(x_i)\psi_{\nu}(x_j)
    dx_ix_j,
$$

and

$$
\langle \mu\nu|\hat{v}|\nu\mu\rangle = \int \psi_{\mu}^*(x_i)\psi_{\nu}^*(x_j)
  V(r_{ij})\psi_{\nu}(x_i)\psi_{\mu}(x_j)
  dx_ix_j.
$$

### Definitions and notations
The direct and exchange matrix elements can be  brought together if we define the antisymmetrized matrix element

$$
\langle \mu\nu|\hat{v}|\mu\nu\rangle_{\mathrm{AS}}= \langle \mu\nu|\hat{v}|\mu\nu\rangle-\langle \mu\nu|\hat{v}|\nu\mu\rangle,
$$

or for a general matrix element

$$
\langle \mu\nu|\hat{v}|\sigma\tau\rangle_{\mathrm{AS}}= \langle \mu\nu|\hat{v}|\sigma\tau\rangle-\langle \mu\nu|\hat{v}|\tau\sigma\rangle.
$$

It has the symmetry property

$$
\langle \mu\nu|\hat{v}|\sigma\tau\rangle_{\mathrm{AS}}= -\langle \mu\nu|\hat{v}|\tau\sigma\rangle_{\mathrm{AS}}=-\langle \nu\mu|\hat{v}|\sigma\tau\rangle_{\mathrm{AS}}.
$$

### Definitions and notations
The antisymmetric matrix element is also hermitian, implying

$$
\langle \mu\nu|\hat{v}|\sigma\tau\rangle_{\mathrm{AS}}= \langle \sigma\tau|\hat{v}|\mu\nu\rangle_{\mathrm{AS}}.
$$

With these notations we rewrite Eq. [(13)](#H2Expectation) as

$$
\begin{equation}
  \int \Phi^*\hat{H}_I\Phi d\mathbf{\tau}
  = \frac{1}{2}\sum_{\mu=1}^N\sum_{\nu=1}^N \langle \mu\nu|\hat{v}|\mu\nu\rangle_{\mathrm{AS}}.
\label{H2Expectation2} \tag{14}
\end{equation}
$$

### Definitions and notations
Combining Eqs. [(12)](#H1Expectation1) and
[(14)](#H2Expectation2) we obtain the energy functional

$$
\begin{equation}
  E[\Phi]
  = \sum_{\mu=1}^N \langle \mu | \hat{h}_0 | \mu \rangle +
  \frac{1}{2}\sum_{{\mu}=1}^N\sum_{{\nu}=1}^N \langle \mu\nu|\hat{v}|\mu\nu\rangle_{\mathrm{AS}}.
\label{FunctionalEPhi} \tag{15}
\end{equation}
$$

which we will use as our starting point for the Hartree-Fock calculations later in this course.


```python
# This cell is mandatory in all Dymos documentation notebooks.
missing_packages = []
try:
    import openmdao.api as om
except ImportError:
    if 'google.colab' in str(get_ipython()):
        !python -m pip install openmdao[notebooks]
    else:
        missing_packages.append('openmdao')
try:
    import dymos as dm
except ImportError:
    if 'google.colab' in str(get_ipython()):
        !python -m pip install dymos
    else:
        missing_packages.append('dymos')
try:
    import pyoptsparse
except ImportError:
    if 'google.colab' in str(get_ipython()):
        !pip install -q condacolab
        import condacolab
        condacolab.install_miniconda()
        !conda install -c conda-forge pyoptsparse
    else:
        missing_packages.append('pyoptsparse')
if missing_packages:
    raise EnvironmentError('This notebook requires the following packages '
                           'please install them and restart this notebook\'s runtime: {",".join(missing_packages)}')
```

# How do I run two phases parallel-in-time?

Complex models sometimes encounter state variables which are best simulated on different time scales, with some state variables changing quickly (fast variables) and some evolving slowly (slow variables).

For instance, and aircraft trajectory optimization which includes vehicle component temperatures might see relatively gradual changes in altitude over the course of a two hour flight while temperatures of some components seem to exhibit step-function-like behavior on the same scale.

To accommodate both fast and slow variables in the same ODE, one would typically need to use a _dense_ grid (with many segments/higher order segments).
This can be unnecessarily burdensome when there are many slow variables or evaluating their rates is particularly expensive.

As a solution, Dymos allows the user to run two phases over the same range of times, where one phase may have a more sparse grid to accommodate the slow variables, and one has a more dense grid for the fast variables.

To connect the two phases, state variable values are passed from the first (slow) phase to the second (fast) phase as non-optimal dynamic control variables.
These values are then used to evaluate the rates of the fast variables.
Since outputs from the first phase in generally will not fall on the appropriate grid points to be used by the second phase, interpolation is necessary.
This is one application of the interpolating timeseries component.

In the following example, we solve the brachistochrone problem but do so to minimize the arclength of the resulting wire instead of the time required for the bead to travel along the wire.
This is a trivial solution which should find a straight line from the starting point to the ending point.

There are two phases involved, the first utilizes the standard ODE for the brachistochrone problem.
The second integrates the arclength (𝑆) of the wire using the equation:

\begin{align}
    S = \int v \sin \theta  \sqrt{1 + \frac{1}{\tan^2 \theta}} dt
\end{align}

## The ODE for the wire arclength

```python
om.display_source('dymos.examples.brachistochrone.doc.test_doc_brachistochrone_tandem_phases.BrachistochroneArclengthODE')
```

The trick is that the bead velocity ($v$) is a state variable solved for in the first phase,
and the wire angle ($\theta$) is a control variable "owned" by the first phase.  In the
second phase they are used as control variables with option ``opt=False`` so that their values are
expected as inputs for the second phase.  We need to connect their values from the first phase
to the second phase, at the `control_input` node subset of the second phase.

In the following example, we instantiate two phases and add an interpolating timeseries to the first phase
which provides outputs at the `control_input` nodes of the second phase.  Those values are
then connected and the entire problem run. The result is that the position and velocity variables
are solved on a relatively coarse grid while the arclength of the wire is solved on a much denser grid.

```python
class BrachistochroneArclengthODE(om.ExplicitComponent):

    def initialize(self):
        self.options.declare('num_nodes', types=int)

    def setup(self):
        nn = self.options['num_nodes']

        # Inputs
        self.add_input('v', val=np.zeros(nn), desc='velocity', units='m/s')
        self.add_input('theta', val=np.zeros(nn), desc='angle of wire', units='rad')
        self.add_output('Sdot', val=np.zeros(nn), desc='rate of change of arclength', units='m/s')

        # Setup partials
        arange = np.arange(nn)

        self.declare_partials(of='Sdot', wrt='v', rows=arange, cols=arange)
        self.declare_partials(of='Sdot', wrt='theta', rows=arange, cols=arange)

    def compute(self, inputs, outputs):
        theta = inputs['theta']
        v = inputs['v']
        outputs['Sdot'] = np.sqrt(1.0 + (1.0/np.tan(theta))**2) * v * np.sin(theta)

    def compute_partials(self, inputs, jacobian):
        theta = inputs['theta']
        v = inputs['v']
        cos_theta = np.cos(theta)
        sin_theta = np.sin(theta)
        tan_theta = np.tan(theta)
        cot_theta = 1.0 / tan_theta
        csc_theta = 1.0 / sin_theta

        jacobian['Sdot', 'v'] = sin_theta * np.sqrt(1.0 + cot_theta**2)
        jacobian['Sdot', 'theta'] = v * (cos_theta * (cot_theta**2 + 1) - cot_theta * csc_theta) / \
            (np.sqrt(1 + cot_theta**2))
```

```python
om.display_source('dymos.examples.brachistochrone.brachistochrone_ode')
```

```python
from dymos.examples.brachistochrone.brachistochrone_ode import BrachistochroneODE

import numpy as np
import matplotlib.pyplot as plt
import openmdao.api as om
import dymos as dm

p = om.Problem(model=om.Group())

p.driver = om.pyOptSparseDriver()
p.driver.options['optimizer'] = 'SLSQP'
p.driver.declare_coloring()

# The transcription of the first phase
tx0 = dm.GaussLobatto(num_segments=10, order=3, compressed=False)

# The transcription for the second phase (and the secondary timeseries outputs from the first phase)
tx1 = dm.Radau(num_segments=20, order=9, compressed=False)

#
# First Phase: Integrate the standard brachistochrone ODE
#
phase0 = dm.Phase(ode_class=BrachistochroneODE, transcription=tx0)

p.model.add_subsystem('phase0', phase0)

phase0.set_time_options(fix_initial=True, duration_bounds=(.5, 10))

phase0.add_state('x', fix_initial=True, fix_final=False)

phase0.add_state('y', fix_initial=True, fix_final=False)

phase0.add_state('v', fix_initial=True, fix_final=False)

phase0.add_control('theta', continuity=True, rate_continuity=True,
                   units='deg', lower=0.01, upper=179.9)

phase0.add_parameter('g', units='m/s**2', val=9.80665)

phase0.add_boundary_constraint('x', loc='final', equals=10)
phase0.add_boundary_constraint('y', loc='final', equals=5)

# Add alternative timeseries output to provide control inputs for the next phase
phase0.add_timeseries('timeseries2', transcription=tx1, subset='control_input')

#
# Second Phase: Integration of ArcLength
#
phase1 = dm.Phase(ode_class=BrachistochroneArclengthODE, transcription=tx1)

p.model.add_subsystem('phase1', phase1)

phase1.set_time_options(fix_initial=True, input_duration=True)

phase1.add_state('S', fix_initial=True, fix_final=False,
                 rate_source='Sdot', units='m')

phase1.add_control('theta', opt=False, units='deg', targets='theta')
phase1.add_control('v', opt=False, units='m/s', targets='v')

#
# Connect the two phases
#
p.model.connect('phase0.t_duration', 'phase1.t_duration')

p.model.connect('phase0.timeseries2.controls:theta', 'phase1.controls:theta')
p.model.connect('phase0.timeseries2.states:v', 'phase1.controls:v')

# Minimize arclength at the end of the second phase
phase1.add_objective('S', loc='final', ref=1)

p.model.linear_solver = om.DirectSolver()
p.setup(check=True)

p['phase0.t_initial'] = 0.0
p['phase0.t_duration'] = 2.0

p.set_val('phase0.states:x', phase0.interp('x', ys=[0, 10]))
p.set_val('phase0.states:y', phase0.interp('y', ys=[10, 5]))
p.set_val('phase0.states:v', phase0.interp('v', ys=[0, 9.9]))
p.set_val('phase0.controls:theta', phase0.interp('theta', ys=[5, 100]))

p['phase0.parameters:g'] = 9.80665

p['phase1.states:S'] = 0.0

dm.run_problem(p)

fig, (ax0, ax1) = plt.subplots(2, 1)
fig.tight_layout()
ax0.plot(p.get_val('phase0.timeseries.states:x'), p.get_val('phase0.timeseries.states:y'), '.')
ax0.set_xlabel('x (m)')
ax0.set_ylabel('y (m)')
ax1.plot(p.get_val('phase1.timeseries.time'), p.get_val('phase1.timeseries.states:S'), '+')
ax1.set_xlabel('t (s)')
ax1.set_ylabel('S (m)')
plt.show()
```

```python
from openmdao.utils.assert_utils import assert_near_equal

expected = np.sqrt((10-0)**2 + (10 - 5)**2)
assert_near_equal(p.get_val('phase1.timeseries.states:S')[-1], expected, tolerance=1.0E-3)
```