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,95 @@
1
+ """Interface entry points for TransitionListener scans.
2
+
3
+ This module exposes the main functions and classes for performing
4
+ scans and single-point evaluations using TransitionListener. It also
5
+ manages global state variables such as debug mode, timeout settings,
6
+ and logging configurations.
7
+
8
+ Part of TransitionListener v2.0
9
+ Documentation: https://tasillo.de/TransitionListener/
10
+
11
+ Authors:
12
+ Jonas Matuszak <jonas.matuszak@kit.edu>
13
+ Carlo Tasillo <carlo.tasillo@ific.uv.es>
14
+ """
15
+
16
+ import logging
17
+ logging.getLogger("numexpr").setLevel(logging.WARNING)
18
+
19
+ from . import state as _state
20
+ from .logging_utils import Logger
21
+ from .config_loader import ScanConfig
22
+ from .pipeline import (
23
+ run_TL, _compute_single_point,
24
+ _build_result_from_context,
25
+ _handle_single_point_error)
26
+ from .samplers import (
27
+ call_by_sampler,
28
+ sample_and_export_to_csv,
29
+ scanfunc,
30
+ extract_parameters,
31
+ log_likelihood,
32
+ prior_transform,
33
+ ultranest,
34
+ )
35
+ from .scans import (
36
+ create_grid,
37
+ line_scan,
38
+ grid_scan,
39
+ table,
40
+ random_scan,
41
+ save_results_1d,
42
+ save_results_2d,
43
+ plot_results_1d,
44
+ plot_results_2d,
45
+ )
46
+ from .single_point import single
47
+ from .cli import main
48
+
49
+ __all__ = [
50
+ "Logger",
51
+ "ScanConfig",
52
+ "run_TL",
53
+ "_compute_single_point",
54
+ "_build_result_from_context",
55
+ "_handle_single_point_error",
56
+ "call_by_sampler",
57
+ "sample_and_export_to_csv",
58
+ "scanfunc",
59
+ "extract_parameters",
60
+ "log_likelihood",
61
+ "prior_transform",
62
+ "ultranest",
63
+ "create_grid",
64
+ "line_scan",
65
+ "grid_scan",
66
+ "table",
67
+ "random_scan",
68
+ "save_results_1d",
69
+ "save_results_2d",
70
+ "plot_results_1d",
71
+ "plot_results_2d",
72
+ "single",
73
+ "main",
74
+ "DEBUGMODE",
75
+ "TIMEOUT",
76
+ "model",
77
+ "errorlogger",
78
+ "resultlogger",
79
+ "config",
80
+ ]
81
+
82
+
83
+ def __getattr__(name):
84
+ """Proxy selected legacy module-level state to :mod:`transitionlistener.interface.state`."""
85
+ if name in {"DEBUGMODE", "TIMEOUT", "model", "errorlogger", "resultlogger", "config"}:
86
+ return getattr(_state, name)
87
+ raise AttributeError(f"module 'transitionlistener.interface' has no attribute '{name}'")
88
+
89
+
90
+ def __setattr__(name, value):
91
+ """Mirror assignments to the shared interface state when applicable."""
92
+ if name in {"DEBUGMODE", "TIMEOUT", "model", "errorlogger", "resultlogger", "config"}:
93
+ setattr(_state, name, value)
94
+ else:
95
+ globals()[name] = value
@@ -0,0 +1,51 @@
1
+ """Entry point for ``tl --check``.
2
+
3
+ All check logic lives in ``tests/check_installation.py`` in the repository.
4
+ For pip installs, hatchling bundles that file as
5
+ ``transitionlistener/check_installation.py`` so it is always reachable.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib.util
11
+ from pathlib import Path
12
+
13
+
14
+ def _load_and_run(path: Path) -> int:
15
+ """Import the installation-check script at ``path`` and execute it."""
16
+ spec = importlib.util.spec_from_file_location("_tl_check_installation", path)
17
+ module = importlib.util.module_from_spec(spec)
18
+ spec.loader.exec_module(module)
19
+ return module.run_checks()
20
+
21
+
22
+ def run_checks() -> int:
23
+ """Locate and run the packaged installation checks.
24
+
25
+ The helper first prefers the repository-local ``tests/check_installation.py``
26
+ file and falls back to the copy bundled into wheel installs.
27
+ """
28
+ # Option 1: repo clone — tests/check_installation.py is at parents[3]/tests/
29
+ # (this file: src/transitionlistener/interface/check.py → parents[3] = repo root)
30
+ repo_path = Path(__file__).resolve().parents[3] / "tests" / "check_installation.py"
31
+ if repo_path.exists():
32
+ return _load_and_run(repo_path)
33
+
34
+ # Option 2: pip install — hatchling bundled it as transitionlistener/check_installation.py
35
+ try:
36
+ import importlib.resources as ir
37
+ import contextlib
38
+ ref = ir.files("transitionlistener") / "check_installation.py"
39
+ with contextlib.ExitStack() as stack:
40
+ bundled = stack.enter_context(ir.as_file(ref))
41
+ return _load_and_run(bundled)
42
+ except Exception:
43
+ pass
44
+
45
+ from transitionlistener import console
46
+ console.print(
47
+ "[bold red]ERROR[/bold red] check_installation.py not found.\n"
48
+ " Repo clone: expected at tests/check_installation.py\n"
49
+ " pip install: rebuild the wheel with hatchling."
50
+ )
51
+ return 1
@@ -0,0 +1,211 @@
1
+ """Command-line interface for TransitionListener.
2
+
3
+ This module exposes the ``tl`` entry point which is used to launch scans and
4
+ other analyses from the terminal. The command line interface is intentionally
5
+ minimal – most of the configuration is supplied through YAML files – but the
6
+ help text should nonetheless offer a concise overview of what the tool does and
7
+ how it can be used. The updates in this module focus on surfacing that
8
+ information so that ``tl -h`` is informative for new users.
9
+
10
+ Part of TransitionListener v2.0
11
+ Documentation: https://tasillo.de/TransitionListener/
12
+
13
+ Authors:
14
+ Jonas Matuszak <jonas.matuszak@kit.edu>
15
+ Carlo Tasillo <carlo.tasillo@ific.uv.es>
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import argparse
21
+ import os
22
+ import textwrap
23
+ import time
24
+
25
+ from transitionlistener import console
26
+ from rich.panel import Panel
27
+
28
+ from . import state
29
+ from .config_loader import ScanConfig
30
+ from .scans import grid_scan, line_scan, random_scan, table
31
+ from .single_point import single
32
+ from .samplers import ultranest
33
+
34
+
35
+ def main():
36
+ """Console entry point."""
37
+ t0 = time.time()
38
+
39
+ parser = argparse.ArgumentParser(
40
+ prog="tl",
41
+ description=textwrap.dedent(
42
+ """
43
+ TransitionListener performs parameter scans and single point
44
+ analyses for cosmological phase transition and gravitational
45
+ wave calculations. Provide a YAML configuration file describing
46
+ the scan type, model inputs, and output location to launch a run.
47
+ """
48
+ ).strip(),
49
+ formatter_class=argparse.RawDescriptionHelpFormatter,
50
+ epilog=textwrap.dedent(
51
+ """
52
+ Common scan types include GridScan, LineScan, RandomScan, SinglePoint,
53
+ UltranestScan, and TableScan. The scan type is selected in the YAML
54
+ configuration file and determines which numerical routine is
55
+ executed.
56
+
57
+ Examples:
58
+ tl -c examples/example_point.yaml -v True
59
+ tl -c examples/example_grid.yaml -j 10
60
+ mpiexec -n 10 tl -c examples/example_ultranest.yaml
61
+
62
+ Documentation: https://www.tasillo.de/transitionlistener_development/
63
+ """
64
+ ).strip(),
65
+ )
66
+ parser.add_argument(
67
+ "-j",
68
+ "--jobs",
69
+ type=int,
70
+ default=1,
71
+ metavar="N",
72
+ help=(
73
+ "Number of parallel worker processes to use for supported scan "
74
+ "types (defaults to 1)."
75
+ ),
76
+ )
77
+ parser.add_argument(
78
+ "-c",
79
+ "--config",
80
+ type=str,
81
+ metavar="FILE.yaml",
82
+ help=(
83
+ "Path to the YAML configuration file that defines the scan. The "
84
+ "file specifies the scan type, physics inputs, and output path."
85
+ ),
86
+ )
87
+ parser.add_argument(
88
+ "-v",
89
+ "--verbose",
90
+ action="store_true",
91
+ help="Enable detailed logging to the console.",
92
+ )
93
+ parser.add_argument(
94
+ "-d",
95
+ "--debug",
96
+ action="store_true",
97
+ help="Enable debug mode (do not suppress runtime warnings).",
98
+ )
99
+ parser.add_argument(
100
+ "-V",
101
+ "--version",
102
+ action="store_true",
103
+ help="Show the TransitionListener version and exit.",
104
+ )
105
+ parser.add_argument(
106
+ "--check",
107
+ action="store_true",
108
+ help="Run a quick installation self-test and exit.",
109
+ )
110
+
111
+ args = parser.parse_args()
112
+
113
+ if args.check:
114
+ from .check import run_checks
115
+ raise SystemExit(run_checks())
116
+
117
+ if args.version:
118
+ from transitionlistener import __version__
119
+ console.print(Panel.fit(f"[bold green]TransitionListener version {__version__}[/bold green]", border_style="green"))
120
+ raise SystemExit(0)
121
+
122
+ if not args.config:
123
+ parser.error("a configuration file must be provided with -c/--config")
124
+
125
+ state.set_debugmode(args.debug)
126
+
127
+ config_path = os.path.expanduser(args.config)
128
+ if not os.path.exists(config_path):
129
+ parser.error(f"configuration file not found: {config_path}")
130
+
131
+ state.config = ScanConfig(config_path)
132
+ os.makedirs(state.config.output_path, exist_ok=True)
133
+
134
+ mpi_rank_env_vars = [
135
+ "OMPI_COMM_WORLD_RANK",
136
+ "PMI_RANK",
137
+ "PMIX_RANK",
138
+ "SLURM_PROCID",
139
+ "RANK",
140
+ "I_MPI_RANK",
141
+ "MPI_LOCALRANKID",
142
+ ]
143
+ mpi_size_env_vars = [
144
+ "OMPI_COMM_WORLD_SIZE",
145
+ "PMI_SIZE",
146
+ "PMIX_SIZE",
147
+ "SLURM_NPROCS",
148
+ "SLURM_NTASKS",
149
+ "WORLD_SIZE",
150
+ "MPIEXEC_TIMEOUT",
151
+ ]
152
+
153
+ rank_value = None
154
+ for env_var in mpi_rank_env_vars:
155
+ value = os.environ.get(env_var)
156
+ if value is not None:
157
+ rank_value = value
158
+ break
159
+
160
+ rank_from_env = rank_value is not None
161
+ if rank_value is None:
162
+ rank_value = "0"
163
+
164
+ try:
165
+ rank = int(rank_value)
166
+ except Exception:
167
+ rank = 0
168
+
169
+ is_mpi = rank_from_env or any(os.environ.get(env_var) is not None for env_var in mpi_size_env_vars)
170
+ if (not is_mpi) or (rank == 0):
171
+ message = "[green bold]Welcome to TransitionListener v2.0![/]"
172
+ console.print(Panel.fit(message, border_style="green"))
173
+ console.print()
174
+ state.config.display()
175
+ message = f"[green]Starting analysis of {state.config.description}[/]"
176
+ console.print(Panel.fit(message, border_style="green"))
177
+ console.print()
178
+
179
+ try:
180
+ if state.config.type == "GridScan":
181
+ grid_scan(state.config, n_jobs=args.jobs)
182
+ elif state.config.type == "LineScan":
183
+ line_scan(state.config, n_jobs=args.jobs)
184
+ elif state.config.type == "RandomScan":
185
+ random_scan(state.config, n_jobs=args.jobs)
186
+ elif state.config.type == "SinglePoint":
187
+ single(state.config, verbose=args.verbose)
188
+ elif state.config.type == "UltranestScan":
189
+ if args.jobs != 1:
190
+ msg = ("Error: Ultranest does not support parallelization through "
191
+ "the -j option. We recommend parallelizing it using MPI. "
192
+ "For this you need to execute: \n\n"
193
+ "mpiexec -n <num_procs> tl -c <config.yaml>\n"
194
+ "or, if `tl` is not installed,\n"
195
+ "mpiexec -n <num_procs> env PYTHONPATH=src "
196
+ "python -m transitionlistener.interface.cli -c <config.yaml>")
197
+ console.print(Panel.fit(f"[bold red]{msg}[/bold red]", border_style="red"))
198
+ raise SystemExit(1)
199
+ ultranest(state.config)
200
+ elif state.config.type == "TableScan":
201
+ table(state.config, n_jobs=args.jobs)
202
+ else:
203
+ raise ValueError("Unknown scan type.")
204
+ except ValueError as err:
205
+ console.print(Panel.fit(str(err), border_style="red"))
206
+ raise SystemExit(1) from err
207
+
208
+ tend = time.time()
209
+ if (not is_mpi) or (rank == 0):
210
+ msg = (f"Finished in {time.strftime('%H:%M:%S', time.gmtime(tend-t0))}")
211
+ console.print(Panel.fit(f"[bold green]{msg}[/bold green]", border_style="green"))
@@ -0,0 +1,264 @@
1
+ """Configuration loader for TransitionListener scan YAML files.
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
+ import yaml
15
+
16
+ from rich.panel import Panel
17
+ from rich.table import Table
18
+
19
+ from transitionlistener.config import all_observables
20
+ from transitionlistener.helper_functions import import_file, load_potential
21
+ from transitionlistener import console
22
+
23
+ from . import state
24
+ from ..observability import GW_Sensitivity_Data
25
+
26
+
27
+ class ScanConfig:
28
+ """Read and store all settings required for a TransitionListener scan."""
29
+
30
+ def __init__(self, file: str):
31
+ """Parse the YAML configuration and expose the fields as attributes."""
32
+ with open(file, "r", encoding="utf-8") as cfile:
33
+ config = yaml.safe_load(cfile)
34
+
35
+ self.type = config["Scan"]
36
+
37
+ state.TIMEOUT = config.get("timeout", 300)
38
+ self.timeout = state.TIMEOUT
39
+
40
+ self.modelfile = config["Modelfile"]
41
+ self.potential_name = config["Potential"]
42
+ state.model = import_file(self.modelfile)
43
+ state.potential = load_potential(self.modelfile, self.potential_name)
44
+ self.model_params = self._resolve_model_parameters(state.potential)
45
+
46
+ self.derived_params = all_observables.copy()
47
+
48
+ self.output_path = config["output_path"]
49
+ if self.output_path[-1] != "/":
50
+ self.output_path += "/"
51
+
52
+ self.format = config["format"]
53
+ if self.format not in ["hdf5", "txt"]:
54
+ raise Exception("Output format must be either 'hdf5' or 'txt'")
55
+
56
+ self.description = config["description"]
57
+ self.plot_description = config["plot_description"]
58
+
59
+ if self.type == "SinglePoint":
60
+ self.params = config["Parameters"]
61
+ self.additional_plots = config.get("additional_plots", {})
62
+ self.plots = None
63
+
64
+ elif self.type == "TableScan":
65
+ self.params = config["Parameters"]
66
+ self.scan_params = config["Parameters"]["scan_params"]
67
+ self.other_params = config["Parameters"]["other_params"] or {}
68
+ try:
69
+ self.N = config["Parameters"]["N"]
70
+ except KeyError:
71
+ raise Exception("Number of random points N must be specified.")
72
+ if self.N < 1 or int(self.N) != self.N:
73
+ raise Exception("Number of random points N must be a positive integer.")
74
+ self.table_file = config["Tablefile"]
75
+ if not os.path.isfile(self.table_file):
76
+ raise Exception(f"Table file {self.table_file} does not exist.")
77
+
78
+ elif self.type == "RandomScan":
79
+ self.params = config["Parameters"]
80
+ self.scan_params = config["Parameters"]["scan_params"]
81
+ self.other_params = config["Parameters"]["other_params"] or {}
82
+ try:
83
+ self.N = config["Parameters"]["N"]
84
+ except KeyError:
85
+ raise Exception("Number of random points N must be specified.")
86
+ if self.N < 1 or int(self.N) != self.N:
87
+ raise Exception("Number of random points N must be a positive integer.")
88
+
89
+ elif self.type == "UltranestScan":
90
+ self.params = config["Parameters"]
91
+ self.scan_params = config["Parameters"]["scan_params"]
92
+ self.other_params = config["Parameters"]["other_params"]
93
+ self.transformation_file = config["Parameters"].get("transformation_file", None)
94
+ self.resume_mode = config.get("resume_mode", "resume")
95
+ valid_resume_modes = {True, "overwrite", "subfolder", "resume", "resume-similar"}
96
+ if self.resume_mode not in valid_resume_modes:
97
+ raise ValueError(
98
+ "For UltranestScan, resume_mode must be one of "
99
+ f"{sorted(repr(mode) for mode in valid_resume_modes)}; "
100
+ f"got {self.resume_mode!r}."
101
+ )
102
+
103
+ elif self.type == "LineScan":
104
+ self.plots = config["plots"]
105
+ if len(list(config["Parameters"]["line_param"].keys())) != 1:
106
+ raise Exception("Line scan requires exactly one line parameter.")
107
+
108
+ self.line_param = list(config["Parameters"]["line_param"].keys())[0]
109
+ if self.line_param not in self.model_params.keys():
110
+ raise Exception(f"Line scan parameter {self.line_param} not found in model parameters.")
111
+
112
+ try:
113
+ self.scale = config["Parameters"]["line_param"][self.line_param]["scale"]
114
+ self.minmax = config["Parameters"]["line_param"][self.line_param]["range"]
115
+ except KeyError:
116
+ raise Exception("Line scan parameter must have 'range' and 'scale' specified.")
117
+
118
+ self.other_params = config["Parameters"]["other_params"] or {}
119
+ try:
120
+ self.N = config["Parameters"]["N"]
121
+ except KeyError:
122
+ raise Exception("Number of line points N must be specified.")
123
+ if self.N < 2 or int(self.N) != self.N:
124
+ raise Exception("Number of line points N must be an integer >= 2.")
125
+
126
+ self.overview_detector_name = config["overview_detector_name"]
127
+ self._validate_overview_detector(self.overview_detector_name)
128
+ self.overview_param_names = config["overview_param_names"]
129
+ self.show_scan_points = config.get("show_scan_points", True)
130
+
131
+ elif self.type == "GridScan":
132
+ self.plots = config["plots"]
133
+ grid_keys = list(config["Parameters"]["grid_params"].keys())
134
+ if set(grid_keys) != {"x", "y"} or len(grid_keys) != 2:
135
+ raise Exception("Grid scan requires exactly two grid parameters named 'x' and 'y'.")
136
+
137
+ try:
138
+ self.N = config["Parameters"]["N"]
139
+ except KeyError:
140
+ raise Exception("Number of grid points N must be specified.")
141
+ if self.N < 2 or int(self.N) != self.N:
142
+ raise Exception("Number of grid points N must be an integer >= 2.")
143
+
144
+ self.minmax = {}
145
+ self.scales = {}
146
+ self.grid_names = {}
147
+
148
+ try:
149
+ for key in grid_keys:
150
+ entry = config["Parameters"]["grid_params"][key]
151
+ self.minmax[key] = [float(val) for val in entry["range"]] # Support 1e-3
152
+ self.scales[key] = entry["scale"]
153
+ self.grid_names[key] = entry["name"]
154
+ except KeyError:
155
+ raise Exception("Grid parameters must have 'name', 'scale' and 'range' specified.")
156
+
157
+ self.other_params = config["Parameters"]["other_params"] or {}
158
+ self.overview_detector_name = config["overview_detector_name"]
159
+ self._validate_overview_detector(self.overview_detector_name)
160
+ self.overview_param_names = config["overview_param_names"]
161
+ self.show_scan_points = config.get("show_scan_points", True)
162
+
163
+ else:
164
+ raise Exception(f"Unknown scan type {self.type}")
165
+
166
+ def _validate_overview_detector(self, detector_name: str) -> None:
167
+ """Ensure the requested overview detector exists in the observability data."""
168
+ available = GW_Sensitivity_Data().det_names
169
+ if detector_name not in available:
170
+ raise ValueError(
171
+ "overview_detector_name must be one of "
172
+ + ", ".join(sorted(available))
173
+ + f"; got '{detector_name}'"
174
+ )
175
+
176
+ @staticmethod
177
+ def _resolve_model_parameters(potential):
178
+ """Return the parameter metadata defined by the imported model module."""
179
+ try:
180
+ params = potential.model_parameters
181
+ except Exception:
182
+ raise AttributeError(
183
+ "The provided model does not expose model_parameters either as a module-level "
184
+ "attribute or on the specific_potential class."
185
+ )
186
+
187
+ return {name: params[name].copy() for name in params}
188
+
189
+ def display(self):
190
+ """Pretty-print the loaded configuration to the console."""
191
+ try:
192
+ summary = Table(show_header=False, box=None, pad_edge=False)
193
+ summary.add_column("Setting", style="cyan")
194
+ summary.add_column("Value")
195
+ summary.add_row("Type", str(self.type))
196
+ summary.add_row("Model", str(self.modelfile))
197
+ summary.add_row("Output", str(self.output_path))
198
+ summary.add_row("Format", str(self.format))
199
+ plots_value = getattr(self, "plots", None)
200
+ if plots_value is not None:
201
+ summary.add_row("Plots", str(plots_value))
202
+ if getattr(self, "description", None):
203
+ summary.add_row("Description", str(self.description))
204
+ console.print(Panel(summary, title="Config", border_style="green"))
205
+
206
+ params_tbl = Table(show_header=True, header_style="bold", box=None, pad_edge=False)
207
+ params_tbl.add_column("Parameter", style="cyan")
208
+ params_tbl.add_column("Value")
209
+
210
+ if self.type == "SinglePoint":
211
+ for k, v in self.params.items():
212
+ try:
213
+ val = f"{float(v):g}"
214
+ except Exception:
215
+ val = str(v)
216
+ params_tbl.add_row(k, val)
217
+ elif self.type == "LineScan":
218
+ pname = self.line_param
219
+ rmin, rmax = self.minmax
220
+ params_tbl.add_row("mode", "Line")
221
+ params_tbl.add_row(pname, f"[{rmin:g}, {rmax:g}] ({self.scale})")
222
+ params_tbl.add_row("N", f"{self.N}")
223
+ elif self.type == "GridScan":
224
+ xname = self.grid_names.get("x", "x")
225
+ yname = self.grid_names.get("y", "y")
226
+ xmin, xmax = self.minmax["x"]
227
+ ymin, ymax = self.minmax["y"]
228
+ xscale = self.scales["x"]
229
+ yscale = self.scales["y"]
230
+ params_tbl.add_row("mode", "Grid")
231
+ params_tbl.add_row("x", f"{xname} [{xmin:g}, {xmax:g}] ({xscale})")
232
+ params_tbl.add_row("y", f"{yname} [{ymin:g}, {ymax:g}] ({yscale})")
233
+ params_tbl.add_row("N per axis", f"{self.N}")
234
+ else:
235
+ if hasattr(self, "scan_params") and isinstance(self.scan_params, dict):
236
+ for k, spec in self.scan_params.items():
237
+ try:
238
+ rmin, rmax = spec.get("range", [None, None])
239
+ scale = spec.get("scale", "")
240
+ if rmin is not None and rmax is not None:
241
+ spec_str = f"[{float(rmin):g}, {float(rmax):g}] ({scale})"
242
+ else:
243
+ spec_str = str(spec)
244
+ except Exception:
245
+ spec_str = str(spec)
246
+ params_tbl.add_row(k, spec_str)
247
+ if hasattr(self, "N"):
248
+ params_tbl.add_row("N", f"{self.N}")
249
+ console.print(Panel(params_tbl, title="Scan parameters", border_style="green"))
250
+
251
+ fixed = getattr(self, "other_params", None)
252
+ if fixed:
253
+ fixed_tbl = Table(show_header=True, header_style="bold", box=None, pad_edge=False)
254
+ fixed_tbl.add_column("Fixed Parameter", style="cyan")
255
+ fixed_tbl.add_column("Value")
256
+ for k, v in fixed.items():
257
+ try:
258
+ val = f"{float(v):g}"
259
+ except Exception:
260
+ val = str(v)
261
+ fixed_tbl.add_row(k, val)
262
+ console.print(Panel(fixed_tbl, title="Fixed Parameters", border_style="green"))
263
+ except Exception:
264
+ pass
@@ -0,0 +1,30 @@
1
+ """Small helpers for logging scan output to disk.
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
+ from pathlib import Path
14
+
15
+
16
+ class Logger:
17
+ """Very small helper that mirrors stdout logging to a file."""
18
+
19
+ def __init__(self, file_name: str | Path):
20
+ """Open `file_name` in append mode so logs persist across scans."""
21
+ self.file = open(file_name, "a")
22
+
23
+ def log(self, message: str):
24
+ """Write `message` to the backing file and flush immediately."""
25
+ self.file.write(message)
26
+ self.file.flush()
27
+
28
+ def close(self):
29
+ """Close the on-disk log file."""
30
+ self.file.close()