JimGW 0.4.1__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.
- jimgw/__init__.py +20 -0
- jimgw/_logging.py +19 -0
- jimgw/cli/__init__.py +267 -0
- jimgw/cli/_config.py +651 -0
- jimgw/cli/_data.py +135 -0
- jimgw/cli/_jim.py +52 -0
- jimgw/cli/_likelihood.py +232 -0
- jimgw/cli/_output.py +184 -0
- jimgw/cli/_prior.py +131 -0
- jimgw/cli/_transforms.py +445 -0
- jimgw/cli/_utils.py +26 -0
- jimgw/cli/_waveform.py +40 -0
- jimgw/core/__init__.py +1 -0
- jimgw/core/base.py +50 -0
- jimgw/core/constants.py +32 -0
- jimgw/core/jim.py +535 -0
- jimgw/core/prior.py +728 -0
- jimgw/core/single_event/__init__.py +0 -0
- jimgw/core/single_event/data.py +804 -0
- jimgw/core/single_event/detector.py +913 -0
- jimgw/core/single_event/likelihood.py +1752 -0
- jimgw/core/single_event/marginalization_config.py +29 -0
- jimgw/core/single_event/polarization.py +88 -0
- jimgw/core/single_event/prior.py +33 -0
- jimgw/core/single_event/time_utils.py +271 -0
- jimgw/core/single_event/transform_utils.py +653 -0
- jimgw/core/single_event/transforms.py +649 -0
- jimgw/core/single_event/utils.py +121 -0
- jimgw/core/single_event/waveform.py +39 -0
- jimgw/core/transforms.py +882 -0
- jimgw/core/utils.py +111 -0
- jimgw/samplers/__init__.py +137 -0
- jimgw/samplers/base.py +121 -0
- jimgw/samplers/blackjax/__init__.py +5 -0
- jimgw/samplers/blackjax/_acceptance_walk_kernel.py +391 -0
- jimgw/samplers/blackjax/_imports.py +31 -0
- jimgw/samplers/blackjax/ns_aw.py +309 -0
- jimgw/samplers/blackjax/nss.py +300 -0
- jimgw/samplers/blackjax/smc.py +881 -0
- jimgw/samplers/config.py +466 -0
- jimgw/samplers/flowmc.py +326 -0
- jimgw/samplers/periodic.py +140 -0
- jimgw/typing.py +10 -0
- jimgw-0.4.1.dist-info/METADATA +135 -0
- jimgw-0.4.1.dist-info/RECORD +48 -0
- jimgw-0.4.1.dist-info/WHEEL +4 -0
- jimgw-0.4.1.dist-info/entry_points.txt +2 -0
- jimgw-0.4.1.dist-info/licenses/LICENSE +22 -0
jimgw/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
4
|
+
|
|
5
|
+
from jimgw._logging import LOG_FORMAT
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = version("jimgw")
|
|
9
|
+
except PackageNotFoundError:
|
|
10
|
+
__version__ = "unknown"
|
|
11
|
+
|
|
12
|
+
# propagate=False isolates jimgw from the root logger to avoid duplicates
|
|
13
|
+
# when the application also configures logging (e.g. via basicConfig).
|
|
14
|
+
_log = logging.getLogger(__name__)
|
|
15
|
+
_log.setLevel(logging.INFO)
|
|
16
|
+
_log.propagate = False
|
|
17
|
+
if not any(isinstance(h, logging.StreamHandler) for h in _log.handlers):
|
|
18
|
+
_h = logging.StreamHandler()
|
|
19
|
+
_h.setFormatter(logging.Formatter(LOG_FORMAT))
|
|
20
|
+
_log.addHandler(_h)
|
jimgw/_logging.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
LOG_FORMAT = "%(levelname)s | %(name)s | %(message)s"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def ensure_logger_handler(logger_name: str, level: int) -> None:
|
|
7
|
+
"""Add a StreamHandler to a logger if it has none, and set propagate=False.
|
|
8
|
+
|
|
9
|
+
Mirrors the self-contained handler setup that jimgw/__init__.py applies to
|
|
10
|
+
the jimgw logger, so third-party loggers (e.g. flowMC) produce output in
|
|
11
|
+
script/notebook contexts without requiring the user to call basicConfig.
|
|
12
|
+
"""
|
|
13
|
+
log = logging.getLogger(logger_name)
|
|
14
|
+
log.setLevel(level)
|
|
15
|
+
log.propagate = False
|
|
16
|
+
if not any(isinstance(h, logging.StreamHandler) for h in log.handlers):
|
|
17
|
+
_h = logging.StreamHandler()
|
|
18
|
+
_h.setFormatter(logging.Formatter(LOG_FORMAT))
|
|
19
|
+
log.addHandler(_h)
|
jimgw/cli/__init__.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import tomllib
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from pydantic import ValidationError
|
|
9
|
+
|
|
10
|
+
from jimgw._logging import LOG_FORMAT
|
|
11
|
+
from jimgw.cli._config import PipelineConfig
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(
|
|
16
|
+
name="jim-run",
|
|
17
|
+
add_completion=False,
|
|
18
|
+
help="Run a jimgw parameter-estimation pipeline from a TOML config file.",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_INIT_TEMPLATE = """\
|
|
22
|
+
seed = 0
|
|
23
|
+
|
|
24
|
+
[data]
|
|
25
|
+
type = "gwosc"
|
|
26
|
+
detectors = ["H1", "L1"]
|
|
27
|
+
trigger_time = 1126259462.4
|
|
28
|
+
duration = 4.0
|
|
29
|
+
post_trigger_duration = 2.0
|
|
30
|
+
psd_duration = 1024.0
|
|
31
|
+
|
|
32
|
+
[waveform]
|
|
33
|
+
approximant = "IMRPhenomXAS"
|
|
34
|
+
f_ref = 20.0
|
|
35
|
+
|
|
36
|
+
[prior]
|
|
37
|
+
M_c = { type = "uniform", min = 10.0, max = 80.0 }
|
|
38
|
+
q = { type = "uniform", min = 0.125, max = 1.0 }
|
|
39
|
+
s1_z = { type = "uniform", min = -0.99, max = 0.99 }
|
|
40
|
+
s2_z = { type = "uniform", min = -0.99, max = 0.99 }
|
|
41
|
+
iota = { type = "sine" }
|
|
42
|
+
d_L = { type = "power_law", min = 1.0, max = 2000.0, alpha = 2.0 }
|
|
43
|
+
t_c = { type = "uniform", min = -0.1, max = 0.1 }
|
|
44
|
+
phase_c = { type = "uniform", min = 0.0, max = 6.283185307179586 } # 2π
|
|
45
|
+
psi = { type = "uniform", min = 0.0, max = 3.141592653589793 } # π
|
|
46
|
+
ra = { type = "uniform", min = 0.0, max = 6.283185307179586 } # 2π
|
|
47
|
+
dec = { type = "cosine" }
|
|
48
|
+
|
|
49
|
+
[likelihood]
|
|
50
|
+
f_min = 20.0
|
|
51
|
+
f_max = 1024.0
|
|
52
|
+
|
|
53
|
+
# Production defaults — for a quick test try: n_chains=100, n_global_steps=100, n_production_loops=2
|
|
54
|
+
[sampler]
|
|
55
|
+
type = "flowmc"
|
|
56
|
+
n_chains = 1000
|
|
57
|
+
n_local_steps = 100
|
|
58
|
+
n_global_steps = 1000
|
|
59
|
+
n_training_loops = 50
|
|
60
|
+
n_production_loops = 10
|
|
61
|
+
n_NFproposal_batch_size = 100
|
|
62
|
+
global_thinning = 100
|
|
63
|
+
|
|
64
|
+
[output]
|
|
65
|
+
dir = "output/my_run"
|
|
66
|
+
# save_corner requires the 'corner' package: pip install corner
|
|
67
|
+
save_corner = false
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.command()
|
|
72
|
+
def run(
|
|
73
|
+
config: Optional[Path] = typer.Argument(None, help="Path to the TOML config file."),
|
|
74
|
+
init: Optional[Path] = typer.Option(
|
|
75
|
+
None,
|
|
76
|
+
"--init",
|
|
77
|
+
help="Write a minimal GW150914-style template config to PATH and exit.",
|
|
78
|
+
metavar="PATH",
|
|
79
|
+
),
|
|
80
|
+
verbose: bool = typer.Option(
|
|
81
|
+
False, "--verbose", "-v", help="Enable verbose logging."
|
|
82
|
+
),
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Run a jimgw parameter-estimation pipeline from CONFIG."""
|
|
85
|
+
level = logging.DEBUG if verbose else logging.INFO
|
|
86
|
+
logging.basicConfig(level=level, format=LOG_FORMAT)
|
|
87
|
+
# jimgw logger is isolated (propagate=False), so set its level directly.
|
|
88
|
+
logging.getLogger("jimgw").setLevel(level)
|
|
89
|
+
|
|
90
|
+
if init is not None:
|
|
91
|
+
if init.exists():
|
|
92
|
+
typer.echo(
|
|
93
|
+
f"Error: {init} already exists. Choose a different path.", err=True
|
|
94
|
+
)
|
|
95
|
+
raise typer.Exit(code=2)
|
|
96
|
+
try:
|
|
97
|
+
init.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
init.write_text(_INIT_TEMPLATE)
|
|
99
|
+
except OSError as exc:
|
|
100
|
+
typer.echo(f"Error: could not write template to {init}: {exc}", err=True)
|
|
101
|
+
raise typer.Exit(code=2) from exc
|
|
102
|
+
typer.echo(f"Template config written to {init}")
|
|
103
|
+
raise typer.Exit()
|
|
104
|
+
|
|
105
|
+
if config is None:
|
|
106
|
+
typer.echo(
|
|
107
|
+
"Error: provide a CONFIG file or use --init to create a template.", err=True
|
|
108
|
+
)
|
|
109
|
+
raise typer.Exit(code=2)
|
|
110
|
+
|
|
111
|
+
if not config.exists():
|
|
112
|
+
typer.echo(f"Error: config file not found: {config}", err=True)
|
|
113
|
+
raise typer.Exit(code=2)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
with open(config, "rb") as f:
|
|
117
|
+
raw = tomllib.load(f)
|
|
118
|
+
except (OSError, tomllib.TOMLDecodeError) as exc:
|
|
119
|
+
typer.echo(f"Error reading config {config}:\n{exc}", err=True)
|
|
120
|
+
raise typer.Exit(code=2) from exc
|
|
121
|
+
|
|
122
|
+
logger.info("Loaded config from %s", config)
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
cfg = PipelineConfig.model_validate(raw)
|
|
126
|
+
except ValidationError as exc:
|
|
127
|
+
typer.echo(f"Config validation error:\n{exc}", err=True)
|
|
128
|
+
raise typer.Exit(code=2) from exc
|
|
129
|
+
|
|
130
|
+
_log_config_summary(cfg)
|
|
131
|
+
_log_versions(cfg.sampler.type)
|
|
132
|
+
|
|
133
|
+
out_dir = cfg.output.dir
|
|
134
|
+
if out_dir.exists() and not cfg.output.overwrite:
|
|
135
|
+
typer.echo(
|
|
136
|
+
f"Error: output directory already exists: {out_dir}. "
|
|
137
|
+
"Set output.overwrite = true to allow overwriting.",
|
|
138
|
+
err=True,
|
|
139
|
+
)
|
|
140
|
+
raise typer.Exit(code=2)
|
|
141
|
+
|
|
142
|
+
import jax
|
|
143
|
+
|
|
144
|
+
jax.config.update("jax_enable_x64", True)
|
|
145
|
+
|
|
146
|
+
from jimgw.cli._data import build_data
|
|
147
|
+
from jimgw.cli._jim import build_jim
|
|
148
|
+
from jimgw.cli._likelihood import build_likelihood
|
|
149
|
+
from jimgw.cli._output import write_outputs
|
|
150
|
+
from jimgw.cli._prior import adapt_prior_for_ns_time, build_prior
|
|
151
|
+
from jimgw.cli._transforms import (
|
|
152
|
+
infer_likelihood_transforms,
|
|
153
|
+
infer_sample_transforms,
|
|
154
|
+
)
|
|
155
|
+
from jimgw.cli._waveform import build_waveform
|
|
156
|
+
|
|
157
|
+
trigger_time: float = cfg.data.trigger_time
|
|
158
|
+
|
|
159
|
+
# Stage 2: waveform
|
|
160
|
+
waveform = build_waveform(cfg.waveform)
|
|
161
|
+
|
|
162
|
+
# Stage 3: data — injection runs receive the already-built waveform
|
|
163
|
+
ifos = build_data(
|
|
164
|
+
cfg.data,
|
|
165
|
+
f_min=cfg.likelihood.f_min,
|
|
166
|
+
f_max=cfg.likelihood.f_max,
|
|
167
|
+
waveform=waveform,
|
|
168
|
+
time_frame=cfg.sampling.time_frame,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# NS-AW requires all sampling-space parameters in [0, 1].
|
|
172
|
+
# Must run before build_prior so the built prior and
|
|
173
|
+
# prior_params already reflect the substitution.
|
|
174
|
+
if cfg.sampler.type == "blackjax-ns-aw":
|
|
175
|
+
modified_prior = adapt_prior_for_ns_time(cfg.prior, cfg.sampling)
|
|
176
|
+
if modified_prior is not None:
|
|
177
|
+
cfg.prior = modified_prior
|
|
178
|
+
|
|
179
|
+
# Stage 4: prior
|
|
180
|
+
prior = build_prior(cfg.prior)
|
|
181
|
+
|
|
182
|
+
# Stage 5: transform inference
|
|
183
|
+
prior_params = frozenset(prior.parameter_names)
|
|
184
|
+
ns_aw = cfg.sampler.type == "blackjax-ns-aw"
|
|
185
|
+
sample_transforms = infer_sample_transforms(
|
|
186
|
+
prior_params,
|
|
187
|
+
trigger_time,
|
|
188
|
+
ifos,
|
|
189
|
+
cfg.sampling,
|
|
190
|
+
unit_cube=ns_aw,
|
|
191
|
+
prior_cfg=cfg.prior,
|
|
192
|
+
)
|
|
193
|
+
likelihood_transforms = infer_likelihood_transforms(
|
|
194
|
+
prior_params,
|
|
195
|
+
trigger_time,
|
|
196
|
+
ifos,
|
|
197
|
+
cfg.sampling,
|
|
198
|
+
cfg.waveform.f_ref,
|
|
199
|
+
phase_marginalization=cfg.likelihood.phase_marginalization,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Stage 6: likelihood
|
|
203
|
+
likelihood = build_likelihood(
|
|
204
|
+
cfg.likelihood,
|
|
205
|
+
ifos,
|
|
206
|
+
waveform,
|
|
207
|
+
trigger_time,
|
|
208
|
+
cfg.waveform.f_ref,
|
|
209
|
+
prior=prior,
|
|
210
|
+
likelihood_transforms=likelihood_transforms,
|
|
211
|
+
data_cfg=cfg.data,
|
|
212
|
+
time_frame=cfg.sampling.time_frame,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Stage 7: build Jim + run sampler
|
|
216
|
+
jim = build_jim(
|
|
217
|
+
likelihood,
|
|
218
|
+
prior,
|
|
219
|
+
sample_transforms,
|
|
220
|
+
likelihood_transforms,
|
|
221
|
+
cfg,
|
|
222
|
+
verbose=verbose,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
jim.sample()
|
|
227
|
+
except Exception as exc:
|
|
228
|
+
logger.error("Sampling failed: %s", exc)
|
|
229
|
+
raise typer.Exit(code=3) from exc
|
|
230
|
+
logger.info("Sampling complete.")
|
|
231
|
+
|
|
232
|
+
# Stage 8: write outputs
|
|
233
|
+
try:
|
|
234
|
+
write_outputs(jim, cfg)
|
|
235
|
+
except FileExistsError as exc:
|
|
236
|
+
typer.echo(f"Error: {exc}", err=True)
|
|
237
|
+
raise typer.Exit(code=2) from exc
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _log_versions(sampler_type: str) -> None:
|
|
241
|
+
dists = ["JimGW", "rippleGW"]
|
|
242
|
+
if sampler_type == "flowmc":
|
|
243
|
+
dists.append("flowMC")
|
|
244
|
+
parts = []
|
|
245
|
+
for dist in dists:
|
|
246
|
+
try:
|
|
247
|
+
parts.append(f"{dist} {version(dist)}")
|
|
248
|
+
except PackageNotFoundError:
|
|
249
|
+
pass
|
|
250
|
+
if parts:
|
|
251
|
+
logger.info(" | ".join(parts))
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _log_config_summary(cfg: PipelineConfig) -> None:
|
|
255
|
+
logger.info("seed: %d", cfg.seed)
|
|
256
|
+
logger.info(
|
|
257
|
+
"data: type=%s, detectors=%s",
|
|
258
|
+
cfg.data.type,
|
|
259
|
+
cfg.data.detectors,
|
|
260
|
+
)
|
|
261
|
+
logger.info(
|
|
262
|
+
"waveform: %s (f_ref=%.1f Hz)", cfg.waveform.approximant, cfg.waveform.f_ref
|
|
263
|
+
)
|
|
264
|
+
param_names = list(cfg.prior.root.keys())
|
|
265
|
+
logger.info("prior: %d parameter(s): %s", len(param_names), param_names)
|
|
266
|
+
logger.info("sampler: type=%s", cfg.sampler.type)
|
|
267
|
+
logger.info("output: %s", cfg.output.dir)
|