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

add objective module and objective class in order to allow maximization problems #170

Merged
merged 2 commits into from
Oct 26, 2023
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Finally, we can solve the problem and get the optimal solution:

```python
>>> m.solve()
>>> m.objective_value
>>> m.objective.value
```
```
17.166
Expand Down
4 changes: 2 additions & 2 deletions benchmark/scripts/benchmark_jump.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function basic_model(n, solver)
@constraint(m, x + y .>= 0)
@objective(m, Min, 2 * sum(x) + sum(y))
optimize!(m)
return objective_value(m)
return objective.value(m)
end

function knapsack_model(n, solver)
Expand All @@ -25,7 +25,7 @@ function knapsack_model(n, solver)
@constraint(m, weight' * x <= 200)
@objective(m, Max, value' * x)
optimize!(m)
return objective_value(m)
return objective.value(m)
end


Expand Down
4 changes: 2 additions & 2 deletions benchmark/scripts/benchmark_linopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def basic_model(n, solver):
m.add_objective(2 * x.sum() + y.sum())
# m.to_file(f"linopy-model.lp")
m.solve(solver)
return m.objective_value
return m.objective.value


def knapsack_model(n, solver):
Expand All @@ -38,7 +38,7 @@ def knapsack_model(n, solver):
m.add_constraints((weight * packages).sum() <= 200)
m.add_objective(-(value * packages).sum()) # use minus because of minimization
m.solve(solver_name=solver)
return -m.objective_value
return -m.objective.value


if __name__ == "__main__":
Expand Down
7 changes: 5 additions & 2 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
Release Notes
=============

.. Upcoming Release
.. ----------------
Upcoming Release
----------------

* It is now possible to set the sense of the objective function to `minimize` or `maximize`. Therefore, a new class `Objective` was introduced which is used in `Model.objective`. It supports the same arithmetic operations as `LinearExpression` and `QuadraticExpression` and contains a `sense` attribute which can be set to `minimize` or `maximize`.


Version 0.2.5
-------------
Expand Down
1 change: 1 addition & 0 deletions linopy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
from linopy.expressions import LinearExpression, QuadraticExpression, merge
from linopy.io import read_netcdf
from linopy.model import Model, Variable, available_solvers
from linopy.objective import Objective
from linopy.remote import RemoteHandler
from linopy.version import version as __version__
3 changes: 3 additions & 0 deletions linopy/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,9 @@ class QuadraticExpression(LinearExpression):
def __init__(self, data, model):
super().__init__(data, model)

if data is None:
da = xr.DataArray([[], []], dims=[FACTOR_DIM, TERM_DIM])
data = Dataset({"coeffs": da, "vars": da, "const": 0})
if FACTOR_DIM not in data.vars.dims:
raise ValueError(f"Data does not include dimension {FACTOR_DIM}")
elif data.sizes[FACTOR_DIM] != 2:
Expand Down
10 changes: 9 additions & 1 deletion linopy/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ def objective_to_file(m, f, log=False, batch_size=10000):
if log:
logger.info("Writing objective.")

f.write("min\n\nobj:\n\n")
sense = m.objective.sense
f.write(f"{sense}\n\nobj:\n\n")
df = m.objective.flat

if np.isnan(df.coeffs).any():
Expand Down Expand Up @@ -354,6 +355,9 @@ def to_gurobipy(m, env=None):
else:
model.setObjective(M.c @ x)

if m.objective.sense == "max":
model.ModelSense = -1

if len(m.constraints):
names = "c" + M.clabels.astype(str).astype(object)
c = model.addMConstr(M.A, x, M.sense, M.b)
Expand Down Expand Up @@ -421,6 +425,10 @@ def to_highspy(m):
num_vars = Q.shape[0]
h.passHessian(num_vars, Q.nnz, 1, Q.indptr, Q.indices, Q.data)

# change objective sense
if m.objective.sense == "max":
h.changeObjectiveSense(highspy.highs_bindings.ObjSense.kMaximize)

return h


Expand Down
4 changes: 2 additions & 2 deletions linopy/matrices.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def c(self):
"Vector of objective coefficients of all non-missing variables."
m = self._parent
ds = m.objective.flat
if isinstance(m.objective, expressions.QuadraticExpression):
if isinstance(m.objective.expression, expressions.QuadraticExpression):
ds = ds[(ds.vars1 == -1) | (ds.vars2 == -1)]
ds["vars"] = ds.vars1.where(ds.vars1 != -1, ds.vars2)

Expand All @@ -125,4 +125,4 @@ def Q(self):
if m.is_linear:
return None

return m.objective.to_matrix()[self.vlabels][:, self.vlabels]
return m.objective.expression.to_matrix()[self.vlabels][:, self.vlabels]
81 changes: 34 additions & 47 deletions linopy/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import logging
import os
import re
import warnings
from pathlib import Path
from tempfile import NamedTemporaryFile, gettempdir

Expand All @@ -20,13 +19,7 @@
from xarray import DataArray, Dataset

from linopy import solvers
from linopy.common import (
as_dataarray,
best_int,
maybe_replace_signs,
replace_by_map,
save_join,
)
from linopy.common import as_dataarray, best_int, maybe_replace_signs, replace_by_map
from linopy.constants import TERM_DIM, ModelStatus, TerminationCondition
from linopy.constraints import AnonymousScalarConstraint, Constraint, Constraints
from linopy.expressions import (
Expand All @@ -36,6 +29,7 @@
)
from linopy.io import to_block_files, to_file, to_gurobipy, to_highspy, to_netcdf
from linopy.matrices import MatrixAccessor
from linopy.objective import Objective
from linopy.solvers import available_solvers, quadratic_solvers
from linopy.variables import ScalarVariable, Variable, Variables

Expand Down Expand Up @@ -75,7 +69,6 @@
"_varnameCounter",
"_connameCounter",
"_blocks",
"_objective_value",
# TODO: check if these should not be mutable
"_chunk",
"_force_dim_names",
Expand Down Expand Up @@ -110,11 +103,9 @@
"""
self._variables = Variables(model=self)
self._constraints = Constraints(model=self)
self._objective = LinearExpression(None, self)
self._objective = Objective(LinearExpression(None, self), self)
self._parameters = Dataset()

self._objective_value = nan

self._status = "initialized"
self._termination_condition = ""
self._xCounter = 0
Expand Down Expand Up @@ -151,8 +142,23 @@
return self._objective

@objective.setter
def objective(self, value) -> LinearExpression:
self.add_objective(value, overwrite=True)
def objective(self, obj) -> Objective:
if not isinstance(obj, Objective):
obj = Objective(obj, self)

self._objective = obj
return self._objective

@property
def sense(self):
"""
Sense of the objective function.
"""
return self.objective.sense

@sense.setter
def sense(self, value):
self.objective.sense = value

@property
def parameters(self):
Expand Down Expand Up @@ -205,15 +211,16 @@
self._termination_condition = TerminationCondition[value].value

@property
@deprecated("0.2.7", "Use `objective.value` instead.")
def objective_value(self):
"""
Objective value of the model.
"""
return self._objective_value
return self._objective.value

@objective_value.setter
def objective_value(self, value):
self._objective_value = float(value)
self._objective.value = value

Check warning on line 223 in linopy/model.py

View check run for this annotation

Codecov / codecov/patch

linopy/model.py#L223

Added line #L223 was not covered by tests

@property
def chunk(self):
Expand Down Expand Up @@ -276,7 +283,6 @@
@property
def scalar_attrs(self):
return [
"objective_value",
"status",
"_xCounter",
"_cCounter",
Expand Down Expand Up @@ -586,14 +592,14 @@
self.constraints.add(constraint)
return constraint

def add_objective(self, expr, overwrite=False):
def add_objective(self, expr, overwrite=False, sense="min"):
"""
Add a linear objective function to the model.
Add an objective function to the model.

Parameters
----------
expr : linopy.LinearExpression
Linear Expressions describing the objective function.
expr : linopy.LinearExpression, linopy.QuadraticExpression
Expression describing the objective function.
overwrite : False, optional
Whether to overwrite the existing objective. The default is False.

Expand All @@ -603,31 +609,12 @@
The objective function assigned to the model.
"""
if not overwrite:
assert self.objective.empty(), (
assert self.objective.expression.empty(), (
"Objective already defined."
" Set `overwrite` to True to force overwriting."
)

if isinstance(expr, (list, tuple)):
expr = self.linexpr(*expr)

if not isinstance(expr, (LinearExpression, QuadraticExpression)):
raise ValueError(
f"Invalid type of `expr` ({type(expr)})."
" Must be a LinearExpression or QuadraticExpression."
)

if self.chunk is not None:
expr = expr.chunk(self.chunk)

if len(expr.coord_dims):
expr = expr.sum()

if expr.const != 0:
raise ValueError("Constant values in objective function not supported.")

self._objective = expr
return self._objective
self.objective.expression = expr
self.objective.sense = sense

def remove_variables(self, name):
"""
Expand Down Expand Up @@ -697,11 +684,11 @@

@property
def is_linear(self):
return type(self.objective) is LinearExpression
return self.objective.is_linear

@property
def is_quadratic(self):
return type(self.objective) is QuadraticExpression
return self.objective.is_quadratic

@property
def type(self):
Expand Down Expand Up @@ -995,7 +982,7 @@
**solver_options,
)

self.objective_value = solved.objective_value
self.objective.value = solved.objective.value
self.status = solved.status
self.termination_condition = solved.termination_condition
for k, v in self.variables.items():
Expand Down Expand Up @@ -1057,7 +1044,7 @@

result.info()

self.objective_value = result.solution.objective
self.objective._value = result.solution.objective
self.status = result.status.status.value
self.termination_condition = result.status.termination_condition.value
self.solver_model = result.solver_model
Expand Down
Loading
Loading