Skip to content

Phase 2: PDM, CE, String-Length, MHAOV, TLS - #2

Merged
tjayasinghe merged 4 commits into
mainfrom
feat/phase2-pdm
Jun 29, 2026
Merged

tjayasinghe merged 4 commits into
mainfrom
feat/phase2-pdm

Conversation

@tjayasinghe

Copy link
Copy Markdown
Owner

Adds the five remaining periodograms from the plan, each plugging into the existing
periodogram/batch/CLI machinery with no orchestration changes.

Methods (numpy CPU + cupy GPU unless noted)

  • PDM — Stellingwerf (1978) phase-dispersion Θ = s²/σ² with overlapping covers.
  • Conditional Entropy — Graham et al. (2013) 2-D phase-magnitude histogram entropy.
  • String-Length — Lafler-Kinman / Dworetsky (1983) folded Euclidean path length.
  • MHAOV — Schwarzenberg-Czerny (1996) multiharmonic AOV F-statistic, via the
    least-squares trig-polynomial projection (equivalent to the orthogonal-polynomial form).
  • TLS — from-scratch limb-darkened matched-filter transit search returning SDE +
    depth/duration/t0 (CPU only for now; a GPU kernel is a follow-up).

The four minimization methods (PDM/CE/String-Length) and TLS set the correct
objective_sense, so the shared N-best peak finder ranks them properly.

Validation

Each method is validated to <1e-9 against a transparent in-test reference implementation,
plus planted-signal recovery. Fold methods alias at period multiples/submultiples, so their
recovery tests bound the period range (standard practice).

Also

Fixes a correctness bug where LightCurve.domain passed as a string (e.g. domain=flux)
was not coerced to the Domain enum, so identity checks in the flux/magnitude conversions
failed and already-flux data was re-converted as magnitudes. Domain is now coerced in
__post_init__.

94 tests (6 GPU-gated); ruff + mypy clean.

🤖 Generated with Claude Code

tjayasinghe and others added 4 commits June 28, 2026 19:03
Stellingwerf (1978) dispersion statistic Theta = s^2 / sigma^2 — a
minimization method strong on non-sinusoidal variables (eclipsing binaries,
RR Lyrae, sawtooth pulsators). One vectorized kernel serves both backends,
numpy (CPU) and cupy (GPU), with Stellingwerf covers for bin-phase robustness.

- pdm_theta() vectorized over the period grid; PDMMethod registers with
  objective_sense=min and a pseudo-Nyquist default frequency grid.
- Validated to <1e-9 against a transparent reference implementation; recovers
  planted sine and eclipser periods.
- gpu-info now lists every GPU-capable method.
- 6 tests (+1 GPU-gated); ruff and mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two fast, GPU-friendly fold statistics, both minimization methods with
numpy (CPU) + cupy (GPU) backends sharing one vectorized kernel:

- Conditional Entropy (Graham et al. 2013): 2-D phase-magnitude histogram
  entropy; robust to sparse, aliased survey sampling.
- String-Length (Lafler-Kinman / Dworetsky 1983): folded phase-sorted
  Euclidean path length; cheap, strong on eclipsing/eccentric shapes.

Both validated to <1e-9 against transparent reference implementations and
recover planted sine/eclipser periods. Shared pseudo-Nyquist grid helper
factored into core.grid (PDM/CE/String-Length). 84 tests (5 GPU-gated);
ruff and mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Schwarzenberg-Czerny (1996) AOV F-statistic from a trigonometric-polynomial
(truncated Fourier, order H) fit at each trial frequency — maximized at the
true frequency and far more sensitive than single-harmonic Lomb-Scargle to
sharply non-sinusoidal signals.

- aov_power(): the model sum of squares is the least-squares projection norm
  onto the 2H+1 harmonic basis (mathematically identical to the orthogonal-
  polynomial method), vectorized over the frequency grid via batched normal
  equations; numpy (CPU) + cupy (GPU) share one kernel.
- Validated to rtol 1e-5 against a per-frequency lstsq reference; recovers a
  planted eclipser period. Single-band for now (multi-band AOV is a follow-up).
- 89 tests (6 GPU-gated); ruff and mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TLS (transit least squares), reimplemented from scratch as a limb-darkened
matched filter:
- limb_darkened_template(): quadratic limb-darkening central-transit profile
  used as the matched-filter kernel (vs BLS's box).
- tls_power(): per-period binned matched filter via a batched circular
  correlation of the folded data with the template across phase and trial
  durations; returns SDE plus depth/duration/t0/signal-residue. CPU (numpy),
  vectorized over a transit-width-spaced period grid; GPU kernel is a planned
  follow-up.
- Validated by limb-darkened transit injection-recovery.

Also fixes a correctness bug: LightCurve.domain passed as a string (e.g.
domain=flux) was not coerced to the Domain enum, so the `is` identity checks
in as_flux/as_magnitude failed and already-flux data was re-converted as if it
were magnitudes. Domain is now coerced in __post_init__.

94 tests (6 GPU-gated); ruff and mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tjayasinghe
tjayasinghe merged commit e69ac01 into main Jun 29, 2026
5 checks passed
@tjayasinghe
tjayasinghe deleted the feat/phase2-pdm branch June 29, 2026 02:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant