specsr 1.0.2__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 (93) hide show
  1. specsr-1.0.2/.gitignore +273 -0
  2. specsr-1.0.2/ARCHITECTURE.md +222 -0
  3. specsr-1.0.2/CHANGELOG.md +71 -0
  4. specsr-1.0.2/LICENSE +21 -0
  5. specsr-1.0.2/PKG-INFO +293 -0
  6. specsr-1.0.2/README.md +218 -0
  7. specsr-1.0.2/configs/finetune/sr1.yaml +38 -0
  8. specsr-1.0.2/configs/finetune/sr2.yaml +91 -0
  9. specsr-1.0.2/configs/finetune/sr2_fluxfix_smoke.yaml +71 -0
  10. specsr-1.0.2/configs/sr1.yaml +31 -0
  11. specsr-1.0.2/configs/sr2.yaml +136 -0
  12. specsr-1.0.2/configs/sweeps/sr1.yaml +186 -0
  13. specsr-1.0.2/configs/sweeps/sr2.yaml +194 -0
  14. specsr-1.0.2/configs/sweeps/zhead.yaml +168 -0
  15. specsr-1.0.2/configs/zhead.yaml +21 -0
  16. specsr-1.0.2/configs/zhead_sr2.yaml +19 -0
  17. specsr-1.0.2/docs/_static/.gitkeep +0 -0
  18. specsr-1.0.2/docs/_static/architecture.png +0 -0
  19. specsr-1.0.2/docs/api/index.md +33 -0
  20. specsr-1.0.2/docs/changelog.md +2 -0
  21. specsr-1.0.2/docs/conf.py +73 -0
  22. specsr-1.0.2/docs/guides/data.md +136 -0
  23. specsr-1.0.2/docs/guides/evaluation.md +51 -0
  24. specsr-1.0.2/docs/guides/installation.md +89 -0
  25. specsr-1.0.2/docs/guides/training.md +181 -0
  26. specsr-1.0.2/docs/guides/tutorials.md +62 -0
  27. specsr-1.0.2/docs/index.md +95 -0
  28. specsr-1.0.2/pyproject.toml +104 -0
  29. specsr-1.0.2/src/specsr/__init__.py +34 -0
  30. specsr-1.0.2/src/specsr/checkpoints.py +125 -0
  31. specsr-1.0.2/src/specsr/cli.py +158 -0
  32. specsr-1.0.2/src/specsr/config.py +71 -0
  33. specsr-1.0.2/src/specsr/data/__init__.py +37 -0
  34. specsr-1.0.2/src/specsr/data/augment.py +195 -0
  35. specsr-1.0.2/src/specsr/data/build.py +345 -0
  36. specsr-1.0.2/src/specsr/data/datasets.py +254 -0
  37. specsr-1.0.2/src/specsr/data/grid.py +257 -0
  38. specsr-1.0.2/src/specsr/data/ingest.py +204 -0
  39. specsr-1.0.2/src/specsr/data/splits.py +354 -0
  40. specsr-1.0.2/src/specsr/data/stitch.py +213 -0
  41. specsr-1.0.2/src/specsr/evaluation/__init__.py +268 -0
  42. specsr-1.0.2/src/specsr/evaluation/figures.py +376 -0
  43. specsr-1.0.2/src/specsr/evaluation/runner.py +74 -0
  44. specsr-1.0.2/src/specsr/inference/__init__.py +24 -0
  45. specsr-1.0.2/src/specsr/inference/pipeline.py +421 -0
  46. specsr-1.0.2/src/specsr/linefit.py +170 -0
  47. specsr-1.0.2/src/specsr/metrics.py +354 -0
  48. specsr-1.0.2/src/specsr/models/__init__.py +36 -0
  49. specsr-1.0.2/src/specsr/models/blocks.py +142 -0
  50. specsr-1.0.2/src/specsr/models/lines.py +142 -0
  51. specsr-1.0.2/src/specsr/models/loaders.py +205 -0
  52. specsr-1.0.2/src/specsr/models/sr1.py +94 -0
  53. specsr-1.0.2/src/specsr/models/sr2.py +464 -0
  54. specsr-1.0.2/src/specsr/models/zhead.py +327 -0
  55. specsr-1.0.2/src/specsr/paths.py +118 -0
  56. specsr-1.0.2/src/specsr/plotting.py +1402 -0
  57. specsr-1.0.2/src/specsr/runtime.py +191 -0
  58. specsr-1.0.2/src/specsr/training/__init__.py +28 -0
  59. specsr-1.0.2/src/specsr/training/common.py +272 -0
  60. specsr-1.0.2/src/specsr/training/losses.py +756 -0
  61. specsr-1.0.2/src/specsr/training/runner.py +114 -0
  62. specsr-1.0.2/src/specsr/training/sr1.py +307 -0
  63. specsr-1.0.2/src/specsr/training/sr2.py +643 -0
  64. specsr-1.0.2/src/specsr/training/zhead.py +418 -0
  65. specsr-1.0.2/src/specsr/training/zhead_sources.py +210 -0
  66. specsr-1.0.2/src/specsr/training/ztransform.py +127 -0
  67. specsr-1.0.2/src/specsr/wandb_plots.py +136 -0
  68. specsr-1.0.2/tests/test_augment.py +186 -0
  69. specsr-1.0.2/tests/test_cli_sweep_overrides.py +110 -0
  70. specsr-1.0.2/tests/test_data_and_config.py +370 -0
  71. specsr-1.0.2/tests/test_evaluation_decode.py +177 -0
  72. specsr-1.0.2/tests/test_grid.py +179 -0
  73. specsr-1.0.2/tests/test_inference_pipeline.py +199 -0
  74. specsr-1.0.2/tests/test_ingest.py +243 -0
  75. specsr-1.0.2/tests/test_losses.py +715 -0
  76. specsr-1.0.2/tests/test_models.py +501 -0
  77. specsr-1.0.2/tests/test_notify_run.py +138 -0
  78. specsr-1.0.2/tests/test_package_reproduces_checkpoints.py +158 -0
  79. specsr-1.0.2/tests/test_public_release.py +222 -0
  80. specsr-1.0.2/tests/test_run_artifacts.py +155 -0
  81. specsr-1.0.2/tests/test_splits.py +234 -0
  82. specsr-1.0.2/tests/test_sr2_loss_line_weighting.py +189 -0
  83. specsr-1.0.2/tests/test_sweep_configs.py +261 -0
  84. specsr-1.0.2/tests/test_tutorial_samples_are_held_out.py +73 -0
  85. specsr-1.0.2/tests/test_version_is_single_sourced.py +53 -0
  86. specsr-1.0.2/tests/test_zhead_checkpoint_naming.py +51 -0
  87. specsr-1.0.2/tests/test_zhead_sources.py +272 -0
  88. specsr-1.0.2/tutorials_for_user/01_quickstart.ipynb +441 -0
  89. specsr-1.0.2/tutorials_for_user/02_your_own_spectrum.ipynb +406 -0
  90. specsr-1.0.2/tutorials_for_user/03_batches_and_trust.ipynb +410 -0
  91. specsr-1.0.2/tutorials_for_user/README.md +81 -0
  92. specsr-1.0.2/tutorials_for_user/sample_24_spectra.npz +0 -0
  93. specsr-1.0.2/tutorials_for_user/sample_one_spectrum.npz +0 -0
@@ -0,0 +1,273 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ /lib/
18
+ /lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # PyPI configuration file
171
+ .pypirc
172
+ # Raw + derived data (repo root only; must not match src/specsr/data)
173
+ /data/
174
+ line_detect/
175
+ sup_res/
176
+ .DS_Store
177
+ toy_model/.DS_Store
178
+ line_detect/line-finding/
179
+
180
+ *.pkl
181
+ *.npz
182
+ *.npy
183
+ wandb/
184
+ .codex
185
+ .claude
186
+
187
+ # ---------------------------------------------------------------
188
+ # specsr project-specific
189
+ # ---------------------------------------------------------------
190
+ # Model weights are NOT version-controlled. They are published to the
191
+ # Hugging Face Hub and fetched on demand by specsr.checkpoints.
192
+ *.pth
193
+ *.pt
194
+ *.ckpt
195
+ *.safetensors
196
+
197
+ # The tutorial spectra are the exception to the blanket *.npz rule above. They
198
+ # are small (~0.9 MB), drawn from the validation split, and the notebooks are
199
+ # useless without them -- a tutorial that needs an 88 GB survey tree first is not
200
+ # a tutorial. Weights stay ignored; those come from the Hub.
201
+ !tutorials_for_user/*.npz
202
+
203
+ # Raw JWST/JADES data lives outside the repo (see SPECSR_JADES_ROOT);
204
+ # only derived products are built here, and those are regenerable.
205
+ JADES_data/
206
+
207
+ # Local caches / editor and OS cruft
208
+ .virtual_documents/
209
+ .ipynb_checkpoints/
210
+ htmlcov/
211
+ .ruff_cache/
212
+
213
+ # Build artefacts from the src/ layout
214
+ src/*.egg-info/
215
+
216
+ # ---------------------------------------------------------------
217
+ # LaTeX build artefacts (papers are compiled locally, not on Overleaf)
218
+ # ---------------------------------------------------------------
219
+ *.aux
220
+ *.bbl
221
+ *.blg
222
+ *.fdb_latexmk
223
+ *.fls
224
+ *.lof
225
+ *.lot
226
+ *.toc
227
+ *.synctex.gz
228
+ *.synctex(busy)
229
+ *.nav
230
+ *.snm
231
+ *.vrb
232
+ *.run.xml
233
+ *.bcf
234
+ *.glo
235
+ *.gls
236
+ *.glg
237
+ *.ist
238
+ *.acn
239
+ *.acr
240
+ *.alg
241
+ *.spl
242
+ *.figlist
243
+ *.makefile
244
+ *.figlist-*
245
+ # latexmk/pdflatex logs and the hyperref bookmark file
246
+ *.out
247
+ # Built PDFs: regenerable from source
248
+
249
+ # Sphinx autosummary stubs: regenerated on every docs build
250
+ docs/api/generated/
251
+
252
+ # Training run outputs (checkpoints + per-stage logs). Every stage writes here
253
+ # via --out-dir; nothing is written back into the source tree.
254
+ runs/
255
+
256
+ # Local checkpoint archives, kept out of git -- weights live on the Hugging Face
257
+ # Hub, and these are working copies. `*.pth` above already excludes the weights
258
+ # themselves; these lines also exclude the READMEs and MD5SUMS beside them.
259
+ checkpoints_baseline_*/
260
+ checkpoints_errorfix_*/
261
+ checkpoints_run5_*/
262
+
263
+ # Cached evaluation predictions and derived result tables (all regenerable).
264
+ # Figures do NOT live here: figure scripts write straight into the manuscript's
265
+ # own plot directory, or into SPECSR_OUTPUT_DIR when there is no manuscript.
266
+ cache/
267
+ checkpoints/release/MD5SUMS
268
+ checkpoints/release/config_logR.yaml
269
+
270
+
271
+ # Package outputs (figures, predictions, evaluation tables). See
272
+ # specsr.paths.output_dir / SPECSR_OUTPUT_DIR.
273
+ /outputs/
@@ -0,0 +1,222 @@
1
+ <!--
2
+ This is the PUBLIC architecture document, shipped to the released repository as
3
+ ARCHITECTURE.md by scripts/make_public_release.sh.
4
+
5
+ It is a different file from the development ARCHITECTURE.md in the private
6
+ repository. That one is a decisions log -- why code moved between directories,
7
+ which experiment forced which change, what a given failure cost -- and is
8
+ addressed to whoever maintains the working tree. This one describes the design
9
+ as it stands, for somebody reading or extending the code.
10
+ -->
11
+
12
+ # Architecture
13
+
14
+ How `specsr` is put together, and the constraints the code enforces on itself.
15
+ For how to *use* it, start with the [documentation](https://aryana-haghjoo.github.io/specsr/)
16
+ or the notebooks in `tutorials_for_user/`.
17
+
18
+ ## The model
19
+
20
+ ![Three-stage spectral super-resolution pipeline](docs/_static/architecture.png)
21
+
22
+ Three stages, trained in order. Each consumes the frozen output of the one
23
+ before it, so an error in an early stage is inherited rather than corrected.
24
+
25
+ **SR1 — super-resolution backbone.** A 1D residual CNN (16 blocks x 120
26
+ channels) that maps a prism spectrum on the common grid to a grating-like
27
+ reconstruction on the same grid, emitting a mean and a per-pixel log-variance.
28
+ It is deliberately conservative: it sharpens structure the input supports and is
29
+ not asked to invent line profiles.
30
+
31
+ **ZHead — redshift inference.** Six dilated convolution blocks x 128 channels
32
+ with attention pooling over wavelength, reading SR1's mean and log-variance. The
33
+ output is a softmax distribution over 1,024 redshift bins spanning 0 <= z <= 15;
34
+ the point estimate is a soft-argmax in a window around the modal bin, and the
35
+ spread comes from the full distribution.
36
+
37
+ Two properties matter and both are structural rather than tuned. The head must
38
+ be **position-aware** — on a logarithmic wavelength grid a redshift is a
39
+ translation, so a convolutional trunk followed by a global mean pool cannot
40
+ measure one even in principle. And it must predict a **distribution** rather than
41
+ a point: the characteristic failure is a misidentified line, which puts a second
42
+ mode elsewhere, and a Gaussian head answers such a posterior with the mean,
43
+ landing in the empty valley between the modes.
44
+ `tests/test_models.py` pins both properties against synthetic data.
45
+
46
+ **SR2 — physics-informed residual refiner.** Multi-head self-attention over 98
47
+ emission-line tokens (`specsr.models.lines`), each emitting a parametric
48
+ Gaussian — amplitude, width, offset — gated by a presence probability, plus a
49
+ CNN branch for smooth continuum corrections. The two branches sum to a residual
50
+ that is added to SR1's output.
51
+
52
+ SR2's five input channels are the prism spectrum, SR1's mean and sigma, a line
53
+ mask `m(lambda; z, sigma_z)` built at the *predicted* redshift, and the redshift
54
+ itself. There is no teacher forcing anywhere: SR2 only ever sees redshifts ZHead
55
+ inferred, never the catalogue value, so the chain behaves at training time the
56
+ way it behaves at inference time.
57
+
58
+ The presence gate is **supervised**, not merely penalised — trained by
59
+ class-balanced cross-entropy against the lines the reference spectrum actually
60
+ shows. A blanket sparsity penalty applies the same downward gradient to every
61
+ line logit whether or not the line is real; because the model forms
62
+ `amplitude * presence`, that becomes a constant multiplier on every amplitude and
63
+ can silently disable the entire line branch while every loss curve still looks
64
+ healthy. `train/presence_sep` — the ratio of gate output on real lines to absent
65
+ ones — is logged and guarded for exactly this reason.
66
+
67
+ Line-mask widths grow with ZHead's predicted `sigma_z`, capped: a spectrum whose
68
+ redshift is uncertain gets a wider mask rather than a confidently misplaced
69
+ narrow one. The cap matters because the same mask weights SR2's in-line loss
70
+ term, and an uncapped mask on a hopeless redshift would relax that term into a
71
+ plain reconstruction loss without any visible symptom.
72
+
73
+ ## The wavelength grid
74
+
75
+ Everything runs on one grid: **logarithmic, constant R = 4,000, 1.0-5.3 µm,
76
+ 6,671 pixels** (`specsr.data.grid.DEFAULT_GRID`). A log grid holds
77
+ lambda/dlambda fixed, which is how a spectrograph actually samples — a fixed
78
+ *fractional* step matches the instrument at every wavelength instead of being too
79
+ coarse in the blue and wasteful in the red.
80
+
81
+ Two rules follow, and both are load-bearing:
82
+
83
+ - **Regrid the low-resolution data up onto the high-resolution sampling; never
84
+ resample the high-resolution spectra down.** A linear grid coarse enough to
85
+ undersample the gratings makes the *grid*, not the model, the ceiling on
86
+ achievable sharpness — and the resulting model looks like it has hit a
87
+ fundamental limit.
88
+ - **Resampling integrates; it does not interpolate.** The physically meaningful
89
+ quantity in an emission line is its integrated flux, an area, not the height of
90
+ the curve at particular abscissae. `resample_flux_conserving` is the default
91
+ for that reason, and `SpecSRPipeline` applies it to any input handed to it on a
92
+ different grid.
93
+
94
+ ## Package layout
95
+
96
+ ```
97
+ src/specsr/
98
+ data/ ingest, grid, stitching, augmentation, datasets, group splits
99
+ models/ SR1, ZHead, SR2, the line catalogue, checkpoint loaders
100
+ training/ the three training loops, losses, redshift transform
101
+ inference/ SpecSRPipeline
102
+ evaluation/ split selection, chain inference, the published figures
103
+ plotting.py every figure in the paper
104
+ metrics.py linefit.py line fitting and summary statistics
105
+ checkpoints.py on-demand weight fetching from the Hugging Face Hub
106
+ paths.py environment-driven data, cache and output directories
107
+ configs/ stage configs and W&B sweep definitions
108
+ scripts/ thin entry points: argument parsing and file IO only
109
+ tests/ unit tests plus checkpoint-reproduction guards
110
+ ```
111
+
112
+ **All library code lives under `src/specsr/`.** `scripts/` parses arguments and
113
+ moves files; it defines no model, dataset or loss. This is not style — the
114
+ package was once a parallel implementation that nothing imported during training,
115
+ and an unexercised copy drifts silently. It did, within a day, and anyone loading
116
+ a checkpoint through the package would have got a different network from the one
117
+ that trained. `tests/test_package_reproduces_checkpoints.py` loads real
118
+ checkpoints through the *package* classes and asserts on measured numbers, so the
119
+ drift cannot recur unnoticed.
120
+
121
+ ## Data flow
122
+
123
+ ```
124
+ raw JADES x1d products
125
+ | specsr.data.ingest read prism and G140M/G235M/G395M extractions
126
+ | specsr.data.stitch one 1-5 µm grating reference per galaxy
127
+ | quality cuts coverage and S/N; secure catalogue redshifts only
128
+ | specsr.data.grid both members resampled onto DEFAULT_GRID
129
+ | specsr.data.augment training galaxies only -> 21 rows each
130
+ v
131
+ paired .npz (LR, HR, sigma, z, parent id, provenance)
132
+ | specsr.data.splits split over parent galaxies
133
+ v
134
+ specsr train sr1 -> zhead -> sr2 each freezing the previous stage
135
+ v
136
+ specsr infer / evaluate, SpecSRPipeline.from_pretrained()
137
+ ```
138
+
139
+ ## Invariants the code enforces
140
+
141
+ These are the assumptions that, when broken, produce results that look correct.
142
+ Each is checked in code rather than left to discipline.
143
+
144
+ **Splits are drawn over parent galaxies, never over rows.** Augmentation
145
+ multiplies each galaxy into 21 highly correlated rows differing only by a small
146
+ redshift offset and noise. A row-wise split puts near-duplicates of every
147
+ held-out galaxy into training and every held-out number comes out optimistic.
148
+ `specsr.data.splits` groups by parent and refuses to return a leaking split;
149
+ `assert_no_group_leakage` is called before training, not after.
150
+
151
+ **Only training galaxies are augmented.** The builder decides the split *before*
152
+ augmenting, so a held-out galaxy contributes exactly one row — its real observed
153
+ spectrum — and `train + val` accounts for every row in the file. A row that must
154
+ never be read is a trap for whoever does not know that. The builder then
155
+ re-derives the split from the file it just wrote and refuses to finish if the two
156
+ disagree.
157
+
158
+ **Evaluation is on original spectra only.** The built product augments every
159
+ galaxy, so an unfiltered held-out partition would be ~95% synthetic rows: it
160
+ would report performance on perturbations rather than observations, over
161
+ correlated samples, inflating apparent *n* and understating uncertainties. The
162
+ default configuration is 80/20 over galaxies with **no sealed test partition**,
163
+ which means the reported number is selected on the set it is reported from; pass
164
+ `val_frac=0.1, allow_empty_test=False` to `get_training_split` to restore a
165
+ three-way split with a withheld test set. Switching costs no retraining, because
166
+ galaxies are permuted once under a fixed seed and training takes the first
167
+ `train_frac`.
168
+
169
+ **Upstream checkpoints are named explicitly, never discovered.** Every stage
170
+ takes its predecessors' paths on the command line, so a checkpoint records what
171
+ it was trained on top of. Evaluating against a directory a running job is
172
+ overwriting compares refiners sitting on top of *different* backbones; both
173
+ numbers are real and neither is comparable to the other.
174
+
175
+ **Checkpoints are never written into the source tree.** Every stage takes
176
+ `--out-dir`, which makes it structurally impossible for a rehearsal run to
177
+ address a real run's output path.
178
+
179
+ **A checkpoint carries what is needed to decode it.** The redshift head stores
180
+ its normalisation bounds; without them the pipeline cannot convert a network
181
+ output back into a redshift, and it raises rather than guessing. Guessing does
182
+ not fail visibly — it returns confident, plausible, wrong redshifts.
183
+
184
+ **Verify checkpoints by measurement, not by hash.** A hash proves two files
185
+ agree; it does not prove either is correct. Check a checkpoint's stored config,
186
+ or measure it against a known number.
187
+
188
+ **Every run logs figures, not only scalars.** Each stage writes a validation
189
+ panel to Weights & Biases alongside its metrics, and the redshift panel is drawn
190
+ by the same `plotting.plot_redshift_panel` the published figure uses. Both of
191
+ this project's expensive failures were invisible in loss curves: a refiner can
192
+ manufacture lines in the wrong place while its loss falls, and scalar redshift
193
+ metrics cannot distinguish ordinary scatter about the diagonal from a second
194
+ locus off it.
195
+
196
+ ## Numerical stability
197
+
198
+ Gradient clipping is not a defence against an exploding gradient.
199
+ `torch.nn.utils.clip_grad_norm_` scales by `max_norm / total_norm`; a single
200
+ non-finite element makes the norm infinite, the scale zero, and their product
201
+ NaN — so clipping *launders* an overflow into every weight, and an adaptive
202
+ optimiser's moments then make it permanent. All three stages call
203
+ `specsr.training.common.clip_grad_or_skip`, which skips the optimiser step when
204
+ the gradient norm is not finite. SR2's line branch is the one that needs it: it
205
+ runs at a gradient norm around 0.04 and spikes many orders of magnitude above
206
+ that, and a meaningful fraction of its steps are skipped. This is containment,
207
+ not a fix.
208
+
209
+ ## Extending it
210
+
211
+ - **A new spectral feature** goes in `specsr.models.lines`. The list order *is*
212
+ the token order the checkpoints were built with, so append rather than insert,
213
+ and expect existing SR2 weights not to load if you change the count.
214
+ - **A different wavelength grid** means rebuilding the dataset and retraining
215
+ from SR1 up: the grid is baked into every checkpoint's input length.
216
+ - **A new stage or loss term** goes under `src/specsr/training/`, with its
217
+ hyperparameters added to that stage's `DEFAULTS`. Sweep configs are validated
218
+ against `DEFAULTS`, so a search axis the trainer does not read fails the test
219
+ suite rather than quietly wasting a sweep.
220
+ - **A new figure** goes in `specsr.plotting` as a function, called from a thin
221
+ script. Wrap it in `plt.rc_context(PAPER_RC)` rather than setting fonts
222
+ locally.
@@ -0,0 +1,71 @@
1
+ # Changelog
2
+
3
+ All notable changes to `specsr` are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project follows
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ <!--
8
+ This is the PUBLIC changelog, shipped to the released repository as
9
+ CHANGELOG.md by scripts/make_public_release.sh.
10
+
11
+ It is deliberately not the same file as the development CHANGELOG.md in the
12
+ private repository. That one records the internal history -- which run
13
+ directory produced which checkpoint, which review comment prompted which
14
+ change -- and is addressed to whoever maintains the working tree. This one is
15
+ addressed to somebody installing the package, and starts at the first public
16
+ release. Add entries here when a change affects users.
17
+ -->
18
+
19
+ ## [1.0.2] - 2026-08-15
20
+
21
+ ### Added
22
+
23
+ - Published to PyPI: `pip install "specsr[hub]"`.
24
+ - `scripts/notify-run --setup` asks for your email address and mail server and
25
+ writes the configuration for you, instead of requiring a hand-written file.
26
+
27
+ ### Fixed
28
+
29
+ - `specsr.__version__` reported a stale hardcoded version rather than the
30
+ installed one.
31
+ - `log_checkpoint_artifact` could raise `AttributeError` at the end of training
32
+ when a `wandb/` run directory shadowed an uninstalled `wandb` library.
33
+
34
+ ## [1.0.1] - 2026-08-15
35
+
36
+ ### Added
37
+
38
+ - First public release of `specsr`: a physics-informed, three-stage model that
39
+ super-resolves JWST/NIRSpec prism spectra (R ~ 100) towards the medium
40
+ gratings (R ~ 1000).
41
+ - `SpecSRPipeline.from_pretrained()` downloads the released weights from the
42
+ Hugging Face Hub on first use; no local data or credentials required.
43
+ - Command line: `specsr build-dataset | train | infer | evaluate`.
44
+ - Three tutorial notebooks with bundled held-out spectra, runnable without the
45
+ survey data.
46
+ - `SPECSR_OUTPUT_DIR` collects everything the package writes -- figures,
47
+ predictions, evaluation tables -- in one place, defaulting to `./outputs`.
48
+ - `ARCHITECTURE.md` describes the three stages, the wavelength grid, and the
49
+ invariants the code enforces on itself.
50
+ - Optional start/finish email notifications for training runs, via
51
+ `scripts/notify-run`. Off unless configured with your own address and mail
52
+ server; unconfigured it is a transparent passthrough.
53
+
54
+ Archived on Zenodo: [10.5281/zenodo.21943197](https://doi.org/10.5281/zenodo.21943197)
55
+ (all versions: [10.5281/zenodo.21943196](https://doi.org/10.5281/zenodo.21943196)).
56
+
57
+ ### Known limitations
58
+
59
+ These are measured, not suspected, and are documented in the paper:
60
+
61
+ - **Absolute flux scale is not predicted.** Training standardises each spectrum
62
+ independently, so the model learns a shape-to-shape mapping and returns output
63
+ on the input's scale. Compare in normalised units.
64
+ - **Integrated line fluxes are systematically under-recovered**, and recovery
65
+ depends strongly on line brightness. Reconstructions are suited to locating
66
+ and detecting lines rather than to measuring their strengths.
67
+ - **The redshift head is a conditioning stage, not a redshift pipeline.** Its
68
+ catastrophic-outlier rate is high enough that a redshift should be checked
69
+ against the line positions it implies.
70
+ - **Super-resolution does not improve on direct analysis of the prism** for the
71
+ [O III] doublet ratio test. See the paper for the comparison.
specsr-1.0.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Aryana Haghjoo
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.