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_frequencyand 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
targetas noise first, then inject; stationary Gaussian noise can be added in a separate noise-focused package.