import ampform.helicity.decay

Extract two-body decay info from a Transition.

class StateWithID(particle: Particle, spin_projection: SupportsFloat, id: int)[source]#

Bases: State

Extension of State that embeds the state ID.

id: int[source]#
classmethod from_transition(transition: FrozenTransition[State, InteractionProperties], state_id: int) StateWithID[source]#
class TwoBodyDecay(parent: StateWithID, children: tuple[StateWithID, StateWithID], interaction: InteractionProperties)[source]#

Bases: object

Two-body sub-decay in a Transition.

This container class ensures that:

  1. a selected node in a Transition is indeed a 1-to-2 body decay

  2. its two children are sorted by whether they decay further or not (see get_helicity_angle_symbols, formulate_wigner_d, and formulate_clebsch_gordan_coefficients).

  3. the TwoBodyDecay is hashable, so that it can be used as a key (see set_dynamics.)

parent: StateWithID[source]#
children: tuple[StateWithID, StateWithID][source]#
interaction: InteractionProperties[source]#
static create(obj) TwoBodyDecay[source]#

Create a TwoBodyDecay instance from an arbitrary object.

More implementations of create() can be implemented with @ampform.helicity.decay._create_two_body_decay.register(TYPE).

classmethod from_transition(transition: FrozenTransition[State, InteractionProperties], node_id: int) TwoBodyDecay[source]#
is_opposite_helicity_state(topology: Topology, state_id: int) bool[source]#

Determine if an edge is an “opposite helicity” state.

This function provides a deterministic way of identifying states in a Topology as “opposite helicity” vs “helicity” state. It enforces that:

  1. state 0 is never an opposite helicity state

  2. the sibling of an opposite helicity state is a helicity state.

>>> from qrules.topology import create_isobar_topologies
>>> topologies = create_isobar_topologies(5)
>>> for topology in topologies:
...     assert not is_opposite_helicity_state(topology, state_id=0)
...     for state_id in set(topology.edges) - topology.incoming_edge_ids:
...         sibling_id = get_sibling_state_id(topology, state_id)
...         assert is_opposite_helicity_state(
...             topology, state_id
...         ) != is_opposite_helicity_state(topology, sibling_id)

The Wigner-\(D\) function for a two-particle state treats one helicity with a negative sign. This sign originates from Eq.(13) in [14] (see also Eq.(6) in [1]). Following [1], we call the state that gets this minus sign the “opposite helicity” state. The other state is called helicity state. The choice of (opposite) helicity state affects not only the sign in the Wigner-\(D\) function, but also the choice of angles: the argument of the Wigner-\(D\) function returned by formulate_wigner_d() are the angles of the helicity state.

get_sibling_state_id(topology: Topology, state_id: int) int[source]#

Get the sibling state ID for a state in an isobar decay.


-- 3 -- 0
     4 -- 1

The sibling state of 1 is 2 and the sibling state of 3 is 4.

get_helicity_info(transition: FrozenTransition[State, InteractionProperties], node_id: int) tuple[State, tuple[State, State]][source]#

Extract in- and outgoing states for a two-body decay node.

get_parent_id(topology: Topology, state_id: int) int | None[source]#

Get the edge ID of the edge from which this state decayed.


This only works on 1-to-\(n\) isobar topologies.

>>> from qrules.topology import create_isobar_topologies
>>> topologies = create_isobar_topologies(3)
>>> topology = topologies[0]
>>> get_parent_id(topology, state_id=0)
>>> get_parent_id(topology, state_id=1)  # parent is the resonance
>>> get_parent_id(topology, state_id=2)
>>> get_parent_id(topology, state_id=3)
>>> get_parent_id(topology, state_id=-1)  # already the top particle
list_decay_chain_ids(topology: Topology, state_id: int) list[int][source]#

Get the edge ID of the edge from which this state decayed.

>>> from qrules.topology import create_isobar_topologies
>>> topologies = create_isobar_topologies(3)
>>> topology = topologies[0]
>>> list_decay_chain_ids(topology, state_id=0)
[0, -1]
>>> list_decay_chain_ids(topology, state_id=1)
[1, 3, -1]
>>> list_decay_chain_ids(topology, state_id=2)
[2, 3, -1]
>>> list_decay_chain_ids(topology, state_id=-1)
get_sorted_states(transition: FrozenTransition[State, InteractionProperties], state_ids: Iterable[int]) list[State][source]#

Get a sorted list of State instances.

In order to ensure correct naming of amplitude coefficients the list has to be sorted by name. The same coefficient names have to be created for two transitions that only differ from a kinematic standpoint (swapped external edges).

assert_isobar_topology(topology: Topology) None[source]#
assert_two_body_decay(topology: Topology, node_id: int) None[source]#
determine_attached_final_state(topology: Topology, state_id: int) list[int][source]#

Determine all final state particles of a transition.

These are attached downward (forward in time) for a given edge (resembling the root).


For edge 5 in Figure topologies[0], we get:

>>> from qrules.topology import create_isobar_topologies
>>> topologies = create_isobar_topologies(5)
>>> determine_attached_final_state(topologies[3], state_id=5)
[0, 3, 4]
>>> import pytest
>>> from ampform._qrules import get_qrules_version
>>> if get_qrules_version() < (0, 10):
...     pytest.skip("Doctest only works for qrules>=0.10")
get_outer_state_ids(obj: ReactionInfo | StateTransition) list[int][source]#
get_outer_state_ids(transition: FrozenTransition[State, InteractionProperties]) list[int]
get_outer_state_ids(reaction: ReactionInfo) list[int]
get_prefactor(transition: FrozenTransition[State, InteractionProperties]) float[source]#

Calculate the product of all prefactors defined in this transition.

group_by_spin_projection(transitions: Iterable[FrozenTransition[State, InteractionProperties]]) list[list[FrozenTransition[State, InteractionProperties]]][source]#

Match final and initial states in groups.

Each Transition corresponds to a specific state transition amplitude. This function groups together transitions, which have the same initial and final state (including spin). This is needed to determine the coherency of the individual amplitude parts.

group_by_topology(transitions: Iterable[StateTransition]) dict[Topology, list[StateTransition]][source]#

Group state transitions by different Topology.