4242 ParityVertex ,
4343 get_decay_chains ,
4444 get_distribution_def ,
45- get_reference_topology ,
4645)
4746from ampform_dpd .spin import create_spin_range
4847
4948if TYPE_CHECKING :
5049 from collections .abc import Callable
5150
52- from ampform_dpd .decay import FinalStateID
51+ from ampform_dpd .decay import FinalStateID , State , StateID
5352 from ampform_dpd .io .serialization .format import ModelDefinition
5453
54+ _REFERENCE_SUBSYSTEMS : dict [StateID , FinalStateID ] = {0 : 1 , 1 : 1 , 2 : 2 , 3 : 3 }
55+ """Reference subsystem to use for the alignment rotation of each state.
56+
57+ Each final state is aligned with respect to its own subsystem and the initial state with
58+ respect to subsystem 1. This is the convention of `ThreeBodyDecays.jl
59+ <https://github.com/mmikhasenko/ThreeBodyDecays.jl>`_, which the serialization format
60+ follows, and it is used instead of the single reference topology that the serialized
61+ model declares.
62+ """
63+
5564
5665def formulate ( # ruff: ignore[too-many-locals]
5766 model : ModelDefinition ,
@@ -124,36 +133,46 @@ def formulate_chain_amplitude( # ruff: ignore[too-many-locals, too-many-positio
124133 to_latex : Callable [[str ], str ] = identity_function ,
125134 additional_builders : dict [str , PropagatorDynamicsBuilder ] | None = None ,
126135) -> dict [sp .Basic , complex | float | sp .Expr ]:
136+ r"""Formulate the amplitude for one decay chain of a serialized model.
137+
138+ This is the serialization counterpart of
139+ `.DalitzPlotDecompositionBuilder.formulate_subsystem_amplitude`: the couplings and
140+ dynamics are read from the model definition instead of being generated, but the
141+ phase conventions, the Kronecker delta over the production helicities, and the sum
142+ over the resonance helicity :math:`\lambda_R` are the same. The two implementations
143+ have to be kept in sync.
144+ """
127145 chain_defs = get_decay_chains (model )
128146 chain_definition = chain_defs [chain_idx ]
129- # -----------------------
130147 dynamics = formulate_dynamics (
131148 chain_definition , model , to_latex , additional_builders
132149 )
133150 for vertex in chain_definition ["vertices" ]:
134151 dynamics *= formulate_form_factor (vertex , model )
135- # -----------------------
136152 weight , weight_val = _get_weight (chain_definition , to_latex )
137- # -----------------------
138- (i , λi_val ), (j , λj_val ) = _get_decay_product_helicities (chain_definition )
153+ i , j = _get_decay_product_ids (chain_definition )
139154 θij , θij_expr = formulate_scattering_angle (i , j )
140155 jR = sp .Rational (chain_definition ["propagators" ][0 ]["spin" ]) # ruff: ignore[non-lowercase-variable-in-function]
141- R_node , λR_val = _get_resonance_helicity (chain_definition ) # ruff: ignore[non-lowercase-variable-in-function]
142- λR = _get_helicity_symbol (R_node )
143- # -----------------------
156+ λR = _get_helicity_symbol (_get_resonance_node (chain_definition ))
144157 A = _generate_amplitude_index_bases ()
145- subsystem_id = get_spectator_id (chain_definition ["topology" ])
158+ spectator_id = get_spectator_id (chain_definition ["topology" ])
159+ states = get_states (model )
160+ helicities = (λ0 , λ1 , λ2 , λ3 )
146161 h_prod = formulate_recoupling (model , chain_idx , vertex_idx = 0 )
147162 h_dec = formulate_recoupling (model , chain_idx , vertex_idx = 1 )
148- amplitude_expression = (
163+ chain_amplitude = (
149164 weight
165+ * sp .sqrt (2 * jR + 1 )
166+ * _formulate_phase_factor (states [spectator_id ], helicities [spectator_id ])
167+ * _formulate_phase_factor (states [j ], helicities [j ])
168+ * δ (λ0 , λR - helicities [spectator_id ])
150169 * h_prod
151170 * h_dec
152- * Wigner .d (jR , λR , λi_val - λj_val , θij )
171+ * Wigner .d (jR , λR , helicities [ i ] - helicities [ j ] , θij )
153172 * dynamics .expression
154173 )
155- amplitude_expression = amplitude_expression . subs ({ λR : λR_val } )
156- amplitude_symbol = A [subsystem_id ][λ0 , λ1 , λ2 , λ3 ]
174+ amplitude_expression = PoolSum ( chain_amplitude , ( λR , create_spin_range ( jR )) )
175+ amplitude_symbol = A [spectator_id ][λ0 , λ1 , λ2 , λ3 ]
157176 return {
158177 amplitude_symbol : amplitude_expression ,
159178 weight : weight_val ,
@@ -162,6 +181,34 @@ def formulate_chain_amplitude( # ruff: ignore[too-many-locals, too-many-positio
162181 }
163182
164183
184+ def _formulate_phase_factor (state : State , helicity : sp .Rational | sp .Symbol ) -> sp .Expr :
185+ r"""Formulate the :math:`(-1)^{j-\lambda}` phase factor of a state."""
186+ return (- 1 ) ** (state .spin - helicity )
187+
188+
189+ def _get_decay_product_ids (
190+ chain_definition : DecayChain ,
191+ ) -> tuple [FinalStateID , FinalStateID ]:
192+ """Get the IDs of the two decay products, ignoring their serialized helicities.
193+
194+ The helicity values from `._get_decay_product_helicities` are not substituted into
195+ the chain amplitude: it is summed over all allowed helicities instead.
196+ """
197+ (i , _ ), (j , _ ) = _get_decay_product_helicities (chain_definition )
198+ return cast ("FinalStateID" , i ), cast ("FinalStateID" , j )
199+
200+
201+ def _get_resonance_node (
202+ chain_definition : DecayChain ,
203+ ) -> tuple [FinalStateID , FinalStateID ]:
204+ """Get the node of the resonance, ignoring its serialized helicity.
205+
206+ See `._get_decay_product_ids` for why the helicity value is discarded.
207+ """
208+ node , _ = _get_resonance_helicity (chain_definition )
209+ return node
210+
211+
165212def _get_decay_product_helicities (
166213 chain_definition : DecayChain ,
167214) -> tuple [tuple [int , sp .Rational ], tuple [int , sp .Rational ]]:
@@ -187,28 +234,38 @@ def formulate_aligned_amplitude(
187234 λ2 : sp .Rational | sp .Symbol ,
188235 λ3 : sp .Rational | sp .Symbol ,
189236) -> tuple [PoolSum , dict [sp .Symbol , sp .Expr ]]:
190- reference_topology = get_reference_topology (model )
191- reference_subsystem = get_spectator_id (reference_topology )
192- wigner_generator = _AlignmentWignerGenerator (reference_subsystem )
237+ generators = {
238+ subsystem_id : _AlignmentWignerGenerator (subsystem_id )
239+ for subsystem_id in sorted (set (_REFERENCE_SUBSYSTEMS .values ()))
240+ }
241+ wigner_generators = {
242+ rotated_state : generators [subsystem_id ]
243+ for rotated_state , subsystem_id in _REFERENCE_SUBSYSTEMS .items ()
244+ }
193245 _λ0 , _λ1 , _λ2 , _λ3 = sp .symbols (R"\lambda_(:4)^{\prime}" , rational = True )
194246 states = get_states (model )
195247 j0 , j1 , j2 , j3 = (states [i ].spin for i in sorted (states ))
196248 A = _generate_amplitude_index_bases ()
197249 amp_expr = PoolSum (
198250 sum (
199251 A [k ][_λ0 , _λ1 , _λ2 , _λ3 ]
200- * wigner_generator (j0 , λ0 , _λ0 , rotated_state = 0 , aligned_subsystem = k )
201- * wigner_generator (j1 , _λ1 , λ1 , rotated_state = 1 , aligned_subsystem = k )
202- * wigner_generator (j2 , _λ2 , λ2 , rotated_state = 2 , aligned_subsystem = k )
203- * wigner_generator (j3 , _λ3 , λ3 , rotated_state = 3 , aligned_subsystem = k )
252+ * wigner_generators [ 0 ] (j0 , λ0 , _λ0 , rotated_state = 0 , aligned_subsystem = k )
253+ * wigner_generators [ 1 ] (j1 , _λ1 , λ1 , rotated_state = 1 , aligned_subsystem = k )
254+ * wigner_generators [ 2 ] (j2 , _λ2 , λ2 , rotated_state = 2 , aligned_subsystem = k )
255+ * wigner_generators [ 3 ] (j3 , _λ3 , λ3 , rotated_state = 3 , aligned_subsystem = k )
204256 for k in get_existing_subsystem_ids (model )
205257 ),
206258 (_λ0 , create_spin_range (j0 )),
207259 (_λ1 , create_spin_range (j1 )),
208260 (_λ2 , create_spin_range (j2 )),
209261 (_λ3 , create_spin_range (j3 )),
210262 )
211- return amp_expr , wigner_generator .angle_definitions
263+ angle_definitions = {
264+ symbol : expression
265+ for generator in generators .values ()
266+ for symbol , expression in generator .angle_definitions .items ()
267+ }
268+ return amp_expr , angle_definitions
212269
213270
214271def _get_weight (
@@ -386,6 +443,8 @@ class ParityRecoupling(sp.Expr):
386443
387444 def evaluate (self ) -> sp .Expr :
388445 λa , λb , λa0 , λb0 , f = self .args
446+ if λa0 == 0 and λb0 == 0 :
447+ return δ (λa , λa0 ) * δ (λb , λb0 )
389448 return δ (λa , λa0 ) * δ (λb , λb0 ) + f * δ (λa , - λa0 ) * δ (λb , - λb0 ) # ty: ignore[unsupported-operator]
390449
391450
0 commit comments