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.
Files changed (95) hide show
  1. transitionlistener/__init__.py +21 -0
  2. transitionlistener/bubbledynamics.py +2250 -0
  3. transitionlistener/bubbledynamics_fixedstep.py +2929 -0
  4. transitionlistener/check_installation.py +309 -0
  5. transitionlistener/ckm.py +91 -0
  6. transitionlistener/colors.py +288 -0
  7. transitionlistener/config.py +185 -0
  8. transitionlistener/constants.py +64 -0
  9. transitionlistener/counterterms/__init__.py +230 -0
  10. transitionlistener/counterterms/cw.py +298 -0
  11. transitionlistener/counterterms/tensors.py +343 -0
  12. transitionlistener/counterterms/twohdm.py +520 -0
  13. transitionlistener/counterterms/twohdm_solver.py +374 -0
  14. transitionlistener/errors.py +315 -0
  15. transitionlistener/examples/bp_et.yaml +64 -0
  16. transitionlistener/examples/bp_lisa.yaml +70 -0
  17. transitionlistener/examples/bp_muares.yaml +69 -0
  18. transitionlistener/examples/bp_pta.yaml +64 -0
  19. transitionlistener/examples/example_dark_photon.yaml +73 -0
  20. transitionlistener/examples/example_grid.yaml +55 -0
  21. transitionlistener/examples/example_line.yaml +51 -0
  22. transitionlistener/examples/example_point.yaml +73 -0
  23. transitionlistener/examples/example_random_conformal.yaml +42 -0
  24. transitionlistener/examples/example_ultranest.yaml +39 -0
  25. transitionlistener/finiteT.py +468 -0
  26. transitionlistener/generic_potential.py +1521 -0
  27. transitionlistener/gridplots.py +592 -0
  28. transitionlistener/gwfopt.py +604 -0
  29. transitionlistener/helper_functions.py +843 -0
  30. transitionlistener/hydrodynamics.py +647 -0
  31. transitionlistener/interface/__init__.py +95 -0
  32. transitionlistener/interface/check.py +51 -0
  33. transitionlistener/interface/cli.py +211 -0
  34. transitionlistener/interface/config_loader.py +264 -0
  35. transitionlistener/interface/logging_utils.py +30 -0
  36. transitionlistener/interface/output_schema.py +296 -0
  37. transitionlistener/interface/pipeline.py +300 -0
  38. transitionlistener/interface/samplers.py +565 -0
  39. transitionlistener/interface/scans.py +468 -0
  40. transitionlistener/interface/single_point.py +581 -0
  41. transitionlistener/interface/state.py +61 -0
  42. transitionlistener/lineplots.py +597 -0
  43. transitionlistener/logo/TL-logo_large.png +0 -0
  44. transitionlistener/logo/TL-logo_small.png +0 -0
  45. transitionlistener/models/TL_2HDM.py +512 -0
  46. transitionlistener/models/TL_conformal_dark_u1.py +202 -0
  47. transitionlistener/models/TL_dark_U1.py +198 -0
  48. transitionlistener/models/TL_dark_U1_g_parameterization.py +198 -0
  49. transitionlistener/models/TL_dark_flipflop.py +274 -0
  50. transitionlistener/models/templatePotential.py +260 -0
  51. transitionlistener/nucleation.py +412 -0
  52. transitionlistener/observability.py +790 -0
  53. transitionlistener/particles.py +436 -0
  54. transitionlistener/pathDeformation.py +1462 -0
  55. transitionlistener/percolation_adaptive_gridbuilders.py +1385 -0
  56. transitionlistener/percolation_adaptive_rate.py +1049 -0
  57. transitionlistener/percolation_adaptivestepsize.py +1632 -0
  58. transitionlistener/percolation_fixedstepsize.py +618 -0
  59. transitionlistener/phases.py +1499 -0
  60. transitionlistener/plot_settings.py +121 -0
  61. transitionlistener/plots.py +1781 -0
  62. transitionlistener/runtime_options.py +278 -0
  63. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/bandwidths.npy +0 -0
  64. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/density.npy +0 -0
  65. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/freqs.npy +0 -0
  66. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log.txt +16 -0
  67. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rhogrid.npy +0 -0
  68. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rholabels.txt +30 -0
  69. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/pulsar_list.txt +1 -0
  70. transitionlistener/tab_data/HLV-O2-PLI.csv +82 -0
  71. transitionlistener/tab_data/HLV-O2-noise.csv +83 -0
  72. transitionlistener/tab_data/HLVK-design-PLI.csv +61 -0
  73. transitionlistener/tab_data/HLVK-design-noise.csv +83 -0
  74. transitionlistener/tab_data/NG15_PLI_hc.csv +31 -0
  75. transitionlistener/tab_data/NG15_noise_hc.csv +56 -0
  76. transitionlistener/tab_data/PLI-dataset.csv +327 -0
  77. transitionlistener/tab_data/energydensities.dat +1001 -0
  78. transitionlistener/tab_data/finiteT_b.dat.txt +1999 -0
  79. transitionlistener/tab_data/finiteT_f.dat.txt +1000 -0
  80. transitionlistener/tab_data/geff_SM.dat +10009 -0
  81. transitionlistener/tab_data/muAres_f_Hz_PLI_h2OmegaGW.csv +131 -0
  82. transitionlistener/tab_data/muAres_f_Hz_h2Omega_noise.csv +99 -0
  83. transitionlistener/tab_data/noise-dataset.csv +327 -0
  84. transitionlistener/tab_data/numberdensities.dat +1001 -0
  85. transitionlistener/tab_data/pressures.dat +1001 -0
  86. transitionlistener/thermodynamics.py +686 -0
  87. transitionlistener/transitionObservables.py +1199 -0
  88. transitionlistener/transitionObservables_fixedstep.py +976 -0
  89. transitionlistener/transitions.py +1000 -0
  90. transitionlistener/tunneling1D.py +910 -0
  91. transitionlistener-2.0.0.dist-info/METADATA +817 -0
  92. transitionlistener-2.0.0.dist-info/RECORD +95 -0
  93. transitionlistener-2.0.0.dist-info/WHEEL +4 -0
  94. transitionlistener-2.0.0.dist-info/entry_points.txt +2 -0
  95. transitionlistener-2.0.0.dist-info/licenses/LICENSE.txt +674 -0
@@ -0,0 +1,296 @@
1
+ """CSV output shaping utilities for TransitionListener interfaces.
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 os
14
+ from typing import Iterable, Sequence
15
+
16
+ import numpy as np
17
+ import pandas as pd
18
+
19
+ # Column groups with preferred ordering
20
+ PTA_LABEL_ORDER = ["NG15_14bins", "NG15_5bins", "NG12_5bins", "IPTA2_13bins"]
21
+ PTA_COLUMNS_ORDER = [
22
+ item
23
+ for label in PTA_LABEL_ORDER
24
+ for item in (
25
+ f"lnL_smoothened_{label}",
26
+ f"lnL_PTArcade_{label}",
27
+ f"lnL_mock_{label}",
28
+ )
29
+ ]
30
+ BASE_RESULT_COLUMNS = ["smoothened_lnL", "PTArcade_lnL", "mock_lnL", "error", "errormsg"]
31
+ DERIVED_PREFERRED_ORDER = [
32
+ "alpha",
33
+ "alpha_theta",
34
+ "alpha_thetabar",
35
+ "alpha_inf",
36
+ "alpha_eq",
37
+ "betaH_S3",
38
+ "betaH_RH",
39
+ "RH",
40
+ "Treh_SM_GeV",
41
+ "Tperc_SM_GeV",
42
+ "g_eff_tot_reh",
43
+ "h_eff_tot_reh",
44
+ "kappa_phi",
45
+ "kappa_sw",
46
+ "kappa_turb",
47
+ "g0",
48
+ "h0",
49
+ "v_wall",
50
+ "D",
51
+ "c_s",
52
+ "c_s_sym",
53
+ "c_s_bro",
54
+ "step",
55
+ "total_steps",
56
+ "Tnuc_SM_GeV",
57
+ "Tcrit_SM_GeV",
58
+ "Tf_SM_GeV",
59
+ "xi_crit",
60
+ ]
61
+ WARNING_COLUMN_ORDER = [
62
+ "WARNING:too_weak_to_compute_perc",
63
+ "WARNING:no_perc_splines",
64
+ "WARNING:betaH_small",
65
+ "WARNING:betaH_very_small",
66
+ "WARNING:betaH_mismatch",
67
+ "WARNING:betaH_nonfinite",
68
+ "WARNING:nucleationRate_nonexponential",
69
+ "WARNING:not_T0_global_min",
70
+ ]
71
+ SNR_COLUMN_ORDER = [
72
+ "B-DECIGO_SNR",
73
+ "BBO_SNR",
74
+ "DECIGO_SNR",
75
+ "EPTA_18_yrs_SNR",
76
+ "ET_SNR",
77
+ "HLVK_design_SNR",
78
+ "HLV_O2_SNR",
79
+ "LISA_SNR",
80
+ "NANOGrav_11_yrs_SNR",
81
+ "NANOGrav_15_yrs_SNR",
82
+ "SKA_10_yrs_SNR",
83
+ "SKA_20_yrs_SNR",
84
+ "SKA_5_yrs_SNR",
85
+ "muAres_SNR",
86
+ ]
87
+ PEAK_COLUMN_PRIORITY = [
88
+ "f_peak_Hz",
89
+ "h2OmegaGW_peak",
90
+ "f_pivot_Hz",
91
+ "h2OmegaGW_at_pivot",
92
+ "DNeff_GW",
93
+ ]
94
+ STATIC_ORDER_GROUPS = (
95
+ WARNING_COLUMN_ORDER,
96
+ PTA_COLUMNS_ORDER,
97
+ SNR_COLUMN_ORDER,
98
+ PEAK_COLUMN_PRIORITY,
99
+ )
100
+
101
+
102
+ class OutputRandomscan:
103
+ """Ouput handler for the randomscan.
104
+
105
+ It opens a file and appends new results as a row to it.
106
+ This is inteded to be used with the listener/worker threads in the randomscan
107
+ module.
108
+ """
109
+
110
+ def __init__(self, output_file: str):
111
+ """Open ``output_file`` in append mode and keep the handle alive across writes."""
112
+ self.file = output_file
113
+
114
+ try:
115
+ self.f = open(self.file, 'a')
116
+ except Exception as e:
117
+ print(e)
118
+
119
+ def stop(self):
120
+ """Stop the output handler and close the file.
121
+
122
+ Parameters
123
+ ----------
124
+ None.
125
+
126
+ Returns
127
+ -------
128
+ None."""
129
+ self.f.close()
130
+
131
+ def write_row(self, inp_params: dict, results: dict):
132
+ """Write a data point to the output file.
133
+
134
+ Parameters
135
+ ----------
136
+ inp_params : dict
137
+ The input parameters of the potential.
138
+ results : dict
139
+ The results that should be written to the csv file.
140
+
141
+ Returns
142
+ -------
143
+ None.
144
+ """
145
+ data = inp_params.copy()
146
+
147
+ if "_meta_mass_columns" in results.keys():
148
+ results.pop("_meta_mass_columns")
149
+
150
+ data.update(results)
151
+ if os.path.getsize(self.file) == 0:
152
+ # write the header:
153
+ header = ','.join(list(data.keys()))
154
+ self.f.write(header + '\n')
155
+
156
+ row = ','.join([str(val) for val in data.values()])
157
+ self.f.write(row + '\n')
158
+ self.f.flush()
159
+
160
+
161
+ class OutputSchema:
162
+ """Manage the CSV schema used for sampler outputs."""
163
+
164
+ def __init__(self, potential: object | None, derived_param_names: Iterable[str]):
165
+ """Store the optional potential factory and the preferred derived-column order."""
166
+ self.potential = potential
167
+ self.derived_keys = list(derived_param_names)
168
+
169
+ def append_row(self, output_file: str, inputparams_dict: dict, res_params: dict) -> None:
170
+ """Append a single result row to ``output_file`` using the configured schema."""
171
+ row, column_order = self._prepare_row(inputparams_dict, res_params)
172
+
173
+ file_exists = os.path.exists(output_file)
174
+ file_nonempty = file_exists and os.path.getsize(output_file) > 0
175
+
176
+ if file_nonempty:
177
+ existing_df = pd.read_csv(output_file)
178
+ total_order = list(dict.fromkeys(column_order + list(existing_df.columns)))
179
+ row_data = {col: row.get(col, np.nan) for col in total_order}
180
+ row_df = pd.DataFrame([row_data], columns=total_order)
181
+ existing_df = existing_df.reindex(columns=total_order, fill_value=np.nan)
182
+ combined = pd.concat([existing_df, row_df], ignore_index=True)
183
+ combined.to_csv(output_file, index=False)
184
+ return
185
+
186
+ row_data = {col: row.get(col, np.nan) for col in column_order}
187
+ row_df = pd.DataFrame([row_data], columns=column_order)
188
+ row_df.to_csv(output_file, index=False)
189
+
190
+ def _prepare_row(self, inputparams_dict: dict, res_params: dict) -> tuple[dict, list[str]]:
191
+ """Merge input/output data and return the normalised row plus its column order."""
192
+ row = {**inputparams_dict}
193
+ row.update(res_params)
194
+
195
+ mass_columns = list(row.pop("_meta_mass_columns", []))
196
+ row.pop("lnL", None)
197
+
198
+ meta_keys = [key for key in row if key.startswith("_meta_")]
199
+ for key in meta_keys:
200
+ row.pop(key, None)
201
+
202
+ error_value = row.get("error")
203
+ if self.potential is not None and error_value not in (0, None):
204
+ for column, value in self._compute_mass_columns(inputparams_dict):
205
+ if column not in mass_columns:
206
+ mass_columns.append(column)
207
+ row[column] = value
208
+
209
+ column_order = _build_column_order(
210
+ inputparams_dict,
211
+ self.derived_keys,
212
+ row.keys(),
213
+ mass_columns,
214
+ )
215
+ return row, column_order
216
+
217
+ def _compute_mass_columns(self, inputparams_dict: dict) -> list[tuple[str, float]]:
218
+ """Compute fallback zero-temperature mass columns for failed parameter points."""
219
+ if self.potential is None:
220
+ return []
221
+ try:
222
+ potential = self.potential(inputparams_dict, verbose=False) # type: ignore[attr-defined]
223
+ except Exception:
224
+ return []
225
+
226
+ getter = getattr(potential, "get_zero_temperature_mass_spectrum", None)
227
+ if getter is None:
228
+ return []
229
+
230
+ try:
231
+ entries = getter()
232
+ except Exception:
233
+ entries = []
234
+
235
+ used_columns: set[str] = set()
236
+ results: list[tuple[str, float]] = []
237
+
238
+ def _unique_column(base: str) -> str:
239
+ candidate = base or "mass"
240
+ index = 2
241
+ while candidate in used_columns:
242
+ candidate = f"{base}_{index}"
243
+ index += 1
244
+ used_columns.add(candidate)
245
+ return candidate
246
+
247
+ for entry in entries or []:
248
+ text_label = str(entry.get("text", "")).strip()
249
+ kind = entry.get("kind", "boson")
250
+ index = int(entry.get("index", 0))
251
+ base_name = text_label or f"mass_spectrum_T0_{kind}_{index:02d}"
252
+ column = _unique_column(base_name)
253
+
254
+ mass_value = entry.get("mass_GeV", np.nan)
255
+ if isinstance(mass_value, (np.ndarray, list, tuple)):
256
+ mass_value = np.asarray(mass_value).flatten()[0] if np.asarray(mass_value).size else np.nan
257
+ try:
258
+ value = float(mass_value)
259
+ except (TypeError, ValueError):
260
+ value = np.nan
261
+
262
+ if not np.isfinite(value):
263
+ if column.startswith("m_G"):
264
+ value = 0.0
265
+ else:
266
+ value = np.nan
267
+
268
+ results.append((column, value))
269
+
270
+ return results
271
+
272
+
273
+ def _build_column_order(
274
+ inputparams_dict: dict,
275
+ derived_keys: Sequence[str],
276
+ row_keys: Iterable[str],
277
+ mass_columns: Sequence[str],
278
+ ) -> list[str]:
279
+ """Construct a stable CSV column order that keeps related observables grouped together."""
280
+ base_order: list[str] = list(inputparams_dict.keys())
281
+ base_order.extend(BASE_RESULT_COLUMNS)
282
+ base_order.extend(
283
+ col for col in DERIVED_PREFERRED_ORDER if col in derived_keys or col in row_keys
284
+ )
285
+ base_order.extend(col for col in derived_keys if col not in DERIVED_PREFERRED_ORDER)
286
+ for group in STATIC_ORDER_GROUPS:
287
+ base_order.extend(group)
288
+
289
+ remaining = sorted(
290
+ col
291
+ for col in row_keys
292
+ if col not in base_order and col not in mass_columns
293
+ )
294
+ base_order.extend(remaining)
295
+ base_order.extend(mass_columns)
296
+ return list(dict.fromkeys(base_order))
@@ -0,0 +1,300 @@
1
+ """Core execution pipeline for evaluating a single parameter point.
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 signal
14
+ from typing import Callable, Dict, Optional
15
+
16
+ import numpy as np
17
+
18
+ from transitionlistener.phases import Phases
19
+ from transitionlistener.transitions import Transitions
20
+ from transitionlistener.transitionObservables import (
21
+ TransitionObservables as _TransitionObservablesAdaptiveStepSize,
22
+ )
23
+ from transitionlistener.transitionObservables_fixedstep import (
24
+ TransitionObservables as _TransitionObservablesFixedStepSize,
25
+ )
26
+
27
+
28
+ def _select_observables_class(pot):
29
+ """Return the TransitionObservables class for the configured algorithm_mode."""
30
+ if pot.config.percolationConf.algorithm_mode == "fixed_step_size":
31
+ return _TransitionObservablesFixedStepSize
32
+ return _TransitionObservablesAdaptiveStepSize
33
+ from transitionlistener.gwfopt import FOPTspectrum
34
+ from transitionlistener.observability import Observability
35
+
36
+ from transitionlistener import console
37
+ from rich.panel import Panel
38
+
39
+ from . import state
40
+ from .logging_utils import Logger
41
+
42
+
43
+ def _find_strongest_transition(transition_observables: dict) -> dict:
44
+ """Return the transition entry with the largest alpha among FOPTs."""
45
+ max_alpha = -np.inf
46
+ strongest_transition = None
47
+ fallback_transition = None
48
+ for obs in transition_observables.values():
49
+ if not obs or obs.get("trantype", 1) != 1:
50
+ continue
51
+ if fallback_transition is None and np.isfinite(obs.get("Treh_SM_GeV", np.nan)):
52
+ fallback_transition = obs
53
+ alpha = obs.get("alpha", np.nan)
54
+ if np.isfinite(alpha) and alpha > max_alpha:
55
+ max_alpha = alpha
56
+ strongest_transition = obs
57
+ return strongest_transition or fallback_transition or {}
58
+
59
+
60
+ def _extract_zero_temperature_mass_spectrum(potential) -> dict:
61
+ """Return observability-style entries describing the T=0 mass spectrum."""
62
+
63
+ if potential is None or not hasattr(potential, "get_zero_temperature_mass_spectrum"):
64
+ return {}
65
+
66
+ entries = potential.get_zero_temperature_mass_spectrum()
67
+ if not entries:
68
+ return {}
69
+
70
+ spectrum_entries: Dict[str, object] = {}
71
+ for entry in entries:
72
+ kind = entry.get("kind", "boson")
73
+ index = entry.get("index", 0)
74
+ base_name = f"mass_spectrum_T0_{kind}_{index:02d}"
75
+ mass_value = entry.get("mass_GeV", np.nan)
76
+ log_value = entry.get("log10_mass", np.nan)
77
+ latex_label = entry.get("latex")
78
+ text_label = entry.get("text")
79
+
80
+ if isinstance(mass_value, (int, float, np.integer, np.floating)) and np.isfinite(mass_value):
81
+ spectrum_entries[f"{base_name}_lin"] = float(mass_value)
82
+ else:
83
+ spectrum_entries[f"{base_name}_lin"] = np.nan
84
+ if isinstance(log_value, (int, float, np.integer, np.floating)) and np.isfinite(log_value):
85
+ spectrum_entries[f"{base_name}_log10"] = float(log_value)
86
+ else:
87
+ spectrum_entries[f"{base_name}_log10"] = np.nan
88
+ if latex_label is not None:
89
+ spectrum_entries[f"{base_name}_latex"] = latex_label
90
+ if text_label is not None:
91
+ spectrum_entries[f"{base_name}_text"] = text_label
92
+ spectrum_entries[f"{base_name}_kind"] = kind
93
+
94
+ return spectrum_entries
95
+
96
+
97
+ def _compute_single_point(
98
+ inputparams_dict: dict,
99
+ potential: object,
100
+ timeout: float,
101
+ verbose: bool,
102
+ include_smbhb: bool,
103
+ max_stage: int = 4,
104
+ stage_callback: Optional[Callable[[str, dict], None]] = None,
105
+ ):
106
+ """Execute the single-point pipeline up to ``max_stage`` and optionally
107
+ notify after each stage."""
108
+
109
+ context: Dict[str, object] = {
110
+ "pot": None,
111
+ "phases": None,
112
+ "transitions": None,
113
+ "transition_observables": None,
114
+ "strongest_transition_observables": None,
115
+ "gwspectrum": None,
116
+ "observability": None,
117
+ }
118
+
119
+ def notify(stage_name: str):
120
+ if stage_callback is not None:
121
+ stage_callback(stage_name, context)
122
+
123
+ if include_smbhb:
124
+ A = inputparams_dict.get("A_smbhb", 1e-15)
125
+ gamma = inputparams_dict.get("gamma_smbhb", 2 / 3)
126
+ astroparams_dict = {"A": A, "gamma": gamma}
127
+ else:
128
+ astroparams_dict = {"A": 0, "gamma": 0}
129
+
130
+ if timeout > 0 and not state.DEBUGMODE:
131
+ signal.alarm(timeout)
132
+ try:
133
+ context["pot"] = potential(inputparams_dict, verbose=verbose)
134
+ notify("potential")
135
+
136
+ if max_stage >= 1:
137
+ context["phases"] = Phases(context["pot"], verbose)
138
+ notify("phases")
139
+
140
+ if max_stage >= 2:
141
+ context["transitions"] = Transitions(context["phases"],
142
+ context["pot"], verbose)
143
+ notify("transitions")
144
+
145
+ if max_stage >= 3:
146
+ TransitionObservables = _select_observables_class(context["pot"])
147
+ transition_observables = TransitionObservables(
148
+ context["pot"], context["phases"],
149
+ context["transitions"], verbose
150
+ )
151
+ context["transition_observables"] = transition_observables
152
+ context["strongest_transition_observables"] = _find_strongest_transition(
153
+ transition_observables.transitionObservables
154
+ )
155
+ notify("observables")
156
+
157
+ if max_stage >= 4:
158
+ try:
159
+ gwspectrum = FOPTspectrum(
160
+ context["strongest_transition_observables"],
161
+ astroparams_dict, verbose
162
+ )
163
+ observability = Observability(
164
+ gwspectrum, verbose, include_smbhb=include_smbhb
165
+ )
166
+ context["gwspectrum"] = gwspectrum
167
+ context["observability"] = observability
168
+ notify("gw")
169
+ except Exception as err:
170
+ if context.get("transition_observables") is None:
171
+ raise
172
+ if verbose:
173
+ print(f"Skipping GW observability after transition observables: {err}")
174
+
175
+ signal.alarm(0)
176
+ return context, None
177
+ except Exception as err:
178
+ signal.alarm(0)
179
+ if state.DEBUGMODE:
180
+ raise
181
+ return context, err
182
+
183
+
184
+ def _build_result_from_context(context: dict) -> dict:
185
+ """Construct the result dictionary returned by ``run_TL``
186
+ from the computation context."""
187
+ transition_observables = context.get("transition_observables")
188
+ strongest = context.get("strongest_transition_observables") or {}
189
+ gwspectrum = context.get("gwspectrum")
190
+ observability = context.get("observability")
191
+ potential = context.get("pot")
192
+
193
+ observability_dict = (
194
+ observability.observability_dict.copy()
195
+ if observability is not None
196
+ else {}
197
+ )
198
+
199
+ return {
200
+ "transitionObservables": transition_observables.transitionObservables
201
+ if transition_observables is not None
202
+ else {},
203
+ "strongestTransitionObservables": strongest,
204
+ "gwspectrum": gwspectrum.gwspec_dict if gwspectrum is not None else {},
205
+ "observability": observability_dict,
206
+ "error": np.nan,
207
+ }
208
+
209
+
210
+ def _handle_single_point_error(
211
+ error: Exception,
212
+ inputparams_dict: dict,
213
+ errorlogger: Logger,
214
+ context: dict,
215
+ ):
216
+ """Log the error and shape a result dictionary containing
217
+ available partial results."""
218
+ error_code = getattr(error, "errorcode", -1)
219
+ message = getattr(error, "message", str(error))
220
+ line = (
221
+ "ERROR of type "
222
+ + str(error_code)
223
+ + " for input parameters "
224
+ + str(inputparams_dict)
225
+ + ": "
226
+ + message
227
+ )
228
+ errorlogger.log(line + "\n")
229
+ console.print(Panel.fit(line, border_style="red"))
230
+
231
+ result = {
232
+ "error": np.array([error_code]),
233
+ }
234
+ transition_observables = context.get("transition_observables")
235
+ if transition_observables is not None:
236
+ result["transitionObservables"] = transition_observables.transitionObservables
237
+ strongest = context.get("strongest_transition_observables")
238
+ if strongest:
239
+ result["strongestTransitionObservables"] = strongest
240
+ gwspectrum = context.get("gwspectrum")
241
+ if gwspectrum is not None:
242
+ result["gwspectrum"] = gwspectrum.gwspec_dict
243
+ observability = context.get("observability")
244
+ potential = context.get("pot")
245
+ observability_dict = (
246
+ observability.observability_dict.copy()
247
+ if observability is not None
248
+ else {}
249
+ )
250
+ spectrum_entries = _extract_zero_temperature_mass_spectrum(potential)
251
+ observability_dict.update(spectrum_entries)
252
+ if observability_dict:
253
+ result["observability"] = observability_dict
254
+
255
+ return result
256
+
257
+
258
+ def run_TL(
259
+ inputparams_dict: dict,
260
+ potential: object,
261
+ errorlogger: Logger,
262
+ resultlogger: Logger,
263
+ timeout: float,
264
+ verbose: bool = False,
265
+ call_from_sampler: bool = False,
266
+ include_smbhb: bool = False,
267
+ return_context: bool = False,
268
+ ):
269
+ """Core execution pipeline that evaluates observables for a
270
+ single parameter point."""
271
+
272
+ def return_result(params):
273
+ observability = params.get("observability", {})
274
+ line = str(inputparams_dict) + ":" + str(observability)
275
+ resultlogger.log(line + "\n")
276
+
277
+ context, error = _compute_single_point(
278
+ inputparams_dict,
279
+ potential,
280
+ timeout,
281
+ verbose,
282
+ include_smbhb,
283
+ max_stage=4,
284
+ )
285
+
286
+ if error is not None:
287
+ if call_from_sampler:
288
+ raise error
289
+ result = _handle_single_point_error(error, inputparams_dict,
290
+ errorlogger, context)
291
+ if return_context:
292
+ return result, context
293
+ return result
294
+
295
+ all_params_dict = _build_result_from_context(context)
296
+ if resultlogger is not None:
297
+ return_result(all_params_dict)
298
+ if return_context:
299
+ return all_params_dict, context
300
+ return all_params_dict