solvephase 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 (101) hide show
  1. solvephase-0.1.0/.gitignore +22 -0
  2. solvephase-0.1.0/AGENTS.md +216 -0
  3. solvephase-0.1.0/CHANGELOG.md +140 -0
  4. solvephase-0.1.0/CONTRIBUTING.md +54 -0
  5. solvephase-0.1.0/LICENSE +21 -0
  6. solvephase-0.1.0/PKG-INFO +198 -0
  7. solvephase-0.1.0/README.md +133 -0
  8. solvephase-0.1.0/ROADMAP.md +49 -0
  9. solvephase-0.1.0/benchmarks/artifacts/v0.1.0-compare-i7-10700-p620.json +95 -0
  10. solvephase-0.1.0/benchmarks/artifacts/v0.1.0-i7-10700-p620.json +801 -0
  11. solvephase-0.1.0/benchmarks/artifacts/v0.1.0-methods-i7-10700-p620.json +286 -0
  12. solvephase-0.1.0/benchmarks/bench_algorithms.py +178 -0
  13. solvephase-0.1.0/benchmarks/compare.py +286 -0
  14. solvephase-0.1.0/benchmarks/methods.py +723 -0
  15. solvephase-0.1.0/benchmarks/render_table.py +146 -0
  16. solvephase-0.1.0/benchmarks/run.py +265 -0
  17. solvephase-0.1.0/docs/api.md +68 -0
  18. solvephase-0.1.0/docs/benchmarks.md +165 -0
  19. solvephase-0.1.0/docs/changelog.md +1 -0
  20. solvephase-0.1.0/docs/choosing.md +129 -0
  21. solvephase-0.1.0/docs/concepts.md +71 -0
  22. solvephase-0.1.0/docs/contributing.md +1 -0
  23. solvephase-0.1.0/docs/conventions.md +59 -0
  24. solvephase-0.1.0/docs/examples.md +23 -0
  25. solvephase-0.1.0/docs/guide/ao-wavefront-sensing.md +296 -0
  26. solvephase-0.1.0/docs/guide/cdi.md +271 -0
  27. solvephase-0.1.0/docs/guide/focal-plane.md +150 -0
  28. solvephase-0.1.0/docs/guide/generic.md +352 -0
  29. solvephase-0.1.0/docs/guide/interop.md +76 -0
  30. solvephase-0.1.0/docs/guide/performance.md +65 -0
  31. solvephase-0.1.0/docs/guide/phase-diversity.md +279 -0
  32. solvephase-0.1.0/docs/guide/pupils-and-bases.md +58 -0
  33. solvephase-0.1.0/docs/guide/tie.md +263 -0
  34. solvephase-0.1.0/docs/index.md +65 -0
  35. solvephase-0.1.0/docs/installation.md +35 -0
  36. solvephase-0.1.0/docs/javascripts/mathjax.js +16 -0
  37. solvephase-0.1.0/docs/quickstart.md +121 -0
  38. solvephase-0.1.0/docs/roadmap.md +1 -0
  39. solvephase-0.1.0/docs/validation.md +54 -0
  40. solvephase-0.1.0/examples/01_ncpa_calibration.py +80 -0
  41. solvephase-0.1.0/examples/02_broadband_undersampled.py +43 -0
  42. solvephase-0.1.0/examples/03_segment_phasing.py +54 -0
  43. solvephase-0.1.0/examples/04_extended_scene_diversity.py +58 -0
  44. solvephase-0.1.0/examples/05_focal_plane_wfs.py +43 -0
  45. solvephase-0.1.0/examples/06_cdi_multistart.py +47 -0
  46. solvephase-0.1.0/examples/07_generic_coded_diffraction.py +44 -0
  47. solvephase-0.1.0/examples/08_tie_microscopy.py +46 -0
  48. solvephase-0.1.0/examples/_common.py +26 -0
  49. solvephase-0.1.0/examples/showcase.py +224 -0
  50. solvephase-0.1.0/examples/solvephase_showcase.webp +0 -0
  51. solvephase-0.1.0/mkdocs.yml +95 -0
  52. solvephase-0.1.0/pyproject.toml +166 -0
  53. solvephase-0.1.0/src/solvephase/__about__.py +3 -0
  54. solvephase-0.1.0/src/solvephase/__init__.py +110 -0
  55. solvephase-0.1.0/src/solvephase/algorithms/__init__.py +4 -0
  56. solvephase-0.1.0/src/solvephase/algorithms/cdi.py +967 -0
  57. solvephase-0.1.0/src/solvephase/algorithms/fast_furious.py +494 -0
  58. solvephase-0.1.0/src/solvephase/algorithms/gerchberg_saxton.py +255 -0
  59. solvephase-0.1.0/src/solvephase/algorithms/lift.py +553 -0
  60. solvephase-0.1.0/src/solvephase/algorithms/phase_diversity.py +658 -0
  61. solvephase-0.1.0/src/solvephase/algorithms/tie.py +693 -0
  62. solvephase-0.1.0/src/solvephase/algorithms/wirtinger.py +865 -0
  63. solvephase-0.1.0/src/solvephase/api.py +228 -0
  64. solvephase-0.1.0/src/solvephase/backend.py +24 -0
  65. solvephase-0.1.0/src/solvephase/basis.py +516 -0
  66. solvephase-0.1.0/src/solvephase/cli.py +131 -0
  67. solvephase-0.1.0/src/solvephase/focal.py +367 -0
  68. solvephase-0.1.0/src/solvephase/interop.py +173 -0
  69. solvephase-0.1.0/src/solvephase/losses.py +169 -0
  70. solvephase-0.1.0/src/solvephase/metrics.py +5 -0
  71. solvephase-0.1.0/src/solvephase/operators.py +391 -0
  72. solvephase-0.1.0/src/solvephase/optimize.py +404 -0
  73. solvephase-0.1.0/src/solvephase/propagation.py +26 -0
  74. solvephase-0.1.0/src/solvephase/pupil.py +5 -0
  75. solvephase-0.1.0/src/solvephase/py.typed +0 -0
  76. solvephase-0.1.0/src/solvephase/result.py +228 -0
  77. solvephase-0.1.0/src/solvephase/retrieval.py +627 -0
  78. solvephase-0.1.0/src/solvephase/simulate.py +113 -0
  79. solvephase-0.1.0/src/solvephase/unwrap.py +5 -0
  80. solvephase-0.1.0/tests/conftest.py +34 -0
  81. solvephase-0.1.0/tests/test_api.py +85 -0
  82. solvephase-0.1.0/tests/test_cdi.py +385 -0
  83. solvephase-0.1.0/tests/test_cli.py +78 -0
  84. solvephase-0.1.0/tests/test_conformance.py +36 -0
  85. solvephase-0.1.0/tests/test_docs.py +36 -0
  86. solvephase-0.1.0/tests/test_fast_furious.py +182 -0
  87. solvephase-0.1.0/tests/test_focal.py +106 -0
  88. solvephase-0.1.0/tests/test_gs_unwrap_metrics.py +84 -0
  89. solvephase-0.1.0/tests/test_interop.py +68 -0
  90. solvephase-0.1.0/tests/test_lift.py +223 -0
  91. solvephase-0.1.0/tests/test_losses_optimize.py +86 -0
  92. solvephase-0.1.0/tests/test_operators.py +161 -0
  93. solvephase-0.1.0/tests/test_phase_diversity.py +308 -0
  94. solvephase-0.1.0/tests/test_pupil_basis.py +122 -0
  95. solvephase-0.1.0/tests/test_retrieval.py +150 -0
  96. solvephase-0.1.0/tests/test_tie.py +435 -0
  97. solvephase-0.1.0/tests/test_wirtinger.py +293 -0
  98. solvephase-0.1.0/validation/artifacts/validation.json +134 -0
  99. solvephase-0.1.0/validation/artifacts/validation.png +0 -0
  100. solvephase-0.1.0/validation/extensions.py +154 -0
  101. solvephase-0.1.0/validation/validate.py +312 -0
@@ -0,0 +1,22 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ site/
7
+ .venv/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ .coverage.*
13
+ coverage.xml
14
+ htmlcov/
15
+ .ipynb_checkpoints/
16
+ validation-artifacts/
17
+ benchmark-smoke.json
18
+ *.npz
19
+ .vscode/
20
+ .idea/
21
+ example-output/
22
+ solvephase-result.npz
@@ -0,0 +1,216 @@
1
+ # AGENTS.md
2
+
3
+ Operating guide for AI agents (and humans) working in `solvephase`. Keep it
4
+ accurate: update it in the same change that alters the layout, commands,
5
+ contracts or conventions below, and add a Gotchas entry whenever something led
6
+ you astray.
7
+
8
+ ## What this repository is
9
+
10
+ `solvephase` is a phase-retrieval library: given intensity measurements, it
11
+ recovers the phase (wavefront) that produced them. It covers
12
+
13
+ - **focal-plane wavefront sensing** for adaptive optics and optical metrology:
14
+ Gerchberg-Saxton/Misell, nonlinear (gradient / Gauss-Newton) retrieval with
15
+ Gaussian, Poisson or amplitude likelihoods, phase diversity (point source
16
+ and extended object), LIFT, Fast & Furious;
17
+ - **coherent diffraction imaging**: ER, HIO, DM, RAAR, RRR, ASR, HPR, OSS,
18
+ shrinkwrap, batched multi-start;
19
+ - **generic measurement models**: Wirtinger flow family with spectral
20
+ initialization;
21
+ - **transport of intensity** (TIE).
22
+
23
+ It runs on NumPy/SciPy, or on CUDA through CuPy with the same code.
24
+
25
+ The generic optics primitives (backend, propagators, pupils, metrics,
26
+ unwrapping) and the cross-package conventions live in
27
+ [`aocore`](https://github.com/jacotay7/aocore): change them there, not here,
28
+ and keep `tests/test_conformance.py` passing (aocore CONVENTIONS.md).
29
+
30
+ It belongs to an AO simulation family and reuses rather than copies it:
31
+ [`aobasis`](https://github.com/jacotay7/aobasis) supplies modal bases (a core
32
+ dependency); [`pyturb`](https://github.com/jacotay7/pyturb) (atmospheric OPD)
33
+ and [`getframes`](https://github.com/jacotay7/getframes) (detector noise) are
34
+ optional `interop` dependencies used for realistic data; HCIPy is an optional
35
+ independent reference for validation only. Never hard-code a filesystem path
36
+ to a sibling repository.
37
+
38
+ ## Layout
39
+
40
+ ```text
41
+ src/solvephase/
42
+ backend.py re-exports aocore.backend (Backend, get_backend, to_numpy, ...)
43
+ propagation.py re-exports aocore.propagation (FFT/MFT/focal/angular-spectrum propagators)
44
+ pupil.py re-exports aocore.pupil (Pupil)
45
+ basis.py Basis: Zernike/KL/Fourier (via aobasis), zonal, segments, DM
46
+ focal.py FocalPlaneModel: forward, vjp (reverse mode), jvp (forward mode)
47
+ losses.py Gaussian, Poisson (deviance), amplitude losses with curvature
48
+ optimize.py device-resident L-BFGS (strong Wolfe), Levenberg-Marquardt, Adam
49
+ retrieval.py FocalPlaneProblem + solve(): the nonlinear focal-plane solver
50
+ unwrap.py re-exports aocore.unwrap (unwrap_phase, wrap)
51
+ metrics.py re-exports aocore.metrics (rms, wavefront_error, strehl_from_rms)
52
+ result.py Result returned by every focal-plane solver
53
+ algorithms/ one module per algorithm family (gerchberg_saxton, cdi, ...)
54
+ api.py retrieve(): the one-call high-level entry point
55
+ tests/ pytest; mirrors the module names
56
+ benchmarks/ speed suite (run.py), head-to-head baselines (compare.py), method
57
+ comparison feeding docs/choosing.md (methods.py), JSON artifacts
58
+ validation/ physics/statistics evidence (Cramer-Rao, independent references)
59
+ docs/ mkdocs-material site
60
+ examples/ headless, deterministic scripts
61
+ ```
62
+
63
+ ## Quality gate (run before handing off)
64
+
65
+ ```bash
66
+ ruff check .
67
+ ruff format --check .
68
+ python -m mypy
69
+ python -m pytest -q --cov=solvephase --cov-report=term-missing
70
+ python -m pytest -q --run-slow -m slow # convergence/statistics tests
71
+ python -m pytest -q --run-gpu -m gpu # needs CuPy + a CUDA device
72
+ mkdocs build --strict
73
+ python -m build
74
+ ```
75
+
76
+ CI has no GPU, so `--run-gpu` locally is the only check of the CuPy paths;
77
+ note the GPU and CuPy version when you report GPU results.
78
+
79
+ ## Conventions (stable contracts)
80
+
81
+ - **Arrays are `(y, x)`**; coordinates sit on pixel centres, centred:
82
+ `(i - (n - 1) / 2) * pitch` (`propagation.centered_coordinates`). The image
83
+ of an on-axis point source is centred in the window (between pixels for
84
+ even sizes).
85
+ - **Sign:** Fraunhofer kernel `exp(-2 pi i x.alpha / lambda)`; a pupil OPD ramp
86
+ `a * x` moves the image to angle `+a` (towards larger column index). Any
87
+ sign/transpose fix needs an analytic ramp test, never trial and error.
88
+ - **Units:** SI. OPD in metres is the user-facing wavefront; modal
89
+ coefficients are RMS OPD in metres (modes are unit RMS). Internally,
90
+ solvers work in radians at the reference wavelength. Pixel scales are
91
+ radians, or `sampling` in pixels per lambda/D (2 = Nyquist).
92
+ - **Normalization:** propagators are unitary (Parseval holds when the window
93
+ captures all light). `FocalPlaneModel` images sum to 1 per channel for an
94
+ unbounded detector; a finite window loses light and is never renormalized.
95
+ - **Adjoints are exact.** Every linear operator ships `forward` and
96
+ `adjoint`, and has an adjoint test `<A x, y> == <x, A^H y>`. Every analytic
97
+ gradient has a finite-difference test.
98
+ - **Backends:** write numerical code against `backend.xp` and `Backend`
99
+ methods (`fft2`, `asarray`, `to_numpy`, ...). Never call NumPy allocation or
100
+ FFT directly in a hot path. Host transfers happen only at named boundaries
101
+ (`to_numpy`, scalar convergence checks every `check_every` iterations).
102
+ GPU results stay on the GPU.
103
+ - **Precision:** double on CPU and single on GPU by default; float32 and
104
+ float64 must both work. Don't silently promote float32 hot paths to float64.
105
+ - **Randomness:** draw on the host from a `numpy.random.Generator`
106
+ (`Backend.random(seed)`) and move to the device, so one seed gives the same
107
+ starts on CPU and GPU. Never use global `np.random` state.
108
+ - **Results:** focal-plane solvers return `result.Result`; other families
109
+ return `Result` too when they produce a pupil wavefront, otherwise a small
110
+ dataclass documented in their module. Every result records `history`,
111
+ `n_iter`, `converged`, `message` and `elapsed`.
112
+ - **Public API:** export from `solvephase/__init__.py` only what users need;
113
+ internal helpers start with `_`.
114
+
115
+ ## Writing code here
116
+
117
+ - Start from a primary reference; cite it in the module docstring and the
118
+ docs page.
119
+ - A test that only checks "it ran" is not a test. Assert recovery accuracy
120
+ against a known truth, adjointness, gradients, invariants (piston, flux,
121
+ shift), or statistics against theory with ensemble tolerances.
122
+ - Mark tests over ~2 s `@pytest.mark.slow`, GPU tests `@pytest.mark.gpu`
123
+ (parametrize devices with `tests/conftest.py::devices()`).
124
+ - Public functions have type hints and NumPy-style docstrings with units.
125
+ - Error messages say why, and what to do instead.
126
+ - Comments and docstrings describe current behaviour; history goes in
127
+ `CHANGELOG.md` (under Unreleased).
128
+ - Every user-visible feature lands with its docs page section and a
129
+ CHANGELOG entry.
130
+ - Docs code fenced as ` ```python ` is executed by `tests/test_docs.py`
131
+ (each page's blocks in order, as one script). Fence illustrative fragments
132
+ that cannot run alone as ` ```py `.
133
+
134
+ ## Maintainer rules
135
+
136
+ - Minor issues worked around rather than fixed get a GitHub issue in the
137
+ affected repository (when credentials allow), linked from a comment at the
138
+ workaround. Bugs in the maintainer's own repos (aobasis, pyturb, getframes,
139
+ makewfs) are fixed at the source, not worked around here. Never file issues
140
+ on third-party repositories; record them in a solvephase issue instead.
141
+ - No planning or status-tracker files in the repo (ROADMAP.md is the one
142
+ forward-looking document).
143
+ - Everything must be usable under the MIT license: never copy code from
144
+ GPL/LGPL/non-commercial/CeCILL sources, and never add patent-encumbered
145
+ algorithms. PIE-family ptychography (Phase Focus patents) is excluded on
146
+ these grounds; other ptychography only if it is clearly unencumbered.
147
+
148
+ ## Gotchas
149
+
150
+ - `aobasis` modes are columns of an `(n_points, n_modes)` matrix;
151
+ `Basis.modes` stores the transpose `(n_modes, n_valid)`.
152
+ - aobasis warns when points lie outside `pupil_radius`; anti-aliased rim
153
+ pixels always do, so `Basis.zernike` silences that warning on purpose.
154
+ - The Poisson loss is evaluated as a deviance (non-negative). The raw
155
+ negative log-likelihood carries a huge data constant that made relative
156
+ `ftol` stops fire after a few iterations.
157
+ - Unweighted Gaussian least squares on raw PSFs is badly conditioned for
158
+ zonal retrieval (the core dominates); prefer Poisson or amplitude losses.
159
+ - Spider vanes split a pupil into disconnected regions; `unwrap_phase`
160
+ bridges their 2 pi multiples with a smooth-surface fit (`bridge=True`).
161
+ - The angular-spectrum propagator removes evanescent waves; with a pitch
162
+ below the wavelength most of the spectrum is evanescent and the field is
163
+ not preserved at zero distance.
164
+ - `offset` on `FocalPlaneModel`/propagators shifts the detector *window*:
165
+ pixel `j` samples angle `(j - (m-1)/2 + offset) * pixel_scale`, so the
166
+ optical axis lands on pixel `(m-1)/2 - offset`. `offset=-0.5` reproduces
167
+ HCIPy / `fftshift` centring.
168
+ - CPU threading traps: threaded OpenBLAS level-1 calls (`np.vdot`,
169
+ `np.dot`, `np.linalg.norm` on >10k elements) stall ~1 ms each when the
170
+ cores are busy - use `Backend.dot`; and many-thread SciPy FFTs are slower
171
+ than one thread for small transforms - `backend._cpu_workers(size)` scales
172
+ the thread count with the transform size.
173
+ - Float32 losses: evaluate data terms and accumulate normal equations in
174
+ float64 (`FocalPlaneProblem` does). Float32 loss values are too noisy for
175
+ line-search and LM acceptance and stall solvers above ~1e5 counts/pixel.
176
+ - The Poisson and amplitude losses continue quadratically below a small
177
+ positive floor; clamping instead gave gradients inconsistent with the
178
+ value whenever a line search probed negative model values.
179
+ - Background parameters are scaled by robust border noise, not the image
180
+ standard deviation (a bright PSF core inflates it by orders of magnitude).
181
+ - Simulated data must exercise what you fit: tests once failed because data
182
+ had a background the problem did not fit, or modes the basis lacked.
183
+ - Extended-scene phase diversity needs compact scenes (blurred scene inside
184
+ the field); apodization subtracts the edge level, not the mean; the
185
+ reduced metric's regularization must be tiny (~1/peak SNR^2).
186
+ - CDI: a square or symmetric support invites twin/translation stagnation in
187
+ single-start tests (use irregular outlines or several starts); RAAR needs
188
+ beta close to 1 on noise-free data.
189
+ - TIE: masked/Neumann CG in float32 must project the per-region constant out
190
+ of the preconditioned residual and make right-hand sides consistent;
191
+ samples must vanish well inside the window (periodic propagation edges
192
+ otherwise corrupt dI/dz).
193
+ - LIFT: an astigmatism coefficient in the basis produces an exact twin
194
+ `a' = -a - 2d`; this is physics, not a solver bug (`LIFT` reports the
195
+ smaller one).
196
+ - Fast & Furious needs an integer FFT size and a centro-symmetric pupil, and
197
+ its first step must apply even diversity (`first_even=True`).
198
+ - OpenBLAS level-2/3 calls (`matmul`, gemv) on small operands stall like
199
+ level-1 under load (~3 ms vs 80 us for `np.einsum`); `MatrixOperator`
200
+ uses einsum on the CPU for small products.
201
+ - Octanary coded-diffraction spectral initializations need `diag(A^H A)`
202
+ whitening beyond ~1e4 pixels, otherwise the leading eigenvector locks onto
203
+ single pixels. Wirtinger flow with the paper's `mu_max = 0.2` diverges for
204
+ n >= 128 (default 0.1). Stop slowly contracting first-order methods on
205
+ residual stagnation, not on iterate change.
206
+ - Fourier magnitudes alone defeat the Wirtinger family (shift and twin
207
+ ambiguities); that model belongs to the CDI projection solvers.
208
+ - Always pass `encoding="utf-8"` to `read_text`/`write_text`/`open` for text:
209
+ Windows defaults to cp1252 and the docs contain non-ASCII characters.
210
+ - Chaotic projection algorithms (DM, RAAR, HIO) land in platform-dependent
211
+ places after a fixed number of iterations (FFT rounding differs across
212
+ OS/BLAS builds); assert robust reductions, not tight ratios.
213
+ - Tests import shared helpers as `from conftest import devices` (`tests/`
214
+ is not a package).
215
+ - GPU timings on a shared device (or CPU timings under load) vary 2-5x;
216
+ record the load and hardware with any performance claim.
@@ -0,0 +1,140 @@
1
+ # Changelog
2
+
3
+ All notable changes to `solvephase` are documented here. The project follows
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.1.0] - 2026-10-07
9
+
10
+ First release.
11
+
12
+ ### Focal-plane phase retrieval
13
+
14
+ - `FocalPlaneModel`: pupil-to-detector image formation for any number of
15
+ diversity channels.
16
+ - Broadband light (exact per-wavelength sampling).
17
+ - Any detector sampling, including undersampled, with pixel integration
18
+ (`oversample`).
19
+ - Reverse-mode (`vjp`) and forward-mode (`jvp`) derivatives, verified
20
+ against finite differences.
21
+ - Propagators with exact adjoints:
22
+ - zero-padded FFT;
23
+ - matrix Fourier transform, stacked over wavelengths;
24
+ - angular spectrum (Fresnel).
25
+
26
+ An automatic engine choice picks FFT or MFT per model. The model matches
27
+ HCIPy to machine precision.
28
+ - `FocalPlaneProblem` + `solve`: maximum-likelihood retrieval.
29
+ - Losses: Poisson deviance with read noise, weighted Gaussian, or the
30
+ amplitude metric.
31
+ - Nuisance fits: per-image flux, background and registration tilt, and an
32
+ optional zonal pupil amplitude.
33
+ - Regularization: Tikhonov, a KL-turbulence prior (MAP), and zonal
34
+ smoothness.
35
+ - Optimizers: Levenberg-Marquardt with an exact Gauss-Newton/Fisher
36
+ matrix, L-BFGS with a strong-Wolfe line search, or Adam. All are
37
+ device-resident.
38
+ - Single precision reaches the double-precision noise floor, because
39
+ likelihoods and normal equations accumulate in float64.
40
+ - `gerchberg_saxton`: Gerchberg-Saxton and the multi-plane Misell algorithm,
41
+ with exact modulus projection on the full FFT grid.
42
+ - `unwrap_phase`: weighted least-squares unwrapping over obscured, spidered
43
+ and segmented pupils (DCT-preconditioned CG), with 2π bridging of
44
+ disconnected regions.
45
+ - `retrieve`: one-call retrieval with a robust default strategy.
46
+ - Amplitude-metric Levenberg-Marquardt from a flat start and from
47
+ Gerchberg-Saxton; the better fit is kept.
48
+ - Then a Poisson maximum-likelihood polish and optional zonal refinement.
49
+
50
+ ### Algorithm families
51
+
52
+ - Phase diversity with an unknown extended object
53
+ (`phase_diversity`, `PhaseDiversityProblem`).
54
+ - Gonsalves/Paxman reduced metric with an exact gradient.
55
+ - Apodization and a diffraction-cutoff frequency mask.
56
+ - Automatic noise and regularization estimates.
57
+ - Wiener object estimate.
58
+ - LIFT (`lift`, `LIFT`, `lift_crlb`): maximum-likelihood low-order sensing
59
+ from one astigmatic image, with a twin check and Cramér-Rao bounds.
60
+ - Fast & Furious (`FastAndFurious`, `simulate_closed_loop`): sequential
61
+ weak-phase focal-plane sensing with the Korkiakoski et al. (2014)
62
+ modifications. It takes one FFT pair per step.
63
+ - Coherent diffraction imaging (`cdi`).
64
+ - Algorithms: ER, HIO, DM, RAAR, RRR, ASR/Douglas-Rachford, HPR and OSS.
65
+ - Constraints: real, positive, amplitude bounds; beamstop masks.
66
+ - Chainable schedules, shrinkwrap, and batched multi-start (one batched
67
+ FFT per iteration).
68
+ - Helpers: `autocorrelation_support`, `align_object` (ambiguity-aware) and
69
+ `simulate_cdi`.
70
+ - Generic measurement models (`solvephase.algorithms.wirtinger`,
71
+ `solvephase.operators`).
72
+ - Algorithms: Wirtinger flow, truncated Wirtinger flow, truncated and
73
+ reweighted amplitude flows, and an L-BFGS solver.
74
+ - Initializations: spectral, truncated, orthogonality-promoting and
75
+ optimal-preprocessing spectral.
76
+ - Operators: Gaussian matrix, coded diffraction and oversampled Fourier,
77
+ each with an exact adjoint.
78
+ - Transport of intensity (`tie`, `simulate_defocus_stack`).
79
+ - Solvers: FFT and DCT Poisson, uniform intensity and Teague's non-uniform
80
+ solution, and an exact masked conjugate-gradient solver.
81
+ - Axial derivative: central differences or a multi-plane polynomial fit.
82
+
83
+ ### Optics and integration
84
+
85
+ - `Pupil`: anti-aliased circular, annular and spidered pupils; segmented
86
+ hexagonal apertures with segment labels; Keck, JWST, VLT and Hubble
87
+ presets.
88
+ - `Basis`:
89
+ - Zernike (annular), KL (with prior variances), Fourier and any other
90
+ aobasis generator;
91
+ - segment piston/tip/tilt;
92
+ - Gaussian and measured DM influence functions;
93
+ - zonal.
94
+ - `solvephase.interop`: pyturb turbulence (`turbulence_opd`) and getframes
95
+ detector frames (`expose`). `expose` converts ADU to electrons and masks
96
+ saturated pixels.
97
+ - Ambiguity-aware metrics: `rms`, `wavefront_error` (with twin handling) and
98
+ `strehl_from_rms`.
99
+ - A `solvephase` command line with `info` and `retrieve`.
100
+ - CPU threading tuned for contended and hyper-threaded machines:
101
+ - FFT worker counts scale with the transform size.
102
+ - BLAS threads are limited, per call, for matrix Fourier transforms and
103
+ Gauss-Newton products (via threadpoolctl). This made broadband
104
+ gradients up to 10x faster.
105
+ - Scalar products avoid threaded level-1 BLAS.
106
+
107
+ ### Comparison
108
+
109
+ - `benchmarks/methods.py` runs every method on one reference problem. All
110
+ focal-plane methods see the same wavefront. It reports:
111
+ - the data each method needs;
112
+ - accuracy at a standard SNR;
113
+ - warm CPU and GPU time;
114
+ - capture range.
115
+
116
+ Its output feeds the comparison tables on the "Choosing an algorithm" docs
117
+ page, which also has a decision flowchart.
118
+ - The README showcase races every focal-plane method on one clock, with a
119
+ live error-versus-time chart, plus a gallery of CDI, coded diffraction and
120
+ TIE.
121
+ - The Keck preset now follows the documented geometry: 10.95 m across the
122
+ corners, pointy-top segments, six 26 mm support arms, and a 2.57 m central
123
+ obscuration.
124
+
125
+ ### Shared core
126
+
127
+ - The backend, pupils, propagators, wavefront metrics and phase unwrapping
128
+ now live in [aocore](https://github.com/jacotay7/aocore), the AO stack's
129
+ shared core, and solvephase re-exports them unchanged.
130
+ - `tests/test_conformance.py` runs aocore's convention checks against
131
+ solvephase: image centring, tilt direction, unit flux and the RMS
132
+ definition.
133
+
134
+ ### Quality
135
+
136
+ - More than 280 tests: adjoints, gradients, recovery accuracy, Cramér-Rao
137
+ efficiency, and CPU/GPU parity.
138
+ - A validation suite against theory and HCIPy.
139
+ - Benchmark and comparison suites with versioned artifacts.
140
+ - An mkdocs documentation site whose code examples are executed in CI.
@@ -0,0 +1,54 @@
1
+ # Contributing to solvephase
2
+
3
+ solvephase is a numerical optics library. A contribution is complete when its
4
+ behaviour, units, tests, documentation and performance implications are all
5
+ clear. Read [AGENTS.md](https://github.com/jacotay7/solvephase/blob/main/AGENTS.md)
6
+ for the conventions and contracts; they apply to human contributors too.
7
+
8
+ ## Development setup
9
+
10
+ ```bash
11
+ git clone https://github.com/jacotay7/solvephase
12
+ cd solvephase
13
+ python -m pip install -e ".[dev,docs,interop,validation]"
14
+ ```
15
+
16
+ For GPU work, also install the CuPy extra that matches your driver
17
+ (`.[cuda12]` or `.[cuda13]`).
18
+
19
+ ## Quality gate
20
+
21
+ ```bash
22
+ ruff check .
23
+ ruff format --check .
24
+ python -m mypy
25
+ python -m pytest -q --cov=solvephase
26
+ python -m pytest -q --run-slow -m slow
27
+ python -m pytest -q --run-gpu -m gpu # needs a CUDA device
28
+ python -m pytest -q -m interop # needs pyturb, getframes, hcipy
29
+ python validation/validate.py --quick --output /tmp/validation
30
+ python benchmarks/run.py --quick
31
+ mkdocs build --strict
32
+ python -m build
33
+ ```
34
+
35
+ ## What a change needs
36
+
37
+ - **New algorithm:** a module under `src/solvephase/algorithms/`, primary
38
+ references in its docstring, tests that assert recovery accuracy (not just
39
+ that it runs), a GPU parity test, a guide page, a benchmark case and a
40
+ CHANGELOG entry.
41
+ - **New operator:** an exact adjoint and an adjoint test.
42
+ - **New gradient:** a finite-difference test.
43
+ - **Performance change:** before/after numbers from `benchmarks/run.py`,
44
+ with the hardware.
45
+
46
+ ## Releases
47
+
48
+ 1. Bump `src/solvephase/__about__.py`.
49
+ 2. Date the CHANGELOG section.
50
+ 3. Merge to `main`.
51
+ 4. Publish a GitHub release whose tag is the bare version (e.g. `0.2.0`).
52
+
53
+ The release workflow builds the distributions and uploads them to PyPI with
54
+ trusted publishing.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jacob Taylor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.5
2
+ Name: solvephase
3
+ Version: 0.1.0
4
+ Summary: Fast, GPU-optional phase retrieval for adaptive optics, optical metrology and coherent imaging.
5
+ Project-URL: Homepage, https://github.com/jacotay7/solvephase
6
+ Project-URL: Documentation, https://jacotay7.github.io/solvephase/
7
+ Project-URL: Repository, https://github.com/jacotay7/solvephase
8
+ Project-URL: Issues, https://github.com/jacotay7/solvephase/issues
9
+ Project-URL: Changelog, https://github.com/jacotay7/solvephase/blob/main/CHANGELOG.md
10
+ Author-email: Jacob Taylor <jacobataylor7@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: adaptive-optics,coherent-diffraction-imaging,gerchberg-saxton,gpu,phase-diversity,phase-retrieval,wavefront-sensing
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.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
24
+ Classifier: Topic :: Scientific/Engineering :: Physics
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.10
27
+ Requires-Dist: aobasis>=2.0
28
+ Requires-Dist: aocore>=0.1
29
+ Requires-Dist: numpy>=1.23
30
+ Requires-Dist: scipy>=1.10
31
+ Requires-Dist: threadpoolctl>=3.0
32
+ Provides-Extra: cuda12
33
+ Requires-Dist: cupy-cuda12x[ctk]>=13.0; extra == 'cuda12'
34
+ Provides-Extra: cuda13
35
+ Requires-Dist: cupy-cuda13x[ctk]>=13.6; extra == 'cuda13'
36
+ Provides-Extra: dev
37
+ Requires-Dist: build>=1.0; extra == 'dev'
38
+ Requires-Dist: matplotlib>=3.7; extra == 'dev'
39
+ Requires-Dist: mypy>=1.8; extra == 'dev'
40
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
41
+ Requires-Dist: pytest-timeout>=2.0; extra == 'dev'
42
+ Requires-Dist: pytest>=7.0; extra == 'dev'
43
+ Requires-Dist: ruff>=0.6; extra == 'dev'
44
+ Provides-Extra: docs
45
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
46
+ Requires-Dist: mkdocs>=1.5; extra == 'docs'
47
+ Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
48
+ Provides-Extra: fits
49
+ Requires-Dist: astropy>=5.0; extra == 'fits'
50
+ Provides-Extra: gpu
51
+ Requires-Dist: cupy-cuda12x[ctk]>=13.0; extra == 'gpu'
52
+ Provides-Extra: interop
53
+ Requires-Dist: getframes>=2.2; extra == 'interop'
54
+ Requires-Dist: pyturb>=1.2; extra == 'interop'
55
+ Provides-Extra: plot
56
+ Requires-Dist: matplotlib>=3.7; extra == 'plot'
57
+ Provides-Extra: test
58
+ Requires-Dist: pytest-cov>=4.0; extra == 'test'
59
+ Requires-Dist: pytest-timeout>=2.0; extra == 'test'
60
+ Requires-Dist: pytest>=7.0; extra == 'test'
61
+ Provides-Extra: validation
62
+ Requires-Dist: hcipy>=0.6; extra == 'validation'
63
+ Requires-Dist: matplotlib>=3.7; extra == 'validation'
64
+ Description-Content-Type: text/markdown
65
+
66
+ # solvephase
67
+
68
+ [![CI](https://github.com/jacotay7/solvephase/actions/workflows/ci.yml/badge.svg)](https://github.com/jacotay7/solvephase/actions/workflows/ci.yml)
69
+ [![PyPI](https://img.shields.io/pypi/v/solvephase.svg)](https://pypi.org/project/solvephase/)
70
+ [![Python](https://img.shields.io/pypi/pyversions/solvephase.svg)](https://pypi.org/project/solvephase/)
71
+ [![Docs](https://img.shields.io/badge/docs-jacotay7.github.io%2Fsolvephase-indigo.svg)](https://jacotay7.github.io/solvephase/)
72
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
73
+
74
+ **Documentation: [jacotay7.github.io/solvephase](https://jacotay7.github.io/solvephase/)**
75
+
76
+ **Fast, GPU-optional phase retrieval for adaptive optics, optical metrology and
77
+ coherent imaging.**
78
+
79
+ <p align="center">
80
+ <img src="examples/solvephase_showcase.webp" width="900" alt="A race between every solvephase focal-plane method on the same aberrated VLT-like wavefront (Gerchberg-Saxton/Misell, modal and zonal maximum likelihood, retrieve(), extended-scene phase diversity, LIFT and Fast &amp; Furious) on one clock, with a live error-versus-time chart and a gallery of CDI, coded-diffraction and TIE reconstructions.">
81
+ </p>
82
+
83
+ `solvephase` recovers phase from intensity. It covers focal-plane wavefront
84
+ sensing (Gerchberg–Saxton/Misell, maximum-likelihood modal and zonal
85
+ retrieval, phase diversity with unknown extended objects, LIFT, Fast &
86
+ Furious), coherent diffraction imaging (ER, HIO, DM, RAAR, RRR, ASR, HPR,
87
+ OSS, shrinkwrap), generic measurement models (Wirtinger, truncated,
88
+ amplitude and reweighted flows) and the transport-of-intensity equation, all
89
+ behind one API. It runs on NumPy by default and on CUDA (CuPy) with one
90
+ argument.
91
+
92
+ ## Install
93
+
94
+ ```bash
95
+ pip install solvephase # CPU
96
+ pip install "solvephase[cuda12]" # + CuPy for CUDA 12.x
97
+ pip install "solvephase[cuda13]" # + CuPy for CUDA 13.x
98
+ pip install "solvephase[interop]" # + pyturb atmospheres and getframes detectors
99
+ ```
100
+
101
+ ## Quickstart
102
+
103
+ ```python
104
+ import solvephase as sp
105
+
106
+ pupil = sp.Pupil.vlt(128) # 8 m, obscured, four vanes
107
+ model = sp.FocalPlaneModel(
108
+ pupil, 1.65e-6, 64, sampling=2.0, diversity=sp.zernike_diversity(pupil, 4, [0.0, 0.4e-6])
109
+ )
110
+ truth = sp.random_aberration(pupil, 100e-9, n_modes=40, start=4, seed=1)
111
+ images = sp.simulate_images(model, truth, photons=1e6, background=10, read_noise=3, seed=2)
112
+
113
+ result = sp.retrieve(
114
+ images, pupil, 1.65e-6, sampling=2.0, diversity=[0.0, 0.4e-6], read_noise=3.0, device="auto"
115
+ )
116
+ print(result.summary()) # converged in a handful of LM steps
117
+ result.opd, result.coefficients # wavefront [m], Zernike [m RMS]
118
+ ```
119
+
120
+ `retrieve` is robust by default. It explores with Levenberg–Marquardt on the
121
+ amplitude metric from a flat start and from Gerchberg–Saxton, keeps the
122
+ better fit, and polishes with the Poisson likelihood. For full control, use
123
+ `FocalPlaneProblem` and `solve` (choose the basis, likelihood, nuisance
124
+ parameters and optimizer). The other families are one call each:
125
+
126
+ ```python
127
+ sp.phase_diversity(model, images_of_extended_scene) # unknown object
128
+ sp.LIFT(pupil, wavelength, 32, sampling=2.0).estimate(img) # one astigmatic image
129
+ sp.FastAndFurious(pupil, wavelength, 64, sampling=2.0).step(img, dm_change)
130
+ sp.cdi(magnitudes, support, schedule="hio:500,er:100", starts=16, device="gpu")
131
+ sp.tie(stack, [-dz, 0, dz], pitch=pitch, wavelength=wavelength)
132
+ ```
133
+
134
+ See **[Choosing an algorithm](https://jacotay7.github.io/solvephase/choosing/)**
135
+ for a side-by-side comparison: images needed, requirements, measured speed,
136
+ accuracy and capture range of every method.
137
+
138
+ ## Highlights
139
+
140
+ - **Exact derivatives everywhere.** Every propagator (FFT, matrix Fourier
141
+ transform, angular spectrum, coded diffraction) has an exact adjoint.
142
+ Gradients and Gauss–Newton Jacobians are analytic, with no autodiff tape.
143
+ Levenberg–Marquardt typically converges in 5–10 iterations.
144
+ - **Statistically efficient.** Poisson + read-noise likelihoods, fitted
145
+ flux, background and registration, masks for bad and saturated pixels.
146
+ The estimator reaches the Cramér–Rao bound in the validation suite.
147
+ - **Physically complete.** Broadband light, any sampling (including
148
+ undersampled detectors), pixel integration, segmented apertures, KL
149
+ priors, and DM influence-function bases.
150
+ - **Fast on CPU and GPU.** Batched FFTs, matrix Fourier transforms on BLAS,
151
+ batched multi-start CDI, and device-resident optimizers. Single precision
152
+ reaches the same noise floor as double.
153
+ - **Validated.** It matches HCIPy to machine precision and the analytic
154
+ Airy normalization, and the validation suite checks its Cramér–Rao
155
+ efficiency and capture range on every CI run.
156
+ - **Part of an AO toolchain.** Modal bases come from
157
+ [aobasis](https://github.com/jacotay7/aobasis), realistic aberrations from
158
+ [pyturb](https://github.com/jacotay7/pyturb), and detector frames from
159
+ [getframes](https://github.com/jacotay7/getframes).
160
+
161
+ ## Benchmarks
162
+
163
+ See the [benchmarks page](https://jacotay7.github.io/solvephase/benchmarks/)
164
+ and the versioned artifacts in [`benchmarks/artifacts`](benchmarks/artifacts).
165
+
166
+ Same problem, same data, on an Intel i7-10700 and an entry-level NVIDIA
167
+ Quadro P620 ([artifacts](benchmarks/artifacts)):
168
+
169
+ | Task | Baseline | solvephase CPU | solvephase GPU |
170
+ |---|---|---|---|
171
+ | Focal-plane retrieval, 128², 36 modes, 2 images (time to solution) | HCIPy + SciPy L-BFGS-B: 8.5 s; `least_squares`: 5.3 s | **0.46 s** | **0.14 s** |
172
+ | Misell/GS, 128², iterations/s | textbook NumPy: 156 | 268 | **1,493** |
173
+ | CDI HIO, 256², iterations/s | textbook NumPy: 252 | 794 | **2,957** (3,492 per start with 16 batched starts) |
174
+ | Broadband (5 λ) gradient, 256² | — | 67 ms | **9 ms** |
175
+ | TIE, 2048², 3 planes | — | 253 ms | **33 ms** |
176
+
177
+ On the focal-plane problem every method reaches 0.12–0.19 nm RMS; solvephase
178
+ gets there 11–18x faster on the CPU and 38–61x faster on the GPU.
179
+
180
+ ## Validation
181
+
182
+ `python validation/validate.py` checks solvephase against theory and an
183
+ independent code and writes a report; see the
184
+ [validation page](https://jacotay7.github.io/solvephase/validation/).
185
+
186
+ ## Development
187
+
188
+ ```bash
189
+ pip install -e ".[dev,docs,interop,validation]"
190
+ python -m pytest -q # fast suite
191
+ python -m pytest -q --run-slow --run-gpu # everything
192
+ ```
193
+
194
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and [AGENTS.md](AGENTS.md).
195
+
196
+ ## License
197
+
198
+ MIT; see [LICENSE](LICENSE).