Advanced: Joint strain and witness channels¶
For minimal usage snippets see Minimal usage. For the legacy strain-only protocol see Custom simulators.
gwmock-noise exposes a second, optional structural protocol,
gwmock_noise.JointStrainWitnessSimulator, for backends that generate
simultaneous strain and auxiliary/witness channels with a documented
statistical relationship between them. It is strictly additive: nothing about
the legacy NoiseSimulator protocol changes, and a NoiseSimulator-only
consumer is unaffected by its existence. A class may implement both protocols,
or only this one.
Required surface¶
A protocol-conformant joint simulator provides:
duration,sampling_frequency,detectors,witnesses, andseedattributesgenerate_joint(...)for one-shot simultaneous realizations, returning aJointRealizationgenerate_joint_stream(...)for stateful chunk iteration, yieldingJointRealizationobjectscovariance()returning the joint one-sided cross-spectral density as aJointCovariancemetadatafor descriptive runtime metadata, in the same role asNoiseSimulator.metadata
JointRealization carries strain and witness dictionaries (each
dict[str, numpy.ndarray], the same per-channel array convention
NoiseSimulator.generate uses), channel_metadata
(dict[str, ChannelMetadata], typed per-channel unit/domain/kind/dtype metadata
for every channel in both dictionaries), and provenance (an
implementation-defined mapping describing how the realization was produced).
Fourier convention¶
Wherever this protocol exposes a frequency-domain quantity (covariance()), the
frequency grid is the one-sided, non-negative grid produced by
numpy.fft.rfftfreq, and the reported density is the one-sided spectral
density -- the convention already used by MultichannelNoiseSimulator.
Continuation contract¶
The same continuation contract NoiseSimulator.generate_stream makes for
strain-only output applies here to both strain and witness channels at once:
consecutive chunks from generate_joint_stream(...) should equal the same
realization a caller would obtain from one seeded generate_joint(...) call
over the combined duration.
The public dummy backend¶
gwmock_noise.JointDummyCorrelatedSimulator is a public, physics-free reference
implementation: every channel is temporally white with a fixed equicorrelation
structure, so every strain/witness pair is genuinely correlated by construction
and covariance() is exact and known in closed form rather than fitted.
from gwmock_noise import JointDummyCorrelatedSimulator
simulator = JointDummyCorrelatedSimulator(
detectors=["H1", "L1"],
witnesses=["SEIS1"],
sampling_frequency=256.0,
coupling=0.3,
)
realization = simulator.generate_joint(4.0, 256.0, ["H1", "L1"], ["SEIS1"], seed=7)
realization.strain["H1"]
realization.witness["SEIS1"]
External discovery without a private import¶
A backend -- including one that lives in a private package this repository never
imports -- becomes discoverable by registering itself under the
gwmock_noise.joint_backends entry-point group in its own package metadata:
[project.entry-points."gwmock_noise.joint_backends"]
my_backend = "my_package.module:MyBackendClass"
gwmock_noise.discover_joint_backends() and
gwmock_noise.load_joint_backend(name) find and import a registered backend
purely through importlib.metadata.entry_points, with no reference to any
specific package in the discovery code itself:
from gwmock_noise import available_joint_backend_names, load_joint_backend
available_joint_backend_names() # e.g. ("dummy_correlated",)
backend_class = load_joint_backend("dummy_correlated")
Conformance suite for your backend¶
gwmock_noise.testing.joint_conformance ships the contract tests gwmock-noise
runs against its own dummy backend, so a downstream package can hold its backend
to exactly the same checks under its own test runner. Subclass
JointBackendConformance under a Test-prefixed name and provide a
joint_backend fixture yielding JointBackendCase instances:
import pytest
from gwmock_noise import load_joint_backend
from gwmock_noise.testing.joint_conformance import (
JointBackendCase,
JointBackendConformance,
)
def build(seed):
return load_joint_backend("my_backend")(sampling_frequency=64.0, seed=seed)
@pytest.fixture(params=[JointBackendCase("my_backend", build, 64.0, "my_backend")])
def joint_backend(request):
return request.param
class TestMyBackendConformance(JointBackendConformance):
pass
The suite checks realization shapes and channel metadata, seeded determinism,
stream continuity, a Hermitian positive-semidefinite covariance over exactly the
realized channels, JSON-native provenance and metadata, refusal of an unknown
channel, and that no witness is disguised as a strain channel or stored as an
array in metadata. It requires pytest, which gwmock-noise does not install at
runtime.