Better abstraction for keeping track of which constraints have been evaluated - #116
Merged
Conversation
problem_jacobian used a problem-global bool: first call evaluates all constraints, later calls skip affine ones entirely, and problem_update_params resets the flag wholesale. Replace it with a per-constraint seen counter against the constraint jacobian's values_version: every constraint is eval'd (affine ones return instantly via the impl-skip from #114), and a block is memcpy'd into the aggregated jacobian only when its version moved. Re-copying after a parameter update happens automatically because expr_set_needs_refresh re-arms the evals, which bump the versions - so the copy skip is per-constraint and needs no reset. The seen array is seeded deliberately stale in problem_init_jacobian so the first call always copies. New poison-based regression test proves the affine slice is not rewritten on re-eval and is restored after a parameter update. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016rQ3LPdi31kNLmTy9F2oEa
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.
Before this PR the problem class kept track on which constraints had to be re-evaluated in each iteration. It is a better abstraction to move this responsibility to the constraints themself. In particular, this is good for the separate Python interface in the future.
Claude below:
Summary
The last of the three values_version follow-ups (after #114 and #115).
problem_jacobiankept its own affine shortcut: a problem-globaljacobian_calledbool, set after the first call, reset wholesale byproblem_update_params. This PR replaces it with the counter scheme from #113:problemgains a per-constraintconstraint_jac_seenarray (allocated and seeded deliberately stale inproblem_init_jacobian, freed infree_problem); thejacobian_calledfield is deleted.problem_jacobiannow callseval_jacobianon every constraint — affine ones return instantly via the impl-skip from affine optimization #114 — and memcpys a constraint's block into the aggregated Jacobian only when that constraint'svalues_versionmoved. The offset walk is unchanged (layout is fixed at init), and theassert(nnz_offset == J->nnz)stays.problem_update_paramsis deleted:expr_set_needs_refreshre-arms the constraint evals, which bump their versions, so the copy re-fires automatically — per constraint, rather than for all constraints at once.Behavior: affine constraints skip both eval work and the memcpy after their first call, exactly as before but per-constraint and automatic; non-affine constraints bump every eval and copy every call, as before.
One pre-existing note (unchanged by this PR): the memcpy source
c->jacobian->xassumes the value-buffer layout matches the init-timeto_csrlayout — true forsparse_matrixandpermuted_dense, but would be wrong if a constraint's top-level Jacobian were ever astacked_pd(block-major storage).Tests
New
test_problem_jacobian_memcpy_skip: an affine parameterized constraint (a*x) plus a non-affine one (exp(x)). After the firstproblem_jacobian, the affine slice ofprob->jacobian->xis poisoned and a re-eval proves it is not rewritten while the non-affine slice is; aproblem_update_paramsthen restores the slice with the new parameter value.Verification
MallocScribble=1 MallocGuardEdges=1 leaks --atExit— 0 leaksPROFILE_ONLYbuild compilesgrepconfirms nojacobian_calledreference remains