TransitionListener 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transitionlistener/__init__.py +21 -0
- transitionlistener/bubbledynamics.py +2250 -0
- transitionlistener/bubbledynamics_fixedstep.py +2929 -0
- transitionlistener/check_installation.py +309 -0
- transitionlistener/ckm.py +91 -0
- transitionlistener/colors.py +288 -0
- transitionlistener/config.py +185 -0
- transitionlistener/constants.py +64 -0
- transitionlistener/counterterms/__init__.py +230 -0
- transitionlistener/counterterms/cw.py +298 -0
- transitionlistener/counterterms/tensors.py +343 -0
- transitionlistener/counterterms/twohdm.py +520 -0
- transitionlistener/counterterms/twohdm_solver.py +374 -0
- transitionlistener/errors.py +315 -0
- transitionlistener/examples/bp_et.yaml +64 -0
- transitionlistener/examples/bp_lisa.yaml +70 -0
- transitionlistener/examples/bp_muares.yaml +69 -0
- transitionlistener/examples/bp_pta.yaml +64 -0
- transitionlistener/examples/example_dark_photon.yaml +73 -0
- transitionlistener/examples/example_grid.yaml +55 -0
- transitionlistener/examples/example_line.yaml +51 -0
- transitionlistener/examples/example_point.yaml +73 -0
- transitionlistener/examples/example_random_conformal.yaml +42 -0
- transitionlistener/examples/example_ultranest.yaml +39 -0
- transitionlistener/finiteT.py +468 -0
- transitionlistener/generic_potential.py +1521 -0
- transitionlistener/gridplots.py +592 -0
- transitionlistener/gwfopt.py +604 -0
- transitionlistener/helper_functions.py +843 -0
- transitionlistener/hydrodynamics.py +647 -0
- transitionlistener/interface/__init__.py +95 -0
- transitionlistener/interface/check.py +51 -0
- transitionlistener/interface/cli.py +211 -0
- transitionlistener/interface/config_loader.py +264 -0
- transitionlistener/interface/logging_utils.py +30 -0
- transitionlistener/interface/output_schema.py +296 -0
- transitionlistener/interface/pipeline.py +300 -0
- transitionlistener/interface/samplers.py +565 -0
- transitionlistener/interface/scans.py +468 -0
- transitionlistener/interface/single_point.py +581 -0
- transitionlistener/interface/state.py +61 -0
- transitionlistener/lineplots.py +597 -0
- transitionlistener/logo/TL-logo_large.png +0 -0
- transitionlistener/logo/TL-logo_small.png +0 -0
- transitionlistener/models/TL_2HDM.py +512 -0
- transitionlistener/models/TL_conformal_dark_u1.py +202 -0
- transitionlistener/models/TL_dark_U1.py +198 -0
- transitionlistener/models/TL_dark_U1_g_parameterization.py +198 -0
- transitionlistener/models/TL_dark_flipflop.py +274 -0
- transitionlistener/models/templatePotential.py +260 -0
- transitionlistener/nucleation.py +412 -0
- transitionlistener/observability.py +790 -0
- transitionlistener/particles.py +436 -0
- transitionlistener/pathDeformation.py +1462 -0
- transitionlistener/percolation_adaptive_gridbuilders.py +1385 -0
- transitionlistener/percolation_adaptive_rate.py +1049 -0
- transitionlistener/percolation_adaptivestepsize.py +1632 -0
- transitionlistener/percolation_fixedstepsize.py +618 -0
- transitionlistener/phases.py +1499 -0
- transitionlistener/plot_settings.py +121 -0
- transitionlistener/plots.py +1781 -0
- transitionlistener/runtime_options.py +278 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/bandwidths.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/density.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/freqs.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log.txt +16 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rhogrid.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rholabels.txt +30 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/pulsar_list.txt +1 -0
- transitionlistener/tab_data/HLV-O2-PLI.csv +82 -0
- transitionlistener/tab_data/HLV-O2-noise.csv +83 -0
- transitionlistener/tab_data/HLVK-design-PLI.csv +61 -0
- transitionlistener/tab_data/HLVK-design-noise.csv +83 -0
- transitionlistener/tab_data/NG15_PLI_hc.csv +31 -0
- transitionlistener/tab_data/NG15_noise_hc.csv +56 -0
- transitionlistener/tab_data/PLI-dataset.csv +327 -0
- transitionlistener/tab_data/energydensities.dat +1001 -0
- transitionlistener/tab_data/finiteT_b.dat.txt +1999 -0
- transitionlistener/tab_data/finiteT_f.dat.txt +1000 -0
- transitionlistener/tab_data/geff_SM.dat +10009 -0
- transitionlistener/tab_data/muAres_f_Hz_PLI_h2OmegaGW.csv +131 -0
- transitionlistener/tab_data/muAres_f_Hz_h2Omega_noise.csv +99 -0
- transitionlistener/tab_data/noise-dataset.csv +327 -0
- transitionlistener/tab_data/numberdensities.dat +1001 -0
- transitionlistener/tab_data/pressures.dat +1001 -0
- transitionlistener/thermodynamics.py +686 -0
- transitionlistener/transitionObservables.py +1199 -0
- transitionlistener/transitionObservables_fixedstep.py +976 -0
- transitionlistener/transitions.py +1000 -0
- transitionlistener/tunneling1D.py +910 -0
- transitionlistener-2.0.0.dist-info/METADATA +817 -0
- transitionlistener-2.0.0.dist-info/RECORD +95 -0
- transitionlistener-2.0.0.dist-info/WHEEL +4 -0
- transitionlistener-2.0.0.dist-info/entry_points.txt +2 -0
- transitionlistener-2.0.0.dist-info/licenses/LICENSE.txt +674 -0
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
"""Single-point evaluation logic with staged plotting callbacks.
|
|
2
|
+
|
|
3
|
+
Part of TransitionListener v2.0
|
|
4
|
+
Documentation: https://tasillo.de/TransitionListener/
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Jonas Matuszak <jonas.matuszak@kit.edu>
|
|
8
|
+
Carlo Tasillo <carlo.tasillo@ific.uv.es>
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from transitionlistener import plots
|
|
18
|
+
from transitionlistener import observability as obs
|
|
19
|
+
from transitionlistener.helper_functions import load_potential
|
|
20
|
+
from transitionlistener import console
|
|
21
|
+
|
|
22
|
+
from .logging_utils import Logger
|
|
23
|
+
from .pipeline import (
|
|
24
|
+
_compute_single_point,
|
|
25
|
+
_build_result_from_context,
|
|
26
|
+
_handle_single_point_error,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_STAGE_NAME_TO_LEVEL = {
|
|
31
|
+
"potential": 0,
|
|
32
|
+
"phases": 1,
|
|
33
|
+
"transitions": 2,
|
|
34
|
+
"observables": 3,
|
|
35
|
+
"gw": 4,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_MASS_GROUP_PREFIX = "mass_spectrum_T0_"
|
|
40
|
+
_MASS_KIND_ORDER = {"boson": 0, "fermion": 1}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _plot_required_stage(plot_key: str, spec: dict) -> int:
|
|
44
|
+
"""Return the minimum pipeline stage needed for a given plot."""
|
|
45
|
+
if plot_key == "potential":
|
|
46
|
+
return 0
|
|
47
|
+
if plot_key == "action":
|
|
48
|
+
return 1
|
|
49
|
+
if plot_key == "energy_density":
|
|
50
|
+
return 1
|
|
51
|
+
if plot_key == "phases":
|
|
52
|
+
include = bool(spec.get("include_transitions?", True))
|
|
53
|
+
return 3 if include else 1
|
|
54
|
+
if plot_key in {"profile", "profileV", "dofs", "percolation"}:
|
|
55
|
+
return 3
|
|
56
|
+
if plot_key == "sensitivities":
|
|
57
|
+
return 0
|
|
58
|
+
if plot_key == "gw_spectrum":
|
|
59
|
+
return 4
|
|
60
|
+
return 4
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _format_numeric_value(key: str, value) -> str:
|
|
64
|
+
"""Render numeric output in a consistent fashion."""
|
|
65
|
+
if isinstance(value, (bool, np.bool_)):
|
|
66
|
+
return str(bool(value))
|
|
67
|
+
if isinstance(value, (int, np.integer)):
|
|
68
|
+
return str(int(value))
|
|
69
|
+
if isinstance(value, (float, np.floating)):
|
|
70
|
+
if not np.isfinite(value):
|
|
71
|
+
return "-"
|
|
72
|
+
if key in {"step", "total_steps"}:
|
|
73
|
+
return str(int(round(value)))
|
|
74
|
+
return f"{float(value):.4e}"
|
|
75
|
+
return str(value)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _format_warning_value(value) -> str:
|
|
79
|
+
"""Render warning flags as booleans."""
|
|
80
|
+
if isinstance(value, (float, np.floating)) and not np.isfinite(value):
|
|
81
|
+
return "False"
|
|
82
|
+
if isinstance(value, (int, np.integer, bool, np.bool_)):
|
|
83
|
+
return str(bool(value))
|
|
84
|
+
return str(bool(value))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class PlotPlan:
|
|
89
|
+
"""Information about requested plots and their pipeline requirements."""
|
|
90
|
+
|
|
91
|
+
specs: dict[str, dict]
|
|
92
|
+
ordered_keys: list[str]
|
|
93
|
+
wants_gw_spectrum: bool
|
|
94
|
+
stage_requirements: dict[str, int]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _build_plot_plan(conf) -> PlotPlan:
|
|
98
|
+
"""Extract plot configuration from the user configuration."""
|
|
99
|
+
plot_specs = conf.additional_plots or {}
|
|
100
|
+
active_plot_keys = [
|
|
101
|
+
key
|
|
102
|
+
for key, spec in plot_specs.items()
|
|
103
|
+
if isinstance(spec, dict) and spec.get("plot?", False)
|
|
104
|
+
]
|
|
105
|
+
wants_gw_spectrum = "gw_spectrum" in active_plot_keys
|
|
106
|
+
ordered_non_gw_keys = [
|
|
107
|
+
key for key in plot_specs.keys() if key in active_plot_keys and key != "gw_spectrum"
|
|
108
|
+
]
|
|
109
|
+
stage_requirements = {
|
|
110
|
+
key: _plot_required_stage(key, plot_specs[key])
|
|
111
|
+
for key in ordered_non_gw_keys
|
|
112
|
+
}
|
|
113
|
+
return PlotPlan(
|
|
114
|
+
specs=plot_specs,
|
|
115
|
+
ordered_keys=ordered_non_gw_keys,
|
|
116
|
+
wants_gw_spectrum=wants_gw_spectrum,
|
|
117
|
+
stage_requirements=stage_requirements,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class PlotExecutor:
|
|
122
|
+
"""Manage additional plots and spectrum rendering during the pipeline."""
|
|
123
|
+
|
|
124
|
+
def __init__(self, conf, input_params: dict, plan: PlotPlan, outpath: str, *, verbose: bool = False):
|
|
125
|
+
"""Store plot requests and the pipeline context needed to satisfy them."""
|
|
126
|
+
self._conf = conf
|
|
127
|
+
self._input_params = input_params
|
|
128
|
+
self._plan = plan
|
|
129
|
+
self._outpath = outpath
|
|
130
|
+
self._verbose = verbose
|
|
131
|
+
self._executed: set[str] = set()
|
|
132
|
+
|
|
133
|
+
def stage_callback(self, stage_name: str, ctx: dict):
|
|
134
|
+
"""Pipeline hook that triggers plots once prerequisites are met."""
|
|
135
|
+
current_level = _STAGE_NAME_TO_LEVEL.get(stage_name, -1)
|
|
136
|
+
for key in self._plan.ordered_keys:
|
|
137
|
+
if key in self._executed:
|
|
138
|
+
continue
|
|
139
|
+
required_stage = self._plan.stage_requirements.get(key, 4)
|
|
140
|
+
if required_stage <= current_level:
|
|
141
|
+
self._run_additional_plot(key, ctx)
|
|
142
|
+
|
|
143
|
+
if stage_name == "gw" and self._plan.wants_gw_spectrum and "gw_spectrum" not in self._executed:
|
|
144
|
+
self._run_gw_spectrum(ctx)
|
|
145
|
+
|
|
146
|
+
def mark_gw_skipped(self, message: str, colour: str = "red"):
|
|
147
|
+
"""Helper to emit a message when the GW spectrum cannot be produced."""
|
|
148
|
+
if self._plan.wants_gw_spectrum and "gw_spectrum" not in self._executed:
|
|
149
|
+
console.print(f"[bold {colour}]{message}[/bold {colour}]")
|
|
150
|
+
self._executed.add("gw_spectrum")
|
|
151
|
+
|
|
152
|
+
def _run_additional_plot(self, key: str, ctx: dict):
|
|
153
|
+
"""Execute one configured additional plot and degrade gracefully on failure."""
|
|
154
|
+
self._executed.add(key)
|
|
155
|
+
spec = self._plan.specs.get(key, {})
|
|
156
|
+
try:
|
|
157
|
+
self._run_additional_plot_impl(key, spec, ctx)
|
|
158
|
+
except Exception as err:
|
|
159
|
+
console.print(
|
|
160
|
+
f"[bold yellow]Skipping additional plot '{key}': {err}[/bold yellow]"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _run_additional_plot_impl(self, key: str, spec: dict, ctx: dict):
|
|
164
|
+
"""Dispatch one additional-plot request to the matching plotting routine."""
|
|
165
|
+
if key == "sensitivities":
|
|
166
|
+
console.print(f"[bold green]Create {key} plot...[/bold green]")
|
|
167
|
+
gw = obs.GW_Sensitivity_Data()
|
|
168
|
+
gw.foldername = self._outpath
|
|
169
|
+
plots.plotSensitivities(gw, showplot=False, call_from_spectrum=False)
|
|
170
|
+
console.print(f"[bold green]Done: {key} plot.[/bold green]")
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
plotter = plots.TLPlots(
|
|
174
|
+
self._input_params,
|
|
175
|
+
self._conf.modelfile,
|
|
176
|
+
self._conf.potential_name,
|
|
177
|
+
self._outpath,
|
|
178
|
+
self._conf.plot_description,
|
|
179
|
+
potential=ctx.get("pot"),
|
|
180
|
+
phases=ctx.get("phases"),
|
|
181
|
+
transitions=ctx.get("transitions"),
|
|
182
|
+
transition_observables=ctx.get("transition_observables"),
|
|
183
|
+
verbose=self._verbose,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
console.print(f"[bold green]Create {key} plot...[/bold green]")
|
|
187
|
+
if key == "action":
|
|
188
|
+
phase_indices_cfg = spec.get("phase_indices", [0, 1])
|
|
189
|
+
if not isinstance(phase_indices_cfg, (list, tuple)) or len(phase_indices_cfg) != 2:
|
|
190
|
+
raise ValueError(
|
|
191
|
+
"additional_plots.action.phase_indices must be a list of two phase keys."
|
|
192
|
+
)
|
|
193
|
+
phase_indices = [phase_indices_cfg[0], phase_indices_cfg[1]]
|
|
194
|
+
plotter.plotAction(
|
|
195
|
+
np.array(spec.get("Tmin_GeV", 0), dtype=float),
|
|
196
|
+
np.array(spec.get("Tmax_GeV", 0), dtype=float),
|
|
197
|
+
phase_indices,
|
|
198
|
+
np.array(spec.get("n", 100), dtype=int),
|
|
199
|
+
)
|
|
200
|
+
elif key == "potential":
|
|
201
|
+
plotter.plotPotential(
|
|
202
|
+
np.array(spec.get("T_GeV", 0), dtype=float),
|
|
203
|
+
np.array(spec.get("phi_ranges_GeV", [0, 1]), dtype=float),
|
|
204
|
+
np.array(spec.get("n", 100), dtype=int),
|
|
205
|
+
)
|
|
206
|
+
elif key == "phases":
|
|
207
|
+
include_transitions = bool(spec.get("include_transitions?", True))
|
|
208
|
+
plotter.plotPhases(
|
|
209
|
+
Tmin_GeV=np.array(spec.get("Tmin_GeV", np.nan), dtype=float),
|
|
210
|
+
Tmax_GeV=np.array(spec.get("Tmax_GeV", np.nan), dtype=float),
|
|
211
|
+
include_transitions=include_transitions,
|
|
212
|
+
plot_squaresum=spec.get("plot_squaresum", True)
|
|
213
|
+
)
|
|
214
|
+
elif key == "profileV":
|
|
215
|
+
plotter.plotProfileV(
|
|
216
|
+
np.array(spec.get("field_index_1", 0), dtype=int),
|
|
217
|
+
np.array(spec.get("field_index_2", 1), dtype=int),
|
|
218
|
+
)
|
|
219
|
+
elif key == "energy_density":
|
|
220
|
+
plotter.plotEnergyDensity(
|
|
221
|
+
np.array(spec.get("Tmin_GeV", np.nan), dtype=float),
|
|
222
|
+
np.array(spec.get("Tmax_GeV", np.nan), dtype=float),
|
|
223
|
+
)
|
|
224
|
+
elif key == "dofs":
|
|
225
|
+
plotter.plotDOFs(
|
|
226
|
+
np.array(spec.get("Tmin_GeV", np.nan), dtype=float),
|
|
227
|
+
np.array(spec.get("Tmax_GeV", np.nan), dtype=float),
|
|
228
|
+
)
|
|
229
|
+
elif key == "profile":
|
|
230
|
+
plotter.plotProfile()
|
|
231
|
+
elif key == "percolation":
|
|
232
|
+
n_action_cfg = spec.get("n_action", None)
|
|
233
|
+
n_action = None if n_action_cfg is None else int(np.asarray(n_action_cfg, dtype=int))
|
|
234
|
+
plotter.plotPercolation(
|
|
235
|
+
Tmin_GeV=np.array(spec.get("Tmin_GeV", np.nan), dtype=float),
|
|
236
|
+
Tmax_GeV=np.array(spec.get("Tmax_GeV", np.nan), dtype=float),
|
|
237
|
+
n_action=n_action,
|
|
238
|
+
)
|
|
239
|
+
console.print(f"[bold green]Done: {key} plot.[/bold green]")
|
|
240
|
+
|
|
241
|
+
def _run_gw_spectrum(self, ctx: dict):
|
|
242
|
+
"""Render the GW spectrum for the strongest transition when available."""
|
|
243
|
+
strongest = ctx.get("strongest_transition_observables") or {}
|
|
244
|
+
if not strongest:
|
|
245
|
+
self.mark_gw_skipped("Skipping GW spectrum plot because no valid transition was found.", colour="yellow")
|
|
246
|
+
return
|
|
247
|
+
console.print(f"[bold green]Create the gw_spectrum plot[/bold green]")
|
|
248
|
+
plots.plotGWSpectrum(strongest, showplot=False, foldername=self._outpath, legendfontsize=5)
|
|
249
|
+
console.print(f"[bold green]Done: gw_spectrum plot.[/bold green]")
|
|
250
|
+
self._executed.add("gw_spectrum")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _write_input_parameters(outpath: str, input_params: dict):
|
|
254
|
+
"""Write the per-point input parameter dictionary to disk.
|
|
255
|
+
|
|
256
|
+
Numeric entries are rendered with the tabular ``{:e}`` format we use
|
|
257
|
+
throughout. Non-numeric entries (string-valued runtime options like
|
|
258
|
+
``precision_mode`` or ``percolation_algorithm_mode``) are written as
|
|
259
|
+
plain ``str(value)``; without this fallback the writer would die with
|
|
260
|
+
``could not convert string to float`` whenever such an option lands in
|
|
261
|
+
the input dict alongside the physical model parameters.
|
|
262
|
+
"""
|
|
263
|
+
with open(outpath + "0_Input_params.txt", "w", encoding="utf-8") as file:
|
|
264
|
+
for name, value in input_params.items():
|
|
265
|
+
try:
|
|
266
|
+
numeric = float(value)
|
|
267
|
+
except (TypeError, ValueError):
|
|
268
|
+
file.write("{:<22} {}\n".format(name, str(value)))
|
|
269
|
+
continue
|
|
270
|
+
file.write("{:<22} {:e}\n".format(name, numeric))
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _write_transition_outputs(outpath: str, conf, result: dict) -> float:
|
|
274
|
+
"""Write the strongest-transition observables and warning flags to disk."""
|
|
275
|
+
transition_names = list(conf.derived_params.keys())
|
|
276
|
+
strongest = result.get("strongestTransitionObservables", {}) if isinstance(result, dict) else {}
|
|
277
|
+
transition_values = [strongest.get(name, np.nan) for name in transition_names]
|
|
278
|
+
|
|
279
|
+
raw_error_value = result.get("error", np.nan) if isinstance(result, dict) else np.nan
|
|
280
|
+
if isinstance(raw_error_value, np.ndarray):
|
|
281
|
+
error_scalar = raw_error_value.flatten()[0] if raw_error_value.size else np.nan
|
|
282
|
+
else:
|
|
283
|
+
error_scalar = raw_error_value
|
|
284
|
+
|
|
285
|
+
all_names = transition_names + ["error"]
|
|
286
|
+
all_values = transition_values + [error_scalar]
|
|
287
|
+
entries = {name: value for name, value in zip(all_names, all_values)}
|
|
288
|
+
|
|
289
|
+
warning_keys = sorted(
|
|
290
|
+
(k for k in entries if k.upper().startswith("WARNING")),
|
|
291
|
+
key=lambda k: (k.lower(), k),
|
|
292
|
+
)
|
|
293
|
+
error_value = entries.get("error", np.nan)
|
|
294
|
+
numerical_keys = sorted(
|
|
295
|
+
(k for k in entries if k not in warning_keys and k != "error"),
|
|
296
|
+
key=lambda k: (k.lower(), k),
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
with open(outpath + "1_All_params.txt", "w", encoding="utf-8") as file:
|
|
300
|
+
for key in numerical_keys:
|
|
301
|
+
file.write("{:<22} {}\n".format(key, _format_numeric_value(key, entries[key])))
|
|
302
|
+
|
|
303
|
+
if numerical_keys and (warning_keys or "error" in entries):
|
|
304
|
+
file.write("\n")
|
|
305
|
+
|
|
306
|
+
if warning_keys:
|
|
307
|
+
file.write("Warnings:\n")
|
|
308
|
+
warning_labels = [
|
|
309
|
+
key.split("WARNING:", 1)[1] if ":" in key else key
|
|
310
|
+
for key in warning_keys
|
|
311
|
+
]
|
|
312
|
+
max_len = max(len(label) for label in warning_labels)
|
|
313
|
+
for key in warning_keys:
|
|
314
|
+
label = key.split("WARNING:", 1)[1] if ":" in key else key
|
|
315
|
+
file.write(f" {label:<{max_len}} {_format_warning_value(entries[key])}\n")
|
|
316
|
+
|
|
317
|
+
if warning_keys and "error" in entries:
|
|
318
|
+
file.write("\n")
|
|
319
|
+
|
|
320
|
+
if "error" in entries:
|
|
321
|
+
if isinstance(error_value, (float, np.floating)):
|
|
322
|
+
error_str = "-" if not np.isfinite(error_value) else f"{float(error_value):.4e}"
|
|
323
|
+
elif isinstance(error_value, (int, np.integer)):
|
|
324
|
+
error_str = str(int(error_value))
|
|
325
|
+
else:
|
|
326
|
+
error_str = str(error_value)
|
|
327
|
+
file.write("{:<22} {}\n".format("error", error_str))
|
|
328
|
+
|
|
329
|
+
return error_scalar
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _write_observability_outputs(outpath: str, result: dict, had_error: bool):
|
|
333
|
+
"""Write detector-by-detector SNR and observability summaries to disk."""
|
|
334
|
+
if had_error:
|
|
335
|
+
msg = "Skipping observability output because errors were detected."
|
|
336
|
+
console.print(f"[bold red]{msg}[/bold red]")
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
observability = result.get("observability", {}) if isinstance(result, dict) else {}
|
|
340
|
+
if not observability:
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
detectors: list[str] = []
|
|
344
|
+
for key in observability.keys():
|
|
345
|
+
detector = None
|
|
346
|
+
if key.endswith("_SNR"):
|
|
347
|
+
detector = key[:-4]
|
|
348
|
+
elif key.endswith("_detectable"):
|
|
349
|
+
detector = key[:-11]
|
|
350
|
+
if detector and detector not in detectors:
|
|
351
|
+
detectors.append(detector)
|
|
352
|
+
|
|
353
|
+
with open(outpath + "2_Observability.txt", "w", encoding="utf-8") as file:
|
|
354
|
+
file.write(
|
|
355
|
+
"{:<20} {:<15} {:<31} {:<12}\n".format(
|
|
356
|
+
"Observatory",
|
|
357
|
+
"SNR",
|
|
358
|
+
"Should have been observed already?",
|
|
359
|
+
"Observable?",
|
|
360
|
+
)
|
|
361
|
+
)
|
|
362
|
+
file.write("-" * 90 + "\n")
|
|
363
|
+
for det in detectors:
|
|
364
|
+
snr = observability.get(f"{det}_SNR", float("nan"))
|
|
365
|
+
detectable = observability.get(f"{det}_detectable", False)
|
|
366
|
+
is_existing = det in obs.EXISTING_OBSERVATORIES
|
|
367
|
+
should_str = (
|
|
368
|
+
_format_numeric_value("detectable", detectable)
|
|
369
|
+
if is_existing
|
|
370
|
+
else "-"
|
|
371
|
+
)
|
|
372
|
+
observable_str = (
|
|
373
|
+
_format_numeric_value("detectable", detectable)
|
|
374
|
+
if not is_existing
|
|
375
|
+
else "-"
|
|
376
|
+
)
|
|
377
|
+
file.write(
|
|
378
|
+
"{:<20} {:<15} {:<31} {:<12}\n".format(
|
|
379
|
+
det,
|
|
380
|
+
_format_numeric_value("snr", snr),
|
|
381
|
+
should_str,
|
|
382
|
+
observable_str,
|
|
383
|
+
)
|
|
384
|
+
)
|
|
385
|
+
file.write("\n")
|
|
386
|
+
|
|
387
|
+
logL_keys = [k for k in observability if k.startswith("logL_")]
|
|
388
|
+
printed_keys = set()
|
|
389
|
+
if logL_keys:
|
|
390
|
+
file.write("PTA likelihood summary:\n")
|
|
391
|
+
file.write("{:<16} {:>6} {:<12} {:>12} {:>12} {:>10} {:>18}\n".format(
|
|
392
|
+
"PTA", "Bins", "Variant", "lnL", "Delta lnL", "Sigma", "Within 3 sigma?"
|
|
393
|
+
))
|
|
394
|
+
file.write("-" * 90 + "\n")
|
|
395
|
+
for label, settings in obs.PTA_LIKELIHOOD_SETTINGS.items():
|
|
396
|
+
bins = int(settings["nbins"])
|
|
397
|
+
display_name = obs.PTA_DISPLAY_NAMES.get(label, label)
|
|
398
|
+
first_row = True
|
|
399
|
+
for variant in obs.PTA_LIKELIHOOD_VARIANTS:
|
|
400
|
+
lnL_key = f"lnL_{variant}_{label}"
|
|
401
|
+
delta_key = f"delta_lnL_{variant}_{label}"
|
|
402
|
+
sigma_key = f"sigma_{variant}_{label}"
|
|
403
|
+
within_key = f"within_3sigma_{variant}_{label}"
|
|
404
|
+
printed_keys.update({lnL_key, delta_key, sigma_key, within_key})
|
|
405
|
+
|
|
406
|
+
lnL_val = observability.get(lnL_key, np.nan)
|
|
407
|
+
delta_val = observability.get(delta_key, np.nan)
|
|
408
|
+
sigma_val = observability.get(sigma_key, np.nan)
|
|
409
|
+
within_val = observability.get(within_key, np.nan)
|
|
410
|
+
|
|
411
|
+
lnL_str = f"{lnL_val:.2f}" if np.isfinite(lnL_val) else "nan"
|
|
412
|
+
delta_str = f"{delta_val:.2f}" if np.isfinite(delta_val) else "nan"
|
|
413
|
+
sigma_str = f"{sigma_val:.2f}" if np.isfinite(sigma_val) else "nan"
|
|
414
|
+
within_str = ""
|
|
415
|
+
if variant == "PTArcade":
|
|
416
|
+
if isinstance(within_val, (bool, np.bool_)):
|
|
417
|
+
within_str = str(bool(within_val))
|
|
418
|
+
elif np.isfinite(within_val):
|
|
419
|
+
within_str = str(bool(within_val < obs.DELTA_LNL_WITHIN_THRESHOLD))
|
|
420
|
+
|
|
421
|
+
name_col = display_name if first_row else ""
|
|
422
|
+
file.write("{:<16} {:>6} {:<12} {:>12} {:>12} {:>10} {:>18}\n".format(
|
|
423
|
+
name_col, bins, variant, lnL_str, delta_str, sigma_str, within_str
|
|
424
|
+
))
|
|
425
|
+
first_row = False
|
|
426
|
+
file.write("\n")
|
|
427
|
+
|
|
428
|
+
written_keys = {
|
|
429
|
+
f"{det}_SNR" for det in detectors
|
|
430
|
+
} | {f"{det}_detectable" for det in detectors}
|
|
431
|
+
if logL_keys:
|
|
432
|
+
written_keys.update(logL_keys)
|
|
433
|
+
written_keys.update(printed_keys)
|
|
434
|
+
|
|
435
|
+
additional_rows: list[tuple[str, str]] = []
|
|
436
|
+
additional_written: set[str] = set()
|
|
437
|
+
mass_rows: list[tuple[tuple[int, int], str, str, str]] = []
|
|
438
|
+
for key, value in observability.items():
|
|
439
|
+
if not key.endswith("_lin"):
|
|
440
|
+
continue
|
|
441
|
+
base_key = key[:-4]
|
|
442
|
+
additional_written.add(key)
|
|
443
|
+
log_key = f"{base_key}_log10"
|
|
444
|
+
latex_key = f"{base_key}_latex"
|
|
445
|
+
text_key = f"{base_key}_text"
|
|
446
|
+
kind_key = f"{base_key}_kind"
|
|
447
|
+
for meta_key in (log_key, latex_key, text_key, kind_key, base_key):
|
|
448
|
+
if meta_key in observability:
|
|
449
|
+
additional_written.add(meta_key)
|
|
450
|
+
|
|
451
|
+
if base_key.startswith(_MASS_GROUP_PREFIX):
|
|
452
|
+
suffix = base_key[len(_MASS_GROUP_PREFIX):]
|
|
453
|
+
kind_hint = observability.get(kind_key, "boson")
|
|
454
|
+
try:
|
|
455
|
+
kind_suffix, index_str = suffix.split("_", 1)
|
|
456
|
+
except ValueError:
|
|
457
|
+
kind_suffix, index_str = kind_hint, "0"
|
|
458
|
+
try:
|
|
459
|
+
index_val = int(index_str)
|
|
460
|
+
except ValueError:
|
|
461
|
+
index_val = 0
|
|
462
|
+
kind = str(kind_hint or kind_suffix)
|
|
463
|
+
order = (_MASS_KIND_ORDER.get(kind, 99), index_val)
|
|
464
|
+
text_label = observability.get(text_key)
|
|
465
|
+
latex_label = observability.get(latex_key, text_label or base_key)
|
|
466
|
+
formatted_mass = _format_numeric_value(base_key, value)
|
|
467
|
+
mass_rows.append((order, str(text_label or base_key), str(latex_label), formatted_mass))
|
|
468
|
+
continue
|
|
469
|
+
|
|
470
|
+
additional_rows.append((base_key, _format_numeric_value(base_key, value)))
|
|
471
|
+
|
|
472
|
+
if additional_rows:
|
|
473
|
+
file.write("Additional information on GW spectrum:\n")
|
|
474
|
+
file.write("{:<30} {:>15}\n".format("Quantity", "Value"))
|
|
475
|
+
file.write("-" * 48 + "\n")
|
|
476
|
+
for name, formatted in additional_rows:
|
|
477
|
+
file.write("{:<30} {:>15}\n".format(name, formatted))
|
|
478
|
+
file.write("\n")
|
|
479
|
+
|
|
480
|
+
written_keys.update(additional_written)
|
|
481
|
+
|
|
482
|
+
if mass_rows:
|
|
483
|
+
file.write("Zero-temperature mass spectrum (GeV):\n")
|
|
484
|
+
file.write("{:<30} {:>15}\n".format("Particle", "Mass"))
|
|
485
|
+
file.write("-" * 48 + "\n")
|
|
486
|
+
for _order, text_label, latex_label, formatted in sorted(mass_rows, key=lambda item: item[0]):
|
|
487
|
+
display = text_label
|
|
488
|
+
file.write("{:<30} {:>15}\n".format(display, formatted))
|
|
489
|
+
file.write("\n")
|
|
490
|
+
|
|
491
|
+
remaining_keys = [k for k in observability if k not in written_keys]
|
|
492
|
+
|
|
493
|
+
warning_keys = sorted(
|
|
494
|
+
(k for k in remaining_keys if k.upper().startswith("WARNING")),
|
|
495
|
+
key=lambda k: (k.lower(), k),
|
|
496
|
+
)
|
|
497
|
+
error_key = "error" if "error" in remaining_keys else None
|
|
498
|
+
numerical_keys = sorted(
|
|
499
|
+
(k for k in remaining_keys if k not in warning_keys and k != error_key),
|
|
500
|
+
key=lambda k: (k.lower(), k),
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
for key in numerical_keys:
|
|
504
|
+
file.write("{:<22} {}\n".format(key, _format_numeric_value(key, observability.get(key))))
|
|
505
|
+
|
|
506
|
+
if numerical_keys and (warning_keys or error_key):
|
|
507
|
+
file.write("\n")
|
|
508
|
+
|
|
509
|
+
if warning_keys:
|
|
510
|
+
file.write("Warnings:\n")
|
|
511
|
+
warning_labels = [
|
|
512
|
+
key.split("WARNING:", 1)[1] if ":" in key else key
|
|
513
|
+
for key in warning_keys
|
|
514
|
+
]
|
|
515
|
+
max_len = max(len(label) for label in warning_labels)
|
|
516
|
+
for key in warning_keys:
|
|
517
|
+
label = key.split("WARNING:", 1)[1] if ":" in key else key
|
|
518
|
+
file.write(f" {label:<{max_len}} {_format_warning_value(observability.get(key))}\n")
|
|
519
|
+
|
|
520
|
+
if warning_keys and error_key:
|
|
521
|
+
file.write("\n")
|
|
522
|
+
|
|
523
|
+
if error_key:
|
|
524
|
+
value = observability.get(error_key)
|
|
525
|
+
if isinstance(value, (float, np.floating)) and not np.isfinite(value):
|
|
526
|
+
value_str = "-"
|
|
527
|
+
elif isinstance(value, (float, np.floating)):
|
|
528
|
+
value_str = f"{float(value):.4e}"
|
|
529
|
+
elif isinstance(value, (int, np.integer)):
|
|
530
|
+
value_str = str(int(value))
|
|
531
|
+
else:
|
|
532
|
+
value_str = str(value)
|
|
533
|
+
file.write("{:<22} {}\n".format("error", value_str))
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def single(conf, verbose: bool = False):
|
|
537
|
+
"""Evaluate a single parameter point and generate the requested plots."""
|
|
538
|
+
outpath = conf.output_path
|
|
539
|
+
Path(outpath).mkdir(parents=True, exist_ok=True)
|
|
540
|
+
|
|
541
|
+
input_params = {key: val for key, val in conf.params.items()}
|
|
542
|
+
plot_plan = _build_plot_plan(conf)
|
|
543
|
+
plot_executor = PlotExecutor(conf, input_params, plot_plan, outpath, verbose=verbose)
|
|
544
|
+
|
|
545
|
+
potential = load_potential(conf.modelfile, conf.potential_name)
|
|
546
|
+
errorlogger = Logger(outpath + "error.log")
|
|
547
|
+
resultlogger = Logger(outpath + "result.log")
|
|
548
|
+
|
|
549
|
+
stage_callback = (
|
|
550
|
+
plot_executor.stage_callback
|
|
551
|
+
if (plot_plan.ordered_keys or plot_plan.wants_gw_spectrum)
|
|
552
|
+
else None
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
context, error = _compute_single_point(
|
|
556
|
+
input_params,
|
|
557
|
+
potential,
|
|
558
|
+
conf.timeout,
|
|
559
|
+
verbose,
|
|
560
|
+
include_smbhb=False,
|
|
561
|
+
max_stage=4,
|
|
562
|
+
stage_callback=stage_callback,
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
if error is not None:
|
|
566
|
+
result = _handle_single_point_error(error, input_params, errorlogger, context)
|
|
567
|
+
plot_executor.mark_gw_skipped(
|
|
568
|
+
"Skipping GW spectrum plot because errors were detected before the spectrum stage."
|
|
569
|
+
)
|
|
570
|
+
else:
|
|
571
|
+
result = _build_result_from_context(context)
|
|
572
|
+
observability = result.get("observability", {})
|
|
573
|
+
line = str(input_params) + ":" + str(observability)
|
|
574
|
+
resultlogger.log(line + "\n")
|
|
575
|
+
|
|
576
|
+
console.print("[bold green]Phase transition analysis done. Saving the results...[/bold green]")
|
|
577
|
+
_write_input_parameters(outpath, input_params)
|
|
578
|
+
|
|
579
|
+
error_scalar = _write_transition_outputs(outpath, conf, result)
|
|
580
|
+
had_error = not np.isnan(error_scalar)
|
|
581
|
+
_write_observability_outputs(outpath, result, had_error)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Shared global state and runtime configuration for the TL interface.
|
|
2
|
+
|
|
3
|
+
Part of TransitionListener v2.0
|
|
4
|
+
Documentation: https://tasillo.de/TransitionListener/
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Jonas Matuszak <jonas.matuszak@kit.edu>
|
|
8
|
+
Carlo Tasillo <carlo.tasillo@ific.uv.es>
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
import signal
|
|
13
|
+
import warnings
|
|
14
|
+
import numpy as np
|
|
15
|
+
from transitionlistener.errors import Timeout
|
|
16
|
+
|
|
17
|
+
DEBUGMODE: bool = False
|
|
18
|
+
TIMEOUT: int = 300
|
|
19
|
+
model = None
|
|
20
|
+
potential = None
|
|
21
|
+
errorlogger = None
|
|
22
|
+
resultlogger = None
|
|
23
|
+
config = None
|
|
24
|
+
|
|
25
|
+
np.random.seed(19971104)
|
|
26
|
+
|
|
27
|
+
_SUPPRESSED_WARNINGS = [
|
|
28
|
+
"invalid value encountered in subtract",
|
|
29
|
+
"overflow encountered in add",
|
|
30
|
+
"invalid value encountered in multiply",
|
|
31
|
+
"overflow encountered in multiply",
|
|
32
|
+
"divide by zero encountered in log",
|
|
33
|
+
"invalid value encountered in scalar subtract",
|
|
34
|
+
"overflow encountered in scalar multiply",
|
|
35
|
+
"divide by zero encountered in scalar divide",
|
|
36
|
+
"invalid value encountered in scalar divide",
|
|
37
|
+
"invalid value encountered in scalar multiply",
|
|
38
|
+
"invalid value encountered in divide",
|
|
39
|
+
"divide by zero encountered in divide",
|
|
40
|
+
"invalid value encountered in scalar divide",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def set_debugmode(enabled: bool) -> None:
|
|
45
|
+
"""Toggle debug mode and update warning filters accordingly."""
|
|
46
|
+
global DEBUGMODE
|
|
47
|
+
DEBUGMODE = bool(enabled)
|
|
48
|
+
warnings.resetwarnings()
|
|
49
|
+
if not DEBUGMODE:
|
|
50
|
+
for warning in _SUPPRESSED_WARNINGS:
|
|
51
|
+
warnings.filterwarnings("ignore", message=f".*{warning}.*", category=RuntimeWarning)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
set_debugmode(DEBUGMODE)
|
|
55
|
+
|
|
56
|
+
def timeout_handler(signum, frame): # pragma: no cover - deterministic signal hook
|
|
57
|
+
"""Raise a :class:`Timeout` when a POSIX alarm fires."""
|
|
58
|
+
msg = f"The calculation timed out after {TIMEOUT} seconds."
|
|
59
|
+
raise Timeout(msg)
|
|
60
|
+
|
|
61
|
+
signal.signal(signal.SIGALRM, timeout_handler)
|