Conservation rules#

Hide code cell content
import attrs
import graphviz
from IPython.display import display

import qrules
from qrules.conservation_rules import (
    SpinEdgeInput,
    SpinNodeInput,
    parity_conservation,
    spin_conservation,
    spin_magnitude_conservation,
)
from qrules.quantum_numbers import Parity

QRules generates MutableTransitions, populates them with quantum numbers (edge properties representing states and nodes properties representing interactions), then checks whether the generated MutableTransitions comply with the rules formulated in the conservation_rules module.

The conservation_rules module can also be used separately. In this notebook, we will illustrate this by checking spin and parity conservation.

Parity conservation#

See parity_conservation():

parity_conservation(
    ingoing_edge_qns=[Parity(-1)],
    outgoing_edge_qns=[Parity(+1), Parity(+1)],
    l_magnitude=1,
)
True

Spin conservation#

spin_conservation() checks whether spin magnitude and spin projections are conserved. In addition, it checks whether the Clebsch-Gordan coefficients are non-zero, meaning that the coupled spins on the interaction nodes are valid as well.

No spin and angular momentum#

spin_conservation(
    ingoing_spins=[
        SpinEdgeInput(0, 0),
    ],
    outgoing_spins=[
        SpinEdgeInput(0, 0),
        SpinEdgeInput(0, 0),
    ],
    interaction_qns=SpinNodeInput(
        l_magnitude=0,  # false if 1
        l_projection=0,
        s_magnitude=0,
        s_projection=0,
    ),
)
True

Non-zero example#

spin_conservation(
    ingoing_spins=[
        SpinEdgeInput(1, 0),
    ],
    outgoing_spins=[
        SpinEdgeInput(1, +1),
        SpinEdgeInput(1, -1),
    ],
    interaction_qns=SpinNodeInput(
        l_magnitude=1,
        l_projection=0,
        s_magnitude=2,
        s_projection=0,
    ),
)
True

Example with a StateTransition#

First, generate some StateTransitions with generate_transitions(), then select one of them:

reaction = qrules.generate_transitions(
    initial_state="J/psi(1S)",
    final_state=["K0", "Sigma+", "p~"],
    allowed_interaction_types="strong",
    formalism="canonical",
)
transition = reaction.transitions[0]

Next, have a look at the edge and node properties, and use the underlying Topology to extract one of the node InteractionProperties with the surrounding states (these are tuples of a Particle and a float spin projection).

Hide code cell source
dot = qrules.io.asdot(transition, render_node=True)
display(graphviz.Source(dot))

dot = qrules.io.asdot(
    transition.topology,
    render_node=True,
    render_resonance_id=True,
    render_initial_state_id=True,
)
display(graphviz.Source(dot))
../../_images/c9ed721c4d0c45c809aa88e07291e812583b658d08bf9451882c03bfbf5c9d69.svg../../_images/ee9b5ff812c4113f2d7d0b64be322f07c7df633d9241b3e72345756c3add590c.svg

We select node \((0)\), which has incoming state ID \(-1\) and outgoing state IDs \(0\) and \(3\):

topology = transition.topology
node_id = 0
in_id, *_ = topology.get_edge_ids_ingoing_to_node(node_id)
out_id1, out_id2, *_ = topology.get_edge_ids_outgoing_from_node(node_id)

incoming_state = transition.states[in_id]
outgoing_state1 = transition.states[out_id1]
outgoing_state2 = transition.states[out_id2]
interaction = transition.interactions[node_id]

spin_magnitude_conservation(
    ingoing_spins=[
        SpinEdgeInput(
            spin_magnitude=incoming_state.particle.spin,
            spin_projection=incoming_state.spin_projection,
        )
    ],
    outgoing_spins=[
        SpinEdgeInput(
            spin_magnitude=outgoing_state1.particle.spin,
            spin_projection=outgoing_state1.spin_projection,
        ),
        SpinEdgeInput(
            spin_magnitude=outgoing_state2.particle.spin,
            spin_projection=outgoing_state2.spin_projection,
        ),
    ],
    interaction_qns=interaction,
)
True

Contrary to expectations, this transition does not conserve spin projection and therefore spin_conservation() returns False:

spin_conservation(
    ingoing_spins=[
        SpinEdgeInput(
            spin_magnitude=incoming_state.particle.spin,
            spin_projection=incoming_state.spin_projection,
        )
    ],
    outgoing_spins=[
        SpinEdgeInput(
            spin_magnitude=outgoing_state1.particle.spin,
            spin_projection=outgoing_state1.spin_projection,
        ),
        SpinEdgeInput(
            spin_magnitude=outgoing_state2.particle.spin,
            spin_projection=outgoing_state2.spin_projection,
        ),
    ],
    interaction_qns=interaction,
)
False

The reason is that AmpForm formulates the HelicityModel with the helicity formalism first and then uses a transformation to get the model in the canonical basis (see formulate_clebsch_gordan_coefficients()). The canonical basis does not conserve helicity (taken to be State.spin_projection).

Modifying StateTransitions#

When checking conservation rules, you may want to modify the properties on the StateTransitions. However, a StateTransition is a FrozenTransition, so it is not possible to modify its interactions and states. The only way around this is to create a new instance with attrs.evolve().

First, we get the instance (in this case one of the InteractionProperties) and substitute its InteractionProperties.l_magnitude:

new_interaction = attrs.evolve(transition.interactions[node_id], l_magnitude=2)
new_interaction
InteractionProperties(
  l_magnitude=2,
  l_projection=None,
  s_magnitude=1.0,
  s_projection=None,
  parity_prefactor=None,
)

We then again use attrs.evolve() to substitute the Transition.interactions of the original StateTransition:

new_interaction_dict = dict(transition.interactions)  # make mutable
new_interaction_dict[node_id] = new_interaction
new_transition = attrs.evolve(transition, interactions=new_interaction_dict)
Hide code cell source
dot = qrules.io.asdot(new_transition, render_node=True)
graphviz.Source(dot)
../../_images/c6d95b5fab1fb8e66b47d2e41de99cbe025d0134a330fb1b30322f5559897bfe.svg