Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
StatisticInterface.py
Go to the documentation of this file.
1"""Public Python interface to the statistical analysis backend.
2
3``StatisticInterface`` is a high-level wrapper around the C++ statistic manager.
4It connects a configured ``ObservableInterface`` to uncertainty propagation,
5maximum-likelihood fitting, confidence-contour computation and likelihood scans.
6"""
7
8from __future__ import annotations
9
10from dataclasses import dataclass, field
11from enum import Enum
12from pathlib import Path
13from typing import Dict, List, Mapping, Optional, Sequence
14
15from pyhyperiso.phyperiso.pyhyperiso import statistic as st
16from pyhyperiso.core.BusinessLogic.ObservableInterface import ObservableInterface
17from pyhyperiso.core.Common.BinnedObservableId import BinnedObservableId
18from pyhyperiso.core.Common.ParamId import ParamId
19from pyhyperiso.core.Statistic.ExperimentObs import ExperimentObs
20from pyhyperiso.core.Statistic.GaussianSummary import GaussianSummary
21from pyhyperiso.core.Statistic.MCResult import MCResult
22from pyhyperiso.core.Statistic.StatisticConfig import StatisticConfig
23
24
25class ProfilingMethod(Enum):
26 """Strategy used to reduce a fit with more than two parameters to 2D.
27
28 Attributes:
29 SLICE: Vary the selected axes while keeping the other fit parameters at
30 their best-fit values.
31 FREE_PROJECTION: Profile all non-displayed fit parameters freely.
32 PRIOR_CONSTRAINED_PROJECTION: Profile non-displayed fit parameters with
33 Gaussian constraints derived from the global fit.
34 """
35
36 SLICE = st.ProfilingMethod.SLICE
37 FREE_PROJECTION = st.ProfilingMethod.FREE_PROJECTION
38 PRIOR_CONSTRAINED_PROJECTION = st.ProfilingMethod.PRIOR_CONSTRAINED_PROJECTION
39
40 def to_cpp(self):
41 """Convert to the bound C++ ``ProfilingMethod`` enum."""
42 return self.value
43
44
45class ContourAlgorithm(Enum):
46 """Algorithm used to extract the requested confidence contour."""
47
48 AMS = st.ContourAlgorithm.AMS
49 MINUIT = st.ContourAlgorithm.MINUIT
50
51 def to_cpp(self):
52 """Convert to the bound C++ ``ContourAlgorithm`` enum."""
53 return self.value
54
55
56class ProfilerMode(Enum):
57 """Backend used while profiling hidden fit or nuisance parameters."""
58
59 MINUIT = st.ProfileBackend.MINUIT
60 LAPLACE_NUISANCE = st.ProfileBackend.LAPLACE_NUISANCE
61
62 def to_cpp(self):
63 """Convert to the C++ contour ``ProfileBackend`` enum."""
64 return self.value
65
66
67# Historical public name kept for source compatibility. ``ProfilerMode`` is
68# the terminology used by the contour engine and by the GUI.
69ProfileBackend = ProfilerMode
70
71
72def _require(value, typ, name: str):
73 """Validate a typed argument and return it unchanged."""
74 if not isinstance(value, typ):
75 raise TypeError(f"{name} must be {typ.__name__}, received {type(value)!r}.")
76 return value
77
78
79def _cpp_param_id(pid: ParamId):
80 """Convert a Python ``ParamId`` to C++."""
81 return _require(pid, ParamId, "ParamId").to_cpp()
82
83
84def _param_from_cpp(cpp_obj) -> ParamId:
85 """Convert a C++ ``ParamId`` to the Python wrapper."""
86 return ParamId.from_cpp(cpp_obj)
87
88
89def _param_float_map_from_cpp(cpp_map) -> Dict[ParamId, float]:
90 """Convert a C++ ``map<ParamId, double>`` to a Python dictionary."""
91 return {_param_from_cpp(k): float(v) for k, v in dict(cpp_map).items()}
92
93
94def _param_float_map_to_cpp(values: Mapping[ParamId, float]):
95 """Convert a Python parameter-value mapping to C++."""
96 return {_cpp_param_id(k): float(v) for k, v in values.items()}
97
98
99def _param_nested_float_map_from_cpp(cpp_map) -> Dict[ParamId, Dict[ParamId, float]]:
100 """Convert a nested C++ parameter matrix map to Python dictionaries."""
101 return {_param_from_cpp(k): _param_float_map_from_cpp(v) for k, v in dict(cpp_map).items()}
102
103
104def _cpp_experiment_obs(obs: ExperimentObs):
105 """Convert a Python ``ExperimentObs`` to its bound C++ value."""
106 return _require(obs, ExperimentObs, "ExperimentObs").to_cpp()
107
108
109def _experiment_float_map_from_cpp(cpp_map) -> Dict[ExperimentObs, float]:
110 """Convert a C++ ``map<ExperimentObs, double>`` to Python."""
111 return {ExperimentObs.from_cpp(k): float(v) for k, v in dict(cpp_map).items()}
112
113
115 cpp_map,
116) -> Dict[ExperimentObs, Dict[ExperimentObs, float]]:
117 """Convert a nested experimental-observable correlation map to Python."""
118 return {
119 ExperimentObs.from_cpp(k): _experiment_float_map_from_cpp(v)
120 for k, v in dict(cpp_map).items()
121 }
122
123
124def _cpp_profiling_method(value: ProfilingMethod):
125 """Convert a Python profiling method to C++."""
126 return _require(value, ProfilingMethod, "profiling_method").to_cpp()
127
128
129def _cpp_profile_backend(value: ProfilerMode):
130 """Convert a Python profile backend to C++."""
131 return _require(value, ProfilerMode, "profile_backend").to_cpp()
132
133
134def _cpp_contour_algorithm(value: ContourAlgorithm):
135 """Convert a Python contour algorithm to C++."""
136 return _require(value, ContourAlgorithm, "contour_algorithm").to_cpp()
137
138
139@dataclass(frozen=True)
141 """Maximum-likelihood fit result keyed by Python parameter identifiers.
142
143 Attributes:
144 fit_ok: Whether the C++ fit result is considered usable.
145 p_hat: Best-fit values for fit parameters.
146 eta_hat: Profiled nuisance values at the best-fit point.
147 p_hat_std: Standard deviations for fit parameters, usually from the
148 profiled covariance.
149 p_correlations: Fit-parameter correlation matrix as nested maps.
150 ell_hat: Minimum negative log-likelihood value.
151 """
152
153 fit_ok: bool = False
154 p_hat: Dict[ParamId, float] = field(default_factory=dict)
155 eta_hat: Dict[ParamId, float] = field(default_factory=dict)
156 p_hat_std: Dict[ParamId, float] = field(default_factory=dict)
157 p_correlations: Dict[ParamId, Dict[ParamId, float]] = field(default_factory=dict)
158 ell_hat: float = 0.0
159
160 @classmethod
161 def from_cpp(cls, cpp_obj) -> "FitResultWithMaps":
162 """Create a Python fit result from the bound C++ result."""
163 return cls(
164 fit_ok=bool(cpp_obj.fit_ok),
165 p_hat=_param_float_map_from_cpp(cpp_obj.p_hat),
166 eta_hat=_param_float_map_from_cpp(cpp_obj.eta_hat),
167 p_hat_std=_param_float_map_from_cpp(cpp_obj.p_hat_std),
168 p_correlations=_param_nested_float_map_from_cpp(cpp_obj.p_correlations),
169 ell_hat=float(cpp_obj.ell_hat),
170 )
171
172 def to_cpp(self):
173 """Convert this result to the bound C++ representation."""
174 cpp = st.FitResultWithMaps()
175 cpp.fit_ok = bool(self.fit_ok)
176 cpp.p_hat = _param_float_map_to_cpp(self.p_hat)
177 cpp.eta_hat = _param_float_map_to_cpp(self.eta_hat)
178 cpp.p_hat_std = _param_float_map_to_cpp(self.p_hat_std)
179 cpp.p_correlations = {
180 _cpp_param_id(k): _param_float_map_to_cpp(v) for k, v in self.p_correlations.items()
181 }
182 cpp.ell_hat = float(self.ell_hat)
183 return cpp
184
185
186@dataclass(frozen=True)
188 """Low-level maximum-likelihood fitter options.
189
190 This mirrors the C++ ``MLFitOptions`` struct. In most user workflows these
191 settings are configured through :class:`StatisticConfig`; this class is kept
192 available for direct conversions and advanced integrations.
193
194 Attributes:
195 run_hesse: Ask the minimizer to compute a covariance/HESSE estimate.
196 request_minos: Request MINOS if the compiled backend supports it.
197 verbose: Enable verbose minimizer output.
198 strategy: Backend strategy; ``0`` means backend default.
199 max_fcn: Maximum number of function calls; ``0`` means backend default.
200 tolerance: Minimizer tolerance; ``0.0`` means backend default.
201 allow_profile_hessian_fallback: Allow numerical profile-Hessian fallback.
202 profile_hessian_step_scale: Step scale for the fallback Hessian.
203 profile_hessian_eig_floor_rel: Relative eigenvalue floor for Hessian
204 regularization.
205 trace_first_evals: Print the first likelihood evaluations.
206 trace_max_evals: Maximum number of traced evaluations.
207 """
208
209 run_hesse: bool = True
210 request_minos: bool = False
211 verbose: bool = False
212 strategy: int = 0
213 max_fcn: int = 0
214 tolerance: float = 0.0
215 allow_profile_hessian_fallback: bool = True
216 profile_hessian_step_scale: float = 1.0
217 profile_hessian_eig_floor_rel: float = 1e-8
218 trace_first_evals: bool = False
219 trace_max_evals: int = 25
220
221 @classmethod
222 def from_cpp(cls, cpp_obj) -> "MLFitOptions":
223 """Create Python fitter options from C++ options."""
224 return cls(
225 run_hesse=bool(cpp_obj.run_hesse),
226 request_minos=bool(cpp_obj.request_minos),
227 verbose=bool(cpp_obj.verbose),
228 strategy=int(cpp_obj.strategy),
229 max_fcn=int(cpp_obj.max_fcn),
230 tolerance=float(cpp_obj.tolerance),
231 allow_profile_hessian_fallback=bool(cpp_obj.allow_profile_hessian_fallback),
232 profile_hessian_step_scale=float(cpp_obj.profile_hessian_step_scale),
233 profile_hessian_eig_floor_rel=float(cpp_obj.profile_hessian_eig_floor_rel),
234 trace_first_evals=bool(cpp_obj.trace_first_evals),
235 trace_max_evals=int(cpp_obj.trace_max_evals),
236 )
237
238 def to_cpp(self):
239 """Convert this options object to C++."""
240 cpp = st.MLFitOptions()
241 cpp.run_hesse = bool(self.run_hesse)
242 cpp.request_minos = bool(self.request_minos)
243 cpp.verbose = bool(self.verbose)
244 cpp.strategy = int(self.strategy)
245 cpp.max_fcn = int(self.max_fcn)
246 cpp.tolerance = float(self.tolerance)
247 cpp.allow_profile_hessian_fallback = bool(self.allow_profile_hessian_fallback)
248 cpp.profile_hessian_step_scale = float(self.profile_hessian_step_scale)
249 cpp.profile_hessian_eig_floor_rel = float(self.profile_hessian_eig_floor_rel)
250 cpp.trace_first_evals = bool(self.trace_first_evals)
251 cpp.trace_max_evals = int(self.trace_max_evals)
252 return cpp
253
254
255@dataclass(frozen=True)
257 """Options controlling confidence-contour computation.
258
259 Attributes:
260 profiling_method: How the two-dimensional contour fixes or profiles
261 parameters.
262 profile_backend: Backend used to evaluate profiled NLL points.
263 primary_contour_method: Contour extraction algorithm.
264 fallback_contour_method: Optional fallback algorithm used by the C++
265 engine if the primary extraction fails.
266 resolution: Resolution parameter passed to the contour extractor.
267
268 Examples:
269 >>> opts = ContourOptions(profile_backend=ProfilerMode.LAPLACE_NUISANCE, resolution=60)
270 """
271
272 profiling_method: ProfilingMethod = ProfilingMethod.SLICE
273 profile_backend: ProfilerMode = ProfilerMode.LAPLACE_NUISANCE
274 primary_contour_method: ContourAlgorithm = ContourAlgorithm.MINUIT
275 fallback_contour_method: Optional[ContourAlgorithm] = None
276 resolution: int = 40
277
278 @classmethod
279 def from_cpp(cls, cpp_obj) -> "ContourOptions":
280 """Create Python contour options from C++ options."""
281 return cls(
282 profiling_method=ProfilingMethod(cpp_obj.profiling_method),
283 profile_backend=ProfilerMode(cpp_obj.profile_backend),
284 primary_contour_method=ContourAlgorithm(cpp_obj.primary_contour_method),
285 fallback_contour_method=None
286 if cpp_obj.fallback_contour_method is None
287 else ContourAlgorithm(cpp_obj.fallback_contour_method),
288 resolution=int(cpp_obj.resolution),
289 )
290
291 def to_cpp(self):
292 """Convert this contour options object to C++."""
293 cpp = st.ContourOptions()
294 cpp.profiling_method = _cpp_profiling_method(self.profiling_method)
295 cpp.profile_backend = _cpp_profile_backend(self.profile_backend)
296 cpp.primary_contour_method = _cpp_contour_algorithm(self.primary_contour_method)
297 cpp.fallback_contour_method = (
298 None
301 )
302 cpp.resolution = int(self.resolution)
303 return cpp
304
305
307 """Python view of a C++ confidence contour.
308
309 The bound object exposes one or more disconnected paths. Each path is a
310 sequence of ``(x, y)`` points in the plane of the two displayed fit
311 parameters.
312 """
313
314 __slots__ = ("_cpp_obj",)
315
316 def __init__(self, cpp_obj) -> None:
317 self._cpp_obj = cpp_obj
318
319 @classmethod
320 def from_cpp(cls, cpp_obj) -> "Contour":
321 """Create a Python contour view from a bound C++ contour object."""
322 return cls(cpp_obj)
323
324 @property
325 def paths(self) -> List[List[tuple[float, float]]]:
326 """Return all disconnected contour paths as ``(x, y)`` coordinates."""
327 return [
328 [(float(point[0]), float(point[1])) for point in path] for path in self._cpp_obj.paths
329 ]
330
331 @property
332 def level(self) -> float:
333 """Return the likelihood or confidence level represented by the contour."""
334 return float(self._cpp_obj.level)
335
336 @property
337 def success(self) -> bool:
338 """Report whether the native contour extraction completed successfully."""
339 return bool(self._cpp_obj.success)
340
341 def _to_cpp(self):
342 """Return the underlying C++ contour object."""
343 return self._cpp_obj
344
345 def __repr__(self) -> str:
346 return f"Contour(success={self.success}, level={self.level:.6g}, paths={len(self.paths)})"
347
348
349@dataclass(frozen=True)
351 """One grid point of a two-dimensional likelihood scan.
352
353 Attributes:
354 x: Value of the first scanned parameter.
355 y: Value of the second scanned parameter.
356 nll: Absolute negative log-likelihood value.
357 delta_nll: Difference with respect to the scan reference point.
358 """
359
360 x: float = 0.0
361 y: float = 0.0
362 nll: float = 0.0
363 delta_nll: float = 0.0
364
365 @classmethod
366 def from_cpp(cls, cpp_obj) -> "LikelihoodScanPoint":
367 """Create a Python scan point from a bound C++ scan point."""
368 return cls(
369 x=float(cpp_obj.x),
370 y=float(cpp_obj.y),
371 nll=float(cpp_obj.nll),
372 delta_nll=float(cpp_obj.delta_nll),
373 )
374
375 def to_cpp(self):
376 """Convert this scan point to C++."""
377 cpp = st.LikelihoodScanPoint()
378 cpp.x = float(self.x)
379 cpp.y = float(self.y)
380 cpp.nll = float(self.nll)
381 cpp.delta_nll = float(self.delta_nll)
382 return cpp
383
384
385@dataclass(frozen=True)
387 """Rectangular two-dimensional likelihood scan result.
388
389 Attributes:
390 x_param: First scanned fit parameter.
391 y_param: Second scanned fit parameter.
392 x_center: Reference value used to center the scan in x.
393 y_center: Reference value used to center the scan in y.
394 nx: Number of x grid points.
395 ny: Number of y grid points.
396 points: Flattened list of scan points.
397 """
398
399 x_param: ParamId
400 y_param: ParamId
401 x_center: float = 0.0
402 y_center: float = 0.0
403 nx: int = 0
404 ny: int = 0
405 points: List[LikelihoodScanPoint] = field(default_factory=list)
406
407 def __post_init__(self) -> None:
408 """Validate scan parameter identifiers."""
409 _require(self.x_paramx_param, ParamId, "x_param")
410 _require(self.y_paramy_param, ParamId, "y_param")
411
412 @classmethod
413 def from_cpp(cls, cpp_obj) -> "LikelihoodScanGrid":
414 """Create a Python scan grid from the bound C++ result."""
415 return cls(
416 x_param=_param_from_cpp(cpp_obj.x_param),
417 y_param=_param_from_cpp(cpp_obj.y_param),
418 x_center=float(cpp_obj.x_center),
419 y_center=float(cpp_obj.y_center),
420 nx=int(cpp_obj.nx),
421 ny=int(cpp_obj.ny),
422 points=[LikelihoodScanPoint.from_cpp(p) for p in cpp_obj.points],
423 )
424
425 def to_cpp(self):
426 """Convert this scan grid to the bound C++ representation."""
427 cpp = st.LikelihoodScanGrid()
428 cpp.x_param = _cpp_param_id(self.x_paramx_param)
429 cpp.y_param = _cpp_param_id(self.y_paramy_param)
430 cpp.x_center = float(self.x_center)
431 cpp.y_center = float(self.y_center)
432 cpp.nx = int(self.nx)
433 cpp.ny = int(self.ny)
434 cpp.points = [p.to_cpp() for p in self.points]
435 return cpp
436
437
439 """High-level Python facade for statistical fits and scans.
440
441 Args:
442 config: Statistic configuration. Python-only fields such as ``p_specs``
443 and ``selected_experiments`` are used by this wrapper.
444 observable_interface: Observable interface already configured with the
445 observables to compute.
446
447 Examples:
448 A typical workflow is::
449
450 stat_cfg = StatisticConfig(MC_draws=1000)
451 stat_cfg.p_specs = [my_param_id]
452 stat = StatisticInterface(stat_cfg, observable_interface=obs_int)
453
454 fit = stat.compute_MLE()
455 print(fit.fit_ok, fit.p_hat)
456
457 contour = stat.compute_confidence_contour(
458 p1=my_param_id,
459 p2=other_param_id,
460 z=1.0,
461 bounds=[-1.0, 1.0, -1.0, 1.0],
462 )
463 """
464
465 def __init__(self, config: StatisticConfig, observable_interface: ObservableInterface) -> None:
466 """Create the C++ statistic interface and apply initial selections."""
467 _require(config, StatisticConfig, "config")
468 _require(observable_interface, ObservableInterface, "observable_interface")
469 self.config = config
470 self._cpp = st.StatisticInterface(config.to_cpp(), observable_interface._to_cpp())
471 # if config.selected_experiments is not None:
472 # self.select_experiments(config.selected_experiments)
473
474 def _to_cpp(self):
475 """Return the underlying C++ statistic interface."""
476 return self._cpp
477
478 def select_experiment(self, experiment: str) -> None:
479 """Restrict the statistic manager to one experiment.
480
481 Args:
482 experiment: Experiment name as stored in the experimental database.
483 """
484 self._cpp.select_experiment(str(experiment))
485
486 def select_experiments(self, experiments: Sequence[str]) -> None:
487 """Restrict the statistic manager to a set of experiments.
488
489 Args:
490 experiments: Experiment names to keep.
491 """
492 self._cpp.select_experiments([str(x) for x in experiments])
493
494 def select_experiments_all(self) -> None:
495 """Clear any experiment selection and use all available experiments."""
497
498 def has_experiment_selection(self) -> bool:
499 """Return whether an experiment filter is currently active."""
500 return bool(self._cpp.has_experiment_selection())
501
502 def selected_experiments(self) -> List[str]:
503 """Return the currently selected experiment names."""
504 return [str(x) for x in self._cpp.selected_experiments()]
505
506 def select_experiment_observables(self, observables: Sequence[ExperimentObs]) -> None:
507 """Restrict statistics to exact experiment/observable/bin entries.
508
509 Args:
510 observables: Exact measurements to retain. Each item combines an
511 experiment label with a binned observable identifier.
512 """
513 self._cpp.select_experiment_observables([_cpp_experiment_obs(obs) for obs in observables])
514
516 """Clear the exact experimental-observable selection."""
518
520 """Return whether an exact measurement filter is active."""
521 return bool(self._cpp.has_experiment_observable_selection())
522
523 def selected_experiment_observables(self) -> List[ExperimentObs]:
524 """Return the active exact measurement selection."""
525 return [ExperimentObs.from_cpp(obs) for obs in self._cpp.selected_experiment_observables()]
526
527 def reload_nuisance_specs(self) -> None:
528 """Reload default and user nuisance specifications in the C++ manager."""
530
531 def set_nuisance_user_file(self, user_yaml_path: str | Path) -> None:
532 """Use a custom user nuisance-specification file.
533
534 Args:
535 user_yaml_path: Path to the YAML file overriding default nuisance
536 specifications.
537 """
538 self._cpp.set_nuisance_user_file(str(user_yaml_path))
539
540 def clear_nuisance_user_file(self) -> None:
541 """Return to the default user nuisance-specification path."""
543
544 def update_cache(self, p_specs: Optional[Sequence[ParamId]] = None) -> None:
545 """Refresh the C++ statistic cache.
546
547 Args:
548 p_specs: Fit parameters to detach from the nuisance set. When
549 omitted, ``self.config.p_specs`` is used.
550 """
551 p_specs = self.config.p_specs if p_specs is None else p_specs
552 self._cpp.update_cache([_cpp_param_id(p) for p in p_specs])
553
554 def compute_uncertainties(self) -> Dict[BinnedObservableId, GaussianSummary]:
555 """Propagate nuisance uncertainties and return Gaussian summaries.
556
557 Returns:
558 Mapping from observable id to Gaussian or split-Gaussian summary.
559 """
560 return {
561 BinnedObservableId.from_cpp(k): GaussianSummary.from_cpp(v)
562 for k, v in self._cpp.compute_uncertainties().items()
563 }
564
566 """Run Monte-Carlo uncertainty propagation and keep raw samples.
567
568 Returns:
569 ``MCResult`` containing raw samples, summaries and covariance.
570 """
571 return MCResult.from_cpp(self._cpp.compute_uncertainties_and_sampling())
572
573 def compute_MLE(self, p_specs: Optional[Sequence[ParamId]] = None) -> FitResultWithMaps:
574 """Compute the maximum-likelihood estimate for selected fit parameters.
575
576 Args:
577 p_specs: Fit parameters. When omitted, ``self.config.p_specs`` is
578 used.
579
580 Returns:
581 Fit result containing best-fit values, profiled nuisances,
582 uncertainties, correlations and minimum NLL.
583
584 Raises:
585 Exception: Propagates C++ errors raised by cache construction,
586 likelihood evaluation or minimization.
587 """
588 p_specs = self.config.p_specs if p_specs is None else p_specs
589 return FitResultWithMaps.from_cpp(
590 self._cpp.compute_MLE([_cpp_param_id(p) for p in p_specs])
591 )
592
594 self,
595 p1: ParamId,
596 p2: ParamId,
597 z: float,
598 bounds: Sequence[float],
599 options: Optional[ContourOptions] = None,
600 ) -> Contour:
601 """Compute a two-dimensional confidence contour.
602
603 Args:
604 p1: First fit parameter.
605 p2: Second fit parameter.
606 z: Gaussian-equivalent confidence level, for example ``1.0`` for an
607 approximate one-sigma contour.
608 bounds: Four values ``[xmin, xmax, ymin, ymax]`` defining the search
609 rectangle.
610 options: Optional contour/profiling options. Defaults to
611 ``ContourOptions()``.
612
613 Returns:
614 ``Contour`` wrapper exposing ``paths``, ``level`` and ``success``.
615
616 Raises:
617 ValueError: If ``bounds`` does not contain exactly four values.
618 Exception: Propagates backend failures, for example if MLE has not
619 been computed before requesting the contour.
620 """
621 if len(bounds) != 4:
622 raise ValueError("bounds must contain exactly 4 values: [xmin, xmax, ymin, ymax].")
623 cpp_options = (
624 ContourOptions().to_cpp()
625 if options is None
626 else _require(options, ContourOptions, "options").to_cpp()
627 )
628 return Contour.from_cpp(
630 _cpp_param_id(p1),
631 _cpp_param_id(p2),
632 float(z),
633 [float(x) for x in bounds],
634 cpp_options,
635 )
636 )
637
638 def prepare_likelihood_for_scan(self, p_specs: Optional[Sequence[ParamId]] = None) -> None:
639 """Prepare and cache a likelihood object for manual two-dimensional scans.
640
641 Args:
642 p_specs: Fit parameters used by the likelihood. Defaults to
643 ``self.config.p_specs``.
644 """
645 p_specs = self.config.p_specs if p_specs is None else p_specs
646 self._cpp.prepare_likelihood_for_scan([_cpp_param_id(p) for p in p_specs])
647
649 self, p_hat: Mapping[ParamId, float], eta_hat: Mapping[ParamId, float]
650 ) -> None:
651 """Override the scan reference point manually.
652
653 Args:
654 p_hat: Fit-parameter values used as scan center/reference.
655 eta_hat: Nuisance values used as scan reference.
656 """
659 )
660
662 self,
663 p1: ParamId,
664 p2: ParamId,
665 x_half_width: float,
666 y_half_width: float,
667 nx: int,
668 ny: int,
669 ) -> LikelihoodScanGrid:
670 """Evaluate the likelihood on a rectangular grid around the current point.
671
672 Args:
673 p1: First scanned parameter.
674 p2: Second scanned parameter.
675 x_half_width: Half-width of the scan range in the first parameter.
676 y_half_width: Half-width of the scan range in the second parameter.
677 nx: Number of grid points in the x direction.
678 ny: Number of grid points in the y direction.
679
680 Returns:
681 A ``LikelihoodScanGrid`` containing all evaluated points.
682 """
683 return LikelihoodScanGrid.from_cpp(
685 _cpp_param_id(p1),
686 _cpp_param_id(p2),
687 float(x_half_width),
688 float(y_half_width),
689 int(nx),
690 int(ny),
691 )
692 )
693
694 def save_likelihood_scan_csv(self, path: str | Path, grid: LikelihoodScanGrid) -> None:
695 """Save a likelihood scan grid to a CSV file.
696
697 Args:
698 path: Output CSV path.
699 grid: Scan grid returned by :meth:`scan_likelihood_around_current_point`.
700 """
702 str(path), _require(grid, LikelihoodScanGrid, "grid").to_cpp()
703 )
704
705 def get_all_obss_deps(self) -> Dict[ParamId, float]:
706 """Return all selected observable dependencies after nuisance pruning."""
708
709 def get_active_observable_dependencies(self) -> Dict[ParamId, float]:
710 """Return dependencies currently visible to the Statistic manager.
711
712 This method is useful for runtime/lambda observables: it verifies that
713 dependencies declared on ``LambdaObservableConfig`` and propagated from
714 custom Wilson lambdas reached the statistic layer.
715 """
717
718 def get_p_specs(self, p_specs: Optional[Sequence[ParamId]] = None) -> Dict[ParamId, float]:
719 """Return central values for selected fit parameters.
720
721 Args:
722 p_specs: Fit parameters to query. Defaults to ``self.config.p_specs``.
723 """
724 p_specs = self.config.p_specs if p_specs is None else p_specs
725 return _param_float_map_from_cpp(self._cpp.get_p_specs([_cpp_param_id(p) for p in p_specs]))
726
727 def get_all_correlations(self) -> Dict[ParamId, Dict[ParamId, float]]:
728 """Return the nuisance correlation matrix as nested parameter maps."""
730
731 def get_all_obs_correlations(self) -> Dict[ExperimentObs, Dict[ExperimentObs, float]]:
732 """Return experimental-observable correlations as nested maps."""
734
735 def get_obs_exp(self) -> Dict[ExperimentObs, float]:
736 """Return experimental central values used by the statistic manager."""
738
739 def print_cache(self) -> None:
740 """Print the current C++ statistic cache for debugging."""
741 self._cpp.print_cache()
742
743
744__all__ = [
745 "StatisticInterface",
746 "FitResultWithMaps",
747 "Contour",
748 "ContourOptions",
749 "ProfilingMethod",
750 "ContourAlgorithm",
751 "ProfilerMode",
752 "ProfileBackend",
753 "MLFitOptions",
754 "LikelihoodScanPoint",
755 "LikelihoodScanGrid",
756]
"ContourOptions" from_cpp(cls, cpp_obj)
"Contour" from_cpp(cls, cpp_obj)
List[List[tuple[float, float]]] paths(self)
"FitResultWithMaps" from_cpp(cls, cpp_obj)
"LikelihoodScanGrid" from_cpp(cls, cpp_obj)
"LikelihoodScanPoint" from_cpp(cls, cpp_obj)
"MLFitOptions" from_cpp(cls, cpp_obj)
Dict[ExperimentObs, float] get_obs_exp(self)
None set_manual_scan_point(self, Mapping[ParamId, float] p_hat, Mapping[ParamId, float] eta_hat)
FitResultWithMaps compute_MLE(self, Optional[Sequence[ParamId]] p_specs=None)
Dict[ParamId, float] get_all_obss_deps(self)
Contour compute_confidence_contour(self, ParamId p1, ParamId p2, float z, Sequence[float] bounds, Optional[ContourOptions] options=None)
Dict[ParamId, float] get_active_observable_dependencies(self)
Dict[ExperimentObs, Dict[ExperimentObs, float]] get_all_obs_correlations(self)
None set_nuisance_user_file(self, str|Path user_yaml_path)
LikelihoodScanGrid scan_likelihood_around_current_point(self, ParamId p1, ParamId p2, float x_half_width, float y_half_width, int nx, int ny)
None update_cache(self, Optional[Sequence[ParamId]] p_specs=None)
None prepare_likelihood_for_scan(self, Optional[Sequence[ParamId]] p_specs=None)
Dict[ParamId, Dict[ParamId, float]] get_all_correlations(self)
None __init__(self, StatisticConfig config, ObservableInterface observable_interface)
None select_experiment_observables(self, Sequence[ExperimentObs] observables)
None select_experiment(self, str experiment)
Dict[ParamId, float] get_p_specs(self, Optional[Sequence[ParamId]] p_specs=None)
None select_experiments(self, Sequence[str] experiments)
Dict[BinnedObservableId, GaussianSummary] compute_uncertainties(self)
List[ExperimentObs] selected_experiment_observables(self)
None save_likelihood_scan_csv(self, str|Path path, LikelihoodScanGrid grid)
_cpp_profile_backend(ProfilerMode value)
Dict[ExperimentObs, float] _experiment_float_map_from_cpp(cpp_map)
ParamId _param_from_cpp(cpp_obj)
_require(value, typ, str name)
_param_float_map_to_cpp(Mapping[ParamId, float] values)
Dict[ParamId, Dict[ParamId, float]] _param_nested_float_map_from_cpp(cpp_map)
_cpp_experiment_obs(ExperimentObs obs)
_cpp_profiling_method(ProfilingMethod value)
Dict[ParamId, float] _param_float_map_from_cpp(cpp_map)
_cpp_contour_algorithm(ContourAlgorithm value)
Dict[ExperimentObs, Dict[ExperimentObs, float]] _experiment_nested_float_map_from_cpp(cpp_map)