Skip to content

Commit de83091

Browse files
authored
FEAT: implement BreitWigner expression classes (#423)
* BEHAVIOR: return expression classes from the BW builder * BREAK: deprecate relativistic_breit_wigner() * BREAK: remove determine_indices re-export from ampform.dynamics * DOC: document multi-channel Breit-Wigner * FEAT: implement MultichannelBreitWigner and ChannelArguments
1 parent 3c6bf45 commit de83091

12 files changed

Lines changed: 392 additions & 77 deletions

File tree

.cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"pyproject.toml"
3939
],
4040
"ignoreWords": [
41+
"Flatté",
4142
"Minkowski",
4243
"arange",
4344
"aslatex",

docs/_extend_docstrings.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,15 @@ def extend_BoostZMatrix() -> None:
294294
)
295295

296296

297+
def extend_BreitWigner() -> None:
298+
from ampform.dynamics import BreitWigner
299+
300+
s, m0, w0, m1, m2, d = sp.symbols("s m0 Gamma0 m1 m2 d", nonnegative=True)
301+
L = sp.Symbol("L", integer=True, nonnegative=True)
302+
expr = BreitWigner(s, m0, w0, m1, m2, angular_momentum=L, meson_radius=d)
303+
_append_latex_doit_definition(expr)
304+
305+
297306
def extend_ComplexSqrt() -> None:
298307
x = sp.Symbol("x", real=True)
299308
expr = ComplexSqrt(x)
@@ -446,6 +455,22 @@ def extend_is_within_phasespace() -> None:
446455
)
447456

448457

458+
def extend_MultichannelBreitWigner() -> None:
459+
from ampform.dynamics import ChannelArguments, MultichannelBreitWigner
460+
461+
s, m0 = sp.symbols("s m0", nonnegative=True)
462+
channels = tuple(
463+
ChannelArguments(
464+
s,
465+
m0,
466+
*sp.symbols(f"g{i}sq m_a{i} m_b{i} L{i} d", nonnegative=True),
467+
)
468+
for i in [1, 2]
469+
)
470+
expr = MultichannelBreitWigner(s, m0, channels=channels)
471+
_append_latex_doit_definition(expr)
472+
473+
449474
def extend_Phi() -> None:
450475
from ampform.kinematics.angles import Phi
451476

@@ -511,6 +536,14 @@ def extend_RotationZMatrix() -> None:
511536
)
512537

513538

539+
def extend_SimpleBreitWigner() -> None:
540+
from ampform.dynamics import SimpleBreitWigner
541+
542+
s, m0, w0 = sp.symbols("s m0 Gamma0", nonnegative=True)
543+
expr = SimpleBreitWigner(s, m0, w0)
544+
_append_latex_doit_definition(expr)
545+
546+
514547
def extend_SphericalHankel1() -> None:
515548
from ampform.dynamics.form_factor import SphericalHankel1
516549

@@ -615,20 +648,6 @@ def extend_get_boost_chain_suffix() -> None:
615648
)
616649

617650

618-
def extend_relativistic_breit_wigner() -> None:
619-
from ampform.dynamics import relativistic_breit_wigner
620-
621-
s, m0, w0 = sp.symbols("s m0 Gamma0")
622-
rel_bw = relativistic_breit_wigner(s, m0, w0)
623-
_append_to_docstring(
624-
relativistic_breit_wigner,
625-
f"""
626-
.. math:: {sp.latex(rel_bw)}
627-
:label: relativistic_breit_wigner
628-
""",
629-
)
630-
631-
632651
def extend_relativistic_breit_wigner_with_ff() -> None:
633652
from ampform.dynamics import relativistic_breit_wigner_with_ff
634653

docs/amplitude.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@
528528
"source": [
529529
"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}`dynamics/custom`).\n",
530530
"\n",
531-
"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:"
531+
"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` and {class}`.BreitWigner` expressions 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:"
532532
]
533533
},
534534
{

docs/dynamics.ipynb

Lines changed: 120 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@
316316
"cell_type": "markdown",
317317
"metadata": {},
318318
"source": [
319-
"The 'normal' {func}`.relativistic_breit_wigner` looks as follows:"
319+
"The {class}`.SimpleBreitWigner` looks as follows:"
320320
]
321321
},
322322
{
@@ -325,11 +325,11 @@
325325
"metadata": {},
326326
"outputs": [],
327327
"source": [
328-
"from ampform.dynamics import relativistic_breit_wigner\n",
328+
"from ampform.dynamics import SimpleBreitWigner\n",
329329
"\n",
330330
"m, m0, w0 = sp.symbols(\"m, m0, Gamma0\", nonnegative=True)\n",
331-
"rel_bw = relativistic_breit_wigner(s=m**2, mass0=m0, gamma0=w0)\n",
332-
"rel_bw"
331+
"bw = SimpleBreitWigner(m**2, m0, w0)\n",
332+
"Math(aslatex({bw: bw.doit(deep=False)}))"
333333
]
334334
},
335335
{
@@ -343,7 +343,7 @@
343343
"cell_type": "markdown",
344344
"metadata": {},
345345
"source": [
346-
"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 <dynamics:Form factor>` and can be obtained with the function {func}`.relativistic_breit_wigner_with_ff`:"
346+
"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 <dynamics:Form factor>` and can be obtained with the function {func}`.relativistic_breit_wigner_with_ff`:"
347347
]
348348
},
349349
{
@@ -354,7 +354,7 @@
354354
"source": [
355355
"from ampform.dynamics import PhaseSpaceFactorSWave, relativistic_breit_wigner_with_ff\n",
356356
"\n",
357-
"rel_bw_with_ff = relativistic_breit_wigner_with_ff(\n",
357+
"bw_with_ff = relativistic_breit_wigner_with_ff(\n",
358358
" s=s,\n",
359359
" mass0=m0,\n",
360360
" gamma0=w0,\n",
@@ -364,7 +364,7 @@
364364
" meson_radius=1,\n",
365365
" phsp_factor=PhaseSpaceFactorSWave,\n",
366366
")\n",
367-
"rel_bw_with_ff"
367+
"bw_with_ff"
368368
]
369369
},
370370
{
@@ -380,13 +380,18 @@
380380
"metadata": {
381381
"jupyter": {
382382
"source_hidden": true
383-
}
383+
},
384+
"mystnb": {
385+
"code_prompt_show": "Show the running-width definition"
386+
},
387+
"tags": [
388+
"hide-input"
389+
]
384390
},
385391
"outputs": [],
386392
"source": [
387393
"from ampform.dynamics import EnergyDependentWidth\n",
388394
"\n",
389-
"L = sp.Symbol(\"L\", integer=True)\n",
390395
"width = EnergyDependentWidth(\n",
391396
" s=s,\n",
392397
" mass0=m0,\n",
@@ -396,8 +401,29 @@
396401
" angular_momentum=L,\n",
397402
" meson_radius=1,\n",
398403
" phsp_factor=PhaseSpaceFactorSWave,\n",
399-
")\n",
400-
"Math(aslatex({width: width.evaluate()}))"
404+
")"
405+
]
406+
},
407+
{
408+
"cell_type": "code",
409+
"execution_count": null,
410+
"metadata": {
411+
"jupyter": {
412+
"source_hidden": true
413+
},
414+
"tags": [
415+
"hide-input"
416+
]
417+
},
418+
"outputs": [],
419+
"source": [
420+
"from ampform.dynamics import BreitWigner\n",
421+
"\n",
422+
"exprs = [\n",
423+
" BreitWigner(s, m0, w0, m1, m2, angular_momentum=L, meson_radius=d),\n",
424+
" width,\n",
425+
"]\n",
426+
"Math(aslatex({e: e.doit(deep=False) for e in exprs}))"
401427
]
402428
},
403429
{
@@ -407,6 +433,87 @@
407433
"It is possible to choose different formulations for the phase space factor $\\rho$, see {doc}`analyticity/phasespace-factors`."
408434
]
409435
},
436+
{
437+
"cell_type": "markdown",
438+
"metadata": {},
439+
"source": [
440+
"### Multi-channel Breit–Wigner"
441+
]
442+
},
443+
{
444+
"cell_type": "code",
445+
"execution_count": null,
446+
"metadata": {},
447+
"outputs": [],
448+
"source": [
449+
"from ampform.dynamics import ChannelArguments, MultichannelBreitWigner\n",
450+
"\n",
451+
"channels = tuple(\n",
452+
" ChannelArguments(\n",
453+
" s=s,\n",
454+
" mass=m0,\n",
455+
" coupling_squared=sp.Symbol(f\"g{i}^2\", nonnegative=True),\n",
456+
" m1=sp.Symbol(f\"m_{{a,{i}}}\"),\n",
457+
" m2=sp.Symbol(f\"m_{{b,{i}}}\"),\n",
458+
" angular_momentum=sp.Symbol(f\"L{i}\"),\n",
459+
" meson_radius=d,\n",
460+
" )\n",
461+
" for i in [1, 2]\n",
462+
")\n",
463+
"multi_bw = MultichannelBreitWigner(s, m0, channels)"
464+
]
465+
},
466+
{
467+
"cell_type": "code",
468+
"execution_count": null,
469+
"metadata": {
470+
"jupyter": {
471+
"source_hidden": true
472+
},
473+
"tags": [
474+
"hide-input"
475+
]
476+
},
477+
"outputs": [],
478+
"source": [
479+
"exprs = [\n",
480+
" multi_bw,\n",
481+
" *channels,\n",
482+
"]\n",
483+
"Math(aslatex({e: e.doit(deep=False) for e in exprs}))"
484+
]
485+
},
486+
{
487+
"cell_type": "markdown",
488+
"metadata": {},
489+
"source": [
490+
"The serialized $g_i^2$ values are coupling constants, not partial widths. Unlike {class}`.EnergyDependentWidth`, the channel terms are therefore not normalized at the pole position. For $L=0$, the expression has the familiar Flatté form:"
491+
]
492+
},
493+
{
494+
"cell_type": "code",
495+
"execution_count": null,
496+
"metadata": {
497+
"jupyter": {
498+
"source_hidden": true
499+
},
500+
"tags": [
501+
"full-width",
502+
"hide-input"
503+
]
504+
},
505+
"outputs": [],
506+
"source": [
507+
"channels = tuple(\n",
508+
" ChannelArguments(\n",
509+
" s, m0, sp.Symbol(f\"g{i}^2\"), sp.Symbol(f\"m_a{i}\"), sp.Symbol(f\"m_b{i}\")\n",
510+
" )\n",
511+
" for i in [1, 2]\n",
512+
")\n",
513+
"expr = MultichannelBreitWigner(s, m0, channels)\n",
514+
"Math(aslatex({expr: expr.doit().simplify()}))"
515+
]
516+
},
410517
{
411518
"cell_type": "markdown",
412519
"metadata": {},
@@ -418,7 +525,7 @@
418525
"cell_type": "markdown",
419526
"metadata": {},
420527
"source": [
421-
"The following shows the effect of {doc}`analyticity/phasespace-factors` a on relativistic Breit–Wigner:"
528+
"The following shows the effect of {doc}`analyticity/phasespace-factors` on a relativistic Breit–Wigner:"
422529
]
423530
},
424531
{
@@ -465,7 +572,7 @@
465572
"args = (m, w0, m0, m1, m2, L, d)\n",
466573
"np_rel_bw_with_ff = sp.lambdify(args, rel_bw_with_ff.doit())\n",
467574
"np_rel_bw_with_ff_ac = sp.lambdify(args, expr=rel_bw_with_ff_ac.doit())\n",
468-
"np_rel_bw = sp.lambdify(args, expr=rel_bw.doit())\n",
575+
"np_rel_bw = sp.lambdify(args, expr=bw.doit())\n",
469576
"\n",
470577
"# Create UI\n",
471578
"x = np.linspace(0, 4, num=500)\n",

docs/dynamics/k-matrix.ipynb

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"from matplotlib import cm\n",
7272
"from matplotlib.transforms import Bbox\n",
7373
"\n",
74-
"from ampform.dynamics import kmatrix, phasespace, relativistic_breit_wigner\n",
74+
"from ampform.dynamics import SimpleBreitWigner, kmatrix, phasespace\n",
7575
"from ampform.sympy import partial_doit, rename_symbols\n",
7676
"from ampform.sympy.slider import create_slider, substitute_indexed_symbols\n",
7777
"\n",
@@ -488,7 +488,7 @@
488488
"cell_type": "markdown",
489489
"metadata": {},
490490
"source": [
491-
"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$):"
491+
"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$):"
492492
]
493493
},
494494
{
@@ -515,9 +515,10 @@
515515
"outputs": [],
516516
"source": [
517517
"s, m1, m2, Gamma1, Gamma2 = sp.symbols(\"s m1 m2 Gamma1 Gamma2\", nonnegative=True)\n",
518-
"bw1 = relativistic_breit_wigner(s, m1, Gamma1)\n",
519-
"bw2 = relativistic_breit_wigner(s, m2, Gamma2)\n",
518+
"bw1 = SimpleBreitWigner(s, m1, Gamma1)\n",
519+
"bw2 = SimpleBreitWigner(s, m2, Gamma2)\n",
520520
"bw = bw1 + bw2\n",
521+
"bw = bw.doit()\n",
521522
"bw"
522523
]
523524
},
@@ -807,7 +808,7 @@
807808
"cell_type": "markdown",
808809
"metadata": {},
809810
"source": [
810-
"Again, as in {ref}`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:"
811+
"Again, as in {ref}`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:"
811812
]
812813
},
813814
{
@@ -1020,7 +1021,7 @@
10201021
"cell_type": "markdown",
10211022
"metadata": {},
10221023
"source": [
1023-
"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."
1024+
"Now again let's compare the compare this with a sum of two {class}`.SimpleBreitWigner`s, now with the two additional $\\beta$-constants."
10241025
]
10251026
},
10261027
{

docs/index.ipynb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,12 @@
110110
"source": [
111111
"import sympy as sp\n",
112112
"\n",
113-
"from ampform.dynamics import relativistic_breit_wigner\n",
113+
"from ampform.dynamics import SimpleBreitWigner\n",
114+
"from ampform.io import aslatex\n",
114115
"\n",
115116
"m, m0, w0 = sp.symbols(\"m m0 Gamma0\")\n",
116-
"relativistic_breit_wigner(s=m**2, mass0=m0, gamma0=w0)"
117+
"bw = SimpleBreitWigner(s=m**2, mass=m0, width=w0)\n",
118+
"Math(aslatex({bw: bw.doit(deep=False)}))"
117119
]
118120
},
119121
{

0 commit comments

Comments
 (0)