 **Chapter 4: [Spectroscopy](CH4-Spectroscopy.ipynb)**

# Chemical Composition in Core-Loss Spectra

[Download](https://raw.githubusercontent.com/gduscher/MSE672-Introduction-to-TEM/main/Spectroscopy/CH4_08-Chemical_Compostion.ipynb)

[](
    https://colab.research.google.com/github/gduscher/MSE672-Introduction-to-TEM/blob/main/Spectroscopy/CH4_08-Chemical_Compostion.ipynb)

part of

 **[MSE672:  Introduction to Transmission Electron Microscopy](../_MSE672_Intro_TEM.ipynb)**

by Gerd Duscher, Spring 2021

Microscopy Facilities

Joint Institute of Advanced Materials

Materials Science & Engineering

The University of Tennessee, Knoxville

Background and methods to analysis and quantification of data acquired with transmission electron microscopes.

## Content

Quantitative determination of composition in a core-loss EELS spectrum

Please cite:

[M. Tian et  al. *Measuring the areal density of nanomaterials by electron energy-loss spectroscopy*
Ultramicroscopy Volume 196, 2019, pages 154-160](https://doi.org/10.1016/j.ultramic.2018.10.009)

as a reference of the here introduced model-based quantification method.

## Load important packages

### Check Installed Packages

```python
import sys
from pkg_resources import get_distribution, DistributionNotFound

def test_package(package_name):
    """Test if package exists and returns version or -1"""
    try:
        version = get_distribution(package_name).version
    except (DistributionNotFound, ImportError) as err:
        version = '-1'
    return version

# Colab setup ------------------
if 'google.colab' in sys.modules:
    !pip install pyTEMlib -q
# pyTEMlib setup ------------------
else:
    if test_package('sidpy') < '0.0.5':
        print('installing sidpy')
        !{sys.executable} -m pip install  --upgrade pyTEMlib -q
    if test_package('pyTEMlib') < '0.2021.4.20':
        print('installing pyTEMlib')
        !{sys.executable} -m pip install  --upgrade pyTEMlib -q
# ------------------------------
print('done')
```

    done

### Import all relevant libraries

Please note that the EELS_tools package from pyTEMlib is essential.

```python
import sys
if 'google.colab' in sys.modules:
    %pylab --no-import-all inline
else:
    %pylab --no-import-all notebook
    %gui qt

import warnings
warnings.filterwarnings('ignore')

## We need to import a few important additional function from matplotlib,
## because we want to demonstrate a few more hidden functionalities of the EELS_tools of pytTEMlib.
from matplotlib.widgets import Cursor
from matplotlib.patches import Rectangle
from matplotlib.widgets import SpanSelector
from scipy.ndimage.filters import gaussian_filter

## import the configuration files of pyTEMlib (we need access to the data folder)
import pyTEMlib
import pyTEMlib.file_tools as ft
import pyTEMlib.eels_tools as eels
import pyTEMlib.interactive_eels as ieels

# For archiving reasons it is a good idea to print the version numbers out at this point
print('pyTEM version: ',pyTEMlib.__version__)
```

    Populating the interactive namespace from numpy and matplotlib
    pyTEM version:  0.2021.04.02

## Chemical Composition

>
> We discuss first the conventional method of EELS chemical compoisition determination
>

In this chapter we use the area under the ionization edge to determine the chemical composition of a (small) sample volume.
The equation used to determine the number of atoms per unit volume $N$ (also called areal density) is:
\begin{equation}
I_{edge}(\beta, \Delta E) = N * I_{0}(\beta) * \sigma_{edge}(\beta, \Delta E)
\end{equation}

$I_0$ is the number of electrons hitting the sample, and so directly comparable to the beam current.

The equation can be approximated assuming that the spectrum has not been corrected for single scattering:
\begin{equation}
I_{edge}(\beta, \Delta E) = N * I_{low-loss}(\beta,\Delta E) *  \sigma_{edge}(\beta, \Delta E)
\end{equation}
where $\beta$ is the collection angle and $\sigma_{edge}$ is the **partial** cross--section (for energy window $\Delta E$) for the core--loss excitation.

The integration interval $\Delta E$ which defines  $I_{edge}(\beta, \Delta E)$ and $I_{low-loss}$ is shown in figure below.

*Valence--loss, core--loss edges and background of SrTiO$_3$]{\label{fig:edge2} Ti-L$_{2,3}$ and O-K (right) core--loss edges and background of SrTiO$_3$. The valence-loss spectrum with the zero--loss $I_{zero-loss}$ and low--loss intensities $I_{low-loss} $ to be used in the quantification are displayed in the background.*

If we cannot determine the intensity of the zero-loss peak  $I_{zero-loss}$  or of the low-loss area $I_{low-loss}$, we still can determine relative chemical compositions of two elements $a$ and $b$ considering that:

\begin{equation}
\frac{N_a}{N_b} = \frac{I_{e_a}(\beta, \Delta E)}{I_0 \sigma_{e_a}(\beta, \Delta E)} \frac{I_0 \sigma_{e_b}(\beta, \Delta E) } {I_{e_b}(\beta, \Delta E)} \nonumber
\end{equation}

\begin{equation}
\frac{N_a}{N_b}=  \frac{I_{e_a}(\beta, \Delta E)\sigma_{e_b}(\beta, \Delta E) }
{I_{e_b}(\beta, \Delta E)\sigma_{e_a}(\beta, \Delta E) }
\end{equation}

and the value $I_0$ cancels out.

The integration windows for the two edges $\Delta E$ should be the same, but can be chosen differently as long as we use a different cross-section $\sigma$ as well. For that case we get:

\begin{equation} \
\frac{N_a}{N_b} =  \frac{I_{e_a}(\beta, \Delta E_a)\sigma_{e_b}(\beta, \Delta E_b) }
{I_{e_b}(\beta, \Delta E_a)\sigma_{e_a}(\beta, \Delta E_a) }
\end{equation}

Note, that the use of different integration windows usually results in a larger error of the quantification.

In order to use the above equation we first  have to determine the background under the edge.
This background  is then subtracted from the spectrum.
Then we integrate over the different parts of the spectrum to determine the integrals (sums) of $I_{edge}$,  and $I_{zero-loss}$, or  $I_{low-loss} $ (depending whether we did a SSD first or not).
After that we have to determine the cross-section ([notebook](CH4_07-Working_with_X-Sections.ipynb)) for each edge for the parameter $\beta$ and $\Delta E$.

### Background Fitting

The core-loss edges occur usually at energy-losses higher than 100 eV, superimposed to a monotonic decreasing background. For quantification of the chemical composition we need the area under the edge of a spectrum, and therefore, need to subtract the intensity of the background under the edge. Here we discuss several methods of how to determine the intensity of the background under the  edge.

The high energy ``tail`` of the plasmon peak follows the power law $A E^{-r}$. The parameter is varies widely and is associated with the intensity of the background. $E$ is the energy loss. The exponent $r$ gives the slope and should be between 2-6. The value $r$ usually decreases with increasing specimen thickness, because of plural-scattering contributions. $r$ also decreases with increasing collection angles $\beta$, but increases with increasing energy--loss.

>
>Consequently, we have to determine the parameters $A$ and $r$ for each ionization edge.
>

The fitting of the power law $A E^{-r}$ (to determine $A$ and $r$) is usually done in an area just before the edge, assuming that the background follows the same power law under the edge.
This fit can only work if the detector dark current and gain variation is corrected prior to the analysis.

A standard technique is to match the pre--edge background $J(E)$ to a function $F(E)$ (here the power law) whose parameter (here $A$ and $r$) minimize the quantity:
\begin{equation}
\chi^2 = \sum \limits_{i} \left[  \frac{J_i - F_i}{\sigma_i} \right]^2
\end{equation}
where $i$ is the index of the channel within the fitting region and $\sigma_i$ represents the statistical error (standard deviation) of the intensity in that channel.

The value $\sigma_i$ is often considered constant (for example $1/3$ or $e^{-1}$). For our problem, the quantum mechanical shot noise is adequate
\begin{equation}
\sigma_i = \ln(J_i - \sqrt{J_i}) - \ln(J_i) \approx \sqrt{J_i}
\end{equation}
where we assume that $J_i$ is in number of electrons and not in counts (number of electrons times a conversion factor).

In the figure below, we see the result and the output of the background fit (plus subtraction).

* Background fit on a spectrum of SrTiO$_3$]{\label{fig:background} Background fit on a spectrum of SrTiO$_3$. The $A$ and $r$ parameter together with the normalized (reduced) $\chi^2_n$ parameter is displayed in the {\bf Results} window.*

### Background Fitting: Spatial Difference

We can use also an experimentally determined background, if impurity or dopant atoms are present in confined areas only. Then,  we can take two spectra: one next to this area and one at this area. The near by area will result in a spectrum without this edge and can be used as a background for quantification of the other spectrum. This method is highly accurate if the sample thickness does not change between these areas. The measurement of ppm levels of dopants and impurity atoms can achieved with this method. This Method will be more closely examined in Section Spatial-Difference

### Background Subtraction Errors

In addition to possible systematic errors, any fitted background to noisy data will be subject to to a random or statistical error.  The signal noise ration (SNR) can be defined as:

\begin{equation}
SNR = I_{edge} [\mbox{var}(I_{edge}]^{-1/2} = I_{edge}/(I_{edge}+ h \quad I_{background})^{1/2}
\end{equation}

where the dimensionless parameter $h$ represents the uncertainty of the background fit due to noise.
If the width of the integration window is sufficiently small (for example the same width as the fitting window) then this factor $h$ is relatively small. For equal windows we can approximate $h = 10$.

### Cross-Section

The cross--section gives us the weight of the edge intensity to compare to different elements or to the total number of electrons (to compute areal density).
Such a cross--sections is compared to an edge intensity in the figure below.

*The shape of a calculated cross-section (black) is compared to the  intensity of a Si-L$_{2,3}$ and Si-$L_{1}$ edge after background subtraction (gray). The SSD--corrected spectrum (red) and the extrapolated background (light blue) are also shown. In the results window, we see the integrated edge intensity, the integrated cross-sections and the determined areal density.*

There are different methods of how to use cross-sections in the quantification of  the chemical composition from spectra:

Egerton gives in his book a tabulated list of generalized oscillator strength (GOS) for different elements. The values for different integration windows $\Delta E$ can be linearly extrapolated for other integration window widths. The GOS have to be extrapolated for the chosen integration window and converted to cross--sections. The GOS are calculated with the Bethe theory in Hydrogenic approximation (see below in chapter \ref{sec:cross-section-calculate}
### Calculation of the Cross--Section

There are two methods in  the literature to calculate the cross--section. One is the one where we assume s states in free atoms and is called Hydrogenic approximation and one which approximates  the free atoms a little more detailed: the Hatree-Slater method.

Both methods are based on free atom calculations, because of the strong bonding of the core--electrons to the nucleus, band-structure (collective) effects can be neglected.

The figure below compares the cross--sections of these two approximations (with a background added) to an experimental spectrum.

*The shape of a Hydrogenic (green) and Hatree--Slater (blue) cross-section (with a background added) is compared to an experimental (SSD-corrected) spectrum of Si.*

### Summary
The power law is not really correct for any larger energy interval, which results in a change of the $r$ exponent throughout the spectrum's energy range.

A polynomial of 2$^{nd}$ order can be used to fit the spectrum, but often leads to strong oscillations in the extrapolated high energy part.

The exact slope of the extrapolated background depends on pre-edge features and noise

>
>Generally the above described classic method of quantification is often non-reproducible, and results in errors often in excess of 100%.
>

In the following we will work with a model based method that reduces the artefacts, increases reproducibility and improves the error to about 3 atom % absolute.

## Load and plot a spectrum

As an example we load the spectrum **1EELS Acquire (high-loss).dm3** from the *example data* folder.

Please see [Loading an EELS Spectrum](LoadEELS.ipynb) for details on storage and plotting.

```python
try:
    current_channel.file.close()
except:
    pass
# Load file
file_name = '../example_data/1EELS Acquire (high-loss).dm3'
main_dataset = ft.open_file(file_name)#os.path.join(current_directory,filename))
current_channel = main_dataset.h5_dataset.parent.parent

if main_dataset.data_type.name != 'SPECTRUM':
    print('NOT what we want here')
else:
    main_dataset.metadata = eels.read_dm3_eels_info(main_dataset.original_metadata)
    main_dataset.metadata['exposure_time'] = main_dataset.metadata['single_exposure_time'] * main_dataset.metadata['number_of_frames']
    main_dataset.view_metadata()

main_dataset.plot()
```

    single_exposure_time : 3.0
    exposure_time : 63.0
    number_of_frames : 21
    collection_angle : 33.0
    convergence_angle : 30.0
    acceleration_voltage : 200000.0
    microscope : Unknown

    <IPython.core.display.Javascript object>

### Which elements are present

To determine which elements are present we add a cursor to the above plot (see [Working with Cross-Sections](CH4_07-Working_with_X-Sections.ipynb) for details) and with a left (right) mouse-click, we will get the major (all) edges in the vincinity of the cursor.

In the example we note that the N-K edge of this boron nitride sample is not at 400keV. We have to adjust the energy-scale.
(THIS SHOULD NOT HAPPEN IN NORMAL SPECTRA AND IS FOR DEMONSTRATION ONLY)

```python
maximal_chemical_shift = 5
energy_scale = main_dataset.energy_loss
cursor = ieels.EdgesAtCursor(main_dataset.view.axis, energy_scale,main_dataset,maximal_chemical_shift)
```

Let's correct the energy scale of the example spectrum.

Again a shift of the enrrgy scale is normal but not a discripancy of the dispersion.

### Probability scale of y-axis

We need to know the total amount of electrons involved in the EELS spectrum

There are three possibilities:
- the intensity of the low loss will give us the counts per acquisition time
- the intensity of the beam in an image
- a direct measurement of the incident beam current

Here we got the low-loss spectrum. For the example please load **1EELS Acquire (low-loss).dm3** from the *example data* folder.

```python
ll_dataset = ft.open_file('../example_data/1EELS Acquire (low-loss).dm3')
ll_dataset.h5_dataset.file.close()
ll_dataset.metadata = eels.read_dm3_eels_info(ll_dataset.original_metadata)
ll_dataset.metadata['exposure_time'] = ll_dataset.metadata['single_exposure_time'] * ll_dataset.metadata['number_of_frames']
ll_dataset.view_metadata()
ll_dataset.plot()
```

    single_exposure_time : 0.01
    exposure_time : 0.21
    number_of_frames : 21
    collection_angle : 33.0
    convergence_angle : 30.0
    acceleration_voltage : 200000.0
    microscope : Unknown

    <IPython.core.display.Javascript object>

### Intensity to Probability Calibration

 We need to calibrate the number of counts with the integration time of the spectrum.

```python
print(f"{ll_dataset.sum():.0f} counts in  {ll_dataset.metadata['exposure_time']:.2f}sec")
I0 = ll_dataset.sum()/ll_dataset.metadata['exposure_time']

I0 = main_dataset.sum()/ll_dataset.metadata['exposure_time']*main_dataset.metadata['exposure_time']
print(f"incident beam current of core--loss spectrum is {I0:.0f} counts")

main_dataset.metadata['intentsity_scale_ppm'] = 1e6/I0
main_dataset.metadata['incident_beam_current_counts'] = I0

spectrum = main_dataset*main_dataset.metadata['intentsity_scale_ppm']
spectrum.quantity = 'probability'
spectrum.units = 'ppm'
spectrum.plot()
```

    115700152 counts in  0.21sec
    incident beam current of core--loss spectrum is 11721591600 counts

    <IPython.core.display.Javascript object>

## Components of a core loss spectrum

-background

-absorption edges

### Plotting of cross sections and spectrum
please note that spectrum and cross sections are not on the same scale

```python

B_Xsection = eels.xsec_xrpa(energy_scale, 200, 5, 10. )/1e10
N_Xsection = eels.xsec_xrpa(energy_scale, 200, 7, 10. ,shift=-6)/1e10       #  xsec  is in barns = 10^28 m2 = 10^10 nm2

fig, ax1 = plt.subplots()

ax1.plot(energy_scale, Ti_Xsection, label='Ti X-section' )
ax1.plot(energy_scale, O_Xsection, label='O X-section' )
ax1.set_xlabel('energy_loss [eV]')
ax1.set_ylabel('probability [atoms/nm$^{2}$]')

ax2 = ax1.twinx()
ax2.plot(energy_scale, spectrum, c='r', label='spectrum')
ax2.tick_params('y', colors='r')
ax2.set_ylabel('probability [ppm]')
#plt.xlim(100,500)
plt.legend();
fig.tight_layout();

```

    <IPython.core.display.Javascript object>

### Background
The other ingredient in a core loss spectrum is the background

The backgrund consists of
- ionization edges to the left of the beginning of the spectrum (offset)
- tail of the plasmon peak (generally a power_law with $\approx A* E^{-3}$)

Here we approximate the background in an energy window before the first ionization edge in the spectrum as a power law with exponent $r\approx 3$

```python
from scipy.optimize import leastsq  ## leastsqure fitting routine fo scipy

# Determine energy window in pixels
bgdStart = 120
bgdWidth = 40
offset = energy_scale[0]
dispersion = energy_scale[1]-energy_scale[0]
startx = int((bgdStart-offset)/dispersion)
endx = startx + int(bgdWidth/dispersion)

x = np.array(energy_scale[startx:endx])
y = np.array(spectrum[startx:endx])

# Initial values of parameters
p0 = np.array([1.0E+20,3])

## background fitting
def bgdfit(p, y, x):
    err = y - (p[0]* np.power(x,(-p[1])))
    return err
p, lsq = leastsq(bgdfit, p0, args=(y, x), maxfev=2000)
print(f'Power-law background with amplitude A: {p[0]:.1f} and exponent -r: {p[1]:.2f}')

#Calculate background over the whole energy scale
background = p[0]* np.power(energy_scale,(-p[1]))

plt.figure()

plt.xlabel('energy_loss [eV]')
plt.ylabel('probability [ppm]')

plt.plot(energy_scale, spectrum, label='spectrum')
plt.plot(energy_scale, background, label='background')
plt.plot(energy_scale, spectrum-background, label='spectrum-background')
plt.legend();
plt.axhline(0, color='gray')
```

    Power-law background with amplitude A: 46400388.8 and exponent -r: 3.27

    <IPython.core.display.Javascript object>

    <matplotlib.lines.Line2D at 0x11a5b16a1f0>

## Fitting a Spectrum

We are revisiting the above the fundamental equation of the chemical composition:

We already calibrated the cross section in per $nm^2$ and so if we start again from the fundamental equation:

\begin{equation}
I_{edge}(\beta, \Delta E) = N I_{0}(\beta) \sigma_{edge}(\beta, \Delta E)
\end{equation}

and as above we calibrate the intensity of the spectrum by $I_{spectrum}/I_0$ then we get::

\begin{equation}
\frac{I_{edge}(\beta, \Delta E)}{I_0} = I^{norm}_{edge}  = N \sigma_{edge}(\beta, \Delta E)
\end{equation}

and if we fit the calibrated intensity with the cross section then we can replace$I^{norm}_{edge}$ by a fitting value $q_{edge}$ multiplied by cross section $\sigma$:

$$
N =  \frac{I_{edge}(\beta, \Delta E)/I_0}{\sigma_{edge}(\beta, \Delta E)}  = \frac{I^{norm}_{edge}}{\sigma_{edge}(\beta, \Delta E)} = \frac{q_{edge} * \sigma_{edge}(\beta, \Delta E)}{\sigma_{edge}(\beta, \Delta E)} = q_{edge}
$$

and N is in atoms per nm$^2$.

So a fit to a callibrated spectrum as above, will get us a ``fitting parameter`` which is an ``areal density`` (which is a legitimate thermodynamic quantity).

And for the relative composition we get:
$$
\frac{N_a}{N_b}= \frac{q_a}{q_b}
$$

In the following we will do this kind of a fit by:
- calibrate the intensity in the spectrum (in ppm)
- using  cross section in units of nm$^2$

>
> Please note that for the relative composition , the $I_0$ will fall out and so a fit to a spectrum without calibrated intensity will still give the relative intensity accurately.
>

### Preparing the fitting mask

Our theoretical cross sections do not include any solid state effects (band structure) and so the fine structure at the onset of the spectra must be omitted in a quantification.

These parts of the spectrum will be simply set to zero. We plot the masked spectrum that will be evaluated.

```python
energy_scale = main_dataset.energy_loss*1.03-3

dispersion = (energy_scale[1] - energy_scale[0])
offset = energy_scale[0]
startx = int((bgdStart-offset)/dispersion)

mask = np.ones(len(energy_scale))
mask[0 : int(startx)] = 0.0;

edges = {}
edges['1'] = {}
edges['1']['Z']=5
edges['1']['symmetry']= 'K1'
edges['2'] = {}
edges['2']['Z']=7
edges['2']['symmetry']= 'K1'

for key in edges:
    print((eels.get_x_sections(edges[key]['Z']))[edges[key]['symmetry']])
    edges[key]['onset'] = (eels.get_x_sections(edges[key]['Z']))[edges[key]['symmetry']]['onset']
    if key == '2':
        edges[key]['onset'] = 390
        print('onset')
    edges[key]['onset_pixel'] = int((edges[key]['onset'] -offset)/dispersion)
    edges[key]['start_exclude'] = int((edges[key]['onset']-5  - offset)/dispersion)
    edges[key]['end_exclude']   = int((edges[key]['onset']+50 - offset)/dispersion)
    print(key)
    if key == '2':
        edges[key]['onset'] = 400
        print('onset')
    mask[edges[key]['start_exclude']:edges[key]['end_exclude']] = 0.0

plt.figure()
plt.plot(energy_scale, spectrum, label='spectrum')
plt.plot(energy_scale, spectrum*mask, label='spectrum')
plt.xlabel('energy-loss [eV]')
plt.ylabel('probability [ppm]');
```

### The Fit

The function **model** just sums the weighted cross-sections and the background.

The background consists of the power-lawbackground before plus a polynomial component allowing for *a variation of the exponent $r$ of the power-law*.

The least square fit is weighted by the noise according to Poison statistic $\sqrt{I(\Delta E)}$.

>
>Please note that the cross sections are for single atoms only and do not cover any solid state effects vsible as strong peaks in the first 50 eV or so past the onset.
>
> We exclude those parts from the fits.

```python
plt.figure()
plt.plot(energy_scale, spectrum, label='spectrum')
plt.plot(energy_scale, spectrum*mask, label='spectrum')
plt.xlabel('energy-loss [eV]')
plt.ylabel('probability [ppm]');

regions = ieels.RegionSelector(plt.gca())

for key in edges:
    print(key)
    regions.set_regions(str(key),edges[key]['onset']-5, 50.)

regions.set_regions('fit region',bgdStart, energy_scale[-1]-bgdStart)
```

    <IPython.core.display.Javascript object>

    1
    2

```python
region_tags = regions.get_regions()
startx = int((region_tags['fit_area']['start_x']-offset)/dispersion)

print(region_tags)
```

    {'1': {'start_x': 183.0, 'width_x': 50.0}, '2': {'start_x': 395, 'width_x': 50.0}, 'fit_area': {'start_x': 120, 'width_x': 507.10249999999996}}

```python
edges
```

    {'1': {'Z': 5,
      'symmetry': 'K1',
      'onset': 188.0,
      'onset_pixel': 341,
      'start_exclude': 322,
      'end_exclude': 535},
     '2': {'Z': 7,
      'symmetry': 'K1',
      'onset': 400,
      'onset_pixel': 1126,
      'start_exclude': 1106,
      'end_exclude': 1320}}

```python
region_tags = regions.get_regions()

mask = np.ones(main_dataset.shape)

#startx = np.searchsorted(tags['energy_scale'],region_tags['fit_area']['start_x'])

mask[0:startx] = 0.0

for key in region_tags:
    end = region_tags[key]['start_x']+region_tags[key]['width_x']
    startx = np.searchsorted(energy_scale,region_tags[key]['start_x'])
    endx   = np.searchsorted(energy_scale,end)
    if key == 'fit_area':
        mask[0:startx] = 0.0
        mask[endx:-1] = 0.0
    else:
        mask[startx:endx] = 0.0

pin = np.array([1.0,1.0,.0,0.0,0.0,0.0, 1.0,1.0,0.001,5,3])
x = energy_scale

blurred = gaussian_filter(spectrum, sigma=5)

y = blurred*1e-6 ## now in probability
y[np.where(y<1e-8)]=1e-8

B_Xsection = eels.xsec_xrpa(energy_scale, 200, 5,  10. )/1e10
N_Xsection = eels.xsec_xrpa(energy_scale, 200, 7,  10. )/1e10       #  xsec  is in barns = 10^-28 m2 = 10^-10 nm2

xsec = np.array([B_Xsection, N_Xsection])
numberOfEdges = 2

def residuals(p,  x, y ):
    err = (y-model(x,p))*mask/np.sqrt(np.abs(y))
    return err

def model(x, p):
    y = (p[9]* np.power(x,(-p[10]))) +p[7]*x+p[8]*x*x
    for i in range(numberOfEdges):
        y = y + p[i] * xsec[i,:]
    return y

p, cov = leastsq(residuals, pin,  args = (x,y) )

print(f"B/N ratio is {p[0]/p[1]:.3f}")

#the B atom areal density of a single layer of h-BN (18.2 nm−2)
print(f" B areal density is {p[0]:.0f} atoms per square nm, which equates {abs(p[0])/18.2:.1f} atomic layers")
print(f" N areal density is {p[1]:.0f} atoms per square nm, which equates {abs(p[1])/18.2:.1f} atomic layers")

```

    B/N ratio is 1.071
     B areal density is 420 atoms per square nm, which equates 23.1 atomic layers
     N areal density is 393 atoms per square nm, which equates 21.6 atomic layers

This result let's us pause a little. How big of and error do we have?

>
>Why is the number of B atoms not equal to the number of N atoms?
>

Naively, we would assume an error of 7 atom%

However, the number of single atomic layers gives us a hint.

There are 23 layers of B and 24 layers of N.

This result suggests that BN is terminated on the B and N face with a layer of B and the error is 2.5 atom%

```python
print(f'The expected ratio between 23 layers of B and 22 layers of N is {23/22:.3f}')
```

    The expected ratio between 23 layers of B and 22 layers of N is 1.045

###  Plotting of the fit

```python
model_spectrum =  model(x, p)*1e6 # in ppm
model_background =  ((p[9]* np.power(x,-p[10])) +p[7]*x+p[8]*x*x)*1e6 # in ppm

plt.figure()
#plt.plot(energy_scale, spectrum, label='spectrum')
plt.plot(energy_scale, blurred, label='blurred spectrum')
plt.plot(x,model_spectrum, label='model')
plt.plot(x,spectrum-model_spectrum, label='difference')
plt.plot(x,(spectrum-model_spectrum), label='difference')
plt.plot(x,model_background, label='background')
plt.plot([x[0],x[-1]],[0,0],c='black')

plt.xlabel('energy-loss [eV]')
plt.ylabel('probability [ppm]')
plt.legend();
```

    <IPython.core.display.Javascript object>

## Summary

We use a cross section in unsits of nm$^2$ and a calibrated spectrum to fit a cross section to each edge.

The fitting parameter is then the areal density of the element.

We only fit the part of the spectrum we know which is the single atom part of the edge, and avoid to fit any solid state effects at the onset of the edge.

The interpreation of solid state effects at the onset are discussed in the [energy-loss near-edge structure (ELNES)](CH4_10-ELNES.ipynb) notebook.

## Navigation
-   **Up Chapter 4: [Imaging](CH4_00-Spectroscopy.ipynb)**
-   **Back: [Introduction to Core-Loss](CH4_07-Introduction_Core_Loss.ipynb)**
-   **Next: [Analysis of Core-Loss](CH4_09-Analysis_Core_Loss.ipynb)**
-   **List of Content: [Front](../_MSE672_Intro_TEM.ipynb)**

```python

```


# PHY321: Two-body problems and Gravitational Forces

**[Morten Hjorth-Jensen](http://mhjgit.github.io/info/doc/web/)**, Department of Physics and Astronomy and Facility for Rare Ion Beams (FRIB), Michigan State University, USA and Department of Physics, University of Oslo, Norway

Date: **Mar 19, 2021**

Copyright 1999-2021, [Morten Hjorth-Jensen](http://mhjgit.github.io/info/doc/web/). Released under CC Attribution-NonCommercial 4.0 license

## Aims and Overarching Motivation

### Monday

Definition of the two-body problem, rewriting the equations in relative and center-of-mass coordinates

**Reading suggestion**: Taylor sections 8.2-8.3

### Wednesday

Preparing the ground for the  gravitional force and its solution in two dimensions

**Reading suggestion**: Taylor chapter 8.4

### Friday

Harmonic Oscillator example. Begin Kepler's laws and computing with classes.

**Reading suggestion**: Taylor section 8.5-8.6

# Two-body Problems

**Note: more text to be added**.

The gravitational potential energy and forces involving two masses $a$ and $b$ are

$$
\begin{eqnarray}
V_{ab}&=&-\frac{Gm_am_b}{|\boldsymbol{r}_a-\boldsymbol{r}_b|},\\
\nonumber
F_{ba}&=&-\frac{Gm_am_b}{|\boldsymbol{r}_a-\boldsymbol{r}_b|^2}\hat{r}_{ab},\\
\nonumber
\hat{r}_{ab}&=&\frac{\boldsymbol{r}_b-\boldsymbol{r}_a}{|\boldsymbol{r}_a-\boldsymbol{r}_b|}.
\end{eqnarray}
$$

Here $G=6.67\times 10^{-11}$ Nm$^2$/kg$^2$, and $F_{ba}$ is the force
on $b$ due to $a$. By inspection, one can see that the force on $b$
due to $a$ and the force on $a$ due to $b$ are equal and opposite. The
net potential energy for a large number of masses would be

$$
\begin{equation}
V=\sum_{a<b}U_{ab}=\frac{1}{2}\sum_{a\ne b}V_{ab}.
\label{_auto1} \tag{1}
\end{equation}
$$

## Relative and Center of Mass Motion

Thus far, we have considered the trajectory as if the force is
centered around a fixed point. For two bodies interacting only with
one another, both masses circulate around the center of mass. One
might think that solutions would become more complex when both
particles move, but we will see here that the problem can be reduced
to one with a single body moving according to a fixed force by
expressing the trajectories for $\boldsymbol{r}_1$ and $\boldsymbol{r}_2$ into the
center-of-mass coordinate $\boldsymbol{R}_{\rm cm}$ and the relative
coordinate $\boldsymbol{r}$,

$$
\begin{eqnarray}
\boldsymbol{R}_{\rm cm}&\equiv&\frac{m_1\boldsymbol{r}_1+m_2\boldsymbol{r}_2}{m_1+m_2},\\
\nonumber
\boldsymbol{r}&\equiv&\boldsymbol{r}_1-\boldsymbol{r_2}.
\end{eqnarray}
$$

Here, we assume the two particles interact only with one another, so
$\boldsymbol{F}_{12}=-\boldsymbol{F}_{21}$ (where $\boldsymbol{F}_{ij}$ is the force on $i$
due to $j$. The equations of motion then become

$$
\begin{eqnarray}
\ddot{\boldsymbol{R}}_{\rm cm}&=&\frac{1}{m_1+m_2}\left\{m_1\ddot{\boldsymbol{r}}_1+m_2\ddot{\boldsymbol{r}}_2\right\}\\
\nonumber
&=&\frac{1}{m_1+m_2}\left\{\boldsymbol{F}_{12}+\boldsymbol{F}_{21}\right\}=0.\\
\ddot{\boldsymbol{r}}&=&\ddot{\boldsymbol{r}}_1-\ddot{\boldsymbol{r}}_2=\left(\frac{\boldsymbol{F}_{12}}{m_1}-\frac{\boldsymbol{F}_{21}}{m_2}\right)\\
\nonumber
&=&\left(\frac{1}{m_1}+\frac{1}{m_2}\right)\boldsymbol{F}_{12}.
\end{eqnarray}
$$

The first expression simply states that the center of mass coordinate
$\boldsymbol{R}_{\rm cm}$ moves at a fixed velocity. The second expression
can be rewritten in terms of the reduced mass $\mu$.

$$
\begin{eqnarray}
\mu \ddot{\boldsymbol{r}}&=&\boldsymbol{F}_{12},\\
\frac{1}{\mu}&=&\frac{1}{m_1}+\frac{1}{m_2},~~~~\mu=\frac{m_1m_2}{m_1+m_2}.
\end{eqnarray}
$$

Thus, one can treat the trajectory as a one-body problem where the
reduced mass is $\mu$, and a second trivial problem for the center of
mass. The reduced mass is especially convenient when one is
considering gravitational problems because then

$$
\begin{eqnarray}
\mu \ddot{r}&=&-\frac{Gm_1m_2}{r^2}\hat{r}\\
\nonumber
&=&-\frac{GM\mu}{r^2}\hat{r},~~~M\equiv m_1+m_2.
\end{eqnarray}
$$

For the gravitational problem, the reduced mass then falls out and the
trajectory depends only on the total mass $M$.

The kinetic energy and momenta also have analogues in center-of-mass
coordinates. The total and relative momenta are

$$
\begin{eqnarray}
\boldsymbol{P}&\equiv&\boldsymbol{p}_1+\boldsymbol{p}_2=M\dot{\boldsymbol{R}}_{\rm cm},\\
\nonumber
\boldsymbol{q}&\equiv&\mu\dot{\boldsymbol{r}}.
\end{eqnarray}
$$

With these definitions, a little algebra shows that the kinetic energy becomes

$$
\begin{eqnarray}
K&=&\frac{1}{2}m_1|\boldsymbol{v}_1|^2+\frac{1}{2}m_2|\boldsymbol{v}_2|^2\\
\nonumber
&=&\frac{1}{2}M|\dot{\boldsymbol{R}}_{\rm cm}|^2
+\frac{1}{2}\mu|\dot{\boldsymbol{r}}|^2\\
\nonumber
&=&\frac{P^2}{2M}+\frac{q^2}{2\mu}.
\end{eqnarray}
$$

The standard strategy is to transform into the center of mass frame,
then treat the problem as one of a single particle of mass $\mu$
undergoing a force $\boldsymbol{F}_{12}$. Scattering angles can also be
expressed in this frame, then transformed into the lab frame. In
practice, one sees examples in the literature where $d\sigma/d\Omega$
expressed in both the "center-of-mass" and in the "laboratory"
frame.

## Deriving Elliptical Orbits

Kepler's laws state that a gravitational orbit should be an ellipse
with the source of the gravitational field at one focus. Deriving this
is surprisingly messy. To do this, we first use angular momentum
conservation to transform the equations of motion so that it is in
terms of $r$ and $\theta$ instead of $r$ and $t$. The overall strategy
is to

1. Find equations of motion for $r$ and $t$ with no angle ($\theta$) mentioned, i.e. $d^2r/dt^2=\cdots$. Angular momentum conservation will be used, and the equation will involve the angular momentum $L$.

2. Use angular momentum conservation to find an expression for $\dot{\theta}$ in terms of $r$.

3. Use the chain rule to convert the equations of motions for $r$, an expression involving $r,\dot{r}$ and $\ddot{r}$, to one involving $r,dr/d\theta$ and $d^2r/d\theta^2$. This is quitecomplicated because the expressions will also involve a substitution $u=1/r$ so that one finds an expression in terms of $u$ and $\theta$.

4. Once $u(\theta)$ is found, you need to show that this can be converted to the familiar form for an ellipse.

The equations of motion give

$$
\begin{eqnarray}
\label{eq:radialeqofmotion} \tag{2}
\frac{d}{dt}r^2&=&\frac{d}{dt}(x^2+y^2)=2x\dot{x}+2y\dot{y}=2r\dot{r},\\
\nonumber
\dot{r}&=&\frac{x}{r}\dot{x}+\frac{y}{r}\dot{y},\\
\nonumber
\ddot{r}&=&\frac{x}{r}\ddot{x}+\frac{y}{r}\ddot{y}
+\frac{\dot{x}^2+\dot{y}^2}{r}
-\frac{\dot{r}^2}{r}.
\end{eqnarray}
$$

Recognizing that the numerator of the third term is the velocity squared, and that it can be written in polar coordinates,

$$
\begin{equation}
v^2=\dot{x}^2+\dot{y}^2=\dot{r}^2+r^2\dot{\theta}^2,
\label{_auto2} \tag{3}
\end{equation}
$$

one can write $\ddot{r}$ as

$$
\begin{eqnarray}
\label{eq:radialeqofmotion2} \tag{4}
\ddot{r}&=&\frac{F_x\cos\theta+F_y\sin\theta}{m}+\frac{\dot{r}^2+r^2\dot{\theta}^2}{r}-\frac{\dot{r}^2}{r}\\
\nonumber
&=&\frac{F}{m}+\frac{r^2\dot{\theta}^2}{r}\\
\nonumber
m\ddot{r}&=&F+\frac{L^2}{mr^3}.
\end{eqnarray}
$$

This derivation used the fact that the force was radial,
$F=F_r=F_x\cos\theta+F_y\sin\theta$, and that angular momentum is
$L=mrv_{\theta}=mr^2\dot{\theta}$. The term $L^2/mr^3=mv^2/r$ behaves
like an additional force. Sometimes this is referred to as a
centrifugal force, but it is not a force. Instead, it is the
consequence of considering the motion in a rotating (and therefore
accelerating) frame.

Now, we switch to the particular case of an attractive inverse square
force, $F=-\alpha/r^2$, and show that the trajectory, $r(\theta)$, is
an ellipse. To do this we transform derivatives w.r.t. time to
derivatives w.r.t. $\theta$ using the chain rule combined with angular
momentum conservation, $\dot{\theta}=L/mr^2$.

$$
\begin{eqnarray}
\label{eq:rtotheta} \tag{5}
\dot{r}&=&\frac{dr}{d\theta}\dot{\theta}=\frac{dr}{d\theta}\frac{L}{mr^2},\\
\nonumber
\ddot{r}&=&\frac{d^2r}{d\theta^2}\dot{\theta}^2
+\frac{dr}{d\theta}\left(\frac{d}{dr}\frac{L}{mr^2}\right)\dot{r}\\
\nonumber
&=&\frac{d^2r}{d\theta^2}\left(\frac{L}{mr^2}\right)^2
-2\frac{dr}{d\theta}\frac{L}{mr^3}\dot{r}\\
\nonumber
&=&\frac{d^2r}{d\theta^2}\left(\frac{L}{mr^2}\right)^2
-\frac{2}{r}\left(\frac{dr}{d\theta}\right)^2\left(\frac{L}{mr^2}\right)^2
\end{eqnarray}
$$

Equating the two expressions for $\ddot{r}$ in Eq.s ([4](#eq:radialeqofmotion2)) and ([5](#eq:rtotheta)) eliminates all the derivatives w.r.t. time, and provides a differential equation with only derivatives w.r.t. $\theta$,

$$
\begin{equation}
\label{eq:rdotdot} \tag{6}
\frac{d^2r}{d\theta^2}\left(\frac{L}{mr^2}\right)^2
-\frac{2}{r}\left(\frac{dr}{d\theta}\right)^2\left(\frac{L}{mr^2}\right)^2
=\frac{F}{m}+\frac{L^2}{m^2r^3},
\end{equation}
$$

that when solved yields the trajectory, i.e. $r(\theta)$. Up to this
point the expressions work for any radial force, not just forces that
fall as $1/r^2$.

The trick to simplifying this differential equation for the inverse
square problems is to make a substitution, $u\equiv 1/r$, and rewrite
the differential equation for $u(\theta)$.

$$
\begin{eqnarray}
r&=&1/u,\\
\nonumber
\frac{dr}{d\theta}&=&-\frac{1}{u^2}\frac{du}{d\theta},\\
\nonumber
\frac{d^2r}{d\theta^2}&=&\frac{2}{u^3}\left(\frac{du}{d\theta}\right)^2-\frac{1}{u^2}\frac{d^2u}{d\theta^2}.
\end{eqnarray}
$$

Plugging these expressions into Eq. ([6](#eq:rdotdot)) gives an
expression in terms of $u$, $du/d\theta$, and $d^2u/d\theta^2$. After
some tedious algebra,

$$
\begin{equation}
\frac{d^2u}{d\theta^2}=-u-\frac{F m}{L^2u^2}.
\label{_auto3} \tag{7}
\end{equation}
$$

For the attractive inverse square law force, $F=-\alpha u^2$,

$$
\begin{equation}
\frac{d^2u}{d\theta^2}=-u+\frac{m\alpha}{L^2}.
\label{_auto4} \tag{8}
\end{equation}
$$

The solution has two arbitrary constants, $A$ and $\theta_0$,

$$
\begin{eqnarray}
\label{eq:Ctrajectory} \tag{9}
u&=&\frac{m\alpha}{L^2}+A\cos(\theta-\theta_0),\\
\nonumber
r&=&\frac{1}{(m\alpha/L^2)+A\cos(\theta-\theta_0)}.
\end{eqnarray}
$$

The radius will be at a minimum when $\theta=\theta_0$ and at a
maximum when $\theta=\theta_0+\pi$. The constant $A$ is related to the
eccentricity of the orbit. When $A=0$ the radius is a constant
$r=L^2/(m\alpha)$, and the motion is circular. If one solved the
expression $mv^2/r=-\alpha/r^2$ for a circular orbit, using the
substitution $v=L/(mr)$, one would reproduce the expression
$r=L^2/(m\alpha)$.

The form describing the elliptical trajectory in
Eq. ([9](#eq:Ctrajectory)) can be identified as an ellipse with one
focus being the center of the ellipse by considering the definition of
an ellipse as being the points such that the sum of the two distances
between the two foci are a constant. Making that distance $2D$, the
distance between the two foci as $2a$, and putting one focus at the
origin,

$$
\begin{eqnarray}
2D&=&r+\sqrt{(r\cos\theta-2a)^2+r^2\sin^2\theta},\\
\nonumber
4D^2+r^2-4Dr&=&r^2+4a^2-4ar\cos\theta,\\
\nonumber
r&=&\frac{D^2-a^2}{D+a\cos\theta}=\frac{1}{D/(D^2-a^2)-a\cos\theta/(D^2-a^2)}.
\end{eqnarray}
$$

By inspection, this is the same form as Eq. ([9](#eq:Ctrajectory)) with $D/(D^2-a^2)=m\alpha/L^2$ and $a/(D^2-a^2)=A$.

Let us remind ourselves about what an ellipse is before we proceed.

```python
%matplotlib inline

import numpy as np
from matplotlib import pyplot as plt
from math import pi

u=1.     #x-position of the center
v=0.5    #y-position of the center
a=2.     #radius on the x-axis
b=1.5    #radius on the y-axis

t = np.linspace(0, 2*pi, 100)
plt.plot( u+a*np.cos(t) , v+b*np.sin(t) )
plt.grid(color='lightgray',linestyle='--')
plt.show()
```

## Effective or Centrifugal Potential

The total energy of a particle is

$$
\begin{eqnarray}
E&=&V(r)+\frac{1}{2}mv_\theta^2+\frac{1}{2}m\dot{r}^2\\
\nonumber
&=&V(r)+\frac{1}{2}mr^2\dot{\theta}^2+\frac{1}{2}m\dot{r}^2\\
\nonumber
&=&V(r)+\frac{L^2}{2mr^2}+\frac{1}{2}m\dot{r}^2.
\end{eqnarray}
$$

The second term then contributes to the energy like an additional
repulsive potential. The term is sometimes referred to as the
"centrifugal" potential, even though it is actually the kinetic energy
of the angular motion. Combined with $V(r)$, it is sometimes referred
to as the "effective" potential,

$$
\begin{eqnarray}
V_{\rm eff}(r)&=&V(r)+\frac{L^2}{2mr^2}.
\end{eqnarray}
$$

Note that if one treats the effective potential like a real potential, one would expect to be able to generate an effective force,

$$
\begin{eqnarray}
F_{\rm eff}&=&-\frac{d}{dr}V(r) -\frac{d}{dr}\frac{L^2}{2mr^2}\\
\nonumber
&=&F(r)+\frac{L^2}{mr^3}=F(r)+m\frac{v_\perp^2}{r},
\end{eqnarray}
$$

which is indeed matches the form for $m\ddot{r}$ in Eq. ([4](#eq:radialeqofmotion2)), which included the **centrifugal** force.

The following code plots this effective potential for a simple choice of parameters, with a standard gravitational potential $-\alpha/r$. Here we have chosen $L=m=\alpha=1$.

```python
# Common imports
import numpy as np
from math import *
import matplotlib.pyplot as plt

Deltax = 0.01
#set up arrays
xinitial = 0.3
xfinal = 5.0
alpha = 1.0   # spring constant
m = 1.0   # mass, you can change these
AngMom = 1.0  #  The angular momentum
n = ceil((xfinal-xinitial)/Deltax)
x = np.zeros(n)
for i in range(n):
    x[i] = xinitial+i*Deltax
V = np.zeros(n)
V = -alpha/x+0.5*AngMom*AngMom/(m*x*x)
# Plot potential
fig, ax = plt.subplots()
ax.set_xlabel('r[m]')
ax.set_ylabel('V[J]')
ax.plot(x, V)
fig.tight_layout()
plt.show()
```

### Gravitational force example

Using the above parameters, we can now study the evolution of the system using for example the velocity Verlet method.
This is done in the code here for an initial radius equal to the minimum of the potential well.  We seen then that the radius is always the same and corresponds to a circle (the radius is always constant).

```python
# Common imports
import numpy as np
import pandas as pd
from math import *
import matplotlib.pyplot as plt
import os

# Where to save the figures and data files
PROJECT_ROOT_DIR = "Results"
FIGURE_ID = "Results/FigureFiles"
DATA_ID = "DataFiles/"

if not os.path.exists(PROJECT_ROOT_DIR):
    os.mkdir(PROJECT_ROOT_DIR)

if not os.path.exists(FIGURE_ID):
    os.makedirs(FIGURE_ID)

if not os.path.exists(DATA_ID):
    os.makedirs(DATA_ID)

def image_path(fig_id):
    return os.path.join(FIGURE_ID, fig_id)

def data_path(dat_id):
    return os.path.join(DATA_ID, dat_id)

def save_fig(fig_id):
    plt.savefig(image_path(fig_id) + ".png", format='png')

# Simple Gravitational Force   -alpha/r

DeltaT = 0.01
#set up arrays
tfinal = 100.0
n = ceil(tfinal/DeltaT)
# set up arrays for t, v and r
t = np.zeros(n)
v = np.zeros(n)
r = np.zeros(n)
# Constants of the model, setting all variables to one for simplicity
alpha = 1.0
AngMom = 1.0  #  The angular momentum
m = 1.0  # scale mass to one
c1 = AngMom*AngMom/(m*m)
c2 = AngMom*AngMom/m
rmin = (AngMom*AngMom/m/alpha)
# Initial conditions
r0 = rmin
v0 = 0.0
r[0] = r0
v[0] = v0
# Start integrating using the Velocity-Verlet  method
for i in range(n-1):
    # Set up acceleration
    a = -alpha/(r[i]**2)+c1/(r[i]**3)
    # update velocity, time and position using the Velocity-Verlet method
    r[i+1] = r[i] + DeltaT*v[i]+0.5*(DeltaT**2)*a
    anew = -alpha/(r[i+1]**2)+c1/(r[i+1]**3)
    v[i+1] = v[i] + 0.5*DeltaT*(a+anew)
    t[i+1] = t[i] + DeltaT
    # Plot position as function of time
fig, ax = plt.subplots(2,1)
ax[0].set_xlabel('time')
ax[0].set_ylabel('radius')
ax[0].plot(t,r)
ax[1].set_xlabel('time')
ax[1].set_ylabel('Velocity')
ax[1].plot(t,v)
save_fig("RadialGVV")
plt.show()
```

Changing the value of the initial position to a value where the energy is positive, leads to an increasing radius with time, a so-called unbound orbit. Choosing on the other hand an initial radius that corresponds to a negative energy and different from the minimum value leads to a radius that oscillates back and forth between two values.

### Harmonic Oscillator in two dimensions

Consider a particle of mass $m$ in a 2-dimensional harmonic oscillator with potential

$$
V=\frac{1}{2}kr^2=\frac{1}{2}k(x^2+y^2).
$$

If the orbit has angular momentum $L$, we can find the radius and angular velocity of the circular orbit as well as the b) the angular frequency of small radial perturbations.

We consider the effective potential. The radius of a circular orbit is at the minimum of the potential (where the effective force is zero).
The potential is plotted here with the parameters $k=m=0.1$ and $L=1.0$.

```python
# Common imports
import numpy as np
from math import *
import matplotlib.pyplot as plt

Deltax = 0.01
#set up arrays
xinitial = 0.5
xfinal = 3.0
k = 1.0   # spring constant
m = 1.0   # mass, you can change these
AngMom = 1.0  #  The angular momentum
n = ceil((xfinal-xinitial)/Deltax)
x = np.zeros(n)
for i in range(n):
    x[i] = xinitial+i*Deltax
V = np.zeros(n)
V = 0.5*k*x*x+0.5*AngMom*AngMom/(m*x*x)
# Plot potential
fig, ax = plt.subplots()
ax.set_xlabel('r[m]')
ax.set_ylabel('V[J]')
ax.plot(x, V)
fig.tight_layout()
plt.show()
```

$$
\begin{eqnarray*}
V_{\rm eff}&=&\frac{1}{2}kr^2+\frac{L^2}{2mr^2}
\end{eqnarray*}
$$

The effective potential looks like that of a harmonic oscillator for
large $r$, but for small $r$, the centrifugal potential repels the
particle from the origin. The combination of the two potentials has a
minimum for at some radius $r_{\rm min}$.

$$
\begin{eqnarray*}
0&=&kr_{\rm min}-\frac{L^2}{mr_{\rm min}^3},\\
r_{\rm min}&=&\left(\frac{L^2}{mk}\right)^{1/4},\\
\dot{\theta}&=&\frac{L}{mr_{\rm min}^2}=\sqrt{k/m}.
\end{eqnarray*}
$$

For particles at $r_{\rm min}$ with $\dot{r}=0$, the particle does not
accelerate and $r$ stays constant, i.e. a circular orbit. The radius
of the circular orbit can be adjusted by changing the angular momentum
$L$.

For the above parameters this minimum is at $r_{\rm min}=1$.

 Now consider small vibrations about $r_{\rm min}$. The effective spring constant is the curvature of the effective potential.

$$
\begin{eqnarray*}
k_{\rm eff}&=&\left.\frac{d^2}{dr^2}V_{\rm eff}(r)\right|_{r=r_{\rm min}}=k+\frac{3L^2}{mr_{\rm min}^4}\\
&=&4k,\\
\omega&=&\sqrt{k_{\rm eff}/m}=2\sqrt{k/m}=2\dot{\theta}.
\end{eqnarray*}
$$

Because the radius oscillates with twice the angular frequency,
the orbit has two places where $r$ reaches a minimum in one
cycle. This differs from the inverse-square force where there is one
minimum in an orbit. One can show that the orbit for the harmonic
oscillator is also elliptical, but in this case the center of the
potential is at the center of the ellipse, not at one of the foci.

The solution is also simple to write down exactly in Cartesian coordinates. The $x$ and $y$ equations of motion separate,

$$
\begin{eqnarray*}
\ddot{x}&=&-kx,\\
\ddot{y}&=&-ky.
\end{eqnarray*}
$$

The general solution can be expressed as

$$
\begin{eqnarray*}
x&=&A\cos\omega_0 t+B\sin\omega_0 t,\\
y&=&C\cos\omega_0 t+D\sin\omega_0 t.
\end{eqnarray*}
$$

The code here finds the solution for $x$ and $y$ using the code we
developed in homework 5 and 6 and the midterm.  Note that this code is
tailored to run in Cartesian coordinates. There is thus no angular
momentum dependent term.

Here we have chose initial conditions that
correspond to the minimum of the effective potential
$r_{\mathrm{min}}$. We have chosen $x_0=r_{\mathrm{min}}$ and
$y_0=0$. Similarly, we use the centripetal acceleration to determine
the initial velocity so that we have a circular motion (see back to the
last question of the midterm). This means that we set the centripetal
acceleration $v^2/r$ equal to the force from the harmonic oscillator $-k\boldsymbol{r}$. Taking the
magnitude of $\boldsymbol{r}$ we have then
$v^2/r=k/mr$, which gives $v=\pm\omega_0r$.

Since the code here solves the equations of motion in cartesian
coordinates and the harmonic oscillator potential leads to forces in
the $x$- and $y$-directions that are decoupled, we have to select the initial velocities and positions so that we don't get that for example $y(t)=0$.

We set $x_0$ to be different from zero and $v_{y0}$ to be different from zero.

```python

DeltaT = 0.001
#set up arrays
tfinal = 5.0
n = ceil(tfinal/DeltaT)
# set up arrays
t = np.zeros(n)
v = np.zeros((n,2))
r = np.zeros((n,2))
radius = np.zeros(n)
# Constants of the model
k = 1.0   # spring constant
m = 1.0   # mass, you can change these
omega02 = k/m  # Frequency
AngMom = 1.0  #  The angular momentum
# Potential minimum
rmin = (AngMom*AngMom/k/m)**0.25
# Initial conditions as compact 2-dimensional arrays, x0=rmin and y0 = 0
x0 = rmin; y0= 0.0
r0 = np.array([x0,y0])
vy0 = sqrt(omega02)*rmin; vx0 = 0.0
v0 = np.array([vx0,vy0])
r[0] = r0
v[0] = v0
# Start integrating using the Velocity-Verlet  method
for i in range(n-1):
    # Set up the acceleration
    a =  -r[i]*omega02
    # update velocity, time and position using the Velocity-Verlet method
    r[i+1] = r[i] + DeltaT*v[i]+0.5*(DeltaT**2)*a
    anew = -r[i+1]*omega02
    v[i+1] = v[i] + 0.5*DeltaT*(a+anew)
    t[i+1] = t[i] + DeltaT
# Plot position as function of time
radius = np.sqrt(r[:,0]**2+r[:,1]**2)
fig, ax = plt.subplots(3,1)
ax[0].set_xlabel('time')
ax[0].set_ylabel('radius squared')
ax[0].plot(t,r[:,0]**2+r[:,1]**2)
ax[1].set_xlabel('time')
ax[1].set_ylabel('x position')
ax[1].plot(t,r[:,0])
ax[2].set_xlabel('time')
ax[2].set_ylabel('y position')
ax[2].plot(t,r[:,1])

fig.tight_layout()
save_fig("2DimHOVV")
plt.show()
```

We see that the radius (to within a given error), we obtain a constant radius.

The following code shows first how we can solve this problem using the radial degrees of freedom only.
Here we need to add the explicit centrifugal barrier.  Note that the variable $r$ depends only on time. There is no $x$ and $y$ directions
since we have transformed the equations to polar coordinates.

```python
DeltaT = 0.01
#set up arrays
tfinal = 10.0
n = ceil(tfinal/DeltaT)
# set up arrays for t, v and r
t = np.zeros(n)
v = np.zeros(n)
r = np.zeros(n)
E = np.zeros(n)
# Constants of the model
AngMom = 1.0  #  The angular momentum
m = 1.0
k = 1.0
omega02 = k/m
c1 = AngMom*AngMom/(m*m)
c2 = AngMom*AngMom/m
rmin = (AngMom*AngMom/k/m)**0.25
# Initial conditions
r0 = rmin
v0 = 0.0
r[0] = r0
v[0] = v0
E[0] = 0.5*m*v0*v0+0.5*k*r0*r0+0.5*c2/(r0*r0)
# Start integrating using the Velocity-Verlet  method
for i in range(n-1):
    # Set up acceleration
    a = -r[i]*omega02+c1/(r[i]**3)
    # update velocity, time and position using the Velocity-Verlet method
    r[i+1] = r[i] + DeltaT*v[i]+0.5*(DeltaT**2)*a
    anew = -r[i+1]*omega02+c1/(r[i+1]**3)
    v[i+1] = v[i] + 0.5*DeltaT*(a+anew)
    t[i+1] = t[i] + DeltaT
    E[i+1] = 0.5*m*v[i+1]*v[i+1]+0.5*k*r[i+1]*r[i+1]+0.5*c2/(r[i+1]*r[i+1])
    # Plot position as function of time
fig, ax = plt.subplots(2,1)
ax[0].set_xlabel('time')
ax[0].set_ylabel('radius')
ax[0].plot(t,r)
ax[1].set_xlabel('time')
ax[1].set_ylabel('Energy')
ax[1].plot(t,E)
save_fig("RadialHOVV")
plt.show()
```

With some work using double angle formulas, one can calculate

$$
\begin{eqnarray*}
r^2&=&x^2+y^2\\
\nonumber
&=&(A^2+C^2)\cos^2(\omega_0t)+(B^2+D^2)\sin^2\omega_0t+(AB+CD)\cos(\omega_0t)\sin(\omega_0t)\\
\nonumber
&=&\alpha+\beta\cos 2\omega_0 t+\gamma\sin 2\omega_0 t,\\
\alpha&=&\frac{A^2+B^2+C^2+D^2}{2},~~\beta=\frac{A^2-B^2+C^2-D^2}{2},~~\gamma=AB+CD,\\
r^2&=&\alpha+(\beta^2+\gamma^2)^{1/2}\cos(2\omega_0 t-\delta),~~~\delta=\arctan(\gamma/\beta),
\end{eqnarray*}
$$

and see that radius oscillates with frequency $2\omega_0$. The
factor of two comes because the oscillation $x=A\cos\omega_0t$ has two
maxima for $x^2$, one at $t=0$ and one a half period later.

## Stability of Orbits

The effective force can be extracted from the effective potential, $V_{\rm eff}$. Beginning from the equations of motion, Eq. ([2](#eq:radialeqofmotion)), for $r$,

$$
\begin{eqnarray}
m\ddot{r}&=&F+\frac{L^2}{mr^3}\\
\nonumber
&=&F_{\rm eff}\\
\nonumber
&=&-\partial_rV_{\rm eff},\\
\nonumber
F_{\rm eff}&=&-\partial_r\left[V(r)+(L^2/2mr^2)\right].
\end{eqnarray}
$$

For a circular orbit, the radius must be fixed as a function of time,
so one must be at a maximum or a minimum of the effective
potential. However, if one is at a maximum of the effective potential
the radius will be unstable. For the attractive Coulomb force the
effective potential will be dominated by the $-\alpha/r$ term for
large $r$ because the centrifugal part falls off more quickly, $\sim
1/r^2$. At low $r$ the centrifugal piece wins and the effective
potential is repulsive. Thus, the potential must have a minimum
somewhere with negative potential. The circular orbits are then stable
to perturbation.

The effective potential is sketched for two cases, a $1/r$ attractive
potential and a $1/r^3$ attractive potential. The $1/r$ case has a
stable minimum, whereas the circular orbit in the $1/r^3$ case is
unstable.

If one considers a potential that falls as $1/r^3$, the situation is
reversed and the point where $\partial_rV$ disappears will be a local
maximum rather than a local minimum. **Fig to come here with code**

The repulsive centrifugal piece dominates at large $r$ and the attractive
Coulomb piece wins out at small $r$. The circular orbit is then at a
maximum of the effective potential and the orbits are unstable. It is
the clear that for potentials that fall as $r^n$, that one must have
$n>-2$ for the orbits to be stable.

Consider a potential $V(r)=\beta r$. For a particle of mass $m$ with
angular momentum $L$, find the angular frequency of a circular
orbit. Then find the angular frequency for small radial perturbations.

For the circular orbit you search for the position $r_{\rm min}$ where the effective potential is minimized,

$$
\begin{eqnarray*}
\partial_r\left\{\beta r+\frac{L^2}{2mr^2}\right\}&=&0,\\
\beta&=&\frac{L^2}{mr_{\rm min}^3},\\
r_{\rm min}&=&\left(\frac{L^2}{\beta m}\right)^{1/3},\\
\dot{\theta}&=&\frac{L}{mr_{\rm min}^2}=\frac{\beta^{2/3}}{(mL)^{1/3}}
\end{eqnarray*}
$$

Now, we can find the angular frequency of small perturbations about the circular orbit. To do this we find the effective spring constant for the effective potential,

$$
\begin{eqnarray*}
k_{\rm eff}&=&\partial_r^2 \left.V_{\rm eff}\right|_{r_{\rm min}}\\
&=&\frac{3L^2}{mr_{\rm min}^4},\\
\omega&=&\sqrt{\frac{k_{\rm eff}}{m}}\\
&=&\frac{\beta^{2/3}}{(mL)^{1/3}}\sqrt{3}.
\end{eqnarray*}
$$

If the two frequencies, $\dot{\theta}$ and $\omega$, differ by an
integer factor, the orbit's trajectory will repeat itself each time
around. This is the case for the inverse-square force,
$\omega=\dot{\theta}$, and for the harmonic oscillator,
$\omega=2\dot{\theta}$. In this case, $\omega=\sqrt{3}\dot{\theta}$,
and the angles at which the maxima and minima occur change with each
orbit.

### Code example with gravitional force

The code example here is meant to illustrate how we can make a plot of the final orbit. We solve the equations in polar coordinates (the example here uses the minimum of the potential as initial value) and then we transform back to cartesian coordinates and plot $x$ versus $y$. We see that we get a perfect circle when we place ourselves at the minimum of the potential energy, as expected.

```python

# Simple Gravitational Force   -alpha/r

DeltaT = 0.01
#set up arrays
tfinal = 8.0
n = ceil(tfinal/DeltaT)
# set up arrays for t, v and r
t = np.zeros(n)
v = np.zeros(n)
r = np.zeros(n)
phi = np.zeros(n)
x = np.zeros(n)
y = np.zeros(n)
# Constants of the model, setting all variables to one for simplicity
alpha = 1.0
AngMom = 1.0  #  The angular momentum
m = 1.0  # scale mass to one
c1 = AngMom*AngMom/(m*m)
c2 = AngMom*AngMom/m
rmin = (AngMom*AngMom/m/alpha)
# Initial conditions, place yourself at the potential min
r0 = rmin
v0 = 0.0  # starts at rest
r[0] = r0
v[0] = v0
phi[0] = 0.0
# Start integrating using the Velocity-Verlet  method
for i in range(n-1):
    # Set up acceleration
    a = -alpha/(r[i]**2)+c1/(r[i]**3)
    # update velocity, time and position using the Velocity-Verlet method
    r[i+1] = r[i] + DeltaT*v[i]+0.5*(DeltaT**2)*a
    anew = -alpha/(r[i+1]**2)+c1/(r[i+1]**3)
    v[i+1] = v[i] + 0.5*DeltaT*(a+anew)
    t[i+1] = t[i] + DeltaT
    phi[i+1] = t[i+1]*c2/(r0**2)
# Find cartesian coordinates for easy plot
x = r*np.cos(phi)
y = r*np.sin(phi)
fig, ax = plt.subplots(3,1)
ax[0].set_xlabel('time')
ax[0].set_ylabel('radius')
ax[0].plot(t,r)
ax[1].set_xlabel('time')
ax[1].set_ylabel('Angle $\cos{\phi}$')
ax[1].plot(t,np.cos(phi))
ax[2].set_ylabel('y')
ax[2].set_xlabel('x')
ax[2].plot(x,y)

save_fig("Phasespace")
plt.show()
```

Try to change the initial value for $r$ and see what kind of orbits you get.
In order to test different energies, it can be useful to look at the plot of the effective potential discussed above.

However, for orbits different from a circle the above code would need modifications in order to allow us to display say an ellipse. For the latter, it is much easier to run our code in cartesian coordinates, as done here. In this code we test also energy conservation and see that it is conserved to numerical precision. The code here is a simple extension of the code we developed for homework 4.

```python
# Common imports
import numpy as np
import pandas as pd
from math import *
import matplotlib.pyplot as plt

DeltaT = 0.01
#set up arrays
tfinal = 10.0
n = ceil(tfinal/DeltaT)
# set up arrays
t = np.zeros(n)
v = np.zeros((n,2))
r = np.zeros((n,2))
E = np.zeros(n)
# Constants of the model
m = 1.0   # mass, you can change these
alpha = 1.0
# Initial conditions as compact 2-dimensional arrays
x0 = 0.5; y0= 0.
r0 = np.array([x0,y0])
v0 = np.array([0.0,1.0])
r[0] = r0
v[0] = v0
rabs = sqrt(sum(r[0]*r[0]))
E[0] = 0.5*m*(v[0,0]**2+v[0,1]**2)-alpha/rabs
# Start integrating using the Velocity-Verlet  method
for i in range(n-1):
    # Set up the acceleration
    rabs = sqrt(sum(r[i]*r[i]))
    a =  -alpha*r[i]/(rabs**3)
    # update velocity, time and position using the Velocity-Verlet method
    r[i+1] = r[i] + DeltaT*v[i]+0.5*(DeltaT**2)*a
    rabs = sqrt(sum(r[i+1]*r[i+1]))
    anew = -alpha*r[i+1]/(rabs**3)
    v[i+1] = v[i] + 0.5*DeltaT*(a+anew)
    E[i+1] = 0.5*m*(v[i+1,0]**2+v[i+1,1]**2)-alpha/rabs
    t[i+1] = t[i] + DeltaT
# Plot position as function of time
fig, ax = plt.subplots(3,1)
ax[0].set_ylabel('y')
ax[0].set_xlabel('x')
ax[0].plot(r[:,0],r[:,1])
ax[1].set_xlabel('time')
ax[1].set_ylabel('y position')
ax[1].plot(t,r[:,0])
ax[2].set_xlabel('time')
ax[2].set_ylabel('y position')
ax[2].plot(t,r[:,1])

fig.tight_layout()
save_fig("2DimGravity")
plt.show()
print(E)
```


```python
from IPython.core.display import HTML, Image
css_file = 'style.css'
HTML(open(css_file, 'r').read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Philosopher:400,700,400italic,700italic' rel='stylesheet' type='text/css'>

```python
from sympy import init_printing, Matrix, symbols
init_printing()
```

# The four fundamental subspaces
# Introducing the matrix space

## The four fundamental subspaces

In this section, we bring together the four fundamentals spaces in linear algebra:

* The column space, $C\left( A \right)$
* The nullspace, $N \left( A \right)$
* The rowspace:
    * All linear combinations of rows
    * All linear combinations of $A^T$ and $C \left( A^T \right)$
* The nullspace of $A^T$, $N \left( A^T \right)$ (also termed the left nullspace of A)

For a matrix (with dimensions noted) $A_{m \times n}$ we that $C \left( A \right)$ is in  $\mathbb{R}^m$, $N \left( A \right)$ is in $\mathbb{R}^n$, $C \left( A^T \right)$ is in $\mathbb{R}^n$, and $N \left( A^T \right)$ is in $\mathbb{R}^m$.

## Calculating basis and dimension

We also need to know the bases and dimensions of these fundamental spaces:

* $C \left( A \right)$
    * The bases are the picot columns
    * The dimension is the rank, $r$
* $N \left( A \right)$
    * The bases are the special solutions (one for every free variable, $n-r$)
    * The dimensions are $n-r$
* $ C \left( A ^T \right)$
    * If $A$ is reduced to row-echelon form, $R$, then $ C\left( R \right) \ne C \left( A \right)$
    * We do have, though, that the rowspace of $R$ is equal to the rowspace of $R$, in other words $ C \left( R^T \right) = C \left( A^T \right)$
* $ N \left( A^T \right) $
    * The bases are such that:
        * $A^T \underline{y} = \underline{0}$
        * $ \underline{y}^T {\left( A^T \right)}^T = \underline{0}^T $
        * $ \underline{y}^T A = \underline{0}^T$
        * These are the pivot columns of $R^T$
    * The dimensions are $m-r$

## Example problems

### Example 1

Consider this example matrix and calculate the bases and dimension for all four fundamental spaces

```python
A = Matrix([[1, 2, 3, 1], [1, 1, 2, 1], [1, 2, 3, 1]]) # We note that rows 1 and three are identical and that
# columns 3 is the addtion of columns 1 and 2 and column 1 equals column 4
A
```

#### Columnspace

```python
A.rref() # Remember that the columnspace contains the pivot columns as a basis
```

* The bases are the pivot columns:
$ \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 0 & 0 \end{bmatrix} $
* This is indeed $\mathbb{R}^3$ (each column vector in the matrix has three components) as $A_{m \times n} = A_{3 \times 4}$

* The rank (no of columns with pivots) is $2$, thus $\text{dim} \left( A \right) = 2$

#### Nullspace

```python
A.nullspace() # Calculating the nullspace vectors
```

* The basis is in $\mathbb{R}^4$ ($A$ has $n = 4$ columns)

```python
A.rref() # No pivots for columns 3 and 4
```

$$\begin{pmatrix}\left[\begin{matrix}1 & 0 & 1 & 1\\0 & 1 & 1 & 0\\0 & 0 & 0 & 0\end{matrix}\right], & \begin{bmatrix}0, & 1\end{bmatrix}\end{pmatrix}$$

* The dimension is $2$ (there are $2$ column vectors, which is indeed $n-r=4-2=2$

#### Rowspace C(AT)

* Here, we are looking for the pivot columns of $A^T$

```python
A_t = A.transpose()
A_t.rref()
```

* The pivot rows of $A$ (columns in $A^T$) are rows $1$ and $2$ (columns $1$ and $2$ of $A^T$)

* As stated above, it is in $\mathbb{R}^4$

* The dimension are $n-r=4-2=2$

#### Nullspace of AT

```python
A_t.nullspace()
```

* Which is in $\mathbb{R}^3$

* The dimension is $1$, since $m-r=3-2=1$ (remember that the rank is the number of pivot columns)

### Example 2

Consider this example matrix (in LU form) and calculate the bases and dimension for all four fundamental spaces

```python
L = Matrix([[1, 0, 0], [2, 1, 0], [-1, 0, 1]])
U = Matrix([[5, 0, 3], [0, 1, 1], [0, 0, 0]])
A = L * U
L, U, A
```

#### Columnspace of A

```python
A.rref()
```

* The basis is thus:
$ \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 0 & 0 \end{bmatrix} $
* Another basis would be the pivot columns of L:
$ \begin{bmatrix} 1 & 0 \\ 2 & 1 \\ -1 & 0 \end{bmatrix} $
* It is in $\mathbb{R}^3$, since $m=3$
* It has a rank of $2$ (two pivot columns)
* Since the dimension of the columnspace is equal to the rank, $\text{dim} \left( A \right) = 2$
    * Note that it is also equal to the number of pivot columns in U

#### Nullspace of A

```python
A.nullspace()
```

$$\begin{bmatrix}\left[\begin{matrix}- \frac{3}{5}\\-1\\1\end{matrix}\right]\end{bmatrix}$$

* The nullspace is in $\mathbb{R}^3$, since $n=3$
* The basis is the special solution(s), which is one column vector for every free variable
    * Since we only have a single free variable, we have a single nullspace column vector
    * This fits in with the fact that it needs to be $n-r$
    * It can also be calculated by taking $U$, setting the free variable to $1$ and solving for the other rows by setting each equal to $0$
* The dimension of the nullspace is also $1$ ($n-r$, i.e. a single column)
    * It is also the number of free variables

#### The rowspace

* This is the columnspace of $A^T$
* Don't take the transpose first!
* Row reduce, identify the rows with pivots and transpose them

```python
A.rref()
```

* The basis can also be written down by identifying the rows with pivots in $U$ and writing them down as columns (getting their transpose)
$$ \begin{bmatrix} 5 & 0 \\ 0 & 1 \\ 3 & 1 \end{bmatrix} $$
* It is in $\mathbb{R}^3$, since $n=3$
* The rank is $2$, which is equal to the dimension, i.e. $\text{dim} \left( A^T \right) = 2$

#### The nullspace of AT

```python
A.transpose().nullspace()
```

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

* It is indeed in $\mathbb{R}^3$, since $m=3$
* A good way to do it is to take the inverse of $L$, such that $L^{-1}A=U$
    * Now the free variable row in $U$ is row three
    * Take the corresponding row in $L^{-1}$ and transpose it
* The dimension in $m-2=3-2=1$

## The matrix space

Note square matrix is also a _vector_ space, because they obey the vector space rules of addition and scalar multiplication.  Subspaces (of same) would include:
* Upper triangular matrices
* Symmetric matrices

```python

```

Mathematics/Linear Algebra/0.0 MIT-18.06 - Jupyter/Lecture_10_Four_Fundamental_Subspaces.ipynb