"""Definitions used internally for type hints and signatures.
`qrules` is strictly typed (enforced through :doc:`mypy <mypy:index>`). This
module bundles structures and definitions that don't serve as data containers
but only as type hints. `.EdgeQuantumNumbers` and `.NodeQuantumNumbers` are the
main structures and serve as a bridge between the :mod:`.particle` and the
:mod:`.conservation_rules` module.
"""
from decimal import Decimal
from fractions import Fraction
from functools import total_ordering
from typing import Any, Generator, NewType, Optional, Union
import attr
from attr.validators import instance_of
from qrules._implementers import implement_pretty_repr
def _check_plus_minus(_: Any, __: attr.Attribute, value: Any) -> None:
if not isinstance(value, int):
raise TypeError(
f"Input for {Parity.__name__} has to be of type {int.__name__},"
f" not {type(value).__name__}"
)
if value not in [-1, +1]:
raise ValueError(f"Parity can only be +1 or -1, not {value}")
[docs]@total_ordering
@attr.frozen(eq=False, hash=True, order=False, repr=False)
class Parity:
value: int = attr.ib(validator=[instance_of(int), _check_plus_minus])
def __eq__(self, other: object) -> bool:
if isinstance(other, Parity):
return self.value == other.value
return self.value == other
def __gt__(self, other: Any) -> bool:
if other is None:
return True
return self.value > int(other)
def __int__(self) -> int:
return self.value
def __neg__(self) -> "Parity":
return Parity(-self.value)
def __repr__(self) -> str:
return f"{self.__class__.__name__}({_to_fraction(self.value)})"
def _to_fraction(value: Union[float, int], render_plus: bool = False) -> str:
label = str(Fraction(value))
if render_plus and value > 0:
return f"+{label}"
return label
[docs]@attr.frozen(init=False)
class EdgeQuantumNumbers: # pylint: disable=too-many-instance-attributes
"""Definition of quantum numbers for edges.
This class defines the types that are used in the
:mod:`.conservation_rules`, for instance in
`.additive_quantum_number_rule`. You can also create data classes (see
`attr.s`) with data members that are typed as the data members of
`.EdgeQuantumNumbers` (see for example `.HelicityParityEdgeInput`) and use
them in conservation rules that satisfy the appropriate rule protocol (see
`.ConservationRule`, `.EdgeQNConservationRule`).
"""
pid = NewType("pid", int)
mass = NewType("mass", float)
width = NewType("width", float)
spin_magnitude = NewType("spin_magnitude", float)
spin_projection = NewType("spin_projection", float)
charge = NewType("charge", int)
isospin_magnitude = NewType("isospin_magnitude", float)
isospin_projection = NewType("isospin_projection", float)
strangeness = NewType("strangeness", int)
charmness = NewType("charmness", int)
bottomness = NewType("bottomness", int)
topness = NewType("topness", int)
baryon_number = NewType("baryon_number", int)
electron_lepton_number = NewType("electron_lepton_number", int)
muon_lepton_number = NewType("muon_lepton_number", int)
tau_lepton_number = NewType("tau_lepton_number", int)
parity = NewType("parity", Parity)
c_parity = NewType("c_parity", Parity)
g_parity = NewType("g_parity", Parity)
for edge_qn_name, edge_qn_type in EdgeQuantumNumbers.__dict__.items():
if not edge_qn_name.startswith("__"):
edge_qn_type.__qualname__ = f"EdgeQuantumNumbers.{edge_qn_name}"
edge_qn_type.__module__ = __name__
# for static typing
EdgeQuantumNumber = Union[
EdgeQuantumNumbers.pid,
EdgeQuantumNumbers.mass,
EdgeQuantumNumbers.width,
EdgeQuantumNumbers.spin_magnitude,
EdgeQuantumNumbers.spin_projection,
EdgeQuantumNumbers.charge,
EdgeQuantumNumbers.isospin_magnitude,
EdgeQuantumNumbers.isospin_projection,
EdgeQuantumNumbers.strangeness,
EdgeQuantumNumbers.charmness,
EdgeQuantumNumbers.bottomness,
EdgeQuantumNumbers.topness,
EdgeQuantumNumbers.baryon_number,
EdgeQuantumNumbers.electron_lepton_number,
EdgeQuantumNumbers.muon_lepton_number,
EdgeQuantumNumbers.tau_lepton_number,
EdgeQuantumNumbers.parity,
EdgeQuantumNumbers.c_parity,
EdgeQuantumNumbers.g_parity,
]
[docs]@attr.frozen(init=False)
class NodeQuantumNumbers:
"""Definition of quantum numbers for interaction nodes."""
l_magnitude = NewType("l_magnitude", float)
l_projection = NewType("l_projection", float)
s_magnitude = NewType("s_magnitude", float)
s_projection = NewType("s_projection", float)
parity_prefactor = NewType("parity_prefactor", float)
for node_qn_name, node_qn_type in NodeQuantumNumbers.__dict__.items():
if not node_qn_name.startswith("__"):
node_qn_type.__qualname__ = f"NodeQuantumNumbers.{node_qn_name}"
node_qn_type.__module__ = __name__
# for static typing
NodeQuantumNumber = Union[
NodeQuantumNumbers.l_magnitude,
NodeQuantumNumbers.l_projection,
NodeQuantumNumbers.s_magnitude,
NodeQuantumNumbers.s_projection,
NodeQuantumNumbers.parity_prefactor,
]
def _to_optional_float(optional_float: Optional[float]) -> Optional[float]:
if optional_float is None:
return None
return float(optional_float)
def _to_optional_int(optional_int: Optional[int]) -> Optional[int]:
if optional_int is None:
return None
return int(optional_int)
[docs]@implement_pretty_repr()
@attr.frozen(order=True)
class InteractionProperties:
"""Immutable data structure containing interaction properties.
Interactions are represented by a node on a `.StateTransitionGraph`. This
class represents the properties that are carried collectively by the edges
that this node connects.
Interaction properties are in particular important in the canonical basis
of the helicity formalism. There, the *coupled spin* and angular momentum
of each interaction are used for the Clebsch-Gordan coefficients for each
term in a sequential amplitude.
.. note:: As opposed to `NodeQuantumNumbers`, the `InteractionProperties`
class serves as an interface to the user.
"""
l_magnitude: Optional[int] = attr.ib( # L cannot be half integer
default=None, converter=_to_optional_int
)
l_projection: Optional[int] = attr.ib(
default=None, converter=_to_optional_int
)
s_magnitude: Optional[float] = attr.ib(
default=None, converter=_to_optional_float
)
s_projection: Optional[float] = attr.ib(
default=None, converter=_to_optional_float
)
parity_prefactor: Optional[float] = attr.ib(
default=None, converter=_to_optional_float
)
[docs]def arange(
x_1: float, x_2: float, delta: float = 1.0
) -> Generator[float, None, None]:
current = Decimal(x_1)
while current < x_2:
yield float(current)
current += Decimal(delta)