Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
BinnedObservableId.py
Go to the documentation of this file.
1"""Identifiers for observables evaluated over a finite kinematic bin."""
2
3from dataclasses import dataclass, field
4from typing import Tuple, Union, Any
5
6from pyhyperiso.phyperiso.pyhyperiso import common
7from pyhyperiso.core.Common.SymbolId import ObservableId
8from pyhyperiso.core.Common.Mapper import ObservableMapper
9from pyhyperiso.core.Common.GeneralEnum import Observables
10
11
12@dataclass
14 """Python wrapper around the C++ ``BinnedObservableId`` composite identifier.
15
16 A ``BinnedObservableId`` uniquely identifies a *binned* observable by:
17 - an unbinned observable id (``ObservableId``),
18 - a bin range ``(low, high)`` stored as a pair of doubles.
19
20 This mirrors the C++ struct::
21
22 struct BinnedObservableId {
23 ObservableId s;
24 std::pair<double,double> p;
25 ...
26 };
27
28 Args:
29 s (Union[ObservableId, str]): Unbinned observable identifier.
30 - If a ``str`` is provided, it is converted to ``ObservableId(str)``.
31 p (Tuple[float, float]): Bin range (low, high). Defaults to (0.0, 0.0).
32
33 Attributes:
34 s (ObservableId): Unbinned observable identifier (Python wrapper).
35 p (Tuple[float, float]): Bin range.
36 _cpp_obj (common.BinnedObservableId): Underlying bound C++ object.
37
38 Notes:
39 - ``flha()`` returns a bound C++ ``LhaID``.
40 - ``from_flha()`` reconstructs a binned id from such an ``LhaID``.
41 - Equality/ordering are implemented in C++ (== and <). This wrapper forwards them.
42 """
43
44 s: Union[ObservableId, str] = field(default_factory=lambda: ObservableId("NULL"))
45 p: Tuple[float, float] = (0.0, 0.0)
46 _cpp_obj: common.BinnedObservableId = field(init=False, repr=False)
47
48 def __post_init__(self) -> None:
49 """Normalize inputs and construct the underlying C++ object.
50
51 Raises:
52 TypeError: If ``p`` is not a pair (low, high).
53 """
54
55 if isinstance(self.ss, str):
56 self.ss = ObservableId(self.ss)
57 elif isinstance(self.ss, Observables):
58 self.ss = ObservableMapper.to_id(self.ss)
59 elif not isinstance(self.ss, ObservableId):
60 raise TypeError(f"s must be ObservableId or str, got {type(self.s)}")
61
62 # Normalize p to (float, float)
63 if not (isinstance(self.pp, (tuple, list, set)) and len(self.pp) == 2):
64 raise TypeError("p must be a (low, high) pair")
65 if isinstance(self.pp, set):
66 low, high = sorted(float(value) for value in self.pp)
67 else:
68 low = float(self.pp[0])
69 high = float(self.pp[1])
70
71 self.pp = (low, high)
72
73 s_cpp = self.ss._to_cpp()
74
75 if low == 0.0 and high == 0.0:
76 self._cpp_obj_cpp_obj = common.BinnedObservableId(s_cpp)
77 else:
78 self._cpp_obj_cpp_obj = common.BinnedObservableId(s_cpp, (low, high))
79
80 def flha(self):
81 """Convert this binned id to its FLHA/LHA representation.
82
83 Returns:
84 common.LhaID: Bound C++ ``LhaID`` for the binned observable.
85
86 Raises:
87 RuntimeError: If the mapping from ``ObservableId`` to FLHA is unknown.
88 """
89 return self._cpp_obj_cpp_obj.flha()
90
91 @classmethod
92 def from_flha(cls, lhaid: Any) -> "BinnedObservableId":
93 """Construct a ``BinnedObservableId`` from a binned FLHA/LHA id.
94
95 Args:
96 lhaid: A bound C++ ``LhaID`` or a wrapper exposing ``to_cpp()``.
97
98 Returns:
99 BinnedObservableId: Wrapped instance reconstructed from the id.
100
101 Raises:
102 RuntimeError: If the id cannot be decoded or the mapping is unknown.
103 """
104 lhaid_cpp = lhaid.to_cpp() if hasattr(lhaid, "to_cpp") else lhaid
105 cpp_obj = common.BinnedObservableId.from_flha(lhaid_cpp)
106 return cls.from_cpp(cpp_obj)
107
108 @classmethod
109 def from_cpp(cls, cpp_obj: common.BinnedObservableId) -> "BinnedObservableId":
110 """Wrap an existing bound C++ ``BinnedObservableId`` instance.
111
112 Args:
113 cpp_obj (common.BinnedObservableId): C++ object coming from pybind.
114
115 Returns:
116 BinnedObservableId: Python wrapper around the provided C++ object.
117 """
118 inst = cls.__new__(cls)
119 inst._cpp_obj = cpp_obj
120
121 inst.s = ObservableId(str(cpp_obj.s))
122 inst.p = (float(cpp_obj.p[0]), float(cpp_obj.p[1]))
123 return inst
124
125 def to_cpp(self) -> common.BinnedObservableId:
126 """Return the underlying bound C++ object."""
127 return self._cpp_obj_cpp_obj
128
129 def to_dict(self) -> dict:
130 """Serialize this identifier into a debug-friendly dict."""
131 return {"s": str(self.ss), "p": self.pp}
132
133 def __repr__(self) -> str:
134 """Return a debug representation."""
135 return f"BinnedObservableId(s={self.s!s}, p={self.p})"
136
137 def __str__(self) -> str:
138 """Return a human-readable representation."""
139 return f"{self.s} [{self.p[0]}, {self.p[1]}]"
140
141 def __eq__(self, other: object) -> bool:
142 """Value equality using the underlying C++ implementation."""
143 if not isinstance(other, BinnedObservableId):
144 return NotImplemented
145 return self._cpp_obj_cpp_obj == other._cpp_obj
146
147 def __lt__(self, other: "BinnedObservableId") -> bool:
148 """Strict ordering using the underlying C++ implementation."""
149 if not isinstance(other, BinnedObservableId):
150 return NotImplemented
151 return self._cpp_obj_cpp_obj < other._cpp_obj
152
153 def __hash__(self) -> int:
154 """Hash using the underlying C++ hash (bound __hash__) if available.
155
156 Falls back to hashing (s, p) in Python if needed.
157 """
158 try:
159 return hash(self._cpp_obj_cpp_obj)
160 except TypeError:
161 return hash((str(self.ss), float(self.pp[0]), float(self.pp[1])))
static IdOf< ObservableTag > to_id(Observables e)
Converts an enum value to an IdOf<Tag>.
"BinnedObservableId" from_cpp(cls, common.BinnedObservableId cpp_obj)