Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d255e99
feat(prewhiten): automated, uncertainty-aware pre-whitening for pulsa…
tjayasinghe Aug 12, 2026
0ea6708
feat(cli): add prewhiten and batch-prewhiten commands
tjayasinghe Aug 12, 2026
39a8dad
feat(gui): add a pre-whitening analysis mode to the desktop app
tjayasinghe Aug 12, 2026
bf7a159
docs: document pre-whitening and bump the version to 1.2.0.dev0
tjayasinghe Aug 12, 2026
0bb1462
fix: five defects found by an adversarial review of the v1.2 subsystem
tjayasinghe Aug 12, 2026
26ed336
feat(prewhiten): spectral window, native Baluev FAP, bootstrap fix
tjayasinghe Aug 12, 2026
6989402
fix(prewhiten): keep short-period pulsators inside the automatic band
tjayasinghe Aug 12, 2026
d8c280f
feat(gui): give GLS and MHAOV the short-period auto band too
tjayasinghe Aug 12, 2026
1bcdb57
fix(gui): draw component markers on the spectrum curve, not above it
tjayasinghe Aug 12, 2026
2f841b7
feat(prewhiten): flag amplitudes that disagree with the spectrum
tjayasinghe Aug 12, 2026
c985b19
fix(gui): dismiss the peak hover label when the markers go away
tjayasinghe Aug 12, 2026
c117219
feat(gui): the peaks toggle now hides the selection band with the mar…
tjayasinghe Aug 12, 2026
128bb44
feat(multiband): native GPU multi-band GLS with honest bootstrap FAP
tjayasinghe Aug 13, 2026
cc42bc4
feat(multiband): pooled multi-band PDM, conditional entropy, string l…
tjayasinghe Aug 13, 2026
7564aae
feat(multiband): load multi-band files everywhere a light curve loads
tjayasinghe Aug 13, 2026
dc8df65
feat(diagnostics): alias/window diagnostics for any periodogram
tjayasinghe Aug 13, 2026
f033acf
feat(interop): nested-pandas / lsdb adapter for the LINCC stack
tjayasinghe Aug 13, 2026
0249080
perf(multiband): adapt the flex solve chunk to the parameter count
tjayasinghe Aug 13, 2026
e3ec415
bench(multiband): Rubin-cadence recovery — 0%/37% single-band vs 82%/…
tjayasinghe Aug 13, 2026
a5ea27e
docs: multi-band guide, LINCC interop guide, changelog, README for v1.2
tjayasinghe Aug 13, 2026
7bd0ea5
feat(batch): reuse the GPU plan cache for multi-band periodograms
tjayasinghe Aug 13, 2026
db44a6f
docs: render COLUMN_PRESETS in the API table; add multi-band benchmar…
tjayasinghe Aug 13, 2026
8b882d2
feat(supersmoother): native SuperSmoother periodogram, all backends
tjayasinghe Aug 13, 2026
58b7275
docs(examples): multi-band capstone section in the tour notebook
tjayasinghe Aug 13, 2026
367a7f4
docs: SuperSmoother across the guide, reference, README, and changelog
tjayasinghe Aug 13, 2026
8a16aa7
feat(benchmarks): validate every multi-band method on real Stripe 82 …
tjayasinghe Aug 13, 2026
1967195
docs: report the real multi-band validation across README, docs, chan…
tjayasinghe Aug 13, 2026
26757ad
perf(gpu): auto-size MHAOV/SuperSmoother trial-grid chunks
tjayasinghe Aug 13, 2026
f89060f
bench: re-run S82 multiband validation under the auto-batch defaults
tjayasinghe Aug 13, 2026
12622ea
bench: refresh single-curve perf sweep under auto-batch defaults
tjayasinghe Aug 14, 2026
4532d07
fix: three defects found by a pre-release audit of v1.2
tjayasinghe Aug 15, 2026
1b12ee8
docs: correct v1.2 documentation against the code, cut 1.2.0 metadata
tjayasinghe Aug 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
327 changes: 327 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ cff-version: 1.2.0
message: "If you use cuPeriod in your research, please cite it as below."
title: cuPeriod
abstract: >-
Optimized, GPU-accelerated periodograms for astronomy: seven period-search methods
(GLS, BLS, PDM, CE, String-Length, MHAOV, TLS) with fast CPU and CUDA backends, scaling
from a single light curve to millions. A portable PyTorch backend extends GPU
Optimized, GPU-accelerated periodograms for astronomy: eight period-search methods (GLS,
BLS, PDM, CE, String-Length, MHAOV, TLS, SuperSmoother) with fast CPU and CUDA backends,
scaling from a single light curve to millions. A portable PyTorch backend extends GPU
acceleration beyond NVIDIA to AMD (ROCm), Intel (XPU), and Apple (MPS) hardware, and a
multi-threaded numba CPU tier gives every method a fast backend even without a GPU. An
optional PySide6 desktop GUI provides an interactive periodogram explorer over the same
API.
API. For classical pulsators it also performs automated, uncertainty-aware
pre-whitening: iterative sinusoid extraction with principled stopping criteria, error
propagation, combination-frequency identification, and g-mode period-spacing tools.
type: software
authors:
- given-names: Tharindu
family-names: Jayasinghe
# Add your ORCID here to make the citation unambiguous, e.g.:
# orcid: "https://orcid.org/0000-0000-0000-0000"
version: 1.1.0
date-released: "2026-07-08"
version: 1.2.0
date-released: "2026-08-14"
license: GPL-3.0-or-later
repository-code: "https://github.com/tjayasinghe/cuPeriod"
url: "https://cuperiod.readthedocs.io"
Expand Down
162 changes: 133 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ fast CPU backends and GPU-accelerated paths that scale from a single light curve
millions. The NVIDIA CUDA fast paths are joined by a portable **PyTorch** backend that also
runs on AMD, Intel, and Apple GPUs (and a CPU-only path), so the accelerated code is no
longer NVIDIA-only. One API, one CLI, and an optional desktop GUI cover every method, with
frictionless column handling, multi-band support, raw-spectrum output, and an
N-best-periods utility.
frictionless column handling, joint multi-band search, alias diagnostics, raw-spectrum
output, and an N-best-periods utility.

📖 **Documentation:** <https://cuperiod.readthedocs.io> — a [5-minute
quickstart](https://cuperiod.readthedocs.io/en/latest/quickstart.html), a full user guide,
Expand All @@ -31,26 +31,50 @@ Every implementation is validated against the standard reference (astropy

- **Validated, not just fast.** GLS and BLS match astropy to floating-point round-off,
and every other method is checked against an independent reference implementation
(PyAstronomy, or a direct implementation of the published algorithm) on identical
grids. CPU, CUDA, and the portable PyTorch backend agree to round-off and pick the
(PyAstronomy, the `supersmoother` package and gatspy, or a direct implementation of the
published algorithm) on identical grids. Across the seven benchmark-suite methods, CPU,
CUDA, and the portable PyTorch backend agree to round-off and pick the
identical best period on 126 real ASAS-SN light curves
across six variability classes plus 12 confirmed *Kepler* transits — 88–96%
harmonic-aware period recovery on this deliberately heterogeneous sample (a synthetic
injection–recovery sweep further characterizes sensitivity vs. signal-to-noise) — see the
[full benchmark report](benchmarks/REPORT.md) and the
[benchmarks docs page](https://cuperiod.readthedocs.io/en/latest/benchmarks.html).
[benchmarks docs page](https://cuperiod.readthedocs.io/en/latest/benchmarks.html). The
multi-band methods are further validated blind on 100 real SDSS Stripe 82 RR Lyrae with
literature periods — the pooled fold statistics recover up to 93% of periods strictly
(97% harmonic-aware) on real five-band data.
- **A fast CPU tier, no GPU required.** The `[fast]` extra's multicore `numba` kernels
make `backend="cpu"` 18x faster than astropy's `BoxLeastSquares` and 2106x faster than
make `backend="cpu"` 20x faster than astropy's `BoxLeastSquares` and 2177x faster than
PyAstronomy's PDM on a representative light curve, while recovering the same periods.
- **GPU acceleration beyond NVIDIA.** The portable PyTorch backend runs every method on
AMD (ROCm), Intel (XPU), and Apple (MPS) GPUs, in addition to the NVIDIA CUDA fast
paths — so the accelerated code isn't locked to one vendor.
- **Built for catalogue scale.** `batch_periodograms` sustains up to 587 light curves/s
- **Built for catalogue scale.** `batch_periodograms` sustains up to 574 light curves/s
(>2 million/hour) on a single GPU, with a resumable batch sink for runs spanning
millions of curves.
- **Seven methods, one API.** GLS, BLS, PDM, CE, String-Length, MHAOV, and TLS share one
entry point, one CLI, and an optional desktop GUI, with frictionless column handling and
multi-band support.
- **Eight methods, one API.** GLS, BLS, PDM, CE, String-Length, MHAOV, TLS, and
SuperSmoother share one entry point, one CLI, and an optional desktop GUI, with
frictionless column handling.
- **Multi-band search that earns its keep.** Seven of the eight methods fit several filters
of one star jointly. The native multi-band GLS offers three models — shared-phase
offsets (the default), independent per-band sinusoids, and a regularized per-band
harmonic model — runs on every backend (a six-band star over 200k frequencies: 55 s
through astropy, **0.13 s** native), and comes with **bootstrap false-alarm
probabilities**, which astropy's `LombScargleMultiband` does not provide at all. On a
simulated Rubin-like cadence with 30 epochs spread over six bands, single-band GLS
recovers 0% of faint RRab proxies and the shared-phase model recovers **82%**.
- **Survey catalogues, in place.** `cuperiod.interop` runs a search directly over LINCC
nested-pandas / lsdb light curves — one row per object, epochs in a nested column — with
no flattening or `groupby`. A partition tier reuses **one GPU engine** across every
object in a dask partition, with verified column presets for ZTF and Rubin DP1.
- **Alias-checked periods.** `alias_diagnostics` measures the sampling's spectral window,
predicts the alias family it implies, scores each competitor against the peak you got,
and says plainly when the period is ambiguous.
- **Pulsators get a frequency solution, not just a period.** `prewhiten` automates the
whole Period04-style loop — GPU/NUFFT amplitude spectrum, iterative sinusoid extraction,
simultaneous re-fitting, principled stopping criteria, propagated uncertainties,
combination-frequency identification, and g-mode period-spacing tools — for one star or
a million.

## Status

Expand All @@ -62,21 +86,24 @@ Implemented now, each with optimized CPU and GPU backends:
| **BLS** | eclipses / box-like transits | yes | yes |
| **MHAOV** | sharply non-sinusoidal signals (multiharmonic AOV) | yes | yes |
| **TLS** | limb-darkened transit matched filter | yes | — |
| **PDM** | non-sinusoidal folds (Stellingwerf) | yes | — |
| **CE** | sparse survey data (conditional entropy) | yes | — |
| **String-Length** | eclipsing / eccentric shapes | yes | — |
| **PDM** | non-sinusoidal folds (Stellingwerf) | yes | yes |
| **CE** | sparse survey data (conditional entropy) | yes | yes |
| **String-Length** | eclipsing / eccentric shapes | yes | yes |
| **SuperSmoother** | any repeating shape, non-parametric (Friedman) | yes | yes |

All seven methods have CPU and GPU backends, plus the full single/batch/CLI machinery.
All eight methods have CPU and GPU backends, plus the full single/batch/CLI machinery.

## Install

```bash
pip install cuperiod # CPU (numpy, scipy, astropy, finufft)
pip install "cuperiod[gpu]" # + CUDA 12 GPU backends (cupy, cufinufft)
pip install "cuperiod[torch]" # + portable PyTorch backend (AMD/Intel/Apple GPUs + CPU)
pip install "cuperiod[fast]" # + numba multicore CPU kernels (all 7 methods, 20-300x)
pip install "cuperiod[fast]" # + numba multicore CPU kernels (7 methods, no GLS, 20-300x)
pip install "cuperiod[gui]" # + interactive desktop GUI (cuperiod-gui)
pip install "cuperiod[pandas]" # + pandas DataFrame ingestion
pip install "cuperiod[nested]" # + nested-pandas light curves (cuperiod.interop)
pip install "cuperiod[lsdb]" # + lsdb HATS catalogs (lazy, dask-partitioned)
```

The `[gpu]` extra needs an NVIDIA GPU with the CUDA 12 runtime; it pulls in `cupy-cuda12x`,
Expand All @@ -85,9 +112,9 @@ backend that reaches AMD (ROCm), Intel (XPU), and Apple-Silicon (MPS) GPUs — a
everywhere — so the accelerated code runs beyond NVIDIA (install the wheel matching your
accelerator from [pytorch.org](https://pytorch.org/get-started/locally/); the default is
CPU-only). The `[fast]` extra adds multicore `numba` CPU kernels that become the default
`"cpu"`/`"auto"` backend for **every** method — BLS, PDM, CE, String-Length, MHAOV, and
TLS — one to two orders of magnitude faster than the fallback CPU paths and matching them
to floating point.
`"cpu"`/`"auto"` backend for **every method but GLS** — BLS, PDM, CE, String-Length,
MHAOV, TLS, and SuperSmoother — one to two orders of magnitude faster than the fallback
CPU paths and matching them to floating point.

Not sure what will run where? `cuperiod doctor` reports every installed backend, the torch
devices it sees and the precision each uses, and what `backend="auto"` resolves to.
Expand Down Expand Up @@ -125,13 +152,63 @@ Method names are case-insensitive (`"gls"` == `"GLS"`).

### Multi-band (one star, several filters)

GLS, BLS, and MHAOV jointly model two or more bands of the same star:
GLS, BLS, MHAOV, PDM, CE, String-Length, and SuperSmoother jointly model two or more bands
of the same star — one period, but each band keeps its own mean, amplitude, and
normalization:

```python
mb = cup.MultiBandLightCurve.from_light_curves({"g": lc_g, "r": lc_r})
pg = cup.periodogram(mb, "GLS") # VanderPlas & Ivezić shared-phase model

# ...or straight from a long-format file / the CLI / batch:
mb = cup.MultiBandLightCurve.from_file("star_ugrizy.parquet", band_column="band")

# One of three joint models (offsets / perband / flex):
settings = cup.GLSSettings(mb_model="flex")
pg = cup.periodogram(mb, "GLS", settings=settings)

# Honest false-alarm probabilities, from a within-band bootstrap:
calib = cup.multiband_fap(mb, settings, n_bootstrap=1000)
print(calib.fap(pg.best_periods(1)[0].power), calib.level(0.01))
```

`mb_model` is `"offsets"` (shared phase + per-band offsets, the default), `"perband"`
(independent per-band sinusoids, `chi2_0`-weighted), or `"flex"` (regularized per-band
harmonics, matching astropy to ~2e-10). All three run natively on finufft, cufinufft, and
torch — astropy is kept only as the reference they're tested against.

Period recovery on a simulated Rubin-like six-band cadence
(`benchmarks/multiband_recovery.py`; faint RRab proxies, 0.20 mag per-point noise, 300
stars per cell, top period within 1% and no harmonic credit), by *total* epochs across all
six bands:

| strategy | 30 epochs | 60 epochs | 120 epochs |
| --- | --- | --- | --- |
| best single band (r) | 0.0% | 37.3% | 97.3% |
| any single band | 0.0% | 49.3% | 99.0% |
| multi-band `offsets` (1,0) | **81.7%** | **99.7%** | 100.0% |

`perband` and `flex` land in between (17.7% / 20.0% at 30 epochs, 97.0% at 60): sharing
the phase is what buys sparse-cadence recovery, which is why `"offsets"` is the default.
These are simulations on a deliberately simplified cadence (random nights, no rolling
cadence) — read the ordering, not the absolute numbers. See the
[multi-band guide](https://cuperiod.readthedocs.io/en/latest/guide/multiband.html).

### Survey catalogs (LINCC: nested-pandas / lsdb)

```python
from cuperiod.interop import nested_periodogram, partition_periodogram

out = nested_periodogram(frame, "lc", preset="ztf_dr22") # row-wise
res = partition_periodogram(cat, preset="rubin_dp1_object", # one GPU engine
method="GLS", backend="gpu") # per partition
```

Runs on the nested layout directly — no flattening, no `groupby`. Column presets for ZTF
DR22, ZTF alerts, and Rubin DP1 object/DIA photometry; a failed object yields NaN rather
than aborting the run. See the
[interop guide](https://cuperiod.readthedocs.io/en/latest/guide/interop.html).

### Backends

`backend="auto"` (default) uses the GPU when available and falls back to CPU. Force a path
Expand All @@ -141,6 +218,25 @@ device: `backend="torch"` (best device present) or `"torch:cpu"` / `"torch:cuda"
`"torch:mps"` / `"torch:xpu"`. On Apple MPS (no float64) it uses float32; `precision="auto"`
keeps float64 everywhere else.

## Pre-whitening a pulsator

```python
solution = cup.prewhiten((time, mag, mag_err))
print(solution.summary()) # ranked frequencies with 1-sigma uncertainties and S/N

for c in solution.components:
print(c.label, c.frequency, c.frequency_error, c.snr, c.combination)

series = cup.find_period_spacing( # gamma Dor / SPB g-mode pattern
[c.period for c in solution.independent()],
[c.amplitude for c in solution.independent()],
)
```

Every judgement call an interactive session leaves to the operator is an explicit setting,
and the result records **why the extraction stopped**. See the
[pre-whitening guide](https://cuperiod.readthedocs.io/en/latest/guide/prewhitening.html).

## Batch processing (millions of light curves)

```python
Expand All @@ -162,29 +258,37 @@ written. `suggest_gpu_workers` sizes the GPU pool from probed device memory.
```bash
cuperiod run star.csv --method GLS,BLS --n-best 10
cuperiod batch "lcs/*.csv" --method GLS --device gpu --out results/
cuperiod prewhiten star.csv --snr 4.6 -n 30 --spacing
cuperiod batch-prewhiten "lcs/*.csv" --out modes.parquet
cuperiod methods # list methods and backends
cuperiod gpu-info # device + suggested worker counts
cuperiod doctor # backends, torch devices, precision, auto-resolution
cuperiod grid-info star.fits -m GLS
```

`run` accepts `--time/--value/--error/--band` overrides and `--domain magnitude|flux`, and
can write JSON (`--out`) and the raw spectrum (`--save-periodogram`).
`run` accepts `--time/--value/--error` column overrides and `--domain magnitude|flux`, and
can write JSON (`--out`) and the raw spectrum (`--save-periodogram`). `--band` splits a
long-format file on its filter column and runs a **joint** multi-band fit; the Python API's
`batch_periodograms(..., band_column=...)` takes the same (the `batch` command has no band
option yet).

## Desktop GUI

An interactive periodogram explorer ships with the `[gui]` extra — run any method with all
of its options, explore the full-resolution spectrum, and watch the phased light curve
update live as you drag across peaks. Single curves or a whole folder in batch mode,
multi-band overlays, and a dark/light theme remembered across launches.
An interactive explorer ships with the `[gui]` extra — run any method with all of its
options, explore the full-resolution spectrum, and watch the phased light curve update
live as you drag across peaks. Single curves or a whole folder in batch mode, multi-band
overlays, and a dark/light theme remembered across launches. An **Analysis** picker
switches the same window to pre-whitening: the amplitude spectrum with the residual
overlaid, a frequency table with uncertainties, and a g-mode period-spacing explorer.

```bash
pip install "cuperiod[gui]" # add [gpu] or [torch] for accelerated backends
cuperiod-gui # or: python -m cuperiod.gui
```

It opens with bundled demo light curves (a *Kepler* transit, six ASAS-SN variables, a
synthetic multi-band curve), so there's something to explore on first launch.
The toolbar's **Load demo** menu always offers a synthetic multi-band curve, so there's
something to explore on first launch. In a source checkout (or with `CUPERIOD_EXAMPLE_DATA`
pointed at `examples/data`) it also lists a *Kepler* transit and six ASAS-SN variables.

## Light-curve inputs

Expand All @@ -201,8 +305,8 @@ for the machine-readable record (also picked up by GitHub's "Cite this repositor
@software{jayasinghe_cuperiod,
author = {Jayasinghe, Tharindu},
title = {cuPeriod},
version = {1.1.0},
date = {2026-07-08},
version = {1.2.0},
date = {2026-08-14},
url = {https://github.com/tjayasinghe/cuPeriod}
}
```
Expand Down
Loading
Loading