def MindtPy_iteration_loop(solve_data, config): """Main loop for MindtPy Algorithms. This is the outermost function for the algorithms in this package; this function controls the progression of solving the model. Parameters ---------- solve_data : MindtPySolveData Data container that holds solve-instance data. config : ConfigBlock The specific configurations for MindtPy. Raises ------ ValueError The strategy value is not correct or not included. """ last_iter_cuts = False while solve_data.mip_iter < config.iteration_limit: solve_data.mip_subiter = 0 # solve MILP main problem if config.strategy in {'OA', 'GOA', 'ECP'}: main_mip, main_mip_results = solve_main(solve_data, config) if main_mip_results is not None: if not config.single_tree: if main_mip_results.solver.termination_condition is tc.optimal: handle_main_optimal(main_mip, solve_data, config) elif main_mip_results.solver.termination_condition is tc.infeasible: handle_main_infeasible(main_mip, solve_data, config) last_iter_cuts = True break else: handle_main_other_conditions( main_mip, main_mip_results, solve_data, config) # Call the MILP post-solve callback with time_code(solve_data.timing, 'Call after main solve'): config.call_after_main_solve(main_mip, solve_data) else: config.logger.info('Algorithm should terminate here.') break else: raise ValueError() # regularization is activated after the first feasible solution is found. if config.add_regularization is not None and solve_data.best_solution_found is not None and not config.single_tree: # the main problem might be unbounded, regularization is activated only when a valid bound is provided. if (solve_data.objective_sense == minimize and solve_data.LB != float('-inf')) or (solve_data.objective_sense == maximize and solve_data.UB != float('inf')): main_mip, main_mip_results = solve_main( solve_data, config, regularization_problem=True) handle_regularization_main_tc( main_mip, main_mip_results, solve_data, config) # TODO: add descriptions for the following code if config.add_regularization is not None and config.single_tree: solve_data.curr_int_sol = get_integer_solution( solve_data.mip, string_zero=True) copy_var_list_values( main_mip.MindtPy_utils.variable_list, solve_data.working_model.MindtPy_utils.variable_list, config) if solve_data.curr_int_sol not in set(solve_data.integer_list): fixed_nlp, fixed_nlp_result = solve_subproblem( solve_data, config) handle_nlp_subproblem_tc( fixed_nlp, fixed_nlp_result, solve_data, config) if algorithm_should_terminate(solve_data, config, check_cycling=True): last_iter_cuts = False break if not config.single_tree and config.strategy != 'ECP': # if we don't use lazy callback, i.e. LP_NLP # Solve NLP subproblem # The constraint linearization happens in the handlers if not config.solution_pool: fixed_nlp, fixed_nlp_result = solve_subproblem( solve_data, config) handle_nlp_subproblem_tc( fixed_nlp, fixed_nlp_result, solve_data, config) # Call the NLP post-solve callback with time_code(solve_data.timing, 'Call after subproblem solve'): config.call_after_subproblem_solve(fixed_nlp, solve_data) if algorithm_should_terminate(solve_data, config, check_cycling=False): last_iter_cuts = True break else: if config.mip_solver == 'cplex_persistent': solution_pool_names = main_mip_results._solver_model.solution.pool.get_names() elif config.mip_solver == 'gurobi_persistent': solution_pool_names = list( range(main_mip_results._solver_model.SolCount)) # list to store the name and objective value of the solutions in the solution pool solution_name_obj = [] for name in solution_pool_names: if config.mip_solver == 'cplex_persistent': obj = main_mip_results._solver_model.solution.pool.get_objective_value( name) elif config.mip_solver == 'gurobi_persistent': main_mip_results._solver_model.setParam( gurobipy.GRB.Param.SolutionNumber, name) obj = main_mip_results._solver_model.PoolObjVal solution_name_obj.append([name, obj]) solution_name_obj.sort( key=itemgetter(1), reverse=solve_data.objective_sense == maximize) counter = 0 for name, _ in solution_name_obj: # the optimal solution of the main problem has been added to integer_list above # so we should skip checking cycling for the first solution in the solution pool if counter >= 1: copy_var_list_values_from_solution_pool(solve_data.mip.MindtPy_utils.variable_list, solve_data.working_model.MindtPy_utils.variable_list, config, solver_model=main_mip_results._solver_model, var_map=main_mip_results._pyomo_var_to_solver_var_map, solution_name=name) solve_data.curr_int_sol = get_integer_solution( solve_data.working_model) if solve_data.curr_int_sol in set(solve_data.integer_list): config.logger.info( 'The same combination has been explored and will be skipped here.') continue else: solve_data.integer_list.append( solve_data.curr_int_sol) counter += 1 fixed_nlp, fixed_nlp_result = solve_subproblem( solve_data, config) handle_nlp_subproblem_tc( fixed_nlp, fixed_nlp_result, solve_data, config) # Call the NLP post-solve callback with time_code(solve_data.timing, 'Call after subproblem solve'): config.call_after_subproblem_solve( fixed_nlp, solve_data) if algorithm_should_terminate(solve_data, config, check_cycling=False): last_iter_cuts = True break if counter >= config.num_solution_iteration: break if config.strategy == 'ECP': add_ecp_cuts(solve_data.mip, solve_data, config) # if config.strategy == 'PSC': # # If the hybrid algorithm is not making progress, switch to OA. # progress_required = 1E-6 # if solve_data.objective_sense == minimize: # log = solve_data.LB_progress # sign_adjust = 1 # else: # log = solve_data.UB_progress # sign_adjust = -1 # # Maximum number of iterations in which the lower (optimistic) # # bound does not improve before switching to OA # max_nonimprove_iter = 5 # making_progress = True # # TODO-romeo Unnecessary for OA and ROA, right? # for i in range(1, max_nonimprove_iter + 1): # try: # if (sign_adjust * log[-i] # <= (log[-i - 1] + progress_required) # * sign_adjust): # making_progress = False # else: # making_progress = True # break # except IndexError: # # Not enough history yet, keep going. # making_progress = True # break # if not making_progress and ( # config.strategy == 'hPSC' or # config.strategy == 'PSC'): # config.logger.info( # 'Not making enough progress for {} iterations. ' # 'Switching to OA.'.format(max_nonimprove_iter)) # config.strategy = 'OA' # if add_no_good_cuts is True, the bound obtained in the last iteration is no reliable. # we correct it after the iteration. if (config.add_no_good_cuts or config.use_tabu_list) and config.strategy != 'FP' and not solve_data.should_terminate and config.add_regularization is None: fix_dual_bound(solve_data, config, last_iter_cuts) config.logger.info( ' =============================================================================================')
def fix_dual_bound(solve_data, config, last_iter_cuts): """Fix the dual bound when no-good cuts or tabu list is activated. Parameters ---------- solve_data : MindtPySolveData Data container that holds solve-instance data. config : ConfigBlock The specific configurations for MindtPy. last_iter_cuts : bool Whether the cuts in the last iteration have been added. """ if config.single_tree: config.logger.info( 'Fix the bound to the value of one iteration before optimal solution is found.') try: if solve_data.objective_sense == minimize: solve_data.LB = solve_data.stored_bound[solve_data.UB] else: solve_data.UB = solve_data.stored_bound[solve_data.LB] except KeyError: config.logger.info('No stored bound found. Bound fix failed.') else: config.logger.info( 'Solve the main problem without the last no_good cut to fix the bound.' 'zero_tolerance is set to 1E-4') config.zero_tolerance = 1E-4 # Solve NLP subproblem # The constraint linearization happens in the handlers if not last_iter_cuts: fixed_nlp, fixed_nlp_result = solve_subproblem(solve_data, config) handle_nlp_subproblem_tc( fixed_nlp, fixed_nlp_result, solve_data, config) MindtPy = solve_data.mip.MindtPy_utils # deactivate the integer cuts generated after the best solution was found. if config.strategy == 'GOA': try: if solve_data.objective_sense == minimize: valid_no_good_cuts_num = solve_data.num_no_good_cuts_added[solve_data.UB] else: valid_no_good_cuts_num = solve_data.num_no_good_cuts_added[solve_data.LB] if config.add_no_good_cuts: for i in range(valid_no_good_cuts_num+1, len(MindtPy.cuts.no_good_cuts)+1): MindtPy.cuts.no_good_cuts[i].deactivate() if config.use_tabu_list: solve_data.integer_list = solve_data.integer_list[:valid_no_good_cuts_num] except KeyError: config.logger.info('No-good cut deactivate failed.') elif config.strategy == 'OA': # Only deactive the last OA cuts may not be correct. # Since integer solution may also be cut off by OA cuts due to calculation approximation. if config.add_no_good_cuts: MindtPy.cuts.no_good_cuts[len( MindtPy.cuts.no_good_cuts)].deactivate() if config.use_tabu_list: solve_data.integer_list = solve_data.integer_list[:-1] if config.add_regularization is not None and MindtPy.find_component('mip_obj') is None: MindtPy.objective_list[-1].activate() mainopt = SolverFactory(config.mip_solver) # determine if persistent solver is called. if isinstance(mainopt, PersistentSolver): mainopt.set_instance(solve_data.mip, symbolic_solver_labels=True) if config.use_tabu_list: tabulist = mainopt._solver_model.register_callback( tabu_list.IncumbentCallback_cplex) tabulist.solve_data = solve_data tabulist.opt = mainopt tabulist.config = config mainopt._solver_model.parameters.preprocessing.reduce.set(1) # If the callback is used to reject incumbents, the user must set the # parameter c.parameters.preprocessing.reduce either to the value 1 (one) # to restrict presolve to primal reductions only or to 0 (zero) to disable all presolve reductions mainopt._solver_model.set_warning_stream(None) mainopt._solver_model.set_log_stream(None) mainopt._solver_model.set_error_stream(None) mip_args = dict(config.mip_solver_args) set_solver_options(mainopt, solve_data, config, solver_type='mip') main_mip_results = mainopt.solve( solve_data.mip, tee=config.mip_solver_tee, **mip_args) if main_mip_results.solver.termination_condition is tc.infeasible: config.logger.info( 'Bound fix failed. The bound fix problem is infeasible') else: update_suboptimal_dual_bound(solve_data, main_mip_results) config.logger.info( 'Fixed bound values: LB: {} UB: {}'. format(solve_data.LB, solve_data.UB)) # Check bound convergence if solve_data.LB + config.bound_tolerance >= solve_data.UB: solve_data.results.solver.termination_condition = tc.optimal
def test_handle_termination_condition(self): """Test the outer approximation decomposition algorithm.""" model = SimpleMINLP() config = _get_MindtPy_config() solve_data = set_up_solve_data(model, config) with time_code(solve_data.timing, 'total', is_main_timer=True), \ create_utility_block(solve_data.working_model, 'MindtPy_utils', solve_data): MindtPy = solve_data.working_model.MindtPy_utils MindtPy = solve_data.working_model.MindtPy_utils setup_results_object(solve_data, config) process_objective(solve_data, config, move_objective=(config.init_strategy == 'FP' or config.add_regularization is not None), use_mcpp=config.use_mcpp, update_var_con_list=config.add_regularization is None ) feas = MindtPy.feas_opt = Block() feas.deactivate() feas.feas_constraints = ConstraintList( doc='Feasibility Problem Constraints') lin = MindtPy.cuts = Block() lin.deactivate() if config.feasibility_norm == 'L1' or config.feasibility_norm == 'L2': feas.nl_constraint_set = RangeSet(len(MindtPy.nonlinear_constraint_list), doc='Integer index set over the nonlinear constraints.') # Create slack variables for feasibility problem feas.slack_var = Var(feas.nl_constraint_set, domain=NonNegativeReals, initialize=1) else: feas.slack_var = Var(domain=NonNegativeReals, initialize=1) # no-good cuts exclude particular discrete decisions lin.no_good_cuts = ConstraintList(doc='no-good cuts') fixed_nlp = solve_data.working_model.clone() TransformationFactory('core.fix_integer_vars').apply_to(fixed_nlp) MindtPy_initialize_main(solve_data, config) # test handle_subproblem_other_termination termination_condition = tc.maxIterations config.add_no_good_cuts = True handle_subproblem_other_termination(fixed_nlp, termination_condition, solve_data, config) self.assertEqual( len(solve_data.mip.MindtPy_utils.cuts.no_good_cuts), 1) # test handle_main_other_conditions main_mip, main_mip_results = solve_main(solve_data, config) main_mip_results.solver.termination_condition = tc.infeasible handle_main_other_conditions( solve_data.mip, main_mip_results, solve_data, config) self.assertIs( solve_data.results.solver.termination_condition, tc.feasible) main_mip_results.solver.termination_condition = tc.unbounded handle_main_other_conditions( solve_data.mip, main_mip_results, solve_data, config) self.assertIn(main_mip.MindtPy_utils.objective_bound, main_mip.component_data_objects(ctype=Constraint)) main_mip.MindtPy_utils.del_component('objective_bound') main_mip_results.solver.termination_condition = tc.infeasibleOrUnbounded handle_main_other_conditions( solve_data.mip, main_mip_results, solve_data, config) self.assertIn(main_mip.MindtPy_utils.objective_bound, main_mip.component_data_objects(ctype=Constraint)) main_mip_results.solver.termination_condition = tc.maxTimeLimit handle_main_other_conditions( solve_data.mip, main_mip_results, solve_data, config) self.assertIs( solve_data.results.solver.termination_condition, tc.maxTimeLimit) main_mip_results.solver.termination_condition = tc.other main_mip_results.solution.status = SolutionStatus.feasible handle_main_other_conditions( solve_data.mip, main_mip_results, solve_data, config) for v1, v2 in zip(main_mip.MindtPy_utils.variable_list, solve_data.working_model.MindtPy_utils.variable_list): self.assertEqual(v1.value, v2.value) # test handle_feasibility_subproblem_tc feas_subproblem = solve_data.working_model.clone() add_feas_slacks(feas_subproblem, config) MindtPy = feas_subproblem.MindtPy_utils MindtPy.feas_opt.activate() if config.feasibility_norm == 'L1': MindtPy.feas_obj = Objective( expr=sum(s for s in MindtPy.feas_opt.slack_var[...]), sense=minimize) elif config.feasibility_norm == 'L2': MindtPy.feas_obj = Objective( expr=sum(s*s for s in MindtPy.feas_opt.slack_var[...]), sense=minimize) else: MindtPy.feas_obj = Objective( expr=MindtPy.feas_opt.slack_var, sense=minimize) handle_feasibility_subproblem_tc( tc.optimal, MindtPy, solve_data, config) handle_feasibility_subproblem_tc( tc.infeasible, MindtPy, solve_data, config) self.assertIs(solve_data.should_terminate, True) self.assertIs(solve_data.results.solver.status, SolverStatus.error) solve_data.should_terminate = False solve_data.results.solver.status = None handle_feasibility_subproblem_tc( tc.maxIterations, MindtPy, solve_data, config) self.assertIs(solve_data.should_terminate, True) self.assertIs(solve_data.results.solver.status, SolverStatus.error) solve_data.should_terminate = False solve_data.results.solver.status = None handle_feasibility_subproblem_tc( tc.solverFailure, MindtPy, solve_data, config) self.assertIs(solve_data.should_terminate, True) self.assertIs(solve_data.results.solver.status, SolverStatus.error) # test NLP subproblem infeasible solve_data.working_model.Y[1].value = 0 solve_data.working_model.Y[2].value = 0 solve_data.working_model.Y[3].value = 0 fixed_nlp, fixed_nlp_results = solve_subproblem(solve_data, config) solve_data.working_model.Y[1].value = None solve_data.working_model.Y[2].value = None solve_data.working_model.Y[3].value = None # test handle_nlp_subproblem_tc fixed_nlp_results.solver.termination_condition = tc.maxTimeLimit handle_nlp_subproblem_tc( fixed_nlp, fixed_nlp_results, solve_data, config) self.assertIs(solve_data.should_terminate, True) self.assertIs( solve_data.results.solver.termination_condition, tc.maxTimeLimit) fixed_nlp_results.solver.termination_condition = tc.maxEvaluations handle_nlp_subproblem_tc( fixed_nlp, fixed_nlp_results, solve_data, config) self.assertIs(solve_data.should_terminate, True) self.assertIs( solve_data.results.solver.termination_condition, tc.maxEvaluations) fixed_nlp_results.solver.termination_condition = tc.maxIterations handle_nlp_subproblem_tc( fixed_nlp, fixed_nlp_results, solve_data, config) self.assertIs(solve_data.should_terminate, True) self.assertIs( solve_data.results.solver.termination_condition, tc.maxEvaluations) # test handle_fp_main_tc config.init_strategy = 'FP' solve_data.fp_iter = 1 init_rNLP(solve_data, config) feas_main, feas_main_results = solve_main( solve_data, config, fp=True) feas_main_results.solver.termination_condition = tc.optimal fp_should_terminate = handle_fp_main_tc( feas_main_results, solve_data, config) self.assertIs(fp_should_terminate, False) feas_main_results.solver.termination_condition = tc.maxTimeLimit fp_should_terminate = handle_fp_main_tc( feas_main_results, solve_data, config) self.assertIs(fp_should_terminate, True) self.assertIs( solve_data.results.solver.termination_condition, tc.maxTimeLimit) feas_main_results.solver.termination_condition = tc.infeasible fp_should_terminate = handle_fp_main_tc( feas_main_results, solve_data, config) self.assertIs(fp_should_terminate, True) feas_main_results.solver.termination_condition = tc.unbounded fp_should_terminate = handle_fp_main_tc( feas_main_results, solve_data, config) self.assertIs(fp_should_terminate, True) feas_main_results.solver.termination_condition = tc.other feas_main_results.solution.status = SolutionStatus.feasible fp_should_terminate = handle_fp_main_tc( feas_main_results, solve_data, config) self.assertIs(fp_should_terminate, False) feas_main_results.solver.termination_condition = tc.solverFailure fp_should_terminate = handle_fp_main_tc( feas_main_results, solve_data, config) self.assertIs(fp_should_terminate, True) # test generate_norm_constraint fp_nlp = solve_data.working_model.clone() config.fp_main_norm = 'L1' generate_norm_constraint(fp_nlp, solve_data, config) self.assertIsNotNone(fp_nlp.MindtPy_utils.find_component( 'L1_norm_constraint')) config.fp_main_norm = 'L2' generate_norm_constraint(fp_nlp, solve_data, config) self.assertIsNotNone(fp_nlp.find_component('norm_constraint')) fp_nlp.del_component('norm_constraint') config.fp_main_norm = 'L_infinity' generate_norm_constraint(fp_nlp, solve_data, config) self.assertIsNotNone(fp_nlp.find_component('norm_constraint')) # test set_solver_options config.mip_solver = 'gams' config.threads = 1 opt = SolverFactory(config.mip_solver) set_solver_options(opt, solve_data, config, 'mip', regularization=False) config.mip_solver = 'gurobi' config.mip_regularization_solver = 'gurobi' config.regularization_mip_threads = 1 opt = SolverFactory(config.mip_solver) set_solver_options(opt, solve_data, config, 'mip', regularization=True) config.nlp_solver = 'gams' config.nlp_solver_args['solver'] = 'ipopt' set_solver_options(opt, solve_data, config, 'nlp', regularization=False) config.nlp_solver_args['solver'] = 'ipopth' set_solver_options(opt, solve_data, config, 'nlp', regularization=False) config.nlp_solver_args['solver'] = 'conopt' set_solver_options(opt, solve_data, config, 'nlp', regularization=False) config.nlp_solver_args['solver'] = 'msnlp' set_solver_options(opt, solve_data, config, 'nlp', regularization=False) config.nlp_solver_args['solver'] = 'baron' set_solver_options(opt, solve_data, config, 'nlp', regularization=False) # test algorithm_should_terminate solve_data.should_terminate = True solve_data.primal_bound = float('inf') self.assertIs(algorithm_should_terminate( solve_data, config, check_cycling=False), True) self.assertIs( solve_data.results.solver.termination_condition, tc.noSolution) solve_data.primal_bound = 100 self.assertIs(algorithm_should_terminate( solve_data, config, check_cycling=False), True) self.assertIs( solve_data.results.solver.termination_condition, tc.feasible) solve_data.primal_bound_progress = [float('inf'), 5, 4, 3, 2, 1] solve_data.primal_bound_progress_time = [1, 2, 3, 4, 5, 6] solve_data.primal_bound = 1 self.assertEqual(get_primal_integral(solve_data, config), 14.5) solve_data.dual_bound_progress = [float('-inf'), 1, 2, 3, 4, 5] solve_data.dual_bound_progress_time = [1, 2, 3, 4, 5, 6] solve_data.dual_bound = 5 self.assertEqual(get_dual_integral(solve_data, config), 14.1) # test check_config config.add_regularization = 'level_L1' config.regularization_mip_threads = 0 config.threads = 8 check_config(config) self.assertEqual(config.regularization_mip_threads, 8) config.mip_solver = 'cplex' config.single_tree = True check_config(config) self.assertEqual(config.mip_solver, 'cplex_persistent') self.assertEqual(config.threads, 1) config.add_slack = True config.max_slack == 0.0 check_config(config) self.assertEqual(config.add_slack, False) config.strategy = 'GOA' config.add_slack = True config.use_mcpp = False config.equality_relaxation = True config.use_fbbt = False config.add_no_good_cuts = False config.use_tabu_list = False check_config(config) self.assertTrue(config.use_mcpp) self.assertTrue(config.use_fbbt) self.assertFalse(config.add_slack) self.assertFalse(config.equality_relaxation) self.assertTrue(config.add_no_good_cuts) self.assertFalse(config.use_tabu_list) config.single_tree = False config.strategy = 'FP' config.init_strategy = 'rNLP' config.iteration_limit = 100 config.add_no_good_cuts = False config.use_tabu_list = True check_config(config) self.assertEqual(config.init_strategy, 'FP') self.assertEqual(config.iteration_limit, 0) self.assertEqual(config.add_no_good_cuts, True) self.assertEqual(config.use_tabu_list, False)
def MindtPy_iteration_loop(solve_data, config): """ Main loop for MindtPy Algorithms This is the outermost function for the algorithms in this package; this function controls the progression of solving the model. Parameters ---------- solve_data: MindtPy Data Container data container that holds solve-instance data config: ConfigBlock contains the specific configurations for the algorithm """ last_iter_cuts = False while solve_data.mip_iter < config.iteration_limit: config.logger.info( '---MindtPy main Iteration %s---' % (solve_data.mip_iter+1)) solve_data.mip_subiter = 0 # solve MILP main problem if config.strategy in {'OA', 'GOA', 'ECP'}: main_mip, main_mip_results = solve_main(solve_data, config) if main_mip_results is not None: if not config.single_tree: if main_mip_results.solver.termination_condition is tc.optimal: handle_main_optimal(main_mip, solve_data, config) elif main_mip_results.solver.termination_condition is tc.infeasible: handle_main_infeasible(main_mip, solve_data, config) last_iter_cuts = True break else: handle_main_other_conditions( main_mip, main_mip_results, solve_data, config) # Call the MILP post-solve callback with time_code(solve_data.timing, 'Call after main solve'): config.call_after_main_solve(main_mip, solve_data) else: config.logger.info('Algorithm should terminate here.') break else: raise NotImplementedError() # regularization is activated after the first feasible solution is found. if config.add_regularization is not None and solve_data.best_solution_found is not None and not config.single_tree: # the main problem might be unbounded, regularization is activated only when a valid bound is provided. if (solve_data.objective_sense == minimize and solve_data.LB != float('-inf')) or (solve_data.objective_sense == maximize and solve_data.UB != float('inf')): main_mip, main_mip_results = solve_main( solve_data, config, regularization_problem=True) handle_regularization_main_tc( main_mip, main_mip_results, solve_data, config) if config.add_regularization is not None and config.single_tree: solve_data.curr_int_sol = get_integer_solution( solve_data.mip, string_zero=True) copy_var_list_values( main_mip.MindtPy_utils.variable_list, solve_data.working_model.MindtPy_utils.variable_list, config) if solve_data.curr_int_sol not in set(solve_data.integer_list): fixed_nlp, fixed_nlp_result = solve_subproblem( solve_data, config) handle_nlp_subproblem_tc( fixed_nlp, fixed_nlp_result, solve_data, config) if algorithm_should_terminate(solve_data, config, check_cycling=True): last_iter_cuts = False break if not config.single_tree and config.strategy != 'ECP': # if we don't use lazy callback, i.e. LP_NLP # Solve NLP subproblem # The constraint linearization happens in the handlers fixed_nlp, fixed_nlp_result = solve_subproblem(solve_data, config) handle_nlp_subproblem_tc( fixed_nlp, fixed_nlp_result, solve_data, config) # Call the NLP post-solve callback with time_code(solve_data.timing, 'Call after subproblem solve'): config.call_after_subproblem_solve(fixed_nlp, solve_data) if algorithm_should_terminate(solve_data, config, check_cycling=False): last_iter_cuts = True break if config.strategy == 'ECP': add_ecp_cuts(solve_data.mip, solve_data, config) # if config.strategy == 'PSC': # # If the hybrid algorithm is not making progress, switch to OA. # progress_required = 1E-6 # if solve_data.objective_sense == minimize: # log = solve_data.LB_progress # sign_adjust = 1 # else: # log = solve_data.UB_progress # sign_adjust = -1 # # Maximum number of iterations in which the lower (optimistic) # # bound does not improve before switching to OA # max_nonimprove_iter = 5 # making_progress = True # # TODO-romeo Unneccesary for OA and ROA, right? # for i in range(1, max_nonimprove_iter + 1): # try: # if (sign_adjust * log[-i] # <= (log[-i - 1] + progress_required) # * sign_adjust): # making_progress = False # else: # making_progress = True # break # except IndexError: # # Not enough history yet, keep going. # making_progress = True # break # if not making_progress and ( # config.strategy == 'hPSC' or # config.strategy == 'PSC'): # config.logger.info( # 'Not making enough progress for {} iterations. ' # 'Switching to OA.'.format(max_nonimprove_iter)) # config.strategy = 'OA' # if add_no_good_cuts is True, the bound obtained in the last iteration is no reliable. # we correct it after the iteration. if (config.add_no_good_cuts or config.use_tabu_list) and config.strategy is not 'FP' and not solve_data.should_terminate and config.add_regularization is None: bound_fix(solve_data, config, last_iter_cuts)
def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, solve_data, config): """ This is a GUROBI callback function defined for LP/NLP based B&B algorithm. Parameters ---------- cb_m: Pyomo model the MIP main problem cb_opt: SolverFactory the gurobi_persistent solver cb_where: int an enum member of gurobipy.GRB.Callback solve_data: MindtPy Data Container data container that holds solve-instance data config: ConfigBlock contains the specific configurations for the algorithm """ if cb_where == gurobipy.GRB.Callback.MIPSOL: # gurobipy.GRB.Callback.MIPSOL means that an integer solution is found during the branch and bound process if solve_data.should_terminate: cb_opt._solver_model.terminate() return cb_opt.cbGetSolution(vars=cb_m.MindtPy_utils.variable_list) handle_lazy_main_feasible_solution_gurobi(cb_m, cb_opt, solve_data, config) if config.add_cuts_at_incumbent: if config.strategy == 'OA': add_oa_cuts(solve_data.mip, None, solve_data, config, cb_opt) # # regularization is activated after the first feasible solution is found. if config.add_regularization is not None and solve_data.best_solution_found is not None: # the main problem might be unbounded, regularization is activated only when a valid bound is provided. if not solve_data.bound_improved and not solve_data.solution_improved: config.logger.info( 'the bound and the best found solution have neither been improved.' 'We will skip solving the regularization problem and the Fixed-NLP subproblem' ) solve_data.solution_improved = False return if ((solve_data.objective_sense == minimize and solve_data.LB != float('-inf')) or (solve_data.objective_sense == maximize and solve_data.UB != float('inf'))): main_mip, main_mip_results = solve_main( solve_data, config, regularization_problem=True) handle_regularization_main_tc(main_mip, main_mip_results, solve_data, config) if solve_data.LB + config.bound_tolerance >= solve_data.UB: config.logger.info('MindtPy exiting on bound convergence. ' 'LB: {} + (tol {}) >= UB: {}\n'.format( solve_data.LB, config.bound_tolerance, solve_data.UB)) solve_data.results.solver.termination_condition = tc.optimal cb_opt._solver_model.terminate() return # # check if the same integer combination is obtained. solve_data.curr_int_sol = get_integer_solution( solve_data.working_model, string_zero=True) if solve_data.curr_int_sol in set(solve_data.integer_list): config.logger.info( 'This integer combination has been explored. ' 'We will skip solving the Fixed-NLP subproblem.') solve_data.solution_improved = False if config.strategy == 'GOA': if config.add_no_good_cuts: var_values = list( v.value for v in solve_data.working_model.MindtPy_utils.variable_list) add_no_good_cuts(var_values, solve_data, config) return elif config.strategy == 'OA': return else: solve_data.integer_list.append(solve_data.curr_int_sol) # solve subproblem # The constraint linearization happens in the handlers fixed_nlp, fixed_nlp_result = solve_subproblem(solve_data, config) handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result, solve_data, config, cb_opt)
def __call__(self): """ This is an inherent function in LazyConstraintCallback in cplex. This function is called whenever an integer solution is found during the branch and bound process """ solve_data = self.solve_data config = self.config opt = self.opt main_mip = self.main_mip if solve_data.should_terminate: self.abort() return self.handle_lazy_main_feasible_solution(main_mip, solve_data, config, opt) if config.add_cuts_at_incumbent: self.copy_lazy_var_list_values( opt, main_mip.MindtPy_utils.variable_list, solve_data.mip.MindtPy_utils.variable_list, config) if config.strategy == 'OA': self.add_lazy_oa_cuts(solve_data.mip, None, solve_data, config, opt) # regularization is activated after the first feasible solution is found. if config.add_regularization is not None and solve_data.best_solution_found is not None: # the main problem might be unbounded, regularization is activated only when a valid bound is provided. if not solve_data.bound_improved and not solve_data.solution_improved: config.logger.info( 'the bound and the best found solution have neither been improved.' 'We will skip solving the regularization problem and the Fixed-NLP subproblem' ) solve_data.solution_improved = False return if ((solve_data.objective_sense == minimize and solve_data.LB != float('-inf')) or (solve_data.objective_sense == maximize and solve_data.UB != float('inf'))): main_mip, main_mip_results = solve_main( solve_data, config, regularization_problem=True) self.handle_lazy_regularization_problem( main_mip, main_mip_results, solve_data, config) if solve_data.LB + config.bound_tolerance >= solve_data.UB: config.logger.info('MindtPy exiting on bound convergence. ' 'LB: {} + (tol {}) >= UB: {}\n'.format( solve_data.LB, config.bound_tolerance, solve_data.UB)) solve_data.results.solver.termination_condition = tc.optimal self.abort() return # check if the same integer combination is obtained. solve_data.curr_int_sol = get_integer_solution( solve_data.working_model, string_zero=True) if solve_data.curr_int_sol in set(solve_data.integer_list): config.logger.info( 'This integer combination has been explored. ' 'We will skip solving the Fixed-NLP subproblem.') solve_data.solution_improved = False if config.strategy == 'GOA': if config.add_no_good_cuts: var_values = list( v.value for v in solve_data.working_model.MindtPy_utils.variable_list) self.add_lazy_no_good_cuts(var_values, solve_data, config, opt) return elif config.strategy == 'OA': return else: solve_data.integer_list.append(solve_data.curr_int_sol) # solve subproblem # The constraint linearization happens in the handlers fixed_nlp, fixed_nlp_result = solve_subproblem(solve_data, config) # add oa cuts if fixed_nlp_result.solver.termination_condition in { tc.optimal, tc.locallyOptimal, tc.feasible }: self.handle_lazy_subproblem_optimal(fixed_nlp, solve_data, config, opt) if solve_data.LB + config.bound_tolerance >= solve_data.UB: config.logger.info('MindtPy exiting on bound convergence. ' 'LB: {} + (tol {}) >= UB: {}\n'.format( solve_data.LB, config.bound_tolerance, solve_data.UB)) solve_data.results.solver.termination_condition = tc.optimal return elif fixed_nlp_result.solver.termination_condition in { tc.infeasible, tc.noSolution }: self.handle_lazy_subproblem_infeasible(fixed_nlp, solve_data, config, opt) else: self.handle_lazy_subproblem_other_termination( fixed_nlp, fixed_nlp_result.solver.termination_condition, solve_data, config)
def MindtPy_initialize_main(solve_data, config): """ Initializes the decomposition algorithm and creates the main MIP/MILP problem. This function initializes the decomposition problem, which includes generating the initial cuts required to build the main MIP/MILP Parameters ---------- solve_data: MindtPy Data Container data container that holds solve-instance data config: ConfigBlock contains the specific configurations for the algorithm """ # if single tree is activated, we need to add bounds for unbounded variables in nonlinear constraints to avoid unbounded main problem. if config.single_tree: var_bound_add(solve_data, config) m = solve_data.mip = solve_data.working_model.clone() next(solve_data.mip.component_data_objects(Objective, active=True)).deactivate() MindtPy = m.MindtPy_utils if config.calculate_dual: m.dual.deactivate() if config.init_strategy == 'FP': MindtPy.cuts.fp_orthogonality_cuts = ConstraintList( doc='Orthogonality cuts in feasibility pump') if config.fp_projcuts: solve_data.working_model.MindtPy_utils.cuts.fp_orthogonality_cuts = ConstraintList( doc='Orthogonality cuts in feasibility pump') if config.strategy == 'OA' or config.init_strategy == 'FP': calc_jacobians(solve_data, config) # preload jacobians MindtPy.cuts.oa_cuts = ConstraintList(doc='Outer approximation cuts') elif config.strategy == 'ECP': calc_jacobians(solve_data, config) # preload jacobians MindtPy.cuts.ecp_cuts = ConstraintList(doc='Extended Cutting Planes') elif config.strategy == 'GOA': MindtPy.cuts.aff_cuts = ConstraintList(doc='Affine cuts') # elif config.strategy == 'PSC': # detect_nonlinear_vars(solve_data, config) # MindtPy.cuts.psc_cuts = ConstraintList( # doc='Partial surrogate cuts') # elif config.strategy == 'GBD': # MindtPy.cuts.gbd_cuts = ConstraintList( # doc='Generalized Benders cuts') # Set default initialization_strategy if config.init_strategy is None: if config.strategy in {'OA', 'GOA'}: config.init_strategy = 'rNLP' else: config.init_strategy = 'max_binary' config.logger.info('{} is the initial strategy being used.' '\n'.format(config.init_strategy)) # Do the initialization if config.init_strategy == 'rNLP': init_rNLP(solve_data, config) elif config.init_strategy == 'max_binary': init_max_binaries(solve_data, config) elif config.init_strategy == 'initial_binary': solve_data.curr_int_sol = get_integer_solution( solve_data.working_model) solve_data.integer_list.append(solve_data.curr_int_sol) if config.strategy != 'ECP': fixed_nlp, fixed_nlp_result = solve_subproblem(solve_data, config) handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result, solve_data, config) elif config.init_strategy == 'FP': init_rNLP(solve_data, config) fp_loop(solve_data, config)