diff --git a/docs/lambda-k-pi/ampform-dpd.ipynb b/docs/lambda-k-pi/ampform-dpd.ipynb new file mode 100644 index 0000000..40b351e --- /dev/null +++ b/docs/lambda-k-pi/ampform-dpd.ipynb @@ -0,0 +1,908 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Amplitude model with `ampform-dpd`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "PWA study on $p \\gamma \\to \\Lambda K^+ \\pi^0$.\n", + "We formulate the helicity amplitude model symbolically using AmpForm-DPD here." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "mystnb": { + "code_prompt_show": "Import Python libraries" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "from __future__ import annotations\n", + "\n", + "import logging\n", + "import os\n", + "import warnings\n", + "from collections import defaultdict\n", + "from fractions import Fraction\n", + "from textwrap import dedent\n", + "\n", + "import ampform\n", + "import graphviz\n", + "import ipywidgets as w\n", + "import jax.numpy as jnp\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import qrules\n", + "import sympy as sp\n", + "from ampform.dynamics.builder import RelativisticBreitWignerBuilder\n", + "from ampform.io import improve_latex_rendering\n", + "from ampform_dpd import DalitzPlotDecompositionBuilder\n", + "from ampform_dpd.adapter.qrules import normalize_state_ids, to_three_body_decay\n", + "from ampform_dpd.decay import DecayNode, ThreeBodyDecayChain\n", + "from ampform_dpd.dynamics.builder import create_mass_symbol, get_mandelstam_s\n", + "from ampform_dpd.io import aslatex\n", + "from IPython.display import SVG, Image, Markdown, Math, display\n", + "from qrules.particle import Particle, Spin, create_particle, load_pdg\n", + "from tensorwaves.data import (\n", + " SympyDataTransformer,\n", + ")\n", + "from tensorwaves.function.sympy import create_parametrized_function\n", + "\n", + "STATIC_PAGE = \"EXECUTE_NB\" in os.environ\n", + "\n", + "os.environ[\"TF_CPP_MIN_LOG_LEVEL\"] = \"3\"\n", + "logging.disable(logging.WARNING)\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "improve_latex_rendering()\n", + "particle_db = load_pdg()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Decay definition" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Particle definitions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [ + "scroll-output", + "full-width", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "def generate_markdown_table(particles: list[str]):\n", + " src = dedent(r\"\"\"\n", + " | Particle | Name | PID | $J^{PC} (I^G)$ | $I_3$ | $M$ | $\\Gamma$ | $Q$ | $S$ | $B$ |\n", + " | :------- |------|-----|----------------|-------|-----|----------|-----|-----|-----|\n", + " \"\"\")\n", + " for name in particles:\n", + " p = particle_db[name]\n", + " src += f\"| ${p.latex}$ | `{p.name}` | {p.pid} | {jpc_ig(p)} | {i_3(p)} | {p.mass:.3g}| {p.width:g} | {p.charge} |{p.strangeness} | {p.baryon_number}|\\n\"\n", + " return src\n", + "\n", + "\n", + "def jpc_ig(particle: Particle) -> str:\n", + " j = format_fraction(particle.spin)\n", + " p = format_parity(particle.parity)\n", + " c = format_parity(particle.c_parity)\n", + " if particle.isospin is None:\n", + " return f\"${j}^{{{p}{c}}}$\"\n", + " i = format_fraction(particle.isospin.magnitude)\n", + " g = format_parity(particle.g_parity)\n", + " return rf\"${j}^{{{p}{c}}} \\; ({i}^{{{g}}})$\"\n", + "\n", + "\n", + "def i_3(particle: Particle) -> str:\n", + " if particle.isospin is None:\n", + " return \"N/A\"\n", + " return f\"${format_fraction(particle.isospin.projection)}$\"\n", + "\n", + "\n", + "def format_fraction(value: float) -> str:\n", + " value = Fraction(value)\n", + " if value.denominator == 1:\n", + " return str(value.numerator)\n", + " return rf\"\\frac{{{value.numerator}}}{{{value.denominator}}}\"\n", + "\n", + "\n", + "def format_parity(parity: int | None) -> str:\n", + " if parity is None:\n", + " return \" \"\n", + " if parity == -1:\n", + " return \"-\"\n", + " if parity == 1:\n", + " return \"+\"\n", + " raise NotImplementedError\n", + "\n", + "\n", + "particles = [\"Lambda\", \"K+\", \"pi0\", \"gamma\", \"p\"]\n", + "src = generate_markdown_table(particles)\n", + "Markdown(src)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the table above, PID is the PDG ID from PDG particle numbering scheme,\n", + "$J$ is the spin, $P$ is the parity, $C$ is the C parity, $I$ is the isospin (magnitude), $G$ is the G parity.\n", + "$I_3$ is the isospin projection (or the 3rd component),\n", + "$M$ is the mass,\n", + "$\\Gamma$ is the width,\n", + "$Q$ is the charge,\n", + "$S$ is the strangeness number,\n", + "and $B$ is the baryon number." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initial state definition" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Mass for $p \\gamma$ system" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "E_lab_gamma = 8.5\n", + "m_proton = 0.938\n", + "m_0 = np.sqrt(2 * E_lab_gamma * m_proton + m_proton**2)\n", + "m_eta = 0.548\n", + "m_pi = 0.135\n", + "m_0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add custom particle $p \\gamma$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pgamma1 = Particle(\n", + " name=\"pgamma1\",\n", + " latex=r\"p\\gamma (s1/2)\",\n", + " spin=0.5,\n", + " mass=m_0,\n", + " charge=1,\n", + " isospin=Spin(1 / 2, +1 / 2),\n", + " baryon_number=1,\n", + " parity=-1,\n", + " pid=99990,\n", + ")\n", + "pgamma2 = create_particle(\n", + " template_particle=pgamma1,\n", + " name=\"pgamma2\",\n", + " latex=R\"p\\gamma (s3/2)\",\n", + " spin=1.5,\n", + " pid=pgamma1.pid + 1,\n", + ")\n", + "particle_db.update([pgamma1, pgamma2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Generate transitions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For simplicity, we use the initial state $p \\gamma$ (with spin-$\\frac{1}{2}$), \n", + "and set the allowed interaction type to be strong only,\n", + "the formalism is selected to be helicity formalism instead of canonical." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{seealso}\n", + "[Helicity versus canonical](https://ampform.readthedocs.io/stable/usage/helicity/formalism.html)\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "reaction = qrules.generate_transitions(\n", + " initial_state=(\"pgamma1\"),\n", + " final_state=[\"Lambda\", \"K+\", \"pi0\"],\n", + " allowed_interaction_types=[\"strong\"],\n", + " formalism=\"helicity\",\n", + " particle_db=particle_db,\n", + " max_angular_momentum=4,\n", + " max_spin_magnitude=4,\n", + " mass_conservation_factor=0,\n", + ")\n", + "reaction = normalize_state_ids(reaction)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "dot = qrules.io.asdot(reaction, collapse_graphs=True)\n", + "graphviz.Source(dot)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + } + }, + "outputs": [], + "source": [ + "decay = to_three_body_decay(reaction.transitions)\n", + "Math(aslatex(decay, with_jp=True))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Formulate amplitude model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_builder = ampform.get_builder(reaction)\n", + "model_builder.config.scalar_initial_state_mass = True\n", + "model_builder.config.stable_final_state_ids = list(reaction.final_state)\n", + "bw_builder = RelativisticBreitWignerBuilder(\n", + " energy_dependent_width=False,\n", + " form_factor=False,\n", + ")\n", + "for name in reaction.get_intermediate_particles().names:\n", + " model_builder.dynamics.assign(name, bw_builder)\n", + "model = model_builder.formulate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.intensity" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + } + }, + "outputs": [], + "source": [ + "def formulate_breit_wigner(\n", + " decay: ThreeBodyDecayChain,\n", + ") -> tuple[sp.Expr, dict[sp.Symbol, complex | float]]:\n", + " decay_node = decay.decay_node\n", + " s = get_mandelstam_s(decay_node)\n", + " parameter_defaults = {}\n", + " breit_wigner, new_pars = _create_breit_wigner(s, decay_node)\n", + " parameter_defaults.update(new_pars)\n", + " return (\n", + " breit_wigner,\n", + " parameter_defaults,\n", + " )\n", + "\n", + "\n", + "def _create_breit_wigner(\n", + " s: sp.Symbol, isobar: DecayNode\n", + ") -> tuple[sp.Expr, dict[sp.Symbol, complex | float]]:\n", + " mass = create_mass_symbol(isobar.parent)\n", + " width = sp.Symbol(Rf\"\\Gamma_{{{isobar.parent.latex}}}\", nonnegative=True)\n", + " breit_wigner_expr = (mass * width) / (mass**2 - s - width * mass * sp.I)\n", + " parameter_defaults: dict[sp.Symbol, complex | float] = {\n", + " mass: isobar.parent.mass,\n", + " width: isobar.parent.width,\n", + " }\n", + " return breit_wigner_expr, parameter_defaults" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_builder = DalitzPlotDecompositionBuilder(decay)\n", + "for chain in model_builder.decay.chains:\n", + " model_builder.dynamics_choices.register_builder(chain, formulate_breit_wigner)\n", + "model = model_builder.formulate(reference_subsystem=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.intensity" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first term in the amplitude model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [ + "hide-input", + "scroll-output" + ] + }, + "outputs": [], + "source": [ + "(symbol, expr), *_ = model.amplitudes.items()\n", + "Math(aslatex({symbol: expr}, terms_per_line=1))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "mystnb": { + "code_prompt_show": "Model parameters" + }, + "tags": [ + "scroll-output", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "if STATIC_PAGE:\n", + " src = aslatex(model.parameter_defaults)\n", + " display(Math(src))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "mystnb": { + "code_prompt_show": "Kinematic variable definitions" + }, + "tags": [ + "hide-input", + "scroll-output" + ] + }, + "outputs": [], + "source": [ + "Math(aslatex(model.variables))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "unfolded_expression = model.full_expression.doit()\n", + "intensity_func = create_parametrized_function(\n", + " expression=unfolded_expression,\n", + " parameters=model.parameter_defaults,\n", + " backend=\"jax\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + } + }, + "outputs": [], + "source": [ + "i, j = 3, 1\n", + "(k,) = {1, 2, 3} - {i, j}\n", + "σk, σk_expr = list(model.invariants.items())[k - 1]\n", + "Math(aslatex({σk: σk_expr}))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "mystnb": { + "code_prompt_show": "Define meshgrid" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "resolution = 1_000\n", + "m = sorted(model.masses, key=str)\n", + "x_min = float(((m[j] + m[k]) ** 2).xreplace(model.masses))\n", + "x_max = float(((m[0] - m[i]) ** 2).xreplace(model.masses))\n", + "y_min = float(((m[i] + m[k]) ** 2).xreplace(model.masses))\n", + "y_max = float(((m[0] - m[j]) ** 2).xreplace(model.masses))\n", + "x_diff = x_max - x_min\n", + "y_diff = y_max - y_min\n", + "x_min -= 0.05 * x_diff\n", + "x_max += 0.05 * x_diff\n", + "y_min -= 0.05 * y_diff\n", + "y_max += 0.05 * y_diff\n", + "X, Y = jnp.meshgrid(\n", + " jnp.linspace(x_min, x_max, num=resolution),\n", + " jnp.linspace(y_min, y_max, num=resolution),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "mystnb": { + "code_prompt_show": "Define data transformer" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "definitions = dict(model.variables)\n", + "definitions[σk] = σk_expr\n", + "definitions = {\n", + " symbol: expr.xreplace(definitions).xreplace(model.masses)\n", + " for symbol, expr in definitions.items()\n", + "}\n", + "\n", + "data_transformer = SympyDataTransformer.from_sympy(definitions, backend=\"jax\")\n", + "dalitz_data = {\n", + " f\"sigma{i}\": X,\n", + " f\"sigma{j}\": Y,\n", + "}\n", + "dalitz_data.update(data_transformer(dalitz_data))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "mystnb": { + "code_prompt_show": "Collect resonance names per sub-system" + }, + "tags": [ + "hide-output", + "hide-input", + "remove-output" + ] + }, + "outputs": [], + "source": [ + "resonances = defaultdict(set)\n", + "for transition in reaction.transitions:\n", + " topology = transition.topology\n", + " top_decay_products = topology.get_edge_ids_outgoing_from_node(0)\n", + " (resonance_id, resonance), *_ = transition.intermediate_states.items()\n", + " recoil_id, *_ = top_decay_products - {resonance_id}\n", + " resonances[recoil_id].add(resonance.particle)\n", + "resonances = {k: sorted(v, key=lambda p: p.mass) for k, v in resonances.items()}\n", + "{k: [p.name for p in v] for k, v in resonances.items()}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "mystnb": { + "code_prompt_show": "Design slider UI" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "sliders = {}\n", + "categorized_sliders_m = defaultdict(list)\n", + "categorized_sliders_gamma = defaultdict(list)\n", + "categorized_cphi_pair = defaultdict(list)\n", + "couplings_name_root = R\"\\mathcal{H}^\\mathrm{decay}\"\n", + "\n", + "for symbol, value in model.parameter_defaults.items():\n", + " if symbol.name.startswith(R\"\\Gamma_{\"):\n", + " slider = w.FloatSlider(\n", + " description=Rf\"\\({sp.latex(symbol)}\\)\",\n", + " min=0.0,\n", + " max=1.0,\n", + " value=value,\n", + " continuous_update=False,\n", + " )\n", + " sliders[symbol.name] = slider\n", + " if symbol.name.startswith(R\"\\Gamma_{K\"):\n", + " categorized_sliders_gamma[1].append(slider)\n", + " elif symbol.name.startswith(R\"\\Gamma_{\\S\"):\n", + " categorized_sliders_gamma[2].append(slider)\n", + " elif symbol.name.startswith(R\"\\Gamma_{N\"):\n", + " categorized_sliders_gamma[3].append(slider)\n", + "\n", + " if symbol.name.startswith(\"m_{\"):\n", + " slider = w.FloatSlider(\n", + " description=Rf\"\\({sp.latex(symbol)}\\)\",\n", + " min=0.63,\n", + " max=4,\n", + " value=value,\n", + " continuous_update=False,\n", + " )\n", + " sliders[symbol.name] = slider\n", + " if symbol.name.startswith(\"m_{K\"):\n", + " categorized_sliders_m[1].append(slider)\n", + " elif symbol.name.startswith(R\"m_{\\S\"):\n", + " categorized_sliders_m[2].append(slider)\n", + " elif symbol.name.startswith(\"m_{N\"):\n", + " categorized_sliders_m[3].append(slider)\n", + "\n", + " if symbol.name.startswith(couplings_name_root):\n", + " c_latex = sp.latex(symbol)\n", + " phi_latex = c_latex.replace(couplings_name_root, R\"\\phi\", 1)\n", + "\n", + " slider_c = w.FloatSlider(\n", + " description=Rf\"\\({c_latex}\\)\",\n", + " min=0,\n", + " max=10,\n", + " value=abs(value),\n", + " continuous_update=False,\n", + " )\n", + " slider_phi = w.FloatSlider(\n", + " description=Rf\"\\({phi_latex}\\)\",\n", + " min=-np.pi,\n", + " max=+np.pi,\n", + " value=np.angle(value),\n", + " continuous_update=False,\n", + " )\n", + "\n", + " sliders[symbol.name] = slider_c\n", + " sliders[symbol.name.replace(couplings_name_root, \"phi\", 1)] = slider_phi\n", + "\n", + " cphi_hbox = w.HBox([slider_c, slider_phi])\n", + " if \"Sigma\" in symbol.name:\n", + " categorized_cphi_pair[2].append(cphi_hbox)\n", + " elif \"N\" in symbol.name:\n", + " categorized_cphi_pair[3].append(cphi_hbox)\n", + " else:\n", + " categorized_cphi_pair[1].append(cphi_hbox)\n", + "\n", + "\n", + "assert len(categorized_sliders_gamma) == 3\n", + "assert len(categorized_sliders_m) == 3\n", + "assert len(categorized_cphi_pair) == 3\n", + "\n", + "subtabs = {}\n", + "for recoild_id, resonance_list in resonances.items():\n", + " subtabs[recoild_id] = []\n", + " for particle in resonance_list:\n", + " m_sliders = [\n", + " slider\n", + " for slider in categorized_sliders_m[recoild_id]\n", + " if particle.latex in slider.description\n", + " ]\n", + " gamma_sliders = [\n", + " slider\n", + " for slider in categorized_sliders_gamma[recoild_id]\n", + " if particle.latex in slider.description\n", + " ]\n", + " cphi_pairs = [\n", + " hbox\n", + " for hbox in categorized_cphi_pair[recoild_id]\n", + " if particle.latex in hbox.children[0].description\n", + " ]\n", + " pole_pair = w.HBox(m_sliders + gamma_sliders)\n", + " resonance_tab = w.VBox([pole_pair, *cphi_pairs])\n", + " subtabs[recoild_id].append(resonance_tab)\n", + "assert len(subtabs) == 3\n", + "\n", + "main_tabs = []\n", + "for recoild_id, slider_boxes in subtabs.items():\n", + " sub_tab_widget = w.Tab(children=slider_boxes)\n", + " for i, particle in enumerate(resonances[recoild_id]):\n", + " sub_tab_widget.set_title(i, particle.name)\n", + "\n", + " main_tabs.append(sub_tab_widget)\n", + "\n", + "UI = w.Tab(children=main_tabs, titles=(\"K*\", \"Σ*\", \"N*\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "mystnb": { + "code_prompt_show": "Define functions for inserting phase" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "def insert_phi(parameters: dict) -> dict:\n", + " updated_parameters = {}\n", + " for key, value in parameters.items():\n", + " if key.startswith(\"phi\"):\n", + " continue\n", + " if key.startswith(couplings_name_root):\n", + " phi_key = key.replace(couplings_name_root, \"phi\")\n", + " if phi_key in parameters:\n", + " phi = parameters[phi_key]\n", + " value *= np.exp(1j * phi) # noqa:PLW2901\n", + " updated_parameters[key] = value\n", + " return updated_parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [ + "hide-input", + "full-width" + ] + }, + "outputs": [], + "source": [ + "%matplotlib widget\n", + "%config InlineBackend.figure_formats = ['png']\n", + "fig_2d, ax_2d = plt.subplots(dpi=200)\n", + "ax_2d.set_title(\"Model-weighted Phase space Dalitz Plot\")\n", + "ax_2d.set_xlabel(R\"$m^2(\\Lambda K^+)\\;\\left[\\mathrm{GeV}^2\\right]$\")\n", + "ax_2d.set_ylabel(R\"$m^2(K^+ \\pi^0)\\;\\left[\\mathrm{GeV}^2\\right]$\")\n", + "\n", + "mesh = None\n", + "\n", + "\n", + "def update_dalitz_plot(**parameters):\n", + " global mesh\n", + " parameters = insert_phi(parameters)\n", + " intensity_func.update_parameters(parameters)\n", + " intensities = intensity_func(dalitz_data) # z\n", + " intensities /= jnp.nansum(intensities) # normalization\n", + "\n", + " if mesh is None:\n", + " mesh = ax_2d.pcolormesh(X, Y, intensities, cmap=\"jet\", vmax=3e-5)\n", + " else:\n", + " mesh.set_array(intensities)\n", + " fig_2d.canvas.draw_idle()\n", + "\n", + "\n", + "interactive_plot = w.interactive_output(update_dalitz_plot, sliders)\n", + "fig_2d.tight_layout()\n", + "fig_2d.colorbar(mesh, ax=ax_2d)\n", + "\n", + "if STATIC_PAGE:\n", + " filename = \"dalitz-plot-dpd.png\"\n", + " fig_2d.savefig(filename)\n", + " plt.close(fig_2d)\n", + " display(UI, Image(filename))\n", + "else:\n", + " display(UI, interactive_plot)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [ + "hide-input", + "full-width", + "scroll-input" + ] + }, + "outputs": [], + "source": [ + "%matplotlib widget\n", + "%config InlineBackend.figure_formats = ['svg']\n", + "\n", + "fig, axes = plt.subplots(figsize=(11, 3.5), ncols=2, sharey=True)\n", + "fig.canvas.toolbar_visible = False\n", + "fig.canvas.header_visible = False\n", + "fig.canvas.footer_visible = False\n", + "ax1, ax2 = axes\n", + "\n", + "for ax in axes:\n", + " recoil_id = 3 if ax is ax1 else 1\n", + " decay_products = sorted(set(reaction.final_state) - {recoil_id})\n", + " product_latex = \" \".join([reaction.final_state[i].latex for i in decay_products])\n", + " ax.set_xlabel(f\"$m({product_latex})$ [GeV]\")\n", + "\n", + "LINES = defaultdict(lambda: None)\n", + "RESONANCE_LINE = defaultdict(lambda: None)\n", + "\n", + "\n", + "def update_plot(**parameters):\n", + " parameters = insert_phi(parameters)\n", + " intensity_func.update_parameters(parameters)\n", + " intensities = intensity_func(dalitz_data) # z\n", + " intensities /= jnp.nansum(intensities) # normalization\n", + "\n", + " max_value = 0\n", + " color_id = 0\n", + " for ax in axes:\n", + " if ax is ax1:\n", + " x = jnp.sqrt(X[0])\n", + " y = jnp.nansum(intensities, axis=0)\n", + " else:\n", + " x = jnp.sqrt(Y[:, 0])\n", + " y = jnp.nansum(intensities, axis=1)\n", + "\n", + " max_value = max(max_value, y.max())\n", + " recoil_id = 3 if ax is ax1 else 1\n", + " if LINES[recoil_id] is None:\n", + " LINES[recoil_id] = ax.plot(x, y, alpha=0.5)[0]\n", + " else:\n", + " LINES[recoil_id].set_ydata(y)\n", + "\n", + " for resonance in resonances[recoil_id]:\n", + " key = f\"m_{{{resonance.latex}}}\"\n", + " val = parameters.get(key, resonance.mass)\n", + " if RESONANCE_LINE[color_id] is None:\n", + " RESONANCE_LINE[color_id] = ax.axvline(\n", + " val,\n", + " c=f\"C{color_id}\",\n", + " ls=\"dotted\",\n", + " label=resonance.name,\n", + " )\n", + " else:\n", + " RESONANCE_LINE[color_id].set_xdata([val, val])\n", + " color_id += 1\n", + "\n", + " for ax in axes:\n", + " ax.set_ylim(0, max_value * 1.1)\n", + "\n", + "\n", + "interactive_plot = w.interactive_output(update_plot, sliders)\n", + "for ax in axes:\n", + " ax.legend(fontsize=\"small\")\n", + "\n", + "if STATIC_PAGE:\n", + " filename = \"histogram-dpd.svg\"\n", + " fig.savefig(filename)\n", + " plt.close(fig)\n", + " display(UI, SVG(filename))\n", + "else:\n", + " display(UI, interactive_plot)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/lambda-k-pi/automated.ipynb b/docs/lambda-k-pi/ampform.ipynb similarity index 100% rename from docs/lambda-k-pi/automated.ipynb rename to docs/lambda-k-pi/ampform.ipynb diff --git a/docs/lambda-k-pi/index.md b/docs/lambda-k-pi/index.md index a3afaa4..78a8251 100644 --- a/docs/lambda-k-pi/index.md +++ b/docs/lambda-k-pi/index.md @@ -1,5 +1,6 @@ # pγ → ΛK⁺π⁰ ```{toctree} -automated +ampform +ampform-dpd ``` diff --git a/pixi.lock b/pixi.lock index 05a3069..7cbe53d 100644 --- a/pixi.lock +++ b/pixi.lock @@ -158,6 +158,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/90/60468aaed8bb12b5bb0e8e100c95ff33c9d097d309e20fbe14b26406307b/ampform-0.15.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/bd/8b662d1eabe1d920fc44db38551483ef450322ef0faefd413f9a0a094621/ampform_dpd-0.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/a2/10639a79341f6c019dedc95bd48a4928eed9f1d1197f4c04f546fc7ae0ff/anyio-4.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -461,6 +462,69 @@ packages: - sphinx-api-relink>=0.0.3 ; extra == 'types' - graphviz ; extra == 'viz' requires_python: '>=3.7' +- kind: pypi + name: ampform-dpd + version: 0.2.0 + url: https://files.pythonhosted.org/packages/9d/bd/8b662d1eabe1d920fc44db38551483ef450322ef0faefd413f9a0a094621/ampform_dpd-0.2.0-py3-none-any.whl + sha256: b2e06a5efd900c2454168c2a24cde21dc319b95e7f77a6c74a24f48b0588bd65 + requires_dist: + - ampform>=0.14.8 + - attrs>=20.1.0 + - cloudpickle + - qrules>=0.10.0 + - sympy>=1.10 + - tensorwaves[jax] + - ampform-dpd[doc] ; extra == 'dev' + - ampform-dpd[jupyter] ; extra == 'dev' + - ampform-dpd[sty] ; extra == 'dev' + - ampform-dpd[test] ; extra == 'dev' + - sphinx-autobuild ; extra == 'dev' + - tox>=1.9 ; extra == 'dev' + - graphviz ; extra == 'doc' + - ipympl ; extra == 'doc' + - ipywidgets ; extra == 'doc' + - matplotlib ; extra == 'doc' + - myst-nb>=0.14 ; extra == 'doc' + - sphinx-api-relink>=0.0.4 ; extra == 'doc' + - sphinx-book-theme ; extra == 'doc' + - sphinx-codeautolink[ipython] ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - sphinx-pybtex-etal-style ; extra == 'doc' + - sphinx-togglebutton ; extra == 'doc' + - sphinxcontrib-bibtex ; extra == 'doc' + - tensorwaves[phsp] ; extra == 'doc' + - tqdm ; extra == 'doc' + - tensorwaves[jax] ; extra == 'jax' + - ampform-dpd[doc] ; extra == 'jupyter' + - black ; extra == 'jupyter' + - isort ; extra == 'jupyter' + - jupyterlab>=3.0 ; extra == 'jupyter' + - jupyterlab ; extra == 'jupyter' + - jupyterlab-code-formatter ; extra == 'jupyter' + - jupyterlab-git ; extra == 'jupyter' + - jupyterlab-lsp ; extra == 'jupyter' + - jupyterlab-myst ; extra == 'jupyter' + - python-lsp-ruff ; extra == 'jupyter' + - python-lsp-server[rope] ; extra == 'jupyter' + - tensorwaves[numba] ; extra == 'numba' + - ampform-dpd[types] ; extra == 'sty' + - mypy ; extra == 'sty' + - pre-commit>=1.4.0 ; extra == 'sty' + - ruff ; extra == 'sty' + - ampform-dpd[tf] ; extra == 'tensorflow' + - nbmake ; extra == 'test' + - numpy ; extra == 'test' + - pytest>=6.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-xdist ; extra == 'test' + - tensorwaves[tf] ; extra == 'tf' + - docutils ; extra == 'types' + - pybtex ; extra == 'types' + - pytest ; extra == 'types' + - sphinx ; extra == 'types' + - sphinx-api-relink>=0.0.4 ; extra == 'types' + requires_python: '>=3.8' - kind: pypi name: anyio version: 4.4.0 diff --git a/pyproject.toml b/pyproject.toml index ea1a57a..a93930e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ requires = ["setuptools"] authors = [{name = "Vitor Shen"}] dependencies = [ "ampform", + "ampform-dpd>=0.2.0,<0.3", "graphviz", "ipympl", "ipywidgets", @@ -109,10 +110,12 @@ builtins-ignorelist = ["display"] "D103", "E303", "E501", + "PLC2401", "PLR2004", "PLW0603", "S101", "T201", + "TCH", ] "docs/conf.py" = [ "D100",