Sort methods

master
Alinson S. Xavier 5 years ago
parent fcb511a2c6
commit 6bc81417ac
No known key found for this signature in database
GPG Key ID: DCA0DAD4D2F58624

@ -81,6 +81,191 @@ class GurobiSolver(InternalSolver):
self.gp.GRB.Callback.MIPNODE, self.gp.GRB.Callback.MIPNODE,
] ]
@overrides
def add_constraint(self, constr: Constraint, name: str) -> None:
assert self.model is not None
lhs = self.gp.quicksum(
self._varname_to_var[varname] * coeff
for (varname, coeff) in constr.lhs.items()
)
if constr.sense == "=":
self.model.addConstr(lhs == constr.rhs, name=name)
elif constr.sense == "<":
self.model.addConstr(lhs <= constr.rhs, name=name)
else:
self.model.addConstr(lhs >= constr.rhs, name=name)
self._dirty = True
self._has_lp_solution = False
self._has_mip_solution = False
@overrides
def are_callbacks_supported(self) -> bool:
return True
@overrides
def build_test_instance_infeasible(self) -> Instance:
return GurobiTestInstanceInfeasible()
@overrides
def build_test_instance_knapsack(self) -> Instance:
return GurobiTestInstanceKnapsack(
weights=[23.0, 26.0, 20.0, 18.0],
prices=[505.0, 352.0, 458.0, 220.0],
capacity=67.0,
)
@overrides
def build_test_instance_redundancy(self) -> Instance:
return GurobiTestInstanceRedundancy()
@overrides
def clone(self) -> "GurobiSolver":
return GurobiSolver(
params=self.params,
lazy_cb_frequency=self.lazy_cb_frequency,
)
@overrides
def fix(self, solution: Solution) -> None:
self._raise_if_callback()
for (varname, value) in solution.items():
if value is None:
continue
var = self._varname_to_var[varname]
var.vtype = self.gp.GRB.CONTINUOUS
var.lb = value
var.ub = value
@overrides
def get_dual(self, cid: str) -> float:
assert self.model is not None
c = self.model.getConstrByName(cid)
if self.is_infeasible():
return c.farkasDual
else:
return c.pi
@overrides
def get_constraint_attrs(self) -> List[str]:
return [
"basis_status",
"category",
"dual_value",
"lazy",
"lhs",
"rhs",
"sa_rhs_down",
"sa_rhs_up",
"sense",
"slack",
"user_features",
]
@overrides
def get_constraints(self) -> Dict[str, Constraint]:
assert self.model is not None
self._raise_if_callback()
if self._dirty:
self.model.update()
self._dirty = False
constraints: Dict[str, Constraint] = {}
for c in self.model.getConstrs():
constr = self._parse_gurobi_constraint(c)
assert c.constrName not in constraints
constraints[c.constrName] = constr
return constraints
@overrides
def get_sense(self) -> str:
assert self.model is not None
if self.model.modelSense == 1:
return "min"
else:
return "max"
@overrides
def get_solution(self) -> Optional[Solution]:
assert self.model is not None
if self.cb_where is not None:
if self.cb_where == self.gp.GRB.Callback.MIPNODE:
return {
v.varName: self.model.cbGetNodeRel(v) for v in self.model.getVars()
}
elif self.cb_where == self.gp.GRB.Callback.MIPSOL:
return {
v.varName: self.model.cbGetSolution(v) for v in self.model.getVars()
}
else:
raise Exception(
f"get_solution can only be called from a callback "
f"when cb_where is either MIPNODE or MIPSOL"
)
if self.model.solCount == 0:
return None
return {v.varName: v.x for v in self.model.getVars()}
@overrides
def get_variable_attrs(self) -> List[str]:
return [
"basis_status",
"category",
"lower_bound",
"obj_coeff",
"reduced_cost",
"sa_lb_down",
"sa_lb_up",
"sa_obj_down",
"sa_obj_up",
"sa_ub_down",
"sa_ub_up",
"type",
"upper_bound",
"user_features",
"value",
]
@overrides
def get_variable_names(self) -> List[VariableName]:
self._raise_if_callback()
assert self.model is not None
return [v.varName for v in self.model.getVars()]
@overrides
def get_variables(self) -> Dict[str, Variable]:
assert self.model is not None
variables = {}
for gp_var in self.model.getVars():
name = gp_var.varName
assert len(name) > 0, f"empty variable name detected"
assert name not in variables, f"duplicated variable name detected: {name}"
var = self._parse_gurobi_var(gp_var)
variables[name] = var
return variables
@overrides
def is_constraint_satisfied(self, constr: Constraint, tol: float = 1e-6) -> bool:
lhs = 0.0
for (varname, coeff) in constr.lhs.items():
var = self._varname_to_var[varname]
lhs += self._get_value(var) * coeff
if constr.sense == "<":
return lhs <= constr.rhs + tol
elif constr.sense == ">":
return lhs >= constr.rhs - tol
else:
return abs(constr.rhs - lhs) < abs(tol)
@overrides
def is_infeasible(self) -> bool:
assert self.model is not None
return self.model.status in [self.gp.GRB.INFEASIBLE, self.gp.GRB.INF_OR_UNBD]
@overrides
def remove_constraint(self, name: str) -> None:
assert self.model is not None
constr = self.model.getConstrByName(name)
self.model.remove(constr)
@overrides @overrides
def set_instance( def set_instance(
self, self,
@ -96,66 +281,14 @@ class GurobiSolver(InternalSolver):
self.model.update() self.model.update()
self._update_vars() self._update_vars()
def _raise_if_callback(self) -> None:
if self.cb_where is not None:
raise Exception("method cannot be called from a callback")
def _update_vars(self) -> None:
assert self.model is not None
self._varname_to_var.clear()
self._original_vtype = {}
self._bin_vars.clear()
for var in self.model.getVars():
assert var.varName not in self._varname_to_var, (
f"Duplicated variable name detected: {var.varName}. "
f"Unique variable names are currently required."
)
self._varname_to_var[var.varName] = var
assert var.vtype in ["B", "C"], (
"Only binary and continuous variables are currently supported. "
"Variable {var.varName} has type {var.vtype}."
)
self._original_vtype[var] = var.vtype
if var.vtype == "B":
self._bin_vars.append(var)
def _apply_params(self, streams: List[Any]) -> None:
assert self.model is not None
with _RedirectOutput(streams):
for (name, value) in self.params.items():
self.model.setParam(name, value)
@overrides @overrides
def solve_lp( def set_warm_start(self, solution: Solution) -> None:
self,
tee: bool = False,
) -> LPSolveStats:
self._raise_if_callback() self._raise_if_callback()
streams: List[Any] = [StringIO()] self._clear_warm_start()
if tee: for (var_name, value) in solution.items():
streams += [sys.stdout] var = self._varname_to_var[var_name]
self._apply_params(streams) if value is not None:
assert self.model is not None var.start = value
for var in self._bin_vars:
var.vtype = self.gp.GRB.CONTINUOUS
var.lb = 0.0
var.ub = 1.0
with _RedirectOutput(streams):
self.model.optimize()
self._dirty = False
for var in self._bin_vars:
var.vtype = self.gp.GRB.BINARY
log = streams[0].getvalue()
self._has_lp_solution = self.model.solCount > 0
self._has_mip_solution = False
opt_value = None
if not self.is_infeasible():
opt_value = self.model.objVal
return LPSolveStats(
lp_value=opt_value,
lp_log=log,
lp_wallclock_time=self.model.runtime,
)
@overrides @overrides
def solve( def solve(
@ -235,142 +368,56 @@ class GurobiSolver(InternalSolver):
) )
@overrides @overrides
def get_solution(self) -> Optional[Solution]: def solve_lp(
assert self.model is not None self,
if self.cb_where is not None: tee: bool = False,
if self.cb_where == self.gp.GRB.Callback.MIPNODE: ) -> LPSolveStats:
return {
v.varName: self.model.cbGetNodeRel(v) for v in self.model.getVars()
}
elif self.cb_where == self.gp.GRB.Callback.MIPSOL:
return {
v.varName: self.model.cbGetSolution(v) for v in self.model.getVars()
}
else:
raise Exception(
f"get_solution can only be called from a callback "
f"when cb_where is either MIPNODE or MIPSOL"
)
if self.model.solCount == 0:
return None
return {v.varName: v.x for v in self.model.getVars()}
@overrides
def get_variable_names(self) -> List[VariableName]:
self._raise_if_callback()
assert self.model is not None
return [v.varName for v in self.model.getVars()]
@overrides
def set_warm_start(self, solution: Solution) -> None:
self._raise_if_callback() self._raise_if_callback()
self._clear_warm_start() streams: List[Any] = [StringIO()]
for (var_name, value) in solution.items(): if tee:
var = self._varname_to_var[var_name] streams += [sys.stdout]
if value is not None: self._apply_params(streams)
var.start = value
@overrides
def get_sense(self) -> str:
assert self.model is not None
if self.model.modelSense == 1:
return "min"
else:
return "max"
@overrides
def is_infeasible(self) -> bool:
assert self.model is not None
return self.model.status in [self.gp.GRB.INFEASIBLE, self.gp.GRB.INF_OR_UNBD]
@overrides
def get_dual(self, cid: str) -> float:
assert self.model is not None
c = self.model.getConstrByName(cid)
if self.is_infeasible():
return c.farkasDual
else:
return c.pi
def _get_value(self, var: Any) -> float:
assert self.model is not None assert self.model is not None
if self.cb_where == self.gp.GRB.Callback.MIPSOL: for var in self._bin_vars:
return self.model.cbGetSolution(var) var.vtype = self.gp.GRB.CONTINUOUS
elif self.cb_where == self.gp.GRB.Callback.MIPNODE: var.lb = 0.0
return self.model.cbGetNodeRel(var) var.ub = 1.0
elif self.cb_where is None: with _RedirectOutput(streams):
return var.x self.model.optimize()
else: self._dirty = False
raise Exception( for var in self._bin_vars:
"get_value cannot be called from cb_where=%s" % self.cb_where var.vtype = self.gp.GRB.BINARY
log = streams[0].getvalue()
self._has_lp_solution = self.model.solCount > 0
self._has_mip_solution = False
opt_value = None
if not self.is_infeasible():
opt_value = self.model.objVal
return LPSolveStats(
lp_value=opt_value,
lp_log=log,
lp_wallclock_time=self.model.runtime,
) )
@overrides @overrides
def add_constraint(self, constr: Constraint, name: str) -> None: def relax(self) -> None:
assert self.model is not None assert self.model is not None
lhs = self.gp.quicksum( self.model.update()
self._varname_to_var[varname] * coeff self.model = self.model.relax()
for (varname, coeff) in constr.lhs.items() self._update_vars()
)
if constr.sense == "=":
self.model.addConstr(lhs == constr.rhs, name=name)
elif constr.sense == "<":
self.model.addConstr(lhs <= constr.rhs, name=name)
else:
self.model.addConstr(lhs >= constr.rhs, name=name)
self._dirty = True
self._has_lp_solution = False
self._has_mip_solution = False
@overrides def _apply_params(self, streams: List[Any]) -> None:
def remove_constraint(self, name: str) -> None:
assert self.model is not None assert self.model is not None
constr = self.model.getConstrByName(name) with _RedirectOutput(streams):
self.model.remove(constr) for (name, value) in self.params.items():
self.model.setParam(name, value)
@overrides
def is_constraint_satisfied(self, constr: Constraint, tol: float = 1e-6) -> bool:
lhs = 0.0
for (varname, coeff) in constr.lhs.items():
var = self._varname_to_var[varname]
lhs += self._get_value(var) * coeff
if constr.sense == "<":
return lhs <= constr.rhs + tol
elif constr.sense == ">":
return lhs >= constr.rhs - tol
else:
return abs(constr.rhs - lhs) < abs(tol)
def _clear_warm_start(self) -> None: def _clear_warm_start(self) -> None:
for var in self._varname_to_var.values(): for var in self._varname_to_var.values():
var.start = self.gp.GRB.UNDEFINED var.start = self.gp.GRB.UNDEFINED
@overrides
def fix(self, solution: Solution) -> None:
self._raise_if_callback()
for (varname, value) in solution.items():
if value is None:
continue
var = self._varname_to_var[varname]
var.vtype = self.gp.GRB.CONTINUOUS
var.lb = value
var.ub = value
@overrides
def relax(self) -> None:
assert self.model is not None
self.model.update()
self.model = self.model.relax()
self._update_vars()
def _extract_warm_start_value(self, log: str) -> Optional[float]:
ws = self.__extract(log, "MIP start with objective ([0-9.e+-]*)")
if ws is None:
return None
return float(ws)
@staticmethod @staticmethod
def __extract( def _extract(
log: str, log: str,
regexp: str, regexp: str,
default: Optional[str] = None, default: Optional[str] = None,
@ -383,53 +430,24 @@ class GurobiSolver(InternalSolver):
value = matches[0] value = matches[0]
return value return value
def __getstate__(self) -> Dict: def _extract_warm_start_value(self, log: str) -> Optional[float]:
return { ws = self._extract(log, "MIP start with objective ([0-9.e+-]*)")
"params": self.params, if ws is None:
"lazy_cb_where": self.lazy_cb_where, return None
} return float(ws)
def __setstate__(self, state: Dict) -> None:
self.params = state["params"]
self.lazy_cb_where = state["lazy_cb_where"]
self.instance = None
self.model = None
self.cb_where = None
@overrides
def clone(self) -> "GurobiSolver":
return GurobiSolver(
params=self.params,
lazy_cb_frequency=self.lazy_cb_frequency,
)
@overrides
def build_test_instance_infeasible(self) -> Instance:
return GurobiTestInstanceInfeasible()
@overrides
def build_test_instance_redundancy(self) -> Instance:
return GurobiTestInstanceRedundancy()
@overrides
def build_test_instance_knapsack(self) -> Instance:
return GurobiTestInstanceKnapsack(
weights=[23.0, 26.0, 20.0, 18.0],
prices=[505.0, 352.0, 458.0, 220.0],
capacity=67.0,
)
@overrides def _get_value(self, var: Any) -> float:
def get_variables(self) -> Dict[str, Variable]:
assert self.model is not None assert self.model is not None
variables = {} if self.cb_where == self.gp.GRB.Callback.MIPSOL:
for gp_var in self.model.getVars(): return self.model.cbGetSolution(var)
name = gp_var.varName elif self.cb_where == self.gp.GRB.Callback.MIPNODE:
assert len(name) > 0, f"empty variable name detected" return self.model.cbGetNodeRel(var)
assert name not in variables, f"duplicated variable name detected: {name}" elif self.cb_where is None:
var = self._parse_gurobi_var(gp_var) return var.x
variables[name] = var else:
return variables raise Exception(
"get_value cannot be called from cb_where=%s" % self.cb_where
)
def _parse_gurobi_var(self, gp_var: Any) -> Variable: def _parse_gurobi_var(self, gp_var: Any) -> Variable:
assert self.model is not None assert self.model is not None
@ -462,20 +480,6 @@ class GurobiSolver(InternalSolver):
var.value = gp_var.x var.value = gp_var.x
return var return var
@overrides
def get_constraints(self) -> Dict[str, Constraint]:
assert self.model is not None
self._raise_if_callback()
if self._dirty:
self.model.update()
self._dirty = False
constraints: Dict[str, Constraint] = {}
for c in self.model.getConstrs():
constr = self._parse_gurobi_constraint(c)
assert c.constrName not in constraints
constraints[c.constrName] = constr
return constraints
def _parse_gurobi_constraint(self, gp_constr: Any) -> Constraint: def _parse_gurobi_constraint(self, gp_constr: Any) -> Constraint:
assert self.model is not None assert self.model is not None
expr = self.model.getRow(gp_constr) expr = self.model.getRow(gp_constr)
@ -501,45 +505,41 @@ class GurobiSolver(InternalSolver):
constr.slack = gp_constr.slack constr.slack = gp_constr.slack
return constr return constr
@overrides def _raise_if_callback(self) -> None:
def are_callbacks_supported(self) -> bool: if self.cb_where is not None:
return True raise Exception("method cannot be called from a callback")
@overrides def _update_vars(self) -> None:
def get_constraint_attrs(self) -> List[str]: assert self.model is not None
return [ self._varname_to_var.clear()
"basis_status", self._original_vtype = {}
"category", self._bin_vars.clear()
"dual_value", for var in self.model.getVars():
"lazy", assert var.varName not in self._varname_to_var, (
"lhs", f"Duplicated variable name detected: {var.varName}. "
"rhs", f"Unique variable names are currently required."
"sa_rhs_down", )
"sa_rhs_up", self._varname_to_var[var.varName] = var
"sense", assert var.vtype in ["B", "C"], (
"slack", "Only binary and continuous variables are currently supported. "
"user_features", "Variable {var.varName} has type {var.vtype}."
] )
self._original_vtype[var] = var.vtype
if var.vtype == "B":
self._bin_vars.append(var)
@overrides def __getstate__(self) -> Dict:
def get_variable_attrs(self) -> List[str]: return {
return [ "params": self.params,
"basis_status", "lazy_cb_where": self.lazy_cb_where,
"category", }
"lower_bound",
"obj_coeff", def __setstate__(self, state: Dict) -> None:
"reduced_cost", self.params = state["params"]
"sa_lb_down", self.lazy_cb_where = state["lazy_cb_where"]
"sa_lb_up", self.instance = None
"sa_obj_down", self.model = None
"sa_obj_up", self.cb_where = None
"sa_ub_down",
"sa_ub_up",
"type",
"upper_bound",
"user_features",
"value",
]
class GurobiTestInstanceInfeasible(Instance): class GurobiTestInstanceInfeasible(Instance):

@ -70,82 +70,95 @@ class BasePyomoSolver(InternalSolver):
self._pyomo_solver.options[key] = value self._pyomo_solver.options[key] = value
@overrides @overrides
def solve_lp( def add_constraint(
self, self,
tee: bool = False, constr: Any,
) -> LPSolveStats: name: str,
self.relax() ) -> None:
streams: List[Any] = [StringIO()] assert self.model is not None
if tee: if isinstance(constr, Constraint):
streams += [sys.stdout] lhs = 0.0
with _RedirectOutput(streams): for (varname, coeff) in constr.lhs.items():
results = self._pyomo_solver.solve(tee=True) var = self._varname_to_var[varname]
self._termination_condition = results["Solver"][0]["Termination condition"] lhs += var * coeff
self._restore_integrality() if constr.sense == "=":
opt_value = None expr = lhs == constr.rhs
elif constr.sense == "<":
expr = lhs <= constr.rhs
else:
expr = lhs >= constr.rhs
cl = pe.Constraint(expr=expr, name=name)
self.model.add_component(name, cl)
self._pyomo_solver.add_constraint(cl)
self._cname_to_constr[name] = cl
else:
self._pyomo_solver.add_constraint(constr)
self._termination_condition = ""
self._has_lp_solution = False self._has_lp_solution = False
self._has_mip_solution = False self._has_mip_solution = False
if not self.is_infeasible():
opt_value = results["Problem"][0]["Lower bound"] @overrides
self._has_lp_solution = True def are_callbacks_supported(self) -> bool:
return LPSolveStats( return False
lp_value=opt_value,
lp_log=streams[0].getvalue(), @overrides
lp_wallclock_time=results["Solver"][0]["Wallclock time"], def build_test_instance_infeasible(self) -> Instance:
return PyomoTestInstanceInfeasible()
@overrides
def build_test_instance_redundancy(self) -> Instance:
return PyomoTestInstanceRedundancy()
@overrides
def build_test_instance_knapsack(self) -> Instance:
return PyomoTestInstanceKnapsack(
weights=[23.0, 26.0, 20.0, 18.0],
prices=[505.0, 352.0, 458.0, 220.0],
capacity=67.0,
) )
def _restore_integrality(self) -> None: @overrides
for var in self._bin_vars: def fix(self, solution: Solution) -> None:
var.domain = pyomo.core.base.set_types.Binary for (varname, value) in solution.items():
if value is None:
continue
var = self._varname_to_var[varname]
var.fix(value)
self._pyomo_solver.update_var(var) self._pyomo_solver.update_var(var)
@overrides @overrides
def solve( def get_constraints(self) -> Dict[str, Constraint]:
self, assert self.model is not None
tee: bool = False,
iteration_cb: Optional[IterationCallback] = None, constraints = {}
lazy_cb: Optional[LazyCallback] = None, for constr in self.model.component_objects(pyomo.core.Constraint):
user_cut_cb: Optional[UserCutCallback] = None, if isinstance(constr, pe.ConstraintList):
) -> MIPSolveStats: for idx in constr:
assert lazy_cb is None, "callbacks are not currently supported" name = f"{constr.name}[{idx}]"
assert user_cut_cb is None, "callbacks are not currently supported" assert name not in constraints
total_wallclock_time = 0 constraints[name] = self._parse_pyomo_constraint(constr[idx])
streams: List[Any] = [StringIO()] else:
if tee: name = constr.name
streams += [sys.stdout] assert name not in constraints
if iteration_cb is None: constraints[name] = self._parse_pyomo_constraint(constr)
iteration_cb = lambda: False
while True: return constraints
logger.debug("Solving MIP...")
with _RedirectOutput(streams): @overrides
results = self._pyomo_solver.solve( def get_constraint_attrs(self) -> List[str]:
tee=True, return [
warmstart=self._is_warm_start_available, "dual_value",
) "lazy",
total_wallclock_time += results["Solver"][0]["Wallclock time"] "lhs",
should_repeat = iteration_cb() "rhs",
if not should_repeat: "sense",
break "slack",
log = streams[0].getvalue() ]
node_count = self._extract_node_count(log)
ws_value = self._extract_warm_start_value(log) @overrides
self._termination_condition = results["Solver"][0]["Termination condition"] def get_dual(self, cid: str) -> float:
lb, ub = None, None constr = self._cname_to_constr[cid]
self._has_mip_solution = False return self._pyomo_solver.dual[constr]
self._has_lp_solution = False
if not self.is_infeasible():
self._has_mip_solution = True
lb = results["Problem"][0]["Lower bound"]
ub = results["Problem"][0]["Upper bound"]
return MIPSolveStats(
mip_lower_bound=lb,
mip_upper_bound=ub,
mip_wallclock_time=total_wallclock_time,
mip_sense=self._obj_sense,
mip_log=log,
mip_nodes=node_count,
mip_warm_start_value=ws_value,
)
@overrides @overrides
def get_solution(self) -> Optional[Solution]: def get_solution(self) -> Optional[Solution]:
@ -172,17 +185,63 @@ class BasePyomoSolver(InternalSolver):
return variables return variables
@overrides @overrides
def set_warm_start(self, solution: Solution) -> None: def get_sense(self) -> str:
self._clear_warm_start() return self._obj_sense
count_fixed = 0
for (var_name, value) in solution.items(): @overrides
if value is None: def get_variables(self) -> Dict[str, Variable]:
continue assert self.model is not None
var = self._varname_to_var[var_name] variables = {}
var.value = solution[var_name] for var in self.model.component_objects(pyomo.core.Var):
count_fixed += 1 for idx in var:
if count_fixed > 0: varname = f"{var}[{idx}]"
self._is_warm_start_available = True if idx is None:
varname = str(var)
variables[varname] = self._parse_pyomo_variable(var[idx])
return variables
@overrides
def get_variable_attrs(self) -> List[str]:
return [
# "basis_status",
"lower_bound",
"obj_coeff",
"reduced_cost",
# "sa_lb_down",
# "sa_lb_up",
# "sa_obj_down",
# "sa_obj_up",
# "sa_ub_down",
# "sa_ub_up",
"type",
"upper_bound",
"value",
]
@overrides
def is_constraint_satisfied(self, constr: Constraint, tol: float = 1e-6) -> bool:
lhs = 0.0
for (varname, coeff) in constr.lhs.items():
var = self._varname_to_var[varname]
lhs += var.value * coeff
if constr.sense == "<":
return lhs <= constr.rhs + tol
elif constr.sense == ">":
return lhs >= constr.rhs - tol
else:
return abs(constr.rhs - lhs) < abs(tol)
@overrides
def is_infeasible(self) -> bool:
return self._termination_condition == TerminationCondition.infeasible
@overrides
def remove_constraint(self, name: str) -> None:
assert self.model is not None
constr = self._cname_to_constr[name]
del self._cname_to_constr[name]
self.model.del_component(constr)
self._pyomo_solver.remove_constraint(constr)
@overrides @overrides
def set_instance( def set_instance(
@ -204,102 +263,109 @@ class BasePyomoSolver(InternalSolver):
self._update_vars() self._update_vars()
self._update_constrs() self._update_constrs()
def _clear_warm_start(self) -> None: @overrides
for var in self._all_vars: def set_warm_start(self, solution: Solution) -> None:
if not var.fixed: self._clear_warm_start()
var.value = None count_fixed = 0
self._is_warm_start_available = False for (var_name, value) in solution.items():
if value is None:
def _update_obj(self) -> None: continue
self._obj_sense = "max" var = self._varname_to_var[var_name]
if self._pyomo_solver._objective.sense == pyomo.core.kernel.objective.minimize: var.value = solution[var_name]
self._obj_sense = "min" count_fixed += 1
if count_fixed > 0:
self._is_warm_start_available = True
def _update_vars(self) -> None: @overrides
assert self.model is not None def solve(
self._all_vars = [] self,
self._bin_vars = [] tee: bool = False,
self._varname_to_var = {} iteration_cb: Optional[IterationCallback] = None,
for var in self.model.component_objects(Var): lazy_cb: Optional[LazyCallback] = None,
for idx in var: user_cut_cb: Optional[UserCutCallback] = None,
self._varname_to_var[f"{var.name}[{idx}]"] = var[idx] ) -> MIPSolveStats:
self._all_vars += [var[idx]] assert lazy_cb is None, "callbacks are not currently supported"
if var[idx].domain == pyomo.core.base.set_types.Binary: assert user_cut_cb is None, "callbacks are not currently supported"
self._bin_vars += [var[idx]] total_wallclock_time = 0
for obj in self.model.component_objects(Objective): streams: List[Any] = [StringIO()]
self._obj = self._parse_pyomo_expr(obj.expr) if tee:
streams += [sys.stdout]
if iteration_cb is None:
iteration_cb = lambda: False
while True:
logger.debug("Solving MIP...")
with _RedirectOutput(streams):
results = self._pyomo_solver.solve(
tee=True,
warmstart=self._is_warm_start_available,
)
total_wallclock_time += results["Solver"][0]["Wallclock time"]
should_repeat = iteration_cb()
if not should_repeat:
break break
log = streams[0].getvalue()
def _update_constrs(self) -> None: node_count = self._extract_node_count(log)
assert self.model is not None ws_value = self._extract_warm_start_value(log)
self._cname_to_constr.clear() self._termination_condition = results["Solver"][0]["Termination condition"]
for constr in self.model.component_objects(pyomo.core.Constraint): lb, ub = None, None
if isinstance(constr, pe.ConstraintList): self._has_mip_solution = False
for idx in constr: self._has_lp_solution = False
self._cname_to_constr[f"{constr.name}[{idx}]"] = constr[idx] if not self.is_infeasible():
else: self._has_mip_solution = True
self._cname_to_constr[constr.name] = constr lb = results["Problem"][0]["Lower bound"]
ub = results["Problem"][0]["Upper bound"]
@overrides return MIPSolveStats(
def fix(self, solution: Solution) -> None: mip_lower_bound=lb,
for (varname, value) in solution.items(): mip_upper_bound=ub,
if value is None: mip_wallclock_time=total_wallclock_time,
continue mip_sense=self._obj_sense,
var = self._varname_to_var[varname] mip_log=log,
var.fix(value) mip_nodes=node_count,
self._pyomo_solver.update_var(var) mip_warm_start_value=ws_value,
)
@overrides @overrides
def add_constraint( def solve_lp(
self, self,
constr: Any, tee: bool = False,
name: str, ) -> LPSolveStats:
) -> None: self.relax()
assert self.model is not None streams: List[Any] = [StringIO()]
if isinstance(constr, Constraint): if tee:
lhs = 0.0 streams += [sys.stdout]
for (varname, coeff) in constr.lhs.items(): with _RedirectOutput(streams):
var = self._varname_to_var[varname] results = self._pyomo_solver.solve(tee=True)
lhs += var * coeff self._termination_condition = results["Solver"][0]["Termination condition"]
if constr.sense == "=": self._restore_integrality()
expr = lhs == constr.rhs opt_value = None
elif constr.sense == "<":
expr = lhs <= constr.rhs
else:
expr = lhs >= constr.rhs
cl = pe.Constraint(expr=expr, name=name)
self.model.add_component(name, cl)
self._pyomo_solver.add_constraint(cl)
self._cname_to_constr[name] = cl
else:
self._pyomo_solver.add_constraint(constr)
self._termination_condition = ""
self._has_lp_solution = False self._has_lp_solution = False
self._has_mip_solution = False self._has_mip_solution = False
if not self.is_infeasible():
opt_value = results["Problem"][0]["Lower bound"]
self._has_lp_solution = True
return LPSolveStats(
lp_value=opt_value,
lp_log=streams[0].getvalue(),
lp_wallclock_time=results["Solver"][0]["Wallclock time"],
)
@overrides @overrides
def remove_constraint(self, name: str) -> None: def relax(self) -> None:
assert self.model is not None for var in self._bin_vars:
constr = self._cname_to_constr[name] lb, ub = var.bounds
del self._cname_to_constr[name] var.setlb(lb)
self.model.del_component(constr) var.setub(ub)
self._pyomo_solver.remove_constraint(constr) var.domain = pyomo.core.base.set_types.Reals
self._pyomo_solver.update_var(var)
@overrides def _clear_warm_start(self) -> None:
def is_constraint_satisfied(self, constr: Constraint, tol: float = 1e-6) -> bool: for var in self._all_vars:
lhs = 0.0 if not var.fixed:
for (varname, coeff) in constr.lhs.items(): var.value = None
var = self._varname_to_var[varname] self._is_warm_start_available = False
lhs += var.value * coeff
if constr.sense == "<":
return lhs <= constr.rhs + tol
elif constr.sense == ">":
return lhs >= constr.rhs - tol
else:
return abs(constr.rhs - lhs) < abs(tol)
@staticmethod @staticmethod
def __extract( def _extract(
log: str, log: str,
regexp: Optional[str], regexp: Optional[str],
default: Optional[str] = None, default: Optional[str] = None,
@ -314,73 +380,23 @@ class BasePyomoSolver(InternalSolver):
value = matches[0] value = matches[0]
return value return value
def _extract_warm_start_value(self, log: str) -> Optional[float]:
value = self.__extract(log, self._get_warm_start_regexp())
if value is None:
return None
return float(value)
def _extract_node_count(self, log: str) -> Optional[int]: def _extract_node_count(self, log: str) -> Optional[int]:
value = self.__extract(log, self._get_node_count_regexp()) value = self._extract(log, self._get_node_count_regexp())
if value is None: if value is None:
return None return None
return int(value) return int(value)
def _get_warm_start_regexp(self) -> Optional[str]: def _extract_warm_start_value(self, log: str) -> Optional[float]:
value = self._extract(log, self._get_warm_start_regexp())
if value is None:
return None return None
return float(value)
def _get_node_count_regexp(self) -> Optional[str]: def _get_node_count_regexp(self) -> Optional[str]:
return None return None
@overrides def _get_warm_start_regexp(self) -> Optional[str]:
def relax(self) -> None: return None
for var in self._bin_vars:
lb, ub = var.bounds
var.setlb(lb)
var.setub(ub)
var.domain = pyomo.core.base.set_types.Reals
self._pyomo_solver.update_var(var)
@overrides
def is_infeasible(self) -> bool:
return self._termination_condition == TerminationCondition.infeasible
@overrides
def get_dual(self, cid: str) -> float:
constr = self._cname_to_constr[cid]
return self._pyomo_solver.dual[constr]
@overrides
def get_sense(self) -> str:
return self._obj_sense
@overrides
def build_test_instance_infeasible(self) -> Instance:
return PyomoTestInstanceInfeasible()
@overrides
def build_test_instance_redundancy(self) -> Instance:
return PyomoTestInstanceRedundancy()
@overrides
def build_test_instance_knapsack(self) -> Instance:
return PyomoTestInstanceKnapsack(
weights=[23.0, 26.0, 20.0, 18.0],
prices=[505.0, 352.0, 458.0, 220.0],
capacity=67.0,
)
@overrides
def get_variables(self) -> Dict[str, Variable]:
assert self.model is not None
variables = {}
for var in self.model.component_objects(pyomo.core.Var):
for idx in var:
varname = f"{var}[{idx}]"
if idx is None:
varname = str(var)
variables[varname] = self._parse_pyomo_variable(var[idx])
return variables
def _parse_pyomo_variable(self, var: pyomo.core.Var) -> Variable: def _parse_pyomo_variable(self, var: pyomo.core.Var) -> Variable:
# Variable type # Variable type
@ -420,24 +436,6 @@ class BasePyomoSolver(InternalSolver):
reduced_cost=rc, reduced_cost=rc,
) )
@overrides
def get_constraints(self) -> Dict[str, Constraint]:
assert self.model is not None
constraints = {}
for constr in self.model.component_objects(pyomo.core.Constraint):
if isinstance(constr, pe.ConstraintList):
for idx in constr:
name = f"{constr.name}[{idx}]"
assert name not in constraints
constraints[name] = self._parse_pyomo_constraint(constr[idx])
else:
name = constr.name
assert name not in constraints
constraints[name] = self._parse_pyomo_constraint(constr)
return constraints
def _parse_pyomo_constraint( def _parse_pyomo_constraint(
self, self,
pyomo_constr: pyomo.core.Constraint, pyomo_constr: pyomo.core.Constraint,
@ -490,38 +488,40 @@ class BasePyomoSolver(InternalSolver):
raise Exception(f"Unknown expression type: {expr.__class__.__name__}") raise Exception(f"Unknown expression type: {expr.__class__.__name__}")
return lhs return lhs
@overrides def _restore_integrality(self) -> None:
def are_callbacks_supported(self) -> bool: for var in self._bin_vars:
return False var.domain = pyomo.core.base.set_types.Binary
self._pyomo_solver.update_var(var)
@overrides def _update_obj(self) -> None:
def get_constraint_attrs(self) -> List[str]: self._obj_sense = "max"
return [ if self._pyomo_solver._objective.sense == pyomo.core.kernel.objective.minimize:
"dual_value", self._obj_sense = "min"
"lazy",
"lhs",
"rhs",
"sense",
"slack",
]
@overrides def _update_vars(self) -> None:
def get_variable_attrs(self) -> List[str]: assert self.model is not None
return [ self._all_vars = []
# "basis_status", self._bin_vars = []
"lower_bound", self._varname_to_var = {}
"obj_coeff", for var in self.model.component_objects(Var):
"reduced_cost", for idx in var:
# "sa_lb_down", self._varname_to_var[f"{var.name}[{idx}]"] = var[idx]
# "sa_lb_up", self._all_vars += [var[idx]]
# "sa_obj_down", if var[idx].domain == pyomo.core.base.set_types.Binary:
# "sa_obj_up", self._bin_vars += [var[idx]]
# "sa_ub_down", for obj in self.model.component_objects(Objective):
# "sa_ub_up", self._obj = self._parse_pyomo_expr(obj.expr)
"type", break
"upper_bound",
"value", def _update_constrs(self) -> None:
] assert self.model is not None
self._cname_to_constr.clear()
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]
else:
self._cname_to_constr[constr.name] = constr
class PyomoTestInstanceInfeasible(Instance): class PyomoTestInstanceInfeasible(Instance):

Loading…
Cancel
Save