jaxht 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 (61) hide show
  1. jaxht-0.1.0/.github/workflows/release.yml +52 -0
  2. jaxht-0.1.0/.github/workflows/test.yml +36 -0
  3. jaxht-0.1.0/.gitignore +25 -0
  4. jaxht-0.1.0/CHANGELOG.md +34 -0
  5. jaxht-0.1.0/CITATION.cff +27 -0
  6. jaxht-0.1.0/CLAUDE.md +195 -0
  7. jaxht-0.1.0/DISCREPANCIES.md +84 -0
  8. jaxht-0.1.0/LICENSE +21 -0
  9. jaxht-0.1.0/PKG-INFO +165 -0
  10. jaxht-0.1.0/README.md +129 -0
  11. jaxht-0.1.0/ROADMAP.md +270 -0
  12. jaxht-0.1.0/docs/accuracy.md +101 -0
  13. jaxht-0.1.0/docs/consumers.md +105 -0
  14. jaxht-0.1.0/docs/design.md +183 -0
  15. jaxht-0.1.0/docs/findings_phase0.md +102 -0
  16. jaxht-0.1.0/docs/gpu.md +218 -0
  17. jaxht-0.1.0/docs/masked.md +197 -0
  18. jaxht-0.1.0/docs/motivation.md +120 -0
  19. jaxht-0.1.0/docs/offgrid.md +121 -0
  20. jaxht-0.1.0/docs/performance.md +145 -0
  21. jaxht-0.1.0/pixi.lock +3819 -0
  22. jaxht-0.1.0/pyproject.toml +118 -0
  23. jaxht-0.1.0/scripts/accuracy_sweep.py +104 -0
  24. jaxht-0.1.0/scripts/bench_transforms.py +126 -0
  25. jaxht-0.1.0/scripts/exploratory/phase0_floor.py +165 -0
  26. jaxht-0.1.0/scripts/gpu_check.py +224 -0
  27. jaxht-0.1.0/scripts/gpu_diagnostic.py +714 -0
  28. jaxht-0.1.0/scripts/masked_sweep.py +161 -0
  29. jaxht-0.1.0/scripts/offgrid_sweep.py +138 -0
  30. jaxht-0.1.0/scripts/profile_adjoint.py +188 -0
  31. jaxht-0.1.0/scripts/profile_offgrid_forward.py +209 -0
  32. jaxht-0.1.0/scripts/profile_ongrid_compile.py +192 -0
  33. jaxht-0.1.0/scripts/submit_compile_probe_2048.sh +50 -0
  34. jaxht-0.1.0/scripts/submit_confirm_2048.sh +51 -0
  35. jaxht-0.1.0/scripts/submit_gpu_diagnostic.sh +92 -0
  36. jaxht-0.1.0/scripts/submit_profile_adjoint.sh +48 -0
  37. jaxht-0.1.0/scripts/submit_profile_offgrid.sh +56 -0
  38. jaxht-0.1.0/src/jht/__init__.py +86 -0
  39. jaxht-0.1.0/src/jht/_nufft.py +171 -0
  40. jaxht-0.1.0/src/jht/_recursion.py +572 -0
  41. jaxht-0.1.0/src/jht/_reference.py +143 -0
  42. jaxht-0.1.0/src/jht/analysis.py +54 -0
  43. jaxht-0.1.0/src/jht/diff.py +100 -0
  44. jaxht-0.1.0/src/jht/healpix.py +350 -0
  45. jaxht-0.1.0/src/jht/masked.py +383 -0
  46. jaxht-0.1.0/src/jht/offgrid.py +209 -0
  47. jaxht-0.1.0/src/jht/py.typed +0 -0
  48. jaxht-0.1.0/src/jht/weights.py +101 -0
  49. jaxht-0.1.0/tests/.gitkeep +0 -0
  50. jaxht-0.1.0/tests/test_accuracy.py +90 -0
  51. jaxht-0.1.0/tests/test_api.py +72 -0
  52. jaxht-0.1.0/tests/test_floor.py +64 -0
  53. jaxht-0.1.0/tests/test_gpu.py +83 -0
  54. jaxht-0.1.0/tests/test_grad.py +258 -0
  55. jaxht-0.1.0/tests/test_healpix.py +132 -0
  56. jaxht-0.1.0/tests/test_masked.py +262 -0
  57. jaxht-0.1.0/tests/test_offgrid.py +253 -0
  58. jaxht-0.1.0/tests/test_recursion.py +223 -0
  59. jaxht-0.1.0/tests/test_vectorized.py +140 -0
  60. jaxht-0.1.0/tests/test_weights.py +79 -0
  61. jaxht-0.1.0/tests/test_wiener.py +346 -0
@@ -0,0 +1,52 @@
1
+ name: release
2
+
3
+ # Build and publish to PyPI on a version tag (e.g. `git tag v0.1.0 && git push --tags`).
4
+ # Uses PyPI Trusted Publishing (OIDC) -- no API token / secret.
5
+ #
6
+ # ONE-TIME PyPI SETUP before the first tag (the project need not exist yet -- use a
7
+ # "pending publisher"): on https://pypi.org -> your account -> Publishing, add a
8
+ # trusted publisher with:
9
+ # PyPI project name: jaxht (distribution name; the import name stays `jht`)
10
+ # Owner: jrcheshire
11
+ # Repository name: jht
12
+ # Workflow name: release.yml
13
+ # Environment name: pypi
14
+ # Keep __version__ in src/jht/__init__.py in sync with the tag (the build reads the
15
+ # version from there via hatch).
16
+
17
+ on:
18
+ push:
19
+ tags: ["v*"]
20
+
21
+ jobs:
22
+ build:
23
+ name: Build sdist + wheel
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.12"
30
+ - run: python -m pip install --upgrade build twine
31
+ - run: python -m build
32
+ - run: python -m twine check dist/*
33
+ - uses: actions/upload-artifact@v4
34
+ with:
35
+ name: dist
36
+ path: dist/
37
+
38
+ publish:
39
+ name: Publish to PyPI (Trusted Publishing)
40
+ needs: build
41
+ runs-on: ubuntu-latest
42
+ environment:
43
+ name: pypi
44
+ url: https://pypi.org/project/jaxht/
45
+ permissions:
46
+ id-token: write # OIDC for Trusted Publishing -- no token needed
47
+ steps:
48
+ - uses: actions/download-artifact@v4
49
+ with:
50
+ name: dist
51
+ path: dist/
52
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,36 @@
1
+ name: test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: ${{ github.workflow }}-${{ github.ref }}
10
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
11
+
12
+ jobs:
13
+ lint-and-test:
14
+ name: Lint + typecheck + tests (linux)
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 30
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - uses: prefix-dev/setup-pixi@v0.8.10
21
+ with:
22
+ # `locked: true` runs `pixi install --locked`, so any pyproject.toml
23
+ # drift from pixi.lock surfaces as a CI failure (the co-commit invariant).
24
+ locked: true
25
+ cache: true
26
+
27
+ - name: Lint
28
+ run: pixi run lint
29
+
30
+ - name: Typecheck
31
+ run: pixi run typecheck
32
+
33
+ # The default pixi env pulls the ducc0/healpy oracles, so this is the full
34
+ # gated parity suite (CPU, float64). The GPU parity gate auto-skips (no device).
35
+ - name: Test
36
+ run: pixi run test
jaxht-0.1.0/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+
9
+ # Pixi / envs
10
+ .pixi/
11
+ .venv/
12
+
13
+ # Tool caches
14
+ .mypy_cache/
15
+ .ruff_cache/
16
+ .pytest_cache/
17
+
18
+ # Scratch / run outputs (not fixtures)
19
+ runs/
20
+ scratch/
21
+ gpu_diag_results.jsonl
22
+
23
+ # OS / editor
24
+ .DS_Store
25
+ *.swp
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to jht are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-06-07
10
+
11
+ Inaugural release.
12
+
13
+ ### Added
14
+ - **On-grid transforms** — spin-0 and spin-2 `synthesis` (a_lm → map) and the
15
+ exact adjoint `adjoint_synthesis`, validated to machine precision against
16
+ healpy **and** ducc0 (spin-2 inverse at the HEALPix floor, no s2fft-style
17
+ structural defect).
18
+ - **Approximate inverse** — `bare_analysis` (Sᵀ W) and `map2alm` (jht's own ring
19
+ quadrature weights + Jacobi iteration; ~1e-13 on band-limited maps).
20
+ - **Quadrature weights** — `ring_weights`, `pixel_weights`.
21
+ - **Partial-sky / masked** — `pseudo_alm`, `deconvolve` (cut-sky CG), `wiener`
22
+ (masked Wiener filter / MUSE inner solve), `constrained_realization`.
23
+ - **Off-grid (NUFFT)** — `synthesis_general` / `adjoint_synthesis_general` for
24
+ spin 0–3 at arbitrary pointings; alm- **and** pointing-differentiable under
25
+ native autodiff. JAX-native replacement for ducc0's `synthesis_general`.
26
+ - **Differentiable real-DOF interface** — `synthesis_real`, `analysis_real`,
27
+ `bandpower`, with the `alm_to_real` / `real_to_alm` isometry and the
28
+ `alm_metric_weight` (2 − δ_m0) bridge.
29
+ - **GPU (CUDA)** — pure JAX, runs on GPU with no code change; a parity harness
30
+ (`scripts/gpu_check.py`) and a single-slot performance diagnostic
31
+ (`scripts/gpu_diagnostic.py`).
32
+
33
+ [Unreleased]: https://github.com/jrcheshire/jht/compare/v0.1.0...HEAD
34
+ [0.1.0]: https://github.com/jrcheshire/jht/releases/tag/v0.1.0
@@ -0,0 +1,27 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use jht in your research, please cite it."
3
+ title: "jht: JAX-native spherical harmonic transforms"
4
+ abstract: >-
5
+ A clean-room, pure-JAX implementation of the forward and inverse spherical
6
+ harmonic transform (map <-> a_lm) for spin-0 and spin-2 fields on the HEALPix
7
+ ring pixelization, plus an off-grid (NUFFT) path for arbitrary pointings.
8
+ GPU-capable, fully differentiable under JAX autodiff, and dependency-controlled
9
+ (JAX + numpy at runtime).
10
+ type: software
11
+ authors:
12
+ - given-names: James
13
+ family-names: Cheshire
14
+ email: cheshire@caltech.edu
15
+ affiliation: California Institute of Technology
16
+ version: 0.1.0
17
+ date-released: 2026-06-07
18
+ license: MIT
19
+ repository-code: "https://github.com/jrcheshire/jht"
20
+ keywords:
21
+ - spherical harmonic transform
22
+ - HEALPix
23
+ - JAX
24
+ - GPU
25
+ - automatic differentiation
26
+ - NUFFT
27
+ - cosmic microwave background
jaxht-0.1.0/CLAUDE.md ADDED
@@ -0,0 +1,195 @@
1
+ # jht — JAX Harmonic Transforms
2
+
3
+ JAX-native spherical harmonic transforms. GPU-capable, fully differentiable,
4
+ and **dependency-controlled** — pure JAX + numpy at runtime, no compiled C++
5
+ extension and no heavyweight third-party SHT library. Born from the BICEP/Keck
6
+ `bk-jax` pipeline's need for a transform it can run on GPU, differentiate
7
+ through, and *own* the numerics of.
8
+
9
+ **Status (2026-06-06): scaffolding only.** This file, `ROADMAP.md`, and the
10
+ `docs/` notes were written to carry the design into a future development
11
+ session. No transforms are implemented yet. **Read `ROADMAP.md` next; the first
12
+ action is the Phase-0 feasibility spike.**
13
+
14
+ ---
15
+
16
+ ## What jht is, in one paragraph
17
+
18
+ A focused, clean-room implementation of the forward and inverse spherical
19
+ harmonic transform (map ↔ aₗₘ) for **spin-0 and spin-2** fields on the
20
+ **HEALPix ring** pixelization, in the **BICEP/Keck angular regime**
21
+ (ℓ_max ≲ 1000, nside ≤ ~2048). It exists to serve the GPU / differentiable
22
+ tier of analysis that a CPU-only C++ transform structurally cannot. It is
23
+ *not* an attempt to reimplement a general all-spin / all-sampling library.
24
+
25
+ ## Scope
26
+
27
+ **In scope (the target):**
28
+ - Spin-0 and spin-2 (T and Q/U / E/B) transforms, forward and adjoint.
29
+ - HEALPix ring pixelization.
30
+ - BK regime: ℓ_max ≲ 1000, nside ≤ ~2048; partial-sky (masked) analysis.
31
+ - GPU execution (the primary motivation) and JAX autodiff (alm-linear).
32
+ - Validation to the relevant accuracy tier against ducc0 / healpy.
33
+
34
+ **Out of scope (for now — keep the surface small):**
35
+ - Arbitrary spin-s, non-HEALPix samplings, extreme ℓ_max. We are not chasing
36
+ s2fft-style generality.
37
+ - **Off-grid synthesis (the NUFFT)** — synthesis at arbitrary detector
38
+ pointings, i.e. the `bk-jax` *sim-forward* path. That is a distinct
39
+ capability (a JAX NUFFT) and a separate, later phase. "JAX-native SHT" ≠
40
+ "escape ducc everywhere": jht replaces the **on-grid** SHT only.
41
+ - Being gratuitously BK-coupled. Write the core so BK assumptions live at the
42
+ edges; the transform itself should be reusable.
43
+
44
+ ## Why jht exists (decision record — see `docs/motivation.md` for the full story)
45
+
46
+ The recurring constraint is a single C++ black box (ducc0) that does the
47
+ harmonic transforms. It is excellent but:
48
+ - **CPU-only.** The forecast / field-level-inference (MUSE) ambitions are
49
+ GPU-hungry; ducc can't go there.
50
+ - **A black box.** ducc is vendored in `bk-jax` as a submodule wrapped in a
51
+ custom XLA FFI — that is *build* control, not *code* control. Its internals
52
+ are unmodifiable C++, and off-grid pointing tangents raise
53
+ `NotImplementedError`.
54
+ - **A build tax.** The C++/scikit-build/CUDA toolchain has repeatedly broken
55
+ CI and local installs.
56
+
57
+ The leading JAX-native alternative, **s2fft**, was evaluated and rejected:
58
+ its **spin-2 HEALPix inverse has a structural precision defect** (measured
59
+ 3–28% error at production ℓ, vs its own documented ~1e-3), and as of
60
+ 2026-06 it is **still at v1.4.0 (12 Feb 2026)** with the relevant issues triaged
61
+ and parked ("no time") and a stagnant PR queue. (The 2026-05 evaluation spike
62
+ ran on v1.3.0; the v1.3.0→v1.4.0 diff is CUDA/build-only, so the spin-2 accuracy
63
+ is byte-identical — the date in the original record was wrong, the conclusion
64
+ stands.) **Do not depend on s2fft, and do not fork it** (owning a large codebase
65
+ you find broken is the opposite of dependency control). Clean-room, *informed
66
+ by* the published methods (libsharp / Kostelec–Rockmore for the spin-2-safe
67
+ ℓ-recursion; Price & McEwen for the JAX branch-free renorm *technique* only —
68
+ explicitly NOT their m-recursion, which is the suspected defect home), is the
69
+ control-aligned path.
70
+
71
+ Crucially, the s2fft evaluation **validated the architecture**: a JAX-native
72
+ SHT integrates cleanly (JIT-clean, spin-0 machine-precision, finite/well-scaled
73
+ VJP). The concept works; one library's spin-2 corner does not.
74
+
75
+ ## Relationship to bk-jax (the consumer)
76
+
77
+ - jht is a **future dependency** of `bk-jax` (`~/bicepkeck/bk-jax`), added as a
78
+ path/pip dependency once Phase 0–1 land. It is developed and versioned here,
79
+ independently — that's the point.
80
+ - **ducc stays in bk-jax for the production path.** The ~1e-4 purity /
81
+ E→B-leakage-critical bandpower pipeline keeps using ducc; that accuracy is
82
+ person-decades of hard-won tuning and is not the thing to risk first.
83
+ - **jht earns its keep in the tier ducc can't serve:** GPU forecast sweeps,
84
+ the MUSE inner Wiener solve, and (eventually) fully-differentiable geometry —
85
+ where the HEALPix ~1e-3 accuracy floor is acceptable.
86
+ - `bk-jax` already dispatches its transform via a `BK_JAX_SHT_BACKEND` env var;
87
+ jht becomes a new backend option there (Phase 3).
88
+
89
+ ## Accuracy philosophy (the validation contract)
90
+
91
+ This is the make-or-break of the project; treat it with the same rigor `bk-jax`
92
+ applies — **a-priori tolerances, gate-driven, and tolerances are NOT relaxed
93
+ without an explicit discussion and sign-off.**
94
+
95
+ - HEALPix has **no sampling theorem**, so any HEALPix SHT is approximate. The
96
+ fundamental floor without ring weights is ~1e-3; ducc reaches ~1e-4 and
97
+ better via **ring quadrature weights + Jacobi (map2alm-style) iteration**.
98
+ - jht's first target is **reach the HEALPix floor with no structural defect**
99
+ (i.e. spin-2 must NOT show s2fft's 3–28% — it must sit at ~1e-3 like spin-0),
100
+ then close toward ducc by adding ring weights + iteration.
101
+ - **Spin-2 analysis is the gate.** It is exactly what s2fft got wrong — and the
102
+ 2026-06 dive pinned the defect to the *analysis quadrature* (missing ring
103
+ weights + polar folding + a stalling unweighted iteration), NOT the recursion
104
+ (see the crux section). Gate the weighted spin-2 round-trip **per-(ℓ,m), below
105
+ the ℓ≤1.5·nside band-limit ceiling** so recursion / quadrature / ceiling errors
106
+ are never conflated; if it reaches the floor flat across all m (no s2fft-style
107
+ m<ℓ leakage), the project is viable.
108
+ - Every transform lands with a parity test vs **both** healpy and ducc0
109
+ (cross-checking conventions), at a documented tolerance. Log any residual
110
+ mismatch (a `DISCREPANCIES.md`, mirroring bk-jax discipline) rather than
111
+ burying it.
112
+
113
+ ## The crux technical risk
114
+
115
+ Two distinct risks — and the 2026-06 literature dive **corrected which one is
116
+ make-or-break** (full reasoning in `docs/design.md` and the session plan
117
+ `~/.claude/plans/yeah-let-s-get-to-lovely-feather.md`):
118
+
119
+ 1. **Recursion stability to ℓ_max~1000 — necessary, but NOT s2fft's bug.**
120
+ Associated-Legendre / Wigner-d recursions underflow at high ℓ; the solved
121
+ technique is libsharp-style enhanced-exponent ("X-number") scaling. jht uses
122
+ the libsharp 3-term ℓ-recursion (Kostelec–Rockmore), in the increasing-ℓ
123
+ direction, with a **branch-free per-step log-renorm** — a *known, solved*
124
+ problem with a published spin-2≡spin-0 accuracy proof (libsharp §5.1.2).
125
+ This is **table-stakes, not the differentiator**: s2fft already rescales its
126
+ recursion and still fails spin-2, so getting the recursion right is necessary
127
+ but not sufficient.
128
+
129
+ 2. **Spin-2 HEALPix *analysis quadrature* — the actual make-or-break.** s2fft's
130
+ defect lives here, not in the recursion: its spin-2 single-mode errors appear
131
+ at ℓ=8/16/32 and *shrink* with ℓ (35→28→22%), spin-0 (identical recursion) is
132
+ machine-precision, and the failures sit far below the ℓ≤1.5·nside ceiling —
133
+ all incompatible with recursion underflow, all consistent with an
134
+ aliasing/folding + missing-weights defect in the spin-2 map2alm path. jht's
135
+ gate is the **weighted spin-2 round-trip, validated per-(ℓ,m) below the
136
+ band-limit ceiling**, so recursion / quadrature / ceiling errors are never
137
+ conflated. Test both risks explicitly and early (Phase 0).
138
+
139
+ ## Conventions — pin and document these from day one
140
+
141
+ These have repeatedly bitten `bk-jax`; do not leave them implicit.
142
+
143
+ - **Polarization sign:** HEALPix-internal vs IAU U-sign. Decide the internal
144
+ convention, document it, and cross-validate against healpy (`pol=True`) and
145
+ ducc. (bk-jax stores HEALPix-internal and flips U at I/O boundaries.)
146
+ - **Spin sign convention** (spin +2 vs −2 / E,B sign).
147
+ - **aₗₘ layout** (healpy m-major triangular packing; real vs complex; which ℓ,m
148
+ ordering) and **normalization** (4π vs orthonormal). Mirror healpy/ducc and
149
+ document the exact mapping.
150
+ - **Differentiability convention:** the on-grid SHT is alm-linear, so JVP/VJP
151
+ are straightforward — but note the **JAX-VJP-convention vs strict-math-adjoint**
152
+ subtlety (the `2·conj(·)` on m>0 modes) that bit bk-jax. Register transposes
153
+ deliberately and test `jacfwd ≡ jacrev` and the inner-product adjoint
154
+ identity.
155
+
156
+ ## Tooling & conventions
157
+
158
+ - **Pixi** is the env tool (`pixi run test|lint|format|typecheck`). Standard.
159
+ - **Pure Python / JAX — no compiled extension.** This is a deliberate feature:
160
+ zero build-toolchain pain, unlike bk-jax's vendored-ducc FFI. Keep it that
161
+ way; if a kernel ever truly needs C++/CUDA, that's a major decision, not a
162
+ drive-by.
163
+ - **Runtime deps = jax + numpy only.** ducc0 / healpy are dev/test oracles in
164
+ the pixi env, never `[project.dependencies]`.
165
+ - **No Jupyter notebooks for working code** — scripts + `ipython %run` for
166
+ exploration (user-level standard).
167
+ - **JAX float64 is opt-in per entry point** (`jax.config.update("jax_enable_x64",
168
+ True)` in tests/scripts; library code does not touch global config) — same
169
+ rule as bk-jax; SHT accuracy work needs float64.
170
+
171
+ ## Attribution (release blocker)
172
+
173
+ Published / shippable author fields must read **"James Cheshire"**
174
+ (`cheshire@caltech.edu`) — already set in `pyproject.toml` `authors`/
175
+ `maintainers`. "jamie" is fine only in the local git author config. This repo
176
+ may become a published dependency; verify the name before any tag / build /
177
+ publish. ("Jamie" must never appear in a shipped artifact.)
178
+
179
+ ## Dev workflow (per transform)
180
+
181
+ 1. Implement the transform over typed JAX arrays (parametric where a systematic
182
+ knob could later live).
183
+ 2. Gate it against healpy **and** ducc0 at an a-priori documented tolerance.
184
+ 3. Pin & document the conventions it assumes (`docs/design.md`).
185
+ 4. `pixi run lint` + `typecheck` before committing; terse single-line commit
186
+ messages (user preference).
187
+ 5. Log any numeric mismatch in `DISCREPANCIES.md`, don't bury it as a TODO.
188
+
189
+ ## Pointers
190
+
191
+ - `ROADMAP.md` — phased plan + hard gates + open questions.
192
+ - `docs/motivation.md` — why jht exists (full decision record).
193
+ - `docs/design.md` — technical design, accuracy tiers, conventions, algorithm.
194
+ - Consumer / sibling context: `~/bicepkeck/bk-jax` (the pipeline that motivates
195
+ jht; do not make jht depend on reading bk-jax to be understood).
@@ -0,0 +1,84 @@
1
+ # DISCREPANCIES
2
+
3
+ Residual numeric mismatches and known-limitation notes, with their cause — logged
4
+ rather than buried as TODOs (mirroring bk-jax discipline). Each entry says
5
+ whether it is a *defect* (to fix) or an *expected property* (to document).
6
+
7
+ ## Spin-2 cut-sky recovery — E/B ambiguous modes (expected, not a defect)
8
+
9
+ `jht.masked.deconvolve` recovers the true a_lm from a cut sky only where the cut
10
+ leaves the modes constrained. For **spin-2** there is an additional fundamental
11
+ floor: on a cut sky, E and B are not cleanly separable — a subspace of modes
12
+ (the *ambiguous modes*, Bunn et al. 2003) is supported almost entirely under the
13
+ mask and is therefore unconstrained by the data. These near-null directions of
14
+ `Sᵀ(MW)S` are not recoverable; CG from `x0=0` returns the minimum-norm solution
15
+ within that subspace, and `reg>0` (Tikhonov) bounds it.
16
+
17
+ - **Status:** expected physics (information loss from the cut), **not** a
18
+ transform defect. The *operator* is correct — gated to ~1e-13 against the
19
+ explicit dense `Sᵀ diag(MW) S` (`tests/test_masked.py::test_operator_matches_dense`)
20
+ and symmetric/PSD — and the pseudo-a_lm reproduces healpy/ducc to machine
21
+ precision. Only the *recoverability* of cut-localized E/B modes is limited.
22
+ - **Manifestation:** in `scripts/masked_sweep.py`, deconvolution error grows as
23
+ fsky shrinks and blows up under heavy apodization (which down-weights data).
24
+ spin-2 reaches the same recovery floor as spin-0 for mild cuts (no s2fft-style
25
+ m<ℓ defect — that was ruled out in Phase 0); the spin-2-specific limitation is
26
+ only the ambiguous-mode subspace at aggressive cuts.
27
+ - **Mitigation / scope:** use `reg>0` to stabilise, restrict the recovered band,
28
+ or — now implemented — bound the ambiguous subspace with a signal prior via
29
+ `jht.masked.wiener` (the Cℓ-informed generalization of `reg`; the MUSE
30
+ inner-solve tier), with `constrained_realization` for posterior draws. The
31
+ operator and recovery are unchanged; the prior simply gives the unconstrained
32
+ subspace a principled, finite answer instead of the min-norm CG one. See
33
+ `docs/masked.md`.
34
+
35
+ ## Differentiability: `jax.linear_transpose` / forward-mode of the raw kernels (limitation, not a defect)
36
+
37
+ The supported autodiff path is JAX's **native reverse/forward AD** through the
38
+ public transforms — `jax.grad` / `vjp` / `jacrev` (complex aₗₘ), and `jacfwd ≡
39
+ jacrev` / FD on the real-DOF layer (`jht.diff`). All Phase-2 gates pass on these
40
+ (`tests/test_grad.py`).
41
+
42
+ Two lower-level entry points are **not** supported, by deliberate choice:
43
+
44
+ - `jax.linear_transpose(synthesis, …)` — the forward `lax.scan` in the recursion
45
+ engine is not directly transposable as a standalone linear function ("Error
46
+ interpreting argument to scan as a JAX value"). `jax.vjp`/`grad` are unaffected:
47
+ they linearize first (baking the static recursion plan as constants) and
48
+ transpose the linearized jaxpr, which is clean. Forward scatters carry
49
+ `unique_indices=True` so the *scatter*-transpose blocker is gone; the residual is
50
+ the scan, which native AD sidesteps.
51
+ - `jax.jacfwd` / `jax.jvp` on the **complex** `synthesis` — `jacfwd` requires
52
+ real-valued inputs (a JAX constraint, not jht-specific). Forward-mode lives on
53
+ the **real-DOF layer** (`synthesis_real`/`analysis_real`), where `jacfwd ≡
54
+ jacrev` to ~1e-16.
55
+
56
+ - **Status:** expected JAX-mechanics limitations with first-class supported
57
+ alternatives, **not** transform defects. A `custom_vjp` would *block* forward
58
+ mode entirely; the only kernel-routing fix needs JAX's internal `jax.core.
59
+ Primitive` API (removed in jax 0.9.2). Native AD is the chosen, version-stable
60
+ path. See `docs/design.md` §Differentiability.
61
+
62
+ ## Off-grid (`synthesis_general`): spin>0 m=0 phantom vs ducc (expected, not a defect)
63
+
64
+ For spin > 0 the off-grid synthesis keeps both real components at every m (Re→first
65
+ plane, Im→second), so the field depends on the **unphysical `Im(a_{l,0})`** at m=0 —
66
+ the same phantom documented on-grid in `docs/design.md`. jht's *strict adjoint*
67
+ `adjoint_synthesis_general` therefore carries a nonzero m=0 imaginary part, while
68
+ ducc0's `adjoint_synthesis_general` zeros it.
69
+
70
+ - **Status:** expected; jht's adjoint is the exact transpose (the inner-product
71
+ identity `<S a, v> == <a, Sᵀ v>_G` holds to ~1e-16, `tests/test_offgrid.py::
72
+ test_adjoint_identity`), ducc applies a different m=0 convention. **Harmless** —
73
+ physical skies have real `a_{l,0}`, so the phantom is never excited. The ducc
74
+ cross-check (`test_adjoint_vs_ducc`) compares the physical m≥1 modes (matched to
75
+ ~1e-13); the m≥1 / real-m=0 content agrees with ducc bit-for-bit.
76
+
77
+ ## Off-grid: the −spin channel carries `(−1)^s` (convention pin, now matched to ducc)
78
+
79
+ The spin-weighted E/B → ±spin mapping is `+s a = −(aE + i aB)`, `−s a =
80
+ −(−1)^s (aE − i aB)`. The `(−1)^s` on the −spin channel is **invisible for even
81
+ spin** (the on-grid 0/2 path never exposed it) but flips the result for **odd spin
82
+ (1, 3)**. Pinned by matching ducc0's `synthesis_general` for spin 1/3 (a sign probe;
83
+ `tests/test_offgrid.py::test_forward_vs_ducc` covers all of spin 0–3). Documented so
84
+ the even-only on-grid convention is not assumed to carry over to odd spin.
jaxht-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 James Cheshire
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.
jaxht-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: jaxht
3
+ Version: 0.1.0
4
+ Summary: JAX-native spherical harmonic transforms (BK-regime first): GPU-capable, differentiable, dependency-controlled.
5
+ Project-URL: Homepage, https://github.com/jrcheshire/jht
6
+ Project-URL: Repository, https://github.com/jrcheshire/jht
7
+ Project-URL: Issues, https://github.com/jrcheshire/jht/issues
8
+ Project-URL: Changelog, https://github.com/jrcheshire/jht/blob/main/CHANGELOG.md
9
+ Author-email: James Cheshire <cheshire@caltech.edu>
10
+ Maintainer-email: James Cheshire <cheshire@caltech.edu>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: automatic-differentiation,cosmic-microwave-background,cosmology,gpu,healpix,jax,nufft,spherical-harmonic-transform
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
23
+ Classifier: Topic :: Scientific/Engineering :: Physics
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: jax>=0.4.30
27
+ Requires-Dist: numpy>=1.24
28
+ Provides-Extra: dev
29
+ Requires-Dist: mypy; extra == 'dev'
30
+ Requires-Dist: pytest; extra == 'dev'
31
+ Requires-Dist: ruff; extra == 'dev'
32
+ Provides-Extra: validation
33
+ Requires-Dist: ducc0; extra == 'validation'
34
+ Requires-Dist: healpy; extra == 'validation'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # jht — JAX Harmonic Transforms
38
+
39
+ JAX-native spherical harmonic transforms (map ↔ aₗₘ): **GPU-capable**, **fully
40
+ differentiable**, and **dependency-controlled** (pure JAX + numpy at runtime — no
41
+ compiled C++ extension, no heavyweight third-party SHT library). Scoped to the
42
+ BICEP/Keck regime — **spin-0 and spin-2** on the **HEALPix RING** pixelization,
43
+ ℓ_max ≲ 1000, nside ≤ ~2048 — but written cleanly so it can serve as a general
44
+ transform dependency.
45
+
46
+ It exists to serve the GPU / differentiable tier of analysis that a CPU-only C++
47
+ transform (ducc0) structurally cannot, while *owning the numerics*. See
48
+ [`docs/motivation.md`](docs/motivation.md) for the full decision record.
49
+
50
+ ## Status (2026-06-10)
51
+
52
+ Phases 0–4 **complete and validated** (190 tests pass + 8 GPU-gated skips,
53
+ CPU/float64):
54
+
55
+ - **On-grid transforms** — spin-0 & spin-2 synthesis (`aₗₘ→map`) and the exact
56
+ adjoint `Sᵀ`, validated to machine precision vs healpy **and** ducc0; spin-2
57
+ inverse at the HEALPix floor with **no s2fft-style structural defect**.
58
+ - **Accuracy** — jht's own ring quadrature weights + Jacobi iteration reach
59
+ ~1e-13 on band-limited maps (matches `healpy.map2alm(use_weights=True)`); see
60
+ [`docs/accuracy.md`](docs/accuracy.md).
61
+ - **Partial-sky** — masked pseudo-aₗₘ, a cut-sky CG deconvolution, and a masked
62
+ Wiener filter / constrained realization (the MUSE inner solve); see
63
+ [`docs/masked.md`](docs/masked.md).
64
+ - **Off-grid (NUFFT)** — `synthesis_general` / `adjoint_synthesis_general`
65
+ evaluate a band-limited field at **arbitrary pointings** (spin 0–3), alm- **and**
66
+ pointing-differentiable. The JAX-native replacement for ducc0's
67
+ `sht.experimental.synthesis_general` (on-grid SHT + this NUFFT = the full ducc0
68
+ surface bk-jax depends on); see [`docs/offgrid.md`](docs/offgrid.md).
69
+ - **Differentiability** — native JAX autodiff (`jacfwd ≡ jacrev`, tight adjoint
70
+ identity), plus a convention-clean real-DOF layer `jht.diff`; see
71
+ [`docs/design.md`](docs/design.md) §Differentiability.
72
+ - **GPU** — pure JAX, so the transforms run on CUDA with no code change. Measured on
73
+ Cannon A100/V100 (fp64): GPU==CPU parity ~1e-13 across the BK regime **including
74
+ nside=2048**, forward synthesis 14–60× the CPU. See **Performance** below and
75
+ [`docs/gpu.md`](docs/gpu.md).
76
+
77
+ ## Install
78
+
79
+ Standard env is [pixi](https://pixi.sh):
80
+
81
+ ```bash
82
+ pixi install # CPU env (osx-arm64 / linux-64)
83
+ pixi run test # the gated suite
84
+ ```
85
+
86
+ GPU (CUDA, linux-64 — see [`docs/gpu.md`](docs/gpu.md)):
87
+
88
+ ```bash
89
+ pixi run -e gpu python scripts/gpu_check.py # on an NVIDIA box
90
+ ```
91
+
92
+ As a dependency in another project (runtime deps are just `jax` + `numpy`):
93
+
94
+ ```bash
95
+ pip install jaxht # once released on PyPI — then `import jht`
96
+ # or track the repo directly:
97
+ pip install "jaxht @ git+https://github.com/jrcheshire/jht.git"
98
+ ```
99
+
100
+ ## Quick start
101
+
102
+ float64 is **opt-in per entry point** — enable it *before creating any array*
103
+ (library code never touches the global config):
104
+
105
+ ```python
106
+ import jax
107
+ jax.config.update("jax_enable_x64", True)
108
+ import jax.numpy as jnp
109
+ import jht
110
+
111
+ nside, lmax, spin = 256, 512, 0
112
+ m = jht.synthesis(alm, nside, lmax, spin=spin) # aₗₘ -> map
113
+ a = jht.map2alm(m, nside, lmax, spin=spin, niter=3) # map -> aₗₘ (weighted + iterated)
114
+ cl = jht.bandpower(a, lmax, spin=spin) # angular auto-power C_ℓ
115
+ ```
116
+
117
+ `spin=2` takes/returns `(E, B)` aₗₘ of shape `(2, …)` and `(Q, U)` maps of shape
118
+ `(2, npix)`. `jht.adjoint_synthesis` is the **exact unweighted transpose** `Sᵀ`
119
+ (the operator seam / VJP), distinct from `map2alm` (the approximate inverse). For
120
+ gradient-based work use the real-DOF layer `jht.synthesis_real` /
121
+ `jht.analysis_real` (plain ℝⁿ→ℝᵐ, no complex-conjugate convention subtlety).
122
+
123
+ ## Conventions
124
+
125
+ healpy m-major triangular aₗₘ packing, orthonormal Yₗₘ with the Condon–Shortley
126
+ phase, HEALPix-internal (COSMO) polarization — verified against healpy 1.19.0 and
127
+ ducc0 0.41.0. Pinned in [`docs/design.md`](docs/design.md).
128
+
129
+ ## Accuracy tiers (the contract)
130
+
131
+ jht targets the **GPU/differentiable tier** where the HEALPix ~1e-3 sampling
132
+ floor is acceptable; weights + iteration close it to ~1e-13 on band-limited
133
+ inputs. It is **not** a drop-in for ducc's purity-critical (~1e-4 E→B-leakage)
134
+ production path. Tolerances are a-priori and gate-driven, never relaxed without
135
+ sign-off. Residual mismatches are logged in
136
+ [`DISCREPANCIES.md`](DISCREPANCIES.md).
137
+
138
+ ## Performance
139
+
140
+ Pure JAX runs unchanged on CUDA. Measured on Cannon A100 (incl. a 20 GB MIG) / V100, fp64:
141
+
142
+ - **GPU==CPU parity ~1e-13** across the BK regime, **including nside=2048** (synthesis and `map2alm`).
143
+ - **Forward synthesis 14–60×** the 8-core CPU; fp64/fp32 ≈ 2.2×.
144
+ - **Off-grid forward** ~0.5–0.9 s at ℓ_max=1000 (independent of the number of points; recursion-bound), with the pointing gradient ~1× a forward.
145
+ - **nside=2048** compiles and runs on GPU — a ~20 GB slice holds synthesis + `map2alm`; the one-time compile is multi-minute (jit-cached).
146
+
147
+ The recurring GPU lesson: fp64/complex scatters are catastrophic on GPU, so jht packs and assembles via **gathers**. CPU perf model + memory in [`docs/performance.md`](docs/performance.md); GPU detail in [`docs/gpu.md`](docs/gpu.md).
148
+
149
+ ## Using jht as a dependency
150
+
151
+ jht is standalone and consumer-agnostic. The operator/grad seam a downstream
152
+ needs (e.g. to use jht *in place of ducc0*) — and the accuracy boundary — are
153
+ documented in [`docs/consumers.md`](docs/consumers.md). Any backend-selection
154
+ wiring lives in the consumer, not here.
155
+
156
+ ## Docs
157
+
158
+ - [`docs/design.md`](docs/design.md) — technical design, conventions, the crux, differentiability.
159
+ - [`docs/accuracy.md`](docs/accuracy.md) — the accuracy contract + ring-weight algorithm.
160
+ - [`docs/masked.md`](docs/masked.md) — partial-sky estimators.
161
+ - [`docs/performance.md`](docs/performance.md) — CPU perf model + memory.
162
+ - [`docs/gpu.md`](docs/gpu.md) — the GPU env, the x64 requirement, the harness.
163
+ - [`docs/consumers.md`](docs/consumers.md) — the downstream-dependency seam.
164
+ - [`docs/motivation.md`](docs/motivation.md) — why jht exists.
165
+ - [`ROADMAP.md`](ROADMAP.md) — phased plan + gates.