```python
import sys
print("Using Python {}.{}.".format(sys.version_info.major, sys.version_info.minor))

```

    Using Python 3.9.

## Importing packages

```python
from sympy import *
from scipy.optimize import toms748
from scipy.integrate import solve_ivp
from scipy.integrate import quad
import numpy as np
init_printing()
```

# Function definitions

```python
# Definition of coordinates
x1, x2, xi1, xi2 = symbols('x_1 x_2 xi_1 xi_2')
# Definition of directional variables
n1, n2 = symbols('n_1 n_2')

# Definition of sliding direction (Out of plane)
n = Matrix([[-1], [0], [0]])
```

```python
# Fundamental solution to laplacian equal to zero and the directional derivative
E = -1/(2*pi) * log(sqrt((x1-xi1)**2 + (x2-xi2)**2))
def ddn(expr):
    NewExpr = (n1/sqrt(n1**2+n2**2))*diff(expr,x1) + (n2/sqrt(n1**2+n2**2))*diff(expr,x2)
    return NewExpr
E
```

```python
# Friction law function symbols and function definitio
mu, muo, a, Sr, Vo, b, L, psi= symbols('mu mu_o a S_r V_o b L psi')

def frictionLaw():
    return muo + a*ln(Sr/Vo) + b* ln(Vo*psi/L)

# State variable relations
def AgingLaw():
    return 1-Sr*psi/L

def RuinaLaw():
    return 1-Sr*psi/L

mu = frictionLaw()
mu
```

```python
# BEM symbols
S, Vp, t, tau, Theta, sigmaN, u, eta= symbols('S V_p t tau Theta sigma_n u eta' )
ThetaA = symbols('Theta_a')

```

# Implementation Aspects

$\int_{\Gamma_F}E\frac{\partial u}{\partial n}dS +
\int_{\Gamma_D}E\frac{\partial u}{\partial n}dS +
{\int_{\Gamma_N}E\frac{\partial u}{\partial n}dS} =
\frac{1}{2}u(x)+
\int_{\Gamma_F}u\frac{\partial E}{\partial n_{\xi}}dS +
\int_{\Gamma_D}u\frac{\partial E}{\partial n_{\xi}}dS +
\int_{\Gamma_N}u\frac{\partial E}{\partial n_{\xi}}dS$

Taking out the terms that become zero, we obtain

$\int_{\Gamma_F}E\frac{\partial u}{\partial n}dS +
\int_{\Gamma_D}E\frac{\partial u}{\partial n}dS
=
\frac{1}{2}u(x) +
\int_{\Gamma_F}u\frac{\partial E}{\partial n_{\xi}}dS$

```python
P1, P2, P3 = symbols('\int_{\Gamma_F}EdS \int_{\Gamma_D}EdS \int_{\Gamma_N}EdS')
Wf, Wa = symbols('W_f W_a')
P1, P2, P3, Wf, Wa
```

```python
Wa = 10
x2 = 12
```

```python
# On-fault slip
def integrandP1(Chi, wa, xo):
    return (ln(abs(xo - wa*Chi)))/Chi**2

def ImproperInt(wa, xo):
    return -abs(wa)/(2*pi)*quad(integrandP1, 1, np.inf, args=(wa, xo))[0]

# On plate boundary
def integrandP2(Chi, xo, wf, h):
    return ln(abs((xo - wf)-h*(Chi+1)/2))

def IntGaussianQuad(xo, wf, h):
    if(xo != h/2 + wf):
        return -wf/(4.0*pi)*quad(integrandP2, -1, 1, args=(xo, wf, h))[0]

```

# Putting it all together

```python
# Definition of mapping matrix from quantities to BEM
M = Matrix([[1, 0], [0, 0], [0, 0], [0, 1]])

M, M.T
```

```python
# Putting it all together
S = 2 * u + Vp * t
tau = M.T*Theta
#Fric = tau + mu * sigmaN + eta* V
tau
```

# Initialization step

```python
u = ln(abs(x2))
u
```

```python
Theta = ThetaA*abs(a)**2/x2**2
Theta
```

```python

```


# Multi-State Model first example

## In this notebook

This notebook provides a simple setting which illustrates basic usage of the model.

## Typical settings

In a typical setting of modelling patient illness trajectories, there are multiple sources of complexity:

1. There could be many states (mild, severe, recovered, released from hospital, death etc.)
2. The probability of each transition and the duration of the stay in each state depend on patient covariates.
3. Patient covariates can change over time, possibly in a manner which depends on the states visited.

In order to introduce the multi-state-model we shall use a much simpler setting where our data arrives from a simple 3 state model and covariates do not change over time or affect the probabilities of transitions between states.

## A Simple Multi-State Setting

Patients start at state 1, state 3 shall be a terminal state and states 1,2 shall be identical in the sense that from both:

1. With probability 1/2 you transition to state 3 within 1 day.
2. With probability 1/2 you transition to state 2 or 1 (depending on the present state), within $t∼exp(λ)$

```python
from pymsm.plotting import state_diagram

state_diagram(
    """
    s1 : 1
    s2: 2
    s3: 3
    s1 --> s2: P=0.5, t~exp(lambda)
    s1 --> s3: P=0.5, t=1
    s2 --> s3: P=0.5, t=1
"""
)
```

A simple Multi-State Model

For this setting, one can show that the expected time until reaching a terminal state is $1+\frac{1}{λ}$ (see proof at the end of this notebook.)

## The Dataset Structure

Let’s load the dataset, which was constructed based on the graph above

```python
from pymsm.examples.first_example_utils import create_toy_setting_dataset
dataset = create_toy_setting_dataset(lambda_param=2)
print('dataset type: {}'.format(type(dataset)))
print('elemnets type: {}'.format(type(dataset[0])))
```

    dataset type: <class 'list'>
    elemnets type: <class 'pymsm.multi_state_competing_risks_model.PathObject'>

The dataset is a list of elements from class PathObject. Each PathObject in the list corresponds to a single sample’s (i.e “patient’s”) observed path. Let’s look at one such object in detail:

```python
first_path = dataset[0]
print(type(first_path))

print('\n------covariates------')
print(first_path.covariates)

print('\n-------states---------')
print(first_path.states)

print('\n--time at each state--')
print(first_path.time_at_each_state)

print('\n------sample id-------')
print(first_path.sample_id)
```

    <class 'pymsm.multi_state_competing_risks_model.PathObject'>

    ------covariates------
    a   -0.669272
    b    0.884765
    dtype: float64

    -------states---------
    [1, 2, 3]

    --time at each state--
    [0.4078647886081198, 1]

    ------sample id-------
    0

We see the following attributes:

1. *covariates* : These are the sample’s covariates. In this case they were randomally generated and do not affect the state transitions, but for a patient this could be a numerical vector with entries such as:
    * “age in years”
    * “is male”
    * “number of days that have passed since hospitalization”
    * etc..
2. *states* : These are the observed states the sample visited, encoded as positive integers. Here we can see the back and forth between states 1 and 2, ending with the only terminal state (state 3).
3. *time_at_each_state* : These are the observed times spent at each state.
4. *sample_id* : (optional) a unique identifier of the patient.

Note: if the last state is a terminal state, then the vector of times should be shorter than the vector of states by 1. Conversely, if the last state is not a terminal state, then the length of the vector of times should be the same as that of the states. In such a case, the sample is inferred to be right censored.

## Updating Covariates Over Time

In order to update the patient covariates over time, we need to define a state-transition function.
In this simple case, the covariates do not change and the function is trivial

```python
def default_update_covariates_function(covariates_entering_origin_state, origin_state=None, target_state=None,
                                       time_at_origin=None, abs_time_entry_to_target_state=None):
    return covariates_entering_origin_state
```

You can define any function, as long as it recieves the following parameter types (in this order):
1. pandas Series (sample covariates when entering the origin state)
2. int (origin state number)
3. int (target state number)
4. float (time spent at origin state)
5. float (absolute time of entry to target state)

If some of the parameters are not used in the function, use a default value of None, as in the example above.

## Defining terminal states

```python
terminal_states = [3]
```

## Fitting the model

Import and init the Model

```python
from pymsm.multi_state_competing_risks_model import MultiStateModel
multi_state_model = MultiStateModel(dataset, terminal_states, default_update_covariates_function,
                                    ['covariate_1', 'covariate_2'])
```

Fit the Model

```python
multi_state_model.fit()
```

    Fitting Model at State: 1
    >>> Fitting Transition to State: 2, n events: 702
    >>> Fitting Transition to State: 3, n events: 674
    Fitting Model at State: 2
    >>> Fitting Transition to State: 3, n events: 326
    >>> Fitting Transition to State: 1, n events: 376

## Making predictions

Predictions are done via monte carlo simulation. Initial patient covariates, along with the patient’s current state are supplied. The next states are sequentially sampled via the model parameters. The process concludes when the patient arrives at a terminal state or the number of transitions exceeds the specified maximum.

```python
import numpy as np

all_mcs = multi_state_model.run_monte_carlo_simulation(
              # the current covariates of the patient.
              # especially important to use updated covariates in case of
              # time varying covariates along with a prediction from a point in time
              # during hospitalization
              sample_covariates = np.array([0.2,-0.3]),

              # in this setting samples start at state 1, but
              # in general this can be any non-terminal state which
              # then serves as the simulation starting point
              origin_state = 1,

              # in this setting we start predictions from time 0, but
              # predictions can be made from any point in time during the
              # patient's trajectory
              current_time = 0,

              # If there is an observed upper limit on the number of transitions, we recommend
              # setting this value to that limit in order to prevent generation of outlier paths
              max_transitions = 100,

              # the number of paths to simulate:
              n_random_samples = 1000)
```

    100%|█████████████████████████████████████████████████████████████████████████████| 1000/1000 [00:04<00:00, 232.83it/s]

## The Simulation Results Format:

Each run is described by a list of states and times spent at each state (same format as the dataset the model is fit to).

```python
mc = all_mcs[0]
print(mc.states)
print(mc.time_at_each_state)

mc = all_mcs[1]
print(mc.states)
print(mc.time_at_each_state)
```

    [1, 2, 1, 3]
    [3.2023886466379428, 0.9495424935730798, 0.15962949075341282]
    [1, 2, 1, 3]
    [1.8968554459242266, 1.6332968427722088, 2.7134405014544503]

## Analyzing The Results

Recall we could compute the expected time for this simple setting? We will now see that the model provides an accurate estimate of this expected value of $1+\frac{1}{\lambda}$

```python
from pymsm.examples.first_example_utils import plot_total_time_until_terminal_state

plot_total_time_until_terminal_state(all_mcs, true_lambda=2)
```

## Conclusions

This notebook provides a simple example usage of the multi-state model, beginning with the structure of the dataset used to fit the model and up to a simple analysis of the model’s predictions.

By following this process you can fit the model to any such dataset and make predictions

## Appendix 1 - Demonstrating that the expected time until reaching the terminal state is $1+\frac{1}{λ}$

Let T be the random variable denoting the time until reaching the terminal state #3, and let $S2$ be the random variable denoting the second state visited by the sample (recall all patients start at state 1, that is: $S1=1$).
From the law of total expectation:

\begin{equation}
\mathbf{E}[T] = \mathbf{E}[\mathbf{E}[T|S_2]] = \mathbf{P}(S_2 = 3)\cdot\mathbf{E}[T|S_2 = 3] + \mathbf{P}(S_2 = 2)\cdot\mathbf{E}[T|S_2 = 2]
\end{equation}

Denote $T=T_1+T_{2^+}$ (“The total time is the sum of the time of the first transition plus the time from arrival to the second state onwards”). Then:

\begin{equation}
=\frac{1}{2}\cdot1 + \frac{1}{2}\cdot\mathbf{E}[T_1 + T_{2^+}|S_2 = 2] = \frac{1}{2}+\frac{1}{2}\cdot(\mathbf{E}[T_1|S_2 = 2] + \mathbf{E}[T_{2^+}]|S_2 = 2) \\= \frac{1}{2}\cdot1 + \frac{1}{2}\cdot(\frac{1}{λ}+\mathbf{E}[T])
\end{equation}

We then have:

\begin{equation}
2\cdot\mathbf{E}[T] = 1 + (\frac{1}{λ} + \mathbf{E}[T])
\end{equation}

and:

\begin{equation}
{E}[T] = 1 + \frac{1}{λ}
\end{equation}


# Exercise session nº 3
---
# Geometry, epistasis, and developmental patterning

__*Sacha Ichbiah, 07/02/22, ENS Paris*__

This subject is extracted from :
> Françis Corson, Eric Siggia, *Geometry, epistasis, and developmental patterning*, PNAS, 2012.
> https://doi.org/10.1073/pnas.1201505109

And the implementation is inspired from :
> Lisa Bedin https://colab.research.google.com/drive/1X7hAimJ12D7iA98kBdbmEGOnWw5VAYHP?usp=sharing

 During development, cells or tissues become competent when they can sense signals that direct them toward a particular fate; they are specified or committed to a fate when the signal can be removed with no change in outcome. Cells are determined when they cannot be directed to alternative fates by other signals. Differentiation ensues when specialized gene batteries are induced along with characteristic morphology. None of these concepts can be tested in a completely controlled way in vivo, and the recent creation of induced pluripotent stem cells shows that even differentiation can be reversed.

These concepts admit a natural __geometric representation__, which can be formalized in the language of dynamical systems, also called the geometric theory of differential equations. When the molecular details are not accessible, a system’s __effective behavior__ may be represented in terms of a small number of aggregate variables, and qualitatively different behaviors enumerated according to the geometrical structure of __trajectories__ or __topology__. The fates that are accessible to a cell are associated with __attractors__ — the valleys in Waddington’s “epigenetic landscape” - to which neighboring trajectories converge.

The description of cellular differentiation with energy landscapes dates from __Conrad Hal Waddington__, a founding father of __epigenetics__, who viewed the evolution as a process happening via mutations of the genes involved in the developpment. The energy landscape in which the cells lived is determined by the genome of the individual. Thus mutation would affect the landscapes, and therefore affect cellular states.

>__Epigenetics :__ The study of changes in cells caused by modification of gene expression rather than alteration of the genetic code itself.

>__Epistasis :__ When one or several genes avoid the expression of other genes

>__Hypomorph (hypermorph) :__ A type of mutation in which the altered gene product possesses a reduced (enhanced) level of activity

>__Autocrine :__ Denoting or relating to a cell-produced substance that has an effect on the cell by which it is secreted.

>__Differentiation :__ Process through which cells specialized themselves into a given cell-type

Developmental signaling networks are indeed composed of dozens of components whose interactions are very difficult to quantify in an embryo. The authors of the paper model the discrete hierarchy of phenotypic models using energy landscapes. This model encompasses both signaling and commitment, and allows to predict the correlated cell fate probabilities for a cross between any two backgrounds/conditions.  The two signaling pathways are combined additively, without interactions, and epistasis only arises from the nonlinear dynamical flow in the landscape defined by the geometric model.

The biological example considered here is the vulva of the nematode C. Elegans, in which two signaling pathways are integrated : Induction by __EGF__ and lateral signaling through __Notch__. The genetic experiments seems to indicate a mutual pathway repression. This model fits these experimental data, but also quantifies the contributions of extrinsic vs. intrinsic sources of noise in the penetrance of mutant phenotypes in signaling hypomorphs.

During this session, we will model the energy landscape described in the paper and make several predictions from this model, linked to biological data.

---

# 0 - Landscape topology

There are three possible topologies of the landscape. The landscape of the right is choosen to model vulval patterning based on experimental data.
All transitions between two fates are permissible, there is a central source (__open circle__) and saddles (__crosses__) between each pair of fate.

### Governing equations :
The state of each cell is parametrized by a two-dimensional vector $\vec{r} = x\vec{e}_x + y \vec{e}_y$ and its dynamics are defined as follows :

$
\begin{align}
\vec{f}(\vec{r}) &= 2 \vec{r} + c_2[-2x\vec{e}_x + (y^2 + x^2)\vec{e}_y] \newline
\frac{d\vec{r}}{dt} &= \frac{1}{\tau}[\vec{\sigma}_1(\vec{f}(\vec{r}) + \vec{m}) - \vec{r}] + \vec{\eta}(t) \newline
\vec{\sigma}_1 &= \tanh(||\vec{f}||)\frac{\vec{f}}{||\vec{f}||} \newline
\vec{m} &= \vec{m}_0 + l_1 \vec{m}_1 + l_2 \vec{m}_2 \newline
\left< \eta_i(t) \eta_j(t')\right> &= 2D \delta_{ij} \delta(t-t')
\end{align}
$

$\vec{f}(\vec{r})$ is the term in the equations that make the dynamics multistable.

### Initial conditions :

We assume that the initial state is monostable. Thus we remove the term $\vec{f}$ from the equations that maked them multistable. The dynamics are given by :

$
\dfrac{d\vec{r}}{dt} = \frac{1}{\tau}[\vec{\sigma}_1(\vec{m}_0) - \vec{r}] + \vec{\eta}(t) $

### __Question 1__
> What is the steady state of the initial distribution of $\vec{r}$ ?

### __Question 2__
> Discretize the equation with a forward euler-scheme. from which distribution do you draw $\eta$ at discrete times ?

```python
from utils import *

import numpy as np # Numerical computing library
import matplotlib
import matplotlib.pyplot as plt # Plotting library
```

```python
#The given parameters are infered from biological data.
#A big part of the study was to determine all these parameters from the experiments.
#We will only give the phenotypical landscapes

tau = 1./2.18
D = 0.16**2/4.
c2 = 1

def sigmaonefunc(x):
    norm = np.linalg.norm(x)
    sigma = np.tanh(norm)*x/norm
    return(sigma)

def f(r):
    s = 2*r
    x,y = r
    e1 = np.array([1,0])
    e2 = np.array([0,1])
    s-=e1*c2*(2*x*y)
    s+=e2*c2*(y**2-x**2)
    return(s)
```

# I - Unbiased Dynamics

We want to find the fixed points of the energy landscape. We will consider the situation without noise $(\eta = 0)$ and without bias $(\vec{m}=0)$

$
\dfrac{d\vec{r}}{dt} = \dfrac{1}{\tau}[\vec{\sigma}_1(\vec{f}(\vec{r}) - \vec{r}]$

### __Question 3__
> Integrate and plot these dynamics with $t\in[0,20]$ from the given initial conditions. How many basins do you find ? Find the coordinates of the basins from these dynamics.

__Tip :__ You can do an euler scheme or use _scipy.integrate.odeint_ to find the trajectories.

```python
import scipy.integrate

#This function works by defining a function func(r,t) giving the value of dr/dt, and the timepoints.

def dynamic_unbiased(r, t):
    f_value = f(r)
    sigma1 = sigmaonefunc(f_value)
    return (sigma1 - r) / tau

time = np.linspace(0, 20, 1000)
initial_conditions = [(.0001, .0001), (.0001, -.0001), (-.0001, .0001), (-.0001, -.0001), (-0.001,0),(0.001,0),(1e-6,-0.001),(-1e-6,-0.001)]

# Do the simulations.
trajectory = {}
for j, ic in enumerate(initial_conditions):

    ...

# Draw the trajectories.
fig,ax = plot_voronoi_setup()
plt.title("Trajectories in the unbiased case")

plt.plot(...)

# Find the coordinates of the basins : (tip : There are 3 basins)

converging_points = ...
print("Converging points :",converging_points)
```

### Phase Diagram : Basins of attraction

We now want to have a representation of the energy landscape by computing a vector field of the dynamics at each point. The vector field should lead to one of the basins established before. More concretely, we will plot the vector field $\dfrac{dr}{dt}$ on the grid $[-\sqrt{2},\sqrt{2}]^2$.

To do that, we will evaluate the vector field at each point of a discretized square grid Z , and use matplotlib.pyplot.streamplot to plot it.

```python
# We determine the x and y points of the grid

xlist20 = np.linspace(-np.sqrt(2), np.sqrt(2), 20)
ylist20 = np.linspace(-np.sqrt(2), np.sqrt(2), 20)

# We determine the values of the vector field on this grid
X20, Y20 = np.meshgrid(xlist20, ylist20)
Z20 = np.zeros((20,20,2))

for a,b in np.ndindex(X20.shape):
    x, y = X20[a,b],Y20[a,b]
    Z20[a,b] = dynamic_unbiased(np.array([x,y]), 0)
```

```python
fig,ax = plot_voronoi_setup(plot_bassins=True,markersize=200)
plt.streamplot(X20, Y20, Z20[:,:,0], Z20[:,:,1],color = 'black', linewidth = 1.5,density = 1)
plt.show()
```

# II - Biased Dynamics

Now we work with biased dynamics $\vec{m} \neq \vec{0}$. We have :

$
\begin{align}
\frac{d\vec{r}}{dt} &= \frac{1}{\tau}[\vec{\sigma}_1(\vec{f}(\vec{r}) + \vec{m}) - \vec{r}] + \vec{\eta}(t) \newline
\vec{m} &= \vec{m}_0 + l_1 \vec{m}_1 + l_2 \vec{m}_2 \newline
\end{align}
$

Where $\vec{m}_0$ represent the natural tendency to go to a 3° fate, $\vec{m}_1$ represent the EGF signaling, leading to the 1° fate, and $\vec{m}_2$ represent the Notch signaling, leading to the 2° fate.

These biased dynamics obviously change the topological landscape that was observed previously. We will determine these new landscapes by plotting the vector field of the dynamics, as before.

```python
stationary_points = np.array([[-0.86156317, -0.49742373],[ 0.86156317, -0.49742373],[1.60591136e-14, 9.94847458e-01]])
```

```python
m0_norm = 0.39
m1_norm = 4.60
m2_norm = 5.97

m0 = m0_norm * stationary_points[0] / np.linalg.norm(stationary_points[0])  # fate 3°
m1 = m1_norm * stationary_points[1] / np.linalg.norm(stationary_points[1])  # EGF signaling
m2 = m2_norm * stationary_points[2] / np.linalg.norm(stationary_points[2])  # Notch signaling
m0, np.linalg.norm(m0), np.linalg.norm(stationary_points[0]), np.linalg.norm(stationary_points[1]), np.linalg.norm(stationary_points[2])
m_norm_max = np.max([m0_norm, m1_norm, m2_norm])
```

### Phase Diagram : Basins of attraction

### __Question 4__

> Write the derivative function in the case of the biased dynamics. Plot the vector field of the dynamics for each scenario, as was done before un the case of unbiased dynamics. What do you observe ?

```python
def dynamic_biased(r, t, l1, l2):
    ...
    return

scenarios = [
    {'l1': 0, 'l2': 0},
    {'l1': 0.16, 'l2': 0},
    {'l1': 0.5, 'l2': 0},
    {'l1': 0., 'l2': 0.16},
    {'l1': 0., 'l2': 0.5}
    ]

fig, ax = plt.subplots(1,5,figsize=(25,10))

for j,params in enumerate(scenarios) :

    l1, l2 = [params[key] for key in ["l1","l2"]]

    Z20 = np.zeros((20,20,2))

    for a,b in np.ndindex(X20.shape):
        x, y = X20[a,b],Y20[a,b]
        Z20[a,b] = dynamic_biased(np.array([x,y]), 0,l1,l2)

    ax[j].streamplot(X20, Y20, Z20[:,:,0], Z20[:,:,1],color = 'black', linewidth = 1.5,density = 1)
    ax[j].set_aspect('equal')
    ax[j].axis('off')
    ax[j].set_title(params)

```

# III - Vulval Precursor Cells (VPCs) Trajectories

C-Elegans is a nematode that has an invariange lineage pattern : the cells always divide in the same way to form the same lineage tree in the normal development. Thus, mutations alterating this lineage provide important informations about the mechanisms involved to obtain these fates.

The vulva of Caenorhabditis elegans forms from a row of vulval precursor cells (VPCs), which are competent to adopt three possible fates :

- 1° in red, induced by EGF from the neighboring anchor cell (AC)
- 2° in green, induced by Notch lateraly among VPCs
- 3° in blue, in absence of induction

The pattern of fates among the daugthers of these precursors cells is shown on the figure bellow. When development is perturbed, other patterns of the three fates are obtained, often with variable results for genetically identical animals. Thus experimental outcomes are described as probabilities. We will show how modifying the previous landscape according to these mutations allows to retrieve experimental features.

We will integrate the dynamics of the five cells P4,P5,P6,P7,P8 at each time points.
From the previous situation, we now need to integrate the interaction between several cells through EGF and Notch. for EGF :

#### EGF signaling

The EGF signal is fixed and decreases exponentially with the distance from the anchor cell (AC):

$l_1 = l_1^0 \times [\gamma^2, \gamma, 1, \gamma, \gamma^2]$ respectively, for $[P4.p,P5.p,P6.p,P7.p,P8.p]$

#### Notch signaling

On the other hand, the lateral signal emitted by each VPC depends on its current state. The lateral signal is defined by a function $L_2(\vec{r})$ which varies as a sigmoid from zero to one when $\vec{r}$ crosses a threshold line that runs diagonally through the 3°, 1° domains.

$\begin{align}
L_2(\vec{r}) &= \sigma_2(n_0 + \vec{n}_1 \cdot \vec{r}) \newline
\sigma_2(u) &= \dfrac{1+\text{tanh}2u}{2}
\end{align}$

To relate the signal, L2, produced by a cell and its neighbors to Notch signaling, l2, in that cell, we introduce a ratio $\alpha$ to parameterize the relative importance of autocrine and paracrine signaling  (Notch ligands or Deltas include both transmembrane and diffusible ligands). Thus for P6.p:

$l_2(P6.p) = \alpha L_2(P6.p) + L_2(P5.p) + L_2(P7.p)$

### __Question 5__

> With a forward-Euler scheme, integrate and plot the trajectories of the five VPC in the case of normal (WT) development.

Discretizing with the Euler method a stochastic differential equation is called a the Euler-Maruyama method.

https://en.wikipedia.org/wiki/Euler–Maruyama_method This is quite natural to do so but also theoretically grounded !

```python
gamma = 0.16
alpha = 1.14
n0 = -1.23
rot1 = -48.28 * np.pi / 180.
n1_norm = 3
n1 = n1_norm * np.array([np.cos(rot1), np.sin(rot1)])
n1_normal = np.array([np.sin(rot1), -np.cos(rot1)])
l1_over = 7.39
l1_0 = 0.55
```

```python
def sigmatwofunc(u):
    sigma = (1 + np.tanh(2*u)) / 2
    return(sigma)

def L2(u):
    return(sigmatwofunc(n0+np.dot(n1,u)))

nsteps = 100
nit = 200

timepoints = np.linspace(0,1,nsteps)
dt = timepoints[1]

L1 = ...

Startpoints = np.zeros((nit,5,2))
Endpoints   = np.zeros((nit,5,2))
Trajectories= np.zeros((nit,nsteps,5,2))

R4 = np.zeros((nsteps,2))
R5 = np.zeros((nsteps,2))
R6 = np.zeros((nsteps,2))
R7 = np.zeros((nsteps,2))
R8 = np.zeros((nsteps,2))

#Compute trajectories
for n in range(0,nsteps):

    if n ==0 :

        # define initial positions of the cells in the landscape :
        R4[n] = ...
        R5[n] = ...
        R6[n] = ...
        R7[n] = ...
        R8[n] = ...
        continue

    #Notch signaling
    l2_p4 = ...
    l2_p5 = ...
    l2_p6 = ...
    l2_p7 = ...
    l2_p8 = ...

    ...
    R4[n]=...

    ...
    R5[n]=...

    ...
    R6[n]=...

    ...
    R7[n]=...

    ...
    R8[n]=...

...

#plot trajectories
fig,ax = plot_voronoi_setup()
...

```

# IV - Predictions :

### EGF overexpression :

EGF overexpression can be implemented easily by changing the multiplicative coefficient $l_1^0$ in the expression :

$l_1 = l_1^0 \times [\gamma^2, \gamma, 1, \gamma, \gamma^2]$ respectively, for $[P4.p,P5.p,P6.p,P7.p,P8.p]$

### __Question 6__

> Integrate and plot the trajectories of the five VPC in the case of EGF overexpressed mutants, replacing $l_1^0$ by $l_1^{\text{over}}$. Compare to the wild-type trajectories.

### Anchor cell Ablation :

An in-vivo experiment to test our model is to remove the anchor cell (AC) responsible for EGF induction during the development. This is implemented in our model by putting $l_1^0 = 0$ at one moment during the development (corresponding to the moment where the anchor cell is ablated)

### __Question 7__

> Integrate and plot the trajectories of the five VPC and ablate the anchor cell (AC) at $t=0.4$, replacing $l_1^0$ by $0$. Compare to the wild-type trajectories.

### Influence of EGF/Notch Hypomorphs on cell fates :

We now study the joint influence of reduced level of EGF and Notch. This can again be tested in-vivo, where gene expression can be halved by removing a single allele of the gene. In our model, this can be done by changing the norm of $\vec{m}_1$ (for EGF) and of $\vec{m}_2$ (for Notch).

$\vec{m} = \vec{m}_0 + l_1 \vec{m}_1 + l_2 \vec{m}_2$

### __Question 8__

> Integrate and plot the trajectories of the five VPC for each hypomorph config listed bellow. Compare to the wild-type trajectories.

We can even go further and plot the frequency of \#2 fates of P5/7 progrenitors with Notch and EGF levels going from the WT levels to zero. This gives us a mutational landscape indicating the frequency of changes of fates of progenitors.

```python

```

# Conclusion

Development is a dynamical process with competing fates, and it is often hard to visualize the consequences of genetic perturbations. The phenotypic model presented here effectively codifies the genotype-to-phenotype map because the influence of each gene is generally limited to one parameter. Geometric reasoning provides a compact, non-redundant parameterization of signaling and fate specification.

The philosophy of starting with a simplified but global model for developmental patterning meshes well with basic biology : Signaling, commitment, and determination are discrete terms applied to a continuous process. It is unnatural to imagine one model for signaling and another for commitment and determination : the same genes are involved, and the regulation of competence by external signals would fit naturally within our description. A unified model also makes it very simple to incorporate noise, which is a prominent feature of any interesting mutant and of course affects the average trajectories as well as the probabilities of the final states. The existence of a predictive dynamic model should encourage experiments to measure crosses among the available alleles as well as temporal signaling perturbations in mutant backgrounds. Variability in the timing of fate determination is a strong consequence of the flow geometry and should also be measured.

The model studied describes the experiments and time window of C-Elegans VPC differentiation but not much more. There are no parameters that up-regulate receptors at the beginning of competence or shut down pathways after the first VPC division. From this model, we can say nothing about the differentiation that ensues near the fixed points, which clearly requires additional dimensions. However the fixed points are merely compact ways of creating the simplest parameterizations for the boundaries between them and enumerating the various flow topologies.


# Denavit Hartenberg Notation

Kevin Walchko
Created: 10 July 2017

---

Denavit Hartenberg (DH) is an attempt to standardize how we represent serial manipulators (i.e., robot arms). It is typically one of the first ways you learn. It is really easy (methodical) to do forward kinematics, but becomes more challenging when doing inverse kinematics. Here we are going to introduce what is goning on, but you need to focus on the DH process. If you follow the process, then all will work out fine. Don't get too hung up on the begining math, understand the concepts so you can follow the DH process.

## Objectives

- understand coordinate frames (we will see these again)
- apply rotations and translations to objects in 3d space (we will see these again)
- calculate DH forward kinematics for a serial link mechanism
- understand homogenous transformations (we will see these again)
- understand Euler sequences (we will see these again)

## References

- [Wikipedia modified DH](https://en.wikipedia.org/wiki/Denavit%E2%80%93Hartenberg_parameters)
- [darpa robot challenge](https://www.youtube.com/watch?v=diaZFIUBMBQ)
- [darpa robot fails](https://youtu.be/wX0KagJ1du8)
- [Walking robot](https://vimeo.com/194676675)

## Setup

```python
%matplotlib inline

from __future__ import print_function
from __future__ import division
import numpy as np
from math import cos, sin, pi

from IPython.display import HTML # need this for embedding a movie in an iframe
```

# Rise of the Robots

Robot arms and legs are hard to control (lots of math), but are required for most robotic applications.

DARPA Orbital Express, was a space robotics demo to perform on-orbit survicing (repair, refueling, etc). This demo proved if we build modular satellites, we can extend the on-orbit life.

EOD robots used to neutralize IEDs. Their arm is equiped with a [variety of payloads](http://www.army-technology.com/projects/irobot-510-packbot-multi-mission-robot/) to handle various types of IEDs.

Prosthetic limbs in the olden days werew a wooden peg to replace your leg. Today, robotic arms and legs are becomming fully functional replacements, [controlled via neural impulses](http://www.jhuapl.edu/newscenter/stories/st120524.asp) from the person. Although people might tie these robotic limbs to the rise of cyborgs, but depending on your definition of cyborg, you could also call a person with an implanted pacemaker a cyborg (see definition below).

**Cyborg:** [*noun*] a person whose physiological functioning is aided by or dependent upon a mechanical or electronic device. [ref](http://www.dictionary.com/browse/cyborg)

# Kinematics of Serial Manipulators

Kinematics is the study of motion without regard to the forces which cause it. Kinematics of manipulators involves the study of the geometric and time based properties of the motion, and in particular how the various links move with respect to one another and with time.

**Pose:** The combination of position and orientation of a frame relative to a reference or inertial frame. Think [*Zoolander*](http://www.imdb.com/title/tt0196229/) on a fashion shoot, "strike a pose"

This lesson will talk about matrix and vector operations. A nice review of the various mathematical operators is [wikipedia](https://en.wikipedia.org/wiki/Matrix_multiplication). Take a look at how you multiply 2 matrices together (Matrix Product (two matrices)) and it will give you an idea of how it works. Ultimately we will use `numpy` to do these operations for us.

## Coordinate Frames

We want to describe positions and orientations of bodies in space relative to a [reference](https://en.wikipedia.org/wiki/Frame_of_reference) (or in some cases an [inertial](https://en.wikipedia.org/wiki/Inertial_frame_of_reference)) frame. So if we wanted to know where something was, we could define it from this coordinate frame as $[x,y,z]$.

We are going to make this more complex and define multiple coordinate frames, which will all be rotated in some fashion, and try to determine a point (maybe the end-effector of a robot arm) relative to a base (inertial) reference frame.

## Manipulators

There are 2 types of manipulators in robotics: serial and parallel. They are used in different applications for manufacturing.

Definitions:

- **Forward Kinematics:** Given a robot's joint angles, where is the robot's end-effector located?
- **Inverse Kinematics:** Given a point in 3D space where we want the end-effector, what are the joint angles to get us there?

For most robotic applications, we need to be able to calculate both the forward and reverse.

| Type     | Pro                     | Con                        |
|----------|-------------------------|----------------------------|
| Serial   | easy forward kinematics | complex inverse kinematics |
| Parallel | easy inverse kinematics | complex forward kinematics |

For this class we are going to focus on serial manipulators.

## Serial Manipulators

- We generally draw a simplified version of the manipulator and attach a “frame” to each rigid body link
    - The simplified version only has:
        - *Revolute joints:* joints that rotate
        - *Prismatic joints:* joints that move linearly (think telescoping)
    - By combining these in various combinations, we can make anything. For example, a spherical joint (ball-and-socket like your shoulder) is generally composed of 3 co-located (not physically real) rotational joints. This makes the math easier.
- The frames follow the serial manipulator's body
- All of our frames will be a righthand-coordinate system (RHS) ... sorry lefties
- There's some freedom in how we choose the frame's position and orientation relative to the body
- Denavit-Hartenberg (DH) notation partially standardizes this process, however, there are classical DH parameters and modified DH parameters. We are using modified (see reading in Craig). In the end you end up with the same equations, however, some people (i.e., Craig) didn't like how the classical notation was written.

An example (sort of) is shown below. Notice the real robot is represented as a simpler drawing of little metal bars:

The KR270 has 6 joints and is a standard industrial type robot for manufacturing. Notice again, the wrist, is represented as 2 revolute joints co-located (on top of each other) which is not realistic.

## Rotation Matrix

A rotation matrix transforms (or rotates) a point from one location to another. If we start off simple and look at a 2D matrix:

$$
R =
\begin{bmatrix}
  cos(\theta) & -sin(\theta) \\
  sin(\theta) & cos(\theta)
\end{bmatrix}
$$

If I have a 2D point located at $v = [x,y]$ and I want to rotate it (or turn it in the 2D plane), I can do that with this matrix as $v' = Rv$ where $v'$ is the new 2D position.

If we expand this to 3D, so our matrix would be a 3x3, we can start to do things like this:

Now, please note, the beginning of this `gif` starts off with a translation (or movement), the does a rotation as the cube spins around. Another great example, is your arm! If you rotate your elbow, your hand moves. You can define that rotation my a matrix operation. Also note, we are not changing the length or size of anything when we do a rotation. We are just moving something (usually in some sort of circular/arc fashion) from one place to another. Again, when you rotate your arm, it doesn't change size does it? If it does, go see a doctor!

### Properties

A nice thing about rotation matricies, is:

$$
R^B_A = (R^A_B)^T = (R^A_B)^{-1}
$$

This is because they are orthonormal (the dot product of the x, y, and z components is 0). This means a matrix inverse and matrix transpose produce the same results, which is good since matrix inverse is CPU intensive compared to a matrix transpose (shown below).

$$
R = \begin{bmatrix}
  1 & 2 & 3 \\
  4 & 5 & 6 \\
  7 & 8 & 9
\end{bmatrix} \\
R^T = \begin{bmatrix}
  1 & 4 & 7 \\
  2 & 5 & 8 \\
  3 & 6 & 9
\end{bmatrix} = inv(R) = R^{-1}
$$

Notice a transpose just turns matrix columns into matrix rows ... no math!

**Note:** the magnitude of the rows and columns of a *real* rotation matrix are 1. The example above is sloppy in that fact. I was lazy and just wanted to remind you how transpose worked.

```python
# rotation example

# euler angles: 0 0 0
Rab = np.eye(3) # so this rotation transforms from b to a
Pb = np.array([0,0,1])
Pa = Rab.dot(Pb)
print('Original position', Pb)
print('\nRotation:\n', Rab)
print('\nNew orientation:', Pa)
```

    Original position [0 0 1]

    Rotation:
     [[1. 0. 0.]
     [0. 1. 0.]
     [0. 0. 1.]]

    New orientation: [0. 0. 1.]

```python
# rotate 45 deg about x-axis
Rab = np.array([[1,0,0], [0,cos(pi/4), -sin(pi/4)], [0,sin(pi/4),cos(pi/4)]])
Pb = np.array([0,0,1])
Pa = Rab.dot(Pb)
print('Original position', Pb)
print('\nRotation:\n', Rab)
print('\nNew orientation:', Pa)
```

    Original position [0 0 1]

    Rotation:
     [[ 1.          0.          0.        ]
     [ 0.          0.70710678 -0.70710678]
     [ 0.          0.70710678  0.70710678]]

    New orientation: [ 0.         -0.70710678  0.70710678]

## Position and Orientation

You should aready be familar with vectors from other courses. We are only going to deal with 2D (in a plane: x, y) or 3D (x, y, z) vectors for our position. We are going to have to reference the position of an object relative to multiple referenece frames. This section is going to lay the basic mathematical foundation.

Now the position/orientation, or pose, of {B} relative to {A} is:

$$
P^A = R^A_B P^B
$$

Now ultimately, we want to know the location of a point in {B} relative to an inertial frame (say the base of our robot arm) in {A}.

What the final equation means is the position of $P^B$ in frame A is equal to the offset between {A} and {B} (i.e., $P^A_B$) plus the change in orientation between {A} and {B} (i.e., $R^A_B P^B$).

```python
# rotate 45 deg about x-axis
# add a translation in now
Rab = np.array([[1,0,0], [0,cos(pi/4), -sin(pi/4)], [0,sin(pi/4),cos(pi/4)]])
Pb = np.array([0,0,1])
Pab = np.array([3,0,0]) # {b} is 3 units infront of {a}
Pa = Pab + Rab.dot(Pb)
print('Original position', Pb)
print('\nRotation:\n', Rab)
print('\nTranslation:\n', Pab)
print('\nNew position:', Pa)
```

    Original position [0 0 1]

    Rotation:
     [[ 1.          0.          0.        ]
     [ 0.          0.70710678 -0.70710678]
     [ 0.          0.70710678  0.70710678]]

    Translation:
     [3 0 0]

    New position: [ 3.         -0.70710678  0.70710678]

## Homogeneous Transforms

This is typically not how we do the math. Instead, robotics combines these opertions together in a form that becomes easier to program. A compact representation of the translation and rotation is known as the **Homogeneous Transformation**. This allows us to combine the rotation ($R^A_B$) and translation ($P^A_B$) of the general transform in a single matrix form.

$$
T^A_B = \begin{bmatrix}
   & R^A_B & & P^A_B \\
  0 & 0 & 0 & 1
\end{bmatrix} \\
\begin{bmatrix}
  P^A \\
  1
\end{bmatrix} =
T^A_B
\begin{bmatrix}
  P^B \\
  1
\end{bmatrix}
$$

This compact notation allows us to use `numpy` or `matlab` to write series of equations as matricies and do standard matrix operations on them. Now, as we attach frames to serial manipulator links, we will be able to combine these matricies together to calculate where the end effector is.

```python
# Now let's do the combined homogenious matrix and see if we get the same answer
# rotate 45 deg about x-axis
# add a translation in now
Tab = np.array([[1,0,0,3], [0,cos(pi/4), -sin(pi/4),0], [0,sin(pi/4),cos(pi/4),0],[0,0,0,1]])
Pb = np.array([0,0,1,1])
Pa = Tab.dot(Pb)
print('Original position', Pb)
print('\nRotation and translation:\n', Tab)
print('\nNew position:', Pa)
```

    Original position [0 0 1 1]

    Rotation and translation:
     [[ 1.          0.          0.          3.        ]
     [ 0.          0.70710678 -0.70710678  0.        ]
     [ 0.          0.70710678  0.70710678  0.        ]
     [ 0.          0.          0.          1.        ]]

    New position: [ 3.         -0.70710678  0.70710678  1.        ]

# Denavit-Hartenberg (DH)

Now that we have a basic understanding of translation and rotation, we can look at a process (for serial manipulators) to automate it. We will use the DH method to develop the symbolic equations and use python `sympy` to simplify them so we can program the equations.

## Process (Craig, section 3.4 & 3.6, pg 67)

Now the process laid out in Craig's book, defines some parameters for eash link in a robot arm:

| | | |
|------------|:--------------|:------------------------------------------------------|
| $a_i$      | link length   | distance from $z_i$ to $z_{i+1}$ measured along $x_i$ |
| $d_i$      | offset        | distance from $x_{i-1}$ to $x_i$ along $z_i$          |
| $\alpha_i$ | twist         | angle from $z_i$ to $z_{i+1}$ measured about $x_i$    |
| $\theta_i$ | rotation      | angle from $x_{i−1}$ to $x_i$ measured about $z_i$    |

**Note:** on a quiz or GR I will give you these definitions, I don't even memorize them. But know the process below

Summary of steps:

1. Identify the joint axes and imagine (or draw) infinite lines along them. For steps 2 through 5 below, consider two of these neighboring lines (at axes i and i+1).
1. Identify the comon perpendicular betwen them,or point of intersection. At the point of intersection, or at the point where the comon perpendicular meets the $i^{th}$ axis, asign the link-frame origin.
1. Asign the $Z_i$ axis pointing along the $i^{th}$ joint axis.
1. A sign the axis pointing along the comon perpendicular, or, if the axes intersect, asign k1 to be normal to the plane containing the two axes.
1. Asign the y axis to complete a right-hand cordinate system ... honestly don't draw y-axis
1. Asign {0} to match {1} when the first joint variable is zero. For {N}, chose an origin location and $X_N$ direction freely, but generaly so as to cause as many linkage parameters as possible to become zero.

Now, once you have the parameters, you can enter them into the followng matrix to get the relationship between frame i and frame i+1. Note, that these are **not** euler angles, but rather:

1. A translation along z by d
1. Rotation about z by $\theta$
1. translation along x by a
1. Rotation about x by $\alpha$

This sequence turns into the the following homogenious transform:

$$
\begin{eqnarray}
T^{i-1}_i = R_x(\alpha_{i-1}) D_x(a_{i-1}) R_z(\theta_i) D_z(d_i) \\
\\
T^{i-1}_i = \begin{bmatrix}
  \cos(\theta_i)                   & -\sin(\theta_i)                  & 0                   & a_{i-1} \\
  \sin(\theta_i)\cos(\alpha_{i-1}) & \cos(\theta_i)\cos(\alpha_{i-1}) & -\sin(\alpha_{i-1}) & -\sin(\alpha_{i-1})d_i \\
  \sin(\theta_i)\sin(\alpha_{i-1}) & \cos(\theta_i)\sin(\alpha_{i-1}) & \cos(\alpha_{i-1})  & \cos(\alpha_{i-1})d_i \\
  0                                & 0                                & 0                   & 1
\end{bmatrix}
\end{eqnarray}
$$

You will create 1 matrix for each link in your serial manipulator. Then you can multiply these matricies together to get the transform from base frame {0} to end effector frame {3} by: $T^0_3 = T^0_1 T^1_2 T^2_3$

Also, the general format of **every** homogenious matrix is:

$$
T_{4x4} =
\begin{bmatrix}
  R_{3x3} & t_{3x1} \\
  \begin{bmatrix} 0 & 0 & 0 \end{bmatrix} & 1
\end{bmatrix}
$$

where $R$ is your rotation matrix, $t$ is your translation, and T is your homogenious matrix. Note, later when we do computer vision, you will see the homogenious matrix refered to as $H$. Basically no one has agreed to what variables are called across different fields of study. So be flexible and try to recognize things for what they are ... you will see these again.

```python
HTML('<p><a href="https://vimeo.com/238147402">Denavit–Hartenberg parameters</a> from <a href="https://vimeo.com/user59907133">kevin</a> on <a href="https://vimeo.com">Vimeo</a>.</p>')
```

Denavit–Hartenberg parameters from kevin on Vimeo.

## Simple 2D Example

Following the process for assigning frames to a manipulator, you get the following:

Looking at the above frames, they are related by:

| Link | $a_{i-1}$ | $\alpha_{i-1}$ | $d_i$ | $\theta_i$ |
|------|-----------|----------------|-------|------------|
| 1    | 0     | 0          | 0     | $\theta_1$ |
| 2    | $a_1$ | 0          | 0     | $\theta_2$ |
| 3    | $a_2$ | 0          | 0     | 0          |

Now using Craig eqn 3.6, we can substitute these values in and get the relationship between the inertial frame and the end effector. However note, $\theta_i$ are variable parameters. Typically we would simplify these equations down leaving only the $\theta_i$ parameters. Let's use the python symbolic toolbox to generate the equations of motion.

```python
# Let's grab some libraries to help us manipulate symbolic equations
import sympy
from sympy import symbols, sin, cos, pi, simplify

def makeT(a, alpha, d, theta):
    # create a modified DH homogenious matrix
    # this is the same matrix as above
    return np.array([
        [           cos(theta),           -sin(theta),           0,             a],
        [sin(theta)*cos(alpha), cos(theta)*cos(alpha), -sin(alpha), -d*sin(alpha)],
        [sin(theta)*sin(alpha), cos(theta)*sin(alpha),  cos(alpha),  d*cos(alpha)],
        [                    0,                     0,           0,             1]
    ])

def simplifyT(tt):
    """
    This goes through each element of a matrix and tries to simplify it.
    """
    ret = tt.copy()
    for i, row in enumerate(tt):
        for j, col in enumerate(row):
            ret[i,j] = simplify(col)
    return ret

def subs(tt, m):
    """
    This allows you to simplify the trigonomic mess that kinematics can
    create and also substitute in some inputs in the process

    Yes, this is basically the same as above. I could combine these into 1
    function, but I wanted to beclearer on what I am doing.
    """
    ret = tt.copy()
    for i, row in enumerate(tt):
        for j, col in enumerate(row):
            try:
                ret[i,j] = col.subs(m)
            except:
                ret[i,j] = simplify(col)
    return ret
```

```python
# make thetas (t) and link lengths (a) symbolics
t1, t2 = symbols('t1 t2')
a1, a2 = symbols('a1 a2')

# let's create our matrices
T1 = makeT(0, 0, 0, t1)
T2 = makeT(a1, 0, 0, t2)
T3 = makeT(a2, 0, 0, 0)

# T13 = T1 * T2 * T3
T13 = T1.dot(T2.dot(T3))

print('T1 = ', T1)
print('T2 = ', T2)
print('T3 = ', T3)
print('\nSo the combined homogenious matrix is:\n')
print('T13 = ', T13)
```

    T1 =  [[cos(t1) -sin(t1) 0 0]
     [sin(t1) cos(t1) 0 0]
     [0 0 1 0]
     [0 0 0 1]]
    T2 =  [[cos(t2) -sin(t2) 0 a1]
     [sin(t2) cos(t2) 0 0]
     [0 0 1 0]
     [0 0 0 1]]
    T3 =  [[1 0 0 a2]
     [0 1 0 0]
     [0 0 1 0]
     [0 0 0 1]]

    So the combined homogenious matrix is:

    T13 =  [[-sin(t1)*sin(t2) + cos(t1)*cos(t2) -sin(t1)*cos(t2) - sin(t2)*cos(t1) 0
      -a2*sin(t1)*sin(t2) + (a1 + a2*cos(t2))*cos(t1)]
     [sin(t1)*cos(t2) + sin(t2)*cos(t1) -sin(t1)*sin(t2) + cos(t1)*cos(t2) 0
      a2*sin(t2)*cos(t1) + (a1 + a2*cos(t2))*sin(t1)]
     [0 0 1 0]
     [0 0 0 1]]

So that looks a little messy with all of the sines and cosines ... let's use the python symbolic capabilities in `sympy` to help us reduce this a little and figure out where the end-effector is relative to the base (e.g. inertial frame).

```python
ans = simplifyT(T13)
print(ans)
print('-'*25)
print('position x: {}'.format(ans[0,3]))
print('position y: {}'.format(ans[1,3]))
```

    [[cos(t1 + t2) -sin(t1 + t2) 0 a1*cos(t1) + a2*cos(t1 + t2)]
     [sin(t1 + t2) cos(t1 + t2) 0 a1*sin(t1) + a2*sin(t1 + t2)]
     [0 0 1 0]
     [0 0 0 1]]
    -------------------------
    position x: a1*cos(t1) + a2*cos(t1 + t2)
    position y: a1*sin(t1) + a2*sin(t1 + t2)

Later, we will derive the same 2 link manipulator a different way and come up with the same equations for position of the end-effector in the x, y plane. For simple manipulators, DH is overkill, however, for most real manipulators (remember the DARPA videos), it is useful to understand what is going on if you do robotics.

```python
# what if I wanted to substitute in an angle?
# just give it an array of tuples
ans = subs(T13, [(t1, 0)])  # here it is only t1, but I could do: [(t1, angle), (t2, angle)]
print(ans)
print('-'*25)
print('position x: {}'.format(ans[0,3]))
print('position y: {}'.format(ans[1,3]))
```

    [[cos(t2) -sin(t2) 0 a1 + a2*cos(t2)]
     [sin(t2) cos(t2) 0 a2*sin(t2)]
     [0 0 1 0]
     [0 0 0 1]]
    -------------------------
    position x: a1 + a2*cos(t2)
    position y: a2*sin(t2)

# KUKA KR270 Example

Now following the steps above, we get the complete DH table below. Notice, the distances in the picture (d) have been changed to m, because I didn't want people getting confused between distance d and DH parameter d. Therefore everything is now distance m to remove confusion:

| Link | $a_{i-1}$ | $\alpha_{i-1}$ | $d_i$ | $\theta_i$ |
|------|-----------|----------------|-------|------------|
| 1    | 0         | 0              | $m_1$ | $\theta_1$ |
| 2    | $m_2$     | 90             | 0     | $\theta_2$ |
| 3    | $m_3$     | 0              | 0     | $\theta_3$ |
| 4    | $m_5$     | -90            | $m_4$ | $\theta_4$ |
| 5    | 0         | 90             | 0     | $\theta_5$ |
| 6    | 0         | -90            | $m_6$ | $\theta_6$ |

A step-by-step walk through is found [here](lsn7-extra.pdf) (this is included in the zip file and called `lsn7-extra.pdf`), make sure you understand this process because you will be tested on it. Also, I strongly suggest you do not wait until the night before the test to learn this!

# Exercises

Practice makes perfect. Try to do the following from Craig so you will have success on the GR.

- Try to reproduce the Kuka KR270 parameter table above on your own. There may be small differences depending on some decisions with your frame locations for {4} and {6}.
- Puma Figure 3.18, try to get the same DH parameters as in table 3-21.
- Yasukawa L-3, Figure 3.25, answers in table 3.26

I **guarantee** there will be a DH problem on the GR where you have to draw frames or fill out the table or something, so please practice it.

# Questions

1. What are the parts of the homogenious transform (i.e., in the matrix, what do they mean)?
1. For the DH process, what is the sequence of rotations (about which axes) and translations?
1. If there is no rotation between 2 coordinate frames, what does the rotation matrix look like?
1. If there is no translation between 2 coordinate frames, what does the translation vector look like?
1. What is an inertial frame?
1. Are the diffences between a serial manipulator and a parallel manipulator?

-----------

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.


# A study on spontaneous decay rate of an atom in presence of a square dielectric waveguide using BEM approach

In these notes, I calculate the Local Density of States (LDOS), or the imaginary part of the on-site Green's function and hence the modified spontaneous emission rate of an atom in presence of a square dielectric waveguide using the Boundary Element Method (BEM). The BEM code is from Prof. Alejandro Manjavacas's group.

This is an [IJulia notebook](https://github.com/JuliaLang/IJulia.jl), which provides a nice
browser-based [Jupyter](http://jupyter.org/) interface to the [Julia language](http://julialang.org/), a high-level dynamic language (similar to Matlab or Python+SciPy) for technical computing.  The notebook allows us to combine code and results in one place.

We are only manipulating the generated data from the simulation results in this notebook. As a brief recap of the simulation process, I have used a compiled BEM code by Alejandro's group written in C++ called `bem2D` on a cluster computing system. A configuration C++ code is defined in the file `scripts_ldos.cpp` which is in the same folder as `bem2D`. I compiled the script and put the generated executable into another folder called `p3`, for example, by
```
g++ scripts_ldos.cpp -lstdc++ -o ../p3/scripts_ldos
```
Then ran the executable and submitted the generated PBS script to the cluster system to run the simulation:
```
cd ../p3/
./scripts_ldos
qsub ldos_N_1_lam_894_eps_4_0_a_300_b_300_c_5_x_0_y_350_q_0_2_401.pbs
```
Notice that the name of the PBS script is automatically generated based on the configuration parameters for this simulation. The name will vary for different simulations. After running the script, I got a set of data files--one has the same name as the PBS script but with a `.dat` extension for the data table storing the calculated LDOS values; there are another two `*.dat` files for the geometry of boundary and dieletric function distribution. We will look into those data files in the following sections.

## 3D dielectric waveguide simulated in 2D

Just a little more detail on the simulation itself: By assuming the waveguide along z-axis or the light propagation direction is uniform, one can completely solve the boundary condition problem of dipole emission by simulating the field in one single layer of the xy cross section. The z-component of the field only adds in a phase factor. The data of the simulated result is stored in the /data/ folder of this repo.

We can read in this data as a matrix of numbers by the `readdlm` function in Julia with the `header=false` option meaning that it reads the first line as the beginning of the data entries without a list of strings describing each column.

Now, let's plot the results.  I'll use the [PyPlot](https://github.com/stevengj/PyPlot.jl) package in Julia, which is an interface to the sophisticated [matplotlib](http://matplotlib.org/) Python plotting library.   We'll plot three things:

* The waveguide structure in terms of $\epsilon$ and the interface boundary between two media.
* Plot the LDOS components for a fixed dipole position.
* Calculate the waveguide-modified spontaneous decay rate when the dipole varies its position outside of the waveguide.

### Plotting the boundary and index of refraction profile of the waveguide in the xy cross section

Our boundary points are meshed in the file `/data/p3/geom_regions_a_300_b_300_c_5.data`, which positions where the equivalent charges and current sources to be computed in the BEM simulation. The waveguide has a square cross-section of a width $a=b=300nm$ ($nm$ is the unit of length) and a index of refraction of $n_1=n_{core}=2$ for the waveguide material and $n_2=n_{clad}=1$ for the vacuum clad.

It is good to plot out the mesh of index of refraction in space and find out how good is the mesh resolution. This can be done by plotting out the output eps file in a simple data table format, which ends in `.dat`.

The following will first print out some of the data in order to figure out the physics meaning of the dimensions. They should contain the coordinate and index of refraction information for the simulation.

Another set of data is taken at $r=330nm$ position with a slightly different setting--mainly the edges are having a larger radius. We can plot the result below.

As plotted below, the dipole is changing position from $225$nm to $420$nm from the origin (center of the waveguide) along the x-axis.

The guide mode contribution seems very small compared to the nanofiber case using the BEM approach. As discussed in other tests, the BEM calculation on the guided mode contribution part is not accurate due to the fact that the dielectric function has a zero imaginary part or loss in the frequency domain which will cause an infinite narrow peak of the LDOS function and leads to unphysical solution.
To avoid this difficulty, we will use other method to calculate the guided mode contribution part to the Green's function tensor.

# Calculation of Green's function tensor using BEM

BEM can output full local field components so that computing the radiative mode contribution to the full Green's function tensor is possible. With the Green's function tensor, one can then calculate the modified decay rates with dipoles orientated along arbitrary directions--including the dipole transitions corresponding to $\sigma_\pm$ and $\pi$ transitions.

```julia
# Load data for different dipole positions.
rp_BEM=160:10:600;#[170,190,210,230,250,270,290,310,330,350,370,390,410,430,450,470];
lenrp=length(rp_BEM);
lendr=201;
E_dipolex = readdlm("data/D2/dipolex_E_N_1_lam_852_eps_4_0.001_a_300_b_300_c_6_x_330_y_0_q_0_2_201.dat", header=false);
k_vec=E_dipolex[:,2];
Ex_dx=zeros(Complex{Float64},lendr,lenrp); Ey_dx=zeros(Complex{Float64},lendr,lenrp); Ez_dx=zeros(Complex{Float64},lendr,lenrp);
Ex_dy=zeros(Complex{Float64},lendr,lenrp); Ey_dy=zeros(Complex{Float64},lendr,lenrp); Ez_dy=zeros(Complex{Float64},lendr,lenrp);
Ex_dz=zeros(Complex{Float64},lendr,lenrp); Ey_dz=zeros(Complex{Float64},lendr,lenrp); Ez_dz=zeros(Complex{Float64},lendr,lenrp);
for ii=2:2:(lenrp-1)
    E_dipolex = readdlm("data/D2/dipolex_E_N_1_lam_852_eps_4_0.001_a_300_b_300_c_6_x_$(rp_BEM[ii])_y_0_q_0_2_201.dat", header=false); #",rp_list[ii],"
    Ex_dx[:,ii]=E_dipolex[:,5]+im*E_dipolex[:,6];
    Ey_dx[:,ii]=E_dipolex[:,7]+im*E_dipolex[:,8];
    Ez_dx[:,ii]=E_dipolex[:,9]+im*E_dipolex[:,10];

    E_dipoley = readdlm("data/D2/dipoley_E_N_1_lam_852_eps_4_0.001_a_300_b_300_c_6_x_$(rp_BEM[ii])_y_0_q_0_2_201.dat", header=false); #",rp_list[ii],"
    Ex_dy[:,ii]=E_dipoley[:,5]+im*E_dipoley[:,6];
    Ey_dy[:,ii]=E_dipoley[:,7]+im*E_dipoley[:,8];
    Ez_dy[:,ii]=E_dipoley[:,9]+im*E_dipoley[:,10];

    E_dipolez = readdlm("data/D2/dipolez_E_N_1_lam_852_eps_4_0.001_a_300_b_300_c_6_x_$(rp_BEM[ii])_y_0_q_0_2_201.dat", header=false); #",rp_list[ii],"
    Ex_dz[:,ii]=E_dipolez[:,5]+im*E_dipolez[:,6];
    Ey_dz[:,ii]=E_dipolez[:,7]+im*E_dipolez[:,8];
    Ez_dz[:,ii]=E_dipolez[:,9]+im*E_dipolez[:,10];
end
for ii=1:2:lenrp
    E_dipolex = readdlm("data/D2/dipolex_E_N_1_lam_852_eps_4_0.001_a_300_b_300_c_6_x_$(rp_BEM[ii])_y_0_q_0_2_201.dat", header=false); #",rp_list[ii],"
    Ex_dx[:,ii]=E_dipolex[:,5]+im*E_dipolex[:,6];
    Ey_dx[:,ii]=E_dipolex[:,7]+im*E_dipolex[:,8];
    Ez_dx[:,ii]=E_dipolex[:,9]+im*E_dipolex[:,10];

    E_dipoley = readdlm("data/D2/dipoley_E_N_1_lam_852_eps_4_0.001_a_300_b_300_c_6_x_$(rp_BEM[ii])_y_0_q_0_2_201.dat", header=false); #",rp_list[ii],"
    Ex_dy[:,ii]=E_dipoley[:,5]+im*E_dipoley[:,6];
    Ey_dy[:,ii]=E_dipoley[:,7]+im*E_dipoley[:,8];
    Ez_dy[:,ii]=E_dipoley[:,9]+im*E_dipoley[:,10];

    E_dipolez = readdlm("data/D2/dipolez_E_N_1_lam_852_eps_4_0.001_a_300_b_300_c_6_x_$(rp_BEM[ii])_y_0_q_0_2_201.dat", header=false); #",rp_list[ii],"
    Ex_dz[:,ii]=E_dipolez[:,5]+im*E_dipolez[:,6];
    Ey_dz[:,ii]=E_dipolez[:,7]+im*E_dipolez[:,8];
    Ez_dz[:,ii]=E_dipolez[:,9]+im*E_dipolez[:,10];
end
# Calculate the diagonal elements of the Green's function tensor from the radiation mode contribution.
c=2.99792458e8;
au=1.72e7; # This is the atomic unit in the CGS-units: $q/a_0^2$ statvolts/cm. In SI units, it becomes $e/(4π\varepsilon_0a_0^2)$ = 5.2e11 V/m.
lambda0=0.895e-6;
ω=2.*pi*c/lambda0;
GFT_rad_ind=zeros(Complex{Float64},3,3,lenrp);
Gxx_rad_ind=zeros(Complex{Float64},lenrp);
Gxy_rad_ind=zeros(Complex{Float64},lenrp);
Gxz_rad_ind=zeros(Complex{Float64},lenrp);
Gyx_rad_ind=zeros(Complex{Float64},lenrp);
Gyy_rad_ind=zeros(Complex{Float64},lenrp);
Gyz_rad_ind=zeros(Complex{Float64},lenrp);
Gzx_rad_ind=zeros(Complex{Float64},lenrp);
Gzy_rad_ind=zeros(Complex{Float64},lenrp);
Gzz_rad_ind=zeros(Complex{Float64},lenrp);
G0=Inf + 2.0/3.*(ω/c)^3*im;
gamma_rad_BEM_rp_average=zeros(lenrp);
gamma_rad_BEM_rp_sigmap=zeros(lenrp);
gamma_rad_BEM_rp_sigmam=zeros(lenrp);
gamma_rad_BEM_rp_pi=zeros(lenrp);
# Define the unitary dipole orientation vector.
e_dipole_sigmap=[-1.;-1.0*im;0]/sqrt(2);
e_dipole_sigmam=[1.; -1.0*im;0]/sqrt(2);
e_dipole_pi=[0.; 0.; 1.];
breakpoint=100; # The index of k vector to cut off for the radiative mode in the range of [0,1]k_0.
using NumericalIntegration
for ii=1:lenrp
    Gxx_rad_ind[ii]=integrate(k_vec[1:breakpoint],Ex_dx[1:breakpoint,ii],Trapezoidal())*(ω/c)^3/pi^2*au*4.0/3.;#G0+
    Gyy_rad_ind[ii]=integrate(k_vec[1:breakpoint],Ey_dy[1:breakpoint,ii],Trapezoidal())*(ω/c)^3/pi^2*au*4.0/3.;#G0+
    Gzz_rad_ind[ii]=integrate(k_vec[1:breakpoint],Ez_dz[1:breakpoint,ii],Trapezoidal())*(ω/c)^3/pi^2*au*4.0/3.;#G0+
    Gyx_rad_ind[ii]=integrate(k_vec[1:breakpoint],Ey_dx[1:breakpoint,ii],Trapezoidal())*(ω/c)^3/pi^2*au*4.0/3.;
    Gzx_rad_ind[ii]=integrate(k_vec[1:breakpoint],Ez_dx[1:breakpoint,ii],Trapezoidal())*(ω/c)^3/pi^2*au*4.0/3.;
    Gxy_rad_ind[ii]=integrate(k_vec[1:breakpoint],Ex_dy[1:breakpoint,ii],Trapezoidal())*(ω/c)^3/pi^2*au*4.0/3.;
    Gzy_rad_ind[ii]=integrate(k_vec[1:breakpoint],Ez_dy[1:breakpoint,ii],Trapezoidal())*(ω/c)^3/pi^2*au*4.0/3.;
    Gxz_rad_ind[ii]=integrate(k_vec[1:breakpoint],Ex_dz[1:breakpoint,ii],Trapezoidal())*(ω/c)^3/pi^2*au*4.0/3.;
    Gyz_rad_ind[ii]=integrate(k_vec[1:breakpoint],Ey_dz[1:breakpoint,ii],Trapezoidal())*(ω/c)^3/pi^2*au*4.0/3.;
    GFT_rad_ind[:,:,ii]=[Gxx_rad_ind[ii] Gxy_rad_ind[ii] Gxz_rad_ind[ii];
        Gyx_rad_ind[ii] Gyy_rad_ind[ii] Gzy_rad_ind[ii];
        Gzx_rad_ind[ii] Gzy_rad_ind[ii] Gzz_rad_ind[ii]];
end

# Calculate the relative averaged decay rate at the given dipole located at x=405nm and y=0.
gamma0=imag(G0);
for ii =1:lenrp
    gamma_rad_BEM_rp_average[ii]=1+trace(imag(GFT_rad_ind[:,:,ii]))/gamma0/3.;
    gamma_rad_BEM_rp_sigmap[ii]=1+real((e_dipole_sigmap'*imag(GFT_rad_ind[:,:,ii])*e_dipole_sigmap)/gamma0)[1];
    gamma_rad_BEM_rp_sigmam[ii]=1+real((e_dipole_sigmam'*imag(GFT_rad_ind[:,:,ii])*e_dipole_sigmam)/gamma0)[1];
    gamma_rad_BEM_rp_pi[ii]=1+real((e_dipole_pi'*imag(GFT_rad_ind[:,:,ii])*e_dipole_pi)/gamma0)[1];
end

# Recalculate the diagonal GFT elements with a dipole placed at r'=405nm from the fiber axis with lower imaginary part of epsilon.
E_dipolex = readdlm(join(["data/D2/dipolex_E_N_1_lam_852_eps_4_0.001_a_300_b_300_c_6_x_","330","_y_0_q_0_2_201.dat"]), header=false)
Ex_dx_r0=E_dipolex[:,5]+im*E_dipolex[:,6];
Ey_dx_r0=E_dipolex[:,7]+im*E_dipolex[:,8];
Ez_dx_r0=E_dipolex[:,9]+im*E_dipolex[:,10];
E_dipoley = readdlm(join(["data/D2/dipoley_E_N_1_lam_852_eps_4_0.001_a_300_b_300_c_6_x_","330","_y_0_q_0_2_201.dat"]), header=false)
Ex_dy_r0=E_dipoley[:,5]+im*E_dipoley[:,6];
Ey_dy_r0=E_dipoley[:,7]+im*E_dipoley[:,8];
Ez_dy_r0=E_dipoley[:,9]+im*E_dipoley[:,10];
E_dipolez = readdlm(join(["data/D2/dipolez_E_N_1_lam_852_eps_4_0.001_a_300_b_300_c_6_x_","330","_y_0_q_0_2_201.dat"]), header=false)
Ex_dz_r0=E_dipolez[:,5]+im*E_dipolez[:,6];
Ey_dz_r0=E_dipolez[:,7]+im*E_dipolez[:,8];
Ez_dz_r0=E_dipolez[:,9]+im*E_dipolez[:,10];
Gxx_rad_r0=integrate(k_vec[1:breakpoint],Ex_dx_r0[1:breakpoint],Trapezoidal())*(ω/c)^3/pi^2*au*4.0/3.;
Gyy_rad_r0=integrate(k_vec[1:breakpoint],Ey_dy_r0[1:breakpoint],Trapezoidal())*(ω/c)^3/pi^2*au*4.0/3.;
Gzz_rad_r0=integrate(k_vec[1:breakpoint],Ez_dz_r0[1:breakpoint],Trapezoidal())*(ω/c)^3/pi^2*au*4.0/3.;
gamma_rad_BEM_r0=imag(Gxx_rad_r0+Gyy_rad_r0+Gzz_rad_r0)/3/gamma0;

# Plot out gamma_rad as a function of dipole position.
using PyPlot
figure(figsize=(16,3));
subplot(1,2,1)
plot((1:breakpoint)/100.,real(Ex_dx[1:breakpoint,4]),"r-")
plot((1:breakpoint)/100.,imag(Ex_dx[1:breakpoint,4]),"b-")
ylabel(L"E(\beta)")
xlabel(L"\beta/k_0")
legend(["Real","Imag"],loc="upper left")

subplot(1,2,2)
a=300.;
#plot(rp0_test[1,:]/1.e-9, 1+sum(gamma_rad,2), "r-", linewidth=2.0)
plot(rp_BEM-a/2,real(gamma_rad_BEM_rp_average),"b-");
plot(330-a/2,real(gamma_rad_BEM_r0)+1.,"mo");
plot(rp_BEM-a/2,gamma_rad_BEM_rp_sigmap,"r--");
plot(rp_BEM-a/2,gamma_rad_BEM_rp_sigmam,"m.-");
plot(rp_BEM-a/2,gamma_rad_BEM_rp_pi,"k.");
xlabel("(r'-a/2)/nm");
ylabel(L"\Gamma_{rad}^{BEM}/\Gamma_0");
#ylim([0,1.2])
legend(["average",L"average_{no\,\,loss}",L"\sigma_+",L"\sigma_-",L"\pi"],loc="upper right",fontsize=12);
#gamma_rad_BEM_rp_sigmap
```

The first figure (on the left) shows the real and imaginary parts of the $E_x$ component changes as a function of $\beta=k_z$ in the radiative mode regime.

The second figure (on the right) shows the decay rates caused by non-guided modes and decomposed into the $\sigma_\pm$ and $\pi$ transitions and their average.
In calculating the averaged non-guided mode induced decay rates, we define
$$\begin{align}
\frac{\Gamma_{rad}^{ave}}{\Gamma_0} &= 1+ \frac{\sum_{i=x,y,z}\mathrm{Im}\left[\mathbf{e}_i^*\cdot \mathbf{G}_{ind,rad}(\mathbf{r}',\mathbf{r}')\cdot \mathbf{e}_i\right]}{\sum_{i=x,y,z} \mathrm{Im}\left[\mathbf{e}_i^*\cdot \mathbf{G}_0(\mathbf{r}',\mathbf{r}')\cdot \mathbf{e}_i\right]}\\
&=1+ \frac{\sum_{i=x,y,z}\mathrm{Im}\left[\mathbf{e}_i^*\cdot \mathbf{G}_{ind,rad}(\mathbf{r}',\mathbf{r}')\cdot \mathbf{e}_i\right]}{3\mathrm{Im}\left[G_0(\mathbf{r}',\mathbf{r}')\right]}\\
&= 1+ \frac{\mathrm{Tr}\left\{\mathrm{Im}\left[ \mathbf{G}_{ind,rad}(\mathbf{r}',\mathbf{r}')\right]\right\}}{3\mathrm{Im}\left[G_0(\mathbf{r}',\mathbf{r}')\right]}
\end{align}$$
with the free-space Green's function scaled by $G_0(\mathbf{r}',\mathbf{r}';\omega)=\frac{2}{3}k_0^3$ in the CGS units as $\mathbf{G}_0=G_0\mathbb{1}$ and the numerical result of the waveguide-induced Green's function tensor elements
$$ G_{ind,rad}^{ij}(\mathbf{r}',\mathbf{r}')=\frac{2k_0^2*a.u.}{3\pi^2}\int_{-k_0}^{k_0} d\beta E_j^i(\mathbf{r}')=\frac{4k_0^2*a.u.}{3\pi^2}\int_{0}^{k_0} d\beta E_j^i(\mathbf{r}')$$
calculated via BEM by putting a unit dipole in atomic units orientated along $j$ direction while the $i$-th electric field component is measured at the dipole position.
We have used $\varepsilon=4+0.01i$ (with a small loss) and $\varepsilon=4$ (without loss) to calculate the Green's function tensors as well as the averaged corresponding decay rate contributions.
These two cases doesn't show a noticeable difference with one sampling data point as shown in the purple dot points.

In calculating the corresponding contributions from the $\sigma_\pm$ and $\pi$ transitions of the atoms, we have defined
$$\begin{align}
\frac{\Gamma_{rad}^{\mathbf{e}_q}}{\Gamma_0} &= 1+ \frac{\mathrm{Im}\left[\mathbf{e}_q^*\cdot \mathbf{G}_{ind,rad}(\mathbf{r}',\mathbf{r}')\cdot \mathbf{e}_q\right]}{ \mathrm{Im}\left[\mathbf{e}_q^*\cdot \mathbf{G}_0(\mathbf{r}',\mathbf{r}')\cdot \mathbf{e}_q\right]}
=1+ \frac{\mathrm{Im}\left[\mathbf{e}_q^*\cdot \mathbf{G}_{ind,rad}(\mathbf{r}',\mathbf{r}')\cdot \mathbf{e}_q\right]}{\mathrm{Im}\left[G_0(\mathbf{r}',\mathbf{r}')\right]},
\end{align}$$
where the three orthogonal dipole transition bases
$$\begin{align}
\mathbf{e}_\pm &=\mp \frac{\mathbf{e}_{\tilde{x}}\pm i\mathbf{e}_{\tilde{y}}}{\sqrt{2}}\\
\mathbf{e}_0 &=\mathbf{e}_{\tilde{z}}
\end{align}$$
correspond to the $\sigma_\pm$ and $\pi$ transitions of the atoms.
These basis vectors are quantization-axis dependent, but in our calculation above, we assume the $z$-axis or the waveguide axis is the quantization axis, and hence $\mathbf{e}_{\tilde{x}}=\mathbf{e}_x$, $\mathbf{e}_{\tilde{y}}=\mathbf{e}_y$ and $\mathbf{e}_{\tilde{z}}=\mathbf{e}_z$.

As you can see, when the atoms are placed around $200$nm from the nanofiber surface, different dipole transitions make a noticeable difference.

To wrap up, the total decay rates
$$\begin{align}
\Gamma \propto \mathbf{e}_d^*\cdot \mathrm{Im}\left[\mathbf{G}(\mathbf{r}',\mathbf{r}')\right]\cdot \mathbf{e}_d,
\end{align}$$
where $$\mathbf{G}=\mathbf{G}_{hom}+\mathbf{G}_{inhom}=\mathbf{G}_{rad,free-space}+\mathbf{G}_{rad,ind}+\mathbf{G}_{gyd}$$
with $\mathbf{G}_{rad,free-space}=\mathbf{G}_0$ and $\mathrm{Im}\left[ \mathbf{G}_0(\mathbf{r}',\mathbf{r}')\right]=\frac{2}{3}\mathbb{1}$.
In the end, the total decay rate
$$\begin{align}\Gamma = \Gamma_{rad}+\Gamma_{gyd}=\Gamma_{rad,free-space}+\Gamma_{rad,ind}+\Gamma_{gyd}\end{align}$$
with $\Gamma_{rad,free-space}=\Gamma_0$.

```julia
# Export data to a MAT file.
using MAT
matopen("data/D2/Julia_swg_GFT_decayrates_rad_D2.mat", "w") do matfile
    write(matfile,"omega0",ω)
    write(matfile,"rp_BEM",collect(rp_BEM))
    write(matfile,"gamma0",gamma0)
    write(matfile,"e_dipole_sigmap",e_dipole_sigmap)
    write(matfile,"e_dipole_sigmam",e_dipole_sigmam)
    write(matfile,"e_dipole_pi",e_dipole_pi)
    write(matfile,"gamma_rad_BEM_rp_average",gamma_rad_BEM_rp_average)
    write(matfile,"gamma_rad_BEM_rp_sigmap",gamma_rad_BEM_rp_sigmap)
    write(matfile,"gamma_rad_BEM_rp_sigmam",gamma_rad_BEM_rp_sigmam)
    write(matfile,"gamma_rad_BEM_rp_pi",gamma_rad_BEM_rp_pi)
    write(matfile,"a",a)
    write(matfile,"GFT_rad_rp",GFT_rad_ind)
    write(matfile,"Gxx_rad_rp",Gxx_rad_ind)
    write(matfile,"Gxy_rad_rp",Gxy_rad_ind)
    write(matfile,"Gxz_rad_rp",Gxz_rad_ind)
    write(matfile,"Gyx_rad_rp",Gyx_rad_ind)
    write(matfile,"Gyy_rad_rp",Gyy_rad_ind)
    write(matfile,"Gyz_rad_rp",Gyz_rad_ind)
    write(matfile,"Gzx_rad_rp",Gzx_rad_ind)
    write(matfile,"Gzy_rad_rp",Gzy_rad_ind)
    write(matfile,"Gzz_rad_rp",Gzz_rad_ind)
end
```

```julia

```