Source code for pyomo.contrib.mindtpy.extended_cutting_plane

# -*- coding: utf-8 -*-

#  ___________________________________________________________________________
#
#  Pyomo: Python Optimization Modeling Objects
#  Copyright (c) 2008-2024
#  National Technology and Engineering Solutions of Sandia, LLC
#  Under the terms of Contract DE-NA0003525 with National Technology and
#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
#  rights in this software.
#  This software is distributed under the 3-clause BSD License.
#  ___________________________________________________________________________

from pyomo.contrib.gdpopt.util import time_code, get_main_elapsed_time
from pyomo.contrib.mindtpy.util import calc_jacobians
from pyomo.core import ConstraintList
from pyomo.opt import SolverFactory
from pyomo.contrib.mindtpy.config_options import _get_MindtPy_ECP_config
from pyomo.contrib.mindtpy.algorithm_base_class import _MindtPyAlgorithm
from pyomo.contrib.mindtpy.cut_generation import add_ecp_cuts
from pyomo.opt import TerminationCondition as tc


[docs] @SolverFactory.register( 'mindtpy.ecp', doc='MindtPy: Mixed-Integer Nonlinear Decomposition Toolbox in Pyomo' ) class MindtPy_ECP_Solver(_MindtPyAlgorithm): """ Decomposition solver for Mixed-Integer Nonlinear Programming (MINLP) problems. The MindtPy (Mixed-Integer Nonlinear Decomposition Toolbox in Pyomo) solver applies a variety of decomposition-based approaches to solve Mixed-Integer Nonlinear Programming (MINLP) problems. This class includes: - Extended Cutting Plane (ECP) """ CONFIG = _get_MindtPy_ECP_config()
[docs] def MindtPy_iteration_loop(self): """Main loop for MindtPy Algorithms. This is the outermost function for the Extended Cutting Plane algorithm in this package; this function controls the progress of solving the model. Raises ------ ValueError The strategy value is not correct or not included. """ while self.mip_iter < self.config.iteration_limit: # solve MIP main problem main_mip, main_mip_results = self.solve_main() if self.handle_main_mip_termination(main_mip, main_mip_results): break # Call the MIP post-solve callback with time_code(self.timing, 'Call after main solve'): self.config.call_after_main_solve(main_mip) if self.algorithm_should_terminate(): self.last_iter_cuts = False break add_ecp_cuts(self.mip, self.jacobians, self.config, self.timing) self.config.logger.info( ' ===============================================================================================' )
[docs] def check_config(self): config = self.config # if ecp tolerance is not provided use bound tolerance if config.ecp_tolerance is None: config.ecp_tolerance = config.absolute_bound_tolerance super().check_config()
[docs] def initialize_mip_problem(self): """Deactivate the nonlinear constraints to create the MIP problem.""" super().initialize_mip_problem() self.jacobians = calc_jacobians( self.mip.MindtPy_utils.nonlinear_constraint_list, self.config.differentiate_mode, ) # preload jacobians self.mip.MindtPy_utils.cuts.ecp_cuts = ConstraintList( doc='Extended Cutting Planes' )
[docs] def init_rNLP(self): """Initialize the problem by solving the relaxed NLP and then store the optimal variable values obtained from solving the rNLP. Raises ------ ValueError MindtPy unable to handle the termination condition of the relaxed NLP. """ super().init_rNLP(add_oa_cuts=False)
[docs] def algorithm_should_terminate(self): """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 self.results.solver.termination_condition to the appropriate condition, i.e. optimal, maxIterations, maxTimeLimit). Returns ------- bool True if the algorithm should terminate, False otherwise. """ if self.should_terminate: if self.primal_bound == self.primal_bound_progress[0]: self.results.solver.termination_condition = tc.noSolution else: self.results.solver.termination_condition = tc.feasible return True return ( self.bounds_converged() or self.reached_iteration_limit() or self.reached_time_limit() or self.reached_stalling_limit() or self.all_nonlinear_constraint_satisfied() )
def all_nonlinear_constraint_satisfied(self): # check to see if the nonlinear constraints are satisfied config = self.config MindtPy = self.mip.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) as e: # Set lower_slack (upper_slack below) less than -config.ecp_tolerance in this case. config.logger.error(e, exc_info=True) 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) as e: config.logger.error(e, exc_info=True) 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) self.primal_bound = self.dual_bound config.logger.info( 'MindtPy-ECP exiting on nonlinear constraints satisfaction. ' 'Primal Bound: {} Dual Bound: {}\n'.format( self.primal_bound, self.dual_bound ) ) self.best_solution_found = self.mip.clone() self.results.solver.termination_condition = tc.optimal return True