From 3b01104f586f9a33d1386ff89c5c3e57b3ae7fc7 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 1 Nov 2023 21:24:39 -0400 Subject: [PATCH] fix Gurobi single tree cycle handling --- pyomo/contrib/mindtpy/algorithm_base_class.py | 3 +++ pyomo/contrib/mindtpy/single_tree.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index a0216b5d054..33b2f2c1d04 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -106,6 +106,8 @@ def __init__(self, **kwds): self.curr_int_sol = [] self.should_terminate = False self.integer_list = [] + # dictionary {integer solution (list): cuts index (list)} + self.int_sol_2_cuts_ind = dict() # Set up iteration counters self.nlp_iter = 0 @@ -794,6 +796,7 @@ def MindtPy_initialization(self): self.integer_list.append(self.curr_int_sol) fixed_nlp, fixed_nlp_result = self.solve_subproblem() self.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result) + self.int_sol_2_cuts_ind[self.curr_int_sol] = list(range(1, len(self.mip.MindtPy_utils.cuts.oa_cuts) + 1)) elif config.init_strategy == 'FP': self.init_rNLP() self.fp_loop() diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 9595a9fc9be..dacde73a79e 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -941,15 +941,26 @@ def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config): ) return elif config.strategy == 'OA': + # Refer to the official document of GUROBI. + # Your callback should be prepared to cut off solutions that violate any of your lazy constraints, including those that have already been added. Node solutions will usually respect previously added lazy constraints, but not always. + # https://www.gurobi.com/documentation/current/refman/cs_cb_addlazy.html + # If this happens, MindtPy will look for the index of corresponding cuts, instead of solving the fixed-NLP again. + for ind in mindtpy_solver.int_sol_2_cuts_ind[mindtpy_solver.curr_int_sol]: + cb_opt.cbLazy(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts[ind]) return else: mindtpy_solver.integer_list.append(mindtpy_solver.curr_int_sol) + if config.strategy == 'OA': + cut_ind = len(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts) # solve subproblem # The constraint linearization happens in the handlers fixed_nlp, fixed_nlp_result = mindtpy_solver.solve_subproblem() mindtpy_solver.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result, cb_opt) + if config.strategy == 'OA': + # store the cut index corresponding to current integer solution. + mindtpy_solver.int_sol_2_cuts_ind[mindtpy_solver.curr_int_sol] = list(range(cut_ind + 1, len(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts) + 1)) def handle_lazy_main_feasible_solution_gurobi(cb_m, cb_opt, mindtpy_solver, config):