test: pin Fitness's NaN guard contract under jax.grad#53
Merged
Conversation
Asserts what the guard actually promises: the VALUE is guarded, the GRADIENT is not. Also records the disproof of the remedy that looks obvious but fails -- an output-side double-where cannot repair a tape that already carries a non-finite derivative -- so nobody re-prescribes it. JAX-only, so it lives here rather than test_autofit/ (unit tests are numpy-only). Runs on CPU in seconds; no A100 needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Part of PyAutoFit#1391. Upstream PR: PyAutoLabs/PyAutoFit#1392 (must merge first — library-first gate).
Pins what
Fitness's NaN/inf resample guard actually promises: it protects the value, never the gradient.Why
Fitness.callguards withxp.where(xp.isnan(ll), resample_figure_of_merit, ll). Searches reading only the figure of merit are protected. Gradient consumers are not:jax.graddifferentiates both branches of awhereand multiplies the unselected one by zero, so0 * NaN = NaN. Diagnosed in autolens_workspace_developer#104; it retroactively explains why #101's trajectories died with a non-finite objective rather than a NaN one, and whyoptax.apply_if_finitelatched at the cliff.These assertions exist so that limitation cannot silently regress into a false promise — and so the disproof of the obvious-looking fix is recorded where the next person will find it.
Scripts Changed
scripts/jax_assertions/fitness_nan_gradient_contract.pyAsserts:
assert_guard_repairs_the_value— the guard delivers what it advertises; no NaN value reaches the search.assert_guard_does_not_repair_the_gradient— a guarded point still yields a NaN gradient. Deliberately pins broken-looking behaviour: if it ever fails, the guard's reach changed andfitness.py's docstring must be revisited.assert_gradient_is_fine_when_the_masked_derivative_is_finite— the trap is narrower than "never protects gradients":log(x<0)is NaN in value but its derivative1/xis finite, so0 * -1 = 0and nothing leaks.assert_output_side_double_where_does_not_fix_it— the disproof, so nobody re-prescribes it.assert_input_side_safe_x_does_fix_it— what a real fix looks like, and why it cannot live inFitness(it needs to know which input is invalid).assert_valid_points_are_unaffected— none of the above perturbs a well-behaved model.Placement
JAX-only, so it belongs here rather than
test_autofit/— the library's unit tests are numpy-only, which is exactly whyjax_assertions/exists. Mirrors the siblingfitness_dispatch.py(sameaf.ex.Gaussian+Fitnessconstruction, module-levelassert_*functions,__main__runner).Testing
python scripts/jax_assertions/fitness_nan_gradient_contract.py→ all 6 assertions pass. CPU, seconds, no A100.Gate
jax_assertions/script, no existing script touched.PyAutoLens: 1 uncommitted source change(s).