{ "cells": [ { "cell_type": "markdown", "id": "26960bb2", "metadata": {}, "source": [ "# Introduction to Wavelength Frame Multiplication\n", "\n", "This notebook aims to explain the concept of wavelength frame multiplication (WFM),\n", "why is it used, how it works, and what results can be expected from using WFM at a neutron beamline.\n", "\n", "Much of the material presented here was inspired by / copied from the paper by [Schmakat et al. (2020)](#schmakat2020),\n", "which we highly recommend to the reader, for more details on how a WFM chopper system is designed." ] }, { "cell_type": "code", "execution_count": null, "id": "4e4505fd", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "plt.ioff() # Turn of auto-showing of figures\n", "import scipp as sc\n", "from scipp import constants\n", "import scippneutron as scn\n", "import ess.wfm as wfm\n", "import ess.choppers as ch" ] }, { "cell_type": "code", "execution_count": null, "id": "aacfce07-bbc4-4d76-a8fa-852cb0e1ca8f", "metadata": { "nbsphinx": "hidden" }, "outputs": [], "source": [ "# Note: this cell defines code to generate figures which are used below.\n", "# This code is hidden in the documentation pages.\n", "from matplotlib.patches import Rectangle\n", "import inspect\n", "import io\n", "\n", "fig_props = {\"figsize\": (6.5, 4.75), \"dpi\": 96}\n", "\n", "\n", "def figure1():\n", " t_P = sc.scalar(2.860e+03, unit='us')\n", " detector_position = sc.vector(value=[0., 0., 60.0], unit='m')\n", " z_det = sc.norm(detector_position).value\n", "\n", " fig, ax = plt.subplots(**fig_props)\n", " ax.add_patch(\n", " Rectangle((0, 0), t_P.value, -0.05 * z_det, lw=1, fc='grey', ec='k', zorder=10))\n", " # Indicate source pulse and add the duration.\n", " ax.text(0,\n", " -0.05 * z_det,\n", " \"Source pulse ({} {})\".format(t_P.value, t_P.unit),\n", " ha=\"left\",\n", " va=\"top\",\n", " fontsize=8)\n", " ax.plot([0, 1.0e4], [z_det] * 2, lw=3, color='grey')\n", " ax.text(0., z_det, 'Detector', ha='left', va='top')\n", " # Draw 2 neutron paths\n", " ax.plot([0.02 * t_P.value, 4.0e3], [0, z_det], lw=2, color='r')\n", " ax.plot([t_P.value, 4.0e3], [0, z_det], lw=2, color='b')\n", " ax.text(3.7e3, 0.5 * z_det, r'$\\lambda_{1}$', ha='left', va='center', color='b')\n", " ax.text(1.5e3, 0.5 * z_det, r'$\\lambda_{2}$', ha='left', va='center', color='r')\n", " ax.set_xlabel(\"Time [microseconds]\")\n", " ax.set_ylabel(\"Distance [m]\")\n", " ax.set_title('Figure 1')\n", " return fig\n", "\n", "\n", "def figure2():\n", " t_P = sc.scalar(2.860e+03, unit='us')\n", " detector_position = sc.vector(value=[0., 0., 60.0], unit='m')\n", " t_0 = sc.scalar(5.0e+02, unit='us')\n", " t_A = (t_0 + t_P).value\n", " z_det = sc.norm(detector_position).value\n", " fig, ax = plt.subplots(**fig_props)\n", " ax.add_patch(\n", " Rectangle((0, 0), (t_P + t_0).value,\n", " -0.05 * z_det,\n", " lw=1,\n", " fc='grey',\n", " ec='k',\n", " zorder=10))\n", "\n", " # Indicate source pulse and add the duration.\n", " ax.text(0, -0.05 * z_det, \"Source pulse\", ha=\"left\", va=\"top\", fontsize=8)\n", " ax.plot([0, 3.1e4], [z_det] * 2, lw=3, color='grey')\n", " ax.text(0., z_det, 'Detector', ha='left', va='top')\n", "\n", " dt = 1000.0\n", " z_wfm = 15.0\n", " xmin = 0.0\n", " for i in range(3):\n", " xmax = 3000.0 + (i * 2.0 * dt)\n", " ax.plot([xmin, xmax], [z_wfm] * 2, color='k')\n", " xmin = xmax + dt\n", " ax.plot([xmin, 3.1e4], [z_wfm] * 2, color='k')\n", " ax.text(25000.0, z_wfm, \"WFM\", ha='left', va='top')\n", " ax.plot([t_A, 5000.0], [0, z_det], color='r')\n", " ax.plot([2600.0, 5000.0], [0, z_det], color='b')\n", " ax.plot([t_A, 13000.0], [0, z_det], color='r')\n", " ax.plot([2400.0, 13000.0], [0, z_det], color='b')\n", " ax.plot([t_A, 21500.0], [0, z_det], color='r')\n", " ax.plot([2200.0, 21500.0], [0, z_det], color='b')\n", "\n", " ax.set_xlabel(\"Time [microseconds]\")\n", " ax.set_ylabel(\"Distance [m]\")\n", " ax.set_title('Figure 2')\n", " return fig\n", "\n", "\n", "def figure3():\n", " x = np.linspace(0, 5.0, 100)\n", " a = 4.0\n", " b = 0.0\n", " c = 1.5e10\n", " d = 3.0\n", " e = 3.0\n", " y = 2.0 * c / (np.exp(-a * (x - b)) + 1.0) - c\n", " n = 60\n", " y2 = c * np.exp(-e * (x - d))\n", " y[n:] = y2[n:]\n", " fig, ax = plt.subplots(**fig_props)\n", " ax.plot(x, y, lw=2, color='k')\n", "\n", " i1 = 5\n", " i2 = 65\n", " ax.fill([x[i1]] + x[i1:i2].tolist() + [x[i2 - 1]], [0] + y[i1:i2].tolist() + [0],\n", " alpha=0.3)\n", "\n", " fs = 15\n", "\n", " ax.axvline(x[i1], color='k')\n", " ax.axvline(x[i2 - 1], color='k')\n", " ax.text(x[i1], 1.58e10, r' $t_{0}$', ha='left', va='top', fontsize=fs)\n", " ax.text(x[i2 - 1], 1.58e10, r' $t_{\\rm A}$', ha='left', va='top', fontsize=fs)\n", " ax.plot([4.5] * 2, [0, 0.3e10], color='k')\n", " ax.text(4.5, 0.3e10, r'$t_{\\rm B}$', ha='center', va='bottom', fontsize=fs)\n", " ax.annotate(text='',\n", " xy=(x[i1], 0.7e10),\n", " xytext=(x[i2 - 1], 0.7e10),\n", " arrowprops=dict(arrowstyle='<->'))\n", " ax.text(0.5 * (x[i1] + x[i2 - 1]),\n", " 0.7e10,\n", " 'utilised\\n pulse length',\n", " ha='center',\n", " va='bottom',\n", " fontsize=fs)\n", " ax.text(0.5 * (x[i1] + x[i2 - 1]),\n", " 0.7e10,\n", " r'$t_{\\rm P}$',\n", " ha='center',\n", " va='top',\n", " fontsize=fs)\n", "\n", " ax.set_ylim(0., 1.6e10)\n", " ax.set_xlabel(\"Time [ms]\")\n", " ax.set_ylabel(r\"Flux density $[{\\rm n/s/cm}^2]$\")\n", " ax.set_title('Figure 3')\n", " return fig\n", "\n", "\n", "def figure4():\n", " coords = wfm.make_fake_beamline(\n", " nframes=1,\n", " chopper_wfm_1_position=sc.vector(value=[0.0, 0.0, 4.5], unit='m'),\n", " chopper_wfm_2_position=sc.vector(value=[0.0, 0.0, 5.5], unit='m')\n", " )\n", " coords['position'] = sc.vector(value=[0., 0., 15.], unit='m')\n", " ds = sc.Dataset(coords=coords)\n", "\n", " z_det = sc.norm(ds.coords[\"position\"]).value\n", " t_0 = ds.coords[\"source_pulse_t_0\"].value\n", " t_A = (ds.coords[\"source_pulse_length\"] + ds.coords[\"source_pulse_t_0\"]).value\n", " t_B = (ds.coords[\"source_pulse_length\"] + 2.0 * ds.coords[\"source_pulse_t_0\"]).value\n", " z_foc = 12.0\n", " tmax_glob = 1.4e4\n", " height = 0.02\n", "\n", " chopper_wfm1 = coords[\"chopper_wfm_1\"].value\n", " chopper_wfm2 = coords[\"chopper_wfm_2\"].value\n", "\n", " fig, ax = plt.subplots(**fig_props)\n", " ax.add_patch(\n", " Rectangle((0, 0), t_B, -height * z_det, lw=1, fc='lightgrey', ec='k',\n", " zorder=10))\n", " ax.add_patch(\n", " Rectangle((ds.coords[\"source_pulse_t_0\"].value, 0),\n", " ds.coords[\"source_pulse_length\"].value,\n", " -height * z_det,\n", " lw=1,\n", " fc='grey',\n", " ec='k',\n", " zorder=10))\n", "\n", " # Indicate source pulse and add the duration.\n", " ax.text(ds.coords[\"source_pulse_t_0\"].value,\n", " -height * z_det,\n", " r\"$t_{0}$\",\n", " ha=\"center\",\n", " va=\"top\",\n", " fontsize=8)\n", " ax.text(t_A, -height * z_det, r\"$t_{A}$\", ha=\"center\", va=\"top\", fontsize=8)\n", " ax.text(t_B, -height * z_det, r\"$t_{B}$\", ha=\"center\", va=\"top\", fontsize=8)\n", "\n", " z_wfm = sc.norm(0.5 * (chopper_wfm1[\"position\"].data + chopper_wfm2[\"position\"].data)).value\n", " xmin = ch.time_open(chopper_wfm1).values[0]\n", " xmax = ch.time_closed(chopper_wfm1).values[0]\n", " dt = xmax - xmin\n", "\n", " ax.plot([0, xmin], [z_wfm] * 2, color='k')\n", " ax.plot([xmax, tmax_glob], [z_wfm] * 2, color='k')\n", " ax.text(tmax_glob, z_wfm, \"WFMC\", ha='right', va='top')\n", "\n", " slope_lambda_max = z_wfm / (xmin - t_0)\n", " slope_lambda_min = z_wfm / (xmax - t_A)\n", " int_lambda_max = z_wfm - slope_lambda_max * xmin\n", " int_lambda_min = z_wfm - slope_lambda_min * xmax\n", " x_lambda_max = (z_det - int_lambda_max) / slope_lambda_max\n", " x_lambda_min = (z_det - int_lambda_min) / slope_lambda_min\n", "\n", " ax.plot([t_0, x_lambda_max, x_lambda_max + dt, t_0 + dt], [0.0, z_det, z_det, 0],\n", " color='C0')\n", " ax.plot([t_A, x_lambda_min, x_lambda_min - dt, t_A - dt], [0.0, z_det, z_det, 0],\n", " color='C2')\n", "\n", " x_lambda_max_foc = (z_foc - int_lambda_max) / slope_lambda_max + dt\n", " x_lambda_min_foc = (z_foc - int_lambda_min) / slope_lambda_min - dt\n", " ax.plot([0, x_lambda_min_foc], [z_foc] * 2, color='k')\n", " ax.plot([x_lambda_max_foc, tmax_glob], [z_foc] * 2, color='k')\n", " ax.text(0.0, z_foc, \"FOC\", ha='left', va='top')\n", "\n", " slope_lambda_min_prime = z_wfm / (xmin - t_B)\n", " slope_lambda_max_prime = z_wfm / xmax\n", " int_lambda_max_prime = z_wfm - slope_lambda_max_prime * xmax\n", " int_lambda_min_prime = z_wfm - slope_lambda_min_prime * xmin\n", " x_lambda_max_prime = (z_foc - int_lambda_max_prime) / slope_lambda_max_prime\n", " x_lambda_min_prime = (z_foc - int_lambda_min_prime) / slope_lambda_min_prime\n", "\n", " ax.plot([t_B, x_lambda_min_prime], [0.0, z_foc], color='k', ls='dashed', lw=1)\n", " ax.plot([0, x_lambda_max_prime], [0.0, z_foc], color='k', ls='dashed', lw=1)\n", "\n", " ax.text(x_lambda_min - dt,\n", " z_det,\n", " r'$\\lambda_{\\rm min}$',\n", " ha='right',\n", " va='top',\n", " color='C2')\n", " ax.text(x_lambda_max + dt,\n", " z_det,\n", " r'$\\lambda_{\\rm max}$',\n", " ha='left',\n", " va='top',\n", " color='C0')\n", " ax.text(x_lambda_min_prime,\n", " z_foc,\n", " r\"$\\lambda_{\\rm min}^{'}$\",\n", " ha='left',\n", " va='top',\n", " color='k')\n", " ax.text(x_lambda_max_prime,\n", " z_foc,\n", " r\"$\\lambda_{\\rm max}^{'}$\",\n", " ha='left',\n", " va='top',\n", " color='k')\n", "\n", " ax.plot([xmin] * 2, [z_wfm, z_det + 1.0], lw=1, color='k')\n", " ax.plot([x_lambda_min - dt] * 2, [z_det, z_det + 1.0], lw=1, color='k')\n", " ax.plot([x_lambda_min] * 2, [z_det, z_det + 1.0], lw=1, color='k')\n", " ax.plot([x_lambda_max + dt] * 2, [z_det, z_det + 1.0], lw=1, color='k')\n", " ax.plot([x_lambda_max] * 2, [z_det, z_det + 1.0], lw=1, color='k')\n", "\n", " ax.fill([t_0 + dt, t_A - dt, 3013.10], [0, 0, 3.2963],\n", " color='mediumpurple',\n", " alpha=0.3,\n", " zorder=-2)\n", " ax.fill([x_lambda_min, x_lambda_max, 4515.077], [z_det, z_det, 6.704],\n", " color='mediumpurple',\n", " alpha=0.3,\n", " zorder=-2)\n", "\n", " ax.annotate(text='',\n", " xy=(xmin, z_det + 0.7),\n", " xytext=(x_lambda_min - dt, z_det + 0.7),\n", " arrowprops=dict(arrowstyle='<->'))\n", " ax.text(0.5 * (xmin + x_lambda_min - dt),\n", " z_det + 0.7,\n", " r'$t(\\lambda_{\\rm min})$',\n", " va='bottom',\n", " ha='center')\n", " ax.text(x_lambda_min - 0.5 * dt,\n", " z_det + 0.7,\n", " r'$\\Delta t$',\n", " va='bottom',\n", " ha='center')\n", " ax.text(x_lambda_max + 0.5 * dt,\n", " z_det + 0.7,\n", " r'$\\Delta t$',\n", " va='bottom',\n", " ha='center')\n", "\n", " ax.plot([0, tmax_glob], [z_det] * 2, lw=3, color='grey')\n", " ax.text(0., z_det, 'Detector', ha='left', va='top')\n", "\n", " ax.grid(True, color='lightgray', linestyle=\"dotted\")\n", " ax.set_axisbelow(True)\n", " ax.set_xlabel(\"Time [microseconds]\")\n", " ax.set_ylabel(\"Distance [m]\")\n", " ax.set_title('Figure 4')\n", " return fig\n", "\n", "\n", "def figure5():\n", " coords = wfm.make_fake_beamline(\n", " nframes=1,\n", " chopper_wfm_1_position=sc.vector(value=[0.0, 0.0, 4.5], unit='m'),\n", " chopper_wfm_2_position=sc.vector(value=[0.0, 0.0, 5.5], unit='m')\n", " )\n", " coords['position'] = sc.vector(value=[0., 0., 15.], unit='m')\n", " ds = sc.Dataset(coords=coords)\n", " z_det = sc.norm(ds.coords[\"position\"]).value\n", " frames = wfm.get_frames(ds)\n", " fig = wfm.plot.time_distance_diagram(ds)\n", " ax = fig.get_axes()[0]\n", "\n", " chopper_wfm1 = coords[\"chopper_wfm_1\"].value\n", " chopper_wfm2 = coords[\"chopper_wfm_2\"].value\n", " z_wfm = sc.norm(0.5 * (chopper_wfm1[\"position\"].data + chopper_wfm2[\"position\"].data)).value\n", " xmax = ch.time_closed(chopper_wfm1).values[0]\n", " z_foc = 12.0\n", "\n", " ax.plot([xmax] * 2, [z_wfm, z_det + 1.0], lw=1, color='k')\n", " ax.plot([0, frames[\"time_max\"].values[0]], [z_wfm] * 2,\n", " lw=1,\n", " color='k',\n", " ls='dotted')\n", " ax.text(frames[\"time_max\"].values[0],\n", " z_wfm,\n", " r'$z_{\\rm WFM}$',\n", " ha='left',\n", " va='center')\n", "\n", " ax.plot([0, 5770.5], [z_foc] * 2, color='k')\n", " ax.plot([9578.9, frames[\"time_max\"].values[0]], [z_foc] * 2, color='k')\n", " ax.text(frames[\"time_max\"].values[0], z_foc, 'FOC', ha='right', va='bottom')\n", "\n", " ax.plot([(frames[\"time_min\"] + frames[\"delta_time_min\"]).values[0]] * 2,\n", " [z_det, z_det + 1.0],\n", " lw=1,\n", " color='k')\n", " ax.plot([frames[\"time_min\"].values[0]] * 2, [z_det, z_det + 1.0], lw=1, color='k')\n", " ax.plot([(frames[\"time_max\"] - frames[\"delta_time_max\"]).values[0]] * 2,\n", " [z_det, z_det + 1.0],\n", " lw=1,\n", " color='k')\n", " ax.plot([frames[\"time_max\"].values[0]] * 2, [z_det, z_det + 1.0], lw=1, color='k')\n", "\n", " xmid = (0.5 * ((frames[\"time_min\"] + frames[\"time_min\"] +\n", " frames[\"delta_time_min\"]).data)).values[0]\n", " ax.plot([xmid] * 2, [z_det, z_det + 0.5], lw=1, color='k')\n", "\n", " ax.annotate(text='',\n", " xy=(xmax, z_det + 0.4),\n", " xytext=(xmid, z_det + 0.4),\n", " arrowprops=dict(arrowstyle='<->'))\n", " ax.text(0.5 * (xmax + frames[\"time_min\"].values),\n", " z_det + 0.4,\n", " r'$t(\\lambda_{N})$',\n", " va='bottom',\n", " ha='center')\n", " ax.text(xmid,\n", " z_det + 1.0,\n", " r'$\\Delta t(\\lambda_{N})$',\n", " va='bottom',\n", " ha='center',\n", " color='C2')\n", " ax.text((0.5 * ((frames[\"time_max\"] + frames[\"time_max\"] -\n", " frames[\"delta_time_max\"]).data)).values,\n", " z_det + 1.0,\n", " r'$\\Delta t(\\lambda_{N+1})$',\n", " va='bottom',\n", " ha='center',\n", " color='C0')\n", " ax.plot([xmax, xmid], [z_wfm, z_det], lw=1, ls='dashed', color='k')\n", " ax.text(frames['time_min'].values,\n", " z_det,\n", " r'$\\lambda_{N}$ ',\n", " ha='right',\n", " va='top',\n", " color='C2')\n", " ax.text(frames['time_max'].values,\n", " z_det,\n", " r'$\\lambda_{N+1}$',\n", " ha='left',\n", " va='top',\n", " color='C0')\n", " ax.annotate(text='',\n", " xy=(0, 14),\n", " xytext=(xmax, 14),\n", " arrowprops=dict(arrowstyle='<->'))\n", " ax.text(0.5 * xmax, 14, r'$t_{\\rm WFM}(N)$', va='top', ha='center')\n", "\n", " ax.lines[4].set_color('C2')\n", " ax.patches[2].set_color('mediumpurple')\n", " ax.set_xlim(-400, 12500)\n", " ax.set_title('Figure 5')\n", " return fig\n", "\n", "\n", "def figure6():\n", " coords = wfm.make_fake_beamline(\n", " nframes=2,\n", " chopper_wfm_1_position=sc.vector(value=[0.0, 0.0, 4.5], unit='m'),\n", " chopper_wfm_2_position=sc.vector(value=[0.0, 0.0, 5.5], unit='m')\n", " )\n", " coords['position'] = sc.vector(value=[0., 0., 15.], unit='m')\n", " ds = sc.Dataset(coords=coords)\n", " frames = wfm.get_frames(ds)\n", " fig = wfm.plot.time_distance_diagram(ds)\n", " ax = fig.get_axes()[0]\n", "\n", " chopper_wfm1 = coords[\"chopper_wfm_1\"].value\n", " chopper_wfm2 = coords[\"chopper_wfm_2\"].value\n", " z_wfm = sc.norm(0.5 * (chopper_wfm1[\"position\"].data + chopper_wfm2[\"position\"].data)).value\n", " z_det = sc.norm(ds.coords[\"position\"]).value\n", "\n", " ax.plot([0, frames[\"time_max\"].values[-1]], [z_wfm] * 2,\n", " lw=1,\n", " color='k',\n", " ls='dotted')\n", " ax.text(frames[\"time_max\"].values[-1],\n", " z_wfm,\n", " r'$z_{\\rm WFM}$',\n", " ha='left',\n", " va='center')\n", "\n", " ax.plot([(frames[\"time_min\"] + frames[\"delta_time_min\"]).values] * 2,\n", " [z_det, z_det + 1.0],\n", " lw=1,\n", " color='k')\n", " ax.plot([frames[\"time_min\"].values] * 2, [z_det, z_det + 1.0], lw=1, color='k')\n", " ax.plot([(frames[\"time_max\"] - frames[\"delta_time_max\"]).values] * 2,\n", " [z_det, z_det + 1.0],\n", " lw=1,\n", " color='k')\n", " ax.plot([frames[\"time_max\"].values] * 2, [z_det, z_det + 1.0], lw=1, color='k')\n", "\n", " xmid_min = (0.5 * ((frames[\"time_min\"] + frames[\"time_min\"] +\n", " frames[\"delta_time_min\"]).data)).values\n", " xmid_max = (0.5 * ((frames[\"time_max\"] + frames[\"time_max\"] -\n", " frames[\"delta_time_max\"]).data)).values\n", "\n", " ax.text(xmid_min[0],\n", " z_det + 1.0,\n", " r'$\\lambda_{N=1}$',\n", " va='bottom',\n", " ha='center',\n", " color='C2')\n", " ax.text(xmid_max[0],\n", " z_det + 1.0,\n", " r'$\\lambda_{2}$',\n", " va='bottom',\n", " ha='center',\n", " color='C0')\n", " ax.text(xmid_min[1],\n", " z_det + 1.0,\n", " r'$\\lambda_{2}$',\n", " va='bottom',\n", " ha='center',\n", " color='C0')\n", " ax.text(xmid_max[1],\n", " z_det + 1.0,\n", " r'$\\lambda_{3}$',\n", " va='bottom',\n", " ha='center',\n", " color='C1')\n", "\n", " ax.lines[6].set_color('C2')\n", " ax.lines[8].set_color('C0')\n", " ax.patches[2].set_color('mediumpurple')\n", " ax.patches[5].set_color('grey')\n", " ax.set_title('Figure 6')\n", " return fig" ] }, { "cell_type": "markdown", "id": "51753c2f", "metadata": {}, "source": [ "