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,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()
|