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,468 @@
1
+ """Scan routines for different sampling strategies.
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 multiprocessing
14
+ from typing import Iterable
15
+
16
+ import h5py
17
+ import numpy as np
18
+ from tqdm import tqdm
19
+
20
+ from transitionlistener.gridplots import plot2dData
21
+ from transitionlistener.lineplots import plot1dData
22
+ from transitionlistener.helper_functions import load_potential
23
+ from transitionlistener import console
24
+ from rich.panel import Panel
25
+
26
+ from .logging_utils import Logger
27
+ from .pipeline import run_TL
28
+ from .samplers import scanfunc, sample_and_export_to_csv, rand_worker, rand_listener
29
+
30
+
31
+ def _to_scalar_or_nan(value) -> float:
32
+ """Convert value to a scalar float when possible, otherwise NaN."""
33
+ try:
34
+ arr = np.asarray(value)
35
+ except Exception:
36
+ return float("nan")
37
+ if arr.size == 0:
38
+ return float("nan")
39
+ if arr.size == 1:
40
+ try:
41
+ return float(np.squeeze(arr))
42
+ except Exception:
43
+ return float("nan")
44
+ return float("nan")
45
+
46
+
47
+ def create_grid(xminmax: tuple, yminmax: tuple, N: int,
48
+ xscale: str, yscale: str) -> tuple[np.ndarray]:
49
+ """Create a regular grid of x/y coordinates."""
50
+ if xscale == "lin":
51
+ xrange = np.linspace(*xminmax, num=N)
52
+ elif xscale == "log":
53
+ xrange = np.logspace(*xminmax, num=N)
54
+ else:
55
+ raise ValueError("xscale must be 'lin' or 'log'")
56
+
57
+ if yscale == "lin":
58
+ yrange = np.linspace(*yminmax, num=N)
59
+ elif yscale == "log":
60
+ yrange = np.logspace(*yminmax, num=N)
61
+ else:
62
+ raise ValueError("yscale must be 'lin' or 'log'")
63
+
64
+ return xrange, yrange
65
+
66
+
67
+ def line_scan(conf, n_jobs: int):
68
+ """1D scan over only one parameter."""
69
+ outpath = conf.output_path
70
+
71
+ if conf.scale == "lin":
72
+ xrange = np.linspace(*conf.minmax, num=conf.N)
73
+ elif conf.scale == "log":
74
+ xrange = np.logspace(*conf.minmax, num=conf.N)
75
+ else:
76
+ raise ValueError("xscale must be 'lin' or 'log'")
77
+
78
+ input_params_list = []
79
+ for _, x in enumerate(xrange):
80
+ inputparam_dict = {conf.line_param: x}
81
+ for key, val in conf.other_params.items():
82
+ inputparam_dict[key] = val
83
+ input_params_list.append((inputparam_dict, conf.modelfile,
84
+ conf.potential_name, outpath, conf.timeout))
85
+
86
+ if n_jobs != 1:
87
+ n_jobs = n_jobs - 1
88
+ pool = multiprocessing.Pool(processes=n_jobs)
89
+ results = []
90
+
91
+ for result in tqdm(pool.imap(func=scanfunc,
92
+ iterable=input_params_list), total=conf.N):
93
+ results.append(result)
94
+
95
+ save_results_1d(
96
+ results,
97
+ conf.N,
98
+ xrange,
99
+ outpath,
100
+ conf.derived_params,
101
+ conf.format,
102
+ conf.line_param,
103
+ conf.other_params,
104
+ )
105
+
106
+ if conf.plots:
107
+ plot_results_1d(results, conf, xrange, outpath)
108
+
109
+
110
+ def grid_scan(conf, n_jobs: int):
111
+ """2D grid scan."""
112
+ outpath = conf.output_path
113
+ xrange, yrange = create_grid(conf.minmax["x"], conf.minmax["y"],
114
+ conf.N, conf.scales["x"], conf.scales["y"])
115
+
116
+ input_params_list = []
117
+ for _, x in enumerate(xrange):
118
+ for _, y in enumerate(yrange):
119
+ inputparam_dict = {}
120
+ for key, val in conf.grid_names.items():
121
+ if key == "x":
122
+ inputparam_dict[val] = x
123
+ elif key == "y":
124
+ inputparam_dict[val] = y
125
+ for key, val in conf.other_params.items():
126
+ inputparam_dict[key] = val
127
+ input_params_list.append((inputparam_dict, conf.modelfile,
128
+ conf.potential_name, outpath, conf.timeout))
129
+
130
+ if n_jobs != 1:
131
+ n_jobs = n_jobs - 1
132
+ pool = multiprocessing.Pool(processes=n_jobs)
133
+ results = []
134
+
135
+ for result in tqdm(pool.imap(func=scanfunc,
136
+ iterable=input_params_list), total=conf.N**2):
137
+ results.append(result)
138
+ msg = ("Phase transition analysis done. Now, save the "
139
+ "results and check if signal is observable.")
140
+ console.print(Panel.fit(f"[bold green]{msg}[/bold green]", border_style="green"))
141
+
142
+ save_results_2d(
143
+ results,
144
+ conf.N,
145
+ xrange,
146
+ yrange,
147
+ outpath,
148
+ conf.derived_params.copy(),
149
+ conf.format,
150
+ conf.grid_names,
151
+ conf.other_params,
152
+ )
153
+
154
+ if conf.plots:
155
+ plot_results_2d(results, conf, xrange, yrange, outpath)
156
+
157
+
158
+ def table(conf, n_jobs: int):
159
+ """Evaluate points specified in a CSV table."""
160
+ try:
161
+ file = np.genfromtxt(conf.table_file, delimiter=',')
162
+ file_transpose = np.transpose(file)
163
+ except Exception as exc:
164
+ raise Exception(f"Error reading table file {conf.table_file}: {exc}")
165
+
166
+ offset = 1 if np.isnan(file_transpose[0][0]) else 0
167
+
168
+ input_params_list = []
169
+ for i in range(conf.N):
170
+ inputparam_dict = {}
171
+ for name in conf.scan_params.keys():
172
+ inputparam_dict[name] = float(file_transpose[conf.scan_params[name]["index"]][i + offset])
173
+ for key, val in conf.other_params.items():
174
+ inputparam_dict[key] = val
175
+ input_params_list.append(
176
+ (
177
+ inputparam_dict,
178
+ conf.modelfile,
179
+ conf.potential_name,
180
+ conf.output_path,
181
+ conf.derived_params,
182
+ conf.timeout,
183
+ )
184
+ )
185
+
186
+ if n_jobs != 1:
187
+ n_jobs = n_jobs - 1
188
+ pool = multiprocessing.Pool(processes=n_jobs)
189
+ results = []
190
+
191
+ for result in tqdm(pool.imap(func=sample_and_export_to_csv,
192
+ iterable=input_params_list), total=conf.N):
193
+ results.append(result)
194
+
195
+
196
+ def random_scan(conf, n_jobs: int):
197
+ """Random parameter scan."""
198
+ N = conf.N
199
+
200
+ if n_jobs != 1:
201
+ n_jobs = n_jobs - 1
202
+
203
+ manager = multiprocessing.Manager()
204
+ q = manager.Queue()
205
+ pool = multiprocessing.Pool(processes=n_jobs)
206
+ watcher = multiprocessing.Process(target=rand_listener,
207
+ args=(q, conf.output_path,
208
+ conf.derived_params,))
209
+ # Make sure the I/O worker is running
210
+ watcher.start()
211
+
212
+ input_params_list = []
213
+ for _ in range(N):
214
+ inputparam_dict = {}
215
+ cube = np.random.rand(len(conf.scan_params))
216
+ input_params_untransformed = _random_prior_transform(cube, conf)
217
+ for i, name in enumerate(conf.scan_params.keys()):
218
+ inputparam_dict[name] = input_params_untransformed[i]
219
+ for key, val in conf.other_params.items():
220
+ inputparam_dict[key] = val
221
+ input_params_list.append(
222
+ (
223
+ inputparam_dict,
224
+ conf.modelfile,
225
+ conf.potential_name,
226
+ conf.output_path,
227
+ conf.derived_params,
228
+ conf.timeout,
229
+ )
230
+ )
231
+
232
+ jobs = []
233
+ for i in range(N):
234
+ job = pool.apply_async(rand_worker, (input_params_list[i], q))
235
+ jobs.append(job)
236
+
237
+ for job in jobs:
238
+ job.get()
239
+
240
+ q.put('kill')
241
+ pool.close()
242
+ pool.join()
243
+
244
+
245
+ def _random_prior_transform(cube: np.ndarray, conf) -> np.ndarray:
246
+ """Transforms the unit cube to the prior space defined in the configuration."""
247
+ params = cube.copy()
248
+ for i, name in enumerate(conf.scan_params.keys()):
249
+ prior = conf.scan_params[name]["range"]
250
+ try:
251
+ low = float(prior[0])
252
+ except ValueError:
253
+ low = eval(prior[0])
254
+ try:
255
+ high = float(prior[1])
256
+ except ValueError:
257
+ high = eval(prior[1])
258
+
259
+ if conf.scan_params[name]["scale"] == "log":
260
+ params[i] = 10 ** (np.log10(low) + cube[i] * (np.log10(high) - np.log10(low)))
261
+ else:
262
+ params[i] = low + cube[i] * (high - low)
263
+ return params
264
+
265
+
266
+ def save_results_2d(res_dict: Iterable[dict], N: int, xrange: np.ndarray,
267
+ yrange: np.ndarray, outpath: str, outnames: dict,
268
+ format: str, grid_names: dict, other_params: dict):
269
+ """Save the result list to a hdf5 or txt file."""
270
+ outnames.update({"error": "error"})
271
+ h = np.array(res_dict).reshape(N, N)
272
+ res = np.full((N, N, len(outnames)), np.nan)
273
+
274
+ for i in range(N):
275
+ for j in range(N):
276
+ for k, name in enumerate(outnames):
277
+ if "strongestTransitionObservables" in h[i, j]:
278
+ if name in h[i, j]["strongestTransitionObservables"]:
279
+ val = h[i, j]["strongestTransitionObservables"][name]
280
+ res[i, j, k] = _to_scalar_or_nan(val)
281
+ if "error" in h[i, j]:
282
+ res[i, j, k] = _to_scalar_or_nan(h[i, j]["error"])
283
+
284
+ if format == "hdf5":
285
+ with h5py.File(outpath + "data.hdf5", "w") as f:
286
+ f.attrs["xname"] = grid_names["x"]
287
+ f.attrs["yname"] = grid_names["y"]
288
+ f.attrs["xrange"] = xrange
289
+ f.attrs["yrange"] = yrange
290
+ for name, par in zip(other_params.keys(), other_params.values()):
291
+ f.attrs[name] = par
292
+
293
+ d = f.create_group('data')
294
+ for on in outnames:
295
+ d.create_dataset(on, shape=(N, N))
296
+
297
+ for i, x in enumerate(xrange):
298
+ for j, y in enumerate(yrange):
299
+ for k, on in enumerate(outnames):
300
+ d[on][i, j] = res[i, j, k]
301
+ elif format == "txt":
302
+ for k, on in enumerate(outnames):
303
+ data = np.zeros((N, N))
304
+ for i, x in enumerate(xrange):
305
+ for j, y in enumerate(yrange):
306
+ data[i, j] = res[i, j, k]
307
+ np.savetxt(outpath + on + ".txt", data)
308
+
309
+ xname = grid_names["x"]
310
+ yname = grid_names["y"]
311
+ np.savetxt(outpath + "xrange.txt", xrange,
312
+ header=f"X input range for parameter: {xname:}")
313
+ np.savetxt(outpath + "yrange.txt", yrange,
314
+ header=f"Y input range for parameter: {yname:}")
315
+
316
+ with open(outpath + "other_params.txt", "w") as fh:
317
+ fh.write("# " + "\t".join(other_params.keys()) + "\n")
318
+ fh.write("\t".join(str(v) for v in other_params.values()) + "\n")
319
+
320
+
321
+ def plot_results_2d(results_dict: Iterable[dict], conf, xrange: np.ndarray, yrange: np.ndarray, outpath: str):
322
+ """Make the grid plots."""
323
+ derived_param_names = [name for name in conf.derived_params.keys()]
324
+ derived_param_plot_names = [name for name in conf.derived_params.values()]
325
+ grid_params_names = [conf.grid_names["x"], conf.grid_names["y"]]
326
+ grid_params_plot_names = [conf.model_params[name]["plotname"] for name in grid_params_names]
327
+ overview_param_plot_names = [
328
+ plot_name for name, plot_name in zip(derived_param_names, derived_param_plot_names)
329
+ if name in conf.overview_param_names
330
+ ]
331
+
332
+ scale = conf.scales["x"] + conf.scales["y"]
333
+ all_params = np.array(results_dict).reshape(conf.N, conf.N)
334
+
335
+ grid = plot2dData(
336
+ all_params,
337
+ x=xrange,
338
+ y=yrange,
339
+ scale=scale,
340
+ xy_plot_names=grid_params_plot_names,
341
+ foldername=outpath,
342
+ derived_params_names=derived_param_names,
343
+ derived_params_plot_names=derived_param_plot_names,
344
+ overview_title=conf.plot_description,
345
+ show_scan_points=conf.show_scan_points,
346
+ )
347
+ def _run_plot_step(step_name: str, fn):
348
+ try:
349
+ fn()
350
+ except Exception as exc:
351
+ msg = f"[yellow]Skipping plot step '{step_name}': {exc}[/yellow]"
352
+ console.print(Panel.fit(msg, border_style="yellow"))
353
+
354
+ # Produce the paper-critical four-panel overview first.
355
+ _run_plot_step(
356
+ "overview_plot",
357
+ lambda: grid.plot_overview(
358
+ overview_detector_name=conf.overview_detector_name,
359
+ overview_param_names=conf.overview_param_names,
360
+ overview_param_plot_names=overview_param_plot_names,
361
+ ),
362
+ )
363
+ _run_plot_step("log_params", grid.plot_log_params)
364
+ _run_plot_step("lin_params", grid.plot_lin_params)
365
+ _run_plot_step("save_SNRs", grid.save_SNRs)
366
+ _run_plot_step("plot_SNRs", grid.plot_SNRs)
367
+ _run_plot_step("save_logLs", grid.save_logLs)
368
+ _run_plot_step("plot_logLs", grid.plot_logLs)
369
+ _run_plot_step("save_add_infos", grid.save_add_infos)
370
+ _run_plot_step("plot_add_infos", grid.plot_add_infos)
371
+ _run_plot_step("plot_mass_spectrum", grid.plot_mass_spectrum)
372
+ _run_plot_step("plot_errors", grid.plot_errors)
373
+
374
+
375
+ def save_results_1d(res_dict: Iterable[dict], N: int, xrange: np.ndarray, outpath: str,
376
+ outnames: dict, format: str, line_param: str, other_params: dict):
377
+ """Save the result list to a hdf5 or txt file."""
378
+ outnames.update({"error": "error"})
379
+ h = np.array(res_dict).reshape(N)
380
+ res = np.full((N, len(outnames)), np.nan)
381
+
382
+ for i in range(N):
383
+ for k, name in enumerate(outnames):
384
+ if "strongestTransitionObservables" in h[i]:
385
+ if name in h[i]["strongestTransitionObservables"]:
386
+ val = h[i]["strongestTransitionObservables"][name]
387
+ res[i, k] = _to_scalar_or_nan(val)
388
+ if "error" in h[i]:
389
+ res[i, k] = _to_scalar_or_nan(h[i]["error"])
390
+
391
+ if format == "hdf5":
392
+ with h5py.File(outpath + "data.hdf5", "w") as f:
393
+ f.attrs["xname"] = line_param
394
+ f.attrs["xrange"] = xrange
395
+ for name, par in zip(other_params.keys(), other_params.values()):
396
+ f.attrs[name] = par
397
+
398
+ d = f.create_group('data')
399
+ for on in outnames:
400
+ d.create_dataset(on, shape=(N,))
401
+
402
+ for i, x in enumerate(xrange):
403
+ for k, on in enumerate(outnames):
404
+ d[on][i] = res[i, k]
405
+ elif format == "txt":
406
+ for k, on in enumerate(outnames):
407
+ data = np.zeros((N,))
408
+ for i, x in enumerate(xrange):
409
+ data[i] = res[i, k]
410
+ np.savetxt(outpath + on + ".txt", data)
411
+
412
+ np.savetxt(outpath + "xrange.txt", xrange,
413
+ header=f"X input range for parameter: {line_param:}")
414
+ with open(outpath + "other_params.txt", "w") as fh:
415
+ fh.write("# " + "\t".join(other_params.keys()) + "\n")
416
+ fh.write("\t".join(str(v) for v in other_params.values()) + "\n")
417
+
418
+
419
+ def plot_results_1d(results_dict: Iterable[dict], conf, xrange: np.ndarray, outpath: str):
420
+ """Generate line scan plots."""
421
+ derived_param_names = [name for name in conf.derived_params.keys()]
422
+ derived_param_plot_names = [name for name in conf.derived_params.values()]
423
+ x_plot_name = conf.model_params[conf.line_param]["plotname"]
424
+ overview_param_plot_names = [
425
+ plot_name for name, plot_name in zip(derived_param_names, derived_param_plot_names)
426
+ if name in conf.overview_param_names
427
+ ]
428
+
429
+ scale = conf.scale
430
+ all_params = np.array(results_dict).reshape(conf.N)
431
+
432
+ grid = plot1dData(
433
+ all_params,
434
+ x=xrange,
435
+ scale=scale,
436
+ x_plot_name=x_plot_name,
437
+ foldername=outpath,
438
+ derived_params_names=derived_param_names,
439
+ derived_params_plot_names=derived_param_plot_names,
440
+ overview_title=conf.plot_description,
441
+ show_scan_points=conf.show_scan_points,
442
+ )
443
+ def _run_plot_step(step_name: str, fn):
444
+ try:
445
+ fn()
446
+ except Exception as exc:
447
+ msg = f"[yellow]Skipping plot step '{step_name}': {exc}[/yellow]"
448
+ console.print(Panel.fit(msg, border_style="yellow"))
449
+
450
+ # Produce the paper-critical four-panel overview first.
451
+ _run_plot_step(
452
+ "overview_plot",
453
+ lambda: grid.plot_overview(
454
+ overview_detector_name=conf.overview_detector_name,
455
+ overview_param_names=conf.overview_param_names,
456
+ overview_param_plot_names=overview_param_plot_names,
457
+ ),
458
+ )
459
+ _run_plot_step("plot_log_params", grid.plot_log_params)
460
+ _run_plot_step("plot_lin_params", grid.plot_lin_params)
461
+ _run_plot_step("save_SNRs", grid.save_SNRs)
462
+ _run_plot_step("plot_SNRs", grid.plot_SNRs)
463
+ _run_plot_step("save_logLs", grid.save_logLs)
464
+ _run_plot_step("plot_logLs", grid.plot_logLs)
465
+ _run_plot_step("save_add_infos", grid.save_add_infos)
466
+ _run_plot_step("plot_add_infos", grid.plot_add_infos)
467
+ _run_plot_step("plot_mass_spectrum", grid.plot_mass_spectrum)
468
+ _run_plot_step("plot_errors", grid.plot_errors)