Evaluate f_info for real in Gauss-Newton init instead of zero-filling#234
Evaluate f_info for real in Gauss-Newton init instead of zero-filling#234Sumu004 wants to merge 1 commit into
Conversation
…closures AbstractGaussNewton.init filled state.f_info with a same-shaped placeholder of zeros (tree_full_like(f_info_struct, 0, ...)) rather than a real evaluation of _make_f_info. This silently zeroed out any values closed over inside f_info.jac too -- in particular, the point at which jax.linearize captured its linearization inside _make_f_info. ClassicalTrustRegion.predict_reduction calls f_info.jac.mv(y_diff) unconditionally on every call to step -- including the very first, where f_info is still this placeholder -- even though the decision it feeds into (accept) is separately forced to True via accept | first_step. If the residual function involves an operation that is nan-sensitive at zero (e.g. a division), the corrupted placeholder produces a genuine nan in predicted_reduction's value, which then contaminates the JVP even though the decision itself is unaffected by it. GaussNewton (no trust region) doesn't hit this, which is why the original report singles out LevenbergMarquardt/InverseLevenbergMarquardt specifically -- both inherit this same init with no override. Fix: call _make_f_info(fn, y, args, tags, jac) directly in init (y is already available as a parameter), matching what step already does for subsequent iterations, instead of eval_shape + zero-fill. This costs one extra linearization at init in exchange for never fabricating ill-defined closure data. Fixes patrick-kidger#229
|
Thank you for the fix! (And gosh, sorry for the delay getting to this - both your fix and the original issue.) So I think this might still actually have a slight bug: we'd never update the linearisation point from the initial value used. This is because of these lines: optimistix/optimistix/_solver/gauss_newton.py Lines 254 to 259 in 6415ab1 which always keep the "old" jaxpr (as noted in the comment!) with the mistake being it does not distinguish between a I've not tracked down exactly what is holding the WDYT? |
Thanks @NiallOswald for the root-cause tracing in #229 — this implements what you diagnosed.
AbstractGaussNewton.initfilledstate.f_infowith zeros viatree_full_like, which also zeroes any data closed over insidef_info.jac(thejax.linearizecapture).ClassicalTrustRegion.predict_reductioncallsf_info.jac.mv(...)unconditionally on every step, including the first, so a residual function that's nan-sensitive at a zeroed closure value produces a realnanthat contaminates the JVP — even though the accept/reject decision itself is separately forced correct on the first step.GaussNewton(no trust region) is unaffected;LevenbergMarquardt/InverseLevenbergMarquardtboth inherit the sameinitand are.Fix: call
_make_f_infodirectly ininitinstead of faking its shape with zeros — exactly whatstepalready does every other iteration. Costs one extra linearization at init.I don't have a working JAX install, so I've verified this by tracing the exact call paths rather than running it — would appreciate a maintainer confirming against the real reproducer.
Fixes #229