|
|
|
@ -6,7 +6,7 @@ import logging
|
|
|
|
|
import re
|
|
|
|
|
import sys
|
|
|
|
|
from io import StringIO
|
|
|
|
|
from typing import Any, List, Dict, Optional, Tuple
|
|
|
|
|
from typing import Any, List, Dict, Optional
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
import pyomo
|
|
|
|
@ -18,6 +18,7 @@ from pyomo.core.base.constraint import ConstraintList
|
|
|
|
|
from pyomo.core.expr.numeric_expr import SumExpression, MonomialTermExpression
|
|
|
|
|
from pyomo.opt import TerminationCondition
|
|
|
|
|
from pyomo.opt.base.solvers import SolverFactory
|
|
|
|
|
from scipy.sparse import coo_matrix
|
|
|
|
|
|
|
|
|
|
from miplearn.instance.base import Instance
|
|
|
|
|
from miplearn.solvers import _RedirectOutput, _none_if_empty
|
|
|
|
@ -58,6 +59,7 @@ class BasePyomoSolver(InternalSolver):
|
|
|
|
|
self._pyomo_solver: SolverFactory = solver_factory
|
|
|
|
|
self._obj_sense: str = "min"
|
|
|
|
|
self._varname_to_var: Dict[bytes, pe.Var] = {}
|
|
|
|
|
self._varname_to_idx: Dict[str, int] = {}
|
|
|
|
|
self._cname_to_constr: Dict[str, pe.Constraint] = {}
|
|
|
|
|
self._termination_condition: str = ""
|
|
|
|
|
self._has_lp_solution = False
|
|
|
|
@ -84,23 +86,24 @@ class BasePyomoSolver(InternalSolver):
|
|
|
|
|
assert cf.lhs is not None
|
|
|
|
|
assert cf.rhs is not None
|
|
|
|
|
assert self.model is not None
|
|
|
|
|
for (i, name) in enumerate(cf.names):
|
|
|
|
|
lhs = 0.0
|
|
|
|
|
for (varname, coeff) in cf.lhs[i]:
|
|
|
|
|
var = self._varname_to_var[varname]
|
|
|
|
|
lhs += var * coeff
|
|
|
|
|
lhs = cf.lhs.tocsr()
|
|
|
|
|
for i in range(len(cf.names)):
|
|
|
|
|
row = lhs[i, :]
|
|
|
|
|
lhsi = 0.0
|
|
|
|
|
for j in range(row.getnnz()):
|
|
|
|
|
lhsi += self._all_vars[row.indices[j]] * row.data[j]
|
|
|
|
|
if cf.senses[i] == b"=":
|
|
|
|
|
expr = lhs == cf.rhs[i]
|
|
|
|
|
expr = lhsi == cf.rhs[i]
|
|
|
|
|
elif cf.senses[i] == b"<":
|
|
|
|
|
expr = lhs <= cf.rhs[i]
|
|
|
|
|
expr = lhsi <= cf.rhs[i]
|
|
|
|
|
elif cf.senses[i] == b">":
|
|
|
|
|
expr = lhs >= cf.rhs[i]
|
|
|
|
|
expr = lhsi >= cf.rhs[i]
|
|
|
|
|
else:
|
|
|
|
|
raise Exception(f"Unknown sense: {cf.senses[i]}")
|
|
|
|
|
cl = pe.Constraint(expr=expr, name=name)
|
|
|
|
|
self.model.add_component(name.decode(), cl)
|
|
|
|
|
cl = pe.Constraint(expr=expr, name=cf.names[i])
|
|
|
|
|
self.model.add_component(cf.names[i].decode(), cl)
|
|
|
|
|
self._pyomo_solver.add_constraint(cl)
|
|
|
|
|
self._cname_to_constr[name] = cl
|
|
|
|
|
self._cname_to_constr[cf.names[i]] = cl
|
|
|
|
|
self._termination_condition = ""
|
|
|
|
|
self._has_lp_solution = False
|
|
|
|
|
self._has_mip_solution = False
|
|
|
|
@ -119,18 +122,18 @@ class BasePyomoSolver(InternalSolver):
|
|
|
|
|
assert cf.lhs is not None
|
|
|
|
|
assert cf.rhs is not None
|
|
|
|
|
assert cf.senses is not None
|
|
|
|
|
x = [v.value for v in self._all_vars]
|
|
|
|
|
lhs = cf.lhs.tocsr() * x
|
|
|
|
|
result = []
|
|
|
|
|
for (i, name) in enumerate(cf.names):
|
|
|
|
|
lhs = 0.0
|
|
|
|
|
for (varname, coeff) in cf.lhs[i]:
|
|
|
|
|
var = self._varname_to_var[varname]
|
|
|
|
|
lhs += var.value * coeff
|
|
|
|
|
if cf.senses[i] == "<":
|
|
|
|
|
result.append(lhs <= cf.rhs[i] + tol)
|
|
|
|
|
elif cf.senses[i] == ">":
|
|
|
|
|
result.append(lhs >= cf.rhs[i] - tol)
|
|
|
|
|
for i in range(len(lhs)):
|
|
|
|
|
if cf.senses[i] == b"<":
|
|
|
|
|
result.append(lhs[i] <= cf.rhs[i] + tol)
|
|
|
|
|
elif cf.senses[i] == b">":
|
|
|
|
|
result.append(lhs[i] >= cf.rhs[i] - tol)
|
|
|
|
|
elif cf.senses[i] == b"=":
|
|
|
|
|
result.append(abs(cf.rhs[i] - lhs[i]) < tol)
|
|
|
|
|
else:
|
|
|
|
|
result.append(abs(cf.rhs[i] - lhs) < tol)
|
|
|
|
|
raise Exception(f"unknown sense: {cf.senses[i]}")
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
@overrides
|
|
|
|
@ -163,15 +166,17 @@ class BasePyomoSolver(InternalSolver):
|
|
|
|
|
) -> Constraints:
|
|
|
|
|
model = self.model
|
|
|
|
|
assert model is not None
|
|
|
|
|
|
|
|
|
|
names: List[str] = []
|
|
|
|
|
rhs: List[float] = []
|
|
|
|
|
lhs: List[List[Tuple[bytes, float]]] = []
|
|
|
|
|
senses: List[str] = []
|
|
|
|
|
dual_values: List[float] = []
|
|
|
|
|
slacks: List[float] = []
|
|
|
|
|
lhs_row: List[int] = []
|
|
|
|
|
lhs_col: List[int] = []
|
|
|
|
|
lhs_data: List[float] = []
|
|
|
|
|
lhs: Optional[coo_matrix] = None
|
|
|
|
|
|
|
|
|
|
def _parse_constraint(c: pe.Constraint) -> None:
|
|
|
|
|
def _parse_constraint(c: pe.Constraint, row: int) -> None:
|
|
|
|
|
assert model is not None
|
|
|
|
|
if with_static:
|
|
|
|
|
# Extract RHS and sense
|
|
|
|
@ -192,30 +197,31 @@ class BasePyomoSolver(InternalSolver):
|
|
|
|
|
|
|
|
|
|
if with_lhs:
|
|
|
|
|
# Extract LHS
|
|
|
|
|
lhsc = []
|
|
|
|
|
expr = c.body
|
|
|
|
|
if isinstance(expr, SumExpression):
|
|
|
|
|
for term in expr._args_:
|
|
|
|
|
if isinstance(term, MonomialTermExpression):
|
|
|
|
|
lhsc.append(
|
|
|
|
|
(
|
|
|
|
|
term._args_[1].name.encode(),
|
|
|
|
|
float(term._args_[0]),
|
|
|
|
|
)
|
|
|
|
|
lhs_row.append(row)
|
|
|
|
|
lhs_col.append(
|
|
|
|
|
self._varname_to_idx[term._args_[1].name]
|
|
|
|
|
)
|
|
|
|
|
lhs_data.append(float(term._args_[0]))
|
|
|
|
|
elif isinstance(term, _GeneralVarData):
|
|
|
|
|
lhsc.append((term.name.encode(), 1.0))
|
|
|
|
|
lhs_row.append(row)
|
|
|
|
|
lhs_col.append(self._varname_to_idx[term.name])
|
|
|
|
|
lhs_data.append(1.0)
|
|
|
|
|
else:
|
|
|
|
|
raise Exception(
|
|
|
|
|
f"Unknown term type: {term.__class__.__name__}"
|
|
|
|
|
)
|
|
|
|
|
elif isinstance(expr, _GeneralVarData):
|
|
|
|
|
lhsc.append((expr.name.encode(), 1.0))
|
|
|
|
|
lhs_row.append(row)
|
|
|
|
|
lhs_col.append(self._varname_to_idx[expr.name])
|
|
|
|
|
lhs_data.append(1.0)
|
|
|
|
|
else:
|
|
|
|
|
raise Exception(
|
|
|
|
|
f"Unknown expression type: {expr.__class__.__name__}"
|
|
|
|
|
)
|
|
|
|
|
lhs.append(lhsc)
|
|
|
|
|
|
|
|
|
|
# Extract dual values
|
|
|
|
|
if self._has_lp_solution:
|
|
|
|
@ -225,20 +231,26 @@ class BasePyomoSolver(InternalSolver):
|
|
|
|
|
if self._has_mip_solution or self._has_lp_solution:
|
|
|
|
|
slacks.append(model.slack[c])
|
|
|
|
|
|
|
|
|
|
for constr in model.component_objects(pyomo.core.Constraint):
|
|
|
|
|
curr_row = 0
|
|
|
|
|
for (i, constr) in enumerate(model.component_objects(pyomo.core.Constraint)):
|
|
|
|
|
if isinstance(constr, pe.ConstraintList):
|
|
|
|
|
for idx in constr:
|
|
|
|
|
names.append(f"{constr.name}[{idx}]")
|
|
|
|
|
_parse_constraint(constr[idx])
|
|
|
|
|
names.append(constr[idx].name)
|
|
|
|
|
_parse_constraint(constr[idx], curr_row)
|
|
|
|
|
curr_row += 1
|
|
|
|
|
else:
|
|
|
|
|
names.append(constr.name)
|
|
|
|
|
_parse_constraint(constr)
|
|
|
|
|
_parse_constraint(constr, curr_row)
|
|
|
|
|
curr_row += 1
|
|
|
|
|
|
|
|
|
|
if len(lhs_data) > 0:
|
|
|
|
|
lhs = coo_matrix((lhs_data, (lhs_row, lhs_col))).tocoo()
|
|
|
|
|
|
|
|
|
|
return Constraints(
|
|
|
|
|
names=_none_if_empty(np.array(names, dtype="S")),
|
|
|
|
|
rhs=_none_if_empty(np.array(rhs, dtype=float)),
|
|
|
|
|
senses=_none_if_empty(np.array(senses, dtype="S")),
|
|
|
|
|
lhs=_none_if_empty(lhs),
|
|
|
|
|
lhs=lhs,
|
|
|
|
|
slacks=_none_if_empty(np.array(slacks, dtype=float)),
|
|
|
|
|
dual_values=_none_if_empty(np.array(dual_values, dtype=float)),
|
|
|
|
|
)
|
|
|
|
@ -264,7 +276,7 @@ class BasePyomoSolver(InternalSolver):
|
|
|
|
|
for index in var:
|
|
|
|
|
if var[index].fixed:
|
|
|
|
|
continue
|
|
|
|
|
solution[f"{var}[{index}]".encode()] = var[index].value
|
|
|
|
|
solution[var[index].name.encode()] = var[index].value
|
|
|
|
|
return solution
|
|
|
|
|
|
|
|
|
|
@overrides
|
|
|
|
@ -289,9 +301,9 @@ class BasePyomoSolver(InternalSolver):
|
|
|
|
|
|
|
|
|
|
# Variable name
|
|
|
|
|
if idx is None:
|
|
|
|
|
names.append(str(var))
|
|
|
|
|
names.append(var.name)
|
|
|
|
|
else:
|
|
|
|
|
names.append(f"{var}[{idx}]")
|
|
|
|
|
names.append(var[idx].name)
|
|
|
|
|
|
|
|
|
|
if with_static:
|
|
|
|
|
# Variable type
|
|
|
|
@ -556,12 +568,14 @@ class BasePyomoSolver(InternalSolver):
|
|
|
|
|
self._all_vars = []
|
|
|
|
|
self._bin_vars = []
|
|
|
|
|
self._varname_to_var = {}
|
|
|
|
|
self._varname_to_idx = {}
|
|
|
|
|
for var in self.model.component_objects(Var):
|
|
|
|
|
for idx in var:
|
|
|
|
|
varname = f"{var.name}[{idx}]".encode()
|
|
|
|
|
if idx is None:
|
|
|
|
|
varname = var.name.encode()
|
|
|
|
|
self._varname_to_var[varname] = var[idx]
|
|
|
|
|
varname = var.name
|
|
|
|
|
if idx is not None:
|
|
|
|
|
varname = var[idx].name
|
|
|
|
|
self._varname_to_var[varname.encode()] = var[idx]
|
|
|
|
|
self._varname_to_idx[varname] = len(self._all_vars)
|
|
|
|
|
self._all_vars += [var[idx]]
|
|
|
|
|
if var[idx].domain == pyomo.core.base.set_types.Binary:
|
|
|
|
|
self._bin_vars += [var[idx]]
|
|
|
|
@ -575,7 +589,7 @@ class BasePyomoSolver(InternalSolver):
|
|
|
|
|
for constr in self.model.component_objects(pyomo.core.Constraint):
|
|
|
|
|
if isinstance(constr, pe.ConstraintList):
|
|
|
|
|
for idx in constr:
|
|
|
|
|
self._cname_to_constr[f"{constr.name}[{idx}]"] = constr[idx]
|
|
|
|
|
self._cname_to_constr[constr[idx].name] = constr[idx]
|
|
|
|
|
else:
|
|
|
|
|
self._cname_to_constr[constr.name] = constr
|
|
|
|
|
|
|
|
|
|