aetherscan 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,1187 @@
1
+ Metadata-Version: 2.4
2
+ Name: aetherscan
3
+ Version: 1.0.0
4
+ Summary: Breakthrough Listen's first end-to-end production-grade DL pipeline for SETI @ scale
5
+ Project-URL: Homepage, https://github.com/zachtheyek/Aetherscan
6
+ Project-URL: Issues, https://github.com/zachtheyek/Aetherscan/issues
7
+ Author-email: Zach Yek <xiaoxiyek1.5@gmail.com>
8
+ Maintainer-email: Zach Yek <xiaoxiyek1.5@gmail.com>
9
+ License-Expression: BSD-3-Clause
10
+ License-File: LICENSE
11
+ Keywords: Anomaly Detection,Data Methods,Deep Learning,Machine Learning,Radio Astronomy,SETI,Signal Processing,Technosignatures
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: GPU :: NVIDIA CUDA :: 12
14
+ Classifier: Environment :: GPU :: NVIDIA CUDA :: 12 :: 12.4
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: BSD License
17
+ Classifier: Natural Language :: English
18
+ Classifier: Operating System :: Unix
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
24
+ Requires-Python: <3.13,>=3.10
25
+ Requires-Dist: astropy<7,>=6.1
26
+ Requires-Dist: h5py<3.12,>=3.11
27
+ Requires-Dist: hdf5plugin
28
+ Requires-Dist: huggingface-hub<1.22,>=1.21
29
+ Requires-Dist: imageio<3,>=2.34
30
+ Requires-Dist: joblib<2,>=1.4
31
+ Requires-Dist: matplotlib<3.10,>=3.9
32
+ Requires-Dist: numpy<2.0,>=1.26
33
+ Requires-Dist: psutil<6.1,>=6.0
34
+ Requires-Dist: python-dotenv<2,>=1.2.2
35
+ Requires-Dist: scikit-image<0.25,>=0.24
36
+ Requires-Dist: scikit-learn<1.6,>=1.5
37
+ Requires-Dist: scipy<1.14,>=1.13
38
+ Requires-Dist: setigen<3,>=2.6
39
+ Requires-Dist: setuptools<81,>=78.1.1
40
+ Requires-Dist: shap<0.47,>=0.46.0
41
+ Requires-Dist: slack-sdk<4,>=3.31.0
42
+ Requires-Dist: tensorflow[and-cuda]==2.17.*
43
+ Requires-Dist: umap-learn<0.6,>=0.5.6
44
+ Provides-Extra: dashboard
45
+ Requires-Dist: pandas<3,>=2.0; extra == 'dashboard'
46
+ Requires-Dist: plotly<6,>=5.24; extra == 'dashboard'
47
+ Requires-Dist: streamlit<1.41,>=1.39; extra == 'dashboard'
48
+ Provides-Extra: dev
49
+ Requires-Dist: pre-commit; extra == 'dev'
50
+ Requires-Dist: pytest; extra == 'dev'
51
+ Requires-Dist: ruff; extra == 'dev'
52
+ Description-Content-Type: text/markdown
53
+
54
+ <h1 align="center">📡 Aetherscan 📡</h1>
55
+ <p align="center">
56
+ <img src="docs/assets/aetherscan-banner.png" alt="Aetherscan">
57
+ </p>
58
+ <p align="center">
59
+ Breakthrough Listen's first end-to-end production-grade deep learning pipeline for SETI @ scale
60
+ <br />
61
+ <br />
62
+ <a href="https://github.com/zachtheyek/Aetherscan/actions/workflows/tests.yml"><img src="https://github.com/zachtheyek/Aetherscan/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
63
+ <a href="https://pypi.org/project/aetherscan/"><img src="https://img.shields.io/pypi/v/aetherscan.svg" alt="PyPI"></a>
64
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-BSD_3--Clause-blue.svg" alt="License"></a>
65
+ <a href="https://www.python.org/"><img src="https://img.shields.io/badge/Python-3.10%E2%80%933.12-blue.svg" alt="Python"></a>
66
+ <a href="https://www.tensorflow.org/"><img src="https://img.shields.io/badge/TensorFlow-2.17-orange.svg" alt="TensorFlow"></a>
67
+ <a href="https://developer.nvidia.com/cuda-toolkit"><img src="https://img.shields.io/badge/CUDA-12.4%E2%80%9312.8-green.svg" alt="CUDA"></a>
68
+ </p>
69
+ </p>
70
+
71
+ ---
72
+
73
+ ## Overview
74
+
75
+ Aetherscan is a deep learning pipeline for detecting anomalies in radio spectrograms with technosignature-like characteristics. It combines a beta-VAE (for dimensionality reduction/feature extraction) with a Random Forest ensemble (for candidate detection), trained on ~30m unique cadence snippets using a composite loss that balances reconstruction, KL divergence, and true/false clustering. The pipeline is designed with performance in mind, by default running single-node distributed training & inference, using zero-copy parallelism during pre- and post-processing.
76
+
77
+ The model architecture is based on [Ma et al. 2023](https://arxiv.org/abs/2301.12670) ("_A deep-learning search for technosignatures from 820 unique stars_"), extending the research prototype into a production-ready system capable of near real-time inference.
78
+
79
+ ---
80
+
81
+ ## Installation
82
+
83
+ ### System Requirements
84
+
85
+ Aetherscan supports two install paths off the same source tree. The NGC container is the canonical runtime on both clusters; the conda env is kept as an alternative for users who can't or don't want to use containers on Ampere.
86
+
87
+ **NGC container (canonical, runs on both clusters)**
88
+
89
+ - Ubuntu 24.04
90
+ - ≥1x NVIDIA GPU:
91
+ - Blackwell (sm_120, e.g. RTX PRO 6000) — driver ≥570 (native CUDA 12.8)
92
+ - Ampere (sm_86, e.g. RTX A4000) — driver ≥550 (host CUDA 12.3) via CUDA forward compatibility
93
+ - VRAM: **≥8 GB per GPU** recommended — measured peaks ~6 GB/GPU (training) and ~2.5 GB/GPU (inference) on the v1.0.0 release runs; gradient accumulation keeps per-GPU VRAM low
94
+ - RAM: **≥288 GB** for full-scale training and default catalog-scale inference (measured peaks ~260 GB training / ~200 GB inference, plus headroom — a strict-256 GB host sits too close to the training peak and risks OOM under page-cache pressure). Means are much lower (~150 GB training / ~36 GB inference); inference RAM scales with `--prefetch-depth` × the largest in-flight cadence, so lower `--prefetch-depth` for smaller-RAM hosts or small catalogs
95
+ - Disk: full-scale training round data ~150 GB per retained round (float16 default), up to ~3 TB with `--keep-round-data`; inference stamps are auto-pruned by default (~1 MB/cadence metadata retained + a transient ~5–20 GB/cadence × `--prefetch-depth` during extraction)
96
+ - Apptainer 1.4+ or SingularityCE 4.1+ (Python 3.12 / TF 2.17 / CUDA 12.8 live inside the container)
97
+ - See [`docs/GPU_RUNTIME_GUIDE.md`](docs/GPU_RUNTIME_GUIDE.md) for the full runbook
98
+
99
+ **Conda env (alternative, Ampere only)**
100
+
101
+ - Ubuntu 24.04
102
+ - ≥1x NVIDIA GPU:
103
+ - Ampere with CUDA 12.3+ driver
104
+ - VRAM / RAM same as above
105
+ - Python 3.10 / TF 2.17 (managed by conda)
106
+
107
+ > [!NOTE]
108
+ > There are no plans to support non-Nvidia GPUs
109
+
110
+ > [!NOTE]
111
+ > The figures above are measured from the v1.0.0 release runs — training on 6× RTX A4000 (16 GB) + 503 GB RAM (tag `train_20260729_152426`) and inference on 5× RTX PRO 6000 (96 GB) + 503 GB RAM over a 350-cadence `/datag` catalog subset (tag `inf_20260731_182011`) — via the always-on resource instrumentation (`system_resources` DB rows). They characterize full-scale runs; small runs need substantially less.
112
+
113
+ ### Run From Container
114
+
115
+ > [!NOTE]
116
+ > This is the canonical install path, and the only option for Blackwell clusters
117
+
118
+ **1. Clone the repository**
119
+
120
+ ```bash
121
+ git clone https://github.com/zachtheyek/Aetherscan.git
122
+ cd Aetherscan
123
+ ```
124
+
125
+ **2. Build the `.sif` image**
126
+
127
+ The same [`aetherscan.def`](aetherscan.def) recipe builds with either runtime — use whichever is installed on the host. Build on the cluster you intend to run on so the resulting `.sif` is produced by that cluster's native runtime:
128
+
129
+ ```bash
130
+ # SingularityCE (e.g. Blackwell cluster running 4.1.1)
131
+ singularity build aetherscan-ngc25.02.sif aetherscan.def
132
+
133
+ # Apptainer (e.g. Ampere cluster running v1.4.5)
134
+ apptainer build aetherscan-ngc25.02.sif aetherscan.def
135
+ ```
136
+
137
+ Build takes ~9 minutes and produces a ~9 GB image. On hardened HPC nodes you may also need the `--fakeroot` flag, and to redirect `SINGULARITY_TMPDIR` / `APPTAINER_TMPDIR` and `SINGULARITY_CACHEDIR` / `APPTAINER_CACHEDIR` to scratch storage; the full troubleshooting walkthrough lives in [`docs/GPU_RUNTIME_GUIDE.md`](docs/GPU_RUNTIME_GUIDE.md).
138
+
139
+ **3. Set up monitoring dashboards in tmux (optional)**
140
+
141
+ > [!Tip]
142
+ > Subsequent pipeline runs may proceed from the current step (3) onward
143
+
144
+ The repo ships a convenience script that instantiates a four-window tmux session for monitoring system resources (`htop` + a CPU/MEM ticker), GPU state (`watch nvidia-smi`), shared memory buffers (`watch ls /dev/shm`), and models/outputs dirs (`watch tree`):
145
+
146
+ ```bash
147
+ ./utils/start_tmux_session.sh
148
+ ```
149
+
150
+ Idempotent — re-running attaches to the existing session instead of recreating it.
151
+
152
+ **4. Configure secrets and paths (optional)**
153
+
154
+ Aetherscan reads secrets and path overrides from a `.env` file at the repo root. [`utils/run_container.sh`](utils/run_container.sh) auto-loads `<repo>/.env` into its own environment before launching the container and forwards the relevant keys via `--env`, so no `source .env` or inline prefix is needed.
155
+
156
+ ```ini
157
+ # .env example
158
+
159
+ # If none specified, defaults to /datax/scratch/zachy/{data|models|outputs}/aetherscan
160
+ # Note, CLI flags (--data-path, --model-path, --output-path) override these
161
+ AETHERSCAN_DATA_PATH=/path/to/data
162
+ AETHERSCAN_MODEL_PATH=/path/to/models
163
+ AETHERSCAN_OUTPUT_PATH=/path/to/outputs
164
+
165
+ # Optional: comma-separated extra host paths for run_container.sh to bind 1:1, for
166
+ # data outside the standard dirs (e.g. parent dir with raw .h5 files for inference)
167
+ AETHERSCAN_EXTRA_BINDS=/extra/host/paths
168
+
169
+ # If none specified, Slack integration is automatically disabled
170
+ SLACK_BOT_TOKEN=your-slack-bot-token
171
+ SLACK_CHANNEL=your-slack-channel
172
+
173
+ # Only needed for uploading model weights to the HuggingFace Hub (train --hf-upload);
174
+ # downloads (the inference default) hit a public repo and need no token
175
+ HF_TOKEN=your-huggingface-write-token
176
+ ```
177
+
178
+ > [!TIP]
179
+ > See [`SECURITY.md`](SECURITY.md) for best practices on managing `.env` files.
180
+
181
+ If you'd rather set them directly in your shell (skipping `.env`), `export` works equivalently and takes precedence over `.env` for any keys it sets — useful for one-off overrides:
182
+
183
+ ```bash
184
+ export SLACK_BOT_TOKEN="your-slack-bot-token"
185
+ export SLACK_CHANNEL="your-slack-channel"
186
+ ...
187
+
188
+ ./utils/run_container.sh python -m aetherscan.main train ...
189
+ ```
190
+
191
+ The `AETHERSCAN_*` paths are bind-mounted 1:1 between host and container, so they must already exist on the host before the pipeline starts. The `utils/run_container.sh` wrapper forwards `SLACK_*`, `AETHERSCAN_*`, and `HF_TOKEN` into the container explicitly; if you need additional env vars on the container side, extend the wrapper's `--env` list.
192
+
193
+ **5. Run pipeline**
194
+
195
+ ```bash
196
+ ./utils/run_container.sh python -m aetherscan.main {train|inference} \
197
+ --save-tag train
198
+ ```
199
+
200
+ The `utils/run_container.sh` wrapper auto-detects whether `apptainer` or `singularity` is on PATH (Apptainer wins when both are present), sets `--nv` for GPU passthrough, and binds the repo + `AETHERSCAN_{DATA,MODEL,OUTPUT}_PATH` 1:1 between host and container so absolute paths persisted in the DB stay valid across both. `PYTHONPATH` is set automatically inside the container — no inline prefix needed.
201
+
202
+ See the [Usage Examples](#usage-examples) section below for further ways to invoke the Aetherscan pipeline.
203
+
204
+ ### Run From Source
205
+
206
+ > [!NOTE]
207
+ > This is an alternative install path for Ampere clusters
208
+
209
+ **1. Clone the repository**
210
+
211
+ ```bash
212
+ git clone https://github.com/zachtheyek/Aetherscan.git
213
+ cd Aetherscan
214
+ ```
215
+
216
+ **2. Create conda environment**
217
+
218
+ ```bash
219
+ conda env create -f environment.yml
220
+ conda activate aetherscan
221
+ ```
222
+
223
+ **3. Set up monitoring dashboards in tmux (optional)**
224
+
225
+ > [!Tip]
226
+ > Subsequent pipeline runs may proceed from the current step (3) onward
227
+
228
+ The repo ships a convenience script that instantiates a four-window tmux session for monitoring system resources (`htop` + a CPU/MEM ticker), GPU state (`watch nvidia-smi`), shared memory buffers (`watch ls /dev/shm`), and models/outputs dirs (`watch tree`):
229
+
230
+ ```bash
231
+ ./utils/start_tmux_session.sh
232
+ ```
233
+
234
+ Idempotent — re-running attaches to the existing session instead of recreating it.
235
+
236
+ > [!Note]
237
+ > If you skip the tmux helper, it's recommended to run these two exports manually before launching the pipeline — the script's pipeline pane sets them for you, and without them you may hit TF library-loading issues or noisy startup logs:
238
+
239
+ ```bash
240
+ export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$CONDA_PREFIX/lib:$LD_LIBRARY_PATH
241
+ export TF_CPP_MIN_LOG_LEVEL=1
242
+ ```
243
+
244
+ **4. Configure secrets and paths (optional)**
245
+
246
+ Same `.env` file format and precedence rules as [Run From Container](#run-from-container) step 4. Two differences on this path:
247
+
248
+ - `<repo>/.env` is loaded directly into `os.environ` at the top of `main.py` via [python-dotenv](https://pypi.org/project/python-dotenv/) — no wrapper script in the loop — so **every** key in `.env` is visible to the pipeline, not just the subset the container wrapper forwards via `--env`.
249
+ - No host→container bind mounts, so `AETHERSCAN_*` paths only need to exist when the pipeline actually accesses them, not at startup.
250
+
251
+ Multiprocess worker pools inherit the values via `os.environ` as usual.
252
+
253
+ **5. Run pipeline**
254
+
255
+ ```bash
256
+ PYTHONPATH=src python -m aetherscan.main {train|inference} \
257
+ --save-tag train
258
+ ```
259
+
260
+ `PYTHONPATH=src` makes the `aetherscan` package importable from `src/` without a `pip install -e .` step. No inline `KEY=VALUE` prefix is needed for Slack credentials — the `.env` auto-load runs before any worker process is spawned, so `os.environ` inheritance to multiprocess pools is automatic.
261
+
262
+ See the [Usage Examples](#usage-examples) section below for further ways to invoke the Aetherscan pipeline.
263
+
264
+ ---
265
+
266
+ ## Usage Examples
267
+
268
+ > [!NOTE]
269
+ > `main.py` is the designated pipeline entry point.
270
+ > Non-development workflows should avoid directly calling other scripts/modules.
271
+
272
+ > [!NOTE]
273
+ > Each scenario below is shown twice — first with the container wrapper (canonical), then with the conda-env source invocation (alternative). CLI flags are identical between the two; only the launcher differs.
274
+
275
+ ### Training
276
+
277
+ > [!TIP]
278
+ > The examples below cover a small number of scenarios. For the full set of available flags, see [Train Command Help](#train-command-help).
279
+
280
+ **Default training run**
281
+
282
+ ```bash
283
+ # Container (canonical)
284
+ ./utils/run_container.sh python -m aetherscan.main train
285
+
286
+ # Source (Ampere conda env)
287
+ PYTHONPATH=src python -m aetherscan.main train
288
+ ```
289
+
290
+ **Training with custom parameters**
291
+
292
+ ```bash
293
+ # Container
294
+ ./utils/run_container.sh python -m aetherscan.main train \
295
+ --train-files real_filtered_LARGE_HIP110750.npy real_filtered_LARGE_HIP13402.npy real_filtered_LARGE_HIP8497.npy \
296
+ --num-training-rounds 20 \
297
+ --epochs-per-round 100 \
298
+ --curriculum-schedule exponential \
299
+ --save-tag test
300
+
301
+ # Source
302
+ PYTHONPATH=src python -m aetherscan.main train \
303
+ --train-files real_filtered_LARGE_HIP110750.npy real_filtered_LARGE_HIP13402.npy real_filtered_LARGE_HIP8497.npy \
304
+ --num-training-rounds 20 \
305
+ --epochs-per-round 100 \
306
+ --curriculum-schedule exponential \
307
+ --save-tag test
308
+ ```
309
+
310
+ **Resume from checkpoint**
311
+
312
+ ```bash
313
+ # Container
314
+ ./utils/run_container.sh python -m aetherscan.main train \
315
+ --load-dir checkpoints \
316
+ --load-tag round_10 \
317
+ --save-tag test
318
+
319
+ # Source
320
+ PYTHONPATH=src python -m aetherscan.main train \
321
+ --load-dir checkpoints \
322
+ --load-tag round_10 \
323
+ --save-tag test
324
+ ```
325
+
326
+ > [!WARNING]
327
+ > Per-round checkpoints live under `checkpoints/` — `--load-tag round_XX` without `--load-dir checkpoints` is rejected at validation (it used to silently resume from the newest stale model in the models root instead).
328
+
329
+ **Training with an explicit per-GPU memory cap (e.g. on an older Ampere GPU with lower VRAM)**
330
+
331
+ ```bash
332
+ # Container
333
+ ./utils/run_container.sh python -m aetherscan.main train \
334
+ --gpu-memory-limit-mb 14000 \
335
+ --save-tag test
336
+
337
+ # Source
338
+ PYTHONPATH=src python -m aetherscan.main train \
339
+ --gpu-memory-limit-mb 14000 \
340
+ --save-tag test
341
+ ```
342
+
343
+ **Watching the live dashboard from your local browser (SSH port forwarding)**
344
+
345
+ Each `train`/`inference` run auto-launches a Streamlit dashboard on the cluster node (enabled by
346
+ default), served on that node's `localhost:8501` (`config.monitor.dashboard_port`, default `8501`).
347
+ It reads the run's live SQLite DB, so it updates as the pipeline progresses. Because it binds to the
348
+ node's loopback interface, view it locally by opening an SSH tunnel that forwards the port, then
349
+ browsing to the forwarded address:
350
+
351
+ ```bash
352
+ # From your local machine — forward the dashboard port from the cluster node running the pipeline:
353
+ ssh -L 8501:localhost:8501 <cluster-host>
354
+ # ...then open http://localhost:8501 in your local browser. Keep the tunnel open while you watch.
355
+ ```
356
+
357
+ If you launched the run on a non-default port, forward that port instead. The pipeline also logs the
358
+ exact `ssh -L …` command when it starts the dashboard.
359
+
360
+ ### Inference
361
+
362
+ > [!TIP]
363
+ > The examples below cover a small number of scenarios. For the full set of available flags, see [Inference Command Help](#inference-command-help).
364
+
365
+ **Default inference run**
366
+
367
+ ```bash
368
+ # Container (canonical)
369
+ ./utils/run_container.sh python -m aetherscan.main inference
370
+
371
+ # Source (Ampere conda env)
372
+ PYTHONPATH=src python -m aetherscan.main inference
373
+ ```
374
+
375
+ **Inference on a pre-processed `.npy` file**
376
+
377
+ ```bash
378
+ # Container
379
+ ./utils/run_container.sh python -m aetherscan.main inference \
380
+ --test-files real_filtered_LARGE_test_HIP15638.npy \
381
+ --encoder-path /datax/scratch/zachy/models/aetherscan/vae_encoder_train_20260101_120000.keras \
382
+ --rf-path /datax/scratch/zachy/models/aetherscan/random_forest_train_20260101_120000.joblib \
383
+ --config-path /datax/scratch/zachy/models/aetherscan/config_train_20260101_120000.json \
384
+ --classification-threshold 0.99
385
+
386
+ # Source
387
+ PYTHONPATH=src python -m aetherscan.main inference \
388
+ --test-files real_filtered_LARGE_test_HIP15638.npy \
389
+ --encoder-path /datax/scratch/zachy/models/aetherscan/vae_encoder_train_20260101_120000.keras \
390
+ --rf-path /datax/scratch/zachy/models/aetherscan/random_forest_train_20260101_120000.joblib \
391
+ --config-path /datax/scratch/zachy/models/aetherscan/config_train_20260101_120000.json \
392
+ --classification-threshold 0.99
393
+ ```
394
+
395
+ **Inference from raw `.h5` files (invokes energy detection preprocessing)**
396
+
397
+ ```bash
398
+ # Container — if the raw .h5 paths in the CSV live outside the standard bind
399
+ # mounts (e.g. under /datag), then we bind them via AETHERSCAN_EXTRA_BINDS
400
+ AETHERSCAN_EXTRA_BINDS=/datag ./utils/run_container.sh python -m aetherscan.main inference \
401
+ --inference-files complete_cadences_catalog.csv \
402
+ --encoder-path /path/to/vae_encoder.keras \
403
+ --rf-path /path/to/random_forest.joblib \
404
+ --config-path /path/to/config.json \
405
+ --save-tag inf
406
+
407
+ # Source
408
+ PYTHONPATH=src python -m aetherscan.main inference \
409
+ --inference-files complete_cadences_catalog.csv \
410
+ --encoder-path /path/to/vae_encoder.keras \
411
+ --rf-path /path/to/random_forest.joblib \
412
+ --config-path /path/to/config.json \
413
+ --save-tag inf
414
+ ```
415
+
416
+ **Inference with async-allocator fallbacks (e.g. on a 5-GPU Blackwell topology)**
417
+
418
+ ```bash
419
+ # Container
420
+ ./utils/run_container.sh python -m aetherscan.main inference \
421
+ --no-async-allocator \
422
+ --save-tag inf
423
+
424
+ # Source
425
+ PYTHONPATH=src python -m aetherscan.main inference \
426
+ --no-async-allocator \
427
+ --save-tag inf
428
+ ```
429
+
430
+ ---
431
+
432
+ ## CLI Reference
433
+
434
+ Aetherscan uses a hierarchical configuration system with dataclass-based configs, whose state can be modified both at command time and runtime. At command time, the user can specify values via:
435
+
436
+ 1. **Defaults** - Defined in `src/aetherscan/config.py`
437
+ 2. **Environment variables** - For paths and secrets
438
+ 3. **CLI flags** - Override defaults & environment variables on startup
439
+
440
+ At runtime, the singleton `Config` instance can be accessed via `get_config()` and modified programmatically.
441
+
442
+ Read [docs/CONFIG_AND_CLI.md](/docs/CONFIG_AND_CLI.md) to learn more.
443
+
444
+ ### Top-Level Help
445
+
446
+ Aetherscan dispatches to one of two subcommands via the first positional argument. Regenerate this output with `./utils/run_container.sh python utils/print_cli_help.py top` (container) or `PYTHONPATH=src python utils/print_cli_help.py top` (source).
447
+
448
+ ```
449
+ usage: [-h] {train,inference} ...
450
+
451
+ Aetherscan Pipeline -- Breakthrough Listen's first end-to-end production-grade
452
+ DL pipeline for SETI @ scale
453
+
454
+ positional arguments:
455
+ {train,inference} Command to execute
456
+ train Execute training pipeline
457
+ inference Execute inference pipeline
458
+
459
+ options:
460
+ -h, --help show this help message and exit
461
+ ```
462
+
463
+ ### Train Command Help
464
+
465
+ The Aetherscan training pipeline exposes the following CLI flags to the user. Regenerate this output with `./utils/run_container.sh python utils/print_cli_help.py train` (container) or `PYTHONPATH=src python utils/print_cli_help.py train` (source).
466
+
467
+ ```
468
+ usage: train [-h] [--seed SEED] [--unseeded]
469
+ [--tf-deterministic-ops | --no-tf-deterministic-ops]
470
+ [--n-processes N_PROCESSES] [--data-path DATA_PATH]
471
+ [--model-path MODEL_PATH] [--output-path OUTPUT_PATH]
472
+ [--dashboard | --no-dashboard] [--dashboard-port DASHBOARD_PORT]
473
+ [--benchmark-report | --no-benchmark-report]
474
+ [--vae-latent-dim VAE_LATENT_DIM]
475
+ [--vae-dense-layer-size VAE_DENSE_LAYER_SIZE]
476
+ [--vae-kernel-size VAE_KERNEL_SIZE VAE_KERNEL_SIZE]
477
+ [--vae-beta VAE_BETA] [--vae-alpha VAE_ALPHA]
478
+ [--rf-n-estimators RF_N_ESTIMATORS] [--rf-bootstrap RF_BOOTSTRAP]
479
+ [--rf-max-features RF_MAX_FEATURES] [--rf-n-jobs RF_N_JOBS]
480
+ [--rf-seed RF_SEED] [--num-replicas NUM_REPLICAS]
481
+ [--gpu-memory-limit-mb GPU_MEMORY_LIMIT_MB]
482
+ [--nccl-num-packs NCCL_NUM_PACKS]
483
+ [--async-allocator | --no-async-allocator]
484
+ [--num-observations NUM_OBSERVATIONS] [--width-bin WIDTH_BIN]
485
+ [--downsample-factor DOWNSAMPLE_FACTOR] [--time-bins TIME_BINS]
486
+ [--freq-resolution FREQ_RESOLUTION]
487
+ [--time-resolution TIME_RESOLUTION]
488
+ [--num-target-backgrounds NUM_TARGET_BACKGROUNDS]
489
+ [--background-load-chunk-size BACKGROUND_LOAD_CHUNK_SIZE]
490
+ [--max-chunks-per-file MAX_CHUNKS_PER_FILE]
491
+ [--train-files TRAIN_FILES [TRAIN_FILES ...]]
492
+ [--num-training-rounds NUM_TRAINING_ROUNDS]
493
+ [--epochs-per-round EPOCHS_PER_ROUND]
494
+ [--num-samples-beta-vae NUM_SAMPLES_BETA_VAE]
495
+ [--num-samples-rf NUM_SAMPLES_RF]
496
+ [--train-val-split TRAIN_VAL_SPLIT]
497
+ [--per-replica-batch-size PER_REPLICA_BATCH_SIZE]
498
+ [--effective-batch-size EFFECTIVE_BATCH_SIZE]
499
+ [--per-replica-val-batch-size PER_REPLICA_VAL_BATCH_SIZE]
500
+ [--signal-injection-chunk-size SIGNAL_INJECTION_CHUNK_SIZE]
501
+ [--data-gen-task-size DATA_GEN_TASK_SIZE]
502
+ [--round-data-dir ROUND_DATA_DIR]
503
+ [--overlap-data-generation | --no-overlap-data-generation]
504
+ [--keep-round-data | --no-keep-round-data]
505
+ [--plot-injection-subsampling-count PLOT_INJECTION_SUBSAMPLING_COUNT]
506
+ [--plot-injection-outlier-percentile PLOT_INJECTION_OUTLIER_PERCENTILE]
507
+ [--latent-viz-num-cadences-per-type LATENT_VIZ_NUM_CADENCES_PER_TYPE]
508
+ [--latent-viz-step-interval LATENT_VIZ_STEP_INTERVAL]
509
+ [--latent-viz-umap-fit-max-samples LATENT_VIZ_UMAP_FIT_MAX_SAMPLES]
510
+ [--latent-viz-umap-n-neighbors LATENT_VIZ_UMAP_N_NEIGHBORS [LATENT_VIZ_UMAP_N_NEIGHBORS ...]]
511
+ [--latent-viz-umap-min-dist LATENT_VIZ_UMAP_MIN_DIST [LATENT_VIZ_UMAP_MIN_DIST ...]]
512
+ [--latent-viz-gif-max-frames LATENT_VIZ_GIF_MAX_FRAMES]
513
+ [--latent-viz-gif-duration-ms LATENT_VIZ_GIF_DURATION_MS]
514
+ [--latent-traversal-every-round | --no-latent-traversal-every-round]
515
+ [--latent-traversal-num-steps LATENT_TRAVERSAL_NUM_STEPS]
516
+ [--latent-traversal-max-sigma LATENT_TRAVERSAL_MAX_SIGMA]
517
+ [--snr-base SNR_BASE] [--initial-snr-range INITIAL_SNR_RANGE]
518
+ [--final-snr-range FINAL_SNR_RANGE]
519
+ [--curriculum-schedule CURRICULUM_SCHEDULE]
520
+ [--exponential-decay-rate EXPONENTIAL_DECAY_RATE]
521
+ [--step-easy-rounds STEP_EASY_ROUNDS]
522
+ [--step-hard-rounds STEP_HARD_ROUNDS]
523
+ [--base-learning-rate BASE_LEARNING_RATE]
524
+ [--min-learning-rate MIN_LEARNING_RATE]
525
+ [--min-pct-improvement MIN_PCT_IMPROVEMENT]
526
+ [--patience-threshold PATIENCE_THRESHOLD]
527
+ [--lr-reduction-factor LR_REDUCTION_FACTOR]
528
+ [--max-retries MAX_RETRIES] [--retry-delay RETRY_DELAY]
529
+ [--hf-upload | --no-hf-upload] [--hf-repo-id HF_REPO_ID]
530
+ [--load-dir LOAD_DIR] [--load-tag LOAD_TAG]
531
+ [--start-round START_ROUND] [--save-tag SAVE_TAG]
532
+ [--force-tag | --no-force-tag]
533
+
534
+ options:
535
+ -h, --help show this help message and exit
536
+ --seed SEED Root random seed for reproducible runs: every random
537
+ stream derives from it — data generation, dataset
538
+ split/shuffles, TF weight init, the VAE sampling layer
539
+ (training AND inference), the random forest,
540
+ UMAP/KMeans plot fits, and plot subsampling. Defaults
541
+ to a concrete value (reproducible out of the box);
542
+ must be >= 0. To run unseeded, pass --unseeded
543
+ --unseeded Opt OUT of the seeded default: draw every random
544
+ stream from OS entropy (non-reproducible). Mutually
545
+ exclusive with --seed
546
+ --tf-deterministic-ops, --no-tf-deterministic-ops
547
+ Force deterministic TensorFlow/cuDNN op
548
+ implementations
549
+ (tf.config.experimental.enable_op_determinism) for
550
+ bit-exact GPU reproducibility at some speed cost.
551
+ Default: enabled — without it, cuDNN autotune noise
552
+ can flip near-threshold candidates between identical
553
+ runs; opt out with --no-tf-deterministic-ops
554
+ --n-processes N_PROCESSES
555
+ Worker-process count for the multiprocessing pools
556
+ (energy detection + stamp extraction at inference;
557
+ data generation at training). Default: all cores. Host
558
+ tuning: never layered from a saved --config-path, so a
559
+ config recorded on a bigger host cannot oversubscribe
560
+ this one (must be >= 1)
561
+ --data-path DATA_PATH
562
+ Path to data directory (overrides AETHERSCAN_DATA_PATH
563
+ environment variable)
564
+ --model-path MODEL_PATH
565
+ Path to model directory (overrides
566
+ AETHERSCAN_MODEL_PATH environment variable)
567
+ --output-path OUTPUT_PATH
568
+ Path to output directory (overrides
569
+ AETHERSCAN_OUTPUT_PATH environment variable)
570
+ --dashboard, --no-dashboard
571
+ Auto-launch the live monitoring Streamlit dashboard
572
+ for this run; SSH-forward the port to view it
573
+ (default: on). Use --no-dashboard to disable
574
+ --dashboard-port DASHBOARD_PORT
575
+ Port for the auto-launched live dashboard (default:
576
+ 8501)
577
+ --benchmark-report, --no-benchmark-report
578
+ Render the end-of-run benchmark report (stage timeline
579
+ + bottleneck suggestions) and post it to Slack
580
+ (default: on). Use --no-benchmark-report to disable
581
+ --vae-latent-dim VAE_LATENT_DIM
582
+ Dimensionality of the VAE latent space (bottleneck
583
+ size)
584
+ --vae-dense-layer-size VAE_DENSE_LAYER_SIZE
585
+ Size of dense layer in VAE architecture (should match
586
+ frequency bins after downsampling)
587
+ --vae-kernel-size VAE_KERNEL_SIZE VAE_KERNEL_SIZE
588
+ Kernel size for Conv2D layers as two integers (e.g.,
589
+ --vae-kernel-size 3 3)
590
+ --vae-beta VAE_BETA Beta coefficient for KL divergence loss term in beta-
591
+ VAE (controls disentanglement)
592
+ --vae-alpha VAE_ALPHA
593
+ Alpha coefficient for clustering loss term in VAE
594
+ (controls cluster separation)
595
+ --rf-n-estimators RF_N_ESTIMATORS
596
+ Number of decision trees in the random forest ensemble
597
+ --rf-bootstrap RF_BOOTSTRAP
598
+ Whether to use bootstrap sampling when building trees
599
+ (enables bagging)
600
+ --rf-max-features RF_MAX_FEATURES
601
+ Number of features to consider for splits: 'sqrt',
602
+ 'log2', or a float (fraction of features)
603
+ --rf-n-jobs RF_N_JOBS
604
+ Number of parallel jobs for random forest training (-1
605
+ uses all CPU cores)
606
+ --rf-seed RF_SEED DEPRECATED: explicit random forest seed override. The
607
+ RF seed now derives from the root --seed (#279); this
608
+ alias remains for existing scripts and logs a
609
+ deprecation warning when used.
610
+ --num-replicas NUM_REPLICAS
611
+ Number of GPUs to use for the distributed strategy. If
612
+ omitted, the strategy uses every GPU visible to TF;
613
+ otherwise it is restricted to the first N physical
614
+ GPUs and the rest are left untouched. Must be >= 1 and
615
+ <= the number of physical GPUs on your machine.
616
+ --gpu-memory-limit-mb GPU_MEMORY_LIMIT_MB
617
+ Per-GPU memory cap in MiB. Omit to use memory-growth-
618
+ only (recommended on Blackwell). Set for TF to
619
+ allocate a fixed logical device of a given size per
620
+ physical GPU (e.g. 14000)
621
+ --nccl-num-packs NCCL_NUM_PACKS
622
+ num_packs for NCCL/HierarchicalCopy all-reduce. Lower
623
+ values (e.g. 1) reduces tiny-tensor latency; higher
624
+ values (e.g. >=4) can help bandwidth on >4-GPU
625
+ topologies.
626
+ --async-allocator, --no-async-allocator
627
+ Toggle TF_GPU_ALLOCATOR=cuda_malloc_async (default:
628
+ enabled). Pass --no-async-allocator as a workaround
629
+ for NGC 25.02 multi-GPU OOM bugs.
630
+ --num-observations NUM_OBSERVATIONS
631
+ Number of observations per cadence snippet (e.g., 6
632
+ for 3 ON + 3 OFF)
633
+ --width-bin WIDTH_BIN
634
+ Number of frequency bins per observation (spectral
635
+ resolution)
636
+ --downsample-factor DOWNSAMPLE_FACTOR
637
+ Downsampling factor for frequency bins (reduces
638
+ spectral dimension)
639
+ --time-bins TIME_BINS
640
+ Number of time bins per observation (temporal
641
+ resolution)
642
+ --freq-resolution FREQ_RESOLUTION
643
+ Frequency resolution in Hz (determined by instrument)
644
+ --time-resolution TIME_RESOLUTION
645
+ Time resolution in seconds (determined by instrument)
646
+ --num-target-backgrounds NUM_TARGET_BACKGROUNDS
647
+ Number of background (noise-only) cadences to load for
648
+ training data generation
649
+ --background-load-chunk-size BACKGROUND_LOAD_CHUNK_SIZE
650
+ Maximum number of background cadences to process at
651
+ once during loading (memory management)
652
+ --max-chunks-per-file MAX_CHUNKS_PER_FILE
653
+ Maximum number of chunks to load from a single data
654
+ file (limits per-file contribution)
655
+ --train-files TRAIN_FILES [TRAIN_FILES ...]
656
+ Space-separated list of training data file names
657
+ (e.g., real_filtered_LARGE_HIP110750.npy)
658
+ --num-training-rounds NUM_TRAINING_ROUNDS
659
+ Total number of training rounds in curriculum learning
660
+ schedule
661
+ --epochs-per-round EPOCHS_PER_ROUND
662
+ Number of epochs to train the VAE per curriculum
663
+ learning round
664
+ --num-samples-beta-vae NUM_SAMPLES_BETA_VAE
665
+ Number of training samples to generate for beta-VAE
666
+ per round (must be divisible by 4)
667
+ --num-samples-rf NUM_SAMPLES_RF
668
+ Number of training samples to generate for random
669
+ forest (must be divisible by 4)
670
+ --train-val-split TRAIN_VAL_SPLIT
671
+ Fraction of data to use for training vs validation
672
+ (e.g., 0.8 = 80% train, 20% val)
673
+ --per-replica-batch-size PER_REPLICA_BATCH_SIZE
674
+ Batch size per GPU/device replica during training
675
+ --effective-batch-size EFFECTIVE_BATCH_SIZE
676
+ Effective batch size for gradient accumulation across
677
+ all replicas
678
+ --per-replica-val-batch-size PER_REPLICA_VAL_BATCH_SIZE
679
+ Batch size per GPU/device replica during validation
680
+ --signal-injection-chunk-size SIGNAL_INJECTION_CHUNK_SIZE
681
+ Maximum cadences to process at once during synthetic
682
+ signal injection (must be divisible by 4)
683
+ --data-gen-task-size DATA_GEN_TASK_SIZE
684
+ Cadences per batched signal-injection worker task
685
+ (workers write results straight into the round's on-
686
+ disk memmap; must be >= 1)
687
+ --round-data-dir ROUND_DATA_DIR
688
+ Directory for disk-backed per-round training datasets
689
+ (defaults to <data-path>/training/round_data; needs
690
+ ~2.2x one round's size free when data-generation
691
+ overlap is enabled, ~1.1x otherwise)
692
+ --overlap-data-generation, --no-overlap-data-generation
693
+ Generate round k+1's training data in a background
694
+ producer process while round k trains (default:
695
+ enabled). Pass --no-overlap-data-generation to fall
696
+ back to sequential in-process generation for debugging
697
+ --keep-round-data, --no-keep-round-data
698
+ Retain each round's on-disk training data after that
699
+ round finishes (default: disabled — round k's data
700
+ directory is deleted as soon as round k's training
701
+ completes). Enable for debugging
702
+ --plot-injection-subsampling-count PLOT_INJECTION_SUBSAMPLING_COUNT
703
+ Max points per stat name, per signal type, for A→B
704
+ intensity bias scatter plots. Outliers are
705
+ prioritized, with the difference made up from randomly
706
+ sampling without replacement the remaining points
707
+ --plot-injection-outlier-percentile PLOT_INJECTION_OUTLIER_PERCENTILE
708
+ Threshold for points to always be included in A→B
709
+ intensity bias scatter plots
710
+ --latent-viz-num-cadences-per-type LATENT_VIZ_NUM_CADENCES_PER_TYPE
711
+ Number of cadences per signal type for latent space
712
+ visualization batch (total points = 4× this value × 6
713
+ observations per cadence)
714
+ --latent-viz-step-interval LATENT_VIZ_STEP_INTERVAL
715
+ Capture a latent space snapshot every N training steps
716
+ (lower = more snapshots, more DB writes, and larger
717
+ storage costs)
718
+ --latent-viz-umap-fit-max-samples LATENT_VIZ_UMAP_FIT_MAX_SAMPLES
719
+ Maximum number of pooled latent vectors used to fit
720
+ the UMAP model (remaining vectors are projected via
721
+ transform; lower = faster, higher = more faithful
722
+ embedding)
723
+ --latent-viz-umap-n-neighbors LATENT_VIZ_UMAP_N_NEIGHBORS [LATENT_VIZ_UMAP_N_NEIGHBORS ...]
724
+ UMAP n_neighbors values to sweep for latent space
725
+ visualization (e.g., --latent-viz-umap-n-neighbors 5
726
+ 15 30 50)
727
+ --latent-viz-umap-min-dist LATENT_VIZ_UMAP_MIN_DIST [LATENT_VIZ_UMAP_MIN_DIST ...]
728
+ UMAP min_dist values to sweep for latent space
729
+ visualization (e.g., --latent-viz-umap-min-dist 0.0
730
+ 0.1 0.5)
731
+ --latent-viz-gif-max-frames LATENT_VIZ_GIF_MAX_FRAMES
732
+ Maximum number of frames in latent space GIF output
733
+ (snapshots beyond this limit are log-subsampled,
734
+ prioritizing earlier training steps)
735
+ --latent-viz-gif-duration-ms LATENT_VIZ_GIF_DURATION_MS
736
+ Milliseconds per frame in latent space GIF output
737
+ --latent-traversal-every-round, --no-latent-traversal-every-round
738
+ Render latent-dimension traversal figures at the end
739
+ of every training round, in addition to the end-of-
740
+ training set (default: disabled)
741
+ --latent-traversal-num-steps LATENT_TRAVERSAL_NUM_STEPS
742
+ Number of traversal steps per latent dimension (must
743
+ be odd and >= 3 so the center column is the
744
+ unperturbed class-mean decode)
745
+ --latent-traversal-max-sigma LATENT_TRAVERSAL_MAX_SIGMA
746
+ Latent traversal range in per-dimension standard
747
+ deviations: steps span [-max_sigma, +max_sigma] (must
748
+ be > 0)
749
+ --snr-base SNR_BASE Base signal-to-noise ratio for curriculum learning
750
+ (minimum SNR difficulty level)
751
+ --initial-snr-range INITIAL_SNR_RANGE
752
+ SNR range for initial (easiest) training rounds
753
+ (signals sampled from snr_base to snr_base +
754
+ initial_snr_range)
755
+ --final-snr-range FINAL_SNR_RANGE
756
+ SNR range for final (hardest) training rounds (signals
757
+ sampled from snr_base to snr_base + final_snr_range).
758
+ Ignored if only training for 1 round
759
+ --curriculum-schedule CURRICULUM_SCHEDULE
760
+ Curriculum difficulty progression schedule: 'linear',
761
+ 'exponential', or 'step'
762
+ --exponential-decay-rate EXPONENTIAL_DECAY_RATE
763
+ Decay rate for exponential curriculum schedule (must
764
+ be negative; more negative = faster difficulty
765
+ increase)
766
+ --step-easy-rounds STEP_EASY_ROUNDS
767
+ Number of rounds with easy signals when using step
768
+ curriculum schedule
769
+ --step-hard-rounds STEP_HARD_ROUNDS
770
+ Number of rounds with hard signals when using step
771
+ curriculum schedule
772
+ --base-learning-rate BASE_LEARNING_RATE
773
+ Initial learning rate for Adam optimizer
774
+ --min-learning-rate MIN_LEARNING_RATE
775
+ Learning rate floor for adaptive learning rate
776
+ reduction
777
+ --min-pct-improvement MIN_PCT_IMPROVEMENT
778
+ Minimum fractional validation loss improvement to
779
+ avoid LR reduction (e.g., 0.001 = 0.1%)
780
+ --patience-threshold PATIENCE_THRESHOLD
781
+ Number of consecutive epochs without minimum
782
+ improvement before reducing learning rate
783
+ --lr-reduction-factor LR_REDUCTION_FACTOR
784
+ Multiplicative factor for learning rate reduction
785
+ (e.g., 0.2 reduces LR by 20%)
786
+ --max-retries MAX_RETRIES
787
+ Maximum number of retry attempts when training fails
788
+ due to errors
789
+ --retry-delay RETRY_DELAY
790
+ Delay in seconds between retry attempts after training
791
+ failure
792
+ --hf-upload, --no-hf-upload
793
+ Upload the final model artifacts (encoder, decoder,
794
+ random forest, config) plus a generated model card to
795
+ the HuggingFace Hub after training completes, tagging
796
+ the commit with --save-tag (default: disabled = local-
797
+ only). Requires HF_TOKEN in the environment (via .env)
798
+ --hf-repo-id HF_REPO_ID
799
+ HuggingFace model repo id (namespace/name) for weight
800
+ upload/download (default: zachtheyek/aetherscan)
801
+ --load-dir LOAD_DIR Subdirectory for checkpoint loading (relative to
802
+ --model-path)
803
+ --load-tag LOAD_TAG Checkpoint to load. A full run tag
804
+ ({command}_YYYYMMDD_HHMMSS) resumes that run in place
805
+ (its tag is adopted, so the resumed attempt writes
806
+ under the same run). round_XX (requires --load-dir
807
+ checkpoints) seeds a fresh run from that per-round
808
+ checkpoint, resuming from round XX+1 unless --start-
809
+ round is given.
810
+ --start-round START_ROUND
811
+ Round to begin/resume training from
812
+ --save-tag SAVE_TAG Run label prefix: one of test, train, inf, bench. The
813
+ datetime is appended automatically at runtime (e.g.
814
+ train_20260101_120000). Defaults to the subcommand
815
+ (train->train, inference->inf) if omitted.
816
+ --force-tag, --no-force-tag
817
+ Override the fail-early save-tag collision guard:
818
+ proceed even when an explicitly-provided --save-tag
819
+ matches existing artifacts, DB rows, or (with --hf-
820
+ upload) an existing HuggingFace tag (default:
821
+ disabled)
822
+ ```
823
+
824
+ ### Inference Command Help
825
+
826
+ The Aetherscan inference pipeline exposes the following CLI flags to the user. Regenerate this output with `./utils/run_container.sh python utils/print_cli_help.py inference` (container) or `PYTHONPATH=src python utils/print_cli_help.py inference` (source).
827
+
828
+ ```
829
+ usage: inference [-h] [--seed SEED] [--unseeded]
830
+ [--tf-deterministic-ops | --no-tf-deterministic-ops]
831
+ [--n-processes N_PROCESSES] [--data-path DATA_PATH]
832
+ [--model-path MODEL_PATH] [--output-path OUTPUT_PATH]
833
+ [--dashboard | --no-dashboard]
834
+ [--dashboard-port DASHBOARD_PORT]
835
+ [--benchmark-report | --no-benchmark-report]
836
+ [--num-replicas NUM_REPLICAS]
837
+ [--gpu-memory-limit-mb GPU_MEMORY_LIMIT_MB]
838
+ [--async-allocator | --no-async-allocator]
839
+ [--test-files TEST_FILES [TEST_FILES ...]]
840
+ [--inference-files INFERENCE_FILES [INFERENCE_FILES ...]]
841
+ [--encoder-path ENCODER_PATH] [--rf-path RF_PATH]
842
+ [--config-path CONFIG_PATH]
843
+ [--per-replica-batch-size PER_REPLICA_BATCH_SIZE]
844
+ [--classification-threshold CLASSIFICATION_THRESHOLD]
845
+ [--screening-threshold SCREENING_THRESHOLD]
846
+ [--mc-draws MC_DRAWS]
847
+ [--reference-cloud-size REFERENCE_CLOUD_SIZE]
848
+ [--prefetch-depth PREFETCH_DEPTH]
849
+ [--cadence-group-by-cols CADENCE_GROUP_BY_COLS [CADENCE_GROUP_BY_COLS ...]]
850
+ [--cadence-h5-path-col CADENCE_H5_PATH_COL]
851
+ [--cadence-expected-obs CADENCE_EXPECTED_OBS]
852
+ [--coarse-channel-width COARSE_CHANNEL_WIDTH]
853
+ [--coarse-channel-log-interval COARSE_CHANNEL_LOG_INTERVAL]
854
+ [--bandpass-method BANDPASS_METHOD]
855
+ [--pfb-taps-per-channel PFB_TAPS_PER_CHANNEL]
856
+ [--bandpass-debug-plot | --no-bandpass-debug-plot]
857
+ [--spline-order SPLINE_ORDER]
858
+ [--detection-window-size DETECTION_WINDOW_SIZE]
859
+ [--detection-step-size DETECTION_STEP_SIZE]
860
+ [--stat-threshold STAT_THRESHOLD] [--stamp-width STAMP_WIDTH]
861
+ [--store-downsampled-stamps | --no-store-downsampled-stamps]
862
+ [--overlap-search | --no-overlap-search]
863
+ [--overlap-fraction OVERLAP_FRACTION]
864
+ [--preprocess-output-dir PREPROCESS_OUTPUT_DIR]
865
+ [--prune-stamps | --no-prune-stamps]
866
+ [--inference-viz | --no-inference-viz]
867
+ [--inference-viz-scope {full,new}]
868
+ [--stamp-gallery-top-k STAMP_GALLERY_TOP_K]
869
+ [--max-candidate-plots MAX_CANDIDATE_PLOTS]
870
+ [--max-retries MAX_RETRIES] [--retry-delay RETRY_DELAY]
871
+ [--hf-repo-id HF_REPO_ID] [--hf-revision HF_REVISION]
872
+ [--save-tag SAVE_TAG] [--force-tag | --no-force-tag]
873
+
874
+ options:
875
+ -h, --help show this help message and exit
876
+ --seed SEED Root random seed for reproducible runs: every random
877
+ stream derives from it — data generation, dataset
878
+ split/shuffles, TF weight init, the VAE sampling layer
879
+ (training AND inference), the random forest,
880
+ UMAP/KMeans plot fits, and plot subsampling. Defaults
881
+ to a concrete value (reproducible out of the box);
882
+ must be >= 0. To run unseeded, pass --unseeded
883
+ --unseeded Opt OUT of the seeded default: draw every random
884
+ stream from OS entropy (non-reproducible). Mutually
885
+ exclusive with --seed
886
+ --tf-deterministic-ops, --no-tf-deterministic-ops
887
+ Force deterministic TensorFlow/cuDNN op
888
+ implementations
889
+ (tf.config.experimental.enable_op_determinism) for
890
+ bit-exact GPU reproducibility at some speed cost.
891
+ Default: enabled — without it, cuDNN autotune noise
892
+ can flip near-threshold candidates between identical
893
+ runs; opt out with --no-tf-deterministic-ops
894
+ --n-processes N_PROCESSES
895
+ Worker-process count for the multiprocessing pools
896
+ (energy detection + stamp extraction at inference;
897
+ data generation at training). Default: all cores. Host
898
+ tuning: never layered from a saved --config-path, so a
899
+ config recorded on a bigger host cannot oversubscribe
900
+ this one (must be >= 1)
901
+ --data-path DATA_PATH
902
+ Path to data directory (overrides AETHERSCAN_DATA_PATH
903
+ environment variable)
904
+ --model-path MODEL_PATH
905
+ Path to model directory (overrides
906
+ AETHERSCAN_MODEL_PATH environment variable)
907
+ --output-path OUTPUT_PATH
908
+ Path to output directory (overrides
909
+ AETHERSCAN_OUTPUT_PATH environment variable)
910
+ --dashboard, --no-dashboard
911
+ Auto-launch the live monitoring Streamlit dashboard
912
+ for this run; SSH-forward the port to view it
913
+ (default: on). Use --no-dashboard to disable
914
+ --dashboard-port DASHBOARD_PORT
915
+ Port for the auto-launched live dashboard (default:
916
+ 8501)
917
+ --benchmark-report, --no-benchmark-report
918
+ Render the end-of-run benchmark report (stage timeline
919
+ + bottleneck suggestions) and post it to Slack
920
+ (default: on). Use --no-benchmark-report to disable
921
+ --num-replicas NUM_REPLICAS
922
+ Number of GPUs to use for the distributed strategy. If
923
+ omitted, the strategy uses every GPU visible to TF;
924
+ otherwise it is restricted to the first N physical
925
+ GPUs and the rest are left untouched. Must be >= 1 and
926
+ <= the number of physical GPUs on your machine.
927
+ --gpu-memory-limit-mb GPU_MEMORY_LIMIT_MB
928
+ Per-GPU memory cap in MiB. Omit to use memory-growth-
929
+ only (recommended on Blackwell). Set for TF to
930
+ allocate a fixed logical device of a given size per
931
+ physical GPU (e.g. 14000)
932
+ --async-allocator, --no-async-allocator
933
+ Toggle TF_GPU_ALLOCATOR=cuda_malloc_async (default:
934
+ enabled). Pass --no-async-allocator as a workaround
935
+ for NGC 25.02 multi-GPU OOM bugs.
936
+ --test-files TEST_FILES [TEST_FILES ...]
937
+ Space-separated list of testing data file names (e.g.,
938
+ real_filtered_LARGE_test_HIP15638.npy)
939
+ --inference-files INFERENCE_FILES [INFERENCE_FILES ...]
940
+ Space-separated list of inference catalog file names
941
+ (e.g. complete_cadences_catalog.csv). Expects .h5
942
+ filepaths to individual observations, and sufficient
943
+ metadata for recovering cadence groupings. If
944
+ provided, triggers the energy detection preprocessing
945
+ pipeline and takes precedence over --test-files
946
+ --encoder-path ENCODER_PATH
947
+ Path to trained VAE encoder model file (.keras).
948
+ Optional: when none of --encoder-path/--rf-
949
+ path/--config-path are given, the artifacts are
950
+ downloaded from the HuggingFace Hub (see --hf-repo-
951
+ id/--hf-revision); provide either all three local
952
+ paths or none
953
+ --rf-path RF_PATH Path to trained Random Forest model file (.joblib).
954
+ Optional: see --encoder-path for the all-three-or-none
955
+ rule
956
+ --config-path CONFIG_PATH
957
+ Path to config file from corresponding training run
958
+ (.json). Optional: see --encoder-path for the all-
959
+ three-or-none rule
960
+ --per-replica-batch-size PER_REPLICA_BATCH_SIZE
961
+ Batch size per GPU/device replica during inference
962
+ --classification-threshold CLASSIFICATION_THRESHOLD
963
+ Science threshold for candidate detection, applied to
964
+ the pass-2 MC mean probability (the two-pass cascade's
965
+ final score)
966
+ --screening-threshold SCREENING_THRESHOLD
967
+ Permissive pass-1 screening threshold of the two-pass
968
+ cascade (tuned for recall; must not exceed
969
+ --classification-threshold). Snippets below it are
970
+ rejected without MC scoring
971
+ --mc-draws MC_DRAWS Seeded Monte-Carlo latent draws per pass-2 survivor
972
+ (mean carries the science threshold; std is the
973
+ reported uncertainty spread)
974
+ --reference-cloud-size REFERENCE_CLOUD_SIZE
975
+ Size of the seeded uniform reservoir of pass-1 rejects
976
+ MC-scored as the candidate uncertainty plot's survey
977
+ background (0 disables)
978
+ --prefetch-depth PREFETCH_DEPTH
979
+ Cadences preprocessed+loaded ahead of the GPU stage in
980
+ the streaming loop (>= 1). Each unit of depth overlaps
981
+ energy-detection reads with stamp extraction and the
982
+ serial per-cadence sections, costing one in-flight
983
+ cadence of RAM (up to ~65 GB for RFI-dense C-band
984
+ cadences); outputs are identical at any depth
985
+ (default: 3 per the on-cluster A/B)
986
+ --cadence-group-by-cols CADENCE_GROUP_BY_COLS [CADENCE_GROUP_BY_COLS ...]
987
+ Space-separated list of CSV column names whose joint
988
+ value defines cadence membership (e.g., Target Session
989
+ Band 'Cadence ID' Frequency)
990
+ --cadence-h5-path-col CADENCE_H5_PATH_COL
991
+ CSV column containing the .h5 file path for each
992
+ observation (default: '.h5 path')
993
+ --cadence-expected-obs CADENCE_EXPECTED_OBS
994
+ Required number of observations per cadence (default:
995
+ 6 for ABACAD)
996
+ --coarse-channel-width COARSE_CHANNEL_WIDTH
997
+ Number of fine channels per coarse channel (default:
998
+ 1048576)
999
+ --coarse-channel-log-interval COARSE_CHANNEL_LOG_INTERVAL
1000
+ Progress-logging cadence for energy detection, in
1001
+ coarse channels per log line. Default: ~25% milestone
1002
+ lines per ON file (the per-channel lines were 62% of a
1003
+ run's Slack-bound log volume); pass an explicit N to
1004
+ restore every-N-channels lines. Parallelism itself
1005
+ comes from the persistent worker pool, not this knob.
1006
+ --bandpass-method BANDPASS_METHOD
1007
+ Bandpass flattening method for energy detection: 'pfb'
1008
+ (default) divides each coarse channel by the
1009
+ instrument's static polyphase-filterbank response;
1010
+ 'spline' fits and subtracts a per-channel spline
1011
+ --pfb-taps-per-channel PFB_TAPS_PER_CHANNEL
1012
+ PFB prototype-filter taps per coarse channel for
1013
+ --bandpass-method pfb (default: 12, the
1014
+ GBT/Breakthrough Listen backend value). INSTRUMENT-
1015
+ DEPENDENT: must match the backend that produced the
1016
+ .h5 files
1017
+ --bandpass-debug-plot, --no-bandpass-debug-plot
1018
+ Save a per-cadence bandpass-flattening overlay debug
1019
+ plot (raw vs flattened integrated spectrum for a few
1020
+ sampled coarse channels) under plots/inference/{save-
1021
+ tag}/ (default: off)
1022
+ --spline-order SPLINE_ORDER
1023
+ Spline order for bandpass fitting with --bandpass-
1024
+ method spline (default: 16)
1025
+ --detection-window-size DETECTION_WINDOW_SIZE
1026
+ Sliding window size in fine channels for normality
1027
+ test (default: 256)
1028
+ --detection-step-size DETECTION_STEP_SIZE
1029
+ Step size in fine channels for sliding window
1030
+ (default: 128)
1031
+ --stat-threshold STAT_THRESHOLD
1032
+ D'Agostino-Pearson statistic threshold for hit
1033
+ detection (default: 2048.0)
1034
+ --stamp-width STAMP_WIDTH
1035
+ Width in fine channels of the extracted stamp around
1036
+ each hit (default: 4096; must equal --width-bin)
1037
+ --store-downsampled-stamps, --no-store-downsampled-stamps
1038
+ Downsample stamps along frequency (by --downsample-
1039
+ factor) at extraction time, storing stamp_width //
1040
+ downsample_factor bins per stamp (~8x smaller at
1041
+ defaults; default: enabled). Pass --no-store-
1042
+ downsampled-stamps to archive raw-resolution stamps;
1043
+ loading handles both layouts.
1044
+ --overlap-search, --no-overlap-search
1045
+ Additionally extract stamps offset by
1046
+ ±overlap_fraction*stamp_width around each hit. Pass
1047
+ --no-overlap-search to disable when the config default
1048
+ is True.
1049
+ --overlap-fraction OVERLAP_FRACTION
1050
+ Fractional offset (relative to stamp_width) for
1051
+ overlap-search stamps (default: 0.5)
1052
+ --preprocess-output-dir PREPROCESS_OUTPUT_DIR
1053
+ Directory for per-cadence .npy outputs from
1054
+ preprocessing. Default: a per-CSV directory {data_path
1055
+ }/inference/preprocessed/<csv_stem>_ed<hash>/ keyed on
1056
+ the energy-detection config fingerprint — runs sharing
1057
+ an ED config reuse each other's stamps automatically,
1058
+ and any ED-config change resolves to a fresh
1059
+ directory. Pass a directory explicitly to pin/share
1060
+ one location (reuse is still guarded by the sidecar's
1061
+ recorded h5 paths and ED fingerprint)
1062
+ --prune-stamps, --no-prune-stamps
1063
+ Delete each cadence's stamp .npy right after its
1064
+ 'inferred' manifest row lands, keeping the metadata
1065
+ .json plus a ~196 KB snippet sidecar per candidate —
1066
+ resume rides the DB row, and only stamps this run
1067
+ freshly extracted are ever pruned. Without pruning a
1068
+ full catalog writes ~30-90 TB of stamps. Default: AUTO
1069
+ — enabled for the fingerprint-scoped default cache
1070
+ directory, disabled when --preprocess-output-dir is
1071
+ set explicitly. Pass --no-prune-stamps to keep every
1072
+ stamp (slice-scale runs wanting the cross-run rerun
1073
+ cache).
1074
+ --inference-viz, --no-inference-viz
1075
+ Render the inference visualization suite (energy
1076
+ detection distributions, hit spectrum, bandpass
1077
+ overlay, stamp/candidate galleries, confidence
1078
+ distribution, latent projection, summary card) at the
1079
+ end of a CSV inference run, saved under
1080
+ plots/inference/{save_tag}/ and uploaded to Slack
1081
+ (default: enabled). Pass --no-inference-viz to
1082
+ disable.
1083
+ --inference-viz-scope {full,new}
1084
+ Which cadences the metadata-driven viz figures cover:
1085
+ 'full' (default) renders the whole accumulated tag
1086
+ every successful pass; 'new' renders only cadences
1087
+ inferred this pass — recommended for resumed multi-
1088
+ pass catalog campaigns, where 'full' re-pays the
1089
+ entire catalog's viz tail on every pass. DB-sourced
1090
+ candidate figures always cover the full tag either
1091
+ way.
1092
+ --stamp-gallery-top-k STAMP_GALLERY_TOP_K
1093
+ Number of top-statistic stamps shown in the stamp
1094
+ gallery figure, each as a 6-observation waterfall grid
1095
+ (default: 12)
1096
+ --max-candidate-plots MAX_CANDIDATE_PLOTS
1097
+ Maximum number of per-candidate figures rendered per
1098
+ run, highest confidence first (default: 50; the
1099
+ candidate gallery is unaffected)
1100
+ --max-retries MAX_RETRIES
1101
+ Maximum number of retry attempts for inference
1102
+ (including preprocessing) on failure
1103
+ --retry-delay RETRY_DELAY
1104
+ Delay in seconds between inference retry attempts
1105
+ --hf-repo-id HF_REPO_ID
1106
+ HuggingFace model repo id (namespace/name) for weight
1107
+ upload/download (default: zachtheyek/aetherscan)
1108
+ --hf-revision HF_REVISION
1109
+ HuggingFace revision (tag, branch, or commit hash) to
1110
+ pin the model download to when no local artifact paths
1111
+ are given (default: v{package version} when running as
1112
+ an installed release, else the repo's latest release
1113
+ tag — highest semver vX.Y.Z tag; a release tag is
1114
+ required for a no-artifact download)
1115
+ --save-tag SAVE_TAG Run label prefix: one of test, train, inf, bench. The
1116
+ datetime is appended automatically at runtime (e.g.
1117
+ inf_20260101_120000). Defaults to the subcommand
1118
+ (train->train, inference->inf) if omitted.
1119
+ --force-tag, --no-force-tag
1120
+ Override the fail-early save-tag collision guard:
1121
+ proceed even when an explicitly-provided --save-tag
1122
+ matches a previous run's saved config or DB rows
1123
+ (default: disabled)
1124
+ ```
1125
+
1126
+ ---
1127
+
1128
+ ## Known Issues
1129
+
1130
+ For a list of known issues, limitations, and workarounds, see [`KNOWN_ISSUES.md`](/KNOWN_ISSUES.md).
1131
+
1132
+ ---
1133
+
1134
+ ## Contributing To Aetherscan
1135
+
1136
+ Contributions are welcome! Quick start:
1137
+
1138
+ ```bash
1139
+ git clone https://github.com/zachtheyek/Aetherscan.git
1140
+ cd Aetherscan
1141
+
1142
+ singularity build aetherscan-ngc25.02.sif aetherscan.def
1143
+ # or:
1144
+ apptainer build aetherscan-ngc25.02.sif aetherscan.def
1145
+
1146
+ ./utils/start_tmux_session.sh
1147
+
1148
+ pre-commit install
1149
+ ```
1150
+
1151
+ - PRs: Must be linked to an existing issue and pass all hooks
1152
+ - Commits: Must carry a verified GPG signature — see [Commit Signing (GPG)](CONTRIBUTING.md#commit-signing-gpg)
1153
+ - Branches: Use `feature/`, `hotfix/`, or `misc/` prefixes
1154
+ - Code style: PEP-8 with minor relaxations, enforced via [ruff](https://docs.astral.sh/ruff/) (see [pyproject.toml](pyproject.toml))
1155
+
1156
+ See [`CONTRIBUTING.md`](/CONTRIBUTING.md) for full guidelines on workflow, project structure, and testing.
1157
+
1158
+ ---
1159
+
1160
+ ## Citations
1161
+
1162
+ If you use Aetherscan in your research, please cite it using GitHub's citations feature.
1163
+
1164
+ <p align="center">
1165
+ <img src="docs/assets/github-citation-button.png" alt="Citations">
1166
+ </p>
1167
+
1168
+ See [`CITATION.cff`](CITATION.cff) for details
1169
+
1170
+ ---
1171
+
1172
+ ## Security
1173
+
1174
+ Aetherscan is committed to responsible disclosure. Quick reference:
1175
+
1176
+ - **Report vulnerabilities:** Open a [GitHub Discussion](https://github.com/zachtheyek/Aetherscan/discussions) with the "security" label (non-critical) or contact [@zachtheyek](https://breakthroughlisten.slack.com/archives/D01SJG0L0TE) on Slack (critical; expect a response within 48-72h)
1177
+ - **Incident response:** Contain compromised credentials immediately, then assess scope, notify affected parties, remediate, and document
1178
+ - **Secrets:** Never commit tokens; use `.env` files (gitignored). Rotate immediately if compromised
1179
+ - **Automated scanning:** [gitleaks](https://github.com/gitleaks/gitleaks) pre-commit hook blocks accidental secret commits; GitHub Dependabot monitors for vulnerable dependencies
1180
+
1181
+ See [`SECURITY.md`](SECURITY.md) for more details.
1182
+
1183
+ ---
1184
+
1185
+ ## License
1186
+
1187
+ Aetherscan is distributed under the BSD-3-Clause license, a permissive license that allows commercial use, modification, and distribution with minimal restrictions. See [LICENSE](LICENSE) for details. All contributions to the project are assumed to be licensed under the same terms.