fix(constraints): CSRConstraint stores label columns; to_constraint on a CSR payload returns an unassigned CSRConstraint - #929
Conversation
Build cost — v1 vs legacyv1 build peak & time relative to legacy, on this commit — not a comparison against master (that is CodSpeed).
Full table (time + peak, mean)📊 Interactive plots + CSV: download the semantics-report-v1-vs-legacy artifact from this run. Report-only · not a gate · refreshed on every push · obsolete once legacy is dropped. |
Merging this PR will regress 1 benchmark
|
… label the sparse freeze path like the dense one (#926) - store _active_positions instead of overloading _con_labels; active_labels() raises on an unassigned constraint and Constraints.add rejects it - from_payload keeps term-less rows so freeze=False matches dense labels; assign_labels drops them as from_mutable does - sparse freeze path runs the infinity, dim-name and multiindex checks - FILL_VALUE coeffs is NaN, matching expressions and CSRConstraint.data - missing-label guard also rejects the -1 sentinel
Closes #926. Supersedes #927.
(handwritten intent goes here)
Note
The following content was generated by AI (Claude Code), reviewed by @FabianHofmann.
Changes proposed in this Pull Request
CSRConstraintstored dense variable positions as CSR column indices, with a width fixed at freeze time. Anyadd_variablesorremove_variablesafter afreeze=Trueconstraint mademodel.matricesfail withinconsistent shapes. This PR rebases #927 onto the merged #870 and completes the remaining two points of #926.Label columns (points 1, 3, 5 of #926, from #927)
CSRConstraintstores raw variable labels as columns (widthmodel._xCounter). A shared helper maps labels to dense positions once at matrix assembly, for both the denseConstraintand the frozen path, and raises if a constraint references a removed variable.dataandindptrarrays by identity and preserves the int32 index dtype, so the persistent snapshot diff can still skip untouched frozen constraints._csr_columns: labels; the reader maps legacy position files back through the storedvlabelsand raises on unknown tags.CSRConstraint.indexesbuilt an xarrayIndexeswithout a variables mapping, which madeModelSnapshot.capturefail on any model with a frozen constraint.Unassigned CSR constraints (points 2 and 4 of #926)
CSRConstraint.from_payloadkeeps the payload's label columns as they are and no longer needs the variable label index. It returns an unassigned constraint.CSRConstraintstores the flat grid positions of its active rows; labels are derived ascindex + position, andactive_labels()raises while unassigned.Constraints.addrejects unassigned constraints.LinearExpression.to_constrainton a CSR-backed expression returns that unassignedCSRConstraintdirectly.Constraint._from_pending, the lazyConstraint.databranch andextract_csr_pendingfrom feat(v1): CSR-backed sparse groupby-sum - skew-independent build memory (6-9x on the #745 hub case) #870 are removed.Model.add_constraintsbuilds the anonymous constraint once via_constraint_from_lhs; for aCSRConstraintunder freeze it only assigns labels, otherwise it materializes to the dense path as before. The sparse freeze path now also honours thescalingargument, which it previously ignored. It also runs the infinite-rhs, dimension-name and multiindex checks that the dense path runs, and it labels a group without terms like the dense path in both freeze modes (term-less rows are kept while unassigned and dropped on freeze, asfrom_mutabledoes).CSRConstraint.datapads unused term slots incoeffswith NaN instead of 0, andconstraints.FILL_VALUE["coeffs"]follows, matching the expressions fill value.to_constraint,le/ge/eqand the comparison operators are widened toConstraintBase.Tests
Add/remove variables after freeze against the unfrozen model, netCDF round-trip incl. legacy layout, buffer identity and empty diff for an untouched frozen model,
to_constrainton a CSR lhs returning an unassignedCSRConstraintthat labels equivalently to the dense path, row scaling on the frozen path, errors on label access of an unassigned constraint, label parity of a group without terms for both freeze modes, and infinite-rhs rejection on both paths.Verification
ruff check/ruff formatmypy linopysolvers.pypytest -k "not xpress" --ignore=test/remoteChecklist
AGENTS.md).doc.doc/release_notes.rstof the upcoming release is included.