mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 01:18:52 -06:00
Solver: Make attributes private; ensure we're not calling them directly
Helps with Julia/JuMP integration.
This commit is contained in:
@@ -68,8 +68,7 @@ class BasicCollector:
|
|||||||
|
|
||||||
if self.write_mps:
|
if self.write_mps:
|
||||||
# Add lazy constraints to model
|
# Add lazy constraints to model
|
||||||
if model.lazy_enforce is not None:
|
model._lazy_enforce_collected()
|
||||||
model.lazy_enforce(model, model.lazy_)
|
|
||||||
|
|
||||||
# Save MPS file
|
# Save MPS file
|
||||||
model.write(mps_filename)
|
model.write(mps_filename)
|
||||||
|
|||||||
@@ -24,10 +24,8 @@ class MemorizingLazyComponent(_BaseMemorizingConstrComponent):
|
|||||||
model: AbstractModel,
|
model: AbstractModel,
|
||||||
stats: Dict[str, Any],
|
stats: Dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
if model.lazy_enforce is None:
|
|
||||||
return
|
|
||||||
assert self.constrs_ is not None
|
assert self.constrs_ is not None
|
||||||
violations = self.predict("Predicting violated lazy constraints...", test_h5)
|
violations = self.predict("Predicting violated lazy constraints...", test_h5)
|
||||||
logger.info(f"Enforcing {len(violations)} constraints ahead-of-time...")
|
logger.info(f"Enforcing {len(violations)} constraints ahead-of-time...")
|
||||||
model.lazy_enforce(model, violations)
|
model.lazy_enforce(violations)
|
||||||
stats["Lazy Constraints: AOT"] = len(violations)
|
stats["Lazy Constraints: AOT"] = len(violations)
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ class AbstractModel(ABC):
|
|||||||
WHERE_LAZY = "lazy"
|
WHERE_LAZY = "lazy"
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.lazy_enforce: Optional[Callable] = None
|
self._lazy_enforce: Optional[Callable] = None
|
||||||
self.lazy_separate: Optional[Callable] = None
|
self._lazy_separate: Optional[Callable] = None
|
||||||
self.lazy_: Optional[List[Any]] = None
|
self._lazy: Optional[List[Any]] = None
|
||||||
self.cuts_enforce: Optional[Callable] = None
|
self._cuts_enforce: Optional[Callable] = None
|
||||||
self.cuts_separate: Optional[Callable] = None
|
self._cuts_separate: Optional[Callable] = None
|
||||||
self.cuts_: Optional[List[Any]] = None
|
self._cuts: Optional[List[Any]] = None
|
||||||
self.cuts_aot_: Optional[List[Any]] = None
|
self._cuts_aot: Optional[List[Any]] = None
|
||||||
self.where = self.WHERE_DEFAULT
|
self._where = self.WHERE_DEFAULT
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def add_constrs(
|
def add_constrs(
|
||||||
@@ -85,3 +85,13 @@ class AbstractModel(ABC):
|
|||||||
|
|
||||||
def set_cuts(self, cuts: List) -> None:
|
def set_cuts(self, cuts: List) -> None:
|
||||||
self.cuts_aot_ = cuts
|
self.cuts_aot_ = cuts
|
||||||
|
|
||||||
|
def lazy_enforce(self, violations: List[Any]) -> None:
|
||||||
|
if self._lazy_enforce is not None:
|
||||||
|
self._lazy_enforce(self, violations)
|
||||||
|
|
||||||
|
def _lazy_enforce_collected(self) -> None:
|
||||||
|
"""Adds all lazy constraints identified in the callback as actual model constraints. Useful for generating
|
||||||
|
a final MPS file with the constraints that were required in this run."""
|
||||||
|
if self._lazy_enforce is not None:
|
||||||
|
self._lazy_enforce(self, self._lazy)
|
||||||
|
|||||||
@@ -21,36 +21,36 @@ def _gurobi_callback(model: AbstractModel, gp_model: gp.Model, where: int) -> No
|
|||||||
assert isinstance(gp_model, gp.Model)
|
assert isinstance(gp_model, gp.Model)
|
||||||
|
|
||||||
# Lazy constraints
|
# Lazy constraints
|
||||||
if model.lazy_separate is not None:
|
if model._lazy_separate is not None:
|
||||||
assert model.lazy_enforce is not None
|
assert model._lazy_enforce is not None
|
||||||
assert model.lazy_ is not None
|
assert model._lazy is not None
|
||||||
if where == GRB.Callback.MIPSOL:
|
if where == GRB.Callback.MIPSOL:
|
||||||
model.where = model.WHERE_LAZY
|
model._where = model.WHERE_LAZY
|
||||||
violations = model.lazy_separate(model)
|
violations = model._lazy_separate(model)
|
||||||
if len(violations) > 0:
|
if len(violations) > 0:
|
||||||
model.lazy_.extend(violations)
|
model._lazy.extend(violations)
|
||||||
model.lazy_enforce(model, violations)
|
model._lazy_enforce(model, violations)
|
||||||
|
|
||||||
# User cuts
|
# User cuts
|
||||||
if model.cuts_separate is not None:
|
if model._cuts_separate is not None:
|
||||||
assert model.cuts_enforce is not None
|
assert model._cuts_enforce is not None
|
||||||
assert model.cuts_ is not None
|
assert model._cuts is not None
|
||||||
if where == GRB.Callback.MIPNODE:
|
if where == GRB.Callback.MIPNODE:
|
||||||
status = gp_model.cbGet(GRB.Callback.MIPNODE_STATUS)
|
status = gp_model.cbGet(GRB.Callback.MIPNODE_STATUS)
|
||||||
if status == GRB.OPTIMAL:
|
if status == GRB.OPTIMAL:
|
||||||
model.where = model.WHERE_CUTS
|
model._where = model.WHERE_CUTS
|
||||||
if model.cuts_aot_ is not None:
|
if model._cuts_aot is not None:
|
||||||
violations = model.cuts_aot_
|
violations = model._cuts_aot
|
||||||
model.cuts_aot_ = None
|
model._cuts_aot = None
|
||||||
logger.info(f"Enforcing {len(violations)} cuts ahead-of-time...")
|
logger.info(f"Enforcing {len(violations)} cuts ahead-of-time...")
|
||||||
else:
|
else:
|
||||||
violations = model.cuts_separate(model)
|
violations = model._cuts_separate(model)
|
||||||
if len(violations) > 0:
|
if len(violations) > 0:
|
||||||
model.cuts_.extend(violations)
|
model._cuts.extend(violations)
|
||||||
model.cuts_enforce(model, violations)
|
model._cuts_enforce(model, violations)
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
model.where = model.WHERE_DEFAULT
|
model._where = model.WHERE_DEFAULT
|
||||||
|
|
||||||
|
|
||||||
def _gurobi_add_constr(gp_model: gp.Model, where: str, constr: Any) -> None:
|
def _gurobi_add_constr(gp_model: gp.Model, where: str, constr: Any) -> None:
|
||||||
@@ -64,11 +64,11 @@ def _gurobi_add_constr(gp_model: gp.Model, where: str, constr: Any) -> None:
|
|||||||
|
|
||||||
def _gurobi_set_required_params(model: AbstractModel, gp_model: gp.Model) -> None:
|
def _gurobi_set_required_params(model: AbstractModel, gp_model: gp.Model) -> None:
|
||||||
# Required parameters for lazy constraints
|
# Required parameters for lazy constraints
|
||||||
if model.lazy_enforce is not None:
|
if model._lazy_enforce is not None:
|
||||||
gp_model.setParam("PreCrush", 1)
|
gp_model.setParam("PreCrush", 1)
|
||||||
gp_model.setParam("LazyConstraints", 1)
|
gp_model.setParam("LazyConstraints", 1)
|
||||||
# Required parameters for user cuts
|
# Required parameters for user cuts
|
||||||
if model.cuts_enforce is not None:
|
if model._cuts_enforce is not None:
|
||||||
gp_model.setParam("PreCrush", 1)
|
gp_model.setParam("PreCrush", 1)
|
||||||
|
|
||||||
|
|
||||||
@@ -87,10 +87,10 @@ class GurobiModel(AbstractModel):
|
|||||||
cuts_enforce: Optional[Callable] = None,
|
cuts_enforce: Optional[Callable] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.lazy_separate = lazy_separate
|
self._lazy_separate = lazy_separate
|
||||||
self.lazy_enforce = lazy_enforce
|
self._lazy_enforce = lazy_enforce
|
||||||
self.cuts_separate = cuts_separate
|
self._cuts_separate = cuts_separate
|
||||||
self.cuts_enforce = cuts_enforce
|
self._cuts_enforce = cuts_enforce
|
||||||
self.inner = inner
|
self.inner = inner
|
||||||
|
|
||||||
def add_constrs(
|
def add_constrs(
|
||||||
@@ -118,7 +118,7 @@ class GurobiModel(AbstractModel):
|
|||||||
stats["Added constraints"] += nconstrs
|
stats["Added constraints"] += nconstrs
|
||||||
|
|
||||||
def add_constr(self, constr: Any) -> None:
|
def add_constr(self, constr: Any) -> None:
|
||||||
_gurobi_add_constr(self.inner, self.where, constr)
|
_gurobi_add_constr(self.inner, self._where, constr)
|
||||||
|
|
||||||
def extract_after_load(self, h5: H5File) -> None:
|
def extract_after_load(self, h5: H5File) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -168,10 +168,10 @@ class GurobiModel(AbstractModel):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
self._extract_after_mip_solution_pool(h5)
|
self._extract_after_mip_solution_pool(h5)
|
||||||
if self.lazy_ is not None:
|
if self._lazy is not None:
|
||||||
h5.put_scalar("mip_lazy", json.dumps(self.lazy_))
|
h5.put_scalar("mip_lazy", json.dumps(self._lazy))
|
||||||
if self.cuts_ is not None:
|
if self._cuts is not None:
|
||||||
h5.put_scalar("mip_cuts", json.dumps(self.cuts_))
|
h5.put_scalar("mip_cuts", json.dumps(self._cuts))
|
||||||
|
|
||||||
def fix_variables(
|
def fix_variables(
|
||||||
self,
|
self,
|
||||||
@@ -196,8 +196,8 @@ class GurobiModel(AbstractModel):
|
|||||||
stats["Fixed variables"] = n_fixed
|
stats["Fixed variables"] = n_fixed
|
||||||
|
|
||||||
def optimize(self) -> None:
|
def optimize(self) -> None:
|
||||||
self.lazy_ = []
|
self._lazy = []
|
||||||
self.cuts_ = []
|
self._cuts = []
|
||||||
|
|
||||||
def callback(_: gp.Model, where: int) -> None:
|
def callback(_: gp.Model, where: int) -> None:
|
||||||
_gurobi_callback(self, self.inner, where)
|
_gurobi_callback(self, self.inner, where)
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ class PyomoModel(AbstractModel):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.inner = model
|
self.inner = model
|
||||||
self.solver_name = solver_name
|
self.solver_name = solver_name
|
||||||
self.lazy_separate = lazy_separate
|
self._lazy_separate = lazy_separate
|
||||||
self.lazy_enforce = lazy_enforce
|
self._lazy_enforce = lazy_enforce
|
||||||
self.cuts_separate = cuts_separate
|
self._cuts_separate = cuts_separate
|
||||||
self.cuts_enforce = cuts_enforce
|
self._cuts_enforce = cuts_enforce
|
||||||
self.solver = pe.SolverFactory(solver_name)
|
self.solver = pe.SolverFactory(solver_name)
|
||||||
self.is_persistent = hasattr(self.solver, "set_instance")
|
self.is_persistent = hasattr(self.solver, "set_instance")
|
||||||
if self.is_persistent:
|
if self.is_persistent:
|
||||||
@@ -53,8 +53,8 @@ class PyomoModel(AbstractModel):
|
|||||||
assert (
|
assert (
|
||||||
self.solver_name == "gurobi_persistent"
|
self.solver_name == "gurobi_persistent"
|
||||||
), "Callbacks are currently only supported on gurobi_persistent"
|
), "Callbacks are currently only supported on gurobi_persistent"
|
||||||
if self.where in [AbstractModel.WHERE_CUTS, AbstractModel.WHERE_LAZY]:
|
if self._where in [AbstractModel.WHERE_CUTS, AbstractModel.WHERE_LAZY]:
|
||||||
_gurobi_add_constr(self.solver, self.where, constr)
|
_gurobi_add_constr(self.solver, self._where, constr)
|
||||||
else:
|
else:
|
||||||
# outside callbacks, add_constr shouldn't do anything, as the constraint
|
# outside callbacks, add_constr shouldn't do anything, as the constraint
|
||||||
# has already been added to the ConstraintList object
|
# has already been added to the ConstraintList object
|
||||||
@@ -129,10 +129,10 @@ class PyomoModel(AbstractModel):
|
|||||||
h5.put_scalar("mip_obj_value", obj_value)
|
h5.put_scalar("mip_obj_value", obj_value)
|
||||||
h5.put_scalar("mip_obj_bound", obj_bound)
|
h5.put_scalar("mip_obj_bound", obj_bound)
|
||||||
h5.put_scalar("mip_gap", self._gap(obj_value, obj_bound))
|
h5.put_scalar("mip_gap", self._gap(obj_value, obj_bound))
|
||||||
if self.lazy_ is not None:
|
if self._lazy is not None:
|
||||||
h5.put_scalar("mip_lazy", repr(self.lazy_))
|
h5.put_scalar("mip_lazy", repr(self._lazy))
|
||||||
if self.cuts_ is not None:
|
if self._cuts is not None:
|
||||||
h5.put_scalar("mip_cuts", repr(self.cuts_))
|
h5.put_scalar("mip_cuts", repr(self._cuts))
|
||||||
|
|
||||||
def fix_variables(
|
def fix_variables(
|
||||||
self,
|
self,
|
||||||
@@ -147,10 +147,10 @@ class PyomoModel(AbstractModel):
|
|||||||
self.solver.update_var(var)
|
self.solver.update_var(var)
|
||||||
|
|
||||||
def optimize(self) -> None:
|
def optimize(self) -> None:
|
||||||
self.lazy_ = []
|
self._lazy = []
|
||||||
self.cuts_ = []
|
self._cuts = []
|
||||||
|
|
||||||
if self.lazy_enforce is not None or self.cuts_enforce is not None:
|
if self._lazy_enforce is not None or self._cuts_enforce is not None:
|
||||||
assert (
|
assert (
|
||||||
self.solver_name == "gurobi_persistent"
|
self.solver_name == "gurobi_persistent"
|
||||||
), "Callbacks are currently only supported on gurobi_persistent"
|
), "Callbacks are currently only supported on gurobi_persistent"
|
||||||
|
|||||||
@@ -39,6 +39,6 @@ def _build_model() -> PyomoModel:
|
|||||||
def test_pyomo_callback() -> None:
|
def test_pyomo_callback() -> None:
|
||||||
model = _build_model()
|
model = _build_model()
|
||||||
model.optimize()
|
model.optimize()
|
||||||
assert model.lazy_ is not None
|
assert model._lazy is not None
|
||||||
assert len(model.lazy_) > 0
|
assert len(model._lazy) > 0
|
||||||
assert model.inner.x.value == 0.0
|
assert model.inner.x.value == 0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user