Пример #1
0
    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.dual_bound_improved and not solve_data.primal_bound_improved:
                config.logger.debug(
                    '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.primal_bound_improved = False
                return
            if solve_data.dual_bound != solve_data.dual_bound_progress[0]:
                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 abs(solve_data.primal_bound -
               solve_data.dual_bound) <= config.absolute_bound_tolerance:
            config.logger.info(
                'MindtPy exiting on bound convergence. '
                '|Primal Bound: {} - Dual Bound: {}| <= (absolute tolerance {})  \n'
                .format(solve_data.primal_bound, solve_data.dual_bound,
                        config.absolute_bound_tolerance))
            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.debug(
                'This integer combination has been explored. '
                'We will skip solving the Fixed-NLP subproblem.')
            solve_data.primal_bound_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 abs(solve_data.primal_bound -
                   solve_data.dual_bound) <= config.absolute_bound_tolerance:
                config.logger.info(
                    'MindtPy exiting on bound convergence. '
                    '|Primal Bound: {} - Dual Bound: {}| <= (absolute tolerance {})  \n'
                    .format(solve_data.primal_bound, solve_data.dual_bound,
                            config.absolute_bound_tolerance))
                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)
Пример #2
0
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 : MindtPySolveData
        Data container that holds solve-instance data.
    config : ConfigBlock
        The specific configurations for MindtPy.
    """
    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.dual_bound_improved and not solve_data.primal_bound_improved:
                config.logger.debug(
                    '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.primal_bound_improved = False
                return
            if solve_data.dual_bound != solve_data.dual_bound_progress[0]:
                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 abs(solve_data.primal_bound -
               solve_data.dual_bound) <= config.absolute_bound_tolerance:
            config.logger.info(
                'MindtPy exiting on bound convergence. '
                '|Primal Bound: {} - Dual Bound: {}| <= (absolute tolerance {})  \n'
                .format(solve_data.primal_bound, solve_data.dual_bound,
                        config.absolute_bound_tolerance))
            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.debug(
                'This integer combination has been explored. '
                'We will skip solving the Fixed-NLP subproblem.')
            solve_data.primal_bound_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)
Пример #3
0
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.

    Parameters
    ----------
    solve_data : MindtPySolveData
        Data container that holds solve-instance data.
    config : ConfigBlock
        The specific configurations for MindtPy.
    """
    # 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:
        add_var_bound(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))
    config.logger.info(
        ' ============================================================================================='
    )
    config.logger.info(
        ' {:>9} | {:>15} | {:>15} | {:>11} | {:>11} | {:^7} | {:>7}\n'.format(
            'Iteration', 'Subproblem Type', 'Objective Value', 'Lower Bound',
            'Upper Bound', ' Gap ', 'Time(s)'))
    # 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)
Пример #4
0
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.

    Args:
        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(
        ' ============================================================================================='
    )
Пример #5
0
def algorithm_should_terminate(solve_data, config, check_cycling):
    """Checks if the algorithm should terminate at the given point.

    This function determines whether the algorithm should terminate based on the solver options and progress.
    (Sets the solve_data.results.solver.termination_condition to the appropriate condition, i.e. optimal,
    maxIterations, maxTimeLimit).

    Args:
        solve_data (MindtPySolveData): data container that holds solve-instance data.
        config (ConfigBlock): the specific configurations for MindtPy.
        check_cycling (bool): check for a special case that causes a binary variable to loop through the same values.

    Returns:
        bool: True if the algorithm should terminate else returns False.
    """
    if solve_data.should_terminate:
        if solve_data.objective_sense == minimize:
            if solve_data.UB == float('inf'):
                solve_data.results.solver.termination_condition = tc.noSolution
            else:
                solve_data.results.solver.termination_condition = tc.feasible
        else:
            if solve_data.LB == float('-inf'):
                solve_data.results.solver.termination_condition = tc.noSolution
            else:
                solve_data.results.solver.termination_condition = tc.feasible
        return True

    # Check bound convergence
    if solve_data.abs_gap <= config.bound_tolerance:
        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 True
    # Check relative bound convergence
    if solve_data.best_solution_found is not None:
        if solve_data.rel_gap <= config.relative_bound_tolerance:
            config.logger.info(
                'MindtPy exiting on bound convergence. '
                '(UB: {} - LB: {})/ (1e-10+|bestinteger|:{}) <= relative tolerance: {}'
                .format(
                    solve_data.UB, solve_data.LB,
                    abs(solve_data.UB if solve_data.objective_sense ==
                        minimize else solve_data.LB),
                    config.relative_bound_tolerance))
            solve_data.results.solver.termination_condition = tc.optimal
            return True

    # Check iteration limit
    if solve_data.mip_iter >= config.iteration_limit:
        config.logger.info('MindtPy unable to converge bounds '
                           'after {} main iterations.'.format(
                               solve_data.mip_iter))
        config.logger.info('Final bound values: LB: {}  UB: {}'.format(
            solve_data.LB, solve_data.UB))
        if config.single_tree:
            solve_data.results.solver.termination_condition = tc.feasible
        else:
            solve_data.results.solver.termination_condition = tc.maxIterations
        return True

    # Check time limit
    if get_main_elapsed_time(solve_data.timing) >= config.time_limit:
        config.logger.info('MindtPy unable to converge bounds '
                           'before time limit of {} seconds. '
                           'Elapsed: {} seconds'.format(
                               config.time_limit,
                               get_main_elapsed_time(solve_data.timing)))
        config.logger.info('Final bound values: LB: {}  UB: {}'.format(
            solve_data.LB, solve_data.UB))
        solve_data.results.solver.termination_condition = tc.maxTimeLimit
        return True

    # Check if algorithm is stalling
    if (len(solve_data.LB_progress) >= config.stalling_limit and solve_data.objective_sense == maximize) or \
        (len(solve_data.UB_progress) >= config.stalling_limit and solve_data.objective_sense == minimize):
        if (abs(solve_data.LB_progress[-1] - solve_data.LB_progress[-config.stalling_limit]) <= config.zero_tolerance and solve_data.objective_sense == maximize) or \
            (abs(solve_data.UB_progress[-1] - solve_data.UB_progress[-config.stalling_limit]) <= config.zero_tolerance and solve_data.objective_sense == minimize):
            config.logger.info('Algorithm is not making enough progress. '
                               'Exiting iteration loop.')
            config.logger.info('Final bound values: LB: {}  UB: {}'.format(
                solve_data.LB, solve_data.UB))
            if solve_data.best_solution_found is not None:
                solve_data.results.solver.termination_condition = tc.feasible
            else:
                # TODO: Is it correct to set solve_data.working_model as the best_solution_found?
                # In function copy_var_list_values, skip_fixed is set to True in default.
                solve_data.best_solution_found = solve_data.working_model.clone(
                )
                config.logger.warning(
                    'Algorithm did not find a feasible solution. '
                    'Returning best bound solution. Consider increasing stalling_limit or bound_tolerance.'
                )
                solve_data.results.solver.termination_condition = tc.noSolution

            return True

    if config.strategy == 'ECP':
        # check to see if the nonlinear constraints are satisfied
        MindtPy = solve_data.working_model.MindtPy_utils
        nonlinear_constraints = [c for c in MindtPy.nonlinear_constraint_list]
        for nlc in nonlinear_constraints:
            if nlc.has_lb():
                try:
                    lower_slack = nlc.lslack()
                except (ValueError, OverflowError):
                    # Set lower_slack (upper_slack below) less than -config.ecp_tolerance in this case.
                    lower_slack = -10 * config.ecp_tolerance
                if lower_slack < -config.ecp_tolerance:
                    config.logger.debug(
                        'MindtPy-ECP continuing as {} has not met the '
                        'nonlinear constraints satisfaction.'
                        '\n'.format(nlc))
                    return False
            if nlc.has_ub():
                try:
                    upper_slack = nlc.uslack()
                except (ValueError, OverflowError):
                    upper_slack = -10 * config.ecp_tolerance
                if upper_slack < -config.ecp_tolerance:
                    config.logger.debug(
                        'MindtPy-ECP continuing as {} has not met the '
                        'nonlinear constraints satisfaction.'
                        '\n'.format(nlc))
                    return False
        # For ECP to know whether to know which bound to copy over (primal or dual)
        if solve_data.objective_sense == minimize:
            solve_data.UB = solve_data.LB
        else:
            solve_data.LB = solve_data.UB
        config.logger.info(
            'MindtPy-ECP exiting on nonlinear constraints satisfaction. '
            'LB: {} UB: {}\n'.format(solve_data.LB, solve_data.UB))

        solve_data.best_solution_found = solve_data.working_model.clone()
        solve_data.results.solver.termination_condition = tc.optimal
        return True

    # Cycling check
    if check_cycling:
        if config.cycling_check or config.use_tabu_list:
            solve_data.curr_int_sol = get_integer_solution(solve_data.mip)
            if config.cycling_check and solve_data.mip_iter >= 1:
                if solve_data.curr_int_sol in set(solve_data.integer_list):
                    config.logger.info(
                        'Cycling happens after {} main iterations. '
                        'The same combination is obtained in iteration {} '
                        'This issue happens when the NLP subproblem violates constraint qualification. '
                        'Convergence to optimal solution is not guaranteed.'.
                        format(
                            solve_data.mip_iter,
                            solve_data.integer_list.index(
                                solve_data.curr_int_sol) + 1))
                    config.logger.info(
                        'Final bound values: LB: {}  UB: {}'.format(
                            solve_data.LB, solve_data.UB))
                    # TODO determine solve_data.LB, solve_data.UB is inf or -inf.
                    solve_data.results.solver.termination_condition = tc.feasible
                    return True
            solve_data.integer_list.append(solve_data.curr_int_sol)

    # if not algorithm_is_making_progress(solve_data, config):
    #     config.logger.debug(
    #         'Algorithm is not making enough progress. '
    #         'Exiting iteration loop.')
    #     return True
    return False
Пример #6
0
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)
Пример #7
0
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. 

    Args:
        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 (MindtPySolveData): data container that holds solve-instance data.
        config (ConfigBlock): the specific configurations for MindtPy.
    """
    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.debug(
                    '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.debug(
                '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)