Skip to content

Commit 55b7dff

Browse files
author
Clara Witte
committed
Add missing functionalities and fix bugs
1 parent 5b70135 commit 55b7dff

File tree

2 files changed

+97
-35
lines changed

2 files changed

+97
-35
lines changed

pyomo/contrib/appsi/solvers/maingo.py

+60-21
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ def __init__(self, solver):
120120
super(MAiNGOResults, self).__init__()
121121
self.wallclock_time = None
122122
self.cpu_time = None
123+
self.globally_optimal = None
123124
self.solution_loader = MAiNGOSolutionLoader(solver=solver)
124125

125126

@@ -228,9 +229,14 @@ def solve(self, model, timer: HierarchicalTimer = None):
228229
self._last_results_object.solution_loader.invalidate()
229230
if timer is None:
230231
timer = HierarchicalTimer()
231-
timer.start("set_instance")
232-
self.set_instance(model)
233-
timer.stop("set_instance")
232+
if model is not self._model:
233+
timer.start("set_instance")
234+
self.set_instance(model)
235+
timer.stop("set_instance")
236+
else:
237+
timer.start("Update")
238+
self.update(timer=timer)
239+
timer.stop("Update")
234240
res = self._solve(timer)
235241
self._last_results_object = res
236242
if self.config.report_timing:
@@ -285,7 +291,7 @@ def _process_domain_and_bounds(self, var):
285291
return lb, ub, vtype
286292

287293
def _add_variables(self, variables: List[_GeneralVarData]):
288-
for ndx, var in enumerate(variables):
294+
for var in variables:
289295
varname = self._symbol_map.getSymbol(var, self._labeler)
290296
lb, ub, vtype = self._process_domain_and_bounds(var)
291297
self._maingo_vars.append(
@@ -331,10 +337,11 @@ def set_instance(self, model):
331337
con_list=self._cons,
332338
objective=self._objective,
333339
idmap=self._pyomo_var_to_solver_var_id_map,
340+
logger=logger,
334341
)
335342

336343
def _add_constraints(self, cons: List[_GeneralConstraintData]):
337-
self._cons = cons
344+
self._cons += cons
338345

339346
def _add_sos_constraints(self, cons: List[_SOSConstraintData]):
340347
if len(cons) >= 1:
@@ -344,7 +351,8 @@ def _add_sos_constraints(self, cons: List[_SOSConstraintData]):
344351
pass
345352

346353
def _remove_constraints(self, cons: List[_GeneralConstraintData]):
347-
pass
354+
for con in cons:
355+
self._cons.remove(con)
348356

349357
def _remove_sos_constraints(self, cons: List[_SOSConstraintData]):
350358
if len(cons) >= 1:
@@ -354,28 +362,48 @@ def _remove_sos_constraints(self, cons: List[_SOSConstraintData]):
354362
pass
355363

356364
def _remove_variables(self, variables: List[_GeneralVarData]):
357-
pass
365+
removed_maingo_vars = []
366+
for var in variables:
367+
varname = self._symbol_map.getSymbol(var, self._labeler)
368+
del self._maingo_vars[self._pyomo_var_to_solver_var_id_map[id(var)]]
369+
removed_maingo_vars += [self._pyomo_var_to_solver_var_id_map[id(var)]]
370+
del self._pyomo_var_to_solver_var_id_map[id(var)]
371+
372+
for pyomo_var, maingo_var_id in self._pyomo_var_to_solver_var_id_map.items():
373+
# How many variables before current var where removed?
374+
num_removed = 0
375+
for removed_var in removed_maingo_vars:
376+
if removed_var <= maingo_var_id:
377+
num_removed += 1
378+
self._pyomo_var_to_solver_var_id_map[pyomo_var] = (
379+
maingo_var_id - num_removed
380+
)
358381

359382
def _remove_params(self, params: List[_ParamData]):
360383
pass
361384

362385
def _update_variables(self, variables: List[_GeneralVarData]):
363-
pass
386+
for var in variables:
387+
if id(var) not in self._pyomo_var_to_solver_var_id_map:
388+
raise ValueError(
389+
'The Var provided to update_var needs to be added first: {0}'.format(
390+
var
391+
)
392+
)
393+
lb, ub, vtype = self._process_domain_and_bounds(var)
394+
self._maingo_vars[self._pyomo_var_to_solver_var_id_map[id(var)]] = (
395+
MaingoVar(name=var.name, type=vtype, lb=lb, ub=ub, init=var.value)
396+
)
364397

365398
def update_params(self):
366-
pass
399+
vars = [var[0] for var in self._vars.values()]
400+
self._update_variables(vars)
367401

368402
def _set_objective(self, obj):
369-
if obj is None:
370-
raise NotImplementedError(
371-
"MAiNGO needs a objective. Please set a dummy objective."
372-
)
373-
else:
374-
if not obj.sense in {minimize, maximize}:
375-
raise ValueError(
376-
"Objective sense is not recognized: {0}".format(obj.sense)
377-
)
378-
self._objective = obj
403+
404+
if not obj.sense in {minimize, maximize}:
405+
raise ValueError("Objective sense is not recognized: {0}".format(obj.sense))
406+
self._objective = obj
379407

380408
def _postsolve(self, timer: HierarchicalTimer):
381409
config = self.config
@@ -388,7 +416,9 @@ def _postsolve(self, timer: HierarchicalTimer):
388416

389417
if status in {maingopy.GLOBALLY_OPTIMAL, maingopy.FEASIBLE_POINT}:
390418
results.termination_condition = TerminationCondition.optimal
419+
results.globally_optimal = True
391420
if status == maingopy.FEASIBLE_POINT:
421+
results.globally_optimal = False
392422
logger.warning(
393423
"MAiNGO did only find a feasible solution but did not prove its global optimality."
394424
)
@@ -425,8 +455,8 @@ def _postsolve(self, timer: HierarchicalTimer):
425455

426456
timer.start("load solution")
427457
if config.load_solution:
428-
if not results.best_feasible_objective is None:
429-
if results.termination_condition != TerminationCondition.optimal:
458+
if results.termination_condition is TerminationCondition.optimal:
459+
if not results.globally_optimal:
430460
logger.warning(
431461
"Loading a feasible but suboptimal solution. "
432462
"Please set load_solution=False and check "
@@ -487,6 +517,15 @@ def get_reduced_costs(self, vars_to_load=None):
487517
def get_duals(self, cons_to_load=None):
488518
raise ValueError("MAiNGO does not support returning Duals")
489519

520+
def update(self, timer: HierarchicalTimer = None):
521+
super(MAiNGO, self).update(timer=timer)
522+
self._solver_model = maingo_solvermodel.SolverModel(
523+
var_list=self._maingo_vars,
524+
con_list=self._cons,
525+
objective=self._objective,
526+
idmap=self._pyomo_var_to_solver_var_id_map,
527+
logger=logger,
528+
)
490529

491530
def _set_maingo_options(self):
492531
pass

pyomo/contrib/appsi/solvers/maingo_solvermodel.py

+37-14
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from pyomo.common.dependencies import attempt_import
1515
from pyomo.core.base.var import ScalarVar
16+
from pyomo.core.base.expression import ScalarExpression
1617
import pyomo.core.expr.expr_common as common
1718
import pyomo.core.expr as EXPR
1819
from pyomo.core.expr.numvalue import (
@@ -178,50 +179,66 @@ def visiting_potential_leaf(self, node):
178179
return True, maingo_var
179180

180181
def _monomial_to_maingo(self, node):
181-
if node.__class__ is ScalarVar:
182-
var = node
183-
const = 1
184-
else:
185-
const, var = node.args
186-
maingo_var_id = self.idmap[id(var)]
187-
maingo_var = self.variables[maingo_var_id]
182+
const, var = node.args
188183
if const.__class__ not in native_types:
189184
const = value(const)
190185
if var.is_fixed():
191186
return const * var.value
192187
if not const:
193188
return 0
189+
maingo_var = self._var_to_maingo(var)
194190
if const in _plusMinusOne:
195191
if const < 0:
196192
return -maingo_var
197193
else:
198194
return maingo_var
199195
return const * maingo_var
200196

197+
def _var_to_maingo(self, var):
198+
maingo_var_id = self.idmap[id(var)]
199+
maingo_var = self.variables[maingo_var_id]
200+
return maingo_var
201+
201202
def _linear_to_maingo(self, node):
202203
values = [
203204
(
204205
self._monomial_to_maingo(arg)
205-
if (arg.__class__ in {EXPR.MonomialTermExpression, ScalarVar})
206-
else (value(arg))
206+
if (arg.__class__ is EXPR.MonomialTermExpression)
207+
else (
208+
value(arg)
209+
if arg.__class__ in native_numeric_types
210+
else (
211+
self._var_to_maingo(arg)
212+
if arg.is_variable_type()
213+
else value(arg)
214+
)
215+
)
207216
)
208217
for arg in node.args
209218
]
210219
return sum(values)
211220

212221

213222
class SolverModel(maingopy.MAiNGOmodel):
214-
def __init__(self, var_list, objective, con_list, idmap):
223+
def __init__(self, var_list, objective, con_list, idmap, logger):
215224
maingopy.MAiNGOmodel.__init__(self)
216225
self._var_list = var_list
217226
self._con_list = con_list
218227
self._objective = objective
219228
self._idmap = idmap
229+
self._logger = logger
230+
self._no_objective = False
231+
232+
if self._objective is None:
233+
self._logger.warning("No objective given, setting a dummy objective of 1.")
234+
self._no_objective = True
220235

221236
def build_maingo_objective(self, obj, visitor):
237+
if self._no_objective:
238+
return visitor.variables[-1]
222239
maingo_obj = visitor.dfs_postorder_stack(obj.expr)
223240
if obj.sense == maximize:
224-
maingo_obj *= -1
241+
return -1 * maingo_obj
225242
return maingo_obj
226243

227244
def build_maingo_constraints(self, cons, visitor):
@@ -235,7 +252,7 @@ def build_maingo_constraints(self, cons, visitor):
235252
ineqs += [visitor.dfs_postorder_stack(con.lower - con.body)]
236253
elif con.has_ub():
237254
ineqs += [visitor.dfs_postorder_stack(con.body - con.upper)]
238-
elif con.has_ub():
255+
elif con.has_lb():
239256
ineqs += [visitor.dfs_postorder_stack(con.lower - con.body)]
240257
else:
241258
raise ValueError(
@@ -245,18 +262,24 @@ def build_maingo_constraints(self, cons, visitor):
245262
return eqs, ineqs
246263

247264
def get_variables(self):
248-
return [
265+
vars = [
249266
maingopy.OptimizationVariable(
250267
maingopy.Bounds(var.lb, var.ub), var.type, var.name
251268
)
252269
for var in self._var_list
253270
]
271+
if self._no_objective:
272+
vars += [maingopy.OptimizationVariable(maingopy.Bounds(1, 1), "dummy_obj")]
273+
return vars
254274

255275
def get_initial_point(self):
256-
return [
276+
initial = [
257277
var.init if not var.init is None else (var.lb + var.ub) / 2.0
258278
for var in self._var_list
259279
]
280+
if self._no_objective:
281+
initial += [1]
282+
return initial
260283

261284
def evaluate(self, maingo_vars):
262285
visitor = ToMAiNGOVisitor(maingo_vars, self._idmap)

0 commit comments

Comments
 (0)