Skip to content

Commit

Permalink
Merge pull request #2887 from ZedongPeng/mindtpy-rewrite
Browse files Browse the repository at this point in the history
Mindtpy rewrite
  • Loading branch information
mrmundt authored Aug 23, 2023
2 parents c569b78 + 0d9430d commit 91a31d0
Show file tree
Hide file tree
Showing 25 changed files with 1,103 additions and 2,602 deletions.
1,233 changes: 571 additions & 662 deletions pyomo/contrib/mindtpy/algorithm_base_class.py

Large diffs are not rendered by default.

18 changes: 2 additions & 16 deletions pyomo/contrib/mindtpy/config_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ def _add_subsolver_configs(CONFIG):
'cplex_persistent',
'appsi_cplex',
'appsi_gurobi',
# 'appsi_highs', TODO: feasibility pump now fails with appsi_highs #2951
]
),
description='MIP subsolver name',
Expand Down Expand Up @@ -619,6 +620,7 @@ def _add_subsolver_configs(CONFIG):
'cplex_persistent',
'appsi_cplex',
'appsi_gurobi',
# 'appsi_highs',
]
),
description='MIP subsolver for regularization problem',
Expand Down Expand Up @@ -840,22 +842,6 @@ def _add_roa_configs(CONFIG):
description='The solution limit for the regularization problem since it does not need to be solved to optimality.',
),
)
CONFIG.declare(
'reduce_level_coef',
ConfigValue(
default=False,
description='Whether to reduce level coefficient in ROA single tree when regularization problem is infeasible.',
domain=bool,
),
)
CONFIG.declare(
'use_bb_tree_incumbent',
ConfigValue(
default=False,
description='Whether to use the incumbent solution of branch & bound tree in ROA single tree when regularization problem is infeasible.',
domain=bool,
),
)
CONFIG.declare(
'sqp_lag_scaling_coef',
ConfigValue(
Expand Down
46 changes: 28 additions & 18 deletions pyomo/contrib/mindtpy/cut_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,14 @@ def add_oa_cuts(
The relaxed linear model.
dual_values : list
The value of the duals for each constraint.
solve_data : MindtPySolveData
Data container that holds solve-instance data.
jacobians : ComponentMap
Map nonlinear_constraint --> Map(variable --> jacobian of constraint w.r.t. variable).
objective_sense : Int
Objective sense of model.
mip_constraint_polynomial_degree : Set
The polynomial degrees of constraints that are regarded as linear.
mip_iter : Int
MIP iteration counter.
config : ConfigBlock
The specific configurations for MindtPy.
cb_opt : SolverFactory, optional
Expand Down Expand Up @@ -189,10 +195,12 @@ def add_ecp_cuts(
----------
target_model : Pyomo model
The relaxed linear model.
solve_data : MindtPySolveData
Data container that holds solve-instance data.
jacobians : ComponentMap
Map nonlinear_constraint --> Map(variable --> jacobian of constraint w.r.t. variable)
config : ConfigBlock
The specific configurations for MindtPy.
timing : Timing
Timing.
linearize_active : bool, optional
Whether to linearize the active nonlinear constraints, by default True.
linearize_violated : bool, optional
Expand All @@ -213,9 +221,9 @@ def add_ecp_cuts(
if constr.has_ub():
try:
upper_slack = constr.uslack()
except (ValueError, OverflowError):
config.logger.warning(
'constraint {} has caused either a '
except (ValueError, OverflowError) as e:
config.logger.error(
str(e) + '\nConstraint {} has caused either a '
'ValueError or OverflowError.'
'\n'.format(constr)
)
Expand All @@ -242,9 +250,9 @@ def add_ecp_cuts(
if constr.has_lb():
try:
lower_slack = constr.lslack()
except (ValueError, OverflowError):
config.logger.warning(
'constraint {} has caused either a '
except (ValueError, OverflowError) as e:
config.logger.error(
str(e) + '\nConstraint {} has caused either a '
'ValueError or OverflowError.'
'\n'.format(constr)
)
Expand Down Expand Up @@ -278,14 +286,16 @@ def add_no_good_cuts(target_model, var_values, config, timing, mip_iter=0, cb_op
Parameters
----------
target_model : Block
The model to add no-good cuts to.
var_values : list
Variable values of the current solution, used to generate the cut.
solve_data : MindtPySolveData
Data container that holds solve-instance data.
config : ConfigBlock
The specific configurations for MindtPy.
mip_iter: Int, optional
Mip iteration counter.
timing : Timing
Timing.
mip_iter : Int, optional
MIP iteration counter.
cb_opt : SolverFactory, optional
Gurobi_persistent solver, by default None.
Expand Down Expand Up @@ -346,10 +356,10 @@ def add_affine_cuts(target_model, config, timing):
Parameters
----------
solve_data : MindtPySolveData
Data container that holds solve-instance data.
config : ConfigBlock
The specific configurations for MindtPy.
timing : Timing
Timing.
"""
with time_code(timing, 'Affine cut generation'):
m = target_model
Expand All @@ -365,8 +375,8 @@ def add_affine_cuts(target_model, config, timing):
try:
mc_eqn = mc(constr.body)
except MCPP_Error as e:
config.logger.debug(
'Skipping constraint %s due to MCPP error %s'
config.logger.error(
'\nSkipping constraint %s due to MCPP error %s'
% (constr.name, str(e))
)
continue # skip to the next constraint
Expand Down
70 changes: 23 additions & 47 deletions pyomo/contrib/mindtpy/extended_cutting_plane.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,57 +38,41 @@ class MindtPy_ECP_Solver(_MindtPyAlgorithm):

CONFIG = _get_MindtPy_ECP_config()

def MindtPy_iteration_loop(self, config):
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 progression of
This is the outermost function for the Extended Cutting Plane algorithm in this package; this function controls the progress of
solving the model.
Parameters
----------
config : ConfigBlock
The specific configurations for MindtPy.
Raises
------
ValueError
The strategy value is not correct or not included.
"""
while self.mip_iter < config.iteration_limit:
# solve MILP main problem
main_mip, main_mip_results = self.solve_main(config)
if main_mip_results is not None:
if not config.single_tree:
if main_mip_results.solver.termination_condition is tc.optimal:
self.handle_main_optimal(main_mip, config)
elif main_mip_results.solver.termination_condition is tc.infeasible:
self.handle_main_infeasible(main_mip, config)
self.last_iter_cuts = True
break
else:
self.handle_main_other_conditions(
main_mip, main_mip_results, config
)
# Call the MILP post-solve callback
with time_code(self.timing, 'Call after main solve'):
config.call_after_main_solve(main_mip)
else:
config.logger.info('Algorithm should terminate here.')
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

if self.algorithm_should_terminate(config):
# 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, config, self.timing)
add_ecp_cuts(self.mip, self.jacobians, self.config, self.timing)

# 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
self.config.add_no_good_cuts or self.config.use_tabu_list
) and not self.should_terminate:
self.fix_dual_bound(config, self.last_iter_cuts)
config.logger.info(
self.fix_dual_bound(self.last_iter_cuts)
self.config.logger.info(
' ==============================================================================================='
)

Expand All @@ -107,34 +91,24 @@ def initialize_mip_problem(self):
doc='Extended Cutting Planes'
)

def init_rNLP(self, config):
def init_rNLP(self):
"""Initialize the problem by solving the relaxed NLP and then store the optimal variable
values obtained from solving the rNLP.
Parameters
----------
config : ConfigBlock
The specific configurations for MindtPy.
Raises
------
ValueError
MindtPy unable to handle the termination condition of the relaxed NLP.
"""
super().init_rNLP(config, add_oa_cuts=False)
super().init_rNLP(add_oa_cuts=False)

def algorithm_should_terminate(self, config):
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).
Parameters
----------
config : ConfigBlock
The specific configurations for MindtPy.
Returns
-------
bool
Expand Down Expand Up @@ -164,8 +138,9 @@ def all_nonlinear_constraint_satisfied(self):
if nlc.has_lb():
try:
lower_slack = nlc.lslack()
except (ValueError, OverflowError):
except (ValueError, OverflowError) as e:
# Set lower_slack (upper_slack below) less than -config.ecp_tolerance in this case.
config.logger.error(e)
lower_slack = -10 * config.ecp_tolerance
if lower_slack < -config.ecp_tolerance:
config.logger.debug(
Expand All @@ -177,7 +152,8 @@ def all_nonlinear_constraint_satisfied(self):
if nlc.has_ub():
try:
upper_slack = nlc.uslack()
except (ValueError, OverflowError):
except (ValueError, OverflowError) as e:
config.logger.error(e)
upper_slack = -10 * config.ecp_tolerance
if upper_slack < -config.ecp_tolerance:
config.logger.debug(
Expand Down
13 changes: 3 additions & 10 deletions pyomo/contrib/mindtpy/feasibility_pump.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,9 @@
import logging
from pyomo.contrib.mindtpy.config_options import _get_MindtPy_FP_config
from pyomo.contrib.mindtpy.algorithm_base_class import _MindtPyAlgorithm
from pyomo.core import TransformationFactory, Objective, ConstraintList
from pyomo.contrib.mindtpy.util import (
set_up_logger,
setup_results_object,
add_var_bound,
calc_jacobians,
add_feas_slacks,
)
from pyomo.core import ConstraintList
from pyomo.contrib.mindtpy.util import calc_jacobians
from pyomo.opt import SolverFactory
from pyomo.contrib.gdpopt.util import time_code, lower_logger_level_to
from pyomo.contrib.mindtpy.cut_generation import add_oa_cuts


Expand Down Expand Up @@ -75,5 +68,5 @@ def add_cuts(
linearize_violated,
)

def MindtPy_iteration_loop(self, config):
def MindtPy_iteration_loop(self):
pass
32 changes: 5 additions & 27 deletions pyomo/contrib/mindtpy/global_outer_approximation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,12 @@
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________

import logging
import math
from pyomo.contrib.gdpopt.util import (
time_code,
lower_logger_level_to,
get_main_elapsed_time,
)
from pyomo.contrib.mindtpy.util import (
set_up_logger,
setup_results_object,
get_integer_solution,
copy_var_list_values_from_solution_pool,
add_var_bound,
add_feas_slacks,
)
from pyomo.core import (
TransformationFactory,
minimize,
maximize,
Objective,
ConstraintList,
)

from pyomo.contrib.gdpopt.util import get_main_elapsed_time
from pyomo.core import ConstraintList
from pyomo.opt import SolverFactory
from pyomo.contrib.mindtpy.config_options import _get_MindtPy_GOA_config
from pyomo.contrib.mindtpy.algorithm_base_class import _MindtPyAlgorithm
from pyomo.opt import TerminationCondition as tc
from pyomo.solvers.plugins.solvers.gurobi_direct import gurobipy
from operator import itemgetter
from pyomo.contrib.mindtpy.cut_generation import add_affine_cuts


Expand Down Expand Up @@ -128,5 +106,5 @@ def deactivate_no_good_cuts_when_fixing_bound(self, no_good_cuts):
no_good_cuts[i].deactivate()
if self.config.use_tabu_list:
self.integer_list = self.integer_list[:valid_no_good_cuts_num]
except KeyError:
self.config.logger.info('No-good cut deactivate failed.')
except KeyError as e:
self.config.logger.error(str(e) + '\nDeactivating no-good cuts failed.')
Loading

0 comments on commit 91a31d0

Please sign in to comment.