Skip to content

Commit de4003e

Browse files
committed
Add ATCAT docs, tests, and README embed section
- Add ATCAT class docstring with parameters, example, model license, and ADS citation; fix mag_zp docs in __init__ and from_hf - Add ADS citations and model license sections to Astromer1/2 docstrings - Fix ExplicitMultiBandModel: band_mappings now stores (keys, fn) pairs so each group filters to its own observations before mapping, fixing the list-of-dicts path; fix truncated __call__ docstring and error messages - Add ort.InferenceSession type hint for session parameter across all model classes; guard session.get_inputs() removed (session is always non-None) - Add tests/embed/test_model.py covering SingleBandModel bands routing and ExplicitMultiBandModel band_groups validation, allow_extra_bands, and list-of-dicts output shape - Move ATCAT_LSST_BAND_GROUPS to conftest.py as a session-scoped fixture - Add light_curve.embed section to README with Astromer and ATCAT examples and onnxruntime installation instructions - Update CHANGELOG
1 parent d0f2f35 commit de4003e

8 files changed

Lines changed: 663 additions & 144 deletions

File tree

CHANGELOG.md

Lines changed: 169 additions & 86 deletions
Large diffs are not rendered by default.

light-curve/README.md

Lines changed: 142 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ amplitudes = lc.Amplitude().many(light_curves)
4545
print(f"Amplitude: mean = {np.mean(amplitudes):.3f} mag, std = {np.std(amplitudes):.3f} mag")
4646
```
4747

48-
For conda, alternative package names, and platform-specific installation notes, see the [Installation](#installation)
48+
For conda, alternative package names, and platform-specific installation notes, see
49+
the [Installation](#installation)
4950
section.
5051

5152
## Feature extractors
5253

53-
Most classes implement feature extractors useful for astrophysical source classification and characterization based
54+
Most classes implement feature extractors useful for astrophysical source classification and characterization
55+
based
5456
on light curves.
5557

5658
To list all available feature extractors:
@@ -86,7 +88,8 @@ The Flux/magnitude column indicates whether a feature should be used with flux l
8688
magnitude light curves only, or either.
8789

8890
The `transform=True` column shows the default transformation applied to the output when `transform=True`
89-
is passed to the extractor constructor (default is `transform=None`, which does not apply any transformations).
91+
is passed to the extractor constructor (default is `transform=None`, which does not apply any
92+
transformations).
9093
Transformations are monotonic functions intended to reduce dynamic range and improve the suitability of
9194
raw feature values for machine learning.
9295
You can also select a transformation by name string (e.g. `transform="lg"`) for supported features;
@@ -457,20 +460,24 @@ Meta-features accept other feature extractors and apply them to pre-processed da
457460
#### Periodogram
458461

459462
This feature transforms time-series data into the Lomb-Scargle periodogram, providing an estimate of the power
460-
spectrum. The `peaks` argument specifies how many of the most significant spectral density peaks to return. For
463+
spectrum. The `peaks` argument specifies how many of the most significant spectral density peaks to return.
464+
For
461465
each peak, its period and signal-to-noise ratio are returned:
462466

463467
$$
464468
\text{signal to noise of peak} \equiv \frac{P(\omega_\mathrm{peak}) - \langle P(\omega) \rangle}{\sigma\_{P(\omega)}}
465469
$$
466470

467-
The optional `features` argument accepts a list of additional feature extractors, which are applied to the power
468-
spectrum: frequency is passed as "time," power spectrum is passed as "magnitude," and no uncertainties are set.
471+
The optional `features` argument accepts a list of additional feature extractors, which are applied to the
472+
power
473+
spectrum: frequency is passed as "time," power spectrum is passed as "magnitude," and no uncertainties are
474+
set.
469475

470476
#### Bins
471477

472478
Bins the time series into windows of width $\mathrm{window}$ with respect to some $\mathrm{offset}$.
473-
The $j$-th bin spans $[j \cdot \mathrm{window} + \mathrm{offset},\ (j + 1) \cdot \mathrm{window} + \mathrm{offset}]$.
479+
The $j$-th bin
480+
spans $[j \cdot \mathrm{window} + \mathrm{offset},\ (j + 1) \cdot \mathrm{window} + \mathrm{offset}]$.
474481

475482
The binned time series is defined by
476483
$$t_j^* = (j + \frac12) \cdot \mathrm{window} + \mathrm{offset},$$
@@ -501,7 +508,8 @@ gives the best balance of robustness and final accuracy.
501508

502509
By default, initial parameter values and bounds are estimated from the data. Override them with the
503510
`init` and `bounds` arguments (supported by MCMC-based algorithms only), each a list of values or
504-
`None`s to keep data-derived defaults for individual parameters. `BazinFit` has 5 parameters, `LinexpFit` has 4,
511+
`None`s to keep data-derived defaults for individual parameters. `BazinFit` has 5 parameters, `LinexpFit` has
512+
4,
505513
`VillarFit` has 7; run `help()` on the respective class for the parameter order.
506514

507515
The `ln_prior` argument sets the MCMC prior. It accepts `None` / `"no"` (flat prior, the default),
@@ -511,7 +519,8 @@ distributions (see `help(light_curve.ln_prior)` for the available distribution t
511519
encodes supernova physics constraints; it assumes time values are in days.
512520

513521
Pass `transform=True` to convert the raw fit parameters to a magnitude-like representation: the
514-
amplitude becomes a magnitude (assuming input flux in janskys), the baseline is normalized by the amplitude, the
522+
amplitude becomes a magnitude (assuming input flux in janskys), the baseline is normalized by the amplitude,
523+
the
515524
reference time is
516525
dropped, and the reduced chi^2 is log-scaled. Run `help(lc.BazinFit)` for the exact definition.
517526

@@ -539,9 +548,12 @@ print(values)
539548
#### Rainbow Fit
540549

541550
Rainbow ([Russeil+23](https://arxiv.org/abs/2310.02916)) is a black-body parametric model for transient light
542-
curves. By default, it uses the Bazin function as a model for bolometric flux evolution and a logistic function
543-
for temperature evolution. The user may customize the model by providing their own functions for bolometric flux
544-
and temperature evolution. This example demonstrates the reconstruction of a synthetic light curve with this model.
551+
curves. By default, it uses the Bazin function as a model for bolometric flux evolution and a logistic
552+
function
553+
for temperature evolution. The user may customize the model by providing their own functions for bolometric
554+
flux
555+
and temperature evolution. This example demonstrates the reconstruction of a synthetic light curve with this
556+
model.
545557
`RainbowFit` requires the `iminuit` package.
546558

547559
<!-- name: test_rainbow_fit_example -->
@@ -594,13 +606,15 @@ print(dict(zip(feature.names, values)))
594606
print(f"Goodness of fit: {values[-1]}")
595607
```
596608

597-
Note that while the data generation above uses approximate physical constant values, `RainbowFit` uses CODATA 2018
609+
Note that while the data generation above uses approximate physical constant values, `RainbowFit` uses CODATA
610+
2018
598611
values internally.
599612

600613
### Experimental extractors
601614

602615
The package consists of two parts: a wrapper for the
603-
[`light-curve-feature` Rust crate](https://crates.io/crates/light-curve-feature) (`light_curve_ext` sub-package)
616+
[`light-curve-feature` Rust crate](https://crates.io/crates/light-curve-feature) (`light_curve_ext`
617+
sub-package)
604618
and a pure-Python sub-package `light_curve_py`.
605619
We use the Python implementation to test the Rust implementation and to develop new experimental extractors.
606620
The Python implementation is significantly slower for most extractors and does not provide the same full
@@ -631,12 +645,15 @@ This should print a warning about the experimental status of the Python class.
631645

632646
## Performance
633647

634-
The package is designed for high throughput. The following techniques help extract features with minimal overhead.
648+
The package is designed for high throughput. The following techniques help extract features with minimal
649+
overhead.
635650

636651
### `sorted` and `check` parameters
637652

638-
The `sorted=True` argument tells the extractor that `t` is already sorted in ascending order, and `check=False`
639-
disables validation of NaN/inf values. The defaults are `sorted=None` (the array will be validated and an error raised
653+
The `sorted=True` argument tells the extractor that `t` is already sorted in ascending order, and
654+
`check=False`
655+
disables validation of NaN/inf values. The defaults are `sorted=None` (the array will be validated and an
656+
error raised
640657
if unsorted) and
641658
`check=True`. Passing invalid inputs without validation can cause incorrect results or crashes.
642659

@@ -780,7 +797,8 @@ Run all benchmarks from the Python project folder with
780797
`python3 -mpytest --benchmark-enable tests/test_w_bench.py`, or with slow benchmarks disabled:
781798
`python3 -mpytest -m "not (nobs or multi)" --benchmark-enable tests/test_w_bench.py`.
782799

783-
Below we benchmark the Rust implementation (`rust`) against the [`feets`](https://feets.readthedocs.io/en/latest/)
800+
Below we benchmark the Rust implementation (`rust`) against the [
801+
`feets`](https://feets.readthedocs.io/en/latest/)
784802
package and our own Python implementation (`lc_py`) for a light curve with n=1000 observations.
785803

786804
![Benchmarks, Rust is much faster](https://github.com/light-curve/light-curve-python/raw/main/light-curve/.readme/benchplot_v2.png)
@@ -792,17 +810,112 @@ but the typical extraction time including these features is 20–50 ms for a few
792810

793811
![Benchmark for different number of observations](https://github.com/light-curve/light-curve-python/raw/main/light-curve/.readme/nobs_bench_v2.png)
794812

795-
Benchmark results for both the pure-Python and Rust implementations as a function of the number of observations.
813+
Benchmark results for both the pure-Python and Rust implementations as a function of the number of
814+
observations.
796815
Both axes are on a logarithmic scale.
797816

798817
![Benchmark for multithreading and multiprocessing](https://github.com/light-curve/light-curve-python/raw/main/light-curve/.readme/multi_bench_v2.png)
799818

800-
Processing time per light curve for the feature subset from the first benchmark, as a function of the number of
819+
Processing time per light curve for the feature subset from the first benchmark, as a function of the number
820+
of
801821
CPU cores used. The dataset consists of 10,000 light curves with 1,000 observations each.
802822

803823
See the benchmarks described in more detail in
804824
["Performant feature extraction for photometric time series"](https://arxiv.org/abs/2302.10837).
805825

826+
## Light curve embeddings (`light_curve.embed`)
827+
828+
The `light_curve.embed` submodule provides pretrained neural network models that map raw photometric
829+
time series to dense vector embeddings suitable for downstream machine learning tasks such as
830+
classification, anomaly detection, and similarity search.
831+
Models are loaded directly from HuggingFace (weights are cached locally after the first download)
832+
and require `onnxruntime` and `huggingface-hub`:
833+
834+
```
835+
pip install onnxruntime huggingface_hub
836+
```
837+
838+
See the [onnxruntime install guide](https://onnxruntime.ai/docs/install/) for GPU and platform-specific
839+
packages (`onnxruntime-gpu`, `onnxruntime-directml`, etc.).
840+
Hardware-specific options such as execution providers can be passed via `ort_session_kwargs`
841+
(see [onnxruntime Python API](https://onnxruntime.ai/docs/api/python/api_summary.html)).
842+
843+
### Single-band models: Astromer
844+
845+
`Astromer1` ([Donoso-Oliva et al. 2023](https://ui.adsabs.harvard.edu/abs/2023A%26A...670A..54D/abstract))
846+
and `Astromer2` ([Donoso-Oliva et al. 2026](https://ui.adsabs.harvard.edu/abs/2026A%26A...707A.170D/abstract))
847+
are models pretrained on MACHO light curves.
848+
They accept irregularly-sampled `(time, mag)` pairs and return 256-dimensional embeddings.
849+
850+
<!-- name: test_embed_astromer -->
851+
852+
```python
853+
import numpy as np
854+
from light_curve.embed import Astromer2
855+
856+
model = Astromer2.from_hf(output="mean")
857+
# The default reduction splits long light curves into non-overlapping windows;
858+
# pass e.g. reduction="beginning" to always take the first 200 observations.
859+
860+
rng = np.random.default_rng(0)
861+
time = np.sort(rng.uniform(0, 500, 120)).astype(np.float64)
862+
mag = rng.normal(15, 0.5, 120).astype(np.float64)
863+
864+
# Returns shape (n_bands, n_subsamples, seq_size, embed_dim)
865+
embedding = model(time, mag)
866+
print(embedding.shape) # (1, 1, 1, 256)
867+
```
868+
869+
Multi-band light curves can be embedded per-band by passing a list of band labels to the `bands`
870+
constructor argument:
871+
872+
<!-- name: test_embed_astromer_multiband -->
873+
874+
```python
875+
import numpy as np
876+
from light_curve.embed import Astromer2
877+
878+
model = Astromer2.from_hf(output="mean", bands=["g", "r"])
879+
880+
rng = np.random.default_rng(1)
881+
n = 80
882+
time = np.sort(rng.uniform(0, 300, n)).astype(np.float64)
883+
mag = rng.normal(15, 0.5, n).astype(np.float64)
884+
band = np.array(["g", "r"] * (n // 2))
885+
886+
# n_bands=2: one embedding per band
887+
embedding = model(time, mag, band=band)
888+
print(embedding.shape) # (2, 1, 1, 256)
889+
```
890+
891+
### Multi-band model: ATCAT
892+
893+
`ATCAT` ([Tung 2025](https://ui.adsabs.harvard.edu/abs/2025arXiv251100614T/abstract)) is a model
894+
trained on ELAsTiCC light curves. It processes all six LSST bands jointly and returns
895+
384-dimensional embeddings. Inputs are flux, flux-error, time, and integer band indices (ugrizY → 012345):
896+
897+
<!-- name: test_embed_atcat -->
898+
899+
```python
900+
import numpy as np
901+
from light_curve.embed import ATCAT
902+
903+
model = ATCAT.from_hf(output="last")
904+
905+
rng = np.random.default_rng(2)
906+
n = 120
907+
time = np.sort(rng.uniform(0, 500, n)).astype(np.float32)
908+
flux = rng.normal(100, 10, n).astype(np.float32)
909+
flux_err = np.full(n, 5.0, dtype=np.float32)
910+
band = np.array([i % 6 for i in range(n)]) # ugrizY → 012345
911+
912+
embedding = model(time, flux, flux_err, band)
913+
print(embedding.shape) # (1, 1, 1, 384)
914+
```
915+
916+
Input fluxes should be in AB units. The default zero-point is 31.4 (LSST nJy); set `mag_zp=27.5`
917+
for ELAsTiCC / SNANA FITS data or `mag_zp=8.9` for Jy.
918+
806919
## dm-dt map
807920

808921
In addition to the feature extractors above, the package provides a separate dm–dt mapping tool.
@@ -856,12 +969,12 @@ On PyPI, we publish wheels for the stable CPython ABI, ensuring compatibility wi
856969

857970
### Support matrix
858971

859-
| Arch \ OS | Linux glibc 2.17+ | Linux musl 1.2+ | macOS | Windows |
860-
|-------------|-------------------|--------------------------------|-----------------------|---------------------------|
972+
| Arch \ OS | Linux glibc 2.17+ | Linux musl 1.2+ | macOS | Windows |
973+
|-------------|-------------------|--------------------------------|-----------------------|-----------------------------|
861974
| **x86-64** | PyPI (MKL), conda | PyPI (MKL) | PyPI macOS 15+, conda | PyPI, conda (conda: no GSL) |
862-
| **i686** | src | src || not tested |
863-
| **aarch64** | PyPI | PyPI | PyPI macOS 14+, conda | not tested |
864-
| **ppc64le** | src | not tested (no Rust toolchain) || |
975+
| **i686** | src | src || not tested |
976+
| **aarch64** | PyPI | PyPI | PyPI macOS 14+, conda | not tested |
977+
| **ppc64le** | src | not tested (no Rust toolchain) |||
865978

866979
- **PyPI / conda**: A binary wheel or package is available on pypi.org or anaconda.org.
867980
Local building is not required; the only prerequisite is a recent version of `pip` or `conda`.
@@ -921,7 +1034,8 @@ python -mpip install maturin
9211034
maturin develop --group dev
9221035
```
9231036

924-
Run this command during initial setup. On subsequent runs, activate the environment with `source venv/bin/activate`
1037+
Run this command during initial setup. On subsequent runs, activate the environment with
1038+
`source venv/bin/activate`
9251039
and rebuild Rust code with `maturin develop`. Python-only changes require no rebuild. The `--group dev` flag
9261040
(run from the `light-curve/` directory) is only needed once to install development dependencies.
9271041

@@ -953,7 +1067,8 @@ See the [Benchmarks](#benchmarks) section in [Performance](#performance) for mor
9531067
#### Dependencies and Cargo features
9541068

9551069
The package has a number of compile-time Cargo features, mostly controlling which C/C++ dependencies are used.
956-
Pass the desired features to `maturin` with `--features`; it is also recommended to use `--no-default-features`
1070+
Pass the desired features to `maturin` with `--features`; it is also recommended to use
1071+
`--no-default-features`
9571072
to avoid building unnecessary dependencies.
9581073

9591074
Available features:

light-curve/light_curve/embed/astromer.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,10 @@ def predict_tensors(self, tensors: AstromerInputs) -> np.ndarray:
236236

237237

238238
class Astromer1(_AstromerModel):
239-
"""Astromer 1 embedding model (Donoso-Oliva et al. 2023).
239+
"""Astromer 1 embedding model.
240240
241241
Transformer encoder pretrained on MACHO R-band light curves via masked
242-
magnitude prediction. Accepts irregularly-sampled single-band photometry
242+
magnitude prediction. Accepts single-band photometry
243243
and returns a 256-dimensional embedding (2 layers, 4 attention heads).
244244
245245
The ONNX model is hosted on HuggingFace at
@@ -252,6 +252,16 @@ class Astromer1(_AstromerModel):
252252
253253
Use :meth:`from_hf` to download and load the model directly.
254254
255+
Model license
256+
-------------
257+
MIT.
258+
259+
References
260+
----------
261+
Donoso-Oliva et al. (2023), *ASTROMER: A transformer-based embedding for
262+
the representation of light curves*, A&A 670, A54.
263+
https://ui.adsabs.harvard.edu/abs/2023A%26A...670A..54D/abstract
264+
255265
Parameters
256266
----------
257267
session :
@@ -271,9 +281,9 @@ class Astromer1(_AstromerModel):
271281

272282

273283
class Astromer2(_AstromerModel):
274-
"""Astromer 2 embedding model (Donoso-Oliva et al. 2026).
284+
"""Astromer 2 embedding model.
275285
276-
Pretrained on 1.5 million MACHO light curves. Accepts irregularly-sampled
286+
Pretrained on 1.5 million MACHO light curves. Accepts
277287
single-band photometry and returns a 256-dimensional embedding.
278288
279289
The ONNX model is hosted on HuggingFace at
@@ -286,6 +296,16 @@ class Astromer2(_AstromerModel):
286296
287297
Use :meth:`from_hf` to download and load the model directly.
288298
299+
Model license
300+
-------------
301+
MIT.
302+
303+
References
304+
----------
305+
Donoso-Oliva et al. (2026), *Generalizing across astronomical surveys:
306+
Few-shot light curve classification with Astromer 2*, A&A 707, A170.
307+
https://ui.adsabs.harvard.edu/abs/2026A%26A...707A.170D/abstract
308+
289309
Parameters
290310
----------
291311
session :

0 commit comments

Comments
 (0)