Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
HyperisoMaster.py
Go to the documentation of this file.
1"""High-level controller for initializing and switching Hyperiso sessions."""
2
3from __future__ import annotations
4
5import os
6import tempfile
7from enum import Enum
8from importlib import resources
9from pathlib import Path
10from typing import Any, Mapping, Optional, Union
11
12from pyhyperiso.phyperiso.pyhyperiso.core import APIPath as _CppAPIPath
13from pyhyperiso.phyperiso.pyhyperiso.core import HyperisoMaster as _CppHyperisoMaster
15from pyhyperiso.core.Core.HyperisoConfig import ExternalFlag, HyperisoConfig
16
17PathLike = Union[str, os.PathLike[str]]
18
19
20class APIPath(Enum):
21 """Filesystem path keys accepted by ``HyperisoMaster.pre_init_set_paths``.
22
23 ``LHA_PATH`` is exposed for read-only diagnostics through ``APIAdapter`` but
24 is intentionally rejected by ``pre_init_set_paths`` because the active LHA
25 file is provided through ``init(...)`` or ``switch_lha(...)``.
26 """
27
28 LHA_PATH = _CppAPIPath.LHA_PATH
29 ASSETS_ROOT = _CppAPIPath.ASSETS_ROOT
30
31 DEFAULT_PARAM_VALUES = _CppAPIPath.DEFAULT_PARAM_VALUES
32 DEFAULT_OBS_VALUES = _CppAPIPath.DEFAULT_OBS_VALUES
33 DEFAULT_PARAM_CORR = _CppAPIPath.DEFAULT_PARAM_CORR
34 DEFAULT_OBS_CORR = _CppAPIPath.DEFAULT_OBS_CORR
35 DEFAULT_NUISANCES = _CppAPIPath.DEFAULT_NUISANCES
36
37 USER_SM_PARAMS = _CppAPIPath.USER_SM_PARAMS
38 USER_FLAVOR_PARAMS = _CppAPIPath.USER_FLAVOR_PARAMS
39 USER_DECAY_PARAMS = _CppAPIPath.USER_DECAY_PARAMS
40 USER_OBS_VALUES = _CppAPIPath.USER_OBS_VALUES
41 USER_PARAM_CORR = _CppAPIPath.USER_PARAM_CORR
42 USER_OBS_CORR = _CppAPIPath.USER_OBS_CORR
43 USER_NUISANCES = _CppAPIPath.USER_NUISANCES
44
45 PARAM_MAPPING_DIR = _CppAPIPath.PARAM_MAPPING_DIR
46 TEMPLATE_DIR = _CppAPIPath.TEMPLATE_DIR
47 SPECTRUM_DIR = _CppAPIPath.SPECTRUM_DIR
48 MARTY_TEMP_DIR = _CppAPIPath.MARTY_TEMP_DIR
49
50
52 """High-level Python wrapper around the C++ ``HyperisoMaster``.
53
54 The wrapper configures package-friendly defaults before initialization:
55 read-only assets are looked up under ``pyhyperiso/assets`` when available,
56 and writable caches are placed under the user cache directory unless the
57 caller provides explicit directories.
58 """
59
61 self,
62 *,
63 configure_default_paths: bool = True,
64 assets_root: Optional[PathLike] = None,
65 cache_root: Optional[PathLike] = None,
66 ) -> None:
67 """Create an uninitialized Hyperiso controller.
68
69 Args:
70 configure_default_paths: When ``True``, configure packaged assets
71 and cache directories before any call to ``init``.
72 assets_root: Optional replacement for the packaged read-only
73 assets directory.
74 cache_root: Optional root used to create ``MartyTemp`` and
75 ``Spectrum`` writable cache directories.
76 """
77 self._cpp_obj = _CppHyperisoMaster()
78 self.config: Optional[HyperisoConfig] = None
79
80 if configure_default_paths:
81 self.configure_default_paths(assets_root=assets_root, cache_root=cache_root)
82
83 @staticmethod
84 def _packaged_assets_root() -> Optional[Path]:
85 """Return ``pyhyperiso/assets`` when it is available as a filesystem path."""
86 try:
87 assets = resources.files("pyhyperiso").joinpath("assets")
88 except Exception:
89 return None
90
91 if assets.is_dir():
92 return Path(str(assets)).resolve()
93 return None
94
95 @staticmethod
96 def _legacy_source_assets_root() -> Optional[Path]:
97 """Return the historical source-tree ``Assets`` directory when present."""
98 assets = (
99 Path(__file__).resolve().parent / ".." / ".." / ".." / ".." / ".." / "Assets"
100 ).resolve()
101 return assets if assets.is_dir() else None
102
103 @staticmethod
104 def _default_cache_root() -> Path:
105 """Return a writable cache root without adding a hard platformdirs dependency."""
106 env_root = os.environ.get("HYPERISO_CACHE_ROOT")
107 if env_root:
108 return Path(env_root).expanduser().resolve()
109
110 xdg_cache = os.environ.get("XDG_CACHE_HOME")
111 if xdg_cache:
112 return (Path(xdg_cache).expanduser() / "pyhyperiso").resolve()
113
114 home = os.environ.get("HOME")
115 if home:
116 return (Path(home).expanduser() / ".cache" / "pyhyperiso").resolve()
117
118 return (Path(tempfile.gettempdir()) / "pyhyperiso").resolve()
119
120 @staticmethod
121 def _to_cpp_api_path(path_key: Any) -> Any:
122 """Convert a Python APIPath-like value to the bound C++ enum value."""
123 cpp_value = getattr(path_key, "value", path_key)
124 if isinstance(cpp_value, str):
125 try:
126 return getattr(_CppAPIPath, cpp_value)
127 except AttributeError as exc:
128 raise ValueError(f"Unknown APIPath name: {cpp_value}") from exc
129 return cpp_value
130
132 self,
133 *,
134 assets_root: Optional[PathLike] = None,
135 cache_root: Optional[PathLike] = None,
136 ) -> None:
137 """Configure package assets and writable cache directories before init.
138
139 Args:
140 assets_root: Optional read-only assets directory. When omitted, the
141 wrapper first tries ``pyhyperiso/assets`` and then the historical
142 source-tree ``Assets`` directory.
143 cache_root: Optional writable root. ``MartyTemp`` and ``Spectrum``
144 are created below it. When omitted, a standard user cache root is
145 used.
146 """
147 resolved_assets = (
148 Path(os.fspath(assets_root)).expanduser().resolve()
149 if assets_root is not None
151 )
152
153 if resolved_assets is not None:
154 self.pre_init_set_paths({APIPath.ASSETS_ROOT: resolved_assets})
155
156 resolved_cache_root = (
157 Path(os.fspath(cache_root)).expanduser().resolve()
158 if cache_root is not None
159 else self._default_cache_root()
160 )
161 self.pre_init_set_marty_cache_dir(resolved_cache_root / "MartyTemp")
162 self.pre_init_set_spectrum_cache_dir(resolved_cache_root / "Spectrum")
163
164 @staticmethod
165 def _resolve_lha_path(lha_file: PathLike) -> str:
166 """Prepare an LHA path for the C++ layer.
167
168 Absolute paths are forwarded as absolute paths. Relative paths are kept
169 relative so that the C++ ``MemoryManager`` resolves them under the active
170 ``ASSETS_ROOT``. This is important for wheels where ``Assets`` no longer
171 lives next to the source checkout.
172 """
173 path = os.fspath(lha_file)
174 return os.path.abspath(path) if os.path.isabs(path) else path
175
176 def init(self, lha_file: PathLike, config: Optional[HyperisoConfig] = None) -> None:
177 """Initialize Hyperiso with an LHA file.
178
179 Args:
180 lha_file: Absolute LHA path, or path relative to the active
181 ``ASSETS_ROOT``.
182 config: Optional initialization config. When omitted, the C++
183 default configuration is used and ``self.config`` is set to a
184 default :class:`HyperisoConfig` instance.
185 """
186 resolved_lha = self._resolve_lha_path(lha_file)
187 if config is not None:
188 self._cpp_obj.init(resolved_lha, config.to_cpp())
189 self.config = config
190 else:
191 self._cpp_obj.init(resolved_lha)
192 self.config = HyperisoConfig()
193
195 self,
196 block_name: str,
197 item_count: int = 2,
198 value_idx: int = 1,
199 scale_idx: int = -1,
200 rg_idx: int = -1,
201 bin_idx: int = -1,
202 global_scale: bool = False,
203 ) -> None:
204 """Register an additional LHA block prototype before initialization."""
206 block_name,
207 item_count,
208 value_idx,
209 scale_idx,
210 rg_idx,
211 bin_idx,
212 global_scale,
213 )
214
215 def pre_init_set_marty_path(self, marty_path: PathLike) -> None:
216 """Register an existing MARTY installation before initialization."""
217 self._cpp_obj.pre_init_set_marty_path(os.path.abspath(os.fspath(marty_path)))
218
219 def pre_init_set_softsusy_path(self, softsusy_path: PathLike) -> None:
220 """Register a SOFTSUSY executable or installation directory before initialization.
221
222 ``softsusy_path`` may point directly to ``softpoint.x`` or to a local
223 SOFTSUSY installation/source directory containing one of the usual
224 executable locations: ``softpoint.x``, ``bin/softpoint.x`` or
225 ``src/SOFTSUSY/softpoint.x``.
226 """
227 self._cpp_obj.pre_init_set_softsusy_path(os.path.abspath(os.fspath(softsusy_path)))
228
229 def pre_init_set_paths(self, path_overrides: Mapping[Any, PathLike]) -> None:
230 """Override selected Hyperiso filesystem paths before initialization.
231
232 Args:
233 path_overrides: Mapping from ``APIPath`` keys to replacement paths.
234 Keys may be this wrapper's ``APIPath`` enum, the low-level C++
235 ``APIPath`` enum, another wrapper enum exposing a ``.value``
236 C++ enum, or a string matching an ``APIPath`` name. Values may
237 be strings or ``os.PathLike`` objects.
238 """
239 cpp_overrides = {
240 self._to_cpp_api_path(path_key): os.path.abspath(os.fspath(path_value))
241 for path_key, path_value in path_overrides.items()
242 }
243 self._cpp_obj.pre_init_set_paths(cpp_overrides)
244
245 def pre_init_set_marty_cache_dir(self, cache_dir: PathLike) -> None:
246 """Set the writable MARTY generated-code/cache directory before init."""
247 self._cpp_obj.pre_init_set_marty_cache_dir(os.path.abspath(os.fspath(cache_dir)))
248
249 def pre_init_set_spectrum_cache_dir(self, cache_dir: PathLike) -> None:
250 """Set the writable spectrum cache directory before init."""
251 self._cpp_obj.pre_init_set_spectrum_cache_dir(os.path.abspath(os.fspath(cache_dir)))
252
253 def switch_lha(self, lha_file: PathLike, config: Optional[HyperisoConfig] = None) -> None:
254 """Switch the active LHA input file."""
255 resolved_lha = self._resolve_lha_path(lha_file)
256 if config is not None:
257 self._cpp_obj.switch_lha(resolved_lha, config.to_cpp())
258 self.config = config
259 return
260
261 if self.config is None:
262 raise RuntimeError(
263 "HyperisoMaster.switch_lha() requires a config before init() has been called."
264 )
265
266 self._cpp_obj.switch_lha(resolved_lha, self.config.to_cpp())
267
268 def check_flag(self, flag: ExternalFlag) -> bool:
269 """Return whether an external flag is active."""
270 return bool(self._cpp_obj.check_flag(flag.value))
271
272 @property
273 def model(self) -> Model:
274 """Return the physics model currently active in C++."""
275 return Model(self._cpp_obj.get_model())
276
277 def __repr__(self) -> str:
278 """Return a compact representation including the active model."""
279 return f"<PyHyperisoMaster model={self.model.name}>"
280
281
282__all__ = ["HyperisoMaster", "APIPath"]
None configure_default_paths(self, *Optional[PathLike] assets_root=None, Optional[PathLike] cache_root=None)
None pre_init_set_paths(self, Mapping[Any, PathLike] path_overrides)
None pre_init_set_softsusy_path(self, PathLike softsusy_path)
None pre_init_set_marty_cache_dir(self, PathLike cache_dir)
None pre_init_set_marty_path(self, PathLike marty_path)
None pre_init_set_spectrum_cache_dir(self, PathLike cache_dir)
None __init__(self, *bool configure_default_paths=True, Optional[PathLike] assets_root=None, Optional[PathLike] cache_root=None)
None switch_lha(self, PathLike lha_file, Optional[HyperisoConfig] config=None)
None pre_init_add_block(self, str block_name, int item_count=2, int value_idx=1, int scale_idx=-1, int rg_idx=-1, int bin_idx=-1, bool global_scale=False)
None init(self, PathLike lha_file, Optional[HyperisoConfig] config=None)