Rename Variables and Constraints; move to internal.py

This commit is contained in:
2021-07-06 17:08:22 -05:00
parent c8c29138ca
commit 609c5c7694
8 changed files with 116 additions and 114 deletions

View File

@@ -10,7 +10,6 @@ from typing import List, Any, Dict, Optional, Hashable, Tuple, TYPE_CHECKING
from overrides import overrides
from miplearn.features import VariableFeatures, ConstraintFeatures
from miplearn.instance.base import Instance
from miplearn.solvers import _RedirectOutput
from miplearn.solvers.internal import (
@@ -19,6 +18,8 @@ from miplearn.solvers.internal import (
IterationCallback,
LazyCallback,
MIPSolveStats,
Variables,
Constraints,
)
from miplearn.solvers.pyomo.base import PyomoTestInstanceKnapsack
from miplearn.types import (
@@ -91,7 +92,7 @@ class GurobiSolver(InternalSolver):
]
@overrides
def add_constraints(self, cf: ConstraintFeatures) -> None:
def add_constraints(self, cf: Constraints) -> None:
assert cf.names is not None
assert cf.senses is not None
assert cf.lhs is not None
@@ -120,7 +121,7 @@ class GurobiSolver(InternalSolver):
@overrides
def are_constraints_satisfied(
self,
cf: ConstraintFeatures,
cf: Constraints,
tol: float = 1e-5,
) -> List[bool]:
assert cf.names is not None
@@ -196,7 +197,7 @@ class GurobiSolver(InternalSolver):
with_static: bool = True,
with_sa: bool = True,
with_lhs: bool = True,
) -> ConstraintFeatures:
) -> Constraints:
model = self.model
assert model is not None
assert model.numVars == len(self._gp_vars)
@@ -241,7 +242,7 @@ class GurobiSolver(InternalSolver):
if self._has_lp_solution or self._has_mip_solution:
slacks = model.getAttr("slack", gp_constrs)
return ConstraintFeatures(
return Constraints(
basis_status=basis_status,
dual_values=dual_value,
lhs=lhs,
@@ -300,7 +301,7 @@ class GurobiSolver(InternalSolver):
self,
with_static: bool = True,
with_sa: bool = True,
) -> VariableFeatures:
) -> Variables:
model = self.model
assert model is not None
@@ -347,7 +348,7 @@ class GurobiSolver(InternalSolver):
if model.solCount > 0:
values = model.getAttr("x", self._gp_vars)
return VariableFeatures(
return Variables(
names=self._var_names,
upper_bounds=upper_bounds,
lower_bounds=lower_bounds,

View File

@@ -5,9 +5,8 @@
import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any, List, Optional, List
from typing import Any, Optional, List, Tuple, TYPE_CHECKING
from miplearn.features import VariableFeatures, ConstraintFeatures
from miplearn.instance.base import Instance
from miplearn.types import (
IterationCallback,
@@ -18,6 +17,9 @@ from miplearn.types import (
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from miplearn.features import Sample
@dataclass
class LPSolveStats:
@@ -44,20 +46,90 @@ class MIPSolveStats:
mip_warm_start_value: Optional[float] = None
@dataclass
class Variables:
names: Optional[List[str]] = None
basis_status: Optional[List[str]] = None
lower_bounds: Optional[List[float]] = None
obj_coeffs: Optional[List[float]] = None
reduced_costs: Optional[List[float]] = None
sa_lb_down: Optional[List[float]] = None
sa_lb_up: Optional[List[float]] = None
sa_obj_down: Optional[List[float]] = None
sa_obj_up: Optional[List[float]] = None
sa_ub_down: Optional[List[float]] = None
sa_ub_up: Optional[List[float]] = None
types: Optional[List[str]] = None
upper_bounds: Optional[List[float]] = None
values: Optional[List[float]] = None
@dataclass
class Constraints:
basis_status: Optional[List[str]] = None
dual_values: Optional[List[float]] = None
lazy: Optional[List[bool]] = None
lhs: Optional[List[List[Tuple[str, float]]]] = None
names: Optional[List[str]] = None
rhs: Optional[List[float]] = None
sa_rhs_down: Optional[List[float]] = None
sa_rhs_up: Optional[List[float]] = None
senses: Optional[List[str]] = None
slacks: Optional[List[float]] = None
@staticmethod
def from_sample(sample: "Sample") -> "Constraints":
return Constraints(
basis_status=sample.get("lp_constr_basis_status"),
dual_values=sample.get("lp_constr_dual_values"),
lazy=sample.get("constr_lazy"),
lhs=sample.get("constr_lhs"),
names=sample.get("constr_names"),
rhs=sample.get("constr_rhs"),
sa_rhs_down=sample.get("lp_constr_sa_rhs_down"),
sa_rhs_up=sample.get("lp_constr_sa_rhs_up"),
senses=sample.get("constr_senses"),
slacks=sample.get("lp_constr_slacks"),
)
def __getitem__(self, selected: List[bool]) -> "Constraints":
return Constraints(
basis_status=self._filter(self.basis_status, selected),
dual_values=self._filter(self.dual_values, selected),
names=self._filter(self.names, selected),
lazy=self._filter(self.lazy, selected),
lhs=self._filter(self.lhs, selected),
rhs=self._filter(self.rhs, selected),
sa_rhs_down=self._filter(self.sa_rhs_down, selected),
sa_rhs_up=self._filter(self.sa_rhs_up, selected),
senses=self._filter(self.senses, selected),
slacks=self._filter(self.slacks, selected),
)
def _filter(
self,
obj: Optional[List],
selected: List[bool],
) -> Optional[List]:
if obj is None:
return None
return [obj[i] for (i, selected_i) in enumerate(selected) if selected_i]
class InternalSolver(ABC):
"""
Abstract class representing the MIP solver used internally by LearningSolver.
"""
@abstractmethod
def add_constraints(self, cf: ConstraintFeatures) -> None:
def add_constraints(self, cf: Constraints) -> None:
"""Adds the given constraints to the model."""
pass
@abstractmethod
def are_constraints_satisfied(
self,
cf: ConstraintFeatures,
cf: Constraints,
tol: float = 1e-5,
) -> List[bool]:
"""
@@ -133,7 +205,7 @@ class InternalSolver(ABC):
with_static: bool = True,
with_sa: bool = True,
with_lhs: bool = True,
) -> ConstraintFeatures:
) -> Constraints:
pass
@abstractmethod
@@ -149,7 +221,7 @@ class InternalSolver(ABC):
self,
with_static: bool = True,
with_sa: bool = True,
) -> VariableFeatures:
) -> Variables:
"""
Returns a description of the decision variables in the problem.

View File

@@ -19,7 +19,6 @@ from pyomo.core.expr.numeric_expr import SumExpression, MonomialTermExpression
from pyomo.opt import TerminationCondition
from pyomo.opt.base.solvers import SolverFactory
from miplearn.features import VariableFeatures, ConstraintFeatures
from miplearn.instance.base import Instance
from miplearn.solvers import _RedirectOutput, _none_if_empty
from miplearn.solvers.internal import (
@@ -28,6 +27,8 @@ from miplearn.solvers.internal import (
IterationCallback,
LazyCallback,
MIPSolveStats,
Variables,
Constraints,
)
from miplearn.types import (
SolverParams,
@@ -79,7 +80,7 @@ class BasePyomoSolver(InternalSolver):
self._has_mip_solution = False
@overrides
def add_constraints(self, cf: ConstraintFeatures) -> None:
def add_constraints(self, cf: Constraints) -> None:
assert cf.names is not None
assert cf.senses is not None
assert cf.lhs is not None
@@ -111,7 +112,7 @@ class BasePyomoSolver(InternalSolver):
@overrides
def are_constraints_satisfied(
self,
cf: ConstraintFeatures,
cf: Constraints,
tol: float = 1e-5,
) -> List[bool]:
assert cf.names is not None
@@ -159,7 +160,7 @@ class BasePyomoSolver(InternalSolver):
with_static: bool = True,
with_sa: bool = True,
with_lhs: bool = True,
) -> ConstraintFeatures:
) -> Constraints:
model = self.model
assert model is not None
@@ -233,7 +234,7 @@ class BasePyomoSolver(InternalSolver):
names.append(constr.name)
_parse_constraint(constr)
return ConstraintFeatures(
return Constraints(
names=_none_if_empty(names),
rhs=_none_if_empty(rhs),
senses=_none_if_empty(senses),
@@ -271,7 +272,7 @@ class BasePyomoSolver(InternalSolver):
self,
with_static: bool = True,
with_sa: bool = True,
) -> VariableFeatures:
) -> Variables:
assert self.model is not None
names: List[str] = []
@@ -326,7 +327,7 @@ class BasePyomoSolver(InternalSolver):
if self._has_lp_solution or self._has_mip_solution:
values.append(v.value)
return VariableFeatures(
return Variables(
names=_none_if_empty(names),
types=_none_if_empty(types),
upper_bounds=_none_if_empty(upper_bounds),

View File

@@ -6,8 +6,7 @@ from typing import Any, List
import numpy as np
from miplearn.features import VariableFeatures, ConstraintFeatures
from miplearn.solvers.internal import InternalSolver
from miplearn.solvers.internal import InternalSolver, Variables, Constraints
inf = float("inf")
@@ -40,7 +39,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
# Fetch variables (after-load)
assert_equals(
solver.get_variables(),
VariableFeatures(
Variables(
names=["x[0]", "x[1]", "x[2]", "x[3]", "z"],
lower_bounds=[0.0, 0.0, 0.0, 0.0, 0.0],
upper_bounds=[1.0, 1.0, 1.0, 1.0, 67.0],
@@ -52,7 +51,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
# Fetch constraints (after-load)
assert_equals(
solver.get_constraints(),
ConstraintFeatures(
Constraints(
names=["eq_capacity"],
rhs=[0.0],
lhs=[
@@ -83,7 +82,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
solver.get_variables(with_static=False),
_filter_attrs(
solver.get_variable_attrs(),
VariableFeatures(
Variables(
names=["x[0]", "x[1]", "x[2]", "x[3]", "z"],
basis_status=["U", "B", "U", "L", "U"],
reduced_costs=[193.615385, 0.0, 187.230769, -23.692308, 13.538462],
@@ -103,7 +102,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
solver.get_constraints(with_static=False),
_filter_attrs(
solver.get_constraint_attrs(),
ConstraintFeatures(
Constraints(
basis_status=["N"],
dual_values=[13.538462],
names=["eq_capacity"],
@@ -136,7 +135,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
solver.get_variables(with_static=False),
_filter_attrs(
solver.get_variable_attrs(),
VariableFeatures(
Variables(
names=["x[0]", "x[1]", "x[2]", "x[3]", "z"],
values=[1.0, 0.0, 1.0, 1.0, 61.0],
),
@@ -148,7 +147,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
solver.get_constraints(with_static=False),
_filter_attrs(
solver.get_constraint_attrs(),
ConstraintFeatures(
Constraints(
names=["eq_capacity"],
slacks=[0.0],
),
@@ -156,7 +155,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
)
# Build new constraint and verify that it is violated
cf = ConstraintFeatures(
cf = Constraints(
names=["cut"],
lhs=[[("x[0]", 1.0)]],
rhs=[0.0],
@@ -170,7 +169,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
solver.get_constraints(with_static=True),
_filter_attrs(
solver.get_constraint_attrs(),
ConstraintFeatures(
Constraints(
names=["eq_capacity", "cut"],
rhs=[0.0, 0.0],
lhs=[