Skip to content

Strain injection examples

After producing detector strain (e.g. with Waveforms, Continuous waves and Detector projection), you often need to embed that strain into a longer segment aligned to a science run—typically starting from zeros or from a noise realization (noise generation can live in a separate package). This step is central to software injections, end-to-end mock challenges, and pipeline validation.

This page is examples only. Signatures, defaults, and raised exceptions for inject_strain / inject_strains_sequential are documented only under API → Injection.

API reference

Use API → Injection for the authoritative behavior description; examples below illustrate common patterns.

Example 1 — Inject one CBC into a zero-filled segment

import numpy as np
from gwpy.timeseries import TimeSeries

from gwmock_signal.waveform import WaveformFactory
from gwmock_signal.projection import project_polarizations_to_network
from gwmock_signal.injection import inject_strain

fs = 4096.0
duration = 8.0
t0 = 1_400_000_000.0
n = int(duration * fs)

target = TimeSeries(np.zeros(n), t0=t0, sample_rate=fs)

factory = WaveformFactory()
pol = factory.generate(
    "IMRPhenomD",
    {
        "tc": t0 + 2.0,
        "detector_frame_mass_1": 30.0,
        "detector_frame_mass_2": 25.0,
        "spin_1z": 0.0,
        "spin_2z": 0.0,
        "distance": 400.0,
        "inclination": 0.0,
        "coa_phase": 0.0,
    },
    sampling_frequency=fs,
    minimum_frequency=20.0,
)
strains = project_polarizations_to_network(
    pol,
    ["H1"],
    right_ascension=1.0,
    declination=0.5,
    polarization_angle=0.2,
    earth_rotation=False,
)
h1 = strains["H1"]

out = inject_strain(target, h1)
assert out is not target  # immutability: new object (recommended contract)

Example 2 — Multiple injections in time order

from gwmock_signal.injection import inject_strains_sequential

# target: long segment; inj1, inj2: non-overlapping or overlapping GWpy series
out = inject_strains_sequential(target, [inj1, inj2], interpolate_if_offset=True)

Example 3 — Disable interpolation (strict grid)

If you know injections are exactly aligned to the target grid and want to avoid resampling altogether:

out = inject_strain(target, h1, interpolate_if_offset=False)

When interpolate_if_offset=False and the injection start is not aligned to a target-sample boundary, the function returns target.copy() — i.e., the background unchanged, with a debug-level log message. Use this when you need strict grid alignment guarantees.

Example 4 — One-call CBC injection with inject_cbc_signal

The convenience function inject_cbc_signal orchestrates waveform generation, projection, and injection in one call:

import numpy as np
from gwpy.timeseries import TimeSeries
from gwmock_signal.pipeline import inject_cbc_signal

fs = 4096.0
duration = 8.0
n = int(duration * fs)
t0 = 1_400_000_000.0

params = {
    "detector_frame_mass_1": 36.0,
    "detector_frame_mass_2": 29.0,
    "coa_time": t0 + duration / 2,
    "distance": 410.0,
    "inclination": 0.0,
    "right_ascension": 1.375,
    "declination": -1.211,
    "polarization_angle": 0.0,
}

background = {name: TimeSeries(np.zeros(n), t0=t0, sample_rate=fs) for name in ["H1", "L1"]}

result = inject_cbc_signal(
    "IMRPhenomD",
    params,
    ["H1", "L1"],
    background,
    sampling_frequency=fs,
    minimum_frequency=20.0,
    earth_rotation=False,
)

for name in result.detector_names:
    rms = float(np.sqrt(np.mean(result[name].value ** 2)))
    print(f"{name}: rms={rms:.4e}")

Example 5 — Simulate and save to disk with CBCSimulator.write

The highest-level convenience is CBCSimulator.write, which runs the full pipeline and writes both the strain data and a JSON sidecar with the injection parameters:

from gwmock_signal import CBCSimulator
from gwpy.timeseries import TimeSeries
import numpy as np

sim = CBCSimulator("IMRPhenomD")

# The waveform_model property exposes the approximant name:
print(sim.waveform_model)  # IMRPhenomD

params = {
    "detector_frame_mass_1": 36.0,
    "detector_frame_mass_2": 29.0,
    "coa_time": 1_126_259_462.4,
    "distance": 410.0,
    "inclination": 0.0,
    "right_ascension": 1.375,
    "declination": -1.211,
    "polarization_angle": 0.0,
}
fs = 4096.0
n = 8192
background = {name: TimeSeries(np.zeros(n), t0=params["coa_time"] - 1, sample_rate=fs) for name in ["H1", "L1"]}

result = sim.write(
    "my_injection.h5",
    params,
    ["H1", "L1"],
    background,
    sampling_frequency=fs,
    minimum_frequency=20.0,
    format="hdf5",
)
# Also creates my_injection_params.json with the injection parameters

Supported write formats: "hdf5" (default), "gwf", "npy", "txt".

Example 6 — Inject one CW into a segment with CBC

A continuous wave has no duration of its own, so the segment being generated is defined by the background's epoch and length. The background is the existing strain called out from example 1, so t0 and fs are that example's values: the injection lands on the CBC segment's own time grid, which sampling_frequency has to match. A real CW run would use a much longer segment than that 8 s example; the point here is only that the two signals add.

from gwmock_signal.continuous import ContinuousWaveSimulator

t0 = 1_400_000_000.0  # the CBC background's epoch, from example 1
fs = 4096.0  # and its sample rate, which sampling_frequency must match

cw_sim = ContinuousWaveSimulator(
    earth_ephemeris="earth00-40-DE405.dat.gz",
    sun_ephemeris="sun00-40-DE405.dat.gz",
    reference_time_ssb=t0,
    spindowns=(-1.0e-10,),
)

params = {
    "right_ascension": 1.1,
    "declination": 0.3,
    "frequency": 20.0,
    "initial_phase": 0.4,
    "amplitude_plus": 1.0e-24,
    "amplitude_cross": 7.0e-25,
    "polarization_angle": 0.2,  # optional; defaults to 0.0
}

# The CBC time series is dimensionless.
# Relabels CBC background to `strain` units
cbc_background = TimeSeries(data=out.value, t0=out.t0, dt=out.dt, unit="strain")

result = cw_sim.simulate(
    params=params,
    detector_names=["H1"],
    background={"H1": cbc_background},  # An existing strain with the CBC signal
    sampling_frequency=fs,
    minimum_frequency=0.0,  # required by the shared simulator signature; unused for CW
    earth_rotation=True,  # the only accepted value
)

Pitfalls

  • Units: Target and injection should use compatible strain units (typically dimensionless); mixed units should raise a clear error.
  • No overlap: If the injection lies entirely outside the target span, the API returns a copy of the target (same samples, new object).
  • Interpolation: An off-grid injection is resampled with a band-limited windowed-sinc kernel, which treats the injection as zero outside its span, so an injection that does not taper to zero rings at its edges; prefer aligned waveforms from the same sampling_frequency and GPS grid when possible.
  • Performance: Large segments are memory-bound; avoid unnecessary copies if the API documents mutability (default recommendation: return a new TimeSeries).

Scientific notes

  • Conventions follow common GWpy / LIGO injection practice: coherent addition of strain in the time domain on a fixed grid.
  • For colored noise backgrounds, build target as noise first, then inject; stationary Gaussian noise can be added in a separate noise-focused package.

See also