Rename Variables and Constraints; move to internal.py

master
Alinson S. Xavier 4 years ago
parent c8c29138ca
commit 609c5c7694
No known key found for this signature in database
GPG Key ID: DCA0DAD4D2F58624

@ -12,7 +12,8 @@ from miplearn.classifiers import Classifier
from miplearn.classifiers.counting import CountingClassifier
from miplearn.classifiers.threshold import MinProbabilityThreshold, Threshold
from miplearn.components.component import Component
from miplearn.features import Sample, ConstraintFeatures
from miplearn.features import Sample
from miplearn.solvers.internal import Constraints
from miplearn.instance.base import Instance
from miplearn.types import LearningSolveStats
@ -45,7 +46,7 @@ class StaticLazyConstraintsComponent(Component):
self.threshold_prototype: Threshold = threshold
self.classifiers: Dict[Hashable, Classifier] = {}
self.thresholds: Dict[Hashable, Threshold] = {}
self.pool: ConstraintFeatures = ConstraintFeatures()
self.pool: Constraints = Constraints()
self.violation_tolerance: float = violation_tolerance
self.enforced_cids: Set[Hashable] = set()
self.n_restored: int = 0
@ -82,7 +83,7 @@ class StaticLazyConstraintsComponent(Component):
logger.info("Instance does not have static lazy constraints. Skipping.")
self.enforced_cids = set(self.sample_predict(sample))
logger.info("Moving lazy constraints to the pool...")
constraints = ConstraintFeatures.from_sample(sample)
constraints = Constraints.from_sample(sample)
assert constraints.lazy is not None
assert constraints.names is not None
selected = [

@ -4,9 +4,8 @@
import collections
import numbers
from dataclasses import dataclass
from math import log, isfinite
from typing import TYPE_CHECKING, Dict, Optional, List, Hashable, Tuple, Any
from typing import TYPE_CHECKING, Dict, Optional, List, Hashable, Any
import numpy as np
@ -15,76 +14,6 @@ if TYPE_CHECKING:
from miplearn.instance.base import Instance
@dataclass
class VariableFeatures:
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 ConstraintFeatures:
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") -> "ConstraintFeatures":
return ConstraintFeatures(
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]) -> "ConstraintFeatures":
return ConstraintFeatures(
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 Sample:
def __init__(
self,

@ -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,

@ -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.

@ -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),

@ -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=[

@ -11,9 +11,9 @@ from numpy.testing import assert_array_equal
from miplearn.classifiers import Classifier
from miplearn.classifiers.threshold import Threshold, MinProbabilityThreshold
from miplearn.components.static_lazy import StaticLazyConstraintsComponent
from miplearn.features import Sample, ConstraintFeatures
from miplearn.features import Sample
from miplearn.instance.base import Instance
from miplearn.solvers.internal import InternalSolver
from miplearn.solvers.internal import InternalSolver, Constraints
from miplearn.solvers.learning import LearningSolver
from miplearn.types import (
LearningSolveStats,
@ -118,7 +118,7 @@ def test_usage_with_solver(instance: Instance) -> None:
# Should ask internal solver to verify if constraints in the pool are
# satisfied and add the ones that are not
c = ConstraintFeatures.from_sample(sample)[[False, False, True, False, False]]
c = Constraints.from_sample(sample)[[False, False, True, False, False]]
internal.are_constraints_satisfied.assert_called_once_with(c, tol=1.0)
internal.are_constraints_satisfied.reset_mock()
internal.add_constraints.assert_called_once_with(c)

@ -6,10 +6,9 @@ import numpy as np
from miplearn.features import (
FeaturesExtractor,
VariableFeatures,
ConstraintFeatures,
Sample,
)
from miplearn.solvers.internal import Variables, Constraints
from miplearn.solvers.gurobi import GurobiSolver
from miplearn.solvers.tests import assert_equals
@ -129,7 +128,7 @@ def test_knapsack() -> None:
def test_constraint_getindex() -> None:
cf = ConstraintFeatures(
cf = Constraints(
names=["c1", "c2", "c3"],
rhs=[1.0, 2.0, 3.0],
senses=["=", "<", ">"],
@ -150,7 +149,7 @@ def test_constraint_getindex() -> None:
)
assert_equals(
cf[[True, False, True]],
ConstraintFeatures(
Constraints(
names=["c1", "c3"],
rhs=[1.0, 3.0],
senses=["=", ">"],
@ -177,8 +176,8 @@ def test_assert_equals() -> None:
np.array([[1.0, 2.0], [3.0, 4.0]]),
)
assert_equals(
VariableFeatures(values=np.array([1.0, 2.0])), # type: ignore
VariableFeatures(values=np.array([1.0, 2.0])), # type: ignore
Variables(values=np.array([1.0, 2.0])), # type: ignore
Variables(values=np.array([1.0, 2.0])), # type: ignore
)
assert_equals(np.array([True, True]), [True, True])
assert_equals((1.0,), (1.0,))

Loading…
Cancel
Save