Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 149 additions & 1 deletion docs/usage/qn-transitions.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@
"\n",
"import qrules.io\n",
"from qrules.quantum_numbers import EdgeQuantumNumbers\n",
"from qrules.workflow import create_qn_problem_sets, find_qn_transitions\n",
"from qrules.workflow import (\n",
" create_qn_problem_sets,\n",
" find_qn_transitions,\n",
" generate_qn_transitions,\n",
")\n",
"\n",
"PDG = qrules.load_pdg()"
]
Expand Down Expand Up @@ -163,6 +167,52 @@
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Reaction-level interface"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The staged workflow above offers full control over the problem sets, but the default use-case is covered by a single call to {func}`.generate_qn_transitions`, the quantum-number-level counterpart of {func}`.generate_transitions`. It returns a {class}`.QNReactionInfo`, which resolves the initial and final states to {class}`.Particle` instances — they are fully determined by their PID — while the intermediate states remain quantum-number property maps:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"reaction = generate_qn_transitions(\n",
" initial_state=\"J/psi(1S)\",\n",
" final_state=[\"gamma\", \"pi0\", \"pi0\"],\n",
" particle_db=PDG,\n",
" allowed_intermediate_particles=[\"f(0)(980)\", \"f(0)(1500)\"],\n",
")\n",
"{i: particle.name for i, particle in reaction.final_state.items()}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The rendered transitions now label the initial and final states by particle name:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"source = qrules.io.asmermaid(reaction.transitions[0], render_node=True, markdown=True)\n",
"Markdown(source)"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -228,6 +278,104 @@
"len(qn_transitions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Many-body reactions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Solving at the quantum-number level makes reactions with four- and even five-body final states feasible, for which the workflows *with* spin projections take impractically long (see [ComPWA/qrules#27](https://github.com/ComPWA/qrules/issues/27)). Take $J/\\psi \\to K^+K^-\\pi^+\\pi^-$ with $\\phi(1020)$ and $\\rho(770)$ resonances, where {func}`.create_qn_problem_sets`' :code:`final_state_groupings` argument limits the subsystems to $\\phi\\to K^+K^-$ and $\\rho^0\\to\\pi^+\\pi^-$:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"reaction_4body = generate_qn_transitions(\n",
" initial_state=\"J/psi(1S)\",\n",
" final_state=[\"K+\", \"K-\", \"pi+\", \"pi-\"],\n",
" particle_db=PDG,\n",
" allowed_intermediate_particles=[\"phi(1020)\", \"rho(770)\"],\n",
" allowed_interaction_types=\"strong\",\n",
" final_state_groupings=[[[\"K+\", \"K-\"], [\"pi+\", \"pi-\"]]],\n",
")\n",
"len(reaction_4body.transitions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The transitions can be summarized by collapsing them per decay topology with {func}`.asdot`'s :code:`collapse=\"topology\"` option, where the intermediate edges then list the allowed states in $I^G(J^{PC})$ notation:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Markdown(qrules.io.asmermaid(reaction_4body, collapse=\"topology\", markdown=True))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and the intermediate states are limited to the following charges, isospins, spins, and parities:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"{\n",
" (\n",
" state[EdgeQuantumNumbers.charge],\n",
" state[EdgeQuantumNumbers.isospin_magnitude],\n",
" state[EdgeQuantumNumbers.spin_magnitude],\n",
" state[EdgeQuantumNumbers.parity],\n",
" )\n",
" for transition in reaction_4body.transitions\n",
" for state in transition.intermediate_states.values()\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Even a five-body final state remains tractable — here with an additional $\\pi^0$, which lets the number of allowed decay topologies and quantum-number combinations grow substantially:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"reaction_5body = generate_qn_transitions(\n",
" initial_state=\"J/psi(1S)\",\n",
" final_state=[\"K+\", \"K-\", \"pi+\", \"pi-\", \"pi0\"],\n",
" particle_db=PDG,\n",
" allowed_intermediate_particles=[\"phi(1020)\", \"rho(770)\"],\n",
" allowed_interaction_types=\"strong\",\n",
" final_state_groupings=[[[\"K+\", \"K-\"], [\"pi+\", \"pi-\"]]],\n",
")\n",
"f\"{len(reaction_5body.transitions):,} transitions over {len(reaction_5body.group_by_topology())} topologies\""
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
21 changes: 3 additions & 18 deletions src/qrules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
InitialFacts,
StateDefinitionInput,
create_initial_facts,
to_state_definitions,
)
from qrules.combinatorics import StateDefinition as StateDefinition
from qrules.conservation_rules import (
Expand Down Expand Up @@ -72,8 +73,6 @@
if TYPE_CHECKING:
from collections.abc import Iterable, Sequence

from typing_extensions import TypeIs


def check_reaction_violations( # ruff: ignore[complex-structure, too-many-positional-arguments]
initial_state: StateDefinitionInput | Sequence[StateDefinitionInput],
Expand Down Expand Up @@ -120,7 +119,7 @@ def check_reaction_violations( # ruff: ignore[complex-structure, too-many-posit

.. seealso:: :ref:`usage:Check allowed reactions`
"""
initial_state_definitions = _to_state_definitions(initial_state)
initial_state_definitions = to_state_definitions(initial_state)

if particle_db is None:
particle_db = load_pdg()
Expand Down Expand Up @@ -356,7 +355,7 @@ def generate_transitions( # ruff: ignore[too-many-positional-arguments]
>>> len(reaction.group_by_topology())
3
"""
initial_state_definitions = _to_state_definitions(initial_state)
initial_state_definitions = to_state_definitions(initial_state)
stm = StateTransitionManager(
initial_state=initial_state_definitions,
final_state=final_state,
Expand All @@ -382,20 +381,6 @@ def generate_transitions( # ruff: ignore[too-many-positional-arguments]
return stm.find_solutions(problem_sets)


def _to_state_definitions(
state: StateDefinitionInput | Sequence[StateDefinitionInput], /
) -> list[StateDefinitionInput]:
if _is_state_definition_input(state):
return [state]
return list(state)


def _is_state_definition_input(value: object, /) -> TypeIs[StateDefinitionInput]:
return isinstance(value, str) or (
isinstance(value, tuple) and len(value) == 2 and isinstance(value[0], str)
)


def load_default_particles() -> ParticleCollection:
"""Load the default particle list that comes with `qrules`.

Expand Down
15 changes: 15 additions & 0 deletions src/qrules/combinatorics.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ def as_state_definition(
raise ValueError(msg)


def to_state_definitions(
state: StateDefinitionInput | Sequence[StateDefinitionInput], /
) -> list[StateDefinitionInput]:
"""Wrap a single state definition in a `list`, or copy a sequence of them."""
if _is_state_definition_input(state):
return [state]
return list(state)


def _is_state_definition_input(value: object, /) -> TypeIs[StateDefinitionInput]:
return isinstance(value, str) or (
isinstance(value, tuple) and len(value) == 2 and isinstance(value[0], str)
)


class _KinematicRepresentation: # ruff: ignore[eq-without-hash]
def __init__(
self,
Expand Down
4 changes: 3 additions & 1 deletion src/qrules/io/_dot.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ def _create_preface(self) -> list[str]:
]

def _render(self, obj: Any) -> list[str]:
from qrules.workflow import QNReactionInfo # ruff: ignore[import-outside-top-level]

if isinstance(obj, QNResult):
obj = obj.solutions
if isinstance(obj, ReactionInfo):
if isinstance(obj, (QNReactionInfo, ReactionInfo)):
obj = obj.transitions
if isinstance(obj, abc.Iterable):
return self._render_multiple_transitions(obj)
Expand Down
Loading
Loading