1"""Python wrappers and factories for one-dimensional marginal distributions."""
3from __future__
import annotations
5from typing
import List, Optional, Sequence, cast
7from pyhyperiso.phyperiso.pyhyperiso
import statistic
as st
9from pyhyperiso.core.Statistic.ExperimentObs
import ExperimentObs
10from pyhyperiso.core.Statistic.MarginalConfig
import (
12 GaussianMarginalConfig,
13 LikelihoodMarginalConfig,
16 SplitGaussianMarginalConfig,
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}.")
29 """Convert a Python marginal kind to the C++ enum value."""
30 return _require(kind, MarginalKind,
"MarginalKind").to_cpp()
34 """Convert a Python parameter identifier to its C++ representation."""
35 return _require(pid, ParamId,
"ParamId").to_cpp()
39 """Convert a Python experimental observable wrapper to C++."""
40 return _require(obs, ExperimentObs,
"ExperimentObs").to_cpp()
44 """Convert a supported Python marginal config to C++.
47 TypeError: If ``config`` is not one of the supported marginal config
54 GaussianMarginalConfig,
55 SplitGaussianMarginalConfig,
56 LikelihoodMarginalConfig,
59 raise TypeError(f
"unsupported marginal configuration: {type(config)!r}.")
60 return config.to_cpp()
64 """Base wrapper around a bound C++ marginal distribution.
67 cpp_obj: Bound C++ marginal distribution object.
71 """Store the bound C++ marginal distribution object."""
75 def from_cpp(cls, cpp_obj) -> "MarginalDistribution":
76 """Wrap a C++ marginal with the most specific Python subclass.
79 cpp_obj: Bound C++ marginal distribution object.
82 A subclass wrapper when the C++ type is recognized, otherwise a
83 generic ``MarginalDistribution``.
85 if isinstance(cpp_obj, st.GaussianMarginal):
87 if isinstance(cpp_obj, st.SplitGaussianMarginal):
89 if isinstance(cpp_obj, st.FlatMarginal):
91 if isinstance(cpp_obj, st.LikelihoodMarginal):
96 """Return the underlying C++ object for internal calls."""
99 def rvs(self, n: int) -> List[float]:
100 """Draw independent random variates from the marginal.
103 n: Number of variates to draw.
106 A list of ``n`` floats.
109 ValueError: If ``n`` is negative.
112 raise ValueError(
"n must be >= 0.")
113 return [float(v)
for v
in self.
_cpp_obj.
rvs(int(n))]
116 """Evaluate the log-density at ``x``."""
119 def cdf(self, x: float) -> float:
120 """Evaluate the cumulative distribution function at ``x``."""
123 def ppf(self, p: float) -> float:
124 """Evaluate the quantile function.
127 p: Probability in the closed interval ``[0, 1]``.
130 The value ``x`` such that ``cdf(x) ~= p``.
133 ValueError: If ``p`` is outside ``[0, 1]``.
136 if not 0.0 <= p <= 1.0:
137 raise ValueError(
"p must lie in [0, 1].")
141 """Return the marginal mean."""
145 """Return the marginal standard deviation."""
150 """Wrapper for a symmetric Gaussian marginal distribution."""
156 """Wrapper for an asymmetric split-Gaussian marginal distribution."""
162 """Wrapper for a uniform marginal distribution."""
168 """Wrapper for an empirical likelihood marginal distribution."""
174 """Factory helpers for marginal distributions."""
178 kind: MarginalKind, config: MarginalConfig, seed: Optional[int] =
None
179 ) -> MarginalDistribution:
180 """Create a marginal distribution from a kind and configuration.
183 kind: Marginal family to instantiate.
184 config: Configuration object compatible with ``kind``.
185 seed: Optional RNG seed.
188 A Python wrapper around the bound C++ marginal.
190 cpp_dist = st.MarginalFactory.create(
193 return MarginalDistribution.from_cpp(cpp_dist)
196 def gaussian(mu: float, sigma: float, seed: Optional[int] =
None) -> GaussianMarginalDist:
197 """Create a Gaussian marginal.
201 sigma: Standard deviation.
202 seed: Optional RNG seed.
205 A ``GaussianMarginalDist`` wrapper.
208 GaussianMarginalDist,
209 DistributionFactoryWrapper.create(
210 MarginalKind.GAUSSIAN, GaussianMarginalConfig(mu=mu, sigma=sigma), seed
216 mu: float, sigma_p: float, sigma_m: float, seed: Optional[int] =
None
217 ) -> SplitGaussianMarginalDist:
218 """Create an asymmetric split-Gaussian marginal.
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.
227 A ``SplitGaussianMarginalDist`` wrapper.
230 SplitGaussianMarginalDist,
231 DistributionFactoryWrapper.create(
232 MarginalKind.HALF_GAUSSIAN,
233 SplitGaussianMarginalConfig(mu=mu, sigma_p=sigma_p, sigma_m=sigma_m),
239 def flat(a: float, b: float, seed: Optional[int] =
None) -> FlatMarginalDist:
240 """Create a uniform marginal over ``[a, b]``."""
243 DistributionFactoryWrapper.create(
244 MarginalKind.FLAT, FlatMarginalConfig(a=a, b=b), seed
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.
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.
265 A ``LikelihoodMarginalDist`` wrapper.
268 ValueError: If ``values`` and ``weights`` have different lengths.
270 cfg = LikelihoodMarginalConfig(values=values, weights=weights)
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),
278 return cast(LikelihoodMarginalDist, MarginalDistribution.from_cpp(cpp_obj))
280 LikelihoodMarginalDist,
281 DistributionFactoryWrapper.create(MarginalKind.LIKELIHOOD, cfg, seed),
286 """Wrapper around the C++ ``MarginalConfigFactory``.
288 The C++ factory can infer a marginal configuration from a model parameter or
289 an experimental observable, using the current HyperIso parameter database.
293 """Create the underlying C++ marginal configuration factory."""
297 """Infer a marginal config for a parameter.
300 pid: Parameter identifier.
301 kind: Desired marginal family.
304 A Python marginal configuration dataclass.
309 """Infer a marginal config for an experimental observable.
312 obs: Experimental observable key returned by the statistic layer.
313 kind: Desired marginal family.
316 A Python marginal configuration dataclass.
318 return _config_from_cpp(
325 "FlatMarginalConfig",
326 "GaussianMarginalConfig",
327 "SplitGaussianMarginalConfig",
328 "LikelihoodMarginalConfig",
330 "MarginalDistribution",
331 "GaussianMarginalDist",
332 "SplitGaussianMarginalDist",
334 "LikelihoodMarginalDist",
335 "DistributionFactoryWrapper",
336 "MarginalConfigFactoryWrapper",
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)
float logpdf(self, float x)
"MarginalDistribution" from_cpp(cls, cpp_obj)
None __init__(self, cpp_obj)
List[float] rvs(self, int n)
_cpp_param_id(ParamId pid)
_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>.