diff --git a/week5/nmap5.ipynb b/week5/nmap5.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..a251577116f8c163b6a55c03322f98eb1e4e23b4
--- /dev/null
+++ b/week5/nmap5.ipynb
@@ -0,0 +1,2013 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "incomplete-medline",
+   "metadata": {},
+   "source": [
+    "<h1 style=\"text-align: center; vertical-align: middle;\">Numerical Methods in Accelerator Physics</h1>\n",
+    "<h2 style=\"text-align: center; vertical-align: middle;\">Python examples -- Week 5</h2>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "executive-television",
+   "metadata": {},
+   "source": [
+    "<h2>Run this first!</h2>\n",
+    "\n",
+    "Imports and modules:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "biological-product",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from config5 import np, plt, plot_rfwave, tqdm, trange\n",
+    "from scipy.constants import m_p, e, c\n",
+    "import PyNAFF\n",
+    "%matplotlib inline"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "transsexual-renewal",
+   "metadata": {},
+   "source": [
+    "If the progress bar by `tqdm` (`trange`) later in this document does not work, run this:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "maritime-warner",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "!jupyter nbextension enable --py widgetsnbextension"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "present-mother",
+   "metadata": {},
+   "source": [
+    "<h3>Machine parameters for exercises</h3>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "taken-reminder",
+   "metadata": {},
+   "source": [
+    "The CERN Proton Synchrotron (PS):\n",
+    "- has a circumference of 2π·100m\n",
+    "- takes protons from the PS Booster at a kinetic energy of 2GeV corresponding to a $\\gamma=3.13$\n",
+    "- injects with 50kV of rf voltage, up to 200kV for ramp\n",
+    "- runs at harmonic $h=7$\n",
+    "- has a momentum compaction factor of $\\alpha_c=0.027$\n",
+    "- typical acceleration rate of (up to) $\\dot{B}=2$ T/s, the bending radius is $\\rho=70.08$ m"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "primary-sullivan",
+   "metadata": {},
+   "source": [
+    "<h3 style=\"color: #e6541a;\">Exercises for topics of week 4 (Solutions below)</h3>\n",
+    "\n",
+    "<div style=\"color: #e6541a; margin-top: 2em;\">Based on the CERN Proton Synchrotron (PS) machine parameters: <br />\n",
+    "\n",
+    "1. Track particles given a stationary synchronous particle ($\\varphi_s=0$).\n",
+    "2. Determine the frequency of the synchrotron motion (via NAFF).\n",
+    "3. Track particles given an accelerating synchronous particle ($\\varphi_s>0$).\n",
+    "4. Compute the transition energy $\\gamma_{\\text{t}}$ for CERN PS.\n",
+    "4. Set machine energy ($\\gamma$) above transition / relativistic regime, track like (4) again.\n",
+    "\n",
+    "$\\implies$ take note of the observed trajectories in phase space (for the following)!\n",
+    "</div>\n",
+    "<!-- \n",
+    "3. Derivation of Hamiltonian $\\mathcal{H}(z,\\delta)=T(\\delta) + V(z)$ for stationary synchronous particle ($\\varphi_s=0$) -->"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "sensitive-nature",
+   "metadata": {},
+   "source": [
+    "Some convenience functions to compute the speed β and the relativistic Lorentz factor γ:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "fifteen-utility",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def beta(gamma):\n",
+    "    '''Speed β in units of c from relativistic Lorentz factor γ.'''\n",
+    "    return np.sqrt(1 - gamma**-2)\n",
+    "\n",
+    "def gamma(p):\n",
+    "    '''Relativistic Lorentz factor γ from total momentum p.'''\n",
+    "    return np.sqrt(1 + (p / (mass * c))**2)\n",
+    "\n",
+    "def p_from_gamma(gamma):\n",
+    "    '''Total momentum p from relativistic Lorentz factor γ.'''\n",
+    "    return mass * c * np.sqrt(gamma**2 - 1)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "outdoor-police",
+   "metadata": {},
+   "source": [
+    "We gather all the machine parameters in a class named `Machine`.\n",
+    "\n",
+    "A `Machine` instance knows\n",
+    "- at which energy the synchronous particle (reference γ, `gamma_ref`, or alternatively the momentum `p0()`) currently runs,\n",
+    "- what the acceleration rate is in terms of the synchronous phase φ_s, `phi_s`\n",
+    "- how to compute the phase-slip factor $\\eta$, `eta`, for a particle at a certain momentum p0 + Δp\n",
+    "- how to update the energy of the synchronous particle via `update_gamma_ref()` when a turn has passed"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "developed-canadian",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "charge = e\n",
+    "mass = m_p\n",
+    "\n",
+    "class Machine(object):\n",
+    "    gamma_ref = 3.13\n",
+    "    circumference = 2*np.pi*100\n",
+    "    voltage = 50e3\n",
+    "    harmonic = 7\n",
+    "    alpha_c = 0.027\n",
+    "    phi_s = 0\n",
+    "    \n",
+    "    def __init__(self, gamma_ref=gamma_ref, circumference=circumference,\n",
+    "                 voltage=voltage, harmonic=harmonic, \n",
+    "                 alpha_c=alpha_c, phi_s=phi_s):\n",
+    "        '''Override default settings by giving explicit arguments.'''\n",
+    "        self.gamma_ref = gamma_ref\n",
+    "        self.circumference = circumference\n",
+    "        self.voltage = voltage\n",
+    "        self.harmonic = harmonic\n",
+    "        self.alpha_c = alpha_c\n",
+    "        self.phi_s = phi_s\n",
+    "    \n",
+    "    def eta(self, deltap):\n",
+    "        '''Phase-slip factor for a particle.'''\n",
+    "        p = self.p0() + deltap\n",
+    "        return self.alpha_c - gamma(p)**-2\n",
+    "\n",
+    "    def p0(self):\n",
+    "        '''Momentum of synchronous particle.'''\n",
+    "        return self.gamma_ref * beta(self.gamma_ref) * mass * c\n",
+    "\n",
+    "    def update_gamma_ref(self):\n",
+    "        '''Advance the energy of the synchronous particle\n",
+    "        according to the synchronous phase by one turn.\n",
+    "        '''\n",
+    "        deltap_per_turn = charge * self.voltage / (\n",
+    "            beta(self.gamma_ref) * c) * np.sin(self.phi_s)\n",
+    "        new_p0 = self.p0() + deltap_per_turn\n",
+    "        self.gamma_ref = gamma(new_p0)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "accepted-table",
+   "metadata": {},
+   "source": [
+    "To compute a synchronous phase, you may use these convenience functions to compute the $\\arcsin$ on the interval of [-π/2,π/2] .\n",
+    "\n",
+    "Remember you may likely want to find a synchronous phase on $\\varphi_s\\in[0,π]$ !"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "known-pitch",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def deltap_per_turn(Bdot, rho, circumference, p0):\n",
+    "    Trev = circumference / (beta(gamma(p0)) * c)\n",
+    "    return Bdot * rho * charge * Trev"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "micro-alpha",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def compute_phi_s(deltap_per_turn, p0, voltage):\n",
+    "    '''Return *first* positive phase which matches the\n",
+    "    given Δp/turn on the interval [0, π/2].\n",
+    "    Do check whether you need to use π-φ_s for stability!\n",
+    "    '''\n",
+    "    return np.arcsin(\n",
+    "        deltap_per_turn * beta(gamma(p0)) * c / (charge * voltage)\n",
+    "    )"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "literary-absorption",
+   "metadata": {},
+   "source": [
+    "The tracking equations for the longitudinal plane read:\n",
+    "\n",
+    "$$\\begin{cases}\\,\n",
+    "    z_{n+1} &= z_n - \\eta C \\left(\\cfrac{\\Delta p}{p_0}\\right)_n \\\\\n",
+    "    (\\Delta p)_{n+1} &= (\\Delta p)_n + \\cfrac{q V}{(\\beta c)_n}\\cdot\\left(\\sin\\left(\\varphi_s - \\cfrac{2\\pi}{C}\\cdot hz_{n+1}\\right) - \\sin(\\varphi_s)\\right)\n",
+    "\\end{cases}$$\n",
+    "\n",
+    "We implement them with a leapfrog (half-drift + kick + half-drift) scheme."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "appointed-complex",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def track_one_turn(z_n, deltap_n, machine):\n",
+    "    m = machine\n",
+    "    # half drift\n",
+    "    z_nhalf = z_n - m.eta(deltap_n) * deltap_n / m.p0() * m.circumference / 2\n",
+    "    # rf kick\n",
+    "    amplitude = charge * m.voltage / (beta(gamma(m.p0())) * c)\n",
+    "    phi = m.phi_s - m.harmonic * 2 * np.pi * z_nhalf / m.circumference\n",
+    "    \n",
+    "    m.update_gamma_ref()\n",
+    "    deltap_n1 = deltap_n + amplitude * (np.sin(phi) - np.sin(m.phi_s))\n",
+    "    # half drift\n",
+    "    z_n1 = z_nhalf - m.eta(deltap_n1) * deltap_n1 / m.p0() * m.circumference / 2\n",
+    "    return z_n1, deltap_n1"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "included-meaning",
+   "metadata": {},
+   "source": [
+    "<b>1. Track particles given a stationary synchronous particle</b><br />\n",
+    "The `Machine` instance will keep track of the reference energy during the tracking by calling `update_gamma_ref()` once per turn:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "familiar-dividend",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m = Machine()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "welcome-latex",
+   "metadata": {},
+   "source": [
+    "Particles are tracked by their two longitudinal coordinates $(z, \\Delta p)$. The initial values are stored in `z_ini` and `deltap_ini` as `numpy.array`s. These should have `N` entries for $N$ particles.\n",
+    "\n",
+    "(You may use numpy helper functions such as `np.linspace` or `np.arange` for convenient initialisation!)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "utility-classic",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "n_turns = 3000\n",
+    "deltap_ini = np.linspace(0, 0.01 * m.p0(), 40)#np.array([0.]) #np.linspace(start, end, 20)\n",
+    "z_ini = np.zeros_like(deltap_ini)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "purple-implementation",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "N = len(z_ini)\n",
+    "assert (N == len(deltap_ini))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "formed-james",
+   "metadata": {},
+   "source": [
+    "To store the coordinate values during tracking, prepare some `n_turns` long 2D arrays with `N` entries per turn:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "included-negotiation",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "z = np.zeros((n_turns, N), dtype=np.float64)\n",
+    "deltap = np.zeros_like(z)\n",
+    "\n",
+    "z[0] = z_ini\n",
+    "deltap[0] = deltap_ini"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "under-blanket",
+   "metadata": {},
+   "source": [
+    "We would also like to store the reference gamma for each turn:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "played-marker",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "gammas = np.zeros(n_turns, dtype=np.float64)\n",
+    "gammas[0] = m.gamma_ref"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "atomic-uzbekistan",
+   "metadata": {},
+   "source": [
+    "Let's go, here's the tracking loop over the number of turns `n_turns`!"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "broken-inside",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for i_turn in range(1, n_turns):\n",
+    "    z[i_turn], deltap[i_turn] = track_one_turn(z[i_turn - 1], deltap[i_turn - 1], m)\n",
+    "    gammas[i_turn] = m.gamma_ref"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "opening-stick",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.plot(gammas)\n",
+    "plt.xlabel('Turns')\n",
+    "plt.ylabel('$\\gamma_{ref}$')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "authorized-robinson",
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "plt.scatter(z, deltap / m.p0(), marker='.', s=0.5)\n",
+    "plt.xlim(-50,100)\n",
+    "plt.xlabel('$z$ [m]')\n",
+    "plt.ylabel('$\\Delta p/p_0$')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "altered-morocco",
+   "metadata": {},
+   "source": [
+    "<b>2. Determine the frequency of the synchrotron motion (via NAFF).</b>"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "labeled-clear",
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "plt.plot(((z.T)[:19]).T)\n",
+    "plt.xlabel('turns')\n",
+    "plt.ylabel('$z$ [m]')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "sonic-anthropology",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "freqs_naff = []\n",
+    "\n",
+    "for signal in ((z.T)[:19]):\n",
+    "    freq_naff = PyNAFF.naff(signal, turns=n_turns - 1, nterms=1)\n",
+    "    try:\n",
+    "        freq_naff = freq_naff[0, 1]\n",
+    "    except IndexError:\n",
+    "        freq_naff = 0\n",
+    "    freqs_naff += [freq_naff]\n",
+    "\n",
+    "freqs_naff = np.array(freqs_naff)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "surgical-newport",
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "plt.plot(np.max(((z.T)[:19]).T,axis=0), freqs_naff, c='r', marker='.')\n",
+    "\n",
+    "#plt.xticks([-np.pi/2, 0, np.pi/2, np.pi], [r\"$-\\pi/2$\", \"0\", r\"$\\pi/2$\", r\"$\\pi$\"])\n",
+    "plt.xlabel(r'Maximum amplitude z [m]')\n",
+    "plt.ylabel('Phase advance / \\n' + r'integration step $\\Delta t$ [$2\\pi$]');"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "rotary-accordance",
+   "metadata": {},
+   "source": [
+    "<b>3. Track particles given an accelerating synchronous particle ($\\gamma=3.13$, typical acceleration rate of $\\dot{B}=2$ T/s, bending radius $\\rho=70.08$ m, rf voltage 200kV)</b>"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "novel-collective",
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "deltap_per_turn(2,70.08,2*np.pi*100,p_from_gamma(3.13))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "initial-cinema",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "phi_s=compute_phi_s(deltap_per_turn(2,70.08,2*np.pi*100,p_from_gamma(3.13)),p_from_gamma(3.13),200e3)\n",
+    "phi_s"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "streaming-pleasure",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m = Machine(phi_s=phi_s)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "working-dictionary",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "n_turns = 1000\n",
+    "deltap_ini = np.linspace(0, 0.01 * m.p0(), 20)#np.array([0.]) #np.linspace(start, end, 20)\n",
+    "z_ini = np.zeros_like(deltap_ini)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "anticipated-china",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "N = len(z_ini)\n",
+    "assert (N == len(deltap_ini))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "tested-jurisdiction",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "z = np.zeros((n_turns, N), dtype=np.float64)\n",
+    "deltap = np.zeros_like(z)\n",
+    "\n",
+    "z[0] = z_ini\n",
+    "deltap[0] = deltap_ini"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "accepting-northwest",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "gammas = np.zeros(n_turns, dtype=np.float64)\n",
+    "gammas[0] = m.gamma_ref"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "coordinated-evans",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for i_turn in range(1, n_turns):\n",
+    "    z[i_turn], deltap[i_turn] = track_one_turn(z[i_turn - 1], deltap[i_turn - 1], m)\n",
+    "    gammas[i_turn] = m.gamma_ref"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "genetic-night",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.plot(gammas)\n",
+    "plt.xlabel('Turns')\n",
+    "plt.ylabel('$\\gamma_{ref}$')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "tribal-minimum",
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "plt.scatter(z, deltap / m.p0(), marker='.', s=0.5)\n",
+    "plt.xlabel('$z$ [m]')\n",
+    "plt.ylabel('$\\Delta p/p_0$')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "changed-check",
+   "metadata": {},
+   "source": [
+    "<b>4. Compute the transition energy for CERN PS</b>"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "meaningful-quantum",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "gamma_t=1/np.sqrt(m.alpha_c)\n",
+    "gamma_t"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "based-piano",
+   "metadata": {},
+   "source": [
+    "<b>5. Set machine energy above transition / relativistic regime, track again.</b>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "raised-breach",
+   "metadata": {},
+   "source": [
+    "<b>Same synchronous phase => longitudinal defocusing</b>"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "described-clarity",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m = Machine(gamma_ref=7,phi_s=phi_s)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "neutral-corruption",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "n_turns = 5000\n",
+    "deltap_ini = np.linspace(0, 0.01 * m.p0(), 20)#np.array([0.]) #np.linspace(start, end, 20)\n",
+    "z_ini = np.zeros_like(deltap_ini)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "conditional-moldova",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "N = len(z_ini)\n",
+    "assert (N == len(deltap_ini))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "skilled-concord",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "z = np.zeros((n_turns, N), dtype=np.float64)\n",
+    "deltap = np.zeros_like(z)\n",
+    "\n",
+    "z[0] = z_ini\n",
+    "deltap[0] = deltap_ini"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "injured-worker",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "gammas = np.zeros(n_turns, dtype=np.float64)\n",
+    "gammas[0] = m.gamma_ref"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "european-transportation",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for i_turn in range(1, n_turns):\n",
+    "    z[i_turn], deltap[i_turn] = track_one_turn(z[i_turn - 1], deltap[i_turn - 1], m)\n",
+    "    gammas[i_turn] = m.gamma_ref"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "preceding-provincial",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.plot(gammas)\n",
+    "plt.xlabel('Turns')\n",
+    "plt.ylabel('$\\gamma_{ref}$')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "sharp-denmark",
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "plt.scatter(z, deltap / m.p0(), marker='.', s=0.5)\n",
+    "plt.xlabel('$z$ [m]')\n",
+    "plt.ylabel('$\\Delta p/p_0$')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "confirmed-experiment",
+   "metadata": {},
+   "source": [
+    "<b>Same synchronous phase => longitudinal defocusing</b>"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "regulated-delta",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m = Machine(gamma_ref=7,phi_s=np.pi-phi_s)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "exciting-picking",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "n_turns = 10000\n",
+    "deltap_ini = np.linspace(0, 0.01 * m.p0(), 20)#np.array([0.]) #np.linspace(start, end, 20)\n",
+    "z_ini = np.zeros_like(deltap_ini)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "under-engineering",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "N = len(z_ini)\n",
+    "assert (N == len(deltap_ini))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "bibliographic-tiffany",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "z = np.zeros((n_turns, N), dtype=np.float64)\n",
+    "deltap = np.zeros_like(z)\n",
+    "\n",
+    "z[0] = z_ini\n",
+    "deltap[0] = deltap_ini"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "tracked-hierarchy",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "gammas = np.zeros(n_turns, dtype=np.float64)\n",
+    "gammas[0] = m.gamma_ref"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "becoming-paraguay",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for i_turn in range(1, n_turns):\n",
+    "    z[i_turn], deltap[i_turn] = track_one_turn(z[i_turn - 1], deltap[i_turn - 1], m)\n",
+    "    gammas[i_turn] = m.gamma_ref"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "distant-edgar",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.plot(gammas)\n",
+    "plt.xlabel('Turns')\n",
+    "plt.ylabel('$\\gamma_{ref}$')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "final-hebrew",
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "plt.scatter(z, deltap / m.p0(), marker='.', s=0.5)\n",
+    "plt.xlim(right=100)\n",
+    "plt.ylim(bottom=-0.02)\n",
+    "plt.xlabel('$z$ [m]')\n",
+    "plt.ylabel('$\\Delta p/p_0$')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "coated-supplement",
+   "metadata": {},
+   "source": [
+    "<h3>Week 5: Visualization of synchrotron tune by particle tracking (slide 16)</h3>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "equipped-recording",
+   "metadata": {},
+   "source": [
+    "You can override the default PS parameters by supplying arguments to the `Machine(...)` instantiation, e.g. change the `gamma_ref` by `m = Machine(gamma_ref=20)` or the `phi_s` etc."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "corrected-repair",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m = Machine(voltage=200e3,phi_s=0.456)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "foreign-louisiana",
+   "metadata": {},
+   "source": [
+    "Now we define the kinetic and potential energy terms and the Hamiltonian function(al) itself:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "steady-arbor",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def T(deltap, machine):\n",
+    "    '''Kinetic energy term in Hamiltonian.'''\n",
+    "    return -0.5 * machine.eta(deltap) / machine.p0() * deltap**2\n",
+    "\n",
+    "def U(z, machine, beta_=None):\n",
+    "    '''Potential energy term in Hamiltonian.\n",
+    "    If beta is not given, compute it from synchronous particle.\n",
+    "    '''\n",
+    "    m = machine\n",
+    "    if beta_ is None:\n",
+    "        beta_ = beta(gamma(m.p0()))\n",
+    "    ampl = charge * m.voltage / (beta_ * c * 2 * np.pi * m.harmonic)\n",
+    "    phi = m.phi_s - 2 * np.pi * m.harmonic / m.circumference * z\n",
+    "    # convenience: define z at unstable fixed point\n",
+    "    z_ufp = -m.circumference * (np.pi - 2 * m.phi_s) / (2 * np.pi * m.harmonic)\n",
+    "    # convenience: offset by potential value at unstable fixed point\n",
+    "    # such that unstable fixed point (and separatrix) have 0 potential energy\n",
+    "    return ampl * (-np.cos(phi) + \n",
+    "                   2 * np.pi * m.harmonic / m.circumference * (z - z_ufp) * np.sin(m.phi_s) +\n",
+    "                   -np.cos(m.phi_s))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "flush-defense",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def hamiltonian(z, deltap, machine):\n",
+    "    return T(deltap, machine) + U(z, machine, beta_=beta(gamma(machine.p0() + deltap)))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "foreign-swedish",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def plot_hamiltonian(machine, zleft=-50, zright=50, dpmax=0.01, cbar=True):\n",
+    "    '''Plot Hamiltonian contours across (zleft, zright) and (-dpmax, dpmax).'''\n",
+    "    Z, DP = np.meshgrid(np.linspace(zleft, zright, num=1000), \n",
+    "                        np.linspace(-dpmax, dpmax, num=1000))\n",
+    "    H = hamiltonian(Z, DP * machine.p0(), machine) / machine.p0()\n",
+    "    \n",
+    "    plt.contourf(Z, DP, H, cmap=plt.get_cmap('hot_r'), levels=12,\n",
+    "                 zorder=0, alpha=0.5)\n",
+    "    plt.xlabel('$z$ [m]')\n",
+    "    plt.ylabel(r'$\\delta$')\n",
+    "    if cbar:\n",
+    "        colorbar = plt.colorbar(label=r'$\\mathcal{H}(z,\\Delta p)\\,/\\,p_0$')\n",
+    "        colorbar.ax.axhline(0, lw=2, c='b')\n",
+    "    plt.contour(Z, DP, H, colors='b', linewidths=2, levels=[0])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "planned-values",
+   "metadata": {},
+   "source": [
+    "Let's plot the Hamiltonian landscape on phase space:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "stone-sixth",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plot_hamiltonian(m)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "after-legislature",
+   "metadata": {},
+   "source": [
+    "Tracking just like last lecture:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "confused-sunrise",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "n_turns = 500\n",
+    "deltap_ini = np.linspace(0, 0.01 * m.p0(), 20)\n",
+    "z_ini = np.zeros_like(deltap_ini)\n",
+    "\n",
+    "N = len(z_ini)\n",
+    "assert (N == len(deltap_ini))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "global-marsh",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "z = np.zeros((n_turns, N), dtype=np.float64)\n",
+    "deltap = np.zeros_like(z)\n",
+    "\n",
+    "z[0] = z_ini\n",
+    "deltap[0] = deltap_ini"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "continent-headquarters",
+   "metadata": {},
+   "source": [
+    "Record the evolution of energy $\\gamma_\\mathrm{ref}$ and also Hamiltonian values of particles during tracking:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "purple-token",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "gammas = np.zeros(n_turns, dtype=np.float64)\n",
+    "gammas[0] = m.gamma_ref\n",
+    "\n",
+    "H_values = np.zeros_like(z)\n",
+    "H_values[0] = hamiltonian(z_ini, deltap_ini, m) / m.p0()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "unable-wages",
+   "metadata": {},
+   "source": [
+    "The tracking loop:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "finnish-south",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for i_turn in range(1, n_turns):\n",
+    "    z[i_turn], deltap[i_turn] = track_one_turn(z[i_turn - 1], deltap[i_turn - 1], m)\n",
+    "    gammas[i_turn] = m.gamma_ref\n",
+    "    H_values[i_turn] = hamiltonian(z[i_turn], deltap[i_turn], m) / m.p0()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "taken-configuration",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.plot(gammas)\n",
+    "plt.xlabel('Turns')\n",
+    "plt.ylabel('$\\gamma_{ref}$');"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "computational-specification",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.scatter(z, deltap / m.p0(), marker='.', s=0.5)\n",
+    "plt.xlabel('$z$ [m]')\n",
+    "plt.ylabel('$\\Delta p/p_0$')\n",
+    "plot_hamiltonian(m, zleft=-150, zright=50, dpmax=0.017)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "political-bench",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.plot(H_values, c='C0');\n",
+    "plt.xlabel('Turns')\n",
+    "plt.ylabel(r'$\\mathcal{H}(z,\\Delta p)\\,/\\,p_0$');"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "backed-sentence",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def plot_rf_overview():\n",
+    "    z_range = np.linspace(-150, 40, num=1000)\n",
+    "    # z location of unstable fixed point:\n",
+    "    z_ufp = -m.circumference * (np.pi - 2 * m.phi_s) / (2 * np.pi * m.harmonic)\n",
+    "\n",
+    "    fig, ax = plt.subplots(3, 1, figsize=(6, 10), sharex=True)\n",
+    "\n",
+    "    plt.sca(ax[0])\n",
+    "    plt.plot(z_range, 1e-3 * m.voltage * np.sin(m.phi_s - 2 * np.pi * m.harmonic / m.circumference * z_range))\n",
+    "    plt.axhline(0, c='gray', lw=2)\n",
+    "    plt.axhline(1e-3 * m.voltage * np.sin(m.phi_s), c='purple', lw=2, ls='--')\n",
+    "    plt.axvline(0, c='purple', lw=2)\n",
+    "    plt.axvline(z_ufp, c='red', lw=2)\n",
+    "    plt.ylabel('rf wave $V(z)$ [kV]')\n",
+    "\n",
+    "    plt.sca(ax[1])\n",
+    "    plt.plot(z_range, 1e6 * U(z_range, m) / m.p0())\n",
+    "    plt.axhline(0, c='gray', lw=2)\n",
+    "    plt.ylabel(r'$U(z)\\,/\\,p_0\\cdot 10^6$')\n",
+    "\n",
+    "    plt.scatter([z_ufp], [0], marker='*', c='white', edgecolor='red', zorder=10)\n",
+    "    plt.scatter([0], [U(0, m) / m.p0()], marker='d', c='white', edgecolor='purple', zorder=10)\n",
+    "\n",
+    "    plt.sca(ax[2])\n",
+    "    plot_hamiltonian(m, zleft=z_range[0], zright=z_range[-1], cbar=False)\n",
+    "    plt.scatter([z_ufp], [0], marker='*', c='white', edgecolor='red', zorder=10)\n",
+    "    plt.scatter([0], [0], marker='d', c='white', edgecolor='purple')\n",
+    "    plt.xlabel('$z$ [m]')\n",
+    "    plt.ylabel('$\\delta$')\n",
+    "    plt.subplots_adjust(hspace=0)\n",
+    "    \n",
+    "    return fig, ax"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "exposed-retrieval",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plot_rf_overview();"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "classical-proxy",
+   "metadata": {},
+   "source": [
+    "<h2 style=\"color: #e6541a;\">Exercises based on Tracking (Solutions not included)</h2>\n",
+    "\n",
+    "<div style=\"color: #e6541a; margin-top: 2em;\">\n",
+    "Modify the machine parameters and particle initial conditions to answer the following questions: <br />\n",
+    "    \n",
+    "1. Do the Hamiltonian contours predict the tracked particle trajectories well?\n",
+    "---\n",
+    "2. Compare the stationary synchronous particle situation to the nonlinear pendulum: what is the meaning of the $\\pi$ phase offset of $\\varphi_s$ in terms of the pendulum? What state of the pendulum does the stable and what the unstable fixed point correspond to?\n",
+    "---\n",
+    "3. CERN PS accelerates protons from $\\gamma=3.1$ up to $\\gamma=27.7$. Is the transition energy crossed during acceleration? What does this mean for the setting of the synchronous phase $\\varphi_s$ in the control room? \n",
+    "---\n",
+    "4. What happens to phase focusing at the transition energy $\\gamma=\\gamma_\\mathrm{t}$? Can you explain why? (Think of the phase slippage mechanism.)\n",
+    "</div>\n",
+    "\n",
+    "$\\implies$ please make sure you understand and note down the answers to these questions: the concepts are central to accelerator physics and relevant for the exam."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "covered-toner",
+   "metadata": {},
+   "source": [
+    "<p style=\"color: #e6541a;\">$\\implies$ <i>Bonus: determine the synchrotron tune from tracking simulations (using NAFF) and compare to the derived formula for $Q_s$!</i></p>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "spatial-sodium",
+   "metadata": {},
+   "source": [
+    "<h3>Linear Congruential Generator (slide 19)</h3>"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "armed-strain",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "class RandomNumberGenerator(object):\n",
+    "    def __init__(self, M, a, c, seed):\n",
+    "        self.M = M\n",
+    "        self.a = a\n",
+    "        self.c = c\n",
+    "        self.xk = seed\n",
+    "\n",
+    "    def generate(self):\n",
+    "        xk1 = (self.a * self.xk + self.c) % self.M\n",
+    "        self.xk = xk1\n",
+    "        return xk1 / self.M"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "insured-sellers",
+   "metadata": {},
+   "source": [
+    "Instantiate the linear congruential generator by Lewis et al with a certain `seed`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "sought-cathedral",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "prng_standard = RandomNumberGenerator(\n",
+    "    M=2**31 - 1, \n",
+    "    a=7**5, \n",
+    "    c=0, \n",
+    "    seed=12345)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "dense-comfort",
+   "metadata": {},
+   "source": [
+    "Generate a set of numbers from the sequence and analyse:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "french-modem",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "results = [prng_standard.generate() for i in range(10000)]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "continued-accordance",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.hist(results)\n",
+    "plt.xlabel('$x$')\n",
+    "plt.ylabel('#draws');"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "heated-armenia",
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "plt.scatter(results[:-1], results[1:], s=1, marker='.')\n",
+    "plt.xlabel('$x_k$')\n",
+    "plt.ylabel('$x_{k+1}$');"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "packed-respondent",
+   "metadata": {},
+   "source": [
+    "<p style=\"color: #e6541a;\">$\\implies$ What happens when you change the parameters $M,a,c$?<br /><br />\n",
+    "Try e.g. $a=5$ or $M=2^{31}-2$...</p>"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "decimal-photograph",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "prng_standard2 = RandomNumberGenerator(\n",
+    "    M=2**31 - 2, \n",
+    "    a=7**5, \n",
+    "    c=0, \n",
+    "    seed=12345)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "generic-array",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "results2 = [prng_standard2.generate() for i in range(10000)]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "adult-joseph",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.hist(results2)\n",
+    "plt.xlabel('$x$')\n",
+    "plt.ylabel('#draws');"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ideal-dallas",
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "plt.scatter(results2[:-1], results2[1:], s=1, marker='.')\n",
+    "plt.xlabel('$x_k$')\n",
+    "plt.ylabel('$x_{k+1}$');"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "paperback-certification",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "prng_standard3 = RandomNumberGenerator(\n",
+    "    M=2**31 - 1, \n",
+    "    a=5, \n",
+    "    c=0, \n",
+    "    seed=12345)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "equipped-jewelry",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "results3 = [prng_standard3.generate() for i in range(10000)]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "german-retention",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.hist(results3)\n",
+    "plt.xlabel('$x$')\n",
+    "plt.ylabel('#draws');"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "designed-tribune",
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "plt.scatter(results3[:-1], results3[1:], s=1, marker='.')\n",
+    "plt.xlabel('$x_k$')\n",
+    "plt.ylabel('$x_{k+1}$');"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "authorized-carroll",
+   "metadata": {},
+   "source": [
+    "<h3>Box-Muller method (slide 20)</h3>"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "seeing-invalid",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "prng_1 = RandomNumberGenerator(\n",
+    "    M=2**31 - 1, \n",
+    "    a=7**5, \n",
+    "    c=0, \n",
+    "    seed=12345)\n",
+    "\n",
+    "prng_2 = RandomNumberGenerator(\n",
+    "    M=2**31 - 1, \n",
+    "    a=7**5, \n",
+    "    c=0, \n",
+    "    seed=42)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "informal-capture",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def generate_normal():\n",
+    "    xi1 = prng_1.generate()\n",
+    "    xi2 = prng_1.generate()\n",
+    "    r = np.sqrt(-2 * np.log(xi2))\n",
+    "    x = r * np.cos(2 * np.pi * xi1)\n",
+    "    y = r * np.sin(2 * np.pi * xi1)\n",
+    "    return x, y"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "fewer-series",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "results = np.array(\n",
+    "    [generate_normal() for i in range(10000)]\n",
+    ").flatten()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "thirty-wallace",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.hist(results, bins=20);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "mighty-practice",
+   "metadata": {},
+   "source": [
+    "<h3>NumPy has it all...</h3>\n",
+    "\n",
+    "The `numpy` library implements all of these (based on a better behaved variant of the linear congruential generator):"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "employed-newark",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.hist(np.random.random(size=10000));\n",
+    "plt.xlabel('$x$')\n",
+    "plt.ylabel('#draws');"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "frequent-ottawa",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.hist(np.random.normal(size=10000), bins=20);\n",
+    "plt.xlabel('$x$')\n",
+    "plt.ylabel('#draws');"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d1bb3dda",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "<h3>Initalization of longitudinal particle distribution: Interactive Tracking (slide 24)</h3>\n",
+    "\n",
+    "Initialise a bi-Gaussian distribution in the longitudinal phase-space plane for tracking!\n",
+    "\n",
+    "Refer once more to the CERN PS scenario, below transition and in a stationary rf bucket:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "68fb23fd",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m = Machine(gamma_ref=3.13, phi_s=0)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "df3f1231",
+   "metadata": {},
+   "source": [
+    "The length of the rf bucket corresponds to $C/h$:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "7ec8568c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m.circumference / m.harmonic"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "12e5014c",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "We choose an rms bunch length of $\\sigma_z=10\\,$m:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "83008665",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "sigma_z = 10"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "36894af5",
+   "metadata": {},
+   "source": [
+    "The corresponding rms momentum difference $\\sigma_{\\Delta p}$ is given through the equilibrium condition (equal Hamiltonian values):"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b96b8267",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "sigma_deltap = np.sqrt(\n",
+    "    2 * m.p0() / -m.eta(0) * \n",
+    "    charge * m.voltage * np.pi * m.harmonic / (beta(gamma(m.p0())) * c * m.circumference**2)\n",
+    ") * sigma_z"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "0383dda6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "sigma_dp = sigma_deltap / m.p0()\n",
+    "sigma_dp"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "26a48a18",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "plot_hamiltonian(m)\n",
+    "plt.scatter([sigma_z, 0], [0, sigma_dp], marker='*', c='k');"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3b0ea4d8",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "Back to tracking, out of the box:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9ba73ca6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "N = 1000\n",
+    "n_turns = 5000"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3eda594b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "np.random.seed(12345)\n",
+    "\n",
+    "z_ini = np.random.normal(loc=0, scale=sigma_z, size=N)\n",
+    "deltap_ini = np.random.normal(loc=0, scale=sigma_deltap, size=N)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a107fe88",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "z = np.zeros((n_turns, N), dtype=np.float64)\n",
+    "deltap = np.zeros_like(z)\n",
+    "\n",
+    "z[0] = z_ini\n",
+    "deltap[0] = deltap_ini"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "709fcf0d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for i_turn in trange(1, n_turns):\n",
+    "    z[i_turn], deltap[i_turn] = track_one_turn(z[i_turn - 1], deltap[i_turn - 1], m)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "06a939d9",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "Now let's return to the outlined Monte-Carlo approach, analysing the results in terms of statistical moments.\n",
+    "\n",
+    "First the centroid:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4c9d702a",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "-"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "plt.plot(np.mean(z, axis=1))\n",
+    "\n",
+    "plt.xlabel('Turns')\n",
+    "plt.ylabel(r'$\\langle z \\rangle$');"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3aa00a3e",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "Then the rms beam size (bunch length):"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "1fdb9767",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.plot(np.std(z, axis=1))\n",
+    "\n",
+    "plt.xlabel('Turns')\n",
+    "plt.ylabel(r'$\\sigma_z$');"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ecbcca76",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "<h2>What happened?</h2>\n",
+    "\n",
+    "Let's look at the generated initial distribution of macro-particles:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "71433316",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plot_hamiltonian(m);\n",
+    "plt.scatter(z[0], deltap[0] / m.p0(), marker='.', s=1);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0142e1f8",
+   "metadata": {},
+   "source": [
+    "$\\implies$ particles have been generated outside the rf bucket! "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "256c1d02",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "<h2>Rejection Sampling Method: Cut particles outside of the separatrix</h2>\n",
+    "\n",
+    "A solution to the problem of generating particles outside the separatrix: <i>reject</i> them at generation!\n",
+    "\n",
+    "$\\leadsto$ a word of caution: this approach modifies the effective distribution function (and the correspondingly generated effective rms values $\\sigma_z, \\sigma_\\delta$ become smaller)! "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "040a0303",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "np.random.seed(12345)\n",
+    "\n",
+    "z_ini = np.random.normal(loc=0, scale=sigma_z, size=N)\n",
+    "deltap_ini = np.random.normal(loc=0, scale=sigma_deltap, size=N)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "22cf6f11",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "<h2>Rejection Sampling Method: Resample these particles</h2>\n",
+    "\n",
+    "Hamiltonian values of particles outside separatrix are positive (below transition), $\\mathcal{H}>0$ (using the full nonlinear Hamiltonian)!\n",
+    "\n",
+    "NB: Due to the discrete kicks in the finite difference maps, the Hamiltonian is only an approximation for the separatrix: better remain at a few percent distance inside of it!"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f434f623",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "H_safetymargin = 0.05 * hamiltonian(0, 0, m)\n",
+    "\n",
+    "H_values = hamiltonian(z_ini, deltap_ini, m) - H_safetymargin\n",
+    "\n",
+    "while any(H_values >= 0):\n",
+    "    mask_bad = H_values >= 0\n",
+    "    N_bad = np.sum(mask_bad)\n",
+    "    print (N_bad)\n",
+    "    # re-initialise bad particles:\n",
+    "    z_ini[mask_bad] = np.random.normal(loc=0, scale=sigma_z, size=N_bad)\n",
+    "    deltap_ini[mask_bad] = np.random.normal(loc=0, scale=sigma_deltap, size=N_bad)\n",
+    "    # re-evaluate rejection condition\n",
+    "    H_values = hamiltonian(z_ini, deltap_ini, m) - H_safetymargin"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ee349b25",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "Now we should be good to go!"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "be119eb5",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plot_hamiltonian(m);\n",
+    "plt.scatter(z_ini, deltap_ini / m.p0(), marker='.', s=1);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "af1c6162",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "Tracking again..."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b812ae0e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "z = np.zeros((n_turns, N), dtype=np.float64)\n",
+    "deltap = np.zeros_like(z)\n",
+    "\n",
+    "z[0] = z_ini\n",
+    "deltap[0] = deltap_ini"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "644cc2bc",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for i_turn in trange(1, n_turns):\n",
+    "    z[i_turn], deltap[i_turn] = track_one_turn(z[i_turn - 1], deltap[i_turn - 1], m)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ddd7b0f6",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "Looking again at the centroid:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c544cbb3",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.plot(np.mean(z, axis=1))\n",
+    "\n",
+    "plt.xlabel('Turns')\n",
+    "plt.ylabel(r'$\\langle z \\rangle$');"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "580313a8",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "Then the rms bunch length:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9e750dee",
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "plt.plot(np.std(z, axis=1))\n",
+    "\n",
+    "plt.xlabel('Turns')\n",
+    "plt.ylabel(r'$\\sigma_z$');"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1604ad58",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "<p style=\"color: #e6541a;\">$\\implies$ try larger choices of initial $\\sigma_z$ and see where the bunch length evolution saturates.</p>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6ebf9daa",
+   "metadata": {},
+   "source": [
+    "$\\leadsto$ another problem with initialising Gaussian distributions: at larger amplitudes $z$, the small-amplitude approximation with $\\mathcal{H}_\\mathrm{stat,small}$ necessarily breaks down! A Gaussian particle distribution is <b>not</b> in equilibrium for sufficiently large rms values in a nonlinear potential, the particles will <b>filament</b>! (...and the rms emittance will grow, as one can observe in the final equilibrium rms bunch length which is larger than the initial $\\sigma_z$!)\n",
+    "\n",
+    "$\\implies$ generally require full nonlinear Hamiltonian $\\mathcal{H}$ to construct PDF\n",
+    "\n",
+    "$$\\psi(\\mathcal{H})\\propto\\exp\\left(\\cfrac{\\mathcal{H}}{\\mathcal{H}_0}\\right)$$"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "eb412faa",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "<h2>RMS Emittance</h2>\n",
+    "\n",
+    "Define statistical <b>rms emittance</b> as in Lecture 2:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a8ca51ed",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def emittance(z, deltap):\n",
+    "    N = len(z)\n",
+    "    \n",
+    "    # subtract centroids\n",
+    "    z = z - 1/N * np.sum(z)\n",
+    "    deltap = deltap - 1/N * np.sum(deltap)\n",
+    "    \n",
+    "    # compute Σ matrix entries\n",
+    "    z_sq = 1/N * np.sum(z * z)\n",
+    "    deltap_sq = 1/N * np.sum(deltap * deltap)\n",
+    "    crossterm = 1/N * np.sum(z * deltap)\n",
+    "    \n",
+    "    # determinant of Σ matrix\n",
+    "    epsilon = np.sqrt(z_sq * deltap_sq - crossterm * crossterm)\n",
+    "    return epsilon"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "1743b226",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "epsn_z = np.array([emittance(z_i, deltap_i) for z_i, deltap_i in zip(z, deltap)])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5febd72c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.plot(epsn_z / e)\n",
+    "\n",
+    "plt.xlabel('Turns')\n",
+    "plt.ylabel('$\\epsilon_z$ [eV.s]');"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d9417871",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "<p style=\"color: #e6541a;\">$\\implies$ try larger choices of initial $\\sigma_z$ and observe how the emittance evolves.</p>"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "canadian-russian",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.9.18"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}