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