# Revealing Roaming  on the Double Morse Potential Energy Surface with Lagrangian Descriptors

## Introduction
\label{sec:intro}

In this chapter, we analyse the phase space structure of the roaming dynamics in a two degree of freedom potential energy surface consisting of two identical planar Morse potentials separated by a distance. This potential energy surface was previously studied in \cite{carpenter2018dynamics}, and it has two potential wells surrounded by an unbounded flat region containing no critical points. We study the phase space mechanism for the transference between the wells using the method of Lagrangian descriptors.

The roaming mechanism for chemical reactions was reported in \cite{townsend2004roaming}. This paper inspired and motivated further work on the topic that is continuing. Some recent reviews of the topic are  \cite{Bowman2011Suits, Bowman2011roaming, BowmanRoaming, suits2008, Mauguiere2017}.

Following \cite{carpenter2018dynamics}, a concise description of the roaming mechanism that seems to capture all such reactive phenomena attributed to roaming to date is the following. An energised molecule, which appears on the verge of dissociating into two fragments, instead exhibits behaviour where one fragment begins to rotate about the other in the flat region of the potential energy surface, culminating in the two fragments re-encountering each other in a reactive event. This reactive event may be constitutionally identical to one for which a familiar transition state associated with an index one saddle can be identified, but the roaming reaction path is observed to avoid the intrinsic reaction path.

The double Morse potential energy surface allows us to analyse key feature of the reaction dynamics which appear to underlay roaming mechanism. In particular, the double Morse has a flat roaming region where it can be rigorously proven that there are no saddle points. For explaining reaction mechanisms in the context of the potential energy surface index one saddles have been a fundamental building block. This has motivated efforts to locate index one saddle points that can be attributed as the mechanism for roaming (roaming saddles).

However, the work in \cite{carpenter2018dynamics} showed that roaming mechanism in the double Morse potential occurs without the influence of saddle points. In particular, it was demonstrated that roaming was governed by phase space structures governing dynamics in the flat region of the potential energy surface. These phase space structures are normally hyperbolic invariant manifolds (NHIMs) \cite{wiggins_book1994,Wiggins2001,Uzer2002, Wiggins08,Wiggins2016}, which in the case of 2 DoF Hamiltonian systems are unstable periodic orbits \cite{Mauguiere2015}.

The main issue of interest in \cite{carpenter2018dynamics} was the motion of trajectories between the two wells and the intermediary dynamics in the flat region. It was conjectured that three types of unstable periodic orbits mediated the dynamics. The description of roaming given in \cite{carpenter2018dynamics} was based on the behaviour of trajectories initiated on periodic orbit dividing surfaces associated with the three types of periodic orbits. The trajectory behaviour confirmed the conjecture. However, this work did not examine the phase space structures that determine the different types of trajectory behaviour. Such an analysis would involve an understanding of the geometry of the stable and unstable manifolds of the periodic orbits in phase space, similar in spirit to the analysis that has been carried out in \cite{krajvnak2018phase, krajvnak2018influence}.

In this chapter, we will determine the phase space pathways for the roaming mechanism in the double Morse oscillator using the method of Lagrangian descriptors. Lagrangian descriptors are a trajectory diagnostic that has proven useful for revealing phase space structures in dynamical systems.  The method was originally developed to reveal fluid flow structures in Lagrangian transport problems (hence the name Lagrangian descriptors) \cite{chaos}.  However, in recent years, the ease of implementation and interpretation of the method has applied in fields far beyond fluid mechanics. In particular, in theoretical chemistry the method has seen a diverse collection of applications to problems in chemical reaction dynamics in recent years, see, e.g., \cite{patra2018detecting, craven2016deconstructing, craven2017lagrangian, craven2015lagrangian, junginger2016transition, revuelta2017transition, junginger2017chemical, feldmaier2017obtaining, JH2015}.

The method facilitates a high-resolution approach for exploring high dimensional phase space with low dimensional sets.  It can also be applied to both Hamiltonian and non-Hamiltonian systems \cite{lopesino2017} as well as to systems with arbitrary, even stochastic, time-dependence \cite{balibrea2016lagrangian}. Moreover, Lagrangian descriptors can be applied directly to trajectory data sets, without the need for an explicit dynamical system \cite{mmw14}.

The classical Lagrangian descriptor field is computed by selecting a region of phase space and then choosing a grid of initial conditions for trajectories in this set. Points in the set are assigned a value according to the arc length of the trajectory starting at that initial condition after integration for a fixed, finite time, both backward and forward in time (all initial conditions in the grid are integrated for the same time). The idea underlying Lagrangian descriptors is that the influence of phase space structures on trajectories will result in differences in arc length of nearby trajectories. This has been verified and quantified in terms of the notion of singular structures in the Lagrangian descriptor fields, which are easy to recognise visually \cite{ cnsns, lopesino2017}.

Trajectories are the elementary objects that are used to explore phase space.  In fact, phase space structure is built from trajectories and their behaviour. For high dimensional phase space this approach is problematic and prone to issues of interpretation since a tightly grouped set of initial conditions may result in trajectories that become far with respect to each other in high dimensional phase space. The method of Lagrangian descriptors takes a new and different approach by encoding the phase space structure in the initial conditions of trajectories, rather than the precise location of their futures and pasts (after a specified amount of time). Hence, a low dimensional set of phase space can be selected and sampled with a grid of initial conditions of high resolution. Since the phase space structure is encoded in the initial conditions of the trajectories, no resolution in the chosen slice of phase space is lost as the trajectories evolve in time.

This chapter is outlined as follows. In section \ref{sec:DMPES}, we recall the main features of the double Morse potential energy surface and resulting Hamiltonian system as described in \cite{carpenter2018dynamics} that are relevant to our analysis.  In section \ref{sec:config}, we define the features of the double Morse potential energy surface that are relevant to our analysis.  In Section \ref{sec:LD} we describe Lagrangian descriptors. In section \ref{sec:PO}, we show how Lagrangian descriptors can be used to compute periodic orbits and their stable and unstable manifolds. In section \ref{sec:roaming}, we describe the phase space pathways for roaming using the stable and unstable manifolds of the periodic orbits.

## Development of the Problem
\label{sec:DMPES}

The two degrees of freedom Hamiltonian system that we consider is the sum of two identical planar Morse potentials \cite{Morse29} separated by a distance $2b$:

\begin{eqnarray}
V (x, y) & = & D_e \left(1 - \exp\left(-\sqrt{\frac{k}{D_e}}  (\sqrt{(x-b)^2 + y^2} - r_e)\right)  \right)^2 \nonumber \\
&  + &
D_e \left(1 - \exp\left(-\sqrt{\frac{k}{D_e}}  (\sqrt{(x+b)^2 + y^2} - r_e)\right)  \right)^2 -D_e.
\label{eq:2Morse_pot}
\end{eqnarray}

and kinetic energy of the form:

\begin{equation}
T(p_x, p_y) = \frac{p_x^2}{2m} + \frac{p_y^2}{2m}.
\label{eq:KE}
\end{equation}

Therefore the Hamiltonian is:

\begin{equation}
H(x, y, p_x, p_y) = \frac{p_x^2}{2m} + \frac{p_y^2}{2m} + V (x, y).
\label{eq:Ham}
\end{equation}

Following \cite{carpenter2018dynamics}, the parameters for the Hamiltonian are chosen as follows:

\begin{equation}
D_e = 100, \, \, r_e =1, \, \, k=200, m=1, b=3.
\label{eq:params}
\end{equation}

# Features of the Double Morse Potential Energy Surface

\caption{Double Morse potential energy in colour scale. The contour lines black on the figure are the equipotential lines for different values of the potential energy. The potential has two wells and two forbidden regions, each one in the centre of each well. The potential energy converges to a constant value in the asymptotic region.
}\label{fig:V_c}

\caption{Double Morse potential energy surface in colour scale. The potential has a saddle point between the two wells.}
\label{fig:V}

The Figs. \ref{fig:V_c}, and \ref{fig:V} show plots of the potential energy in colour scale and equipotential lines. At the origin, $(x=0, y=0)$ the potential energy has an index one saddle. The potential energy evaluated in the saddle point is $V(0,0) = E_{s}$. This index one saddle point exists for all values of $b$, and $E_{s}$ is below $D_e$ in this example, see figure \ref{fig:V_c}.

The double Morse potential energy function goes exponentially fast to zero in the asymptotic flat region. This almost flat region plays an essential role in the roaming phenomenon, and it is called the roaming region.

# Lagrangian Descriptors and two degree of freedom Hamiltonians

\label{sec:LD}

The classical Lagrangian descriptor is the arc length of a trajectory.  In the present article, we are going to use a variant, the integral of the square of the infinitesimal arc length proposed in \cite{lopesino2017}. Let us consider the autonomous ordinary differential equations

\begin{eqnarray}
\frac{d \mathbf{x}}{dt} = \mathbf{v}(\mathbf{x}), \quad \mathbf{x} \in \mathbb{R}^n \;,\; t \in \mathbb{R}
\end{eqnarray}

  where $\mathbf{v}(\mathbf{x},t) \in C^r$ ($r \geq 1$) in $\mathbf{x}$ and continuous in time. The definition of Lagrangian descriptor depends on the initial condition $\mathbf{x}_{0} = \mathbf{x}(t_0)$, on the time interval $[t_0+\tau_{-},t_0+\tau_{+}]$, and takes the form,

\begin{eqnarray}
M(\mathbf{x}_{0},t_{0},\tau_{+},\tau_{-}) & = & M_{+}(\mathbf{x}_{0},t_{0},\tau_{+}) - M_{-}(\mathbf{x}_{0},t_{0},\tau_{-}) \nonumber  \\
& = &
\displaystyle{ \int^{t_{0}+\tau_{+}}_{t_{0}} \sum_{i=1}^{n} |\dot{x}_{i}(t;\mathbf{x}_{0})|^2 \; dt - \int^{t_{0}}_{t_{0}+\tau_{-}} \sum_{i=1}^{n} |\dot{x}_{i}(t;\mathbf{x}_{0})|^2 \; dt ,} \label{eq:LD}
\end{eqnarray}

  where $\tau_{+} \geqslant 0$ and $ \tau_{-} \leqslant 0 $  are freely chosen parameters and the overdot symbol represents the derivative with respect to the time $t$.

The phase space of a Hamiltonian system with two degrees of freedom has 4 dimensions, considering the conservation of the energy, it is possible to represent the dynamics of the system in the 3-dimensional constant energy manifold. In the constant energy manifold, it is possible to visualise the dynamics and identify the essential structures to understand the dynamics.

The periodic orbits are essential structures to understand the dynamics of the system in the constant energy manifold. Around a stable periodic orbit, the KAM-tori confine the trajectories in the region defined by the tori. In contrast, the dynamics in a neighbourhood of an unstable hyperbolic periodic orbit has a different nature. Its stable and unstable manifolds intersect in the hyperbolic periodic orbit and direct the trajectories in the neighbourhood. The definition of the stable and unstable manifolds $W^{s/u} (\Gamma)$ of the periodic unstable orbit $\Gamma$ is the following,

\begin{equation}
 W^{s/u} (\Gamma) = \lbrace  \mathbf{x}  \vert \mathbf{x}(t)  \rightarrow  \Gamma, t \rightarrow \pm \infty \rbrace.
\end{equation}

In the Hamiltonian system with 2 degrees of freedom, $W^{s/u} (\Gamma)$ has 2 dimensions and form impenetrable barriers in the constant energy manifold. The segments of the stable and unstable manifolds direct the transport between different regions \cite{Kovacks2001}.

Another important property of the stable and unstable manifolds related to the chaotic dynamics is that, if a stable manifold and an unstable manifold intersect transversally at one point, then an infinite number of transversal intersections between them exist. The structure generated by the union of the stable and unstable manifolds is called tangle and defines a set of tubes that direct the dynamics in the phase space. The trajectories in a tube never cross the boundaries of a tube. This fact is a consequence of the uniqueness of the solution of the ordinary differential equations.

The Lagrangian descriptors are convenient tools to explore the phase space structure, in particular, to find stable and unstable manifolds of periodic orbits. To understand the basic idea that underpins the detection, let us consider the behaviour of the trajectories in a neighbourhood of stable manifold $W^{s}(\Gamma)$. The trajectories in $W^{s} (\Gamma)$ converge to the periodic orbit $\Gamma$, and the nearby trajectories in the neighbourhood of $W^{s} (\Gamma)$ having similar behaviour for a finite interval of time. After this interval of time, the trajectories move away from the stable hyperbolic periodic orbit $\Gamma$ following the unstable manifold $W^u(\Gamma)$. This different behaviour generates the singularities in the Lagrangian descriptors \cite{lopesino2017}.

The variant of Lagrangian descriptor in the equation \eqref{eq:LD} has negative sing between the two integrals. This sign allows differentiating between the stable and unstable manifolds on the Lagrangian descriptor plots. The set of singularities on integral with $\tau_+$ shows the stable manifolds and the set of singularities on integral with $\tau_-$ the unstable manifolds. The superposition of the two paters of singularities revels the tangle between the stable and unstable manifolds.

# Periodic orbits and their Stable and Unstable Manifolds

\label{sec:PO}

In order to show the dynamics of this two degree of freedom Hamiltonian system in the regime of dissociation and roaming, let us consider a fixed value of the total energy above the dissociation energy threshold, $E = 101 > D_e $. In this case, the trajectories close to the wells can cross the interaction region and escape to infinity. The trajectories that start in the interaction region are sensitive to initial conditions. That means that two trajectories that start arbitrary close can finish in a different direction in the asymptotic region. This phenomenon is called chaotic scattering and is a kind of transient chaos \cite{Tel_book, Tel2015}.

The chaotic scattering is a common phenomenon in the open Hamiltonian system. Detailed studies of chaotic scattering in Hamiltonian systems with two and three degrees of freedom are in the recent literature \cite{Sanjuan2013, Zapfe2010, Drotos2014, Gonzalez2012}. The tangle between the stable and unstable manifolds generates rich dynamics in the interaction region. However, the dynamics is simple in the asymptotic region, where the trajectories converge to straight lines.

The double Morse potential energy $V$ satisfies the asymptotic condition $Vr^2 \rightarrow 0$ when $r\rightarrow \infty$, then the linear momenta of the trajectories that reach the asymptotic region converge to a constant value \cite{Newton_book}.

The trajectories associated with dissociation reactions start in one well and finish in the asymptotic region. The trajectories associated with roaming reactions start in one well, travel outside the well in the roaming region where the potential energy is almost flat, and reach the other potential well.

In order to define the transport between the two potential wells and the asymptotic region, the configuration space is divided into three regions, see figure \ref{fig:transport}. Two circumferences around each potential well, define the regions $A$ and $B$, and another external circumference around the two wells defines the asymptotic region $C$ where the trajectories are close to straight lines and escape to infinity.

\caption{ Roaming and dissociation trajectories. The trajectories in the figure start in the region $A$ until intersecting the region $B$ or go to the asymptotic region $C$ defined by an outer circle. The trajectories that travel from one well to the other well are the roaming trajectories, and the trajectories that do not reach the other well are the dissociation trajectories.}
\label{fig:transport}

In a previous work \cite{carpenter2018dynamics}, it has been found that the roaming and dissociation trajectories in the system are related to three types of periodic orbits. Let be $\Gamma_1$, $\Gamma_2$, and $\Gamma_3$ periodic orbits representatives of each type. The projection of the three periodic orbits in configuration space is shown in Fig. \ref{fig:orbits_2D}. The projection of periodic orbit $\Gamma_1$ encircles the interaction region and the two potential wells. The projection of $\Gamma_2$ also encircles the two wells, but self intersects at the origin. The projection of $\Gamma_3$ encircles just one well.

In the configuration space, the projection of periodic orbits $\Gamma_2$, $\Gamma_3$ are close, but it is clear that the orbits are separated in the constant energy manifold, see Fig. \ref{fig:orbits_3D}. The three orbits are very close to each other's in a neighbourhood of the point $(x=8.45,y=0,p_x=0)$.

As a consequence of the symmetries $x\rightarrow -x$ and $y \rightarrow -y$ of the potential energy $V(x,y)$, there are 4 orbits type I, 2 orbits type II, and 4 orbits type III in the phase space. It is necessary to consider only one element of every type for the numerical calculations.

\caption{Projection of the periodic orbits $\Gamma_1$, $\Gamma_2$, and $\Gamma_3$ in the configuration space.}
\label{fig:orbits_2D}

\caption{ Periodic orbits $\Gamma_1$, $\Gamma_2$, and $\Gamma_3$ in the constant energy parameterise by coordinates $(x,y,p_x)$.}
\label{fig:orbits_3D}

A common method to find the periodic orbits is based on the Poincar\'e map defined on a 2-dimensional Poincar\'e surface. The Poincar\'e map is a function from the Poincar\'e surface to itself obtained by following trajectories from one intersection of the Poincar\'e surface to the next. The fixed points of the Poincar\'e map are the intersections between the periodic orbits and the Poincar\'e surface. In this system, a natural choice of Poincar\'e surface is the canonical plane $x$-$p_x$ with $y=0$ due to the symmetry $y\rightarrow -y$ of the potential energy. The three periodic orbits $\Gamma_1$, $\Gamma_2$, and $\Gamma_3$ are hyperbolic and considerably unstable. In order to calculate the eigenvalues and eigenvectors of the linear approximation to the Poincar\'e map around the fixed points associated with the three types of periodic orbits is necessary to use multi-precision integrators. These calculations are done with the Taylor integrator method implemented in Julia \cite{Perez2019}. The periods of the periodic orbits and their associated eigenvalues are in Table I. The product between the eigenvalues is close to 1, then the calculations agree with the Hamiltonian properties of the system.

|$\Gamma_1$ | $T_{1} =  30.3774  $ | $\lambda_{1,Max}\simeq 2.0314 \times 10^7$ | $\lambda_{1,min}\simeq 4.9224 \times 10^{-8}$ | $\lambda_{1,Max} \lambda_{1,min} \simeq 0.99999$ |

|$\Gamma_2$ | $T_{2} = 29.1727$    | $\lambda_{2,Max}\simeq 3.4627 \times 10^{12}$ | $\lambda_{2,min}\simeq 2.8965 \times  10^{-13}$ | $\lambda_{2,Max} \lambda_{2,min} \simeq 1.00299$ |

|$\Gamma_3$ | $T_{3} =  14.5671  $ | $\lambda_{3,Max}\simeq 1.8019 \times 10^6$ | $\lambda_{3,min} \simeq 5.5382 \times 10^{-7}$ |  $\lambda_{3,Max} \lambda_{3,min} \simeq 0.99794$ |

\caption{Periods and eigenvalues of the periodic orbits $\Gamma_1$, $\Gamma_2$, and $\Gamma_3$.}

Using the eigenvalues and their corresponding eigenvectors is possible to construct the stable and unstable manifolds of the three periodic orbits. However, the construction and visualisation of the stable and unstable manifolds are not simple tasks because of the size of the eigenvalues, $\lambda_{Max} >> 1$ and $\lambda_{min} \cong 0$, and because the Poincar\'e map requires that the trajectories intersect the Poincar\'e section again. For example, the trajectories in the unstable manifold of the periodic orbit $\Gamma_1$ that go directly to the asymptotic region intersect the Poincar\'e section only for a finite number of iterations before to converge to straight lines. In general, it is not possible to obtain a global visualisation of the tangles between the stable and unstable manifolds using the Poincar\'e map.

An appropriate approach that allows an easier visualisation of the stable and unstable manifolds even in this situations is the use of indicators for chaotic regions, like fast Lyapunov indicators (FLI) \cite{Lega2016}, mean exponential growth factor of nearby orbits (MEGNO) \cite{Cicotta2016}, the smaller (SALI) and the generalized (GALI) alignment indices \cite{Skokos2016}, delay time functions, scattering functions \cite{Gonzalez2012}, and Lagrangian descriptors. The Lagrangian descriptors contain information of the stable and unstable manifolds that intersect the set of initial conditions and do not require that the trajectories intersect any particular predefined set again, as mentioned in section \ref{sec:LD}. It is important to notice that the trajectories should be integrated long enough time to reach a neighbourhood of the geometric structures that generate different abrupt behaviour. In the other case, the chaotic indicator does not show the structure clearly, even if the set of initial conditions intersect the structure.

The calculations of the Lagrangian descriptors are done with the classical ninth order Vernet integrator implemented in Julia \cite{Rackauckas2017}. In this system, the Lagrangian descriptors require standard double-precision to reveal segments of the invariant manifolds. The computational cost necessary to see a signature of the stable and unstable manifolds in the Lagrangian descriptor plots is a fraction of computational cost necessary using the Poincar\'e map. The reason for this difference is related to the computational cost of the integration with the multi-precision libraries and the fact that the calculation of the Lagrangian descriptor does not require that the trajectories cross the set of initial conditions again.

The Fig. \ref{fig:orbits_2D} shows that  $\Gamma_1$, $\Gamma_2$, and $\Gamma_3$ intersect the $x$ axis in a neibourhood of the point $(x= 8.5,y=0)$ and their momentun at the intersection are $(p_{y} < 0, p_x = 0)$. To find these periodic orbits, using the Lagrangian descriptors, is natural to consider initial conditions on the canonical conjugate plane $p_{x_0}$--$x_0$ with $y_0 = 0$ and initial momentum $ p_{y_0} < 0$. The Lagrangian descriptor evaluated in this set of initial conditions for different times is shown in Fig.\ref{fig:LD_time}. When the size of the interval of integration $[\tau_-,\tau_+]$ is increased, the Lagrangian descriptor reveals intersection of the the stable and unstable manifolds with the set of inital conditions. The intersections between the stable and unstable manifolds is the invariant chaotic set. The periodic orbits $\Gamma_1$, $\Gamma_2$, and $\Gamma_3$ are contained in this invariant chaotic set.

\caption{Lagrangian descriptor $M$ evaluated on the plane $p_{x_0}$-$x_0$ with initial $y_0 = 0$ and initial momentum $(p_{y_0} < 0, p_x = 0)$ for different values of $\tau_+$ and $\tau_-$. To show the symmetry of $M$ and the invariant manifolds for this initial conditions, $\tau_+ = - \tau_-$ is selected. When the value of $\tau_+$ is increased, the structure of the tangle between the stable and unstable manifolds is reveled by the abrupt jumps in the value of the Lagrangian descriptor where it converge to a non-differentiable function. The intense blue and yellow lines correspond to segments of the unstable manifolds and stable manifolds respectively.}
\label{fig:LD_time}

In order to show clearly the picks generated by invariant stable and unstable manifolds of one of the three hyperbolic periodic orbits in the Lagrangian descriptor plots and find the periodic orbits, let us consider the symmetry line $y_0 = 0$ in an interval that contain $\Gamma_2$. The Lagrangian descriptor in Fig. \ref{fig:LD_time} with $\tau_{-} = -\tau_+$ evaluated on this line is identically zero due to the symmetries of the system. For this reason, is convenient to consider the Lagrangian descriptor with $\tau_{-} = 0$ and $\tau_+ > 0$, the results are in the Fig. \ref{fig:LD_line_x0}. When the integration time $\tau_+$ is increased enough, the peak generated for the different behaviour of the trajectories reveals the intersections of the stable manifold of the periodic orbit with the $x_0$ axis. Due to the symmetry of the stable and unstable manifolds respect to the $x_0$ axis, this intersection coincides with the intersection with the unstable manifold of the periodic orbit. Finally, the intersection point between the stable and unstable manifolds is the intersection with the periodic orbit with the $x_0$ axis. It is important to notice that the integration time to generate the peak is different from the period of the orbit.

Another simple way to find the intersections between the stable and unstable manifolds is to change the negative sing in the definition \eqref{eq:LD}, then $M_+ + M_- > 0 $ and the points where is this quantity is maximal are the intersections between the stable and unstable manifolds.

\caption{ Lagrangian descriptors evaluated on an interval of the line 𝑥0. The integration times are 𝜏−=0 and 𝜏+>0. The different colour lines in the plots correspond to different values of 𝜏+. The peaks indicate the intersection of the periodic orbit $\Gamma_2$ with the line 𝑥0.
\label{fig:LD_line_x0}}

The structure of the tangle is very rich in the interaction region. To see this structure let us consider the Lagrangian descriptor evaluated on large area of the canonical conjugate plane $p_{x_0}$-$x_0$ at $y_0=0$ with initial $p_{y_0}<0$. The Lagrangian descriptors on the Fig. \ref{fig:LD_plane_y=0} shows the symmetries of the tangles and their complicated structure characteristic of the open chaotic Hamiltonian systems with two degrees of freedom. In the figure, some lines converge directly to constant values of the momentum in the asymptotic region. These lines correspond to the branches of stable and unstable manifolds that go directly to the asymptotic region, close to these branches there are the lobules where the particles escape or enter to the interaction region. Examples of the lobule dynamics in open Hamiltonian systems and chaotic scattering are in \cite{Gonzalez2012,Zapfe2010}. There are three disconnected regions: two unbounded regions on the extremes and one bounded region in the middle surrounded by a white region. The white region corresponds to points where the momentum $p_{x_0}$ is not defined for this value of the energy.

\caption{Lagrangian descriptor $M$ evaluated on the plane $p_{x_0}$-$x_0$ at $y_0=0$ with initial $p_{y_0}<0$.}
\label{fig:LD_plane_y=0}

# Phase Space Pathways for Roaming

\label{sec:roaming}

An important set in the phase space to analyse the transport in the system is the dividing surface of a periodic orbit\cite{Pechukas1979,carpenter2018dynamics}. The projection of the periodic orbit in the configuration space defines a closed curve and a region inside the curve. The dividing surface is a natural surface to analyse all the trajectories of the system that cross the boundary of this region in the configuration space. The algorithm to construct the dividing surface of the periodic orbit $\Gamma$ consists of three steps:

1. Project the periodic orbit $\Gamma$ on the configuration space.

2. Construct the circumference on momentum plane using the equation

$$  p^2_x + p^2_y = 2m( E - V(x,y) )$$

 for every point $(x,y)$ on the projection of the orbit $\Gamma$ in the configuration space.

3. Take the union of all these circumferences in the phase space to construct the dividing surface.

The dividing surface constructed in this way has three relevant properties for analysis of the transport: The first one, the periodic orbit $\Gamma$ and
the orbit with the same projection in configuration space but opposite momentum are contained in the same dividing surface. This fact is a consequence of the time-reversal invariance of the system. The second property is that the periodic orbits used to construct the dividing surface are the boundaries between the regions where the trajectories enter into the phase space region contained by the dividing surface and trajectories that left the same region. The third property, the flux through the dividing surface is minimal. That means, if the dividing surface is deformed, the flux through it increases. The demonstrations of these properties of the dividing surfaces of periodic orbits are in \cite{Pechukas1979}, and for NHIMs with more dimensions in \cite{Waalkens_2004}.

In the system, the periodic orbits $\Gamma_1$, $\Gamma_2$, and $\Gamma_3$ are not generated by any saddle in the potential energy. Their projection in the configuration space are curves that enclose an area. In the case of unstable periodic orbits associated with saddles points, the projection on the configuration space is a line that does not enclose any area. The extremes of the projection are the returning points where the periodic orbit has zero momentum. The dividing surface, in this case, is a surface genus 0 (topologically equivalent to a sphere). An example of this kind of periodic orbits and their respective dividing surface is in \cite{Rafa2019}.

The projection in configuration space of the orbits $\Gamma_1$, $\Gamma_3$ do not have self-intersections; for this reason, the corresponding dividing surfaces are genus 1, (a torus). Another example of this kind of dividing surface is in \cite{Collins_2015}. The projection of the periodic orbit $\Gamma_2$ has an intersection at the origin, the dividing surface, in this case, is a surface genus 2.

A natural parametrization of the dividing surface is given by the arc length $l_0$ of the respective periodic orbit in the phase space and the angle between the initial momentum and the $x_0$ axis, $\phi_0 = \displaystyle{ \arctan{p_{y_0}/p_{x_0}}}$. The next calculation use this parametrisation to analyse the trajectories that cross each of the three dividing surfaces.

The fate map is an appropriate tool to classify the trajectories associated with rooming and dissociation in phase space. A fate map shows the origin and destiny of the trajectories, considering the evolution for an interval of time. In this case, the origin and destiny of the trajectories are the regions $A$, $B$, and $C$ defined in the previous section. The procedure to calculate the fate map on a dividing surface is the following: consider a point on the dividing surface, integrate forward and backwards the trajectory that crosses this point on the dividing surface until each extreme of the trajectory reaches one of the three regions, and label this point with the corresponding fate. The left side of Fig. \ref{fig:FM_LD} show the fate map for each dividing surface. Each different colour in the fate map indicates different transport between regions $A$, $B$, and $C$. The fate maps evaluated on the dividing surfaces of the periodic orbits $\Gamma_1$ and $\Gamma_2$ have all nine possibilities of transport between the regions $A$, $B$, and $C$. In the case for the dividing surface of the periodic orbit $\Gamma_3$, the fate map has only five possibilities.

In the three fate maps, exist red regions ($ C \rightarrow A $), and light blue regions ($ A \rightarrow C$). The light blue regions represent the trajectories that dissociate. The green and orange regions, ($ A \rightarrow B $) and ($ B \rightarrow A $),  are the roaming regions. There are similarities between both of the fate maps for $\Gamma_2$ and $\Gamma_3$. The similarity between the fate maps is a consequence of the proximity between the orbits $\Gamma_3$ and $\Gamma_2$, see Fig. \ref{fig:orbits_3D}.

In the configuration space, all the trajectories that travel from one well to the other well should cross the projection of the periodic orbits $\Gamma_3$ and $\Gamma_2$. For this reason, the trajectories associated with roaming cross the dividing surface of $\Gamma_3$ and $\Gamma_2$ in the phase space.

The trajectories that start in one well and escape to the asymptotic region need to cross the projection of $\Gamma_1$ in the configuration space as well. These trajectories are associated with dissociation. The fate map of the dividing surface of $\Gamma_1$ shows which trajectories escape to the asymptotic region and which trajectories enter the interaction region.

In order to show the relation between the different trajectories fates and the stable and unstable manifolds of the unstable periodic orbits $\Gamma_1$, $\Gamma_2$, and $\Gamma_3$ we calculate the Lagrangian descriptors on the dividing surfaces for the three hyperbolic periodic orbits. The same segments of the trajectories are used to calculate the Lagrangian descriptors and the fate maps. Like in the calculation of the fate maps, the values of $\tau_-$ and $\tau_+$ for each trajectory are the times necessary to reach one of the three regions. Once a trajectory reaches one of the three regions, its integration stops. The results for the Lagrangian descriptors are on the right side of Fig \ref{fig:FM_LD}.

The abrupt jumps in the values of the Lagrangian descriptors are related to the different behaviour of the trajectories in a neighbourhood the stable and unstable manifolds of the unstable periodic orbits in the phase space. The stable and unstable manifolds are surfaces of dimension 2, and their intersections with a dividing surface are lines. There is a complete match between the boundaries of regions in the fate maps and the abrupt changes in the Lagrangian descriptors plots for each dividing surface. This agreement is a manifestation of the division of the phase space into the roaming and dissociation regions by the stable and unstable manifolds.

There are three different types of regions in the plots of the Lagrangian descriptor evaluated on the dividing surface: blue, yellow, and mixed regions. The colours in the Lagrangian descriptor evaluated in the dividing surfaces are related to the fates. The blue regions in the Lagrangian descriptors plots correspond to trajectories that start in one potential well, cross the dividing surface, and finally escape to infinity. These are trajectories associated with dissociation. The yellow regions correspond to trajectories that start in the asymptotic region, cross the dividing surfaces, and finally reach a potential well. The regions with mixed colours in the Lagrangian descriptor plot without abrupt changes are associated with the trajectories that connect the two wells. The mixed colours are related to other segments of the stable and unstable manifolds contained in the same set of initial conditions, and more integration time is necessary to visualise them.

 \label{fig:FM_DS_Gamma1}

 \label{fig:LD_DS_Gamma1}

 \label{fig:FM_DS_Gamma2}

 \label{fig:LD_DS_Gamma2}

 \label{fig:FM_DS_Gamma3}

 \label{fig:LD_DS_Gamma3}

\caption{ Fate map and Lagrangian descriptor evaluated on the dividing surfaces for $\Gamma_1$, $\Gamma_2$, and  $\Gamma_3$.
In order to improve visibility in the Lagrangian descriptor figures, the value $\arctan{0.005M}$ is plotted instead of $M$. The intersection between the dividing surface and stable manifolds and unstable manifolds are the curves with a change in the gradient. The yellow and blue curves, where the gradient change rapidly, are the intersections with the stable and unstable manifolds. There are four large regions in the plot, two regions for dissociation indicated by $D_{+}$ and, the other two regions for the inverse process  $D_{-}$. The roaming regions are indicated by a letter $R$.
\label{fig:FM_LD}}

The procedure to identify the stable and unstable manifolds associated with the transport from the Lagrangian descriptor and fate map plots is the following. For illustration, consider magnifications of the plots around the point $(l_2=25,\phi_2 = 2.565)$, close to the periodic orbit $\Gamma_2$, see Fig. \ref{fig:FM_LD_zoom}. There are fine strips close to $\Gamma_2$ on both magnifications. Then, consider the vertical line of initial conditions in Figs. \ref{fig:LD_DS_Gamma_zoom} and \ref{fig:FM_DS_Gamma2_zoom}, and their trajectories used to calculate the fate map and the Lagrangian descriptor. The projection of these orbits in the configuration space is in Fig. \ref{fig:orbits}. For example, the trajectory on the boundary between the green region ($A \rightarrow B$) and blue region ($A \rightarrow C$) converges to the invariant manifold $W^s(\Gamma_2)$, see the Figs. \ref{fig:FM_DS_Gamma2_zoom} and \ref{fig:orbits_AB_AC}. Continuing with the same procedure for the other boundaries is possible to identify the invariant manifolds associated with the boundaries between regions, see Figs. \ref{fig:orbits_AC_BC}, \ref{fig:orbits_BC_CC}, and \ref{fig:orbits_CC_CA}.

 \label{fig:FM_DS_Gamma2_zoom}

 \label{fig:LD_DS_Gamma_zoom}

\caption{ Fate map and Lagrangian descriptor evaluated on dividing surface of orbit $\Gamma_2$ around the point $(l_2=25,\phi_2 = 2.565)$. To improve visibility in the Lagrangian descriptor figure, the value $\arctan{0.005M}$ is ploted instead $M$ in \ref{fig:LD_DS_Gamma_zoom}.
\label{fig:FM_LD_zoom}}

\caption{ Lagrangian descriptor evaluated on the black vertical line on the figure \ref{fig:LD_DS_Gamma_zoom}. Panel \ref{fig:LD_DS_Gamma2_zoom_zoom_line} is a magnification of panel \ref{fig:LD_DS_Gamma2_zoom_line} aroun the periodic orbit $\Gamma_2$.
\label{fig:LD_line}}

 \label{fig:orbits_AB_AC}

 \label{fig:orbits_AC_BC}

 \label{fig:orbits_BC_CC}

 \label{fig:orbits_CC_CA}

\caption{ Projection of the trajectories that cross the black line in Fig. \ref{fig:FM_DS_Gamma2_zoom} on the dividing surface of $\Gamma_2$. The stable and unstable manifolds are the boundary between two sets of trajectories with a different fate.
\label{fig:orbits}}

# Bibliography

\bibliography{article}


#  Neuromatch Academy: Week 1, Day 1, Tutorial 2
# Model Types: "How" models
__Content creators:__ Matt Laporte, Byron Galbraith, Konrad Kording

__Content reviewers:__ Dalin Guo, Aishwarya Balwani, Madineh Sarvestani, Maryam Vaziri-Pashkam, Michael Waskom

___
# Tutorial Objectives
This is tutorial 2 of a 3-part series on different flavors of models used to understand neural data. In this tutorial we will explore models that can potentially explain *how* the spiking data we have observed is produced

To understand the mechanisms that give rise to the neural data we save in Tutorial 1,  we will build simple neuronal models and compare their spiking response to real data. We will:
- Write code to simulate a  simple "leaky integrate-and-fire" neuron model
- Make the model more complicated — but also more realistic — by adding more physiologically-inspired details

```python
#@title Video 1: "How" models
from IPython.display import IFrame
class BiliVideo(IFrame):
    def __init__(self, id, page=1, width=400, height=300, **kwargs):
        self.id=id
        src = "https://player.bilibili.com/player.html?bvid={0}&page={1}".format(id, page)
        super(BiliVideo, self).__init__(src, width, height, **kwargs)

video = BiliVideo(id='BV1yV41167Di', width=854, height=480, fs=1)
print("Video available at https://www.bilibili.com/video/{0}".format(video.id))
video
```

    Video available at https://www.bilibili.com/video/BV1yV41167Di

# Setup

```python
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
```

```python
#@title Figure Settings
import ipywidgets as widgets #interactive display

%matplotlib inline
%config InlineBackend.figure_format = 'retina'
plt.style.use("/share/dataset/COMMON/nma.mplstyle.txt")

```

```python
#@title Helper Functions
def histogram(counts, bins, vlines=(), ax=None, ax_args=None, **kwargs):
  """Plot a step histogram given counts over bins."""
  if ax is None:
    _, ax = plt.subplots()

  # duplicate the first element of `counts` to match bin edges
  counts = np.insert(counts, 0, counts[0])

  ax.fill_between(bins, counts, step="pre", alpha=0.4, **kwargs)  # area shading
  ax.plot(bins, counts, drawstyle="steps", **kwargs)  # lines

  for x in vlines:
    ax.axvline(x, color='r', linestyle='dotted')  # vertical line

  if ax_args is None:
    ax_args = {}

  # heuristically set max y to leave a bit of room
  ymin, ymax = ax_args.get('ylim', [None, None])
  if ymax is None:
    ymax = np.max(counts)
    if ax_args.get('yscale', 'linear') == 'log':
      ymax *= 1.5
    else:
      ymax *= 1.1
      if ymin is None:
        ymin = 0

  if ymax == ymin:
    ymax = None

  ax_args['ylim'] = [ymin, ymax]

  ax.set(**ax_args)
  ax.autoscale(enable=False, axis='x', tight=True)

def plot_neuron_stats(v, spike_times):
  fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(12, 5))

  # membrane voltage trace
  ax1.plot(v[0:100])
  ax1.set(xlabel='Time', ylabel='Voltage')
  # plot spike events
  for x in spike_times:
    if x >= 100:
      break
    ax1.axvline(x, color='red')

  # ISI distribution
  isi = np.diff(spike_times)
  n_bins = np.arange(isi.min(), isi.max() + 2) - .5
  counts, bins = np.histogram(isi, n_bins)
  vlines = []
  if len(isi) > 0:
    vlines = [np.mean(isi)]
  xmax = max(20, int(bins[-1])+5)
  histogram(counts, bins, vlines=vlines, ax=ax2, ax_args={
    'xlabel': 'Inter-spike interval',
    'ylabel': 'Number of intervals',
    'xlim': [0, xmax]
  })
  plt.show()
```

# Section 1: The Linear Integrate-and-Fire Neuron

How does a neuron spike?

A neuron charges and discharges an electric field across its cell membrane. The state of this electric field can be described by the _membrane potential_. The membrane potential rises due to excitation of the neuron, and when it reaches a threshold a spike occurs. The potential resets, and must rise to a threshold again before the next spike occurs.

One of the simplest models of spiking neuron behavior is the linear integrate-and-fire model neuron. In this model, the neuron increases its membrane potential $V_m$ over time in response to excitatory input currents $I$ scaled by some factor $\alpha$:

\begin{align}
  dV_m = {\alpha}I
\end{align}

Once $V_m$ reaches a threshold value a spike is produced, $V_m$ is reset to a starting value, and the process continues.

Here, we will take the starting and threshold potentials as $0$ and $1$, respectively. So, for example, if $\alpha I=0.1$ is constant---that is, the input current is constant---then $dV_m=0.1$, and at each timestep the membrane potential $V_m$ increases by $0.1$ until after $(1-0)/0.1 = 10$ timesteps it reaches the threshold and resets to $V_m=0$, and so on.

Note that we define the membrane potential $V_m$ as a scalar: a single real (or floating point) number. However, a biological neuron's membrane potential will not be exactly constant at all points on its cell membrane at a given time. We could capture this variation with a more complex model (e.g. with more numbers). Do we need to?

The proposed model is a 1D simplification. There are many details we could add to it, to preserve different parts of the complex structure and dynamics of a real neuron. If we were interested in small or local changes in the membrane potential, our 1D simplification could be a problem. However, we'll assume an idealized "point" neuron model for our current purpose.

#### Spiking Inputs

Given our simplified model for the neuron dynamics, we still need to consider what form the input $I$ will take. How should we specify the firing behavior of the presynaptic neuron(s) providing the inputs to our model neuron?

Unlike in the simple example above, where $\alpha I=0.1$, the input current is generally not constant. Physical inputs tend to vary with time. We can describe this variation with a distribution.

We'll assume the input current $I$ over a timestep is due to equal contributions from a non-negative ($\ge 0$) integer number of input spikes arriving in that timestep. Our model neuron might integrate currents from 3 input spikes in one timestep, and 7 spikes in the next timestep. We should see similar behavior when sampling from our distribution.

Given no other information about the input neurons, we will also assume that the distribution has a mean (i.e. mean rate, or number of spikes received per timestep), and that the spiking events of the input neuron(s) are independent in time. Are these reasonable assumptions in the context of real neurons?

A suitable distribution given these assumptions is the Poisson distribution, which we'll use to model $I$:

\begin{align}
  I \sim \mathrm{Poisson}(\lambda)
\end{align}

where $\lambda$ is the mean of the distribution: the average rate of spikes received per timestep.

### Exercise 1: Compute $dV_m$

For your first exercise, you will write the code to compute the change in voltage $dV_m$ (per timestep) of the linear integrate-and-fire model neuron. The rest of the code to handle numerical integration is provided for you, so you just need to fill in a definition for `dv` in the `lif_neuron` function below. The value of $\lambda$ for the Poisson random variable is given by the function argument `rate`.

The [`scipy.stats`](https://docs.scipy.org/doc/scipy/reference/stats.html) package is a great resource for working with and sampling from various probability distributions. We will use the `scipy.stats.poisson` class and its method `rvs` to produce Poisson-distributed random samples. In this tutorial, we have imported this package with the alias `stats`, so you should refer to it in your code as `stats.poisson`.

```python
def lif_neuron(n_steps=1000, alpha=0.01, rate=10):
  """ Simulate a linear integrate-and-fire neuron.

  Args:
    n_steps (int): The number of time steps to simulate the neuron's activity.
    alpha (float): The input scaling factor
    rate (int): The mean rate of incoming spikes

  """
  # precompute Poisson samples for speed
  exc = stats.poisson(rate).rvs(n_steps)

  v = np.zeros(n_steps)
  spike_times = []

  ################################################################################
  # Students: compute dv, then comment out or remove the next line
  raise NotImplementedError("Excercise: compute the change in membrane potential")
  ################################################################################

  for i in range(1, n_steps):

    dv = ...

    v[i] = v[i-1] + dv
    if v[i] > 1:
      spike_times.append(i)
      v[i] = 0

  return v, spike_times

# Uncomment these lines after completing the lif_neuron function
# v, spike_times = lif_neuron()
# plot_neuron_stats(v, spike_times)
```

[*Click for solution*](https://github.com/erlichlab/course-content/tree/master//tutorials/W1D1_ModelTypes/solutions/W1D1_Tutorial2_Solution_f8960ca1.py)

*Example output:*

## Interactive Demo: Linear-IF neuron
Like last time, you can now explore how various parametes of the LIF model influence the ISI distribution.

```python
#@title

#@markdown You don't need to worry about how the code works – but you do need to **run the cell** to enable the sliders.

def _lif_neuron(n_steps=1000, alpha=0.01, rate=10):
  exc = stats.poisson(rate).rvs(n_steps)
  v = np.zeros(n_steps)
  spike_times = []
  for i in range(1, n_steps):
    dv = alpha * exc[i]
    v[i] = v[i-1] + dv
    if v[i] > 1:
      spike_times.append(i)
      v[i] = 0
  return v, spike_times

@widgets.interact(
  n_steps=widgets.FloatLogSlider(1000.0, min=2, max=4),
  alpha=widgets.FloatLogSlider(0.01, min=-2, max=-1),
  rate=widgets.IntSlider(10, min=5, max=20)
)
def plot_lif_neuron(n_steps=1000, alpha=0.01, rate=10):
  v, spike_times = _lif_neuron(int(n_steps), alpha, rate)
  plot_neuron_stats(v, spike_times)
```

```python
#@title Video 2: Linear-IF models
from IPython.display import IFrame
class BiliVideo(IFrame):
    def __init__(self, id, page=1, width=400, height=300, **kwargs):
        self.id=id
        src = "https://player.bilibili.com/player.html?bvid={0}&page={1}".format(id, page)
        super(BiliVideo, self).__init__(src, width, height, **kwargs)

video = BiliVideo(id='BV1iZ4y1u7en', width=854, height=480, fs=1)
print("Video available at https://www.bilibili.com/video/{0}".format(video.id))
video
```

    Video available at https://www.bilibili.com/video/BV1iZ4y1u7en

# Section 2: Inhibitory signals

Our linear integrate-and-fire neuron from the previous section was indeed able to produce spikes. However, our ISI histogram doesn't look much like empirical ISI histograms seen in Tutorial 1, which had an exponential-like shape. What is our model neuron missing, given that it doesn't behave like a real neuron?

In the previous model we only considered excitatory behavior -- the only way the membrane potential could decrease was upon a spike event. We know, however, that there are other factors that can drive $V_m$ down. First is the natural tendency of the neuron to return to some steady state or resting potential. We can update our previous model as follows:

\begin{align}
  dV_m = -{\beta}V_m + {\alpha}I
\end{align}

where $V_m$ is the current membrane potential and $\beta$ is some leakage factor. This is a basic form of the popular Leaky Integrate-and-Fire model neuron (for a more detailed discussion of the LIF Neuron, see the Appendix).

We also know that in addition to excitatory presynaptic neurons, we can have inhibitory presynaptic neurons as well. We can model these inhibitory neurons with another Poisson random variable:

\begin{align}
I = I_{exc} - I_{inh} \\
I_{exc} \sim \mathrm{Poisson}(\lambda_{exc}) \\
I_{inh} \sim \mathrm{Poisson}(\lambda_{inh})
\end{align}

where $\lambda_{exc}$ and $\lambda_{inh}$ are the average spike rates (per timestep) of the excitatory and inhibitory presynaptic neurons, respectively.

### Exercise 2: Compute $dV_m$ with inhibitory signals

For your second exercise, you will again write the code to compute the change in voltage $dV_m$, though now of the LIF model neuron described above. Like last time, the rest of the code needed to handle the neuron dynamics are provided for you, so you just need to fill in a definition for `dv` below.

```python
def lif_neuron_inh(n_steps=1000, alpha=0.5, beta=0.1, exc_rate=10, inh_rate=10):
  """ Simulate a simplified leaky integrate-and-fire neuron with both excitatory
  and inhibitory inputs.

  Args:
    n_steps (int): The number of time steps to simulate the neuron's activity.
    alpha (float): The input scaling factor
    beta (float): The membrane potential leakage factor
    exc_rate (int): The mean rate of the incoming excitatory spikes
    inh_rate (int): The mean rate of the incoming inhibitory spikes
  """

  # precompute Poisson samples for speed
  exc = stats.poisson(exc_rate).rvs(n_steps)
  inh = stats.poisson(inh_rate).rvs(n_steps)

  v = np.zeros(n_steps)
  spike_times = []

  ###############################################################################
  # Students: compute dv, then comment out or remove the next line
  raise NotImplementedError("Excercise: compute the change in membrane potential")
  ################################################################################

  for i in range(1, n_steps):

    dv = ...

    v[i] = v[i-1] + dv
    if v[i] > 1:
      spike_times.append(i)
      v[i] = 0

  return v, spike_times

# Uncomment these lines do make the plot once you've completed the function
#v, spike_times = lif_neuron_inh()
#plot_neuron_stats(v, spike_times)
```

[*Click for solution*](https://github.com/erlichlab/course-content/tree/master//tutorials/W1D1_ModelTypes/solutions/W1D1_Tutorial2_Solution_4d9a2677.py)

*Example output:*

## Interactive Demo: LIF + inhibition neuron

```python
#@title
#@markdown **Run the cell** to enable the sliders.
def _lif_neuron_inh(n_steps=1000, alpha=0.5, beta=0.1, exc_rate=10, inh_rate=10):
  """ Simulate a simplified leaky integrate-and-fire neuron with both excitatory
  and inhibitory inputs.

  Args:
    n_steps (int): The number of time steps to simulate the neuron's activity.
    alpha (float): The input scaling factor
    beta (float): The membrane potential leakage factor
    exc_rate (int): The mean rate of the incoming excitatory spikes
    inh_rate (int): The mean rate of the incoming inhibitory spikes
  """
  # precompute Poisson samples for speed
  exc = stats.poisson(exc_rate).rvs(n_steps)
  inh = stats.poisson(inh_rate).rvs(n_steps)

  v = np.zeros(n_steps)
  spike_times = []
  for i in range(1, n_steps):
    dv = -beta * v[i-1] + alpha * (exc[i] - inh[i])
    v[i] = v[i-1] + dv
    if v[i] > 1:
      spike_times.append(i)
      v[i] = 0

  return v, spike_times

@widgets.interact(n_steps=widgets.FloatLogSlider(1000.0, min=2.5, max=4),
                  alpha=widgets.FloatLogSlider(0.5, min=-1, max=1),
                  beta=widgets.FloatLogSlider(0.1, min=-1, max=0),
                  exc_rate=widgets.IntSlider(12, min=10, max=20),
                  inh_rate=widgets.IntSlider(12, min=10, max=20))
def plot_lif_neuron(n_steps=1000, alpha=0.5, beta=0.1, exc_rate=10, inh_rate=10):
  v, spike_times = _lif_neuron_inh(int(n_steps), alpha, beta, exc_rate, inh_rate)
  plot_neuron_stats(v, spike_times)
```

```python
#@title Video 3: LIF + inhibition
from IPython.display import IFrame
class BiliVideo(IFrame):
    def __init__(self, id, page=1, width=400, height=300, **kwargs):
        self.id=id
        src = "https://player.bilibili.com/player.html?bvid={0}&page={1}".format(id, page)
        super(BiliVideo, self).__init__(src, width, height, **kwargs)

video = BiliVideo(id='BV1nV41167mS', width=854, height=480, fs=1)
print("Video available at https://www.bilibili.com/video/{0}".format(video.id))
video
```

    Video available at https://www.bilibili.com/video/BV1nV41167mS

#Summary

In this tutorial we gained some intuition for the mechanisms that produce the observed behavior in our real neural data. First, we built a simple neuron model with excitatory input and saw that it's behavior, measured using the ISI distribution, did not match our real neurons. We then improved our model by adding leakiness and inhibitory input. The behavior of this balanced model was much closer to the real neural data.

# Bonus

### Why do neurons spike?

A neuron stores energy in an electric field across its cell membrane, by controlling the distribution of charges (ions) on either side of the membrane. This energy is rapidly discharged to generate a spike when the field potential (or membrane potential) crosses a threshold. The membrane potential may be driven toward or away from this threshold, depending on inputs from other neurons: excitatory or inhibitory, respectively. The membrane potential tends to revert to a resting potential, for example due to the leakage of ions across the membrane, so that reaching the spiking threshold depends not only on the amount of input ever received following the last spike, but also the timing of the inputs.

The storage of energy by maintaining a field potential across an insulating membrane can be modeled by a capacitor. The leakage of charge across the membrane can be modeled by a resistor. This is the basis for the leaky integrate-and-fire neuron model.

### The LIF Model Neuron

The full equation for the LIF neuron is

\begin{align}
C_{m}\frac{dV_m}{dt} = -(V_m - V_{rest})/R_{m} + I
\end{align}

where $C_m$ is the membrane capacitance, $R_M$ is the membrane resistance, $V_{rest}$ is the resting potential, and $I$ is some input current (from other neurons, an electrode, ...).

In our above examples we set many of these parameters to convenient values ($C_m = R_m = dt = 1$, $V_{rest} = 0$) to focus more on the general behavior of the model. However, these too can be manipulated to achieve different dynamics, or to ensure the dimensions of the problem are preserved between simulation units and experimental units (e.g. with $V_m$ given in millivolts, $R_m$ in megaohms, $t$ in milliseconds).


# Appendix extras

```python
%matplotlib inline
%load_ext autoreload
%autoreload 2

import warnings
warnings.filterwarnings('ignore')
```

    Duplicate key in file WindowsPath('C:/Users/maa/.matplotlib/stylelib/paper.mplstyle'), line 462 ('figure.figsize   : 5, 3   ## figure size in inches')
    Duplicate key in file WindowsPath('C:/Users/maa/.matplotlib/stylelib/paper.mplstyle'), line 463 ('figure.dpi       : 100        ## figure dots per inch')

```python
# %load imports.py
"""
These is the standard setup for the notebooks.
"""

%matplotlib inline
%load_ext autoreload
%autoreload 2

import sys
sys.path.append("../../")

import pandas as pd
pd.options.display.max_rows = 999
pd.options.display.max_columns = 999
pd.set_option("display.max_columns", None)
import numpy as np
import os
import matplotlib.pyplot as plt
from collections import OrderedDict
import copy
from sklearn.pipeline import Pipeline
from rolldecayestimators.transformers import CutTransformer, LowpassFilterDerivatorTransformer, ScaleFactorTransformer, OffsetTransformer
from rolldecayestimators.direct_estimator_cubic import EstimatorQuadraticB, EstimatorCubic
from rolldecayestimators.ikeda_estimator import IkedaQuadraticEstimator
import src.equations as equations
import rolldecayestimators.lambdas as lambdas
from rolldecayestimators.substitute_dynamic_symbols import lambdify
import rolldecayestimators.symbols as symbols
import sympy as sp

from sympy.physics.vector.printing import vpprint, vlatex
from IPython.display import display, Math, Latex

from sklearn.metrics import r2_score
import shipflowmotionshelpers.shipflowmotionshelpers as helpers
import src.visualization.visualize as visualize
import scipy
from copy import deepcopy
import joblib
```

    The autoreload extension is already loaded. To reload it, use:
      %reload_ext autoreload

```python
import warnings
warnings.filterwarnings('ignore')
```

```python
if os.name == 'nt':
    plt.style.use('paper.mplstyle')  # Windows

from scipy.integrate import solve_ivp
from scipy.optimize import least_squares
from rolldecayestimators import fit_on_amplitudes
from reports import mdl_results
import rolldecayestimators.ikeda as ikeda_classes
from pyscores2.indata import Indata
from pyscores2.output import OutputFile
import joblib
import src.visualization.visualize as visualize
from rolldecayestimators import equations_ikeda_naked
from sympy import latex
from rolldecayestimators import logarithmic_decrement
from copy import deepcopy
import src.helpers
from src.notebook_to_latex import Equation, equation_dict

## Examples:
from reports import mdl_results
import reports.examples as examples
import reports.examples.ikeda_V_phi_a
import reports.examples.analytical_numerical
import reports.examples.eddy_sigma
import reports.examples.ikeda_section_table
import reports.examples.decision_tree
import reports.examples.KVLCC2_section_table
import reports.examples.logaritimic_decrement
import reports.examples.mdl
from reports.examples.mdl import plot_amplitudes
import reports.examples.ikeda
import reports.examples.hybrid_0
import reports.examples.hybrid_speed
import reports.examples.FNPF
import reports.examples.KVLCC2
```

    Duplicate key in file 'paper.mplstyle', line 462 ('figure.figsize   : 5, 3   ## figure size in inches')
    Duplicate key in file 'paper.mplstyle', line 463 ('figure.dpi       : 100        ## figure dots per inch')

## Analytical solution

A differential equation for a linearly decaying motion can be written as:

```python
Equation(equations.diff_eq, max_length=100, label='eq:linear')
```

$\displaystyle
\begin{equation}
\omega_{0}^{2} y + 2 \omega_{0} \zeta \dot{y} + \ddot{y} = 0
\label{eq:linear}
\end{equation}
$

Which has an analytical solution :

```python
Equation(equations.analytical_solution, label='eq:analytical_solution', max_length=200)
```

$\displaystyle
\begin{equation}
\begin{aligned}
\phi = \left(\left(\frac{\zeta \phi_{0}}{\sqrt{1 - \zeta^{2}}} + \frac{\dot{\phi}_{0}}{\omega_{0} \sqrt{1 - \zeta^{2}}}\right) \operatorname{sin}\left(\omega_{0} t \sqrt{1 - \zeta^{2}}\right) \\ + \phi_{0} \operatorname{cos}\left(\omega_{0} t \sqrt{1 - \zeta^{2}}\right)\right) e^{- \omega_{0} t \zeta}
\end{aligned}
\label{eq:analytical_solution}
\end{equation}
$

In the usual case of having no initial roll velocity ($\phi_0=0$) this equation can be simlified to:

```python
no_initial_speed = sp.simplify(equations.analytical_solution.subs(symbols.phi_0_dot,0))
Equation(no_initial_speed, label='eq:analytical_solution_no_initial_speed')
```

$\displaystyle
\begin{equation}
\begin{aligned}
\phi = \frac{\left(\zeta \operatorname{sin}\left(\omega_{0} t \sqrt{1 - \zeta^{2}}\right) + \sqrt{1 \\ - \zeta^{2}} \operatorname{cos}\left(\omega_{0} t \sqrt{1 - \zeta^{2}}\right)\right) \phi_{0} e^{- \omega_{0} t \zeta}}{\sqrt{1 - \zeta^{2}}}
\end{aligned}
\label{eq:analytical_solution_no_initial_speed}
\end{equation}
$

And the damping coefficient $\zeta$ is very small for ships so that $\sqrt{1-\zeta}$ is almost 1 and the solution can be further simplified, into something that can easily be recognized as a decaying oscillation:

```python
no_initial_speed_zeta_small = no_initial_speed.subs(
    [(sp.sqrt(1-symbols.zeta**2),1),
     (symbols.zeta*sp.sin(symbols.omega0*symbols.t),0),
    ])
Equation(no_initial_speed_zeta_small,label='eq:analytical_solution_no_initial_speed_zeta_small')
```

$\displaystyle
\begin{equation}
\phi = \phi_{0} e^{- \omega_{0} t \zeta} \operatorname{cos}\left(\omega_{0} t\right)
\label{eq:analytical_solution_no_initial_speed_zeta_small}
\end{equation}
$

## Time and frequency domain

\label{se:time_and_frequency}
Ikeda's method is defined in the frequency domain, where the damping is expressed as a function of roll angle amplitude $\phi_a$ and roll angle frequency $\omega$. The equivalent linear damping $B_e$ (not to confuse with eddy damping $B_E$) is a way to convert the frequency domain damping into time domain damping , so that time domain roll simulations can be conducted with results from Ikeda's method. The most general way to determine $B_e$ is to assume that the energy loss due to damping during a half cycle of roll is the same when nonlinear and linear dampings are used . The $B_e$ can then be calculated as a Fourier series expansion of the damping model, which in the case of a cubic model yields as:

```python
Equation(equations.B_e_equation, label='eq:B_e_equation')
```

$\displaystyle
\begin{equation}
B_{e} = B_{1} + \frac{8 B_{2} \omega_{0} \phi_{a}}{3 \pi}
\label{eq:B_e_equation}
\end{equation}
$

The expression above gives a relation between the frequency domain quantities $\phi_a$, $\omega$ and the time domain quantities $B_1$ and $B_2$ which can be used to obtain the latter ones from the Ikeda's method. This can be done by calculating the damping with Ikeda's method for a variation of frequencies and amplitudes and then fit the above expression to this damping and obtain $B_1$, $B_2$:

$$B^{Ikeda}(\phi_a, \omega) = B_e(\phi_a, \omega)$$

In the case of a roll decay test $\phi_a$ is the only parameter being varied. In order to verify this approach, an alternative method using the logaritmic decrement  to obtaining the frequency domain quantities from time series result is also used.

This method is investigated on simulation results with a quadratic model: $B_1 = 0.05$, $B_2 = 0.9$, $A_{44} = 1.0$, $C_1 = 5.0$. The peak values, being the only known amplitudes from this signal, is shown in the figure below.

```python
examples.logaritimic_decrement.show_figure_1()
```

The decrement is calculated as the ratio between every other peak, so that negative and positive roll peaks are separated. This decrement can be calulated for each peak:
$$ \Delta_n = \frac{\phi_{a,n}}{\phi_{a,n+2}}$$

The nondimensional $\zeta$ damping coefficient for each peak can be calculated from the logaritimic decrement:

$$\zeta_n = \frac{\delta_n}{2\pi}=\frac{ln(\Delta_n)}{2\pi}$$

The dimensional damping $B_n$ (Nm*s) can be then be obtained:

```python
Equation(equations.B_1_zeta_eq.subs(symbols.B_1, symbols.B), label='eq:B_1_zeta_eq')
```

$\displaystyle
\begin{equation}
B = 2 A_{44} \omega_{0} \zeta
\label{eq:B_1_zeta_eq}
\end{equation}
$

So the damping can be obtained for each oscillation this way but it is not obvious which of the roll amplitudes these dampings correspond to. Four different choices were tried, associating damping $B_n$ with the following amplitudes:
* A : $\phi_n$
* B : $\phi_{n+1}$
* C : $\phi_{n+2}$
* D : $(\phi_n + \phi_{n+1} + \phi_{n+2})/3$

```python
examples.logaritimic_decrement.show_figure_2();
```

Taking the mean value of peak n and the following two peaks (alt.D) seem to be the approach that best matches the $B_e$ (predicted using the linearized equivalent damping method). The  linearized equivalent damping method therefore seems to be the best way to convert $B_1$, $B_2$, $B_3$ to frequency domain, considering the difficulties with the logaritmic decrement method as described above. The only time linearized equivalent damping method has not been used in this paper, is when damping at individual peaks from a time signal should be converted to frequency domain.

# References


#  CS109B Data Science 2: Advanced Topics in Data Science

## Lab 4 - Bayesian Analysis

**Harvard University**

**Spring 2020**

**Instructors:** Mark Glickman, Pavlos Protopapas, and Chris Tanner

**Lab Instructors:** Chris Tanner and Eleni Angelaki Kaxiras

**Content:** Eleni Angelaki Kaxiras

---

```python
## RUN THIS CELL TO PROPERLY HIGHLIGHT THE EXERCISES
import requests
from IPython.core.display import HTML
styles = requests.get("https://raw.githubusercontent.com/Harvard-IACS/2019-CS109B/master/content/styles/cs109.css").text
HTML(styles)
```

```python
import pymc3 as pm
from pymc3 import summary
```

```python
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as stats
import pandas as pd
%matplotlib inline

import warnings
warnings.filterwarnings('ignore')
```

```python
print('Running on PyMC3 v{}'.format(pm.__version__))
```

    Running on PyMC3 v3.8

```javascript
%%javascript
IPython.OutputArea.auto_scroll_threshold = 20000;
```

    <IPython.core.display.Javascript object>

## Learning Objectives

By the end of this lab, you should be able to:
* Understand how probability distributions work.
* Apply Bayes Rule in calculating probabilities.
* Understand how to apply Bayesian analysis using PyMC3
* Avoid getting fired when talking to your Bayesian employer.

**This lab corresponds to Lectures 6, 7, and 8, and maps to Homework 3.**

## Table of Contents

1. The Bayesian Way of Thinking or Is this a Fair Coin?
2. [Intro to `pyMC3`](#pymc3).
3. [Bayesian Linear Regression](#blr).
4. [Try this at Home: Example on Mining Disasters](#no4).

## 1. The Bayesian way of Thinking

```
Here is my state of knowledge about the situation. Here is some data, I am now going to revise my state of knowledge.
```

Table Exercise: Discuss the statement above with your table mates and make sure everyone understands what it means and what constitutes Bayesian way of thinking. Finally, count the Bayesians among you.

### A. Bayes Rule

\begin{equation}
\label{eq:bayes}
P(A|\textbf{B}) = \frac{P(\textbf{B} |A) P(A) }{P(\textbf{B})}
\end{equation}

$P(A|\textbf{B})$ is the **posterior** distribution, prob(hypothesis | data)

$P(\textbf{B} |A)$ is the **likelihood** function, how probable is my data **B** for different values of the parameters

$P(A)$ is the marginal probability to observe the data, called the **prior**, this captures our belief about the data before observing it.

$P(\textbf{B})$ is the marginal distribution (sometimes called marginal likelihood)

Table Exercise: Solve the Monty Hall Paradox using Bayes Rule.

You are invited to play a game. There are 3 doors behind **one** of which are the keys to a brand new red Tesla. There is a goat behind each of the other two.

You are asked to pick one door, and let's say you pick **Door1**. The host knows where the keys are. Of the two remaining closed doors, he will always open the door that has a goat behind it. He'll say "I will do you a favor and open **Door2**". So he opens Door2 inside which there is, of course, a goat. He now asks you, do you want to open the initial Door you chose or change to **Door3**? Generally, in this game, when you are presented with this choice should you swap the doors?

**Initial Steps:**
- Start by defining the `events` of this probabilities game. One definition is:

    - $A_i$: car is behind door $i$

    - $B_i$ host opens door $i$

$i\in[1,2,3]$

- In more math terms, the question is: is the probability that the price is behind **Door 1** higher than the probability that the price is behind **Door2**, given that an event **has occured**?

### B. Bayes Rule written with Probability Distributions

We have data that we believe come from an underlying distribution of unknown parameters. If we find those parameters, we know everything about the process that generated this data and we can make inferences (create new data).

\begin{equation}
\label{eq:bayes}
P(\theta|\textbf{D}) = \frac{P(\textbf{D} |\theta) P(\theta) }{P(\textbf{D})}
\end{equation}

#### But what is $\theta \;$?

$\theta$ is an unknown yet fixed set of parameters. In Bayesian inference we express our belief about what $\theta$ might be and instead of trying to guess $\theta$ exactly, we look for its **probability distribution**. What that means is that we are looking for the **parameters** of that distribution. For example, for a Poisson distribution our $\theta$ is only $\lambda$. In a normal distribution, our $\theta$ is often just $\mu$ and $\sigma$.

### C. A review of Common Probability Distributions

#### Discrete Distributions

The random variable has a **probability mass function (pmf)** which measures the probability that our random variable will take a specific value $y$, denoted $P(Y=y)$.

- **Bernoulli** (binary outcome, success has probability $\theta$, $one$ trial):
$
P(Y=k) =  \theta^k(1-\theta)^{1-k}
$

- **Binomial** (binary outcome, success has probability $\theta$, $n$ trials):
\begin{equation}
P(Y=k) =  {{n}\choose{k}} \cdot \theta^k(1-\theta)^{n-k}
\end{equation}

*Note*: Binomial(1,$p$) = Bernouli($p$)

- **Negative Binomial**

- **Poisson** (counts independent events occurring at a rate)
\begin{equation}
P\left( Y=y|\lambda \right) = \frac{{e^{ - \lambda } \lambda ^y }}{{y!}}
\end{equation}
y = 0,1,2,...

- **Discrete Uniform**

- **Categorical, or Multinulli** (random variables can take any of K possible categories, each having its own probability; this is a generalization of the Bernoulli distribution for a discrete variable with more than two possible outcomes, such as the roll of a die)

- **Dirichlet-multinomial** (a generalization of the beta distribution for many variables)

#### Continuous Distributions

The random variable has a **probability density function (pdf)**.
- **Uniform** (variable equally likely to be near each value in interval $(a,b)$)
\begin{equation}
P(X = x) = \frac{1}{b - a}
\end{equation}
anywhere within the interval $(a, b)$, and zero elsewhere.

- **Normal** (a.k.a. Gaussian)
\begin{equation}
X \sim  \mathcal{N}(\mu,\,\sigma^{2})
\end{equation}

    A Normal distribution can be parameterized either in terms of precision $\tau$ or standard deviation ($\sigma^{2}$. The link between the two is given by
\begin{equation}
\tau = \frac{1}{\sigma^{2}}
\end{equation}
 - Mean $\mu$
 - Variance $\frac{1}{\tau}$ or $\sigma^{2}$
 - Parameters: `mu: float`, `sigma: float` or `tau: float`

- **Beta** (variable ($\theta$) taking on values in the interval $[0,1]$, and parametrized by two positive parameters, $\alpha$ and $\beta$ that control the shape of the distribution.

*Note:*Beta is a good distribution to use for priors (beliefs) because its range is $[0,1]$ which is the natural range for a probability and because we can model a wide range of functions by changing the $\alpha$ and $\beta$ parameters.

\begin{equation}
\label{eq:beta}
P(\theta) = \frac{1}{B(\alpha, \beta)} {\theta}^{\alpha - 1} (1 - \theta)^{\beta - 1} \propto {\theta}^{\alpha - 1} (1 - \theta)^{\beta - 1}
\end{equation}

where the normalisation constant, $B$, is a beta function of $\alpha$ and $\beta$,

\begin{equation}
B(\alpha, \beta) = \int_{t=0}^1 t^{\alpha - 1} (1 - t)^{\beta - 1} dt.
\end{equation}

- **Exponential**

- **Gamma**

 #### Code Resources:
 - Statistical Distributions in numpy/scipy: [scipy.stats](https://docs.scipy.org/doc/scipy/reference/stats.html)
 - Statistical Distributions in pyMC3: [distributions in PyMC3](https://docs.pymc.io/api/distributions.html) (we will see those below).

Exercise: Plot a Discrete variable

Change the value of $\mu$ in the Poisson PMF and see how the plot changes. Remember that the y-axis in a discrete probability distribution shows the probability of the random variable having a specific value in the x-axis.

\begin{equation}
P\left( X=k \right) = \frac{{e^{ - \mu } \mu ^k }}{{k!}}
\end{equation}

**stats.poisson.pmf(x, mu)** $\mu$(mu) is our $\theta$ in this case.

```python
plt.style.use('seaborn-darkgrid')
x = np.arange(0, 30)
for m in [0.5, 3, 8]:
    pmf = stats.poisson.pmf(x, m)
    plt.plot(x, pmf, 'o', alpha=0.5, label='$\mu$ = {}'.format(m))
plt.xlabel('random variable', fontsize=12)
plt.ylabel('probability', fontsize=12)
plt.legend(loc=1)
plt.ylim=(-0.1)
plt.show()
```

```python
# same for binomial
plt.style.use('seaborn-darkgrid')
x = np.arange(0, 22)
ns = [10, 17]
ps = [0.5, 0.7]
for n, p in zip(ns, ps):
    pmf = stats.binom.pmf(x, n, p)
    plt.plot(x, pmf, 'o', alpha=0.5, label='n = {}, p = {}'.format(n, p))
plt.xlabel('x', fontsize=14)
plt.ylabel('f(x)', fontsize=14)
plt.legend(loc=1)
plt.show()
```

```python
# discrete uniform
plt.style.use('seaborn-darkgrid')
ls = [0]
us = [3] # watch out, this number can only be integer!
for l, u in zip(ls, us):
    x = np.arange(l, u+1)
    pmf = [1.0 / (u - l + 1)] * len(x)
    plt.plot(x, pmf, '-o', label='lower = {}, upper = {}'.format(l, u))
plt.xlabel('x', fontsize=12)
plt.ylabel('probability P(x)', fontsize=12)
plt.legend(loc=1)
plt.show()
```

Exercise: Plot a continuous variable

Change the value of $\mu$ in the Uniform PDF and see how the plot changes.

Remember that the y-axis in a continuous probability distribution does not shows the actual probability of the random variable having a specific value in the x-axis because that probability is zero!. Instead, to see the probability that the variable is within a small margin we look at the integral below the curve of the PDF.

The uniform is often used as a noninformative prior.

```
Uniform - numpy.random.uniform(a=0.0, b=1.0, size)
```

$\alpha$ and $\beta$ are our parameters. `size` is how many tries to perform.
Our $\theta$ is basically the combination of the parameters a,b. We can also call it
\begin{equation}
\mu = (a+b)/2
\end{equation}

```python
from scipy.stats import uniform

r = uniform.rvs(size=1000)
plt.plot(r, uniform.pdf(r),'r-', lw=5, alpha=0.6, label='uniform pdf')
plt.hist(r, density=True, histtype='stepfilled', alpha=0.2)
plt.ylabel(r'probability density')
plt.xlabel(f'random variable')
plt.legend(loc='best', frameon=False)
plt.show()
```

```python
from scipy.stats import beta

alphas = [0.5, 1.5, 3.0]
betas = [0.5, 1.5, 3.0]
x = np.linspace(0, 1, 1000)
colors = ['red', 'green', 'blue']

fig, ax = plt.subplots(figsize=(8, 5))

for a, b, colors in zip(alphas, betas, colors):
    dist = beta(a, b)
    plt.plot(x, dist.pdf(x), c=colors,
             label=f'a={a}, b={b}')

ax.set_ylim(0, 3)

ax.set_xlabel(r'$\theta$')
ax.set_ylabel(r'$p(\theta|\alpha,\beta)$')
ax.set_title('Beta Distribution')

ax.legend(loc='best')
fig.show();
```

```python
plt.style.use('seaborn-darkgrid')
x = np.linspace(-5, 5, 1000)
mus = [0., 0., 0., -2.]
sigmas = [0.4, 1., 2., 0.4]
for mu, sigma in zip(mus, sigmas):
    pdf = stats.norm.pdf(x, mu, sigma)
    plt.plot(x, pdf, label=r'$\mu$ = '+ f'{mu},' + r'$\sigma$ = ' + f'{sigma}')
plt.xlabel('random variable', fontsize=12)
plt.ylabel('probability density', fontsize=12)
plt.legend(loc=1)
plt.show()
```

```python
plt.style.use('seaborn-darkgrid')
x = np.linspace(-5, 5, 1000)
mus = [0., 0., 0., -2.] # mean
sigmas = [0.4, 1., 2., 0.4] # std
for mu, sigma in zip(mus, sigmas):
      plt.plot(x, uniform.pdf(x, mu, sigma), lw=5, alpha=0.4, \
               label=r'$\mu$ = '+ f'{mu},' + r'$\sigma$ = ' + f'{sigma}')
plt.xlabel('random variable', fontsize=12)
plt.ylabel('probability density', fontsize=12)
plt.legend(loc=1)
plt.show()
```

### D. Is this a Fair Coin?

We do not want to promote gambling but let's say you visit the casino in **Monte Carlo**. You want to test your theory that casinos are dubious places where coins have been manipulated to have a larger probability for tails. So you will try to estimate how fair a coin is based on 100 flips.

You begin by flipping the coin. You get either Heads ($H$) or Tails ($T$) as our observed data and want to see if your posterior probabilities change as you obtain more data, that is, more coin flips. A nice way to visualize this is to plot the posterior probabilities as we observe more flips (data).

We will be using Bayes rule. $\textbf{D}$ is our data.

\begin{equation}
\label{eq:bayes}
P(\theta|\textbf{D}) = \frac{P(\textbf{D} |\theta) P(\theta) }{P(\textbf{D})}
\end{equation}

In the case of a coin toss when we observe $k$ heads in $n$ tosses:
\begin{equation}
\label{eq:bayes}
P(\theta|\textbf{k}) = Beta(\alpha + \textbf{k}, \beta + n - \textbf{k})
\end{equation}

we can say that $\alpha$ and $\beta$ play the roles of a "prior number of heads" and "prior number of tails".

```python
# play with the priors - here we manually set them but we could be sampling from a separate Beta
trials = np.array([0, 1, 3, 5, 10, 15, 20, 100, 200, 300])
heads = np.array([0, 1, 2, 4, 8, 10, 10, 50, 180, 150])
x = np.linspace(0, 1, 100)

# for simplicity we set a,b=1

plt.figure(figsize=(10,8))
for k, N in enumerate(trials):
    sx = plt.subplot(len(trials)/2, 2, k+1)
    posterior = stats.beta.pdf(x, 1 + heads[k], 1 + trials[k] - heads[k])
    plt.plot(x, posterior, alpha = 0.5, label=f'{trials[k]} tosses\n {heads[k]} heads');
    plt.fill_between(x, 0, posterior, color="#348ABD", alpha=0.4)
    plt.legend(loc='upper left', fontsize=10)
    plt.legend()
    plt.autoscale(tight=True)

plt.suptitle("Posterior probabilities for coin flips", fontsize=15);
plt.tight_layout()
plt.subplots_adjust(top=0.88)
```

 [Top](#top)

## 2. Introduction to `pyMC3`

PyMC3 is a Python library for programming Bayesian analysis, and more specifically, data creation, model definition, model fitting, and posterior analysis. It uses the concept of a `model` which contains assigned parametric statistical distributions to unknown quantities in the model. Within models we define random variables and their distributions. A distribution requires at least a `name` argument, and other `parameters` that define it. You may also use the `logp()` method in the model to build the model log-likelihood function. We define and fit the model.

PyMC3 includes a comprehensive set of pre-defined statistical distributions that can be used as model building blocks. Although they are not meant to be used outside of a `model`, you can invoke them by using the prefix `pm`, as in `pm.Normal`.

#### Markov Chain Monte Carlo (MCMC) Simulations

PyMC3 uses the **No-U-Turn Sampler (NUTS)** and the **Random Walk Metropolis**, two Markov chain Monte Carlo (MCMC) algorithms for sampling in posterior space. Monte Carlo gets into the name because when we sample in posterior space, we choose our next move via a pseudo-random process. NUTS is a sophisticated algorithm that can handle a large number of unknown (albeit continuous) variables.

```python
with pm.Model() as model:
    z = pm.Normal('z', mu=0., sigma=5.)
    x = pm.Normal('x', mu=z, sigma=1., observed=5.)
print(x.logp({'z': 2.5}))
print(z.random(10, 100)[:10])
```

    -4.043938533204672
    [-2.13557568  2.66425478 -2.51159652  1.44283883  6.87962507 -2.65427022
     -6.035913   -8.86538933 -0.36432833  2.65636786]

**References**:

- *Salvatier J, Wiecki TV, Fonnesbeck C. 2016. Probabilistic programming in Python using PyMC3. PeerJ Computer Science 2:e55* [(https://doi.org/10.7717/peerj-cs.55)](https://doi.org/10.7717/peerj-cs.55)
- [Distributions in PyMC3](https://docs.pymc.io/api/distributions.html)
- [More Details on Distributions](https://docs.pymc.io/developer_guide.html)

Information about PyMC3 functions including descriptions of distributions, sampling methods, and other functions, is available via the `help` command.

```python
#help(pm.Poisson)
```

 [Top](#top)

## 3. Bayesian Linear Regression

Let's say we want to predict outcomes Y as normally distributed observations with an expected value $mu$ that is a linear function of two predictor variables, $\bf{x}_1$ and $\bf{x}_2$.

\begin{equation}
\mu = \alpha + \beta_1 \bf{x}_1 + \beta_2 x_2
\end{equation}

\begin{equation}
Y \sim  \mathcal{N}(\mu,\,\sigma^{2})
\end{equation}

where $\sigma^2$ represents the measurement error.

In this example, we will use $\sigma^2 = 10$

We also choose the parameters as normal distributions:

\begin{eqnarray}
\alpha \sim  \mathcal{N}(0,\,10) \\
\beta_i \sim  \mathcal{N}(0,\,10) \\
\sigma^2 \sim  |\mathcal{N}(0,\,10)|
\end{eqnarray}

We will artificially create the data to predict on. We will then see if our model predicts them correctly.

```python
# Initialize random number generator
np.random.seed(123)

# True parameter values
alpha, sigma = 1, 1
beta = [1, 2.5]

# Size of dataset
size = 100

# Predictor variable
X1 = np.linspace(0, 1, size)
X2 = np.linspace(0,.2, size)

# Simulate outcome variable
Y = alpha + beta[0]*X1 + beta[1]*X2 + np.random.randn(size)*sigma

fig, ax = plt.subplots(1,2, figsize=(10,6), sharex=True)
ax[0].scatter(X1,Y)
ax[1].scatter(X2,Y)
ax[0].set_xlabel(r'$x_1$', fontsize=14)
ax[0].set_ylabel(r'$Y$', fontsize=14)
ax[1].set_xlabel(r'$x_2$', fontsize=14)
ax[1].set_ylabel(r'$Y$', fontsize=14)
```

```python
from pymc3 import Model, Normal, HalfNormal

basic_model = Model()

with basic_model:

    # Priors for unknown model parameters, specifically create stochastic random variables
    # with Normal prior distributions for the regression coefficients,
    # and a half-normal distribution for the standard deviation of the observations, σ.
    alpha = Normal('alpha', mu=0, sd=10)
    beta = Normal('beta', mu=0, sd=10, shape=2)
    sigma = HalfNormal('sigma', sd=1)

    # Expected value of outcome - posterior
    mu = alpha + beta[0]*X1 + beta[1]*X2

    # Likelihood (sampling distribution) of observations
    Y_obs = Normal('Y_obs', mu=mu, sd=sigma, observed=Y)
```

```python
# model fitting with sampling
from pymc3 import NUTS, sample, find_MAP
from scipy import optimize

with basic_model:

    # obtain starting values via MAP
    start = find_MAP(fmin=optimize.fmin_powell)

    # instantiate sampler
    step = NUTS(scaling=start)

    # draw 2000 posterior samples
    trace = sample(2000, step, start=start)
```

    logp = -164.5:   5%|▌         | 270/5000 [00:00<00:02, 2307.72it/s]

    Optimization terminated successfully.
             Current function value: 164.496957
             Iterations: 6
             Function evaluations: 271

    logp = -164.5:   5%|▌         | 271/5000 [00:01<00:27, 174.16it/s]
    Multiprocess sampling (2 chains in 2 jobs)
    NUTS: [sigma, beta, alpha]
    Sampling 2 chains, 0 divergences: 100%|██████████| 5000/5000 [00:22<00:00, 222.10draws/s]
    The number of effective samples is smaller than 10% for some parameters.

```python
from pymc3 import traceplot

traceplot(trace);
```

```python
results = pm.summary(trace,
                     var_names=['alpha', 'beta', 'sigma'])
results
```

      mean
      sd
      hpd_3%
      hpd_97%
      mcse_mean
      mcse_sd
      ess_mean
      ess_sd
      ess_bulk
      ess_tail
      r_hat

      alpha
      1.010
      0.225
      0.606
      1.452
      0.005
      0.003
      2248.0
      2248.0
      2246.0
      2149.0
      1.0

      beta[0]
      1.351
      1.983
      -2.329
      5.006
      0.104
      0.078
      362.0
      320.0
      361.0
      606.0
      1.0

      beta[1]
      0.898
      9.812
      -16.505
      19.738
      0.505
      0.358
      377.0
      377.0
      375.0
      583.0
      1.0

      sigma
      1.145
      0.082
      0.994
      1.298
      0.001
      0.001
      3573.0
      3542.0
      3586.0
      2889.0
      1.0

This linear regression example is from the original paper on PyMC3: *Salvatier J, Wiecki TV, Fonnesbeck C. 2016. Probabilistic programming in Python using PyMC3. PeerJ Computer Science 2:e55 https://doi.org/10.7717/peerj-cs.55*

 [Top](#top)

## 4. Try this at Home: Example on Mining Disasters
We will go over the classical `mining disasters from 1851 to 1962` dataset.

This example is from the [pyMC3 Docs](https://docs.pymc.io/notebooks/getting_started.html).

```python
import pandas as pd
disaster_data = pd.Series([4, 5, 4, 0, 1, 4, 3, 4, 0, 6, 3, 3, 4, 0, 2, 6,
                           3, 3, 5, 4, 5, 3, 1, 4, 4, 1, 5, 5, 3, 4, 2, 5,
                           2, 2, 3, 4, 2, 1, 3, np.nan, 2, 1, 1, 1, 1, 3, 0, 0,
                           1, 0, 1, 1, 0, 0, 3, 1, 0, 3, 2, 2, 0, 1, 1, 1,
                           0, 1, 0, 1, 0, 0, 0, 2, 1, 0, 0, 0, 1, 1, 0, 2,
                           3, 3, 1, np.nan, 2, 1, 1, 1, 1, 2, 4, 2, 0, 0, 1, 4,
                           0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1])
fontsize = 12
years = np.arange(1851, 1962)
plt.figure(figsize=(10,5))
#plt.scatter(years, disaster_data);
plt.bar(years, disaster_data)
plt.ylabel('Disaster count', size=fontsize)
plt.xlabel('Year', size=fontsize);
plt.title('Was there a Turning Point in Mining disasters from 1851 to 1962?', size=15);
```

#### Building the model

**Step1:** We choose the probability model for our experiment. Occurrences of disasters in the time series is thought to follow a **Poisson** process with a large **rate** parameter in the early part of the time series, and from one with a smaller **rate** in the later part. We are interested in locating the change point in the series, which perhaps is related to changes in mining safety regulations.

```
disasters = pm.Poisson('disasters', rate, observed=disaster_data)
```

We have two rates, `early_rate` if $t<=s$, and `late_rate` if $t>s$, where $s$ is the year the switch was made (a.k.a. the `switchpoint`).

**Step2:** Choose a prior distributions of the two rates, what we believe the rates were before we observed the data, and the switchpoint. We choose Exponential.
```
early_rate = pm.Exponential('early_rate', 1)
```

The parameters of this model are:

**Note:** Watch for missing values. Missing values are handled transparently by passing a MaskedArray or a pandas.DataFrame. Behind the scenes, another random variable, disasters.missing_values is created to model the missing values. If you pass a np.array with missing values you will get an error.

```python
with pm.Model() as disaster_model:

    # discrete
    switchpoint = pm.DiscreteUniform('switchpoint', lower=years.min(), upper=years.max(), testval=1900)

    # Priors for pre- and post-switch rates number of disasters
    early_rate = pm.Exponential('early_rate', 1)
    late_rate = pm.Exponential('late_rate', 1)

    # our theta - allocate appropriate Poisson rates to years before and after current
    # switch is an `if` statement in puMC3
    rate = pm.math.switch(switchpoint >= years, early_rate, late_rate)

    # our observed data as a likelihood function of the `rate` parameters
    # shows how we think our data is distributed
    disasters = pm.Poisson('disasters', rate, observed=disaster_data)
```

#### Model Fitting

```python
# there are defaults but we can also more explicitly set the sampling algorithms
with disaster_model:

    # for continuous variables
    step1 = pm.NUTS([early_rate, late_rate])

    # for discrete variables
    step2 = pm.Metropolis([switchpoint, disasters.missing_values[0]] )

    trace = pm.sample(10000, step=[step1, step2])
    # try different number of samples
    #trace = pm.sample(5000, step=[step1, step2])
```

    Multiprocess sampling (2 chains in 2 jobs)
    CompoundStep
    >NUTS: [late_rate, early_rate]
    >CompoundStep
    >>Metropolis: [disasters_missing]
    >>Metropolis: [switchpoint]
    Sampling 2 chains, 0 divergences: 100%|██████████| 21000/21000 [00:17<00:00, 1199.03draws/s]
    The number of effective samples is smaller than 10% for some parameters.

#### Posterior Analysis

On the left side plots we notice that our early rate is between 2.5 and 3.5 disasters a year. In the late period it seems to be between 0.6 and 1.2 so definitely lower.

The right side plots show the samples we drew to come to our conclusion.

```python
pm.traceplot(trace, ['early_rate', 'late_rate', 'switchpoint'], figsize=(20,10));
```

```python
results = pm.summary(trace,
                     var_names=['early_rate', 'late_rate', 'switchpoint'])
results
```

      mean
      sd
      hpd_3%
      hpd_97%
      mcse_mean
      mcse_sd
      ess_mean
      ess_sd
      ess_bulk
      ess_tail
      r_hat

      early_rate
      3.083
      0.289
      2.547
      3.619
      0.003
      0.002
      8871.0
      8871.0
      8816.0
      12558.0
      1.0

      late_rate
      0.930
      0.119
      0.711
      1.151
      0.001
      0.001
      9792.0
      9671.0
      9805.0
      11462.0
      1.0

      switchpoint
      1889.965
      2.444
      1885.000
      1894.000
      0.099
      0.070
      611.0
      611.0
      641.0
      916.0
      1.0


```python
import sys
print(sys.version)
```

    3.6.3 |Anaconda custom (64-bit)| (default, Nov  8 2017, 18:10:31)
    [GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]

```python
sys.version_info
```

    sys.version_info(major=3, minor=6, micro=3, releaselevel='final', serial=0)

```python
import sympy
sympy.__version__
```

    '1.1.1'

```python
from sympy import *
init_printing()
```

The next cell implements a `class FE(Lambda)` similar to the definition in `fun_expr.Function_from_Expression`.

I had expected there is no need to define a `__new__()` method for `class FE(Lambda)`. But something goes wrong and i can not figure out, what it is. If line 5 in the next cell

`return super().__new__(cls, variables, expr)`

is commented, the `__new__()` method of `FE` is defined and everything works as expected, but

`FE(x,x)`

is not the IdentitiyFunction anymore. Line 5 of the next cell is a shortcut to `Lambda.__new__()`. Alternatively to uncomment this line, the whole method `FE.__new__()` could be commented. Then

`FE(x,x)`

results in the IdentityFunction, but is no more of class `FE` with the result, that i.e.

`FE(x,x).diff(x) == 0`

holds true. Somehow -i have no clue, how- the inheritance hierarchy is broken. Maybe the problem is, that

`sympy.functions.elementary.miscellaneous.IdentityFunction`

inherits from `Lambda`.

```python
class FE(Lambda):
    def __new__(cls, variables, expr):
        # shortcut:
        # comment the next line and everything works
        return super().__new__(cls, variables, expr)
        """
        Create the body of the function.

        __new__ must be redefined in order to avoid to
        return the IdentityFunction. The IdentityFunction is
        not of type FE, so there are no methods of this class
        available.
        """

        # see source of Lambda for the next lines
        from sympy.utilities.iterables import iterable
        v = list(variables) if iterable(variables) else [variables]
        for i in v:
            if not getattr(i, 'is_Symbol', False):
                raise TypeError('variable is not a symbol: %s' % i)

        obj = Expr.__new__(cls, Tuple(*v), sympify(expr))
        obj.nargs = FiniteSet(len(v))
        return obj

    def diff(self, *symbols, **assumptions):
        """
        Differentiate function.

        Returns a function with the variables of this function
        and the differentiated expression.

        If as_function=False then return differentiated expression

        Example:
        >>> from sympy import *
        >>> a,x = symbols('a,x')
        >>> f = FE(x, a*x**2)
        >>> f.diff() # returns error
        >>> f.diff(x)
        ((x) \mapsto 2*a*x)
        >>> f.diff(x, as_function = False)
        2*a*x
        """
        as_function = assumptions.pop('as_function',True)
        expr = self.expr.diff(*symbols, **assumptions)
        if as_function:
            return FE(self.variables, expr)
        return expr

    def integrate(self, *args, **kwargs):
        """
        integrate returns the result as expression

        If as_function=True return result as function
        of the remaining variables. If no variables are left,
        raise TypeError('no variable left to define a function')

        Example:
        >>> from sympy import *
        >>> from myfunction import Function_from_Expression as FE
        >>> a,x = symbols('a,x')
        >>> f = FE(x, a*x**2)
        >>> f.integrate(x)
        a*x**3/3
        >>> x_0, x_1 = symbols('x_0,x_1')
        >>> f.integrate((x,x_0,x_1))
        -a*x_0**3/3 + a*x_1**3/3
        >>> f.integrate(x,as_function=True)
        ((x) \mapsto a*x**3/3)
        >>> f.integrate((x,1,2), as_function=True) # raises TypeError
        """
        as_function = kwargs.pop('as_function', False)
        expr = self.expr.integrate(*args, **kwargs)

        if as_function:
            free_symbols = expr.free_symbols
            new_variables = tuple(v for v in self.variables if v in free_symbols)
            if new_variables:
                return FE(new_variables, expr)
            else:
                raise TypeError('no variable left to define a function')

        return expr

```

```python
x = Symbol('x')
f = FE(x, x**2)
f
```

```python
f.is_Function
```

    True

```python
f_1 = f.diff(x)
f_1
```

```python
e = f.integrate(x, as_function=True)
e
```

```python
g = FE(x,x)
g
```

```python
type(g)
```

    sympy.functions.elementary.miscellaneous.IdentityFunction

```python
g.is_Function
```

    True

```python
isinstance(g,FE)
```

    False

```python
isinstance(g,Lambda)
```

    True

```python
isinstance(g,sympy.functions.elementary.miscellaneous.IdentityFunction)
```

    True

```python
# wrong:
g_1 = g.diff(x)
g_1
```

```python
g.variables
```

```python
g.expr
```

```python
g(x).diff(x)
```

```python
h = FE(x, x**2/2)
h
```

```python
isinstance(h,FE)
```

    True

```python
isinstance(h.diff(x),FE)
```

    False

```python
isinstance(h.diff(x),Lambda)
```

    True

```python
# wrong
h.diff(x).diff(x)
```

```python
# correct
h.diff(x,2)
```

```python
# weird
h.diff(x)(x).diff(x)
```