{{ message }}
Repository navigation
Handle singular Newton matrices in the Anderson solver - #241
Merged
Merged
Conversation
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
steadystate()crashed withLinAlgError: SVD did not converge in Linear Least Squareson a PID plus nonlinear plant loop. The message points at the Anderson least squares, which is not where the problem is.Root cause: the Newton matrix
jac - Iis structurally singular whenever a state's derivative does not depend on that state.PIDhasdx/dt = ufor its integrator state, so that Jacobian row is zero. AnIntegratoris the same shape.dx/dt = -x**2 + uhas a zero Jacobian atx = 0, which is exactly where the first iterate lands.scipy.linalg.lu_factordoes not raise on a singular matrix, it emits aLinAlgWarningand leaves a zero pivot behind, after whichlu_solvereturnsinf/nan. The scalar path divides byjac - 1.0with no guard at all. Those values propagate through the connections into other blocks and only surface many iterations later, somewhere unrelated, as the SVD failure.Changes
optim/anderson.py:_factorize()returnsNonefor a singular matrix instead of a factorization with a zero pivot, checking the diagonal ofUagainst a scaled tolerance._singular_step()applies the pseudo-inverse on the range of the Newton matrix and makes no correction on its null space, where the residual carries no information about the state.Recorded in the docstring because it is not obvious: substituting a plain fixed-point step
x <- gon the null space was tried and is worse. On a closed loop it injects the full residual every sweep and the iteration diverges (residual1e11) rather than stalling.What this does and does not fix
It fixes the crash. The reported PID loop still does not converge, and now says so truthfully with per-block residuals instead of failing with an unrelated exception.
That remaining non-convergence is a property of the block-local steady-state scheme, not of this change:
_solveruns a Gauss-Seidel sweep where each block solves only its own equations, and a PID's integrator equilibrium is set by the outer loop, which the local solve cannot see. Worth its own issue.Test plan
_factorizeand_singular_step, plus system-level tests that the loop no longer raisesLinAlgErrorand that noinf/nanreaches the block statesIntegrator) still converges to its equilibriumLinAlgError