Skip to content

Commit 9529854

Browse files
authored
ENH: automatically select best reference subsystem (#178)
* ENH: document `formulate(reference_subsystem=None)`
1 parent c50b0e6 commit 9529854

File tree

3 files changed

+79
-14
lines changed

3 files changed

+79
-14
lines changed

docs/lc2pkpi.ipynb

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -254,31 +254,44 @@
254254
"model_builder = DalitzPlotDecompositionBuilder(DECAY, min_ls=(False, True))\n",
255255
"for chain in model_builder.decay.chains:\n",
256256
" model_builder.dynamics_choices.register_builder(chain, formulate_breit_wigner)\n",
257-
"model = model_builder.formulate(reference_subsystem=1)\n",
257+
"model = model_builder.formulate()\n",
258258
"model.intensity"
259259
]
260260
},
261+
{
262+
"cell_type": "markdown",
263+
"metadata": {},
264+
"source": [
265+
"Notice that subsystem `3` (with the $\\Lambda^*$ resonances and recoil $\\pi^+$) has been selected as reference subsystem for the Wigner‑$d$ functions. This is computationally more efficient, because that subsystem contains most resonances."
266+
]
267+
},
261268
{
262269
"cell_type": "code",
263270
"execution_count": null,
264271
"metadata": {
265272
"tags": [
266-
"hide-input",
267-
"full-width",
268-
"hide-output",
269273
"scroll-output"
270274
]
271275
},
272276
"outputs": [],
273277
"source": [
274-
"Latex(aslatex(model.amplitudes).replace(R\"\\sum_\", R\"\\sum\\limits_\"))"
278+
"Latex(aslatex(model.amplitudes, terms_per_line=1))"
275279
]
276280
},
277281
{
278282
"cell_type": "markdown",
279283
"metadata": {},
280284
"source": [
281-
"By default, the aligned amplitudes are built up of Wigner-$d$ functions, Clebsch–Gordan coefficients ($C$), a resonance parametrization ($\\mathcal{R}(\\sigma)$), and two coupling symbols $\\mathcal{H}^\\text{prod}_\\dots,\\mathcal{H}^\\text{dec}_\\dots$. In some cases, you want to combine the couplings into one scaling factor. That can be done with the `use_coefficients` flag:"
285+
"By default, the aligned amplitudes are built up of Wigner-$d$ functions, Clebsch–Gordan coefficients ($C$), a resonance parametrization ($\\mathcal{R}(\\sigma)$), and two coupling symbols $\\mathcal{H}^\\text{prod}_{\\dots}, \\mathcal{H}^\\text{dec}_{\\dots}$. In some cases, you want to combine the couplings into one scaling factor. That can be done with the `use_coefficients` flag. The following example also explicitly specifies the reference subsystem (which is otherwise automatically chosen)."
286+
]
287+
},
288+
{
289+
"cell_type": "code",
290+
"execution_count": null,
291+
"metadata": {},
292+
"outputs": [],
293+
"source": [
294+
"model = model_builder.formulate(reference_subsystem=1, use_coefficients=True)"
282295
]
283296
},
284297
{
@@ -289,14 +302,13 @@
289302
"source_hidden": true
290303
},
291304
"tags": [
292-
"full-width"
305+
"hide-input"
293306
]
294307
},
295308
"outputs": [],
296309
"source": [
297-
"model = model_builder.formulate(reference_subsystem=1, use_coefficients=True)\n",
298310
"(symbol, expr), *_ = model.amplitudes.items()\n",
299-
"Latex(aslatex({symbol: expr}).replace(R\"\\sum_\", R\"\\sum\\limits_\"))"
311+
"Latex(aslatex({symbol: expr}, terms_per_line=1))"
300312
]
301313
}
302314
],
@@ -319,7 +331,7 @@
319331
"name": "python",
320332
"nbconvert_exporter": "python",
321333
"pygments_lexer": "ipython3",
322-
"version": "3.12.8"
334+
"version": "3.12.11"
323335
}
324336
},
325337
"nbformat": 4,

src/ampform_dpd/__init__.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,25 @@ def __init__(
9191

9292
def formulate(
9393
self,
94-
reference_subsystem: FinalStateID = 1,
94+
reference_subsystem: FinalStateID | None = None,
9595
*,
9696
cleanup_summations: bool = False,
9797
use_coefficients: bool = False,
9898
) -> AmplitudeModel:
99-
_check_reference_subsystems(self.decay, reference_subsystem)
99+
"""Formulate the amplitude model given the configuration of this builder.
100+
101+
Args:
102+
reference_subsystem: The subsystem to use as reference for the alignment of
103+
helicities. If `None`, the subsystem with the most resonances is chosen.
104+
cleanup_summations: Whether to remove helicity indices in the summations if
105+
their corresponding state is spinless.
106+
use_coefficients: Whether to use a single complex coefficient per decay
107+
chain, instead of separate coefficients for each helicity coupling.
108+
"""
109+
if reference_subsystem is None:
110+
reference_subsystem = _get_best_reference_subsystems(self.decay)
111+
else:
112+
_check_reference_subsystems(self.decay, reference_subsystem)
100113
helicity_symbols: tuple[sp.Symbol, sp.Symbol, sp.Symbol, sp.Symbol] = (
101114
sp.symbols("lambda:4", rational=True)
102115
)
@@ -244,9 +257,12 @@ def formulate_aligned_amplitude(
244257
λ1: sp.Rational | sp.Symbol,
245258
λ2: sp.Rational | sp.Symbol,
246259
λ3: sp.Rational | sp.Symbol,
247-
reference_subsystem: FinalStateID = 1,
260+
reference_subsystem: FinalStateID | None = None,
248261
) -> tuple[PoolSum, dict[sp.Symbol, sp.Expr]]:
249-
_check_reference_subsystems(self.decay, reference_subsystem)
262+
if reference_subsystem is None:
263+
reference_subsystem = _get_best_reference_subsystems(self.decay)
264+
else:
265+
_check_reference_subsystems(self.decay, reference_subsystem)
250266
wigner_generator = _AlignmentWignerGenerator(reference_subsystem)
251267
_λ0, _λ1, _λ2, _λ3 = sp.symbols(R"\lambda_(0:4)^{\prime}", rational=True)
252268
j0, j1, j2, j3 = (self.decay.states[i].spin for i in sorted(self.decay.states))
@@ -274,6 +290,17 @@ def _product(obj: Any | Iterable):
274290
return obj
275291

276292

293+
def _get_best_reference_subsystems(decay: ThreeBodyDecay) -> FinalStateID:
294+
subsystem_ids = _get_subsystem_ids(decay)
295+
if not subsystem_ids:
296+
msg = f"Decay {_get_decay_description(decay)} has no subsystems"
297+
raise ValueError(msg)
298+
resonances_per_subsystem = [
299+
(k, len(decay.get_subsystem(k).chains)) for k in subsystem_ids
300+
]
301+
return max(resonances_per_subsystem, key=operator.itemgetter(1))[0]
302+
303+
277304
def _check_reference_subsystems(
278305
decay: ThreeBodyDecay, reference_subsystem: FinalStateID
279306
) -> None:

tests/test_decay.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from qrules.transition import ReactionInfo
6+
7+
from ampform_dpd import (
8+
_get_best_reference_subsystems, # pyright: ignore[reportPrivateUsage]
9+
)
10+
from ampform_dpd.adapter.qrules import to_three_body_decay
111
from ampform_dpd.decay import IsobarNode, Particle
212

13+
if TYPE_CHECKING:
14+
from qrules.transition import ReactionInfo
15+
316
# https://compwa-org--129.org.readthedocs.build/report/018.html#resonances-and-ls-scheme
417
dummy_args = dict(mass=0, width=0)
518
Λc = Particle("Λc", latex=R"\Lambda_c^+", spin=0.5, parity=+1, **dummy_args)
@@ -20,3 +33,16 @@ def test_ls(self):
2033
assert node.interaction is not None
2134
assert node.interaction.L == L
2235
assert node.interaction.S == S
36+
37+
38+
def test_get_best_reference_subsystems(
39+
a2pipipi_reaction: ReactionInfo,
40+
xib2pkk_reaction: ReactionInfo,
41+
jpsi2pksigma_reaction: ReactionInfo,
42+
):
43+
decay = to_three_body_decay(a2pipipi_reaction.transitions)
44+
assert _get_best_reference_subsystems(decay) == 1
45+
decay = to_three_body_decay(xib2pkk_reaction.transitions)
46+
assert _get_best_reference_subsystems(decay) == 2
47+
decay = to_three_body_decay(jpsi2pksigma_reaction.transitions)
48+
assert _get_best_reference_subsystems(decay) == 2

0 commit comments

Comments
 (0)