Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Upcoming Version
**Bug fixes**

* LP file export now honors bounds tightened below ``[0, 1]`` on a binary variable via the ``.lower``/``.upper`` setters after creation (e.g. ``upper = 0``). Previously such bounds were written only by ``io_api="direct"`` and dropped by ``io_api="lp"``. (https://github.com/PyPSA/linopy/issues/776)
* Freezing an empty constraint group (e.g. an empty ``isel`` slice) no longer raises ``ValueError: cannot reshape array of size 0``. ``Model(freeze_constraints=True)`` and ``Constraint.freeze()`` now round-trip zero-row constraints losslessly.

Version 0.8.0
-------------
Expand Down
2 changes: 1 addition & 1 deletion linopy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -1081,7 +1081,7 @@ def from_mutable(
# Build active_mask aligned with con_labels (rows in csr)
# Use same filter as to_matrix: label != -1 AND at least one var != -1
labels_flat = con.labels.values.ravel()
vars_flat = con.vars.values.reshape(len(labels_flat), -1)
vars_flat = con.vars.values.reshape(len(labels_flat), con.nterm)
active_mask = (labels_flat != -1) & (vars_flat != -1).any(axis=1)
rhs = con.rhs.values.ravel()[active_mask]
sign_vals = con.sign.values.ravel()
Expand Down
21 changes: 21 additions & 0 deletions test/test_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,27 @@ def test_empty_constraints_repr() -> None:
Model().constraints.__repr__()


@pytest.mark.parametrize("freeze_constraints", [True, False])
def test_constraint_handles_empty_rows(freeze_constraints: bool) -> None:
"""An empty constraint group must be accepted and solve cleanly."""

m = Model(freeze_constraints=freeze_constraints)
x = m.add_variables(
lower=0.0,
coords=[range(3), range(2)],
dims=["time", "product"],
name="x",
)
empty = x.isel(time=range(1, 1))
c = m.add_constraints(empty == 0, name="empty")
assert isinstance(c, linopy.constraints.ConstraintBase)
assert c.size == 0
# Solving a model with only an empty constraint group is also fine.
m.add_objective(x.sum())
m.solve("highs", io_api="direct", output_flag=False)
assert m.status == "ok"


def test_cannot_create_constraint_without_variable() -> None:
model = linopy.Model()
with pytest.raises(ValueError):
Expand Down
Loading