Visualize solutions#

The io module allows you to convert MutableTransition, Topology instances, and ProblemSets to DOT language with asdot(). You can visualize its output with third-party libraries, such as Graphviz. This is particularly useful after running find_solutions(), which produces a ReactionInfo object with a list of MutableTransition instances (see Generate transitions).

Topologies#

First of all, here are is an example of how to visualize a group of Topology instances. We use create_isobar_topologies() and create_n_body_topology() to create a few standard topologies.

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

import qrules
from qrules.particle import Spin
from qrules.topology import create_isobar_topologies, create_n_body_topology
from qrules.transition import State
topology = create_n_body_topology(2, 4)
graphviz.Source(qrules.io.asdot(topology, render_initial_state_id=True))
../../_images/fde1d867e8ea71d86f44f28aa57dd3f21a0163db17447e6ed67d29fcb8a3d27e.svg

Note the IDs of the nodes is also rendered if there is more than node:

topologies = create_isobar_topologies(4)
graphviz.Source(qrules.io.asdot(topologies))
../../_images/61a2589985401a6892b71ecc3a98085016762a2206bb4c5848453fa854cb2b59.svg

This can be turned on or off with the arguments of asdot():

topologies = create_isobar_topologies(3)
graphviz.Source(qrules.io.asdot(topologies, render_node=False))
../../_images/3412df546f05b64af047fa81bbc9c540ebcacf00d855bd9c807ca11feb7cf405.svg

asdot() provides other options as well:

topologies = create_isobar_topologies(5)
dot = qrules.io.asdot(
    topologies[0],
    render_final_state_id=False,
    render_resonance_id=True,
    render_node=False,
)
display(graphviz.Source(dot))
../../_images/c2108fa9a892b20b51867851cf6c74e9ed763d6e68624700a14cbd6c41b20a93.svg

ProblemSets#

As noted in Generate transitions, the StateTransitionManager provides more control than the façade function generate_transitions(). One advantages, is that the StateTransitionManager first generates a set of ProblemSets with create_problem_sets() that you can further configure if you wish.

from qrules.settings import InteractionType

stm = qrules.StateTransitionManager(
    initial_state=["J/psi(1S)"],
    final_state=["K0", "Sigma+", "p~"],
    formalism="canonical-helicity",
)
stm.set_allowed_interaction_types([InteractionType.STRONG, InteractionType.EM])
problem_sets = stm.create_problem_sets()

Note that the output of create_problem_sets() is a dict with float values as keys (representing the interaction strength) and lists of ProblemSets as values.

sorted(problem_sets, reverse=True)
[3600.0, 60.0, 1.0]
problem_set = problem_sets[60.0][0]
dot = qrules.io.asdot(problem_set, render_node=True)
graphviz.Source(dot)
../../_images/42ca155ee9c9b8f5f90c26a62f21ecd1dc0873ba4fb04f723103c6b5634713f8.svg

Quantum number solutions#

As noted in 3. Find solutions, a ProblemSet can be fed to StateTransitionManager.find_solutions() directly to get a ReactionInfo object. ReactionInfo is a final result that consists of Particles, but in the intermediate steps, QRules works with sets of quantum numbers. One can inspect these intermediate generated quantum numbers by using find_quantum_number_transitions() and inspecting is output. Note that the resulting object is again a dict with strengths as keys and a list of solution as values.

qn_solutions = stm.find_quantum_number_transitions(problem_sets)
{strength: len(values) for strength, values in qn_solutions.items()}
{3600.0: 36, 60.0: 72, 1.0: 36}

The list of solutions consist of a tuple of a QNProblemSet (compare ProblemSets) and a QNResult:

strong_qn_solutions = qn_solutions[3600.0]
qn_problem_set, qn_result = strong_qn_solutions[0]
Hide code cell source
dot = qrules.io.asdot(qn_problem_set, render_node=True)
graphviz.Source(dot)
../../_images/ed156af976cbb0f98e902cfef17adda2b7c4592af8a8bea292cb128595496141.svg
Hide code cell source
dot = qrules.io.asdot(qn_result, render_node=True)
graphviz.Source(dot)
../../_images/78dc5f11685c561833724b20c22a7f9bb21f1825bb3c9a3827683b54d190b7ef.svg

StateTransitions#

After finding the Quantum number solutions, QRules finds Particle definitions that match these quantum numbers. All these steps are hidden in the convenience functions StateTransitionManager.find_solutions() and generate_transitions(). In the following, we’ll visualize the allowed transitions for the decay \(\psi' \to \gamma\eta\eta\) as an example.

import qrules

reaction = qrules.generate_transitions(
    initial_state="psi(2S)",
    final_state=["gamma", "eta", "eta"],
    allowed_interaction_types="EM",
)

As noted in 3. Find solutions, the transitions contain all spin projection combinations (which is necessary for the ampform package). It is possible to convert all these solutions to DOT language with asdot(). To avoid visualizing all solutions, we just take a subset of the transitions:

dot = qrules.io.asdot(reaction.transitions[::50][:3])  # just some selection

This str of DOT language for the list of MutableTransition instances can then be visualized with a third-party library, for instance, with graphviz.Source:

import graphviz

dot = qrules.io.asdot(
    reaction.transitions[::50][:3], render_node=False
)  # just some selection
graphviz.Source(dot)
../../_images/941cd153e9f2b5b927090abf20054cbb188fe8f7534a3530c5d91dfa7789efc8.svg

You can also serialize the DOT string to file with io.write(). The file extension for a DOT file is .gv:

qrules.io.write(reaction, "decay_topologies_with_spin.gv")

Collapse graphs#

Since this list of all possible spin projections transitions is rather long, it is often useful to use strip_spin=True or collapse_graphs=True to bundle comparable graphs. First, strip_spin=True allows one collapse (ignore) the spin projections (we again show a selection only):

dot = qrules.io.asdot(reaction.transitions[:3], strip_spin=True)
graphviz.Source(dot)
../../_images/f7dc60d4308b06a24eb27cde4aa9d67716fa33fdae420a3f8281ef534a2d019a.svg

or, with stripped node properties:

dot = qrules.io.asdot(reaction.transitions[:3], strip_spin=True, render_node=True)
graphviz.Source(dot)
../../_images/e38278546a79a8018b3efeccb393aa2bb0538842965f9dc997aaedb63ce4b7c1.svg

Note

By default, asdot() renders edge IDs, because they represent the (final) state IDs as well. In the example above, we switched this off.

If that list is still too much, there is collapse_graphs=True, which bundles all graphs with the same final state groupings:

dot = qrules.io.asdot(reaction, collapse_graphs=True, render_node=False)
graphviz.Source(dot)
../../_images/494c311ce26954c002e36b42f9b58391c947502268d44fe1d6992e1ccdb0fae2.svg

Other state renderings#

The convert() method makes it possible to convert the types of its states. This for instance allows us to only render the spin states on in a Transition:

spin_transitions = sorted({
    t.convert(lambda s: Spin(s.particle.spin, s.spin_projection))
    for t in reaction.transitions
})
some_selection = spin_transitions[::67][:3]
dot = qrules.io.asdot(some_selection, render_node=True)
graphviz.Source(dot)
../../_images/82c05c92eac40c1f52802e4a85930038fa97bc8dd690ce9ae0a4bdea03cc6483.svg

Or any other properties of a State:

Hide code cell source
def render_mass(state: State, digits: int = 3) -> str:
    mass = round(state.particle.mass, digits)
    width = round(state.particle.width, digits)
    if width == 0:
        return str(mass)
    return f"{mass}±{width}"


mass_transitions = sorted({
    t.convert(
        state_converter=render_mass,
        interaction_converter=lambda _: None,
    )
    for t in reaction.transitions
})
dot = qrules.io.asdot(mass_transitions[::10])
graphviz.Source(dot)
../../_images/7067b5603d1054cca9b4548965d1a81330d1868572872e3f6c5893325f29a4f9.svg

Styling#

The asdot() function also takes Graphviz attributes. These can be used to modify the layout of the whole figure. Examples are the size, color, and fontcolor. Edges and nodes can be styled with edge_style and node_style respectively:

dot = qrules.io.asdot(
    reaction.transitions[0],
    render_node=True,
    size=12,
    bgcolor="white",
    edge_style={
        "color": "red",
        "arrowhead": "open",
        "fontcolor": "blue",
        "fontsize": 25,
    },
    node_style={
        "color": "gray",
        "penwidth": 2,
        "shape": "ellipse",
        "style": "dashed",
    },
)
display(graphviz.Source(dot))
../../_images/e8df3d8eb37c61d4af3f94fc013cc410e79c2f764e293b1c4b2e171bdff93fae.svg