Conservation rules

import attr
import graphviz

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

QRules generates StateTransitionGraphs, populates them with quantum numbers (edge properties representing states and nodes properties representing interactions), then checks whether the generated StateTransitionGraphs 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).

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/conservation_17_0.svg../_images/conservation_17_1.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 frozen, so it is not possible to modify its interactions and states. The only way around this is to create a new instance with attr.evolve().

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

new_interaction = attr.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 attr.evolve() to substitute the StateTransition.interactions of the original StateTransition:

new_interaction_dict = dict(transition.interactions)  # make mutable
new_interaction_dict[node_id] = new_interaction
new_transition = attr.evolve(transition, interactions=new_interaction_dict)
dot = qrules.io.asdot(new_transition, render_node=True)
graphviz.Source(dot)
../_images/conservation_28_0.svg