From 510c6f617b286d7f8f4e31104a3be35a557beeb4 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Mon, 6 May 2024 12:29:37 +0200 Subject: [PATCH] DOC: add TR-028 with PDG Python API example (#271) --- .cspell.json | 2 + docs/conf.py | 1 + docs/report/028.ipynb | 453 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 456 insertions(+) create mode 100755 docs/report/028.ipynb diff --git a/.cspell.json b/.cspell.json index 4a68c217..983eca55 100644 --- a/.cspell.json +++ b/.cspell.json @@ -231,11 +231,13 @@ "ipyplot", "ipython", "ipywidgets", + "isdigit", "isinstance", "isnan", "isort", "jaxlib", "joinpath", + "jpsi", "juliaup", "jupyterlab", "kernelspec", diff --git a/docs/conf.py b/docs/conf.py index e962eff9..214f19a9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -192,6 +192,7 @@ def get_nb_exclusion_patterns() -> list[str]: ), "numba": ("https://numba.pydata.org/numba-doc/latest", None), "numpy": (f"https://numpy.org/doc/{pin_minor('numpy')}", None), + "pdg": ("https://pdgapi.lbl.gov/doc", None), "plotly": ("https://plotly.com/python-api-reference/", None), "pwa": ("https://pwa.readthedocs.io", None), "python": ("https://docs.python.org/3", None), diff --git a/docs/report/028.ipynb b/docs/report/028.ipynb new file mode 100755 index 00000000..60417923 --- /dev/null +++ b/docs/report/028.ipynb @@ -0,0 +1,453 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```{autolink-concat}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "PDG" + ] + }, + "source": [ + "::::{margin}\n", + ":::{card} Example of how to query the PDG Python API for decay\n", + "TR-028\n", + "^^^\n", + "This report shows how to search all known decays in the PDG using [its new Python API](https://pdgapi.lbl.gov/doc) and search three-body decays that have three equal particles in the final state.\n", + "+++\n", + "🚧 [compwa.github.io#271](https://github.com/ComPWA/compwa.github.io/issues/271)\n", + ":::\n", + "::::" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "# PDG Python API: decay query" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%pip install -q pdg==0.0.6 tqdm==4.66.2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "from __future__ import annotations" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import pdg\n", + "\n", + "PDG = pdg.connect()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "This creates a [`PdgApi`](https://pdgapi.lbl.gov/doc/pdg.api.html#pdg.api.PdgApi) instance containing the following type of objects:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "{type(obj) for obj in PDG.get_all()}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "In this example, we ask the question **which particles can decay to a final state with three equal particles?** For this, we use [`PdgBranchingFraction`](https://pdgapi.lbl.gov/doc/pdg.decay.html#pdg.decay.PdgBranchingFraction)s, which contain information about particle decays in their [`description`](https://pdgapi.lbl.gov/doc/pdg.data.html#pdg.data.PdgData.description):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + } + }, + "outputs": [], + "source": [ + "jpsi_decay = PDG.get(\"M070.313/2023\")\n", + "type(jpsi_decay)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "jpsi_decay.description" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "So, let's pull all the decay descriptions from the PDG and do some clean up with {meth}`str.strip` and {obj}`set`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from pdg.decay import PdgBranchingFraction\n", + "\n", + "all_decays = {obj for obj in PDG.get_all() if isinstance(obj, PdgBranchingFraction)}\n", + "decay_descriptions = {dec.description.strip() for dec in all_decays}\n", + "len(decay_descriptions)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "To get more insight into the decay products, we create a new {obj}`set` of decay descriptions, but now describe each item as an initial state with a {obj}`tuple` of decay products. We again have to do a bit of cleaning here. The final state description sometimes contains digits, like `\"3pi0\"`, which we want to be rendered as `(\"pi0\", \"pi0\", \"pi0\")`.\n", + "\n", + "Note that we decay all state descriptions in the decay chain into account. For example,\n", + "```python\n", + "\"J/psi(1S) --> rho(1700) pi --> pi+ pi- pi0\"\n", + "```\n", + "\n", + "has two 'final' states:\n", + "```python\n", + "(\"rho(1700)\", \"pi\")\n", + "(\"pi+\", \"pi-\", \"pi0\")\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "jupyter": { + "source_hidden": true + }, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "def create_final_state(description: str) -> tuple[str, ...]:\n", + " items = []\n", + " for particle in description.split():\n", + " particle = particle.strip()\n", + " if particle in {\"\", \",\"}:\n", + " continue\n", + " multiplier = particle[0]\n", + " if multiplier.isdigit():\n", + " particles = int(multiplier) * particle[1:]\n", + " items.extend(particles)\n", + " else:\n", + " items.append(particle)\n", + " return tuple(sorted(items))\n", + "\n", + "\n", + "decays: set[tuple[str, tuple[str, ...]]] = set()\n", + "for description in decay_descriptions:\n", + " initial_state, *final_states = description.split(\" --> \")\n", + " initial_state = initial_state.strip()\n", + " for final_state in final_states:\n", + " decays.add((initial_state, create_final_state(final_state)))\n", + "len(decays)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Now selecting the three-body decays is an easy matter using filters on comprehensions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "three_body_decays = {\n", + " (initial_state, final_state)\n", + " for initial_state, final_state in decays\n", + " if len(final_state) == 3\n", + "}\n", + "len(three_body_decays)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "equal_state_3body_decays = {\n", + " (initial_state, final_state)\n", + " for initial_state, final_state in three_body_decays\n", + " if len(set(final_state)) == 1\n", + "}\n", + "sorted(equal_state_3body_decays)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Finally, and optionally, we can filter out final states that are not well defined, such as `g g g`, by checking whether they are defined in the PDG database." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "jupyter": { + "source_hidden": true + }, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "for initial_state, final_state in sorted(equal_state_3body_decays):\n", + " try:\n", + " for name in (initial_state, *final_state):\n", + " PDG.get_particle_by_name(name)\n", + " except ValueError:\n", + " pass\n", + " else:\n", + " print(f\"{initial_state:>20} → {' '.join(final_state)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + ":::{warning}\n", + "Not all final state in the [`description`](https://pdgapi.lbl.gov/doc/pdg.data.html#pdg.data.PdgData.description)s can be programmatically deciphered as individual particles. One could try to use [regular expressions](https://docs.python.org/3/howto/regex.html), but it's hard to cover all cases. Consider for instance the following case which contains $S$ and $D$ waves.\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "[dec for dec in decay_descriptions if dec.startswith(\"a_1(1260)\")]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Additionally, not all decays seem to be included. Here is an attempt to find $J/\\psi \\to \\pi^0 \\pi^0 \\pi^0$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "jupyter": { + "source_hidden": true + }, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "import re\n", + "\n", + "sorted(\n", + " decay\n", + " for decay in decay_descriptions\n", + " if decay.startswith(\"J/psi\") and re.match(r\".*(3 ?pi|pi.*pi.*pi).*\", decay)\n", + ")" + ] + } + ], + "metadata": { + "colab": { + "toc_visible": true + }, + "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.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}