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
3 changes: 3 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def __get_newtypes(some_type: type) -> list:
"NodeType": "typing.TypeVar",
"ParticleWithSpin": ("obj", "qrules.particle.ParticleWithSpin"),
"Path": "pathlib.Path",
"QNTransition": ("obj", "qrules.workflow.QNTransition"),
"qrules.topology.EdgeType": "typing.TypeVar",
"qrules.topology.NodeType": "typing.TypeVar",
"Rule": ("obj", "qrules.argument_handling.Rule"),
Expand Down Expand Up @@ -104,6 +105,7 @@ def __get_newtypes(some_type: type) -> list:
"qrules.solving.GraphElementProperties": "obj",
"qrules.solving.GraphSettings": "obj",
"qrules.transition.StateTransition": "obj",
"qrules.workflow.QNTransition": "obj",
}
author = ""
autodoc_default_options = {
Expand All @@ -124,6 +126,7 @@ def __get_newtypes(some_type: type) -> list:
"GraphElementProperties": "qrules.solving.GraphElementProperties",
"GraphSettings": "qrules.solving.GraphSettings",
"InitialFacts": "qrules.combinatorics.InitialFacts",
"QNTransition": "qrules.workflow.QNTransition",
"StateTransition": "qrules.transition.StateTransition",
}
autodoc_typehints_format = "short"
Expand Down
1 change: 1 addition & 0 deletions docs/usage.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
"maxdepth: 2\n",
"---\n",
"usage/reaction\n",
"usage/qn-transitions\n",
"usage/particle\n",
"usage/visualize\n",
"usage/conservation\n",
Expand Down
265 changes: 265 additions & 0 deletions docs/usage/qn-transitions.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Transitions without spin projections\n",
"\n",
":::{autolink-concat}\n",
":::"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The workflows described in {doc}`/usage/reaction` generate a transition for every allowed combination of **spin projections** of the initial and final state, because a helicity amplitude model requires each of those combinations. If you are only interested in which intermediate states and quantum numbers are allowed — for instance, which $J^{PC}$ resonances can appear in a Dalitz-plot decomposition — the spin projections merely multiply the number of {class}`.QNProblemSet`s that have to be solved. This page shows how to generate transitions directly at the $J^{P(C)}$ level with the {mod}`.workflow` module, which is considerably faster."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
},
"tags": [
"hide-cell"
]
},
"outputs": [],
"source": [
"from IPython.display import Markdown\n",
"\n",
"import qrules.io\n",
"from qrules.quantum_numbers import EdgeQuantumNumbers\n",
"from qrules.workflow import create_qn_problem_sets, find_qn_transitions\n",
"\n",
"PDG = qrules.load_pdg()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Problem sets without spin projections"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As an example, take the reaction $J/\\psi \\to \\gamma\\pi^0\\pi^0$ with two $f_0$ resonances as allowed intermediate states. By default, {func}`.create_qn_problem_sets` expands the initial and final state over all combinations of their allowed spin projections:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"expanded = create_qn_problem_sets(\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",
"sum(len(problems) for problems in expanded.problem_sets.values())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The $J/\\psi$ has three allowed spin projections and the photon has two, so every problem set appears in six spin-projection combinations. With :code:`spin_projections=False`, this Cartesian expansion is skipped altogether and the problem sets only differ in decay topology and interaction types:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"qn_problem_sets = create_qn_problem_sets(\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",
" spin_projections=False,\n",
")\n",
"sum(len(problems) for problems in qn_problem_sets.problem_sets.values())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The resulting problem sets contain no {attr}`~.EdgeQuantumNumbers.spin_projection`, {attr}`~.NodeQuantumNumbers.l_projection`, or {attr}`~.NodeQuantumNumbers.s_projection` quantum numbers at all. They are identical to what {func}`.strip_spin_projections` produces from the expanded collection, but without ever generating the expansion."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Solve at the quantum-number level"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"{func}`.find_qn_transitions` solves these problem sets purely at the quantum-number level: no particle database is consulted for the intermediate states. It returns {obj}`.QNTransition`s, whose states and interactions are property maps of quantum numbers instead of {class}`.State` and {class}`.InteractionProperties` objects."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"qn_transitions = find_qn_transitions(qn_problem_sets)\n",
"len(qn_transitions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The transitions differ only in the quantum numbers of the intermediate state and the $LS$-couplings of the interaction nodes. They can be visualized with {func}`.asdot`, just like ordinary transitions:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"source = qrules.io.asmermaid(qn_transitions[0], render_node=True, markdown=True)\n",
"Markdown(source)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Since the property maps can be inspected directly, it is easy to summarize for example the allowed $J^{PC}$ quantum numbers of the intermediate state:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"{\n",
" (\n",
" state[EdgeQuantumNumbers.spin_magnitude],\n",
" state[EdgeQuantumNumbers.parity],\n",
" state[EdgeQuantumNumbers.c_parity],\n",
" )\n",
" for transition in qn_transitions\n",
" for state in transition.intermediate_states.values()\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Fewer combinatorics for larger reactions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The spin-projection expansion multiplies the number of problem sets by $\\prod_i(2s_i+1)$ over all initial and final states $i$ (with the exception of massless states, which have no $0$ projection). Skipping it therefore matters most for many-body final states with spin. Take $J/\\psi \\to p\\bar p\\pi^0\\pi^0$, where the expansion factor is $3 \\times 2 \\times 2 = 12$:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"expanded = create_qn_problem_sets(\n",
" initial_state=[\"J/psi(1S)\"],\n",
" final_state=[\"p\", \"p~\", \"pi0\", \"pi0\"],\n",
" particle_db=PDG,\n",
" allowed_intermediate_particles=[\"N(1440)\"],\n",
")\n",
"sum(len(problems) for problems in expanded.problem_sets.values())"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"qn_problem_sets = create_qn_problem_sets(\n",
" initial_state=[\"J/psi(1S)\"],\n",
" final_state=[\"p\", \"p~\", \"pi0\", \"pi0\"],\n",
" particle_db=PDG,\n",
" allowed_intermediate_particles=[\"N(1440)\"],\n",
" spin_projections=False,\n",
")\n",
"sum(len(problems) for problems in qn_problem_sets.problem_sets.values())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The projection-free problem sets are not only fewer, but each of them is also cheaper to solve, because the spin projections do not appear as variables in the constraint problem:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"qn_transitions = find_qn_transitions(qn_problem_sets)\n",
"len(qn_transitions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
":::{seealso}\n",
"To generate particle-level transitions *with* spin projections — as required for a helicity amplitude model — use {func}`.find_solutions` or {func}`.generate_transitions` as described in {doc}`/usage/reaction`. If the problem sets have already been created with spin projections, they can still be reduced afterwards with {func}`.strip_spin_projections`.\n",
":::"
]
}
],
"metadata": {
"colab": {
"toc_visible": true
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.12"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
14 changes: 14 additions & 0 deletions src/qrules/combinatorics.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,27 @@ def create_initial_facts(
initial_state: Sequence[StateDefinitionInput],
final_state: Sequence[StateDefinitionInput],
particle_db: ParticleCollection,
expand_spin_projections: bool = True,
) -> list[InitialFacts]:
"""Attach the initial and final states to the external edges of a `.Topology`.

By default, one `.InitialFacts` is created for every combination of allowed spin
projections of the initial and final state. With
:code:`expand_spin_projections=False`, this Cartesian expansion is skipped and a
single `.InitialFacts` without spin projections is returned, for solving at the
:math:`J^{P(C)}` level (see `.strip_spin_projections`).
"""
states = __create_states_with_spin_projections(
list(topology.incoming_edge_ids) + list(topology.outgoing_edge_ids),
list(map(as_state_definition, initial_state))
+ list(map(as_state_definition, final_state)),
particle_db,
)
if not expand_spin_projections:
projection_free_states = {
edge_id: (particle_db[name], None) for edge_id, (name, _) in states.items()
}
return [MutableTransition(topology, projection_free_states)] # type: ignore[arg-type]
spin_states = __generate_spin_combinations(states, particle_db)
return [MutableTransition(topology, states) for states in spin_states]

Expand Down
Loading
Loading