Note

This page was generated from a Jupyter Notebook. Download the notebook (.ipynb)

[1]:
# Skipped in CI: Colab/bootstrap dependency install cell.

Coupling Analysis: Estimating Transfer Paths Between Channels๏ƒ

Open In Colab

This case study promotes the legacy PEM injection tutorial into the public docs. The output is a coupling function: a frequency-dependent estimate of how witness-channel noise projects into the target channel.

That estimate is powerful for diagnosis, but it still models a transfer path rather than proving the physical mechanism behind it.

[2]:
import matplotlib.pyplot as plt
import numpy as np
from astropy import units as u

from gwexpy.analysis import PercentileThreshold, SigmaThreshold, estimate_coupling
from gwexpy.noise.asd import from_pygwinc
from gwexpy.noise.wave import from_asd
from gwexpy.timeseries import TimeSeries, TimeSeriesDict

fs = 2048
duration = 32
t = np.linspace(0, duration, duration * fs, endpoint=False)

# Build background and injection states so the injected witness excess can be distinguished from the nominal detector background.
asd_target = from_pygwinc("aLIGO", fmin=3.0, fmax=fs / 2, df=1.0 / duration, quantity="strain")
asd_target.name = "L0:CAL-DARM_STRAIN"
wit_name = "L0:PEM-VOLT_GND"
wit_bkg = TimeSeries(np.random.normal(0, 1.0, len(t)), t0=0, sample_rate=fs, unit=u.V, name=wit_name)
wit_inj = TimeSeries(np.random.normal(0, 1.0, len(t)), t0=duration, sample_rate=fs, unit=u.V, name=wit_name)
wit_inj += TimeSeries(np.random.normal(0, 20, len(t)), t0=duration, sample_rate=fs, unit=u.V, name=wit_name).bandpass(20, 600)

cf_true = 2e-23 * asd_target.unit * u.Hz**0.5 / wit_bkg.unit
data_bkg = TimeSeriesDict({asd_target.name: from_asd(asd_target, duration, fs, t0=0).highpass(6).notch(8) + cf_true * wit_bkg, wit_bkg.name: wit_bkg})
data_inj = TimeSeriesDict({asd_target.name: from_asd(asd_target, duration, fs, t0=duration).highpass(6).notch(8) + cf_true * wit_inj, wit_inj.name: wit_inj})

1. Estimate the coupling function๏ƒ

The coupling function compares target excess to witness excess. If the witness injection does not produce significant target excess, the result becomes an upper limit rather than a direct estimate.

[3]:
result = estimate_coupling(data_inj, data_bkg, fftlength=2.0, witness=wit_name)

result.plot_cf()
plt.axhline(cf_true.value, color="red", linestyle="--", alpha=0.8, label=f"True CF ({cf_true:.2e})")
plt.ylim(5e-24, 5e-22)
plt.xlim(10, 1020)
plt.legend()
plt.show()

../../../../_images/web_en_user_guide_tutorials_case_coupling_analysis_5_0.png

2. Compare threshold strategies๏ƒ

Changing the threshold strategy changes how conservative the coupling estimate is. This matters when injection lines are intermittent or when the target channel barely rises above background.

[4]:
strategy_sigma = SigmaThreshold(sigma=5.0)
strategy_pct = PercentileThreshold(percentile=95, factor=2.0)

result_sigma = estimate_coupling(data_inj, data_bkg, fftlength=1.0, threshold_witness=strategy_sigma)
result_pct = estimate_coupling(data_inj, data_bkg, fftlength=1.0, threshold_witness=strategy_pct)

result_sigma.plot_cf(label="SigmaThreshold")
result_pct.plot_cf(label="PercentileThreshold")
plt.legend()
plt.show()

../../../../_images/web_en_user_guide_tutorials_case_coupling_analysis_7_0.png
../../../../_images/web_en_user_guide_tutorials_case_coupling_analysis_7_1.png