Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
MarginalConfig.py
Go to the documentation of this file.
1"""Configuration dataclasses for one-dimensional marginal distributions.
2
3Each dataclass mirrors a C++ marginal configuration struct and exposes a
4``to_cpp`` method used by the distribution factories. These configurations are
5combined with copulas to build joint distributions in the statistic backend.
6"""
7
8from __future__ import annotations
9
10from dataclasses import dataclass
11from enum import Enum
12from typing import Any, Sequence, Union
13
14from pyhyperiso.phyperiso.pyhyperiso import statistic as st
15
16
17class MarginalKind(Enum):
18 """Supported one-dimensional marginal distribution families.
19
20 Attributes:
21 GAUSSIAN: Symmetric Gaussian marginal.
22 HALF_GAUSSIAN: Split/asymmetric Gaussian marginal in the backend naming.
23 FLAT: Uniform marginal over a finite interval.
24 LIKELIHOOD: Empirical marginal represented by values and weights.
25 """
26
27 GAUSSIAN = "GAUSSIAN"
28 HALF_GAUSSIAN = "HALF_GAUSSIAN"
29 FLAT = "FLAT"
30 LIKELIHOOD = "LIKELIHOOD"
31
32 def to_cpp(self) -> Any:
33 """Convert the Python enum value to the bound C++ ``MarginalType``."""
34 return getattr(st.MarginalType, self.value)
35
36
37@dataclass(frozen=True)
39 """Configuration for a uniform marginal distribution.
40
41 Args:
42 a: Lower bound of the support.
43 b: Upper bound of the support.
44
45 Examples:
46 >>> cfg = FlatMarginalConfig(a=-1.0, b=1.0)
47 >>> cfg.a, cfg.b
48 (-1.0, 1.0)
49 """
50
51 a: float = 0.0
52 b: float = 1.0
53
54 def to_cpp(self) -> Any:
55 """Build a bound C++ ``FlatMarginalCfg`` instance."""
56 return st.FlatMarginalCfg(self.aa, self.bb)
57
58 @classmethod
59 def from_cpp(cls, cpp: Any) -> "FlatMarginalConfig":
60 """Create a Python config from a bound C++ flat config."""
61 return cls(a=float(cpp.a), b=float(cpp.b))
62
63
64@dataclass(frozen=True)
66 """Configuration for a Gaussian marginal distribution.
67
68 Args:
69 mu: Central value of the Gaussian.
70 sigma: Standard deviation. It should be strictly positive.
71 """
72
73 mu: float = 0.0
74 sigma: float = 1.0
75
76 def to_cpp(self) -> Any:
77 """Build a bound C++ ``GaussianMarginalCfg`` instance."""
78 return st.GaussianMarginalCfg(self.mumu, self.sigmasigma)
79
80 @classmethod
81 def from_cpp(cls, cpp: Any) -> "GaussianMarginalConfig":
82 """Create a Python config from a bound C++ Gaussian config."""
83 return cls(mu=float(cpp.mu), sigma=float(cpp.sigma))
84
85
86@dataclass(frozen=True)
88 """Configuration for an asymmetric split-Gaussian marginal.
89
90 Args:
91 mu: Central value or mode of the distribution.
92 sigma_p: Right-side standard deviation, used for values above ``mu``.
93 sigma_m: Left-side standard deviation, used for values below ``mu``.
94 """
95
96 mu: float = 0.0
97 sigma_p: float = 1.0
98 sigma_m: float = 1.0
99
100 def to_cpp(self) -> Any:
101 """Build a bound C++ ``SplitGaussianMarginalCfg`` instance."""
102 return st.SplitGaussianMarginalCfg(self.mumu, self.sigma_psigma_p, self.sigma_msigma_m)
103
104 @classmethod
105 def from_cpp(cls, cpp: Any) -> "SplitGaussianMarginalConfig":
106 """Create a Python config from a bound C++ split-Gaussian config."""
107 return cls(mu=float(cpp.mu), sigma_p=float(cpp.sigma_p), sigma_m=float(cpp.sigma_m))
108
109
110@dataclass(frozen=True)
112 """Configuration for an empirical likelihood marginal.
113
114 Args:
115 values: Grid or sample values supporting the empirical likelihood.
116 weights: Non-negative weights associated with ``values``. The two
117 sequences must have the same length.
118
119 Raises:
120 ValueError: If ``values`` and ``weights`` have different lengths.
121
122 Examples:
123 >>> cfg = LikelihoodMarginalConfig(values=[0.0, 1.0], weights=[0.25, 0.75])
124 >>> len(cfg.values) == len(cfg.weights)
125 True
126 """
127
128 values: Sequence[float]
129 weights: Sequence[float]
130
131 def __post_init__(self) -> None:
132 """Validate the empirical likelihood support."""
133 if len(self.valuesvalues) != len(self.weightsweights):
134 raise ValueError("LikelihoodMarginalConfig: values et weights must have the same size.")
135
136 def to_cpp(self) -> Any:
137 """Build a bound C++ ``LikelihoodMarginalCfg`` instance."""
138 return st.LikelihoodMarginalCfg(
139 list(map(float, self.valuesvalues)), list(map(float, self.weightsweights))
140 )
141
142 @classmethod
143 def from_cpp(cls, cpp: Any) -> "LikelihoodMarginalConfig":
144 """Create a Python config from a bound C++ likelihood config."""
145 return cls(values=list(map(float, cpp.values)), weights=list(map(float, cpp.weights)))
146
147
148MarginalConfig = Union[
149 FlatMarginalConfig,
150 GaussianMarginalConfig,
151 SplitGaussianMarginalConfig,
152 LikelihoodMarginalConfig,
153]
154
155
156def _config_from_cpp(cpp_cfg: Any) -> MarginalConfig:
157 """Dispatch a bound C++ marginal config to the matching Python dataclass.
158
159 Args:
160 cpp_cfg: Bound C++ marginal configuration object.
161
162 Returns:
163 The corresponding Python configuration dataclass.
164
165 Raises:
166 TypeError: If the C++ config type is not recognized.
167 """
168 if isinstance(cpp_cfg, st.FlatMarginalCfg):
169 return FlatMarginalConfig.from_cpp(cpp_cfg)
170 if isinstance(cpp_cfg, st.GaussianMarginalCfg):
171 return GaussianMarginalConfig.from_cpp(cpp_cfg)
172 if isinstance(cpp_cfg, st.SplitGaussianMarginalCfg):
173 return SplitGaussianMarginalConfig.from_cpp(cpp_cfg)
174 if isinstance(cpp_cfg, st.LikelihoodMarginalCfg):
175 return LikelihoodMarginalConfig.from_cpp(cpp_cfg)
176
177 raise TypeError(f"Config C++ not known: {type(cpp_cfg)!r}")