Conversation
5 tasks
…puts solve() seeded m_x by reading back its own output properties. That looks like a warm start and is not one. The unknowns are created with add_output, not add_history_output, so statev_map never collects them -- it skips anything !is_history() -- and unpack() never restores them. One instance serves every integration point on a thread: umat_interface builds one material_context per thread and states why that is sufficient, "a consequence of the evaluator being stateless: all point state lives in the host's STATEV array". material_point_evaluator is blunter: "anything the evaluator remembered across calls would be state from a trial the host has since discarded". Seeding from the properties is exactly such a memory. Point N starts from point N-1's converged answer, and after a failure from a diverged iterate. For a system with several real roots that does not merely change the iteration count, it selects WHICH root is returned -- converged, plausible, and not the root this point is on. Cold start every solve, which makes the solver point-local by construction. That is what backward_euler already does: it passes a fixed literal seed rather than reading back its own "delta". Two tests, and the first is the one that matters. It drives two different problems through ONE instance, changing them in place with set_parameter, and requires each answer to equal a fresh instance's. x^2 + y = a; x + y^2 = b has several real roots, so the seed is observable in the result rather than only in the iteration count. The second does the same with a diverging problem in between, for the failure path. An earlier version of this change kept the property seeding and only held it back after a failure. That was the wrong level: it removed failure-dependence and left point-order-dependence, and added a second piece of hidden cross-call state to a class whose surrounding contract is that nothing is retained between calls. SolvesArePointLocal fails against that version too, which is the point of writing it this way. Warm-starting is still worth having -- plane_stress_evaluator keeps eps_33 in its own STATEV slot precisely because a cold start costs several extra graph evaluations per Gauss point -- but it has to be per point, which means the unknowns becoming history properties. Left to #21 with the zero-seed caveat for a model whose jacobian is singular at the origin. No production caller: vector_newton appears only in the factory registration and its own tests. 282/282 pass.
petlenz
force-pushed
the
fix/vector-newton-seed
branch
from
September 13, 2026 16:37
be28b3b to
1264719
Compare
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.
vector_newton::solve()seededm_xby reading back its own output properties. That looks like a warm start and is not one.The unknowns are created with
add_output, notadd_history_output, sostatev_mapnever collects them — it skips anything!is_history()— andunpack()never restores them. One instance serves every integration point on a thread:umat_interfacebuilds onematerial_contextper thread and states why that is sufficient —and
material_point_evaluatoris blunter —Seeding from the properties is exactly such a memory. Point N starts from point N−1's converged answer, and after a failure from a diverged iterate. For a system with several real roots that does not merely change the iteration count — it selects which root is returned, converged and plausible and not the root this point is on.
The fix
Cold start every solve, which makes the solver point-local by construction.
backward_euleralready works this way: it passes a fixed literal seed rather than reading back its owndelta.Tests
The first is the one that matters: it drives two different problems through one instance, changing them in place with
set_parameter, and requires each answer to equal a fresh instance's.x² + y = a ; x + y² = bhas several real roots, so the seed is observable in the result, not just in the iteration count.SolvesArePointLocalAFailedSolveDoesNotMoveTheNextMutation-verified against three implementations:
SolvesArePointLocalAFailedSolveDoesNotMoveTheNextmain(seed from properties)What changed since the first version of this PR
The first version kept the property seeding and only held it back after a failure. That was the wrong level — it removed failure-dependence and left point-order-dependence, and it added a second piece of hidden cross-call state to a class whose surrounding contract is that nothing is retained between calls.
SolvesArePointLocalfails against that version too, which is why it is written as a cross-problem test rather than a cross-attempt one.The first version also accidentally carried ~400 lines of unrelated CalculiX adapter work that was sitting uncommitted in the working tree. That is out, and preserved.
Left to #21
Warm-starting is worth having —
plane_stress_evaluatorkeepseps_33in its own STATEV slot precisely because a cold start costs several extra graph evaluations per Gauss point — but it has to be per point, which means the unknowns becoming history properties. The zero seed also assumes a non-singular Jacobian at the origin; a model that needs otherwise wants an explicit initial guess, which is the same follow-up.No production caller:
vector_newtonappears only in the factory registration and its own tests. 282/282 pass.