Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

constraints: sanitize infinity values #370

Merged
merged 2 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -5,6 +5,7 @@ Upcoming Version
----------------

* When writing out an LP file, large variables and constraints are now chunked to avoid memory issues. This is especially useful for large models with constraints with many terms. The chunk size can be set with the `slice_size` argument in the `solve` function.
* Constraints which of the form `<= infinity` and `>= -infinity` are now automatically filtered out when solving. The `solve` function now has a new argument `sanitize_infinities` to control this feature. Default is set to `True`.

Version 0.3.15
--------------
Expand Down
36 changes: 32 additions & 4 deletions linopy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@
to_polars,
)
from linopy.config import options
from linopy.constants import EQUAL, HELPER_DIMS, TERM_DIM, SIGNS_pretty
from linopy.constants import (
EQUAL,
GREATER_EQUAL,
HELPER_DIMS,
LESS_EQUAL,
TERM_DIM,
SIGNS_pretty,
)
from linopy.types import ConstantLike

if TYPE_CHECKING:
Expand Down Expand Up @@ -851,17 +858,17 @@ def equalities(self) -> "Constraints":
"""
return self[[n for n, s in self.items() if (s.sign == EQUAL).all()]]

def sanitize_zeros(self):
def sanitize_zeros(self) -> None:
"""
Filter out terms with zero and close-to-zero coefficient.
"""
for name in list(self):
for name in self:
not_zero = abs(self[name].coeffs) > 1e-10
constraint = self[name]
constraint.vars = self[name].vars.where(not_zero, -1)
constraint.coeffs = self[name].coeffs.where(not_zero)

def sanitize_missings(self):
def sanitize_missings(self) -> None:
"""
Set constraints labels to -1 where all variables in the lhs are
missing.
Expand All @@ -872,6 +879,27 @@ def sanitize_missings(self):
contains_non_missing, -1
)

def sanitize_infinities(self) -> None:
"""
Replace infinite values in the constraints with a large value.
"""
for name in self:
constraint = self[name]
invalid_infinity_values = (
(constraint.sign == LESS_EQUAL) & (constraint.rhs == -np.inf)
) | ((constraint.sign == GREATER_EQUAL) & (constraint.rhs == np.inf))
if invalid_infinity_values.any():
raise ValueError(
f"Constraint {name} contains incorrect infinite values."
)
FabianHofmann marked this conversation as resolved.
Show resolved Hide resolved

valid_infinity_values = (
(constraint.sign == LESS_EQUAL) & (constraint.rhs == np.inf)
) | ((constraint.sign == GREATER_EQUAL) & (constraint.rhs == -np.inf))
self[name].data["labels"] = self[name].labels.where(
~valid_infinity_values, -1
)

def get_name_by_label(self, label: Union[int, float]) -> str:
"""
Get the constraint name of the constraint containing the passed label.
Expand Down
6 changes: 6 additions & 0 deletions linopy/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,7 @@ def solve(
keep_files: bool = False,
env: None = None,
sanitize_zeros: bool = True,
sanitize_infinities: bool = True,
slice_size: int = 2_000_000,
remote: None = None,
**solver_options,
Expand Down Expand Up @@ -1003,6 +1004,8 @@ def solve(
Whether to set terms with zero coefficient as missing.
This will remove unneeded overhead in the lp file writing.
The default is True.
sanitize_infinities : bool, optional
Whether to filter out constraints that are subject to `<= inf` or `>= -inf`.
slice_size : int, optional
Size of the slice to use for writing the lp file. The slice size
is used to split large variables and constraints into smaller
Expand Down Expand Up @@ -1083,6 +1086,9 @@ def solve(
if sanitize_zeros:
self.constraints.sanitize_zeros()

if sanitize_infinities:
self.constraints.sanitize_infinities()

if self.is_quadratic and solver_name not in quadratic_solvers:
raise ValueError(
f"Solver {solver_name} does not support quadratic problems."
Expand Down
22 changes: 22 additions & 0 deletions test/test_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,25 @@ def test_constraints_flat():

assert isinstance(m.constraints.flat, pd.DataFrame)
assert not m.constraints.flat.empty


def test_sanitize_infinities():
m = Model()

lower = xr.DataArray(np.zeros((10, 10)), coords=[range(10), range(10)])
upper = xr.DataArray(np.ones((10, 10)), coords=[range(10), range(10)])
x = m.add_variables(lower, upper, name="x")
y = m.add_variables(name="y")

# Test correct infinities
m.add_constraints(x <= np.inf, name="con_inf")
m.add_constraints(y >= -np.inf, name="con_neg_inf")
m.constraints.sanitize_infinities()
assert (m.constraints["con_inf"].labels == -1).all()
assert (m.constraints["con_neg_inf"].labels == -1).all()

# Test incorrect infinities
m.add_constraints(x >= np.inf, name="con_wrong_inf")
m.add_constraints(y <= -np.inf, name="con_wrong_neg_inf")
with pytest.raises(ValueError):
m.constraints.sanitize_infinities()
4 changes: 1 addition & 3 deletions test/test_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,9 +498,7 @@ def test_infeasible_model(model, solver, io_api):
model.compute_infeasibilities()


@pytest.mark.parametrize(
"solver,io_api", [p for p in params if p[0] not in ["glpk", "cplex", "mindopt"]]
)
@pytest.mark.parametrize("solver,io_api", params)
def test_model_with_inf(model_with_inf, solver, io_api):
status, condition = model_with_inf.solve(solver, io_api=io_api)
assert condition == "optimal"
Expand Down
Loading