origami-astro 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. origami_astro-0.1.0/LICENSE +21 -0
  2. origami_astro-0.1.0/PKG-INFO +408 -0
  3. origami_astro-0.1.0/README.md +353 -0
  4. origami_astro-0.1.0/origami/__init__.py +9 -0
  5. origami_astro-0.1.0/origami/cli.py +183 -0
  6. origami_astro-0.1.0/origami/compare.py +123 -0
  7. origami_astro-0.1.0/origami/config.py +239 -0
  8. origami_astro-0.1.0/origami/embedders.py +95 -0
  9. origami_astro-0.1.0/origami/features.py +101 -0
  10. origami_astro-0.1.0/origami/lightcurves.py +167 -0
  11. origami_astro-0.1.0/origami/models/__init__.py +45 -0
  12. origami_astro-0.1.0/origami/models/astromer.py +77 -0
  13. origami_astro-0.1.0/origami/models/chronos.py +78 -0
  14. origami_astro-0.1.0/origami/models/mantis.py +63 -0
  15. origami_astro-0.1.0/origami/models/moirai.py +99 -0
  16. origami_astro-0.1.0/origami/models/moment.py +60 -0
  17. origami_astro-0.1.0/origami/models/starclr.py +270 -0
  18. origami_astro-0.1.0/origami/models/timestral.py +88 -0
  19. origami_astro-0.1.0/origami/models/toto.py +87 -0
  20. origami_astro-0.1.0/origami/tasks.py +161 -0
  21. origami_astro-0.1.0/origami/transforms.py +154 -0
  22. origami_astro-0.1.0/origami_astro.egg-info/PKG-INFO +408 -0
  23. origami_astro-0.1.0/origami_astro.egg-info/SOURCES.txt +38 -0
  24. origami_astro-0.1.0/origami_astro.egg-info/dependency_links.txt +1 -0
  25. origami_astro-0.1.0/origami_astro.egg-info/entry_points.txt +2 -0
  26. origami_astro-0.1.0/origami_astro.egg-info/requires.txt +46 -0
  27. origami_astro-0.1.0/origami_astro.egg-info/top_level.txt +1 -0
  28. origami_astro-0.1.0/pyproject.toml +56 -0
  29. origami_astro-0.1.0/setup.cfg +4 -0
  30. origami_astro-0.1.0/tests/test_cli.py +118 -0
  31. origami_astro-0.1.0/tests/test_compare.py +153 -0
  32. origami_astro-0.1.0/tests/test_lightcurves.py +112 -0
  33. origami_astro-0.1.0/tests/test_model_astromer.py +69 -0
  34. origami_astro-0.1.0/tests/test_model_chronos.py +19 -0
  35. origami_astro-0.1.0/tests/test_model_mantis.py +54 -0
  36. origami_astro-0.1.0/tests/test_model_moirai.py +100 -0
  37. origami_astro-0.1.0/tests/test_model_moment.py +63 -0
  38. origami_astro-0.1.0/tests/test_model_starclr.py +86 -0
  39. origami_astro-0.1.0/tests/test_model_timestral.py +59 -0
  40. origami_astro-0.1.0/tests/test_model_toto.py +52 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lorenzo Monti
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,408 @@
1
+ Metadata-Version: 2.4
2
+ Name: origami-astro
3
+ Version: 0.1.0
4
+ Summary: Fold, embed, probe: compare foundation-model embeddings, hand-crafted and catalogue features of astronomical light curves.
5
+ Author-email: Lorenzo Monti <lorenzomonti42@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/LorenzoMonti/origami.git
8
+ Project-URL: Documentation, https://github.com/LorenzoMonti/origami#readme
9
+ Project-URL: Issues, https://github.com/LorenzoMonti/origami/issues
10
+ Keywords: astronomy,light curves,time series,foundation models,embeddings,benchmark
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: numpy>=1.22
21
+ Requires-Dist: scipy>=1.10
22
+ Requires-Dist: pandas>=1.5
23
+ Requires-Dist: scikit-learn>=1.3
24
+ Requires-Dist: pyyaml>=6.0
25
+ Requires-Dist: tabulate>=0.9
26
+ Provides-Extra: features
27
+ Requires-Dist: light-curve>=0.9; extra == "features"
28
+ Provides-Extra: chronos
29
+ Requires-Dist: torch>=2.0; extra == "chronos"
30
+ Requires-Dist: chronos-forecasting>=2.0; extra == "chronos"
31
+ Provides-Extra: mantis
32
+ Requires-Dist: torch>=2.0; extra == "mantis"
33
+ Requires-Dist: mantis-tsfm>=1.1; extra == "mantis"
34
+ Provides-Extra: astromer
35
+ Requires-Dist: onnxruntime>=1.15; extra == "astromer"
36
+ Requires-Dist: huggingface_hub>=0.20; extra == "astromer"
37
+ Provides-Extra: starclr
38
+ Requires-Dist: torch>=2.0; extra == "starclr"
39
+ Requires-Dist: safetensors>=0.4; extra == "starclr"
40
+ Provides-Extra: timestral
41
+ Requires-Dist: torch>=2.4; extra == "timestral"
42
+ Provides-Extra: moirai
43
+ Requires-Dist: uni2ts>=2.0; extra == "moirai"
44
+ Requires-Dist: torch<2.5,>=2.1; extra == "moirai"
45
+ Requires-Dist: transformers<4.57; extra == "moirai"
46
+ Provides-Extra: moment
47
+ Requires-Dist: torch>=2.0; extra == "moment"
48
+ Requires-Dist: momentfm>=0.1.4; extra == "moment"
49
+ Provides-Extra: toto
50
+ Requires-Dist: torch>=2.5; extra == "toto"
51
+ Requires-Dist: toto-models>=1.0; python_version >= "3.12" and extra == "toto"
52
+ Provides-Extra: dev
53
+ Requires-Dist: pytest>=7.0; extra == "dev"
54
+ Dynamic: license-file
55
+
56
+ <p align="center">
57
+ <picture>
58
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/LorenzoMonti/origami/main/docs/logo-dark.svg">
59
+ <img src="https://raw.githubusercontent.com/LorenzoMonti/origami/main/docs/logo.svg" alt="origami" width="300">
60
+ </picture>
61
+ </p>
62
+
63
+ <p align="center"><b>Fold, embed, probe.</b><br>
64
+ Compare foundation-model embeddings, hand-crafted features and catalogue columns
65
+ of astronomical light curves, under identical conditions.</p>
66
+
67
+ ---
68
+
69
+ `origami` answers one kind of question: **which representation of a light curve carries
70
+ the information you need, and how robust is it?** It puts every candidate on the same
71
+ footing:
72
+ - the same objects;
73
+ - the same (sub)sampled epochs;
74
+ - the same folds;
75
+ - the same simple probe.
76
+
77
+ Candidates can be embeddings from frozen time-series foundation models, classical features,
78
+ values from a catalogue, or your own model. Typical questions:
79
+ - Do embeddings from a pretrained model add anything to Fourier parameters for predicting a
80
+ physical quantity?
81
+ - Which model separates my classes best, and does that survive when curves have 20
82
+ epochs instead of 200?
83
+ - Does a representation learned on bright objects transfer to faint ones, or from one
84
+ survey to another?
85
+
86
+ `origami` is a benchmarking and selection tool. It does not train foundation models, and
87
+ it is not a classifier for producing catalogues.
88
+
89
+ ```bash
90
+ pip install "origami-astro[features,chronos]"
91
+ ```
92
+
93
+ **Highlights**
94
+ - **Light curves:** irregular, single-band, any length, each with a provenance tag
95
+ (survey, band).
96
+ - **Models:** 31 checkpoints from 10 model families behind one interface (Chronos,
97
+ Chronos-Bolt, Chronos-2, Moirai, MOMENT, Mantis, Toto 2.0, Timestral, Astromer, StarCLR).
98
+ Weights are downloaded on first use.
99
+ - **Tasks:** regression, classification, clustering and anomaly detection. Evaluation can use
100
+ cross-validation, an explicit train/test split, or metrics per group.
101
+ - **Robustness tests:** controlled epoch subsampling, identical for every representation.
102
+ - **Clusters:** a YAML + command-line workflow runs one SLURM array task per model, works on
103
+ compute nodes without internet, and collects everything into tables at the end.
104
+
105
+ ## Contents
106
+ - [Quick start](#quick-start)
107
+ - [Concepts](#concepts)
108
+ - [Light curves](#light-curves)
109
+ - [Input transforms](#input-transforms)
110
+ - [Representations](#representations)
111
+ - [Tasks and metrics](#tasks-and-metrics)
112
+ - [Command-line workflow](#command-line-workflow)
113
+ - [Models](#models)
114
+ - [Installation and environments](#installation-and-environments)
115
+ - [Good practice](#good-practice)
116
+ - [Adding a model](#adding-a-model)
117
+ - [Licence](#licence)
118
+
119
+ ## Quick start
120
+
121
+ ```python
122
+ import origami as ori
123
+ import pandas as pd
124
+
125
+ cat = pd.read_csv("catalog.csv") # one row per object: id, period, target, ...
126
+ lcs = ori.LightCurves.from_csv_dir("light_curves/", # one CSV per object: time, mag, err
127
+ ids=cat["id"], err_col="mag_err", survey="ztf", band="r")
128
+ lcs = lcs.with_periods(cat["period"]) # needed by folded inputs and Fourier features
129
+
130
+ reps = {
131
+ "chronos": ori.Embedder("chronos-bolt-small", input="folded-grid"),
132
+ "mantis": ori.Embedder("mantis-v2", input="folded-grid"),
133
+ "fourier": ori.HandCrafted("fourier"),
134
+ "physical": ori.HandCrafted("physical"),
135
+ "period": ori.Catalog(cat, ["period"]),
136
+ }
137
+ res = ori.compare(lcs, cat["target"], reps, task="regression",
138
+ combine=[("chronos", "physical"), ("mantis", "physical"), ("fourier", "period")],
139
+ subsample_epochs=["all", 50, 20], seeds=[0, 1, 2])
140
+
141
+ print(res.table()) # primary metric (RMSE): rows = representations, columns = epoch levels
142
+ res.records # every run with all metrics, as a DataFrame
143
+ ```
144
+
145
+ ## Concepts
146
+
147
+ ### Light curves
148
+ `LightCurves` holds a set of single-band curves. Each curve has:
149
+ - its own time, magnitude and error arrays (any length);
150
+ - an identifier;
151
+ - a provenance tag (`survey`, `band`);
152
+ - optionally a period, a reference epoch and the period's origin (`"catalog"` or `"estimated"`).
153
+
154
+ | constructor | input |
155
+ |---|---|
156
+ | `LightCurves.from_csv_dir(dir, ids=..., ...)` | one CSV per object; magnitudes with errors, or fluxes (errors are then converted) |
157
+ | `LightCurves.from_frame(df, id_col=..., ...)` | a long table, one row per observation |
158
+ | `LightCurves.from_arrays(times, mags, errs, ...)` | lists of arrays |
159
+
160
+ Other operations:
161
+ - `lcs.with_periods(periods, epochs=None)` attaches periods.
162
+ - `lcs.subsample(k, seed)` keeps k random epochs per curve. The selection is reproducible and
163
+ does not depend on the other curves in the set.
164
+
165
+ ### Input transforms
166
+ How a curve becomes the sequence a model sees. Set it per embedder with `input=`.
167
+
168
+ | `input` | sequence | needs a period |
169
+ |---|---|---|
170
+ | `raw` | magnitudes in time order, z-normalised, with real timestamps | no |
171
+ | `folded` | magnitudes sorted by phase, z-normalised | yes |
172
+ | `folded-grid` | phase-folded curve resampled on a regular grid (default 200 points), z-normalised, maximum light at phase 0 | yes |
173
+ | `padded-starembed` | last 200 points, zero left-padding: the protocol of [StarEmbed](https://arxiv.org/abs/2510.06200), for comparison | no |
174
+
175
+ How `folded-grid` resamples the curve:
176
+ - It uses a smoothing spline, with automatic smoothing chosen by GCV.
177
+ - If that fit collapses to a near-linear curve, it falls back to a Fourier series whose
178
+ number of harmonics is chosen by BIC.
179
+ - The curve is aligned on maximum light, so the sequence does not depend on the reference epoch.
180
+
181
+ Every transform also passes, for each curve, the times and the magnitude scale removed by
182
+ z-normalisation. Models that use timestamps receive the times; models trained on unscaled
183
+ magnitudes get the scale back.
184
+
185
+ ### Representations
186
+ Anything with a `name` and a `transform(lcs) -> array (n_curves, dim)` method.
187
+
188
+ | class | what it gives |
189
+ |---|---|
190
+ | `Embedder(model, input=...)` | frozen foundation-model embedding: encoder states mean-pooled over the positions that hold real data; cached on disk |
191
+ | `HandCrafted("light-curve")` | about 30 period-free statistics from the [`light-curve`](https://github.com/light-curve/light-curve-python) package |
192
+ | `HandCrafted("fourier", n_harmonics=3)` | Fourier amplitudes and cos/sin of φ21, φ31 on the folded curve |
193
+ | `HandCrafted("physical")` | log period and folded peak-to-peak amplitude |
194
+ | `Catalog(table, columns)` | columns of an external table, row-aligned with the curves |
195
+ | your own object | e.g. an autoencoder: anything implementing `transform(lcs)` |
196
+
197
+ `combine=[("a", "b"), ...]` evaluates concatenations of representations. In distance-based
198
+ tasks each concatenated block is weighted by 1/√width, so a narrow feature block is not
199
+ outweighed by a wide embedding.
200
+
201
+ ### Tasks and metrics
202
+ | `task` | probe | metrics |
203
+ |---|---|---|
204
+ | `regression` | standardised ridge regression (RidgeCV) | RMSE, MAE |
205
+ | `classification` | standardised logistic regression with balanced class weights | balanced accuracy, macro F1 |
206
+ | `clustering` | k-means (k = number of classes), HDBSCAN, or [CLiMB](https://github.com/LorenzoMonti/CLiMB) | ARI, NMI |
207
+ | `anomaly` | kNN distance or isolation forest, fitted on the normal objects of the training folds (`y` = 1 for anomalies) | AUROC, average precision |
208
+
209
+ The probes are deliberately simple and fixed, so that results measure the representation
210
+ and not the model on top of it.
211
+
212
+ Evaluation options of `compare`:
213
+ - **`folds`**: defaults to 5-fold cross-validation, stratified for classification and
214
+ anomaly. Pass your own fold index to reuse an existing protocol.
215
+ - **`split=(train_mask, test_mask)`**: train on one subset and score another, e.g. bright
216
+ versus faint, or one survey versus another.
217
+ - **`groups=labels`**: also report metrics per group, e.g. magnitude bins or classes. Read
218
+ them with `res.group_table()`.
219
+ - **`subsample_epochs` and `seeds`**: thin every curve to the given numbers of epochs,
220
+ identically for all representations.
221
+ - **Task options** are passed through, e.g. `method="hdbscan"`, `detector="isolation-forest"`
222
+ or `pca=20`.
223
+
224
+ ## Command-line workflow
225
+
226
+ For large runs, especially on clusters whose compute nodes have no internet access, an
227
+ experiment is described by one YAML file:
228
+
229
+ ```yaml
230
+ name: my_experiment
231
+ output: /path/to/runs/my_experiment
232
+ data:
233
+ light_curves: /path/to/light_curves # one CSV per object, "{id}.csv"
234
+ catalog: /path/to/catalog.csv # one row per object
235
+ id_col: id
236
+ time_col: time
237
+ mag_col: mag
238
+ err_col: mag_err # or flux_col + flux_err_col
239
+ survey: ztf
240
+ band: r
241
+ period_col: period # optional
242
+ epoch_col: epoch # optional
243
+ fold_col: fold # optional, otherwise k-fold
244
+ representations:
245
+ bolt: {model: chronos-bolt-small, input: folded-grid}
246
+ mantis: {model: mantis-v2, input: folded-grid}
247
+ moirai: {model: moirai-2.0-small, input: folded-grid, setup: "source ~/venvs/origami-moirai/bin/activate"}
248
+ astromer: {model: astromer2-macho, input: raw}
249
+ fourier: {features: fourier}
250
+ physical: {features: physical}
251
+ period: {catalog: [period]}
252
+ my_model: {python: "my_package.encoders:MyEncoder", path: /path/to/my_package, kwargs: {checkpoint: model.pt}, version: v1}
253
+ combine: [[bolt, physical], [mantis, physical], [fourier, period]]
254
+ tasks:
255
+ - {task: regression, target: my_target}
256
+ - {task: classification, target: class}
257
+ - task: classification
258
+ target: class
259
+ split: {train: "mag < 19", test: "mag >= 19"} # pandas queries, or {column, train, test}
260
+ groups: {column: mag, bins: [16, 18, 19, 20, 21]}
261
+ - {task: clustering, target: class, method: hdbscan}
262
+ subsample_epochs: [all, 50, 20]
263
+ seeds: [0, 1, 2]
264
+ ```
265
+
266
+ ```bash
267
+ origami models # available models (licence, size, install, installed?)
268
+ origami prefetch exp.yaml # on a machine with internet: download all weights
269
+ origami slurm exp.yaml --account ACC --partition PART \
270
+ --setup "module load python" --setup "source ~/venvs/origami/bin/activate"
271
+ sbatch origami_my_experiment.sbatch # one array task per model, each runs `origami embed`
272
+ origami status exp.yaml # which embeddings are stored / missing
273
+ origami collect exp.yaml # run all tasks, write results/*.csv and summary.md
274
+ ```
275
+
276
+ What each step does:
277
+ - **`origami embed exp.yaml --representation NAME`** computes one representation for every
278
+ epoch level and seed. It also works without SLURM.
279
+ - **Embeddings** are stored under `output/embeddings`. Their key covers the model and its
280
+ settings, the input transform, the code version and the data, including subsampling and seed.
281
+ - **`collect` never runs a model.** If an embedding is missing, it says which representation
282
+ to rerun.
283
+ - **User models** (`python:`) are computed and cached like the foundation models. Change
284
+ `version` whenever your weights change.
285
+ - **`setup:`** gives a representation a shell line run by its own array task, for models
286
+ that need a separate environment.
287
+ - **Offline nodes:** the generated job sets `HF_HUB_OFFLINE=1`.
288
+
289
+ ## Models
290
+
291
+ `origami models` prints the live list and whether each package is installed.
292
+
293
+ | family | checkpoints | params | licence of the weights | uses timestamps | extra |
294
+ |---|---|---|---|---|---|
295
+ | [Chronos](https://github.com/amazon-science/chronos-forecasting) (T5) | `chronos-t5-tiny/mini/small/base` | 8M–200M | Apache-2.0 | no | `chronos` |
296
+ | Chronos-Bolt | `chronos-bolt-tiny/small` | 9M–48M | Apache-2.0 | no | `chronos` |
297
+ | Chronos-2 | `chronos-2-small`, `chronos-2` | 28M, 120M | Apache-2.0 | no | `chronos` |
298
+ | [Moirai](https://github.com/SalesforceAIResearch/uni2ts) | `moirai-1.1-small/base`, `moirai-2.0-small` | 11M–91M | **CC-BY-NC-4.0 (non-commercial)** | no | `moirai` |
299
+ | [MOMENT](https://huggingface.co/AutonLab/MOMENT-1-small) | `moment-1-small/base/large` | 38M–346M | MIT | no | `moment` |
300
+ | [Mantis](https://huggingface.co/paris-noah/Mantis-8M) | `mantis-8m`, `mantis-plus`, `mantis-v2`, `mantis-v2-cls` | 4M–8M | Apache-2.0 | no | `mantis` |
301
+ | [Toto 2.0](https://github.com/DataDog/toto) | `toto-2.0-4m/22m/313m/1b/2.5b` | 4M–2.5B | Apache-2.0 | no | `toto` |
302
+ | [Timestral](https://github.com/mistralai/timestral) | `timestral-5m/28m/123m/209m` | 5M–209M | **Modified MIT: excludes companies above $20M monthly revenue** | no | `timestral` |
303
+ | [Astromer](https://github.com/astromer-science) | `astromer1-macho`, `astromer2-macho`, `astromer1-ztfdr20` | 0.7M–4M | CC-BY-4.0; **GPL-3.0 for `astromer1-ztfdr20`** | **yes** | `astromer` |
304
+ | [StarCLR](https://github.com/dj-y/StarCLR-Inference) | `starclr` | 19M | MIT | **yes** | `starclr` |
305
+
306
+ Notes on specific families:
307
+ - **Astromer** runs through the ONNX exports of the
308
+ [light-curve](https://github.com/light-curve/prep-models) project, so it needs neither
309
+ TensorFlow nor the original package.
310
+ - **StarCLR** uses a PyTorch re-implementation of the official inference code. Its weights
311
+ come from [Zenodo](https://doi.org/10.5281/zenodo.19728042), are verified by checksum,
312
+ and are stored in `$ORIGAMI_WEIGHTS`.
313
+ - **Toto, Timestral and Moirai** have no public embedding API. Their states are read from
314
+ internal modules, stated in each plugin's docstring, so their package versions are pinned.
315
+ - **Not included yet:**
316
+ - models that require `trust_remote_code`;
317
+ - models without public pretrained weights.
318
+
319
+ **The weights are not part of `origami`.** They are downloaded from their original
320
+ sources, and their licences govern your use of them. Check the non-commercial,
321
+ copyleft and revenue clauses marked above before using those models outside academic
322
+ research.
323
+
324
+ ## Installation and environments
325
+
326
+ ```bash
327
+ pip install "origami-astro[features,chronos,mantis,astromer,starclr]"
328
+ ```
329
+
330
+ | extra | adds |
331
+ |---|---|
332
+ | `features` | hand-crafted features (`light-curve`) |
333
+ | `chronos`, `mantis`, `astromer`, `starclr`, `timestral` | those model families (they can share one environment) |
334
+ | `moirai`, `moment`, `toto` | those model families, **each in its own environment** (see below) |
335
+ | `dev` | tests (`pytest`) |
336
+
337
+ Some model packages pin incompatible versions:
338
+
339
+ | environment | extras | reason |
340
+ |---|---|---|
341
+ | main | `features, chronos, mantis, astromer, starclr, timestral` | — |
342
+ | moirai | `features, moirai` | `uni2ts` 2.0 pins `torch<2.5` and `numpy~=1.26` |
343
+ | moment | `features, moment` | `momentfm` pins `transformers==4.33.3` and `numpy==1.25.2` |
344
+ | toto | `features, toto` | `toto2` needs Python ≥ 3.12 |
345
+
346
+ In the YAML workflow, point each representation to its environment with `setup:`.
347
+
348
+ Timestral is not on PyPI; install it from GitHub:
349
+ ```bash
350
+ pip install "timestral @ git+https://github.com/mistralai/timestral.git@a534bdbd244d64f3d9d743f46c7bf6fc330ed4ff"
351
+ ```
352
+
353
+ On clusters, build environments on top of the site's Python/PyTorch module
354
+ (`python -m venv --system-site-packages ...`), so that only the model packages are added.
355
+
356
+ Environment variables:
357
+ - `ORIGAMI_CACHE`: embedding cache in interactive use (the CLI stores embeddings under `output/embeddings`).
358
+ - `ORIGAMI_WEIGHTS`: where weights not hosted on Hugging Face are stored.
359
+ - `HF_HUB_OFFLINE=1`: use only weights that are already downloaded.
360
+
361
+ Requires Python ≥ 3.10.
362
+
363
+ ## Good practice
364
+
365
+ 1. **Compare input transforms, not only models.** For periodic sources, folded and raw
366
+ inputs can give very different results with the same model. Each model also expects data
367
+ that looks like its pre-training data: scale, flux versus magnitude, regular versus
368
+ irregular sampling.
369
+ 2. **Put back what normalisation removes.** Z-normalised, folded inputs carry no period and
370
+ no amplitude. Combine embeddings with `HandCrafted("physical")` or catalogue values
371
+ before comparing them with features that contain this information.
372
+ 3. **Mind collapsed embeddings.** Some models map all objects to nearly parallel vectors,
373
+ with the information in small residual directions. Standardised probes, which `origami`
374
+ always uses, recover it; raw distances do not.
375
+ 4. **Do not assume larger checkpoints are better.** Compare sizes within a family.
376
+ 5. **Report variability.** Use several `seeds` and fixed `folds`, and check that differences
377
+ between representations exceed the fold-to-fold scatter before ranking them.
378
+ 6. **Check where the labels come from.** If the target was derived from light-curve
379
+ features (e.g. a quantity computed from Fourier parameters), a representation can score
380
+ well by re-learning that relation. Validate on independently measured labels when possible.
381
+
382
+ ## Adding a model
383
+
384
+ Add one file to `origami/models/`; it is discovered automatically.
385
+
386
+ ```python
387
+ MODELS = {"my-model": dict(family="my", hf_id="org/my-model", license="MIT", extra="my",
388
+ context=512, params="10M", uses_time=False)}
389
+
390
+ class Encoder:
391
+ def __init__(self, spec, device=None):
392
+ ... # heavy imports here, not at module level
393
+
394
+ def encode(self, values, masks, times, batch_size=256, scales=None): # `scales` is optional
395
+ ... # return (n_curves, dim): states mean-pooled over positions where mask is True
396
+ ```
397
+
398
+ Add a test in `tests/test_model_<name>.py` that checks:
399
+ - shape, finite values and determinism;
400
+ - that a curve's embedding does not change when it is batched with longer curves.
401
+
402
+ ## Licence
403
+
404
+ `origami` is released under the [MIT licence](https://github.com/LorenzoMonti/origami/blob/main/LICENSE). Model weights are downloaded from
405
+ their original sources and keep their own licences (see [Models](#models)).
406
+
407
+ If you use `origami` in your work, please cite this repository and the papers of the
408
+ models you use.