Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .binder/runtime.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
python-3.12
python-3.13
4 changes: 3 additions & 1 deletion .github/release-drafter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ categories:
- title: 📝 Documentation
label: 📝 Docs
- title: 🔨 Maintenance
label: 🔨 Maintenance
labels:
- 🔨 Maintenance
- ⬆️ Lock
- title: 🖱️ Developer Experience
label: 🖱️ DX

Expand Down
16 changes: 8 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repos:
- id: check-useless-excludes

- repo: https://github.com/ComPWA/policy
rev: 0.7.1
rev: 0.7.3
hooks:
- id: check-dev-files
args:
Expand Down Expand Up @@ -62,9 +62,9 @@ repos:
metadata.vscode
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.8
rev: v0.14.0
hooks:
- id: ruff
- id: ruff-check
args: [--fix]
types_or: [python, pyi, jupyter]
- id: ruff-format
Expand Down Expand Up @@ -104,14 +104,14 @@ repos:
- id: taplo-format

- repo: https://github.com/pappasam/toml-sort
rev: v0.24.2
rev: v0.24.3
hooks:
- id: toml-sort
args:
- --in-place

- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.33.2
rev: 0.34.1
hooks:
- id: check-jsonschema
name: Check CITATION.cff
Expand All @@ -129,7 +129,7 @@ repos:
- id: cspell

- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: 3.2.1
rev: 3.4.0
hooks:
- id: editorconfig-checker
name: editorconfig
Expand All @@ -150,11 +150,11 @@ repos:
- python

- repo: https://github.com/ComPWA/pyright-pre-commit
rev: v1.1.403
rev: v1.1.406
hooks:
- id: pyright

- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.8.8
rev: 0.9.2
hooks:
- id: uv-lock
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.12
3.13
2 changes: 1 addition & 1 deletion .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: 2
build:
os: ubuntu-24.04
tools:
python: "3.12"
python: "3.13"
commands:
- |-
export PIXI_HOME=$READTHEDOCS_VIRTUALENV_PATH
Expand Down
22 changes: 19 additions & 3 deletions docs/usage/reaction.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,24 @@
"outputs": [],
"source": [
"stm.set_allowed_intermediate_particles(r\"f\\([02]\\)\", regex=True)\n",
"reaction = stm.find_solutions(problem_sets)\n",
"assert len(reaction.get_intermediate_particles().names) == 13"
"reaction = stm.find_solutions(problem_sets)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
},
"tags": [
"remove-cell"
]
},
"outputs": [],
"source": [
"n_resonances = len(reaction.get_intermediate_particles())\n",
"assert n_resonances == 14, n_resonances"
]
},
{
Expand Down Expand Up @@ -604,7 +620,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.19"
"version": "3.12.11"
}
},
"nbformat": 4,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ uv run \
help = "Run all tests on a specific Python version"

[[tool.poe.tasks.test-py.args]]
default = "3.12"
default = "3.13"
help = "Selected Python version"
name = "version"
positional = true
Expand Down
17 changes: 17 additions & 0 deletions src/qrules/_attrs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from __future__ import annotations

from fractions import Fraction
from typing import SupportsFloat

from qrules.quantum_numbers import Parity


def to_fraction(value: SupportsFloat) -> Fraction:
float_value = float(value)
if float_value == -0.0:
float_value = 0.0
return Fraction(float_value)


def to_parity(value: Parity | int) -> Parity:
return Parity(value)
59 changes: 29 additions & 30 deletions src/qrules/conservation_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
from attrs import define, field, frozen
from attrs.converters import optional

from qrules._attrs import to_fraction, to_parity
from qrules.quantum_numbers import EdgeQuantumNumbers as EdgeQN
from qrules.quantum_numbers import NodeQuantumNumbers as NodeQN
from qrules.quantum_numbers import arange
Expand Down Expand Up @@ -186,9 +187,9 @@ def parity_conservation(

@frozen
class HelicityParityEdgeInput:
parity: EdgeQN.parity = field(converter=EdgeQN.parity)
spin_magnitude: EdgeQN.spin_magnitude = field(converter=EdgeQN.spin_magnitude)
spin_projection: EdgeQN.spin_projection = field(converter=EdgeQN.spin_projection)
parity: EdgeQN.parity = field(converter=to_parity)
spin_magnitude: EdgeQN.spin_magnitude = field(converter=to_fraction)
spin_projection: EdgeQN.spin_projection = field(converter=to_fraction)


def parity_conservation_helicity(
Expand Down Expand Up @@ -229,16 +230,18 @@ def parity_conservation_helicity(

@frozen
class CParityEdgeInput:
spin_magnitude: EdgeQN.spin_magnitude = field(converter=EdgeQN.spin_magnitude)
pid: EdgeQN.pid = field(converter=EdgeQN.pid)
c_parity: Optional[EdgeQN.c_parity] = field(converter=EdgeQN.c_parity, default=None)
spin_magnitude: EdgeQN.spin_magnitude = field(converter=to_fraction)
pid: EdgeQN.pid = field(converter=int)
c_parity: Optional[EdgeQN.c_parity] = field(
converter=optional(to_parity), default=None
)


@frozen
class CParityNodeInput:
# These converters currently do not do anything, as "NewType"s do not have constructors
l_magnitude: NodeQN.l_magnitude = field(converter=NodeQN.l_magnitude)
s_magnitude: NodeQN.s_magnitude = field(converter=NodeQN.s_magnitude)
l_magnitude: NodeQN.l_magnitude = field(converter=to_fraction)
s_magnitude: NodeQN.s_magnitude = field(converter=to_fraction)


def c_parity_conservation(
Expand Down Expand Up @@ -284,18 +287,18 @@ def _get_c_parity_multiparticle(

@frozen
class GParityEdgeInput:
isospin_magnitude: EdgeQN.isospin_magnitude = field(
converter=EdgeQN.isospin_magnitude
isospin_magnitude: EdgeQN.isospin_magnitude = field(converter=to_fraction)
spin_magnitude: EdgeQN.spin_magnitude = field(converter=to_fraction)
pid: EdgeQN.pid = field(converter=int)
g_parity: Optional[EdgeQN.g_parity] = field(
converter=optional(to_parity), default=None
)
spin_magnitude: EdgeQN.spin_magnitude = field(converter=EdgeQN.spin_magnitude)
pid: EdgeQN.pid = field(converter=EdgeQN.pid)
g_parity: Optional[EdgeQN.g_parity] = field(converter=EdgeQN.g_parity, default=None)


@frozen
class GParityNodeInput:
l_magnitude: NodeQN.l_magnitude = field(converter=NodeQN.l_magnitude)
s_magnitude: NodeQN.s_magnitude = field(converter=NodeQN.s_magnitude)
l_magnitude: NodeQN.l_magnitude = field(converter=to_fraction)
s_magnitude: NodeQN.s_magnitude = field(converter=to_fraction)


def g_parity_conservation( # noqa: C901
Expand Down Expand Up @@ -375,9 +378,9 @@ def check_g_parity_isobar(

@frozen
class IdenticalParticleSymmetryOutEdgeInput:
spin_magnitude: EdgeQN.spin_magnitude = field(converter=EdgeQN.spin_magnitude)
spin_projection: EdgeQN.spin_projection = field(converter=EdgeQN.spin_projection)
pid: EdgeQN.pid = field(converter=EdgeQN.pid)
spin_magnitude: EdgeQN.spin_magnitude = field(converter=to_fraction)
spin_projection: EdgeQN.spin_projection = field(converter=to_fraction)
pid: EdgeQN.pid = field(converter=int)


def identical_particle_symmetrization(
Expand Down Expand Up @@ -455,16 +458,16 @@ def _is_clebsch_gordan_coefficient_zero(

@frozen
class SpinNodeInput:
l_magnitude: NodeQN.l_magnitude = field(converter=NodeQN.l_magnitude)
l_projection: NodeQN.l_projection = field(converter=NodeQN.l_projection)
s_magnitude: NodeQN.s_magnitude = field(converter=NodeQN.s_magnitude)
s_projection: NodeQN.s_projection = field(converter=NodeQN.s_projection)
l_magnitude: NodeQN.l_magnitude = field(converter=to_fraction)
l_projection: NodeQN.l_projection = field(converter=to_fraction)
s_magnitude: NodeQN.s_magnitude = field(converter=to_fraction)
s_projection: NodeQN.s_projection = field(converter=to_fraction)


@frozen
class SpinMagnitudeNodeInput:
l_magnitude: NodeQN.l_magnitude = field(converter=NodeQN.l_magnitude)
s_magnitude: NodeQN.s_magnitude = field(converter=NodeQN.s_magnitude)
l_magnitude: NodeQN.l_magnitude = field(converter=to_fraction)
s_magnitude: NodeQN.s_magnitude = field(converter=to_fraction)


def ls_spin_validity(spin_input: SpinNodeInput) -> bool:
Expand Down Expand Up @@ -585,12 +588,8 @@ def __spin_couplings(spin1: _Spin, spin2: _Spin) -> set[_Spin]:

@define
class IsoSpinEdgeInput:
isospin_magnitude: EdgeQN.isospin_magnitude = field(
converter=EdgeQN.isospin_magnitude
)
isospin_projection: EdgeQN.isospin_projection = field(
converter=EdgeQN.isospin_projection
)
isospin_magnitude: EdgeQN.isospin_magnitude = field(converter=to_fraction)
isospin_projection: EdgeQN.isospin_projection = field(converter=to_fraction)


def _check_spin_valid(magnitude: Fraction, projection: Fraction) -> bool:
Expand Down
24 changes: 7 additions & 17 deletions src/qrules/particle.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
from fractions import Fraction
from functools import total_ordering
from math import copysign
from typing import TYPE_CHECKING, Any, Callable, SupportsFloat
from typing import TYPE_CHECKING, Any, Callable

import attrs
from attrs import field, frozen
from attrs.converters import optional
from attrs.validators import instance_of

from qrules._attrs import to_fraction, to_parity
from qrules.conservation_rules import GellMannNishijimaInput, gellmann_nishijima
from qrules.quantum_numbers import Parity, _float_as_signed_str

Expand All @@ -44,13 +45,6 @@
_LOGGER = logging.getLogger(__name__)


def _to_fraction(value: SupportsFloat) -> Fraction:
float_value = float(value)
if float_value == -0.0:
float_value = 0.0
return Fraction(float_value)


def _validate_fraction_for_spin(
instance: Spin,
attribute: Attribute, # noqa: ARG001
Expand Down Expand Up @@ -82,11 +76,11 @@ class Spin: # noqa: PLW1641
"""Safe, immutable data container for spin **with projection**."""

magnitude: Fraction = field(
converter=_to_fraction,
converter=to_fraction,
validator=_validate_fraction_for_spin,
)
projection: Fraction = field(
converter=_to_fraction,
converter=to_fraction,
validator=_validate_fraction_for_spin,
)

Expand Down Expand Up @@ -125,10 +119,6 @@ def _render_fraction(fraction: Fraction, plusminus: bool = False) -> str:
return str(fraction)


def _to_parity(value: Parity | int) -> Parity:
return Parity(int(value))


def _to_spin(value: Spin | tuple[Fraction, Fraction] | tuple[float, float]) -> Spin:
if isinstance(value, tuple):
return Spin(*value)
Expand Down Expand Up @@ -173,9 +163,9 @@ class Particle:
electron_lepton_number: int = field(default=0, validator=instance_of(int))
muon_lepton_number: int = field(default=0, validator=instance_of(int))
tau_lepton_number: int = field(default=0, validator=instance_of(int))
parity: Parity | None = field(converter=optional(_to_parity), default=None)
c_parity: Parity | None = field(converter=optional(_to_parity), default=None)
g_parity: Parity | None = field(converter=optional(_to_parity), default=None)
parity: Parity | None = field(converter=optional(to_parity), default=None)
c_parity: Parity | None = field(converter=optional(to_parity), default=None)
g_parity: Parity | None = field(converter=optional(to_parity), default=None)

def __attrs_post_init__(self) -> None:
if self.isospin is not None and not gellmann_nishijima(
Expand Down
6 changes: 4 additions & 2 deletions src/qrules/quantum_numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
from collections.abc import Generator


def _to_parity(value: int) -> Literal[-1, 1]:
def _to_parity_int(value: int | Parity) -> Literal[-1, 1]:
if isinstance(value, Parity):
return value.value
if not isinstance(value, int):
msg = f"Parity must be an integer, not {type(value)}"
raise TypeError(msg)
Expand All @@ -35,7 +37,7 @@ def _to_parity(value: int) -> Literal[-1, 1]:
@total_ordering
@frozen(eq=False, hash=True, order=False, repr=False)
class Parity: # noqa: PLW1641
value: Literal[-1, 1] = field(converter=_to_parity)
value: Literal[-1, 1] = field(converter=_to_parity_int)

def __eq__(self, other: object) -> bool:
if isinstance(other, Parity):
Expand Down
Loading