Skip to main content
gaplike

tests docs PyPI license: MIT

Inference on gapped / windowed stationary Gaussian data.

gaplike provides everything needed to do joint signal + noise-parameter estimation when a stationary time series is interrupted by data gaps (or multiplied by any window): gap-pattern generation with arbitrary patterns, windowed frequency-domain covariances, and a hierarchy of likelihoods — from the cheap Whittle approximation to the dense windowed covariance and the exact time-domain likelihood, evaluated either in closed form (two components, simultaneous diagonalization) or matrix-free at any scale by preconditioned conjugate gradients (gaplike.cg: the covariance is never formed, one solve is a few hundred FFT pairs).

Four acts on the same record: a raw cut, then a taper on the record edges, then sharp-edged gaps, then tapered gap edges. Watch the spectral kernel, the modelled PSD and the bin-to-bin correlation matrix move together. Rendered by notebooks/anim_leakage.py.

Companion package to "Zurückbleiben bitte: the impact of gaps on noise and signal parameter inference" (O. Burke, F. Pozzoli & M. Muratore); the paper/ folder reproduces every figure of that paper.

Only numpy and scipy are required. The two-component LISA TDI-2 A/E noise model is built in; any user-defined PSD components and any waveform work the same way.

Documentation: https://gaplike.github.io/gaplike/

Install

gaplike needs only numpy and scipy. Pick one environment and install everything into it: the commonest way to lose an afternoon here is to install with one tool and run with another.

From PyPI

pip install gaplike                 # everything except paper/ and the notebooks
pip install "gaplike[pe]"           # + emcee, corner, matplotlib

That is all the package itself needs. Everything below installs from a clone instead, which you want only if you also need the paper reproduction pipeline in paper/, the notebooks, or the test suite.

Into an environment you already have (conda, system Python, ...)

python -m pip install -e ".[pe,dev,docs]"
python -m sphinx -b html docs docs/_build/html
python -m pytest

The python -m form ties the installer and the command to the same interpreter, which is what keeps them in step.

Into a fresh virtual environment

uv venv
source .venv/bin/activate          # <- do not skip this
uv pip install -e ".[pe,dev,docs]"
sphinx-build -b html docs docs/_build/html
pytest

Without the activate line, uv installs into .venv while sphinx-build and pytest still come from whatever is on your PATH, and you get a confusing "module not found" for something you just watched install. uv run <command> does the same job without activating.

Extras

extra pulls in needed for
pe emcee, corner, matplotlib sampling, the notebooks, every paper figure
dev pytest the test suite
docs sphinx, furo, myst-parser, sphinx-copybutton building the documentation
mbhb lisabeta the MBHB waveform adapter only

The MBHB waveform adapter additionally needs lisabeta, which is not pulled in by any of the lines above:

uv pip install -e ".[pe,mbhb]"          # + lisabeta (from PyPI)

[mbhb] is a separate extra because exactly one factory needs it. Everything else — every likelihood, every gap builder, the CG solver, the paper figures from the cached chains, and notebooks/exact_inference_demo.ipynb — runs without lisabeta, and the import happens inside gaplike.waveform.lisabeta_mbhb_ae, so a missing install fails only there.

Installing from PyPI gives wheels. Building the GitLab sources instead requires FFTW and a CMake toolchain, and the FFTW location has to be handed to the build explicitly:

brew install fftw                       # macOS;  apt install libfftw3-dev on Debian/Ubuntu
SKBUILD_CMAKE_DEFINE="FFTW_ROOT=$(brew --prefix fftw)" \
  uv pip install "lisabeta @ git+https://gitlab.in2p3.fr/marsat/lisabeta.git"

Quickstart

import numpy as np
import gaplike as gl

n, dt = 2880, 15.0                          # 12 h at 15 s cadence
f_lo, f_hi = 1e-4, 3.1e-2                   # analysis band [Hz]

# --- 1. gaps: ANY pattern -------------------------------------------------
mask = gl.gaps.periodic_mask(n, 10, 50)              # comb: 150 s out of 750 s
# mask = gl.gaps.random_mask(n, dt, rate_per_day=40, duration_s=600, rng=1)
# mask = gl.gaps.mask_from_intervals(n, dt, [(21000, 25000)])
gate = gl.gaps.gate_from_mask(mask, dt, taper_s=0.0) # rectangular (or tapered)
w    = gl.gaps.effective_window(gate, gl.gaps.segment_window(n, 0.05))

# --- 2. noise model: two components; lam = log10 deviations of the component
#        POWERS, so Sigma(lam) = 10^(lam_0) C_0 + 10^(lam_1) C_1, truth at 0
comps = list(gl.psd.lisa_tdi2_ae().values())         # [S_tm(f), S_oms(f)]
S_tot = gl.psd.one_sided_grid(lambda f: sum(c(f) for c in comps), n, dt)
x = gl.simulate.noise_td(S_tot, dt, np.random.default_rng(0), nch=2)

# --- 3. likelihood hierarchy ------------------------------------------------
L_full = gl.FullCovariance.from_window(w, comps, dt, f_lo, f_hi)   # dense windowed cov
L_conv = gl.DiagonalLikelihood.convolved(w, comps, dt, f_lo, f_hi) # exact diagonal
L_whit = gl.DiagonalLikelihood.whittle(w, comps, dt, f_lo, f_hi,
                                       scale_by_window_power=True) # ~ Whittle
L_td   = gl.TimeDomainExact(mask, comps, dt)                       # EXACT, no window

for L in (L_td, L_full, L_conv, L_whit):
    r = L.transform(x)                       # residual = data - template
    print(type(L).__name__, L.loglike(r, (0.0, 0.0)) - L.loglike(r, (0.05, 0.0)))

# --- 4. the same exact likelihood, matrix-free (large N) --------------------
rcg  = gl.RestrictedCG(mask, comps, dt, rtol=1e-8)   # no O(m^2) storage, no O(m^3) setup
quad, iters = rcg.quad_form((0.0, 0.0), L_td.transform(x))
# pair with a determinant: closed form from TimeDomainExact (2 components),
# or stochastic Lanczos quadrature in general

Each tier carries its own constant offset from its internal normalization, so log-likelihoods are not comparable across tiers — only differences (between parameter points, or between templates) are meaningful.

Templates from any waveform: wrap a frequency-domain callable in gl.Waveform(fd_func, n, dt) and use .td(theta); then resid = L.transform(data_td - h_td). For LISA MBHBs, gl.waveform.lisabeta_mbhb_ae(n, dt, f_lo, f_hi) gives the (A, E) TDI-2 IMRPhenomHM waveform used in the paper — with any source parameters, any parametrization (to_params) and any approximant (wf_kw); see notebooks/mbhb_gaps_demo.ipynb.

Reproducing the paper figures

All commands run from paper/ with the [pe] extra installed. The cached PE chains ship in paper/results/, so every chain-based figure regenerates in minutes without lisabeta and without sampling; lisabeta and the from- scratch runs are needed only to regenerate the chains themselves.

cd paper

# -- chain-based figures, from the cached results/ (no lisabeta needed) -----
python make_figures.py        # corner_key_{A,B}, corner_noise_{A,B},
                              # corner_fullkey_{A,B}, upsilon_xi(.png/_heatmap),
                              # overview                           (~10 min)
python plot_scenC.py          # corner_key_C_full_diag_td, corner_noise_C_...,
                              # cov_colormap_C
python plot_ABC.py            # corner_key_full_ABC
python fig_cov_colormap.py    # cov_colormap_ABC

# -- the CG scaling figure (gaplike only: no lisabeta, no chains) -----------
python fig_cg_scaling.py      # times a^T Sigma_OO^-1 a, dense vs matrix-free
                              # preconditioned CG -> results/cg_scaling.json
                              # (~15-30 min; --kmax-cg/--kmax-dense to shorten)
python _mkfig.py              # -> figures/cg_scaling.{png,pdf}

# -- full regeneration of the chains (lisabeta + hours of sampling) ---------
python driver.py              # A/B x {full,diag,bare,psd}   (~80 min, 2 cores)
python scenC_run.py           # C: FD full + convolved diagonal + exact TD PE
python ensemble_noise.py      # 300-realization ensemble (Table: pseudo-true
                              # points, sandwich scatter)

Figure-to-script map: every corner_*, upsilon_xi* and overview come from make_figures.py; the three standalone scripts above cover the scenario-C corner, the A/B/C overlay corner and the covariance colormaps; fig_cg_scaling.py + _mkfig.py produce the dense-vs-CG scaling figure. results/cg_scaling.json ships with the reference timings (2 cores of an Intel Xeon at 2.80 GHz) so _mkfig.py works out of the box.

The likelihood hierarchy

class covariance model cost / eval guarantees
TimeDomainExact stationary covariance restricted to observed samples O(m) after one O(m³) setup exact: scatter/width = width/true-width = 1 by construction; no window anywhere
FullCovariance dense windowed FD covariance (band-restricted) O(r) after one O(N_b³) setup exact within the band-restricted, circularly-symmetric FD reduction
DiagonalLikelihood.convolved exact diagonal of the windowed covariance O(N_b) unbiased widths per bin; ignores bin–bin correlations
DiagonalLikelihood.whittle raw PSD (optionally × window power W₂) O(N_b) Whittle / "normalizing constant" approximation
gaplike.cg / RestrictedCG same model as TimeDomainExact, matrix-free 4 FFTs × a few hundred iterations, no setup, no storage exact quadratic forms to a chosen tolerance; any number of components; determinant not included

Noise parameters are the same throughout: lam_k are log10 deviations of the component powers from their reference values, Sigma(lam) = sum_k 10^(lam_k) C_k, with the truth at lam = 0.

Both pencil classes (TimeDomainExact, FullCovariance) additionally assume the covariance is linear in exactly two components: one simultaneous diagonalization then makes every likelihood evaluation, Fisher matrix and determinant closed-form. The diagonal tiers and the conjugate gradient route accept any number of components; gaplike.cg is the escape hatch when the two-component structure is not available (spline-knot spectra, extra components) or when m is too large to factorize anything.

The paper's analytic mis-specification machinery (Fisher blocks under the true windowed covariance, leading-order biases, MLE scatter, the Υ/Ξ diagnostics, KL pseudo-true parameters, Godambe–White sandwich) lives in paper/diagnostics.py, on top of the package.

Honest error bars: Υ and Ξ

Two numbers summarize what an approximate likelihood does to an inference: Υ = true scatter / quoted width (calibration — is the error bar honest?) and Ξ = quoted width / exact-analysis width (efficiency — was all the information used?). Honest data cannot beat the exact analysis, so Υ·Ξ ≥ 1, with equality only for the exact likelihood.

The four regimes as repeated experiments — every case a measured value from Table III of the paper. A PP plot sees only Υ: the calibrated-but-inflated analysis (top right, Ξ = 9.3) passes any PP test while quoting error bars 9.3× wider than the data allow; the companion assets/upsilon_xi_pp.gif shows exactly that. Rendered by notebooks/anim_upsilon_xi.py.

The Υ and Ξ diagnostics page in the docs derives both quantities, compares them with PP plots, and explains how to read them. The interactive Υ–Ξ explorer (also a single offline file, assets/upsilon_xi_explorer.html) puts them on sliders: drag a point around the (Ξ, Υ) plane with the forbidden Υ·Ξ < 1 region hatched out, click the paper's measured anchors to load them, and watch the intervals and the PP curve respond.

Repository layout

path content
src/gaplike/ the package: gaps, psd, covariance, simulate, likelihood, cg, waveform
tests/ unit tests (small-N brute-force exactness of every tier, CG vs dense) + machine-precision regression against the paper pipeline
notebooks/exact_inference_demo.ipynb executable end-to-end example (no lisabeta): gapped two-channel LISA noise + toy chirp, joint 4-parameter PE with TimeDomainExact and FullCovariance, comparison corner
notebooks/mbhb_gaps_demo.ipynb inject an MBHB with arbitrary parameters (lisabeta), compare five gap configurations, and set up a full 13-parameter inference ready to launch
paper/ full paper reproduction (scenarios A/B/C, PE, every figure incl. the CG scaling benchmark) — see paper/README.md
assets/, notebooks/anim_upsilon_xi.py talk media: the gap-leakage animation, the Υ/Ξ animations and the interactive Υ–Ξ explorer

Tests

uv pip install -e ".[dev]"
pytest                    # ~40 s; the paper regression needs tests/data/reference.json

tests/test_paper_regression.py rebuilds the paper's three gap scenarios purely from package primitives and reproduces the original pipeline's windows, covariances, noise realization and all likelihood values to ~1e-9.

Citing

If you use gaplike, please cite the software (see CITATION.cff) together with Burke, Pozzoli & Muratore (2026), Zurückbleiben bitte: the impact of window functions on noise and signal parameter inference (in prep.).

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gaplike-0.2.0.tar.gz (45.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gaplike-0.2.0-py3-none-any.whl (32.8 kB view details)

Uploaded Python 3

File details

Details for the file gaplike-0.2.0.tar.gz.

File metadata

  • Download URL: gaplike-0.2.0.tar.gz
  • Upload date:
  • Size: 45.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for gaplike-0.2.0.tar.gz
Algorithm Hash digest
SHA256 7ffb6b34f2e8b2078455c7de8689fa80e60ae6b0df5f6248c19f48d157098418
MD5 5115f63c7c672e333b8b78960fe39633
BLAKE2b-256 ab487a9bacc07f4f0df2aba62f7876f66ee0887aa47aee4000e1218e737972fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for gaplike-0.2.0.tar.gz:

Publisher: publish.yml on gaplike/gaplike

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file gaplike-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: gaplike-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 32.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for gaplike-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 af70cf6341e2f176bbb71f7de8105c696a9842cffb0bbb9f7d045489f4ada3dc
MD5 e7a3c5698d801a3518b110e97e7ae086
BLAKE2b-256 3da003032b97b36e77fb0faafd233470fe55d73cf092869b8aa6610a20123cd2

See more details on using hashes here.

Provenance

The following attestation bundles were made for gaplike-0.2.0-py3-none-any.whl:

Publisher: publish.yml on gaplike/gaplike

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page