From 71eacde80e755f6d39e3c62b3b7076076b52f5e8 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Mon, 20 May 2024 23:31:21 +0200 Subject: [PATCH 1/8] FEAT: implement `BreitWigner` expression classes --- docs/_extend_docstrings.py | 47 +++++++++++++ docs/usage/dynamics.ipynb | 57 +++++++++++++-- src/ampform/dynamics/__init__.py | 116 +++++++++++++++++++++++++++++-- tests/dynamics/test_builder.py | 5 +- 4 files changed, 210 insertions(+), 15 deletions(-) diff --git a/docs/_extend_docstrings.py b/docs/_extend_docstrings.py index c1527b070..58e1a22d1 100644 --- a/docs/_extend_docstrings.py +++ b/docs/_extend_docstrings.py @@ -181,6 +181,15 @@ def extend_BreakupMomentumSquared() -> None: _append_latex_doit_definition(expr, deep=True) +def extend_BreitWigner() -> None: + from ampform.dynamics import BreitWigner + + s, m0, w0, m1, m2, d = sp.symbols("s m0 Gamma0 m1 m2 d", nonnegative=True) + L = sp.Symbol("L", integer=True, nonnegative=True) + expr = BreitWigner(s, m0, w0, m1, m2, angular_momentum=L, meson_radius=d) + _append_latex_doit_definition(expr) + + def extend_ComplexSqrt() -> None: from ampform.sympy.math import ComplexSqrt @@ -358,6 +367,36 @@ def extend_is_within_phasespace() -> None: ) +def extend_MultichannelBreitWigner() -> None: + from ampform.dynamics import ( + ChannelArguments, + MultichannelBreitWigner, + SimpleBreitWigner, + ) + + s, m0, w0 = sp.symbols("s m0 Gamma0", nonnegative=True) + channels = [ + ChannelArguments(*sp.symbols("Gamma1 m_a1 m_b1 L1 d", nonnegative=True)), + ChannelArguments(*sp.symbols("Gamma2 m_a2 m_b2 L2 d", nonnegative=True)), + ] + expr = MultichannelBreitWigner(s, m0, channels=channels) + _append_latex_doit_definition(expr) + bw = SimpleBreitWigner(s, m0, w0) + _append_to_docstring( + MultichannelBreitWigner, + Rf""" + with :math:`{sp.latex(bw)}` defined by Equation :eq:`SimpleBreitWigner`, a + `SimpleBreitWigner`. + """, + ) + _append_to_docstring( + ChannelArguments.formulate_width, + Rf""" + .. math:: {sp.latex(channels[0].formulate_width(s, m0))} + """, + ) + + def extend_PhaseSpaceFactor() -> None: from ampform.dynamics.phasespace import PhaseSpaceFactor @@ -473,6 +512,14 @@ def extend_RotationZMatrix() -> None: ) +def extend_SimpleBreitWigner() -> None: + from ampform.dynamics import SimpleBreitWigner + + s, m0, w0 = sp.symbols("s m0 Gamma0", nonnegative=True) + expr = SimpleBreitWigner(s, m0, w0) + _append_latex_doit_definition(expr) + + def extend_SphericalHankel1() -> None: from ampform.dynamics.form_factor import SphericalHankel1 diff --git a/docs/usage/dynamics.ipynb b/docs/usage/dynamics.ipynb index a19e15b87..afe3c5559 100644 --- a/docs/usage/dynamics.ipynb +++ b/docs/usage/dynamics.ipynb @@ -409,6 +409,29 @@ "rel_bw" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "jupyter": { + "source_hidden": true + }, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "from ampform.dynamics import SimpleBreitWigner\n", + "\n", + "bw = SimpleBreitWigner(s, m0, w0)\n", + "Math(aslatex({bw: bw.doit(deep=False)}))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -457,16 +480,12 @@ "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "source_hidden": true - }, "tags": [] }, "outputs": [], "source": [ "from ampform.dynamics import EnergyDependentWidth\n", "\n", - "L = sp.Symbol(\"L\", integer=True)\n", "width = EnergyDependentWidth(\n", " s=s,\n", " mass0=m0,\n", @@ -476,8 +495,34 @@ " angular_momentum=L,\n", " meson_radius=1,\n", " phsp_factor=PhaseSpaceFactorSWave,\n", - ")\n", - "Math(aslatex({width: width.evaluate()}))" + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "jupyter": { + "source_hidden": true + }, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "from ampform.dynamics import BreitWigner\n", + "\n", + "exprs = [\n", + " BreitWigner(s, m0, w0, m1, m2, angular_momentum=L, meson_radius=d),\n", + " SimpleBreitWigner(s, m0, w0),\n", + " width,\n", + "]\n", + "Math(aslatex({e: e.doit(deep=False) for e in exprs}))" ] }, { diff --git a/src/ampform/dynamics/__init__.py b/src/ampform/dynamics/__init__.py index 671c91bcb..4b9f33aac 100644 --- a/src/ampform/dynamics/__init__.py +++ b/src/ampform/dynamics/__init__.py @@ -9,6 +9,7 @@ from warnings import warn import sympy as sp +from attrs import asdict, frozen # pyright: reportUnusedImport=false from ampform.dynamics.form_factor import ( @@ -31,9 +32,66 @@ from sympy.printing.latex import LatexPrinter +@unevaluated +class SimpleBreitWigner(sp.Expr): + """Simple, non-relativistic Breit-Wigner with :math:`1` in the nominator.""" + + s: Any + mass: Any + width: Any + _latex_repr_ = R"\mathcal{{R}}^\mathrm{{BW}}\left({s}; {mass}, {width}\right)" + + def evaluate(self): + s, m0, w0 = self.args + return 1 / (m0**2 - s - m0 * w0 * sp.I) + + +@unevaluated +class BreitWigner(sp.Expr): + """Relativistic Breit-Wigner with :math:`1` in the nominator. + + `SimpleBreitWigner` with `EnergyDependentWidth` as width (see Equations + :eq:`SimpleBreitWigner` and :eq:`EnergyDependentWidth`). + """ + + s: Any + mass: Any + width: Any + m1: Any = 0 + m2: Any = 0 + angular_momentum: Any = 0 + meson_radius: Any = 1 + phsp_factor: PhaseSpaceFactorProtocol = argument( + default=PhaseSpaceFactor, sympify=False + ) + + def evaluate(self): + width = self.energy_dependent_width() + expr = SimpleBreitWigner(self.s, self.mass, width) + if self.angular_momentum == 0 and self.m1 == 0 and self.m2 == 0: + return expr.evaluate() + return expr + + def energy_dependent_width(self) -> sp.Expr: + s, m0, w0, m1, m2, ang_mom, d = self.args + if ang_mom == 0 and m1 == 0 and m2 == 0: + return w0 # type:ignore[return-value] + return EnergyDependentWidth(s, m0, w0, m1, m2, ang_mom, d, self.phsp_factor) + + def _latex_repr_(self, printer: LatexPrinter, *args) -> str: + s = printer._print(self.s) + function_symbol = R"\mathcal{R}^\mathrm{BW}" + mass = printer._print(self.mass) + width = printer._print(self.width) + arg = Rf"\left({s}; {mass}, {width}\right)" + angular_momentum = printer._print(self.angular_momentum) + if isinstance(self.angular_momentum, sp.Integer): + return Rf"{function_symbol}_{{L={angular_momentum}}}{arg}" + return Rf"{function_symbol}_{{{angular_momentum}}}{arg}" + + @unevaluated class EnergyDependentWidth(sp.Expr): - # cspell:ignore asner r"""Mass-dependent width, coupled to the pole position of the resonance. See Equation (50.28) in :pdg-review:`2021; Resonances; p.9` and @@ -75,13 +133,59 @@ def _latex_repr_(self, printer: LatexPrinter, *args) -> str: return Rf"{name}\left({s}\right)" +@unevaluated +class MultichannelBreitWigner(sp.Expr): + """`BreitWigner` for multiple channels.""" + + s: Any + mass: Any + channels: list[ChannelArguments] = argument(sympify=False) + + def evaluate(self): + s = self.s + m0 = self.mass + width = sum(channel.formulate_width(s, m0) for channel in self.channels) + return SimpleBreitWigner(s, m0, width) + + def _latex_repr_(self, printer: LatexPrinter, *args) -> str: + latex = R"\mathcal{R}^\mathrm{BW}_\mathrm{multi}\left(" + latex += printer._print(self.s) + "; " + latex += ", ".join(printer._print(channel.width) for channel in self.channels) + latex += R"\right)" + return latex + + +@frozen +class ChannelArguments: + """Arguments for a channel in a `MultichannelBreitWigner`.""" + + width: Any + m1: Any = 0 + m2: Any = 0 + angular_momentum: Any = 0 + meson_radius: Any = 1 + + def __attrs_post_init__(self) -> None: + for name, value in asdict(self).items(): + object.__setattr__(self, name, sp.sympify(value)) + + def formulate_width(self, s: Any, m0: Any) -> sp.Expr: + w0 = self.width + m1 = self.m1 + m2 = self.m2 + angular_momentum = self.angular_momentum + meson_radius = self.meson_radius + ff2 = FormFactor(s, m1, m2, angular_momentum, meson_radius) ** 2 + return ff2 * w0 * m0 / sp.sqrt(s) + + def relativistic_breit_wigner(s, mass0, gamma0) -> sp.Expr: """Relativistic Breit-Wigner lineshape. See :ref:`usage/dynamics:_Without_ form factor` and :cite:`ParticleDataGroup:2020ssz`. """ - return gamma0 * mass0 / (mass0**2 - s - gamma0 * mass0 * sp.I) + return gamma0 * mass0 * SimpleBreitWigner(s, mass0, gamma0) def relativistic_breit_wigner_with_ff( # noqa: PLR0917 @@ -99,13 +203,11 @@ def relativistic_breit_wigner_with_ff( # noqa: PLR0917 See :ref:`usage/dynamics:_With_ form factor` and :pdg-review:`2021; Resonances; p.9`. """ - form_factor = FormFactor(s, m_a, m_b, angular_momentum, meson_radius) - energy_dependent_width = EnergyDependentWidth( + ff = FormFactor(s, m_a, m_b, angular_momentum, meson_radius) + bw = BreitWigner( s, mass0, gamma0, m_a, m_b, angular_momentum, meson_radius, phsp_factor ) - return (mass0 * gamma0 * form_factor) / ( - mass0**2 - s - energy_dependent_width * mass0 * sp.I - ) + return mass0 * gamma0 * ff * bw def formulate_form_factor(s, m_a, m_b, angular_momentum, meson_radius) -> sp.Expr: diff --git a/tests/dynamics/test_builder.py b/tests/dynamics/test_builder.py index a12a4f732..35f8a1e79 100644 --- a/tests/dynamics/test_builder.py +++ b/tests/dynamics/test_builder.py @@ -2,7 +2,7 @@ import sympy as sp from qrules.particle import Particle -from ampform.dynamics import EnergyDependentWidth +from ampform.dynamics import EnergyDependentWidth, SimpleBreitWigner from ampform.dynamics.builder import ( RelativisticBreitWignerBuilder, TwoBodyKinematicVariableSet, @@ -43,7 +43,8 @@ def test_simple_breit_wigner( s = variable_set.incoming_state_mass**2 m0 = sp.Symbol("m_{N}", nonnegative=True) w0 = sp.Symbol(R"\Gamma_{N}", nonnegative=True) - assert bw == w0 * m0 / (-sp.I * w0 * m0 - s + m0**2) + simple_bw = SimpleBreitWigner(s, m0, w0) + assert bw == w0 * m0 * simple_bw assert set(parameters) == {m0, w0} assert parameters[m0] == particle.mass assert parameters[w0] == particle.width From 2831f149b8543bda172d9820ec464b490fb0aad5 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 21 May 2024 10:00:19 +0200 Subject: [PATCH 2/8] =?UTF-8?q?DOC:=20use=20en-dash=20in=20Breit=E2=80=93W?= =?UTF-8?q?igner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/usage/dynamics.ipynb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/usage/dynamics.ipynb b/docs/usage/dynamics.ipynb index afe3c5559..6f28bea82 100644 --- a/docs/usage/dynamics.ipynb +++ b/docs/usage/dynamics.ipynb @@ -368,14 +368,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Relativistic Breit-Wigner" + "## Relativistic Breit–Wigner" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "AmpForm has two types of relativistic Breit-Wigner functions. Both are compared below ― for more info, see the links to the API." + "AmpForm has two types of relativistic Breit–Wigner functions. Both are compared below ― for more info, see the links to the API." ] }, { @@ -443,7 +443,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The relativistic Breit-Wigner can be adapted slightly, so that its amplitude goes to zero at threshold ($m_0 = m1 + m2$) and that it becomes normalizable. This is done with {ref}`form factors ` and can be obtained with the function {func}`.relativistic_breit_wigner_with_ff`:" + "The relativistic Breit–Wigner can be adapted slightly, so that its amplitude goes to zero at threshold ($m_0 = m1 + m2$) and that it becomes normalizable. This is done with {ref}`form factors ` and can be obtained with the function {func}`.relativistic_breit_wigner_with_ff`:" ] }, { @@ -545,7 +545,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The following shows the effect of {doc}`/usage/dynamics/analytic-continuation` a on relativistic Breit-Wigner:" + "The following shows the effect of {doc}`/usage/dynamics/analytic-continuation` a on relativistic Breit–Wigner:" ] }, { From 38acc09df5746bcdd4166fb1eb12d0bf74a3ff52 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 21 May 2024 10:33:28 +0200 Subject: [PATCH 3/8] DOC: show how to use `MultichannelBreitWigner` --- docs/usage/dynamics.ipynb | 116 +++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/docs/usage/dynamics.ipynb b/docs/usage/dynamics.ipynb index 6f28bea82..eb6cf612f 100644 --- a/docs/usage/dynamics.ipynb +++ b/docs/usage/dynamics.ipynb @@ -535,6 +535,110 @@ { "cell_type": "markdown", "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "### Multi-channel Breit–Wigner" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from ampform.dynamics import ChannelArguments, MultichannelBreitWigner\n", + "\n", + "channels = [\n", + " ChannelArguments(\n", + " width=sp.Symbol(f\"Gamma{i}\"),\n", + " m1=sp.Symbol(f\"m_{{a,{i}}}\"),\n", + " m2=sp.Symbol(f\"m_{{b,{i}}}\"),\n", + " angular_momentum=sp.Symbol(f\"L{i}\"),\n", + " meson_radius=d,\n", + " )\n", + " for i in [1, 2]\n", + "]\n", + "multi_bw = MultichannelBreitWigner(s, m0, channels)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "jupyter": { + "source_hidden": true + }, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "exprs = [\n", + " multi_bw,\n", + " SimpleBreitWigner(s, m0, w0),\n", + " FormFactor(s, m1, m2, L, d),\n", + "]\n", + "Math(aslatex({e: e.doit(deep=False) for e in exprs}))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Note that {class}`.MultichannelBreitWigner` simplifies to a Flatté function. This is especially clear for $L=0$:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "jupyter": { + "source_hidden": true + }, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "channels = [ChannelArguments(sp.Symbol(f\"Gamma{i}\")) for i in [1, 2]]\n", + "expr = MultichannelBreitWigner(s, m0, channels)\n", + "Math(aslatex({expr: expr.doit().simplify()}))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, "tags": [] }, "source": [ @@ -543,7 +647,13 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "The following shows the effect of {doc}`/usage/dynamics/analytic-continuation` a on relativistic Breit–Wigner:" ] @@ -552,9 +662,13 @@ "cell_type": "code", "execution_count": null, "metadata": { + "editable": true, "jupyter": { "source_hidden": true }, + "slideshow": { + "slide_type": "" + }, "tags": [ "hide-cell", "remove-output" From 6d51e385ab664e161fe1c3641933320064287ba0 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 21 May 2024 10:51:07 +0200 Subject: [PATCH 4/8] BEHAVIOR: formulate channel width with `EnergyDependentWidth` --- docs/_extend_docstrings.py | 6 ------ docs/usage/dynamics.ipynb | 2 +- src/ampform/dynamics/__init__.py | 20 ++++++++++++-------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/_extend_docstrings.py b/docs/_extend_docstrings.py index 28865b32d..0fda7283f 100644 --- a/docs/_extend_docstrings.py +++ b/docs/_extend_docstrings.py @@ -389,12 +389,6 @@ def extend_MultichannelBreitWigner() -> None: `SimpleBreitWigner`. """, ) - _append_to_docstring( - ChannelArguments.formulate_width, - Rf""" - .. math:: {sp.latex(channels[0].formulate_width(s, m0))} - """, - ) def extend_PhaseSpaceFactor() -> None: diff --git a/docs/usage/dynamics.ipynb b/docs/usage/dynamics.ipynb index eb6cf612f..09ea995bb 100644 --- a/docs/usage/dynamics.ipynb +++ b/docs/usage/dynamics.ipynb @@ -592,7 +592,7 @@ "exprs = [\n", " multi_bw,\n", " SimpleBreitWigner(s, m0, w0),\n", - " FormFactor(s, m1, m2, L, d),\n", + " EnergyDependentWidth(s, m0, w0, m1, m2, L, d),\n", "]\n", "Math(aslatex({e: e.doit(deep=False) for e in exprs}))" ] diff --git a/src/ampform/dynamics/__init__.py b/src/ampform/dynamics/__init__.py index c3419c51f..a11cbe857 100644 --- a/src/ampform/dynamics/__init__.py +++ b/src/ampform/dynamics/__init__.py @@ -164,19 +164,23 @@ class ChannelArguments: m2: Any = 0 angular_momentum: Any = 0 meson_radius: Any = 1 + phsp_factor: PhaseSpaceFactorProtocol = PhaseSpaceFactor def __attrs_post_init__(self) -> None: for name, value in asdict(self).items(): object.__setattr__(self, name, sp.sympify(value)) - def formulate_width(self, s: Any, m0: Any) -> sp.Expr: - w0 = self.width - m1 = self.m1 - m2 = self.m2 - angular_momentum = self.angular_momentum - meson_radius = self.meson_radius - ff2 = FormFactor(s, m1, m2, angular_momentum, meson_radius) ** 2 - return ff2 * w0 * m0 / sp.sqrt(s) + def formulate_width(self, s: Any, m0: Any) -> EnergyDependentWidth: + return EnergyDependentWidth( + s, + m0, + self.width, + self.m1, + self.m2, + self.angular_momentum, + self.meson_radius, + self.phsp_factor, + ) def relativistic_breit_wigner(s, mass0, gamma0) -> sp.Expr: From d48d4844b893e2c5c0e73ae512d56549d0d884e4 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 21 May 2024 10:53:44 +0200 Subject: [PATCH 5/8] DOC: improve signature `energy_dependent_width()` --- src/ampform/dynamics/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ampform/dynamics/__init__.py b/src/ampform/dynamics/__init__.py index a11cbe857..d7e105670 100644 --- a/src/ampform/dynamics/__init__.py +++ b/src/ampform/dynamics/__init__.py @@ -72,7 +72,7 @@ def evaluate(self): return expr.evaluate() return expr - def energy_dependent_width(self) -> sp.Expr: + def energy_dependent_width(self) -> EnergyDependentWidth | sp.Basic: s, m0, w0, m1, m2, ang_mom, d = self.args if ang_mom == 0 and m1 == 0 and m2 == 0: return w0 # type:ignore[return-value] From c5e6a76bbbf9aa70d512b917a2d7d886a049de21 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 21 May 2024 11:33:55 +0200 Subject: [PATCH 6/8] DOC: improve API deprecation message --- src/ampform/dynamics/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ampform/dynamics/__init__.py b/src/ampform/dynamics/__init__.py index d7e105670..2c8390c93 100644 --- a/src/ampform/dynamics/__init__.py +++ b/src/ampform/dynamics/__init__.py @@ -217,7 +217,8 @@ def relativistic_breit_wigner_with_ff( # noqa: PLR0917 def formulate_form_factor(s, m_a, m_b, angular_momentum, meson_radius) -> sp.Expr: """Formulate a Blatt-Weisskopf form factor. - .. deprecated:: 0.16 + .. deprecated:: 0.16.0 + Use `.FormFactor` instead. """ warn( message="Use the FormFactor expression class instead.", From 7bd70832ac3fece6d6fe60fe0cc5516e01fad049 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 21 May 2024 11:31:32 +0200 Subject: [PATCH 7/8] BEHAVIOR: multiply by mass*width in nominator --- src/ampform/dynamics/__init__.py | 6 +++--- src/ampform/dynamics/builder.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ampform/dynamics/__init__.py b/src/ampform/dynamics/__init__.py index 2c8390c93..4231ddbe4 100644 --- a/src/ampform/dynamics/__init__.py +++ b/src/ampform/dynamics/__init__.py @@ -43,7 +43,7 @@ class SimpleBreitWigner(sp.Expr): def evaluate(self): s, m0, w0 = self.args - return 1 / (m0**2 - s - m0 * w0 * sp.I) + return m0 * w0 / (m0**2 - s - m0 * w0 * sp.I) @unevaluated @@ -189,7 +189,7 @@ def relativistic_breit_wigner(s, mass0, gamma0) -> sp.Expr: See :ref:`usage/dynamics:_Without_ form factor` and :cite:`ParticleDataGroup:2020ssz`. """ - return gamma0 * mass0 * SimpleBreitWigner(s, mass0, gamma0) + return SimpleBreitWigner(s, mass0, gamma0) def relativistic_breit_wigner_with_ff( # noqa: PLR0917 @@ -211,7 +211,7 @@ def relativistic_breit_wigner_with_ff( # noqa: PLR0917 bw = BreitWigner( s, mass0, gamma0, m_a, m_b, angular_momentum, meson_radius, phsp_factor ) - return mass0 * gamma0 * ff * bw + return ff * bw def formulate_form_factor(s, m_a, m_b, angular_momentum, meson_radius) -> sp.Expr: diff --git a/src/ampform/dynamics/builder.py b/src/ampform/dynamics/builder.py index 7e717e661..5d87f1906 100644 --- a/src/ampform/dynamics/builder.py +++ b/src/ampform/dynamics/builder.py @@ -9,7 +9,7 @@ from attrs import field, frozen from attrs.validators import instance_of -from ampform.dynamics import EnergyDependentWidth, FormFactor, relativistic_breit_wigner +from ampform.dynamics import EnergyDependentWidth, FormFactor, SimpleBreitWigner from ampform.dynamics.phasespace import ( EqualMassPhaseSpaceFactor, PhaseSpaceFactor, @@ -156,10 +156,10 @@ def __simple_breit_wigner( identifier = resonance.latex if resonance.latex else resonance.name res_mass = sp.Symbol(f"m_{{{identifier}}}", nonnegative=True) res_width = sp.Symbol(Rf"\Gamma_{{{identifier}}}", nonnegative=True) - expression = relativistic_breit_wigner( + expression = SimpleBreitWigner( s=inv_mass**2, - mass0=res_mass, - gamma0=res_width, + mass=res_mass, + width=res_width, ) parameter_defaults = { res_mass: resonance.mass, From 221618a28cb1cae20c8040b519d742b2f391e8aa Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 21 May 2024 11:34:32 +0200 Subject: [PATCH 8/8] BREAK: deprecate `relativistic_breit_wigner()` --- docs/_extend_docstrings.py | 14 ----------- docs/usage.ipynb | 6 +++-- docs/usage/amplitude.ipynb | 2 +- docs/usage/dynamics.ipynb | 37 ++++++------------------------ docs/usage/dynamics/k-matrix.ipynb | 13 ++++++----- src/ampform/dynamics/__init__.py | 4 ++++ src/ampform/dynamics/builder.py | 4 ++-- 7 files changed, 25 insertions(+), 55 deletions(-) diff --git a/docs/_extend_docstrings.py b/docs/_extend_docstrings.py index 0fda7283f..2d9915065 100644 --- a/docs/_extend_docstrings.py +++ b/docs/_extend_docstrings.py @@ -645,20 +645,6 @@ def extend_get_boost_chain_suffix() -> None: ) -def extend_relativistic_breit_wigner() -> None: - from ampform.dynamics import relativistic_breit_wigner - - s, m0, w0 = sp.symbols("s m0 Gamma0") - rel_bw = relativistic_breit_wigner(s, m0, w0) - _append_to_docstring( - relativistic_breit_wigner, - f""" - .. math:: {sp.latex(rel_bw)} - :label: relativistic_breit_wigner - """, - ) - - def extend_relativistic_breit_wigner_with_ff() -> None: from ampform.dynamics import relativistic_breit_wigner_with_ff diff --git a/docs/usage.ipynb b/docs/usage.ipynb index 4fe601cc9..10bb1b3c5 100644 --- a/docs/usage.ipynb +++ b/docs/usage.ipynb @@ -115,10 +115,12 @@ "source": [ "import sympy as sp\n", "\n", - "from ampform.dynamics import relativistic_breit_wigner\n", + "from ampform.dynamics import SimpleBreitWigner\n", + "from ampform.io import aslatex\n", "\n", "m, m0, w0 = sp.symbols(\"m m0 Gamma0\")\n", - "relativistic_breit_wigner(s=m**2, mass0=m0, gamma0=w0)" + "bw = SimpleBreitWigner(s=m**2, mass=m0, width=w0)\n", + "Math(aslatex({bw: bw.doit(deep=False)}))" ] }, { diff --git a/docs/usage/amplitude.ipynb b/docs/usage/amplitude.ipynb index d022e41a6..c1d0897aa 100644 --- a/docs/usage/amplitude.ipynb +++ b/docs/usage/amplitude.ipynb @@ -555,7 +555,7 @@ "source": [ "To set dynamics for specific resonances, use {meth}`.DynamicsSelector.assign` on the same {attr}`.HelicityAmplitudeBuilder.dynamics` attribute. You can set the dynamics to be any kind of {class}`~sympy.core.expr.Expr`, as long as you keep track of which {class}`~sympy.core.symbol.Symbol` names you use (see {doc}`/usage/dynamics/custom`).\n", "\n", - "AmpForm does provide a few common {mod}`.dynamics` functions, which can be constructed as {class}`~sympy.core.expr.Expr` with the correct {class}`~sympy.core.symbol.Symbol` names using {meth}`.DynamicsSelector.assign`. This function takes specific {mod}`.dynamics.builder` functions and classes, such as {class}`.RelativisticBreitWignerBuilder`, which can create {func}`.relativistic_breit_wigner` functions for specific resonances. Here's an example for a relativistic Breit-Wigner _with form factor_ for the intermediate resonances and use a Blatt-Weisskopf barrier factor for the production decay:" + "AmpForm does provide a few common {mod}`.dynamics` functions, which can be constructed as {class}`~sympy.core.expr.Expr` with the correct {class}`~sympy.core.symbol.Symbol` names using {meth}`.DynamicsSelector.assign`. This function takes specific {mod}`.dynamics.builder` functions and classes, such as {class}`.RelativisticBreitWignerBuilder`, which can create {class}`.SimpleBreitWigner` functions for specific resonances. Here's an example for a relativistic Breit-Wigner _with form factor_ for the intermediate resonances and use a Blatt-Weisskopf barrier factor for the production decay:" ] }, { diff --git a/docs/usage/dynamics.ipynb b/docs/usage/dynamics.ipynb index 09ea995bb..f93bb0677 100644 --- a/docs/usage/dynamics.ipynb +++ b/docs/usage/dynamics.ipynb @@ -391,7 +391,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The 'normal' {func}`.relativistic_breit_wigner` looks as follows:" + "The {class}`.SimpleBreitWigner` looks as follows:" ] }, { @@ -401,34 +401,11 @@ "tags": [] }, "outputs": [], - "source": [ - "from ampform.dynamics import relativistic_breit_wigner\n", - "\n", - "m, m0, w0 = sp.symbols(\"m, m0, Gamma0\", nonnegative=True)\n", - "rel_bw = relativistic_breit_wigner(s=m**2, mass0=m0, gamma0=w0)\n", - "rel_bw" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "editable": true, - "jupyter": { - "source_hidden": true - }, - "slideshow": { - "slide_type": "" - }, - "tags": [ - "hide-input" - ] - }, - "outputs": [], "source": [ "from ampform.dynamics import SimpleBreitWigner\n", "\n", - "bw = SimpleBreitWigner(s, m0, w0)\n", + "m, m0, w0 = sp.symbols(\"m, m0, Gamma0\", nonnegative=True)\n", + "bw = SimpleBreitWigner(m**2, m0, w0)\n", "Math(aslatex({bw: bw.doit(deep=False)}))" ] }, @@ -443,7 +420,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The relativistic Breit–Wigner can be adapted slightly, so that its amplitude goes to zero at threshold ($m_0 = m1 + m2$) and that it becomes normalizable. This is done with {ref}`form factors ` and can be obtained with the function {func}`.relativistic_breit_wigner_with_ff`:" + "The relativistic Breit–Wigner can be adapted slightly, so that its amplitude goes to zero at threshold, $s = \\left(m_1 + m_2\\right)^2$, and that it becomes normalizable. This is done with {ref}`form factors ` and can be obtained with the function {func}`.relativistic_breit_wigner_with_ff`:" ] }, { @@ -456,7 +433,7 @@ "source": [ "from ampform.dynamics import PhaseSpaceFactorSWave, relativistic_breit_wigner_with_ff\n", "\n", - "rel_bw_with_ff = relativistic_breit_wigner_with_ff(\n", + "bw_with_ff = relativistic_breit_wigner_with_ff(\n", " s=s,\n", " mass0=m0,\n", " gamma0=w0,\n", @@ -466,7 +443,7 @@ " meson_radius=1,\n", " phsp_factor=PhaseSpaceFactorSWave,\n", ")\n", - "rel_bw_with_ff" + "bw_with_ff" ] }, { @@ -711,7 +688,7 @@ ")\n", "np_rel_bw = sp.lambdify(\n", " args=(m, w0, L, d, m0, m1, m2),\n", - " expr=rel_bw.doit(),\n", + " expr=bw.doit(),\n", ")\n", "\n", "# Set sliders\n", diff --git a/docs/usage/dynamics/k-matrix.ipynb b/docs/usage/dynamics/k-matrix.ipynb index b401194e2..497de35a8 100644 --- a/docs/usage/dynamics/k-matrix.ipynb +++ b/docs/usage/dynamics/k-matrix.ipynb @@ -128,7 +128,7 @@ "from mpl_interactions.controller import Controls\n", "\n", "import symplot\n", - "from ampform.dynamics import PhaseSpaceFactor, kmatrix, relativistic_breit_wigner\n", + "from ampform.dynamics import PhaseSpaceFactor, SimpleBreitWigner, kmatrix\n", "\n", "logging.basicConfig()\n", "logging.getLogger().setLevel(logging.ERROR)\n", @@ -578,7 +578,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Notice how the $\\boldsymbol{K}$-matrix reduces to a {func}`.relativistic_breit_wigner` in the case of one channel and one pole (but for a residue constant $\\gamma$):" + "Notice how the $\\boldsymbol{K}$-matrix reduces to a {class}`.SimpleBreitWigner` in the case of one channel and one pole (but for a residue constant $\\gamma$):" ] }, { @@ -605,9 +605,10 @@ "outputs": [], "source": [ "s, m1, m2, Gamma1, Gamma2 = sp.symbols(\"s m1 m2 Gamma1 Gamma2\", nonnegative=True)\n", - "bw1 = relativistic_breit_wigner(s, m1, Gamma1)\n", - "bw2 = relativistic_breit_wigner(s, m2, Gamma2)\n", + "bw1 = SimpleBreitWigner(s, m1, Gamma1)\n", + "bw2 = SimpleBreitWigner(s, m2, Gamma2)\n", "bw = bw1 + bw2\n", + "bw = bw.doit()\n", "bw" ] }, @@ -875,7 +876,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Again, as in {ref}`usage/dynamics/k-matrix:Non-relativistic K-matrix`, the $\\boldsymbol{K}$-matrix reduces to something of a {func}`.relativistic_breit_wigner`. This time, the width has been replaced by a {class}`.EnergyDependentWidth` and some {class}`.PhaseSpaceFactor`s have been inserted that take care of the decay into two decay products:" + "Again, as in {ref}`usage/dynamics/k-matrix:Non-relativistic K-matrix`, the $\\boldsymbol{K}$-matrix reduces to something of a {class}`.SimpleBreitWigner`. This time, the width has been replaced by a {class}`.EnergyDependentWidth` and some {class}`.PhaseSpaceFactor`s have been inserted that take care of the decay into two decay products:" ] }, { @@ -1092,7 +1093,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now again let's compare the compare this with a sum of two {func}`.relativistic_breit_wigner`s, now with the two additional $\\beta$-constants." + "Now again let's compare the compare this with a sum of two {class}`.SimpleBreitWigner`s, now with the two additional $\\beta$-constants." ] }, { diff --git a/src/ampform/dynamics/__init__.py b/src/ampform/dynamics/__init__.py index 4231ddbe4..866425bf5 100644 --- a/src/ampform/dynamics/__init__.py +++ b/src/ampform/dynamics/__init__.py @@ -188,7 +188,11 @@ def relativistic_breit_wigner(s, mass0, gamma0) -> sp.Expr: See :ref:`usage/dynamics:_Without_ form factor` and :cite:`ParticleDataGroup:2020ssz`. + + .. deprecated:: 0.16.0 + Use `.SimpleBreitWigner` instead. """ + warn("Use SimpleBreitWigner instead", category=DeprecationWarning, stacklevel=1) return SimpleBreitWigner(s, mass0, gamma0) diff --git a/src/ampform/dynamics/builder.py b/src/ampform/dynamics/builder.py index 5d87f1906..0d779488f 100644 --- a/src/ampform/dynamics/builder.py +++ b/src/ampform/dynamics/builder.py @@ -57,7 +57,7 @@ class ResonanceDynamicsBuilder(Protocol): Follow this `~typing.Protocol` when defining a builder function that is to be used by `.DynamicsSelector.assign`. For an example, see the source code - `.create_relativistic_breit_wigner`, which creates a `.relativistic_breit_wigner`. + `.create_relativistic_breit_wigner`, which creates a `.SimpleBreitWigner`. .. seealso:: :doc:`/usage/dynamics/custom` """ @@ -236,7 +236,7 @@ def __create_symbols( create_relativistic_breit_wigner = RelativisticBreitWignerBuilder( form_factor=False ).__call__ -"""Create a `.relativistic_breit_wigner` for a two-body decay. +"""Create a `.SimpleBreitWigner` for a two-body decay. This is a convenience function for a `RelativisticBreitWignerBuilder` _without_ form factor.