Skip to content

Commit 16227f0

Browse files
authored
FEAT: implement general dispersion integral (#480)
* BREAK: move break-up momentum to `kinematics` module * DOC: update link to Chung paper
1 parent 1c4bc64 commit 16227f0

File tree

14 files changed

+1248
-240
lines changed

14 files changed

+1248
-240
lines changed

.cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@
251251
"hlines",
252252
"htmlcov",
253253
"imap",
254+
"infty",
254255
"ipynb",
255256
"ipython",
256257
"isort",
@@ -289,6 +290,7 @@
289290
"topness",
290291
"unevaluatable",
291292
"unitarity",
293+
"unphysical",
292294
"venv",
293295
"weisskopf",
294296
"yticklabels",

docs/_extend_docstrings.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,20 @@
2121
from sympy.printing.numpy import NumPyPrinter
2222

2323
from ampform.dynamics.phasespace import (
24-
BreakupMomentum,
25-
BreakupMomentumComplex,
26-
BreakupMomentumKallen,
27-
BreakupMomentumSplitSqrt,
28-
BreakupMomentumSquared,
2924
PhaseSpaceFactor,
3025
PhaseSpaceFactorKallen,
3126
PhaseSpaceFactorSplitSqrt,
3227
PhaseSpaceFactorSWave,
3328
)
3429
from ampform.io import aslatex
3530
from ampform.kinematics.lorentz import ArraySize, FourMomentumSymbol
31+
from ampform.kinematics.phasespace import (
32+
BreakupMomentum,
33+
BreakupMomentumComplex,
34+
BreakupMomentumKallen,
35+
BreakupMomentumSplitSqrt,
36+
BreakupMomentumSquared,
37+
)
3638
from ampform.sympy._array_expressions import ArrayMultiplication
3739
from ampform.sympy._cache import get_readable_hash, make_hashable
3840

@@ -204,18 +206,12 @@ def extend_BoostZMatrix() -> None:
204206
)
205207

206208

207-
def extend_PhaseSpaceFactorComplex() -> None:
208-
from ampform.dynamics.phasespace import PhaseSpaceFactorComplex
209+
def extend_ChewMandelstamIntegral() -> None:
210+
from ampform.dynamics.phasespace import ChewMandelstamIntegral
209211

210-
s, m_a, m_b = sp.symbols("s, m_a, m_b")
211-
expr = PhaseSpaceFactorComplex(s, m_a, m_b)
212+
s, m_a, m_b, ell = sp.symbols(R"s m_a m_b \ell")
213+
expr = ChewMandelstamIntegral(s, m_a, m_b, ell)
212214
_append_latex_doit_definition(expr)
213-
_append_to_docstring(
214-
PhaseSpaceFactorComplex,
215-
"""
216-
with :math:`q^2(s)` defined as :eq:`BreakupMomentumSquared`.
217-
""",
218-
)
219215

220216

221217
def extend_ComplexSqrt() -> None:
@@ -409,6 +405,20 @@ def extend_PhaseSpaceFactorAbs() -> None:
409405
)
410406

411407

408+
def extend_PhaseSpaceFactorComplex() -> None:
409+
from ampform.dynamics.phasespace import PhaseSpaceFactorComplex
410+
411+
s, m_a, m_b = sp.symbols("s, m_a, m_b")
412+
expr = PhaseSpaceFactorComplex(s, m_a, m_b)
413+
_append_latex_doit_definition(expr)
414+
_append_to_docstring(
415+
PhaseSpaceFactorComplex,
416+
"""
417+
with :math:`q^2(s)` defined as :eq:`BreakupMomentumSquared`.
418+
""",
419+
)
420+
421+
412422
def extend_Phi() -> None:
413423
from ampform.kinematics.angles import Phi
414424

docs/bibliography.bib

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ @misc{chungPrimerKmatrixFormalism1995
6464
author = {Chung, Suh-Urk},
6565
year = {1995},
6666
month = mar,
67-
url = {http://www.ep.ph.bham.ac.uk/exp/WA102/pics/chung5.ps.gz}
67+
url = {https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=88b101a5300736f78293cf10116c32e5d25e3c91}
6868
}
6969

7070
@techreport{chungSpinFormalismsUpdated2014,

docs/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ def _get_dataclasses(module):
288288
linkcheck_anchors = False
289289
linkcheck_ignore = [
290290
"http://www.curtismeyer.com",
291+
"https://citeseerx.ist.psu.edu/document",
291292
"https://doi.org/10.1002", # 403 for onlinelibrary.wiley.com
292293
"https://doi.org/10.1093", # 403 for PTEP
293294
"https://doi.org/10.1103", # 403 for Phys Rev D
@@ -344,6 +345,7 @@ def _get_dataclasses(module):
344345
".rst": "restructuredtext",
345346
}
346347
suppress_warnings = [
348+
"myst.directive_unknown",
347349
"myst.domains",
348350
# skipping unknown output mime type: application/json
349351
# https://github.com/ComPWA/ampform/runs/8132373732?check_suite_focus=true#step:5:127

docs/usage/dynamics.ipynb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"dynamics/custom\n",
7878
"dynamics/analytic-continuation\n",
7979
"dynamics/k-matrix\n",
80+
"dynamics/riemann-sheets\n",
8081
"```\n",
8182
"\n",
8283
"```{autolink-skip}\n",
@@ -674,7 +675,7 @@
674675
"name": "python",
675676
"nbconvert_exporter": "python",
676677
"pygments_lexer": "ipython3",
677-
"version": "3.11.9"
678+
"version": "3.13.11"
678679
}
679680
},
680681
"nbformat": 4,

docs/usage/dynamics/analytic-continuation.ipynb

Lines changed: 116 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@
9999
"from IPython.display import Markdown, Math\n",
100100
"from matplotlib_inline.backend_inline import set_matplotlib_formats\n",
101101
"\n",
102-
"from ampform.dynamics.phasespace import BreakupMomentum\n",
102+
"from ampform.dynamics.phasespace import ChewMandelstamIntegral\n",
103103
"from ampform.io import aslatex\n",
104+
"from ampform.kinematics.phasespace import BreakupMomentum\n",
104105
"\n",
105106
"\n",
106107
"def display_doit(exprs: list[sp.Expr], deep: bool = False) -> Math:\n",
@@ -365,9 +366,11 @@
365366
"S = X + 1j * Y\n",
366367
"ϵi = 1e-7j\n",
367368
"\n",
368-
"parameters = {m1: 0.2, m2: 0.6}\n",
369-
"thr_neg = (parameters[m1] - parameters[m2]) ** 2\n",
370-
"thr_pos = (parameters[m1] + parameters[m2]) ** 2\n",
369+
"m1_val = 0.2\n",
370+
"m2_val = 0.6\n",
371+
"parameters = {m1: m1_val, m2: m2_val}\n",
372+
"thr_neg = (m1_val - m2_val) ** 2\n",
373+
"thr_pos = (m1_val + m2_val) ** 2\n",
371374
"\n",
372375
"fig, axes = plt.subplots(\n",
373376
" dpi=200,\n",
@@ -546,6 +549,114 @@
546549
"})"
547550
]
548551
},
552+
{
553+
"cell_type": "markdown",
554+
"metadata": {},
555+
"source": [
556+
"## Dispersion integral"
557+
]
558+
},
559+
{
560+
"cell_type": "markdown",
561+
"metadata": {},
562+
"source": [
563+
"To get an analytic phasespace factor for higher angular momenta, the one has to compute the dispersion integral. According to [PDG 2025, §50. Resonances, Eq. (50.45)](https://pdg.lbl.gov/2025/reviews/rpp2025-rev-resonances.pdf#page=16) the once-substracted dispersion integral is given by:\n",
564+
"\n",
565+
"$$\n",
566+
"\\Sigma_a(s+0 i)=\\frac{s-s_{\\mathrm{thr}_a}}{\\pi} \\int_{s_{\\mathrm{thr}_a}}^{\\infty} \\frac{\\rho_a\\left(s^{\\prime}\\right) n_a^2\\left(s^{\\prime}\\right)}{\\left(s^{\\prime}-s_{\\mathrm{thr}_a}\\right)\\left(s^{\\prime}-s-i 0\\right)} \\mathrm{d} s^{\\prime}\n",
567+
"$$"
568+
]
569+
},
570+
{
571+
"cell_type": "code",
572+
"execution_count": null,
573+
"metadata": {},
574+
"outputs": [],
575+
"source": [
576+
"L = sp.Symbol(\"L\", integer=True, nonnegative=True)\n",
577+
"integral_expr = ChewMandelstamIntegral(s, m1, m2, L)\n",
578+
"integral_expr.doit(deep=False)"
579+
]
580+
},
581+
{
582+
"cell_type": "code",
583+
"execution_count": null,
584+
"metadata": {},
585+
"outputs": [],
586+
"source": [
587+
"integral_s_wave_func = sp.lambdify(\n",
588+
" [s, m1, m2, integral_expr.epsilon],\n",
589+
" integral_expr.subs(L, 0).doit(),\n",
590+
")\n",
591+
"integral_s_wave_func = np.vectorize(integral_s_wave_func)"
592+
]
593+
},
594+
{
595+
"cell_type": "code",
596+
"execution_count": null,
597+
"metadata": {
598+
"jupyter": {
599+
"source_hidden": true
600+
},
601+
"mystnb": {
602+
"code_prompt_show": "Same definition for P-wave function"
603+
},
604+
"tags": [
605+
"hide-input"
606+
]
607+
},
608+
"outputs": [],
609+
"source": [
610+
"integral_p_wave_func = sp.lambdify(\n",
611+
" [s, m1, m2, integral_expr.epsilon],\n",
612+
" integral_expr.subs(L, 1).doit(),\n",
613+
")\n",
614+
"integral_p_wave_func = np.vectorize(integral_p_wave_func)"
615+
]
616+
},
617+
{
618+
"cell_type": "code",
619+
"execution_count": null,
620+
"metadata": {
621+
"jupyter": {
622+
"source_hidden": true
623+
},
624+
"tags": [
625+
"hide-input"
626+
]
627+
},
628+
"outputs": [],
629+
"source": [
630+
"s_values = np.linspace(-0.15, 1.4, num=200)\n",
631+
"s_wave_values = integral_s_wave_func(s_values, m1_val, m2_val, epsilon=1e-5)\n",
632+
"p_wave_values = integral_p_wave_func(s_values, m1_val, m2_val, epsilon=1e-5)\n",
633+
"\n",
634+
"l_val = [0, 1]\n",
635+
"fig, axes = plt.subplots(figsize=(6, 7), nrows=2, sharex=True)\n",
636+
"fig.patch.set_facecolor(\"none\")\n",
637+
"ax1, ax2 = axes\n",
638+
"fig.suptitle(f\"Symbolic dispersion integrals for $m_1={m1_val:.2f}, m_2={m2_val:.2f}$\")\n",
639+
"for ax in axes:\n",
640+
" ax.axhline(0, linewidth=0.5, c=\"black\")\n",
641+
" ax.axvline(thr_pos, linestyle=\"--\", color=\"red\", alpha=0.7)\n",
642+
" ax.patch.set_facecolor(\"none\")\n",
643+
" ax.set_title(f\"$L = {l_val}$\")\n",
644+
" ax.set_ylabel(R\"$16\\pi \\; \\Sigma(s)$\")\n",
645+
"axes[-1].set_xlabel(\"$s$ (GeV$^2$)\")\n",
646+
"\n",
647+
"ax1.set_title(\"$S$-wave ($L=0$)\")\n",
648+
"ax1.plot(s_values, 16 * np.pi * s_wave_values.real, label=\"real\")\n",
649+
"ax1.plot(s_values, 16 * np.pi * s_wave_values.imag, label=\"imaginary\")\n",
650+
"\n",
651+
"ax2.set_title(\"$P$-wave ($L=1$)\")\n",
652+
"ax2.plot(s_values, 16 * np.pi * p_wave_values.real, label=\"real\")\n",
653+
"ax2.plot(s_values, 16 * np.pi * p_wave_values.imag, label=\"imaginary\")\n",
654+
"\n",
655+
"ax1.legend(framealpha=0)\n",
656+
"fig.tight_layout()\n",
657+
"plt.show()"
658+
]
659+
},
549660
{
550661
"cell_type": "markdown",
551662
"metadata": {},
@@ -813,7 +924,7 @@
813924
"name": "python",
814925
"nbconvert_exporter": "python",
815926
"pygments_lexer": "ipython3",
816-
"version": "3.13.9"
927+
"version": "3.13.11"
817928
}
818929
},
819930
"nbformat": 4,

0 commit comments

Comments
 (0)