From 52ad8b232938c346a5a5a5589198207792ae2f49 Mon Sep 17 00:00:00 2001 From: Annalena Kofler Date: Wed, 22 Oct 2025 14:19:37 +0200 Subject: [PATCH 01/15] Fix version check for models that don't contain the version key (before v0.3.3). Important for old Zenodo models. --- dingo/core/posterior_models/build_model.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dingo/core/posterior_models/build_model.py b/dingo/core/posterior_models/build_model.py index ef4190120..1c38a0fae 100644 --- a/dingo/core/posterior_models/build_model.py +++ b/dingo/core/posterior_models/build_model.py @@ -44,7 +44,13 @@ def build_model_from_kwargs( if filename is not None: d, _ = torch_load_with_fallback(filename, preferred_map_location="meta") - check_minimum_version(d["version"]) + if "version" in d: + check_minimum_version(d["version"]) + else: + print( + f"Warning: The DINGO model loaded from {filename} was trained with a version older than v0.3.3. " + ) + check_minimum_version("") update_model_config(d["metadata"]["train_settings"]["model"]) # Backward compat posterior_model_type = d["metadata"]["train_settings"]["model"][ "posterior_model_type" From 4b5aeb0784d87f594f6672fe6c719e0f7f2b4dff Mon Sep 17 00:00:00 2001 From: Annalena Kofler Date: Wed, 22 Oct 2025 14:24:28 +0200 Subject: [PATCH 02/15] Improve logging --- dingo/core/posterior_models/build_model.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dingo/core/posterior_models/build_model.py b/dingo/core/posterior_models/build_model.py index 1c38a0fae..2f7571032 100644 --- a/dingo/core/posterior_models/build_model.py +++ b/dingo/core/posterior_models/build_model.py @@ -47,10 +47,8 @@ def build_model_from_kwargs( if "version" in d: check_minimum_version(d["version"]) else: - print( - f"Warning: The DINGO model loaded from {filename} was trained with a version older than v0.3.3. " - ) - check_minimum_version("") + # version was introduced in v0.3.3 + check_minimum_version("dingo=0.3.2") update_model_config(d["metadata"]["train_settings"]["model"]) # Backward compat posterior_model_type = d["metadata"]["train_settings"]["model"][ "posterior_model_type" From 174f913a480925db3a88743feede80a18d0b75c9 Mon Sep 17 00:00:00 2001 From: Heinrich Campe Date: Tue, 30 Jun 2026 13:34:35 +0200 Subject: [PATCH 03/15] claude setup --- .gitignore | 4 ++++ CLAUDE.md | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index 3a5bb4048..a6c7293c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# codex stuff +AGENTS*.md + + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..81e9e3a34 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,39 @@ +Dingo: gravitational-wave parameter inference using neural posterior estimation. + +## Architecture +- **`dingo/core/`** — model-agnostic ML/inference: `posterior_models/` (normalizing flows, flow matching, score diffusion), `nn/` (embedding nets, neural spline flows), `dataset.py` (HDF5 serialization), `result.py` (posterior samples + importance sampling). +- **`dingo/gw/`** — GW-specific: `likelihood.py`, `domains/`, `waveform_generator/`, `transforms/`, `training/`, `inference/`. +- **`dingo/pipe/`** — CLI pipeline (bilby_pipe-based): data generation → sampling → importance sampling → plotting. `parser.py` (INI), `main.py` (completed config + DAG), `nodes/` (HTCondor). +- **`dingo/asimov/`** — Asimov (LVK) integration. + +## Commands +- `uv sync`; `uv run pytest [tests/...::test]`; `uv run black dingo tests`. + +## Code writing — stop at the first option that solves the task correctly +1. Don't build behavior that isn't needed. +2. Use the standard library when it already solves the problem. +3. Use an existing repository or platform feature. +4. Use an already-installed dependency. +5. Inline a one-line solution when it stays readable; don't wrap it in a one-line helper. +6. Otherwise, write the minimum task-specific code that works. + +General principles: +- No speculative abstractions, dependencies, classes, or boilerplate. Prefer deletion over addition, boring code over clever code. +- When two approaches are similarly small, choose the one with correct edge-case behavior. +- This economy does **not** apply to validation at trust boundaries, errors preventing silent data corruption, security, or scientific correctness. +- Non-trivial new logic must leave one small runnable check that fails if the behavior breaks. +- Reuse existing functions/classes/patterns before writing new ones. +- Strictly avoid one-line / single-use helpers — inline unless extraction clearly improves readability. +- Prefer f-strings. Add a short docstring to new/modified functions. Start new executable modules with a module docstring. +- Avoid adding new classes unless asked; if one seems necessary, ask first. + +## Testing +- Think about the test before implementing. Add a focused test or assert-based self-check for new logic. +- Tests are deterministic (RNG seeded); run a single test with `uv run pytest ::`. + +## Git +- Don't run interactive git, and don't commit or push unless asked. + +## Boundaries +- Don't modify the user's environment or add dependencies without explicit approval. +- Don't call functions from `gw/` into `core/` (keep `core/` domain-agnostic). \ No newline at end of file From b578d9dc891030d1da04988e26d2f86ded12e931 Mon Sep 17 00:00:00 2001 From: Heinrich Campe <49278231+hcampe@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:32:16 +0200 Subject: [PATCH 04/15] more work --- dingo/core/dataset.py | 9 +- dingo/gw/dataset/generate_dataset.py | 111 ++++++------------ dingo/gw/dataset/waveform_dataset.py | 7 +- .../waveform_generator/waveform_generator.py | 7 +- 4 files changed, 52 insertions(+), 82 deletions(-) diff --git a/dingo/core/dataset.py b/dingo/core/dataset.py index 49f9718b8..e15a42fac 100644 --- a/dingo/core/dataset.py +++ b/dingo/core/dataset.py @@ -1,11 +1,14 @@ from typing import List, Optional, Union import ast +import logging import h5py import numpy as np import pandas as pd from dingo.core.utils.misc import get_version +log = logging.getLogger(__name__) + def recursive_hdf5_save(group, d): for k, v in d.items(): @@ -136,7 +139,7 @@ def __init__( self.from_dictionary(dictionary) def to_file(self, file_name: str, mode: str = "w"): - print("Saving dataset to " + str(file_name)) + log.info(f"Saving dataset to {file_name}") save_dict = { k: v for k, v in vars(self).items() @@ -150,9 +153,9 @@ def to_file(self, file_name: str, mode: str = "w"): f.attrs["dataset_type"] = self.dataset_type def from_file(self, file_name: str): - print(f"Loading dataset from {str(file_name)}.") + log.info(f"Loading dataset from {file_name}.") if self._leave_on_disk_keys: - print(f"Omitting data keys {self._leave_on_disk_keys}.") + log.info(f"Omitting data keys {self._leave_on_disk_keys}.") with h5py.File(file_name, "r") as f: loaded_dict = recursive_hdf5_load( diff --git a/dingo/gw/dataset/generate_dataset.py b/dingo/gw/dataset/generate_dataset.py index a52ca9865..0b4fda8a0 100644 --- a/dingo/gw/dataset/generate_dataset.py +++ b/dingo/gw/dataset/generate_dataset.py @@ -1,30 +1,30 @@ -import argparse import copy -import textwrap +import logging from multiprocessing import Pool from pathlib import Path from typing import Dict, Tuple from functools import partial +import hydra import numpy as np import pandas as pd -import yaml from bilby.gw.prior import BBHPriorDict +from hydra.utils import instantiate +from omegaconf import DictConfig, OmegaConf from threadpoolctl import threadpool_limits from torchvision.transforms import Compose from dingo.gw.dataset.waveform_dataset import WaveformDataset -from dingo.gw.domains import build_domain -from dingo.gw.prior import build_prior_with_defaults from dingo.gw.SVD import ApplySVD, SVDBasis from dingo.gw.transforms import WhitenFixedASD from dingo.gw.waveform_generator import ( - NewInterfaceWaveformGenerator, WaveformGenerator, generate_waveforms_parallel, ) from dingo.core.utils.misc import call_func_strict_output_dim +log = logging.getLogger(__name__) + def generate_parameters_and_polarizations( waveform_generator: WaveformGenerator, @@ -47,7 +47,7 @@ def generate_parameters_and_polarizations( pandas DataFrame of parameters dictionary of numpy arrays corresponding to waveform polarizations """ - print("Generating dataset of size " + str(num_samples)) + log.info("Generating dataset of size " + str(num_samples)) parameters = pd.DataFrame(prior.sample(num_samples)) if num_processes > 1: @@ -67,12 +67,12 @@ def generate_parameters_and_polarizations( polarizations_ok = {k: v[idx_ok] for k, v in polarizations.items()} parameters_ok = parameters.iloc[idx_ok] failed_percent = 100 * len(idx_failed) / len(parameters) - print( + log.info( f"{len(idx_failed)} out of {len(parameters)} configuration ({failed_percent:.1f}%) failed to generate." ) with pd.option_context("display.max_rows", None, "display.max_columns", None): - print(parameters.iloc[idx_failed]) - print( + log.info(parameters.iloc[idx_failed]) + log.info( f"Only returning the {len(idx_ok)} successfully generated configurations." ) return parameters_ok, polarizations_ok @@ -115,7 +115,7 @@ def train_svd_basis(dataset: WaveformDataset, size: int, n_train: int): ) test_parameters.reset_index(drop=True, inplace=True) - print("Building SVD basis.") + log.info("Building SVD basis.") basis = SVDBasis() basis.generate_basis(train_data, size) @@ -139,39 +139,36 @@ def train_svd_basis(dataset: WaveformDataset, size: int, n_train: int): return basis, n_train, n_test -def generate_dataset(settings: Dict, num_processes: int) -> WaveformDataset: +def _dataset_settings(cfg: DictConfig) -> Dict: + settings = OmegaConf.to_container(cfg, resolve=True) + settings.pop("out_file", None) + settings.pop("num_processes", None) + return settings + + +def generate_dataset(cfg: DictConfig) -> WaveformDataset: """ Generate a waveform dataset. Parameters ---------- - settings : dict - Dictionary of settings to configure the dataset - num_processes : int + cfg : DictConfig + Hydra configuration for dataset generation. Returns ------- A WaveformDataset based on the settings. """ - prior = build_prior_with_defaults(settings["intrinsic_prior"]) - domain = build_domain(settings["domain"]) - - new_interface_flag = settings["waveform_generator"].get("new_interface", False) - if new_interface_flag: - waveform_generator = NewInterfaceWaveformGenerator( - domain=domain, - **settings["waveform_generator"], - ) - else: - waveform_generator = WaveformGenerator( - domain=domain, - **settings["waveform_generator"], - ) + prior = instantiate(cfg.intrinsic_prior, _convert_="all") + domain = instantiate(cfg.domain) + waveform_generator = instantiate(cfg.waveform_generator, domain=domain) + num_processes = cfg.get("num_processes", 1) + settings = _dataset_settings(cfg) dataset_dict = {"settings": settings} - if "compression" in settings: + if settings.get("compression", None) is not None: compression_transforms = [] if "whitening" in settings["compression"]: @@ -246,63 +243,25 @@ def generate_dataset(settings: Dict, num_processes: int) -> WaveformDataset: dataset_dict["parameters"] = parameters dataset_dict["polarizations"] = polarizations - dataset_dict[settings["num_samples"]] = len(parameters) dataset = WaveformDataset(dictionary=dataset_dict) return dataset -def parse_args(): - parser = argparse.ArgumentParser( - formatter_class=argparse.RawDescriptionHelpFormatter, - description=textwrap.dedent( - """\ - Generate a waveform dataset based on a settings file. - """ - ), - ) - parser.add_argument( - "--settings_file", - type=str, - required=True, - help="YAML file containing database settings", - ) - parser.add_argument( - "--num_processes", - type=int, - default=1, - help="Number of processes to use in pool for parallel waveform generation", - ) - parser.add_argument( - "--out_file", - type=str, - default="waveform_dataset.hdf5", - help="Name of file for storing dataset.", - ) - return parser.parse_args() - - -def _generate_dataset_main( - settings_file: str, out_file: str, num_processes: int -) -> None: - if not Path(settings_file).is_file(): - raise FileNotFoundError(f"dataset generation, failed to find {settings_file}") +@hydra.main( + version_base="1.3", + config_path="../../../configs/gw/dataset", + config_name="generate_dataset", +) +def main(cfg: DictConfig) -> None: + out_file = cfg.out_file if not Path(out_file).parent.is_dir(): raise FileNotFoundError( f"dataset generation: can not create {out_file}: " f"{Path(out_file).parent} does not exist" ) - # Load settings - with open(settings_file, "r") as f: - settings = yaml.safe_load(f) - - dataset = generate_dataset(settings, num_processes) + dataset = generate_dataset(cfg) dataset.to_file(str(out_file)) -def main() -> None: - args = parse_args() - _generate_dataset_main(args.settings_file, args.out_file, args.num_processes) - - if __name__ == "__main__": main() diff --git a/dingo/gw/dataset/waveform_dataset.py b/dingo/gw/dataset/waveform_dataset.py index af750f5be..43d298c5e 100644 --- a/dingo/gw/dataset/waveform_dataset.py +++ b/dingo/gw/dataset/waveform_dataset.py @@ -2,6 +2,7 @@ import h5py import numpy as np import torch.utils.data +from hydra.utils import instantiate from torchvision.transforms import Compose from dingo.core.dataset import DingoDataset, recursive_hdf5_load @@ -100,7 +101,11 @@ def load_supplemental( svd_size_update : int If provided, reduces the SVD size when decompressing (for speed). """ - self.domain = build_domain(self.settings["domain"]) + domain_settings = self.settings["domain"] + if "_target_" in domain_settings: + self.domain = instantiate(domain_settings) + else: + self.domain = build_domain(domain_settings) # We always call update_domain() (even if domain_update is None) because we # want to be sure that the data are consistent with the saved settings. In diff --git a/dingo/gw/waveform_generator/waveform_generator.py b/dingo/gw/waveform_generator/waveform_generator.py index 43f6a6488..f673cf699 100644 --- a/dingo/gw/waveform_generator/waveform_generator.py +++ b/dingo/gw/waveform_generator/waveform_generator.py @@ -1,4 +1,5 @@ from functools import partial +import logging from multiprocessing import Pool from math import isclose @@ -31,6 +32,8 @@ ) from dingo.gw.transforms.waveform_transforms import DecimateAll +log = logging.getLogger(__name__) + class WaveformGenerator: """Generate polarizations using LALSimulation routines in the specified domain for a @@ -142,12 +145,12 @@ def spin_conversion_phase(self): @spin_conversion_phase.setter def spin_conversion_phase(self, value): if value is None: - print( + log.info( "Setting spin_conversion_phase = None. Using phase parameter for " "conversion to cartesian spins." ) else: - print( + log.info( f"Setting spin_conversion_phase = {value}. Using this value for the " f"phase parameter for conversion to cartesian spins." ) From 345b2ccfd5cecdbe398115951571ea4de26135c8 Mon Sep 17 00:00:00 2001 From: cmfabbri Date: Wed, 1 Jul 2026 09:16:35 +0200 Subject: [PATCH 05/15] WIP: Import logger and replace print statements with logging calls (issue #152) Co-Authored-By: Claude Sonnet 4.6 --- dingo/core/dataset.py | 7 +-- dingo/core/posterior_models/base_model.py | 19 +++---- dingo/core/result.py | 40 +++++++------- dingo/core/samplers.py | 30 +++++------ dingo/core/utils/condor_utils.py | 10 ++-- dingo/core/utils/pt_to_hdf5.py | 4 +- dingo/core/utils/trainutils.py | 45 +++++++++------- dingo/gw/SVD.py | 29 +++++++---- dingo/gw/conversion/spin_conversion.py | 4 +- dingo/gw/data/data_download.py | 3 +- dingo/gw/data/data_preparation.py | 7 +-- .../gw/dataset/evaluate_multibanded_domain.py | 31 +++++------ dingo/gw/dataset/generate_dataset.py | 11 ++-- dingo/gw/dataset/generate_dataset_dag.py | 4 +- dingo/gw/dataset/utils.py | 7 +-- dingo/gw/download_strain_data.py | 7 +-- dingo/gw/importance_sampling/diagnostics.py | 3 +- .../importance_sampling/importance_weights.py | 13 ++--- dingo/gw/inference/gw_samplers.py | 7 +-- dingo/gw/injection.py | 5 +- dingo/gw/likelihood.py | 13 ++--- dingo/gw/noise/asd_dataset.py | 9 ++-- dingo/gw/noise/generate_dataset.py | 7 +-- dingo/gw/noise/synthetic/asd_sampling.py | 3 +- dingo/gw/noise/utils.py | 3 +- dingo/gw/result.py | 21 ++++---- dingo/gw/training/train_builders.py | 31 ++++++----- dingo/gw/training/train_pipeline.py | 52 +++++++++---------- dingo/gw/training/train_pipeline_condor.py | 13 ++--- dingo/gw/training/utils.py | 16 +++--- dingo/gw/transforms/waveform_transforms.py | 3 +- .../waveform_generator/waveform_generator.py | 9 ++-- dingo/pipe/dag_creator.py | 6 ++- dingo/pipe/dingo_result.py | 5 +- dingo/pipe/nodes/generation_node.py | 4 +- dingo/pipe/nodes/pe_summary_node.py | 6 ++- dingo/pipe/plot.py | 6 ++- dingo/pipe/pp_test.py | 4 +- 38 files changed, 274 insertions(+), 223 deletions(-) diff --git a/dingo/core/dataset.py b/dingo/core/dataset.py index 49f9718b8..62893e765 100644 --- a/dingo/core/dataset.py +++ b/dingo/core/dataset.py @@ -4,6 +4,7 @@ import numpy as np import pandas as pd +from dingo.core.utils.logging_utils import logger from dingo.core.utils.misc import get_version @@ -136,7 +137,7 @@ def __init__( self.from_dictionary(dictionary) def to_file(self, file_name: str, mode: str = "w"): - print("Saving dataset to " + str(file_name)) + logger.info("Saving dataset to " + str(file_name)) save_dict = { k: v for k, v in vars(self).items() @@ -150,9 +151,9 @@ def to_file(self, file_name: str, mode: str = "w"): f.attrs["dataset_type"] = self.dataset_type def from_file(self, file_name: str): - print(f"Loading dataset from {str(file_name)}.") + logger.info(f"Loading dataset from {str(file_name)}.") if self._leave_on_disk_keys: - print(f"Omitting data keys {self._leave_on_disk_keys}.") + logger.info(f"Omitting data keys {self._leave_on_disk_keys}.") with h5py.File(file_name, "r") as f: loaded_dict = recursive_hdf5_load( diff --git a/dingo/core/posterior_models/base_model.py b/dingo/core/posterior_models/base_model.py index 68508d199..5df191c3d 100755 --- a/dingo/core/posterior_models/base_model.py +++ b/dingo/core/posterior_models/base_model.py @@ -10,6 +10,7 @@ import torch import dingo.core.utils as utils +from dingo.core.utils.logging_utils import logger from torch.utils.data import Dataset import time import numpy as np @@ -198,7 +199,7 @@ def network_to_device(self, device): # raise NotImplementedError('This needs testing!') # # dim = 0 [512, ...] -> [256, ...], [256, ...] on 2 GPUs # self.network = torch.nn.DataParallel(self.network) - print(f"Putting posterior model to device {self.device}.") + logger.info(f"Putting posterior model to device {self.device}.") self.network.to(self.device) def initialize_optimizer_and_scheduler(self): @@ -394,7 +395,7 @@ def train( if test_only: test_loss = test_epoch(self, test_loader) - print(f"test loss: {test_loss:.3f}") + logger.info(f"test loss: {test_loss:.3f}") else: while not runtime_limits.limits_exceeded(self.epoch): @@ -403,24 +404,24 @@ def train( # Training lr = utils.get_lr(self.optimizer) with threadpool_limits(limits=1, user_api="blas"): - print(f"\nStart training epoch {self.epoch} with lr {lr}") + logger.info(f"Start training epoch {self.epoch} with lr {lr}") time_start = time.time() train_loss = train_epoch(self, train_loader) train_time = time.time() - time_start - print( + logger.info( "Done. This took {:2.0f}:{:2.0f} min.".format( *divmod(train_time, 60) ) ) # Testing - print(f"Start testing epoch {self.epoch}") + logger.info(f"Start testing epoch {self.epoch}") time_start = time.time() test_loss = test_epoch(self, test_loader) test_time = time.time() - time_start - print( + logger.info( "Done. This took {:2.0f}:{:2.0f} min.".format( *divmod(time.time() - time_start, 60) ) @@ -449,7 +450,7 @@ def train( } ) except ImportError: - print("wandb not installed. Skipping logging to wandb.") + logger.warning("wandb not installed. Skipping logging to wandb.") if early_stopping is not None: # Whether to use train or test loss @@ -464,9 +465,9 @@ def train( join(train_dir, "best_model.pt"), save_training_info=False ) if early_stopping.early_stop: - print("Early stopping") + logger.info("Early stopping") break - print(f"Finished training epoch {self.epoch}.\n") + logger.info(f"Finished training epoch {self.epoch}.") def train_epoch(pm, dataloader): diff --git a/dingo/core/result.py b/dingo/core/result.py index 67017271a..026441de4 100644 --- a/dingo/core/result.py +++ b/dingo/core/result.py @@ -16,6 +16,7 @@ from bilby.core.prior import Constraint, DeltaFunction, PriorDict from dingo.core.dataset import DingoDataset +from dingo.core.utils.logging_utils import logger from dingo.core.density import train_unconditional_density_estimator from dingo.core.utils.misc import recursive_check_dicts_are_equal from dingo.core.utils.plotting import get_latex_labels, plot_corner_multi @@ -167,23 +168,24 @@ def reset_event(self, event_dataset): ): # This is really just for notification. Actions are only taken if the # event metadata differ. - print("\nNew event data differ from existing.") + logger.warning("New event data differ from existing.") self.context = context if self.event_metadata is not None and self.event_metadata != event_metadata: - print("Changes") - print("=======") + logger.warning("Changes") + logger.warning("=======") old_minus_new = dict(freeze(self.event_metadata) - freeze(event_metadata)) - print("Old event metadata:") + logger.warning("Old event metadata:") for k in sorted(old_minus_new): - print(f" {k}: {self.event_metadata[k]}") + logger.warning(f" {k}: {self.event_metadata[k]}") new_minus_old = dict(freeze(event_metadata) - freeze(self.event_metadata)) - print("New event metadata:") + logger.warning("New event metadata:") if self.importance_sampling_metadata.get("updates") is None: self.importance_sampling_metadata["updates"] = {} for k in sorted(new_minus_old): - print(f" {k}: {event_metadata[k]}") + logger.warning(f" {k}: {event_metadata[k]}") + self.importance_sampling_metadata["updates"][k] = event_metadata[k] self.importance_sampling_metadata["updates"][k] = event_metadata[k] self._rebuild_domain(verbose=True) @@ -312,12 +314,12 @@ def importance_sample(self, num_processes: int = 1, **likelihood_kwargs): valid_samples = np.isfinite(log_prior + delta_log_prob_target) theta = theta.iloc[valid_samples] - print(f"Calculating {len(theta)} likelihoods.") + logger.info(f"Calculating {len(theta)} likelihoods.") t0 = time.time() log_likelihood = self.likelihood.log_likelihood_multi( theta, num_processes=num_processes ) - print(f"Done. This took {time.time() - t0:.2f} seconds.") + logger.info(f"Done. This took {time.time() - t0:.2f} seconds.") self.log_noise_evidence = self.likelihood.log_Zn self.samples["log_prior"] = log_prior @@ -494,7 +496,7 @@ def rejection_sample( rng = np.random.default_rng(random_state) weights = self.samples["weights"].to_numpy(dtype=float) - print( + logger.info( f"Rejection sampling: {self.num_samples} samples, " f"ESS = {self.effective_sample_size:.0f} " f"(efficiency = {100 * self.sample_efficiency:.1f}%)" @@ -526,7 +528,7 @@ def rejection_sample( unweighted = unweighted.drop( columns=["weights", "log_prob", "delta_log_prob_target"], errors="ignore" ) - print(f"Produced {len(unweighted)} unweighted samples.") + logger.info(f"Produced {len(unweighted)} unweighted samples.") return unweighted def parameter_subset(self, parameters): @@ -619,12 +621,12 @@ def print_summary(self): Display the number of samples, and (if importance sampling is complete) the log evidence and number of effective samples. """ - print("Number of samples:", len(self.samples)) + logger.info("Number of samples: %d", len(self.samples)) if self.log_evidence is not None: - print( + logger.info( f"Log(evidence): {self.log_evidence:.3f} +- {self.log_evidence_std:.3f}" ) - print( + logger.info( f"Effective samples {self.n_eff:.1f}: " f"(Sample efficiency = {100 * self.sample_efficiency:.2f}%)" ) @@ -815,7 +817,7 @@ def plot_log_probs(self, filename="log_probs.png"): plt.tight_layout() plt.savefig(filename) else: - print("Results not importance sampled. Cannot produce log_prob plot.") + logger.warning("Results not importance sampled. Cannot produce log_prob plot.") def plot_weights(self, filename="weights.png"): """Make a scatter plot of samples weights vs log proposal.""" @@ -844,7 +846,7 @@ def plot_weights(self, filename="weights.png"): plt.tight_layout() plt.savefig(filename) else: - print("Results not importance sampled. Cannot plot weights.") + logger.warning("Results not importance sampled. Cannot plot weights.") def get_all_injection_credible_levels( self, keys: list[str] = None, weighted: bool = False @@ -1020,7 +1022,7 @@ def make_pp_plot( pvalues = [] latex_labels = get_latex_labels(results[0].prior) - print("Key: KS-test p-value") + logger.info("Key: KS-test p-value") for ii, key in enumerate(credible_levels): pp = np.array( [ @@ -1030,7 +1032,7 @@ def make_pp_plot( ) pvalue = scipy.stats.kstest(credible_levels[key], "uniform").pvalue pvalues.append(pvalue) - print("{}: {}".format(key, pvalue)) + logger.info("{}: {}".format(key, pvalue)) label = "{} ({:2.3f})".format(latex_labels[key], pvalue) plt.plot(x_values, pp, lines[ii], label=label, **kwargs) @@ -1040,7 +1042,7 @@ def make_pp_plot( pvalues=pvalues, names=list(credible_levels.keys()), ) - print("Combined p-value: {}".format(pvals.combined_pvalue)) + logger.info("Combined p-value: {}".format(pvals.combined_pvalue)) if title: ax.set_title( diff --git a/dingo/core/samplers.py b/dingo/core/samplers.py index efc4fc1fa..ab12ddf17 100644 --- a/dingo/core/samplers.py +++ b/dingo/core/samplers.py @@ -11,6 +11,7 @@ from torchvision.transforms import Compose from dingo.core.posterior_models import BasePosteriorModel +from dingo.core.utils.logging_utils import logger from dingo.core.result import Result from dingo.core.result import DATA_KEYS as RESULT_DATA_KEYS from dingo.core.utils import torch_detach_to_cpu, IterationTracker @@ -157,7 +158,7 @@ def _run_sampler( x = [x] else: if context is not None: - print("Unconditional model. Ignoring context.") + logger.warning("Unconditional model. Ignoring context.") x = [] # For a normalizing flow, we get the log_prob for "free" when sampling, @@ -205,7 +206,7 @@ def run_sampler( """ self.samples = None - print(f"Running sampler to generate {num_samples} samples.") + logger.info(f"Running sampler to generate {num_samples} samples.") t0 = time.time() if not self.unconditional_model: if self.context is None: @@ -229,7 +230,7 @@ def run_sampler( # correction for t_ref) and represent as DataFrame. self._post_process(samples) self.samples = pd.DataFrame(samples) - print(f"Done. This took {time.time() - t0:.1f} s.") + logger.info(f"Done. This took {time.time() - t0:.1f} s.") sys.stdout.flush() def log_prob(self, samples: pd.DataFrame | dict) -> np.ndarray: @@ -430,8 +431,8 @@ def _run_sampler( init_samples = self.init_sampler._run_sampler(num_samples, context) else: if self.num_iterations == 1: - print( - f"Warning: Removing initial outliers, but only carrying out " + logger.warning( + f"Removing initial outliers, but only carrying out " f"{self.num_iterations} GNPE iteration. This risks biasing " f"results." ) @@ -515,16 +516,15 @@ def _run_sampler( p: x["extrinsic_parameters"][p] for p in self.gnpe_proxy_parameters } - print( - f"it {i}.\tmin pvalue: {self.iteration_tracker.pvalue_min:.3f}" - f"\tproxy mean: ", - *[f"{torch.mean(v).item():.5f}" for v in proxies.values()], - "\tproxy std:", - *[f"{torch.std(v).item():.5f}" for v in proxies.values()], - "\ttimes:", - time_sample_start - start_time, - time_sample_end - time_sample_start, - time.time() - time_sample_end, + proxy_means = " ".join(f"{torch.mean(v).item():.5f}" for v in proxies.values()) + proxy_stds = " ".join(f"{torch.std(v).item():.5f}" for v in proxies.values()) + logger.debug( + f"it {i}. min pvalue: {self.iteration_tracker.pvalue_min:.3f}" + f" proxy mean: {proxy_means}" + f" proxy std: {proxy_stds}" + f" times: {time_sample_start - start_time:.3f}" + f" {time_sample_end - time_sample_start:.3f}" + f" {time.time() - time_sample_end:.3f}" ) # diff --git a/dingo/core/utils/condor_utils.py b/dingo/core/utils/condor_utils.py index 1b0e2bbeb..cc51de919 100644 --- a/dingo/core/utils/condor_utils.py +++ b/dingo/core/utils/condor_utils.py @@ -2,6 +2,8 @@ from os.path import join import yaml +from dingo.core.utils.logging_utils import logger + def resubmit_condor_job(train_dir, train_settings, epoch): """ @@ -12,14 +14,14 @@ def resubmit_condor_job(train_dir, train_settings, epoch): :return: """ if 'condor_settings' in train_settings: - print('Copying log files') + logger.info("Copying log files") copy_logfiles(train_dir, epoch=epoch) if epoch >= train_settings['train_settings']['runtime_limits'][ 'max_epochs_total']: - print('Training complete, job will not be resubmitted') + logger.info("Training complete, job will not be resubmitted") else: - print('Training incomplete, resubmitting job.') + logger.info("Training incomplete, resubmitting job.") create_submission_file_and_submit_job(train_dir) @@ -76,7 +78,7 @@ def copy_logfiles(log_dir, epoch, name='info', suffixes=('.err','.log','.out')): try: copyfile(src, dest) except: - print('Could not copy ' + src) + logger.warning("Could not copy " + src) if __name__ == '__main__': diff --git a/dingo/core/utils/pt_to_hdf5.py b/dingo/core/utils/pt_to_hdf5.py index 06e6aa96c..c3a0f7e81 100644 --- a/dingo/core/utils/pt_to_hdf5.py +++ b/dingo/core/utils/pt_to_hdf5.py @@ -5,6 +5,8 @@ import json import argparse +from dingo.core.utils.logging_utils import logger + def parse_args(): parser = argparse.ArgumentParser( @@ -32,7 +34,7 @@ def main(): # This is required for use on CVMFS root, ext = os.path.splitext(args.out_file) out_file_name = f'{root}_v{args.model_version_number}{ext}' - print('Output will be written to', out_file_name) + logger.info(f'Output will be written to {out_file_name}') # Load data into CPU memory since we'll be saving it using CPU libraries d = torch.load(args.in_file, map_location=torch.device("cpu")) diff --git a/dingo/core/utils/trainutils.py b/dingo/core/utils/trainutils.py index bc7d12154..87de405d0 100644 --- a/dingo/core/utils/trainutils.py +++ b/dingo/core/utils/trainutils.py @@ -5,6 +5,8 @@ import csv from typing import Literal +from dingo.core.utils.logging_utils import logger + class AvgTracker: def __init__(self): @@ -85,7 +87,7 @@ def __call__(self, val_loss: float): elif score < self.best_score + self.delta: self.counter += 1 if self.verbose: - print(f"EarlyStopping counter: {self.counter} out of {self.patience}") + logger.info(f"EarlyStopping counter: {self.counter} out of {self.patience}") if self.counter >= self.patience: self.early_stop = True return False @@ -124,23 +126,26 @@ def get_avg(self): def print_info(self, batch_idx): if batch_idx % self.print_freq == 0: - print( - "{} Epoch: {} [{}/{} ({:.0f}%)]".format( + td, td_avg = self.times["Dataloader"].x, self.times["Dataloader"].get_avg() + tn, tn_avg = self.times["Network"].x, self.times["Network"].get_avg() + logger.debug( + "{} Epoch: {} [{}/{} ({:.0f}%)] " + "Loss: {:.3f} ({:.3f}) " + "Time Dataloader: {:.3f} ({:.3f}) " + "Time Network: {:.3f} ({:.3f})".format( self.mode, self.epoch, min(batch_idx * self.batch_size, self.len_dataset), self.len_dataset, 100.0 * batch_idx * self.batch_size / self.len_dataset, - ), - end="\t\t", + self.loss, + self.get_avg(), + td, + td_avg, + tn, + tn_avg, + ) ) - # print loss - print(f"Loss: {self.loss:.3f} ({self.get_avg():.3f})", end="\t\t") - # print computation times - td, td_avg = self.times["Dataloader"].x, self.times["Dataloader"].get_avg() - tn, tn_avg = self.times["Network"].x, self.times["Network"].get_avg() - print(f"Time Dataloader: {td:.3f} ({td_avg:.3f})", end="\t\t") - print(f"Time Network: {tn:.3f} ({tn_avg:.3f})") class RuntimeLimits: @@ -195,8 +200,8 @@ def limits_exceeded(self, epoch: int = None): # check time limit for run if self.max_time_per_run is not None: if time.time() - self.time_start >= self.max_time_per_run: - print( - f"Stop run: Time limit of {self.max_time_per_run} s " f"exceeded." + logger.info( + f"Stop run: Time limit of {self.max_time_per_run} s exceeded." ) return True # check epoch limit for run @@ -204,14 +209,14 @@ def limits_exceeded(self, epoch: int = None): if epoch is None: raise ValueError("epoch required") if epoch - self.epoch_start >= self.max_epochs_per_run: - print( + logger.info( f"Stop run: Epoch limit of {self.max_epochs_per_run} per run reached." ) return True # check total epoch limit if self.max_epochs_total is not None: if epoch >= self.max_epochs_total: - print( + logger.info( f"Stop run: Total epoch limit of {self.max_epochs_total} reached." ) return True @@ -316,13 +321,13 @@ def save_model(pm, log_dir, model_prefix="model", checkpoint_epochs=None): """ # save current model model_name = join(log_dir, f"{model_prefix}_latest.pt") - print(f"Saving model to {model_name}.", end=" ") + logger.info(f"Saving model to {model_name}.") pm.save_model(model_name, save_training_info=True) - print("Done.") + logger.info("Done.") # potentially copy model to a checkpoint if checkpoint_epochs is not None and pm.epoch % checkpoint_epochs == 0: model_name_cp = join(log_dir, f"{model_prefix}_{pm.epoch:03d}.pt") - print(f"Copy model to checkpoint {model_name_cp}.", end=" ") + logger.info(f"Copying model to checkpoint {model_name_cp}.") copyfile(model_name, model_name_cp) - print("Done.") + logger.info("Done.") diff --git a/dingo/gw/SVD.py b/dingo/gw/SVD.py index e77b7761d..561d60f9a 100644 --- a/dingo/gw/SVD.py +++ b/dingo/gw/SVD.py @@ -3,6 +3,7 @@ import scipy from sklearn.utils.extmath import randomized_svd from dingo.core.dataset import DingoDataset +from dingo.core.utils.logging_utils import logger class SVDBasis(DingoDataset): @@ -155,15 +156,25 @@ def print_validation_summary(self): if "mismatch" in col: n = int(col.split(sep="=")[-1]) mismatches = self.mismatches[col] - print(f"n = {n}") - print(" Mean mismatch = {}".format(np.mean(mismatches))) - print(" Standard deviation = {}".format(np.std(mismatches))) - print(" Max mismatch = {}".format(np.max(mismatches))) - print(" Median mismatch = {}".format(np.median(mismatches))) - print(" Percentiles:") - print(" 99 -> {}".format(np.percentile(mismatches, 99))) - print(" 99.9 -> {}".format(np.percentile(mismatches, 99.9))) - print(" 99.99 -> {}".format(np.percentile(mismatches, 99.99))) + logger.info( + f"n = {n}\n" + " Mean mismatch = {}\n" + " Standard deviation = {}\n" + " Max mismatch = {}\n" + " Median mismatch = {}\n" + " Percentiles:\n" + " 99 -> {}\n" + " 99.9 -> {}\n" + " 99.99 -> {}".format( + np.mean(mismatches), + np.std(mismatches), + np.max(mismatches), + np.median(mismatches), + np.percentile(mismatches, 99), + np.percentile(mismatches, 99.9), + np.percentile(mismatches, 99.99), + ) + ) def decompress(self, coefficients: np.ndarray): """ diff --git a/dingo/gw/conversion/spin_conversion.py b/dingo/gw/conversion/spin_conversion.py index 9e4b481cf..f2a40b45b 100644 --- a/dingo/gw/conversion/spin_conversion.py +++ b/dingo/gw/conversion/spin_conversion.py @@ -8,6 +8,8 @@ import lalsimulation as LS from threadpoolctl import threadpool_limits +from dingo.core.utils.logging_utils import logger + DINGO_PE_SPIN_PARAMETERS = ( "theta_jn", @@ -225,5 +227,5 @@ def _convert_phase(f_ref, sc_phase_old, sc_phase_new, sample): return sample_pe_new except RuntimeError: - print("Failed to convert spins. Saving sample unchanged.") + logger.warning("Failed to convert spins. Saving sample unchanged.") return sample diff --git a/dingo/gw/data/data_download.py b/dingo/gw/data/data_download.py index 31e6b1c92..c0e2f6ed1 100644 --- a/dingo/gw/data/data_download.py +++ b/dingo/gw/data/data_download.py @@ -3,6 +3,7 @@ import pycbc.psd import math +from dingo.core.utils.logging_utils import logger from dingo.gw.gwutils import get_window @@ -42,7 +43,7 @@ def download_psd(det, time_start, time_psd, window, f_s): # if strain for PSD data contains nan, shift segment for PSD if np.max(np.isnan(psd_strain)): dt = math.ceil(np.where(np.isnan(psd_strain))[0][-1] / f_s) - print( + logger.warning( f"Nan encountered in strain data for PSD estimation for detector {det}. " f"Shifting strain segment by {dt} seconds." ) diff --git a/dingo/gw/data/data_preparation.py b/dingo/gw/data/data_preparation.py index 0b06b1602..4f355b8b4 100644 --- a/dingo/gw/data/data_preparation.py +++ b/dingo/gw/data/data_preparation.py @@ -4,6 +4,7 @@ from gwpy.timeseries import TimeSeries from dingo.core.dataset import DingoDataset +from dingo.core.utils.logging_utils import logger from dingo.core.utils.misc import recursive_check_dicts_are_equal from dingo.gw.data.data_download import download_raw_data from dingo.gw.gwutils import get_window @@ -45,11 +46,11 @@ def load_raw_data(time_event, settings, event_dataset=None): f"{dataset.settings}" ) data = vars(dataset)[event] - print(f"Data for event at {event} found in {event_dataset}.") + logger.info(f"Data for event at {event} found in {event_dataset}.") return data # if this did not work, download the data - print(f"Downloading data for event at {event}.") + logger.info(f"Downloading data for event at {event}.") data = download_raw_data(time_event, **settings) # optionally save this data to event_dataset @@ -57,7 +58,7 @@ def load_raw_data(time_event, settings, event_dataset=None): dataset = DingoDataset( dictionary={event: data, "settings": settings}, data_keys=[event] ) - print(f"Saving data for event at {event} to {event_dataset}.") + logger.info(f"Saving data for event at {event} to {event_dataset}.") dataset.to_file(event_dataset, mode="a") return data diff --git a/dingo/gw/dataset/evaluate_multibanded_domain.py b/dingo/gw/dataset/evaluate_multibanded_domain.py index b20a2fb43..484603c29 100644 --- a/dingo/gw/dataset/evaluate_multibanded_domain.py +++ b/dingo/gw/dataset/evaluate_multibanded_domain.py @@ -4,6 +4,7 @@ import yaml from scipy.interpolate import interp1d +from dingo.core.utils.logging_utils import logger from dingo.gw.dataset import generate_parameters_and_polarizations from dingo.gw.domains import build_domain, MultibandedFrequencyDomain from dingo.gw.gwutils import get_mismatch @@ -35,13 +36,13 @@ def _evaluate_multibanding_main( settings["intrinsic_prior"]["chirp_mass"] = prior["chirp_mass"].minimum # Rebuild prior with updated settings. prior = build_prior_with_defaults(settings["intrinsic_prior"]) - print("Prior") + logger.info("Prior") for k, v in prior.items(): - print(f"{k}: {v}") + logger.info(f"{k}: {v}") domain = build_domain(settings["domain"]) - print("\nDomain") - print(domain.domain_dict) + logger.info("\nDomain") + logger.info(domain.domain_dict) if not isinstance(domain, MultibandedFrequencyDomain): raise ValueError("Waveform dataset domain not a MultibandedFrequencyDomain.") @@ -88,21 +89,21 @@ def _evaluate_multibanding_main( asd_file="aLIGO_ZERO_DET_high_P_asd.txt", ) - print("\nMismatches between UFD waveforms and MFD waveforms interpolated to MFD.") - print( + logger.info("\nMismatches between UFD waveforms and MFD waveforms interpolated to MFD.") + logger.info( "This is a conservative estimate of the MFD performance when training " "networks." ) mismatches = np.concatenate([v for v in mismatches.values()]) - print(f"num_samples = {num_samples}") - print(" Mean mismatch = {}".format(np.mean(mismatches))) - print(" Standard deviation = {}".format(np.std(mismatches))) - print(" Max mismatch = {}".format(np.max(mismatches))) - print(" Median mismatch = {}".format(np.median(mismatches))) - print(" Percentiles:") - print(" 99 -> {}".format(np.percentile(mismatches, 99))) - print(" 99.9 -> {}".format(np.percentile(mismatches, 99.9))) - print(" 99.99 -> {}".format(np.percentile(mismatches, 99.99))) + logger.info(f"num_samples = {num_samples}") + logger.info(" Mean mismatch = {}".format(np.mean(mismatches))) + logger.info(" Standard deviation = {}".format(np.std(mismatches))) + logger.info(" Max mismatch = {}".format(np.max(mismatches))) + logger.info(" Median mismatch = {}".format(np.median(mismatches))) + logger.info(" Percentiles:") + logger.info(" 99 -> {}".format(np.percentile(mismatches, 99))) + logger.info(" 99.9 -> {}".format(np.percentile(mismatches, 99.9))) + logger.info(" 99.99 -> {}".format(np.percentile(mismatches, 99.99))) def parse_args(): diff --git a/dingo/gw/dataset/generate_dataset.py b/dingo/gw/dataset/generate_dataset.py index a52ca9865..873568c13 100644 --- a/dingo/gw/dataset/generate_dataset.py +++ b/dingo/gw/dataset/generate_dataset.py @@ -24,6 +24,7 @@ generate_waveforms_parallel, ) from dingo.core.utils.misc import call_func_strict_output_dim +from dingo.core.utils.logging_utils import logger def generate_parameters_and_polarizations( @@ -47,7 +48,7 @@ def generate_parameters_and_polarizations( pandas DataFrame of parameters dictionary of numpy arrays corresponding to waveform polarizations """ - print("Generating dataset of size " + str(num_samples)) + logger.info("Generating dataset of size " + str(num_samples)) parameters = pd.DataFrame(prior.sample(num_samples)) if num_processes > 1: @@ -67,12 +68,12 @@ def generate_parameters_and_polarizations( polarizations_ok = {k: v[idx_ok] for k, v in polarizations.items()} parameters_ok = parameters.iloc[idx_ok] failed_percent = 100 * len(idx_failed) / len(parameters) - print( + logger.warning( f"{len(idx_failed)} out of {len(parameters)} configuration ({failed_percent:.1f}%) failed to generate." ) with pd.option_context("display.max_rows", None, "display.max_columns", None): - print(parameters.iloc[idx_failed]) - print( + logger.warning(parameters.iloc[idx_failed].to_string()) + logger.warning( f"Only returning the {len(idx_ok)} successfully generated configurations." ) return parameters_ok, polarizations_ok @@ -115,7 +116,7 @@ def train_svd_basis(dataset: WaveformDataset, size: int, n_train: int): ) test_parameters.reset_index(drop=True, inplace=True) - print("Building SVD basis.") + logger.info("Building SVD basis.") basis = SVDBasis() basis.generate_basis(train_data, size) diff --git a/dingo/gw/dataset/generate_dataset_dag.py b/dingo/gw/dataset/generate_dataset_dag.py index 395fa8fa8..c24c74b32 100644 --- a/dingo/gw/dataset/generate_dataset_dag.py +++ b/dingo/gw/dataset/generate_dataset_dag.py @@ -5,6 +5,8 @@ import yaml import copy +from dingo.core.utils.logging_utils import logger + # Fixed file names svd_fn = "svd.hdf5" settings_part_fn = "settings_part.yaml" @@ -284,7 +286,7 @@ def main(): pass dagman.build() - print(f"DAG submission file written to {args.submit}.") + logger.info(f"DAG submission file written to {args.submit}.") if __name__ == "__main__": diff --git a/dingo/gw/dataset/utils.py b/dingo/gw/dataset/utils.py index e8911f87c..aa5e84855 100644 --- a/dingo/gw/dataset/utils.py +++ b/dingo/gw/dataset/utils.py @@ -6,6 +6,7 @@ import yaml from typing import List +from dingo.core.utils.logging_utils import logger from dingo.gw.SVD import SVDBasis from dingo.gw.dataset.generate_dataset import train_svd_basis from dingo.gw.dataset.waveform_dataset import WaveformDataset @@ -26,7 +27,7 @@ def merge_datasets(dataset_list: List[WaveformDataset]) -> WaveformDataset: WaveformDataset containing the merged data. """ - print(f"Merging {len(dataset_list)} datasets into one.") + logger.info(f"Merging {len(dataset_list)} datasets into one.") # This ensures that all the keys are copied into the new dataset. The "extensive" # parts of the dataset (parameters, waveforms) will be overwritten by the combined @@ -103,7 +104,7 @@ def merge_datasets_cli(): merged_dataset.settings = settings merged_dataset.to_file(args.out_file) - print( + logger.info( f"Complete. New dataset consists of {merged_dataset.settings['num_samples']} " f"samples." ) @@ -151,7 +152,7 @@ def build_svd_cli(): basis, n_train, n_test = train_svd_basis(dataset, args.size, n_train) # FIXME: This is not an ideal treatment. We should update the waveform generation # to always provide the requested number of waveforms. - print( + logger.info( f"SVD basis trained based on {n_train} waveforms and validated on {n_test} " f"waveforms. Note that if this differs from number requested, it will not be " f"reflected in the settings file. This is likely due to EOB failure to " diff --git a/dingo/gw/download_strain_data.py b/dingo/gw/download_strain_data.py index 336954098..87aa55693 100644 --- a/dingo/gw/download_strain_data.py +++ b/dingo/gw/download_strain_data.py @@ -2,6 +2,7 @@ import pycbc.psd from gwpy.timeseries import TimeSeries +from dingo.core.utils.logging_utils import logger from dingo.gw.domains import UniformFrequencyDomain from dingo.gw.gwutils import ( get_window, @@ -107,14 +108,14 @@ def download_strain_data_in_FD(det, time_event, time_segment, time_buffer, windo array with the frequency domain strain """ # download strain data - print("Downloading strain data for event.", end=" ") + logger.info("Downloading strain data for event.") event_strain = TimeSeries.fetch_open_data( det, time_event + time_buffer - time_segment, time_event + time_buffer, cache=True, ) - print("Done.") + logger.info("Done.") # transform to FD if type(window) == dict: @@ -165,7 +166,7 @@ def download_event_data_in_FD( """ data = {"waveform": {}, "asds": {}} for det in detectors: - print("Detector {:}:".format(det)) + logger.info("Detector {:}:".format(det)) data["waveform"][det] = download_strain_data_in_FD( det, time_event, time_segment, time_buffer, window diff --git a/dingo/gw/importance_sampling/diagnostics.py b/dingo/gw/importance_sampling/diagnostics.py index 39cc95bb5..c178502f8 100644 --- a/dingo/gw/importance_sampling/diagnostics.py +++ b/dingo/gw/importance_sampling/diagnostics.py @@ -3,6 +3,7 @@ import math import pandas as pd import matplotlib.pyplot as plt +from dingo.core.utils.logging_utils import logger from dingo.core.utils.plotting import plot_corner_multi from dingo.gw.result import Result @@ -219,7 +220,7 @@ def plot_diagnostics( inds = np.where(weights > threshold)[0] theta_new = theta.loc[inds] weights_new = weights[inds] - print( + logger.info( f"Generating cornerplot with {len(theta_new)} out of {len(theta)} IS samples." ) diff --git a/dingo/gw/importance_sampling/importance_weights.py b/dingo/gw/importance_sampling/importance_weights.py index 7f88fa67e..e6c60a482 100644 --- a/dingo/gw/importance_sampling/importance_weights.py +++ b/dingo/gw/importance_sampling/importance_weights.py @@ -9,6 +9,7 @@ from os.path import dirname, join, isfile, exists import argparse +from dingo.core.utils.logging_utils import logger from dingo.core.posterior_models import NormalizingFlowPosteriorModel from dingo.gw.result import Result from dingo.gw.inference.gw_samplers import GWSampler @@ -100,20 +101,20 @@ def main(): "path", join(args.outdir, f"nde-{event_name}.pt") ) if isfile(nde_name): - print(f"Loading nde at {nde_name} for event {event_name}.") + logger.info(f"Loading nde at {nde_name} for event {event_name}.") nde = NormalizingFlowPosteriorModel( model_filename=nde_name, device=settings["nde"]["training"]["device"], load_training_info=False, ) else: - print(f"Training new nde for event {event_name}.") + logger.info(f"Training new nde for event {event_name}.") nde = result.train_unconditional_flow( inference_parameters, settings["nde"], train_dir=args.outdir, ) - print(f"Renaming trained nde model to {nde_name}.") + logger.info(f"Renaming trained nde model to {nde_name}.") rename(join(args.outdir, "model_latest.pt"), nde_name) # Step 1a: Sample from proposal. @@ -162,10 +163,10 @@ def main(): # print(np.std(log_evidences) / np.mean(log_evidences_std)) if synthetic_phase: - print(f"Sampling synthetic phase.") + logger.info("Sampling synthetic phase.") result.sample_synthetic_phase(synthetic_phase_kwargs) - print(f"Importance sampling.") + logger.info("Importance sampling.") result.importance_sample( num_processes=settings.get("num_processes", 1), time_marginalization_kwargs=time_marginalization_kwargs, @@ -179,7 +180,7 @@ def main(): diagnostics_dir = join(args.outdir, "IS-diagnostics") if not exists(diagnostics_dir): makedirs(diagnostics_dir) - print("Plotting diagnostics.") + logger.info("Plotting diagnostics.") plot_diagnostics( result, diagnostics_dir, diff --git a/dingo/gw/inference/gw_samplers.py b/dingo/gw/inference/gw_samplers.py index 2a1b63d89..c7d8f5585 100644 --- a/dingo/gw/inference/gw_samplers.py +++ b/dingo/gw/inference/gw_samplers.py @@ -8,6 +8,7 @@ from torchvision.transforms import Compose from dingo.core.samplers import Sampler, GNPESampler +from dingo.core.utils.logging_utils import logger from dingo.core.transforms import GetItem, RenameKey from dingo.gw.domains import ( MultibandedFrequencyDomain, @@ -240,7 +241,7 @@ def _post_process(self, samples: Union[dict, pd.DataFrame], inverse: bool = Fals for k, p in prior.items(): if isinstance(p, DeltaFunction) and k not in samples: v = p.peak - print(f"Adding fixed parameter {k} = {v} from prior.") + logger.info(f"Adding fixed parameter {k} = {v} from prior.") samples[k] = p.peak * np.ones(num_samples) else: # Drop non-inference parameters from samples. @@ -472,8 +473,8 @@ def _initialize_transforms(self): self.gnpe_parameters += transform.input_parameter_names for k, v in transform.kernel.items(): self.gnpe_kernel[k] = v - print("GNPE parameters: ", self.gnpe_parameters) - print("GNPE kernel: ", self.gnpe_kernel) + logger.info(f"GNPE parameters: {self.gnpe_parameters}") + logger.info(f"GNPE kernel: {self.gnpe_kernel}") self.transform_pre = Compose(transform_pre) diff --git a/dingo/gw/injection.py b/dingo/gw/injection.py index 87f7a0660..f1679fb47 100644 --- a/dingo/gw/injection.py +++ b/dingo/gw/injection.py @@ -2,6 +2,7 @@ from bilby.gw.detector import InterferometerList from torchvision.transforms import Compose +from dingo.core.utils.logging_utils import logger from dingo.gw.noise.asd_dataset import ASDDataset from dingo.gw.domains import ( UniformFrequencyDomain, @@ -312,7 +313,7 @@ def asd(self, asd): if set(asd.asds.keys()) != set(ifo_names): raise KeyError("ASDDataset ifos do not match signal.") if asd.domain.domain_dict != self.data_domain.domain_dict: - print("Updating ASDDataset domain to match data domain.") + logger.info("Updating ASDDataset domain to match data domain.") domain_dict = self.data_domain.domain_dict asd.update_domain(domain_dict) elif isinstance(asd, dict): @@ -402,7 +403,7 @@ def injection(self, theta): raise ValueError("self.asd must be set in order to produce injections.") if self.whiten: - print("self.whiten was set to True. Resetting to False.") + logger.warning("self.whiten was set to True. Resetting to False.") self.whiten = False data = {} diff --git a/dingo/gw/likelihood.py b/dingo/gw/likelihood.py index 49b29941f..f48893ad2 100644 --- a/dingo/gw/likelihood.py +++ b/dingo/gw/likelihood.py @@ -22,6 +22,7 @@ ) from dingo.gw.domains import build_domain from dingo.gw.data.data_preparation import get_event_data_and_domain +from dingo.core.utils.logging_utils import logger class StationaryGaussianGWLikelihood(GWSignal, Likelihood): @@ -173,7 +174,7 @@ def __init__( # use endpoint = False for grid, since phase = 0/2pi are equivalent self.phase_grid = np.linspace(0, 2 * np.pi, n_grid, endpoint=False) else: - print("Using phase marginalization with (2,2) mode approximation.") + logger.info("Using phase marginalization with (2,2) mode approximation.") # Initialize calibration marginalization using the setter from GWSignal. self.calibration_marginalization_kwargs = calibration_marginalization_kwargs @@ -870,15 +871,15 @@ def main(): try: l = likelihood.log_prob(theta) except: - print(idx) + logger.warning(idx) l = float("nan") log_likelihoods.append(l) log_likelihoods = np.array(log_likelihoods) log_likelihoods = log_likelihoods[~np.isnan(log_likelihoods)] - print(f"mean: {np.mean(log_likelihoods)}") - print(f"std: {np.std(log_likelihoods)}") - print(f"max: {np.max(log_likelihoods)}") - print(f"min: {np.min(log_likelihoods)}") + logger.info(f"mean: {np.mean(log_likelihoods)}") + logger.info(f"std: {np.std(log_likelihoods)}") + logger.info(f"max: {np.max(log_likelihoods)}") + logger.info(f"min: {np.min(log_likelihoods)}") if __name__ == "__main__": diff --git a/dingo/gw/noise/asd_dataset.py b/dingo/gw/noise/asd_dataset.py index 10e9cf9ef..9e3d1fb02 100644 --- a/dingo/gw/noise/asd_dataset.py +++ b/dingo/gw/noise/asd_dataset.py @@ -4,6 +4,7 @@ import numpy as np +from dingo.core.utils.logging_utils import logger from dingo.gw.domains import build_domain, UniformFrequencyDomain from dingo.gw.domains.base_frequency_domain import BaseFrequencyDomain from dingo.gw.gwutils import * @@ -59,8 +60,8 @@ def __init__( self.gps_times.pop(ifo) if "window_factor" in self.settings["domain_dict"]: - print( - "Warning: 'window_factor' is no longer used in ASDDataset. " + logger.warning( + "'window_factor' is no longer used in ASDDataset. " "Removing from settings." ) self.settings["domain_dict"].pop("window_factor") @@ -135,14 +136,14 @@ def update_domain(self, domain_update): self.domain.domain_dict["type"] == "UniformFrequencyDomain" and domain_update["type"] == "MultibandedFrequencyDomain" ): - print("Updating ASD dataset to MultibandedFrequencyDomain.") + logger.info("Updating ASD dataset to MultibandedFrequencyDomain.") asd_dataset_decimated = {} mfd = build_domain(domain_update) ufd = mfd.base_domain if not check_domain_compatibility(self.asds, ufd): # If the ASD length is not compatible with the new base UFD, # first truncate it. - print( + logger.info( f" Truncating first to new base UniformFrequencyDomain: f_max " f"{self.domain.f_max} Hz -> {ufd.f_max} Hz" ) diff --git a/dingo/gw/noise/generate_dataset.py b/dingo/gw/noise/generate_dataset.py index e8750e6e2..2a4c8b383 100644 --- a/dingo/gw/noise/generate_dataset.py +++ b/dingo/gw/noise/generate_dataset.py @@ -12,6 +12,7 @@ from dingo.gw.noise.asd_dataset import ASDDataset from dingo.gw.noise.generate_dataset_dag import create_dag from dingo.gw.noise.utils import merge_datasets, get_time_segments +from dingo.core.utils.logging_utils import logger def parse_args(): @@ -94,11 +95,11 @@ def generate_dataset(): pass dagman.build() - print(f"DAG submission file written.") + logger.info("DAG submission file written.") else: - print("Downloading strain data and estimating PSDs...") + logger.info("Downloading strain data and estimating PSDs...") asd_filename_list = download_and_estimate_psds( args.data_dir, settings, time_segments, verbose=args.verbose ) @@ -106,7 +107,7 @@ def generate_dataset(): det: [ASDDataset(asd_file) for asd_file in asd_file_list] for det, asd_file_list in asd_filename_list.items() } - print("Merging single dataset files into one...") + logger.info("Merging single dataset files into one...") dataset = merge_datasets(asd_dataset_list) filename = args.out_name if filename is None: diff --git a/dingo/gw/noise/synthetic/asd_sampling.py b/dingo/gw/noise/synthetic/asd_sampling.py index 707ca586d..e5bfef1a6 100644 --- a/dingo/gw/noise/synthetic/asd_sampling.py +++ b/dingo/gw/noise/synthetic/asd_sampling.py @@ -2,6 +2,7 @@ import numpy as np from scipy import stats +from dingo.core.utils.logging_utils import logger from dingo.gw.noise.synthetic.asd_parameterization import fit_broadband_noise from dingo.gw.noise.asd_dataset import ASDDataset from dingo.gw.noise.synthetic.utils import ( @@ -53,7 +54,7 @@ def fit(self, weights=None): weights=weights, ) except np.linalg.LinAlgError: - print( + logger.warning( "Warning: Singular Matrix encountered in spectral KDE. Adding small Gaussian noise..." ) perturbed_features = spectral_features[:, i, :] + np.random.normal( diff --git a/dingo/gw/noise/utils.py b/dingo/gw/noise/utils.py index 43d1f82ce..2d9fa4578 100644 --- a/dingo/gw/noise/utils.py +++ b/dingo/gw/noise/utils.py @@ -12,6 +12,7 @@ import yaml from gwpy.table import EventTable +from dingo.core.utils.logging_utils import logger from dingo.gw.noise.asd_dataset import ASDDataset """ @@ -156,7 +157,7 @@ def merge_datasets(asd_dataset_list): merged_dict = {"asds": {}, "gps_times": {}} for det, asd_list in asd_dataset_list.items(): - print(f"Merging {len(asd_list)} datasets into one for detector {det}.") + logger.info(f"Merging {len(asd_list)} datasets into one for detector {det}.") merged_dict["asds"][det] = np.vstack( [asd_dataset.asds[det] for asd_dataset in asd_list] ) diff --git a/dingo/gw/result.py b/dingo/gw/result.py index 51616032d..b0499ce28 100644 --- a/dingo/gw/result.py +++ b/dingo/gw/result.py @@ -14,6 +14,7 @@ ) from dingo.core.multiprocessing import apply_func_with_multiprocessing from dingo.core.result import Result as CoreResult +from dingo.core.utils.logging_utils import logger from dingo.gw.conversion import change_spin_conversion_phase from dingo.gw.domains import MultibandedFrequencyDomain from dingo.gw.domains import build_domain @@ -199,18 +200,14 @@ def _rebuild_domain(self, verbose=False): ) if verbose: - print("Rebuilding domain as follows:") - print( - yaml.dump( - domain_dict, - default_flow_style=False, - sort_keys=False, - ) + logger.info( + "Rebuilding domain as follows:\n" + + yaml.dump(domain_dict, default_flow_style=False, sort_keys=False) ) self.domain = build_domain(domain_dict) else: if verbose: - print("No domain updates found; domain not rebuilt.") + logger.info("No domain updates found; domain not rebuilt.") def _build_prior(self): """Build the prior based on model metadata. Called by __init__().""" @@ -359,7 +356,7 @@ def _build_likelihood( if "updates" in self.importance_sampling_metadata: if "T" in self.importance_sampling_metadata["updates"]: delta_f_new = 1 / self.importance_sampling_metadata["updates"]["T"] - print( + logger.info( f'Updating waveform generation delta_f from {wfg_domain_dict["delta_f"]} to {delta_f_new}.' ) wfg_domain_dict["delta_f"] = delta_f_new @@ -452,7 +449,7 @@ def sample_calibration_parameters(self, calibration_sampling_kwargs: dict): # Sample calibration parameters and calculate log_prob num_samples = len(self.samples) - print(f"Sampling calibration parameters for {num_samples} samples.") + logger.info(f"Sampling calibration parameters for {num_samples} samples.") delta_log_prob = np.zeros(num_samples) @@ -571,7 +568,7 @@ def sample_synthetic_phase( self.synthetic_phase_kwargs.get("num_processes", 1), num_valid_samples // 10 ) - print(f"Estimating synthetic phase for {num_valid_samples} samples.") + logger.info(f"Estimating synthetic phase for {num_valid_samples} samples.") t0 = time.time() if not inverse: @@ -660,7 +657,7 @@ def sample_synthetic_phase( self.samples["log_prob"] = log_prob_array del self.samples["phase"] - print(f"Done. This took {time.time() - t0:.2f} s.") + logger.info(f"Done. This took {time.time() - t0:.2f} s.") def get_samples_bilby_phase(self, num_processes=1): """ diff --git a/dingo/gw/training/train_builders.py b/dingo/gw/training/train_builders.py index ca62856bb..2642eff3d 100755 --- a/dingo/gw/training/train_builders.py +++ b/dingo/gw/training/train_builders.py @@ -6,6 +6,7 @@ from threadpoolctl import threadpool_limits from bilby.gw.detector import InterferometerList +from dingo.core.utils.logging_utils import logger from dingo.gw.SVD import SVDBasis from dingo.gw.dataset.waveform_dataset import WaveformDataset @@ -82,9 +83,9 @@ def set_train_transforms(wfd, data_settings, asd_dataset_path, omit_transforms=N List of sub-transforms to omit from the full composition. """ - print(f"Setting train transforms.") + logger.info("Setting train transforms.") if omit_transforms is not None: - print("Omitting \n\t" + "\n\t".join([t.__name__ for t in omit_transforms])) + logger.info("Omitting: " + ", ".join([t.__name__ for t in omit_transforms])) # By passing the wfd domain when instantiating the noise dataset, this ensures the # domains will match. In particular, it truncates the ASD dataset beyond the new @@ -142,9 +143,9 @@ def set_train_transforms(wfd, data_settings, asd_dataset_path, omit_transforms=N # parameters. try: standardization_dict = data_settings["standardization"] - print("Using previously-calculated parameter standardizations.") + logger.info("Using previously-calculated parameter standardizations.") except KeyError: - print("Calculating new parameter standardizations.") + logger.info("Calculating new parameter standardizations.") standardization_dict = get_standardization_dict( extrinsic_prior_dict, wfd, @@ -258,7 +259,7 @@ def build_svd_for_embedding_network( ], ) - print("Generating waveforms for embedding network SVD initialization.") + logger.info("Generating waveforms for embedding network SVD initialization.") time_start = time.time() ifos = list(wfd[0]["waveform"].keys()) waveform_len = len(wfd[0]["waveform"][ifos[0]]) @@ -296,25 +297,25 @@ def build_svd_for_embedding_network( waveforms[ifo][lower : lower + n] = strains[:n] if lower + n == num_waveforms: break - print(f"...done. This took {time.time() - time_start:.0f} s.") + logger.info(f"Done. This took {time.time() - time_start:.0f} s.") # Reset the standard sharing strategy. torch.multiprocessing.set_sharing_strategy(old_sharing_strategy) - print("Generating SVD basis for ifo:") + logger.info("Generating SVD basis for ifo:") time_start = time.time() basis_dict = {} for ifo in ifos: basis = SVDBasis() basis.generate_basis(waveforms[ifo][:num_training_samples], size) basis_dict[ifo] = basis - print(f"...{ifo} done.") - print(f"...this took {time.time() - time_start:.0f} s.") + logger.info(f"...{ifo} done.") + logger.info(f"Done. This took {time.time() - time_start:.0f} s.") if out_dir is not None: - print(f"Testing SVD basis matrices.") + logger.info("Testing SVD basis matrices.") for ifo, basis in basis_dict.items(): - print(f"...{ifo}:") + logger.info(f"...{ifo}:") basis.compute_test_mismatches( waveforms[ifo][num_training_samples:], parameters=parameters.iloc[num_training_samples:].reset_index( @@ -323,19 +324,17 @@ def build_svd_for_embedding_network( verbose=True, ) basis.to_file(os.path.join(out_dir, f"svd_{ifo}.hdf5")) - print("Done") + logger.info("Done.") # Return V matrices in standard order. Drop the elements below domain.min_idx, # since the neural network expects data truncated below these. The dropped elements # should be 0. - print(f"Truncating SVD matrices below index {wfd.domain.min_idx}.") - print("...V matrix shapes:") + logger.info(f"Truncating SVD matrices below index {wfd.domain.min_idx}.") V_rb_list = [] for ifo in data_settings["detectors"]: V = basis_dict[ifo].V assert np.allclose(V[: wfd.domain.min_idx], 0) V = V[wfd.domain.min_idx :] - print(" " + str(V.shape)) + logger.info(f" V matrix shape for {ifo}: {V.shape}") V_rb_list.append(V) - print("\n") return V_rb_list diff --git a/dingo/gw/training/train_pipeline.py b/dingo/gw/training/train_pipeline.py index ac1420b0f..544f4b1ac 100644 --- a/dingo/gw/training/train_pipeline.py +++ b/dingo/gw/training/train_pipeline.py @@ -27,6 +27,7 @@ build_train_and_test_loaders, ) from dingo.core.utils.trainutils import EarlyStopping +from dingo.core.utils.logging_utils import logger from dingo.gw.dataset import WaveformDataset from dingo.core.posterior_models import BasePosteriorModel @@ -61,20 +62,20 @@ def copy_files_to_local( if local_dir is not None: file_name = file_path.split("/")[-1] local_file_path = os.path.join(local_dir, file_name) - print(f"Copying file to {local_file_path}") + logger.info(f"Copying file to {local_file_path}") # Copy file start_time = time.time() shutil.copy(file_path, local_file_path) elapsed_time = time.time() - start_time - print("Done. This took {:2.0f}:{:2.0f} min.".format(*divmod(elapsed_time, 60))) + logger.info("Done. This took {:2.0f}:{:2.0f} min.".format(*divmod(elapsed_time, 60))) elif leave_keys_on_disk and is_condor: - print( - f"Warning: leave_waveforms_on_disk defaults to True, but local_cache_path is not specified. " - f"This means that the waveforms will be loaded during training from {local_file_path} ." + logger.warning( + f"leave_waveforms_on_disk defaults to True, but local_cache_path is not specified. " + f"This means that the waveforms will be loaded during training from {local_file_path}. " f"This can lead to unexpected long times for data loading during training due to network traffic. " f"To prevent this, specify 'local_cache_path = tmp' in the local settings or set " f"leave_waveforms_on_disk = False. However, the latter is not recommended for large datasets since " - f"it can lead to memory issues when loading the entire dataset into RAM. " + f"it can lead to memory issues when loading the entire dataset into RAM." ) return local_file_path @@ -122,7 +123,7 @@ def prepare_training_new( if train_settings["model"].get("embedding_kwargs", None): # First, build the SVD for seeding the embedding network. - print("\nBuilding SVD for initialization of embedding network.") + logger.info("Building SVD for initialization of embedding network.") initial_weights["V_rb_list"] = build_svd_for_embedding_network( wfd, train_settings["data"], @@ -153,9 +154,8 @@ def prepare_training_new( "train_settings": train_settings, } - print("\nInitializing new posterior model.") - print("Complete settings:") - print(yaml.dump(full_settings, default_flow_style=False, sort_keys=False)) + logger.info("Initializing new posterior model.") + logger.info("Complete settings:\n" + yaml.dump(full_settings, default_flow_style=False, sort_keys=False)) pm = build_model_from_kwargs( settings=full_settings, @@ -173,7 +173,7 @@ def prepare_training_new( **local_settings["wandb"], ) except ImportError: - print("WandB is enabled but not installed.") + logger.warning("WandB is enabled but not installed.") return pm, wfd @@ -226,7 +226,7 @@ def prepare_training_resume( **local_settings["wandb"], ) except ImportError: - print("WandB is enabled but not installed.") + logger.warning("WandB is enabled but not installed.") return pm, wfd @@ -278,7 +278,7 @@ def initialize_stage( if not resume: # New optimizer and scheduler. If we are resuming, these should have been # loaded from the checkpoint. - print("Initializing new optimizer and scheduler.") + logger.info("Initializing new optimizer and scheduler.") pm.optimizer_kwargs = stage["optimizer"] pm.scheduler_kwargs = stage["scheduler"] pm.initialize_optimizer_and_scheduler() @@ -295,7 +295,7 @@ def initialize_stage( ) n_grad = get_number_of_model_parameters(pm.network, (True,)) n_nograd = get_number_of_model_parameters(pm.network, (False,)) - print(f"Fixed parameters: {n_nograd}\nLearnable parameters: {n_grad}\n") + logger.info(f"Fixed parameters: {n_nograd}\nLearnable parameters: {n_grad}") return train_loader, test_loader @@ -343,14 +343,12 @@ def train_stages( stage = stages[n] if pm.epoch == end_epochs[n] - stage["epochs"]: - print(f"\nBeginning training stage {n}. Settings:") - print(yaml.dump(stage, default_flow_style=False, sort_keys=False)) + logger.info(f"Beginning training stage {n}. Settings:\n" + yaml.dump(stage, default_flow_style=False, sort_keys=False)) train_loader, test_loader = initialize_stage( pm, wfd, stage, local_settings["num_workers"], resume=False ) else: - print(f"\nResuming training in stage {n}. Settings:") - print(yaml.dump(stage, default_flow_style=False, sort_keys=False)) + logger.info(f"Resuming training in stage {n}. Settings:\n" + yaml.dump(stage, default_flow_style=False, sort_keys=False)) train_loader, test_loader = initialize_stage( pm, wfd, stage, local_settings["num_workers"], resume=True ) @@ -359,7 +357,7 @@ def train_stages( try: early_stopping = EarlyStopping(**stage["early_stopping"]) except Exception: - print( + logger.warning( "Early stopping settings invalid. Please pass 'patience', 'delta', 'metric'" ) raise @@ -381,10 +379,10 @@ def train_stages( if pm.epoch == end_epochs[n]: save_file = os.path.join(train_dir, f"model_stage_{n}.pt") - print(f"Training stage complete. Saving to {save_file}.") + logger.info(f"Training stage complete. Saving to {save_file}.") pm.save_model(save_file, save_training_info=True) if runtime_limits.local_limits_exceeded(pm.epoch): - print("Local runtime limits reached. Ending program.") + logger.info("Local runtime limits reached. Ending program.") break if pm.epoch == end_epochs[-1]: @@ -443,7 +441,7 @@ def train_local(): os.makedirs(args.train_dir, exist_ok=True) if args.settings_file is not None: - print("Beginning new training run.") + logger.info("Beginning new training run.") with open(args.settings_file, "r") as fp: train_settings = yaml.safe_load(fp) @@ -462,7 +460,7 @@ def train_local(): local_settings["wandb"]["id"] = wandb.util.generate_id() except ImportError: - print("wandb not installed, cannot generate run id.") + logger.warning("wandb not installed, cannot generate run id.") yaml.dump(local_settings, f, default_flow_style=False, sort_keys=False) pm, wfd = prepare_training_new(train_settings, args.train_dir, local_settings) @@ -470,7 +468,7 @@ def train_local(): else: if not os.path.isfile(args.checkpoint): raise FileNotFoundError(f"Checkpoint not found: {args.checkpoint}") - print("Resuming training run.") + logger.info("Resuming training run.") with open(os.path.join(args.train_dir, "local_settings.yaml"), "r") as f: local_settings = yaml.safe_load(f) pm, wfd = prepare_training_resume( @@ -482,11 +480,11 @@ def train_local(): if complete: if args.exit_command: - print( + logger.info( f"All training stages complete. Executing exit command: {args.exit_command}." ) os.system(args.exit_command) else: - print("All training stages complete.") + logger.info("All training stages complete.") else: - print("Program terminated due to runtime limit.") + logger.info("Program terminated due to runtime limit.") diff --git a/dingo/gw/training/train_pipeline_condor.py b/dingo/gw/training/train_pipeline_condor.py index 418909d3e..95f118add 100644 --- a/dingo/gw/training/train_pipeline_condor.py +++ b/dingo/gw/training/train_pipeline_condor.py @@ -4,6 +4,7 @@ import yaml import argparse +from dingo.core.utils.logging_utils import logger from dingo.gw.training import ( prepare_training_new, prepare_training_resume, @@ -61,7 +62,7 @@ def copy_logfiles(log_dir, epoch, name="info", suffixes=(".err", ".log", ".out") try: copyfile(src, dest) except: - print("Could not copy " + src) + logger.warning("Could not copy " + src) def train_condor(): @@ -90,7 +91,7 @@ def train_condor(): raise FileNotFoundError( f"Checkpoint not found: {join(args.train_dir, args.checkpoint)}" ) - print("Beginning new training run.") + logger.info("Beginning new training run.") with open(join(args.train_dir, "train_settings.yaml"), "r") as f: train_settings = yaml.safe_load(f) @@ -109,7 +110,7 @@ def train_condor(): local_settings["wandb"]["id"] = wandb.util.generate_id() except ImportError: - print("wandb not installed, cannot generate run id.") + logger.warning("wandb not installed, cannot generate run id.") yaml.dump(local_settings, f, default_flow_style=False, sort_keys=False) pm, wfd = prepare_training_new( @@ -117,7 +118,7 @@ def train_condor(): ) else: - print("Resuming training run.") + logger.info("Resuming training run.") with open(os.path.join(args.train_dir, "local_settings.yaml"), "r") as f: local_settings = yaml.safe_load(f) pm, wfd = prepare_training_resume( @@ -128,7 +129,7 @@ def train_condor(): complete = train_stages(pm, wfd, args.train_dir, local_settings) - print("Copying log files") + logger.info("Copying log files") copy_logfiles(args.train_dir, epoch=pm.epoch) # @@ -136,7 +137,7 @@ def train_condor(): # if complete: - print( + logger.info( f"Training complete, job will not be resubmitted. Executing exit command: {args.exit_command}." ) if args.exit_command: diff --git a/dingo/gw/training/utils.py b/dingo/gw/training/utils.py index 4cd0732d8..2aeedc964 100644 --- a/dingo/gw/training/utils.py +++ b/dingo/gw/training/utils.py @@ -5,6 +5,7 @@ import yaml from dingo.core.utils.backward_compatibility import torch_load_with_fallback +from dingo.core.utils.logging_utils import logger def append_stage(): @@ -25,7 +26,7 @@ def append_stage(): if k.startswith("stage_") ] num_stages = len(stages) - print(f"Checkpoint training plan consists of {num_stages} stages.") + logger.info(f"Checkpoint training plan consists of {num_stages} stages.") with open(args.stage_settings_file, "r") as f: new_stage = yaml.safe_load(f) @@ -39,21 +40,20 @@ def append_stage(): current_epoch = d["epoch"] stage_epoch = np.sum([s["epochs"] for s in stages[: args.replace]]) if current_epoch > stage_epoch: - print( - f"WARNING: Modification to training plan changes a training stage " + logger.warning( + f"Modification to training plan changes a training stage " f"that has already started. Current model epoch is {current_epoch}. " f"Proceed at your own risk!" ) - print(f"Replacing planned stage {args.replace} with new stage.") + logger.info(f"Replacing planned stage {args.replace} with new stage.") new_stage_number = args.replace else: - print(f"Appending new stage to training plan.") + logger.info("Appending new stage to training plan.") new_stage_number = num_stages d["metadata"]["train_settings"]["training"][f"stage_{new_stage_number}"] = new_stage - print("Summary of new training plan:") - print( - yaml.dump( + logger.info( + "Summary of new training plan:\n" + yaml.dump( d["metadata"]["train_settings"]["training"], default_flow_style=False, sort_keys=False, diff --git a/dingo/gw/transforms/waveform_transforms.py b/dingo/gw/transforms/waveform_transforms.py index f6d022435..39f884836 100644 --- a/dingo/gw/transforms/waveform_transforms.py +++ b/dingo/gw/transforms/waveform_transforms.py @@ -1,6 +1,7 @@ from typing import Optional import numpy as np +from dingo.core.utils.logging_utils import logger from dingo.gw.domains import MultibandedFrequencyDomain, UniformFrequencyDomain @@ -443,7 +444,7 @@ def __init__( self.maximum_frequency = maximum_frequency if print_output: - print( + logger.info( f"Transform MaskDataForFrequencyRangeUpdate activated:" f" Settings: \n" f" - Minimum_frequency update: {self.minimum_frequency}\n" diff --git a/dingo/gw/waveform_generator/waveform_generator.py b/dingo/gw/waveform_generator/waveform_generator.py index 43f6a6488..a7a341ee4 100644 --- a/dingo/gw/waveform_generator/waveform_generator.py +++ b/dingo/gw/waveform_generator/waveform_generator.py @@ -30,6 +30,7 @@ TimeDomain, ) from dingo.gw.transforms.waveform_transforms import DecimateAll +from dingo.core.utils.logging_utils import logger class WaveformGenerator: @@ -142,12 +143,12 @@ def spin_conversion_phase(self): @spin_conversion_phase.setter def spin_conversion_phase(self, value): if value is None: - print( + logger.info( "Setting spin_conversion_phase = None. Using phase parameter for " "conversion to cartesian spins." ) else: - print( + logger.info( f"Setting spin_conversion_phase = {value}. Using this value for the " f"phase parameter for conversion to cartesian spins." ) @@ -583,7 +584,7 @@ def generate_FD_waveform( # numbers if multibanding is used. If that happens, turn off multibanding to # fix this. if max(np.max(np.abs(hp.data.data)), np.max(np.abs(hc.data.data))) > 1e-20: - print( + logger.warning( f"Generation with parameters {parameters_lal} likely numerically " f"unstable due to multibanding, turn off multibanding." ) @@ -606,7 +607,7 @@ def generate_FD_waveform( *parameters_lal[lal_dict_idx + 1 :], ) if max(np.max(np.abs(hp.data.data)), np.max(np.abs(hc.data.data))) > 1e-20: - print( + logger.warning( f"Warning: turning off multibanding for parameters {parameters_lal}" f" likely numerically might not have fixed it, check manually." ) diff --git a/dingo/pipe/dag_creator.py b/dingo/pipe/dag_creator.py index 077864265..1223a553b 100644 --- a/dingo/pipe/dag_creator.py +++ b/dingo/pipe/dag_creator.py @@ -5,7 +5,9 @@ import copy from bilby_pipe.job_creation.dag import Dag -from bilby_pipe.utils import BilbyPipeError, logger +import logging + +from bilby_pipe.utils import BilbyPipeError from dingo.pipe.utils import _strip_unwanted_submission_keys from dingo.pipe.nodes.generation_node import GenerationNode @@ -15,7 +17,7 @@ from .nodes.plot_node import PlotNode, PlotPPNode from .nodes.sampling_node import SamplingNode -logger.name = "dingo_pipe" +logger = logging.getLogger("dingo.pipe") def get_trigger_time_list(inputs): diff --git a/dingo/pipe/dingo_result.py b/dingo/pipe/dingo_result.py index 4b0096e13..b06c1dfdd 100644 --- a/dingo/pipe/dingo_result.py +++ b/dingo/pipe/dingo_result.py @@ -1,8 +1,11 @@ import argparse +import logging from pathlib import Path from dingo.gw.result import Result +logger = logging.getLogger("dingo.pipe") + def main(): parser = argparse.ArgumentParser() @@ -15,7 +18,7 @@ def main(): args = parser.parse_args() if args.merge: - print(f"Merging {len(args.result)} parts into complete Result.") + logger.info(f"Merging {len(args.result)} parts into complete Result.") sub_results = [] for file_name in args.result: sub_results.append(Result(file_name=file_name)) diff --git a/dingo/pipe/nodes/generation_node.py b/dingo/pipe/nodes/generation_node.py index edbd4d9b2..6f7cfcc57 100644 --- a/dingo/pipe/nodes/generation_node.py +++ b/dingo/pipe/nodes/generation_node.py @@ -1,3 +1,4 @@ +import logging import os from bilby_pipe.job_creation.nodes import GenerationNode as BilbyGenerationNode @@ -5,6 +6,8 @@ from dingo.pipe.utils import _strip_unwanted_submission_keys +logger = logging.getLogger("dingo.pipe") + class GenerationNode(BilbyGenerationNode): @@ -101,7 +104,6 @@ def __init__(self, inputs, trigger_time, idx, dag, parent=None, importance_sampl for fname in self.extract_paths_from_dict(frame_files) if fname.startswith(self.inputs.data_find_urltype) ]: - from bilby_pipe.utils import logger logger.warning( "The following frame files were identified by gwdatafind for this analysis. " "These frames may not be found by the data generation stage as file " diff --git a/dingo/pipe/nodes/pe_summary_node.py b/dingo/pipe/nodes/pe_summary_node.py index 6762ece07..876e7a0d1 100644 --- a/dingo/pipe/nodes/pe_summary_node.py +++ b/dingo/pipe/nodes/pe_summary_node.py @@ -1,8 +1,10 @@ import os from bilby_pipe.job_creation.nodes import PESummaryNode as BilbyPESummaryNode -from bilby_pipe.utils import BilbyPipeError, logger +import logging -logger.name = "dingo_pipe" +from bilby_pipe.utils import BilbyPipeError + +logger = logging.getLogger("dingo.pipe") class PESummaryNode(BilbyPESummaryNode): diff --git a/dingo/pipe/plot.py b/dingo/pipe/plot.py index 7dc8af756..dbd69de0c 100644 --- a/dingo/pipe/plot.py +++ b/dingo/pipe/plot.py @@ -1,9 +1,11 @@ from bilby_pipe.bilbyargparser import BilbyArgParser -from bilby_pipe.utils import get_command_line_arguments, logger, parse_args +from bilby_pipe.utils import get_command_line_arguments, parse_args + +import logging from dingo.gw.result import Result -logger.name = "dingo_pipe" +logger = logging.getLogger("dingo.pipe") def create_parser(): diff --git a/dingo/pipe/pp_test.py b/dingo/pipe/pp_test.py index 0716b24e6..bbc85502d 100644 --- a/dingo/pipe/pp_test.py +++ b/dingo/pipe/pp_test.py @@ -1,12 +1,12 @@ import argparse from pathlib import Path -from bilby_pipe.utils import logger +import logging from dingo.core.result import make_pp_plot from dingo.gw.result import Result -logger.name = "dingo_pipe" +logger = logging.getLogger("dingo.pipe") def create_parser(): From f78608fda3d11b33433dd2dfb87b91c7d1d9ab5d Mon Sep 17 00:00:00 2001 From: cmfabbri Date: Wed, 1 Jul 2026 09:16:50 +0200 Subject: [PATCH 06/15] WIP: Integrate bilby and dingo loggers to share log file (issue #152) Co-Authored-By: Claude Sonnet 4.6 --- dingo/core/utils/logging_utils.py | 70 ++++++++++++++++++------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/dingo/core/utils/logging_utils.py b/dingo/core/utils/logging_utils.py index e626fce0b..0de9d14e8 100644 --- a/dingo/core/utils/logging_utils.py +++ b/dingo/core/utils/logging_utils.py @@ -2,12 +2,6 @@ import os import sys -""" -Utility functions -""" - -# This is not currently used. We should improve our logging throughout. - def check_directory_exists_and_if_not_mkdir(directory, logger): """Checks if the given directory exists and creates it if it does not exist @@ -16,8 +10,6 @@ def check_directory_exists_and_if_not_mkdir(directory, logger): ---------- directory: str Name of the directory - - Borrowed from bilby-pipe: https://git.ligo.org/lscsoft/bilby_pipe/ """ if not os.path.exists(directory): os.makedirs(directory) @@ -27,7 +19,11 @@ def check_directory_exists_and_if_not_mkdir(directory, logger): def setup_logger(outdir=None, label=None, log_level="INFO"): - """Setup logging output: call at the start of the script to use + """Setup logging output: call at the start of the script to use. + + Sets up the dingo logger and, if bilby is available, also calls bilby's + setup_logger so that both loggers share the same file handler and their + entries appear together in the same log file. Parameters ---------- @@ -37,10 +33,7 @@ def setup_logger(outdir=None, label=None, log_level="INFO"): ['debug', 'info', 'warning'] Either a string from the list above, or an integer as specified in https://docs.python.org/2/library/logging.html#logging-levels - - Borrowed from bilby-pipe: https://git.ligo.org/lscsoft/bilby_pipe/ """ - if "-v" in sys.argv or "--verbose" in sys.argv: log_level = "DEBUG" @@ -52,12 +45,25 @@ def setup_logger(outdir=None, label=None, log_level="INFO"): else: level = int(log_level) + # Set up bilby's logger first so we can share its file handler. + bilby_logger = None + try: + from bilby.core.utils.log import setup_logger as bilby_setup_logger + + bilby_setup_logger(outdir=outdir or ".", label=label, log_level=log_level) + bilby_logger = logging.getLogger("bilby") + except ImportError: + pass + logger = logging.getLogger("dingo") logger.propagate = False logger.setLevel(level) - streams = [isinstance(h, logging.StreamHandler) for h in logger.handlers] - if len(streams) == 0 or not all(streams): + # Add a stream handler if not already present. + if not any( + isinstance(h, logging.StreamHandler) and not isinstance(h, logging.FileHandler) + for h in logger.handlers + ): stream_handler = logging.StreamHandler() stream_handler.setFormatter( logging.Formatter( @@ -67,22 +73,28 @@ def setup_logger(outdir=None, label=None, log_level="INFO"): stream_handler.setLevel(level) logger.addHandler(stream_handler) - if any([isinstance(h, logging.FileHandler) for h in logger.handlers]) is False: - if label: - if outdir: - check_directory_exists_and_if_not_mkdir(outdir, logger) - else: - outdir = "." - log_file = f"{outdir}/{label}.log" - file_handler = logging.FileHandler(log_file) - file_handler.setFormatter( - logging.Formatter( - "%(asctime)s %(levelname)-8s: %(message)s", datefmt="%H:%M" - ) + # Share bilby's file handler so both loggers write to the same log file. + if bilby_logger is not None: + for handler in bilby_logger.handlers: + if isinstance(handler, logging.FileHandler): + if not any(isinstance(h, logging.FileHandler) for h in logger.handlers): + logger.addHandler(handler) + + # Fall back to a standalone file handler if bilby is unavailable. + if label and not any(isinstance(h, logging.FileHandler) for h in logger.handlers): + if outdir: + check_directory_exists_and_if_not_mkdir(outdir, logger) + else: + outdir = "." + log_file = f"{outdir}/{label}.log" + file_handler = logging.FileHandler(log_file) + file_handler.setFormatter( + logging.Formatter( + "%(asctime)s %(levelname)-8s: %(message)s", datefmt="%H:%M" ) - - file_handler.setLevel(level) - logger.addHandler(file_handler) + ) + file_handler.setLevel(level) + logger.addHandler(file_handler) for handler in logger.handlers: handler.setLevel(level) From b02968ebd81b28d3aba95ed3f4ba1e37a8864b4b Mon Sep 17 00:00:00 2001 From: Heinrich Campe <49278231+hcampe@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:38:17 +0200 Subject: [PATCH 07/15] generate dataset w hydra --- .gitignore | 3 + HYDRA_TODO.md | 426 ++++++++++++++++++++++ configs/examples/generate_dataset.yaml | 42 +++ dingo/gw/dataset/generate_dataset.py | 38 +- pyproject.toml | 1 + uv.lock | 483 +++++++++++++------------ 6 files changed, 757 insertions(+), 236 deletions(-) create mode 100644 HYDRA_TODO.md create mode 100644 configs/examples/generate_dataset.yaml diff --git a/.gitignore b/.gitignore index a6c7293c0..5329a843c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # codex stuff AGENTS*.md +# run dir +my_runs/ + # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/HYDRA_TODO.md b/HYDRA_TODO.md new file mode 100644 index 000000000..ecc322c7a --- /dev/null +++ b/HYDRA_TODO.md @@ -0,0 +1,426 @@ +# Hydra Migration TODO + +This document tracks a staged migration of Dingo from ad hoc YAML files, +`argparse` / `configargparse`, and hand-written config builders toward Hydra. + +## Migration Philosophy + +Do this in stages rather than trying to refactor the whole configuration model in +one pass. + +1. **Stage 1: Hydra wrappers with old internal dictionaries** + - Replace public CLI parsers with `@hydra.main(...)` entrypoints. + - Replace old example/settings YAML files with Hydra configs that are mostly + one-to-one copies of the old settings. + - Keep these Stage 1 configs together in one examples-like folder under + `configs/`, mirroring the current `examples/` organization as much as + possible. + - Convert `DictConfig` to plain dictionaries internally with + `OmegaConf.to_container(cfg, resolve=True)`. + - Let the existing implementation consume those dictionaries as before. + - Preserve settings that are saved into datasets, models, and results. + - Replace command-line `print(...)` status output with module loggers: + `log = logging.getLogger(__name__)` and `log.info(...)`. + - Remove the old argparse/configargparse command-line arguments for migrated + commands, as was done for `dingo_generate_dataset`. + +2. **Stage 2: Organize configs with Hydra groups and defaults** + - Split the Stage 1 examples-like config folder into reusable groups. + - Deduplicate examples and common settings. + - Keep code mostly dictionary-based. + +3. **Stage 3: Refactor internals to use Hydra-native construction** + - Replace builder functions and string switches with `_target_` and + `hydra.utils.instantiate(...)`. + - Make transform/model/domain/prior construction more composable. + - Move defaults that are currently hard-coded in Python into default Hydra + configs/config groups. + - Make configs complete: options that are currently omitted from example + YAMLs because code supplies implicit defaults should become explicit + defaults in Hydra configs. + - Revisit saved metadata format and backward compatibility deliberately. + +## Stage 1 Definition Of Done + +For each migrated CLI: + +- [ ] Public entrypoint is wrapped by `@hydra.main(...)`. +- [ ] Old `argparse`, `configargparse`, or `BilbyArgParser` parsing is removed + from that entrypoint. +- [ ] Config lives in one examples-like Stage 1 folder under `configs/`. +- [ ] Existing settings YAMLs/examples have equivalent Hydra configs. +- [ ] `.ini` files are explicitly out of scope for this migration pass. +- [ ] The Hydra config is converted to a plain dict before entering most + existing business logic. +- [ ] Settings saved into HDF5/PT/result metadata remain loadable. +- [ ] User-facing progress messages use `logging` instead of `print`. +- [ ] A small smoke test exists or is documented. + +Recommended shared pattern: + +```python +import logging + +import hydra +from omegaconf import DictConfig, OmegaConf + +log = logging.getLogger(__name__) + + +@hydra.main(version_base="1.3", config_path="...", config_name="...") +def main(cfg: DictConfig) -> None: + settings = OmegaConf.to_container(cfg, resolve=True) + ... +``` + +Recommended default Hydra run-directory policy: + +```yaml +hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} + job: + name: + chdir: true +``` + +Use `hydra.job.chdir: true` in Stage 1 so all relative outputs from a run land +in the Hydra run directory together with `generate_dataset.log` and `.hydra/` +metadata. The run root is controlled by the required `DINGO_RUNS` environment +variable. + +If a user explicitly wants old current-working-directory behavior, use both: + +```bash +hydra.job.chdir=false hydra.run.dir=. +``` + +Testing showed that `hydra.job.chdir=false` alone is not enough: application +outputs land in the current working directory, but Hydra logs and `.hydra/` +metadata still go to `hydra.run.dir`. + +Use `compression: null` rather than `compression: None`. + +Do not migrate `.ini` files for now. In particular, leave Asimov/plugin-style +`.ini` configuration outside the Hydra migration TODO unless this is reopened +explicitly later. + +## Entrypoint Inventory + +Public console scripts are defined in `pyproject.toml`. + +### Dataset Generation + +- [x] `dingo_generate_dataset` + - File: `dingo/gw/dataset/generate_dataset.py` + - Stage 1 has started. + - Uses Hydra and `instantiate(...)` for some objects already, so it is partly + beyond a purely superficial Stage 1 pass. + - Remaining concern: compression/SVD logic is still procedural and should be + revisited in Stage 3. + +- [ ] `dingo_generate_dataset_dag` + - File: `dingo/gw/dataset/generate_dataset_dag.py` + - Defer Condor/DAG integration to Stage 3. + - Stage 3 goal: submit Condor jobs through a Hydra-aware launcher/workflow + rather than hand-written temporary YAML chunks and manual submit files. + - This may be analogous in spirit to existing Hydra Slurm launcher workflows, + but Condor will likely need project-specific integration. + - Estimated Stage 1 difficulty: out of scope. + - Estimated Stage 3 difficulty: high. + +- [ ] `dingo_evaluate_multibanded_domain` + - File: `dingo/gw/dataset/evaluate_multibanded_domain.py` + - Stage 1: straightforward wrapper around current settings dict. + - Stage 3: replace `build_domain`, `build_prior_with_defaults`, and manual + waveform-generator branching with Hydra targets. + - Estimated Stage 1 difficulty: low. + - Estimated Stage 3 difficulty: medium. + +- [ ] `dingo_build_svd` + - File: `dingo/gw/dataset/utils.py` + - Stage 1: small CLI migration. + - Stage 3: likely becomes part of a Hydra-instantiated compression/SVD + workflow. + - Estimated Stage 1 difficulty: low. + - Estimated Stage 3 difficulty: medium. + +- [ ] `dingo_merge_datasets` + - File: `dingo/gw/dataset/utils.py` + - Stage 1: small CLI migration. + - Stage 3: probably little to do. + - Estimated Stage 1 difficulty: low. + - Estimated Stage 3 difficulty: low. + +### Noise / ASD Generation + +- [ ] `dingo_generate_asd_dataset` + - File: `dingo/gw/noise/generate_dataset.py` + - Stage 1: replace parser/settings-file handling with Hydra. + - Stage 1 should cover local generation only. + - Condor/DAG behavior should remain as-is or be deferred. + - Estimated Stage 1 difficulty: medium. + - Estimated Stage 3 difficulty: medium/high for Condor integration. + +- [ ] `dingo_estimate_psds` + - File: `dingo/gw/noise/asd_estimation.py` + - Stage 1: straightforward wrapper around current dict settings. + - Stage 3: domain construction and PSD-estimation settings could become more + declarative. + - Estimated Stage 1 difficulty: low/medium. + - Estimated Stage 3 difficulty: medium. + +- [ ] `dingo_generate_synthetic_asd_dataset` + - File: `dingo/gw/noise/synthetic/generate_dataset.py` + - Stage 1: straightforward config migration. + - Stage 3: parameterization and sampling could become separate config groups. + - Estimated Stage 1 difficulty: low. + - Estimated Stage 3 difficulty: medium. + +- [ ] `dingo_merge_asd_datasets` + - File: `dingo/gw/noise/utils.py` + - Stage 1: small CLI migration. + - Estimated Stage 1 difficulty: low. + - Estimated Stage 3 difficulty: low. + +### Training + +- [ ] `dingo_train` + - File: `dingo/gw/training/train_pipeline.py` + - Stage 1: replace parser with Hydra, while still converting to the old + `train_settings` and `local_settings` dicts. + - Watch out: + - New training versus checkpoint resume are currently mutually exclusive CLI + modes. + - `local` settings are popped out and saved separately as + `local_settings.yaml`. + - Train settings are saved into model metadata. + - Several progress messages still use `print`. + - Estimated Stage 1 difficulty: medium/high. + - Estimated Stage 3 difficulty: high. + +- [ ] `dingo_train_condor` + - File: `dingo/gw/training/train_pipeline_condor.py` + - Defer to Stage 3 with the rest of Condor integration. + - Goal: a Hydra-aware Condor submission path, ideally with a clean + single-command link from config composition to job submission. + - Watch out: + - Reads/writes `local_settings.yaml`. + - Creates Condor submit files and mutates local settings. + - Estimated Stage 1 difficulty: out of scope. + - Estimated Stage 3 difficulty: high. + +- [ ] `dingo_append_training_stage` + - File: `dingo/gw/training/utils.py` + - Stage 1: small Hydra CLI. + - Watch out: modifies model metadata/training-stage settings. + - Estimated Stage 1 difficulty: low. + - Estimated Stage 3 difficulty: low/medium. + +### Pipe / Inference + +The `dingo_pipe*` family is the largest migration area because it is based on +`BilbyArgParser` / `configargparse` and has bilby_pipe-style conventions. + +- [ ] `dingo_pipe` + - File: `dingo/pipe/main.py` + - Uses parser from `dingo/pipe/parser.py`. + - Estimated Stage 1 difficulty: high. + - Estimated Stage 3 difficulty: very high. + +- [ ] `dingo_pipe_generation` + - File: `dingo/pipe/data_generation.py` + - Estimated Stage 1 difficulty: high. + - Estimated Stage 3 difficulty: high. + +- [ ] `dingo_pipe_sampling` + - File: `dingo/pipe/sampling.py` + - Estimated Stage 1 difficulty: high. + - Estimated Stage 3 difficulty: high. + +- [ ] `dingo_pipe_importance_sampling` + - File: `dingo/pipe/importance_sampling.py` + - Estimated Stage 1 difficulty: high. + - Estimated Stage 3 difficulty: high. + +- [ ] `dingo_pipe_plot` + - File: `dingo/pipe/plot.py` + - Estimated Stage 1 difficulty: medium/high. + - Estimated Stage 3 difficulty: medium. + +- [ ] `dingo_pipe_pp_test` + - File: `dingo/pipe/pp_test.py` + - Estimated Stage 1 difficulty: low/medium. + - Estimated Stage 3 difficulty: medium. + +- [ ] `dingo_result` + - File: `dingo/pipe/dingo_result.py` + - Small CLI, but connected to pipe output conventions. + - Estimated Stage 1 difficulty: low. + - Estimated Stage 3 difficulty: low. + +### Other Utilities + +- [ ] `dingo_pt_to_hdf5` + - File: `dingo/core/utils/pt_to_hdf5.py` + - Small argparse CLI. + - Estimated Stage 1 difficulty: low. + - Estimated Stage 3 difficulty: low. + +- [ ] `dingo_ls` + - File: `dingo/gw/ls_cli.py` + - Inspection tool rather than a config-driven workflow. + - Could stay argparse for a while, but migrate for consistency if Stage 1 aims + to eliminate all public parsers. + - Estimated Stage 1 difficulty: low. + - Estimated Stage 3 difficulty: low. + +- [ ] Importance-weight generation + - File: `dingo/gw/importance_sampling/importance_weights.py` + - Not currently listed in `project.scripts`, but it is a config-driven CLI. + - Estimated Stage 1 difficulty: low/medium. + - Estimated Stage 3 difficulty: medium. + +- [ ] Unconditional density estimation + - File: `dingo/core/density/unconditional_density_estimation.py` + - Not currently listed in `project.scripts`, but has an argparse settings CLI. + - Estimated Stage 1 difficulty: low. + - Estimated Stage 3 difficulty: medium. + +## Stage 2 Config Group Candidates + +- `domain` +- `waveform_generator` +- `intrinsic_prior` +- `extrinsic_prior` +- `inference_parameters` +- `dataset` +- `asd_dataset` +- `noise` +- `model/posterior` +- `model/embedding` +- `training/stage` +- `training/local` +- `condor` (Stage 3 only) +- `pipe/event` +- `pipe/sampler` +- `importance_sampling` +- `defaults` +- `defaults/prior` +- `defaults/recovery` +- `defaults/nde` + +## Stage 3 Refactor Hotspots + +- [ ] Move Python-defined defaults into Hydra default configs + - Goal: make default choices visible, overridable, and composable from the + config tree rather than hidden in module globals or helper functions. + - Related goal: make configs complete. If an option affects behavior, it + should appear in a default config somewhere, even if most examples inherit + it unchanged. + - Likely config groups: + - `defaults/prior/intrinsic` + - `defaults/prior/extrinsic` + - `defaults/inference_parameters` + - `defaults/density_recovery` + - `defaults/importance_sampling` + - `defaults/nde` + - `defaults/training` + - Keep a deliberate backward-compatibility story for old saved metadata that + uses strings such as `default`. + +- [ ] Audit implicit constructor/function defaults and expose them in config + - Example configs currently omit many options because Python functions and + constructors provide defaults. + - Stage 3 should decide which defaults are part of the public configuration + surface and place them in default configs. + - This is different from Stage 1, where copied configs may remain incomplete + as long as old code handles them. + - Candidate areas: + - waveform generator optional kwargs such as `f_start`, `mode_list`, + `spin_conversion_phase`, and interface-specific options + - dataset compression/SVD/whitening options + - training local/runtime/checkpoint options + - optimizer and scheduler defaults + - transform defaults in the training data pipeline + - pipe/inference defaults currently supplied by parser defaults + - ASD/noise generation defaults such as frequency range, channels, and + Condor settings + +- [ ] `dingo/gw/domains/build_domain.py` + - Replace `type`-based dispatch with Hydra instantiation. + - Keep metadata/backward compatibility in mind. + +- [ ] `dingo/gw/prior.py` + - Replace `default` string handling and prior builders with explicit config + targets or reusable config groups. + - Current code-defined defaults: + - `default_intrinsic_dict` + - `default_extrinsic_dict` + - `default_inference_parameters` + +- [ ] Waveform generator construction + - Replace `new_interface` flags and manual class branching with `_target_`. + +- [ ] Compression/SVD workflow + - Harder than plain instantiation because SVD may be loaded from file or + trained from generated waveforms. + - Possible long-term design: compression is an ordered transform list, with + SVD handled by a project-specific factory target. + +- [ ] `dingo/gw/training/train_builders.py` + - Large procedural transform pipeline. + - Prime candidate for Hydra targets, but many transforms need runtime objects: + domain, detectors, ASD datasets, standardization, priors, reference time. + +- [ ] `dingo/core/posterior_models/build_model.py` + - Replace `posterior_model_type` dispatch with `_target_`, while preserving + saved-model compatibility. + +- [ ] `dingo/core/utils/torchutils.py` + - Replace optimizer/scheduler `type` switches with `_target_` configs. + +- [ ] Condor integration as a Hydra workflow/launcher + - Keep Condor-specific migration out of Stage 1. + - Stage 3 goal: compose the job config with Hydra and submit Condor jobs from + that composed config. + - Desired user experience: a single Hydra-driven command can configure and + submit the Condor job. + - Existing Hydra Slurm launchers may be useful as conceptual references, but + HTCondor integration will likely require project-specific code. + - Affected areas include: + - `dingo/gw/dataset/generate_dataset_dag.py` + - `dingo/gw/noise/generate_dataset.py` + - `dingo/gw/training/train_pipeline_condor.py` + +- [ ] `dingo/pipe/default_settings.py` + - Move `DENSITY_RECOVERY_SETTINGS` and `IMPORTANCE_SAMPLING_SETTINGS` to + Hydra default configs. + - Current named defaults: + - `ProxyRecoveryDefault` + - `PhaseRecoveryDefault` + - `MultibandingDefault` + +- [ ] `dingo/core/density/nde_settings.py` + - Move `get_default_nde_settings_3d(...)` and `DEFAULT_NDE_SETTINGS_2D` into + Hydra configs. + - These can likely become reusable `defaults/nde` entries plus overrides for + dimension/device/parameters. + +- [ ] `dingo/pipe/parser.py` + - Biggest migration knot. This is both a CLI parser and a compatibility layer + with bilby_pipe-style settings. + +## Suggested Order + +1. Finish Stage 1 for standalone dataset/noise utilities. +2. Do Stage 1 for training. +3. Decide whether `dingo_pipe` should be a compatibility wrapper around Hydra + configs or a full Hydra CLI. +4. Do Stage 2 grouping for dataset generation, ASD generation, and training + before touching pipe internals. +5. Start Stage 3 with the small builder switches: + domain, prior, waveform generator, optimizer, scheduler. +6. Design Condor integration as a Stage 3 Hydra workflow/launcher problem. +7. Tackle training transforms and model construction. +8. Leave pipe/parser migration until the rest of the config story is stable. diff --git a/configs/examples/generate_dataset.yaml b/configs/examples/generate_dataset.yaml new file mode 100644 index 000000000..6ac937e0e --- /dev/null +++ b/configs/examples/generate_dataset.yaml @@ -0,0 +1,42 @@ +# settings for domain of waveforms +domain: + type: UniformFrequencyDomain + f_min: 20.0 + f_max: 1024.0 + delta_f: 0.25 # Expressions like 1.0/8.0 would require eval and are not supported + +# settings for waveform generator +waveform_generator: + approximant: IMRPhenomD + f_ref: 20.0 + # f_start: 15.0 # Optional setting useful for EOB waveforms. Overrides f_min when generating waveforms. + +# Dataset only samples over intrinsic parameters. Extrinsic parameters are chosen at train time. +intrinsic_prior: + mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1') + mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2') + chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=100.0, name='chirp_mass') + mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') + phase: default + chi_1: bilby.gw.prior.AlignedSpin(name='chi_1', a_prior=Uniform(minimum=0, maximum=0.9)) + chi_2: bilby.gw.prior.AlignedSpin(name='chi_2', a_prior=Uniform(minimum=0, maximum=0.9)) + theta_jn: default + # Reference values for fixed (extrinsic) parameters. These are needed to generate a waveform. + luminosity_distance: 100.0 # Mpc + geocent_time: 0.0 # s + +# Dataset size +num_samples: 10000 + +compression: null + +num_processes: 1 + +out_file: waveform_dataset.hdf5 + +hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} + job: + name: generate_dataset + chdir: true diff --git a/dingo/gw/dataset/generate_dataset.py b/dingo/gw/dataset/generate_dataset.py index 0b4fda8a0..b35cf49c3 100644 --- a/dingo/gw/dataset/generate_dataset.py +++ b/dingo/gw/dataset/generate_dataset.py @@ -9,15 +9,17 @@ import numpy as np import pandas as pd from bilby.gw.prior import BBHPriorDict -from hydra.utils import instantiate from omegaconf import DictConfig, OmegaConf from threadpoolctl import threadpool_limits from torchvision.transforms import Compose from dingo.gw.dataset.waveform_dataset import WaveformDataset +from dingo.gw.domains import build_domain +from dingo.gw.prior import build_prior_with_defaults from dingo.gw.SVD import ApplySVD, SVDBasis from dingo.gw.transforms import WhitenFixedASD from dingo.gw.waveform_generator import ( + NewInterfaceWaveformGenerator, WaveformGenerator, generate_waveforms_parallel, ) @@ -139,33 +141,43 @@ def train_svd_basis(dataset: WaveformDataset, size: int, n_train: int): return basis, n_train, n_test -def _dataset_settings(cfg: DictConfig) -> Dict: +def _settings_from_config(cfg: DictConfig) -> Dict: settings = OmegaConf.to_container(cfg, resolve=True) settings.pop("out_file", None) settings.pop("num_processes", None) return settings -def generate_dataset(cfg: DictConfig) -> WaveformDataset: +def generate_dataset(settings: Dict, num_processes: int) -> WaveformDataset: """ Generate a waveform dataset. Parameters ---------- - cfg : DictConfig - Hydra configuration for dataset generation. + settings : dict + Dictionary of settings to configure the dataset + num_processes : int Returns ------- A WaveformDataset based on the settings. """ - prior = instantiate(cfg.intrinsic_prior, _convert_="all") - domain = instantiate(cfg.domain) - waveform_generator = instantiate(cfg.waveform_generator, domain=domain) - num_processes = cfg.get("num_processes", 1) + prior = build_prior_with_defaults(settings["intrinsic_prior"]) + domain = build_domain(settings["domain"]) + + new_interface_flag = settings["waveform_generator"].get("new_interface", False) + if new_interface_flag: + waveform_generator = NewInterfaceWaveformGenerator( + domain=domain, + **settings["waveform_generator"], + ) + else: + waveform_generator = WaveformGenerator( + domain=domain, + **settings["waveform_generator"], + ) - settings = _dataset_settings(cfg) dataset_dict = {"settings": settings} if settings.get("compression", None) is not None: @@ -243,13 +255,14 @@ def generate_dataset(cfg: DictConfig) -> WaveformDataset: dataset_dict["parameters"] = parameters dataset_dict["polarizations"] = polarizations + dataset_dict[settings["num_samples"]] = len(parameters) dataset = WaveformDataset(dictionary=dataset_dict) return dataset @hydra.main( version_base="1.3", - config_path="../../../configs/gw/dataset", + config_path="../../../configs/examples", config_name="generate_dataset", ) def main(cfg: DictConfig) -> None: @@ -259,7 +272,8 @@ def main(cfg: DictConfig) -> None: f"dataset generation: can not create {out_file}: " f"{Path(out_file).parent} does not exist" ) - dataset = generate_dataset(cfg) + settings = _settings_from_config(cfg) + dataset = generate_dataset(settings, cfg.num_processes) dataset.to_file(str(out_file)) diff --git a/pyproject.toml b/pyproject.toml index 7eef484df..e3341fc16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ dependencies = [ "glasflow", "gwpy", "h5py", + "hydra-core", "lalsuite>=7.15", "matplotlib", "numpy", diff --git a/uv.lock b/uv.lock index 3b604a4a2..fbdef97b1 100644 --- a/uv.lock +++ b/uv.lock @@ -28,6 +28,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034, upload-time = "2021-11-06T17:52:23.524Z" } + [[package]] name = "appnope" version = "0.1.4" @@ -78,11 +84,11 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "astropy-iers-data", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "pyerfa", marker = "python_full_version < '3.11'" }, - { name = "pyyaml", marker = "python_full_version < '3.11'" }, + { name = "astropy-iers-data" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pyerfa" }, + { name = "pyyaml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8a/f8/9c6675ab4c646b95aae2762d108f6be4504033d91bd50da21daa62cab5ce/astropy-6.1.7.tar.gz", hash = "sha256:a405ac186306b6cb152e6df2f7444ab8bd764e4127d7519da1b3ae4dd65357ef", size = 7063411, upload-time = "2024-11-22T21:22:34.373Z" } wheels = [ @@ -127,11 +133,11 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "astropy-iers-data", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pyerfa", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, + { name = "astropy-iers-data" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pyerfa" }, + { name = "pyyaml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7b/92/2dce2d48347efc3346d08ca7995b152d242ebd170c571f7c9346468d8427/astropy-7.2.0.tar.gz", hash = "sha256:ae48bc26b1feaeb603cd94bd1fa1aa39137a115fe931b7f13787ab420e8c3070", size = 7057774, upload-time = "2025-11-25T22:36:41.916Z" } wheels = [ @@ -265,24 +271,24 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "astropy", version = "6.1.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "bilby", extra = ["gw"], marker = "python_full_version < '3.11'" }, - { name = "configargparse", marker = "python_full_version < '3.11'" }, - { name = "corner", marker = "python_full_version < '3.11'" }, - { name = "dynesty", marker = "python_full_version < '3.11'" }, - { name = "future", marker = "python_full_version < '3.11'" }, - { name = "gwosc", marker = "python_full_version < '3.11'" }, - { name = "gwpy", version = "3.0.13", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "jinja2", marker = "python_full_version < '3.11'" }, - { name = "ligo-gracedb", marker = "python_full_version < '3.11'" }, - { name = "matplotlib", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "plotly", marker = "python_full_version < '3.11'" }, - { name = "pycondor", marker = "python_full_version < '3.11'" }, - { name = "python-ligo-lw", marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "seaborn", marker = "python_full_version < '3.11'" }, - { name = "tqdm", marker = "python_full_version < '3.11'" }, + { name = "astropy", version = "6.1.7", source = { registry = "https://pypi.org/simple" } }, + { name = "bilby", extra = ["gw"] }, + { name = "configargparse" }, + { name = "corner" }, + { name = "dynesty" }, + { name = "future" }, + { name = "gwosc" }, + { name = "gwpy", version = "3.0.13", source = { registry = "https://pypi.org/simple" } }, + { name = "jinja2" }, + { name = "ligo-gracedb" }, + { name = "matplotlib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "plotly" }, + { name = "pycondor" }, + { name = "python-ligo-lw" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" } }, + { name = "seaborn" }, + { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/11/89/231ec5533fd46c40b080bb1dd7aa8947f69b6ca65259c048804e2ae9aa6f/bilby_pipe-1.8.0.tar.gz", hash = "sha256:b6155b82f2dc3afbd5de69598641edc716b6b8331c902ef35c9a3a2d547dc83a", size = 57085085, upload-time = "2025-12-20T15:58:56.843Z" } wheels = [ @@ -300,24 +306,24 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "astropy", version = "7.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "bilby", extra = ["gw"], marker = "python_full_version >= '3.11'" }, - { name = "configargparse", marker = "python_full_version >= '3.11'" }, - { name = "corner", marker = "python_full_version >= '3.11'" }, - { name = "dynesty", marker = "python_full_version >= '3.11'" }, - { name = "future", marker = "python_full_version >= '3.11'" }, - { name = "gwosc", marker = "python_full_version >= '3.11'" }, - { name = "gwpy", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "igwn-ligolw", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "ligo-gracedb", marker = "python_full_version >= '3.11'" }, - { name = "matplotlib", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "plotly", marker = "python_full_version >= '3.11'" }, - { name = "pycondor", marker = "python_full_version >= '3.11'" }, - { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "seaborn", marker = "python_full_version >= '3.11'" }, - { name = "tqdm", marker = "python_full_version >= '3.11'" }, + { name = "astropy", version = "7.2.0", source = { registry = "https://pypi.org/simple" } }, + { name = "bilby", extra = ["gw"] }, + { name = "configargparse" }, + { name = "corner" }, + { name = "dynesty" }, + { name = "future" }, + { name = "gwosc" }, + { name = "gwpy", version = "4.0.1", source = { registry = "https://pypi.org/simple" } }, + { name = "igwn-ligolw", version = "2.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "jinja2" }, + { name = "ligo-gracedb" }, + { name = "matplotlib" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } }, + { name = "plotly" }, + { name = "pycondor" }, + { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" } }, + { name = "seaborn" }, + { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e3/41/66780cb4fdb3d008a593a175e17bec61fd2f88f8609cd68860ec4a308302/bilby_pipe-1.8.1.tar.gz", hash = "sha256:1b287f2db4756770502d10e05c12b941ea2837f1f96e273ff0fe2949a407fab0", size = 57085114, upload-time = "2026-04-07T17:53:20.489Z" } wheels = [ @@ -562,7 +568,7 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } wheels = [ @@ -635,7 +641,7 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ @@ -864,6 +870,7 @@ dependencies = [ { name = "gwpy", version = "3.0.13", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "gwpy", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "h5py" }, + { name = "hydra-core" }, { name = "lalsuite" }, { name = "matplotlib" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -948,6 +955,7 @@ requires-dist = [ { name = "gwpy" }, { name = "h5py" }, { name = "htcondor", marker = "extra == 'dev'" }, + { name = "hydra-core" }, { name = "ipykernel", marker = "extra == 'dev'" }, { name = "lalsuite", specifier = ">=7.15" }, { name = "linkify-it-py", marker = "extra == 'dev'" }, @@ -1058,7 +1066,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -1276,21 +1284,21 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "astropy", version = "6.1.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "dateparser", marker = "python_full_version < '3.11'" }, - { name = "dqsegdb2", marker = "python_full_version < '3.11'" }, - { name = "gwdatafind", marker = "python_full_version < '3.11'" }, - { name = "gwosc", marker = "python_full_version < '3.11'" }, - { name = "h5py", marker = "python_full_version < '3.11'" }, - { name = "igwn-segments", marker = "python_full_version < '3.11'" }, - { name = "ligotimegps", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "matplotlib", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "python-dateutil", marker = "python_full_version < '3.11'" }, - { name = "requests", marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "tqdm", marker = "python_full_version < '3.11'" }, + { name = "astropy", version = "6.1.7", source = { registry = "https://pypi.org/simple" } }, + { name = "dateparser" }, + { name = "dqsegdb2" }, + { name = "gwdatafind" }, + { name = "gwosc" }, + { name = "h5py" }, + { name = "igwn-segments" }, + { name = "ligotimegps", version = "2.0.1", source = { registry = "https://pypi.org/simple" } }, + { name = "matplotlib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" } }, + { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ff/5e/a210178c2ca8ac3ff2df25634f960077a1b537e9187321c27a101bb99d50/gwpy-3.0.13.tar.gz", hash = "sha256:59a2dc90a9c2edde3efae090303c0f8afeb6c644dae6c8fdd4d92bf0799374b1", size = 1542843, upload-time = "2025-07-02T09:30:47.183Z" } wheels = [ @@ -1308,21 +1316,21 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "astropy", version = "7.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "dateparser", marker = "python_full_version >= '3.11'" }, - { name = "dqsegdb2", marker = "python_full_version >= '3.11'" }, - { name = "gwdatafind", marker = "python_full_version >= '3.11'" }, - { name = "gwosc", marker = "python_full_version >= '3.11'" }, - { name = "h5py", marker = "python_full_version >= '3.11'" }, - { name = "igwn-segments", marker = "python_full_version >= '3.11'" }, - { name = "ligotimegps", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "matplotlib", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, - { name = "requests", marker = "python_full_version >= '3.11'" }, - { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "tqdm", marker = "python_full_version >= '3.11'" }, + { name = "astropy", version = "7.2.0", source = { registry = "https://pypi.org/simple" } }, + { name = "dateparser" }, + { name = "dqsegdb2" }, + { name = "gwdatafind" }, + { name = "gwosc" }, + { name = "h5py" }, + { name = "igwn-segments" }, + { name = "ligotimegps", version = "2.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "matplotlib" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" } }, + { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/92/71/1e652734a0e8f23e0f9dbf5df5698e66fa49aef4446d2c6c9eb914921192/gwpy-4.0.1.tar.gz", hash = "sha256:477aa69bd40506bfb0df5ec779d9dd6fef562a3bfabe58a52b297e25c57f61f0", size = 1615849, upload-time = "2026-02-03T10:14:59.045Z" } wheels = [ @@ -1374,17 +1382,31 @@ wheels = [ [[package]] name = "htcondor" -version = "25.7.2" +version = "25.11.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/7c/b88e13d03ab47c930da456bdfbe0428e9098fe8341c099e4f200f44c6830/htcondor-25.7.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:2c30fff3d704437786ce5526d6a8e444c1246b2996d264a07254c31415488562", size = 50208679, upload-time = "2026-03-12T15:42:42.497Z" }, - { url = "https://files.pythonhosted.org/packages/ba/0e/87386e8028934d502d63b8314d830aaa6b9fe209a2b3173a423fb7ab54a3/htcondor-25.7.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ac6bc73c338828b73f44fdcd4ff5407f70810314bd1e160a99fcd2c3f2e54f34", size = 51634164, upload-time = "2026-03-12T15:42:46.353Z" }, - { url = "https://files.pythonhosted.org/packages/bf/fa/027cd625887cf4e37ddff4c33dc9e9c5e9ef18d620b14babc3da98f175c3/htcondor-25.7.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:60fb891fae4680743b5f72108ec29020094edbf7fb3214a49055bcd79ac7fd00", size = 50208821, upload-time = "2026-03-12T15:42:50.351Z" }, - { url = "https://files.pythonhosted.org/packages/c5/06/6a48f90656c2d252c79449821f694612d002a6ad37d822778df51a2d3bef/htcondor-25.7.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c020d64225daf8bb3c765fab933859dfa73e899485fcd57767c46d736ab9dfe5", size = 51640592, upload-time = "2026-03-12T15:42:54.822Z" }, - { url = "https://files.pythonhosted.org/packages/a5/82/536f95681b0a1bea5f3709917dba270133e93b14626cff36e80959bea13a/htcondor-25.7.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:94af431820294fea9b28abae7298ca3d7d52ab584f2c79327d27eff03f8cc5ae", size = 50209472, upload-time = "2026-03-12T15:42:58.991Z" }, - { url = "https://files.pythonhosted.org/packages/90/ef/4725f304f0e718cb0f0fca00fdf44a21ff3d42116553a62ee9026a52ed43/htcondor-25.7.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d462cf68b7ca39ee91923c3716ee9e0c46713a153232b5f7b68643cb91448621", size = 51631016, upload-time = "2026-03-12T15:43:04.048Z" }, - { url = "https://files.pythonhosted.org/packages/88/03/afef2f6d0d3cacefa2c2a97736439ad44c3b77148d77caa20bbfa507ac16/htcondor-25.7.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5097700ec9c609c752f2e76665031b9925633cff2cba6fe1db2d2ef270e88dcd", size = 50212296, upload-time = "2026-03-12T15:43:08.521Z" }, - { url = "https://files.pythonhosted.org/packages/d8/94/350a5e0c412095666cf1e771969e226072ab6233ebceabd5994337aad726/htcondor-25.7.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:306bf1f714299341b556d46b3ea662822d8ef58780b450177bce5b720ce24e3b", size = 51645252, upload-time = "2026-03-12T15:43:13.913Z" }, + { url = "https://files.pythonhosted.org/packages/94/05/4582995aa615d0ac0e2b20379a3cfd656b5b67147fc71eb028331ebc4569/htcondor-25.11.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:9580aea503bf0b009d0831876028f8e6aaec010cc2526789a1b388790d27efa5", size = 51111733, upload-time = "2026-06-11T14:29:16.357Z" }, + { url = "https://files.pythonhosted.org/packages/c5/96/935019a84d2cb1c74bcd94ab92fea87c83f812dd008cf14271c3179c40ff/htcondor-25.11.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:9a3dfc83e3b498ccbee7198eb570508a6b8e614aaf7dbbe87906ad8a69dfbc93", size = 52554834, upload-time = "2026-06-11T14:29:19.451Z" }, + { url = "https://files.pythonhosted.org/packages/02/cf/052453b10b58673ebbe55d88fe84a307c42c30a39b949714fb123ecea962/htcondor-25.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6c4e9a74d1eca96cfe7fd83ac38b1f6e79310ff90450f480f2e9d508b5f8c16a", size = 51111931, upload-time = "2026-06-11T14:29:22.461Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fd/9ab2e532a977a809a7a103d5b3d547b9d772f6ca745d516f3f5269ab12ae/htcondor-25.11.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:77f0ae0bf51e2f5227d5ee18804e642ed7643472d59de631e2a164a6643e7db8", size = 52554719, upload-time = "2026-06-11T14:29:25.355Z" }, + { url = "https://files.pythonhosted.org/packages/ee/eb/f2f8387f16be10f6bbd92a4b2b2d44c8b29c732bedf5bbe60e14b6e0a30e/htcondor-25.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5f5a2951acb37bc7554af8ee377fa5ed507eca4e31c219643019b59150ff325b", size = 51114270, upload-time = "2026-06-11T14:29:28.452Z" }, + { url = "https://files.pythonhosted.org/packages/d0/01/219e1047d40cc2254a5a8b791c5c7d26bbf336f7f9b3ebdad57f4d762b4a/htcondor-25.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:32b95deb53bb0129983d9e341b22d96241da830f6acdf61c4594815df2c38193", size = 52552026, upload-time = "2026-06-11T14:29:31.297Z" }, + { url = "https://files.pythonhosted.org/packages/c5/38/da64ab166e271fa3088f573fcda7e4e58817bd9e209f2c7c41e227be95d7/htcondor-25.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:8cd7efde67d42cbbda3e7f2a564c4c0b27ca7ee60eb8f0482c538628a4176def", size = 51125765, upload-time = "2026-06-11T14:29:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f2/b1b3133d3401f795b1975f093ad95e6d2d8fe8c0df0429e321e4e5832573/htcondor-25.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:46252c51c9aebf0c9412c52b2c7602ba22734a8157ae9c2983e9dce2547f1d0b", size = 52563721, upload-time = "2026-06-11T14:29:36.875Z" }, +] + +[[package]] +name = "hydra-core" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "omegaconf" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/0b/7c0d941311aadc6479ec01767edba9c8a07db1452685de3567ed3058d0c9/hydra_core-1.3.3.tar.gz", hash = "sha256:b7477ee21f08b62f71bf0126d44695c048dc7e9c0cc79e2d593b707cb1e44048", size = 3262532, upload-time = "2026-06-11T05:54:26.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/57/4e39f85347f77144d2ad12e87d5df8fb8f17023f9bd9e8c6e903a128382c/hydra_core-1.3.3-py3-none-any.whl", hash = "sha256:cf349fc393f486f250e5825592c3d0a50c0af3effd726cf8dd5b637a7cb464e3", size = 154706, upload-time = "2026-06-11T05:54:24.917Z" }, ] [[package]] @@ -1420,11 +1442,11 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "igwn-segments", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "python-dateutil", marker = "python_full_version < '3.11'" }, - { name = "pyyaml", marker = "python_full_version < '3.11'" }, - { name = "tqdm", marker = "python_full_version < '3.11'" }, + { name = "igwn-segments" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1b/f4/ec600abf6891ffc203547b69949be5b34b03bb705433da385a562b93a796/igwn_ligolw-2.0.1.tar.gz", hash = "sha256:e5d740830d497dfcf13d1101b284a1f8bed98d5bd8164a0c236ab4a97eddd2fb", size = 2333107, upload-time = "2025-04-08T12:33:42.741Z" } wheels = [ @@ -1457,11 +1479,11 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "igwn-segments", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, - { name = "tqdm", marker = "python_full_version >= '3.11'" }, + { name = "igwn-segments" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/87/db/4c9420b13b1fd874b4cbc3beb74c96c2d4e17a5fb9c2ead91cdca7b2cfc9/igwn_ligolw-2.1.0.tar.gz", hash = "sha256:02a7201445d5dba8179a28f097b1ca9839a6564a5503201ee922dcbc9aa31c9d", size = 2337001, upload-time = "2025-05-02T12:50:34.755Z" } wheels = [ @@ -1596,17 +1618,17 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version < '3.11'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "jedi", marker = "python_full_version < '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, - { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "stack-data", marker = "python_full_version < '3.11'" }, - { name = "traitlets", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "exceptiongroup" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088, upload-time = "2025-05-31T16:39:09.613Z" } wheels = [ @@ -1624,17 +1646,17 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version >= '3.11'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, - { name = "jedi", marker = "python_full_version >= '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, - { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, - { name = "pygments", marker = "python_full_version >= '3.11'" }, - { name = "stack-data", marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/12/51/a703c030f4928646d390b4971af4938a1b10c9dfce694f0d99a0bb073cb2/ipython-9.8.0.tar.gz", hash = "sha256:8e4ce129a627eb9dd221c41b1d2cdaed4ef7c9da8c17c63f6f578fe231141f83", size = 4424940, upload-time = "2025-12-03T10:18:24.353Z" } wheels = [ @@ -1646,7 +1668,7 @@ name = "ipython-pygments-lexers" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } wheels = [ @@ -1944,7 +1966,7 @@ name = "ligo-segments" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "six", marker = "python_full_version < '3.11'" }, + { name = "six" }, ] sdist = { url = "https://files.pythonhosted.org/packages/81/60/8de5c89e4e5fc760649cee0c773418ecc920c3dae21ac5656fd3e5e21a9d/ligo-segments-1.4.0.tar.gz", hash = "sha256:e072a844713c5b02efdcaf5bfe4c3a8cd9ef225b08cfd3202a4e185e0f71f5dc", size = 51015, upload-time = "2021-10-18T14:47:39.224Z" } wheels = [ @@ -2640,7 +2662,7 @@ name = "numpy-typing-compat" version = "20251206.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/77/83/dd90774d6685664cbe5525645a50c4e6c7454207aee552918790e879137f/numpy_typing_compat-20251206.2.3.tar.gz", hash = "sha256:18e00e0f4f2040fe98574890248848c7c6831a975562794da186cf4f3c90b935", size = 5009, upload-time = "2025-12-06T20:02:04.177Z" } wheels = [ @@ -2781,6 +2803,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, ] +[[package]] +name = "omegaconf" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/3d/e4b57b8d9008c6ebe0d5eff901f91d5700cf7bdb8c8863df817463a7fd5e/omegaconf-2.3.1.tar.gz", hash = "sha256:e5e7de64aeebeddaf8e6d3f7a783b32ac2a01c0fbd9c878012caecb891a1f42a", size = 3298472, upload-time = "2026-06-11T05:05:12.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/0e/152509871bf30df6fc38569f52a2db9b55dd41aae957adae50a053ac7778/omegaconf-2.3.1-py3-none-any.whl", hash = "sha256:3d701d14e9a8828f1edd28bb70b725908b34277cdd72cf7d6a83f94dadc6b6a0", size = 79502, upload-time = "2026-06-11T05:05:09.954Z" }, +] + [[package]] name = "optype" version = "0.9.3" @@ -2790,7 +2825,7 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/88/3c/9d59b0167458b839273ad0c4fc5f62f787058d8f5aed7f71294963a99471/optype-0.9.3.tar.gz", hash = "sha256:5f09d74127d316053b26971ce441a4df01f3a01943601d3712dd6f34cdfbaf48", size = 96143, upload-time = "2025-03-31T17:00:08.392Z" } wheels = [ @@ -2808,7 +2843,7 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "typing-extensions", marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d7/93/6b9e43138ce36fbad134bd1a50460a7bbda61105b5a964e4cf773fe4d845/optype-0.15.0.tar.gz", hash = "sha256:457d6ca9e7da19967ec16d42bdf94e240b33b5d70a56fbbf5b427e5ea39cf41e", size = 99978, upload-time = "2025-12-08T12:32:41.422Z" } wheels = [ @@ -2817,8 +2852,8 @@ wheels = [ [package.optional-dependencies] numpy = [ - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "numpy-typing-compat", marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy-typing-compat" }, ] [[package]] @@ -3161,26 +3196,26 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "astropy", version = "6.1.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "beautifulsoup4", marker = "python_full_version < '3.11'" }, - { name = "cython", marker = "python_full_version < '3.11'" }, - { name = "gwdatafind", marker = "python_full_version < '3.11'" }, - { name = "h5py", marker = "python_full_version < '3.11'" }, - { name = "igwn-ligolw", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "igwn-segments", marker = "python_full_version < '3.11'" }, - { name = "jinja2", marker = "python_full_version < '3.11'" }, - { name = "lalsuite", marker = "python_full_version < '3.11'" }, - { name = "lscsoft-glue", marker = "python_full_version < '3.11'" }, - { name = "mako", marker = "python_full_version < '3.11'" }, - { name = "matplotlib", marker = "python_full_version < '3.11'" }, - { name = "mpld3", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pegasus-wms-api", marker = "python_full_version < '3.11'" }, - { name = "pillow", marker = "python_full_version < '3.11'" }, - { name = "pykerr", marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "setuptools", marker = "python_full_version < '3.11'" }, - { name = "tqdm", marker = "python_full_version < '3.11'" }, + { name = "astropy", version = "6.1.7", source = { registry = "https://pypi.org/simple" } }, + { name = "beautifulsoup4" }, + { name = "cython" }, + { name = "gwdatafind" }, + { name = "h5py" }, + { name = "igwn-ligolw", version = "2.0.1", source = { registry = "https://pypi.org/simple" } }, + { name = "igwn-segments" }, + { name = "jinja2" }, + { name = "lalsuite" }, + { name = "lscsoft-glue" }, + { name = "mako" }, + { name = "matplotlib" }, + { name = "mpld3" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "pegasus-wms-api" }, + { name = "pillow" }, + { name = "pykerr" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" } }, + { name = "setuptools" }, + { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fe/9b/834a129763dc752db22eecacc71bfa3fa0b04e1cfd9c7b00a1b417c837ec/PyCBC-2.9.0.tar.gz", hash = "sha256:f9bf981814d6d522ad0c9affadcfd3f3d2ea8422b047d70755b06c03c08e342e", size = 3574557, upload-time = "2025-07-03T13:22:45.353Z" } wheels = [ @@ -3206,26 +3241,26 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "astropy", version = "7.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "beautifulsoup4", marker = "python_full_version >= '3.11'" }, - { name = "cython", marker = "python_full_version >= '3.11'" }, - { name = "gwdatafind", marker = "python_full_version >= '3.11'" }, - { name = "h5py", marker = "python_full_version >= '3.11'" }, - { name = "igwn-ligolw", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "igwn-segments", marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "lalsuite", marker = "python_full_version >= '3.11'" }, - { name = "lscsoft-glue", marker = "python_full_version >= '3.11'" }, - { name = "mako", marker = "python_full_version >= '3.11'" }, - { name = "matplotlib", marker = "python_full_version >= '3.11'" }, - { name = "mpld3", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "pegasus-wms-api", marker = "python_full_version >= '3.11'" }, - { name = "pillow", marker = "python_full_version >= '3.11'" }, - { name = "pykerr", marker = "python_full_version >= '3.11'" }, - { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "setuptools", marker = "python_full_version >= '3.11'" }, - { name = "tqdm", marker = "python_full_version >= '3.11'" }, + { name = "astropy", version = "7.2.0", source = { registry = "https://pypi.org/simple" } }, + { name = "beautifulsoup4" }, + { name = "cython" }, + { name = "gwdatafind" }, + { name = "h5py" }, + { name = "igwn-ligolw", version = "2.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "igwn-segments" }, + { name = "jinja2" }, + { name = "lalsuite" }, + { name = "lscsoft-glue" }, + { name = "mako" }, + { name = "matplotlib" }, + { name = "mpld3" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } }, + { name = "pegasus-wms-api" }, + { name = "pillow" }, + { name = "pykerr" }, + { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" } }, + { name = "setuptools" }, + { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/3e/bd821e5760ceab12de6607a4a385e78f301951531c2453b21e167484e966/pycbc-2.10.0.tar.gz", hash = "sha256:3c1034e4089d256936c4d80f9c1fa1348c1ff604357a47a4beffbcac2c135edf", size = 3595057, upload-time = "2026-01-07T15:59:25.808Z" } wheels = [ @@ -3394,8 +3429,8 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "setuptools", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "setuptools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4b/3f/ee1bc44b080fc1e81d293cd07bed563d254bc1997d63a3b8053804a87dfd/pyfftw-0.15.0.tar.gz", hash = "sha256:2f16b9854a40c8fdd10aa5803b24ddc6ab49f9cd559dbd7f07e7d61aa205c1ca", size = 164003, upload-time = "2024-11-06T16:01:19.293Z" } wheels = [ @@ -3439,7 +3474,7 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/f2/2d/e38439b7f937e8bf91a9ff2b8d9713d0d8e64e980fc00e8d1945b8a5b74b/pyfftw-0.15.1.tar.gz", hash = "sha256:bbcde6d40d165e1cbaf12dde062ebfebe9e43394cac8c166e699ba2c9a4b0461", size = 192838, upload-time = "2025-10-22T19:58:56.683Z" } wheels = [ @@ -3586,13 +3621,13 @@ name = "python-ligo-lw" version = "1.8.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ligo-segments", marker = "python_full_version < '3.11'" }, - { name = "lscsoft-glue", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "python-dateutil", marker = "python_full_version < '3.11'" }, - { name = "pyyaml", marker = "python_full_version < '3.11'" }, - { name = "six", marker = "python_full_version < '3.11'" }, - { name = "tqdm", marker = "python_full_version < '3.11'" }, + { name = "ligo-segments" }, + { name = "lscsoft-glue" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "six" }, + { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/13/b9/bc552b9b2cd0b0bddffae90aa7f1b95229d323dadb742a48576c7a67ab3f/python_ligo_lw-1.8.4.tar.gz", hash = "sha256:1ccaefeeda2a1cddca22b021ec88f550e24f87293a6f9753b3912e41cf0c95d1", size = 2428804, upload-time = "2025-06-30T14:37:58.437Z" } @@ -3890,7 +3925,7 @@ name = "roman-numerals-py" version = "4.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "roman-numerals", marker = "python_full_version >= '3.11'" }, + { name = "roman-numerals" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", hash = "sha256:f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9", size = 4274, upload-time = "2025-12-17T18:25:41.153Z" } wheels = [ @@ -4008,10 +4043,10 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "joblib", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "threadpoolctl", marker = "python_full_version < '3.11'" }, + { name = "joblib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" } }, + { name = "threadpoolctl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } wheels = [ @@ -4053,10 +4088,10 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "joblib", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "threadpoolctl", marker = "python_full_version >= '3.11'" }, + { name = "joblib" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } }, + { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" } }, + { name = "threadpoolctl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } wheels = [ @@ -4095,7 +4130,7 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } wheels = [ @@ -4157,7 +4192,7 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" } wheels = [ @@ -4212,7 +4247,7 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "optype", version = "0.9.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "optype", version = "0.9.3", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/35c43bd7d412add4adcd68475702571b2489b50c40b6564f808b2355e452/scipy_stubs-1.15.3.0.tar.gz", hash = "sha256:e8f76c9887461cf9424c1e2ad78ea5dac71dd4cbb383dc85f91adfe8f74d1e17", size = 275699, upload-time = "2025-05-08T16:58:35.139Z" } wheels = [ @@ -4230,7 +4265,7 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "optype", version = "0.15.0", source = { registry = "https://pypi.org/simple" }, extra = ["numpy"], marker = "python_full_version >= '3.11'" }, + { name = "optype", version = "0.15.0", source = { registry = "https://pypi.org/simple" }, extra = ["numpy"] }, ] sdist = { url = "https://files.pythonhosted.org/packages/08/91/1700d2a1a9f64f19bb019a547e510b99a6af1fef49641a0bce86bc85fb8e/scipy_stubs-1.16.3.3.tar.gz", hash = "sha256:af47578875d5557567225a16ec1b9b38a48c4c4377d92396413ebd65406c44ee", size = 361468, upload-time = "2025-12-08T13:45:38.37Z" } wheels = [ @@ -4400,23 +4435,23 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "alabaster", marker = "python_full_version < '3.11'" }, - { name = "babel", marker = "python_full_version < '3.11'" }, - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version < '3.11'" }, - { name = "imagesize", marker = "python_full_version < '3.11'" }, - { name = "jinja2", marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "requests", marker = "python_full_version < '3.11'" }, - { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, + { name = "tomli" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } wheels = [ @@ -4434,23 +4469,23 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ - { name = "alabaster", marker = "python_full_version >= '3.11'" }, - { name = "babel", marker = "python_full_version >= '3.11'" }, - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version >= '3.11'" }, - { name = "imagesize", marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pygments", marker = "python_full_version >= '3.11'" }, - { name = "requests", marker = "python_full_version >= '3.11'" }, - { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" }, - { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" }, + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals-py" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } wheels = [ From 84e3063ce797061bbd7c7a71ef6a59fecd46f7f1 Mon Sep 17 00:00:00 2001 From: cmfabbri Date: Wed, 1 Jul 2026 16:09:46 +0200 Subject: [PATCH 08/15] WIP: Import logger and call setup_logger in dingo_pipe main (issue #152) Co-Authored-By: Claude Sonnet 4.6 --- dingo/pipe/main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dingo/pipe/main.py b/dingo/pipe/main.py index 1c52cedcf..9bdb00bd1 100644 --- a/dingo/pipe/main.py +++ b/dingo/pipe/main.py @@ -16,12 +16,15 @@ convert_string_to_dict, get_colored_string, get_command_line_arguments, - logger, parse_args, convert_prior_string_input, BilbyPipeError, ) +import logging +from dingo.core.utils.logging_utils import setup_logger +logger = logging.getLogger("dingo.pipe") + from .dag_creator import generate_dag from .parser import create_parser from .utils import dict_to_string @@ -618,6 +621,8 @@ def main(): parser = create_parser(top_level=True) args, unknown_args = parse_args(get_command_line_arguments(), parser) + setup_logger(outdir=args.outdir, label=args.label) + importance_sampling_updates, model_args = fill_in_arguments_from_model(args) inputs = MainInput(args, unknown_args, importance_sampling_updates) write_complete_config_file(parser, args, inputs) From 5c77054d6f17063f95db6ffb5321ab87575d404a Mon Sep 17 00:00:00 2001 From: cmfabbri Date: Thu, 2 Jul 2026 10:34:29 +0200 Subject: [PATCH 09/15] adding setup_logger to create log files --- dingo/gw/dataset/generate_dataset.py | 4 +++- dingo/gw/dataset/generate_dataset_dag.py | 3 ++- dingo/gw/dataset/utils.py | 7 ++++++- dingo/gw/noise/generate_dataset.py | 3 ++- dingo/gw/training/train_pipeline.py | 3 ++- dingo/gw/training/train_pipeline_condor.py | 3 ++- 6 files changed, 17 insertions(+), 6 deletions(-) diff --git a/dingo/gw/dataset/generate_dataset.py b/dingo/gw/dataset/generate_dataset.py index 873568c13..4e5886cad 100644 --- a/dingo/gw/dataset/generate_dataset.py +++ b/dingo/gw/dataset/generate_dataset.py @@ -24,7 +24,7 @@ generate_waveforms_parallel, ) from dingo.core.utils.misc import call_func_strict_output_dim -from dingo.core.utils.logging_utils import logger +from dingo.core.utils.logging_utils import logger, setup_logger def generate_parameters_and_polarizations( @@ -302,6 +302,8 @@ def _generate_dataset_main( def main() -> None: args = parse_args() + out_path = Path(args.out_file) + setup_logger(outdir=str(out_path.parent), label=out_path.stem) _generate_dataset_main(args.settings_file, args.out_file, args.num_processes) diff --git a/dingo/gw/dataset/generate_dataset_dag.py b/dingo/gw/dataset/generate_dataset_dag.py index c24c74b32..718149f72 100644 --- a/dingo/gw/dataset/generate_dataset_dag.py +++ b/dingo/gw/dataset/generate_dataset_dag.py @@ -5,7 +5,7 @@ import yaml import copy -from dingo.core.utils.logging_utils import logger +from dingo.core.utils.logging_utils import logger, setup_logger # Fixed file names svd_fn = "svd.hdf5" @@ -266,6 +266,7 @@ def create_dag(args, settings): def main(): args = parse_args() + setup_logger(outdir=args.temp_dir, label="generate_dataset_dag") # Load settings with open(args.settings_file, "r") as f: diff --git a/dingo/gw/dataset/utils.py b/dingo/gw/dataset/utils.py index aa5e84855..5308ef320 100644 --- a/dingo/gw/dataset/utils.py +++ b/dingo/gw/dataset/utils.py @@ -1,12 +1,13 @@ import argparse import textwrap import copy +from pathlib import Path import pandas as pd import numpy as np import yaml from typing import List -from dingo.core.utils.logging_utils import logger +from dingo.core.utils.logging_utils import logger, setup_logger from dingo.gw.SVD import SVDBasis from dingo.gw.dataset.generate_dataset import train_svd_basis from dingo.gw.dataset.waveform_dataset import WaveformDataset @@ -88,6 +89,8 @@ def merge_datasets_cli(): "--settings_file", type=str, help="YAML file containing new dataset settings." ) args = parser.parse_args() + out_path = Path(args.out_file) + setup_logger(outdir=str(out_path.parent), label=out_path.stem) dataset_list = [] for i in range(args.num_parts): @@ -142,6 +145,8 @@ def build_svd_cli(): "Remainder are used for validation.", ) args = parser.parse_args() + out_path = Path(args.out_file) + setup_logger(outdir=str(out_path.parent), label=out_path.stem) dataset = WaveformDataset(file_name=args.dataset_file) if args.num_train is None: diff --git a/dingo/gw/noise/generate_dataset.py b/dingo/gw/noise/generate_dataset.py index 2a4c8b383..24f5c34a8 100644 --- a/dingo/gw/noise/generate_dataset.py +++ b/dingo/gw/noise/generate_dataset.py @@ -12,7 +12,7 @@ from dingo.gw.noise.asd_dataset import ASDDataset from dingo.gw.noise.generate_dataset_dag import create_dag from dingo.gw.noise.utils import merge_datasets, get_time_segments -from dingo.core.utils.logging_utils import logger +from dingo.core.utils.logging_utils import logger, setup_logger def parse_args(): @@ -59,6 +59,7 @@ def generate_dataset(): Creates and saves an ASD dataset """ args = parse_args() + setup_logger(outdir=args.data_dir, label="asd_dataset") # Load settings settings_file = ( diff --git a/dingo/gw/training/train_pipeline.py b/dingo/gw/training/train_pipeline.py index 544f4b1ac..5668c389b 100644 --- a/dingo/gw/training/train_pipeline.py +++ b/dingo/gw/training/train_pipeline.py @@ -27,7 +27,7 @@ build_train_and_test_loaders, ) from dingo.core.utils.trainutils import EarlyStopping -from dingo.core.utils.logging_utils import logger +from dingo.core.utils.logging_utils import logger, setup_logger from dingo.gw.dataset import WaveformDataset from dingo.core.posterior_models import BasePosteriorModel @@ -439,6 +439,7 @@ def train_local(): args = parse_args() os.makedirs(args.train_dir, exist_ok=True) + setup_logger(outdir=args.train_dir, label="dingo_train") if args.settings_file is not None: logger.info("Beginning new training run.") diff --git a/dingo/gw/training/train_pipeline_condor.py b/dingo/gw/training/train_pipeline_condor.py index 95f118add..06550f5a2 100644 --- a/dingo/gw/training/train_pipeline_condor.py +++ b/dingo/gw/training/train_pipeline_condor.py @@ -4,7 +4,7 @@ import yaml import argparse -from dingo.core.utils.logging_utils import logger +from dingo.core.utils.logging_utils import logger, setup_logger from dingo.gw.training import ( prepare_training_new, prepare_training_resume, @@ -79,6 +79,7 @@ def train_condor(): help="Optional command to execute after completion of training.", ) args = parser.parse_args() + setup_logger(outdir=args.train_dir, label="dingo_train") if not args.start_submission: From 08ad8004264f7a0bb861419da34c9ff3a8d50ce1 Mon Sep 17 00:00:00 2001 From: cmfabbri Date: Thu, 2 Jul 2026 10:52:57 +0200 Subject: [PATCH 10/15] calling setup_logger to create log files --- dingo/pipe/plot.py | 2 ++ dingo/pipe/sampling.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dingo/pipe/plot.py b/dingo/pipe/plot.py index dbd69de0c..772fa68f9 100644 --- a/dingo/pipe/plot.py +++ b/dingo/pipe/plot.py @@ -3,6 +3,7 @@ import logging +from dingo.core.utils.logging_utils import setup_logger from dingo.gw.result import Result logger = logging.getLogger("dingo.pipe") @@ -48,6 +49,7 @@ def create_parser(): def main(): args, _ = parse_args(get_command_line_arguments(), create_parser()) + setup_logger(outdir=args.outdir, label=args.label) logger.info(f"Generating plots for results file {args.result}") diff --git a/dingo/pipe/sampling.py b/dingo/pipe/sampling.py index 9138066bc..e038e7022 100644 --- a/dingo/pipe/sampling.py +++ b/dingo/pipe/sampling.py @@ -7,10 +7,12 @@ from bilby_pipe.input import Input from bilby_pipe.utils import ( parse_args, - logger, convert_string_to_dict, resolve_filename_with_transfer_fallback, ) +import logging +from dingo.core.utils.logging_utils import setup_logger +logger = logging.getLogger("dingo.pipe") from dingo.core.posterior_models.build_model import build_model_from_kwargs from dingo.gw.data.event_dataset import EventDataset @@ -216,6 +218,7 @@ def create_sampling_parser(): def main(): """Data analysis main logic""" args, unknown_args = parse_args(sys.argv[1:], create_sampling_parser()) + setup_logger(outdir=args.outdir, label=args.label) # log_version_information() analysis = SamplingInput(args, unknown_args) analysis.run_sampler() From 974f15d32c562b856c7cfadcaeece2b7ad1255d1 Mon Sep 17 00:00:00 2001 From: cmfabbri Date: Thu, 2 Jul 2026 10:54:39 +0200 Subject: [PATCH 11/15] Replace bilby_pipe logger with dingo.pipe logger and call setup_logger --- dingo/pipe/data_generation.py | 6 ++++-- dingo/pipe/importance_sampling.py | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/dingo/pipe/data_generation.py b/dingo/pipe/data_generation.py index c8117027a..e465b3865 100644 --- a/dingo/pipe/data_generation.py +++ b/dingo/pipe/data_generation.py @@ -10,12 +10,12 @@ from bilby.core.prior import PriorDict from bilby_pipe.utils import ( parse_args, - logger, convert_string_to_dict, convert_prior_string_input, resolve_filename_with_transfer_fallback, BilbyPipeError, ) +import logging import lalsimulation as LS import numpy as np @@ -24,8 +24,9 @@ from dingo.gw.domains import UniformFrequencyDomain, build_domain_from_model_metadata from dingo.gw.injection import Injection from dingo.pipe.parser import create_parser +from dingo.core.utils.logging_utils import setup_logger -logger.name = "dingo_pipe" +logger = logging.getLogger("dingo.pipe") class DataGenerationInput(BilbyDataGenerationInput): @@ -534,6 +535,7 @@ def create_generation_parser(): def main(): """Data generation main logic""" args, unknown_args = parse_args(sys.argv[1:], create_generation_parser()) + setup_logger(outdir=args.outdir, label=args.label) # log_version_information() data = DataGenerationInput(args, unknown_args) data.save_hdf5() diff --git a/dingo/pipe/importance_sampling.py b/dingo/pipe/importance_sampling.py index 541e8ebb2..bb195a647 100644 --- a/dingo/pipe/importance_sampling.py +++ b/dingo/pipe/importance_sampling.py @@ -8,12 +8,14 @@ from bilby_pipe.input import Input from bilby_pipe.utils import ( parse_args, - logger, convert_string_to_dict, convert_prior_string_input, resolve_filename_with_transfer_fallback, BilbyPipeError, ) +import logging +from dingo.core.utils.logging_utils import setup_logger +logger = logging.getLogger("dingo.pipe") from dingo.gw.data.event_dataset import EventDataset from dingo.gw.domains import MultibandedFrequencyDomain @@ -21,8 +23,6 @@ from dingo.pipe.parser import create_parser from dingo.gw.result import Result -logger.name = "dingo_pipe" - class ImportanceSamplingInput(Input): def __init__(self, args, unknown_args): @@ -285,6 +285,7 @@ def create_sampling_parser(): def main(): """Data analysis main logic""" args, unknown_args = parse_args(sys.argv[1:], create_sampling_parser()) + setup_logger(outdir=args.outdir, label=args.label) # log_version_information() analysis = ImportanceSamplingInput(args, unknown_args) analysis.run_sampler() From 92651e472758b006875317a47a33c397a41cc052 Mon Sep 17 00:00:00 2001 From: cmfabbri Date: Thu, 2 Jul 2026 12:05:15 +0200 Subject: [PATCH 12/15] add logger name to log file --- dingo/core/utils/logging_utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dingo/core/utils/logging_utils.py b/dingo/core/utils/logging_utils.py index 0de9d14e8..dd21cd426 100644 --- a/dingo/core/utils/logging_utils.py +++ b/dingo/core/utils/logging_utils.py @@ -77,6 +77,12 @@ def setup_logger(outdir=None, label=None, log_level="INFO"): if bilby_logger is not None: for handler in bilby_logger.handlers: if isinstance(handler, logging.FileHandler): + handler.setFormatter( + logging.Formatter( + "%(asctime)s %(name)s %(levelname)-8s: %(message)s", + datefmt="%H:%M", + ) + ) if not any(isinstance(h, logging.FileHandler) for h in logger.handlers): logger.addHandler(handler) @@ -90,7 +96,7 @@ def setup_logger(outdir=None, label=None, log_level="INFO"): file_handler = logging.FileHandler(log_file) file_handler.setFormatter( logging.Formatter( - "%(asctime)s %(levelname)-8s: %(message)s", datefmt="%H:%M" + "%(asctime)s %(name)s %(levelname)-8s: %(message)s", datefmt="%H:%M" ) ) file_handler.setLevel(level) From 859bfee49928a023fc1fb299116a874a9dcf7ac8 Mon Sep 17 00:00:00 2001 From: Heinrich Campe <49278231+hcampe@users.noreply.github.com> Date: Fri, 3 Jul 2026 16:21:25 +0200 Subject: [PATCH 13/15] stages 1,2 done; code not fully working yet, but the structure is there --- HYDRA_TODO.md | 376 +++++++++++++----- configs/__init__.py | 0 configs/append_training_stage.yaml | 28 ++ configs/domain/multibanded_frequency.yaml | 8 + configs/domain/uniform_frequency.yaml | 4 + configs/estimate_psds.yaml | 33 ++ configs/evaluate_multibanded_domain.yaml | 13 + configs/experiment/asd_fiducial.yaml | 13 + configs/experiment/asd_full.yaml | 13 + configs/experiment/asd_toy.yaml | 13 + configs/experiment/generate_fmpe_dataset.yaml | 16 + configs/experiment/generate_gnpe_dataset.yaml | 16 + configs/experiment/generate_npe_dataset.yaml | 16 + configs/experiment/generate_toy_dataset.yaml | 14 + configs/experiment/importance_sampling.yaml | 16 + configs/experiment/synthetic_asd.yaml | 17 + configs/experiment/train_fmpe.yaml | 65 +++ configs/experiment/train_gnpe.yaml | 72 ++++ configs/experiment/train_gnpe_init.yaml | 57 +++ configs/experiment/train_npe.yaml | 65 +++ configs/experiment/train_toy.yaml | 45 +++ configs/extrinsic_prior/default.yaml | 5 + configs/extrinsic_prior/fmpe.yaml | 5 + configs/generate_asd_dataset.yaml | 35 ++ configs/generate_dataset.yaml | 15 + configs/generate_synthetic_asd_dataset.yaml | 33 ++ configs/hydra/default.yaml | 6 + configs/importance_weights.yaml | 47 +++ configs/intrinsic_prior/default.yaml | 10 + configs/intrinsic_prior/fmpe.yaml | 14 + configs/intrinsic_prior/precessing.yaml | 14 + .../precessing_multibanded_test.yaml | 14 + configs/merge_asd_datasets.yaml | 35 ++ configs/model/fmpe.yaml | 31 ++ configs/model/gnpe.yaml | 21 + configs/model/gnpe_init.yaml | 21 + configs/model/npe.yaml | 21 + configs/model/toy_npe.yaml | 21 + configs/model/unconditional_npe.yaml | 12 + configs/optimizer/adam.yaml | 2 + configs/scheduler/cosine.yaml | 2 + configs/train.yaml | 61 +++ configs/unconditional_density_estimation.yaml | 29 ++ configs/waveform_generator/phenom_d.yaml | 4 + configs/waveform_generator/phenom_pv2.yaml | 4 + configs/waveform_generator/phenom_xphm.yaml | 4 + .../unconditional_density_estimation.py | 44 +- dingo/core/posterior_models/base_model.py | 21 +- dingo/core/result.py | 43 +- dingo/core/samplers.py | 36 +- dingo/core/utils/pt_to_hdf5.py | 93 +++-- dingo/core/utils/trainutils.py | 46 ++- dingo/gw/SVD.py | 25 +- .../gw/dataset/evaluate_multibanded_domain.py | 89 ++--- dingo/gw/dataset/generate_dataset.py | 3 +- dingo/gw/dataset/utils.py | 41 +- dingo/gw/importance_sampling/diagnostics.py | 5 +- .../importance_sampling/importance_weights.py | 104 ++--- dingo/gw/inference/gw_samplers.py | 13 +- dingo/gw/ls_cli.py | 116 +++--- dingo/gw/noise/asd_dataset.py | 9 +- dingo/gw/noise/asd_estimation.py | 85 ++-- dingo/gw/noise/generate_dataset.py | 115 ++---- dingo/gw/noise/synthetic/asd_sampling.py | 12 +- dingo/gw/noise/synthetic/generate_dataset.py | 91 ++--- dingo/gw/noise/utils.py | 72 ++-- dingo/gw/result.py | 39 +- dingo/gw/training/train_builders.py | 37 +- dingo/gw/training/train_pipeline.py | 154 ++++--- dingo/gw/training/utils.py | 50 +-- dingo/gw/transforms/waveform_transforms.py | 8 +- .../waveform_generator/waveform_generator.py | 6 +- old_examples/__init__.py | 0 old_examples/append_training_stage.yaml | 28 ++ old_examples/estimate_psds.yaml | 29 ++ old_examples/evaluate_multibanded_domain.yaml | 45 +++ old_examples/generate_asd_dataset.yaml | 31 ++ .../generate_dataset.yaml | 0 .../generate_synthetic_asd_dataset.yaml | 31 ++ old_examples/importance_weights.yaml | 57 +++ old_examples/merge_asd_datasets.yaml | 31 ++ old_examples/train.yaml | 84 ++++ .../unconditional_density_estimation.yaml | 42 ++ pyproject.toml | 2 +- 84 files changed, 2356 insertions(+), 747 deletions(-) create mode 100644 configs/__init__.py create mode 100644 configs/append_training_stage.yaml create mode 100644 configs/domain/multibanded_frequency.yaml create mode 100644 configs/domain/uniform_frequency.yaml create mode 100644 configs/estimate_psds.yaml create mode 100644 configs/evaluate_multibanded_domain.yaml create mode 100644 configs/experiment/asd_fiducial.yaml create mode 100644 configs/experiment/asd_full.yaml create mode 100644 configs/experiment/asd_toy.yaml create mode 100644 configs/experiment/generate_fmpe_dataset.yaml create mode 100644 configs/experiment/generate_gnpe_dataset.yaml create mode 100644 configs/experiment/generate_npe_dataset.yaml create mode 100644 configs/experiment/generate_toy_dataset.yaml create mode 100644 configs/experiment/importance_sampling.yaml create mode 100644 configs/experiment/synthetic_asd.yaml create mode 100644 configs/experiment/train_fmpe.yaml create mode 100644 configs/experiment/train_gnpe.yaml create mode 100644 configs/experiment/train_gnpe_init.yaml create mode 100644 configs/experiment/train_npe.yaml create mode 100644 configs/experiment/train_toy.yaml create mode 100644 configs/extrinsic_prior/default.yaml create mode 100644 configs/extrinsic_prior/fmpe.yaml create mode 100644 configs/generate_asd_dataset.yaml create mode 100644 configs/generate_dataset.yaml create mode 100644 configs/generate_synthetic_asd_dataset.yaml create mode 100644 configs/hydra/default.yaml create mode 100644 configs/importance_weights.yaml create mode 100644 configs/intrinsic_prior/default.yaml create mode 100644 configs/intrinsic_prior/fmpe.yaml create mode 100644 configs/intrinsic_prior/precessing.yaml create mode 100644 configs/intrinsic_prior/precessing_multibanded_test.yaml create mode 100644 configs/merge_asd_datasets.yaml create mode 100644 configs/model/fmpe.yaml create mode 100644 configs/model/gnpe.yaml create mode 100644 configs/model/gnpe_init.yaml create mode 100644 configs/model/npe.yaml create mode 100644 configs/model/toy_npe.yaml create mode 100644 configs/model/unconditional_npe.yaml create mode 100644 configs/optimizer/adam.yaml create mode 100644 configs/scheduler/cosine.yaml create mode 100644 configs/train.yaml create mode 100644 configs/unconditional_density_estimation.yaml create mode 100644 configs/waveform_generator/phenom_d.yaml create mode 100644 configs/waveform_generator/phenom_pv2.yaml create mode 100644 configs/waveform_generator/phenom_xphm.yaml create mode 100644 old_examples/__init__.py create mode 100644 old_examples/append_training_stage.yaml create mode 100644 old_examples/estimate_psds.yaml create mode 100644 old_examples/evaluate_multibanded_domain.yaml create mode 100644 old_examples/generate_asd_dataset.yaml rename {configs/examples => old_examples}/generate_dataset.yaml (100%) create mode 100644 old_examples/generate_synthetic_asd_dataset.yaml create mode 100644 old_examples/importance_weights.yaml create mode 100644 old_examples/merge_asd_datasets.yaml create mode 100644 old_examples/train.yaml create mode 100644 old_examples/unconditional_density_estimation.yaml diff --git a/HYDRA_TODO.md b/HYDRA_TODO.md index ecc322c7a..f4186cf51 100644 --- a/HYDRA_TODO.md +++ b/HYDRA_TODO.md @@ -40,6 +40,17 @@ one pass. defaults in Hydra configs. - Revisit saved metadata format and backward compatibility deliberately. +## Stage 1 Scope + +Hydra Stage 1 covers scripts that consume substantial YAML/settings configs or +launch run-like workflows where a run directory, saved config, and Hydra +overrides are useful. + +Tiny inspection, conversion, compatibility, debug/demo, and maintenance scripts +with only a few command-line arguments are intentionally allowed to stay as +argparse scripts. They should still use logging for user-facing progress where +we touch them, but they do not need Hydra configs. + ## Stage 1 Definition Of Done For each migrated CLI: @@ -54,6 +65,13 @@ For each migrated CLI: existing business logic. - [ ] Settings saved into HDF5/PT/result metadata remain loadable. - [ ] User-facing progress messages use `logging` instead of `print`. +- [ ] Any `print(...)` statements reachable from the migrated CLI are replaced + by logging calls, including prints in helper modules called by that CLI. +- [ ] Python warnings emitted by migrated CLIs are routed through logging, e.g. + with `logging.captureWarnings(True)`, so they appear in both stdout and + the Hydra log consistently. +- [ ] Smoke tests compare captured stdout with the Hydra log file; they should + match exactly, confirming all progress output went through logging. - [ ] A small smoke test exists or is documented. Recommended shared pattern: @@ -65,7 +83,7 @@ import hydra from omegaconf import DictConfig, OmegaConf log = logging.getLogger(__name__) - +logging.captureWarnings(True) @hydra.main(version_base="1.3", config_path="...", config_name="...") def main(cfg: DictConfig) -> None: @@ -89,22 +107,107 @@ in the Hydra run directory together with `generate_dataset.log` and `.hydra/` metadata. The run root is controlled by the required `DINGO_RUNS` environment variable. +When running local Stage 1 smoke tests, set `DINGO_RUNS` to the repo-local +`my_runs/` directory so the generated run directories are easy to inspect: + +```bash +DINGO_RUNS=/home/hcampe/dingo/my_runs +``` + +For migrated CLIs, capture stdout during smoke tests and compare it against the +Hydra log file in the corresponding run directory. For example: + +```bash +diff -u captured_stdout.txt my_runs//.log +``` + If a user explicitly wants old current-working-directory behavior, use both: ```bash hydra.job.chdir=false hydra.run.dir=. ``` -Testing showed that `hydra.job.chdir=false` alone is not enough: application -outputs land in the current working directory, but Hydra logs and `.hydra/` -metadata still go to `hydra.run.dir`. - Use `compression: null` rather than `compression: None`. Do not migrate `.ini` files for now. In particular, leave Asimov/plugin-style `.ini` configuration outside the Hydra migration TODO unless this is reopened explicitly later. +## Known Follow-Ups + +- [ ] Decide on the long-term config package layout. For Stage 1, + `configs/` is made importable and included in setuptools package + discovery so console scripts can use ordinary relative + `@hydra.main(config_path=...)` values. + In Stage 2/3, decide whether to keep this top-level `configs` package, + move configs under `dingo/`, or introduce a shared config-search-path + helper/plugin. + +## Stage 1 Status + +Stage 1 is complete for the scoped set of settings/config-driven workflows. +Remaining parser-based scripts are explicitly excluded below because they are +small utilities, compatibility helpers, debug/demo mains, `.ini`-based pipe +commands, or Condor/DAG submission paths deferred to Stage 3. + +## Stage 2 Status + +Stage 2 has started. The Stage 1 `configs/examples/` folder was renamed to +`configs/old_examples/`, and migrated command configs now live directly under +`configs/`. + +Current group structure: + +- `domain` +- `waveform_generator` +- `intrinsic_prior` +- `extrinsic_prior` +- `model` +- `optimizer` +- `scheduler` +- `hydra` +- `experiment` + +The group configs contain default settings, including several defaults that were +previously implicit in examples or Python helper code. The `experiment` group +contains overrides for documented/example workflows such as toy NPE, NPE, GNPE, +GNPE initialization, FMPE, ASD variants, synthetic ASD generation, and +importance sampling. + +The single `domain/uniform_frequency.yaml` entry is now the high-resolution +uniform-frequency variant (`delta_f: 0.125`). Workflows that intentionally use +the old coarser domain, such as the toy waveform dataset example, override +`domain.delta_f` downstream. + +Posterior-model and embedding-model settings have been unified into a single +`model` group, with one file per model family. The toy-sized normalizing-flow +configuration from the documentation is named `model/toy_npe.yaml`; the +production NPE settings are named `model/npe.yaml`; the unconditional density +estimator used by density recovery/importance weighting is named +`model/unconditional_npe.yaml`. + +Stage 2 audit notes: + +- The old examples and documentation are now represented by experiment configs + for toy NPE, production NPE, GNPE, GNPE initialization, FMPE, ASD variants, + synthetic ASD generation, and importance sampling. +- The examples' explicit unconditional NDE settings are represented by + `model/unconditional_npe.yaml` and used by `importance_weights.yaml` and + `unconditional_density_estimation.yaml`. +- Training-stage `early_stopping: null` is explicit in the base stage and the + documented experiment stages. +- Condor resource blocks from production training examples are kept only as + settings data where they already existed in examples. Hydra-aware Condor + submission remains Stage 3 work. + +## Spotted Bugs + +- [ ] `dingo_build_svd`: the old `num_train is None` branch used + `len(WaveformDataset)`, i.e. the class object, which raises + `TypeError: object of type 'type' has no len()`. This was changed to + `len(dataset)`, the loaded instance length. Mention this deliberate bug + fix in the PR even though the CLI itself is no longer Hydra-migrated. + ## Entrypoint Inventory Public console scripts are defined in `pyproject.toml`. @@ -129,73 +232,112 @@ Public console scripts are defined in `pyproject.toml`. - Estimated Stage 1 difficulty: out of scope. - Estimated Stage 3 difficulty: high. -- [ ] `dingo_evaluate_multibanded_domain` +- [x] `dingo_evaluate_multibanded_domain` - File: `dingo/gw/dataset/evaluate_multibanded_domain.py` - - Stage 1: straightforward wrapper around current settings dict. + - Stage 1 implemented: `main` is wrapped with Hydra and uses + `configs/examples/evaluate_multibanded_domain.yaml`. + - This remains in scope because it has a structured domain, waveform + generator, prior, compression, and sample-count config. - Stage 3: replace `build_domain`, `build_prior_with_defaults`, and manual waveform-generator branching with Hydra targets. - Estimated Stage 1 difficulty: low. - Estimated Stage 3 difficulty: medium. -- [ ] `dingo_build_svd` +- [x] `dingo_build_svd` - File: `dingo/gw/dataset/utils.py` - - Stage 1: small CLI migration. + - Explicit Stage 1 exception: keep as a small argparse utility because it has + only a few CLI arguments and does not consume a full settings config. + - Still uses logging for user-facing progress. + - Keeps the deliberate bug fix in the old `num_train is None` path: use + `len(dataset)` rather than `len(WaveformDataset)`. - Stage 3: likely becomes part of a Hydra-instantiated compression/SVD workflow. - Estimated Stage 1 difficulty: low. - Estimated Stage 3 difficulty: medium. -- [ ] `dingo_merge_datasets` +- [x] `dingo_merge_datasets` - File: `dingo/gw/dataset/utils.py` - - Stage 1: small CLI migration. + - Explicit Stage 1 exception: keep as a small argparse utility. It has only a + few CLI arguments; the optional `settings_file` is a metadata override, not + a run-defining config. + - Uses logging for user-facing progress. - Stage 3: probably little to do. - Estimated Stage 1 difficulty: low. - Estimated Stage 3 difficulty: low. ### Noise / ASD Generation -- [ ] `dingo_generate_asd_dataset` +- [x] `dingo_generate_asd_dataset` - File: `dingo/gw/noise/generate_dataset.py` - - Stage 1: replace parser/settings-file handling with Hydra. - - Stage 1 should cover local generation only. - - Condor/DAG behavior should remain as-is or be deferred. + - Stage 1 implemented for local generation: `generate_dataset` is wrapped + directly with Hydra and uses `configs/examples/generate_asd_dataset.yaml`. + - Parser/settings-file handling was replaced by a shallow Hydra config that + preserves the old settings dict shape internally. + - Condor/DAG behavior is explicitly deferred to Stage 3 under Hydra. + - Uses `logging.captureWarnings(True)` and logs user-facing progress instead + of printing. + - Smoke test output is in + `my_runs/2026-07-01_14-43-30_generate_asd_dataset_dataset_settings.T=1.0,dataset_settings.detectors=[H1,L1],dataset_settings.f_s=16,dataset_settings.observing_run=O1,dataset_settings.time_psd=4`. - Estimated Stage 1 difficulty: medium. - Estimated Stage 3 difficulty: medium/high for Condor integration. -- [ ] `dingo_estimate_psds` +- [x] `dingo_estimate_psds` - File: `dingo/gw/noise/asd_estimation.py` - - Stage 1: straightforward wrapper around current dict settings. + - Stage 1 implemented: `download_and_estimate_cli` is wrapped directly with + Hydra and uses `configs/examples/estimate_psds.yaml`. + - The existing `download_and_estimate_psds(...)` dict-based implementation is + unchanged. + - Uses `logging.captureWarnings(True)`. + - Smoke test output is in + `my_runs/2026-07-01_14-55-15_estimate_psds_dataset_settings.T=1.0,dataset_settings.detectors=[H1,L1],dataset_settings.f_s=16,dataset_settings.observing_run=O1,dataset_settings.time_psd=4`. - Stage 3: domain construction and PSD-estimation settings could become more declarative. - Estimated Stage 1 difficulty: low/medium. - Estimated Stage 3 difficulty: medium. -- [ ] `dingo_generate_synthetic_asd_dataset` +- [x] `dingo_generate_synthetic_asd_dataset` - File: `dingo/gw/noise/synthetic/generate_dataset.py` - - Stage 1: straightforward config migration. + - Stage 1 implemented: `main` is wrapped with Hydra and uses + `configs/examples/generate_synthetic_asd_dataset.yaml`. + - The existing `generate_dataset(...)` implementation remains dict-based. + - Uses `logging.captureWarnings(True)`. + - Smoke test output is in + `my_runs/2026-07-01_14-44-49_generate_synthetic_asd_dataset_parameterization_settings.num_spectral_segments=2,parameterization_settings.num_spline_positions=3,sampling_settings=null`. - Stage 3: parameterization and sampling could become separate config groups. - Estimated Stage 1 difficulty: low. - Estimated Stage 3 difficulty: medium. -- [ ] `dingo_merge_asd_datasets` +- [x] `dingo_merge_asd_datasets` - File: `dingo/gw/noise/utils.py` - - Stage 1: small CLI migration. + - Stage 1 implemented: `merge_datasets_cli` is wrapped directly with Hydra + and uses `configs/examples/merge_asd_datasets.yaml`. + - Uses `logging.captureWarnings(True)` and logs user-facing progress instead + of printing. + - Smoke test output is in + `my_runs/2026-07-01_14-43-31_merge_asd_datasets_dataset_settings.T=1.0,dataset_settings.detectors=[H1,L1],dataset_settings.f_s=16,dataset_settings.observing_run=O1,dataset_settings.time_psd=4`. - Estimated Stage 1 difficulty: low. - Estimated Stage 3 difficulty: low. ### Training -- [ ] `dingo_train` +- [x] `dingo_train` - File: `dingo/gw/training/train_pipeline.py` - - Stage 1: replace parser with Hydra, while still converting to the old - `train_settings` and `local_settings` dicts. - - Watch out: - - New training versus checkpoint resume are currently mutually exclusive CLI - modes. - - `local` settings are popped out and saved separately as - `local_settings.yaml`. - - Train settings are saved into model metadata. - - Several progress messages still use `print`. + - Stage 1 implemented: `train_local` is wrapped directly with Hydra and uses + `configs/examples/train.yaml`. + - New training uses the Hydra config body as the old `train_settings` dict; + checkpoint resume uses `checkpoint=`. + - `local` settings are still popped out and saved separately as + `local_settings.yaml`. + - Input dataset paths are resolved before training so `hydra.job.chdir=true` + does not break file loading; these resolved paths are saved in model + metadata. + - Training progress reachable from this CLI now uses logging instead of + printing, including `train_builders`, `BasePosteriorModel.train`, and + `trainutils`. + - Training omits `hydra.job.override_dirname` from its run directory template + because real training overrides are commonly too large for filesystem-safe + paths. + - Smoke test output is in `my_runs/2026-07-01_15-26-09_train`. - Estimated Stage 1 difficulty: medium/high. - Estimated Stage 3 difficulty: high. @@ -210,83 +352,121 @@ Public console scripts are defined in `pyproject.toml`. - Estimated Stage 1 difficulty: out of scope. - Estimated Stage 3 difficulty: high. -- [ ] `dingo_append_training_stage` +- [x] `dingo_append_training_stage` - File: `dingo/gw/training/utils.py` - - Stage 1: small Hydra CLI. - - Watch out: modifies model metadata/training-stage settings. + - Stage 1 implemented: `append_stage` is wrapped directly with Hydra and + uses `configs/examples/append_training_stage.yaml`. + - The new stage is provided directly in the Hydra config under `stage`. + - Modifies model metadata/training-stage settings as before. + - Smoke test output is in + `my_runs/2026-07-01_15-26-55_append_training_stage_stage.batch_size=1,stage.epochs=2`. - Estimated Stage 1 difficulty: low. - Estimated Stage 3 difficulty: low/medium. ### Pipe / Inference -The `dingo_pipe*` family is the largest migration area because it is based on -`BilbyArgParser` / `configargparse` and has bilby_pipe-style conventions. - -- [ ] `dingo_pipe` - - File: `dingo/pipe/main.py` - - Uses parser from `dingo/pipe/parser.py`. - - Estimated Stage 1 difficulty: high. - - Estimated Stage 3 difficulty: very high. - -- [ ] `dingo_pipe_generation` - - File: `dingo/pipe/data_generation.py` - - Estimated Stage 1 difficulty: high. - - Estimated Stage 3 difficulty: high. - -- [ ] `dingo_pipe_sampling` - - File: `dingo/pipe/sampling.py` - - Estimated Stage 1 difficulty: high. - - Estimated Stage 3 difficulty: high. - -- [ ] `dingo_pipe_importance_sampling` - - File: `dingo/pipe/importance_sampling.py` - - Estimated Stage 1 difficulty: high. - - Estimated Stage 3 difficulty: high. - -- [ ] `dingo_pipe_plot` - - File: `dingo/pipe/plot.py` - - Estimated Stage 1 difficulty: medium/high. - - Estimated Stage 3 difficulty: medium. - -- [ ] `dingo_pipe_pp_test` - - File: `dingo/pipe/pp_test.py` - - Estimated Stage 1 difficulty: low/medium. - - Estimated Stage 3 difficulty: medium. - -- [ ] `dingo_result` - - File: `dingo/pipe/dingo_result.py` - - Small CLI, but connected to pipe output conventions. - - Estimated Stage 1 difficulty: low. - - Estimated Stage 3 difficulty: low. +Explicitly out of scope for this Hydra migration for now. Keep the +`dingo_pipe*` family, `dingo_result`, and their `.ini` / bilby_pipe-style +configuration conventions as they are unless this decision is reopened later. ### Other Utilities -- [ ] `dingo_pt_to_hdf5` +- [x] `dingo_pt_to_hdf5` - File: `dingo/core/utils/pt_to_hdf5.py` - - Small argparse CLI. + - Explicit Stage 1 exception: keep as a small argparse conversion utility + because it has only `in_file`, `out_file`, and `model_version_number`. + - Uses `logging.captureWarnings(True)` and stream-only logging instead of + printing, without Hydra and without saving a log file. - Estimated Stage 1 difficulty: low. - Estimated Stage 3 difficulty: low. -- [ ] `dingo_ls` +- [x] `dingo_ls` - File: `dingo/gw/ls_cli.py` - - Inspection tool rather than a config-driven workflow. - - Could stay argparse for a while, but migrate for consistency if Stage 1 aims - to eliminate all public parsers. + - Explicit Stage 1 exception: keep this as a small argparse inspection script + because it has only one positional argument and is not a config-driven + workflow. + - Uses `logging.captureWarnings(True)` and stream-only logging instead of + printing, without Hydra and without saving a log file. The stream formatter + matches the standard Hydra log format. + - `Result.print_summary()` was updated to use logging because it is reachable + from this CLI. + - Smoke test output was captured in + `/tmp/dingo_ls_stdout_standard_format.out`. - Estimated Stage 1 difficulty: low. - Estimated Stage 3 difficulty: low. -- [ ] Importance-weight generation +- [x] Importance-weight generation - File: `dingo/gw/importance_sampling/importance_weights.py` - - Not currently listed in `project.scripts`, but it is a config-driven CLI. + - Stage 1 implemented: `main` is wrapped with Hydra and uses + `configs/examples/importance_weights.yaml`. + - The old `--settings` / YAML-loading path was removed; the Hydra config body + is converted to a plain settings dict internally. + - Input parameter-sample and calibration-envelope paths are resolved before + use so `hydra.job.chdir=true` does not break file loading. + - User-facing progress reachable from this CLI now uses logging instead of + printing, including sampler progress, result summaries, and diagnostics. + - Config-composition smoke was captured in + `/tmp/dingo_importance_weights_cfg.out`. A full run still needs a + physically meaningful GW result and likelihood setup. - Estimated Stage 1 difficulty: low/medium. - Estimated Stage 3 difficulty: medium. -- [ ] Unconditional density estimation +- [x] Unconditional density estimation - File: `dingo/core/density/unconditional_density_estimation.py` - - Not currently listed in `project.scripts`, but has an argparse settings CLI. + - Stage 1 implemented: the stale `--settings` parser was replaced by a Hydra + entry point using `configs/examples/unconditional_density_estimation.yaml`. + - The Hydra config takes `result_file`, `train_dir`, and the old estimator + settings; internally the estimator still receives a plain settings dict. + - Smoke test output is in + `my_runs/unconditional_density_estimation_stage1_smoke`. - Estimated Stage 1 difficulty: low. - Estimated Stage 3 difficulty: medium. +### Explicitly Not Ported To Hydra + +These are intentionally excluded from Stage 1. Mention them in the PR so it is +clear this is a scoped migration rather than an incomplete sweep. + +- Condor / DAG submission: + - `dingo_generate_dataset_dag` + (`dingo/gw/dataset/generate_dataset_dag.py`) + - `dingo_train_condor` + (`dingo/gw/training/train_pipeline_condor.py`) + - Noise-generation DAG helper + (`dingo/gw/noise/generate_dataset_dag.py`) + - `dingo/core/utils/condor_utils.py` + - Rationale: Condor integration belongs in Stage 3, ideally as a + Hydra-aware submission path rather than shallow argparse replacement. +- Pipe / `.ini` workflows: + - `dingo_pipe` + - `dingo_pipe_generation` + - `dingo_pipe_sampling` + - `dingo_pipe_importance_sampling` + - `dingo_pipe_plot` + - `dingo_pipe_pp_test` + - `dingo_result` + - Rationale: keep bilby_pipe-style `.ini` conventions for now. +- Small public utilities kept as argparse: + - `dingo_ls` + - `dingo_pt_to_hdf5` + - `dingo_build_svd` + - `dingo_merge_datasets` + - Rationale: only a few command-line arguments and no full run-defining + settings config. These may still use logging for progress output. +- Compatibility scripts: + - `compatibility/*.py` + - Rationale: one-off saved-artifact migration helpers. +- Miscellaneous maintenance scripts: + - `misc_scripts/*.py` + - Rationale: ad hoc plotting, monitoring, evaluation, and maintenance tools. + Some take a settings file, but they are not public run-launching workflows. +- Debug/demo module mains: + - `dingo/gw/likelihood.py` + - `dingo/gw/waveform_generator/waveform_generator.py` + - `dingo/core/nn/enets.py` + - `dingo/core/nn/nsf.py` + - Rationale: local debug/demo entry points, not user-facing config workflows. + ## Stage 2 Config Group Candidates - `domain` @@ -297,13 +477,10 @@ The `dingo_pipe*` family is the largest migration area because it is based on - `dataset` - `asd_dataset` - `noise` -- `model/posterior` -- `model/embedding` +- `model` - `training/stage` - `training/local` - `condor` (Stage 3 only) -- `pipe/event` -- `pipe/sampler` - `importance_sampling` - `defaults` - `defaults/prior` @@ -338,14 +515,33 @@ The `dingo_pipe*` family is the largest migration area because it is based on as long as old code handles them. - Candidate areas: - waveform generator optional kwargs such as `f_start`, `mode_list`, - `spin_conversion_phase`, and interface-specific options + `spin_conversion_phase`, `new_interface`, `lmax_nyquist`, and + interface-specific options - dataset compression/SVD/whitening options - training local/runtime/checkpoint options - optimizer and scheduler defaults - - transform defaults in the training data pipeline + - transform defaults in the training data pipeline, including + `zero_noise: false`, optional `random_strain_cropping`, derived + `context_parameters`, and derived `standardization` - pipe/inference defaults currently supplied by parser defaults - ASD/noise generation defaults such as frequency range, channels, and Condor settings + - dataloader defaults such as fixed split seed, `pin_memory`, worker seed + initialization, and `persistent_workers` + - early stopping defaults (`patience`, `verbose`, `delta`, `metric`) + - runtime limit defaults (`max_time_per_run`, `max_epochs_per_run`, + `max_epochs_total`, `epoch_start`) + +- [ ] `dingo/core/density/nde_settings.py` + - Move or retire `get_default_nde_settings_3d(...)` and + `DEFAULT_NDE_SETTINGS_2D`. + - These currently define additional unconditional NDE architectures: + - 3D: 5 flow steps, hidden dimension 128, batch size 4096, 10 epochs, + Adam learning rate 0.005, cosine `T_max: 10`. + - 2D GNPE proxy recovery: 5 flow steps, hidden dimension 64, batch size + 4096, 10 epochs, Adam learning rate 0.001, cosine `T_max: 10`. + - Decide whether these become separate `model/*` and training config groups, + or whether they are superseded by `model/unconditional_npe.yaml`. - [ ] `dingo/gw/domains/build_domain.py` - Replace `type`-based dispatch with Hydra instantiation. @@ -400,6 +596,8 @@ The `dingo_pipe*` family is the largest migration area because it is based on - `ProxyRecoveryDefault` - `PhaseRecoveryDefault` - `MultibandingDefault` + - These are intentionally deferred while `.ini`/pipe workflows remain outside + the Stage 1/2 Hydra migration. - [ ] `dingo/core/density/nde_settings.py` - Move `get_default_nde_settings_3d(...)` and `DEFAULT_NDE_SETTINGS_2D` into diff --git a/configs/__init__.py b/configs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/configs/append_training_stage.yaml b/configs/append_training_stage.yaml new file mode 100644 index 000000000..88bcd1c58 --- /dev/null +++ b/configs/append_training_stage.yaml @@ -0,0 +1,28 @@ +defaults: + - hydra: default + - optimizer: adam + - scheduler: cosine + - _self_ + +checkpoint: ??? +out_file: updated_model.pt +replace: null + +stage: + optimizer: ${optimizer} + scheduler: ${scheduler} + epochs: 20 + asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + freeze_rb_layer: false + batch_size: 64 + early_stopping: null + +hydra: + job: + name: append_training_stage + config: + override_dirname: + exclude_keys: + - checkpoint + - out_file + - stage.asd_dataset_path diff --git a/configs/domain/multibanded_frequency.yaml b/configs/domain/multibanded_frequency.yaml new file mode 100644 index 000000000..9b9289d68 --- /dev/null +++ b/configs/domain/multibanded_frequency.yaml @@ -0,0 +1,8 @@ +type: MultibandedFrequencyDomain +nodes: [20.0, 26.0, 34.0, 46.0, 62.0, 78.0, 1038.0] +delta_f_initial: 0.0625 +base_domain: + type: UniformFrequencyDomain + f_min: 20.0 + f_max: 1037.9375 + delta_f: 0.0625 diff --git a/configs/domain/uniform_frequency.yaml b/configs/domain/uniform_frequency.yaml new file mode 100644 index 000000000..cb44035cd --- /dev/null +++ b/configs/domain/uniform_frequency.yaml @@ -0,0 +1,4 @@ +type: UniformFrequencyDomain +f_min: 20.0 +f_max: 1024.0 +delta_f: 0.125 diff --git a/configs/estimate_psds.yaml b/configs/estimate_psds.yaml new file mode 100644 index 000000000..36e58ca2f --- /dev/null +++ b/configs/estimate_psds.yaml @@ -0,0 +1,33 @@ +defaults: + - hydra: default + - _self_ + +data_dir: asd_dataset +time_segments_file: ??? +verbose: false + +dataset_settings: + f_min: 0 + f_max: 2048 + f_s: 4096 + time_psd: 1024 + T: 8.0 + window: + roll_off: 0.4 + type: tukey + time_gap: 0 + num_psds_max: 1 + channels: null + detectors: + - H1 + - L1 + observing_run: O1 + +hydra: + job: + name: estimate_psds + config: + override_dirname: + exclude_keys: + - data_dir + - time_segments_file diff --git a/configs/evaluate_multibanded_domain.yaml b/configs/evaluate_multibanded_domain.yaml new file mode 100644 index 000000000..f372aa01f --- /dev/null +++ b/configs/evaluate_multibanded_domain.yaml @@ -0,0 +1,13 @@ +defaults: + - hydra: default + - domain: multibanded_frequency + - waveform_generator: phenom_xphm + - intrinsic_prior: precessing_multibanded_test + - _self_ + +num_samples: 1 +compression: null + +hydra: + job: + name: evaluate_multibanded_domain diff --git a/configs/experiment/asd_fiducial.yaml b/configs/experiment/asd_fiducial.yaml new file mode 100644 index 000000000..d69d57528 --- /dev/null +++ b/configs/experiment/asd_fiducial.yaml @@ -0,0 +1,13 @@ +# @package _global_ + +dataset_settings: + f_s: 4096 + time_psd: 1024 + T: 8.0 + window: + roll_off: 0.4 + type: tukey + time_gap: 0 + num_psds_max: 1 + detectors: [H1, L1] + observing_run: O1 diff --git a/configs/experiment/asd_full.yaml b/configs/experiment/asd_full.yaml new file mode 100644 index 000000000..ca2a2704b --- /dev/null +++ b/configs/experiment/asd_full.yaml @@ -0,0 +1,13 @@ +# @package _global_ + +dataset_settings: + f_s: 4096 + time_psd: 1024 + T: 8.0 + window: + roll_off: 0.4 + type: tukey + time_gap: 0 + num_psds_max: 0 + detectors: [H1, L1] + observing_run: O1 diff --git a/configs/experiment/asd_toy.yaml b/configs/experiment/asd_toy.yaml new file mode 100644 index 000000000..b1e86708e --- /dev/null +++ b/configs/experiment/asd_toy.yaml @@ -0,0 +1,13 @@ +# @package _global_ + +dataset_settings: + f_s: 4096 + time_psd: 1024 + T: 4.0 + window: + roll_off: 0.4 + type: tukey + time_gap: 0 + num_psds_max: 1 + detectors: [H1, L1] + observing_run: O1 diff --git a/configs/experiment/generate_fmpe_dataset.yaml b/configs/experiment/generate_fmpe_dataset.yaml new file mode 100644 index 000000000..a62c125fe --- /dev/null +++ b/configs/experiment/generate_fmpe_dataset.yaml @@ -0,0 +1,16 @@ +# @package _global_ + +defaults: + - override /domain: uniform_frequency + - override /waveform_generator: phenom_pv2 + - override /intrinsic_prior: fmpe + - _self_ + +num_samples: 5000000 +compression: + svd: + size: 200 + num_training_samples: 50000 + num_validation_samples: 10000 + whitening: aLIGO_ZERO_DET_high_P_asd.txt +out_file: training_data/waveform_dataset.hdf5 diff --git a/configs/experiment/generate_gnpe_dataset.yaml b/configs/experiment/generate_gnpe_dataset.yaml new file mode 100644 index 000000000..13cd02db9 --- /dev/null +++ b/configs/experiment/generate_gnpe_dataset.yaml @@ -0,0 +1,16 @@ +# @package _global_ + +defaults: + - override /domain: uniform_frequency + - override /waveform_generator: phenom_xphm + - override /intrinsic_prior: precessing + - _self_ + +num_samples: 5000000 +compression: + svd: + size: 200 + num_training_samples: 50000 + num_validation_samples: 10000 + whitening: aLIGO_ZERO_DET_high_P_asd.txt +out_file: training_data/waveform_dataset.hdf5 diff --git a/configs/experiment/generate_npe_dataset.yaml b/configs/experiment/generate_npe_dataset.yaml new file mode 100644 index 000000000..ebc141c8e --- /dev/null +++ b/configs/experiment/generate_npe_dataset.yaml @@ -0,0 +1,16 @@ +# @package _global_ + +defaults: + - override /domain: uniform_frequency + - override /waveform_generator: phenom_xphm + - override /intrinsic_prior: default + - _self_ + +num_samples: 5000000 +compression: + svd: + size: 200 + num_training_samples: 50000 + num_validation_samples: 10000 + whitening: aLIGO_ZERO_DET_high_P_asd.txt +out_file: training_data/waveform_dataset.hdf5 diff --git a/configs/experiment/generate_toy_dataset.yaml b/configs/experiment/generate_toy_dataset.yaml new file mode 100644 index 000000000..386dac175 --- /dev/null +++ b/configs/experiment/generate_toy_dataset.yaml @@ -0,0 +1,14 @@ +# @package _global_ + +defaults: + - override /domain: uniform_frequency + - override /waveform_generator: phenom_d + - override /intrinsic_prior: default + - _self_ + +domain: + delta_f: 0.25 +num_samples: 10000 +compression: null +num_processes: 1 +out_file: waveform_dataset.hdf5 diff --git a/configs/experiment/importance_sampling.yaml b/configs/experiment/importance_sampling.yaml new file mode 100644 index 000000000..0ab8eb7c2 --- /dev/null +++ b/configs/experiment/importance_sampling.yaml @@ -0,0 +1,16 @@ +# @package _global_ + +num_samples: 1000 +time_marginalization: + n_fft: 5 +slice_plots: + num_slice_plots: 10 + params_slice2d: + - [phase, geocent_time] + - [phase, tilt_1] +calibration_marginalization: + num_calibration_curves: 100 + num_calibration_nodes: 10 + calibration_envelope: + H1: /path/to/H1-calibration-envelope + L1: /path/to/L1-calibration-envelope diff --git a/configs/experiment/synthetic_asd.yaml b/configs/experiment/synthetic_asd.yaml new file mode 100644 index 000000000..91782b5bc --- /dev/null +++ b/configs/experiment/synthetic_asd.yaml @@ -0,0 +1,17 @@ +# @package _global_ + +parameterization_settings: + num_spline_positions: 30 + num_spectral_segments: 400 + sigma: 0.14 + delta_f: -1 + smoothen: true +sampling_settings: + bandwidth_spectral: 0.5 + bandwidth_spline: 0.25 + split_frequencies: + - 30 + - 100 + rescaling_asd_paths: + H1: /path/to/rescaling_asd_H1.hdf5 + L1: /path/to/rescaling_asd_L1.hdf5 diff --git a/configs/experiment/train_fmpe.yaml b/configs/experiment/train_fmpe.yaml new file mode 100644 index 000000000..84eaca337 --- /dev/null +++ b/configs/experiment/train_fmpe.yaml @@ -0,0 +1,65 @@ +# @package _global_ + +defaults: + - override /extrinsic_prior: fmpe + - override /model: fmpe + - _self_ + +data: + waveform_dataset_path: training_data/waveform_dataset.hdf5 + train_fraction: 0.95 + domain_update: + f_min: 20.0 + f_max: 512.0 + svd_size_update: 150 + detectors: [H1, L1] + ref_time: 1126259462.391 + inference_parameters: + - chirp_mass + - mass_ratio + - a_1 + - a_2 + - tilt_1 + - tilt_2 + - phi_12 + - phi_jl + - theta_jn + - luminosity_distance + - geocent_time + - ra + - dec + - psi + +training: + evaluate: true + stage_0: + epochs: 400 + asd_dataset_path: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 + freeze_rb_layer: true + batch_size: 4096 + early_stopping: null + optimizer: + lr: 0.0005 + scheduler: + T_max: 400 + stage_1: + epochs: 100 + asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + freeze_rb_layer: true + batch_size: 4096 + early_stopping: null + optimizer: + type: adam + lr: 1.0e-5 + scheduler: + type: cosine + T_max: 100 + +local: + device: cuda + num_workers: 31 + runtime_limits: + max_time_per_run: 360000 + max_epochs_per_run: 500 + checkpoint_epochs: 200 + leave_waveforms_on_disk: true diff --git a/configs/experiment/train_gnpe.yaml b/configs/experiment/train_gnpe.yaml new file mode 100644 index 000000000..d3342ae27 --- /dev/null +++ b/configs/experiment/train_gnpe.yaml @@ -0,0 +1,72 @@ +# @package _global_ + +defaults: + - override /model: gnpe + - _self_ + +data: + waveform_dataset_path: training_data/waveform_dataset.hdf5 + train_fraction: 0.95 + domain_update: + f_min: 20.0 + f_max: 1024.0 + svd_size_update: 200 + detectors: [H1, L1] + ref_time: 1126259462.391 + gnpe_time_shifts: + kernel: bilby.core.prior.Uniform(minimum=-0.001, maximum=0.001) + exact_equiv: true + inference_parameters: + - chirp_mass + - mass_ratio + - a_1 + - a_2 + - tilt_1 + - tilt_2 + - phi_12 + - phi_jl + - theta_jn + - luminosity_distance + - geocent_time + - ra + - dec + - psi + +training: + stage_0: + epochs: 300 + asd_dataset_path: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 + freeze_rb_layer: true + batch_size: 4096 + early_stopping: null + optimizer: + lr: 5.0e-5 + scheduler: + T_max: 300 + stage_1: + epochs: 150 + asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + freeze_rb_layer: false + batch_size: 4096 + early_stopping: null + optimizer: + type: adam + lr: 1.0e-5 + scheduler: + type: cosine + T_max: 150 + +local: + device: cuda + num_workers: 32 + runtime_limits: + max_time_per_run: 3600000 + max_epochs_per_run: 500 + checkpoint_epochs: 10 + leave_waveforms_on_disk: true + condor: + num_cpus: 16 + memory_cpus: 128000 + num_gpus: 1 + memory_gpus: 8000 + request_disk: 50GB diff --git a/configs/experiment/train_gnpe_init.yaml b/configs/experiment/train_gnpe_init.yaml new file mode 100644 index 000000000..f47dd2cba --- /dev/null +++ b/configs/experiment/train_gnpe_init.yaml @@ -0,0 +1,57 @@ +# @package _global_ + +defaults: + - override /model: gnpe_init + - _self_ + +data: + waveform_dataset_path: training_data/waveform_dataset.hdf5 + train_fraction: 0.95 + domain_update: + f_min: 20.0 + f_max: 1024.0 + svd_size_update: 200 + detectors: [H1, L1] + ref_time: 1126259462.391 + inference_parameters: + - H1_time + - L1_time + +training: + stage_0: + epochs: 150 + asd_dataset_path: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 + freeze_rb_layer: true + batch_size: 4096 + early_stopping: null + optimizer: + lr: 5.0e-5 + scheduler: + T_max: 150 + stage_1: + epochs: 75 + asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + freeze_rb_layer: false + batch_size: 4096 + early_stopping: null + optimizer: + type: adam + lr: 1.0e-5 + scheduler: + type: cosine + T_max: 75 + +local: + device: cpu + num_workers: 6 + runtime_limits: + max_time_per_run: 3600000 + max_epochs_per_run: 500 + checkpoint_epochs: 10 + leave_waveforms_on_disk: true + condor: + num_cpus: 16 + memory_cpus: 128000 + num_gpus: 1 + memory_gpus: 8000 + request_disk: 50GB diff --git a/configs/experiment/train_npe.yaml b/configs/experiment/train_npe.yaml new file mode 100644 index 000000000..0ecd913ec --- /dev/null +++ b/configs/experiment/train_npe.yaml @@ -0,0 +1,65 @@ +# @package _global_ + +defaults: + - override /model: npe + - _self_ + +data: + waveform_dataset_path: training_data/waveform_dataset.hdf5 + train_fraction: 0.95 + domain_update: + f_min: 20.0 + f_max: 1024.0 + svd_size_update: 200 + detectors: [H1, L1] + ref_time: 1126259462.391 + inference_parameters: + - chirp_mass + - mass_ratio + - chi_1 + - chi_2 + - theta_jn + - dec + - ra + - geocent_time + - luminosity_distance + - psi + +training: + stage_0: + epochs: 300 + asd_dataset_path: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 + freeze_rb_layer: true + batch_size: 4096 + early_stopping: null + optimizer: + lr: 5.0e-5 + scheduler: + T_max: 300 + stage_1: + epochs: 150 + asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + freeze_rb_layer: false + batch_size: 4096 + early_stopping: null + optimizer: + type: adam + lr: 1.0e-5 + scheduler: + type: cosine + T_max: 150 + +local: + device: cuda + num_workers: 32 + runtime_limits: + max_time_per_run: 3600000 + max_epochs_per_run: 500 + checkpoint_epochs: 50 + leave_waveforms_on_disk: true + condor: + num_cpus: 16 + memory_cpus: 128000 + num_gpus: 1 + memory_gpus: 8000 + request_disk: 50GB diff --git a/configs/experiment/train_toy.yaml b/configs/experiment/train_toy.yaml new file mode 100644 index 000000000..687fad524 --- /dev/null +++ b/configs/experiment/train_toy.yaml @@ -0,0 +1,45 @@ +# @package _global_ + +defaults: + - override /model: toy_npe + - _self_ + +data: + waveform_dataset_path: training_data/waveform_dataset.hdf5 + train_fraction: 0.95 + domain_update: null + svd_size_update: null + detectors: [H1, L1] + ref_time: 1126259462.391 + inference_parameters: + - chirp_mass + - mass_ratio + - chi_1 + - chi_2 + - theta_jn + - dec + - ra + - geocent_time + - luminosity_distance + - psi + - phase + +training: + stage_0: + epochs: 20 + asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + freeze_rb_layer: true + batch_size: 64 + early_stopping: null + optimizer: + lr: 0.0001 + scheduler: + T_max: 20 + +local: + device: cpu + num_workers: 6 + runtime_limits: + max_time_per_run: 3600000 + max_epochs_per_run: 30 + checkpoint_epochs: 15 diff --git a/configs/extrinsic_prior/default.yaml b/configs/extrinsic_prior/default.yaml new file mode 100644 index 000000000..702057e6d --- /dev/null +++ b/configs/extrinsic_prior/default.yaml @@ -0,0 +1,5 @@ +dec: default +ra: default +geocent_time: bilby.core.prior.Uniform(minimum=-0.10, maximum=0.10, name='geocent_time') +psi: default +luminosity_distance: bilby.core.prior.Uniform(minimum=100.0, maximum=1000.0, name='luminosity_distance') diff --git a/configs/extrinsic_prior/fmpe.yaml b/configs/extrinsic_prior/fmpe.yaml new file mode 100644 index 000000000..4d79f9f7e --- /dev/null +++ b/configs/extrinsic_prior/fmpe.yaml @@ -0,0 +1,5 @@ +dec: default +ra: default +geocent_time: bilby.core.prior.Uniform(minimum=-0.03, maximum=0.03, name='geocent_time') +psi: default +luminosity_distance: bilby.core.prior.Uniform(minimum=100.0, maximum=1000.0, name='luminosity_distance') diff --git a/configs/generate_asd_dataset.yaml b/configs/generate_asd_dataset.yaml new file mode 100644 index 000000000..aba442e62 --- /dev/null +++ b/configs/generate_asd_dataset.yaml @@ -0,0 +1,35 @@ +defaults: + - hydra: default + - _self_ + +data_dir: asd_dataset +time_segments_file: null +out_name: null +verbose: false + +dataset_settings: + f_min: 0 + f_max: 2048 + f_s: 4096 + time_psd: 1024 + T: 8.0 + window: + roll_off: 0.4 + type: tukey + time_gap: 0 + num_psds_max: 1 + channels: null + detectors: + - H1 + - L1 + observing_run: O1 + +hydra: + job: + name: generate_asd_dataset + config: + override_dirname: + exclude_keys: + - data_dir + - time_segments_file + - out_name diff --git a/configs/generate_dataset.yaml b/configs/generate_dataset.yaml new file mode 100644 index 000000000..05838a47b --- /dev/null +++ b/configs/generate_dataset.yaml @@ -0,0 +1,15 @@ +defaults: + - hydra: default + - domain: uniform_frequency + - waveform_generator: phenom_d + - intrinsic_prior: default + - _self_ + +num_samples: 10000 +compression: null +num_processes: 1 +out_file: waveform_dataset.hdf5 + +hydra: + job: + name: generate_dataset diff --git a/configs/generate_synthetic_asd_dataset.yaml b/configs/generate_synthetic_asd_dataset.yaml new file mode 100644 index 000000000..71070af80 --- /dev/null +++ b/configs/generate_synthetic_asd_dataset.yaml @@ -0,0 +1,33 @@ +defaults: + - hydra: default + - _self_ + +asd_dataset: ??? +num_samples: 500 +num_processes: 1 +out_file: synthetic_asd_dataset.hdf5 +verbose: false + +parameterization_settings: + num_spline_positions: 30 + num_spectral_segments: 400 + sigma: 0.14 + delta_f: -1 + smoothen: true + +sampling_settings: + bandwidth_spectral: 0.5 + bandwidth_spline: 0.25 + split_frequencies: + - 30 + - 100 + rescaling_asd_paths: null + +hydra: + job: + name: generate_synthetic_asd_dataset + config: + override_dirname: + exclude_keys: + - asd_dataset + - out_file diff --git a/configs/hydra/default.yaml b/configs/hydra/default.yaml new file mode 100644 index 000000000..7b9f91d97 --- /dev/null +++ b/configs/hydra/default.yaml @@ -0,0 +1,6 @@ +# @package _global_ +hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} + job: + chdir: true diff --git a/configs/importance_weights.yaml b/configs/importance_weights.yaml new file mode 100644 index 000000000..7f924ac7a --- /dev/null +++ b/configs/importance_weights.yaml @@ -0,0 +1,47 @@ +defaults: + - hydra: default + - model: unconditional_npe + - optimizer: adam + - scheduler: cosine + - _self_ + +outdir: . +parameter_samples: ??? +num_samples: 1000 +event_dataset: null +time_marginalization: + n_fft: 5 +phase_marginalization: null +synthetic_phase: null +calibration_marginalization: null +num_processes: 1 + +slice_plots: + num_slice_plots: 10 + params_slice2d: + - [phase, geocent_time] + - [phase, tilt_1] + +nde: + path: null + data: + parameter_samples: null + parameters: null + model: ${model} + training: + optimizer: ${optimizer} + scheduler: ${scheduler} + device: cpu + num_workers: 0 + train_fraction: 0.9 + batch_size: 512 + epochs: 20 + +hydra: + job: + name: importance_weights + config: + override_dirname: + exclude_keys: + - parameter_samples + - nde.path diff --git a/configs/intrinsic_prior/default.yaml b/configs/intrinsic_prior/default.yaml new file mode 100644 index 000000000..609f3d6e7 --- /dev/null +++ b/configs/intrinsic_prior/default.yaml @@ -0,0 +1,10 @@ +mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1') +mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2') +chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=100.0, name='chirp_mass') +mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') +phase: default +chi_1: bilby.gw.prior.AlignedSpin(name='chi_1', a_prior=Uniform(minimum=0, maximum=0.9)) +chi_2: bilby.gw.prior.AlignedSpin(name='chi_2', a_prior=Uniform(minimum=0, maximum=0.9)) +theta_jn: default +luminosity_distance: 100.0 +geocent_time: 0.0 diff --git a/configs/intrinsic_prior/fmpe.yaml b/configs/intrinsic_prior/fmpe.yaml new file mode 100644 index 000000000..30d74ebb4 --- /dev/null +++ b/configs/intrinsic_prior/fmpe.yaml @@ -0,0 +1,14 @@ +mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_1') +mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_2') +chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=20.0, maximum=120.0, name='chirp_mass') +mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') +phase: default +a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_1') +a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_2') +tilt_1: default +tilt_2: default +phi_12: default +phi_jl: default +theta_jn: default +luminosity_distance: 100.0 +geocent_time: 0.0 diff --git a/configs/intrinsic_prior/precessing.yaml b/configs/intrinsic_prior/precessing.yaml new file mode 100644 index 000000000..5400d688d --- /dev/null +++ b/configs/intrinsic_prior/precessing.yaml @@ -0,0 +1,14 @@ +mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_1') +mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_2') +chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=150.0, name='chirp_mass') +mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') +phase: default +a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_1') +a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_2') +tilt_1: default +tilt_2: default +phi_12: default +phi_jl: default +theta_jn: default +luminosity_distance: 100.0 +geocent_time: 0.0 diff --git a/configs/intrinsic_prior/precessing_multibanded_test.yaml b/configs/intrinsic_prior/precessing_multibanded_test.yaml new file mode 100644 index 000000000..7c3f469c1 --- /dev/null +++ b/configs/intrinsic_prior/precessing_multibanded_test.yaml @@ -0,0 +1,14 @@ +mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1') +mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2') +chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=100.0, name='chirp_mass') +mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') +phase: default +a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_1') +a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_2') +tilt_1: default +tilt_2: default +phi_12: default +phi_jl: default +theta_jn: default +luminosity_distance: 100.0 +geocent_time: 0.0 diff --git a/configs/merge_asd_datasets.yaml b/configs/merge_asd_datasets.yaml new file mode 100644 index 000000000..1dbafebb1 --- /dev/null +++ b/configs/merge_asd_datasets.yaml @@ -0,0 +1,35 @@ +defaults: + - hydra: default + - _self_ + +data_dir: asd_dataset +time_segments_file: null +num_parts: -1 +out_name: null + +dataset_settings: + f_min: 0 + f_max: 2048 + f_s: 4096 + time_psd: 1024 + T: 8.0 + window: + roll_off: 0.4 + type: tukey + time_gap: 0 + num_psds_max: 1 + channels: null + detectors: + - H1 + - L1 + observing_run: O1 + +hydra: + job: + name: merge_asd_datasets + config: + override_dirname: + exclude_keys: + - data_dir + - time_segments_file + - out_name diff --git a/configs/model/fmpe.yaml b/configs/model/fmpe.yaml new file mode 100644 index 000000000..94241980e --- /dev/null +++ b/configs/model/fmpe.yaml @@ -0,0 +1,31 @@ +posterior_model_type: flow_matching +posterior_kwargs: + activation: gelu + batch_norm: true + context_with_glu: false + dropout: 0.0 + hidden_dims: [4096, 4096, 4096, 2048, 2048, 2048, 1024, 1024, 1024, 1024, 1024, 1024, 512, 512, 512, 512, 512, 512, 512, 512, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 128, 128, 128, 128, 128, 64, 64, 64, 32, 32, 32, 16, 16, 16] + sigma_min: 0.001 + theta_embedding_kwargs: + embedding_net: + activation: gelu + hidden_dims: [16, 32, 64, 128, 256] + output_dim: 256 + type: DenseResidualNet + encoding: + encode_all: false + frequencies: 0 + theta_with_glu: true + time_prior_exponent: 1 + type: DenseResidualNet +embedding_kwargs: + activation: gelu + batch_norm: true + dropout: 0.0 + hidden_dims: + - 2048 + output_dim: 2048 + svd: + num_training_samples: 50000 + num_validation_samples: 10000 + size: 150 diff --git a/configs/model/gnpe.yaml b/configs/model/gnpe.yaml new file mode 100644 index 000000000..17bd48972 --- /dev/null +++ b/configs/model/gnpe.yaml @@ -0,0 +1,21 @@ +posterior_model_type: normalizing_flow +posterior_kwargs: + num_flow_steps: 30 + base_transform_kwargs: + hidden_dim: 1024 + num_transform_blocks: 5 + activation: elu + dropout_probability: 0.0 + batch_norm: true + num_bins: 8 + base_transform_type: rq-coupling +embedding_kwargs: + output_dim: 128 + hidden_dims: [1024, 1024, 1024, 1024, 1024, 1024, 512, 512, 512, 512, 512, 512, 256, 256, 256, 256, 256, 256, 128, 128, 128, 128, 128, 128] + activation: elu + dropout: 0.0 + batch_norm: true + svd: + num_training_samples: 50000 + num_validation_samples: 10000 + size: 200 diff --git a/configs/model/gnpe_init.yaml b/configs/model/gnpe_init.yaml new file mode 100644 index 000000000..ee39f8ec4 --- /dev/null +++ b/configs/model/gnpe_init.yaml @@ -0,0 +1,21 @@ +posterior_model_type: normalizing_flow +posterior_kwargs: + num_flow_steps: 15 + base_transform_kwargs: + hidden_dim: 512 + num_transform_blocks: 5 + activation: elu + dropout_probability: 0.0 + batch_norm: true + num_bins: 8 + base_transform_type: rq-coupling +embedding_kwargs: + output_dim: 128 + hidden_dims: [512, 512, 512, 512, 512, 512, 256, 256, 256, 256, 256, 256, 128, 128, 128, 128, 128, 128] + activation: elu + dropout: 0.0 + batch_norm: true + svd: + num_training_samples: 50000 + num_validation_samples: 10000 + size: 200 diff --git a/configs/model/npe.yaml b/configs/model/npe.yaml new file mode 100644 index 000000000..17bd48972 --- /dev/null +++ b/configs/model/npe.yaml @@ -0,0 +1,21 @@ +posterior_model_type: normalizing_flow +posterior_kwargs: + num_flow_steps: 30 + base_transform_kwargs: + hidden_dim: 1024 + num_transform_blocks: 5 + activation: elu + dropout_probability: 0.0 + batch_norm: true + num_bins: 8 + base_transform_type: rq-coupling +embedding_kwargs: + output_dim: 128 + hidden_dims: [1024, 1024, 1024, 1024, 1024, 1024, 512, 512, 512, 512, 512, 512, 256, 256, 256, 256, 256, 256, 128, 128, 128, 128, 128, 128] + activation: elu + dropout: 0.0 + batch_norm: true + svd: + num_training_samples: 50000 + num_validation_samples: 10000 + size: 200 diff --git a/configs/model/toy_npe.yaml b/configs/model/toy_npe.yaml new file mode 100644 index 000000000..295545298 --- /dev/null +++ b/configs/model/toy_npe.yaml @@ -0,0 +1,21 @@ +posterior_model_type: normalizing_flow +posterior_kwargs: + num_flow_steps: 5 + base_transform_kwargs: + hidden_dim: 64 + num_transform_blocks: 5 + activation: elu + dropout_probability: 0.0 + batch_norm: true + num_bins: 8 + base_transform_type: rq-coupling +embedding_kwargs: + output_dim: 128 + hidden_dims: [1024, 512, 256, 128] + activation: elu + dropout: 0.0 + batch_norm: true + svd: + num_training_samples: 1000 + num_validation_samples: 100 + size: 50 diff --git a/configs/model/unconditional_npe.yaml b/configs/model/unconditional_npe.yaml new file mode 100644 index 000000000..41c500b77 --- /dev/null +++ b/configs/model/unconditional_npe.yaml @@ -0,0 +1,12 @@ +posterior_model_type: normalizing_flow +embedding_kwargs: null +posterior_kwargs: + num_flow_steps: 10 + base_transform_kwargs: + hidden_dim: 128 + num_transform_blocks: 2 + activation: elu + dropout_probability: 0.1 + batch_norm: true + num_bins: 8 + base_transform_type: rq-coupling diff --git a/configs/optimizer/adam.yaml b/configs/optimizer/adam.yaml new file mode 100644 index 000000000..30ba48b22 --- /dev/null +++ b/configs/optimizer/adam.yaml @@ -0,0 +1,2 @@ +type: adam +lr: 0.0001 diff --git a/configs/scheduler/cosine.yaml b/configs/scheduler/cosine.yaml new file mode 100644 index 000000000..c45a236f1 --- /dev/null +++ b/configs/scheduler/cosine.yaml @@ -0,0 +1,2 @@ +type: cosine +T_max: 20 diff --git a/configs/train.yaml b/configs/train.yaml new file mode 100644 index 000000000..631369b33 --- /dev/null +++ b/configs/train.yaml @@ -0,0 +1,61 @@ +defaults: + - hydra: default + - extrinsic_prior: default + - model: toy_npe + - optimizer: adam + - scheduler: cosine + - _self_ + +checkpoint: null +train_dir: train +exit_command: "" + +data: + extrinsic_prior: ${extrinsic_prior} + waveform_dataset_path: training_data/waveform_dataset.hdf5 + train_fraction: 0.95 + domain_update: null + svd_size_update: null + detectors: + - H1 + - L1 + ref_time: 1126259462.391 + inference_parameters: + - chirp_mass + - mass_ratio + - chi_1 + - chi_2 + - theta_jn + - dec + - ra + - geocent_time + - luminosity_distance + - psi + - phase + +training: + evaluate: false + stage_0: + optimizer: ${optimizer} + scheduler: ${scheduler} + epochs: 20 + asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + freeze_rb_layer: true + batch_size: 64 + early_stopping: null + +local: + device: cpu + num_workers: 6 + runtime_limits: + max_time_per_run: 3600000 + max_epochs_per_run: 30 + checkpoint_epochs: 15 + leave_waveforms_on_disk: true + local_cache_path: null + wandb: false + test_only: false + +hydra: + job: + name: train diff --git a/configs/unconditional_density_estimation.yaml b/configs/unconditional_density_estimation.yaml new file mode 100644 index 000000000..8129c0f5f --- /dev/null +++ b/configs/unconditional_density_estimation.yaml @@ -0,0 +1,29 @@ +defaults: + - hydra: default + - model: unconditional_npe + - optimizer: adam + - scheduler: cosine + - _self_ + +result_file: ??? +train_dir: nde + +data: + parameters: null + +training: + optimizer: ${optimizer} + scheduler: ${scheduler} + device: cpu + num_workers: 0 + train_fraction: 0.9 + batch_size: 512 + epochs: 20 + +hydra: + job: + name: unconditional_density_estimation + config: + override_dirname: + exclude_keys: + - result_file diff --git a/configs/waveform_generator/phenom_d.yaml b/configs/waveform_generator/phenom_d.yaml new file mode 100644 index 000000000..4ec9d64b3 --- /dev/null +++ b/configs/waveform_generator/phenom_d.yaml @@ -0,0 +1,4 @@ +approximant: IMRPhenomD +f_ref: 20.0 +f_start: null +spin_conversion_phase: null diff --git a/configs/waveform_generator/phenom_pv2.yaml b/configs/waveform_generator/phenom_pv2.yaml new file mode 100644 index 000000000..f4390b2fb --- /dev/null +++ b/configs/waveform_generator/phenom_pv2.yaml @@ -0,0 +1,4 @@ +approximant: IMRPhenomPv2 +f_ref: 20.0 +f_start: null +spin_conversion_phase: 0.0 diff --git a/configs/waveform_generator/phenom_xphm.yaml b/configs/waveform_generator/phenom_xphm.yaml new file mode 100644 index 000000000..8c1a14c17 --- /dev/null +++ b/configs/waveform_generator/phenom_xphm.yaml @@ -0,0 +1,4 @@ +approximant: IMRPhenomXPHM +f_ref: 20.0 +f_start: null +spin_conversion_phase: 0.0 diff --git a/dingo/core/density/unconditional_density_estimation.py b/dingo/core/density/unconditional_density_estimation.py index 53cd08858..48d056cae 100644 --- a/dingo/core/density/unconditional_density_estimation.py +++ b/dingo/core/density/unconditional_density_estimation.py @@ -1,14 +1,20 @@ import copy +import logging +import os +import hydra import torch +import numpy as np +from hydra.utils import to_absolute_path +from omegaconf import DictConfig, OmegaConf from dingo.core.utils import build_train_and_test_loaders from dingo.core.utils.trainutils import RuntimeLimits -import numpy as np -import argparse - from dingo.core.posterior_models import NormalizingFlowPosteriorModel +log = logging.getLogger(__name__) +logging.captureWarnings(True) + class SampleDataset(torch.utils.data.Dataset): """ @@ -110,14 +116,24 @@ def train_unconditional_density_estimator( return model -def parse_args(): - parser = argparse.ArgumentParser( - description="Unconditional density estimation for dingo samples." - ) - parser.add_argument( - "--settings", - type=str, - required=True, - help="Path to settings file.", - ) - return parser.parse_args() +@hydra.main( + version_base="1.3", + config_path="../../../configs", + config_name="unconditional_density_estimation", +) +def main(cfg: DictConfig): + from dingo.core.result import Result + + settings = OmegaConf.to_container(cfg, resolve=True) + result_file = to_absolute_path(settings.pop("result_file")) + train_dir = settings.pop("train_dir") + os.makedirs(train_dir, exist_ok=True) + + log.info(f"Loading result from {result_file}.") + result = Result(file_name=result_file) + log.info(f"Training unconditional density estimator in {train_dir}.") + train_unconditional_density_estimator(result, settings, train_dir) + + +if __name__ == "__main__": + main() diff --git a/dingo/core/posterior_models/base_model.py b/dingo/core/posterior_models/base_model.py index 68508d199..16e347f00 100755 --- a/dingo/core/posterior_models/base_model.py +++ b/dingo/core/posterior_models/base_model.py @@ -4,6 +4,7 @@ """ from abc import abstractmethod, ABC +import logging import os from os.path import join import h5py @@ -23,6 +24,8 @@ from dingo.core.utils.trainutils import EarlyStopping +log = logging.getLogger(__name__) + class BasePosteriorModel(ABC): """ @@ -198,7 +201,7 @@ def network_to_device(self, device): # raise NotImplementedError('This needs testing!') # # dim = 0 [512, ...] -> [256, ...], [256, ...] on 2 GPUs # self.network = torch.nn.DataParallel(self.network) - print(f"Putting posterior model to device {self.device}.") + log.info(f"Putting posterior model to device {self.device}.") self.network.to(self.device) def initialize_optimizer_and_scheduler(self): @@ -394,7 +397,7 @@ def train( if test_only: test_loss = test_epoch(self, test_loader) - print(f"test loss: {test_loss:.3f}") + log.info(f"test loss: {test_loss:.3f}") else: while not runtime_limits.limits_exceeded(self.epoch): @@ -403,24 +406,24 @@ def train( # Training lr = utils.get_lr(self.optimizer) with threadpool_limits(limits=1, user_api="blas"): - print(f"\nStart training epoch {self.epoch} with lr {lr}") + log.info(f"\nStart training epoch {self.epoch} with lr {lr}") time_start = time.time() train_loss = train_epoch(self, train_loader) train_time = time.time() - time_start - print( + log.info( "Done. This took {:2.0f}:{:2.0f} min.".format( *divmod(train_time, 60) ) ) # Testing - print(f"Start testing epoch {self.epoch}") + log.info(f"Start testing epoch {self.epoch}") time_start = time.time() test_loss = test_epoch(self, test_loader) test_time = time.time() - time_start - print( + log.info( "Done. This took {:2.0f}:{:2.0f} min.".format( *divmod(time.time() - time_start, 60) ) @@ -449,7 +452,7 @@ def train( } ) except ImportError: - print("wandb not installed. Skipping logging to wandb.") + log.info("wandb not installed. Skipping logging to wandb.") if early_stopping is not None: # Whether to use train or test loss @@ -464,9 +467,9 @@ def train( join(train_dir, "best_model.pt"), save_training_info=False ) if early_stopping.early_stop: - print("Early stopping") + log.info("Early stopping") break - print(f"Finished training epoch {self.epoch}.\n") + log.info(f"Finished training epoch {self.epoch}.\n") def train_epoch(pm, dataloader): diff --git a/dingo/core/result.py b/dingo/core/result.py index 67017271a..8d7ad2a1e 100644 --- a/dingo/core/result.py +++ b/dingo/core/result.py @@ -1,4 +1,5 @@ import copy +import logging import math import tempfile import time @@ -29,6 +30,8 @@ "log_noise_evidence", ] +log = logging.getLogger(__name__) + def _clip_weights(weights: np.ndarray, num_clip: int) -> np.ndarray: """Replace the ``num_clip`` largest weights with their mean, then re-normalize. @@ -167,23 +170,23 @@ def reset_event(self, event_dataset): ): # This is really just for notification. Actions are only taken if the # event metadata differ. - print("\nNew event data differ from existing.") + log.info("\nNew event data differ from existing.") self.context = context if self.event_metadata is not None and self.event_metadata != event_metadata: - print("Changes") - print("=======") + log.info("Changes") + log.info("=======") old_minus_new = dict(freeze(self.event_metadata) - freeze(event_metadata)) - print("Old event metadata:") + log.info("Old event metadata:") for k in sorted(old_minus_new): - print(f" {k}: {self.event_metadata[k]}") + log.info(f" {k}: {self.event_metadata[k]}") new_minus_old = dict(freeze(event_metadata) - freeze(self.event_metadata)) - print("New event metadata:") + log.info("New event metadata:") if self.importance_sampling_metadata.get("updates") is None: self.importance_sampling_metadata["updates"] = {} for k in sorted(new_minus_old): - print(f" {k}: {event_metadata[k]}") + log.info(f" {k}: {event_metadata[k]}") self.importance_sampling_metadata["updates"][k] = event_metadata[k] self._rebuild_domain(verbose=True) @@ -312,12 +315,12 @@ def importance_sample(self, num_processes: int = 1, **likelihood_kwargs): valid_samples = np.isfinite(log_prior + delta_log_prob_target) theta = theta.iloc[valid_samples] - print(f"Calculating {len(theta)} likelihoods.") + log.info(f"Calculating {len(theta)} likelihoods.") t0 = time.time() log_likelihood = self.likelihood.log_likelihood_multi( theta, num_processes=num_processes ) - print(f"Done. This took {time.time() - t0:.2f} seconds.") + log.info(f"Done. This took {time.time() - t0:.2f} seconds.") self.log_noise_evidence = self.likelihood.log_Zn self.samples["log_prior"] = log_prior @@ -494,7 +497,7 @@ def rejection_sample( rng = np.random.default_rng(random_state) weights = self.samples["weights"].to_numpy(dtype=float) - print( + log.info( f"Rejection sampling: {self.num_samples} samples, " f"ESS = {self.effective_sample_size:.0f} " f"(efficiency = {100 * self.sample_efficiency:.1f}%)" @@ -526,7 +529,7 @@ def rejection_sample( unweighted = unweighted.drop( columns=["weights", "log_prob", "delta_log_prob_target"], errors="ignore" ) - print(f"Produced {len(unweighted)} unweighted samples.") + log.info(f"Produced {len(unweighted)} unweighted samples.") return unweighted def parameter_subset(self, parameters): @@ -619,12 +622,12 @@ def print_summary(self): Display the number of samples, and (if importance sampling is complete) the log evidence and number of effective samples. """ - print("Number of samples:", len(self.samples)) + log.info(f"Number of samples: {len(self.samples)}") if self.log_evidence is not None: - print( + log.info( f"Log(evidence): {self.log_evidence:.3f} +- {self.log_evidence_std:.3f}" ) - print( + log.info( f"Effective samples {self.n_eff:.1f}: " f"(Sample efficiency = {100 * self.sample_efficiency:.2f}%)" ) @@ -815,7 +818,7 @@ def plot_log_probs(self, filename="log_probs.png"): plt.tight_layout() plt.savefig(filename) else: - print("Results not importance sampled. Cannot produce log_prob plot.") + log.info("Results not importance sampled. Cannot produce log_prob plot.") def plot_weights(self, filename="weights.png"): """Make a scatter plot of samples weights vs log proposal.""" @@ -844,7 +847,7 @@ def plot_weights(self, filename="weights.png"): plt.tight_layout() plt.savefig(filename) else: - print("Results not importance sampled. Cannot plot weights.") + log.info("Results not importance sampled. Cannot plot weights.") def get_all_injection_credible_levels( self, keys: list[str] = None, weighted: bool = False @@ -1020,7 +1023,7 @@ def make_pp_plot( pvalues = [] latex_labels = get_latex_labels(results[0].prior) - print("Key: KS-test p-value") + log.info("Key: KS-test p-value") for ii, key in enumerate(credible_levels): pp = np.array( [ @@ -1030,7 +1033,7 @@ def make_pp_plot( ) pvalue = scipy.stats.kstest(credible_levels[key], "uniform").pvalue pvalues.append(pvalue) - print("{}: {}".format(key, pvalue)) + log.info("{}: {}".format(key, pvalue)) label = "{} ({:2.3f})".format(latex_labels[key], pvalue) plt.plot(x_values, pp, lines[ii], label=label, **kwargs) @@ -1040,7 +1043,7 @@ def make_pp_plot( pvalues=pvalues, names=list(credible_levels.keys()), ) - print("Combined p-value: {}".format(pvals.combined_pvalue)) + log.info("Combined p-value: {}".format(pvals.combined_pvalue)) if title: ax.set_title( @@ -1090,5 +1093,3 @@ def freeze(d): elif isinstance(d, list): return tuple(freeze(value) for value in d) return d - - diff --git a/dingo/core/samplers.py b/dingo/core/samplers.py index efc4fc1fa..03e25b22f 100644 --- a/dingo/core/samplers.py +++ b/dingo/core/samplers.py @@ -1,9 +1,9 @@ import copy +import logging import math import time from pathlib import Path from typing import Optional, Union -import sys import numpy as np import pandas as pd @@ -22,6 +22,8 @@ # Sampler classes are motivated by the approach of Bilby. # +log = logging.getLogger(__name__) + class Sampler(object): """ @@ -157,7 +159,7 @@ def _run_sampler( x = [x] else: if context is not None: - print("Unconditional model. Ignoring context.") + log.info("Unconditional model. Ignoring context.") x = [] # For a normalizing flow, we get the log_prob for "free" when sampling, @@ -205,7 +207,7 @@ def run_sampler( """ self.samples = None - print(f"Running sampler to generate {num_samples} samples.") + log.info(f"Running sampler to generate {num_samples} samples.") t0 = time.time() if not self.unconditional_model: if self.context is None: @@ -229,8 +231,7 @@ def run_sampler( # correction for t_ref) and represent as DataFrame. self._post_process(samples) self.samples = pd.DataFrame(samples) - print(f"Done. This took {time.time() - t0:.1f} s.") - sys.stdout.flush() + log.info(f"Done. This took {time.time() - t0:.1f} s.") def log_prob(self, samples: pd.DataFrame | dict) -> np.ndarray: """ @@ -430,7 +431,7 @@ def _run_sampler( init_samples = self.init_sampler._run_sampler(num_samples, context) else: if self.num_iterations == 1: - print( + log.info( f"Warning: Removing initial outliers, but only carrying out " f"{self.num_iterations} GNPE iteration. This risks biasing " f"results." @@ -515,16 +516,21 @@ def _run_sampler( p: x["extrinsic_parameters"][p] for p in self.gnpe_proxy_parameters } - print( + log.info( f"it {i}.\tmin pvalue: {self.iteration_tracker.pvalue_min:.3f}" - f"\tproxy mean: ", - *[f"{torch.mean(v).item():.5f}" for v in proxies.values()], - "\tproxy std:", - *[f"{torch.std(v).item():.5f}" for v in proxies.values()], - "\ttimes:", - time_sample_start - start_time, - time_sample_end - time_sample_start, - time.time() - time_sample_end, + f"\tproxy mean: " + + " ".join(f"{torch.mean(v).item():.5f}" for v in proxies.values()) + + "\tproxy std: " + + " ".join(f"{torch.std(v).item():.5f}" for v in proxies.values()) + + "\ttimes: " + + " ".join( + str(t) + for t in ( + time_sample_start - start_time, + time_sample_end - time_sample_start, + time.time() - time_sample_end, + ) + ) ) # diff --git a/dingo/core/utils/pt_to_hdf5.py b/dingo/core/utils/pt_to_hdf5.py index 06e6aa96c..195ecfe1f 100644 --- a/dingo/core/utils/pt_to_hdf5.py +++ b/dingo/core/utils/pt_to_hdf5.py @@ -1,38 +1,71 @@ import os +import argparse +import logging +import sys + import torch -import numpy as np import h5py import json -import argparse + + +log = logging.getLogger(__name__) +logging.captureWarnings(True) def parse_args(): parser = argparse.ArgumentParser( - description="Convert the weights of a trained Dingo model from a PyTorch pickle .pt file to HDF5," - " for distribution in the LVK's CVMFS.", - epilog="Training history (optimizer_state_dict) is discarded.") - parser.add_argument("-i", "--in_file", type=str, required=True, - help='Input model ".pt" weights file') - parser.add_argument("-o", "--out_file", type=str, required=True, - help='Output model ".hdf5" weights file') - parser.add_argument("-n", "--model_version_number", type=int, required=True, - help="Model version number (integer). " - "Will be included in the output filename and metadata.") + description=( + "Convert the weights of a trained Dingo model from a PyTorch pickle " + ".pt file to HDF5, for distribution in the LVK's CVMFS." + ), + epilog="Training history (optimizer_state_dict) is discarded.", + ) + parser.add_argument( + "-i", + "--in_file", + type=str, + required=True, + help='Input model ".pt" weights file', + ) + parser.add_argument( + "-o", "--out_file", type=str, required=True, help='Output model ".hdf5" file' + ) + parser.add_argument( + "-n", + "--model_version_number", + type=int, + required=True, + help=( + "Model version number (integer). Will be included in the output " + "filename and metadata." + ), + ) return parser.parse_args() +def configure_logging(): + logging.basicConfig( + level=logging.INFO, + format="[%(asctime)s][%(name)s][%(levelname)s] - %(message)s", + stream=sys.stdout, + force=True, + ) + + def main(): + configure_logging() args = parse_args() - if os.path.splitext(args.in_file)[-1] != '.pt': - raise ValueError('Expected a .pt input file') - if os.path.splitext(args.out_file)[-1] != '.hdf5': - raise ValueError('Expected a .hdf5 output file') + + if os.path.splitext(args.in_file)[-1] != ".pt": + raise ValueError("Expected a .pt input file") + if os.path.splitext(args.out_file)[-1] != ".hdf5": + raise ValueError("Expected a .hdf5 output file") # Build output filename with the version number for this network # This is required for use on CVMFS root, ext = os.path.splitext(args.out_file) - out_file_name = f'{root}_v{args.model_version_number}{ext}' - print('Output will be written to', out_file_name) + out_file_name = f"{root}_v{args.model_version_number}{ext}" + log.info(f"Output will be written to {out_file_name}") # Load data into CPU memory since we'll be saving it using CPU libraries d = torch.load(args.in_file, map_location=torch.device("cpu")) @@ -40,20 +73,19 @@ def main(): # Collect the names of the dicts that can be serialized to JSON; model_state_dict and # optimizer_state_dict contain torch.tensors and cannot be JSON serialized # In addition, we drop dicts related to training information that is not needed at inference time - dicts_to_serialize = ['model_kwargs', 'epoch', 'metadata'] + dicts_to_serialize = ["model_kwargs", "epoch", "metadata"] - - with h5py.File(out_file_name, 'w') as f: + with h5py.File(out_file_name, "w") as f: # Save small nested dicts as json - grp = f.create_group('serialized_dicts') + grp = f.create_group("serialized_dicts") for k in dicts_to_serialize: dict_str = json.dumps(d[k]) grp.create_dataset(k, data=dict_str) # Save the OrderedDict containing the model weights # The keys are ordered alphanumerically as well. - grp_model = f.create_group('model_weights') - for k, v in d['model_state_dict'].items(): + grp_model = f.create_group("model_weights") + for k, v in d["model_state_dict"].items(): if len(v.size()) > 0: grp_model.create_dataset(k, data=v.numpy(), fletcher32=True) else: @@ -66,17 +98,18 @@ def main(): # Metadata for CVMFS LVK distribution # This needs to be exactly the same as the "basename" of the hdf5 file - f.attrs['CANONICAL_FILE_BASENAME'] = os.path.basename(out_file_name) + f.attrs["CANONICAL_FILE_BASENAME"] = os.path.basename(out_file_name) # Add a few metadata entries as attributes - f.attrs['approximant'] = d['metadata']['dataset_settings']['waveform_generator']['approximant'] - f.attrs['epoch'] = d['epoch'] + f.attrs["approximant"] = d["metadata"]["dataset_settings"][ + "waveform_generator" + ]["approximant"] + f.attrs["epoch"] = d["epoch"] # Add the dingo version used for training - f.attrs['version'] = str(d.get('version')) + f.attrs["version"] = str(d.get("version")) # Make it clear to dingo_ls that this is file contains model weights - f.attrs['dataset_type'] = 'trained_model' + f.attrs["dataset_type"] = "trained_model" if __name__ == "__main__": main() - diff --git a/dingo/core/utils/trainutils.py b/dingo/core/utils/trainutils.py index bc7d12154..a420e8d82 100644 --- a/dingo/core/utils/trainutils.py +++ b/dingo/core/utils/trainutils.py @@ -1,10 +1,13 @@ import time import os +import logging import numpy as np from os.path import join, isfile import csv from typing import Literal +log = logging.getLogger(__name__) + class AvgTracker: def __init__(self): @@ -85,7 +88,9 @@ def __call__(self, val_loss: float): elif score < self.best_score + self.delta: self.counter += 1 if self.verbose: - print(f"EarlyStopping counter: {self.counter} out of {self.patience}") + log.info( + f"EarlyStopping counter: {self.counter} out of {self.patience}" + ) if self.counter >= self.patience: self.early_stop = True return False @@ -124,23 +129,26 @@ def get_avg(self): def print_info(self, batch_idx): if batch_idx % self.print_freq == 0: - print( - "{} Epoch: {} [{}/{} ({:.0f}%)]".format( + td, td_avg = self.times["Dataloader"].x, self.times["Dataloader"].get_avg() + tn, tn_avg = self.times["Network"].x, self.times["Network"].get_avg() + log.info( + "{} Epoch: {} [{}/{} ({:.0f}%)]\t\t" + "Loss: {:.3f} ({:.3f})\t\t" + "Time Dataloader: {:.3f} ({:.3f})\t\t" + "Time Network: {:.3f} ({:.3f})".format( self.mode, self.epoch, min(batch_idx * self.batch_size, self.len_dataset), self.len_dataset, 100.0 * batch_idx * self.batch_size / self.len_dataset, - ), - end="\t\t", + self.loss, + self.get_avg(), + td, + td_avg, + tn, + tn_avg, + ) ) - # print loss - print(f"Loss: {self.loss:.3f} ({self.get_avg():.3f})", end="\t\t") - # print computation times - td, td_avg = self.times["Dataloader"].x, self.times["Dataloader"].get_avg() - tn, tn_avg = self.times["Network"].x, self.times["Network"].get_avg() - print(f"Time Dataloader: {td:.3f} ({td_avg:.3f})", end="\t\t") - print(f"Time Network: {tn:.3f} ({tn_avg:.3f})") class RuntimeLimits: @@ -195,7 +203,7 @@ def limits_exceeded(self, epoch: int = None): # check time limit for run if self.max_time_per_run is not None: if time.time() - self.time_start >= self.max_time_per_run: - print( + log.info( f"Stop run: Time limit of {self.max_time_per_run} s " f"exceeded." ) return True @@ -204,14 +212,14 @@ def limits_exceeded(self, epoch: int = None): if epoch is None: raise ValueError("epoch required") if epoch - self.epoch_start >= self.max_epochs_per_run: - print( + log.info( f"Stop run: Epoch limit of {self.max_epochs_per_run} per run reached." ) return True # check total epoch limit if self.max_epochs_total is not None: if epoch >= self.max_epochs_total: - print( + log.info( f"Stop run: Total epoch limit of {self.max_epochs_total} reached." ) return True @@ -316,13 +324,13 @@ def save_model(pm, log_dir, model_prefix="model", checkpoint_epochs=None): """ # save current model model_name = join(log_dir, f"{model_prefix}_latest.pt") - print(f"Saving model to {model_name}.", end=" ") + log.info(f"Saving model to {model_name}.") pm.save_model(model_name, save_training_info=True) - print("Done.") + log.info("Done.") # potentially copy model to a checkpoint if checkpoint_epochs is not None and pm.epoch % checkpoint_epochs == 0: model_name_cp = join(log_dir, f"{model_prefix}_{pm.epoch:03d}.pt") - print(f"Copy model to checkpoint {model_name_cp}.", end=" ") + log.info(f"Copy model to checkpoint {model_name_cp}.") copyfile(model_name, model_name_cp) - print("Done.") + log.info("Done.") diff --git a/dingo/gw/SVD.py b/dingo/gw/SVD.py index e77b7761d..2cad5b40d 100644 --- a/dingo/gw/SVD.py +++ b/dingo/gw/SVD.py @@ -1,9 +1,14 @@ +import logging + import numpy as np import pandas as pd import scipy from sklearn.utils.extmath import randomized_svd from dingo.core.dataset import DingoDataset +log = logging.getLogger(__name__) + + class SVDBasis(DingoDataset): dataset_type = "svd_basis" @@ -155,15 +160,17 @@ def print_validation_summary(self): if "mismatch" in col: n = int(col.split(sep="=")[-1]) mismatches = self.mismatches[col] - print(f"n = {n}") - print(" Mean mismatch = {}".format(np.mean(mismatches))) - print(" Standard deviation = {}".format(np.std(mismatches))) - print(" Max mismatch = {}".format(np.max(mismatches))) - print(" Median mismatch = {}".format(np.median(mismatches))) - print(" Percentiles:") - print(" 99 -> {}".format(np.percentile(mismatches, 99))) - print(" 99.9 -> {}".format(np.percentile(mismatches, 99.9))) - print(" 99.99 -> {}".format(np.percentile(mismatches, 99.99))) + log.info(f"n = {n}") + log.info(" Mean mismatch = {}".format(np.mean(mismatches))) + log.info(" Standard deviation = {}".format(np.std(mismatches))) + log.info(" Max mismatch = {}".format(np.max(mismatches))) + log.info(" Median mismatch = {}".format(np.median(mismatches))) + log.info(" Percentiles:") + log.info(" 99 -> {}".format(np.percentile(mismatches, 99))) + log.info(" 99.9 -> {}".format(np.percentile(mismatches, 99.9))) + log.info( + " 99.99 -> {}".format(np.percentile(mismatches, 99.99)) + ) def decompress(self, coefficients: np.ndarray): """ diff --git a/dingo/gw/dataset/evaluate_multibanded_domain.py b/dingo/gw/dataset/evaluate_multibanded_domain.py index b20a2fb43..07a27d635 100644 --- a/dingo/gw/dataset/evaluate_multibanded_domain.py +++ b/dingo/gw/dataset/evaluate_multibanded_domain.py @@ -1,7 +1,9 @@ -import argparse +import logging +from typing import Dict +import hydra import numpy as np -import yaml +from omegaconf import DictConfig, OmegaConf from scipy.interpolate import interp1d from dingo.gw.dataset import generate_parameters_and_polarizations @@ -14,14 +16,20 @@ generate_waveforms_parallel, ) +log = logging.getLogger(__name__) +logging.captureWarnings(True) -def _evaluate_multibanding_main( - settings_file: str, + +def _settings_from_config(cfg: DictConfig) -> Dict: + settings = OmegaConf.to_container(cfg, resolve=True) + settings.pop("num_samples", None) + return settings + + +def evaluate_multibanding( + settings: Dict, num_samples: int, ): - with open(settings_file, "r") as f: - settings = yaml.safe_load(f) - # Ignore any compression settings if "compression" in settings: del settings["compression"] @@ -35,13 +43,13 @@ def _evaluate_multibanding_main( settings["intrinsic_prior"]["chirp_mass"] = prior["chirp_mass"].minimum # Rebuild prior with updated settings. prior = build_prior_with_defaults(settings["intrinsic_prior"]) - print("Prior") + log.info("Prior") for k, v in prior.items(): - print(f"{k}: {v}") + log.info(f"{k}: {v}") domain = build_domain(settings["domain"]) - print("\nDomain") - print(domain.domain_dict) + log.info("\nDomain") + log.info(domain.domain_dict) if not isinstance(domain, MultibandedFrequencyDomain): raise ValueError("Waveform dataset domain not a MultibandedFrequencyDomain.") @@ -88,43 +96,34 @@ def _evaluate_multibanding_main( asd_file="aLIGO_ZERO_DET_high_P_asd.txt", ) - print("\nMismatches between UFD waveforms and MFD waveforms interpolated to MFD.") - print( + log.info( + "\nMismatches between UFD waveforms and MFD waveforms interpolated to MFD." + ) + log.info( "This is a conservative estimate of the MFD performance when training " "networks." ) mismatches = np.concatenate([v for v in mismatches.values()]) - print(f"num_samples = {num_samples}") - print(" Mean mismatch = {}".format(np.mean(mismatches))) - print(" Standard deviation = {}".format(np.std(mismatches))) - print(" Max mismatch = {}".format(np.max(mismatches))) - print(" Median mismatch = {}".format(np.median(mismatches))) - print(" Percentiles:") - print(" 99 -> {}".format(np.percentile(mismatches, 99))) - print(" 99.9 -> {}".format(np.percentile(mismatches, 99.9))) - print(" 99.99 -> {}".format(np.percentile(mismatches, 99.99))) - - -def parse_args(): - parser = argparse.ArgumentParser( - formatter_class=argparse.RawDescriptionHelpFormatter, - description="Evaluate performance of multibanding on waveform dataset.", - ) - parser.add_argument( - "--settings-file", - type=str, - required=True, - help="YAML file containing database settings", - ) - parser.add_argument( - "--num-samples", - type=int, - default=5000, - help="Number of waveform evaluations for comparison.", - ) - return parser.parse_args() + log.info(f"num_samples = {num_samples}") + log.info(" Mean mismatch = {}".format(np.mean(mismatches))) + log.info(" Standard deviation = {}".format(np.std(mismatches))) + log.info(" Max mismatch = {}".format(np.max(mismatches))) + log.info(" Median mismatch = {}".format(np.median(mismatches))) + log.info(" Percentiles:") + log.info(" 99 -> {}".format(np.percentile(mismatches, 99))) + log.info(" 99.9 -> {}".format(np.percentile(mismatches, 99.9))) + log.info(" 99.99 -> {}".format(np.percentile(mismatches, 99.99))) + + +@hydra.main( + version_base="1.3", + config_path="../../../configs", + config_name="evaluate_multibanded_domain", +) +def main(cfg: DictConfig) -> None: + settings = _settings_from_config(cfg) + evaluate_multibanding(settings, cfg.num_samples) -def main() -> None: - args = parse_args() - _evaluate_multibanding_main(args.settings_file, args.num_samples) +if __name__ == "__main__": + main() diff --git a/dingo/gw/dataset/generate_dataset.py b/dingo/gw/dataset/generate_dataset.py index b35cf49c3..9a5da8c44 100644 --- a/dingo/gw/dataset/generate_dataset.py +++ b/dingo/gw/dataset/generate_dataset.py @@ -26,6 +26,7 @@ from dingo.core.utils.misc import call_func_strict_output_dim log = logging.getLogger(__name__) +logging.captureWarnings(True) def generate_parameters_and_polarizations( @@ -262,7 +263,7 @@ def generate_dataset(settings: Dict, num_processes: int) -> WaveformDataset: @hydra.main( version_base="1.3", - config_path="../../../configs/examples", + config_path="../../../configs", config_name="generate_dataset", ) def main(cfg: DictConfig) -> None: diff --git a/dingo/gw/dataset/utils.py b/dingo/gw/dataset/utils.py index e8911f87c..4257581a4 100644 --- a/dingo/gw/dataset/utils.py +++ b/dingo/gw/dataset/utils.py @@ -1,15 +1,30 @@ +import copy +import logging import argparse +import sys import textwrap -import copy +from typing import List + import pandas as pd import numpy as np import yaml -from typing import List from dingo.gw.SVD import SVDBasis from dingo.gw.dataset.generate_dataset import train_svd_basis from dingo.gw.dataset.waveform_dataset import WaveformDataset +log = logging.getLogger(__name__) +logging.captureWarnings(True) + + +def configure_logging(): + logging.basicConfig( + level=logging.INFO, + format="[%(asctime)s][%(name)s][%(levelname)s] - %(message)s", + stream=sys.stdout, + force=True, + ) + def merge_datasets(dataset_list: List[WaveformDataset]) -> WaveformDataset: """ @@ -26,7 +41,7 @@ def merge_datasets(dataset_list: List[WaveformDataset]) -> WaveformDataset: WaveformDataset containing the merged data. """ - print(f"Merging {len(dataset_list)} datasets into one.") + log.info(f"Merging {len(dataset_list)} datasets into one.") # This ensures that all the keys are copied into the new dataset. The "extensive" # parts of the dataset (parameters, waveforms) will be overwritten by the combined @@ -59,14 +74,15 @@ def merge_datasets_cli(): parallelized waveform generation. """ + configure_logging() parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent( """\ Combine a collection of datasets into one. - - Datasets must be in sequentially-labeled HDF5 files with a fixed prefix. - The settings for the new dataset will be based on those of the first file. + + Datasets must be in sequentially-labeled HDF5 files with a fixed prefix. + The settings for the new dataset will be based on those of the first file. Optionally, replace the settings with those specified in a YAML file. """ ), @@ -103,7 +119,7 @@ def merge_datasets_cli(): merged_dataset.settings = settings merged_dataset.to_file(args.out_file) - print( + log.info( f"Complete. New dataset consists of {merged_dataset.settings['num_samples']} " f"samples." ) @@ -114,6 +130,7 @@ def build_svd_cli(): Command-line function to build an SVD based on an uncompressed dataset file. """ + configure_logging() parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent( @@ -137,21 +154,23 @@ def build_svd_cli(): parser.add_argument( "--num_train", type=int, - help="Number of waveforms to use for training SVD. " - "Remainder are used for validation.", + help=( + "Number of waveforms to use for training SVD. Remainder are used " + "for validation." + ), ) args = parser.parse_args() dataset = WaveformDataset(file_name=args.dataset_file) if args.num_train is None: - n_train = len(WaveformDataset) + n_train = len(dataset) else: n_train = args.num_train basis, n_train, n_test = train_svd_basis(dataset, args.size, n_train) # FIXME: This is not an ideal treatment. We should update the waveform generation # to always provide the requested number of waveforms. - print( + log.info( f"SVD basis trained based on {n_train} waveforms and validated on {n_test} " f"waveforms. Note that if this differs from number requested, it will not be " f"reflected in the settings file. This is likely due to EOB failure to " diff --git a/dingo/gw/importance_sampling/diagnostics.py b/dingo/gw/importance_sampling/diagnostics.py index 39cc95bb5..1733dbcbf 100644 --- a/dingo/gw/importance_sampling/diagnostics.py +++ b/dingo/gw/importance_sampling/diagnostics.py @@ -1,4 +1,5 @@ from os.path import join +import logging import numpy as np import math import pandas as pd @@ -6,6 +7,8 @@ from dingo.core.utils.plotting import plot_corner_multi from dingo.gw.result import Result +log = logging.getLogger(__name__) + def plot_posterior_slice2d( sampler, theta, theta_range, n_grid=100, num_processes=1, outname=None @@ -219,7 +222,7 @@ def plot_diagnostics( inds = np.where(weights > threshold)[0] theta_new = theta.loc[inds] weights_new = weights[inds] - print( + log.info( f"Generating cornerplot with {len(theta_new)} out of {len(theta)} IS samples." ) diff --git a/dingo/gw/importance_sampling/importance_weights.py b/dingo/gw/importance_sampling/importance_weights.py index 7f88fa67e..94e922868 100644 --- a/dingo/gw/importance_sampling/importance_weights.py +++ b/dingo/gw/importance_sampling/importance_weights.py @@ -2,53 +2,63 @@ Step 1: Train unconditional nde Step 2: Set up likelihood and prior """ + +import logging from pathlib import Path -import yaml from os import rename, makedirs -from os.path import dirname, join, isfile, exists -import argparse +from os.path import join, isfile, exists + +import hydra +from hydra.utils import to_absolute_path +from omegaconf import DictConfig, OmegaConf from dingo.core.posterior_models import NormalizingFlowPosteriorModel from dingo.gw.result import Result from dingo.gw.inference.gw_samplers import GWSampler from dingo.gw.importance_sampling.diagnostics import plot_diagnostics +log = logging.getLogger(__name__) +logging.captureWarnings(True) -def parse_args(): - parser = argparse.ArgumentParser( - description="Importance sampling (IS) for dingo models." - ) - parser.add_argument( - "--settings", - type=str, - required=True, - help="Path to settings file.", - ) - parser.add_argument( - "--outdir", - type=str, - default=None, - help="Output directory for the unconditional nde and IS results.", - ) - args = parser.parse_args() - - if args.outdir is None: - args.outdir = dirname(args.settings) - - return args +def _resolve_importance_input_paths(settings: dict) -> None: + parameter_samples = settings.get("parameter_samples") + if parameter_samples is None: + parameter_samples = ( + settings.get("nde", {}).get("data", {}).get("parameter_samples") + ) + if parameter_samples is None: + raise KeyError("Missing required setting parameter_samples.") -def main(): - # parse args, load settings, load dingo parameter samples - args = parse_args() - with open(args.settings, "r") as fp: - settings = yaml.safe_load(fp) - try: - result = Result(file_name=settings["parameter_samples"]) - except KeyError: - # except statement for backward compatibility - result = Result(file_name=settings["nde"]["data"]["parameter_samples"]) + parameter_samples = to_absolute_path(parameter_samples) + settings["parameter_samples"] = parameter_samples + if "nde" in settings: + settings["nde"].setdefault("data", {}) + settings["nde"]["data"]["parameter_samples"] = parameter_samples + + calibration = settings.get("calibration_marginalization") + if calibration and "calibration_envelope" in calibration: + calibration["calibration_envelope"] = { + ifo: to_absolute_path(path) + for ifo, path in calibration["calibration_envelope"].items() + } + + +@hydra.main( + version_base="1.3", + config_path="../../../configs", + config_name="importance_weights", +) +def main(cfg: DictConfig): + settings = OmegaConf.to_container(cfg, resolve=True) + outdir = settings.pop("outdir") + if outdir is None: + outdir = "." + makedirs(outdir, exist_ok=True) + _resolve_importance_input_paths(settings) + + result = Result(file_name=settings["parameter_samples"]) metadata = result.settings samples = result.samples # for time marginalization, we drop geocent time from the samples @@ -96,25 +106,23 @@ def main(): if "log_prob" not in samples.columns: # Use GPS time as name for now. event_name = str(result.event_metadata["time_event"]) - nde_name = settings["nde"].get( - "path", join(args.outdir, f"nde-{event_name}.pt") - ) + nde_name = settings["nde"].get("path") or join(outdir, f"nde-{event_name}.pt") if isfile(nde_name): - print(f"Loading nde at {nde_name} for event {event_name}.") + log.info(f"Loading nde at {nde_name} for event {event_name}.") nde = NormalizingFlowPosteriorModel( model_filename=nde_name, device=settings["nde"]["training"]["device"], load_training_info=False, ) else: - print(f"Training new nde for event {event_name}.") + log.info(f"Training new nde for event {event_name}.") nde = result.train_unconditional_flow( inference_parameters, settings["nde"], - train_dir=args.outdir, + train_dir=outdir, ) - print(f"Renaming trained nde model to {nde_name}.") - rename(join(args.outdir, "model_latest.pt"), nde_name) + log.info(f"Renaming trained nde model to {nde_name}.") + rename(join(outdir, "model_latest.pt"), nde_name) # Step 1a: Sample from proposal. nde_sampler = GWSampler(model=nde) @@ -162,10 +170,10 @@ def main(): # print(np.std(log_evidences) / np.mean(log_evidences_std)) if synthetic_phase: - print(f"Sampling synthetic phase.") + log.info("Sampling synthetic phase.") result.sample_synthetic_phase(synthetic_phase_kwargs) - print(f"Importance sampling.") + log.info("Importance sampling.") result.importance_sample( num_processes=settings.get("num_processes", 1), time_marginalization_kwargs=time_marginalization_kwargs, @@ -173,13 +181,13 @@ def main(): calibration_marginalization_kwargs=calibration_marginalization_kwargs, ) result.print_summary() - result.to_file(file_name=Path(args.outdir, "dingo_samples_weighted.hdf5")) + result.to_file(file_name=Path(outdir, "dingo_samples_weighted.hdf5")) # Diagnostics - diagnostics_dir = join(args.outdir, "IS-diagnostics") + diagnostics_dir = join(outdir, "IS-diagnostics") if not exists(diagnostics_dir): makedirs(diagnostics_dir) - print("Plotting diagnostics.") + log.info("Plotting diagnostics.") plot_diagnostics( result, diagnostics_dir, diff --git a/dingo/gw/inference/gw_samplers.py b/dingo/gw/inference/gw_samplers.py index 2a1b63d89..ecfabf822 100644 --- a/dingo/gw/inference/gw_samplers.py +++ b/dingo/gw/inference/gw_samplers.py @@ -1,3 +1,4 @@ +import logging from typing import Union, Protocol import numpy as np @@ -34,12 +35,13 @@ MaskDataForFrequencyRangeUpdate, ) +log = logging.getLogger(__name__) + class SamplerProtocol(Protocol): base_model_metadata: dict - def _initialize_transforms(self) -> None: - ... + def _initialize_transforms(self) -> None: ... class _GWMixinProtocol(SamplerProtocol): @@ -173,7 +175,6 @@ def _build_domain(self: Sampler): if "domain_update" in data_settings: self.domain.update(data_settings["domain_update"]) - def _correct_reference_time( self: Sampler, samples: Union[dict, pd.DataFrame], inverse: bool = False ): @@ -240,7 +241,7 @@ def _post_process(self, samples: Union[dict, pd.DataFrame], inverse: bool = Fals for k, p in prior.items(): if isinstance(p, DeltaFunction) and k not in samples: v = p.peak - print(f"Adding fixed parameter {k} = {v} from prior.") + log.info(f"Adding fixed parameter {k} = {v} from prior.") samples[k] = p.peak * np.ones(num_samples) else: # Drop non-inference parameters from samples. @@ -472,8 +473,8 @@ def _initialize_transforms(self): self.gnpe_parameters += transform.input_parameter_names for k, v in transform.kernel.items(): self.gnpe_kernel[k] = v - print("GNPE parameters: ", self.gnpe_parameters) - print("GNPE kernel: ", self.gnpe_kernel) + log.info(f"GNPE parameters: {self.gnpe_parameters}") + log.info(f"GNPE kernel: {self.gnpe_kernel}") self.transform_pre = Compose(transform_pre) diff --git a/dingo/gw/ls_cli.py b/dingo/gw/ls_cli.py index 5c401bee7..6abd3d72e 100644 --- a/dingo/gw/ls_cli.py +++ b/dingo/gw/ls_cli.py @@ -1,10 +1,11 @@ import argparse import json +import logging +import sys from pathlib import Path -from pprint import pprint +from pprint import pformat import h5py -import torch import yaml from dingo.core.dataset import DingoDataset @@ -15,19 +16,34 @@ from dingo.gw.SVD import SVDBasis -def ls(): +log = logging.getLogger(__name__) +logging.captureWarnings(True) + + +def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("file_name", type=str) - args = parser.parse_args() + return parser.parse_args() - path = Path(args.file_name) + +def ls(): + logging.basicConfig( + level=logging.INFO, + format="[%(asctime)s][%(name)s][%(levelname)s] - %(message)s", + stream=sys.stdout, + force=True, + ) + args = parse_args() + file_name = args.file_name + + path = Path(file_name) if path.suffix == ".pt": - print("Extracting information about torch model.\n") + log.info("Extracting information about torch model.\n") d, _ = torch_load_with_fallback(path, preferred_map_location="meta") - print(f"Version: {d.get('version')}\n") - print(f"Model epoch: {d['epoch']}\n") - print("Model metadata:") - print( + log.info(f"Version: {d.get('version')}\n") + log.info(f"Model epoch: {d['epoch']}\n") + log.info("Model metadata:") + log.info( yaml.dump( d["metadata"], default_flow_style=False, @@ -37,14 +53,14 @@ def ls(): elif path.suffix == ".hdf5": - dataset_type = determine_dataset_type(args.file_name) + dataset_type = determine_dataset_type(file_name) if dataset_type == "gw_result" or dataset_type == "core_result": - result = Result(file_name=args.file_name) - print(f"Version: {result.version}") - print("\nDingo Result\n" + "============\n") + result = Result(file_name=file_name) + log.info(f"Version: {result.version}") + log.info("\nDingo Result\n" + "============\n") - print( + log.info( "Metadata\n" + "--------\n" + yaml.dump( @@ -54,7 +70,7 @@ def ls(): ) ) if result.event_metadata: - print( + log.info( "Event information:\n" + "------------------\n" + yaml.dump( @@ -64,7 +80,7 @@ def ls(): ), ) if result.importance_sampling_metadata is not None: - print( + log.info( "Importance sampling:\n" + "--------------------\n" + yaml.dump( @@ -74,28 +90,28 @@ def ls(): ), ) if result.log_evidence: - print("Summary:\n" + "--------") + log.info("Summary:\n" + "--------") result.print_summary() elif dataset_type == "svd_basis": - svd = SVDBasis(file_name=args.file_name) - print(f"Dingo version: {svd.version}") - print("\nSVD Basis\n" + "=========\n") + svd = SVDBasis(file_name=file_name) + log.info(f"Dingo version: {svd.version}") + log.info("\nSVD Basis\n" + "=========\n") - print(f"Basis size: {svd.n}.") - print("\nValidation summary:\n" + "-------------------") + log.info(f"Basis size: {svd.n}.") + log.info("\nValidation summary:\n" + "-------------------") svd.print_validation_summary() elif dataset_type == "waveform_dataset": waveform_dataset = WaveformDataset( - file_name=args.file_name, leave_waveforms_on_disk=True + file_name=file_name, leave_waveforms_on_disk=True ) - print(f"Dingo version: {waveform_dataset.version}") - print("\nWaveform dataset\n" + "================\n") + log.info(f"Dingo version: {waveform_dataset.version}") + log.info("\nWaveform dataset\n" + "================\n") - print(f"Dataset size: {len(waveform_dataset)}") + log.info(f"Dataset size: {len(waveform_dataset)}") - print( + log.info( "\nSettings\n" + "--------\n" + yaml.dump( @@ -107,18 +123,18 @@ def ls(): if waveform_dataset.svd: svd = SVDBasis(dictionary=waveform_dataset.svd) - print("\nSVD validation summary:\n" + "---------------------------") + log.info("\nSVD validation summary:\n" + "---------------------------") svd.print_validation_summary() elif dataset_type == "asd_dataset": - asd_dataset = ASDDataset(file_name=args.file_name) - print(f"Dingo version: {asd_dataset.version}") - print("\nASD dataset\n" + "================\n") + asd_dataset = ASDDataset(file_name=file_name) + log.info(f"Dingo version: {asd_dataset.version}") + log.info("\nASD dataset\n" + "================\n") - print(f"Dataset size: {asd_dataset.length_info}\n") - print(f"GPS times (min/max): {asd_dataset.gps_info}") + log.info(f"Dataset size: {asd_dataset.length_info}\n") + log.info(f"GPS times (min/max): {asd_dataset.gps_info}") - print( + log.info( "\nSettings\n" + "--------\n" + yaml.dump( @@ -129,35 +145,35 @@ def ls(): ) elif dataset_type == "trained_model": - with h5py.File(args.file_name, "r") as f: - print("Extracting information about torch model.\n") - print(f"Version: {f.attrs['version']}") - print(f"Model epoch: {f.attrs['epoch']}") - print("Model metadata:") + with h5py.File(file_name, "r") as f: + log.info("Extracting information about torch model.\n") + log.info(f"Version: {f.attrs['version']}") + log.info(f"Model epoch: {f.attrs['epoch']}") + log.info("Model metadata:") for d in ["model_kwargs", "metadata"]: json_data = json.loads(f["serialized_dicts"][d][()]) - print(f"\n{d}:\n" + "-" * (len(d) + 1)) - pprint(json_data) + log.info(f"\n{d}:\n" + "-" * (len(d) + 1)) + log.info(pformat(json_data)) else: # Legacy (before dataset_type identifier). try: - svd = SVDBasis(file_name=args.file_name) - print(f"SVD dataset of size n={svd.n}.") - print("Validation summary:") + svd = SVDBasis(file_name=file_name) + log.info(f"SVD dataset of size n={svd.n}.") + log.info("Validation summary:") svd.print_validation_summary() except KeyError: dataset = DingoDataset( - file_name=args.file_name, + file_name=file_name, data_keys=[ "svd", ], ) if dataset.settings is not None: - print( + log.info( yaml.dump( dataset.settings, default_flow_style=False, @@ -166,13 +182,13 @@ def ls(): ) if dataset.svd is not None: svd = SVDBasis(dictionary=dataset.svd) - print("SVD validation summary:") + log.info("SVD validation summary:") svd.print_validation_summary() elif path.suffix == ".yaml": with open(path, "r") as f: settings = yaml.safe_load(f) - print( + log.info( yaml.dump( settings, default_flow_style=False, @@ -181,7 +197,7 @@ def ls(): ) else: - print("File type unrecognized.") + log.info("File type unrecognized.") def determine_dataset_type(file_name): diff --git a/dingo/gw/noise/asd_dataset.py b/dingo/gw/noise/asd_dataset.py index 10e9cf9ef..b4a378f75 100644 --- a/dingo/gw/noise/asd_dataset.py +++ b/dingo/gw/noise/asd_dataset.py @@ -1,4 +1,5 @@ import copy +import logging from pathlib import Path from typing import Iterable, Optional @@ -9,6 +10,8 @@ from dingo.gw.gwutils import * from dingo.gw.dataset import DingoDataset +log = logging.getLogger(__name__) + HIGH_ASD_VALUE = 1.0 @@ -59,7 +62,7 @@ def __init__( self.gps_times.pop(ifo) if "window_factor" in self.settings["domain_dict"]: - print( + log.info( "Warning: 'window_factor' is no longer used in ASDDataset. " "Removing from settings." ) @@ -135,14 +138,14 @@ def update_domain(self, domain_update): self.domain.domain_dict["type"] == "UniformFrequencyDomain" and domain_update["type"] == "MultibandedFrequencyDomain" ): - print("Updating ASD dataset to MultibandedFrequencyDomain.") + log.info("Updating ASD dataset to MultibandedFrequencyDomain.") asd_dataset_decimated = {} mfd = build_domain(domain_update) ufd = mfd.base_domain if not check_domain_compatibility(self.asds, ufd): # If the ASD length is not compatible with the new base UFD, # first truncate it. - print( + log.info( f" Truncating first to new base UniformFrequencyDomain: f_max " f"{self.domain.f_max} Hz -> {ufd.f_max} Hz" ) diff --git a/dingo/gw/noise/asd_estimation.py b/dingo/gw/noise/asd_estimation.py index 8239b3eab..bc6e9e290 100644 --- a/dingo/gw/noise/asd_estimation.py +++ b/dingo/gw/noise/asd_estimation.py @@ -1,10 +1,12 @@ -import argparse +import logging import os import pickle from os.path import join +import hydra import numpy as np -import yaml +from hydra.utils import to_absolute_path +from omegaconf import DictConfig, OmegaConf from tqdm import tqdm from dingo.gw.domains import build_domain @@ -13,6 +15,9 @@ from dingo.gw.noise.asd_dataset import ASDDataset from dingo.gw.noise.utils import psd_data_path +log = logging.getLogger(__name__) +logging.captureWarnings(True) + def download_and_estimate_psds( data_dir: str, @@ -69,6 +74,11 @@ def download_and_estimate_psds( } w = get_window(window_kwargs) asd_filename_list = {det: [] for det in detectors} + total_segments = sum(len(time_segments[det]) for det in detectors) + log.info( + f"Estimating PSDs for {total_segments} time segments across " + f"{len(time_segments)} detector(s)." + ) for det in detectors: psd_path = psd_data_path(data_dir, run, det) os.makedirs(psd_path, exist_ok=True) @@ -83,63 +93,54 @@ def download_and_estimate_psds( else: estimation_kwargs["det"] = det + log.info(f"Processing {len(time_segments[det])} PSD segment(s) for {det}.") for index, (start, end) in enumerate( tqdm(time_segments[det], disable=not verbose) ): filename = join(psd_path, f"asd_{start}.hdf5") asd_filename_list[det].append(filename) - if not os.path.exists(filename): - dataset_dict = { - "settings": { - "dataset_settings": settings["dataset_settings"], - "domain_dict": domain.domain_dict, - } + if os.path.exists(filename): + log.info(f"ASD file already exists, skipping {filename}.") + continue + + log.info(f"Estimating ASD for {det} segment starting at {start}.") + dataset_dict = { + "settings": { + "dataset_settings": settings["dataset_settings"], + "domain_dict": domain.domain_dict, } - psd = estimate_single_psd(time_start=start, **estimation_kwargs) - asd = np.sqrt(psd[domain.min_idx : domain.max_idx + 1]) - gps_time = start + } + psd = estimate_single_psd(time_start=start, **estimation_kwargs) + asd = np.sqrt(psd[domain.min_idx : domain.max_idx + 1]) + gps_time = start - dataset_dict["asds"] = {det: np.array([asd])} - dataset_dict["gps_times"] = {det: np.array([gps_time])} + dataset_dict["asds"] = {det: np.array([asd])} + dataset_dict["gps_times"] = {det: np.array([gps_time])} - dataset = ASDDataset(dictionary=dataset_dict) - dataset.to_file(file_name=filename) + dataset = ASDDataset(dictionary=dataset_dict) + dataset.to_file(file_name=filename) + log.info("PSD estimation complete.") return asd_filename_list -def download_and_estimate_cli(): +@hydra.main( + version_base="1.3", + config_path="../../../configs", + config_name="estimate_psds", +) +def download_and_estimate_cli(cfg: DictConfig): """ Command-line function to download strain data and estimate PSDs based on the data. Used for parallelized ASD dataset generation. """ - parser = argparse.ArgumentParser() - parser.add_argument( - "--data_dir", - type=str, - required=True, - help="Path where the PSD data is to be stored.", - ) - parser.add_argument( - "--settings_file", - type=str, - required=True, - help="Path to a settings file containing the settings for the dataset generation", - ) - parser.add_argument( - "--time_segments_file", - type=str, - default=None, - help="Path to a file containing the time segments for which PSDs should be estimated", - ) - - args = parser.parse_args() - # Load settings - with open(args.settings_file, "r") as f: - settings = yaml.safe_load(f) + settings = OmegaConf.to_container(cfg, resolve=True) + data_dir = to_absolute_path(settings.pop("data_dir")) + time_segments_file = to_absolute_path(settings.pop("time_segments_file")) + verbose = settings.pop("verbose") - with open(args.time_segments_file, "rb") as f: + with open(time_segments_file, "rb") as f: time_segments = pickle.load(f) - download_and_estimate_psds(args.data_dir, settings, time_segments) + download_and_estimate_psds(data_dir, settings, time_segments, verbose=verbose) diff --git a/dingo/gw/noise/generate_dataset.py b/dingo/gw/noise/generate_dataset.py index e8750e6e2..526a3bd94 100644 --- a/dingo/gw/noise/generate_dataset.py +++ b/dingo/gw/noise/generate_dataset.py @@ -1,77 +1,40 @@ -import argparse -import textwrap +import logging from os.path import join +import hydra import os -import yaml import pickle +from hydra.utils import to_absolute_path +from omegaconf import DictConfig, OmegaConf from dingo.gw.noise.asd_estimation import ( download_and_estimate_psds, ) from dingo.gw.noise.asd_dataset import ASDDataset -from dingo.gw.noise.generate_dataset_dag import create_dag from dingo.gw.noise.utils import merge_datasets, get_time_segments +log = logging.getLogger(__name__) +logging.captureWarnings(True) -def parse_args(): - parser = argparse.ArgumentParser( - formatter_class=argparse.RawDescriptionHelpFormatter, - description=textwrap.dedent( - """\ - Generate an ASD dataset based on a settings file. - """ - ), - ) - parser.add_argument( - "--data_dir", - type=str, - required=True, - help="Path where the PSD data is to be stored. Must contain a 'settings.yaml' file.", - ) - parser.add_argument( - "--settings_file", - type=str, - default=None, - help="Path to a settings file in case two different datasets are generated in the same directory", - ) - parser.add_argument( - "--time_segments_file", - type=str, - default=None, - help="Optional file containing a dictionary of a list of time segments that should be used for estimating PSDs." - "This has to be a pickle file.", - ) - parser.add_argument( - "--out_name", - type=str, - default=None, - help="Path to resulting ASD dataset", - ) - parser.add_argument("--verbose", action="store_true") - return parser.parse_args() - - -def generate_dataset(): +@hydra.main( + version_base="1.3", + config_path="../../../configs", + config_name="generate_asd_dataset", +) +def generate_dataset(cfg: DictConfig): """ Creates and saves an ASD dataset """ - args = parse_args() - - # Load settings - settings_file = ( - args.settings_file - if args.settings_file is not None - else join(args.data_dir, "asd_dataset_settings.yaml") - ) - with open(settings_file, "r") as f: - settings = yaml.safe_load(f) - - data_dir = args.data_dir - - if args.time_segments_file: - with open(args.time_segments_file, "rb") as f: + data_dir = to_absolute_path(cfg.data_dir) + settings = OmegaConf.to_container(cfg, resolve=True) + time_segments_file = settings.pop("time_segments_file") + out_name = settings.pop("out_name") + verbose = settings.pop("verbose") + settings.pop("data_dir") + + if time_segments_file: + with open(to_absolute_path(time_segments_file), "rb") as f: time_segments = pickle.load(f) else: time_segments = get_time_segments(settings["dataset_settings"]) @@ -84,31 +47,37 @@ def generate_dataset(): if "condor" in settings: - dagman = create_dag(data_dir, settings_file, time_segments, args.out_name) - - try: - dagman.visualize( - join(data_dir, "tmp", "condor", "ASD_dataset_generation_workflow.png") - ) - except: - pass - - dagman.build() - print(f"DAG submission file written.") + raise NotImplementedError( + "Hydra Stage 1 only supports local ASD dataset generation. " + "Condor/DAG ASD generation is deferred to Stage 3." + ) + # Legacy Condor/DAG implementation to translate in Stage 3: + # + # dagman = create_dag(data_dir, settings_file, time_segments, out_name) + # + # try: + # dagman.visualize( + # join(data_dir, "tmp", "condor", "ASD_dataset_generation_workflow.png") + # ) + # except: + # pass + # + # dagman.build() + # log.info("DAG submission file written.") else: - print("Downloading strain data and estimating PSDs...") + log.info("Downloading strain data and estimating PSDs...") asd_filename_list = download_and_estimate_psds( - args.data_dir, settings, time_segments, verbose=args.verbose + data_dir, settings, time_segments, verbose=verbose ) asd_dataset_list = { det: [ASDDataset(asd_file) for asd_file in asd_file_list] for det, asd_file_list in asd_filename_list.items() } - print("Merging single dataset files into one...") + log.info("Merging single dataset files into one...") dataset = merge_datasets(asd_dataset_list) - filename = args.out_name + filename = out_name if filename is None: run = settings["dataset_settings"]["observing_run"] filename = join(data_dir, f"asds_{run}.hdf5") diff --git a/dingo/gw/noise/synthetic/asd_sampling.py b/dingo/gw/noise/synthetic/asd_sampling.py index 707ca586d..1c2f941cb 100644 --- a/dingo/gw/noise/synthetic/asd_sampling.py +++ b/dingo/gw/noise/synthetic/asd_sampling.py @@ -1,4 +1,5 @@ import copy +import logging import numpy as np from scipy import stats @@ -8,11 +9,14 @@ get_index_for_elem, ) +log = logging.getLogger(__name__) + class KDE: """ Kernel Density Estimation (KDE) class for sampling ASDs. """ + def __init__(self, parameter_dict, sampling_settings): """ Parameters @@ -53,7 +57,7 @@ def fit(self, weights=None): weights=weights, ) except np.linalg.LinAlgError: - print( + log.info( "Warning: Singular Matrix encountered in spectral KDE. Adding small Gaussian noise..." ) perturbed_features = spectral_features[:, i, :] + np.random.normal( @@ -74,7 +78,7 @@ def fit(self, weights=None): split_indices = sorted(split_indices) for i in range(len(split_indices) - 1): - vals = y_values[:, split_indices[i]: split_indices[i + 1]].T + vals = y_values[:, split_indices[i] : split_indices[i + 1]].T kde_vals = stats.gaussian_kde( vals, bw_method=float(self.settings["bandwidth_spline"]) ) @@ -110,9 +114,7 @@ def sample(self, num_samples, rescaling_ys=None): # rescale base noise if rescaling_ys: y_values_mean = np.mean(y_values, axis=0) - y_values = ( - y_values - y_values_mean[None, :] + rescaling_ys[det] - ) + y_values = y_values - y_values_mean[None, :] + rescaling_ys[det] parameters_dicts[det]["y_values"] = y_values return parameters_dicts diff --git a/dingo/gw/noise/synthetic/generate_dataset.py b/dingo/gw/noise/synthetic/generate_dataset.py index 0f59c17bf..a3896a384 100644 --- a/dingo/gw/noise/synthetic/generate_dataset.py +++ b/dingo/gw/noise/synthetic/generate_dataset.py @@ -1,65 +1,24 @@ -import argparse import copy -import textwrap +import logging import numpy as np from typing import Dict -import yaml +import hydra +from hydra.utils import to_absolute_path +from omegaconf import DictConfig, OmegaConf from dingo.gw.noise.asd_dataset import ASDDataset from dingo.gw.noise.synthetic.asd_parameterization import parameterize_asd_dataset from dingo.gw.noise.synthetic.asd_sampling import KDE, get_rescaling_params from dingo.gw.noise.synthetic.utils import reconstruct_psds_from_parameters - -def parse_args(): - parser = argparse.ArgumentParser( - formatter_class=argparse.RawDescriptionHelpFormatter, - description=textwrap.dedent( - """\ - Generate a synthetic noise ASD dataset from an existing dataset of real ASDs. ASDs can be parameterized to generate - smooth PSDs, e.g. to obtain a distribution over ASDs similar to BayesWave ASDs, or we can create a dataset over - synthetic PSDs to augment the training distribution and enhance robustness. In particular, this allows us to - shift a distribution over ASDs from a source to a target observing run, where insufficient data is available. - """ - ), - ) - parser.add_argument( - "--asd_dataset", - type=str, - required=True, - help="Path to existing ASD dataset to be parameterized and re-sampled", - ) - parser.add_argument( - "--settings_file", - type=str, - required=True, - help="YAML file containing database settings", - ) - parser.add_argument( - "--num_samples", - type=int, - default=500, - help="Number of samples to draw from the parameterized ASDs", - ) - parser.add_argument( - "--num_processes", - type=int, - default=1, - help="Number of processes to use in pool for parallel parameterization", - ) - parser.add_argument( - "--out_file", - type=str, - default="synthetic_asd_dataset.hdf5", - help="Name of file for storing dataset.", - ) - parser.add_argument("--verbose", action="store_true") - - return parser.parse_args() +log = logging.getLogger(__name__) +logging.captureWarnings(True) -def generate_dataset(real_dataset, settings: Dict, num_samples, num_processes: int, verbose: bool): +def generate_dataset( + real_dataset, settings: Dict, num_samples, num_processes: int, verbose: bool +): """ Generate a synthetic ASD dataset from an existing dataset of real ASDs. @@ -98,7 +57,9 @@ def generate_dataset(real_dataset, settings: Dict, num_samples, num_processes: i settings["parameterization_settings"], ) parameters_dict = kde.sample(num_samples, rescaling_params) - synthetic_dataset_dict["settings"]["sampling_settings"] = settings["sampling_settings"] + synthetic_dataset_dict["settings"]["sampling_settings"] = settings[ + "sampling_settings" + ] asds_dict = {} for det, params in parameters_dict.items(): @@ -115,24 +76,28 @@ def generate_dataset(real_dataset, settings: Dict, num_samples, num_processes: i synthetic_dataset_dict["asd_parameterizations"] = parameters_dict synthetic_dataset_dict["asds"] = asds_dict - return ASDDataset( - dictionary=synthetic_dataset_dict - ) - + return ASDDataset(dictionary=synthetic_dataset_dict) -def main(): - args = parse_args() - # Load settings - with open(args.settings_file, "r") as f: - settings = yaml.safe_load(f) +@hydra.main( + version_base="1.3", + config_path="../../../../configs", + config_name="generate_synthetic_asd_dataset", +) +def main(cfg: DictConfig): + settings = OmegaConf.to_container(cfg, resolve=True) + asd_dataset = to_absolute_path(settings.pop("asd_dataset")) + num_samples = settings.pop("num_samples") + num_processes = settings.pop("num_processes") + out_file = settings.pop("out_file") + verbose = settings.pop("verbose") - real_dataset = ASDDataset(file_name=args.asd_dataset) + real_dataset = ASDDataset(file_name=asd_dataset) synthetic_dataset = generate_dataset( - real_dataset, settings, args.num_samples, args.num_processes, args.verbose + real_dataset, settings, num_samples, num_processes, verbose ) - synthetic_dataset.to_file(args.out_file) + synthetic_dataset.to_file(out_file) if __name__ == "__main__": diff --git a/dingo/gw/noise/utils.py b/dingo/gw/noise/utils.py index 43d1f82ce..dc94d5b44 100644 --- a/dingo/gw/noise/utils.py +++ b/dingo/gw/noise/utils.py @@ -1,4 +1,3 @@ -import argparse import os.path import pickle from io import StringIO @@ -6,14 +5,20 @@ from os.path import isfile import glob import copy +import logging +import hydra import numpy as np import requests -import yaml +from hydra.utils import to_absolute_path +from omegaconf import DictConfig, OmegaConf from gwpy.table import EventTable from dingo.gw.noise.asd_dataset import ASDDataset +log = logging.getLogger(__name__) +logging.captureWarnings(True) + """ Catalogue against which to check that no event is present in the estimated PSDs """ @@ -156,7 +161,7 @@ def merge_datasets(asd_dataset_list): merged_dict = {"asds": {}, "gps_times": {}} for det, asd_list in asd_dataset_list.items(): - print(f"Merging {len(asd_list)} datasets into one for detector {det}.") + log.info(f"Merging {len(asd_list)} datasets into one for detector {det}.") merged_dict["asds"][det] = np.vstack( [asd_dataset.asds[det] for asd_dataset in asd_list] ) @@ -171,51 +176,26 @@ def merge_datasets(asd_dataset_list): return merged -def merge_datasets_cli(): +@hydra.main( + version_base="1.3", + config_path="../../../configs", + config_name="merge_asd_datasets", +) +def merge_datasets_cli(cfg: DictConfig): """ Command-line function to combine a collection of datasets into one. Used for parallelized ASD dataset generation. """ - parser = argparse.ArgumentParser() - parser.add_argument( - "--data_dir", - type=str, - required=True, - help="Path where the PSD data is to be stored.", - ) - parser.add_argument( - "--settings_file", - type=str, - required=True, - help="Path to a settings file that contains the settings for the ASD dataset generation", - ) - parser.add_argument( - "--time_segments_file", - type=str, - default=None, - help="Path to a file containing the time segments for which PSDs should be estimated", - ) - parser.add_argument( - "--num_parts", - type=int, - default=-1, - help="Number of ASD datasets that should be merged", - ) - parser.add_argument( - "--out_name", - type=str, - default=None, - help="File name of merged dataset", - ) - args = parser.parse_args() - - with open(args.settings_file, "r") as f: - settings = yaml.safe_load(f) + settings = OmegaConf.to_container(cfg, resolve=True) + data_dir = to_absolute_path(settings.pop("data_dir")) + time_segments_file = settings.pop("time_segments_file") + num_parts = settings.pop("num_parts") + out_name = settings.pop("out_name") time_segments = None - if args.time_segments_file: - with open(args.time_segments_file, "rb") as f: + if time_segments_file: + with open(to_absolute_path(time_segments_file), "rb") as f: time_segments = pickle.load(f) detectors = settings["dataset_settings"]["detectors"] @@ -223,7 +203,7 @@ def merge_datasets_cli(): asd_dataset_list = {det: [] for det in detectors} for det in detectors: - file_dir = psd_data_path(args.data_dir, observing_run, det) + file_dir = psd_data_path(data_dir, observing_run, det) if time_segments: filenames = [ join(file_dir, f"asd_{seg[0]}.hdf5") @@ -232,14 +212,16 @@ def merge_datasets_cli(): ] else: # if no time_segments are specified, use the first 'num_parts' ASD datasets filenames = sorted(glob.glob(join(file_dir, f"asd_*.hdf5"))) - num_parts = min(args.num_parts, len(filenames)) if args.num_parts > 0 else len(filenames) + num_parts = ( + min(num_parts, len(filenames)) if num_parts > 0 else len(filenames) + ) filenames = filenames[:num_parts] asd_dataset_list[det] = [ASDDataset(filename) for filename in filenames] merged_dataset = merge_datasets(asd_dataset_list) - filename = args.out_name + filename = out_name if filename is None: run = settings["dataset_settings"]["observing_run"] - filename = join(args.data_dir, f"asds_{run}.hdf5") + filename = join(data_dir, f"asds_{run}.hdf5") merged_dataset.to_file(filename) diff --git a/dingo/gw/result.py b/dingo/gw/result.py index 51616032d..b5d130ba3 100644 --- a/dingo/gw/result.py +++ b/dingo/gw/result.py @@ -1,4 +1,5 @@ import copy +import logging import time from typing import Optional @@ -24,6 +25,7 @@ RANDOM_STATE = 150914 +log = logging.getLogger(__name__) class Result(CoreResult): @@ -199,8 +201,8 @@ def _rebuild_domain(self, verbose=False): ) if verbose: - print("Rebuilding domain as follows:") - print( + log.info("Rebuilding domain as follows:") + log.info( yaml.dump( domain_dict, default_flow_style=False, @@ -210,7 +212,7 @@ def _rebuild_domain(self, verbose=False): self.domain = build_domain(domain_dict) else: if verbose: - print("No domain updates found; domain not rebuilt.") + log.info("No domain updates found; domain not rebuilt.") def _build_prior(self): """Build the prior based on model metadata. Called by __init__().""" @@ -359,7 +361,7 @@ def _build_likelihood( if "updates" in self.importance_sampling_metadata: if "T" in self.importance_sampling_metadata["updates"]: delta_f_new = 1 / self.importance_sampling_metadata["updates"]["T"] - print( + log.info( f'Updating waveform generation delta_f from {wfg_domain_dict["delta_f"]} to {delta_f_new}.' ) wfg_domain_dict["delta_f"] = delta_f_new @@ -415,13 +417,18 @@ def sample_calibration_parameters(self, calibration_sampling_kwargs: dict): self.calibration_sampling_kwargs = calibration_sampling_kwargs # Handle correction_type defaults - correction_type = self.calibration_sampling_kwargs.get("correction_type", "data") + correction_type = self.calibration_sampling_kwargs.get( + "correction_type", "data" + ) if correction_type is None: correction_type_dict = { - ifo: CALIBRATION_CORRECTION_TYPE_LOOKUP[ifo] for ifo in self.interferometers + ifo: CALIBRATION_CORRECTION_TYPE_LOOKUP[ifo] + for ifo in self.interferometers } elif correction_type == "data" or correction_type == "template": - correction_type_dict = {ifo: correction_type for ifo in self.interferometers} + correction_type_dict = { + ifo: correction_type for ifo in self.interferometers + } elif isinstance(correction_type, dict): correction_type_dict = correction_type else: @@ -440,7 +447,7 @@ def sample_calibration_parameters(self, calibration_sampling_kwargs: dict): ) # Removing the delta function priors on the frequency nodes, amplitude and phase. - # Usually the frequency nodes are set to delta functions, but we also remove the + # Usually the frequency nodes are set to delta functions, but we also remove the # the amplitude and phase delta functions if present. # This avoids large log probs and log priors, since the density of a delta function # at the sampled point is infinite. The delta functions do not affect the sampling, @@ -452,14 +459,14 @@ def sample_calibration_parameters(self, calibration_sampling_kwargs: dict): # Sample calibration parameters and calculate log_prob num_samples = len(self.samples) - print(f"Sampling calibration parameters for {num_samples} samples.") + log.info(f"Sampling calibration parameters for {num_samples} samples.") delta_log_prob = np.zeros(num_samples) # Here we will sample the calibration parameters from the prior. - # We treat the *prior as the proposal* distribution and - # therefore add the log_prob of the sampled calibration parameters - # to the existing log_prob. We also will update the prior + # We treat the *prior as the proposal* distribution and + # therefore add the log_prob of the sampled calibration parameters + # to the existing log_prob. We also will update the prior # to include the calibration priors using the importance_sampling_metadata prior_update = self.importance_sampling_metadata.get("prior_update", {}) for ifo, prior in calibration_priors.items(): @@ -571,7 +578,7 @@ def sample_synthetic_phase( self.synthetic_phase_kwargs.get("num_processes", 1), num_valid_samples // 10 ) - print(f"Estimating synthetic phase for {num_valid_samples} samples.") + log.info(f"Estimating synthetic phase for {num_valid_samples} samples.") t0 = time.time() if not inverse: @@ -660,7 +667,7 @@ def sample_synthetic_phase( self.samples["log_prob"] = log_prob_array del self.samples["phase"] - print(f"Done. This took {time.time() - t0:.2f} s.") + log.info(f"Done. This took {time.time() - t0:.2f} s.") def get_samples_bilby_phase(self, num_processes=1): """ @@ -690,7 +697,9 @@ def get_samples_bilby_phase(self, num_processes=1): num_processes=num_processes, ) - def get_pesummary_samples(self, num_processes=1, resampling_method="clip+rejection"): + def get_pesummary_samples( + self, num_processes=1, resampling_method="clip+rejection" + ): """Samples in a form suitable for PESummary. These samples are adjusted to undo certain conventions used internally by diff --git a/dingo/gw/training/train_builders.py b/dingo/gw/training/train_builders.py index ca62856bb..f3695015e 100755 --- a/dingo/gw/training/train_builders.py +++ b/dingo/gw/training/train_builders.py @@ -1,5 +1,6 @@ from typing import List, Optional import copy +import logging import torch.multiprocessing import torchvision @@ -28,6 +29,8 @@ from dingo.gw.gwutils import * from dingo.core.utils import * +log = logging.getLogger(__name__) + def build_dataset( data_settings: dict, @@ -82,9 +85,9 @@ def set_train_transforms(wfd, data_settings, asd_dataset_path, omit_transforms=N List of sub-transforms to omit from the full composition. """ - print(f"Setting train transforms.") + log.info("Setting train transforms.") if omit_transforms is not None: - print("Omitting \n\t" + "\n\t".join([t.__name__ for t in omit_transforms])) + log.info("Omitting \n\t" + "\n\t".join([t.__name__ for t in omit_transforms])) # By passing the wfd domain when instantiating the noise dataset, this ensures the # domains will match. In particular, it truncates the ASD dataset beyond the new @@ -142,9 +145,9 @@ def set_train_transforms(wfd, data_settings, asd_dataset_path, omit_transforms=N # parameters. try: standardization_dict = data_settings["standardization"] - print("Using previously-calculated parameter standardizations.") + log.info("Using previously-calculated parameter standardizations.") except KeyError: - print("Calculating new parameter standardizations.") + log.info("Calculating new parameter standardizations.") standardization_dict = get_standardization_dict( extrinsic_prior_dict, wfd, @@ -258,7 +261,7 @@ def build_svd_for_embedding_network( ], ) - print("Generating waveforms for embedding network SVD initialization.") + log.info("Generating waveforms for embedding network SVD initialization.") time_start = time.time() ifos = list(wfd[0]["waveform"].keys()) waveform_len = len(wfd[0]["waveform"][ifos[0]]) @@ -277,7 +280,7 @@ def build_svd_for_embedding_network( loader = DataLoader( wfd, batch_size=batch_size, - num_workers= 0, + num_workers=0, worker_init_fn=fix_random_seeds, ) with threadpool_limits(limits=1, user_api="blas"): @@ -296,25 +299,25 @@ def build_svd_for_embedding_network( waveforms[ifo][lower : lower + n] = strains[:n] if lower + n == num_waveforms: break - print(f"...done. This took {time.time() - time_start:.0f} s.") + log.info(f"...done. This took {time.time() - time_start:.0f} s.") # Reset the standard sharing strategy. torch.multiprocessing.set_sharing_strategy(old_sharing_strategy) - print("Generating SVD basis for ifo:") + log.info("Generating SVD basis for ifo:") time_start = time.time() basis_dict = {} for ifo in ifos: basis = SVDBasis() basis.generate_basis(waveforms[ifo][:num_training_samples], size) basis_dict[ifo] = basis - print(f"...{ifo} done.") - print(f"...this took {time.time() - time_start:.0f} s.") + log.info(f"...{ifo} done.") + log.info(f"...this took {time.time() - time_start:.0f} s.") if out_dir is not None: - print(f"Testing SVD basis matrices.") + log.info("Testing SVD basis matrices.") for ifo, basis in basis_dict.items(): - print(f"...{ifo}:") + log.info(f"...{ifo}:") basis.compute_test_mismatches( waveforms[ifo][num_training_samples:], parameters=parameters.iloc[num_training_samples:].reset_index( @@ -323,19 +326,19 @@ def build_svd_for_embedding_network( verbose=True, ) basis.to_file(os.path.join(out_dir, f"svd_{ifo}.hdf5")) - print("Done") + log.info("Done") # Return V matrices in standard order. Drop the elements below domain.min_idx, # since the neural network expects data truncated below these. The dropped elements # should be 0. - print(f"Truncating SVD matrices below index {wfd.domain.min_idx}.") - print("...V matrix shapes:") + log.info(f"Truncating SVD matrices below index {wfd.domain.min_idx}.") + log.info("...V matrix shapes:") V_rb_list = [] for ifo in data_settings["detectors"]: V = basis_dict[ifo].V assert np.allclose(V[: wfd.domain.min_idx], 0) V = V[wfd.domain.min_idx :] - print(" " + str(V.shape)) + log.info(" " + str(V.shape)) V_rb_list.append(V) - print("\n") + log.info("\n") return V_rb_list diff --git a/dingo/gw/training/train_pipeline.py b/dingo/gw/training/train_pipeline.py index ac1420b0f..7379e9df5 100644 --- a/dingo/gw/training/train_pipeline.py +++ b/dingo/gw/training/train_pipeline.py @@ -1,14 +1,16 @@ from typing import Optional, Tuple +import logging import os +import hydra import numpy as np import yaml -import argparse import shutil -import textwrap import time from copy import deepcopy +from hydra.utils import to_absolute_path +from omegaconf import DictConfig, OmegaConf from threadpoolctl import threadpool_limits from dingo.core.posterior_models.build_model import ( @@ -30,6 +32,9 @@ from dingo.gw.dataset import WaveformDataset from dingo.core.posterior_models import BasePosteriorModel +log = logging.getLogger(__name__) +logging.captureWarnings(True) + def copy_files_to_local( file_path: str, @@ -61,14 +66,16 @@ def copy_files_to_local( if local_dir is not None: file_name = file_path.split("/")[-1] local_file_path = os.path.join(local_dir, file_name) - print(f"Copying file to {local_file_path}") + log.info(f"Copying file to {local_file_path}") # Copy file start_time = time.time() shutil.copy(file_path, local_file_path) elapsed_time = time.time() - start_time - print("Done. This took {:2.0f}:{:2.0f} min.".format(*divmod(elapsed_time, 60))) + log.info( + "Done. This took {:2.0f}:{:2.0f} min.".format(*divmod(elapsed_time, 60)) + ) elif leave_keys_on_disk and is_condor: - print( + log.info( f"Warning: leave_waveforms_on_disk defaults to True, but local_cache_path is not specified. " f"This means that the waveforms will be loaded during training from {local_file_path} ." f"This can lead to unexpected long times for data loading during training due to network traffic. " @@ -120,9 +127,10 @@ def prepare_training_new( # The embedding network is assumed to have an SVD projection layer. If other types # of embedding networks are added in the future, update this code. - if train_settings["model"].get("embedding_kwargs", None): + svd_settings = (train_settings["model"].get("embedding_kwargs") or {}).get("svd") + if svd_settings and svd_settings.get("num_training_samples", 0) > 0: # First, build the SVD for seeding the embedding network. - print("\nBuilding SVD for initialization of embedding network.") + log.info("\nBuilding SVD for initialization of embedding network.") initial_weights["V_rb_list"] = build_svd_for_embedding_network( wfd, train_settings["data"], @@ -153,13 +161,13 @@ def prepare_training_new( "train_settings": train_settings, } - print("\nInitializing new posterior model.") - print("Complete settings:") - print(yaml.dump(full_settings, default_flow_style=False, sort_keys=False)) + log.info("\nInitializing new posterior model.") + log.info("Complete settings:") + log.info(yaml.dump(full_settings, default_flow_style=False, sort_keys=False)) pm = build_model_from_kwargs( settings=full_settings, - initial_weights=initial_weights, + initial_weights=initial_weights or None, device=local_settings["device"], ) @@ -173,7 +181,7 @@ def prepare_training_new( **local_settings["wandb"], ) except ImportError: - print("WandB is enabled but not installed.") + log.info("WandB is enabled but not installed.") return pm, wfd @@ -226,7 +234,7 @@ def prepare_training_resume( **local_settings["wandb"], ) except ImportError: - print("WandB is enabled but not installed.") + log.info("WandB is enabled but not installed.") return pm, wfd @@ -278,7 +286,7 @@ def initialize_stage( if not resume: # New optimizer and scheduler. If we are resuming, these should have been # loaded from the checkpoint. - print("Initializing new optimizer and scheduler.") + log.info("Initializing new optimizer and scheduler.") pm.optimizer_kwargs = stage["optimizer"] pm.scheduler_kwargs = stage["scheduler"] pm.initialize_optimizer_and_scheduler() @@ -295,7 +303,7 @@ def initialize_stage( ) n_grad = get_number_of_model_parameters(pm.network, (True,)) n_nograd = get_number_of_model_parameters(pm.network, (False,)) - print(f"Fixed parameters: {n_nograd}\nLearnable parameters: {n_grad}\n") + log.info(f"Fixed parameters: {n_nograd}\nLearnable parameters: {n_grad}\n") return train_loader, test_loader @@ -343,14 +351,14 @@ def train_stages( stage = stages[n] if pm.epoch == end_epochs[n] - stage["epochs"]: - print(f"\nBeginning training stage {n}. Settings:") - print(yaml.dump(stage, default_flow_style=False, sort_keys=False)) + log.info(f"\nBeginning training stage {n}. Settings:") + log.info(yaml.dump(stage, default_flow_style=False, sort_keys=False)) train_loader, test_loader = initialize_stage( pm, wfd, stage, local_settings["num_workers"], resume=False ) else: - print(f"\nResuming training in stage {n}. Settings:") - print(yaml.dump(stage, default_flow_style=False, sort_keys=False)) + log.info(f"\nResuming training in stage {n}. Settings:") + log.info(yaml.dump(stage, default_flow_style=False, sort_keys=False)) train_loader, test_loader = initialize_stage( pm, wfd, stage, local_settings["num_workers"], resume=True ) @@ -359,7 +367,7 @@ def train_stages( try: early_stopping = EarlyStopping(**stage["early_stopping"]) except Exception: - print( + log.info( "Early stopping settings invalid. Please pass 'patience', 'delta', 'metric'" ) raise @@ -381,10 +389,10 @@ def train_stages( if pm.epoch == end_epochs[n]: save_file = os.path.join(train_dir, f"model_stage_{n}.pt") - print(f"Training stage complete. Saving to {save_file}.") + log.info(f"Training stage complete. Saving to {save_file}.") pm.save_model(save_file, save_training_info=True) if runtime_limits.local_limits_exceeded(pm.epoch): - print("Local runtime limits reached. Ending program.") + log.info("Local runtime limits reached. Ending program.") break if pm.epoch == end_epochs[-1]: @@ -393,66 +401,39 @@ def train_stages( return False -def parse_args(): - parser = argparse.ArgumentParser( - formatter_class=argparse.RawDescriptionHelpFormatter, - description=textwrap.dedent( - """\ - Train a neural network for gravitational-wave single-event inference. - - This program can be called in one of two ways: - a) with a settings file. This will create a new network based on the - contents of the settings file. - b) with a checkpoint file. This will resume training from the checkpoint. - """ - ), - ) - parser.add_argument( - "--settings_file", - type=str, - help="YAML file containing training settings.", - ) - parser.add_argument( - "--train_dir", required=True, help="Directory for Dingo training output." - ) - parser.add_argument( - "--checkpoint", - type=str, - help="Checkpoint file from which to resume training.", +def _resolve_training_input_paths(train_settings: dict) -> None: + train_settings["data"]["waveform_dataset_path"] = to_absolute_path( + train_settings["data"]["waveform_dataset_path"] ) - parser.add_argument( - "--exit_command", - type=str, - default="", - help="Optional command to execute after completion of training.", - ) - args = parser.parse_args() - - # The settings file and checkpoint are mutually exclusive. - if args.checkpoint is None and args.settings_file is None: - parser.error("Must specify either a checkpoint file or a settings file.") - if args.checkpoint is not None and args.settings_file is not None: - parser.error("Cannot specify both a checkpoint file and a settings file.") - - return args + for stage in train_settings["training"].values(): + if isinstance(stage, dict) and "asd_dataset_path" in stage: + stage["asd_dataset_path"] = to_absolute_path(stage["asd_dataset_path"]) -def train_local(): - args = parse_args() +@hydra.main( + version_base="1.3", + config_path="../../../configs", + config_name="train", +) +def train_local(cfg: DictConfig): + settings = OmegaConf.to_container(cfg, resolve=True) + checkpoint = settings.pop("checkpoint") + train_dir = settings.pop("train_dir") + exit_command = settings.pop("exit_command") - os.makedirs(args.train_dir, exist_ok=True) + os.makedirs(train_dir, exist_ok=True) - if args.settings_file is not None: - print("Beginning new training run.") - with open(args.settings_file, "r") as fp: - train_settings = yaml.safe_load(fp) + if checkpoint is None: + log.info("Beginning new training run.") + train_settings = settings + _resolve_training_input_paths(train_settings) # Extract the local settings from train settings file, save it separately. This # file can later be modified, and the settings take effect immediately upon # resuming. local_settings = train_settings.pop("local") - with open(os.path.join(args.train_dir, "local_settings.yaml"), "w") as f: + with open(os.path.join(train_dir, "local_settings.yaml"), "w") as f: if ( local_settings.get("wandb", False) and "id" not in local_settings["wandb"].keys() @@ -462,31 +443,30 @@ def train_local(): local_settings["wandb"]["id"] = wandb.util.generate_id() except ImportError: - print("wandb not installed, cannot generate run id.") + log.info("wandb not installed, cannot generate run id.") yaml.dump(local_settings, f, default_flow_style=False, sort_keys=False) - pm, wfd = prepare_training_new(train_settings, args.train_dir, local_settings) + pm, wfd = prepare_training_new(train_settings, train_dir, local_settings) else: - if not os.path.isfile(args.checkpoint): - raise FileNotFoundError(f"Checkpoint not found: {args.checkpoint}") - print("Resuming training run.") - with open(os.path.join(args.train_dir, "local_settings.yaml"), "r") as f: + checkpoint = to_absolute_path(checkpoint) + if not os.path.isfile(checkpoint): + raise FileNotFoundError(f"Checkpoint not found: {checkpoint}") + log.info("Resuming training run.") + with open(os.path.join(train_dir, "local_settings.yaml"), "r") as f: local_settings = yaml.safe_load(f) - pm, wfd = prepare_training_resume( - args.checkpoint, local_settings, args.train_dir - ) + pm, wfd = prepare_training_resume(checkpoint, local_settings, train_dir) with threadpool_limits(limits=1, user_api="blas"): - complete = train_stages(pm, wfd, args.train_dir, local_settings) + complete = train_stages(pm, wfd, train_dir, local_settings) if complete: - if args.exit_command: - print( - f"All training stages complete. Executing exit command: {args.exit_command}." + if exit_command: + log.info( + f"All training stages complete. Executing exit command: {exit_command}." ) - os.system(args.exit_command) + os.system(exit_command) else: - print("All training stages complete.") + log.info("All training stages complete.") else: - print("Program terminated due to runtime limit.") + log.info("Program terminated due to runtime limit.") diff --git a/dingo/gw/training/utils.py b/dingo/gw/training/utils.py index 4cd0732d8..8e9736280 100644 --- a/dingo/gw/training/utils.py +++ b/dingo/gw/training/utils.py @@ -1,23 +1,28 @@ -import argparse +import logging +import hydra import numpy as np import torch import yaml +from hydra.utils import to_absolute_path +from omegaconf import DictConfig, OmegaConf from dingo.core.utils.backward_compatibility import torch_load_with_fallback +log = logging.getLogger(__name__) +logging.captureWarnings(True) -def append_stage(): - parser = argparse.ArgumentParser() - parser.add_argument("--checkpoint", type=str, required=True) - parser.add_argument("--stage_settings_file", type=str, required=True) - parser.add_argument("--out_file", type=str, required=True) - parser.add_argument("--replace", type=int) - args = parser.parse_args() +@hydra.main( + version_base="1.3", + config_path="../../../configs", + config_name="append_training_stage", +) +def append_stage(cfg: DictConfig): + cfg = OmegaConf.to_container(cfg, resolve=True) # trying to load on CUDA, MPS, HIP or CPU - d, _ = torch_load_with_fallback(args.checkpoint) + d, _ = torch_load_with_fallback(to_absolute_path(cfg["checkpoint"])) stages = [ v @@ -25,34 +30,33 @@ def append_stage(): if k.startswith("stage_") ] num_stages = len(stages) - print(f"Checkpoint training plan consists of {num_stages} stages.") + log.info(f"Checkpoint training plan consists of {num_stages} stages.") - with open(args.stage_settings_file, "r") as f: - new_stage = yaml.safe_load(f) + new_stage = cfg["stage"] - if args.replace is not None: - if args.replace < 0 or args.replace >= num_stages: + if cfg["replace"] is not None: + if cfg["replace"] < 0 or cfg["replace"] >= num_stages: raise ValueError( - f"Invalid argument replace={args.replace}. Valid values " + f"Invalid argument replace={cfg['replace']}. Valid values " f"are {list(range(num_stages))}." ) current_epoch = d["epoch"] - stage_epoch = np.sum([s["epochs"] for s in stages[: args.replace]]) + stage_epoch = np.sum([s["epochs"] for s in stages[: cfg["replace"]]]) if current_epoch > stage_epoch: - print( + log.info( f"WARNING: Modification to training plan changes a training stage " f"that has already started. Current model epoch is {current_epoch}. " f"Proceed at your own risk!" ) - print(f"Replacing planned stage {args.replace} with new stage.") - new_stage_number = args.replace + log.info(f"Replacing planned stage {cfg['replace']} with new stage.") + new_stage_number = cfg["replace"] else: - print(f"Appending new stage to training plan.") + log.info("Appending new stage to training plan.") new_stage_number = num_stages d["metadata"]["train_settings"]["training"][f"stage_{new_stage_number}"] = new_stage - print("Summary of new training plan:") - print( + log.info("Summary of new training plan:") + log.info( yaml.dump( d["metadata"]["train_settings"]["training"], default_flow_style=False, @@ -60,4 +64,4 @@ def append_stage(): ) ) - torch.save(d, args.out_file) + torch.save(d, cfg["out_file"]) diff --git a/dingo/gw/transforms/waveform_transforms.py b/dingo/gw/transforms/waveform_transforms.py index f6d022435..7bba751ef 100644 --- a/dingo/gw/transforms/waveform_transforms.py +++ b/dingo/gw/transforms/waveform_transforms.py @@ -1,8 +1,12 @@ from typing import Optional +import logging + import numpy as np from dingo.gw.domains import MultibandedFrequencyDomain, UniformFrequencyDomain +log = logging.getLogger(__name__) + class DecimateAll(object): """Transform operator for decimation to multibanded frequency domain.""" @@ -436,14 +440,14 @@ def __init__( maximum_frequency: Optional[float | dict[str, float]] Update of f_max. If a float, the same value will be used for all detectors. print_output: bool - Whether to write print statements to the console. + Whether to write settings information to the log. """ self.sample_frequencies = domain.sample_frequencies self.minimum_frequency = minimum_frequency self.maximum_frequency = maximum_frequency if print_output: - print( + log.info( f"Transform MaskDataForFrequencyRangeUpdate activated:" f" Settings: \n" f" - Minimum_frequency update: {self.minimum_frequency}\n" diff --git a/dingo/gw/waveform_generator/waveform_generator.py b/dingo/gw/waveform_generator/waveform_generator.py index f673cf699..078d926a8 100644 --- a/dingo/gw/waveform_generator/waveform_generator.py +++ b/dingo/gw/waveform_generator/waveform_generator.py @@ -586,7 +586,7 @@ def generate_FD_waveform( # numbers if multibanding is used. If that happens, turn off multibanding to # fix this. if max(np.max(np.abs(hp.data.data)), np.max(np.abs(hc.data.data))) > 1e-20: - print( + log.info( f"Generation with parameters {parameters_lal} likely numerically " f"unstable due to multibanding, turn off multibanding." ) @@ -609,7 +609,7 @@ def generate_FD_waveform( *parameters_lal[lal_dict_idx + 1 :], ) if max(np.max(np.abs(hp.data.data)), np.max(np.abs(hc.data.data))) > 1e-20: - print( + log.info( f"Warning: turning off multibanding for parameters {parameters_lal}" f" likely numerically might not have fixed it, check manually." ) @@ -1624,7 +1624,7 @@ def sum_contributions_m(x_m, phase_shift=0.0): pol_m = wfg.generate_hplus_hcross_m(p) phase_shift = np.random.uniform(high=2 * np.pi) - print(f"{phase_shift:.2f}") + log.info(f"{phase_shift:.2f}") pol = sum_contributions_m(pol_m, phase_shift=phase_shift) pol_ref = wfg.generate_hplus_hcross({**p, "phase": p["phase"] + phase_shift}) diff --git a/old_examples/__init__.py b/old_examples/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/old_examples/append_training_stage.yaml b/old_examples/append_training_stage.yaml new file mode 100644 index 000000000..d0e2aa228 --- /dev/null +++ b/old_examples/append_training_stage.yaml @@ -0,0 +1,28 @@ +checkpoint: ??? +out_file: updated_model.pt +replace: null + +stage: + epochs: 20 + asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + freeze_rb_layer: false + optimizer: + type: adam + lr: 1.0e-5 + scheduler: + type: cosine + T_max: 20 + batch_size: 64 + +hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} + job: + name: append_training_stage + chdir: true + config: + override_dirname: + exclude_keys: + - checkpoint + - out_file + - stage.asd_dataset_path diff --git a/old_examples/estimate_psds.yaml b/old_examples/estimate_psds.yaml new file mode 100644 index 000000000..0a7ed5cbd --- /dev/null +++ b/old_examples/estimate_psds.yaml @@ -0,0 +1,29 @@ +data_dir: asd_dataset +time_segments_file: ??? +verbose: false + +dataset_settings: + f_s: 4096 + time_psd: 1024 + T: 8.0 + window: + roll_off: 0.4 + type: tukey + time_gap: 0 + num_psds_max: 1 + detectors: + - H1 + - L1 + observing_run: O1 + +hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} + job: + name: estimate_psds + chdir: true + config: + override_dirname: + exclude_keys: + - data_dir + - time_segments_file diff --git a/old_examples/evaluate_multibanded_domain.yaml b/old_examples/evaluate_multibanded_domain.yaml new file mode 100644 index 000000000..e4fc40d2d --- /dev/null +++ b/old_examples/evaluate_multibanded_domain.yaml @@ -0,0 +1,45 @@ +# settings for domain of waveforms +domain: + type: MultibandedFrequencyDomain + nodes: [20.0, 26.0, 34.0, 46.0, 62.0, 78.0, 1038.0] + delta_f_initial: 0.0625 + base_domain: + type: UniformFrequencyDomain + f_min: 20.0 + f_max: 1037.9375 + delta_f: 0.0625 + +# settings for waveform generator +waveform_generator: + approximant: IMRPhenomXPHM + f_ref: 20.0 + spin_conversion_phase: 0.0 + +# Dataset only samples over intrinsic parameters. Extrinsic parameters are chosen at train time. +intrinsic_prior: + mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1') + mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2') + chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=100.0, name='chirp_mass') + mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') + phase: default + a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_1') + a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_2') + tilt_1: default + tilt_2: default + phi_12: default + phi_jl: default + theta_jn: default + # Reference values for fixed (extrinsic) parameters. These are needed to generate a waveform. + luminosity_distance: 100.0 # Mpc + geocent_time: 0.0 # s + +num_samples: 1 + +compression: null + +hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} + job: + name: evaluate_multibanded_domain + chdir: true diff --git a/old_examples/generate_asd_dataset.yaml b/old_examples/generate_asd_dataset.yaml new file mode 100644 index 000000000..6a1bc2327 --- /dev/null +++ b/old_examples/generate_asd_dataset.yaml @@ -0,0 +1,31 @@ +data_dir: asd_dataset +time_segments_file: null +out_name: null +verbose: false + +dataset_settings: + f_s: 4096 + time_psd: 1024 + T: 8.0 + window: + roll_off: 0.4 + type: tukey + time_gap: 0 + num_psds_max: 1 + detectors: + - H1 + - L1 + observing_run: O1 + +hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} + job: + name: generate_asd_dataset + chdir: true + config: + override_dirname: + exclude_keys: + - data_dir + - time_segments_file + - out_name diff --git a/configs/examples/generate_dataset.yaml b/old_examples/generate_dataset.yaml similarity index 100% rename from configs/examples/generate_dataset.yaml rename to old_examples/generate_dataset.yaml diff --git a/old_examples/generate_synthetic_asd_dataset.yaml b/old_examples/generate_synthetic_asd_dataset.yaml new file mode 100644 index 000000000..be569927d --- /dev/null +++ b/old_examples/generate_synthetic_asd_dataset.yaml @@ -0,0 +1,31 @@ +asd_dataset: ??? +num_samples: 500 +num_processes: 1 +out_file: synthetic_asd_dataset.hdf5 +verbose: false + +parameterization_settings: + num_spline_positions: 30 + num_spectral_segments: 400 + sigma: 0.14 + delta_f: -1 + smoothen: true + +sampling_settings: + bandwidth_spectral: 0.5 + bandwidth_spline: 0.25 + split_frequencies: + - 30 + - 100 + +hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} + job: + name: generate_synthetic_asd_dataset + chdir: true + config: + override_dirname: + exclude_keys: + - asd_dataset + - out_file diff --git a/old_examples/importance_weights.yaml b/old_examples/importance_weights.yaml new file mode 100644 index 000000000..c1a158d19 --- /dev/null +++ b/old_examples/importance_weights.yaml @@ -0,0 +1,57 @@ +outdir: . +parameter_samples: ??? +num_samples: 1000 +event_dataset: null +time_marginalization: + n_fft: 5 +phase_marginalization: null +synthetic_phase: null +calibration_marginalization: null +num_processes: 1 + +slice_plots: + num_slice_plots: 10 + params_slice2d: + - [phase, geocent_time] + - [phase, tilt_1] + +nde: + path: null + data: + parameters: null + model: + posterior_model_type: normalizing_flow + posterior_kwargs: + num_flow_steps: 10 + base_transform_kwargs: + hidden_dim: 128 + num_transform_blocks: 2 + activation: elu + dropout_probability: 0.1 + batch_norm: true + num_bins: 8 + base_transform_type: rq-coupling + training: + device: cpu + num_workers: 0 + train_fraction: 0.9 + batch_size: 512 + epochs: 20 + optimizer: + type: adam + lr: 0.003 + scheduler: + type: cosine + T_max: 20 + +hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} + job: + name: importance_weights + chdir: true + config: + override_dirname: + exclude_keys: + - parameter_samples + - nde.path diff --git a/old_examples/merge_asd_datasets.yaml b/old_examples/merge_asd_datasets.yaml new file mode 100644 index 000000000..289fd535e --- /dev/null +++ b/old_examples/merge_asd_datasets.yaml @@ -0,0 +1,31 @@ +data_dir: asd_dataset +time_segments_file: null +num_parts: -1 +out_name: null + +dataset_settings: + f_s: 4096 + time_psd: 1024 + T: 8.0 + window: + roll_off: 0.4 + type: tukey + time_gap: 0 + num_psds_max: 1 + detectors: + - H1 + - L1 + observing_run: O1 + +hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} + job: + name: merge_asd_datasets + chdir: true + config: + override_dirname: + exclude_keys: + - data_dir + - time_segments_file + - out_name diff --git a/old_examples/train.yaml b/old_examples/train.yaml new file mode 100644 index 000000000..736181fb5 --- /dev/null +++ b/old_examples/train.yaml @@ -0,0 +1,84 @@ +checkpoint: null +train_dir: train +exit_command: "" + +data: + waveform_dataset_path: training_data/waveform_dataset.hdf5 + train_fraction: 0.95 + detectors: + - H1 + - L1 + extrinsic_prior: + dec: default + ra: default + geocent_time: bilby.core.prior.Uniform(minimum=-0.10, maximum=0.10, name='geocent_time') + psi: default + luminosity_distance: bilby.core.prior.Uniform(minimum=100.0, maximum=1000.0, name='luminosity_distance') + ref_time: 1126259462.391 + inference_parameters: + - chirp_mass + - mass_ratio + - chi_1 + - chi_2 + - theta_jn + - dec + - ra + - geocent_time + - luminosity_distance + - psi + - phase + +model: + posterior_model_type: normalizing_flow + posterior_kwargs: + num_flow_steps: 5 + base_transform_kwargs: + hidden_dim: 64 + num_transform_blocks: 5 + activation: elu + dropout_probability: 0.0 + batch_norm: true + num_bins: 8 + base_transform_type: rq-coupling + embedding_kwargs: + output_dim: 128 + hidden_dims: + - 1024 + - 512 + - 256 + - 128 + activation: elu + dropout: 0.0 + batch_norm: true + svd: + num_training_samples: 1000 + num_validation_samples: 100 + size: 50 + +training: + stage_0: + epochs: 20 + asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + freeze_rb_layer: true + optimizer: + type: adam + lr: 0.0001 + scheduler: + type: cosine + T_max: 20 + batch_size: 64 + +local: + device: cpu + num_workers: 6 + runtime_limits: + max_time_per_run: 3600000 + max_epochs_per_run: 30 + checkpoint_epochs: 15 + +hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name} + job: + name: train + chdir: true diff --git a/old_examples/unconditional_density_estimation.yaml b/old_examples/unconditional_density_estimation.yaml new file mode 100644 index 000000000..32f5845d4 --- /dev/null +++ b/old_examples/unconditional_density_estimation.yaml @@ -0,0 +1,42 @@ +result_file: ??? +train_dir: nde + +data: + parameters: null + +model: + posterior_model_type: normalizing_flow + posterior_kwargs: + num_flow_steps: 10 + base_transform_kwargs: + hidden_dim: 128 + num_transform_blocks: 2 + activation: elu + dropout_probability: 0.1 + batch_norm: true + num_bins: 8 + base_transform_type: rq-coupling + +training: + device: cpu + num_workers: 0 + train_fraction: 0.9 + batch_size: 512 + epochs: 20 + optimizer: + type: adam + lr: 0.003 + scheduler: + type: cosine + T_max: 20 + +hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} + job: + name: unconditional_density_estimation + chdir: true + config: + override_dirname: + exclude_keys: + - result_file diff --git a/pyproject.toml b/pyproject.toml index e3341fc16..83bf80f23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ dependencies = [ ] [tool.setuptools.packages.find] -include = ["dingo*"] +include = ["dingo*", "configs*"] namespaces = false [tool.setuptools_scm] From 2c8fecdfd9beaa10d44620d9313249c2682c5b45 Mon Sep 17 00:00:00 2001 From: Heinrich Campe <49278231+hcampe@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:22:28 +0200 Subject: [PATCH 14/15] started refactoring the code to use hydra and more cleanly separate configs and code --- HYDRA_TODO.md | 434 ++++++++++++++++-- configs/append_training_stage.yaml | 9 +- configs/domain/multibanded_frequency.yaml | 4 +- configs/domain/uniform_frequency.yaml | 2 +- configs/experiment/generate_fmpe_dataset.yaml | 24 +- configs/experiment/generate_gnpe_dataset.yaml | 24 +- configs/experiment/generate_npe_dataset.yaml | 24 +- configs/experiment/train_fmpe.yaml | 31 +- configs/experiment/train_gnpe.yaml | 87 +++- configs/experiment/train_gnpe_init.yaml | 31 +- configs/experiment/train_npe.yaml | 31 +- configs/experiment/train_toy.yaml | 14 +- configs/extrinsic_prior/default.yaml | 13 +- configs/extrinsic_prior/fmpe.yaml | 13 +- configs/hydra/default.yaml | 5 + configs/intrinsic_prior/default.yaml | 23 +- configs/intrinsic_prior/fmpe.yaml | 31 +- configs/intrinsic_prior/precessing.yaml | 31 +- .../precessing_multibanded_test.yaml | 31 +- configs/model/fmpe.yaml | 2 +- configs/model/gnpe.yaml | 2 +- configs/model/gnpe_init.yaml | 2 +- configs/model/npe.yaml | 2 +- configs/model/toy_npe.yaml | 2 +- configs/model/unconditional_npe.yaml | 2 +- configs/optimizer/adam.yaml | 3 +- configs/scheduler/cosine.yaml | 3 +- configs/train.yaml | 66 ++- configs/waveform_generator/phenom_d.yaml | 1 + configs/waveform_generator/phenom_pv2.yaml | 1 + configs/waveform_generator/phenom_xphm.yaml | 1 + dingo/asimov/asimov.py | 9 +- dingo/core/dataset.py | 19 +- dingo/core/density/__init__.py | 1 - dingo/core/density/nde_settings.py | 73 --- .../unconditional_density_estimation.py | 13 +- dingo/core/posterior_models/base_model.py | 54 ++- dingo/core/posterior_models/build_model.py | 65 +-- dingo/core/posterior_models/cflow_base.py | 2 +- .../core/posterior_models/normalizing_flow.py | 2 +- dingo/core/result.py | 40 +- dingo/core/samplers.py | 12 +- dingo/core/utils/__init__.py | 1 + dingo/core/utils/condor_utils.py | 3 +- dingo/core/utils/hydra_utils.py | 30 ++ dingo/core/utils/logging_utils.py | 72 ++- dingo/core/utils/pt_to_hdf5.py | 19 +- dingo/core/utils/torchutils.py | 34 +- dingo/core/utils/trainutils.py | 20 +- dingo/gw/SVD.py | 20 +- dingo/gw/conversion/spin_conversion.py | 3 +- dingo/gw/data/data_download.py | 5 +- dingo/gw/data/data_preparation.py | 4 +- dingo/gw/dataset/compression.py | 94 ++++ .../gw/dataset/evaluate_multibanded_domain.py | 76 ++- dingo/gw/dataset/generate_dataset.py | 177 ++----- dingo/gw/dataset/generate_dataset_dag.py | 5 +- dingo/gw/dataset/utils.py | 30 +- dingo/gw/dataset/waveform_dataset.py | 99 ++-- dingo/gw/domains/build_domain.py | 17 +- dingo/gw/download_strain_data.py | 5 +- dingo/gw/gwutils.py | 18 +- dingo/gw/importance_sampling/diagnostics.py | 4 +- .../importance_sampling/importance_weights.py | 14 +- dingo/gw/inference/gw_samplers.py | 40 +- dingo/gw/injection.py | 19 +- dingo/gw/likelihood.py | 9 +- dingo/gw/ls_cli.py | 85 ++-- dingo/gw/noise/asd_dataset.py | 8 +- dingo/gw/noise/asd_estimation.py | 12 +- dingo/gw/noise/generate_dataset.py | 8 +- dingo/gw/noise/synthetic/asd_sampling.py | 4 +- dingo/gw/noise/synthetic/generate_dataset.py | 2 +- dingo/gw/noise/utils.py | 4 +- dingo/gw/prior.py | 74 +-- dingo/gw/result.py | 36 +- dingo/gw/training/train_builders.py | 215 +++------ dingo/gw/training/train_pipeline.py | 167 +++++-- dingo/gw/training/train_pipeline_condor.py | 5 +- dingo/gw/training/utils.py | 14 +- dingo/gw/transforms/noise_transforms.py | 8 +- dingo/gw/transforms/parameter_transforms.py | 5 +- dingo/gw/transforms/waveform_transforms.py | 6 +- .../waveform_generator/waveform_generator.py | 16 +- tests/core/test_posterior_model.py | 41 +- .../test_time_delay_from_geocenter.py | 10 +- tests/gw/test_mfd.py | 20 +- tests/gw/test_prior_split.py | 13 +- tests/gw/test_ufd.py | 14 +- tests/gw/test_waveform_dataset.py | 97 ++-- tests/gw/transforms/test_batch_transforms.py | 14 +- .../gw/transforms/test_detector_projection.py | 12 +- tests/gw/waveform_generator/test_wfg_m.py | 4 +- tests/gw/waveform_generator/test_wfg_mfd.py | 4 +- tests/test_hydra_config_targets.py | 260 +++++++++++ 95 files changed, 1995 insertions(+), 1220 deletions(-) delete mode 100644 dingo/core/density/nde_settings.py create mode 100644 dingo/core/utils/hydra_utils.py create mode 100644 dingo/gw/dataset/compression.py create mode 100644 tests/test_hydra_config_targets.py diff --git a/HYDRA_TODO.md b/HYDRA_TODO.md index f4186cf51..8c4c54fba 100644 --- a/HYDRA_TODO.md +++ b/HYDRA_TODO.md @@ -219,8 +219,10 @@ Public console scripts are defined in `pyproject.toml`. - Stage 1 has started. - Uses Hydra and `instantiate(...)` for some objects already, so it is partly beyond a purely superficial Stage 1 pass. - - Remaining concern: compression/SVD logic is still procedural and should be - revisited in Stage 3. + - Stage 3 progress: compression is now represented as an ordered list of + Hydra-instantiated transform configs. The train-from-waveforms SVD path is + a Hydra target that returns an `SVDBasis`, while the produced SVD arrays are + still saved as dataset artifact data. - [ ] `dingo_generate_dataset_dag` - File: `dingo/gw/dataset/generate_dataset_dag.py` @@ -489,6 +491,142 @@ clear this is a scoped migration rather than an incomplete sweep. ## Stage 3 Refactor Hotspots +- [ ] Stage 3 implementation philosophy + - Prefer moving logic into Hydra configs through defaults, interpolation, and + `_target_` entries. + - Source-code changes should usually delete old construction/defaulting code + or replace it with direct `hydra.utils.instantiate(...)` calls. + - Avoid adding new source-code logic. If a new helper seems necessary, discuss + it first, except for the already-agreed generic + `instantiate_with_runtime_dependencies(...)` helper. + - Expect config files to grow and pure Python construction code to shrink. + - Start implementation with easier direct-instantiation migrations to + establish patterns before touching the transform/metadata-heavy pieces. + +- [ ] Define the Stage 3 config-construction contract + - Goal: replace old `from_config` / `build_*` / string-dispatch constructors + with Hydra-native `_target_` configs and `hydra.utils.instantiate(...)` + as the normal construction mechanism. + - Target architecture: configs describe the object graph, code defines the + objects/functions, and Hydra composes plus instantiates. Avoid config + completion helpers, compatibility-shaped builders, and side-channel + mutation of settings dictionaries. + - Stage 3 implementation rule: only remove old config-construction code or + replace it by `_target_` configs plus `hydra.utils.instantiate(...)`. + Do not introduce new factories, adapters, compatibility builders, or + config-normalization layers without first discussing the design and getting + explicit agreement. + - Approved helper exception: a small generic + `instantiate_with_runtime_dependencies(...)` helper is acceptable for + Hydra-instantiating transform lists whose entries sometimes need live + runtime objects. This helper should remain generic and should not encode + Dingo-specific config completion logic. + - Runtime-dependent quantities should be handled by the instantiated objects + or by explicit setup methods on those objects, not by patching config + dictionaries before construction. + - Avoid mixing two construction idioms for the same object family. Once a + family is migrated, prefer `_target_` configs over parallel `type` strings + plus builder switches. + - Difficulty: medium/high because the easy cases are straightforward, but the + boundary between declarative construction and normal object lifecycle must + be chosen deliberately. + +- [ ] Define the saved-metadata contract for Hydra configs + - Current datasets, models, and results save settings dictionaries as + metadata. Stage 3 must decide what gets saved once configs contain + `_target_`, interpolations, defaults composition, and possibly instantiated + Python objects. + - Candidate policy: + - Save the resolved, plain-container snapshot of the composed Hydra config + as the authoritative run/model/dataset metadata. + - Put as much as possible into the composed config before object + construction starts: user choices, defaults, resolved paths, dimensions, + standardization settings/statistics, and other values that can be known + or computed ahead of time. + - All paths saved in metadata should be absolute/resolved paths. + - Save the exact config produced by Hydra, rather than translating it into + the old `dataset_settings` / `train_settings` metadata shape. + - Prefer loading saved config metadata back as an OmegaConf/DictConfig-style + object so code can use dot access consistently instead of ordinary nested + dictionary indexing. + - For results, keep the original resolved model config separate from + result-specific metadata. The model config records the proposal/model that + produced the samples; event, inference, and importance-sampling metadata + record what was done to/with those samples. + - Compute parameter standardization before model construction and include + the resulting standardization statistics in the resolved config. + - Prefer Hydra/OmegaConf composition, interpolation, and resolvers for this + ahead-of-time computation over code that mutates config dictionaries + during construction. + - For simple derived values, use OmegaConf interpolation, e.g. `${...}`, to + reference values already present elsewhere in the config. + - For heavier ahead-of-time computations, use `_target_` config blocks and + `hydra.utils.instantiate(...)` on that config subtree. This is acceptable + when the target is the actual computation/object being configured, not a + new config-factory layer. + - Do not save live instantiated objects; save importable targets, + primitive parameters, paths, versions, and artifact provenance instead. + - Keep produced arrays/tensors outside the config: model weights, + optimizer/scheduler state, SVD basis arrays, result samples, context/event + strain arrays, etc. These can remain stored in dedicated artifact/data + fields broadly as they are today. + - Keep the metadata structure close to the config structure so users can + understand saved artifacts by looking at the configs. + - Do not design the new metadata around legacy dictionary shapes unless a + concrete loader requirement is reintroduced. + - If old artifacts need support later, prefer a narrow explicit migration + shim at the loading boundary rather than carrying legacy shape through the + new config design. + - Progress: + - HDF5-backed `DingoDataset` settings are now saved as resolved YAML and + loaded as OmegaConf/DictConfig metadata, with a fallback for old + Python-literal attributes. + - Posterior-model metadata is held as DictConfig in memory for dot access, + but saved to checkpoints as resolved plain containers so `torch.load` + remains compatible with safe loading. + - Result-specific metadata remains separate from model metadata as before. + - Training computes missing parameter standardizations before the + configured training transforms are instantiated, writes the computed + statistics back into the training settings, and syncs the resolved + transform config that consumes them. + - Difficulty: very high. This is a cross-cutting compatibility and + reproducibility contract, even if we choose not to preserve old metadata + formats. + +- [ ] Replace manual config autocompletion/resolution with Hydra-era derived + metadata handling + - Current hotspot: `dingo/core/posterior_models/build_model.py` contains + `autocomplete_model_kwargs(...)`, which mutates model settings in-place + based on a runtime data sample. + - It currently fills: + - `embedding_kwargs.input_dims` from waveform/data shape + - `posterior_kwargs.input_dim` from the parameter vector length + - `embedding_kwargs.added_context` from whether GNPE proxy context exists + - `posterior_kwargs.context_dim` from embedding output dimension plus + optional GNPE proxy dimension + - This is not plain static config composition: the values depend on the + realized training dataset and transform pipeline. + - Candidate policy: + - Keep user-configurable architecture choices in Hydra configs. + - Remove hidden in-place config mutation. + - Move any dimensions or settings that can be known before construction into + the composed Hydra config, using explicit config values or Hydra + resolution mechanisms rather than `autocomplete_model_kwargs(...)`. + - Only leave shape inference inside instantiated objects when the value + genuinely cannot be known before the object is constructed. + - Save the resolved config and any resulting learned/artifact state; do not + save a separately patched config unless the object itself owns that state. + - Related pipe-only helper: `dingo/pipe/main.py` has + `write_complete_config_file(...)`, which writes a completed `.ini` file. + Since `.ini` pipe workflows are out of scope for the current Hydra + migration, keep this separate for now; if pipe migrates later, Hydra's + composed config output should replace most of this behavior. + - Difficulty: high. It sits at the intersection of model construction, + transform realization, and saved metadata. + - Design status: agreed. `autocomplete_model_kwargs(...)` should become + superfluous. Ahead-of-time computable dimensions should live in the resolved + Hydra config through interpolation or explicit ahead-of-time computation. + - [ ] Move Python-defined defaults into Hydra default configs - Goal: make default choices visible, overridable, and composable from the config tree rather than hidden in module globals or helper functions. @@ -503,8 +641,8 @@ clear this is a scoped migration rather than an incomplete sweep. - `defaults/importance_sampling` - `defaults/nde` - `defaults/training` - - Keep a deliberate backward-compatibility story for old saved metadata that - uses strings such as `default`. + - Replace old sentinel values such as `default` with explicit defaults-group + choices where possible. - [ ] Audit implicit constructor/function defaults and expose them in config - Example configs currently omit many options because Python functions and @@ -523,6 +661,8 @@ clear this is a scoped migration rather than an incomplete sweep. - transform defaults in the training data pipeline, including `zero_noise: false`, optional `random_strain_cropping`, derived `context_parameters`, and derived `standardization` + - ahead-of-time computed model dimensions, replacing + `autocomplete_model_kwargs(...)` where possible - pipe/inference defaults currently supplied by parser defaults - ASD/noise generation defaults such as frequency range, channels, and Condor settings @@ -532,7 +672,7 @@ clear this is a scoped migration rather than an incomplete sweep. - runtime limit defaults (`max_time_per_run`, `max_epochs_per_run`, `max_epochs_total`, `epoch_start`) -- [ ] `dingo/core/density/nde_settings.py` +- [x] `dingo/core/density/nde_settings.py` - Move or retire `get_default_nde_settings_3d(...)` and `DEFAULT_NDE_SETTINGS_2D`. - These currently define additional unconditional NDE architectures: @@ -540,44 +680,181 @@ clear this is a scoped migration rather than an incomplete sweep. Adam learning rate 0.005, cosine `T_max: 10`. - 2D GNPE proxy recovery: 5 flow steps, hidden dimension 64, batch size 4096, 10 epochs, Adam learning rate 0.001, cosine `T_max: 10`. - - Decide whether these become separate `model/*` and training config groups, - or whether they are superseded by `model/unconditional_npe.yaml`. + - Retired in favor of Hydra configs, primarily `model/unconditional_npe.yaml` + plus `unconditional_density_estimation.yaml`. - [ ] `dingo/gw/domains/build_domain.py` - Replace `type`-based dispatch with Hydra instantiation. - - Keep metadata/backward compatibility in mind. - -- [ ] `dingo/gw/prior.py` + - New metadata should save the resolved Hydra domain config, not a parallel + legacy domain dictionary. + - Design status: agreed/easy. Use `_target_` domain configs directly. + `build_domain(...)` and `build_domain_from_model_metadata(...)` should + become unnecessary once downstream code reads the resolved Hydra config and + calls `hydra.utils.instantiate(...)`. + - Initial Stage 3 pass: default Hydra domain configs now use `_target_`, and + `build_domain(...)` instantiates `_target_` configs while keeping the legacy + `type` path for saved metadata and existing callers. + - Progress: `dingo_generate_dataset` and + `dingo_evaluate_multibanded_domain` now instantiate the Hydra domain config + directly instead of calling `build_domain(...)`. + +- [x] `dingo/gw/prior.py` - Replace `default` string handling and prior builders with explicit config targets or reusable config groups. - Current code-defined defaults: - `default_intrinsic_dict` - `default_extrinsic_dict` - `default_inference_parameters` + - Agreed design: + - Remove `default` sentinel strings from the Hydra-era configs. + - The intrinsic prior config should target `BBHPriorDict` directly. + - The `BBHPriorDict` config should contain explicit entries for the + individual parameter priors/constraints. + - Prefer making the prior dictionary target explicit over instantiating each + Bilby prior object separately, unless a specific parameter requires a + different treatment. + - Retire `build_prior_with_defaults(...)` once all callers consume the + instantiated/configured prior dictionary. + - Progress: intrinsic-prior Hydra group configs now target + `bilby.gw.prior.BBHPriorDict` directly, with explicit entries replacing + `default` sentinels. Extrinsic-prior configs now target + `dingo.gw.prior.BBHExtrinsicPriorDict` directly. Dataset generation, + multibanded-domain evaluation, training, result, sampler, and injection + paths consume these explicit configs. `build_prior_with_defaults(...)`, + `get_extrinsic_prior_dict(...)`, and the Python-level default prior / + inference-parameter globals were retired. Pipe remains out of scope. - [ ] Waveform generator construction - Replace `new_interface` flags and manual class branching with `_target_`. + - Design status: agreed/easy. Use `_target_` waveform-generator configs + directly and remove manual branching once callers instantiate from config. + - Progress: waveform-generator Hydra group configs now target + `dingo.gw.waveform_generator.WaveformGenerator`, and the two dataset + entrypoints instantiate them directly with the chosen domain. - [ ] Compression/SVD workflow - Harder than plain instantiation because SVD may be loaded from file or trained from generated waveforms. - - Possible long-term design: compression is an ordered transform list, with - SVD handled by a project-specific factory target. + - Possible long-term design: compression is an ordered Hydra-instantiated + transform list. An SVD step can be configured as a normal object that knows + whether to load an existing basis or train/build one from configured inputs. + - Agreed design: + - Represent compression as an ordered list of transform configs. + - Use different Hydra targets for the different SVD construction paths, + e.g. one target that loads an existing `SVDBasis` from file and another + target that trains a new `SVDBasis`. + - The train-SVD target should return only the `SVDBasis` object needed by + `ApplySVD`. Auxiliary information such as actual train/validation counts + or mismatch summaries belongs in metadata/provenance, not in the return + value used by the transform. + - The realized SVD basis should continue to be saved as a produced dataset + artifact, embedded in the waveform dataset as today. + - The config should save the requested SVD construction method and inputs, + not the SVD arrays themselves. + - Dataset metadata should save the resolved compression config/request. Any + realized SVD arrays remain produced artifact data, saved in the waveform + dataset as today. + - Progress: + - `train_svd_basis(...)` now returns only the `SVDBasis` used by + `ApplySVD`. + - Dataset-generation compression configs are now ordered lists of + Hydra-instantiated transforms. + - `train_svd_basis_from_waveforms(...)` is available as a Hydra target for + training an `SVDBasis` from generated waveforms and passing it into + `ApplySVD`. + - The generated SVD arrays remain embedded in the waveform dataset as today. + - `WaveformDataset` can reload list-based compression metadata and rebuild + decompression transforms. + - `load_svd_basis(...)` is available as a Hydra target for loading an + existing `SVDBasis` from file and passing it into `ApplySVD`. + - Small smoke: `my_runs/compression_svd_smoke` generates and reloads a + compressed one-sample dataset with a two-sample SVD-training override. + - Remaining work: decide how much SVD training provenance/mismatch + information should be saved as metadata. - [ ] `dingo/gw/training/train_builders.py` - Large procedural transform pipeline. - Prime candidate for Hydra targets, but many transforms need runtime objects: domain, detectors, ASD datasets, standardization, priors, reference time. - -- [ ] `dingo/core/posterior_models/build_model.py` - - Replace `posterior_model_type` dispatch with `_target_`, while preserving - saved-model compatibility. - -- [ ] `dingo/core/utils/torchutils.py` + - Long-term design option: configs define an ordered transform pipeline with + `_target_` entries. Transform objects should receive their dependencies + through Hydra-instantiated object graphs or through explicit object setup, + not through config mutation. + - Agreed runtime-dependency pattern: + - Instantiate expensive/shared runtime objects once, e.g. effective domain, + waveform dataset, ASD dataset, standardization, and optionally + `InterferometerList`. + - Package them in a `runtime_dependencies` dictionary. + - Instantiate transform configs with a generic helper, conceptually: + + ```python + transforms = [ + hydra.utils.instantiate(t_cfg)(**runtime_dependencies) + if t_cfg.get("_partial_", False) + else hydra.utils.instantiate(t_cfg) + for t_cfg in cfg.transforms + ] + ``` + + - Passing runtime objects this way is not a performance concern because + Python passes references, and this happens at transform construction time, + not for every sample. + - Transforms that consume runtime dependencies should fail early if a + required dependency is missing. Avoid silent swallowing of misspelled + dependency names. + - Do not use fake config interpolations such as `${runtime:asd_dataset}` + unless a real resolver is explicitly designed later; Hydra does not + provide such a runtime-object resolver by default. + - Decide which transform state belongs in config, which belongs inside the + instantiated transform/model objects, and which artifact provenance should + be saved. + - Difficulty: very high. This is probably the hardest non-Condor refactor + because the pipeline mixes config, runtime datasets, derived state, and + metadata persistence. + - Progress: the default training transform order now lives in `configs/train.yaml` + as `_target_` entries. `set_train_transforms(...)` instantiates that list via + the generic `instantiate_with_runtime_dependencies(...)` helper. Cheap + dependencies such as priors, interferometer lists, reference time, + parameter dictionaries, standardization, selected output keys, and GNPE + transforms are now config-owned and recursively instantiated. The live + runtime dependency dictionary is reduced to the waveform domain and ASD + dataset. `zero_noise` is handled through the ordinary omit-transform path, + and `build_dataset(...)` was retired in favor of instantiating the + configured `WaveformDataset`. The standardization-prefix transforms are + explicit in config so the old standardization behavior is computed before + the final transform list is instantiated. + +- [x] `dingo/core/posterior_models/build_model.py` + - Replace `posterior_model_type` dispatch with `_target_`. + - Remove `autocomplete_model_kwargs(...)` as a config-mutation step. Its + responsibilities should either become explicit config fields or normal + shape-inference behavior of the instantiated model/embedding objects. + - Design status: agreed. Model/checkpoint metadata should save the exact + resolved Hydra config plus produced checkpoint state. Old + `posterior_model_type` dispatch and config autocompletion should disappear + in favor of `_target_` / `instantiate`. + - Progress: model configs now carry posterior-model `_target_` entries, the + `posterior_model_type` dispatch was removed from the Hydra model path, and + `autocomplete_model_kwargs(...)` was retired. Model dimensions are computed + before model construction in `prepare_training_new(...)` and saved into the + config metadata. + +- [x] `dingo/core/utils/torchutils.py` - Replace optimizer/scheduler `type` switches with `_target_` configs. + - Design status: agreed/easy. Optimizers and schedulers should be direct + `_target_` configs, removing the current string-switch helpers. + - Initial Stage 3 pass: default optimizer/scheduler configs now use + `_target_` partials, and the existing helper functions instantiate those + configs while keeping legacy `type` settings for older stage/checkpoint + metadata. + - Progress: the string-switch fallbacks have been removed from the helper + functions, and `BasePosteriorModel.initialize_optimizer_and_scheduler()` + now calls `hydra.utils.instantiate(...)` directly. - [ ] Condor integration as a Hydra workflow/launcher - Keep Condor-specific migration out of Stage 1. + - Discuss later before implementation. This is intentionally not part of the + first Stage 3 implementation pass. - Stage 3 goal: compose the job config with Hydra and submit Condor jobs from that composed config. - Desired user experience: a single Hydra-driven command can configure and @@ -599,26 +876,113 @@ clear this is a scoped migration rather than an incomplete sweep. - These are intentionally deferred while `.ini`/pipe workflows remain outside the Stage 1/2 Hydra migration. -- [ ] `dingo/core/density/nde_settings.py` - - Move `get_default_nde_settings_3d(...)` and `DEFAULT_NDE_SETTINGS_2D` into - Hydra configs. - - These can likely become reusable `defaults/nde` entries plus overrides for - dimension/device/parameters. - - [ ] `dingo/pipe/parser.py` - Biggest migration knot. This is both a CLI parser and a compatibility layer with bilby_pipe-style settings. +## Stage 3 Testing Requirements + +- [ ] Add extensive tests for Hydra configs and instantiated behavior. + - Any option included in the configs should be covered by a test at the + appropriate level. + - At minimum, every config group/file should have composition tests. + - Every `_target_` config should have an instantiation test, unless + instantiation is intentionally too expensive; in that case add a cheaper + test that validates the config shape and document the skipped expensive + behavior. + - Behavior-affecting options should have unit or smoke tests that exercise the + resulting object behavior, not only config composition. + - Important override combinations should be tested, especially for: + - domain choices and domain updates + - waveform generator variants + - prior groups + - compression/SVD load-vs-train choices + - transform pipeline variants, including GNPE context, zero noise, random + cropping, and stage-specific ASD/noise settings + - model families and model dimensions + - optimizer/scheduler configs + - metadata saving/loading with dot-access config restoration + - Tests should confirm that saved metadata contains the resolved Hydra config, + absolute paths, ahead-of-time computed values such as standardization, and + separate artifact/result state. + - Progress: `tests/test_hydra_config_targets.py` covers Hydra target + composition for domain, waveform generator, priors, models, + optimizer/scheduler, SVD load-from-file, standardization-prefix transforms, + and an in-memory training-transform smoke that verifies standardization is + computed before final transform instantiation. + ## Suggested Order -1. Finish Stage 1 for standalone dataset/noise utilities. -2. Do Stage 1 for training. -3. Decide whether `dingo_pipe` should be a compatibility wrapper around Hydra - configs or a full Hydra CLI. -4. Do Stage 2 grouping for dataset generation, ASD generation, and training - before touching pipe internals. -5. Start Stage 3 with the small builder switches: - domain, prior, waveform generator, optimizer, scheduler. -6. Design Condor integration as a Stage 3 Hydra workflow/launcher problem. -7. Tackle training transforms and model construction. -8. Leave pipe/parser migration until the rest of the config story is stable. +1. Start with easy direct `_target_` migrations: + domain, waveform generator, optimizer, and scheduler. +2. Move Python-defined defaults into Hydra config groups and make configs + complete. This should be mostly config edits plus deletion of old defaulting + code. +3. Migrate priors: remove `default` sentinels, target `BBHPriorDict` directly, + and retire `build_prior_with_defaults(...)`. +4. Migrate model construction: replace `posterior_model_type` dispatch with + `_target_` configs and make `autocomplete_model_kwargs(...)` unnecessary + through interpolation / ahead-of-time config computation. +5. Migrate compression/SVD as an ordered transform list with load-vs-train SVD + targets and produced SVD arrays saved as dataset artifacts. +6. Migrate the training transform pipeline using explicit transform configs and + the agreed generic `instantiate_with_runtime_dependencies(...)` helper. +7. Update metadata saving/loading to save the exact resolved Hydra config, + restore dot access on load, keep result metadata separate, and keep produced + artifacts in dedicated data/state fields. +8. Add or expand tests throughout each step; do not leave broad config options + untested. +9. Discuss Condor integration later as a separate Stage 3 workflow/launcher + design. +10. Leave pipe/parser migration until the rest of the config story is stable. + +## Stage 3 Risk Notes + +These are the areas that need the most care, even if implementation starts with +the easier direct-instantiation changes. + +1. **Saved metadata contract** - very high difficulty. + - Cross-cuts datasets, trained models, results, checkpoint resume, and + reproducibility. + - Needs a deliberate policy for saving resolved Hydra config snapshots that + already include all ahead-of-time-computable values, plus artifact + provenance and truly produced state without reintroducing parallel legacy + metadata dictionaries. +2. **Training transform pipeline** - very high difficulty. + - Many transforms are configured partly by settings and partly by runtime + objects: waveform/domain metadata, detectors, ASD datasets, priors, + standardization, reference time, and context parameters. + - Target design should be a Hydra-instantiated transform/object graph, with + runtime setup owned by the objects rather than by config patching. +3. **Compression/SVD workflow** - high difficulty. + - Compression can mean loading an existing basis, training a basis from + generated waveforms, applying transforms, and saving enough provenance to + reproduce the dataset. + - Target design should be a declarative compression-target list of + Hydra-instantiated objects, including SVD steps. +4. **Model construction and checkpoint metadata** - high difficulty. + - Replace `posterior_model_type` dispatch with `_target_`. + - Model configs also interact strongly with saved metadata. +5. **Config autocompletion / derived model dimensions** - high difficulty. + - `autocomplete_model_kwargs(...)` currently hard-codes model config + resolution based on a runtime data sample. + - Target design should remove hidden config mutation. Dimensions and related + settings should be computed into the resolved Hydra config beforehand + whenever possible; object-level inference is only the fallback for values + that cannot exist before construction. +6. **Prior construction and defaults** - medium/high difficulty. + - Moving `default` strings to explicit prior config groups is conceptually + simple, but Bilby prior objects and constraints make it easy to + accidentally change behavior. +7. **Domain and waveform generator construction** - medium difficulty. + - Good candidates for early `_target_` migration once the saved-config + policy is clear. + - Main risks are waveform-generator interface flags and keeping the config + readable. +8. **Optimizer/scheduler construction** - low/medium difficulty. + - Natural `_target_` candidates with relatively small metadata surface. + - Good later cleanup once the harder contracts are settled. +9. **Condor integration** - high difficulty, but separable and deferred. + - Important for Stage 3, yet mostly an orchestration/launcher design problem + rather than a core config-object construction problem. + - Discuss later before implementation. diff --git a/configs/append_training_stage.yaml b/configs/append_training_stage.yaml index 88bcd1c58..1dca1ca82 100644 --- a/configs/append_training_stage.yaml +++ b/configs/append_training_stage.yaml @@ -12,7 +12,12 @@ stage: optimizer: ${optimizer} scheduler: ${scheduler} epochs: 20 - asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + asd_dataset: + _target_: dingo.gw.noise.asd_dataset.ASDDataset + file_name: training_data/asd_dataset/asds_O1.hdf5 + ifos: null + precision: single + domain_update: null freeze_rb_layer: false batch_size: 64 early_stopping: null @@ -25,4 +30,4 @@ hydra: exclude_keys: - checkpoint - out_file - - stage.asd_dataset_path + - stage.asd_dataset.file_name diff --git a/configs/domain/multibanded_frequency.yaml b/configs/domain/multibanded_frequency.yaml index 9b9289d68..2ef881d30 100644 --- a/configs/domain/multibanded_frequency.yaml +++ b/configs/domain/multibanded_frequency.yaml @@ -1,8 +1,8 @@ -type: MultibandedFrequencyDomain +_target_: dingo.gw.domains.MultibandedFrequencyDomain nodes: [20.0, 26.0, 34.0, 46.0, 62.0, 78.0, 1038.0] delta_f_initial: 0.0625 base_domain: - type: UniformFrequencyDomain + _target_: dingo.gw.domains.UniformFrequencyDomain f_min: 20.0 f_max: 1037.9375 delta_f: 0.0625 diff --git a/configs/domain/uniform_frequency.yaml b/configs/domain/uniform_frequency.yaml index cb44035cd..3647af448 100644 --- a/configs/domain/uniform_frequency.yaml +++ b/configs/domain/uniform_frequency.yaml @@ -1,4 +1,4 @@ -type: UniformFrequencyDomain +_target_: dingo.gw.domains.UniformFrequencyDomain f_min: 20.0 f_max: 1024.0 delta_f: 0.125 diff --git a/configs/experiment/generate_fmpe_dataset.yaml b/configs/experiment/generate_fmpe_dataset.yaml index a62c125fe..308d51fd0 100644 --- a/configs/experiment/generate_fmpe_dataset.yaml +++ b/configs/experiment/generate_fmpe_dataset.yaml @@ -8,9 +8,23 @@ defaults: num_samples: 5000000 compression: - svd: - size: 200 - num_training_samples: 50000 - num_validation_samples: 10000 - whitening: aLIGO_ZERO_DET_high_P_asd.txt + - _target_: dingo.gw.transforms.WhitenFixedASD + _partial_: true + _runtime_dependencies_: [domain] + asd_file: aLIGO_ZERO_DET_high_P_asd.txt + inverse: false + - _target_: dingo.gw.SVD.ApplySVD + svd_basis: + _target_: dingo.gw.dataset.compression.train_svd_basis_from_waveforms + _partial_: true + _runtime_dependencies_: + - waveform_generator + - prior + - num_processes + - settings + - compression_transforms + - compression_settings + size: 200 + num_training_samples: 50000 + num_validation_samples: 10000 out_file: training_data/waveform_dataset.hdf5 diff --git a/configs/experiment/generate_gnpe_dataset.yaml b/configs/experiment/generate_gnpe_dataset.yaml index 13cd02db9..4f54a2bb8 100644 --- a/configs/experiment/generate_gnpe_dataset.yaml +++ b/configs/experiment/generate_gnpe_dataset.yaml @@ -8,9 +8,23 @@ defaults: num_samples: 5000000 compression: - svd: - size: 200 - num_training_samples: 50000 - num_validation_samples: 10000 - whitening: aLIGO_ZERO_DET_high_P_asd.txt + - _target_: dingo.gw.transforms.WhitenFixedASD + _partial_: true + _runtime_dependencies_: [domain] + asd_file: aLIGO_ZERO_DET_high_P_asd.txt + inverse: false + - _target_: dingo.gw.SVD.ApplySVD + svd_basis: + _target_: dingo.gw.dataset.compression.train_svd_basis_from_waveforms + _partial_: true + _runtime_dependencies_: + - waveform_generator + - prior + - num_processes + - settings + - compression_transforms + - compression_settings + size: 200 + num_training_samples: 50000 + num_validation_samples: 10000 out_file: training_data/waveform_dataset.hdf5 diff --git a/configs/experiment/generate_npe_dataset.yaml b/configs/experiment/generate_npe_dataset.yaml index ebc141c8e..44d952bf8 100644 --- a/configs/experiment/generate_npe_dataset.yaml +++ b/configs/experiment/generate_npe_dataset.yaml @@ -8,9 +8,23 @@ defaults: num_samples: 5000000 compression: - svd: - size: 200 - num_training_samples: 50000 - num_validation_samples: 10000 - whitening: aLIGO_ZERO_DET_high_P_asd.txt + - _target_: dingo.gw.transforms.WhitenFixedASD + _partial_: true + _runtime_dependencies_: [domain] + asd_file: aLIGO_ZERO_DET_high_P_asd.txt + inverse: false + - _target_: dingo.gw.SVD.ApplySVD + svd_basis: + _target_: dingo.gw.dataset.compression.train_svd_basis_from_waveforms + _partial_: true + _runtime_dependencies_: + - waveform_generator + - prior + - num_processes + - settings + - compression_transforms + - compression_settings + size: 200 + num_training_samples: 50000 + num_validation_samples: 10000 out_file: training_data/waveform_dataset.hdf5 diff --git a/configs/experiment/train_fmpe.yaml b/configs/experiment/train_fmpe.yaml index 84eaca337..8685b7f99 100644 --- a/configs/experiment/train_fmpe.yaml +++ b/configs/experiment/train_fmpe.yaml @@ -6,12 +6,13 @@ defaults: - _self_ data: - waveform_dataset_path: training_data/waveform_dataset.hdf5 + waveform_dataset: + file_name: training_data/waveform_dataset.hdf5 + domain_update: + f_min: 20.0 + f_max: 512.0 + svd_size_update: 150 train_fraction: 0.95 - domain_update: - f_min: 20.0 - f_max: 512.0 - svd_size_update: 150 detectors: [H1, L1] ref_time: 1126259462.391 inference_parameters: @@ -34,7 +35,12 @@ training: evaluate: true stage_0: epochs: 400 - asd_dataset_path: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 + asd_dataset: + _target_: dingo.gw.noise.asd_dataset.ASDDataset + file_name: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 + ifos: ${data.detectors} + precision: single + domain_update: null freeze_rb_layer: true batch_size: 4096 early_stopping: null @@ -44,15 +50,22 @@ training: T_max: 400 stage_1: epochs: 100 - asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + asd_dataset: + _target_: dingo.gw.noise.asd_dataset.ASDDataset + file_name: training_data/asd_dataset/asds_O1.hdf5 + ifos: ${data.detectors} + precision: single + domain_update: null freeze_rb_layer: true batch_size: 4096 early_stopping: null optimizer: - type: adam + _target_: torch.optim.Adam + _partial_: true lr: 1.0e-5 scheduler: - type: cosine + _target_: torch.optim.lr_scheduler.CosineAnnealingLR + _partial_: true T_max: 100 local: diff --git a/configs/experiment/train_gnpe.yaml b/configs/experiment/train_gnpe.yaml index d3342ae27..a934225e0 100644 --- a/configs/experiment/train_gnpe.yaml +++ b/configs/experiment/train_gnpe.yaml @@ -5,17 +5,29 @@ defaults: - _self_ data: - waveform_dataset_path: training_data/waveform_dataset.hdf5 + waveform_dataset: + file_name: training_data/waveform_dataset.hdf5 + domain_update: + f_min: 20.0 + f_max: 1024.0 + svd_size_update: 200 train_fraction: 0.95 - domain_update: - f_min: 20.0 - f_max: 1024.0 - svd_size_update: 200 detectors: [H1, L1] ref_time: 1126259462.391 gnpe_time_shifts: + _target_: dingo.gw.transforms.GNPECoalescenceTimes + ifo_list: + _target_: bilby.gw.detector.InterferometerList + interferometers: ${data.detectors} kernel: bilby.core.prior.Uniform(minimum=-0.001, maximum=0.001) - exact_equiv: true + exact_global_equivariance: true + inference: false + context_parameters: + - L1_time_proxy_relative + selected_keys: + - inference_parameters + - waveform + - context_parameters inference_parameters: - chirp_mass - mass_ratio @@ -31,11 +43,59 @@ data: - ra - dec - psi + standardization_transforms: + - _target_: dingo.gw.transforms.SampleExtrinsicParameters + extrinsic_prior_dict: ${data.extrinsic_prior} + - _target_: dingo.gw.transforms.GetDetectorTimes + ifo_list: + _target_: bilby.gw.detector.InterferometerList + interferometers: ${data.detectors} + ref_time: ${data.ref_time} + - ${data.gnpe_time_shifts} + transforms: + - _target_: dingo.gw.transforms.SampleExtrinsicParameters + extrinsic_prior_dict: ${data.extrinsic_prior} + - _target_: dingo.gw.transforms.GetDetectorTimes + ifo_list: + _target_: bilby.gw.detector.InterferometerList + interferometers: ${data.detectors} + ref_time: ${data.ref_time} + - ${data.gnpe_time_shifts} + - _target_: dingo.gw.transforms.ProjectOntoDetectors + _partial_: true + _runtime_dependencies_: [domain] + ifo_list: + _target_: bilby.gw.detector.InterferometerList + interferometers: ${data.detectors} + ref_time: ${data.ref_time} + - _target_: dingo.gw.transforms.SampleNoiseASD + _partial_: true + _runtime_dependencies_: [asd_dataset] + - _target_: dingo.gw.transforms.WhitenAndScaleStrain + _partial_: true + _runtime_dependencies_: [domain] + - _target_: dingo.gw.transforms.AddWhiteNoiseComplex + - _target_: dingo.gw.transforms.SelectStandardizeRepackageParameters + parameters_dict: + inference_parameters: ${data.inference_parameters} + context_parameters: ${data.context_parameters} + standardization_dict: ${data.standardization} + - _target_: dingo.gw.transforms.RepackageStrainsAndASDS + _partial_: true + _runtime_dependencies_: [domain] + ifos: ${data.detectors} + - _target_: dingo.gw.transforms.UnpackDict + selected_keys: ${data.selected_keys} training: stage_0: epochs: 300 - asd_dataset_path: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 + asd_dataset: + _target_: dingo.gw.noise.asd_dataset.ASDDataset + file_name: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 + ifos: ${data.detectors} + precision: single + domain_update: null freeze_rb_layer: true batch_size: 4096 early_stopping: null @@ -45,15 +105,22 @@ training: T_max: 300 stage_1: epochs: 150 - asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + asd_dataset: + _target_: dingo.gw.noise.asd_dataset.ASDDataset + file_name: training_data/asd_dataset/asds_O1.hdf5 + ifos: ${data.detectors} + precision: single + domain_update: null freeze_rb_layer: false batch_size: 4096 early_stopping: null optimizer: - type: adam + _target_: torch.optim.Adam + _partial_: true lr: 1.0e-5 scheduler: - type: cosine + _target_: torch.optim.lr_scheduler.CosineAnnealingLR + _partial_: true T_max: 150 local: diff --git a/configs/experiment/train_gnpe_init.yaml b/configs/experiment/train_gnpe_init.yaml index f47dd2cba..a56de527f 100644 --- a/configs/experiment/train_gnpe_init.yaml +++ b/configs/experiment/train_gnpe_init.yaml @@ -5,12 +5,13 @@ defaults: - _self_ data: - waveform_dataset_path: training_data/waveform_dataset.hdf5 + waveform_dataset: + file_name: training_data/waveform_dataset.hdf5 + domain_update: + f_min: 20.0 + f_max: 1024.0 + svd_size_update: 200 train_fraction: 0.95 - domain_update: - f_min: 20.0 - f_max: 1024.0 - svd_size_update: 200 detectors: [H1, L1] ref_time: 1126259462.391 inference_parameters: @@ -20,7 +21,12 @@ data: training: stage_0: epochs: 150 - asd_dataset_path: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 + asd_dataset: + _target_: dingo.gw.noise.asd_dataset.ASDDataset + file_name: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 + ifos: ${data.detectors} + precision: single + domain_update: null freeze_rb_layer: true batch_size: 4096 early_stopping: null @@ -30,15 +36,22 @@ training: T_max: 150 stage_1: epochs: 75 - asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + asd_dataset: + _target_: dingo.gw.noise.asd_dataset.ASDDataset + file_name: training_data/asd_dataset/asds_O1.hdf5 + ifos: ${data.detectors} + precision: single + domain_update: null freeze_rb_layer: false batch_size: 4096 early_stopping: null optimizer: - type: adam + _target_: torch.optim.Adam + _partial_: true lr: 1.0e-5 scheduler: - type: cosine + _target_: torch.optim.lr_scheduler.CosineAnnealingLR + _partial_: true T_max: 75 local: diff --git a/configs/experiment/train_npe.yaml b/configs/experiment/train_npe.yaml index 0ecd913ec..f9413befd 100644 --- a/configs/experiment/train_npe.yaml +++ b/configs/experiment/train_npe.yaml @@ -5,12 +5,13 @@ defaults: - _self_ data: - waveform_dataset_path: training_data/waveform_dataset.hdf5 + waveform_dataset: + file_name: training_data/waveform_dataset.hdf5 + domain_update: + f_min: 20.0 + f_max: 1024.0 + svd_size_update: 200 train_fraction: 0.95 - domain_update: - f_min: 20.0 - f_max: 1024.0 - svd_size_update: 200 detectors: [H1, L1] ref_time: 1126259462.391 inference_parameters: @@ -28,7 +29,12 @@ data: training: stage_0: epochs: 300 - asd_dataset_path: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 + asd_dataset: + _target_: dingo.gw.noise.asd_dataset.ASDDataset + file_name: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 + ifos: ${data.detectors} + precision: single + domain_update: null freeze_rb_layer: true batch_size: 4096 early_stopping: null @@ -38,15 +44,22 @@ training: T_max: 300 stage_1: epochs: 150 - asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + asd_dataset: + _target_: dingo.gw.noise.asd_dataset.ASDDataset + file_name: training_data/asd_dataset/asds_O1.hdf5 + ifos: ${data.detectors} + precision: single + domain_update: null freeze_rb_layer: false batch_size: 4096 early_stopping: null optimizer: - type: adam + _target_: torch.optim.Adam + _partial_: true lr: 1.0e-5 scheduler: - type: cosine + _target_: torch.optim.lr_scheduler.CosineAnnealingLR + _partial_: true T_max: 150 local: diff --git a/configs/experiment/train_toy.yaml b/configs/experiment/train_toy.yaml index 687fad524..a970db3ac 100644 --- a/configs/experiment/train_toy.yaml +++ b/configs/experiment/train_toy.yaml @@ -5,10 +5,11 @@ defaults: - _self_ data: - waveform_dataset_path: training_data/waveform_dataset.hdf5 + waveform_dataset: + file_name: training_data/waveform_dataset.hdf5 + domain_update: null + svd_size_update: null train_fraction: 0.95 - domain_update: null - svd_size_update: null detectors: [H1, L1] ref_time: 1126259462.391 inference_parameters: @@ -27,7 +28,12 @@ data: training: stage_0: epochs: 20 - asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + asd_dataset: + _target_: dingo.gw.noise.asd_dataset.ASDDataset + file_name: training_data/asd_dataset/asds_O1.hdf5 + ifos: ${data.detectors} + precision: single + domain_update: null freeze_rb_layer: true batch_size: 64 early_stopping: null diff --git a/configs/extrinsic_prior/default.yaml b/configs/extrinsic_prior/default.yaml index 702057e6d..25b4a17c7 100644 --- a/configs/extrinsic_prior/default.yaml +++ b/configs/extrinsic_prior/default.yaml @@ -1,5 +1,8 @@ -dec: default -ra: default -geocent_time: bilby.core.prior.Uniform(minimum=-0.10, maximum=0.10, name='geocent_time') -psi: default -luminosity_distance: bilby.core.prior.Uniform(minimum=100.0, maximum=1000.0, name='luminosity_distance') +_target_: dingo.gw.prior.BBHExtrinsicPriorDict +_convert_: all +dictionary: + dec: bilby.core.prior.Cosine(minimum=-np.pi/2, maximum=np.pi/2, name='dec') + ra: bilby.core.prior.Uniform(minimum=0., maximum=2*np.pi, boundary="periodic", name="ra") + geocent_time: bilby.core.prior.Uniform(minimum=-0.10, maximum=0.10, name='geocent_time') + psi: bilby.core.prior.Uniform(minimum=0.0, maximum=np.pi, boundary="periodic", name="psi") + luminosity_distance: bilby.core.prior.Uniform(minimum=100.0, maximum=1000.0, name='luminosity_distance') diff --git a/configs/extrinsic_prior/fmpe.yaml b/configs/extrinsic_prior/fmpe.yaml index 4d79f9f7e..ab67e5e28 100644 --- a/configs/extrinsic_prior/fmpe.yaml +++ b/configs/extrinsic_prior/fmpe.yaml @@ -1,5 +1,8 @@ -dec: default -ra: default -geocent_time: bilby.core.prior.Uniform(minimum=-0.03, maximum=0.03, name='geocent_time') -psi: default -luminosity_distance: bilby.core.prior.Uniform(minimum=100.0, maximum=1000.0, name='luminosity_distance') +_target_: dingo.gw.prior.BBHExtrinsicPriorDict +_convert_: all +dictionary: + dec: bilby.core.prior.Cosine(minimum=-np.pi/2, maximum=np.pi/2, name='dec') + ra: bilby.core.prior.Uniform(minimum=0., maximum=2*np.pi, boundary="periodic", name="ra") + geocent_time: bilby.core.prior.Uniform(minimum=-0.03, maximum=0.03, name='geocent_time') + psi: bilby.core.prior.Uniform(minimum=0.0, maximum=np.pi, boundary="periodic", name="psi") + luminosity_distance: bilby.core.prior.Uniform(minimum=100.0, maximum=1000.0, name='luminosity_distance') diff --git a/configs/hydra/default.yaml b/configs/hydra/default.yaml index 7b9f91d97..f32382a96 100644 --- a/configs/hydra/default.yaml +++ b/configs/hydra/default.yaml @@ -4,3 +4,8 @@ hydra: dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} job: chdir: true + job_logging: + loggers: + bilby: + level: INFO + propagate: true diff --git a/configs/intrinsic_prior/default.yaml b/configs/intrinsic_prior/default.yaml index 609f3d6e7..c9a697b87 100644 --- a/configs/intrinsic_prior/default.yaml +++ b/configs/intrinsic_prior/default.yaml @@ -1,10 +1,13 @@ -mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1') -mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2') -chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=100.0, name='chirp_mass') -mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') -phase: default -chi_1: bilby.gw.prior.AlignedSpin(name='chi_1', a_prior=Uniform(minimum=0, maximum=0.9)) -chi_2: bilby.gw.prior.AlignedSpin(name='chi_2', a_prior=Uniform(minimum=0, maximum=0.9)) -theta_jn: default -luminosity_distance: 100.0 -geocent_time: 0.0 +_target_: bilby.gw.prior.BBHPriorDict +_convert_: all +dictionary: + mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1') + mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2') + chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=100.0, name='chirp_mass') + mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') + phase: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phase") + chi_1: bilby.gw.prior.AlignedSpin(name='chi_1', a_prior=Uniform(minimum=0, maximum=0.9)) + chi_2: bilby.gw.prior.AlignedSpin(name='chi_2', a_prior=Uniform(minimum=0, maximum=0.9)) + theta_jn: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='theta_jn') + luminosity_distance: 100.0 + geocent_time: 0.0 diff --git a/configs/intrinsic_prior/fmpe.yaml b/configs/intrinsic_prior/fmpe.yaml index 30d74ebb4..41d0f1737 100644 --- a/configs/intrinsic_prior/fmpe.yaml +++ b/configs/intrinsic_prior/fmpe.yaml @@ -1,14 +1,17 @@ -mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_1') -mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_2') -chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=20.0, maximum=120.0, name='chirp_mass') -mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') -phase: default -a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_1') -a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_2') -tilt_1: default -tilt_2: default -phi_12: default -phi_jl: default -theta_jn: default -luminosity_distance: 100.0 -geocent_time: 0.0 +_target_: bilby.gw.prior.BBHPriorDict +_convert_: all +dictionary: + mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_1') + mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_2') + chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=20.0, maximum=120.0, name='chirp_mass') + mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') + phase: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phase") + a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_1') + a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_2') + tilt_1: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_1') + tilt_2: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_2') + phi_12: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_12") + phi_jl: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_jl") + theta_jn: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='theta_jn') + luminosity_distance: 100.0 + geocent_time: 0.0 diff --git a/configs/intrinsic_prior/precessing.yaml b/configs/intrinsic_prior/precessing.yaml index 5400d688d..c2648da57 100644 --- a/configs/intrinsic_prior/precessing.yaml +++ b/configs/intrinsic_prior/precessing.yaml @@ -1,14 +1,17 @@ -mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_1') -mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_2') -chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=150.0, name='chirp_mass') -mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') -phase: default -a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_1') -a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_2') -tilt_1: default -tilt_2: default -phi_12: default -phi_jl: default -theta_jn: default -luminosity_distance: 100.0 -geocent_time: 0.0 +_target_: bilby.gw.prior.BBHPriorDict +_convert_: all +dictionary: + mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_1') + mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_2') + chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=150.0, name='chirp_mass') + mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') + phase: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phase") + a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_1') + a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_2') + tilt_1: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_1') + tilt_2: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_2') + phi_12: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_12") + phi_jl: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_jl") + theta_jn: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='theta_jn') + luminosity_distance: 100.0 + geocent_time: 0.0 diff --git a/configs/intrinsic_prior/precessing_multibanded_test.yaml b/configs/intrinsic_prior/precessing_multibanded_test.yaml index 7c3f469c1..1db3d7c62 100644 --- a/configs/intrinsic_prior/precessing_multibanded_test.yaml +++ b/configs/intrinsic_prior/precessing_multibanded_test.yaml @@ -1,14 +1,17 @@ -mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1') -mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2') -chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=100.0, name='chirp_mass') -mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') -phase: default -a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_1') -a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_2') -tilt_1: default -tilt_2: default -phi_12: default -phi_jl: default -theta_jn: default -luminosity_distance: 100.0 -geocent_time: 0.0 +_target_: bilby.gw.prior.BBHPriorDict +_convert_: all +dictionary: + mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1') + mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2') + chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=100.0, name='chirp_mass') + mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') + phase: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phase") + a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_1') + a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_2') + tilt_1: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_1') + tilt_2: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_2') + phi_12: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_12") + phi_jl: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_jl") + theta_jn: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='theta_jn') + luminosity_distance: 100.0 + geocent_time: 0.0 diff --git a/configs/model/fmpe.yaml b/configs/model/fmpe.yaml index 94241980e..62d50ced8 100644 --- a/configs/model/fmpe.yaml +++ b/configs/model/fmpe.yaml @@ -1,4 +1,4 @@ -posterior_model_type: flow_matching +_target_: dingo.core.posterior_models.FlowMatchingPosteriorModel posterior_kwargs: activation: gelu batch_norm: true diff --git a/configs/model/gnpe.yaml b/configs/model/gnpe.yaml index 17bd48972..a4296b293 100644 --- a/configs/model/gnpe.yaml +++ b/configs/model/gnpe.yaml @@ -1,4 +1,4 @@ -posterior_model_type: normalizing_flow +_target_: dingo.core.posterior_models.NormalizingFlowPosteriorModel posterior_kwargs: num_flow_steps: 30 base_transform_kwargs: diff --git a/configs/model/gnpe_init.yaml b/configs/model/gnpe_init.yaml index ee39f8ec4..0a53ebfa5 100644 --- a/configs/model/gnpe_init.yaml +++ b/configs/model/gnpe_init.yaml @@ -1,4 +1,4 @@ -posterior_model_type: normalizing_flow +_target_: dingo.core.posterior_models.NormalizingFlowPosteriorModel posterior_kwargs: num_flow_steps: 15 base_transform_kwargs: diff --git a/configs/model/npe.yaml b/configs/model/npe.yaml index 17bd48972..a4296b293 100644 --- a/configs/model/npe.yaml +++ b/configs/model/npe.yaml @@ -1,4 +1,4 @@ -posterior_model_type: normalizing_flow +_target_: dingo.core.posterior_models.NormalizingFlowPosteriorModel posterior_kwargs: num_flow_steps: 30 base_transform_kwargs: diff --git a/configs/model/toy_npe.yaml b/configs/model/toy_npe.yaml index 295545298..652707e93 100644 --- a/configs/model/toy_npe.yaml +++ b/configs/model/toy_npe.yaml @@ -1,4 +1,4 @@ -posterior_model_type: normalizing_flow +_target_: dingo.core.posterior_models.NormalizingFlowPosteriorModel posterior_kwargs: num_flow_steps: 5 base_transform_kwargs: diff --git a/configs/model/unconditional_npe.yaml b/configs/model/unconditional_npe.yaml index 41c500b77..3dc750652 100644 --- a/configs/model/unconditional_npe.yaml +++ b/configs/model/unconditional_npe.yaml @@ -1,4 +1,4 @@ -posterior_model_type: normalizing_flow +_target_: dingo.core.posterior_models.NormalizingFlowPosteriorModel embedding_kwargs: null posterior_kwargs: num_flow_steps: 10 diff --git a/configs/optimizer/adam.yaml b/configs/optimizer/adam.yaml index 30ba48b22..6182e57db 100644 --- a/configs/optimizer/adam.yaml +++ b/configs/optimizer/adam.yaml @@ -1,2 +1,3 @@ -type: adam +_target_: torch.optim.Adam +_partial_: true lr: 0.0001 diff --git a/configs/scheduler/cosine.yaml b/configs/scheduler/cosine.yaml index c45a236f1..5ba12c5a7 100644 --- a/configs/scheduler/cosine.yaml +++ b/configs/scheduler/cosine.yaml @@ -1,2 +1,3 @@ -type: cosine +_target_: torch.optim.lr_scheduler.CosineAnnealingLR +_partial_: true T_max: 20 diff --git a/configs/train.yaml b/configs/train.yaml index 631369b33..8ef705247 100644 --- a/configs/train.yaml +++ b/configs/train.yaml @@ -12,14 +12,19 @@ exit_command: "" data: extrinsic_prior: ${extrinsic_prior} - waveform_dataset_path: training_data/waveform_dataset.hdf5 + waveform_dataset: + _target_: dingo.gw.dataset.WaveformDataset + file_name: training_data/waveform_dataset.hdf5 + precision: single + domain_update: null + svd_size_update: null train_fraction: 0.95 - domain_update: null - svd_size_update: null detectors: - H1 - L1 ref_time: 1126259462.391 + gnpe_time_shifts: null + random_strain_cropping: null inference_parameters: - chirp_mass - mass_ratio @@ -32,6 +37,54 @@ data: - luminosity_distance - psi - phase + context_parameters: [] + selected_keys: + - inference_parameters + - waveform + standardization: + mean: ??? + std: ??? + standardization_transforms: + - _target_: dingo.gw.transforms.SampleExtrinsicParameters + extrinsic_prior_dict: ${data.extrinsic_prior} + - _target_: dingo.gw.transforms.GetDetectorTimes + ifo_list: + _target_: bilby.gw.detector.InterferometerList + interferometers: ${data.detectors} + ref_time: ${data.ref_time} + transforms: + - _target_: dingo.gw.transforms.SampleExtrinsicParameters + extrinsic_prior_dict: ${data.extrinsic_prior} + - _target_: dingo.gw.transforms.GetDetectorTimes + ifo_list: + _target_: bilby.gw.detector.InterferometerList + interferometers: ${data.detectors} + ref_time: ${data.ref_time} + - _target_: dingo.gw.transforms.ProjectOntoDetectors + _partial_: true + _runtime_dependencies_: [domain] + ifo_list: + _target_: bilby.gw.detector.InterferometerList + interferometers: ${data.detectors} + ref_time: ${data.ref_time} + - _target_: dingo.gw.transforms.SampleNoiseASD + _partial_: true + _runtime_dependencies_: [asd_dataset] + - _target_: dingo.gw.transforms.WhitenAndScaleStrain + _partial_: true + _runtime_dependencies_: [domain] + - _target_: dingo.gw.transforms.AddWhiteNoiseComplex + - _target_: dingo.gw.transforms.SelectStandardizeRepackageParameters + parameters_dict: + inference_parameters: ${data.inference_parameters} + context_parameters: ${data.context_parameters} + standardization_dict: ${data.standardization} + - _target_: dingo.gw.transforms.RepackageStrainsAndASDS + _partial_: true + _runtime_dependencies_: [domain] + ifos: ${data.detectors} + - _target_: dingo.gw.transforms.UnpackDict + selected_keys: ${data.selected_keys} training: evaluate: false @@ -39,7 +92,12 @@ training: optimizer: ${optimizer} scheduler: ${scheduler} epochs: 20 - asd_dataset_path: training_data/asd_dataset/asds_O1.hdf5 + asd_dataset: + _target_: dingo.gw.noise.asd_dataset.ASDDataset + file_name: training_data/asd_dataset/asds_O1.hdf5 + ifos: ${data.detectors} + precision: single + domain_update: null freeze_rb_layer: true batch_size: 64 early_stopping: null diff --git a/configs/waveform_generator/phenom_d.yaml b/configs/waveform_generator/phenom_d.yaml index 4ec9d64b3..d56a7d5c7 100644 --- a/configs/waveform_generator/phenom_d.yaml +++ b/configs/waveform_generator/phenom_d.yaml @@ -1,3 +1,4 @@ +_target_: dingo.gw.waveform_generator.WaveformGenerator approximant: IMRPhenomD f_ref: 20.0 f_start: null diff --git a/configs/waveform_generator/phenom_pv2.yaml b/configs/waveform_generator/phenom_pv2.yaml index f4390b2fb..cedd187a6 100644 --- a/configs/waveform_generator/phenom_pv2.yaml +++ b/configs/waveform_generator/phenom_pv2.yaml @@ -1,3 +1,4 @@ +_target_: dingo.gw.waveform_generator.WaveformGenerator approximant: IMRPhenomPv2 f_ref: 20.0 f_start: null diff --git a/configs/waveform_generator/phenom_xphm.yaml b/configs/waveform_generator/phenom_xphm.yaml index 8c1a14c17..488f2c656 100644 --- a/configs/waveform_generator/phenom_xphm.yaml +++ b/configs/waveform_generator/phenom_xphm.yaml @@ -1,3 +1,4 @@ +_target_: dingo.gw.waveform_generator.WaveformGenerator approximant: IMRPhenomXPHM f_ref: 20.0 f_start: null diff --git a/dingo/asimov/asimov.py b/dingo/asimov/asimov.py index 230a211d1..9c83d7d3c 100644 --- a/dingo/asimov/asimov.py +++ b/dingo/asimov/asimov.py @@ -205,9 +205,9 @@ def collect_logs(self): with open(log, "r") as log_f: message = log_f.read() message = message.split("\n") - messages[log.split("/")[-1]] = "\n".join(message[-100:]) + messages[logger.split("/")[-1]] = "\n".join(message[-100:]) except FileNotFoundError: - messages[log.split("/")[-1]] = ( + messages[logger.split("/")[-1]] = ( "There was a problem opening this log file." ) return messages @@ -253,7 +253,10 @@ def fmin_max_are_compatible(self, prod_meta, net_meta): @staticmethod def _net_max_luminosity_distance(metadata): - prior = metadata["train_settings"]["data"]["extrinsic_prior"]["luminosity_distance"] + extrinsic_prior = metadata["train_settings"]["data"]["extrinsic_prior"] + prior = extrinsic_prior.get("dictionary", extrinsic_prior)[ + "luminosity_distance" + ] match = re.findall(r"maximum=[\d]+", prior) assert match return int(match[0].split("=")[-1]) diff --git a/dingo/core/dataset.py b/dingo/core/dataset.py index e15a42fac..45b5c1153 100644 --- a/dingo/core/dataset.py +++ b/dingo/core/dataset.py @@ -4,10 +4,12 @@ import h5py import numpy as np import pandas as pd +import yaml +from omegaconf import OmegaConf from dingo.core.utils.misc import get_version -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) def recursive_hdf5_save(group, d): @@ -139,7 +141,7 @@ def __init__( self.from_dictionary(dictionary) def to_file(self, file_name: str, mode: str = "w"): - log.info(f"Saving dataset to {file_name}") + logger.info(f"Saving dataset to {file_name}") save_dict = { k: v for k, v in vars(self).items() @@ -148,14 +150,17 @@ def to_file(self, file_name: str, mode: str = "w"): with h5py.File(file_name, mode) as f: recursive_hdf5_save(f, save_dict) if self.settings: - f.attrs["settings"] = str(self.settings) + f.attrs["settings"] = OmegaConf.to_yaml( + OmegaConf.create(self.settings), + resolve=True, + ) if self.dataset_type: f.attrs["dataset_type"] = self.dataset_type def from_file(self, file_name: str): - log.info(f"Loading dataset from {file_name}.") + logger.info(f"Loading dataset from {file_name}.") if self._leave_on_disk_keys: - log.info(f"Omitting data keys {self._leave_on_disk_keys}.") + logger.info(f"Omitting data keys {self._leave_on_disk_keys}.") with h5py.File(file_name, "r") as f: loaded_dict = recursive_hdf5_load( @@ -167,7 +172,9 @@ def from_file(self, file_name: str): assert k in self._data_keys setattr(self, k, v) try: - self.settings = ast.literal_eval(f.attrs["settings"]) + self.settings = OmegaConf.create(yaml.safe_load(f.attrs["settings"])) + except yaml.YAMLError: + self.settings = OmegaConf.create(ast.literal_eval(f.attrs["settings"])) except KeyError: self.settings = None # Is this necessary? diff --git a/dingo/core/density/__init__.py b/dingo/core/density/__init__.py index e465636a5..aff991ca2 100644 --- a/dingo/core/density/__init__.py +++ b/dingo/core/density/__init__.py @@ -8,4 +8,3 @@ interpolated_sample_and_log_prob_multi, interpolated_log_prob_multi, ) -from .nde_settings import get_default_nde_settings_3d diff --git a/dingo/core/density/nde_settings.py b/dingo/core/density/nde_settings.py deleted file mode 100644 index e63759843..000000000 --- a/dingo/core/density/nde_settings.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Default settings for unconditional density estimation""" - - -def get_default_nde_settings_3d( - device="cpu", - num_workers=0, - inference_parameters=None, -): - settings = { - "data": { - "inference_parameters": inference_parameters, - }, - "model": { - "type": "nsf", - "num_flow_steps": 5, - "base_transform_kwargs": { - "hidden_dim": 128, - "num_transform_blocks": 2, - "activation": "elu", - "dropout_probability": 0.1, - "batch_norm": True, - "num_bins": 8, - "base_transform_type": "rq-coupling", - }, - "input_dim": 3, - "context_dim": None, - }, - "training": { - "device": device, - "num_workers": num_workers, - "train_fraction": 0.9, - "batch_size": 4096, - "epochs": 10, - "optimizer": {"type": "adam", "lr": 0.005}, - "scheduler": {"type": "cosine", "T_max": 10}, - }, - } - return settings - - -DEFAULT_NDE_SETTINGS_2D = { - "data": { - "inference_parameters": ["GNPE:H1_time_proxy", "GNPE:L1_time_proxy"], - "parameter_samples": None, - }, - "model": { - "type": "nsf", - "num_flow_steps": 5, - "base_transform_kwargs": { - "hidden_dim": 64, - "num_transform_blocks": 2, - "activation": "elu", - "dropout_probability": 0.1, - "batch_norm": "true", - "num_bins": 8, - "base_transform_type": "rq-coupling", - }, - }, - # nde training - "training": { - "device": "cpu", - "num_workers": 0, - "train_fraction": 0.9, - "batch_size": 4096, - "epochs": 10, - "optimizer": {"type": "adam"}, - "lr": 0.001, - "scheduler": { - "type": "cosine", - "T_max": 10, - }, - }, -} diff --git a/dingo/core/density/unconditional_density_estimation.py b/dingo/core/density/unconditional_density_estimation.py index 48d056cae..33dfbb5f4 100644 --- a/dingo/core/density/unconditional_density_estimation.py +++ b/dingo/core/density/unconditional_density_estimation.py @@ -5,14 +5,13 @@ import hydra import torch import numpy as np -from hydra.utils import to_absolute_path +from hydra.utils import instantiate, to_absolute_path from omegaconf import DictConfig, OmegaConf from dingo.core.utils import build_train_and_test_loaders from dingo.core.utils.trainutils import RuntimeLimits -from dingo.core.posterior_models import NormalizingFlowPosteriorModel -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) logging.captureWarnings(True) @@ -79,8 +78,8 @@ def train_unconditional_density_estimator( # set up density estimation network settings["model"]["posterior_kwargs"]["input_dim"] = num_params settings["model"]["posterior_kwargs"]["context_dim"] = None - # TODO: Allow for other types of density estimators (e.g., flow matching). - model = NormalizingFlowPosteriorModel( + model = instantiate( + {"_target_": settings["model"]["_target_"]}, metadata={"train_settings": settings, "base": copy.deepcopy(result.metadata)}, device=settings["training"]["device"], ) @@ -129,9 +128,9 @@ def main(cfg: DictConfig): train_dir = settings.pop("train_dir") os.makedirs(train_dir, exist_ok=True) - log.info(f"Loading result from {result_file}.") + logger.info(f"Loading result from {result_file}.") result = Result(file_name=result_file) - log.info(f"Training unconditional density estimator in {train_dir}.") + logger.info(f"Training unconditional density estimator in {train_dir}.") train_unconditional_density_estimator(result, settings, train_dir) diff --git a/dingo/core/posterior_models/base_model.py b/dingo/core/posterior_models/base_model.py index 9f0e372cc..30a627841 100755 --- a/dingo/core/posterior_models/base_model.py +++ b/dingo/core/posterior_models/base_model.py @@ -11,6 +11,8 @@ import torch import dingo.core.utils as utils +from hydra.utils import instantiate +from omegaconf import OmegaConf from torch.utils.data import Dataset import time import numpy as np @@ -24,7 +26,13 @@ from dingo.core.utils.trainutils import EarlyStopping -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) + + +def _to_plain_container(value): + if OmegaConf.is_config(value): + return OmegaConf.to_container(value, resolve=True) + return value class BasePosteriorModel(ABC): @@ -68,7 +76,7 @@ def __init__( self.scheduler_kwargs = None self.initial_weights = initial_weights - self.metadata = metadata + self.metadata = OmegaConf.create(metadata) if metadata is not None else None if self.metadata is not None: self.model_kwargs = self.metadata["train_settings"]["model"] # Expect self.optimizer_settings and self.scheduler_settings to be set @@ -201,7 +209,7 @@ def network_to_device(self, device): # raise NotImplementedError('This needs testing!') # # dim = 0 [512, ...] -> [256, ...], [256, ...] on 2 GPUs # self.network = torch.nn.DataParallel(self.network) - log.info(f"Putting posterior model to device {self.device}.") + logger.info(f"Putting posterior model to device {self.device}.") self.network.to(self.device) def initialize_optimizer_and_scheduler(self): @@ -210,12 +218,18 @@ def initialize_optimizer_and_scheduler(self): and self.scheduler_kwargs, respectively. """ if self.optimizer_kwargs is not None: - self.optimizer = utils.get_optimizer_from_kwargs( - self.network.parameters(), **self.optimizer_kwargs + optimizer = instantiate(self.optimizer_kwargs) + self.optimizer = ( + optimizer(self.network.parameters()) + if self.optimizer_kwargs.get("_partial_", False) + else optimizer ) if self.scheduler_kwargs is not None: - self.scheduler = utils.get_scheduler_from_kwargs( - self.optimizer, **self.scheduler_kwargs + scheduler = instantiate(self.scheduler_kwargs) + self.scheduler = ( + scheduler(self.optimizer) + if self.scheduler_kwargs.get("_partial_", False) + else scheduler ) def save_model( @@ -236,14 +250,14 @@ def save_model( """ model_dict = { - "model_kwargs": self.model_kwargs, + "model_kwargs": _to_plain_container(self.model_kwargs), "model_state_dict": self.network.state_dict(), "epoch": self.epoch, "version": self.version, } if self.metadata is not None: - model_dict["metadata"] = self.metadata + model_dict["metadata"] = _to_plain_container(self.metadata) if self.context is not None: model_dict["context"] = self.context @@ -252,8 +266,8 @@ def save_model( model_dict["event_metadata"] = self.event_metadata if save_training_info: - model_dict["optimizer_kwargs"] = self.optimizer_kwargs - model_dict["scheduler_kwargs"] = self.scheduler_kwargs + model_dict["optimizer_kwargs"] = _to_plain_container(self.optimizer_kwargs) + model_dict["scheduler_kwargs"] = _to_plain_container(self.scheduler_kwargs) if self.optimizer is not None: model_dict["optimizer_state_dict"] = self.optimizer.state_dict() if self.scheduler is not None: @@ -335,7 +349,7 @@ def load_model( self.epoch = d["epoch"] - self.metadata = d["metadata"] + self.metadata = OmegaConf.create(d["metadata"]) if "context" in d: self.context = d["context"] @@ -397,7 +411,7 @@ def train( if test_only: test_loss = test_epoch(self, test_loader) - log.info(f"test loss: {test_loss:.3f}") + logger.info(f"test loss: {test_loss:.3f}") else: while not runtime_limits.limits_exceeded(self.epoch): @@ -406,24 +420,24 @@ def train( # Training lr = utils.get_lr(self.optimizer) with threadpool_limits(limits=1, user_api="blas"): - log.info(f"\nStart training epoch {self.epoch} with lr {lr}") + logger.info(f"\nStart training epoch {self.epoch} with lr {lr}") time_start = time.time() train_loss = train_epoch(self, train_loader) train_time = time.time() - time_start - log.info( + logger.info( "Done. This took {:2.0f}:{:2.0f} min.".format( *divmod(train_time, 60) ) ) # Testing - log.info(f"Start testing epoch {self.epoch}") + logger.info(f"Start testing epoch {self.epoch}") time_start = time.time() test_loss = test_epoch(self, test_loader) test_time = time.time() - time_start - log.info( + logger.info( "Done. This took {:2.0f}:{:2.0f} min.".format( *divmod(time.time() - time_start, 60) ) @@ -452,7 +466,7 @@ def train( } ) except ImportError: - log.warning("wandb not installed. Skipping logging to wandb.") + logger.warning("wandb not installed. Skipping logging to wandb.") if early_stopping is not None: # Whether to use train or test loss @@ -467,9 +481,9 @@ def train( join(train_dir, "best_model.pt"), save_training_info=False ) if early_stopping.early_stop: - log.info("Early stopping") + logger.info("Early stopping") break - log.info(f"Finished training epoch {self.epoch}.\n") + logger.info(f"Finished training epoch {self.epoch}.\n") def train_epoch(pm, dataloader): diff --git a/dingo/core/posterior_models/build_model.py b/dingo/core/posterior_models/build_model.py index 2f7571032..27d79fed3 100644 --- a/dingo/core/posterior_models/build_model.py +++ b/dingo/core/posterior_models/build_model.py @@ -1,7 +1,5 @@ from dingo.core.posterior_models.base_model import BasePosteriorModel -from dingo.core.posterior_models.flow_matching import FlowMatchingPosteriorModel -from dingo.core.posterior_models.normalizing_flow import NormalizingFlowPosteriorModel -from dingo.core.posterior_models.score_matching import ScoreDiffusionPosteriorModel +from hydra.utils import instantiate from dingo.core.utils.backward_compatibility import ( torch_load_with_fallback, update_model_config, @@ -36,12 +34,6 @@ def build_model_from_kwargs( "Either a filename or a settings dict must be provided, but not both." ) - models_dict = { - "normalizing_flow": NormalizingFlowPosteriorModel, - "flow_matching": FlowMatchingPosteriorModel, - "score_matching": ScoreDiffusionPosteriorModel, - } - if filename is not None: d, _ = torch_load_with_fallback(filename, preferred_map_location="meta") if "version" in d: @@ -50,56 +42,13 @@ def build_model_from_kwargs( # version was introduced in v0.3.3 check_minimum_version("dingo=0.3.2") update_model_config(d["metadata"]["train_settings"]["model"]) # Backward compat - posterior_model_type = d["metadata"]["train_settings"]["model"][ - "posterior_model_type" - ] + model_settings = d["metadata"]["train_settings"]["model"] else: update_model_config(settings["train_settings"]["model"]) # Backward compat - posterior_model_type = settings["train_settings"]["model"][ - "posterior_model_type" - ] - - if not posterior_model_type.lower() in models_dict: - raise ValueError("No valid posterior model type specified.") - - model = models_dict[posterior_model_type.lower()] - - return model(model_filename=filename, metadata=settings, **kwargs) - - -def autocomplete_model_kwargs(model_kwargs: dict, data_sample: list): - """ - Autocomplete the model kwargs from train_settings and data_sample from the dataloader: - - * set input dimension of embedding net to shape of data_sample[1] - * set dimension of parameter space to len(data_sample[0]) - * set added_context flag of embedding net if required for gnpe proxies - * set context dim of posterior model to output dim of embedding net + gnpe proxy dim + model_settings = settings["train_settings"]["model"] - Parameters - ---------- - model_kwargs: dict - Model settings, which are modified in-place. - data_sample: list - Sample from dataloader (e.g., wfd[0]) used for autocomplection. - Should be of format [parameters, GW data, gnpe_proxies], where the - last element is only there is GNPE proxies are required. - """ + if "_target_" not in model_settings: + raise KeyError("Model settings need a Hydra _target_.") - # set input dims from ifo_list and domain information - model_kwargs["embedding_kwargs"]["input_dims"] = list(data_sample[1].shape) - # set dimension of parameter space of posterior model - model_kwargs["posterior_kwargs"]["input_dim"] = len(data_sample[0]) - # set added_context flag of embedding net if GNPE proxies are required - # set context dim of nsf to output dim of embedding net + GNPE proxy dim - try: - gnpe_proxy_dim = len(data_sample[2]) - model_kwargs["embedding_kwargs"]["added_context"] = True - model_kwargs["posterior_kwargs"]["context_dim"] = ( - model_kwargs["embedding_kwargs"]["output_dim"] + gnpe_proxy_dim - ) - except IndexError: - model_kwargs["embedding_kwargs"]["added_context"] = False - model_kwargs["posterior_kwargs"]["context_dim"] = model_kwargs[ - "embedding_kwargs" - ]["output_dim"] + model_target = {"_target_": model_settings["_target_"]} + return instantiate(model_target, model_filename=filename, metadata=settings, **kwargs) diff --git a/dingo/core/posterior_models/cflow_base.py b/dingo/core/posterior_models/cflow_base.py index 2f9b09bbc..16e821420 100644 --- a/dingo/core/posterior_models/cflow_base.py +++ b/dingo/core/posterior_models/cflow_base.py @@ -123,7 +123,7 @@ def rhs_of_joint_ode(self, t, theta_and_div_t, *context_data, hutchinson=False): def initialize_network(self): model_kwargs = { - k: v for k, v in self.model_kwargs.items() if k != "posterior_model_type" + k: v for k, v in self.model_kwargs.items() if not k.startswith("_") } if self.initial_weights is not None: model_kwargs["initial_weights"] = self.initial_weights diff --git a/dingo/core/posterior_models/normalizing_flow.py b/dingo/core/posterior_models/normalizing_flow.py index 8481aeda9..67ebdb6de 100644 --- a/dingo/core/posterior_models/normalizing_flow.py +++ b/dingo/core/posterior_models/normalizing_flow.py @@ -39,7 +39,7 @@ def __init__(self, **kwargs): def initialize_network(self): model_kwargs = { - k: v for k, v in self.model_kwargs.items() if k != "posterior_model_type" + k: v for k, v in self.model_kwargs.items() if not k.startswith("_") } if self.initial_weights is not None: model_kwargs["initial_weights"] = self.initial_weights diff --git a/dingo/core/result.py b/dingo/core/result.py index a2c897a67..5edcdd318 100644 --- a/dingo/core/result.py +++ b/dingo/core/result.py @@ -30,7 +30,7 @@ "log_noise_evidence", ] -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) def _clip_weights(weights: np.ndarray, num_clip: int) -> np.ndarray: @@ -170,23 +170,23 @@ def reset_event(self, event_dataset): ): # This is really just for notification. Actions are only taken if the # event metadata differ. - log.warning("New event data differ from existing.") + logger.warning("New event data differ from existing.") self.context = context if self.event_metadata is not None and self.event_metadata != event_metadata: - log.warning("Changes") - log.warning("=======") + logger.warning("Changes") + logger.warning("=======") old_minus_new = dict(freeze(self.event_metadata) - freeze(event_metadata)) - log.warning("Old event metadata:") + logger.warning("Old event metadata:") for k in sorted(old_minus_new): - log.warning(f" {k}: {self.event_metadata[k]}") + logger.warning(f" {k}: {self.event_metadata[k]}") new_minus_old = dict(freeze(event_metadata) - freeze(self.event_metadata)) - log.warning("New event metadata:") + logger.warning("New event metadata:") if self.importance_sampling_metadata.get("updates") is None: self.importance_sampling_metadata["updates"] = {} for k in sorted(new_minus_old): - log.warning(f" {k}: {event_metadata[k]}") + logger.warning(f" {k}: {event_metadata[k]}") self.importance_sampling_metadata["updates"][k] = event_metadata[k] self._rebuild_domain(verbose=True) @@ -315,12 +315,12 @@ def importance_sample(self, num_processes: int = 1, **likelihood_kwargs): valid_samples = np.isfinite(log_prior + delta_log_prob_target) theta = theta.iloc[valid_samples] - log.info(f"Calculating {len(theta)} likelihoods.") + logger.info(f"Calculating {len(theta)} likelihoods.") t0 = time.time() log_likelihood = self.likelihood.log_likelihood_multi( theta, num_processes=num_processes ) - log.info(f"Done. This took {time.time() - t0:.2f} seconds.") + logger.info(f"Done. This took {time.time() - t0:.2f} seconds.") self.log_noise_evidence = self.likelihood.log_Zn self.samples["log_prior"] = log_prior @@ -497,7 +497,7 @@ def rejection_sample( rng = np.random.default_rng(random_state) weights = self.samples["weights"].to_numpy(dtype=float) - log.info( + logger.info( f"Rejection sampling: {self.num_samples} samples, " f"ESS = {self.effective_sample_size:.0f} " f"(efficiency = {100 * self.sample_efficiency:.1f}%)" @@ -529,7 +529,7 @@ def rejection_sample( unweighted = unweighted.drop( columns=["weights", "log_prob", "delta_log_prob_target"], errors="ignore" ) - log.info(f"Produced {len(unweighted)} unweighted samples.") + logger.info(f"Produced {len(unweighted)} unweighted samples.") return unweighted def parameter_subset(self, parameters): @@ -622,12 +622,12 @@ def print_summary(self): Display the number of samples, and (if importance sampling is complete) the log evidence and number of effective samples. """ - log.info(f"Number of samples: {len(self.samples)}") + logger.info(f"Number of samples: {len(self.samples)}") if self.log_evidence is not None: - log.info( + logger.info( f"Log(evidence): {self.log_evidence:.3f} +- {self.log_evidence_std:.3f}" ) - log.info( + logger.info( f"Effective samples {self.n_eff:.1f}: " f"(Sample efficiency = {100 * self.sample_efficiency:.2f}%)" ) @@ -818,7 +818,7 @@ def plot_log_probs(self, filename="log_probs.png"): plt.tight_layout() plt.savefig(filename) else: - log.warning("Results not importance sampled. Cannot produce log_prob plot.") + logger.warning("Results not importance sampled. Cannot produce log_prob plot.") def plot_weights(self, filename="weights.png"): """Make a scatter plot of samples weights vs log proposal.""" @@ -847,7 +847,7 @@ def plot_weights(self, filename="weights.png"): plt.tight_layout() plt.savefig(filename) else: - log.warning("Results not importance sampled. Cannot plot weights.") + logger.warning("Results not importance sampled. Cannot plot weights.") def get_all_injection_credible_levels( self, keys: list[str] = None, weighted: bool = False @@ -1023,7 +1023,7 @@ def make_pp_plot( pvalues = [] latex_labels = get_latex_labels(results[0].prior) - log.info("Key: KS-test p-value") + logger.info("Key: KS-test p-value") for ii, key in enumerate(credible_levels): pp = np.array( [ @@ -1033,7 +1033,7 @@ def make_pp_plot( ) pvalue = scipy.stats.kstest(credible_levels[key], "uniform").pvalue pvalues.append(pvalue) - log.info("{}: {}".format(key, pvalue)) + logger.info("{}: {}".format(key, pvalue)) label = "{} ({:2.3f})".format(latex_labels[key], pvalue) plt.plot(x_values, pp, lines[ii], label=label, **kwargs) @@ -1043,7 +1043,7 @@ def make_pp_plot( pvalues=pvalues, names=list(credible_levels.keys()), ) - log.info("Combined p-value: {}".format(pvals.combined_pvalue)) + logger.info("Combined p-value: {}".format(pvals.combined_pvalue)) if title: ax.set_title( diff --git a/dingo/core/samplers.py b/dingo/core/samplers.py index 5474bc5bd..a4511ac64 100644 --- a/dingo/core/samplers.py +++ b/dingo/core/samplers.py @@ -22,7 +22,7 @@ # Sampler classes are motivated by the approach of Bilby. # -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) class Sampler(object): @@ -159,7 +159,7 @@ def _run_sampler( x = [x] else: if context is not None: - log.warning("Unconditional model. Ignoring context.") + logger.warning("Unconditional model. Ignoring context.") x = [] # For a normalizing flow, we get the log_prob for "free" when sampling, @@ -207,7 +207,7 @@ def run_sampler( """ self.samples = None - log.info(f"Running sampler to generate {num_samples} samples.") + logger.info(f"Running sampler to generate {num_samples} samples.") t0 = time.time() if not self.unconditional_model: if self.context is None: @@ -231,7 +231,7 @@ def run_sampler( # correction for t_ref) and represent as DataFrame. self._post_process(samples) self.samples = pd.DataFrame(samples) - log.info(f"Done. This took {time.time() - t0:.1f} s.") + logger.info(f"Done. This took {time.time() - t0:.1f} s.") def log_prob(self, samples: pd.DataFrame | dict) -> np.ndarray: """ @@ -431,7 +431,7 @@ def _run_sampler( init_samples = self.init_sampler._run_sampler(num_samples, context) else: if self.num_iterations == 1: - log.warning( + logger.warning( f"Removing initial outliers, but only carrying out " f"{self.num_iterations} GNPE iteration. This risks biasing " f"results." @@ -516,7 +516,7 @@ def _run_sampler( p: x["extrinsic_parameters"][p] for p in self.gnpe_proxy_parameters } - log.info( + logger.info( f"it {i}.\tmin pvalue: {self.iteration_tracker.pvalue_min:.3f}" f"\tproxy mean: " + " ".join(f"{torch.mean(v).item():.5f}" for v in proxies.values()) diff --git a/dingo/core/utils/__init__.py b/dingo/core/utils/__init__.py index be5b05cf8..16635122b 100644 --- a/dingo/core/utils/__init__.py +++ b/dingo/core/utils/__init__.py @@ -2,3 +2,4 @@ from .trainutils import * from .gnpeutils import * from .misc import * +from .hydra_utils import * diff --git a/dingo/core/utils/condor_utils.py b/dingo/core/utils/condor_utils.py index cc51de919..0ab443554 100644 --- a/dingo/core/utils/condor_utils.py +++ b/dingo/core/utils/condor_utils.py @@ -1,8 +1,9 @@ +import logging import os from os.path import join import yaml -from dingo.core.utils.logging_utils import logger +logger = logging.getLogger(__name__) def resubmit_condor_job(train_dir, train_settings, epoch): diff --git a/dingo/core/utils/hydra_utils.py b/dingo/core/utils/hydra_utils.py new file mode 100644 index 000000000..02e334ec6 --- /dev/null +++ b/dingo/core/utils/hydra_utils.py @@ -0,0 +1,30 @@ +from copy import deepcopy + +from hydra.utils import instantiate +from omegaconf import OmegaConf + + +def instantiate_with_runtime_dependencies(config, runtime_dependencies: dict): + """ + Instantiate a Hydra config, optionally completing a partial with runtime objects. + + If the config contains ``_runtime_dependencies_``, only those named dependencies + are passed to the partial. Otherwise all runtime dependencies are passed. + """ + if OmegaConf.is_config(config): + config = OmegaConf.to_container(config, resolve=True) + else: + config = deepcopy(config) + dependency_names = config.pop("_runtime_dependencies_", None) + obj = instantiate(config) + if not config.get("_partial_", False): + return obj + + if dependency_names is None: + dependencies = runtime_dependencies + else: + dependencies = { + name: runtime_dependencies[name] + for name in dependency_names + } + return obj(**dependencies) diff --git a/dingo/core/utils/logging_utils.py b/dingo/core/utils/logging_utils.py index 9efbc83db..0ab166918 100644 --- a/dingo/core/utils/logging_utils.py +++ b/dingo/core/utils/logging_utils.py @@ -2,6 +2,8 @@ import os import sys +DINGO_LOG_FORMAT = "[%(asctime)s][%(name)s][%(levelname)s] - %(message)s" + def check_directory_exists_and_if_not_mkdir(directory, logger): """Checks if the given directory exists and creates it if it does not exist @@ -18,21 +20,25 @@ def check_directory_exists_and_if_not_mkdir(directory, logger): logger.debug(f"Directory {directory} exists") -def setup_logger(outdir=None, label=None, log_level="INFO"): +def setup_logger(outdir=None, label=None, log_level="INFO", use_bilby: bool = True): """Setup logging output: call at the start of the script to use. - Sets up the dingo logger and, if bilby is available, also calls bilby's - setup_logger so that both loggers share the same file handler and their - entries appear together in the same log file. + Sets up the dingo logger and, optionally, also calls bilby's setup_logger so + that both loggers share the same file handler when a log file is requested. Parameters ---------- - outdir, label: str - If supplied, write the logging output to outdir/label.log + outdir: str, optional + If supplied, write the logging output to this directory. + label: str, optional + Name of the log file without suffix. If omitted and outdir is supplied, + the entrypoint name is used. log_level: str, optional ['debug', 'info', 'warning'] Either a string from the list above, or an integer as specified in https://docs.python.org/2/library/logging.html#logging-levels + use_bilby: + If true and bilby is available, share bilby's file handler. """ if "-v" in sys.argv or "--verbose" in sys.argv: log_level = "DEBUG" @@ -45,15 +51,23 @@ def setup_logger(outdir=None, label=None, log_level="INFO"): else: level = int(log_level) + formatter = logging.Formatter(DINGO_LOG_FORMAT) + save_log = outdir is not None + entrypoint = os.path.splitext(os.path.basename(sys.argv[0]))[0] + if not entrypoint or entrypoint.startswith("-"): + entrypoint = "dingo" + log_label = label or entrypoint + # Set up bilby's logger first so we can share its file handler. bilby_logger = None - try: - from bilby.core.utils.log import setup_logger as bilby_setup_logger + if use_bilby and save_log: + try: + from bilby.core.utils.log import setup_logger as bilby_setup_logger - bilby_setup_logger(outdir=outdir or ".", label=label, log_level=log_level) - bilby_logger = logging.getLogger("bilby") - except ImportError: - pass + bilby_setup_logger(outdir=outdir, label=log_label, log_level=log_level) + bilby_logger = logging.getLogger("bilby") + except ImportError: + pass logger = logging.getLogger("dingo") logger.propagate = False @@ -64,46 +78,28 @@ def setup_logger(outdir=None, label=None, log_level="INFO"): isinstance(h, logging.StreamHandler) and not isinstance(h, logging.FileHandler) for h in logger.handlers ): - stream_handler = logging.StreamHandler() - stream_handler.setFormatter( - logging.Formatter( - "%(asctime)s %(name)s %(levelname)-8s: %(message)s", datefmt="%H:%M" - ) - ) + stream_handler = logging.StreamHandler(sys.stdout) + stream_handler.setFormatter(formatter) stream_handler.setLevel(level) logger.addHandler(stream_handler) # Share bilby's file handler so both loggers write to the same log file. if bilby_logger is not None: for handler in bilby_logger.handlers: + handler.setFormatter(formatter) + handler.setLevel(level) if isinstance(handler, logging.FileHandler): - handler.setFormatter( - logging.Formatter( - "%(asctime)s %(name)s %(levelname)-8s: %(message)s", - datefmt="%H:%M", - ) - ) if not any(isinstance(h, logging.FileHandler) for h in logger.handlers): logger.addHandler(handler) # Fall back to a standalone file handler if bilby is unavailable. - if label and not any(isinstance(h, logging.FileHandler) for h in logger.handlers): - if outdir: - check_directory_exists_and_if_not_mkdir(outdir, logger) - else: - outdir = "." - log_file = f"{outdir}/{label}.log" + if save_log and not any(isinstance(h, logging.FileHandler) for h in logger.handlers): + check_directory_exists_and_if_not_mkdir(outdir, logger) + log_file = os.path.join(outdir, f"{log_label}.log") file_handler = logging.FileHandler(log_file) - file_handler.setFormatter( - logging.Formatter( - "%(asctime)s %(name)s %(levelname)-8s: %(message)s", datefmt="%H:%M" - ) - ) + file_handler.setFormatter(formatter) file_handler.setLevel(level) logger.addHandler(file_handler) for handler in logger.handlers: handler.setLevel(level) - - -logger = logging.getLogger("dingo") diff --git a/dingo/core/utils/pt_to_hdf5.py b/dingo/core/utils/pt_to_hdf5.py index 4c24c69df..f88154f91 100644 --- a/dingo/core/utils/pt_to_hdf5.py +++ b/dingo/core/utils/pt_to_hdf5.py @@ -1,16 +1,16 @@ import os import argparse import logging -import sys import torch import h5py import json +from dingo.core.utils.logging_utils import setup_logger -log = logging.getLogger(__name__) -logging.captureWarnings(True) +logger = logging.getLogger(__name__) +logging.captureWarnings(True) def parse_args(): @@ -44,17 +44,8 @@ def parse_args(): return parser.parse_args() -def configure_logging(): - logging.basicConfig( - level=logging.INFO, - format="[%(asctime)s][%(name)s][%(levelname)s] - %(message)s", - stream=sys.stdout, - force=True, - ) - - def main(): - configure_logging() + setup_logger(use_bilby=False) args = parse_args() if os.path.splitext(args.in_file)[-1] != ".pt": @@ -66,7 +57,7 @@ def main(): # This is required for use on CVMFS root, ext = os.path.splitext(args.out_file) out_file_name = f"{root}_v{args.model_version_number}{ext}" - log.info(f"Output will be written to {out_file_name}") + logger.info(f"Output will be written to {out_file_name}") # Load data into CPU memory since we'll be saving it using CPU libraries d = torch.load(args.in_file, map_location=torch.device("cpu")) diff --git a/dingo/core/utils/torchutils.py b/dingo/core/utils/torchutils.py index 2d109d7bb..69488a2a1 100644 --- a/dingo/core/utils/torchutils.py +++ b/dingo/core/utils/torchutils.py @@ -5,6 +5,7 @@ from torch.utils.data import DataLoader from typing import Union, Tuple, Iterable import bilby +from hydra.utils import instantiate def fix_random_seeds(_): @@ -84,20 +85,10 @@ def get_optimizer_from_kwargs( ------- optimizer """ - optimizers_dict = { - "adagrad": torch.optim.Adagrad, - "adam": torch.optim.Adam, - "adamw": torch.optim.AdamW, - "lbfgs": torch.optim.LBFGS, - "RMSprop": torch.optim.RMSprop, - "sgd": torch.optim.SGD, - } - if not "type" in optimizer_kwargs: - raise KeyError("Optimizer type needs to be specified.") - if not optimizer_kwargs["type"].lower() in optimizers_dict: - raise ValueError("No valid optimizer specified.") - optimizer = optimizers_dict[optimizer_kwargs.pop("type")] - return optimizer(model_parameters, **optimizer_kwargs) + optimizer = instantiate(optimizer_kwargs) + if optimizer_kwargs.get("_partial_", False): + return optimizer(model_parameters) + return optimizer def get_scheduler_from_kwargs( @@ -122,17 +113,10 @@ def get_scheduler_from_kwargs( ------- scheduler """ - schedulers_dict = { - "step": torch.optim.lr_scheduler.StepLR, - "cosine": torch.optim.lr_scheduler.CosineAnnealingLR, - "reduce_on_plateau": torch.optim.lr_scheduler.ReduceLROnPlateau, - } - if not "type" in scheduler_kwargs: - raise KeyError("Scheduler type needs to be specified.") - if not scheduler_kwargs["type"].lower() in schedulers_dict: - raise ValueError("No valid scheduler specified.") - scheduler = schedulers_dict[scheduler_kwargs.pop("type")] - return scheduler(optimizer, **scheduler_kwargs) + scheduler = instantiate(scheduler_kwargs) + if scheduler_kwargs.get("_partial_", False): + return scheduler(optimizer) + return scheduler def perform_scheduler_step( diff --git a/dingo/core/utils/trainutils.py b/dingo/core/utils/trainutils.py index a420e8d82..4d58eb2d8 100644 --- a/dingo/core/utils/trainutils.py +++ b/dingo/core/utils/trainutils.py @@ -6,7 +6,7 @@ import csv from typing import Literal -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) class AvgTracker: @@ -88,7 +88,7 @@ def __call__(self, val_loss: float): elif score < self.best_score + self.delta: self.counter += 1 if self.verbose: - log.info( + logger.info( f"EarlyStopping counter: {self.counter} out of {self.patience}" ) if self.counter >= self.patience: @@ -131,7 +131,7 @@ def print_info(self, batch_idx): if batch_idx % self.print_freq == 0: td, td_avg = self.times["Dataloader"].x, self.times["Dataloader"].get_avg() tn, tn_avg = self.times["Network"].x, self.times["Network"].get_avg() - log.info( + logger.info( "{} Epoch: {} [{}/{} ({:.0f}%)]\t\t" "Loss: {:.3f} ({:.3f})\t\t" "Time Dataloader: {:.3f} ({:.3f})\t\t" @@ -203,7 +203,7 @@ def limits_exceeded(self, epoch: int = None): # check time limit for run if self.max_time_per_run is not None: if time.time() - self.time_start >= self.max_time_per_run: - log.info( + logger.info( f"Stop run: Time limit of {self.max_time_per_run} s " f"exceeded." ) return True @@ -212,14 +212,14 @@ def limits_exceeded(self, epoch: int = None): if epoch is None: raise ValueError("epoch required") if epoch - self.epoch_start >= self.max_epochs_per_run: - log.info( + logger.info( f"Stop run: Epoch limit of {self.max_epochs_per_run} per run reached." ) return True # check total epoch limit if self.max_epochs_total is not None: if epoch >= self.max_epochs_total: - log.info( + logger.info( f"Stop run: Total epoch limit of {self.max_epochs_total} reached." ) return True @@ -324,13 +324,13 @@ def save_model(pm, log_dir, model_prefix="model", checkpoint_epochs=None): """ # save current model model_name = join(log_dir, f"{model_prefix}_latest.pt") - log.info(f"Saving model to {model_name}.") + logger.info(f"Saving model to {model_name}.") pm.save_model(model_name, save_training_info=True) - log.info("Done.") + logger.info("Done.") # potentially copy model to a checkpoint if checkpoint_epochs is not None and pm.epoch % checkpoint_epochs == 0: model_name_cp = join(log_dir, f"{model_prefix}_{pm.epoch:03d}.pt") - log.info(f"Copy model to checkpoint {model_name_cp}.") + logger.info(f"Copy model to checkpoint {model_name_cp}.") copyfile(model_name, model_name_cp) - log.info("Done.") + logger.info("Done.") diff --git a/dingo/gw/SVD.py b/dingo/gw/SVD.py index 2cad5b40d..eaf003c1f 100644 --- a/dingo/gw/SVD.py +++ b/dingo/gw/SVD.py @@ -6,7 +6,7 @@ from sklearn.utils.extmath import randomized_svd from dingo.core.dataset import DingoDataset -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) class SVDBasis(DingoDataset): @@ -160,15 +160,15 @@ def print_validation_summary(self): if "mismatch" in col: n = int(col.split(sep="=")[-1]) mismatches = self.mismatches[col] - log.info(f"n = {n}") - log.info(" Mean mismatch = {}".format(np.mean(mismatches))) - log.info(" Standard deviation = {}".format(np.std(mismatches))) - log.info(" Max mismatch = {}".format(np.max(mismatches))) - log.info(" Median mismatch = {}".format(np.median(mismatches))) - log.info(" Percentiles:") - log.info(" 99 -> {}".format(np.percentile(mismatches, 99))) - log.info(" 99.9 -> {}".format(np.percentile(mismatches, 99.9))) - log.info( + logger.info(f"n = {n}") + logger.info(" Mean mismatch = {}".format(np.mean(mismatches))) + logger.info(" Standard deviation = {}".format(np.std(mismatches))) + logger.info(" Max mismatch = {}".format(np.max(mismatches))) + logger.info(" Median mismatch = {}".format(np.median(mismatches))) + logger.info(" Percentiles:") + logger.info(" 99 -> {}".format(np.percentile(mismatches, 99))) + logger.info(" 99.9 -> {}".format(np.percentile(mismatches, 99.9))) + logger.info( " 99.99 -> {}".format(np.percentile(mismatches, 99.99)) ) diff --git a/dingo/gw/conversion/spin_conversion.py b/dingo/gw/conversion/spin_conversion.py index f2a40b45b..2549d475f 100644 --- a/dingo/gw/conversion/spin_conversion.py +++ b/dingo/gw/conversion/spin_conversion.py @@ -1,5 +1,6 @@ # Transformations between PE spins and Cartesian spins from functools import partial +import logging from multiprocessing import Pool import numpy as np @@ -8,7 +9,7 @@ import lalsimulation as LS from threadpoolctl import threadpool_limits -from dingo.core.utils.logging_utils import logger +logger = logging.getLogger(__name__) DINGO_PE_SPIN_PARAMETERS = ( diff --git a/dingo/gw/data/data_download.py b/dingo/gw/data/data_download.py index c0e2f6ed1..4b0b4d2ed 100644 --- a/dingo/gw/data/data_download.py +++ b/dingo/gw/data/data_download.py @@ -1,11 +1,14 @@ +import logging + import numpy as np from gwpy.timeseries import TimeSeries import pycbc.psd import math -from dingo.core.utils.logging_utils import logger from dingo.gw.gwutils import get_window +logger = logging.getLogger(__name__) + def download_psd(det, time_start, time_psd, window, f_s): """ diff --git a/dingo/gw/data/data_preparation.py b/dingo/gw/data/data_preparation.py index 4f355b8b4..729b57881 100644 --- a/dingo/gw/data/data_preparation.py +++ b/dingo/gw/data/data_preparation.py @@ -1,16 +1,18 @@ from os.path import isfile +import logging import numpy as np from gwpy.timeseries import TimeSeries from dingo.core.dataset import DingoDataset -from dingo.core.utils.logging_utils import logger from dingo.core.utils.misc import recursive_check_dicts_are_equal from dingo.gw.data.data_download import download_raw_data from dingo.gw.gwutils import get_window from dingo.gw.domains import UniformFrequencyDomain from dingo.gw.domains import build_domain_from_model_metadata +logger = logging.getLogger(__name__) + def load_raw_data(time_event, settings, event_dataset=None): """ diff --git a/dingo/gw/dataset/compression.py b/dingo/gw/dataset/compression.py new file mode 100644 index 000000000..f45202c8b --- /dev/null +++ b/dingo/gw/dataset/compression.py @@ -0,0 +1,94 @@ +import copy +import logging +from functools import partial +from typing import Dict + +import numpy as np +import pandas as pd +from bilby.gw.prior import BBHPriorDict +from torchvision.transforms import Compose + +from dingo.core.utils.misc import call_func_strict_output_dim +from dingo.gw.SVD import SVDBasis +from dingo.gw.dataset.waveform_dataset import WaveformDataset +from dingo.gw.waveform_generator import WaveformGenerator + +logger = logging.getLogger(__name__) + + +def train_svd_basis(dataset: WaveformDataset, size: int, n_train: int) -> SVDBasis: + """ + Train (and optionally validate) an SVD basis. + """ + train_data = np.vstack([val[:n_train] for val in dataset.polarizations.values()]) + test_data = np.vstack([val[n_train:] for val in dataset.polarizations.values()]) + test_parameters = pd.concat( + [ + # I would like to save the polarization, but saving the dataframe with + # string columns causes problems. Fix this later. + # dataset.parameters.iloc[n_train:].assign(polarization=pol) + dataset.parameters.iloc[n_train:] + for pol in dataset.polarizations + ] + ) + test_parameters.reset_index(drop=True, inplace=True) + + logger.info("Building SVD basis.") + basis = SVDBasis() + basis.generate_basis(train_data, size) + + assert np.allclose(basis.V[: dataset.domain.min_idx], 0) + + if test_data.size != 0: + basis.compute_test_mismatches( + test_data, parameters=test_parameters, verbose=True + ) + + return basis + + +def load_svd_basis(file_name: str) -> SVDBasis: + """Load an existing SVD basis for Hydra-configured compression.""" + return SVDBasis(file_name=file_name) + + +def train_svd_basis_from_waveforms( + size: int, + num_training_samples: int, + num_validation_samples: int = 0, + *, + waveform_generator: WaveformGenerator, + prior: BBHPriorDict, + num_processes: int, + settings: Dict, + compression_transforms: list, + compression_settings: list, +) -> SVDBasis: + """Train an SVD basis from waveforms generated with preceding compression steps.""" + from dingo.gw.dataset.generate_dataset import generate_parameters_and_polarizations + + waveform_generator.transform = Compose(compression_transforms) + + num_samples = num_training_samples + num_validation_samples + func = partial( + generate_parameters_and_polarizations, + waveform_generator, + prior, + num_processes=num_processes, + ) + parameters, polarizations = call_func_strict_output_dim(func, num_samples) + + svd_dataset_settings = copy.deepcopy(settings) + svd_dataset_settings["num_samples"] = len(parameters) + svd_dataset_settings["compression"] = copy.deepcopy(compression_settings) or None + + # We build a WaveformDataset containing the SVD-training waveforms because when + # constructed, it automatically zeroes waveforms below f_min. + svd_dataset = WaveformDataset( + dictionary={ + "parameters": parameters, + "polarizations": polarizations, + "settings": svd_dataset_settings, + } + ) + return train_svd_basis(svd_dataset, size, num_training_samples) diff --git a/dingo/gw/dataset/evaluate_multibanded_domain.py b/dingo/gw/dataset/evaluate_multibanded_domain.py index 07a27d635..10bf7a44d 100644 --- a/dingo/gw/dataset/evaluate_multibanded_domain.py +++ b/dingo/gw/dataset/evaluate_multibanded_domain.py @@ -3,20 +3,16 @@ import hydra import numpy as np +from hydra.utils import instantiate from omegaconf import DictConfig, OmegaConf from scipy.interpolate import interp1d from dingo.gw.dataset import generate_parameters_and_polarizations -from dingo.gw.domains import build_domain, MultibandedFrequencyDomain +from dingo.gw.domains import MultibandedFrequencyDomain from dingo.gw.gwutils import get_mismatch -from dingo.gw.prior import build_prior_with_defaults -from dingo.gw.waveform_generator import ( - NewInterfaceWaveformGenerator, - WaveformGenerator, - generate_waveforms_parallel, -) +from dingo.gw.waveform_generator import generate_waveforms_parallel -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) logging.captureWarnings(True) @@ -38,40 +34,28 @@ def evaluate_multibanding( # # (a) Set geocent_time = 0.12 s (boundary of usual prior + Earth-radius crossing time) # (b) Set chirp mass to bottom end of prior. - prior = build_prior_with_defaults(settings["intrinsic_prior"]) - settings["intrinsic_prior"]["geocent_time"] = 0.12 - settings["intrinsic_prior"]["chirp_mass"] = prior["chirp_mass"].minimum + prior = instantiate(settings["intrinsic_prior"]) + settings["intrinsic_prior"]["dictionary"]["geocent_time"] = 0.12 + settings["intrinsic_prior"]["dictionary"]["chirp_mass"] = prior[ + "chirp_mass" + ].minimum # Rebuild prior with updated settings. - prior = build_prior_with_defaults(settings["intrinsic_prior"]) - log.info("Prior") + prior = instantiate(settings["intrinsic_prior"]) + logger.info("Prior") for k, v in prior.items(): - log.info(f"{k}: {v}") + logger.info(f"{k}: {v}") - domain = build_domain(settings["domain"]) - log.info("\nDomain") - log.info(domain.domain_dict) + domain = instantiate(settings["domain"]) + logger.info("\nDomain") + logger.info(domain.domain_dict) if not isinstance(domain, MultibandedFrequencyDomain): raise ValueError("Waveform dataset domain not a MultibandedFrequencyDomain.") - if settings["waveform_generator"].get("new_interface", False): - waveform_generator_mfd = NewInterfaceWaveformGenerator( - domain=domain, - **settings["waveform_generator"], - ) - waveform_generator_ufd = NewInterfaceWaveformGenerator( - domain=domain.base_domain, - **settings["waveform_generator"], - ) - else: - waveform_generator_mfd = WaveformGenerator( - domain=domain, - **settings["waveform_generator"], - ) - waveform_generator_ufd = WaveformGenerator( - domain=domain.base_domain, - **settings["waveform_generator"], - ) + waveform_generator_mfd = instantiate(settings["waveform_generator"], domain=domain) + waveform_generator_ufd = instantiate( + settings["waveform_generator"], domain=domain.base_domain + ) # Generate MFD waveforms. parameters, polarizations_mfd = generate_parameters_and_polarizations( @@ -96,23 +80,23 @@ def evaluate_multibanding( asd_file="aLIGO_ZERO_DET_high_P_asd.txt", ) - log.info( + logger.info( "\nMismatches between UFD waveforms and MFD waveforms interpolated to MFD." ) - log.info( + logger.info( "This is a conservative estimate of the MFD performance when training " "networks." ) mismatches = np.concatenate([v for v in mismatches.values()]) - log.info(f"num_samples = {num_samples}") - log.info(" Mean mismatch = {}".format(np.mean(mismatches))) - log.info(" Standard deviation = {}".format(np.std(mismatches))) - log.info(" Max mismatch = {}".format(np.max(mismatches))) - log.info(" Median mismatch = {}".format(np.median(mismatches))) - log.info(" Percentiles:") - log.info(" 99 -> {}".format(np.percentile(mismatches, 99))) - log.info(" 99.9 -> {}".format(np.percentile(mismatches, 99.9))) - log.info(" 99.99 -> {}".format(np.percentile(mismatches, 99.99))) + logger.info(f"num_samples = {num_samples}") + logger.info(" Mean mismatch = {}".format(np.mean(mismatches))) + logger.info(" Standard deviation = {}".format(np.std(mismatches))) + logger.info(" Max mismatch = {}".format(np.max(mismatches))) + logger.info(" Median mismatch = {}".format(np.median(mismatches))) + logger.info(" Percentiles:") + logger.info(" 99 -> {}".format(np.percentile(mismatches, 99))) + logger.info(" 99.9 -> {}".format(np.percentile(mismatches, 99.9))) + logger.info(" 99.99 -> {}".format(np.percentile(mismatches, 99.99))) @hydra.main( diff --git a/dingo/gw/dataset/generate_dataset.py b/dingo/gw/dataset/generate_dataset.py index 39c525d69..c4ac0f386 100644 --- a/dingo/gw/dataset/generate_dataset.py +++ b/dingo/gw/dataset/generate_dataset.py @@ -9,23 +9,21 @@ import numpy as np import pandas as pd from bilby.gw.prior import BBHPriorDict +from hydra.utils import instantiate from omegaconf import DictConfig, OmegaConf from threadpoolctl import threadpool_limits from torchvision.transforms import Compose +from dingo.core.utils.hydra_utils import instantiate_with_runtime_dependencies from dingo.gw.dataset.waveform_dataset import WaveformDataset -from dingo.gw.domains import build_domain -from dingo.gw.prior import build_prior_with_defaults -from dingo.gw.SVD import ApplySVD, SVDBasis -from dingo.gw.transforms import WhitenFixedASD +from dingo.gw.SVD import ApplySVD from dingo.gw.waveform_generator import ( - NewInterfaceWaveformGenerator, WaveformGenerator, generate_waveforms_parallel, ) from dingo.core.utils.misc import call_func_strict_output_dim -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) logging.captureWarnings(True) @@ -50,7 +48,7 @@ def generate_parameters_and_polarizations( pandas DataFrame of parameters dictionary of numpy arrays corresponding to waveform polarizations """ - log.info("Generating dataset of size " + str(num_samples)) + logger.info("Generating dataset of size " + str(num_samples)) parameters = pd.DataFrame(prior.sample(num_samples)) if num_processes > 1: @@ -70,12 +68,12 @@ def generate_parameters_and_polarizations( polarizations_ok = {k: v[idx_ok] for k, v in polarizations.items()} parameters_ok = parameters.iloc[idx_ok] failed_percent = 100 * len(idx_failed) / len(parameters) - log.warning( + logger.warning( f"{len(idx_failed)} out of {len(parameters)} configuration ({failed_percent:.1f}%) failed to generate." ) with pd.option_context("display.max_rows", None, "display.max_columns", None): - log.warning(parameters.iloc[idx_failed].to_string()) - log.warning( + logger.warning(parameters.iloc[idx_failed].to_string()) + logger.warning( f"Only returning the {len(idx_ok)} successfully generated configurations." ) return parameters_ok, polarizations_ok @@ -83,63 +81,20 @@ def generate_parameters_and_polarizations( return parameters, polarizations -def train_svd_basis(dataset: WaveformDataset, size: int, n_train: int): - """ - Train (and optionally validate) an SVD basis. - - Parameters - ---------- - dataset : WaveformDataset - Contains waveforms to be used for building SVD. - size : int - Number of elements to keep for the SVD basis. - n_train : int - Number of training waveforms to use. Remaining are used for validation. Note - that the actual number of training waveforms is n_train * len(polarizations), - since there is one waveform used for each polarization. - - Returns - ------- - SVDBasis, n_train, n_test - Since EOB waveforms can fail to generate, provide also the number used in - training and validation. - """ - # Prepare data for training and validation. - train_data = np.vstack([val[:n_train] for val in dataset.polarizations.values()]) - test_data = np.vstack([val[n_train:] for val in dataset.polarizations.values()]) - test_parameters = pd.concat( - [ - # I would like to save the polarization, but saving the dataframe with - # string columns causes problems. Fix this later. - # dataset.parameters.iloc[n_train:].assign(polarization=pol) - dataset.parameters.iloc[n_train:] - for pol in dataset.polarizations - ] - ) - test_parameters.reset_index(drop=True, inplace=True) - - log.info("Building SVD basis.") - basis = SVDBasis() - basis.generate_basis(train_data, size) - - assert np.allclose(basis.V[: dataset.domain.min_idx], 0) - - # Since there is a possibility that the size of the dataset returned by - # generate_parameters_and_polarizations is smaller than requested, we don't assume - # that there are n_test samples. Instead we just look at the size of the test - # dataset. - if test_data.size != 0: - basis.compute_test_mismatches( - test_data, parameters=test_parameters, verbose=True +def _instantiate_compression_transform(transform_config: Dict, runtime_dependencies: Dict): + transform_config = copy.deepcopy(transform_config) + if "svd_basis" not in transform_config: + return instantiate_with_runtime_dependencies( + transform_config, + runtime_dependencies, ) - # Return also the true number of samples. Some EOB waveforms may have failed to - # generate, so this could be smaller than the number requested. - n_ifos = len(dataset.polarizations) - n_train = len(train_data) // n_ifos - n_test = len(test_data) // n_ifos - - return basis, n_train, n_test + svd_basis_config = transform_config.pop("svd_basis") + svd_basis = instantiate_with_runtime_dependencies( + svd_basis_config, + runtime_dependencies, + ) + return instantiate(transform_config, svd_basis=svd_basis) def _settings_from_config(cfg: DictConfig) -> Dict: @@ -164,83 +119,35 @@ def generate_dataset(settings: Dict, num_processes: int) -> WaveformDataset: A WaveformDataset based on the settings. """ - prior = build_prior_with_defaults(settings["intrinsic_prior"]) - domain = build_domain(settings["domain"]) - - new_interface_flag = settings["waveform_generator"].get("new_interface", False) - if new_interface_flag: - waveform_generator = NewInterfaceWaveformGenerator( - domain=domain, - **settings["waveform_generator"], - ) - else: - waveform_generator = WaveformGenerator( - domain=domain, - **settings["waveform_generator"], - ) + prior = instantiate(settings["intrinsic_prior"]) + domain = instantiate(settings["domain"]) + waveform_generator = instantiate(settings["waveform_generator"], domain=domain) dataset_dict = {"settings": settings} if settings.get("compression", None) is not None: compression_transforms = [] - - if "whitening" in settings["compression"]: - compression_transforms.append( - WhitenFixedASD( - domain, - asd_file=settings["compression"]["whitening"], - inverse=False, - ) + compression_settings = [] + runtime_dependencies = { + "domain": domain, + "waveform_generator": waveform_generator, + "prior": prior, + "num_processes": num_processes, + "settings": settings, + "compression_transforms": compression_transforms, + "compression_settings": compression_settings, + } + + for transform_config in settings["compression"]: + compression_transform = _instantiate_compression_transform( + transform_config, + runtime_dependencies, ) + compression_transforms.append(compression_transform) + compression_settings.append(copy.deepcopy(transform_config)) - if "svd" in settings["compression"]: - svd_settings = settings["compression"]["svd"] - - # Load an SVD basis from file, if specified. - if "file" in svd_settings: - basis = SVDBasis(file_name=svd_settings["file"]) - - # Otherwise, generate the basis based on simulated waveforms. - else: - # If using whitened waveforms, then the SVD should be based on these. - waveform_generator.transform = Compose(compression_transforms) - - n_train = svd_settings["num_training_samples"] - n_test = svd_settings.get("num_validation_samples", 0) - - func = partial( - generate_parameters_and_polarizations, - waveform_generator, - prior, - num_processes=num_processes, - ) - parameters, polarizations = call_func_strict_output_dim( - func, n_train + n_test - ) - svd_dataset_settings = copy.deepcopy(settings) - svd_dataset_settings["num_samples"] = len(parameters) - del svd_dataset_settings["compression"]["svd"] - - # We build a WaveformDataset containing the SVD-training waveforms - # because when constructed, it will automatically zero the waveforms - # below f_min. This is useful for EOB waveforms, which are Fourier - # transformed from time domain, and hence are nonzero below f_min. The - # waveforms need to be zeroed below f_min because this corresponds to - # setting the lower bound of the likelihood integral. - - svd_dataset = WaveformDataset( - dictionary={ - "parameters": parameters, - "polarizations": polarizations, - "settings": svd_dataset_settings, - } - ) - basis, n_train, n_test = train_svd_basis( - svd_dataset, svd_settings["size"], n_train - ) - - compression_transforms.append(ApplySVD(basis)) - dataset_dict["svd"] = basis.to_dictionary() + if isinstance(compression_transform, ApplySVD): + dataset_dict["svd"] = compression_transform.svd_basis.to_dictionary() waveform_generator.transform = Compose(compression_transforms) diff --git a/dingo/gw/dataset/generate_dataset_dag.py b/dingo/gw/dataset/generate_dataset_dag.py index 718149f72..d6ab967ba 100644 --- a/dingo/gw/dataset/generate_dataset_dag.py +++ b/dingo/gw/dataset/generate_dataset_dag.py @@ -1,11 +1,14 @@ import os import argparse +import logging from typing import Dict from pycondor import Job, Dagman import yaml import copy -from dingo.core.utils.logging_utils import logger, setup_logger +from dingo.core.utils.logging_utils import setup_logger + +logger = logging.getLogger(__name__) # Fixed file names svd_fn = "svd.hdf5" diff --git a/dingo/gw/dataset/utils.py b/dingo/gw/dataset/utils.py index d0b8e8cc9..73440e153 100644 --- a/dingo/gw/dataset/utils.py +++ b/dingo/gw/dataset/utils.py @@ -1,7 +1,6 @@ import copy import logging import argparse -import sys import textwrap from typing import List @@ -9,23 +8,14 @@ import numpy as np import yaml -from dingo.gw.SVD import SVDBasis -from dingo.gw.dataset.generate_dataset import train_svd_basis +from dingo.core.utils.logging_utils import setup_logger +from dingo.gw.dataset.compression import train_svd_basis from dingo.gw.dataset.waveform_dataset import WaveformDataset -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) logging.captureWarnings(True) -def configure_logging(): - logging.basicConfig( - level=logging.INFO, - format="[%(asctime)s][%(name)s][%(levelname)s] - %(message)s", - stream=sys.stdout, - force=True, - ) - - def merge_datasets(dataset_list: List[WaveformDataset]) -> WaveformDataset: """ Merge a collection of datasets into one. @@ -41,7 +31,7 @@ def merge_datasets(dataset_list: List[WaveformDataset]) -> WaveformDataset: WaveformDataset containing the merged data. """ - log.info(f"Merging {len(dataset_list)} datasets into one.") + logger.info(f"Merging {len(dataset_list)} datasets into one.") # This ensures that all the keys are copied into the new dataset. The "extensive" # parts of the dataset (parameters, waveforms) will be overwritten by the combined @@ -74,7 +64,6 @@ def merge_datasets_cli(): parallelized waveform generation. """ - configure_logging() parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent( @@ -103,8 +92,7 @@ def merge_datasets_cli(): "--settings_file", type=str, help="YAML file containing new dataset settings." ) args = parser.parse_args() - out_path = Path(args.out_file) - setup_logger(outdir=str(out_path.parent), label=out_path.stem) + setup_logger(use_bilby=False) dataset_list = [] for i in range(args.num_parts): @@ -121,7 +109,7 @@ def merge_datasets_cli(): merged_dataset.settings = settings merged_dataset.to_file(args.out_file) - log.info( + logger.info( f"Complete. New dataset consists of {merged_dataset.settings['num_samples']} " f"samples." ) @@ -132,7 +120,6 @@ def build_svd_cli(): Command-line function to build an SVD based on an uncompressed dataset file. """ - configure_logging() parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent( @@ -162,8 +149,7 @@ def build_svd_cli(): ), ) args = parser.parse_args() - out_path = Path(args.out_file) - setup_logger(outdir=str(out_path.parent), label=out_path.stem) + setup_logger(use_bilby=False) dataset = WaveformDataset(file_name=args.dataset_file) if args.num_train is None: @@ -174,7 +160,7 @@ def build_svd_cli(): basis, n_train, n_test = train_svd_basis(dataset, args.size, n_train) # FIXME: This is not an ideal treatment. We should update the waveform generation # to always provide the requested number of waveforms. - log.info( + logger.info( f"SVD basis trained based on {n_train} waveforms and validated on {n_test} " f"waveforms. Note that if this differs from number requested, it will not be " f"reflected in the settings file. This is likely due to EOB failure to " diff --git a/dingo/gw/dataset/waveform_dataset.py b/dingo/gw/dataset/waveform_dataset.py index 43d298c5e..35c996d00 100644 --- a/dingo/gw/dataset/waveform_dataset.py +++ b/dingo/gw/dataset/waveform_dataset.py @@ -3,12 +3,12 @@ import numpy as np import torch.utils.data from hydra.utils import instantiate +from omegaconf import OmegaConf from torchvision.transforms import Compose from dingo.core.dataset import DingoDataset, recursive_hdf5_load from dingo.gw.SVD import SVDBasis, ApplySVD from dingo.gw.domains import build_domain -from dingo.gw.transforms import WhitenFixedASD class WaveformDataset(DingoDataset, torch.utils.data.Dataset): @@ -155,15 +155,36 @@ def update_domain(self, domain_update: Optional[dict] = None): # Determine where any domain adjustment must be applied. If the dataset is SVD # compressed, then adjust the SVD matrices. Otherwise, adjust the dataset # itself (if it already has been loaded). - if ( - self.settings.get("compression", None) is not None - and "svd" in self.settings["compression"] - ): + if self._compression_has_svd(): self.svd["V"] = self.domain.update_data(self.svd["V"], axis=0) elif self.polarizations is not None: for k, v in self.polarizations.items(): self.polarizations[k] = self.domain.update_data(v) + def _compression_entries(self): + compression = self.settings.get("compression", None) + if compression is None: + return [] + if isinstance(compression, dict) or OmegaConf.is_dict(compression): + entries = [] + if "whitening" in compression: + entries.append( + { + "_target_": "dingo.gw.transforms.WhitenFixedASD", + "asd_file": compression["whitening"], + } + ) + if "svd" in compression: + entries.append({"_target_": "dingo.gw.SVD.ApplySVD"}) + return entries + return list(compression) + + def _compression_has_svd(self): + return any( + entry.get("_target_", "").endswith("ApplySVD") + for entry in self._compression_entries() + ) + def initialize_decompression(self, svd_size_update: Optional[int] = None): """ Sets up decompression transforms. These are applied to the raw dataset before @@ -179,37 +200,47 @@ def initialize_decompression(self, svd_size_update: Optional[int] = None): # These transforms must be in reverse order compared to when dataset was # constructed. - if "svd" in self.settings["compression"]: - assert self.svd is not None - - # We allow the option to reduce the size of the SVD used for decompression, - # since decompression is the costliest preprocessing operation. Be careful - # when using this to not introduce a large mismatch. - if svd_size_update is not None: - if svd_size_update > self.svd["V"].shape[-1] or svd_size_update < 0: - raise ValueError( - f"Cannot truncate SVD from size " - f"{self.svd['V'].shape[-1]} to size " - f"{svd_size_update}." + for compression_entry in reversed(self._compression_entries()): + target = compression_entry.get("_target_", "") + + if target.endswith("ApplySVD"): + assert self.svd is not None + + # We allow the option to reduce the size of the SVD used for + # decompression, since decompression is the costliest preprocessing + # operation. Be careful when using this to not introduce a large + # mismatch. + if svd_size_update is not None: + if svd_size_update > self.svd["V"].shape[-1] or svd_size_update < 0: + raise ValueError( + f"Cannot truncate SVD from size " + f"{self.svd['V'].shape[-1]} to size " + f"{svd_size_update}." + ) + self.svd["V"] = self.svd["V"][:, :svd_size_update] + self.svd["s"] = self.svd["s"][:svd_size_update] + if self.polarizations is not None: + for k, v in self.polarizations.items(): + self.polarizations[k] = v[:, :svd_size_update] + + svd_basis = SVDBasis(dictionary=self.svd) + decompression_transform_list.append(ApplySVD(svd_basis, inverse=True)) + + elif target.endswith("WhitenFixedASD"): + transform_config = OmegaConf.to_container( + OmegaConf.create(compression_entry), + resolve=True, + ) + transform_config.pop("_partial_", None) + transform_config.pop("_runtime_dependencies_", None) + decompression_transform_list.append( + instantiate( + transform_config, + domain=self.domain, + inverse=True, + precision=self.precision, ) - self.svd["V"] = self.svd["V"][:, :svd_size_update] - self.svd["s"] = self.svd["s"][:svd_size_update] - if self.polarizations is not None: - for k, v in self.polarizations.items(): - self.polarizations[k] = v[:, :svd_size_update] - - svd_basis = SVDBasis(dictionary=self.svd) - decompression_transform_list.append(ApplySVD(svd_basis, inverse=True)) - - if "whitening" in self.settings["compression"]: - decompression_transform_list.append( - WhitenFixedASD( - self.domain, - asd_file=self.settings["compression"]["whitening"], - inverse=True, - precision=self.precision, ) - ) self.decompression_transform = Compose(decompression_transform_list) diff --git a/dingo/gw/domains/build_domain.py b/dingo/gw/domains/build_domain.py index b0820d87d..63bd4c40a 100644 --- a/dingo/gw/domains/build_domain.py +++ b/dingo/gw/domains/build_domain.py @@ -4,6 +4,7 @@ MultibandedFrequencyDomain, TimeDomain, ) +from hydra.utils import instantiate def build_domain(settings: dict) -> Domain: @@ -20,10 +21,13 @@ def build_domain(settings: dict) -> Domain: ------- A Domain instance of the correct type. """ + if "_target_" in settings: + return instantiate(settings) + if "type" not in settings: raise ValueError( - f'Domain settings must include a "type" key. Settings included ' - f"the keys {settings.keys()}." + 'Domain settings must include a "_target_" or "type" key. ' + f"Settings included the keys {settings.keys()}." ) # The settings other than 'type' correspond to the kwargs of the Domain constructor. @@ -60,8 +64,13 @@ def build_domain_from_model_metadata( A Domain instance of the correct type. """ domain = build_domain(model_metadata["dataset_settings"]["domain"]) - if "domain_update" in model_metadata["train_settings"]["data"]: - domain.update(model_metadata["train_settings"]["data"]["domain_update"]) + data_settings = model_metadata["train_settings"]["data"] + domain_update = data_settings.get("waveform_dataset", {}).get( + "domain_update", + data_settings.get("domain_update"), + ) + if domain_update is not None: + domain.update(domain_update) if base and hasattr(domain, "base_domain"): domain = domain.base_domain return domain diff --git a/dingo/gw/download_strain_data.py b/dingo/gw/download_strain_data.py index 87aa55693..a76a7b528 100644 --- a/dingo/gw/download_strain_data.py +++ b/dingo/gw/download_strain_data.py @@ -1,13 +1,16 @@ +import logging + import numpy as np import pycbc.psd from gwpy.timeseries import TimeSeries -from dingo.core.utils.logging_utils import logger from dingo.gw.domains import UniformFrequencyDomain from dingo.gw.gwutils import ( get_window, ) +logger = logging.getLogger(__name__) + def estimate_single_psd( time_start, diff --git a/dingo/gw/gwutils.py b/dingo/gw/gwutils.py index f2b0730c0..cce4eca8d 100644 --- a/dingo/gw/gwutils.py +++ b/dingo/gw/gwutils.py @@ -6,7 +6,6 @@ from bilby.gw.detector import PowerSpectralDensity -from dingo.gw.prior import default_extrinsic_dict from dingo.gw.prior import BBHExtrinsicPriorDict @@ -26,18 +25,6 @@ def get_window(window_kwargs): raise NotImplementedError(f"Unknown window type {type}.") -def get_extrinsic_prior_dict(extrinsic_prior): - """Build dict for extrinsic prior by starting with - default_extrinsic_dict, and overwriting every element for which - extrinsic_prior is not default. - TODO: Move to dingo.gw.prior.py?""" - extrinsic_prior_dict = default_extrinsic_dict.copy() - for k, v in extrinsic_prior.items(): - if v.lower() != "default": - extrinsic_prior_dict[k] = v - return extrinsic_prior_dict - - def get_mismatch(a, b, domain, asd_file=None): """ Mistmatch is 1 - overlap, where overlap is defined by @@ -100,7 +87,10 @@ def get_standardization_dict( # Some of the extrinsic prior parameters have analytic means and standard # deviations. If possible, this will either get these, or else it will estimate # them numerically. - ext_prior = BBHExtrinsicPriorDict(extrinsic_prior_dict) + if isinstance(extrinsic_prior_dict, BBHExtrinsicPriorDict): + ext_prior = extrinsic_prior_dict + else: + ext_prior = BBHExtrinsicPriorDict(extrinsic_prior_dict) mean_extrinsic, std_extrinsic = ext_prior.mean_std(ext_prior.keys()) # Check that overlap between intrinsic and extrinsic parameters is only diff --git a/dingo/gw/importance_sampling/diagnostics.py b/dingo/gw/importance_sampling/diagnostics.py index 1733dbcbf..ffa0b489c 100644 --- a/dingo/gw/importance_sampling/diagnostics.py +++ b/dingo/gw/importance_sampling/diagnostics.py @@ -7,7 +7,7 @@ from dingo.core.utils.plotting import plot_corner_multi from dingo.gw.result import Result -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) def plot_posterior_slice2d( @@ -222,7 +222,7 @@ def plot_diagnostics( inds = np.where(weights > threshold)[0] theta_new = theta.loc[inds] weights_new = weights[inds] - log.info( + logger.info( f"Generating cornerplot with {len(theta_new)} out of {len(theta)} IS samples." ) diff --git a/dingo/gw/importance_sampling/importance_weights.py b/dingo/gw/importance_sampling/importance_weights.py index 94e922868..83ddcb9d0 100644 --- a/dingo/gw/importance_sampling/importance_weights.py +++ b/dingo/gw/importance_sampling/importance_weights.py @@ -18,7 +18,7 @@ from dingo.gw.inference.gw_samplers import GWSampler from dingo.gw.importance_sampling.diagnostics import plot_diagnostics -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) logging.captureWarnings(True) @@ -108,20 +108,20 @@ def main(cfg: DictConfig): event_name = str(result.event_metadata["time_event"]) nde_name = settings["nde"].get("path") or join(outdir, f"nde-{event_name}.pt") if isfile(nde_name): - log.info(f"Loading nde at {nde_name} for event {event_name}.") + logger.info(f"Loading nde at {nde_name} for event {event_name}.") nde = NormalizingFlowPosteriorModel( model_filename=nde_name, device=settings["nde"]["training"]["device"], load_training_info=False, ) else: - log.info(f"Training new nde for event {event_name}.") + logger.info(f"Training new nde for event {event_name}.") nde = result.train_unconditional_flow( inference_parameters, settings["nde"], train_dir=outdir, ) - log.info(f"Renaming trained nde model to {nde_name}.") + logger.info(f"Renaming trained nde model to {nde_name}.") rename(join(outdir, "model_latest.pt"), nde_name) # Step 1a: Sample from proposal. @@ -170,10 +170,10 @@ def main(cfg: DictConfig): # print(np.std(log_evidences) / np.mean(log_evidences_std)) if synthetic_phase: - log.info("Sampling synthetic phase.") + logger.info("Sampling synthetic phase.") result.sample_synthetic_phase(synthetic_phase_kwargs) - log.info("Importance sampling.") + logger.info("Importance sampling.") result.importance_sample( num_processes=settings.get("num_processes", 1), time_marginalization_kwargs=time_marginalization_kwargs, @@ -187,7 +187,7 @@ def main(cfg: DictConfig): diagnostics_dir = join(outdir, "IS-diagnostics") if not exists(diagnostics_dir): makedirs(diagnostics_dir) - log.info("Plotting diagnostics.") + logger.info("Plotting diagnostics.") plot_diagnostics( result, diagnostics_dir, diff --git a/dingo/gw/inference/gw_samplers.py b/dingo/gw/inference/gw_samplers.py index ecfabf822..1774bc23f 100644 --- a/dingo/gw/inference/gw_samplers.py +++ b/dingo/gw/inference/gw_samplers.py @@ -1,4 +1,5 @@ import logging +from copy import deepcopy from typing import Union, Protocol import numpy as np @@ -6,26 +7,24 @@ from astropy.time import Time from bilby.core.prior import PriorDict, DeltaFunction, Constraint from bilby.gw.detector import InterferometerList +from hydra.utils import instantiate from torchvision.transforms import Compose from dingo.core.samplers import Sampler, GNPESampler from dingo.core.transforms import GetItem, RenameKey +from dingo.core.utils.hydra_utils import instantiate_with_runtime_dependencies from dingo.gw.domains import ( MultibandedFrequencyDomain, build_domain_from_model_metadata, UniformFrequencyDomain, Domain, ) -from dingo.gw.domains import build_domain -from dingo.gw.gwutils import get_extrinsic_prior_dict -from dingo.gw.prior import build_prior_with_defaults from dingo.gw.result import Result from dingo.gw.transforms import ( WhitenAndScaleStrain, RepackageStrainsAndASDS, ToTorch, SelectStandardizeRepackageParameters, - GNPECoalescenceTimes, TimeShiftStrain, GNPEBase, PostCorrectGeocentTime, @@ -35,7 +34,7 @@ MaskDataForFrequencyRangeUpdate, ) -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) class SamplerProtocol(Protocol): @@ -167,13 +166,12 @@ def _build_domain(self: Sampler): Called by __init__() immediately after _build_prior(). """ - self.domain = build_domain( - self.base_model_metadata["dataset_settings"]["domain"] - ) + self.domain = instantiate(self.base_model_metadata["dataset_settings"]["domain"]) data_settings = self.base_model_metadata["train_settings"]["data"] - if "domain_update" in data_settings: - self.domain.update(data_settings["domain_update"]) + domain_update = data_settings.get("waveform_dataset", {}).get("domain_update") + if domain_update is not None: + self.domain.update(domain_update) def _correct_reference_time( self: Sampler, samples: Union[dict, pd.DataFrame], inverse: bool = False @@ -229,11 +227,11 @@ def _post_process(self, samples: Union[dict, pd.DataFrame], inverse: bool = Fals Whether to apply instead the inverse transformation. This is used prior to calculating the log_prob. """ - intrinsic_prior = self.metadata["dataset_settings"]["intrinsic_prior"] - extrinsic_prior = get_extrinsic_prior_dict( + prior = instantiate(self.metadata["dataset_settings"]["intrinsic_prior"]) + extrinsic_prior = instantiate( self.metadata["train_settings"]["data"]["extrinsic_prior"] ) - prior = build_prior_with_defaults({**intrinsic_prior, **extrinsic_prior}) + prior.update(extrinsic_prior) if not inverse: # Add fixed parameters from prior. @@ -241,7 +239,7 @@ def _post_process(self, samples: Union[dict, pd.DataFrame], inverse: bool = Fals for k, p in prior.items(): if isinstance(p, DeltaFunction) and k not in samples: v = p.peak - log.info(f"Adding fixed parameter {k} = {v} from prior.") + logger.info(f"Adding fixed parameter {k} = {v} from prior.") samples[k] = p.peak * np.ones(num_samples) else: # Drop non-inference parameters from samples. @@ -446,12 +444,12 @@ def _initialize_transforms(self): transform_pre = [] transform_pre.append(RenameKey("data", "waveform")) if gnpe_time_settings: + gnpe_time_settings = deepcopy(gnpe_time_settings) + gnpe_time_settings["inference"] = True transform_pre.append( - GNPECoalescenceTimes( - ifo_list, - gnpe_time_settings["kernel"], - gnpe_time_settings["exact_equiv"], - inference=True, + instantiate_with_runtime_dependencies( + gnpe_time_settings, + {"ifo_list": ifo_list}, ) ) transform_pre.append(TimeShiftStrain(ifo_list, self.domain)) @@ -473,8 +471,8 @@ def _initialize_transforms(self): self.gnpe_parameters += transform.input_parameter_names for k, v in transform.kernel.items(): self.gnpe_kernel[k] = v - log.info(f"GNPE parameters: {self.gnpe_parameters}") - log.info(f"GNPE kernel: {self.gnpe_kernel}") + logger.info(f"GNPE parameters: {self.gnpe_parameters}") + logger.info(f"GNPE kernel: {self.gnpe_kernel}") self.transform_pre = Compose(transform_pre) diff --git a/dingo/gw/injection.py b/dingo/gw/injection.py index f1679fb47..352a7417c 100644 --- a/dingo/gw/injection.py +++ b/dingo/gw/injection.py @@ -1,16 +1,17 @@ +import logging + import numpy as np from bilby.gw.detector import InterferometerList +from hydra.utils import instantiate from torchvision.transforms import Compose -from dingo.core.utils.logging_utils import logger from dingo.gw.noise.asd_dataset import ASDDataset from dingo.gw.domains import ( UniformFrequencyDomain, MultibandedFrequencyDomain, ) -from dingo.gw.domains import build_domain, build_domain_from_model_metadata -from dingo.gw.gwutils import get_extrinsic_prior_dict -from dingo.gw.prior import build_prior_with_defaults, split_off_extrinsic_parameters +from dingo.gw.domains import build_domain_from_model_metadata +from dingo.gw.prior import split_off_extrinsic_parameters from dingo.gw.transforms import ( GetDetectorTimes, ProjectOntoDetectors, @@ -23,6 +24,8 @@ NewInterfaceWaveformGenerator, ) +logger = logging.getLogger(__name__) + class GWSignal(object): """ @@ -355,16 +358,16 @@ def from_posterior_model_metadata(cls, metadata): metadata : dict Dict which you can get via PosteriorModel.metadata """ - intrinsic_prior = metadata["dataset_settings"]["intrinsic_prior"] - extrinsic_prior = get_extrinsic_prior_dict( + prior = instantiate(metadata["dataset_settings"]["intrinsic_prior"]) + extrinsic_prior = instantiate( metadata["train_settings"]["data"]["extrinsic_prior"] ) - prior = build_prior_with_defaults({**intrinsic_prior, **extrinsic_prior}) + prior.update(extrinsic_prior) return cls( prior=prior, wfg_kwargs=metadata["dataset_settings"]["waveform_generator"], - wfg_domain=build_domain(metadata["dataset_settings"]["domain"]), + wfg_domain=instantiate(metadata["dataset_settings"]["domain"]), data_domain=build_domain_from_model_metadata(metadata), ifo_list=metadata["train_settings"]["data"]["detectors"], t_ref=metadata["train_settings"]["data"]["ref_time"], diff --git a/dingo/gw/likelihood.py b/dingo/gw/likelihood.py index f48893ad2..6336de53e 100644 --- a/dingo/gw/likelihood.py +++ b/dingo/gw/likelihood.py @@ -1,11 +1,13 @@ from multiprocessing import Pool from typing import Optional +import logging import numpy as np import pandas as pd from scipy.fft import fft from scipy.special import logsumexp from bilby.gw.utils import ln_i0 +from hydra.utils import instantiate from threadpoolctl import threadpool_limits from dingo.core.likelihood import Likelihood @@ -22,7 +24,8 @@ ) from dingo.gw.domains import build_domain from dingo.gw.data.data_preparation import get_event_data_and_domain -from dingo.core.utils.logging_utils import logger + +logger = logging.getLogger(__name__) class StationaryGaussianGWLikelihood(GWSignal, Likelihood): @@ -590,7 +593,7 @@ def _log_likelihood_time_marginalized(self, theta): kappa2_ij[:, j] = np.sum(kappa2_, axis=0) # Marginalize over time; this requires multiplying the likelihoods with the # prior (*not* in log space), summing over the time bins (both axes i and j!), - # and then taking the log. See Eq. (52) in https://arxiv.org/pdf/1809.02293.pdf. + # and then taking the logger. See Eq. (52) in https://arxiv.org/pdf/1809.02293.pdf. # To prevent numerical issues, we use the logsumexp trick. assert kappa2_ij.shape == self.time_prior_log.shape exponent = kappa2_ij + self.time_prior_log @@ -791,7 +794,7 @@ def build_stationary_gaussian_likelihood( # set up likelihood likelihood = StationaryGaussianGWLikelihood( wfg_kwargs=metadata["model"]["dataset_settings"]["waveform_generator"], - wfg_domain=build_domain(metadata["model"]["dataset_settings"]["domain"]), + wfg_domain=instantiate(metadata["model"]["dataset_settings"]["domain"]), data_domain=data_domain, event_data=event_data, t_ref=metadata["event"]["time_event"], diff --git a/dingo/gw/ls_cli.py b/dingo/gw/ls_cli.py index 6abd3d72e..7db853b02 100644 --- a/dingo/gw/ls_cli.py +++ b/dingo/gw/ls_cli.py @@ -1,7 +1,6 @@ import argparse import json import logging -import sys from pathlib import Path from pprint import pformat @@ -11,12 +10,13 @@ from dingo.core.dataset import DingoDataset from dingo.core.result import Result from dingo.core.utils.backward_compatibility import torch_load_with_fallback +from dingo.core.utils.logging_utils import setup_logger from dingo.gw.dataset import WaveformDataset from dingo.gw.noise.asd_dataset import ASDDataset from dingo.gw.SVD import SVDBasis -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) logging.captureWarnings(True) @@ -27,23 +27,18 @@ def parse_args(): def ls(): - logging.basicConfig( - level=logging.INFO, - format="[%(asctime)s][%(name)s][%(levelname)s] - %(message)s", - stream=sys.stdout, - force=True, - ) + setup_logger(use_bilby=False) args = parse_args() file_name = args.file_name path = Path(file_name) if path.suffix == ".pt": - log.info("Extracting information about torch model.\n") + logger.info("Extracting information about torch model.\n") d, _ = torch_load_with_fallback(path, preferred_map_location="meta") - log.info(f"Version: {d.get('version')}\n") - log.info(f"Model epoch: {d['epoch']}\n") - log.info("Model metadata:") - log.info( + logger.info(f"Version: {d.get('version')}\n") + logger.info(f"Model epoch: {d['epoch']}\n") + logger.info("Model metadata:") + logger.info( yaml.dump( d["metadata"], default_flow_style=False, @@ -57,10 +52,10 @@ def ls(): if dataset_type == "gw_result" or dataset_type == "core_result": result = Result(file_name=file_name) - log.info(f"Version: {result.version}") - log.info("\nDingo Result\n" + "============\n") + logger.info(f"Version: {result.version}") + logger.info("\nDingo Result\n" + "============\n") - log.info( + logger.info( "Metadata\n" + "--------\n" + yaml.dump( @@ -70,7 +65,7 @@ def ls(): ) ) if result.event_metadata: - log.info( + logger.info( "Event information:\n" + "------------------\n" + yaml.dump( @@ -80,7 +75,7 @@ def ls(): ), ) if result.importance_sampling_metadata is not None: - log.info( + logger.info( "Importance sampling:\n" + "--------------------\n" + yaml.dump( @@ -90,28 +85,28 @@ def ls(): ), ) if result.log_evidence: - log.info("Summary:\n" + "--------") + logger.info("Summary:\n" + "--------") result.print_summary() elif dataset_type == "svd_basis": svd = SVDBasis(file_name=file_name) - log.info(f"Dingo version: {svd.version}") - log.info("\nSVD Basis\n" + "=========\n") + logger.info(f"Dingo version: {svd.version}") + logger.info("\nSVD Basis\n" + "=========\n") - log.info(f"Basis size: {svd.n}.") - log.info("\nValidation summary:\n" + "-------------------") + logger.info(f"Basis size: {svd.n}.") + logger.info("\nValidation summary:\n" + "-------------------") svd.print_validation_summary() elif dataset_type == "waveform_dataset": waveform_dataset = WaveformDataset( file_name=file_name, leave_waveforms_on_disk=True ) - log.info(f"Dingo version: {waveform_dataset.version}") - log.info("\nWaveform dataset\n" + "================\n") + logger.info(f"Dingo version: {waveform_dataset.version}") + logger.info("\nWaveform dataset\n" + "================\n") - log.info(f"Dataset size: {len(waveform_dataset)}") + logger.info(f"Dataset size: {len(waveform_dataset)}") - log.info( + logger.info( "\nSettings\n" + "--------\n" + yaml.dump( @@ -123,18 +118,18 @@ def ls(): if waveform_dataset.svd: svd = SVDBasis(dictionary=waveform_dataset.svd) - log.info("\nSVD validation summary:\n" + "---------------------------") + logger.info("\nSVD validation summary:\n" + "---------------------------") svd.print_validation_summary() elif dataset_type == "asd_dataset": asd_dataset = ASDDataset(file_name=file_name) - log.info(f"Dingo version: {asd_dataset.version}") - log.info("\nASD dataset\n" + "================\n") + logger.info(f"Dingo version: {asd_dataset.version}") + logger.info("\nASD dataset\n" + "================\n") - log.info(f"Dataset size: {asd_dataset.length_info}\n") - log.info(f"GPS times (min/max): {asd_dataset.gps_info}") + logger.info(f"Dataset size: {asd_dataset.length_info}\n") + logger.info(f"GPS times (min/max): {asd_dataset.gps_info}") - log.info( + logger.info( "\nSettings\n" + "--------\n" + yaml.dump( @@ -146,22 +141,22 @@ def ls(): elif dataset_type == "trained_model": with h5py.File(file_name, "r") as f: - log.info("Extracting information about torch model.\n") - log.info(f"Version: {f.attrs['version']}") - log.info(f"Model epoch: {f.attrs['epoch']}") - log.info("Model metadata:") + logger.info("Extracting information about torch model.\n") + logger.info(f"Version: {f.attrs['version']}") + logger.info(f"Model epoch: {f.attrs['epoch']}") + logger.info("Model metadata:") for d in ["model_kwargs", "metadata"]: json_data = json.loads(f["serialized_dicts"][d][()]) - log.info(f"\n{d}:\n" + "-" * (len(d) + 1)) - log.info(pformat(json_data)) + logger.info(f"\n{d}:\n" + "-" * (len(d) + 1)) + logger.info(pformat(json_data)) else: # Legacy (before dataset_type identifier). try: svd = SVDBasis(file_name=file_name) - log.info(f"SVD dataset of size n={svd.n}.") - log.info("Validation summary:") + logger.info(f"SVD dataset of size n={svd.n}.") + logger.info("Validation summary:") svd.print_validation_summary() except KeyError: @@ -173,7 +168,7 @@ def ls(): ], ) if dataset.settings is not None: - log.info( + logger.info( yaml.dump( dataset.settings, default_flow_style=False, @@ -182,13 +177,13 @@ def ls(): ) if dataset.svd is not None: svd = SVDBasis(dictionary=dataset.svd) - log.info("SVD validation summary:") + logger.info("SVD validation summary:") svd.print_validation_summary() elif path.suffix == ".yaml": with open(path, "r") as f: settings = yaml.safe_load(f) - log.info( + logger.info( yaml.dump( settings, default_flow_style=False, @@ -197,7 +192,7 @@ def ls(): ) else: - log.info("File type unrecognized.") + logger.info("File type unrecognized.") def determine_dataset_type(file_name): diff --git a/dingo/gw/noise/asd_dataset.py b/dingo/gw/noise/asd_dataset.py index b4a378f75..6a9c57615 100644 --- a/dingo/gw/noise/asd_dataset.py +++ b/dingo/gw/noise/asd_dataset.py @@ -10,7 +10,7 @@ from dingo.gw.gwutils import * from dingo.gw.dataset import DingoDataset -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) HIGH_ASD_VALUE = 1.0 @@ -62,7 +62,7 @@ def __init__( self.gps_times.pop(ifo) if "window_factor" in self.settings["domain_dict"]: - log.info( + logger.info( "Warning: 'window_factor' is no longer used in ASDDataset. " "Removing from settings." ) @@ -138,14 +138,14 @@ def update_domain(self, domain_update): self.domain.domain_dict["type"] == "UniformFrequencyDomain" and domain_update["type"] == "MultibandedFrequencyDomain" ): - log.info("Updating ASD dataset to MultibandedFrequencyDomain.") + logger.info("Updating ASD dataset to MultibandedFrequencyDomain.") asd_dataset_decimated = {} mfd = build_domain(domain_update) ufd = mfd.base_domain if not check_domain_compatibility(self.asds, ufd): # If the ASD length is not compatible with the new base UFD, # first truncate it. - log.info( + logger.info( f" Truncating first to new base UniformFrequencyDomain: f_max " f"{self.domain.f_max} Hz -> {ufd.f_max} Hz" ) diff --git a/dingo/gw/noise/asd_estimation.py b/dingo/gw/noise/asd_estimation.py index bc6e9e290..960483643 100644 --- a/dingo/gw/noise/asd_estimation.py +++ b/dingo/gw/noise/asd_estimation.py @@ -15,7 +15,7 @@ from dingo.gw.noise.asd_dataset import ASDDataset from dingo.gw.noise.utils import psd_data_path -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) logging.captureWarnings(True) @@ -75,7 +75,7 @@ def download_and_estimate_psds( w = get_window(window_kwargs) asd_filename_list = {det: [] for det in detectors} total_segments = sum(len(time_segments[det]) for det in detectors) - log.info( + logger.info( f"Estimating PSDs for {total_segments} time segments across " f"{len(time_segments)} detector(s)." ) @@ -93,17 +93,17 @@ def download_and_estimate_psds( else: estimation_kwargs["det"] = det - log.info(f"Processing {len(time_segments[det])} PSD segment(s) for {det}.") + logger.info(f"Processing {len(time_segments[det])} PSD segment(s) for {det}.") for index, (start, end) in enumerate( tqdm(time_segments[det], disable=not verbose) ): filename = join(psd_path, f"asd_{start}.hdf5") asd_filename_list[det].append(filename) if os.path.exists(filename): - log.info(f"ASD file already exists, skipping {filename}.") + logger.info(f"ASD file already exists, skipping {filename}.") continue - log.info(f"Estimating ASD for {det} segment starting at {start}.") + logger.info(f"Estimating ASD for {det} segment starting at {start}.") dataset_dict = { "settings": { "dataset_settings": settings["dataset_settings"], @@ -120,7 +120,7 @@ def download_and_estimate_psds( dataset = ASDDataset(dictionary=dataset_dict) dataset.to_file(file_name=filename) - log.info("PSD estimation complete.") + logger.info("PSD estimation complete.") return asd_filename_list diff --git a/dingo/gw/noise/generate_dataset.py b/dingo/gw/noise/generate_dataset.py index 526a3bd94..9f7cde6d3 100644 --- a/dingo/gw/noise/generate_dataset.py +++ b/dingo/gw/noise/generate_dataset.py @@ -13,7 +13,7 @@ from dingo.gw.noise.asd_dataset import ASDDataset from dingo.gw.noise.utils import merge_datasets, get_time_segments -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) logging.captureWarnings(True) @@ -63,11 +63,11 @@ def generate_dataset(cfg: DictConfig): # pass # # dagman.build() - # log.info("DAG submission file written.") + # logger.info("DAG submission file written.") else: - log.info("Downloading strain data and estimating PSDs...") + logger.info("Downloading strain data and estimating PSDs...") asd_filename_list = download_and_estimate_psds( data_dir, settings, time_segments, verbose=verbose ) @@ -75,7 +75,7 @@ def generate_dataset(cfg: DictConfig): det: [ASDDataset(asd_file) for asd_file in asd_file_list] for det, asd_file_list in asd_filename_list.items() } - log.info("Merging single dataset files into one...") + logger.info("Merging single dataset files into one...") dataset = merge_datasets(asd_dataset_list) filename = out_name if filename is None: diff --git a/dingo/gw/noise/synthetic/asd_sampling.py b/dingo/gw/noise/synthetic/asd_sampling.py index 1c2f941cb..3d0ff1421 100644 --- a/dingo/gw/noise/synthetic/asd_sampling.py +++ b/dingo/gw/noise/synthetic/asd_sampling.py @@ -9,7 +9,7 @@ get_index_for_elem, ) -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) class KDE: @@ -57,7 +57,7 @@ def fit(self, weights=None): weights=weights, ) except np.linalg.LinAlgError: - log.info( + logger.info( "Warning: Singular Matrix encountered in spectral KDE. Adding small Gaussian noise..." ) perturbed_features = spectral_features[:, i, :] + np.random.normal( diff --git a/dingo/gw/noise/synthetic/generate_dataset.py b/dingo/gw/noise/synthetic/generate_dataset.py index a3896a384..b52d4bb2b 100644 --- a/dingo/gw/noise/synthetic/generate_dataset.py +++ b/dingo/gw/noise/synthetic/generate_dataset.py @@ -12,7 +12,7 @@ from dingo.gw.noise.synthetic.asd_sampling import KDE, get_rescaling_params from dingo.gw.noise.synthetic.utils import reconstruct_psds_from_parameters -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) logging.captureWarnings(True) diff --git a/dingo/gw/noise/utils.py b/dingo/gw/noise/utils.py index dc94d5b44..6bd9e333d 100644 --- a/dingo/gw/noise/utils.py +++ b/dingo/gw/noise/utils.py @@ -16,7 +16,7 @@ from gwpy.table import EventTable from dingo.gw.noise.asd_dataset import ASDDataset -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) logging.captureWarnings(True) """ @@ -161,7 +161,7 @@ def merge_datasets(asd_dataset_list): merged_dict = {"asds": {}, "gps_times": {}} for det, asd_list in asd_dataset_list.items(): - log.info(f"Merging {len(asd_list)} datasets into one for detector {det}.") + logger.info(f"Merging {len(asd_list)} datasets into one for detector {det}.") merged_dict["asds"][det] = np.vstack( [asd_dataset.asds[det] for asd_dataset in asd_list] ) diff --git a/dingo/gw/prior.py b/dingo/gw/prior.py index 5fc97129e..02f47b849 100644 --- a/dingo/gw/prior.py +++ b/dingo/gw/prior.py @@ -1,5 +1,3 @@ -from copy import deepcopy - from bilby.gw.prior import BBHPriorDict from bilby.gw.conversion import ( fill_from_fixed_priors, @@ -8,7 +6,7 @@ from bilby.core.prior import Uniform, Sine, Cosine import numpy as np -from typing import Dict, Set, Any +from typing import Set, Any import warnings # Silence INFO and WARNING messages from bilby @@ -99,76 +97,6 @@ def mean_std(self, keys=([]), sample_size=50000, force_numerical=False): return mean, std -default_extrinsic_dict = { - "dec": "bilby.core.prior.Cosine(minimum=-np.pi/2, maximum=np.pi/2, name='dec')", - "ra": 'bilby.core.prior.Uniform(minimum=0., maximum=2*np.pi, boundary="periodic", name="ra")', - "geocent_time": "bilby.core.prior.Uniform(minimum=-0.1, maximum=0.1, name='geocent_time')", - "psi": 'bilby.core.prior.Uniform(minimum=0.0, maximum=np.pi, boundary="periodic", name="psi")', - "luminosity_distance": "bilby.core.prior.Uniform(minimum=100.0, maximum=6000.0, name='luminosity_distance')", -} - -default_intrinsic_dict = { - "mass_1": "bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1')", - "mass_2": "bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2')", - "mass_ratio": "bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio')", - "chirp_mass": "bilby.gw.prior.UniformInComponentsChirpMass(minimum=25.0, maximum=100.0, name='chirp_mass')", - "luminosity_distance": 1000.0, - "theta_jn": "bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='theta_jn')", - "phase": 'bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phase")', - "a_1": "bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_1')", - "a_2": "bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_2')", - "tilt_1": "bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_1')", - "tilt_2": "bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_2')", - "phi_12": 'bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_12")', - "phi_jl": 'bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_jl")', - "geocent_time": 0.0, -} - -default_inference_parameters = [ - "chirp_mass", - "mass_ratio", - "phase", - "a_1", - "a_2", - "tilt_1", - "tilt_2", - "phi_12", - "phi_jl", - "theta_jn", - "luminosity_distance", - "geocent_time", - "ra", - "dec", - "psi", -] - - -def build_prior_with_defaults(prior_settings: Dict[str, str]): - """ - Generate BBHPriorDict based on dictionary of prior settings, - allowing for default values. - - Parameters - ---------- - prior_settings: Dict - A dictionary containing prior definitions for intrinsic parameters - Allowed values for each parameter are: - * 'default' to use a default prior - * a string for a custom prior, e.g., - "Uniform(minimum=10.0, maximum=80.0, name=None, latex_label=None, unit=None, boundary=None)" - - Depending on the particular prior choices the dimensionality of a - parameter sample obtained from the returned GWPriorDict will vary. - """ - - full_prior_settings = deepcopy(prior_settings) - for k, v in prior_settings.items(): - if v == "default": - full_prior_settings[k] = default_intrinsic_dict[k] - - return BBHPriorDict(full_prior_settings) - - def split_off_extrinsic_parameters(theta): """ Split theta into intrinsic and extrinsic parameters. diff --git a/dingo/gw/result.py b/dingo/gw/result.py index b5d130ba3..70b68650c 100644 --- a/dingo/gw/result.py +++ b/dingo/gw/result.py @@ -8,6 +8,7 @@ from bilby.core.prior import Uniform, Constraint, PriorDict, DeltaFunction from bilby.gw.prior import CalibrationPriorDict from bilby_pipe.utils import CALIBRATION_CORRECTION_TYPE_LOOKUP +from hydra.utils import instantiate from dingo.core.density import ( interpolated_sample_and_log_prob_multi, @@ -18,14 +19,12 @@ from dingo.gw.conversion import change_spin_conversion_phase from dingo.gw.domains import MultibandedFrequencyDomain from dingo.gw.domains import build_domain -from dingo.gw.gwutils import get_extrinsic_prior_dict from dingo.gw.likelihood import StationaryGaussianGWLikelihood -from dingo.gw.prior import build_prior_with_defaults from dingo.core.utils.backward_compatibility import check_minimum_version RANDOM_STATE = 150914 -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) class Result(CoreResult): @@ -168,12 +167,13 @@ def _build_domain(self): Called by __init__() immediately after _build_prior(). """ - self.domain = build_domain(self.base_metadata["dataset_settings"]["domain"]) + self.domain = instantiate(self.base_metadata["dataset_settings"]["domain"]) check_minimum_version(self.version, raise_exception=False) data_settings = self.base_metadata["train_settings"]["data"] - if "domain_update" in data_settings: - self.domain.update(data_settings["domain_update"]) + domain_update = data_settings.get("waveform_dataset", {}).get("domain_update") + if domain_update is not None: + self.domain.update(domain_update) def _rebuild_domain(self, verbose=False): """Rebuild the domain based on settings updated for importance sampling. @@ -201,8 +201,8 @@ def _rebuild_domain(self, verbose=False): ) if verbose: - log.info("Rebuilding domain as follows:") - log.info( + logger.info("Rebuilding domain as follows:") + logger.info( yaml.dump( domain_dict, default_flow_style=False, @@ -212,15 +212,17 @@ def _rebuild_domain(self, verbose=False): self.domain = build_domain(domain_dict) else: if verbose: - log.info("No domain updates found; domain not rebuilt.") + logger.info("No domain updates found; domain not rebuilt.") def _build_prior(self): """Build the prior based on model metadata. Called by __init__().""" - intrinsic_prior = self.base_metadata["dataset_settings"]["intrinsic_prior"] - extrinsic_prior = get_extrinsic_prior_dict( + self.prior = instantiate( + self.base_metadata["dataset_settings"]["intrinsic_prior"] + ) + extrinsic_prior = instantiate( self.base_metadata["train_settings"]["data"]["extrinsic_prior"] ) - self.prior = build_prior_with_defaults({**intrinsic_prior, **extrinsic_prior}) + self.prior.update(extrinsic_prior) prior_update = self.importance_sampling_metadata.get("prior_update") if prior_update is not None: @@ -361,11 +363,11 @@ def _build_likelihood( if "updates" in self.importance_sampling_metadata: if "T" in self.importance_sampling_metadata["updates"]: delta_f_new = 1 / self.importance_sampling_metadata["updates"]["T"] - log.info( + logger.info( f'Updating waveform generation delta_f from {wfg_domain_dict["delta_f"]} to {delta_f_new}.' ) wfg_domain_dict["delta_f"] = delta_f_new - wfg_domain = build_domain(wfg_domain_dict) + wfg_domain = instantiate(wfg_domain_dict) self.likelihood = StationaryGaussianGWLikelihood( wfg_kwargs=self.base_metadata["dataset_settings"]["waveform_generator"], @@ -459,7 +461,7 @@ def sample_calibration_parameters(self, calibration_sampling_kwargs: dict): # Sample calibration parameters and calculate log_prob num_samples = len(self.samples) - log.info(f"Sampling calibration parameters for {num_samples} samples.") + logger.info(f"Sampling calibration parameters for {num_samples} samples.") delta_log_prob = np.zeros(num_samples) @@ -578,7 +580,7 @@ def sample_synthetic_phase( self.synthetic_phase_kwargs.get("num_processes", 1), num_valid_samples // 10 ) - log.info(f"Estimating synthetic phase for {num_valid_samples} samples.") + logger.info(f"Estimating synthetic phase for {num_valid_samples} samples.") t0 = time.time() if not inverse: @@ -667,7 +669,7 @@ def sample_synthetic_phase( self.samples["log_prob"] = log_prob_array del self.samples["phase"] - log.info(f"Done. This took {time.time() - t0:.2f} s.") + logger.info(f"Done. This took {time.time() - t0:.2f} s.") def get_samples_bilby_phase(self, num_processes=1): """ diff --git a/dingo/gw/training/train_builders.py b/dingo/gw/training/train_builders.py index f3695015e..7baade18e 100755 --- a/dingo/gw/training/train_builders.py +++ b/dingo/gw/training/train_builders.py @@ -5,68 +5,25 @@ import torch.multiprocessing import torchvision from threadpoolctl import threadpool_limits -from bilby.gw.detector import InterferometerList +from hydra.utils import instantiate from dingo.gw.SVD import SVDBasis from dingo.gw.dataset.waveform_dataset import WaveformDataset -from dingo.gw.domains import build_domain from dingo.gw.transforms import ( - ProjectOntoDetectors, - SampleNoiseASD, - WhitenAndScaleStrain, AddWhiteNoiseComplex, SelectStandardizeRepackageParameters, RepackageStrainsAndASDS, UnpackDict, - GNPECoalescenceTimes, - SampleExtrinsicParameters, - GetDetectorTimes, CropMaskStrainRandom, ) -from dingo.gw.noise.asd_dataset import ASDDataset -from dingo.gw.prior import default_inference_parameters from dingo.gw.gwutils import * from dingo.core.utils import * -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) -def build_dataset( - data_settings: dict, - leave_waveforms_on_disk: Optional[bool] = False, -) -> WaveformDataset: - """Build a dataset based on a settings dictionary. This should contain the path of - a saved waveform dataset. - - This function also truncates the dataset as necessary. - - Parameters - ---------- - data_settings : dict - leave_waveforms_on_disk: bool - If provided, the values associated with the waveforms will not be loaded into memory during initialization. - Instead, they will be loaded from disk when the dataset is accessed. This is useful for reducing the memory - load of large datasets, but can slow down data preprocessing. - - Returns - ------- - WaveformDataset - """ - - # Build and truncate datasets - domain_update = data_settings.get("domain_update", None) - wfd = WaveformDataset( - file_name=data_settings["waveform_dataset_path"], - precision="single", - domain_update=domain_update, - svd_size_update=data_settings.get("svd_size_update"), - leave_waveforms_on_disk=leave_waveforms_on_disk, - ) - return wfd - - -def set_train_transforms(wfd, data_settings, asd_dataset_path, omit_transforms=None): +def set_train_transforms(wfd, data_settings, asd_dataset_config, omit_transforms=None): """ Set the transform attribute of a waveform dataset based on a settings dictionary. The transform takes waveform polarizations, samples random extrinsic parameters, @@ -75,120 +32,62 @@ def set_train_transforms(wfd, data_settings, asd_dataset_path, omit_transforms=N Note that the WaveformDataset is modified in-place, so this function returns nothing. + The Hydra training config distinguishes two transform lists: + + * ``data.standardization_transforms`` is a short prefix pipeline used before this + function is called, in ``train_pipeline._populate_parameter_standardization``. + It reproduces the old procedural behavior where parameter standardizations were + computed after sampling extrinsic parameters, detector times, and optional GNPE + proxy times, but before projection/noise/repackaging. The computed mean/std + dictionary is written back into ``data.standardization`` and into the resolved + ``SelectStandardizeRepackageParameters`` config. + * ``data.transforms`` is the actual training pipeline instantiated here and assigned + to ``wfd.transform``. + + Hydra entries such as ``ifo_list: {_target_: ..., interferometers: + "${data.detectors}"}`` mean that the target is called with keyword arguments, e.g. + ``InterferometerList(interferometers=data_settings["detectors"])`` after OmegaConf + interpolation has resolved ``${data.detectors}``. + Parameters ---------- wfd : WaveformDataset data_settings : dict - asd_dataset_path : str - Path corresponding to the ASD dataset used to generate noise. + asd_dataset_config : dict + Config for the ASD dataset used to generate noise. omit_transforms : List of sub-transforms to omit from the full composition. """ - log.info("Setting train transforms.") + logger.info("Setting train transforms.") if omit_transforms is not None: - log.info("Omitting \n\t" + "\n\t".join([t.__name__ for t in omit_transforms])) + logger.info("Omitting \n\t" + "\n\t".join([t.__name__ for t in omit_transforms])) # By passing the wfd domain when instantiating the noise dataset, this ensures the # domains will match. In particular, it truncates the ASD dataset beyond the new # f_max, and sets it to 1 below f_min. - asd_dataset = ASDDataset( - asd_dataset_path, - ifos=data_settings["detectors"], - precision="single", - domain_update=wfd.domain.domain_dict, - ) + asd_dataset = instantiate(asd_dataset_config, domain_update=wfd.domain.domain_dict) assert wfd.domain == asd_dataset.domain - domain = wfd.domain - - extrinsic_prior_dict = get_extrinsic_prior_dict(data_settings["extrinsic_prior"]) - if data_settings["inference_parameters"] == "default": - data_settings["inference_parameters"] = default_inference_parameters - - ref_time = data_settings["ref_time"] - # Build detector objects - ifo_list = InterferometerList(data_settings["detectors"]) - - # Build transforms. - transforms = [ - SampleExtrinsicParameters(extrinsic_prior_dict), - GetDetectorTimes(ifo_list, ref_time), - ] - - extra_context_parameters = [] - if "gnpe_time_shifts" in data_settings: - d = data_settings["gnpe_time_shifts"] - transforms.append( - GNPECoalescenceTimes( - ifo_list, - d["kernel"], - d["exact_equiv"], - inference=False, - ) - ) - extra_context_parameters += transforms[-1].context_parameters - - # Add the GNPE context to context_parameters the first time the transforms are - # constructed. We do not want to overwrite the ordering of the parameters in - # subsequent runs. - if "context_parameters" not in data_settings: - data_settings["context_parameters"] = [] - for p in extra_context_parameters: - if p not in data_settings["context_parameters"]: - data_settings["context_parameters"].append(p) - - # If the standardization factors have already been set, use those. Otherwise, - # calculate them, and save them within the data settings. - # - # Standardizations are calculated at this point because the present set of - # transforms is sufficient for generating samples of all regression and context - # parameters. - try: - standardization_dict = data_settings["standardization"] - log.info("Using previously-calculated parameter standardizations.") - except KeyError: - log.info("Calculating new parameter standardizations.") - standardization_dict = get_standardization_dict( - extrinsic_prior_dict, - wfd, - data_settings["inference_parameters"] + data_settings["context_parameters"], - torchvision.transforms.Compose(transforms), - ) - data_settings["standardization"] = standardization_dict - - transforms.append(ProjectOntoDetectors(ifo_list, domain, ref_time)) - transforms.append(SampleNoiseASD(asd_dataset)) - transforms.append(WhitenAndScaleStrain(domain.noise_std)) - # We typically add white detector noise. For debugging purposes, this can be turned - # off with zero_noise option in data_settings. - if not data_settings.get("zero_noise", False): - transforms.append(AddWhiteNoiseComplex()) - transforms.append( - SelectStandardizeRepackageParameters( - { - k: data_settings[k] - for k in ["inference_parameters", "context_parameters"] - }, - standardization_dict, - ) - ) - transforms.append( - RepackageStrainsAndASDS(data_settings["detectors"], first_index=domain.min_idx) - ) - if "random_strain_cropping" in data_settings: - transforms.append( - CropMaskStrainRandom(domain, **data_settings["random_strain_cropping"]) - ) - if data_settings["context_parameters"]: - selected_keys = ["inference_parameters", "waveform", "context_parameters"] - else: - selected_keys = ["inference_parameters", "waveform"] - transforms.append(UnpackDict(selected_keys=selected_keys)) + runtime_dependencies = { + "domain": wfd.domain, + "asd_dataset": asd_dataset, + } + + transforms = [] + for transform_config in data_settings["transforms"]: + transform = instantiate_with_runtime_dependencies( + transform_config, + runtime_dependencies, + ) + transforms.append(transform) # Drop transforms that are not desired. This is useful for generating, e.g., # noise-free data, or for producing data not formatted for input to the network. - if omit_transforms is not None: + omit_transforms = list(omit_transforms or []) + if data_settings.get("zero_noise", False): + omit_transforms.append(AddWhiteNoiseComplex) + if omit_transforms: transforms = [t for t in transforms if type(t) not in omit_transforms] wfd.transform = torchvision.transforms.Compose(transforms) @@ -197,7 +96,7 @@ def set_train_transforms(wfd, data_settings, asd_dataset_path, omit_transforms=N def build_svd_for_embedding_network( wfd: WaveformDataset, data_settings: dict, - asd_dataset_path: str, + asd_dataset_config: dict, size: int, num_training_samples: int, num_validation_samples: int, @@ -216,7 +115,7 @@ def build_svd_for_embedding_network( ---------- wfd : WaveformDataset data_settings : dict - asd_dataset_path : str + asd_dataset_config : dict Training waveforms will be whitened with respect to these ASDs. size : int Number of basis elements to include in the SVD projection. @@ -244,14 +143,14 @@ def build_svd_for_embedding_network( torch.multiprocessing.set_sharing_strategy("file_system") # Fix the luminosity distance to a standard value, just in order to generate the SVD. - data_settings["extrinsic_prior"]["luminosity_distance"] = "100.0" + data_settings["extrinsic_prior"]["dictionary"]["luminosity_distance"] = "100.0" # Build the dataset, but with certain transforms omitted. In particular, we want to # build the SVD based on zero-noise waveforms. They should still be whitened though. set_train_transforms( wfd, data_settings, - asd_dataset_path, + asd_dataset_config, omit_transforms=[ AddWhiteNoiseComplex, RepackageStrainsAndASDS, @@ -261,7 +160,7 @@ def build_svd_for_embedding_network( ], ) - log.info("Generating waveforms for embedding network SVD initialization.") + logger.info("Generating waveforms for embedding network SVD initialization.") time_start = time.time() ifos = list(wfd[0]["waveform"].keys()) waveform_len = len(wfd[0]["waveform"][ifos[0]]) @@ -299,25 +198,25 @@ def build_svd_for_embedding_network( waveforms[ifo][lower : lower + n] = strains[:n] if lower + n == num_waveforms: break - log.info(f"...done. This took {time.time() - time_start:.0f} s.") + logger.info(f"...done. This took {time.time() - time_start:.0f} s.") # Reset the standard sharing strategy. torch.multiprocessing.set_sharing_strategy(old_sharing_strategy) - log.info("Generating SVD basis for ifo:") + logger.info("Generating SVD basis for ifo:") time_start = time.time() basis_dict = {} for ifo in ifos: basis = SVDBasis() basis.generate_basis(waveforms[ifo][:num_training_samples], size) basis_dict[ifo] = basis - log.info(f"...{ifo} done.") - log.info(f"...this took {time.time() - time_start:.0f} s.") + logger.info(f"...{ifo} done.") + logger.info(f"...this took {time.time() - time_start:.0f} s.") if out_dir is not None: - log.info("Testing SVD basis matrices.") + logger.info("Testing SVD basis matrices.") for ifo, basis in basis_dict.items(): - log.info(f"...{ifo}:") + logger.info(f"...{ifo}:") basis.compute_test_mismatches( waveforms[ifo][num_training_samples:], parameters=parameters.iloc[num_training_samples:].reset_index( @@ -326,19 +225,19 @@ def build_svd_for_embedding_network( verbose=True, ) basis.to_file(os.path.join(out_dir, f"svd_{ifo}.hdf5")) - log.info("Done") + logger.info("Done") # Return V matrices in standard order. Drop the elements below domain.min_idx, # since the neural network expects data truncated below these. The dropped elements # should be 0. - log.info(f"Truncating SVD matrices below index {wfd.domain.min_idx}.") - log.info("...V matrix shapes:") + logger.info(f"Truncating SVD matrices below index {wfd.domain.min_idx}.") + logger.info("...V matrix shapes:") V_rb_list = [] for ifo in data_settings["detectors"]: V = basis_dict[ifo].V assert np.allclose(V[: wfd.domain.min_idx], 0) V = V[wfd.domain.min_idx :] - log.info(" " + str(V.shape)) + logger.info(" " + str(V.shape)) V_rb_list.append(V) - log.info("\n") + logger.info("\n") return V_rb_list diff --git a/dingo/gw/training/train_pipeline.py b/dingo/gw/training/train_pipeline.py index abf745181..b0e4f5b03 100644 --- a/dingo/gw/training/train_pipeline.py +++ b/dingo/gw/training/train_pipeline.py @@ -4,24 +4,23 @@ import hydra import numpy as np +import torchvision import yaml import shutil import time from copy import deepcopy -from hydra.utils import to_absolute_path +from hydra.utils import instantiate, to_absolute_path from omegaconf import DictConfig, OmegaConf from threadpoolctl import threadpool_limits -from dingo.core.posterior_models.build_model import ( - autocomplete_model_kwargs, - build_model_from_kwargs, -) +from dingo.core.posterior_models.build_model import build_model_from_kwargs +from dingo.core.utils.hydra_utils import instantiate_with_runtime_dependencies from dingo.gw.training.train_builders import ( - build_dataset, set_train_transforms, build_svd_for_embedding_network, ) +from dingo.gw.gwutils import get_standardization_dict from dingo.core.utils.trainutils import RuntimeLimits from dingo.core.utils import ( set_requires_grad_flag, @@ -32,10 +31,59 @@ from dingo.gw.dataset import WaveformDataset from dingo.core.posterior_models import BasePosteriorModel -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) logging.captureWarnings(True) +def _standardization_is_missing(standardization: dict) -> bool: + if standardization in (None, "???"): + return True + return ( + standardization.get("mean") in (None, "???") + or standardization.get("std") in (None, "???") + ) + + +def _populate_parameter_standardization( + wfd: WaveformDataset, + data_settings: dict, + asd_dataset_config: dict, +) -> None: + if not _standardization_is_missing(data_settings.get("standardization")): + logger.info("Using previously-calculated parameter standardizations.") + _sync_parameter_standardization_transform_configs(data_settings) + return + + logger.info("Calculating new parameter standardizations.") + asd_dataset = instantiate(asd_dataset_config, domain_update=wfd.domain.domain_dict) + runtime_dependencies = { + "domain": wfd.domain, + "asd_dataset": asd_dataset, + } + transforms = [ + instantiate_with_runtime_dependencies(transform_config, runtime_dependencies) + for transform_config in data_settings["standardization_transforms"] + ] + selected_parameters = ( + data_settings["inference_parameters"] + data_settings["context_parameters"] + ) + data_settings["standardization"] = get_standardization_dict( + instantiate(data_settings["extrinsic_prior"]), + wfd, + selected_parameters, + torchvision.transforms.Compose(transforms), + ) + _sync_parameter_standardization_transform_configs(data_settings) + + +def _sync_parameter_standardization_transform_configs(data_settings: dict) -> None: + for transform_config in data_settings["transforms"]: + if transform_config.get("_target_", "").endswith( + "SelectStandardizeRepackageParameters" + ): + transform_config["standardization_dict"] = data_settings["standardization"] + + def copy_files_to_local( file_path: str, local_dir: Optional[str], @@ -66,16 +114,16 @@ def copy_files_to_local( if local_dir is not None: file_name = file_path.split("/")[-1] local_file_path = os.path.join(local_dir, file_name) - log.info(f"Copying file to {local_file_path}") + logger.info(f"Copying file to {local_file_path}") # Copy file start_time = time.time() shutil.copy(file_path, local_file_path) elapsed_time = time.time() - start_time - log.info( + logger.info( "Done. This took {:2.0f}:{:2.0f} min.".format(*divmod(elapsed_time, 60)) ) elif leave_keys_on_disk and is_condor: - log.warning( + logger.warning( f"leave_waveforms_on_disk defaults to True, but local_cache_path is not specified. " f"This means that the waveforms will be loaded during training from {local_file_path}. " f"This can lead to unexpected long times for data loading during training due to network traffic. " @@ -112,29 +160,36 @@ def prepare_training_new( """ data_settings = deepcopy(train_settings["data"]) # Optionally copy files to local and update path - data_settings["waveform_dataset_path"] = copy_files_to_local( - file_path=data_settings["waveform_dataset_path"], + data_settings["waveform_dataset"]["file_name"] = copy_files_to_local( + file_path=data_settings["waveform_dataset"]["file_name"], local_dir=local_settings.get("local_cache_path", None), leave_keys_on_disk=local_settings.get("leave_waveforms_on_disk", True), is_condor=True if "condor" in local_settings else False, ) - wfd = build_dataset( - data_settings=data_settings, + wfd = instantiate( + data_settings["waveform_dataset"], leave_waveforms_on_disk=local_settings.get("leave_waveforms_on_disk", True), ) # No transforms yet + train_settings["data"] = data_settings initial_weights = {} + _populate_parameter_standardization( + wfd, + train_settings["data"], + train_settings["training"]["stage_0"]["asd_dataset"], + ) + # The embedding network is assumed to have an SVD projection layer. If other types # of embedding networks are added in the future, update this code. svd_settings = (train_settings["model"].get("embedding_kwargs") or {}).get("svd") if svd_settings and svd_settings.get("num_training_samples", 0) > 0: # First, build the SVD for seeding the embedding network. - log.info("\nBuilding SVD for initialization of embedding network.") + logger.info("\nBuilding SVD for initialization of embedding network.") initial_weights["V_rb_list"] = build_svd_for_embedding_network( wfd, train_settings["data"], - train_settings["training"]["stage_0"]["asd_dataset_path"], + train_settings["training"]["stage_0"]["asd_dataset"], num_workers=local_settings["num_workers"], batch_size=train_settings["training"]["stage_0"]["batch_size"], out_dir=train_dir, @@ -151,19 +206,33 @@ def prepare_training_new( set_train_transforms( wfd, train_settings["data"], - train_settings["training"]["stage_0"]["asd_dataset_path"], + train_settings["training"]["stage_0"]["asd_dataset"], ) - # This modifies the model settings in-place. - autocomplete_model_kwargs(train_settings["model"], wfd[0]) + data_sample = wfd[0] + train_settings["model"]["embedding_kwargs"]["input_dims"] = list( + data_sample[1].shape + ) + train_settings["model"]["posterior_kwargs"]["input_dim"] = len(data_sample[0]) + try: + gnpe_proxy_dim = len(data_sample[2]) + train_settings["model"]["embedding_kwargs"]["added_context"] = True + train_settings["model"]["posterior_kwargs"]["context_dim"] = ( + train_settings["model"]["embedding_kwargs"]["output_dim"] + gnpe_proxy_dim + ) + except IndexError: + train_settings["model"]["embedding_kwargs"]["added_context"] = False + train_settings["model"]["posterior_kwargs"]["context_dim"] = train_settings[ + "model" + ]["embedding_kwargs"]["output_dim"] full_settings = { "dataset_settings": wfd.settings, "train_settings": train_settings, } - log.info("\nInitializing new posterior model.") - log.info("Complete settings:") - log.info(yaml.dump(full_settings, default_flow_style=False, sort_keys=False)) + logger.info("\nInitializing new posterior model.") + logger.info("Complete settings:") + logger.info(yaml.dump(full_settings, default_flow_style=False, sort_keys=False)) pm = build_model_from_kwargs( settings=full_settings, @@ -181,7 +250,7 @@ def prepare_training_new( **local_settings["wandb"], ) except ImportError: - log.warning("WandB is enabled but not installed.") + logger.warning("WandB is enabled but not installed.") return pm, wfd @@ -213,14 +282,14 @@ def prepare_training_resume( ) data_settings = deepcopy(pm.metadata["train_settings"]["data"]) # Optionally copy files to local and update path - data_settings["waveform_dataset_path"] = copy_files_to_local( - file_path=data_settings["waveform_dataset_path"], + data_settings["waveform_dataset"]["file_name"] = copy_files_to_local( + file_path=data_settings["waveform_dataset"]["file_name"], local_dir=local_settings.get("local_cache_path", None), leave_keys_on_disk=local_settings.get("leave_waveforms_on_disk", True), is_condor=True if "condor" in local_settings else False, ) - wfd = build_dataset( - data_settings=data_settings, + wfd = instantiate( + data_settings["waveform_dataset"], leave_waveforms_on_disk=local_settings.get("leave_waveforms_on_disk", True), ) @@ -234,7 +303,7 @@ def prepare_training_resume( **local_settings["wandb"], ) except ImportError: - log.warning("WandB is enabled but not installed.") + logger.warning("WandB is enabled but not installed.") return pm, wfd @@ -273,7 +342,7 @@ def initialize_stage( train_settings = pm.metadata["train_settings"] # Rebuild transforms based on possibly different noise. - set_train_transforms(wfd, train_settings["data"], stage["asd_dataset_path"]) + set_train_transforms(wfd, train_settings["data"], stage["asd_dataset"]) # Allows for changes in batch size between stages. train_loader, test_loader = build_train_and_test_loaders( @@ -286,7 +355,7 @@ def initialize_stage( if not resume: # New optimizer and scheduler. If we are resuming, these should have been # loaded from the checkpoint. - log.info("Initializing new optimizer and scheduler.") + logger.info("Initializing new optimizer and scheduler.") pm.optimizer_kwargs = stage["optimizer"] pm.scheduler_kwargs = stage["scheduler"] pm.initialize_optimizer_and_scheduler() @@ -303,7 +372,7 @@ def initialize_stage( ) n_grad = get_number_of_model_parameters(pm.network, (True,)) n_nograd = get_number_of_model_parameters(pm.network, (False,)) - log.info(f"Fixed parameters: {n_nograd}\nLearnable parameters: {n_grad}\n") + logger.info(f"Fixed parameters: {n_nograd}\nLearnable parameters: {n_grad}\n") return train_loader, test_loader @@ -351,14 +420,14 @@ def train_stages( stage = stages[n] if pm.epoch == end_epochs[n] - stage["epochs"]: - log.info(f"\nBeginning training stage {n}. Settings:") - log.info(yaml.dump(stage, default_flow_style=False, sort_keys=False)) + logger.info(f"\nBeginning training stage {n}. Settings:") + logger.info(yaml.dump(stage, default_flow_style=False, sort_keys=False)) train_loader, test_loader = initialize_stage( pm, wfd, stage, local_settings["num_workers"], resume=False ) else: - log.info(f"\nResuming training in stage {n}. Settings:") - log.info(yaml.dump(stage, default_flow_style=False, sort_keys=False)) + logger.info(f"\nResuming training in stage {n}. Settings:") + logger.info(yaml.dump(stage, default_flow_style=False, sort_keys=False)) train_loader, test_loader = initialize_stage( pm, wfd, stage, local_settings["num_workers"], resume=True ) @@ -367,7 +436,7 @@ def train_stages( try: early_stopping = EarlyStopping(**stage["early_stopping"]) except Exception: - log.warning( + logger.warning( "Early stopping settings invalid. Please pass 'patience', 'delta', 'metric'" ) raise @@ -389,10 +458,10 @@ def train_stages( if pm.epoch == end_epochs[n]: save_file = os.path.join(train_dir, f"model_stage_{n}.pt") - log.info(f"Training stage complete. Saving to {save_file}.") + logger.info(f"Training stage complete. Saving to {save_file}.") pm.save_model(save_file, save_training_info=True) if runtime_limits.local_limits_exceeded(pm.epoch): - log.info("Local runtime limits reached. Ending program.") + logger.info("Local runtime limits reached. Ending program.") break if pm.epoch == end_epochs[-1]: @@ -402,12 +471,14 @@ def train_stages( def _resolve_training_input_paths(train_settings: dict) -> None: - train_settings["data"]["waveform_dataset_path"] = to_absolute_path( - train_settings["data"]["waveform_dataset_path"] + train_settings["data"]["waveform_dataset"]["file_name"] = to_absolute_path( + train_settings["data"]["waveform_dataset"]["file_name"] ) for stage in train_settings["training"].values(): - if isinstance(stage, dict) and "asd_dataset_path" in stage: - stage["asd_dataset_path"] = to_absolute_path(stage["asd_dataset_path"]) + if isinstance(stage, dict) and "asd_dataset" in stage: + stage["asd_dataset"]["file_name"] = to_absolute_path( + stage["asd_dataset"]["file_name"] + ) @hydra.main( @@ -424,7 +495,7 @@ def train_local(cfg: DictConfig): os.makedirs(train_dir, exist_ok=True) if checkpoint is None: - log.info("Beginning new training run.") + logger.info("Beginning new training run.") train_settings = settings _resolve_training_input_paths(train_settings) @@ -443,7 +514,7 @@ def train_local(cfg: DictConfig): local_settings["wandb"]["id"] = wandb.util.generate_id() except ImportError: - log.warning("wandb not installed, cannot generate run id.") + logger.warning("wandb not installed, cannot generate run id.") yaml.dump(local_settings, f, default_flow_style=False, sort_keys=False) pm, wfd = prepare_training_new(train_settings, train_dir, local_settings) @@ -452,7 +523,7 @@ def train_local(cfg: DictConfig): checkpoint = to_absolute_path(checkpoint) if not os.path.isfile(checkpoint): raise FileNotFoundError(f"Checkpoint not found: {checkpoint}") - log.info("Resuming training run.") + logger.info("Resuming training run.") with open(os.path.join(train_dir, "local_settings.yaml"), "r") as f: local_settings = yaml.safe_load(f) pm, wfd = prepare_training_resume(checkpoint, local_settings, train_dir) @@ -462,11 +533,11 @@ def train_local(cfg: DictConfig): if complete: if exit_command: - log.info( + logger.info( f"All training stages complete. Executing exit command: {exit_command}." ) os.system(exit_command) else: - log.info("All training stages complete.") + logger.info("All training stages complete.") else: - log.info("Program terminated due to runtime limit.") + logger.info("Program terminated due to runtime limit.") diff --git a/dingo/gw/training/train_pipeline_condor.py b/dingo/gw/training/train_pipeline_condor.py index 06550f5a2..4bde7af41 100644 --- a/dingo/gw/training/train_pipeline_condor.py +++ b/dingo/gw/training/train_pipeline_condor.py @@ -1,16 +1,19 @@ import os import sys +import logging from os.path import join, isfile import yaml import argparse -from dingo.core.utils.logging_utils import logger, setup_logger +from dingo.core.utils.logging_utils import setup_logger from dingo.gw.training import ( prepare_training_new, prepare_training_resume, train_stages, ) +logger = logging.getLogger(__name__) + def create_submission_file( train_dir: str, condor_settings: dict, filename: str = "submission_file.sub" diff --git a/dingo/gw/training/utils.py b/dingo/gw/training/utils.py index 8e9736280..d864c1852 100644 --- a/dingo/gw/training/utils.py +++ b/dingo/gw/training/utils.py @@ -9,7 +9,7 @@ from dingo.core.utils.backward_compatibility import torch_load_with_fallback -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) logging.captureWarnings(True) @@ -30,7 +30,7 @@ def append_stage(cfg: DictConfig): if k.startswith("stage_") ] num_stages = len(stages) - log.info(f"Checkpoint training plan consists of {num_stages} stages.") + logger.info(f"Checkpoint training plan consists of {num_stages} stages.") new_stage = cfg["stage"] @@ -43,20 +43,20 @@ def append_stage(cfg: DictConfig): current_epoch = d["epoch"] stage_epoch = np.sum([s["epochs"] for s in stages[: cfg["replace"]]]) if current_epoch > stage_epoch: - log.info( + logger.info( f"WARNING: Modification to training plan changes a training stage " f"that has already started. Current model epoch is {current_epoch}. " f"Proceed at your own risk!" ) - log.info(f"Replacing planned stage {cfg['replace']} with new stage.") + logger.info(f"Replacing planned stage {cfg['replace']} with new stage.") new_stage_number = cfg["replace"] else: - log.info("Appending new stage to training plan.") + logger.info("Appending new stage to training plan.") new_stage_number = num_stages d["metadata"]["train_settings"]["training"][f"stage_{new_stage_number}"] = new_stage - log.info("Summary of new training plan:") - log.info( + logger.info("Summary of new training plan:") + logger.info( yaml.dump( d["metadata"]["train_settings"]["training"], default_flow_style=False, diff --git a/dingo/gw/transforms/noise_transforms.py b/dingo/gw/transforms/noise_transforms.py index d864980eb..eb9f382ca 100644 --- a/dingo/gw/transforms/noise_transforms.py +++ b/dingo/gw/transforms/noise_transforms.py @@ -137,7 +137,9 @@ class WhitenAndScaleStrain(object): This accounts for frequency binning """ - def __init__(self, scale_factor): + def __init__(self, scale_factor=None, domain=None): + if domain is not None: + scale_factor = domain.noise_std self.scale_factor = scale_factor def __call__(self, input_sample): @@ -195,8 +197,10 @@ class RepackageStrainsAndASDS(object): i = 2: 1 / (asd * 1e23) """ - def __init__(self, ifos, first_index=0): + def __init__(self, ifos, first_index=0, domain=None): self.ifos = ifos + if domain is not None: + first_index = domain.min_idx self.first_index = first_index def __call__(self, input_sample): diff --git a/dingo/gw/transforms/parameter_transforms.py b/dingo/gw/transforms/parameter_transforms.py index aff5889aa..4ab142bbf 100644 --- a/dingo/gw/transforms/parameter_transforms.py +++ b/dingo/gw/transforms/parameter_transforms.py @@ -12,7 +12,10 @@ class SampleExtrinsicParameters(object): def __init__(self, extrinsic_prior_dict): self.extrinsic_prior_dict = extrinsic_prior_dict - self.prior = BBHExtrinsicPriorDict(extrinsic_prior_dict) + if isinstance(extrinsic_prior_dict, BBHExtrinsicPriorDict): + self.prior = extrinsic_prior_dict + else: + self.prior = BBHExtrinsicPriorDict(extrinsic_prior_dict) def __call__(self, input_sample): sample = input_sample.copy() diff --git a/dingo/gw/transforms/waveform_transforms.py b/dingo/gw/transforms/waveform_transforms.py index 7bba751ef..6b59847ad 100644 --- a/dingo/gw/transforms/waveform_transforms.py +++ b/dingo/gw/transforms/waveform_transforms.py @@ -5,7 +5,7 @@ from dingo.gw.domains import MultibandedFrequencyDomain, UniformFrequencyDomain -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) class DecimateAll(object): @@ -440,14 +440,14 @@ def __init__( maximum_frequency: Optional[float | dict[str, float]] Update of f_max. If a float, the same value will be used for all detectors. print_output: bool - Whether to write settings information to the log. + Whether to write settings information to the logger. """ self.sample_frequencies = domain.sample_frequencies self.minimum_frequency = minimum_frequency self.maximum_frequency = maximum_frequency if print_output: - log.info( + logger.info( f"Transform MaskDataForFrequencyRangeUpdate activated:" f" Settings: \n" f" - Minimum_frequency update: {self.minimum_frequency}\n" diff --git a/dingo/gw/waveform_generator/waveform_generator.py b/dingo/gw/waveform_generator/waveform_generator.py index ec6c01d96..0be82ab38 100644 --- a/dingo/gw/waveform_generator/waveform_generator.py +++ b/dingo/gw/waveform_generator/waveform_generator.py @@ -32,7 +32,7 @@ ) from dingo.gw.transforms.waveform_transforms import DecimateAll -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) class WaveformGenerator: @@ -145,12 +145,12 @@ def spin_conversion_phase(self): @spin_conversion_phase.setter def spin_conversion_phase(self, value): if value is None: - log.info( + logger.info( "Setting spin_conversion_phase = None. Using phase parameter for " "conversion to cartesian spins." ) else: - log.info( + logger.info( f"Setting spin_conversion_phase = {value}. Using this value for the " f"phase parameter for conversion to cartesian spins." ) @@ -586,7 +586,7 @@ def generate_FD_waveform( # numbers if multibanding is used. If that happens, turn off multibanding to # fix this. if max(np.max(np.abs(hp.data.data)), np.max(np.abs(hc.data.data))) > 1e-20: - log.warning( + logger.warning( f"Generation with parameters {parameters_lal} likely numerically " f"unstable due to multibanding, turn off multibanding." ) @@ -609,7 +609,7 @@ def generate_FD_waveform( *parameters_lal[lal_dict_idx + 1 :], ) if max(np.max(np.abs(hp.data.data)), np.max(np.abs(hc.data.data))) > 1e-20: - log.warning( + logger.warning( f"Turning off multibanding for parameters {parameters_lal}" f" likely numerically might not have fixed it, check manually." ) @@ -1569,8 +1569,8 @@ def sum_contributions_m(x_m, phase_shift=0.0): if __name__ == "__main__": import pandas as pd import numpy as np + from bilby.gw.prior import BBHPriorDict from dingo.gw.domains import build_domain - from dingo.gw.prior import build_prior_with_defaults domain_settings = { "type": "UniformFrequencyDomain", @@ -1595,7 +1595,7 @@ def sum_contributions_m(x_m, phase_shift=0.0): "phi_jl": 'bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic")', "geocent_time": 0.0, } - prior = build_prior_with_defaults(intrinsic_dict) + prior = BBHPriorDict(intrinsic_dict) p = prior.sample() p = { "mass_ratio": 0.3501852584069329, @@ -1624,7 +1624,7 @@ def sum_contributions_m(x_m, phase_shift=0.0): pol_m = wfg.generate_hplus_hcross_m(p) phase_shift = np.random.uniform(high=2 * np.pi) - log.info(f"{phase_shift:.2f}") + logger.info(f"{phase_shift:.2f}") pol = sum_contributions_m(pol_m, phase_shift=phase_shift) pol_ref = wfg.generate_hplus_hcross({**p, "phase": p["phase"] + phase_shift}) diff --git a/tests/core/test_posterior_model.py b/tests/core/test_posterior_model.py index 1feb4275d..263143095 100644 --- a/tests/core/test_posterior_model.py +++ b/tests/core/test_posterior_model.py @@ -44,7 +44,7 @@ def data_setup_pm_1(): } d.model_kwargs = { - "posterior_model_type": "normalizing_flow", + "_target_": "dingo.core.posterior_models.NormalizingFlowPosteriorModel", "posterior_kwargs": d.posterior_kwargs, "embedding_kwargs": d.embedding_kwargs, } @@ -59,22 +59,31 @@ def data_setup_optimizer_scheduler(): d = types.SimpleNamespace() d.adam_kwargs = { - "type": "adam", + "_target_": "torch.optim.Adam", + "_partial_": True, "lr": 0.0001, } d.sgd_kwargs = { - "type": "sgd", + "_target_": "torch.optim.SGD", + "_partial_": True, "lr": 0.0001, "momentum": 0.9, } - d.step_kwargs = {"type": "step", "step_size": 3, "gamma": 0.5} + d.step_kwargs = { + "_target_": "torch.optim.lr_scheduler.StepLR", + "_partial_": True, + "step_size": 3, + "gamma": 0.5, + } d.cosine_kwargs = { - "type": "cosine", + "_target_": "torch.optim.lr_scheduler.CosineAnnealingLR", + "_partial_": True, "T_max": 10, } d.rop_kwargs = { - "type": "reduce_on_plateau", + "_target_": "torch.optim.lr_scheduler.ReduceLROnPlateau", + "_partial_": True, "factor": 0.5, "patience": 3, } @@ -115,6 +124,7 @@ def test_pm_saving_and_loading_basic(data_setup_pm_1): # load saved model pm_1 = NormalizingFlowPosteriorModel(model_filename=d.model_filename, device="cpu") + assert pm_1.metadata.train_settings.model._target_ == d.model_kwargs["_target_"] # build a model with identical kwargs pm_2 = NormalizingFlowPosteriorModel(metadata=d.metadata, device="cpu") @@ -222,3 +232,22 @@ def test_pm_scheduler(data_setup_pm_1, data_setup_optimizer_scheduler): factors.append(lr / pm.optimizer.defaults["lr"]) torchutils.perform_scheduler_step(pm.scheduler, loss) assert np.allclose(factors, e.cosine_factors), "Scheduler does not load correctly." + + +def test_pm_hydra_optimizer_scheduler(data_setup_pm_1): + d = data_setup_pm_1 + pm = NormalizingFlowPosteriorModel(metadata=d.metadata, device="cpu") + pm.optimizer_kwargs = { + "_target_": "torch.optim.Adam", + "_partial_": True, + "lr": 0.0001, + } + pm.scheduler_kwargs = { + "_target_": "torch.optim.lr_scheduler.CosineAnnealingLR", + "_partial_": True, + "T_max": 10, + } + pm.initialize_optimizer_and_scheduler() + + assert isinstance(pm.optimizer, torch.optim.Adam) + assert isinstance(pm.scheduler, torch.optim.lr_scheduler.CosineAnnealingLR) diff --git a/tests/gw/conversion/test_time_delay_from_geocenter.py b/tests/gw/conversion/test_time_delay_from_geocenter.py index 73884351e..1471a4801 100644 --- a/tests/gw/conversion/test_time_delay_from_geocenter.py +++ b/tests/gw/conversion/test_time_delay_from_geocenter.py @@ -5,13 +5,19 @@ from bilby.gw.detector import InterferometerList from dingo.gw.transforms import time_delay_from_geocenter -from dingo.gw.prior import BBHExtrinsicPriorDict, default_extrinsic_dict +from dingo.gw.prior import BBHExtrinsicPriorDict + + +DEFAULT_EXTRINSIC_DICT = { + "dec": "bilby.core.prior.Cosine(minimum=-np.pi/2, maximum=np.pi/2, name='dec')", + "ra": 'bilby.core.prior.Uniform(minimum=0., maximum=2*np.pi, boundary="periodic", name="ra")', +} @pytest.fixture def ifo_list_and_prior_and_tref(): ifo_list = InterferometerList(["H1", "L1", "V1"]) - prior = BBHExtrinsicPriorDict({k: default_extrinsic_dict[k] for k in ["ra", "dec"]}) + prior = BBHExtrinsicPriorDict(DEFAULT_EXTRINSIC_DICT) tref = random.randint( 946339215, 1577491218 ) # random gps time between 2010 and 2030 diff --git a/tests/gw/test_mfd.py b/tests/gw/test_mfd.py index be15f22ad..dab4db9b6 100644 --- a/tests/gw/test_mfd.py +++ b/tests/gw/test_mfd.py @@ -67,6 +67,25 @@ def test_mfd_domain_dict(mfd): np.testing.assert_equal(mfd.__dict__, domain2.__dict__) +def test_mfd_hydra_target(mfd_params): + settings = { + "_target_": "dingo.gw.domains.MultibandedFrequencyDomain", + **mfd_params, + "base_domain": { + "_target_": "dingo.gw.domains.UniformFrequencyDomain", + **mfd_params["base_domain"], + }, + } + settings["base_domain"].pop("type") + + domain = build_domain(settings) + domain_expected = MultibandedFrequencyDomain(**mfd_params) + + assert isinstance(domain, MultibandedFrequencyDomain) + assert type(domain.base_domain) is type(domain_expected.base_domain) + np.testing.assert_equal(domain.domain_dict, domain_expected.domain_dict) + + def test_mfd_set_new_range(mfd_params, mfd): domain = mfd # test that ValueErrors are raised for infeasible inputs @@ -182,4 +201,3 @@ def test_mfd_time_translation_torch(mfd): # TODO: Is there a way to improve on this? assert torch.allclose(result[..., 1, :], torch.tensor(0.0), atol=1e-2) assert torch.allclose(result[..., 2, :], torch.tensor(constant_value)) - diff --git a/tests/gw/test_prior_split.py b/tests/gw/test_prior_split.py index 3a307ee2e..741df5da1 100644 --- a/tests/gw/test_prior_split.py +++ b/tests/gw/test_prior_split.py @@ -1,6 +1,15 @@ import numpy as np import pytest -from dingo.gw.prior import BBHExtrinsicPriorDict, BBHPriorDict, default_extrinsic_dict, default_intrinsic_dict +from dingo.gw.prior import BBHExtrinsicPriorDict, BBHPriorDict + + +DEFAULT_EXTRINSIC_DICT = { + "dec": "bilby.core.prior.Cosine(minimum=-np.pi/2, maximum=np.pi/2, name='dec')", + "ra": 'bilby.core.prior.Uniform(minimum=0., maximum=2*np.pi, boundary="periodic", name="ra")', + "geocent_time": "bilby.core.prior.Uniform(minimum=-0.1, maximum=0.1, name='geocent_time')", + "psi": 'bilby.core.prior.Uniform(minimum=0.0, maximum=np.pi, boundary="periodic", name="psi")', + "luminosity_distance": "bilby.core.prior.Uniform(minimum=100.0, maximum=6000.0, name='luminosity_distance')", +} def test_prior_constraint(): @@ -18,7 +27,7 @@ def test_mean_std(): num_samples = 100000 eps = 0.01 keys = ['ra', 'dec', 'luminosity_distance'] - prior = BBHExtrinsicPriorDict(default_extrinsic_dict) + prior = BBHExtrinsicPriorDict(DEFAULT_EXTRINSIC_DICT) mean_exact, std_exact = prior.mean_std(keys) mean_approx, std_approx = prior.mean_std(keys, sample_size=num_samples, diff --git a/tests/gw/test_ufd.py b/tests/gw/test_ufd.py index 5e4157f94..58c0b1d04 100644 --- a/tests/gw/test_ufd.py +++ b/tests/gw/test_ufd.py @@ -40,6 +40,19 @@ def test_FD_domain_dict(uniform_FD_params): assert domain.__dict__ == domain2.__dict__ +def test_FD_hydra_target(uniform_FD_params): + settings = { + "_target_": "dingo.gw.domains.UniformFrequencyDomain", + **uniform_FD_params, + } + domain = build_domain(settings) + assert isinstance(domain, UniformFrequencyDomain) + assert domain.domain_dict == { + "type": "UniformFrequencyDomain", + **uniform_FD_params, + } + + def test_FD_update_data(uniform_FD_params): p = uniform_FD_params domain = UniformFrequencyDomain(**p) @@ -163,4 +176,3 @@ def test_FD_caching(uniform_FD_params): # after clearing the cache, the __call__ method should return the correct # result assert len(domain()) < len(domain_ref()) - diff --git a/tests/gw/test_waveform_dataset.py b/tests/gw/test_waveform_dataset.py index d5150f5c6..9fa129fdd 100644 --- a/tests/gw/test_waveform_dataset.py +++ b/tests/gw/test_waveform_dataset.py @@ -1,58 +1,55 @@ import os import tempfile import uuid +import copy from pathlib import Path from typing import Generator -import yaml import numpy as np import pytest import bilby -from dingo.gw.dataset.generate_dataset import _generate_dataset_main from dingo.gw.dataset.waveform_dataset import WaveformDataset from dingo.gw.domains import Domain from dingo.gw.dataset import generate_dataset -SETTINGS_YAML_SMALL = """\ -# settings for domain of waveforms -domain: - type: UniformFrequencyDomain - f_min: 10.0 - f_max: 1024.0 - delta_f: 1.0 - -# settings for waveform generator -waveform_generator: - approximant: IMRPhenomPv2 - f_ref: 20.0 - -# settings for intrinsic prior over parameters -intrinsic_prior: - # prior for non-fixed parameters - mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1') - mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2') - mass_ratio: bilby.core.prior.Uniform(minimum=0.125, maximum=1.0, name='mass_ratio') - chirp_mass: bilby.core.prior.Uniform(minimum=25.0, maximum=100.0, name='chirp_mass') - phase: default - a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_1') - a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_2') - tilt_1: default - tilt_2: default - phi_12: default - phi_jl: default - theta_jn: default - # reference values for fixed (extrinsic) parameters - luminosity_distance: 100.0 # Mpc - geocent_time: 0.0 # s - -num_samples: 50 - -compression: - svd: - num_training_samples: 10 - size: 5 -""" +SETTINGS_SMALL = { + "domain": { + "_target_": "dingo.gw.domains.UniformFrequencyDomain", + "f_min": 10.0, + "f_max": 1024.0, + "delta_f": 1.0, + }, + "waveform_generator": { + "_target_": "dingo.gw.waveform_generator.WaveformGenerator", + "approximant": "IMRPhenomPv2", + "f_ref": 20.0, + "f_start": None, + "spin_conversion_phase": 0.0, + }, + "intrinsic_prior": { + "_target_": "bilby.gw.prior.BBHPriorDict", + "_convert_": "all", + "dictionary": { + "mass_1": "bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1')", + "mass_2": "bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2')", + "mass_ratio": "bilby.core.prior.Uniform(minimum=0.125, maximum=1.0, name='mass_ratio')", + "chirp_mass": "bilby.core.prior.Uniform(minimum=25.0, maximum=100.0, name='chirp_mass')", + "phase": 'bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phase")', + "a_1": "bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_1')", + "a_2": "bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_2')", + "tilt_1": "bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_1')", + "tilt_2": "bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_2')", + "phi_12": 'bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_12")', + "phi_jl": 'bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_jl")', + "theta_jn": "bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='theta_jn')", + "luminosity_distance": 100.0, + "geocent_time": 0.0, + }, + }, + "num_samples": 50, + "compression": None, +} @pytest.fixture(scope="session") @@ -75,17 +72,14 @@ def generate_waveform_dataset_small(temp_dir: Path) -> Path: in a temporary directory. """ - # Create temp directory and settings file + # Create temp directory and dataset file path = temp_dir / "tmp_test" / str(uuid.uuid4()) path.mkdir(parents=True) - settings_path = path / "settings.yaml" out_file = path / "waveform_dataset.hdf5" num_processes = 4 - with open(settings_path, "w") as fp: - fp.writelines(SETTINGS_YAML_SMALL) - - _generate_dataset_main(str(settings_path), str(out_file), num_processes) + dataset = generate_dataset(SETTINGS_SMALL, num_processes) + dataset.to_file(str(out_file)) return path @@ -228,8 +222,7 @@ def test_load_waveform_dataset_with_leave_polarizations_on_disk( @pytest.fixture def wfd_settings(): - settings = yaml.safe_load(SETTINGS_YAML_SMALL) - return settings + return copy.deepcopy(SETTINGS_SMALL) class BinaryPrior(bilby.prior.Prior): @@ -254,9 +247,9 @@ def test_wfd_size(wfd_settings: str, binary_prior: BinaryPrior): # changing the waveform generator settings to create a prior which will create # failing waveforms for a fraction of the prior. I.e. can't generate negative # chirp masses so the waveform generator will fail - wfd_settings["intrinsic_prior"]["chirp_mass"] = binary_prior - del wfd_settings["intrinsic_prior"]["mass_1"] - del wfd_settings["intrinsic_prior"]["mass_2"] + wfd_settings["intrinsic_prior"]["dictionary"]["chirp_mass"] = binary_prior + del wfd_settings["intrinsic_prior"]["dictionary"]["mass_1"] + del wfd_settings["intrinsic_prior"]["dictionary"]["mass_2"] del wfd_settings["compression"] wfd = generate_dataset(wfd_settings, 1) assert len(wfd) == wfd_settings["num_samples"] diff --git a/tests/gw/transforms/test_batch_transforms.py b/tests/gw/transforms/test_batch_transforms.py index e0556871b..ad782da28 100644 --- a/tests/gw/transforms/test_batch_transforms.py +++ b/tests/gw/transforms/test_batch_transforms.py @@ -17,11 +17,19 @@ RepackageStrainsAndASDS, UnpackDict, ) -from dingo.gw.prior import default_extrinsic_dict from dingo.gw.domains import UniformFrequencyDomain from dingo.gw.noise.asd_dataset import ASDDataset +DEFAULT_EXTRINSIC_DICT = { + "dec": "bilby.core.prior.Cosine(minimum=-np.pi/2, maximum=np.pi/2, name='dec')", + "ra": 'bilby.core.prior.Uniform(minimum=0., maximum=2*np.pi, boundary="periodic", name="ra")', + "geocent_time": "bilby.core.prior.Uniform(minimum=-0.1, maximum=0.1, name='geocent_time')", + "psi": 'bilby.core.prior.Uniform(minimum=0.0, maximum=np.pi, boundary="periodic", name="psi")', + "luminosity_distance": "bilby.core.prior.Uniform(minimum=100.0, maximum=6000.0, name='luminosity_distance')", +} + + @pytest.fixture def standardization_dict(): return { @@ -96,7 +104,7 @@ def transform_list(standardization_dict, asd_dataset, domain): ifo_list = InterferometerList(["H1", "L1"]) ref_time = 1126259462.391 transforms = [ - SampleExtrinsicParameters(default_extrinsic_dict), + SampleExtrinsicParameters(DEFAULT_EXTRINSIC_DICT), GetDetectorTimes(ifo_list, ref_time), GNPECoalescenceTimes(ifo_list, Uniform(minimum=-0.001, maximum=0.001), True), ProjectOntoDetectors(ifo_list, domain, ref_time), @@ -129,4 +137,4 @@ def test_unbatched_training_transforms(transform_list, input_sample_unbatched): transforms = torchvision.transforms.Compose(transform_list) output = transforms(input_sample_unbatched) assert output[0].ndim == 1 - assert output[1].ndim == 3 \ No newline at end of file + assert output[1].ndim == 3 diff --git a/tests/gw/transforms/test_detector_projection.py b/tests/gw/transforms/test_detector_projection.py index 267dc014f..f9bf6a82e 100644 --- a/tests/gw/transforms/test_detector_projection.py +++ b/tests/gw/transforms/test_detector_projection.py @@ -9,10 +9,18 @@ SampleExtrinsicParameters, time_delay_from_geocenter, ) -from dingo.gw.prior import default_extrinsic_dict from dingo.gw.domains import build_domain +DEFAULT_EXTRINSIC_DICT = { + "dec": "bilby.core.prior.Cosine(minimum=-np.pi/2, maximum=np.pi/2, name='dec')", + "ra": 'bilby.core.prior.Uniform(minimum=0., maximum=2*np.pi, boundary="periodic", name="ra")', + "geocent_time": "bilby.core.prior.Uniform(minimum=-0.1, maximum=0.1, name='geocent_time')", + "psi": 'bilby.core.prior.Uniform(minimum=0.0, maximum=np.pi, boundary="periodic", name="psi")', + "luminosity_distance": "bilby.core.prior.Uniform(minimum=100.0, maximum=6000.0, name='luminosity_distance')", +} + + @pytest.fixture def reference_data_research_code(): dir = os.path.dirname(os.path.realpath(__file__)) @@ -30,7 +38,7 @@ def reference_data_research_code(): @pytest.fixture def setup_detector_projection(): # setup arguments - extrinsic_prior_dict = default_extrinsic_dict + extrinsic_prior_dict = DEFAULT_EXTRINSIC_DICT ref_time = 1126259462.391 domain_dict = { "type": "UniformFrequencyDomain", diff --git a/tests/gw/waveform_generator/test_wfg_m.py b/tests/gw/waveform_generator/test_wfg_m.py index cc8769939..d4e32e11d 100644 --- a/tests/gw/waveform_generator/test_wfg_m.py +++ b/tests/gw/waveform_generator/test_wfg_m.py @@ -14,6 +14,7 @@ import pytest import numpy as np from matplotlib import pyplot as plt +from bilby.gw.prior import BBHPriorDict from dingo.gw.waveform_generator import ( WaveformGenerator, @@ -22,7 +23,6 @@ ) from dingo.gw.gwutils import get_mismatch from dingo.gw.domains import build_domain -from dingo.gw.prior import build_prior_with_defaults @pytest.fixture @@ -75,7 +75,7 @@ def intrinsic_prior(approximant): "chi_2": 'bilby.gw.prior.AlignedSpin(name="chi_2", a_prior=Uniform(minimum=0, maximum=0.99))', "geocent_time": 0.0, } - prior = build_prior_with_defaults(intrinsic_dict) + prior = BBHPriorDict(intrinsic_dict) return prior diff --git a/tests/gw/waveform_generator/test_wfg_mfd.py b/tests/gw/waveform_generator/test_wfg_mfd.py index 297df73bd..1157677d1 100644 --- a/tests/gw/waveform_generator/test_wfg_mfd.py +++ b/tests/gw/waveform_generator/test_wfg_mfd.py @@ -1,10 +1,10 @@ import numpy as np import pytest +from bilby.gw.prior import BBHPriorDict from scipy.interpolate import interp1d from dingo.gw.domains import MultibandedFrequencyDomain from dingo.gw.gwutils import get_mismatch -from dingo.gw.prior import build_prior_with_defaults from dingo.gw.waveform_generator import WaveformGenerator, NewInterfaceWaveformGenerator @@ -47,7 +47,7 @@ def intrinsic_prior(approximant): "phi_jl": 'bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic")', "geocent_time": "bilby.core.prior.Uniform(minimum=-0.1, maximum=0.1)", } - prior = build_prior_with_defaults(intrinsic_dict) + prior = BBHPriorDict(intrinsic_dict) return prior diff --git a/tests/test_hydra_config_targets.py b/tests/test_hydra_config_targets.py new file mode 100644 index 000000000..b0904812b --- /dev/null +++ b/tests/test_hydra_config_targets.py @@ -0,0 +1,260 @@ +from pathlib import Path + +import numpy as np +import pandas as pd +import pytest +import torch +from bilby.gw.prior import BBHPriorDict +from hydra import compose, initialize_config_dir +from hydra.utils import instantiate +from omegaconf import OmegaConf + +from dingo.core.utils import get_optimizer_from_kwargs, get_scheduler_from_kwargs +from dingo.gw.SVD import SVDBasis +from dingo.gw.dataset import WaveformDataset +from dingo.gw.domains import ( + MultibandedFrequencyDomain, + UniformFrequencyDomain, + build_domain, +) +from dingo.gw.training.train_builders import set_train_transforms +from dingo.gw.training.train_pipeline import _populate_parameter_standardization +from dingo.gw.waveform_generator import WaveformGenerator + + +def compose_dingo_config(config_name: str, overrides: list[str] | None = None): + config_dir = str((Path(__file__).parents[1] / "configs").resolve()) + with initialize_config_dir(version_base=None, config_dir=config_dir): + return compose(config_name=config_name, overrides=overrides or []) + + +@pytest.mark.parametrize( + ("domain_group", "expected_type"), + [ + ("uniform_frequency", UniformFrequencyDomain), + ("multibanded_frequency", MultibandedFrequencyDomain), + ], +) +def test_domain_config_targets_instantiate(domain_group, expected_type): + cfg = compose_dingo_config("generate_dataset", overrides=[f"domain={domain_group}"]) + domain_settings = OmegaConf.to_container(cfg.domain, resolve=True) + + assert isinstance(build_domain(domain_settings), expected_type) + + +def test_waveform_generator_config_target_instantiates_with_domain(): + cfg = compose_dingo_config("generate_dataset") + domain = instantiate(cfg.domain) + + waveform_generator = instantiate(cfg.waveform_generator, domain=domain) + + assert isinstance(waveform_generator, WaveformGenerator) + assert waveform_generator.domain is domain + + +@pytest.mark.parametrize( + "prior_group", + ["default", "precessing", "precessing_multibanded_test", "fmpe"], +) +def test_intrinsic_prior_config_targets_instantiate(prior_group): + cfg = compose_dingo_config( + "generate_dataset", overrides=[f"intrinsic_prior={prior_group}"] + ) + + prior = instantiate(cfg.intrinsic_prior) + + assert isinstance(prior, BBHPriorDict) + assert "chirp_mass" in prior + assert "mass_ratio" in prior + + +@pytest.mark.parametrize("prior_group", ["default", "fmpe"]) +def test_extrinsic_prior_configs_build_prior_dict(prior_group): + cfg = compose_dingo_config("train", overrides=[f"extrinsic_prior={prior_group}"]) + + prior = instantiate(cfg.data.extrinsic_prior) + + assert set(prior) == { + "dec", + "ra", + "geocent_time", + "psi", + "luminosity_distance", + } + + +@pytest.mark.parametrize( + "model_group", + ["toy_npe", "npe", "gnpe", "gnpe_init", "fmpe", "unconditional_npe"], +) +def test_model_configs_define_posterior_model_target(model_group): + cfg = compose_dingo_config("train", overrides=[f"model={model_group}"]) + + assert cfg.model._target_.startswith("dingo.core.posterior_models.") + assert "posterior_model_type" not in cfg.model + + +@pytest.mark.parametrize( + "experiment", + ["train_toy", "train_npe", "train_gnpe", "train_gnpe_init", "train_fmpe"], +) +def test_training_experiment_stage_optimizers_are_hydra_targets(experiment): + cfg = compose_dingo_config("train", overrides=[f"+experiment={experiment}"]) + + for stage_name, stage in cfg.training.items(): + if not stage_name.startswith("stage_"): + continue + assert "_target_" in stage.optimizer + assert "_partial_" in stage.optimizer + assert "_target_" in stage.scheduler + assert "_partial_" in stage.scheduler + + +@pytest.mark.parametrize( + ("experiment", "expected_targets"), + [ + ( + None, + [ + "dingo.gw.transforms.SampleExtrinsicParameters", + "dingo.gw.transforms.GetDetectorTimes", + ], + ), + ( + "train_gnpe", + [ + "dingo.gw.transforms.SampleExtrinsicParameters", + "dingo.gw.transforms.GetDetectorTimes", + "dingo.gw.transforms.GNPECoalescenceTimes", + ], + ), + ], +) +def test_training_standardization_transforms_are_configured(experiment, expected_targets): + overrides = [] if experiment is None else [f"+experiment={experiment}"] + cfg = compose_dingo_config("train", overrides=overrides) + + assert [t._target_ for t in cfg.data.standardization_transforms] == expected_targets + + if experiment == "train_gnpe": + gnpe_transform = instantiate(cfg.data.standardization_transforms[-1]) + assert list(cfg.data.context_parameters) == gnpe_transform.context_parameters + + +def test_optimizer_scheduler_config_targets_instantiate(): + cfg = compose_dingo_config("train") + network = torch.nn.Linear(2, 1) + optimizer_settings = OmegaConf.to_container( + cfg.training.stage_0.optimizer, resolve=True + ) + scheduler_settings = OmegaConf.to_container( + cfg.training.stage_0.scheduler, resolve=True + ) + + optimizer = get_optimizer_from_kwargs( + network.parameters(), + **optimizer_settings, + ) + scheduler = get_scheduler_from_kwargs(optimizer, **scheduler_settings) + + assert isinstance(optimizer, torch.optim.Adam) + assert isinstance(scheduler, torch.optim.lr_scheduler.CosineAnnealingLR) + + +def test_svd_basis_load_target_instantiates(tmp_path): + file_name = tmp_path / "svd.hdf5" + basis = SVDBasis() + basis.generate_basis(np.eye(4), 2) + basis.to_file(str(file_name)) + + loaded = instantiate( + { + "_target_": "dingo.gw.dataset.compression.load_svd_basis", + "file_name": str(file_name), + } + ) + + assert isinstance(loaded, SVDBasis) + assert loaded.V.shape == (4, 2) + + +def test_training_standardization_is_computed_before_transform_instantiation(): + cfg = compose_dingo_config( + "train", + overrides=[ + "data.inference_parameters=[chirp_mass,mass_ratio]", + "model.embedding_kwargs.svd.num_training_samples=0", + ], + ) + settings = OmegaConf.to_container(cfg, resolve=True) + + domain_config = { + "_target_": "dingo.gw.domains.UniformFrequencyDomain", + "f_min": 0.0, + "f_max": 4.0, + "delta_f": 1.0, + } + domain = instantiate(domain_config) + wfd = WaveformDataset( + dictionary={ + "settings": {"domain": domain_config, "compression": None}, + "parameters": pd.DataFrame( + { + "chirp_mass": [30.0, 31.0, 32.0], + "mass_ratio": [0.5, 0.6, 0.7], + } + ), + "polarizations": { + "h_plus": np.zeros((3, len(domain)), dtype=np.complex64), + "h_cross": np.zeros((3, len(domain)), dtype=np.complex64), + }, + } + ) + settings["training"]["stage_0"]["asd_dataset"] = { + "_target_": "dingo.gw.noise.asd_dataset.ASDDataset", + "dictionary": { + "settings": {"domain_dict": domain.domain_dict}, + "asds": { + "H1": np.ones((2, len(domain))), + "L1": np.ones((2, len(domain))), + }, + "gps_times": { + "H1": np.arange(2), + "L1": np.arange(2), + }, + }, + "ifos": ["H1", "L1"], + "precision": "single", + "domain_update": None, + } + + _populate_parameter_standardization( + wfd, + settings["data"], + settings["training"]["stage_0"]["asd_dataset"], + ) + set_train_transforms( + wfd, + settings["data"], + settings["training"]["stage_0"]["asd_dataset"], + ) + + assert settings["data"]["standardization"]["mean"] == { + "chirp_mass": 31.0, + "mass_ratio": 0.6, + } + assert list(settings["data"]["standardization"]["std"]) == [ + "chirp_mass", + "mass_ratio", + ] + assert [type(t).__name__ for t in wfd.transform.transforms] == [ + "SampleExtrinsicParameters", + "GetDetectorTimes", + "ProjectOntoDetectors", + "SampleNoiseASD", + "WhitenAndScaleStrain", + "AddWhiteNoiseComplex", + "SelectStandardizeRepackageParameters", + "RepackageStrainsAndASDS", + "UnpackDict", + ] From 2e821ae476ad6eb66d6aae7708746e7c1e65d4f1 Mon Sep 17 00:00:00 2001 From: Heinrich Campe <49278231+hcampe@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:02:41 +0200 Subject: [PATCH 15/15] simplified configs --- configs/append_training_stage.yaml | 33 ----- configs/estimate_psds.yaml | 33 ----- configs/evaluate_multibanded_domain.yaml | 13 -- configs/experiment/asd_fiducial.yaml | 13 -- configs/experiment/asd_full.yaml | 13 -- configs/experiment/generate_fmpe_dataset.yaml | 30 ---- configs/experiment/generate_gnpe_dataset.yaml | 30 ---- configs/experiment/generate_npe_dataset.yaml | 30 ---- configs/experiment/generate_toy_dataset.yaml | 1 - configs/experiment/importance_sampling.yaml | 16 -- configs/experiment/synthetic_asd.yaml | 17 --- configs/experiment/train_fmpe.yaml | 78 ---------- configs/experiment/train_gnpe.yaml | 139 ------------------ configs/experiment/train_gnpe_init.yaml | 70 --------- configs/experiment/train_npe.yaml | 78 ---------- configs/extrinsic_prior/default.yaml | 8 - configs/extrinsic_prior/fmpe.yaml | 8 - configs/generate_asd_dataset.yaml | 12 +- configs/generate_dataset.yaml | 25 +++- configs/generate_synthetic_asd_dataset.yaml | 33 ----- configs/hydra/default.yaml | 11 -- configs/importance_weights.yaml | 47 ------ configs/intrinsic_prior/default.yaml | 13 -- configs/intrinsic_prior/fmpe.yaml | 17 --- configs/intrinsic_prior/precessing.yaml | 17 --- .../precessing_multibanded_test.yaml | 17 --- configs/merge_asd_datasets.yaml | 35 ----- configs/model/unconditional_npe.yaml | 12 -- configs/optimizer/adam.yaml | 3 - configs/scheduler/cosine.yaml | 3 - configs/train.yaml | 32 +++- configs/unconditional_density_estimation.yaml | 29 ---- 32 files changed, 59 insertions(+), 857 deletions(-) delete mode 100644 configs/append_training_stage.yaml delete mode 100644 configs/estimate_psds.yaml delete mode 100644 configs/evaluate_multibanded_domain.yaml delete mode 100644 configs/experiment/asd_fiducial.yaml delete mode 100644 configs/experiment/asd_full.yaml delete mode 100644 configs/experiment/generate_fmpe_dataset.yaml delete mode 100644 configs/experiment/generate_gnpe_dataset.yaml delete mode 100644 configs/experiment/generate_npe_dataset.yaml delete mode 100644 configs/experiment/importance_sampling.yaml delete mode 100644 configs/experiment/synthetic_asd.yaml delete mode 100644 configs/experiment/train_fmpe.yaml delete mode 100644 configs/experiment/train_gnpe.yaml delete mode 100644 configs/experiment/train_gnpe_init.yaml delete mode 100644 configs/experiment/train_npe.yaml delete mode 100644 configs/extrinsic_prior/default.yaml delete mode 100644 configs/extrinsic_prior/fmpe.yaml delete mode 100644 configs/generate_synthetic_asd_dataset.yaml delete mode 100644 configs/hydra/default.yaml delete mode 100644 configs/importance_weights.yaml delete mode 100644 configs/intrinsic_prior/default.yaml delete mode 100644 configs/intrinsic_prior/fmpe.yaml delete mode 100644 configs/intrinsic_prior/precessing.yaml delete mode 100644 configs/intrinsic_prior/precessing_multibanded_test.yaml delete mode 100644 configs/merge_asd_datasets.yaml delete mode 100644 configs/model/unconditional_npe.yaml delete mode 100644 configs/optimizer/adam.yaml delete mode 100644 configs/scheduler/cosine.yaml delete mode 100644 configs/unconditional_density_estimation.yaml diff --git a/configs/append_training_stage.yaml b/configs/append_training_stage.yaml deleted file mode 100644 index 1dca1ca82..000000000 --- a/configs/append_training_stage.yaml +++ /dev/null @@ -1,33 +0,0 @@ -defaults: - - hydra: default - - optimizer: adam - - scheduler: cosine - - _self_ - -checkpoint: ??? -out_file: updated_model.pt -replace: null - -stage: - optimizer: ${optimizer} - scheduler: ${scheduler} - epochs: 20 - asd_dataset: - _target_: dingo.gw.noise.asd_dataset.ASDDataset - file_name: training_data/asd_dataset/asds_O1.hdf5 - ifos: null - precision: single - domain_update: null - freeze_rb_layer: false - batch_size: 64 - early_stopping: null - -hydra: - job: - name: append_training_stage - config: - override_dirname: - exclude_keys: - - checkpoint - - out_file - - stage.asd_dataset.file_name diff --git a/configs/estimate_psds.yaml b/configs/estimate_psds.yaml deleted file mode 100644 index 36e58ca2f..000000000 --- a/configs/estimate_psds.yaml +++ /dev/null @@ -1,33 +0,0 @@ -defaults: - - hydra: default - - _self_ - -data_dir: asd_dataset -time_segments_file: ??? -verbose: false - -dataset_settings: - f_min: 0 - f_max: 2048 - f_s: 4096 - time_psd: 1024 - T: 8.0 - window: - roll_off: 0.4 - type: tukey - time_gap: 0 - num_psds_max: 1 - channels: null - detectors: - - H1 - - L1 - observing_run: O1 - -hydra: - job: - name: estimate_psds - config: - override_dirname: - exclude_keys: - - data_dir - - time_segments_file diff --git a/configs/evaluate_multibanded_domain.yaml b/configs/evaluate_multibanded_domain.yaml deleted file mode 100644 index f372aa01f..000000000 --- a/configs/evaluate_multibanded_domain.yaml +++ /dev/null @@ -1,13 +0,0 @@ -defaults: - - hydra: default - - domain: multibanded_frequency - - waveform_generator: phenom_xphm - - intrinsic_prior: precessing_multibanded_test - - _self_ - -num_samples: 1 -compression: null - -hydra: - job: - name: evaluate_multibanded_domain diff --git a/configs/experiment/asd_fiducial.yaml b/configs/experiment/asd_fiducial.yaml deleted file mode 100644 index d69d57528..000000000 --- a/configs/experiment/asd_fiducial.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# @package _global_ - -dataset_settings: - f_s: 4096 - time_psd: 1024 - T: 8.0 - window: - roll_off: 0.4 - type: tukey - time_gap: 0 - num_psds_max: 1 - detectors: [H1, L1] - observing_run: O1 diff --git a/configs/experiment/asd_full.yaml b/configs/experiment/asd_full.yaml deleted file mode 100644 index ca2a2704b..000000000 --- a/configs/experiment/asd_full.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# @package _global_ - -dataset_settings: - f_s: 4096 - time_psd: 1024 - T: 8.0 - window: - roll_off: 0.4 - type: tukey - time_gap: 0 - num_psds_max: 0 - detectors: [H1, L1] - observing_run: O1 diff --git a/configs/experiment/generate_fmpe_dataset.yaml b/configs/experiment/generate_fmpe_dataset.yaml deleted file mode 100644 index 308d51fd0..000000000 --- a/configs/experiment/generate_fmpe_dataset.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# @package _global_ - -defaults: - - override /domain: uniform_frequency - - override /waveform_generator: phenom_pv2 - - override /intrinsic_prior: fmpe - - _self_ - -num_samples: 5000000 -compression: - - _target_: dingo.gw.transforms.WhitenFixedASD - _partial_: true - _runtime_dependencies_: [domain] - asd_file: aLIGO_ZERO_DET_high_P_asd.txt - inverse: false - - _target_: dingo.gw.SVD.ApplySVD - svd_basis: - _target_: dingo.gw.dataset.compression.train_svd_basis_from_waveforms - _partial_: true - _runtime_dependencies_: - - waveform_generator - - prior - - num_processes - - settings - - compression_transforms - - compression_settings - size: 200 - num_training_samples: 50000 - num_validation_samples: 10000 -out_file: training_data/waveform_dataset.hdf5 diff --git a/configs/experiment/generate_gnpe_dataset.yaml b/configs/experiment/generate_gnpe_dataset.yaml deleted file mode 100644 index 4f54a2bb8..000000000 --- a/configs/experiment/generate_gnpe_dataset.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# @package _global_ - -defaults: - - override /domain: uniform_frequency - - override /waveform_generator: phenom_xphm - - override /intrinsic_prior: precessing - - _self_ - -num_samples: 5000000 -compression: - - _target_: dingo.gw.transforms.WhitenFixedASD - _partial_: true - _runtime_dependencies_: [domain] - asd_file: aLIGO_ZERO_DET_high_P_asd.txt - inverse: false - - _target_: dingo.gw.SVD.ApplySVD - svd_basis: - _target_: dingo.gw.dataset.compression.train_svd_basis_from_waveforms - _partial_: true - _runtime_dependencies_: - - waveform_generator - - prior - - num_processes - - settings - - compression_transforms - - compression_settings - size: 200 - num_training_samples: 50000 - num_validation_samples: 10000 -out_file: training_data/waveform_dataset.hdf5 diff --git a/configs/experiment/generate_npe_dataset.yaml b/configs/experiment/generate_npe_dataset.yaml deleted file mode 100644 index 44d952bf8..000000000 --- a/configs/experiment/generate_npe_dataset.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# @package _global_ - -defaults: - - override /domain: uniform_frequency - - override /waveform_generator: phenom_xphm - - override /intrinsic_prior: default - - _self_ - -num_samples: 5000000 -compression: - - _target_: dingo.gw.transforms.WhitenFixedASD - _partial_: true - _runtime_dependencies_: [domain] - asd_file: aLIGO_ZERO_DET_high_P_asd.txt - inverse: false - - _target_: dingo.gw.SVD.ApplySVD - svd_basis: - _target_: dingo.gw.dataset.compression.train_svd_basis_from_waveforms - _partial_: true - _runtime_dependencies_: - - waveform_generator - - prior - - num_processes - - settings - - compression_transforms - - compression_settings - size: 200 - num_training_samples: 50000 - num_validation_samples: 10000 -out_file: training_data/waveform_dataset.hdf5 diff --git a/configs/experiment/generate_toy_dataset.yaml b/configs/experiment/generate_toy_dataset.yaml index 386dac175..65b0281bf 100644 --- a/configs/experiment/generate_toy_dataset.yaml +++ b/configs/experiment/generate_toy_dataset.yaml @@ -3,7 +3,6 @@ defaults: - override /domain: uniform_frequency - override /waveform_generator: phenom_d - - override /intrinsic_prior: default - _self_ domain: diff --git a/configs/experiment/importance_sampling.yaml b/configs/experiment/importance_sampling.yaml deleted file mode 100644 index 0ab8eb7c2..000000000 --- a/configs/experiment/importance_sampling.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# @package _global_ - -num_samples: 1000 -time_marginalization: - n_fft: 5 -slice_plots: - num_slice_plots: 10 - params_slice2d: - - [phase, geocent_time] - - [phase, tilt_1] -calibration_marginalization: - num_calibration_curves: 100 - num_calibration_nodes: 10 - calibration_envelope: - H1: /path/to/H1-calibration-envelope - L1: /path/to/L1-calibration-envelope diff --git a/configs/experiment/synthetic_asd.yaml b/configs/experiment/synthetic_asd.yaml deleted file mode 100644 index 91782b5bc..000000000 --- a/configs/experiment/synthetic_asd.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# @package _global_ - -parameterization_settings: - num_spline_positions: 30 - num_spectral_segments: 400 - sigma: 0.14 - delta_f: -1 - smoothen: true -sampling_settings: - bandwidth_spectral: 0.5 - bandwidth_spline: 0.25 - split_frequencies: - - 30 - - 100 - rescaling_asd_paths: - H1: /path/to/rescaling_asd_H1.hdf5 - L1: /path/to/rescaling_asd_L1.hdf5 diff --git a/configs/experiment/train_fmpe.yaml b/configs/experiment/train_fmpe.yaml deleted file mode 100644 index 8685b7f99..000000000 --- a/configs/experiment/train_fmpe.yaml +++ /dev/null @@ -1,78 +0,0 @@ -# @package _global_ - -defaults: - - override /extrinsic_prior: fmpe - - override /model: fmpe - - _self_ - -data: - waveform_dataset: - file_name: training_data/waveform_dataset.hdf5 - domain_update: - f_min: 20.0 - f_max: 512.0 - svd_size_update: 150 - train_fraction: 0.95 - detectors: [H1, L1] - ref_time: 1126259462.391 - inference_parameters: - - chirp_mass - - mass_ratio - - a_1 - - a_2 - - tilt_1 - - tilt_2 - - phi_12 - - phi_jl - - theta_jn - - luminosity_distance - - geocent_time - - ra - - dec - - psi - -training: - evaluate: true - stage_0: - epochs: 400 - asd_dataset: - _target_: dingo.gw.noise.asd_dataset.ASDDataset - file_name: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 - ifos: ${data.detectors} - precision: single - domain_update: null - freeze_rb_layer: true - batch_size: 4096 - early_stopping: null - optimizer: - lr: 0.0005 - scheduler: - T_max: 400 - stage_1: - epochs: 100 - asd_dataset: - _target_: dingo.gw.noise.asd_dataset.ASDDataset - file_name: training_data/asd_dataset/asds_O1.hdf5 - ifos: ${data.detectors} - precision: single - domain_update: null - freeze_rb_layer: true - batch_size: 4096 - early_stopping: null - optimizer: - _target_: torch.optim.Adam - _partial_: true - lr: 1.0e-5 - scheduler: - _target_: torch.optim.lr_scheduler.CosineAnnealingLR - _partial_: true - T_max: 100 - -local: - device: cuda - num_workers: 31 - runtime_limits: - max_time_per_run: 360000 - max_epochs_per_run: 500 - checkpoint_epochs: 200 - leave_waveforms_on_disk: true diff --git a/configs/experiment/train_gnpe.yaml b/configs/experiment/train_gnpe.yaml deleted file mode 100644 index a934225e0..000000000 --- a/configs/experiment/train_gnpe.yaml +++ /dev/null @@ -1,139 +0,0 @@ -# @package _global_ - -defaults: - - override /model: gnpe - - _self_ - -data: - waveform_dataset: - file_name: training_data/waveform_dataset.hdf5 - domain_update: - f_min: 20.0 - f_max: 1024.0 - svd_size_update: 200 - train_fraction: 0.95 - detectors: [H1, L1] - ref_time: 1126259462.391 - gnpe_time_shifts: - _target_: dingo.gw.transforms.GNPECoalescenceTimes - ifo_list: - _target_: bilby.gw.detector.InterferometerList - interferometers: ${data.detectors} - kernel: bilby.core.prior.Uniform(minimum=-0.001, maximum=0.001) - exact_global_equivariance: true - inference: false - context_parameters: - - L1_time_proxy_relative - selected_keys: - - inference_parameters - - waveform - - context_parameters - inference_parameters: - - chirp_mass - - mass_ratio - - a_1 - - a_2 - - tilt_1 - - tilt_2 - - phi_12 - - phi_jl - - theta_jn - - luminosity_distance - - geocent_time - - ra - - dec - - psi - standardization_transforms: - - _target_: dingo.gw.transforms.SampleExtrinsicParameters - extrinsic_prior_dict: ${data.extrinsic_prior} - - _target_: dingo.gw.transforms.GetDetectorTimes - ifo_list: - _target_: bilby.gw.detector.InterferometerList - interferometers: ${data.detectors} - ref_time: ${data.ref_time} - - ${data.gnpe_time_shifts} - transforms: - - _target_: dingo.gw.transforms.SampleExtrinsicParameters - extrinsic_prior_dict: ${data.extrinsic_prior} - - _target_: dingo.gw.transforms.GetDetectorTimes - ifo_list: - _target_: bilby.gw.detector.InterferometerList - interferometers: ${data.detectors} - ref_time: ${data.ref_time} - - ${data.gnpe_time_shifts} - - _target_: dingo.gw.transforms.ProjectOntoDetectors - _partial_: true - _runtime_dependencies_: [domain] - ifo_list: - _target_: bilby.gw.detector.InterferometerList - interferometers: ${data.detectors} - ref_time: ${data.ref_time} - - _target_: dingo.gw.transforms.SampleNoiseASD - _partial_: true - _runtime_dependencies_: [asd_dataset] - - _target_: dingo.gw.transforms.WhitenAndScaleStrain - _partial_: true - _runtime_dependencies_: [domain] - - _target_: dingo.gw.transforms.AddWhiteNoiseComplex - - _target_: dingo.gw.transforms.SelectStandardizeRepackageParameters - parameters_dict: - inference_parameters: ${data.inference_parameters} - context_parameters: ${data.context_parameters} - standardization_dict: ${data.standardization} - - _target_: dingo.gw.transforms.RepackageStrainsAndASDS - _partial_: true - _runtime_dependencies_: [domain] - ifos: ${data.detectors} - - _target_: dingo.gw.transforms.UnpackDict - selected_keys: ${data.selected_keys} - -training: - stage_0: - epochs: 300 - asd_dataset: - _target_: dingo.gw.noise.asd_dataset.ASDDataset - file_name: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 - ifos: ${data.detectors} - precision: single - domain_update: null - freeze_rb_layer: true - batch_size: 4096 - early_stopping: null - optimizer: - lr: 5.0e-5 - scheduler: - T_max: 300 - stage_1: - epochs: 150 - asd_dataset: - _target_: dingo.gw.noise.asd_dataset.ASDDataset - file_name: training_data/asd_dataset/asds_O1.hdf5 - ifos: ${data.detectors} - precision: single - domain_update: null - freeze_rb_layer: false - batch_size: 4096 - early_stopping: null - optimizer: - _target_: torch.optim.Adam - _partial_: true - lr: 1.0e-5 - scheduler: - _target_: torch.optim.lr_scheduler.CosineAnnealingLR - _partial_: true - T_max: 150 - -local: - device: cuda - num_workers: 32 - runtime_limits: - max_time_per_run: 3600000 - max_epochs_per_run: 500 - checkpoint_epochs: 10 - leave_waveforms_on_disk: true - condor: - num_cpus: 16 - memory_cpus: 128000 - num_gpus: 1 - memory_gpus: 8000 - request_disk: 50GB diff --git a/configs/experiment/train_gnpe_init.yaml b/configs/experiment/train_gnpe_init.yaml deleted file mode 100644 index a56de527f..000000000 --- a/configs/experiment/train_gnpe_init.yaml +++ /dev/null @@ -1,70 +0,0 @@ -# @package _global_ - -defaults: - - override /model: gnpe_init - - _self_ - -data: - waveform_dataset: - file_name: training_data/waveform_dataset.hdf5 - domain_update: - f_min: 20.0 - f_max: 1024.0 - svd_size_update: 200 - train_fraction: 0.95 - detectors: [H1, L1] - ref_time: 1126259462.391 - inference_parameters: - - H1_time - - L1_time - -training: - stage_0: - epochs: 150 - asd_dataset: - _target_: dingo.gw.noise.asd_dataset.ASDDataset - file_name: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 - ifos: ${data.detectors} - precision: single - domain_update: null - freeze_rb_layer: true - batch_size: 4096 - early_stopping: null - optimizer: - lr: 5.0e-5 - scheduler: - T_max: 150 - stage_1: - epochs: 75 - asd_dataset: - _target_: dingo.gw.noise.asd_dataset.ASDDataset - file_name: training_data/asd_dataset/asds_O1.hdf5 - ifos: ${data.detectors} - precision: single - domain_update: null - freeze_rb_layer: false - batch_size: 4096 - early_stopping: null - optimizer: - _target_: torch.optim.Adam - _partial_: true - lr: 1.0e-5 - scheduler: - _target_: torch.optim.lr_scheduler.CosineAnnealingLR - _partial_: true - T_max: 75 - -local: - device: cpu - num_workers: 6 - runtime_limits: - max_time_per_run: 3600000 - max_epochs_per_run: 500 - checkpoint_epochs: 10 - leave_waveforms_on_disk: true - condor: - num_cpus: 16 - memory_cpus: 128000 - num_gpus: 1 - memory_gpus: 8000 - request_disk: 50GB diff --git a/configs/experiment/train_npe.yaml b/configs/experiment/train_npe.yaml deleted file mode 100644 index f9413befd..000000000 --- a/configs/experiment/train_npe.yaml +++ /dev/null @@ -1,78 +0,0 @@ -# @package _global_ - -defaults: - - override /model: npe - - _self_ - -data: - waveform_dataset: - file_name: training_data/waveform_dataset.hdf5 - domain_update: - f_min: 20.0 - f_max: 1024.0 - svd_size_update: 200 - train_fraction: 0.95 - detectors: [H1, L1] - ref_time: 1126259462.391 - inference_parameters: - - chirp_mass - - mass_ratio - - chi_1 - - chi_2 - - theta_jn - - dec - - ra - - geocent_time - - luminosity_distance - - psi - -training: - stage_0: - epochs: 300 - asd_dataset: - _target_: dingo.gw.noise.asd_dataset.ASDDataset - file_name: training_data/asd_dataset_fiducial/asds_O1_fiducial.hdf5 - ifos: ${data.detectors} - precision: single - domain_update: null - freeze_rb_layer: true - batch_size: 4096 - early_stopping: null - optimizer: - lr: 5.0e-5 - scheduler: - T_max: 300 - stage_1: - epochs: 150 - asd_dataset: - _target_: dingo.gw.noise.asd_dataset.ASDDataset - file_name: training_data/asd_dataset/asds_O1.hdf5 - ifos: ${data.detectors} - precision: single - domain_update: null - freeze_rb_layer: false - batch_size: 4096 - early_stopping: null - optimizer: - _target_: torch.optim.Adam - _partial_: true - lr: 1.0e-5 - scheduler: - _target_: torch.optim.lr_scheduler.CosineAnnealingLR - _partial_: true - T_max: 150 - -local: - device: cuda - num_workers: 32 - runtime_limits: - max_time_per_run: 3600000 - max_epochs_per_run: 500 - checkpoint_epochs: 50 - leave_waveforms_on_disk: true - condor: - num_cpus: 16 - memory_cpus: 128000 - num_gpus: 1 - memory_gpus: 8000 - request_disk: 50GB diff --git a/configs/extrinsic_prior/default.yaml b/configs/extrinsic_prior/default.yaml deleted file mode 100644 index 25b4a17c7..000000000 --- a/configs/extrinsic_prior/default.yaml +++ /dev/null @@ -1,8 +0,0 @@ -_target_: dingo.gw.prior.BBHExtrinsicPriorDict -_convert_: all -dictionary: - dec: bilby.core.prior.Cosine(minimum=-np.pi/2, maximum=np.pi/2, name='dec') - ra: bilby.core.prior.Uniform(minimum=0., maximum=2*np.pi, boundary="periodic", name="ra") - geocent_time: bilby.core.prior.Uniform(minimum=-0.10, maximum=0.10, name='geocent_time') - psi: bilby.core.prior.Uniform(minimum=0.0, maximum=np.pi, boundary="periodic", name="psi") - luminosity_distance: bilby.core.prior.Uniform(minimum=100.0, maximum=1000.0, name='luminosity_distance') diff --git a/configs/extrinsic_prior/fmpe.yaml b/configs/extrinsic_prior/fmpe.yaml deleted file mode 100644 index ab67e5e28..000000000 --- a/configs/extrinsic_prior/fmpe.yaml +++ /dev/null @@ -1,8 +0,0 @@ -_target_: dingo.gw.prior.BBHExtrinsicPriorDict -_convert_: all -dictionary: - dec: bilby.core.prior.Cosine(minimum=-np.pi/2, maximum=np.pi/2, name='dec') - ra: bilby.core.prior.Uniform(minimum=0., maximum=2*np.pi, boundary="periodic", name="ra") - geocent_time: bilby.core.prior.Uniform(minimum=-0.03, maximum=0.03, name='geocent_time') - psi: bilby.core.prior.Uniform(minimum=0.0, maximum=np.pi, boundary="periodic", name="psi") - luminosity_distance: bilby.core.prior.Uniform(minimum=100.0, maximum=1000.0, name='luminosity_distance') diff --git a/configs/generate_asd_dataset.yaml b/configs/generate_asd_dataset.yaml index aba442e62..6b128de6b 100644 --- a/configs/generate_asd_dataset.yaml +++ b/configs/generate_asd_dataset.yaml @@ -1,7 +1,3 @@ -defaults: - - hydra: default - - _self_ - data_dir: asd_dataset time_segments_file: null out_name: null @@ -25,11 +21,19 @@ dataset_settings: observing_run: O1 hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} job: name: generate_asd_dataset + chdir: true config: override_dirname: exclude_keys: - data_dir - time_segments_file - out_name + job_logging: + loggers: + bilby: + level: INFO + propagate: true diff --git a/configs/generate_dataset.yaml b/configs/generate_dataset.yaml index 05838a47b..790134a1d 100644 --- a/configs/generate_dataset.yaml +++ b/configs/generate_dataset.yaml @@ -1,15 +1,36 @@ defaults: - - hydra: default - domain: uniform_frequency - waveform_generator: phenom_d - - intrinsic_prior: default - _self_ +intrinsic_prior: + _target_: bilby.gw.prior.BBHPriorDict + _convert_: all + dictionary: + mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1') + mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2') + chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=100.0, name='chirp_mass') + mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') + phase: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phase") + chi_1: bilby.gw.prior.AlignedSpin(name='chi_1', a_prior=Uniform(minimum=0, maximum=0.9)) + chi_2: bilby.gw.prior.AlignedSpin(name='chi_2', a_prior=Uniform(minimum=0, maximum=0.9)) + theta_jn: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='theta_jn') + luminosity_distance: 100.0 + geocent_time: 0.0 + num_samples: 10000 compression: null num_processes: 1 out_file: waveform_dataset.hdf5 hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} job: name: generate_dataset + chdir: true + job_logging: + loggers: + bilby: + level: INFO + propagate: true diff --git a/configs/generate_synthetic_asd_dataset.yaml b/configs/generate_synthetic_asd_dataset.yaml deleted file mode 100644 index 71070af80..000000000 --- a/configs/generate_synthetic_asd_dataset.yaml +++ /dev/null @@ -1,33 +0,0 @@ -defaults: - - hydra: default - - _self_ - -asd_dataset: ??? -num_samples: 500 -num_processes: 1 -out_file: synthetic_asd_dataset.hdf5 -verbose: false - -parameterization_settings: - num_spline_positions: 30 - num_spectral_segments: 400 - sigma: 0.14 - delta_f: -1 - smoothen: true - -sampling_settings: - bandwidth_spectral: 0.5 - bandwidth_spline: 0.25 - split_frequencies: - - 30 - - 100 - rescaling_asd_paths: null - -hydra: - job: - name: generate_synthetic_asd_dataset - config: - override_dirname: - exclude_keys: - - asd_dataset - - out_file diff --git a/configs/hydra/default.yaml b/configs/hydra/default.yaml deleted file mode 100644 index f32382a96..000000000 --- a/configs/hydra/default.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# @package _global_ -hydra: - run: - dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} - job: - chdir: true - job_logging: - loggers: - bilby: - level: INFO - propagate: true diff --git a/configs/importance_weights.yaml b/configs/importance_weights.yaml deleted file mode 100644 index 7f924ac7a..000000000 --- a/configs/importance_weights.yaml +++ /dev/null @@ -1,47 +0,0 @@ -defaults: - - hydra: default - - model: unconditional_npe - - optimizer: adam - - scheduler: cosine - - _self_ - -outdir: . -parameter_samples: ??? -num_samples: 1000 -event_dataset: null -time_marginalization: - n_fft: 5 -phase_marginalization: null -synthetic_phase: null -calibration_marginalization: null -num_processes: 1 - -slice_plots: - num_slice_plots: 10 - params_slice2d: - - [phase, geocent_time] - - [phase, tilt_1] - -nde: - path: null - data: - parameter_samples: null - parameters: null - model: ${model} - training: - optimizer: ${optimizer} - scheduler: ${scheduler} - device: cpu - num_workers: 0 - train_fraction: 0.9 - batch_size: 512 - epochs: 20 - -hydra: - job: - name: importance_weights - config: - override_dirname: - exclude_keys: - - parameter_samples - - nde.path diff --git a/configs/intrinsic_prior/default.yaml b/configs/intrinsic_prior/default.yaml deleted file mode 100644 index c9a697b87..000000000 --- a/configs/intrinsic_prior/default.yaml +++ /dev/null @@ -1,13 +0,0 @@ -_target_: bilby.gw.prior.BBHPriorDict -_convert_: all -dictionary: - mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1') - mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2') - chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=100.0, name='chirp_mass') - mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') - phase: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phase") - chi_1: bilby.gw.prior.AlignedSpin(name='chi_1', a_prior=Uniform(minimum=0, maximum=0.9)) - chi_2: bilby.gw.prior.AlignedSpin(name='chi_2', a_prior=Uniform(minimum=0, maximum=0.9)) - theta_jn: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='theta_jn') - luminosity_distance: 100.0 - geocent_time: 0.0 diff --git a/configs/intrinsic_prior/fmpe.yaml b/configs/intrinsic_prior/fmpe.yaml deleted file mode 100644 index 41d0f1737..000000000 --- a/configs/intrinsic_prior/fmpe.yaml +++ /dev/null @@ -1,17 +0,0 @@ -_target_: bilby.gw.prior.BBHPriorDict -_convert_: all -dictionary: - mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_1') - mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_2') - chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=20.0, maximum=120.0, name='chirp_mass') - mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') - phase: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phase") - a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_1') - a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_2') - tilt_1: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_1') - tilt_2: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_2') - phi_12: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_12") - phi_jl: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_jl") - theta_jn: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='theta_jn') - luminosity_distance: 100.0 - geocent_time: 0.0 diff --git a/configs/intrinsic_prior/precessing.yaml b/configs/intrinsic_prior/precessing.yaml deleted file mode 100644 index c2648da57..000000000 --- a/configs/intrinsic_prior/precessing.yaml +++ /dev/null @@ -1,17 +0,0 @@ -_target_: bilby.gw.prior.BBHPriorDict -_convert_: all -dictionary: - mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_1') - mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=120.0, name='mass_2') - chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=150.0, name='chirp_mass') - mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') - phase: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phase") - a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_1') - a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.99, name='a_2') - tilt_1: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_1') - tilt_2: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_2') - phi_12: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_12") - phi_jl: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_jl") - theta_jn: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='theta_jn') - luminosity_distance: 100.0 - geocent_time: 0.0 diff --git a/configs/intrinsic_prior/precessing_multibanded_test.yaml b/configs/intrinsic_prior/precessing_multibanded_test.yaml deleted file mode 100644 index 1db3d7c62..000000000 --- a/configs/intrinsic_prior/precessing_multibanded_test.yaml +++ /dev/null @@ -1,17 +0,0 @@ -_target_: bilby.gw.prior.BBHPriorDict -_convert_: all -dictionary: - mass_1: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_1') - mass_2: bilby.core.prior.Constraint(minimum=10.0, maximum=80.0, name='mass_2') - chirp_mass: bilby.gw.prior.UniformInComponentsChirpMass(minimum=15.0, maximum=100.0, name='chirp_mass') - mass_ratio: bilby.gw.prior.UniformInComponentsMassRatio(minimum=0.125, maximum=1.0, name='mass_ratio') - phase: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phase") - a_1: bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_1') - a_2: bilby.core.prior.Uniform(minimum=0.0, maximum=0.88, name='a_2') - tilt_1: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_1') - tilt_2: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='tilt_2') - phi_12: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_12") - phi_jl: bilby.core.prior.Uniform(minimum=0.0, maximum=2*np.pi, boundary="periodic", name="phi_jl") - theta_jn: bilby.core.prior.Sine(minimum=0.0, maximum=np.pi, name='theta_jn') - luminosity_distance: 100.0 - geocent_time: 0.0 diff --git a/configs/merge_asd_datasets.yaml b/configs/merge_asd_datasets.yaml deleted file mode 100644 index 1dbafebb1..000000000 --- a/configs/merge_asd_datasets.yaml +++ /dev/null @@ -1,35 +0,0 @@ -defaults: - - hydra: default - - _self_ - -data_dir: asd_dataset -time_segments_file: null -num_parts: -1 -out_name: null - -dataset_settings: - f_min: 0 - f_max: 2048 - f_s: 4096 - time_psd: 1024 - T: 8.0 - window: - roll_off: 0.4 - type: tukey - time_gap: 0 - num_psds_max: 1 - channels: null - detectors: - - H1 - - L1 - observing_run: O1 - -hydra: - job: - name: merge_asd_datasets - config: - override_dirname: - exclude_keys: - - data_dir - - time_segments_file - - out_name diff --git a/configs/model/unconditional_npe.yaml b/configs/model/unconditional_npe.yaml deleted file mode 100644 index 3dc750652..000000000 --- a/configs/model/unconditional_npe.yaml +++ /dev/null @@ -1,12 +0,0 @@ -_target_: dingo.core.posterior_models.NormalizingFlowPosteriorModel -embedding_kwargs: null -posterior_kwargs: - num_flow_steps: 10 - base_transform_kwargs: - hidden_dim: 128 - num_transform_blocks: 2 - activation: elu - dropout_probability: 0.1 - batch_norm: true - num_bins: 8 - base_transform_type: rq-coupling diff --git a/configs/optimizer/adam.yaml b/configs/optimizer/adam.yaml deleted file mode 100644 index 6182e57db..000000000 --- a/configs/optimizer/adam.yaml +++ /dev/null @@ -1,3 +0,0 @@ -_target_: torch.optim.Adam -_partial_: true -lr: 0.0001 diff --git a/configs/scheduler/cosine.yaml b/configs/scheduler/cosine.yaml deleted file mode 100644 index 5ba12c5a7..000000000 --- a/configs/scheduler/cosine.yaml +++ /dev/null @@ -1,3 +0,0 @@ -_target_: torch.optim.lr_scheduler.CosineAnnealingLR -_partial_: true -T_max: 20 diff --git a/configs/train.yaml b/configs/train.yaml index 8ef705247..f524d236d 100644 --- a/configs/train.yaml +++ b/configs/train.yaml @@ -1,15 +1,31 @@ defaults: - - hydra: default - - extrinsic_prior: default - model: toy_npe - - optimizer: adam - - scheduler: cosine - _self_ checkpoint: null train_dir: train exit_command: "" +extrinsic_prior: + _target_: dingo.gw.prior.BBHExtrinsicPriorDict + _convert_: all + dictionary: + dec: bilby.core.prior.Cosine(minimum=-np.pi/2, maximum=np.pi/2, name='dec') + ra: bilby.core.prior.Uniform(minimum=0., maximum=2*np.pi, boundary="periodic", name="ra") + geocent_time: bilby.core.prior.Uniform(minimum=-0.10, maximum=0.10, name='geocent_time') + psi: bilby.core.prior.Uniform(minimum=0.0, maximum=np.pi, boundary="periodic", name="psi") + luminosity_distance: bilby.core.prior.Uniform(minimum=100.0, maximum=1000.0, name='luminosity_distance') + +optimizer: + _target_: torch.optim.Adam + _partial_: true + lr: 0.0001 + +scheduler: + _target_: torch.optim.lr_scheduler.CosineAnnealingLR + _partial_: true + T_max: 20 + data: extrinsic_prior: ${extrinsic_prior} waveform_dataset: @@ -115,5 +131,13 @@ local: test_only: false hydra: + run: + dir: ${oc.env:DINGO_RUNS}/${now:%Y-%m-%d_%H-%M-%S}_${hydra.job.name}_${hydra.job.override_dirname} job: name: train + chdir: true + job_logging: + loggers: + bilby: + level: INFO + propagate: true diff --git a/configs/unconditional_density_estimation.yaml b/configs/unconditional_density_estimation.yaml deleted file mode 100644 index 8129c0f5f..000000000 --- a/configs/unconditional_density_estimation.yaml +++ /dev/null @@ -1,29 +0,0 @@ -defaults: - - hydra: default - - model: unconditional_npe - - optimizer: adam - - scheduler: cosine - - _self_ - -result_file: ??? -train_dir: nde - -data: - parameters: null - -training: - optimizer: ${optimizer} - scheduler: ${scheduler} - device: cpu - num_workers: 0 - train_fraction: 0.9 - batch_size: 512 - epochs: 20 - -hydra: - job: - name: unconditional_density_estimation - config: - override_dirname: - exclude_keys: - - result_file