Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ First public release.
- `cuperiod` command-line interface: `run`, `batch`, `methods`, `gpu-info`, `grid-info`.
- Per-method settings models with `CUPERIOD_<METHOD>_<FIELD>` environment overrides, and
GPU worker auto-sizing.
- Full Sphinx documentation (hosted on Read the Docs) and a reproducible validation +
benchmark suite under `benchmarks/`.
- Full Sphinx documentation (hosted on Read the Docs), a worked-example Jupyter notebook
(`examples/cuperiod_tour.ipynb`), and a reproducible validation + benchmark suite under
`benchmarks/`.

### Robustness

Expand All @@ -50,6 +51,8 @@ First public release.
- The batch process pool uses the `spawn` start method on every platform, so a CPU/GPU
pool no longer deadlocks on Linux (the default `fork` copies parent native thread pools
/ CUDA contexts into the workers).
- Method-name lookup ignores case and non-alphanumeric separators, so `"String-Length"`,
`"StringLength"` and `"STRINGLENGTH"` all resolve (as documented).

### Validated

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ frequency, power = pg.frequency, pg.power

Method names are case-insensitive (`"gls"` == `"GLS"`).

> 📓 **New here?** The [`examples/cuperiod_tour.ipynb`](examples/cuperiod_tour.ipynb)
> notebook works through real light curves — a Cepheid, an RR Lyrae, an eclipsing binary,
> a Mira, and a *Kepler* exoplanet — showing each periodogram and phase-folded result.

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

GLS, BLS, and (soon) MHAOV jointly model two or more bands of the same star:
Expand Down
3 changes: 3 additions & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ See {doc}`guide/cli`.

**Next steps**

- 📓 The [example notebook](https://github.com/tjayasinghe/cuPeriod/blob/main/examples/cuperiod_tour.ipynb)
— a guided tour over real light curves (Cepheid, RR Lyrae, eclipsing binary, Mira, and a
*Kepler* exoplanet) with every periodogram and phase-fold.
- {doc}`guide/methods` — pick the right method for your signal.
- {doc}`guide/results` — everything `best_periods` and `Periodogram` can do.
- {doc}`guide/batch` — scale to thousands or millions of light curves.
34 changes: 34 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Examples

## [`cuperiod_tour.ipynb`](cuperiod_tour.ipynb) — a guided tour

A hands-on walkthrough of cuPeriod on **real light curves**. For each kind of object it
loads the data, runs the appropriate periodogram, reads the peak, and phase-folds to
reveal the signal — three pictures per star (raw → periodogram → phased).

| Object | Method | What it teaches |
| --- | --- | --- |
| Classical Cepheid | **GLS** | the basics: raw → periodogram → phased |
| RR Lyrae | **MHAOV** | sharp, multiharmonic pulsations |
| Eclipsing binary | **BLS** (+ the GLS *P*/2 trap) | choosing the right method |
| Long-period variable (Mira) | **PDM** | non-sinusoidal folds, long baselines |
| Exoplanet (Kepler KIC 7532973) | **TLS** | a transit matched filter |
| — | several at once | comparing methods, reading the N-best peaks |

### Run it

```bash
pip install cuperiod matplotlib pandas pyarrow # add "cuperiod[gpu]" to use a GPU
jupyter lab cuperiod_tour.ipynb
```

The notebook is **fully self-contained and offline** — the `data/` folder holds the
bundled light curves, so no download is needed. With an NVIDIA GPU the same code runs on
the GPU automatically (`backend="auto"`).

### Data provenance

- `data/asassn_examples.parquet` — 6 public [ASAS-SN](https://asas-sn.osu.edu/) *g*-band
light curves (one per variability class), each with its VSX literature period.
- `data/kepler_KIC7532973.csv` — *Kepler* PDCSAP flux for a confirmed hot-Jupiter host,
fetched once with [lightkurve](https://docs.lightkurve.org/).
576 changes: 576 additions & 0 deletions examples/cuperiod_tour.ipynb

Large diffs are not rendered by default.

Binary file added examples/data/asassn_examples.parquet
Binary file not shown.
10,298 changes: 10,298 additions & 0 deletions examples/data/kepler_KIC7532973.csv

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/cuperiod/core/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ class MultiResult:
results: Mapping[str, Periodogram]

def __getitem__(self, method: str) -> Periodogram:
return self.results[method.upper()]
key = "".join(ch for ch in method if ch.isalnum()).upper()
return self.results[key]

def __iter__(self) -> Any:
return iter(self.results)
Expand Down
20 changes: 16 additions & 4 deletions src/cuperiod/methods/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,22 +172,34 @@ class MethodInfo:
all_backends: tuple[str, ...]


def _normalize_name(name: str) -> str:
"""Canonical registry key: uppercase with non-alphanumerics removed.

Lets ``"String-Length"``, ``"StringLength"`` and ``"STRINGLENGTH"`` (and ``"gls"`` /
``"GLS"``) all resolve to the same method.
"""
return "".join(ch for ch in name if ch.isalnum()).upper()


def register(method: PeriodogramMethod) -> PeriodogramMethod:
"""Register ``method`` under its uppercase name. Returns it (for decoration)."""
_REGISTRY[method.name.upper()] = method
"""Register ``method`` under its normalized name. Returns it (for decoration)."""
_REGISTRY[_normalize_name(method.name)] = method
return method


def get_method(name: str) -> PeriodogramMethod:
"""Look up a registered method by (case-insensitive) name.
"""Look up a registered method by name.

Matching ignores case and any non-alphanumeric characters, so ``"String-Length"``,
``"StringLength"`` and ``"STRINGLENGTH"`` all resolve to the same method.

Raises
------
UnknownMethodError
If no method is registered under ``name``.
"""
try:
return _REGISTRY[name.upper()]
return _REGISTRY[_normalize_name(name)]
except KeyError:
raise UnknownMethodError(
f"unknown method {name!r}; registered: {sorted(_REGISTRY)}"
Expand Down
21 changes: 21 additions & 0 deletions tests/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ def test_lookup_is_case_insensitive() -> None:
assert get_method("gls") is get_method("GLS")


def test_lookup_ignores_separators() -> None:
# The documented "String-Length" spelling (and underscores) must resolve.
sl = get_method("STRINGLENGTH")
assert get_method("String-Length") is sl
assert get_method("StringLength") is sl
assert get_method("string_length") is sl


def test_multiresult_getitem_normalizes() -> None:
import numpy as np

import cuperiod as cup

rng = np.random.default_rng(0)
t = np.sort(rng.uniform(0, 80, 300))
y = 12 + 0.3 * np.sin(2 * np.pi * t / 2.0) + 0.02 * rng.standard_normal(300)
res = cup.periodogram((t, y, np.full(300, 0.02)), ["GLS", "String-Length"])
assert res["String-Length"].method == "STRINGLENGTH"
assert res["stringlength"] is res["String-Length"]


def test_unknown_method_raises() -> None:
with pytest.raises(UnknownMethodError):
get_method("not-a-method")
Expand Down
Loading