fix: Variable.fix() value alignment on named dimensions#774
Merged
Conversation
f3e60b4 to
da634cc
Compare
da634cc to
7b657f1
Compare
fix() converted the value with as_dataarray().broadcast_like(self.labels), which aligns only by dimension name and so worked solely for the default `dim_0`. On a named dimension, a positional value (list/array) gained a spurious `dim_0` and broadcast across the real dimension instead of onto it, silently building a wrong fix constraint (one fixing every entry to every value). Use broadcast_to_coords against the variable's own coords — the same coords- aware alignment add_variables uses for lower/upper: scalars broadcast, positional inputs land on the right dimension, named pandas/xarray inputs align by coordinate value, and a mismatch raises an error naming the variable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7b657f1 to
21e97dc
Compare
FabianHofmann
left a comment
Collaborator
There was a problem hiding this comment.
great catch, will pull this in quickly, just have to check one thing
FabianHofmann
left a comment
Collaborator
There was a problem hiding this comment.
all resolved - added a test class for covering more cases of alignment. (could argue it duplicates parts of the tests of broadcast_to_coords, but it's good to have them here as well to avoid regression)
FabianHofmann
approved these changes
Jun 9, 2026
for more information, see https://pre-commit.ci
* feat: fix variables via bound collapse, honoring bounds at export Variable.fix() now collapses lower = upper = value (the JuMP/Pyomo/gurobipy meaning of "fix") instead of adding a __fix__ equality constraint. Pre-fix bounds are stashed as _stashed_lower / _stashed_upper data variables on the variable's own Dataset, so unfix() restores them and the state round-trips through netcdf for free. Fixing is now a pure value change, so fix/re-solve loops stay on the persistent in-place update path instead of forcing a solver rebuild, and model.constraints is no longer polluted with __fix__ entries. A fix value outside the current bounds warns and overrides; fixing a binary to anything other than 0/1 raises. Removes FIX_CONSTRAINT_PREFIX and the __fix__ cleanup in remove_variables. Honoring a fixed binary that stays binary required fixing binary-bound export, which several paths hardcoded to [0, 1]: the LP writer now emits explicit bounds for fixed binaries, and the HiGHS and Mosek direct backends no longer override binary bounds (they already come from M.lb/M.ub). This also fixes the latent bug that a binary's bounds could not be restricted at all. Tests: bound-collapse unit coverage in test_fix_relax.py, and a cross-solver, cross-io integration test (test_fixed_variable_is_held) asserting the fix is honored when solving for continuous, integer and binary, in both bound directions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(fix): strict binary validation, no stash-var leak in flat, generalized STASHED_ATTRS fix() rejects non-0/1 binary values (np.isclose) before rounding; flat/to_polars drop internal stash vars; bound checks use direct attrs subscript and hoisted locals. * refact comments and tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Fabian <fab.hof@gmx.de> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
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.
This came up during working on another PR.
Small but important bugfix. Very obvious and easy since we introduced clean unternal helpers for such things.
Note
AI-assisted implementation.
Bug
Variable.fix(value)aligned the value withas_dataarray(value).broadcast_like(self.labels), which aligns only by dimension name. That happens to work only for the defaultdim_0. On a variable with a named dimension, a positional value (list/array) gained a spuriousdim_0and was broadcast across the real dimension instead of aligned onto it — silently producing a wrong fix constraint.This affects the released v0.7.0
fix().Fix
Use
broadcast_to_coordsagainst the variable's own coords — the same coords-aware alignmentadd_variablesuses forlower/upper:Tests
TestFixValueAlignmentintest_fix_relax.pyis parameterized over the value dtype — scalar (broadcast), list, ndarray, named Series (in order and reordered), and a named DataArray — asserting the fix constraint aligns to the named dimension (dims == ("time",)) with correct values, plus an unknown-dimension rejection case.🤖 Generated with Claude Code