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.
- solvephase-0.1.0/.gitignore +22 -0
- solvephase-0.1.0/AGENTS.md +216 -0
- solvephase-0.1.0/CHANGELOG.md +140 -0
- solvephase-0.1.0/CONTRIBUTING.md +54 -0
- solvephase-0.1.0/LICENSE +21 -0
- solvephase-0.1.0/PKG-INFO +198 -0
- solvephase-0.1.0/README.md +133 -0
- solvephase-0.1.0/ROADMAP.md +49 -0
- solvephase-0.1.0/benchmarks/artifacts/v0.1.0-compare-i7-10700-p620.json +95 -0
- solvephase-0.1.0/benchmarks/artifacts/v0.1.0-i7-10700-p620.json +801 -0
- solvephase-0.1.0/benchmarks/artifacts/v0.1.0-methods-i7-10700-p620.json +286 -0
- solvephase-0.1.0/benchmarks/bench_algorithms.py +178 -0
- solvephase-0.1.0/benchmarks/compare.py +286 -0
- solvephase-0.1.0/benchmarks/methods.py +723 -0
- solvephase-0.1.0/benchmarks/render_table.py +146 -0
- solvephase-0.1.0/benchmarks/run.py +265 -0
- solvephase-0.1.0/docs/api.md +68 -0
- solvephase-0.1.0/docs/benchmarks.md +165 -0
- solvephase-0.1.0/docs/changelog.md +1 -0
- solvephase-0.1.0/docs/choosing.md +129 -0
- solvephase-0.1.0/docs/concepts.md +71 -0
- solvephase-0.1.0/docs/contributing.md +1 -0
- solvephase-0.1.0/docs/conventions.md +59 -0
- solvephase-0.1.0/docs/examples.md +23 -0
- solvephase-0.1.0/docs/guide/ao-wavefront-sensing.md +296 -0
- solvephase-0.1.0/docs/guide/cdi.md +271 -0
- solvephase-0.1.0/docs/guide/focal-plane.md +150 -0
- solvephase-0.1.0/docs/guide/generic.md +352 -0
- solvephase-0.1.0/docs/guide/interop.md +76 -0
- solvephase-0.1.0/docs/guide/performance.md +65 -0
- solvephase-0.1.0/docs/guide/phase-diversity.md +279 -0
- solvephase-0.1.0/docs/guide/pupils-and-bases.md +58 -0
- solvephase-0.1.0/docs/guide/tie.md +263 -0
- solvephase-0.1.0/docs/index.md +65 -0
- solvephase-0.1.0/docs/installation.md +35 -0
- solvephase-0.1.0/docs/javascripts/mathjax.js +16 -0
- solvephase-0.1.0/docs/quickstart.md +121 -0
- solvephase-0.1.0/docs/roadmap.md +1 -0
- solvephase-0.1.0/docs/validation.md +54 -0
- solvephase-0.1.0/examples/01_ncpa_calibration.py +80 -0
- solvephase-0.1.0/examples/02_broadband_undersampled.py +43 -0
- solvephase-0.1.0/examples/03_segment_phasing.py +54 -0
- solvephase-0.1.0/examples/04_extended_scene_diversity.py +58 -0
- solvephase-0.1.0/examples/05_focal_plane_wfs.py +43 -0
- solvephase-0.1.0/examples/06_cdi_multistart.py +47 -0
- solvephase-0.1.0/examples/07_generic_coded_diffraction.py +44 -0
- solvephase-0.1.0/examples/08_tie_microscopy.py +46 -0
- solvephase-0.1.0/examples/_common.py +26 -0
- solvephase-0.1.0/examples/showcase.py +224 -0
- solvephase-0.1.0/examples/solvephase_showcase.webp +0 -0
- solvephase-0.1.0/mkdocs.yml +95 -0
- solvephase-0.1.0/pyproject.toml +166 -0
- solvephase-0.1.0/src/solvephase/__about__.py +3 -0
- solvephase-0.1.0/src/solvephase/__init__.py +110 -0
- solvephase-0.1.0/src/solvephase/algorithms/__init__.py +4 -0
- solvephase-0.1.0/src/solvephase/algorithms/cdi.py +967 -0
- solvephase-0.1.0/src/solvephase/algorithms/fast_furious.py +494 -0
- solvephase-0.1.0/src/solvephase/algorithms/gerchberg_saxton.py +255 -0
- solvephase-0.1.0/src/solvephase/algorithms/lift.py +553 -0
- solvephase-0.1.0/src/solvephase/algorithms/phase_diversity.py +658 -0
- solvephase-0.1.0/src/solvephase/algorithms/tie.py +693 -0
- solvephase-0.1.0/src/solvephase/algorithms/wirtinger.py +865 -0
- solvephase-0.1.0/src/solvephase/api.py +228 -0
- solvephase-0.1.0/src/solvephase/backend.py +24 -0
- solvephase-0.1.0/src/solvephase/basis.py +516 -0
- solvephase-0.1.0/src/solvephase/cli.py +131 -0
- solvephase-0.1.0/src/solvephase/focal.py +367 -0
- solvephase-0.1.0/src/solvephase/interop.py +173 -0
- solvephase-0.1.0/src/solvephase/losses.py +169 -0
- solvephase-0.1.0/src/solvephase/metrics.py +5 -0
- solvephase-0.1.0/src/solvephase/operators.py +391 -0
- solvephase-0.1.0/src/solvephase/optimize.py +404 -0
- solvephase-0.1.0/src/solvephase/propagation.py +26 -0
- solvephase-0.1.0/src/solvephase/pupil.py +5 -0
- solvephase-0.1.0/src/solvephase/py.typed +0 -0
- solvephase-0.1.0/src/solvephase/result.py +228 -0
- solvephase-0.1.0/src/solvephase/retrieval.py +627 -0
- solvephase-0.1.0/src/solvephase/simulate.py +113 -0
- solvephase-0.1.0/src/solvephase/unwrap.py +5 -0
- solvephase-0.1.0/tests/conftest.py +34 -0
- solvephase-0.1.0/tests/test_api.py +85 -0
- solvephase-0.1.0/tests/test_cdi.py +385 -0
- solvephase-0.1.0/tests/test_cli.py +78 -0
- solvephase-0.1.0/tests/test_conformance.py +36 -0
- solvephase-0.1.0/tests/test_docs.py +36 -0
- solvephase-0.1.0/tests/test_fast_furious.py +182 -0
- solvephase-0.1.0/tests/test_focal.py +106 -0
- solvephase-0.1.0/tests/test_gs_unwrap_metrics.py +84 -0
- solvephase-0.1.0/tests/test_interop.py +68 -0
- solvephase-0.1.0/tests/test_lift.py +223 -0
- solvephase-0.1.0/tests/test_losses_optimize.py +86 -0
- solvephase-0.1.0/tests/test_operators.py +161 -0
- solvephase-0.1.0/tests/test_phase_diversity.py +308 -0
- solvephase-0.1.0/tests/test_pupil_basis.py +122 -0
- solvephase-0.1.0/tests/test_retrieval.py +150 -0
- solvephase-0.1.0/tests/test_tie.py +435 -0
- solvephase-0.1.0/tests/test_wirtinger.py +293 -0
- solvephase-0.1.0/validation/artifacts/validation.json +134 -0
- solvephase-0.1.0/validation/artifacts/validation.png +0 -0
- solvephase-0.1.0/validation/extensions.py +154 -0
- 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.
|
solvephase-0.1.0/LICENSE
ADDED
|
@@ -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
|
+
[](https://github.com/jacotay7/solvephase/actions/workflows/ci.yml)
|
|
69
|
+
[](https://pypi.org/project/solvephase/)
|
|
70
|
+
[](https://pypi.org/project/solvephase/)
|
|
71
|
+
[](https://jacotay7.github.io/solvephase/)
|
|
72
|
+
[](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 & 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).
|