Skip to content

Protocol Contracts

This page defines the stable public interfaces that gwmock consumes from the split-out gwmock-pop, gwmock-signal, and gwmock-noise packages. If a third-party package implements the same public protocol, gwmock can consume it without depending on that package's internal classes.

Population protocol: GWPopSimulator

Upstream definition: gwmock_pop.protocols.simulator.GWPopSimulator

GWPopSimulator is a @runtime_checkable typing.Protocol.

Member Type
parameter_names Sequence[str] property; stable across calls
source_type str property; must be non-empty
simulate(n_samples, **kwargs) Mapping[str, ArrayLike]; each value is length n_samples

gwmock validates the backend with isinstance(obj, GWPopSimulator). The returned mapping must use the same key order as parameter_names, and every array-like value must have the same leading dimension so gwmock can slice the batch into per-event parameter dictionaries.

Signal protocol: GWSimulator

Upstream definition: gwmock_signal.simulator.GWSimulator

Member Type
required_params frozenset[str] property
simulate(params, detector_names, ...) Returns DetectorStrainStack
register_waveform_model (optional) (name: str, factory: Callable \| WaveformBackend) -> None

For third-party packages, the preferred approach is to subclass gwmock_signal.GWSimulator directly. A class with the same public required_params and simulate(...) surface is also compatible with the consumer-side contract.

The stable boundary is simulate(...). gwmock expects a DetectorStrainStack and should not need backend-specific helpers such as generate_polarizations(). register_waveform_model(...) is only relevant for backends that accept callable waveform overrides.

Noise protocol: NoiseSimulator

Upstream definitions: gwmock_noise.simulators.protocol.NoiseSimulator and gwmock_noise.open_stream(...)

NoiseSimulator is a @runtime_checkable typing.Protocol.

Member Type
duration, sampling_frequency, detectors, seed Required attributes
generate(...) dict[str, np.ndarray] one-shot output
generate_stream(chunk_duration, ...) Iterator[dict[str, np.ndarray]]
metadata dict property

gwmock consumes the streaming form through gwmock_noise.open_stream(...). The contract is stateful continuation across yielded chunks: chunk N+1 must continue the filter state of chunk N, so concatenating consecutive chunks matches a single longer run.

Backend references

The protocol-oriented backend reference is the backend: field in the orchestration config. A backend identifier may resolve as:

  1. A built-in alias registered by gwmock.
  2. An entry point in a gwmock.* group.
  3. A module:Class reference imported directly by gwmock.
  4. A legacy module.Class reference, which still works but emits a DeprecationWarning.

For third-party integrations, the important part is that resolution ends in a class or instance that satisfies the relevant upstream protocol.

Worked example: third-party GWPopSimulator

Any class with the required public members satisfies the population protocol; it does not need to inherit from a gwmock-specific base class.

from collections.abc import Mapping, Sequence
from typing import Any

import jax.numpy as jnp


class GalacticBinaryPopulation:
    @property
    def parameter_names(self) -> Sequence[str]:
        return (
            "coa_time",
            "chirp_mass",
            "luminosity_distance",
            "right_ascension",
            "declination",
            "polarization_angle",
        )

    @property
    def source_type(self) -> str:
        return "bbh"

    def simulate(self, n_samples: int, **kwargs: Any) -> Mapping[str, jnp.ndarray]:
        if n_samples <= 0:
            raise ValueError("n_samples must be positive")

        return {
            "coa_time": jnp.linspace(1_577_491_218.0, 1_577_491_218.0 + n_samples, n_samples),
            "chirp_mass": jnp.full((n_samples,), 28.0),
            "luminosity_distance": jnp.full((n_samples,), 400.0),
            "right_ascension": jnp.zeros((n_samples,)),
            "declination": jnp.zeros((n_samples,)),
            "polarization_angle": jnp.zeros((n_samples,)),
        }

Once that class is importable from my_pkg.populations, expose it through an entry point:

[project.entry-points."gwmock.population"]
galactic_binaries = "my_pkg.populations:GalacticBinaryPopulation"

Then reference the entry-point alias in YAML:

orchestration:
    population:
        backend: galactic_binaries
        n-samples: 128
        arguments:
            catalog_path: /data/catalogs/galactic-binaries.h5
    signal:
        backend: my_pkg.signals:GalacticBinarySimulator
        waveform-model: IMRPhenomXPHM
        detectors:
            - E1_triangle_emr
            - E2_triangle_emr
            - E3_triangle_emr
    noise:
        backend: my_pkg.noise:LaserShotNoise
        arguments:
            seed: 42
            psd_file: ET_10_full_cryo_psd.txt

The same pattern applies to signal and noise backends: implement the upstream protocol, make the class importable, and reference it through the corresponding backend: field when that orchestration section is resolved through the public backend registry.