Repository navigation
feat: route interferometer numba vs FFT curvature by a cost model - #632
Merged
Merged
Conversation
The numba direct_conv route gate was a fixed 60 non-zeros per source column,
calibrated at the autolens_profiling#226 extents (70^2-280^2). On the Aris
512x512 mask (extent 341^2) it sent 2,577 vertices (106 nnz/col) to the FFT
route at 44.1 s/call against 34.2 s for direct_conv.
The default routing is now a two-constant cost model over quantities known at
routing time: t_fft = c_fft * S * [rfft2+irfft2 work of the (2ny,2nx) padded
extent, pocketfft cost_guess incl. factorisation/Bluestein], t_dc = c_dc * nnz
* ny * nx. Constants fitted by least squares on log seconds to the 9 paired
timings (6 bake-off cells of autolens_profiling#226 + 3 Aris 512 points);
predicted faster route matches the measured one at all nine, ratio residuals
0.68-1.47.
Settings: interferometer_numba_nnz_per_source_max packaged default -> null
(cost model); positive value = hard gate (unchanged); 0 = kill switch
(unchanged). New interferometer_route ("numba" | "fft" | None) forces a route
for benchmarking. _use_interferometer_numba takes extent_shape, passed from
dataset.sparse_operator (y_shape, x_shape).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 15, 2026
Collaborator
Author
|
Workspace PR: PyAutoLabs/autolens_profiling#420 (phase-2 Aris routing record) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The NumPy single-mapper interferometer inversion chooses between the numba
direct_convcurvature route (InversionInterferometerSparseNumba) and the FFT route (InversionInterferometerSparse). Until now the factory used a fixed gate of 60 mean non-zeros per source column, calibrated on the autolens_profiling#226 geometries. On the Aris 512x512 mask (91,507 px, extent 341x341, FFT grid 682x682) the measured crossover is ~130-150, so at 2,577 vertices (106 nnz/col) the gate picked the FFT route: 44.1 s/call against 34.2 s for direct_conv.The default routing is now a two-constant cost model over quantities known at routing time (source pixels
S, total mapping non-zeros, the extent rectangle fromdataset.sparse_operator):t_fft = c_fft · S · [rfft2 + irfft2 work of the (2ny, 2nx) padded extent]. The transform work is scipy/pocketfft's own planner estimate (cost_guess: n × sum of prime factors, with the Bluestein switch), so awkward lengths cost what they really cost: 682 = 2·11·31 and 166 = 2·83 are 2-5x dearer per element than 560 = 2^4·5·7.t_dc = c_dc · nnz · ny · nx(the kernel scatters the W~ row over the whole extent once per non-zero; flat inSat fixed nnz, as measured: 34→41 s over 2,577→5,746 vertices).c_fft = 7.62e-10,c_dc = 1.77e-09: least squares on log seconds over 9 paired single-thread timings (6 docs: refactor docstrings for autoarray/mask package #226 bake-off cells + the 3 Aris 512 points). The predicted faster route matches the measured one at all nine. Measured/predictedt_fft/t_dcratio residuals are 0.68-1.47 (log-rms 0.26). Implied crossovers: ~51/58/58 nnz/col at the 70²/140²/280² bake-off extents (measured ~60 Delaunay / ~77 rect), ~166 at the Aris 512 extent (measured between 106 and 188).N log2 N.API Changes
Settings.interferometer_numba_nnz_per_source_max: the packaged default changes from60.0tonull, which means the cost model. A positive value is still a hard gate, so explicit A/B settings keep their meaning, and0is still the kill switch.Settings(interferometer_route=None | "numba" | "fft")(config keyinterferometer_route: null) forces a route for benchmarking."numba"overrides the gate and the kill switch but still requires an inversion the kernel can represent.factory._use_interferometer_numba(private) gains anextent_shapekwarg. New helperscurvature_route_seconds_from,fft_route_work_from,direct_conv_work_fromand_fft_length_costare ininversion_interferometer_numba_util.See full details below.
Test Plan
pytest test_autoarray -xin the worktree: 2055 passed, 4 xfailedtest_factory.py): every measured point (Aris 512 ×3, Aris 125 ×3, docs: refactor docstrings for autoarray/mask package #226 bake-off ×6) routes to the measured faster route under the default; crossover moves with extent; override semantics (hard gate, kill switch, forced route, missing extent); FFT-length cost penalises awkward lengths.jaxpackage that raisesModuleNotFoundErroron PYTHONPATH (60/60 in the three touched test files).pyauto-heart smoke --root <task worktree>: 165 curated scripts PASS / 0 FAIL across autofit, autogalaxy, autolens, autolens_test, euclid and howtolens (incl. autolensinterferometer/modeling.py,interferometer/features/pixelization/delaunay.py,datacube/modeling_array_free.py, autolens_testinterferometer/jax_likelihood/*,datacube/shared_preloads.py,misc/jax_assertions/fit_interferometer_sparse_operator.py) + 6 notebooks, no failures; autocti / autocti_test environments not built locally (GSL headers missing for arcticpy, environment-only, no interferometer code path)Witness
On both the Aris 125x125 (5,496 px) and 512x512 (91,507 px) masks, the default routing picks the faster of numba direct_conv vs NumPy FFT curvature at every tested vertex count (1.4k-5.7k), asserted by a routing test.
test__interferometer_route__default_cost_model_picks_the_measured_faster_routeasserts this. As a live check, the branch's factory was run on the original Aris datasets, eager, routing only (fit.inversionclass; nothing was re-timed because the machine is shared). Timings are from the #628 prompt table and PROGRESS3/4.--gate 60reproduces it)The predictions cover the curvature build only. The 125 measured times are whole likelihood calls, and the dense solve common to both routes dominates them at large
S.Decision taken
The FFT work term is pocketfft's
cost_guess(factorisation-aware), notN log2 N. The issue's plan sketchedextent·log2(extent). With that form no single constant ratio routes both the bake-off cells and the Aris 512 points correctly: fitted on all nine points it mis-routes Aris 512 at 2,577 vertices (the bug being fixed), and fitted on the 512 points alone it mis-routes two bake-off cells. The factorisation term explains the gap, because the Aris FFT lengths (682, 166) are awkward. The micro-benchmark above confirms it.N log2 Nform above, and a fixed per-mask-size table of gates. A table does not generalise to an unmeasured extent and needs re-measuring per mask.interferometer_numba_nnz_per_source_max: 60.0inautoarray/config/general.yaml(or per run,Settings(interferometer_numba_nnz_per_source_max=60.0)), orgit revert <merge sha>.Heart RED development override
Heart is RED. The current verdict (2026-10-15 ~21:20) is
RELEASE READINESS ✗ RED score 55with reasons "release validation FAILED (stage integrate)" (red) and "PyAutoFit: open PR 8d old" (warning). Both are organism-scope and unrelated to this branch. The live human authorized the development-only override in session 3e3dea38 (AskUserQuestion answer "Yes, override for development (Recommended)") for the three PyAutoArray Aris prompts, including this one. The full reason list at launch is recorded on #628 and in PyAutoMindactive.md. Permitted: commit, push, PR-open. Merge is a separate human act with all required checks green. Heart remains RED for release purposes. This branch does not claim to fix Heart.Validation checklist (--auto run — plan was not pre-approved)
test__interferometer_route__settings_overrides+test__factory__gate_of_zero_disables_the_numba_path; interferometer_route forcing → basis-citedtest__factory__interferometer_route_forces_the_route; Witness → basis-cited 12-point routing test + independent recompute of the constants (exact match). Review note: the Aris 125 'numba faster' labels compare against JAX FFT on 2.8-3.6 cores; the model predicts single-thread NumPy FFT slower still · Heart RED under the recorded development overrideCampaign: PyAutoPulse#62, task
interferometer_large_mask_aris. Phase 2 (an autolens_profiling re-measurement record on both masks with the new default) follows after this PR merges.Closes #628
Full API Changes (for automation & release notes)
Added
Settings(interferometer_route: Optional[str] = None)and propertySettings.interferometer_route:None|"numba"|"fft"; invalid values raiseValueErrorat construction.general.inversion.interferometer_route: null.inversion_interferometer_numba_util.curvature_route_seconds_from(source_pixels, nnz, extent_shape) -> (t_fft, t_direct_conv)inversion_interferometer_numba_util.fft_route_work_from(source_pixels, extent_shape),direct_conv_work_from(nnz, extent_shape),_fft_length_cost(n)FFT_ROUTE_SECONDS_PER_UNIT = 7.62e-10,DIRECT_CONV_SECONDS_PER_UNIT = 1.77e-09Changed Signature
factory._use_interferometer_numba(linear_obj_list, settings=None, xp=np, extent_shape=None)(private; new optional kwarg)Changed Behaviour
interferometer_numba_nnz_per_source_maxdefault60.0→null.Settings().interferometer_numba_nnz_per_source_maxnow returnsNone(the cost model) rather than60.0. The property's return type isOptional[float].0unchanged.Migration
Settings(interferometer_numba_nnz_per_source_max=60.0)or the same key in a workspacegeneral.yaml.🤖 Generated with Claude Code