mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-08 02:18:51 -06:00
Extract LHS as a sparse matrix
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user