Repository navigation
fix: finite forward-mode gradient for FitPositionsImagePairAll(Solved) - #768
Merged
Merged
Conversation
#767) The PointSolver pads its image positions with inf sentinel rows, and square_distance gave them a forward-mode tangent of 2*(d - inf)*0 = NaN, which exp carried into every component of jax.jacfwd -- the default AnalysisPoint gradient_mode="forward" -- so every gradient search on the image-plane all-pairs likelihood was stuck on NaN. Reverse mode happened to discard it. all_permutations_log_likelihoods now wraps the likelihoods in a jax.custom_jvp: the primal is the unchanged per-position computation (jitted log likelihoods stay bit-identical, fiducial 7.743201200876812), and the tangent is the JVP of a padding-safe vectorized form (finite placeholder + mask). A per-position double-where was not enough: XLA's CPU fusion of the jitted forward graph still leaked NaN from the discarded branch, and a fully vectorized primal moved the fiducial by 1 ULP. Tests: jitted jacfwd == grad == central FD over 8 PRNGKey draws with inf padding (both classes, red on unfixed source); Fitness forward/reverse parity over 17 vectors now also covers both pair-all classes with a real PointSolver. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S11WE9oj7Mvkfhc4EPBnyN
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
Fixes #767. Since 2026.9.27.2,
AnalysisPoint.gradient_mode = "forward"makes gradient searches differentiate withjax.jacfwd, and forFitPositionsImagePairAll/FitPositionsImagePairAllSolvedthat forward gradient was NaN in every component, so everyMultiStartAdamstart was non-finite. Reverse mode was finite.Root cause. The
PointSolverpads its image positions withinfsentinel rows (remove_infinities=False).square_distancecomputes(d - inf) ** 2: the value is right (inf, solog_p = -inf), but the forward tangent is2 * (d - inf) * 0 = NaN, whichexpin the log-sum-exp carried into the log likelihood. Reverse mode happened to drop it.Fix. Under JAX,
all_permutations_log_likelihoodswraps the likelihoods in ajax.custom_jvp:7.743201200876812, the autolens_profilingFIDUCIAL_SOLVED_LOG_L_BY_BACKEND["cpu"]pin, checked);_log_likelihoods_padding_safe, which scores padded rows on a finite placeholder and masks them back to-inf(double-where), vectorized over model positions.The NumPy path takes the same old code with no wrapper. Two simpler fixes were tried and rejected:
wherestill produced NaN once jitted. XLA's algebraic simplifier plus fusion leaked the non-finite discarded branch. It was finite without jit and NaN with jit, and--xla_disable_hlo_passes=algsimpremoved the NaN.…811).API Changes
None — internal.
all_permutations_log_likelihoodskeeps its signature and values. New private helpers:_log_sum_exp,_log_likelihoods_from,_log_likelihoods_padding_safe.Test Plan
test_pair_all.py::test__forward_mode_gradient_with_padded_model_positions__finite_and_matches_reverse_and_fd, for both classes. A smooth mocked solver withinfpadding is jitted, and overPRNGKey(0..7)jacfwdmust be finite, non-zero, equal tograd(rtol 1e-6) and equal to a central FD (rtol 1e-6). FD is checked on the mock only because the real solver's primal is discretised atpixel_scale_precision.test__padded_model_positions__log_likelihoods_identical_to_padding_removed: with padding, the log likelihoods are identical to those without it (NumPy exact, JAX 1e-14).test_analysis_point_gradient_mode.pyparity (subprocess,Fitness+register_model, 17 vectors = medians +PRNGKey(0..15)) is now parametrised overFitPositionsSourceSolved,FitPositionsImagePairAllSolvedandFitPositionsImagePairAllwith a realPointSolver. It asserts forward is finite and forward == reverse ==Fitness.grad(rtol 1e-6 for the image-plane fits, where implicit-diff fwd/rev agree to ~1e-10, and 1e-8 for source-plane as before). It also asserts the gradient is non-zero.pair_all.pyreverted, the 4 new image-plane tests fail and the source-plane control passes.main, reverse gradients unchanged (≤3e-12 abs), forward == reverse (max rel 8e-11).Fitness.grad, declared forward):[-41.62, 53.13, 31.79, 2.80, 1781.74], which equals reverse. It was NaN×5 before.pytest test_autolens/on CPU fp64: 825 passed, 1 xfailed. The point subset gave 200 passed, including thestatic_latticetie pins.autolens_workspace_test/scripts/point_source/jax_grad/gradient.pyandjax_likelihood/image_plane.pyboth pass.Downstream users of
FitPositionsImagePairAll(Solved)(no edits needed — no API change)scripts/point_source/jax_grad/gradient.py(reverse-modejax.value_and_grad, which is why it never caught this),jax_likelihood/{image_plane,point,solver_extent}.py,visualization/{visualization,visualization_jax,modeling_visualization_jit}.pyscripts/point_source_image/likelihood_breakdown/{image_plane,gpu_bottleneck_map,solver_config_sweep,static_lattice_ab,vertex_dedup_ab,_point_solver_stage_map}.py,likelihood_runtime/{image_plane,image_plane_solved}.py. Thefiducial_bit_exactgates pin7.743201200876812(CPU), which is unchanged here. The GPU pin (…806) was not re-measured.scripts/point_source/{fit,modeling,plot}.py,point_source/features/*,guides/point_source_pairing.py,cluster/{modeling,likelihood_function}.py,multi_dataset/features/imaging_and_point_source/modeling.pyHeart override
Heart is RED (
release validation FAILED (stage integrate)). That reason has nothing to do with this change. The human granted the AUTONOMY.md "Human override for Heart RED (development only)" in-session (2026-10-04), covering push + PR-open only. This PR must not be merged until it gets a separate human/prmwith green CI.Patch release worth considering: the defect is live for every user of 2026.9.27.2+ who runs a gradient search on the image-plane all-pairs fits.
🤖 Generated with Claude Code
https://claude.ai/code/session_01S11WE9oj7Mvkfhc4EPBnyN