Skip to content

fix: Variable.fix() value alignment on named dimensions#774

Merged
FabianHofmann merged 8 commits into
masterfrom
fix/fix-value-coord-alignment
Jun 9, 2026
Merged

fix: Variable.fix() value alignment on named dimensions#774
FabianHofmann merged 8 commits into
masterfrom
fix/fix-value-coord-alignment

Conversation

@FBumann

@FBumann FBumann commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

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 with as_dataarray(value).broadcast_like(self.labels), which aligns only by dimension name. That happens to work only for the default dim_0. On a variable with a named dimension, a positional value (list/array) gained a spurious dim_0 and was broadcast across the real dimension instead of aligned onto it — silently producing a wrong fix constraint.

import linopy, pandas as pd

m = linopy.Model()
t = m.add_variables(lower=-5, upper=5, coords=[pd.Index([2020, 2030, 2040], name="time")], name="t")
t.fix([1.0, 2.0, 3.0])

# before: __fix__t constraint has dims (time: 3, dim_0: 3) — a 3×3 block fixing
#         every t[time] to all of 1, 2, 3 at once (infeasible); only a soft warning
# after:  __fix__t has dims (time: 3) with rhs [1, 2, 3]

This affects the released v0.7.0 fix().

Fix

Use broadcast_to_coords against the variable's own coords — the same coords-aware alignment add_variables uses for lower/upper:

  • scalars broadcast across the dimension,
  • positional inputs (list / ndarray) land on the right dimension,
  • named pandas/xarray inputs align by coordinate value (reordered indexes reindex),
  • a value carrying an unknown dimension raises an error that names the variable.

Tests

TestFixValueAlignment in test_fix_relax.py is 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

@FBumann FBumann force-pushed the fix/fix-value-coord-alignment branch from f3e60b4 to da634cc Compare June 8, 2026 17:08
@FBumann FBumann requested a review from FabianHofmann June 8, 2026 17:12
@FBumann FBumann changed the title Fix Variable.fix() value alignment on named dimensions fix: Variable.fix() value alignment on named dimensions Jun 8, 2026
@FBumann FBumann force-pushed the fix/fix-value-coord-alignment branch from da634cc to 7b657f1 Compare June 8, 2026 20:00
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>
@FBumann FBumann force-pushed the fix/fix-value-coord-alignment branch from 7b657f1 to 21e97dc Compare June 8, 2026 20:02

@FabianHofmann FabianHofmann left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great catch, will pull this in quickly, just have to check one thing

Comment thread linopy/variables.py

@FabianHofmann FabianHofmann left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 and others added 5 commits June 9, 2026 08:40
* 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>
@FabianHofmann FabianHofmann added this to the v0.8.0 milestone Jun 9, 2026
@FabianHofmann FabianHofmann merged commit 9eeb838 into master Jun 9, 2026
19 checks passed
@FabianHofmann FabianHofmann deleted the fix/fix-value-coord-alignment branch June 9, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants