Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
MCResult.py
Go to the documentation of this file.
1"""Monte-Carlo result wrappers for the statistic module.
2
3The C++ statistic layer can sample nuisance parameters, propagate them through
4the observable model, summarize the resulting observable samples and estimate an
5observable covariance matrix. This module exposes Python dataclasses mirroring
6those C++ result structures.
7"""
8
9from __future__ import annotations
10
11from dataclasses import dataclass, field
12from typing import Dict, List, Mapping, Sequence
13
14from pyhyperiso.phyperiso.pyhyperiso import statistic as _cpp_stat
15from pyhyperiso.core.Common.BinnedObservableId import BinnedObservableId
16from pyhyperiso.core.Common.ParamId import ParamId
17from pyhyperiso.core.Math.RealMatrix import Matrix
18from pyhyperiso.core.Statistic.GaussianSummary import GaussianSummary
19
20
21ObsSample = Dict[BinnedObservableId, float]
22"""One Monte-Carlo observable vector indexed by binned observable id."""
23
24ObsSamples = List[ObsSample]
25"""Collection of Monte-Carlo observable vectors."""
26
27NuisanceSample = Dict[ParamId, float]
28"""One sampled nuisance-parameter vector indexed by parameter id."""
29
30NuisanceSamples = List[NuisanceSample]
31"""Collection of nuisance-parameter samples."""
32
33
34def _require(value, typ, name: str):
35 """Validate a wrapper argument type and return the original value."""
36 if not isinstance(value, typ):
37 raise TypeError(f"{name} must be {typ.__name__}, received {type(value)!r}.")
38 return value
39
40
41def _cpp_binned_observable_id(obs: BinnedObservableId):
42 """Convert a Python binned observable id to C++."""
43 return _require(obs, BinnedObservableId, "BinnedObservableId").to_cpp()
44
45
46def _cpp_param_id(pid: ParamId):
47 """Convert a Python parameter id to C++."""
48 return _require(pid, ParamId, "ParamId").to_cpp()
49
50
51def _obs_sample_from_cpp(row) -> ObsSample:
52 """Convert one C++ observable sample row to a Python mapping."""
53 return {BinnedObservableId.from_cpp(k): float(v) for k, v in dict(row).items()}
54
55
56def _obs_sample_to_cpp(row: Mapping[BinnedObservableId, float]):
57 """Convert one Python observable sample row to a C++ mapping."""
58 return {_cpp_binned_observable_id(k): float(v) for k, v in row.items()}
59
60
61def _nuisance_sample_from_cpp(row) -> NuisanceSample:
62 """Convert one C++ nuisance sample row to a Python mapping."""
63 return {ParamId.from_cpp(k): float(v) for k, v in dict(row).items()}
64
65
66def _nuisance_sample_to_cpp(row: Mapping[ParamId, float]):
67 """Convert one Python nuisance sample row to a C++ mapping."""
68 return {_cpp_param_id(k): float(v) for k, v in row.items()}
69
70
71@dataclass
73 """Options controlling Monte-Carlo uncertainty propagation.
74
75 Attributes:
76 draws: Number of accepted Monte-Carlo predictions to generate.
77 skew_abs_threshold: Absolute skewness threshold below which an
78 observable distribution is treated as symmetric.
79 covariance_ridge_rel: Relative diagonal ridge added before covariance
80 inversion.
81 covariance_ridge_abs: Absolute minimum diagonal ridge.
82 retry_failed_predictions: Whether failed model predictions should be
83 skipped and retried.
84 max_prediction_failures: Maximum number of rejected predictions before
85 the backend propagates the failure.
86 """
87
88 draws: int = 10000
89 skew_abs_threshold: float = 0.2
90 covariance_ridge_rel: float = 1e-8
91 covariance_ridge_abs: float = 1e-12
92 retry_failed_predictions: bool = True
93 max_prediction_failures: int = 20000
94
95 @classmethod
96 def from_cpp(cls, cpp_obj) -> "MCConfig":
97 """Create Python options from a bound C++ ``MCConfig``."""
98 return cls(
99 draws=int(cpp_obj.draws),
100 skew_abs_threshold=float(cpp_obj.skew_abs_threshold),
101 covariance_ridge_rel=float(cpp_obj.covariance_ridge_rel),
102 covariance_ridge_abs=float(cpp_obj.covariance_ridge_abs),
103 retry_failed_predictions=bool(cpp_obj.retry_failed_predictions),
104 max_prediction_failures=int(cpp_obj.max_prediction_failures),
105 )
106
107 def to_cpp(self):
108 """Convert this config to a bound C++ ``MCConfig`` object."""
109 cpp = _cpp_stat.MCConfig()
110 cpp.draws = int(self.draws)
111 cpp.skew_abs_threshold = float(self.skew_abs_threshold)
112 cpp.covariance_ridge_rel = float(self.covariance_ridge_rel)
113 cpp.covariance_ridge_abs = float(self.covariance_ridge_abs)
114 cpp.retry_failed_predictions = bool(self.retry_failed_predictions)
115 cpp.max_prediction_failures = int(self.max_prediction_failures)
116 return cpp
117
118
119@dataclass
121 """Observable covariance estimated from Monte-Carlo samples.
122
123 Attributes:
124 ids: Observable ordering used by ``mean``, ``covariance`` and
125 ``covariance_inv``.
126 mean: Sample mean for each observable in ``ids`` order.
127 covariance: Regularized sample covariance matrix.
128 covariance_inv: Inverse covariance matrix used by the chi-square backend.
129 """
130
131 ids: List[BinnedObservableId] = field(default_factory=list)
132 mean: List[float] = field(default_factory=list)
133 covariance: Matrix | None = None
134 covariance_inv: Matrix | None = None
135
136 @classmethod
137 def from_cpp(cls, cpp_obj) -> "MCObservableCovariance":
138 """Create a Python covariance object from C++ output."""
139 return cls(
140 ids=[BinnedObservableId.from_cpp(x) for x in cpp_obj.ids],
141 mean=[float(x) for x in cpp_obj.mean],
142 covariance=Matrix.from_cpp(cpp_obj.covariance),
143 covariance_inv=Matrix.from_cpp(cpp_obj.covariance_inv),
144 )
145
146 def to_cpp(self):
147 """Convert this covariance wrapper to a bound C++ object."""
148 cpp = _cpp_stat.MCObservableCovariance()
149 cpp.ids = [_cpp_binned_observable_id(x) for x in self.ids]
150 cpp.mean = [float(x) for x in self.mean]
151 if self.covariance is not None:
152 cpp.covariance = self.covariance.to_cpp()
153 if self.covariance_inv is not None:
154 cpp.covariance_inv = self.covariance_inv.to_cpp()
155 return cpp
156
157
158@dataclass
160 """Raw Monte-Carlo realization.
161
162 Attributes:
163 sampled_obss: Accepted observable predictions, one mapping per draw.
164 sampled_params: Nuisance-parameter values used for each accepted draw.
165 """
166
167 sampled_obss: ObsSamples = field(default_factory=list)
168 sampled_params: NuisanceSamples = field(default_factory=list)
169
170 @classmethod
171 def from_cpp(cls, cpp_obj) -> "MCRealization":
172 """Create a Python realization from a bound C++ ``MCRealization``."""
173 return cls(
174 sampled_obss=[_obs_sample_from_cpp(row) for row in cpp_obj.sampled_obss],
175 sampled_params=[_nuisance_sample_from_cpp(row) for row in cpp_obj.sampled_params],
176 )
177
178 def to_cpp(self):
179 """Convert this realization to the bound C++ representation."""
180 cpp = _cpp_stat.MCRealization()
181 cpp.sampled_obss = [_obs_sample_to_cpp(row) for row in self.sampled_obss]
182 cpp.sampled_params = [_nuisance_sample_to_cpp(row) for row in self.sampled_params]
183 return cpp
184
185
186@dataclass
188 """Complete Monte-Carlo uncertainty-propagation result.
189
190 Attributes:
191 mc_real: Raw accepted samples and nuisance draws.
192 summary: Per-observable Gaussian or split-Gaussian summaries.
193 covariance: Observable covariance estimate and inverse.
194 """
195
196 mc_real: MCRealization = field(default_factory=MCRealization)
197 summary: List[GaussianSummary] = field(default_factory=list)
198 covariance: MCObservableCovariance = field(default_factory=MCObservableCovariance)
199
200 @classmethod
201 def from_cpp(cls, cpp_obj) -> "MCResult":
202 """Create a Python result from a bound C++ ``MCResult``."""
203 return cls(
204 mc_real=MCRealization.from_cpp(cpp_obj.mc_real),
205 summary=[GaussianSummary.from_cpp(gs) for gs in cpp_obj.summary],
206 covariance=MCObservableCovariance.from_cpp(cpp_obj.covariance),
207 )
208
209 def to_cpp(self):
210 """Convert this result to the bound C++ representation."""
211 cpp = _cpp_stat.MCResult()
212 cpp.mc_real = self.mc_real.to_cpp()
213 cpp.summary = [gs.to_cpp() for gs in self.summary]
214 cpp.covariance = self.covariance.to_cpp()
215 return cpp
216
217
219 samples: Sequence[Mapping[BinnedObservableId, float]],
220) -> List[BinnedObservableId]:
221 """Return the observable ordering inferred from the first sample.
222
223 Args:
224 samples: Sequence of observable sample mappings.
225
226 Returns:
227 Observable identifiers in the backend order inferred from the first row.
228
229 Raises:
230 Exception: Propagates backend errors, for example when ``samples`` is
231 empty.
232 """
233 cpp_samples = [_obs_sample_to_cpp(row) for row in samples]
234 return [
235 BinnedObservableId.from_cpp(x)
236 for x in _cpp_stat.covariance_ids_from_first_sample(cpp_samples)
237 ]
238
239
241 samples: Sequence[Mapping[BinnedObservableId, float]],
242 ids: Sequence[BinnedObservableId],
243 ridge_rel: float = 1e-8,
244 ridge_abs: float = 1e-12,
245) -> MCObservableCovariance:
246 """Estimate an observable covariance matrix from Monte-Carlo samples.
247
248 Args:
249 samples: Observable samples produced by Monte-Carlo propagation.
250 ids: Observable ordering to use for the covariance matrix.
251 ridge_rel: Relative diagonal ridge added before matrix inversion.
252 ridge_abs: Absolute minimum diagonal ridge.
253
254 Returns:
255 A ``MCObservableCovariance`` containing the mean, covariance and inverse
256 covariance.
257
258 Examples:
259 >>> ids = covariance_ids_from_first_sample(samples)
260 >>> cov = covariance_from_obs_samples(samples, ids)
261 >>> len(cov.ids) == len(cov.mean)
262 True
263 """
264 cpp_samples = [_obs_sample_to_cpp(row) for row in samples]
265 cpp_ids = [_cpp_binned_observable_id(x) for x in ids]
266 return MCObservableCovariance.from_cpp(
267 _cpp_stat.covariance_from_obs_samples(
268 cpp_samples, cpp_ids, float(ridge_rel), float(ridge_abs)
269 )
270 )
271
272
273__all__ = [
274 "ObsSample",
275 "ObsSamples",
276 "NuisanceSample",
277 "NuisanceSamples",
278 "MCConfig",
279 "MCObservableCovariance",
280 "MCRealization",
281 "MCResult",
282 "covariance_ids_from_first_sample",
283 "covariance_from_obs_samples",
284]
float skew_abs_threshold
Definition MCResult.py:89
bool retry_failed_predictions
Definition MCResult.py:92
float covariance_ridge_rel
Definition MCResult.py:90
"MCConfig" from_cpp(cls, cpp_obj)
Definition MCResult.py:96
float covariance_ridge_abs
Definition MCResult.py:91
int max_prediction_failures
Definition MCResult.py:93
"MCObservableCovariance" from_cpp(cls, cpp_obj)
Definition MCResult.py:137
ObsSamples sampled_obss
Definition MCResult.py:167
"MCRealization" from_cpp(cls, cpp_obj)
Definition MCResult.py:171
NuisanceSamples sampled_params
Definition MCResult.py:168
MCObservableCovariance covariance
Definition MCResult.py:198
"MCResult" from_cpp(cls, cpp_obj)
Definition MCResult.py:201
MCRealization mc_real
Definition MCResult.py:196
MCObservableCovariance covariance_from_obs_samples(Sequence[Mapping[BinnedObservableId, float]] samples, Sequence[BinnedObservableId] ids, float ridge_rel=1e-8, float ridge_abs=1e-12)
Definition MCResult.py:245
NuisanceSample _nuisance_sample_from_cpp(row)
Definition MCResult.py:61
_cpp_binned_observable_id(BinnedObservableId obs)
Definition MCResult.py:41
ObsSample _obs_sample_from_cpp(row)
Definition MCResult.py:51
_cpp_param_id(ParamId pid)
Definition MCResult.py:46
List[BinnedObservableId] covariance_ids_from_first_sample(Sequence[Mapping[BinnedObservableId, float]] samples)
Definition MCResult.py:220
_require(value, typ, str name)
Definition MCResult.py:34
_nuisance_sample_to_cpp(Mapping[ParamId, float] row)
Definition MCResult.py:66
_obs_sample_to_cpp(Mapping[BinnedObservableId, float] row)
Definition MCResult.py:56