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,597 @@
|
|
|
1
|
+
"""Helpers to visualise line scans of TransitionListener observables.
|
|
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 re
|
|
14
|
+
|
|
15
|
+
from .observability import GW_Sensitivity_Data
|
|
16
|
+
import numpy as np
|
|
17
|
+
import matplotlib.pyplot as plt
|
|
18
|
+
from matplotlib.ticker import FixedLocator, FuncFormatter
|
|
19
|
+
from .colors import *
|
|
20
|
+
from .plot_settings import plot_settings as TL_plot_settings
|
|
21
|
+
from .plot_settings import add_TL_logo
|
|
22
|
+
from .plot_settings import TLcmap
|
|
23
|
+
|
|
24
|
+
_MASS_GROUP_PREFIX = "mass_spectrum_T0_"
|
|
25
|
+
_MASS_KIND_ORDER = {"boson": 0, "fermion": 1}
|
|
26
|
+
|
|
27
|
+
plt.rcParams.update(TL_plot_settings)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _as_scalar_or_nan(value) -> float:
|
|
31
|
+
"""Convert an arbitrary value to a scalar float when possible."""
|
|
32
|
+
try:
|
|
33
|
+
arr = np.asarray(value)
|
|
34
|
+
except Exception:
|
|
35
|
+
return np.nan
|
|
36
|
+
if arr.size == 0:
|
|
37
|
+
return np.nan
|
|
38
|
+
if arr.size == 1:
|
|
39
|
+
try:
|
|
40
|
+
return float(np.squeeze(arr))
|
|
41
|
+
except Exception:
|
|
42
|
+
return np.nan
|
|
43
|
+
return np.nan
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _detectable_from_snr(snr: float, threshold: float) -> bool:
|
|
47
|
+
"""Infer detectability from SNR when legacy scan exports omit the flag."""
|
|
48
|
+
return bool(
|
|
49
|
+
np.isfinite(snr)
|
|
50
|
+
and np.isfinite(threshold)
|
|
51
|
+
and snr > 0.0
|
|
52
|
+
and threshold > 0.0
|
|
53
|
+
and snr > threshold
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _prepare_axes(ax : plt.Axes):
|
|
58
|
+
"""Apply common styling to the axes of a line plot."""
|
|
59
|
+
ax.tick_params(axis='both', which='both',
|
|
60
|
+
direction="in", width=0.5, zorder=1000)
|
|
61
|
+
ax.xaxis.set_ticks_position('both')
|
|
62
|
+
ax.yaxis.set_ticks_position('both')
|
|
63
|
+
for axis in ["top", 'bottom','left','right']:
|
|
64
|
+
ax.spines[axis].set_linewidth(0.5)
|
|
65
|
+
ax.spines[axis].set_zorder(10000)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _pow10_tick_formatter(value: float, _pos: float) -> str:
|
|
69
|
+
"""Format a log10-space value as 10^x."""
|
|
70
|
+
if not np.isfinite(value):
|
|
71
|
+
return ""
|
|
72
|
+
rounded = int(np.round(value))
|
|
73
|
+
if abs(value - rounded) < 1e-8:
|
|
74
|
+
return rf"$10^{{{rounded}}}$"
|
|
75
|
+
return rf"$10^{{{value:.1f}}}$"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _style_log10_y_axis(ax: plt.Axes) -> None:
|
|
79
|
+
"""Display y-axis values (stored in log10 space) as powers of ten."""
|
|
80
|
+
y0, y1 = ax.get_ylim()
|
|
81
|
+
if not (np.isfinite(y0) and np.isfinite(y1)):
|
|
82
|
+
return
|
|
83
|
+
lo, hi = (y0, y1) if y0 <= y1 else (y1, y0)
|
|
84
|
+
|
|
85
|
+
major_start = int(np.floor(lo))
|
|
86
|
+
major_end = int(np.ceil(hi))
|
|
87
|
+
major = np.arange(major_start, major_end + 1, dtype=float)
|
|
88
|
+
if major.size == 0:
|
|
89
|
+
major = np.array([np.round(0.5 * (lo + hi))], dtype=float)
|
|
90
|
+
|
|
91
|
+
minor: list[float] = []
|
|
92
|
+
for exp in range(major_start - 1, major_end + 1):
|
|
93
|
+
for digit in range(2, 10):
|
|
94
|
+
val = exp + np.log10(digit)
|
|
95
|
+
if lo <= val <= hi:
|
|
96
|
+
minor.append(val)
|
|
97
|
+
|
|
98
|
+
ax.yaxis.set_major_locator(FixedLocator(major))
|
|
99
|
+
if minor:
|
|
100
|
+
ax.yaxis.set_minor_locator(FixedLocator(minor))
|
|
101
|
+
ax.yaxis.set_major_formatter(FuncFormatter(_pow10_tick_formatter))
|
|
102
|
+
ax.tick_params(axis="y", which="major", length=3.5)
|
|
103
|
+
ax.tick_params(axis="y", which="minor", length=2.0)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _show_every_second_major_ylabel(ax: plt.Axes) -> None:
|
|
107
|
+
"""Keep all major ticks but label only every second one."""
|
|
108
|
+
for idx, tick in enumerate(ax.yaxis.get_major_ticks()):
|
|
109
|
+
visible = (idx % 2 == 0)
|
|
110
|
+
tick.label1.set_visible(visible)
|
|
111
|
+
# Keep right-side ticks but avoid a second labeled y-axis.
|
|
112
|
+
tick.label2.set_visible(False)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _finish_plot(fig : plt.Figure, ax: plt.Axes, x_plot_name: str, scale: str,
|
|
116
|
+
x: np.ndarray, title: str, save_path: str):
|
|
117
|
+
"""Configure the axes of a line plot and save the figure."""
|
|
118
|
+
_prepare_axes(ax)
|
|
119
|
+
ax.set_xlabel(x_plot_name)
|
|
120
|
+
if scale == 'log':
|
|
121
|
+
ax.set_xscale('log')
|
|
122
|
+
ax.set_xlim(x[0], x[-1])
|
|
123
|
+
ax.set_title(title)
|
|
124
|
+
add_TL_logo(loc="upper right", ax=ax)
|
|
125
|
+
plt.savefig(save_path)
|
|
126
|
+
plt.close(fig)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _plot_scan_series(ax: plt.Axes, x: np.ndarray, values: np.ndarray, *,
|
|
130
|
+
show_points: bool = True, **kwargs):
|
|
131
|
+
"""Draw scan curves with small markers so isolated finite points are visible."""
|
|
132
|
+
style = dict(
|
|
133
|
+
marker="o" if show_points else None,
|
|
134
|
+
markersize=2.4,
|
|
135
|
+
linewidth=1.2,
|
|
136
|
+
zorder=2,
|
|
137
|
+
)
|
|
138
|
+
style.update(kwargs)
|
|
139
|
+
ax.plot(x, values, **style)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class plot1dData(GW_Sensitivity_Data):
|
|
143
|
+
"""Render one-dimensional scan outputs as line plots and diagnostic summaries."""
|
|
144
|
+
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
data_dict_array,
|
|
148
|
+
x,
|
|
149
|
+
scale,
|
|
150
|
+
x_plot_name,
|
|
151
|
+
foldername,
|
|
152
|
+
derived_params_names,
|
|
153
|
+
derived_params_plot_names,
|
|
154
|
+
overview_title,
|
|
155
|
+
show_scan_points=True,
|
|
156
|
+
):
|
|
157
|
+
"""Cache the scan data and prepare intermediate arrays for plotting.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
data_dict_array : Sequence[dict]
|
|
162
|
+
List of dictionaries produced by ``run_TL`` along the scan axis.
|
|
163
|
+
x : numpy.ndarray
|
|
164
|
+
Sample points of the scan axis.
|
|
165
|
+
scale : str
|
|
166
|
+
Axis scaling mode, either ``\"lin\"`` or ``\"log\"``.
|
|
167
|
+
x_plot_name : str
|
|
168
|
+
Label for the horizontal axis (LaTeX strings supported).
|
|
169
|
+
foldername : str
|
|
170
|
+
Output directory where exported figures and tables are written.
|
|
171
|
+
derived_params_names : Iterable[str]
|
|
172
|
+
Keys of derived observables to extract from ``strongestTransitionObservables``.
|
|
173
|
+
derived_params_plot_names : Iterable[str]
|
|
174
|
+
Human-readable labels corresponding to ``derived_params_names``.
|
|
175
|
+
overview_title : str
|
|
176
|
+
Title used in the overview figure.
|
|
177
|
+
"""
|
|
178
|
+
super().__init__()
|
|
179
|
+
self.foldername = foldername
|
|
180
|
+
self.data_dict_array = data_dict_array
|
|
181
|
+
self.scale = scale
|
|
182
|
+
self.x_plot_name = x_plot_name
|
|
183
|
+
self.derived_params_names = derived_params_names
|
|
184
|
+
self.derived_params_plot_names = derived_params_plot_names
|
|
185
|
+
self.overview_title = overview_title
|
|
186
|
+
self.show_scan_points = show_scan_points
|
|
187
|
+
self.x = x
|
|
188
|
+
self.N = len(x)
|
|
189
|
+
self.SNR, self.detectable, self.logL = self.retrieve_observability_data()
|
|
190
|
+
self.derived_params_array_dict = self.retrieve_derived_params()
|
|
191
|
+
self.add_spec_info = self.retrieve_add_spec_info()
|
|
192
|
+
|
|
193
|
+
self.log_params = dict()
|
|
194
|
+
self.lin_params = dict()
|
|
195
|
+
|
|
196
|
+
for name in self.derived_params_names:
|
|
197
|
+
with np.errstate(invalid="ignore", divide="ignore"):
|
|
198
|
+
self.log_params[name] = np.log10(self.derived_params_array_dict[name])
|
|
199
|
+
self.lin_params[name] = self.derived_params_array_dict[name]
|
|
200
|
+
|
|
201
|
+
def retrieve_observability_data(self):
|
|
202
|
+
"""Extract detector SNRs, detectability flags and PTA log-likelihoods."""
|
|
203
|
+
n = self.N
|
|
204
|
+
SNR = np.full((n, len(self.det_names)), np.nan)
|
|
205
|
+
detectable = np.full((n, len(self.det_names)), False)
|
|
206
|
+
logL = np.full((n, len(self.PTA_names)), np.nan)
|
|
207
|
+
|
|
208
|
+
for i in range(n):
|
|
209
|
+
if "observability" in self.data_dict_array[i]:
|
|
210
|
+
obs_dict = self.data_dict_array[i]["observability"]
|
|
211
|
+
for k, dn in enumerate(self.det_names):
|
|
212
|
+
if dn + "_SNR" in obs_dict:
|
|
213
|
+
SNR[i, k] = _as_scalar_or_nan(
|
|
214
|
+
obs_dict[dn + "_SNR"]
|
|
215
|
+
)
|
|
216
|
+
if dn + "_detectable" in obs_dict:
|
|
217
|
+
detectable[i, k] = bool(
|
|
218
|
+
_as_scalar_or_nan(
|
|
219
|
+
obs_dict[dn + "_detectable"]
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
detectable[i, k] = _detectable_from_snr(
|
|
224
|
+
SNR[i, k],
|
|
225
|
+
self.det[dn]["thr"],
|
|
226
|
+
)
|
|
227
|
+
for l, pl in enumerate(self.PTA_labels):
|
|
228
|
+
if "logL_" + pl in obs_dict:
|
|
229
|
+
logL[i, l] = _as_scalar_or_nan(
|
|
230
|
+
obs_dict["logL_" + pl]
|
|
231
|
+
)
|
|
232
|
+
return SNR, detectable, logL
|
|
233
|
+
|
|
234
|
+
def retrieve_derived_params(self):
|
|
235
|
+
"""Collect derived parameters and error codes from the scan output."""
|
|
236
|
+
n = self.N
|
|
237
|
+
derived_params = dict()
|
|
238
|
+
for name in self.derived_params_names:
|
|
239
|
+
derived_params[name] = np.full(n, np.nan, dtype=float)
|
|
240
|
+
derived_params["error"] = np.full(n, np.nan, dtype=float)
|
|
241
|
+
|
|
242
|
+
for i in range(n):
|
|
243
|
+
for name in self.derived_params_names:
|
|
244
|
+
if "strongestTransitionObservables" in self.data_dict_array[i]:
|
|
245
|
+
if name in self.data_dict_array[i]["strongestTransitionObservables"]:
|
|
246
|
+
derived_params[name][i] = _as_scalar_or_nan(
|
|
247
|
+
self.data_dict_array[i]["strongestTransitionObservables"][name]
|
|
248
|
+
)
|
|
249
|
+
if "error" in self.data_dict_array[i]:
|
|
250
|
+
derived_params["error"][i] = _as_scalar_or_nan(
|
|
251
|
+
self.data_dict_array[i]["error"]
|
|
252
|
+
)
|
|
253
|
+
return derived_params
|
|
254
|
+
|
|
255
|
+
def retrieve_add_spec_info(self):
|
|
256
|
+
"""Gather additional observability metadata that is not an SNR or log-likelihood."""
|
|
257
|
+
n = self.N
|
|
258
|
+
|
|
259
|
+
add_spec_names = set()
|
|
260
|
+
latex_labels: dict[str, str | None] = {}
|
|
261
|
+
text_labels: dict[str, str | None] = {}
|
|
262
|
+
kind_labels: dict[str, str | None] = {}
|
|
263
|
+
for i in range(n):
|
|
264
|
+
obs_dict = self.data_dict_array[i].get("observability", {})
|
|
265
|
+
for key in obs_dict:
|
|
266
|
+
if any(marker in key for marker in ["SNR", "logL", "detectable"]):
|
|
267
|
+
continue
|
|
268
|
+
if key.endswith("_lin"):
|
|
269
|
+
base_name = key[:-4]
|
|
270
|
+
add_spec_names.add(base_name)
|
|
271
|
+
latex_labels.setdefault(base_name, obs_dict.get(f"{base_name}_latex"))
|
|
272
|
+
text_labels.setdefault(base_name, obs_dict.get(f"{base_name}_text"))
|
|
273
|
+
kind_labels.setdefault(base_name, obs_dict.get(f"{base_name}_kind"))
|
|
274
|
+
|
|
275
|
+
mass_group_members: list[tuple[int, int, str, str | None]] = []
|
|
276
|
+
for name in add_spec_names:
|
|
277
|
+
if not name.startswith(_MASS_GROUP_PREFIX):
|
|
278
|
+
continue
|
|
279
|
+
suffix = name[len(_MASS_GROUP_PREFIX):]
|
|
280
|
+
parts = suffix.split("_", 1)
|
|
281
|
+
if len(parts) != 2:
|
|
282
|
+
continue
|
|
283
|
+
kind, index_str = parts
|
|
284
|
+
try:
|
|
285
|
+
index = int(index_str)
|
|
286
|
+
except ValueError:
|
|
287
|
+
continue
|
|
288
|
+
order = _MASS_KIND_ORDER.get(kind, 99)
|
|
289
|
+
mass_group_members.append((order, index, name, kind))
|
|
290
|
+
|
|
291
|
+
groups = {}
|
|
292
|
+
if mass_group_members:
|
|
293
|
+
groups["mass_spectrum_T0"] = [
|
|
294
|
+
{"name": name, "index": index, "kind": kind}
|
|
295
|
+
for order, index, name, kind in sorted(mass_group_members)
|
|
296
|
+
]
|
|
297
|
+
|
|
298
|
+
add_spec_info_lin = {name: np.full(n, np.nan, dtype=float) for name in add_spec_names}
|
|
299
|
+
add_spec_info_log10 = {name: np.full(n, np.nan, dtype=float) for name in add_spec_names}
|
|
300
|
+
|
|
301
|
+
for i in range(n):
|
|
302
|
+
obs_dict = self.data_dict_array[i].get("observability", {})
|
|
303
|
+
for name in add_spec_names:
|
|
304
|
+
lin_key = f"{name}_lin"
|
|
305
|
+
log_key = f"{name}_log10"
|
|
306
|
+
if lin_key in obs_dict:
|
|
307
|
+
add_spec_info_lin[name][i] = _as_scalar_or_nan(obs_dict[lin_key])
|
|
308
|
+
if log_key in obs_dict:
|
|
309
|
+
add_spec_info_log10[name][i] = _as_scalar_or_nan(obs_dict[log_key])
|
|
310
|
+
if name not in latex_labels or latex_labels[name] is None:
|
|
311
|
+
latex_labels[name] = obs_dict.get(f"{name}_latex", latex_labels.get(name))
|
|
312
|
+
if name not in text_labels or text_labels[name] is None:
|
|
313
|
+
text_labels[name] = obs_dict.get(f"{name}_text", text_labels.get(name))
|
|
314
|
+
if name not in kind_labels or kind_labels[name] is None:
|
|
315
|
+
kind_labels[name] = obs_dict.get(f"{name}_kind", kind_labels.get(name))
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
"lin": add_spec_info_lin,
|
|
319
|
+
"log10": add_spec_info_log10,
|
|
320
|
+
"latex": {name: latex_labels.get(name, name) for name in add_spec_names},
|
|
321
|
+
"text": {name: text_labels.get(name) for name in add_spec_names},
|
|
322
|
+
"kind": {name: kind_labels.get(name) for name in add_spec_names},
|
|
323
|
+
"groups": groups,
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
def plot_SNRs(self):
|
|
327
|
+
"""Generate line plots of the signal-to-noise ratio for every detector."""
|
|
328
|
+
for dn in self.det_names:
|
|
329
|
+
detector_index = self.det_names.index(dn)
|
|
330
|
+
SNR = self.SNR[:, detector_index]
|
|
331
|
+
thr = self.det[dn]["thr"]
|
|
332
|
+
|
|
333
|
+
fig, ax = plt.subplots()
|
|
334
|
+
_plot_scan_series(ax, self.x, SNR, show_points=self.show_scan_points)
|
|
335
|
+
ax.axhline(thr, color='red', linestyle='--', zorder=1)
|
|
336
|
+
ax.set_yscale('log')
|
|
337
|
+
_finish_plot(fig, ax, self.x_plot_name, self.scale, self.x,
|
|
338
|
+
R"SNR for "+self.det[dn]["plot_name"], self.foldername+"SNR_"+dn+".pdf")
|
|
339
|
+
|
|
340
|
+
def plot_logLs(self):
|
|
341
|
+
"""Generate line plots of the PTA log-likelihood for every collaboration."""
|
|
342
|
+
for pl in self.PTA_labels:
|
|
343
|
+
detector_index = self.PTA_labels.index(pl)
|
|
344
|
+
logL = self.logL[:, detector_index]
|
|
345
|
+
fig, ax = plt.subplots()
|
|
346
|
+
_plot_scan_series(ax, self.x, logL, show_points=self.show_scan_points)
|
|
347
|
+
_finish_plot(fig, ax, self.x_plot_name, self.scale, self.x,
|
|
348
|
+
R"$\log L$ for "+pl, self.foldername+"logL_"+pl+".pdf")
|
|
349
|
+
|
|
350
|
+
def save_SNRs(self):
|
|
351
|
+
"""Export the log10 SNR array as plain-text tables."""
|
|
352
|
+
for dn in self.det_names:
|
|
353
|
+
detector_index = self.det_names.index(dn)
|
|
354
|
+
np.seterr(invalid="ignore")
|
|
355
|
+
logSNR = np.log10(self.SNR[:, detector_index])
|
|
356
|
+
np.seterr(invalid="warn")
|
|
357
|
+
np.savetxt(self.foldername+"log_SNR_"+dn+".txt", logSNR)
|
|
358
|
+
|
|
359
|
+
def save_logLs(self):
|
|
360
|
+
"""Export the PTA log-likelihood arrays as plain-text tables."""
|
|
361
|
+
for i, pl in enumerate(self.PTA_labels):
|
|
362
|
+
logL = self.logL[:, i]
|
|
363
|
+
np.savetxt(self.foldername+"log_L_"+pl+".txt", logL)
|
|
364
|
+
|
|
365
|
+
def plot_add_infos(self):
|
|
366
|
+
"""Visualise auxiliary observability quantities along the scan axis."""
|
|
367
|
+
add_spec_info_lin = self.add_spec_info["lin"]
|
|
368
|
+
add_spec_info_log10 = self.add_spec_info["log10"]
|
|
369
|
+
add_spec_info_latex = self.add_spec_info["latex"]
|
|
370
|
+
|
|
371
|
+
mass_group = {
|
|
372
|
+
member.get("name")
|
|
373
|
+
for member in self.add_spec_info.get("groups", {}).get("mass_spectrum_T0", [])
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
for pname, values in add_spec_info_lin.items():
|
|
377
|
+
if pname in mass_group:
|
|
378
|
+
continue
|
|
379
|
+
if np.all(np.isnan(values)):
|
|
380
|
+
continue
|
|
381
|
+
fig, ax = plt.subplots()
|
|
382
|
+
_plot_scan_series(ax, self.x, values, show_points=self.show_scan_points)
|
|
383
|
+
_finish_plot(fig, ax, self.x_plot_name, self.scale, self.x,
|
|
384
|
+
add_spec_info_latex.get(pname, pname),
|
|
385
|
+
self.foldername+"Add_info_"+pname+".pdf")
|
|
386
|
+
|
|
387
|
+
log_values = add_spec_info_log10.get(pname)
|
|
388
|
+
if log_values is None or np.all(np.isnan(log_values)):
|
|
389
|
+
continue
|
|
390
|
+
fig, ax = plt.subplots()
|
|
391
|
+
_plot_scan_series(ax, self.x, log_values, show_points=self.show_scan_points)
|
|
392
|
+
_finish_plot(fig, ax, self.x_plot_name, self.scale, self.x,
|
|
393
|
+
r"$\log_{10}$ " + add_spec_info_latex.get(pname, pname),
|
|
394
|
+
self.foldername+"Add_info_log10_"+pname+".pdf")
|
|
395
|
+
|
|
396
|
+
def plot_mass_spectrum(self):
|
|
397
|
+
"""Render the zero-temperature mass spectrum as per-particle plots."""
|
|
398
|
+
groups = self.add_spec_info.get("groups", {})
|
|
399
|
+
mass_group = groups.get("mass_spectrum_T0", [])
|
|
400
|
+
if not mass_group:
|
|
401
|
+
return
|
|
402
|
+
|
|
403
|
+
latex_labels = self.add_spec_info["latex"]
|
|
404
|
+
text_labels = self.add_spec_info.get("text", {})
|
|
405
|
+
|
|
406
|
+
def _math_content(label: str) -> str:
|
|
407
|
+
if label.startswith("$") and label.endswith("$") and len(label) >= 2:
|
|
408
|
+
return label[1:-1]
|
|
409
|
+
return label
|
|
410
|
+
|
|
411
|
+
def _safe_component(component: str | None, fallback: str) -> str:
|
|
412
|
+
if not component:
|
|
413
|
+
return fallback
|
|
414
|
+
safe = re.sub(r"[^A-Za-z0-9_.-]+", "_", component.strip())
|
|
415
|
+
safe = safe.strip("._")
|
|
416
|
+
return safe or fallback
|
|
417
|
+
|
|
418
|
+
for member in mass_group:
|
|
419
|
+
pname = member.get("name")
|
|
420
|
+
index = member.get("index", 0)
|
|
421
|
+
kind = member.get("kind", "boson")
|
|
422
|
+
latex_label = latex_labels.get(pname, pname)
|
|
423
|
+
math_label = _math_content(latex_label)
|
|
424
|
+
text_label = text_labels.get(pname)
|
|
425
|
+
file_stub = _safe_component(text_label, f"{kind}_{index:02d}")
|
|
426
|
+
|
|
427
|
+
linear_title = rf"${math_label} / \text{{GeV}}$"
|
|
428
|
+
log_title = rf"$\log_{{10}} {math_label} / \text{{GeV}}$"
|
|
429
|
+
|
|
430
|
+
lin_values = self.add_spec_info["lin"].get(pname)
|
|
431
|
+
if lin_values is not None and not np.all(np.isnan(lin_values)):
|
|
432
|
+
fig, ax = plt.subplots()
|
|
433
|
+
_plot_scan_series(ax, self.x, lin_values, show_points=self.show_scan_points)
|
|
434
|
+
_finish_plot(
|
|
435
|
+
fig,
|
|
436
|
+
ax,
|
|
437
|
+
self.x_plot_name,
|
|
438
|
+
self.scale,
|
|
439
|
+
self.x,
|
|
440
|
+
linear_title,
|
|
441
|
+
self.foldername + f"Mass_spectrum_T0_lin_{file_stub}.pdf",
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
log_values = self.add_spec_info["log10"].get(pname)
|
|
445
|
+
if log_values is not None and not np.all(np.isnan(log_values)):
|
|
446
|
+
fig, ax = plt.subplots()
|
|
447
|
+
_plot_scan_series(ax, self.x, log_values, show_points=self.show_scan_points)
|
|
448
|
+
_finish_plot(
|
|
449
|
+
fig,
|
|
450
|
+
ax,
|
|
451
|
+
self.x_plot_name,
|
|
452
|
+
self.scale,
|
|
453
|
+
self.x,
|
|
454
|
+
log_title,
|
|
455
|
+
self.foldername + f"Mass_spectrum_T0_log10_{file_stub}.pdf",
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
def save_add_infos(self):
|
|
459
|
+
"""Save the additional observability information as plain-text tables."""
|
|
460
|
+
add_spec_info_lin = self.add_spec_info["lin"]
|
|
461
|
+
add_spec_info_log10 = self.add_spec_info["log10"]
|
|
462
|
+
if not add_spec_info_lin:
|
|
463
|
+
return
|
|
464
|
+
|
|
465
|
+
mass_group = {
|
|
466
|
+
member.get("name")
|
|
467
|
+
for member in self.add_spec_info.get("groups", {}).get("mass_spectrum_T0", [])
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
for pname, values in add_spec_info_lin.items():
|
|
471
|
+
if pname in mass_group:
|
|
472
|
+
continue
|
|
473
|
+
np.savetxt(self.foldername+"Add_info_lin_"+pname+".txt", values)
|
|
474
|
+
if pname in add_spec_info_log10:
|
|
475
|
+
np.savetxt(
|
|
476
|
+
self.foldername+"Add_info_log10_"+pname+".txt",
|
|
477
|
+
add_spec_info_log10[pname],
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
def plot_log_params(self):
|
|
481
|
+
"""Plot the logarithmic version of the derived parameters."""
|
|
482
|
+
# Log plots
|
|
483
|
+
for name, plotname in zip(self.derived_params_names, self.derived_params_plot_names):
|
|
484
|
+
# skip parameters whose name contains "WARNING"
|
|
485
|
+
if "WARNING" in name or "step" in name:
|
|
486
|
+
continue
|
|
487
|
+
fig, ax = plt.subplots()
|
|
488
|
+
_plot_scan_series(ax, self.x, self.log_params[name], show_points=self.show_scan_points)
|
|
489
|
+
_finish_plot(fig, ax, self.x_plot_name, self.scale, self.x,
|
|
490
|
+
R"$\mathrm{log}_{10}$ "+plotname, self.foldername+"log_plot_"+name+".pdf")
|
|
491
|
+
|
|
492
|
+
def plot_lin_params(self):
|
|
493
|
+
"""Plot the linear-scale version of the derived parameters."""
|
|
494
|
+
for name, plotname in zip(self.derived_params_names, self.derived_params_plot_names):
|
|
495
|
+
fig, ax = plt.subplots()
|
|
496
|
+
_plot_scan_series(ax, self.x, self.lin_params[name], show_points=self.show_scan_points)
|
|
497
|
+
if "WARNING" in name or "step" in name:
|
|
498
|
+
_finish_plot(fig, ax, self.x_plot_name, self.scale, self.x,
|
|
499
|
+
plotname, self.foldername+name+".pdf")
|
|
500
|
+
else:
|
|
501
|
+
_finish_plot(fig, ax, self.x_plot_name, self.scale, self.x,
|
|
502
|
+
plotname, self.foldername+"lin_plot_"+name+".pdf")
|
|
503
|
+
|
|
504
|
+
def plot_overview(self, overview_detector_name="LISA",
|
|
505
|
+
overview_param_names= ("alpha", "RH", "Treh_SM_GeV"),
|
|
506
|
+
overview_param_plot_names=(R"$\alpha_\mathrm{tot}$",
|
|
507
|
+
R"$RH_\mathrm{perc}$", R"$T^{\mathrm{reh}}_{\mathrm{SM}}$ / $\mathrm{GeV}$"),
|
|
508
|
+
figsize=(6.4, 4.2)):
|
|
509
|
+
"""Create a four-panel summary containing three observables and the SNR."""
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
# Define parameters to plot (3 GW params + 1 SNR)
|
|
513
|
+
param_names = overview_param_names
|
|
514
|
+
param_plot_names = list(overview_param_plot_names)
|
|
515
|
+
SNR_name = overview_detector_name
|
|
516
|
+
detector_label = str(overview_detector_name).replace("_", " ")
|
|
517
|
+
SNR_plot_name = f"SNR ({detector_label})"
|
|
518
|
+
|
|
519
|
+
# Prepare overview parameters: keep Treh linear, others in log10-space.
|
|
520
|
+
lin_overview_params = {"Treh_SM_GeV"}
|
|
521
|
+
overview_params = {}
|
|
522
|
+
for name in param_names:
|
|
523
|
+
if name in lin_overview_params:
|
|
524
|
+
overview_params[name] = self.lin_params[name]
|
|
525
|
+
else:
|
|
526
|
+
overview_params[name] = self.log_params[name]
|
|
527
|
+
|
|
528
|
+
# prepare SNR
|
|
529
|
+
detector_index = self.det_names.index(SNR_name)
|
|
530
|
+
SNR = self.SNR[:, detector_index]
|
|
531
|
+
logSNR = np.full_like(SNR, np.nan, dtype=float)
|
|
532
|
+
valid = np.isfinite(SNR) & (SNR > 0)
|
|
533
|
+
logSNR[valid] = np.log10(SNR[valid])
|
|
534
|
+
thr = self.det[SNR_name]["thr"]
|
|
535
|
+
log_thr = np.log10(thr) if np.isfinite(thr) and thr > 0 else np.nan
|
|
536
|
+
|
|
537
|
+
# Prepare axes
|
|
538
|
+
fig, axs = plt.subplots(2, 2, figsize=figsize)
|
|
539
|
+
ax1 = axs[0, 0]
|
|
540
|
+
ax2 = axs[0, 1]
|
|
541
|
+
ax3 = axs[1, 0]
|
|
542
|
+
ax4 = axs[1, 1]
|
|
543
|
+
|
|
544
|
+
# Plot 3 params
|
|
545
|
+
GW_axes = (ax1, ax2, ax3)
|
|
546
|
+
for i, ax in enumerate(GW_axes):
|
|
547
|
+
pname = param_names[i]
|
|
548
|
+
_plot_scan_series(GW_axes[i], self.x, overview_params[pname],
|
|
549
|
+
show_points=self.show_scan_points)
|
|
550
|
+
_prepare_axes(ax)
|
|
551
|
+
if self.scale == 'log':
|
|
552
|
+
ax.set_xscale('log')
|
|
553
|
+
ax.set_xlim(self.x[0], self.x[-1])
|
|
554
|
+
ax.set_ylabel(param_plot_names[i])
|
|
555
|
+
if pname in lin_overview_params:
|
|
556
|
+
pass
|
|
557
|
+
else:
|
|
558
|
+
_style_log10_y_axis(ax)
|
|
559
|
+
if str(pname) == "alpha":
|
|
560
|
+
_show_every_second_major_ylabel(ax)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
# Plot SNR
|
|
564
|
+
_plot_scan_series(ax4, self.x, logSNR, show_points=self.show_scan_points)
|
|
565
|
+
if np.isfinite(log_thr):
|
|
566
|
+
ax4.axhline(log_thr, color='red', linestyle='--', zorder=1)
|
|
567
|
+
_prepare_axes(ax4)
|
|
568
|
+
if self.scale == 'log':
|
|
569
|
+
ax4.set_xscale('log')
|
|
570
|
+
ax4.set_xlim(self.x[0], self.x[-1])
|
|
571
|
+
ax4.set_ylabel(SNR_plot_name)
|
|
572
|
+
_style_log10_y_axis(ax4)
|
|
573
|
+
|
|
574
|
+
# Keep x-labels only in the bottom row.
|
|
575
|
+
ax1.set_xlabel("")
|
|
576
|
+
ax2.set_xlabel("")
|
|
577
|
+
ax1.tick_params(axis="x", labelbottom=False)
|
|
578
|
+
ax2.tick_params(axis="x", labelbottom=False)
|
|
579
|
+
ax3.set_xlabel(self.x_plot_name)
|
|
580
|
+
ax4.set_xlabel(self.x_plot_name)
|
|
581
|
+
|
|
582
|
+
if str(self.overview_title).strip():
|
|
583
|
+
fig.suptitle(self.overview_title)
|
|
584
|
+
plt.tight_layout()
|
|
585
|
+
|
|
586
|
+
# Savefig
|
|
587
|
+
add_TL_logo(loc="upper right", ax=ax4)
|
|
588
|
+
plt.savefig(self.foldername+"overview_plot.pdf")
|
|
589
|
+
plt.close(fig)
|
|
590
|
+
|
|
591
|
+
def plot_errors(self):
|
|
592
|
+
"""Plot the error-code trace along the scan axis."""
|
|
593
|
+
errors = self.derived_params_array_dict["error"]
|
|
594
|
+
fig, ax = plt.subplots()
|
|
595
|
+
ax.plot(self.x, errors, "X")
|
|
596
|
+
_finish_plot(fig, ax, self.x_plot_name, self.scale, self.x,
|
|
597
|
+
R"Errorcodes", self.foldername+"Errors.pdf")
|
|
Binary file
|
|
Binary file
|