1"""Python wrapper for the observable computation interface.
3This module exposes a Pythonic facade over the C++ ``ObservableInterface``.
4It lets users select observables, compute theory predictions, inspect
5experimental inputs, manage observable dependencies, and update parameters
6used by the observable layer.
8The public API accepts and returns Python wrapper objects only. Conversion to
9and from the pybind11 objects is intentionally kept inside this module.
12 >>> from pyhyperiso.core.BusinessLogic.ObservableInterface import ObservableInterface
13 >>> from pyhyperiso.core.Common.GeneralEnum import Observables, QCDOrder
14 >>> oi = ObservableInterface()
15 >>> oi.add_observable(Observables.BR_BS_MUMU, QCDOrder.NNLO, add_dependencies=True)
16 >>> values = oi.compute_observable(Observables.BR_BS_MUMU)
17 >>> central = oi.compute_observable_central(Observables.BR_BS_MUMU)
20from __future__
import annotations
22from typing
import Dict, List, Mapping, Sequence, Set, Tuple
24from pyhyperiso.phyperiso.pyhyperiso.observable
import (
25 ObservableInterface
as _CppObservableInterface,
44 if not isinstance(value, typ):
45 raise TypeError(f
"{name} must be {typ.__name__}, received {type(value)!r}.")
50 return _require(obs, Observables,
"obs").value
54 return _require(obs, ObservableId,
"obs")._to_cpp()
58 return _require(obs, BinnedObservableId,
"obs").to_cpp()
61def _cpp_bin(bin_range: Sequence[float]) -> Tuple[float, float]:
62 if not (isinstance(bin_range, (tuple, list))
and len(bin_range) == 2):
63 raise TypeError(
"bin must be un tuple/list (q2_min, q2_max).")
64 q2_min = float(bin_range[0])
65 q2_max = float(bin_range[1])
67 raise ValueError(
"bin must satisfy q2_min < q2_max.")
68 return (q2_min, q2_max)
72 return _require(order, QCDOrder,
"qcd_order").value
76 return _require(decay, Decays,
"decay").value
80 return _require(u_type, UncertaintyType,
"u_type").value
84 return _require(pid, ParamId,
"pid").to_cpp()
88 return ParamId.from_cpp(cpp_obj)
93 parts = code.get_parts()
96 "set_param/get_param via ObservableInterface requires a single-entry LhaID."
102 """High-level API used to configure and compute observables.
104 ``ObservableInterface`` mirrors the C++ class of the same name. It owns an
105 underlying C++ observable manager and exposes a chainable Python API for
108 * register observables by enum, by internal ``ObservableId`` or by binned id;
109 * compute one observable or all registered observables;
110 * inspect experimental central values and uncertainties;
111 * add explicit parameter dependencies for likelihood/statistical workflows;
112 * set or read model parameters through the global parameter store.
115 ``add_dependencies=True`` asks the C++ observable manager to attach the
116 full dependency allow-list known for the selected observable. This is
117 important for subsequent statistical workflows, because those
118 dependencies are used to decide which nuisance parameters should be
119 propagated or profiled.
121 The QCD order is passed to the C++ decay computation. Depending on the
122 backend and observable, the actual Wilson-coefficient order may be
123 limited by backend capabilities.
126 >>> oi = ObservableInterface()
127 >>> oi.add_observable(Observables.BR_BS_MUMU, QCDOrder.NNLO, True)
128 >>> oi.get_current_observables()
130 >>> oi.compute_observable_central(Observables.BR_BS_MUMU)
135 """Create a new observable interface backed by a fresh C++ manager."""
139 self, config: LambdaDecayConfig, add_observables: bool =
True
140 ) ->
"ObservableInterface":
141 """Register a lambda-backed custom decay and its observables.
144 config: Runtime decay configuration containing custom observables and
145 optionally custom Wilson groups.
146 add_observables: If ``True``, select every observable declared in
147 ``config`` immediately.
150 ``self`` for fluent chaining.
152 if isinstance(config, LambdaDecayConfig):
153 config = config.to_cpp()
158 def from_cpp(cls, cpp_obj) -> "ObservableInterface":
159 """Wrap an existing C++ ``ObservableInterface`` instance.
162 cpp_obj: Bound C++ object produced by pybind11.
165 A Python ``ObservableInterface`` sharing ownership of ``cpp_obj``.
167 inst = cls.__new__(cls)
168 inst._cpp_obj = cpp_obj
172 """Return the underlying pybind11 object.
175 The wrapped C++ ``ObservableInterface`` object.
183 add_dependencies: bool =
True,
184 ) ->
"ObservableInterface":
185 """Register an observable using the public observable enum.
188 obs: Observable enum value to register.
189 qcd_order: Maximum QCD order requested for the corresponding decay.
190 add_dependencies: Whether to also attach the C++ dependency
191 allow-list for the observable.
194 ``self`` to support fluent chaining.
205 add_dependencies: bool =
True,
206 ) ->
"ObservableInterface":
207 """Register an observable using an internal ``ObservableId``.
210 obs: Internal observable identifier.
211 qcd_order: Maximum QCD order requested for the corresponding decay.
212 add_dependencies: Whether to also attach known parameter
213 dependencies for this observable.
216 ``self`` to support fluent chaining.
225 obs: BinnedObservableId,
227 add_dependencies: bool =
True,
228 ) ->
"ObservableInterface":
229 """Register a single binned observable.
232 obs: Binned observable identifier, including the observable id and
234 qcd_order: Maximum QCD order requested for the corresponding decay.
235 add_dependencies: Whether to also attach known parameter
236 dependencies for this observable.
239 ``self`` to support fluent chaining.
248 obs_names: Mapping[Observables, QCDOrder],
249 add_dependencies: bool =
True,
250 ) ->
"ObservableInterface":
251 """Register several enum-based observables at once.
254 obs_names: Mapping from observable enum values to requested QCD
256 add_dependencies: Whether to attach dependencies for every
257 observable in the mapping.
260 ``self`` to support fluent chaining.
263 >>> oi.add_observables({
264 ... Observables.BR_BS_MUMU: QCDOrder.NNLO,
265 ... Observables.BR_BD_MUMU: QCDOrder.NNLO,
266 ... }, add_dependencies=True)
274 obs_names: Mapping[ObservableId, QCDOrder],
275 add_dependencies: bool =
True,
276 ) ->
"ObservableInterface":
277 """Register several id-based observables at once.
280 obs_names: Mapping from internal observable ids to requested QCD
282 add_dependencies: Whether to attach dependencies for every
283 observable in the mapping.
286 ``self`` to support fluent chaining.
296 add_dependencies: bool =
True,
297 bin: Tuple[float, float] = (1.0, 6.0),
298 ) ->
"ObservableInterface":
299 """Register every observable attached to a decay channel.
302 decay: Decay family whose observables should be registered.
303 qcd_order: Maximum QCD order requested for all observables in the
305 add_dependencies: Whether to attach dependencies for the selected
307 bin: q² interval used only for observables that require bins. Mixed
308 decays such as ``B -> K* ll`` keep their non-binned observables
312 ``self`` to support fluent chaining.
317 bool(add_dependencies),
323 """Return whether a decay has at least one q²-binned observable.
326 decay: Decay family to inspect.
329 ``True`` if at least one observable in the decay requires a q² bin.
334 """Return whether an enum observable requires a q² bin.
337 obs: Observable enum value to inspect.
340 ``True`` if the observable must be added/computed as binned.
345 """Return whether an internal observable id requires a q² bin."""
349 """Add one explicit parameter dependency to an enum observable.
352 obs: Observable enum value.
353 pid: Parameter identifier to mark as a dependency.
356 ``self`` to support fluent chaining.
362 """Add one explicit parameter dependency to an id-based observable.
365 obs: Internal observable identifier.
366 pid: Parameter identifier to mark as a dependency.
369 ``self`` to support fluent chaining.
375 self, obs: Observables, pids: Set[ParamId]
376 ) ->
"ObservableInterface":
377 """Add several dependencies to an enum observable.
380 obs: Observable enum value.
381 pids: Set of parameter identifiers to attach.
384 ``self`` to support fluent chaining.
392 self, obs: ObservableId, pids: Set[ParamId]
393 ) ->
"ObservableInterface":
394 """Add several dependencies to an id-based observable.
397 obs: Internal observable identifier.
398 pids: Set of parameter identifiers to attach.
401 ``self`` to support fluent chaining.
409 """Remove one enum observable from the current selection.
412 obs: Observable enum value to remove.
415 ``self`` to support fluent chaining.
421 """Remove one id-based observable from the current selection.
424 obs: Internal observable identifier to remove.
427 ``self`` to support fluent chaining.
433 """Remove several enum observables from the current selection.
436 ids: Set of observable enum values to remove.
439 ``self`` to support fluent chaining.
445 """Remove every selected observable associated with a decay family.
448 decay: Decay family whose observables should be removed.
451 ``self`` to support fluent chaining.
457 """Compute the theory prediction for one enum observable.
460 obs: Observable enum value to compute.
463 List of observable values. Unbinned observables usually return a
464 single entry. Binned observables return one entry per bin.
467 ObservableValue.from_cpp(v)
472 """Compute the theory prediction for one id-based observable.
475 obs: Internal observable identifier to compute.
478 List of observable values. Binned observables can contain multiple
479 entries with non-null ``ObservableValue.bin``.
482 ObservableValue.from_cpp(v)
487 """Compute one observable at one explicit q² bin.
490 obs: Binned observable identifier.
493 The C++ ``ObservableValue`` for the requested bin.
495 return ObservableValue.from_cpp(
500 """Compute a scalar central value for an unbinned enum observable.
503 obs: Observable enum value to compute.
506 The single predicted value.
509 RuntimeError: If the C++ layer returns no value or more than one
510 value, which indicates that the observable is binned and should
511 be read with :meth:`compute_observable`.
515 raise RuntimeError(
"compute_observable returned an empty list.")
517 raise RuntimeError(
"Binned observable: use compute_observable() to retrieve all bins.")
521 """Compute a scalar central value for an unbinned id-based observable.
524 obs: Internal observable identifier to compute.
527 The single predicted value.
530 RuntimeError: If the C++ layer returns no value or more than one
531 value, which indicates that the observable is binned and should
532 be read with :meth:`compute_observable_id`.
536 raise RuntimeError(
"compute_observable_id returned an empty list.")
539 "Binned observable: use compute_observable_id() to retrieve all bins."
543 def compute_all(self) -> Dict[ObservableId, List[ObservableValue]]:
544 """Compute all currently registered observables.
547 Mapping from observable id to a list of predicted values. Each list
548 may contain several bins.
552 ObservableId(str(cpp_id)): [ObservableValue.from_cpp(v)
for v
in values]
553 for cpp_id, values
in cpp_map.items()
557 """Return the experimental central value for an enum observable.
560 obs: Observable enum value.
563 Experimental central value stored in the C++ observable database.
568 """Return the experimental central value for an id-based observable.
571 obs: Internal observable identifier.
574 Experimental central value stored in the C++ observable database.
579 """Return the experimental central value for one binned observable."""
585 u_type: UncertaintyType = UncertaintyType.COMBINED,
587 """Return an experimental uncertainty for an enum observable.
590 obs: Observable enum value.
591 u_type: Type of uncertainty to retrieve. The default is the combined
595 Requested experimental uncertainty.
606 u_type: UncertaintyType = UncertaintyType.COMBINED,
608 """Return an experimental uncertainty for an id-based observable.
611 obs: Internal observable identifier.
612 u_type: Type of uncertainty to retrieve. The default is the combined
616 Requested experimental uncertainty.
626 obs: BinnedObservableId,
627 u_type: UncertaintyType = UncertaintyType.COMBINED,
629 """Return an experimental uncertainty for one binned observable."""
637 """Return the currently registered observable/bin identifiers.
640 List of ``BinnedObservableId`` objects currently selected in the
641 C++ observable manager.
646 """Return all known parameter dependencies for an enum observable.
649 obs: Observable enum value.
652 Set of parameter identifiers allowed as dependencies for this
660 """Return all known parameter dependencies for an id-based observable.
663 obs: Internal observable identifier.
666 Set of parameter identifiers allowed as dependencies for this
672 """Set the concrete configuration object used by one decay engine.
675 decay: Decay family to configure.
676 config: Python decay-configuration wrapper. The concrete wrapper class
677 must match the selected decay engine, for example ``BKllConfig`` for
678 ``Decays.B__K_l_l`` or ``BKstarllConfig`` for
679 ``Decays.B__Kstar_l_l``.
682 ``self`` to support fluent chaining.
684 if not isinstance(config, DecayConfig):
685 raise TypeError(f
"config must inherit DecayConfig, got {type(config).__name__}.")
689 def set_param(self, pid: ParamId, value: float) ->
None:
690 """Set one model parameter in the C++ parameter store.
693 pid: Parameter identifier. ``pid.type`` must be defined and
694 ``pid.code`` must contain exactly one LHA code entry.
695 value: New parameter value.
698 ValueError: If ``pid.type`` is missing or if the LHA code is not a
700 TypeError: If ``pid`` or ``pid.type`` has an invalid Python type.
703 After mutating parameters, call :meth:`reload_params` and possibly
704 :meth:`enable_obs` if the selected decays cache input parameters.
708 raise ValueError(
"pid.type must be defined for set_param().")
709 _require(pid.type, ParameterType,
"pid.type")
715 """Read one model parameter from the C++ parameter store.
718 pid: Parameter identifier. ``pid.type`` must be defined and
719 ``pid.code`` must contain exactly one LHA code entry.
722 Parameter value returned by the C++ parameter provider.
725 ValueError: If ``pid.type`` is missing or if the LHA code is not a
727 TypeError: If ``pid`` or ``pid.type`` has an invalid Python type.
731 raise ValueError(
"pid.type must be defined before calling get_param().")
732 _require(pid.type, ParameterType,
"pid.type")
736 """Reload cached parameters for every registered decay.
738 This forwards to the C++ manager and is typically used after calling
744 """Force the C++ manager to enable/re-enable selected observables.
746 This can rebuild or refresh the internal observable state after a larger
747 configuration change.
752 """Configure the number of threads used by a decay that supports it.
755 decay: Decay family whose decay engine should receive the thread setting.
756 n_threads: Number of worker threads requested by the C++ decay
757 implementation. ``0`` delegates to hardware concurrency for
763 """Configure the number of threads used by the ``B -> K* ll`` decay.
766 n_threads: Number of worker threads requested by the C++ decay
772 """Configure the number of threads used by the ``B -> K ll`` decay.
775 n_threads: Number of worker threads requested by the C++ decay
781 """Configure the number of threads used by the ``Bs -> phi ll`` decay.
784 n_threads: Number of worker threads requested by the C++ decay
790 """Configure the number of threads used by the ``Lambda_b -> Lambda ll`` decay.
793 n_threads: Number of worker threads requested by the C++ decay
799__all__ = [
"ObservableInterface"]
Dict[ObservableId, List[ObservableValue]] compute_all(self)
List[BinnedObservableId] get_current_observables(self)
"ObservableInterface" remove_observables_from_decay(self, Decays decay)
bool is_observable_id_binned(self, ObservableId obs)
bool is_observable_binned(self, Observables obs)
bool is_decay_binned(self, Decays decay)
"ObservableInterface" add_lambda_decay(self, LambdaDecayConfig config, bool add_observables=True)
"ObservableInterface" remove_observable(self, Observables obs)
"ObservableInterface" add_observables_from_decay(self, Decays decay, QCDOrder qcd_order, bool add_dependencies=True, Tuple[float, float] bin=(1.0, 6.0))
"ObservableInterface" remove_observable_id(self, ObservableId obs)
"ObservableInterface" add_observable_id_parameters(self, ObservableId obs, Set[ParamId] pids)
"ObservableInterface" add_observable_id(self, ObservableId obs, QCDOrder qcd_order, bool add_dependencies=True)
Set[ParamId] get_all_ops_deps(self, Observables obs)
Set[ParamId] get_all_ops_deps_id(self, ObservableId obs)
float get_exp_uncertainty(self, Observables obs, UncertaintyType u_type=UncertaintyType.COMBINED)
float compute_observable_id_central(self, ObservableId obs)
None set_bsphi_threads(self, int n_threads)
"ObservableInterface" add_observable_parameters(self, Observables obs, Set[ParamId] pids)
"ObservableInterface" add_observable_id_parameter(self, ObservableId obs, ParamId pid)
None set_bkll_threads(self, int n_threads)
"ObservableInterface" add_binned_observable(self, BinnedObservableId obs, QCDOrder qcd_order, bool add_dependencies=True)
"ObservableInterface" add_observable_ids(self, Mapping[ObservableId, QCDOrder] obs_names, bool add_dependencies=True)
"ObservableInterface" remove_observables(self, Set[Observables] ids)
get_param(self, ParamId pid)
"ObservableInterface" add_observables(self, Mapping[Observables, QCDOrder] obs_names, bool add_dependencies=True)
None set_lblll_threads(self, int n_threads)
None set_param(self, ParamId pid, float value)
"ObservableInterface" add_observable_parameter(self, Observables obs, ParamId pid)
List[ObservableValue] compute_observable(self, Observables obs)
float get_exp_value(self, Observables obs)
None set_decay_threads(self, Decays decay, int n_threads)
None set_bkstarll_threads(self, int n_threads)
ObservableValue compute_binned_observable(self, BinnedObservableId obs)
float get_exp_uncertainty_id(self, ObservableId obs, UncertaintyType u_type=UncertaintyType.COMBINED)
"ObservableInterface" add_observable(self, Observables obs, QCDOrder qcd_order, bool add_dependencies=True)
"ObservableInterface" from_cpp(cls, cpp_obj)
"ObservableInterface" set_decay_config(self, Decays decay, DecayConfig config)
float get_exp_value_id(self, ObservableId obs)
float compute_observable_central(self, Observables obs)
float get_exp_uncertainty_binned(self, BinnedObservableId obs, UncertaintyType u_type=UncertaintyType.COMBINED)
float get_exp_value_binned(self, BinnedObservableId obs)
List[ObservableValue] compute_observable_id(self, ObservableId obs)
_cpp_observable_enum(Observables obs)
_cpp_param_id(ParamId pid)
_cpp_uncertainty_type(UncertaintyType u_type)
_cpp_binned_observable_id(BinnedObservableId obs)
ParamId _param_from_cpp(cpp_obj)
_require(value, typ, str name)
_cpp_qcd_order(QCDOrder order)
_cpp_observable_id(ObservableId obs)
int _single_lha_code(LhaID code)
Tuple[float, float] _cpp_bin(Sequence[float] bin_range)