Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
MarginalDistribution.py
Go to the documentation of this file.
1"""Python wrappers and factories for one-dimensional marginal distributions."""
2
3from __future__ import annotations
4
5from typing import List, Optional, Sequence, cast
6
7from pyhyperiso.phyperiso.pyhyperiso import statistic as st
8from pyhyperiso.core.Common.ParamId import ParamId
9from pyhyperiso.core.Statistic.ExperimentObs import ExperimentObs
10from pyhyperiso.core.Statistic.MarginalConfig import (
11 FlatMarginalConfig,
12 GaussianMarginalConfig,
13 LikelihoodMarginalConfig,
14 MarginalConfig,
15 MarginalKind,
16 SplitGaussianMarginalConfig,
17 _config_from_cpp,
18)
19
20
21def _require(value, typ, name: str):
22 """Validate a user-facing argument type and return the original value."""
23 if not isinstance(value, typ):
24 raise TypeError(f"{name} must be {typ.__name__}, received {type(value)!r}.")
25 return value
26
27
28def _cpp_marginal_kind(kind: MarginalKind):
29 """Convert a Python marginal kind to the C++ enum value."""
30 return _require(kind, MarginalKind, "MarginalKind").to_cpp()
31
32
33def _cpp_param_id(pid: ParamId):
34 """Convert a Python parameter identifier to its C++ representation."""
35 return _require(pid, ParamId, "ParamId").to_cpp()
36
37
38def _cpp_experiment_obs(obs: ExperimentObs):
39 """Convert a Python experimental observable wrapper to C++."""
40 return _require(obs, ExperimentObs, "ExperimentObs").to_cpp()
41
42
43def _cpp_marginal_config(config: MarginalConfig):
44 """Convert a supported Python marginal config to C++.
45
46 Raises:
47 TypeError: If ``config`` is not one of the supported marginal config
48 dataclasses.
49 """
50 if not isinstance(
51 config,
52 (
53 FlatMarginalConfig,
54 GaussianMarginalConfig,
55 SplitGaussianMarginalConfig,
56 LikelihoodMarginalConfig,
57 ),
58 ):
59 raise TypeError(f"unsupported marginal configuration: {type(config)!r}.")
60 return config.to_cpp()
61
62
64 """Base wrapper around a bound C++ marginal distribution.
65
66 Args:
67 cpp_obj: Bound C++ marginal distribution object.
68 """
69
70 def __init__(self, cpp_obj) -> None:
71 """Store the bound C++ marginal distribution object."""
72 self._cpp_obj = cpp_obj
73
74 @classmethod
75 def from_cpp(cls, cpp_obj) -> "MarginalDistribution":
76 """Wrap a C++ marginal with the most specific Python subclass.
77
78 Args:
79 cpp_obj: Bound C++ marginal distribution object.
80
81 Returns:
82 A subclass wrapper when the C++ type is recognized, otherwise a
83 generic ``MarginalDistribution``.
84 """
85 if isinstance(cpp_obj, st.GaussianMarginal):
86 return GaussianMarginalDist(cpp_obj)
87 if isinstance(cpp_obj, st.SplitGaussianMarginal):
88 return SplitGaussianMarginalDist(cpp_obj)
89 if isinstance(cpp_obj, st.FlatMarginal):
90 return FlatMarginalDist(cpp_obj)
91 if isinstance(cpp_obj, st.LikelihoodMarginal):
92 return LikelihoodMarginalDist(cpp_obj)
93 return cls(cpp_obj)
94
95 def _to_cpp(self):
96 """Return the underlying C++ object for internal calls."""
97 return self._cpp_obj
98
99 def rvs(self, n: int) -> List[float]:
100 """Draw independent random variates from the marginal.
101
102 Args:
103 n: Number of variates to draw.
104
105 Returns:
106 A list of ``n`` floats.
107
108 Raises:
109 ValueError: If ``n`` is negative.
110 """
111 if int(n) < 0:
112 raise ValueError("n must be >= 0.")
113 return [float(v) for v in self._cpp_obj.rvs(int(n))]
114
115 def logpdf(self, x: float) -> float:
116 """Evaluate the log-density at ``x``."""
117 return float(self._cpp_obj.logpdf(float(x)))
118
119 def cdf(self, x: float) -> float:
120 """Evaluate the cumulative distribution function at ``x``."""
121 return float(self._cpp_obj.cdf(float(x)))
122
123 def ppf(self, p: float) -> float:
124 """Evaluate the quantile function.
125
126 Args:
127 p: Probability in the closed interval ``[0, 1]``.
128
129 Returns:
130 The value ``x`` such that ``cdf(x) ~= p``.
131
132 Raises:
133 ValueError: If ``p`` is outside ``[0, 1]``.
134 """
135 p = float(p)
136 if not 0.0 <= p <= 1.0:
137 raise ValueError("p must lie in [0, 1].")
138 return float(self._cpp_obj.ppf(p))
139
140 def mean(self) -> float:
141 """Return the marginal mean."""
142 return float(self._cpp_obj.mean())
143
144 def std(self) -> float:
145 """Return the marginal standard deviation."""
146 return float(self._cpp_obj.std())
147
148
150 """Wrapper for a symmetric Gaussian marginal distribution."""
151
152 pass
153
154
155class SplitGaussianMarginalDist(MarginalDistribution):
156 """Wrapper for an asymmetric split-Gaussian marginal distribution."""
157
158 pass
159
160
162 """Wrapper for a uniform marginal distribution."""
163
164 pass
165
166
168 """Wrapper for an empirical likelihood marginal distribution."""
169
170 pass
171
172
174 """Factory helpers for marginal distributions."""
175
176 @staticmethod
178 kind: MarginalKind, config: MarginalConfig, seed: Optional[int] = None
179 ) -> MarginalDistribution:
180 """Create a marginal distribution from a kind and configuration.
181
182 Args:
183 kind: Marginal family to instantiate.
184 config: Configuration object compatible with ``kind``.
185 seed: Optional RNG seed.
186
187 Returns:
188 A Python wrapper around the bound C++ marginal.
189 """
190 cpp_dist = st.MarginalFactory.create(
191 _cpp_marginal_kind(kind), _cpp_marginal_config(config), seed
192 )
193 return MarginalDistribution.from_cpp(cpp_dist)
194
195 @staticmethod
196 def gaussian(mu: float, sigma: float, seed: Optional[int] = None) -> GaussianMarginalDist:
197 """Create a Gaussian marginal.
198
199 Args:
200 mu: Central value.
201 sigma: Standard deviation.
202 seed: Optional RNG seed.
203
204 Returns:
205 A ``GaussianMarginalDist`` wrapper.
206 """
207 return cast(
208 GaussianMarginalDist,
209 DistributionFactoryWrapper.create(
210 MarginalKind.GAUSSIAN, GaussianMarginalConfig(mu=mu, sigma=sigma), seed
211 ),
212 )
213
214 @staticmethod
216 mu: float, sigma_p: float, sigma_m: float, seed: Optional[int] = None
217 ) -> SplitGaussianMarginalDist:
218 """Create an asymmetric split-Gaussian marginal.
219
220 Args:
221 mu: Central value or mode.
222 sigma_p: Right-side standard deviation.
223 sigma_m: Left-side standard deviation.
224 seed: Optional RNG seed.
225
226 Returns:
227 A ``SplitGaussianMarginalDist`` wrapper.
228 """
229 return cast(
230 SplitGaussianMarginalDist,
231 DistributionFactoryWrapper.create(
232 MarginalKind.HALF_GAUSSIAN,
233 SplitGaussianMarginalConfig(mu=mu, sigma_p=sigma_p, sigma_m=sigma_m),
234 seed,
235 ),
236 )
237
238 @staticmethod
239 def flat(a: float, b: float, seed: Optional[int] = None) -> FlatMarginalDist:
240 """Create a uniform marginal over ``[a, b]``."""
241 return cast(
242 FlatMarginalDist,
243 DistributionFactoryWrapper.create(
244 MarginalKind.FLAT, FlatMarginalConfig(a=a, b=b), seed
245 ),
246 )
247
248 @staticmethod
250 values: Sequence[float],
251 weights: Sequence[float],
252 seed: Optional[int] = None,
253 standardize: bool = False,
254 ) -> LikelihoodMarginalDist:
255 """Create an empirical likelihood marginal.
256
257 Args:
258 values: Empirical support values.
259 weights: Weights associated with each support value.
260 seed: Optional RNG seed.
261 standardize: If true, use the backend constructor that standardizes
262 the likelihood marginal.
263
264 Returns:
265 A ``LikelihoodMarginalDist`` wrapper.
266
267 Raises:
268 ValueError: If ``values`` and ``weights`` have different lengths.
269 """
270 cfg = LikelihoodMarginalConfig(values=values, weights=weights)
271 if standardize:
272 cpp_obj = st.LikelihoodMarginal(
273 list(map(float, cfg.values)),
274 list(map(float, cfg.weights)),
275 int(0 if seed is None else seed),
276 True,
277 )
278 return cast(LikelihoodMarginalDist, MarginalDistribution.from_cpp(cpp_obj))
279 return cast(
280 LikelihoodMarginalDist,
281 DistributionFactoryWrapper.create(MarginalKind.LIKELIHOOD, cfg, seed),
282 )
283
284
286 """Wrapper around the C++ ``MarginalConfigFactory``.
287
288 The C++ factory can infer a marginal configuration from a model parameter or
289 an experimental observable, using the current HyperIso parameter database.
290 """
291
292 def __init__(self) -> None:
293 """Create the underlying C++ marginal configuration factory."""
294 self._cpp_obj = st.MarginalConfigFactory()
295
296 def create_from_param(self, pid: ParamId, kind: MarginalKind) -> MarginalConfig:
297 """Infer a marginal config for a parameter.
298
299 Args:
300 pid: Parameter identifier.
301 kind: Desired marginal family.
302
303 Returns:
304 A Python marginal configuration dataclass.
305 """
306 return _config_from_cpp(self._cpp_obj.create(_cpp_param_id(pid), _cpp_marginal_kind(kind)))
307
308 def create_from_observable(self, obs: ExperimentObs, kind: MarginalKind) -> MarginalConfig:
309 """Infer a marginal config for an experimental observable.
310
311 Args:
312 obs: Experimental observable key returned by the statistic layer.
313 kind: Desired marginal family.
314
315 Returns:
316 A Python marginal configuration dataclass.
317 """
318 return _config_from_cpp(
319 self._cpp_obj.create(_cpp_experiment_obs(obs), _cpp_marginal_kind(kind))
320 )
321
322
323__all__ = [
324 "MarginalKind",
325 "FlatMarginalConfig",
326 "GaussianMarginalConfig",
327 "SplitGaussianMarginalConfig",
328 "LikelihoodMarginalConfig",
329 "MarginalConfig",
330 "MarginalDistribution",
331 "GaussianMarginalDist",
332 "SplitGaussianMarginalDist",
333 "FlatMarginalDist",
334 "LikelihoodMarginalDist",
335 "DistributionFactoryWrapper",
336 "MarginalConfigFactoryWrapper",
337]
GaussianMarginalDist gaussian(float mu, float sigma, Optional[int] seed=None)
LikelihoodMarginalDist likelihood(Sequence[float] values, Sequence[float] weights, Optional[int] seed=None, bool standardize=False)
FlatMarginalDist flat(float a, float b, Optional[int] seed=None)
SplitGaussianMarginalDist split_gaussian(float mu, float sigma_p, float sigma_m, Optional[int] seed=None)
MarginalDistribution create(MarginalKind kind, MarginalConfig config, Optional[int] seed=None)
MarginalConfig create_from_param(self, ParamId pid, MarginalKind kind)
MarginalConfig create_from_observable(self, ExperimentObs obs, MarginalKind kind)
"MarginalDistribution" from_cpp(cls, cpp_obj)
_require(value, typ, str name)
_cpp_marginal_config(MarginalConfig config)
_cpp_experiment_obs(ExperimentObs obs)
_cpp_marginal_kind(MarginalKind kind)
Hash specialization for SymbolId<Tag>.
Definition BlockName.h:353