From bb03a18464b6f5f1f62dc696db2bed3686433d74 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 21 Aug 2023 16:54:28 -0400 Subject: [PATCH 01/32] fix load_state_into_pyomo bug --- pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index 9a5dc50ef7b..fe3e62eb497 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -426,7 +426,7 @@ def load_state_into_pyomo(self, bound_multipliers=None): # not we are minimizing or maximizing - this is done in the ASL interface # for ipopt, but does not appear to be done in cyipopt. obj_sign = 1.0 - objs = list(m.component_data_objects(ctype=pyo.Objective, descend_into=True)) + objs = list(m.component_data_objects(ctype=pyo.Objective, active=True, descend_into=True)) assert len(objs) == 1 if objs[0].sense == pyo.maximize: obj_sign = -1.0 From ca54afc4c17ad23d3c417732a1570571dae957a1 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 7 Sep 2023 23:47:53 -0400 Subject: [PATCH 02/32] add the support of greybox in mindtpy --- pyomo/contrib/mindtpy/algorithm_base_class.py | 80 ++++++++++++++----- pyomo/contrib/mindtpy/config_options.py | 8 ++ pyomo/contrib/mindtpy/cut_generation.py | 47 +++++++++++ pyomo/contrib/mindtpy/feasibility_pump.py | 7 +- .../mindtpy/global_outer_approximation.py | 1 + pyomo/contrib/mindtpy/outer_approximation.py | 13 ++- 6 files changed, 134 insertions(+), 22 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 7def1dcaab3..e1d6d3e98ba 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -34,6 +34,7 @@ SolutionStatus, SolverStatus, ) +from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock from pyomo.core import ( minimize, maximize, @@ -289,7 +290,7 @@ def model_is_valid(self): results = self.mip_opt.solve( self.original_model, tee=config.mip_solver_tee, - load_solutions=False, + load_solutions=config.load_solutions, **config.mip_solver_args, ) if len(results.solution) > 0: @@ -323,6 +324,11 @@ def build_ordered_component_lists(self, model): ctype=Constraint, active=True, descend_into=(Block) ) ) + util_block.grey_box_list = list( + model.component_data_objects( + ctype=ExternalGreyBoxBlock, active=True, descend_into=(Block) + ) + ) util_block.linear_constraint_list = list( c for c in util_block.constraint_list @@ -352,7 +358,9 @@ def build_ordered_component_lists(self, model): # preserve a deterministic ordering. util_block.variable_list = list( v - for v in model.component_data_objects(ctype=Var, descend_into=(Block)) + for v in model.component_data_objects( + ctype=Var, descend_into=(Block, ExternalGreyBoxBlock) + ) if v in var_set ) util_block.discrete_variable_list = list( @@ -802,18 +810,22 @@ def init_rNLP(self, add_oa_cuts=True): MindtPy unable to handle the termination condition of the relaxed NLP. """ config = self.config - m = self.working_model.clone() + self.rnlp = self.working_model.clone() config.logger.debug('Relaxed NLP: Solve relaxed integrality') - MindtPy = m.MindtPy_utils - TransformationFactory('core.relax_integer_vars').apply_to(m) + MindtPy = self.rnlp.MindtPy_utils + TransformationFactory('core.relax_integer_vars').apply_to(self.rnlp) nlp_args = dict(config.nlp_solver_args) update_solver_timelimit(self.nlp_opt, config.nlp_solver, self.timing, config) with SuppressInfeasibleWarning(): + print('solving rnlp') results = self.nlp_opt.solve( - m, tee=config.nlp_solver_tee, load_solutions=False, **nlp_args + self.rnlp, + tee=config.nlp_solver_tee, + load_solutions=config.load_solutions, + **nlp_args, ) if len(results.solution) > 0: - m.solutions.load_from(results) + self.rnlp.solutions.load_from(results) subprob_terminate_cond = results.solver.termination_condition if subprob_terminate_cond in {tc.optimal, tc.feasible, tc.locallyOptimal}: main_objective = MindtPy.objective_list[-1] @@ -841,24 +853,24 @@ def init_rNLP(self, add_oa_cuts=True): ): # TODO: recover the opposite dual when cyipopt issue #2831 is solved. dual_values = ( - list(-1 * m.dual[c] for c in MindtPy.constraint_list) + list(-1 * self.rnlp.dual[c] for c in MindtPy.constraint_list) if config.calculate_dual_at_solution else None ) else: dual_values = ( - list(m.dual[c] for c in MindtPy.constraint_list) + list(self.rnlp.dual[c] for c in MindtPy.constraint_list) if config.calculate_dual_at_solution else None ) copy_var_list_values( - m.MindtPy_utils.variable_list, + self.rnlp.MindtPy_utils.variable_list, self.mip.MindtPy_utils.variable_list, config, ) if config.init_strategy == 'FP': copy_var_list_values( - m.MindtPy_utils.variable_list, + self.rnlp.MindtPy_utils.variable_list, self.working_model.MindtPy_utils.variable_list, config, ) @@ -867,6 +879,7 @@ def init_rNLP(self, add_oa_cuts=True): linearize_active=True, linearize_violated=True, cb_opt=None, + nlp=self.rnlp, ) for var in self.mip.MindtPy_utils.discrete_variable_list: # We don't want to trigger the reset of the global stale @@ -936,7 +949,10 @@ def init_max_binaries(self): mip_args = dict(config.mip_solver_args) update_solver_timelimit(self.mip_opt, config.mip_solver, self.timing, config) results = self.mip_opt.solve( - m, tee=config.mip_solver_tee, load_solutions=False, **mip_args + m, + tee=config.mip_solver_tee, + load_solutions=config.load_solutions, + **mip_args, ) if len(results.solution) > 0: m.solutions.load_from(results) @@ -1055,7 +1071,7 @@ def solve_subproblem(self): results = self.nlp_opt.solve( self.fixed_nlp, tee=config.nlp_solver_tee, - load_solutions=False, + load_solutions=config.load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -1153,6 +1169,7 @@ def handle_subproblem_optimal(self, fixed_nlp, cb_opt=None, fp=False): linearize_active=True, linearize_violated=True, cb_opt=cb_opt, + nlp=self.fixed_nlp, ) var_values = list(v.value for v in fixed_nlp.MindtPy_utils.variable_list) @@ -1229,6 +1246,7 @@ def handle_subproblem_infeasible(self, fixed_nlp, cb_opt=None): linearize_active=True, linearize_violated=True, cb_opt=cb_opt, + nlp=feas_subproblem, ) # Add a no-good cut to exclude this discrete option var_values = list(v.value for v in fixed_nlp.MindtPy_utils.variable_list) @@ -1311,6 +1329,12 @@ def solve_feasibility_subproblem(self): update_solver_timelimit( self.feasibility_nlp_opt, config.nlp_solver, self.timing, config ) + TransformationFactory('contrib.deactivate_trivial_constraints').apply_to( + feas_subproblem, + tmp=True, + ignore_infeasible=False, + tolerance=config.constraint_tolerance, + ) with SuppressInfeasibleWarning(): try: with time_code(self.timing, 'feasibility subproblem'): @@ -1346,6 +1370,9 @@ def solve_feasibility_subproblem(self): constr.activate() active_obj.activate() MindtPy.feas_obj.deactivate() + TransformationFactory('contrib.deactivate_trivial_constraints').revert( + feas_subproblem + ) return feas_subproblem, feas_soln def handle_feasibility_subproblem_tc(self, subprob_terminate_cond, MindtPy): @@ -1480,7 +1507,10 @@ def fix_dual_bound(self, last_iter_cuts): self.mip_opt, config.mip_solver, self.timing, config ) main_mip_results = self.mip_opt.solve( - self.mip, tee=config.mip_solver_tee, load_solutions=False, **mip_args + self.mip, + tee=config.mip_solver_tee, + load_solutions=config.load_solutions, + **mip_args, ) if len(main_mip_results.solution) > 0: self.mip.solutions.load_from(main_mip_results) @@ -1564,7 +1594,10 @@ def solve_main(self): try: main_mip_results = self.mip_opt.solve( - self.mip, tee=config.mip_solver_tee, load_solutions=False, **mip_args + self.mip, + tee=config.mip_solver_tee, + load_solutions=config.load_solutions, + **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. if len(main_mip_results.solution) > 0: @@ -1617,7 +1650,10 @@ def solve_fp_main(self): mip_args = self.set_up_mip_solver() main_mip_results = self.mip_opt.solve( - self.mip, tee=config.mip_solver_tee, load_solutions=False, **mip_args + self.mip, + tee=config.mip_solver_tee, + load_solutions=config.load_solutions, + **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. # if config.single_tree or config.use_tabu_list: @@ -1659,7 +1695,7 @@ def solve_regularization_main(self): main_mip_results = self.regularization_mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=False, + load_solutions=config.load_solutions, **dict(config.mip_solver_args), ) if len(main_mip_results.solution) > 0: @@ -1871,7 +1907,7 @@ def handle_main_unbounded(self, main_mip): main_mip_results = self.mip_opt.solve( main_mip, tee=config.mip_solver_tee, - load_solutions=False, + load_solutions=config.load_solutions, **config.mip_solver_args, ) if len(main_mip_results.solution) > 0: @@ -2263,7 +2299,10 @@ def solve_fp_subproblem(self): with SuppressInfeasibleWarning(): with time_code(self.timing, 'fp subproblem'): results = self.nlp_opt.solve( - fp_nlp, tee=config.nlp_solver_tee, load_solutions=False, **nlp_args + fp_nlp, + tee=config.nlp_solver_tee, + load_solutions=config.load_solutions, + **nlp_args, ) if len(results.solution) > 0: fp_nlp.solutions.load_from(results) @@ -2482,6 +2521,9 @@ def initialize_mip_problem(self): getattr(self.mip, 'ipopt_zU_out', _DoNothing()).deactivate() MindtPy = self.mip.MindtPy_utils + if len(MindtPy.grey_box_list) > 0: + for grey_box in MindtPy.grey_box_list: + grey_box.deactivate() if config.init_strategy == 'FP': MindtPy.cuts.fp_orthogonality_cuts = ConstraintList( diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index ed0c86baae9..507cbd995f8 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -494,6 +494,14 @@ def _add_common_configs(CONFIG): domain=bool, ), ) + CONFIG.declare( + 'load_solutions', + ConfigValue( + default=True, + description='Whether to load solutions in solve() function', + domain=bool, + ), + ) def _add_subsolver_configs(CONFIG): diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index c0449054baa..5613155ee7d 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -181,6 +181,53 @@ def add_oa_cuts( ) +def add_oa_cuts_for_grey_box( + target_model, jacobians_model, config, objective_sense, mip_iter, cb_opt=None +): + sign_adjust = -1 if objective_sense == minimize else 1 + if config.add_slack: + slack_var = target_model.MindtPy_utils.cuts.slack_vars.add() + for target_model_grey_box, jacobian_model_grey_box in zip( + target_model.MindtPy_utils.grey_box_list, + jacobians_model.MindtPy_utils.grey_box_list, + ): + jacobian_matrix = ( + jacobian_model_grey_box.get_external_model() + .evaluate_jacobian_outputs() + .toarray() + ) + for index, output in enumerate(target_model_grey_box.outputs.values()): + dual_value = jacobians_model.dual[jacobian_model_grey_box][ + output.name.replace("outputs", "output_constraints") + ] + target_model.MindtPy_utils.cuts.oa_cuts.add( + expr=copysign(1, sign_adjust * dual_value) + * ( + sum( + jacobian_matrix[index][var_index] * (var - value(var)) + for var_index, var in enumerate( + target_model_grey_box.inputs.values() + ) + ) + ) + - (output - value(output)) + - (slack_var if config.add_slack else 0) + <= 0 + ) + # TODO: gurobi_persistent currently does not support greybox model. + if ( + config.single_tree + and config.mip_solver == 'gurobi_persistent' + and mip_iter > 0 + and cb_opt is not None + ): + cb_opt.cbLazy( + target_model.MindtPy_utils.cuts.oa_cuts[ + len(target_model.MindtPy_utils.cuts.oa_cuts) + ] + ) + + def add_ecp_cuts( target_model, jacobians, diff --git a/pyomo/contrib/mindtpy/feasibility_pump.py b/pyomo/contrib/mindtpy/feasibility_pump.py index 5716400598a..990f56b8f93 100644 --- a/pyomo/contrib/mindtpy/feasibility_pump.py +++ b/pyomo/contrib/mindtpy/feasibility_pump.py @@ -52,7 +52,12 @@ def initialize_mip_problem(self): ) def add_cuts( - self, dual_values, linearize_active=True, linearize_violated=True, cb_opt=None + self, + dual_values, + linearize_active=True, + linearize_violated=True, + cb_opt=None, + nlp=None, ): add_oa_cuts( self.mip, diff --git a/pyomo/contrib/mindtpy/global_outer_approximation.py b/pyomo/contrib/mindtpy/global_outer_approximation.py index ee3ffb62f55..dfb7ef54630 100644 --- a/pyomo/contrib/mindtpy/global_outer_approximation.py +++ b/pyomo/contrib/mindtpy/global_outer_approximation.py @@ -95,6 +95,7 @@ def add_cuts( linearize_active=True, linearize_violated=True, cb_opt=None, + nlp=None, ): add_affine_cuts(self.mip, self.config, self.timing) diff --git a/pyomo/contrib/mindtpy/outer_approximation.py b/pyomo/contrib/mindtpy/outer_approximation.py index 99d9cea1bd4..6cf0b26cb37 100644 --- a/pyomo/contrib/mindtpy/outer_approximation.py +++ b/pyomo/contrib/mindtpy/outer_approximation.py @@ -16,7 +16,7 @@ from pyomo.opt import SolverFactory from pyomo.contrib.mindtpy.config_options import _get_MindtPy_OA_config from pyomo.contrib.mindtpy.algorithm_base_class import _MindtPyAlgorithm -from pyomo.contrib.mindtpy.cut_generation import add_oa_cuts +from pyomo.contrib.mindtpy.cut_generation import add_oa_cuts, add_oa_cuts_for_grey_box @SolverFactory.register( @@ -102,7 +102,12 @@ def initialize_mip_problem(self): ) def add_cuts( - self, dual_values, linearize_active=True, linearize_violated=True, cb_opt=None + self, + dual_values, + linearize_active=True, + linearize_violated=True, + cb_opt=None, + nlp=None, ): add_oa_cuts( self.mip, @@ -117,6 +122,10 @@ def add_cuts( linearize_active, linearize_violated, ) + if len(self.mip.MindtPy_utils.grey_box_list) > 0: + add_oa_cuts_for_grey_box( + self.mip, nlp, self.config, self.objective_sense, self.mip_iter, cb_opt + ) def deactivate_no_good_cuts_when_fixing_bound(self, no_good_cuts): # Only deactivate the last OA cuts may not be correct. From ec05a3d49f10b13c27ee73a87d392f3b6fd6e722 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 8 Sep 2023 00:05:51 -0400 Subject: [PATCH 03/32] add grey box test in mindtpy --- pyomo/contrib/mindtpy/tests/MINLP_simple.py | 42 +++++- .../mindtpy/tests/MINLP_simple_grey_box.py | 140 ++++++++++++++++++ .../mindtpy/tests/test_mindtpy_grey_box.py | 75 ++++++++++ 3 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py create mode 100644 pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 5663c93af8b..6e1518e1b63 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -35,14 +35,23 @@ RangeSet, Var, minimize, + Block, ) from pyomo.common.collections import ComponentMap +from pyomo.contrib.mindtpy.tests.MINLP_simple_grey_box import GreyBoxModel +from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock + + +def build_model_external(m): + ex_model = GreyBoxModel(initial={"X1": 0, "X2": 0, "Y1": 0, "Y2": 1, "Y3": 1}) + m.egb = ExternalGreyBoxBlock() + m.egb.set_external_model(ex_model) class SimpleMINLP(ConcreteModel): """Convex MINLP problem Assignment 6 APSE.""" - def __init__(self, *args, **kwargs): + def __init__(self, grey_box=False, *args, **kwargs): """Create the problem.""" kwargs.setdefault('name', 'SimpleMINLP') super(SimpleMINLP, self).__init__(*args, **kwargs) @@ -83,6 +92,37 @@ def __init__(self, *args, **kwargs): m.objective = Objective( expr=Y[1] + 1.5 * Y[2] + 0.5 * Y[3] + X[1] ** 2 + X[2] ** 2, sense=minimize ) + + if not grey_box: + m.objective = Objective( + expr=Y[1] + 1.5 * Y[2] + 0.5 * Y[3] + X[1] ** 2 + X[2] ** 2, + sense=minimize, + ) + else: + def _model_i(b): + build_model_external(b) + + m.my_block = Block(rule=_model_i) + + for i in m.I: + + def eq_inputX(m): + return m.X[i] == m.my_block.egb.inputs["X" + str(i)] + + con_name = "con_X_" + str(i) + m.add_component(con_name, Constraint(expr=eq_inputX)) + + for j in m.J: + + def eq_inputY(m): + return m.Y[j] == m.my_block.egb.inputs["Y" + str(j)] + + con_name = "con_Y_" + str(j) + m.add_component(con_name, Constraint(expr=eq_inputY)) + + # add objective + m.objective = Objective(expr=m.my_block.egb.outputs['z'], sense=minimize) + """Bound definitions""" # x (continuous) upper bounds x_ubs = {1: 4, 2: 4} diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py new file mode 100644 index 00000000000..069d2d894f4 --- /dev/null +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -0,0 +1,140 @@ +import numpy as np +import pyomo.environ as pyo +from scipy.sparse import coo_matrix +from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxModel +from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock + + +class GreyBoxModel(ExternalGreyBoxModel): + """Greybox model to compute the example OF.""" + + def __init__(self, initial, use_exact_derivatives=True, verbose=True): + """ + Parameters + + use_exact_derivatives: bool + If True, the exact derivatives are used. If False, the finite difference + approximation is used. + verbose: bool + If True, print information about the model. + """ + self._use_exact_derivatives = use_exact_derivatives + self.verbose = verbose + self.initial = initial + + # For use with exact Hessian + self._output_con_mult_values = np.zeros(1) + + if not use_exact_derivatives: + raise NotImplementedError("use_exact_derivatives == False not supported") + + def input_names(self): + """Return the names of the inputs.""" + self.input_name_list = ["X1", "X2", "Y1", "Y2", "Y3"] + + return self.input_name_list + + def equality_constraint_names(self): + """Return the names of the equality constraints.""" + # no equality constraints + return [] + + def output_names(self): + """Return the names of the outputs.""" + return ['z'] + + def set_output_constraint_multipliers(self, output_con_multiplier_values): + """Set the values of the output constraint multipliers.""" + # because we only have one output constraint + assert len(output_con_multiplier_values) == 1 + np.copyto(self._output_con_mult_values, output_con_multiplier_values) + + def finalize_block_construction(self, pyomo_block): + """Finalize the construction of the ExternalGreyBoxBlock.""" + if self.initial is not None: + print("initialized") + pyomo_block.inputs["X1"].value = self.initial["X1"] + pyomo_block.inputs["X2"].value = self.initial["X2"] + pyomo_block.inputs["Y1"].value = self.initial["Y1"] + pyomo_block.inputs["Y2"].value = self.initial["Y2"] + pyomo_block.inputs["Y3"].value = self.initial["Y3"] + + else: + print("uninitialized") + for n in self.input_name_list: + pyomo_block.inputs[n].value = 1 + + pyomo_block.inputs["X1"].setub(4) + pyomo_block.inputs["X1"].setlb(0) + + pyomo_block.inputs["X2"].setub(4) + pyomo_block.inputs["X2"].setlb(0) + + pyomo_block.inputs["Y1"].setub(1) + pyomo_block.inputs["Y1"].setlb(0) + + pyomo_block.inputs["Y2"].setub(1) + pyomo_block.inputs["Y2"].setlb(0) + + pyomo_block.inputs["Y3"].setub(1) + pyomo_block.inputs["Y3"].setlb(0) + + def set_input_values(self, input_values): + """Set the values of the inputs.""" + self._input_values = list(input_values) + + def evaluate_equality_constraints(self): + """Evaluate the equality constraints.""" + # Not sure what this function should return with no equality constraints + return None + + def evaluate_outputs(self): + """Evaluate the output of the model.""" + # form matrix as a list of lists + # M = self._extract_and_assemble_fim() + x1 = self._input_values[0] + x2 = self._input_values[1] + y1 = self._input_values[2] + y2 = self._input_values[3] + y3 = self._input_values[4] + # z + z = x1**2 + x2**2 + y1 + 1.5 * y2 + 0.5 * y3 + + if self.verbose: + pass + # print("\n Consider inputs [x1,x2,y1,y2,y3] =\n",x1, x2, y1, y2, y3) + # print(" z = ",z,"\n") + + return np.asarray([z], dtype=np.float64) + + def evaluate_jacobian_equality_constraints(self): + """Evaluate the Jacobian of the equality constraints.""" + return None + + ''' + def _extract_and_assemble_fim(self): + M = np.zeros((self.n_parameters, self.n_parameters)) + for i in range(self.n_parameters): + for k in range(self.n_parameters): + M[i,k] = self._input_values[self.ele_to_order[(i,k)]] + + return M + ''' + + def evaluate_jacobian_outputs(self): + """Evaluate the Jacobian of the outputs.""" + if self._use_exact_derivatives: + + # compute gradient of log determinant + row = np.zeros(5) # to store row index + col = np.zeros(5) # to store column index + data = np.zeros(5) # to store data + + row[0], col[0], data[0] = (0, 0, 2 * self._input_values[0]) # x1 + row[0], col[1], data[1] = (0, 1, 2 * self._input_values[1]) # x2 + row[0], col[2], data[2] = (0, 2, 1) # y1 + row[0], col[3], data[3] = (0, 3, 1.5) # y2 + row[0], col[4], data[4] = (0, 4, 0.5) # y3 + + # sparse matrix + return coo_matrix((data, (row, col)), shape=(1, 5)) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py new file mode 100644 index 00000000000..d9ba683d198 --- /dev/null +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -0,0 +1,75 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# 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. +# ___________________________________________________________________________ + +"""Tests for the MindtPy solver.""" +from pyomo.core.expr.calculus.diff_with_sympy import differentiate_available +import pyomo.common.unittest as unittest +from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP +from pyomo.environ import SolverFactory, value, maximize +from pyomo.opt import TerminationCondition + + +model_list = [SimpleMINLP(grey_box=True)] +required_solvers = ('cyipopt', 'glpk') +if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): + subsolvers_available = True +else: + subsolvers_available = False + + +@unittest.skipIf( + not subsolvers_available, + 'Required subsolvers %s are not available' % (required_solvers,), +) +@unittest.skipIf( + not differentiate_available, 'Symbolic differentiation is not available' +) +class TestMindtPy(unittest.TestCase): + """Tests for the MindtPy solver plugin.""" + + def check_optimal_solution(self, model, places=1): + for var in model.optimal_solution: + self.assertAlmostEqual( + var.value, model.optimal_solution[var], places=places + ) + + def test_OA_rNLP(self): + """Test the outer approximation decomposition algorithm.""" + with SolverFactory('mindtpy') as opt: + for model in model_list: + model = model.clone() + results = opt.solve( + model, + strategy='OA', + init_strategy='rNLP', + mip_solver=required_solvers[1], + nlp_solver=required_solvers[0], + calculate_dual_at_solution=True, + nlp_solver_args={ + 'options': { + 'hessian_approximation': 'limited-memory', + 'linear_solver': 'mumps', + } + }, + ) + + self.assertIn( + results.solver.termination_condition, + [TerminationCondition.optimal, TerminationCondition.feasible], + ) + self.assertAlmostEqual( + value(model.objective.expr), model.optimal_value, places=1 + ) + self.check_optimal_solution(model) + + +if __name__ == '__main__': + unittest.main() From c2877a3fa0589d379aeb622ae889cac2672ef9be Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 8 Sep 2023 14:49:15 -0400 Subject: [PATCH 04/32] black format --- pyomo/contrib/mindtpy/tests/MINLP_simple.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 6e1518e1b63..91976997c34 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -99,6 +99,7 @@ def __init__(self, grey_box=False, *args, **kwargs): sense=minimize, ) else: + def _model_i(b): build_model_external(b) From ccff4167e61363a84d2899016dd390eeb5c596c1 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 8 Sep 2023 14:52:42 -0400 Subject: [PATCH 05/32] black format --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 1 - pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 069d2d894f4..562e88ea667 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -124,7 +124,6 @@ def _extract_and_assemble_fim(self): def evaluate_jacobian_outputs(self): """Evaluate the Jacobian of the outputs.""" if self._use_exact_derivatives: - # compute gradient of log determinant row = np.zeros(5) # to store row index col = np.zeros(5) # to store column index diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index a37507dee6b..0b6774a7d1a 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -426,7 +426,11 @@ def load_state_into_pyomo(self, bound_multipliers=None): # not we are minimizing or maximizing - this is done in the ASL interface # for ipopt, but does not appear to be done in cyipopt. obj_sign = 1.0 - objs = list(m.component_data_objects(ctype=pyo.Objective, active=True, descend_into=True)) + objs = list( + m.component_data_objects( + ctype=pyo.Objective, active=True, descend_into=True + ) + ) assert len(objs) == 1 if objs[0].sense == pyo.maximize: obj_sign = -1.0 From e461e93b8253f4d00ae50b1dd9056ed8f09437b2 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Sun, 10 Sep 2023 21:49:08 -0400 Subject: [PATCH 06/32] add comment --- pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index 0b6774a7d1a..d2a55c67f48 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -426,6 +426,8 @@ def load_state_into_pyomo(self, bound_multipliers=None): # not we are minimizing or maximizing - this is done in the ASL interface # for ipopt, but does not appear to be done in cyipopt. obj_sign = 1.0 + # since we will assert the number of objective functions, + # we only focus on active objective function. objs = list( m.component_data_objects( ctype=pyo.Objective, active=True, descend_into=True From 13a72133f5be5b612a4585dd989d32f3fc7435f8 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 19 Sep 2023 19:33:25 -0400 Subject: [PATCH 07/32] remove the support of greybox in LP/NLP gurobi --- pyomo/contrib/mindtpy/cut_generation.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index 5613155ee7d..dd60b004830 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -215,17 +215,18 @@ def add_oa_cuts_for_grey_box( <= 0 ) # TODO: gurobi_persistent currently does not support greybox model. - if ( - config.single_tree - and config.mip_solver == 'gurobi_persistent' - and mip_iter > 0 - and cb_opt is not None - ): - cb_opt.cbLazy( - target_model.MindtPy_utils.cuts.oa_cuts[ - len(target_model.MindtPy_utils.cuts.oa_cuts) - ] - ) + # https://github.com/Pyomo/pyomo/issues/3000 + # if ( + # config.single_tree + # and config.mip_solver == 'gurobi_persistent' + # and mip_iter > 0 + # and cb_opt is not None + # ): + # cb_opt.cbLazy( + # target_model.MindtPy_utils.cuts.oa_cuts[ + # len(target_model.MindtPy_utils.cuts.oa_cuts) + # ] + # ) def add_ecp_cuts( From 20ccbdc5663fef2280408cda46d68bc0c21eebae Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 19 Sep 2023 19:41:48 -0400 Subject: [PATCH 08/32] black format --- pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index d2a55c67f48..945e9a05f51 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -426,7 +426,7 @@ def load_state_into_pyomo(self, bound_multipliers=None): # not we are minimizing or maximizing - this is done in the ASL interface # for ipopt, but does not appear to be done in cyipopt. obj_sign = 1.0 - # since we will assert the number of objective functions, + # since we will assert the number of objective functions, # we only focus on active objective function. objs = list( m.component_data_objects( From 4288e338459a8d5b785403daf4a30de87ed1a9e9 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 11:54:35 -0400 Subject: [PATCH 09/32] update import source --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 562e88ea667..00b78e7b89f 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -1,6 +1,5 @@ -import numpy as np -import pyomo.environ as pyo -from scipy.sparse import coo_matrix +from pyomo.common.dependencies import numpy as np +from pyomo.common.dependencies.scipy.sparse import coo_matrix from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxModel from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock From 8d7f6c5915f881dbdf272cef2ccc83ead5772450 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 12:06:49 -0400 Subject: [PATCH 10/32] remove redundant import --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 8fd728a7c9b..3684a6c3234 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,8 +11,6 @@ import abc import logging -import numpy as np -from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From 807e4f5fea8f91b09680ec4cea2a84b1a5fa45fd Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 14:29:27 -0400 Subject: [PATCH 11/32] add config check for load_solutions --- pyomo/contrib/mindtpy/algorithm_base_class.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index e1d6d3e98ba..f3877304adb 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -817,7 +817,6 @@ def init_rNLP(self, add_oa_cuts=True): nlp_args = dict(config.nlp_solver_args) update_solver_timelimit(self.nlp_opt, config.nlp_solver, self.timing, config) with SuppressInfeasibleWarning(): - print('solving rnlp') results = self.nlp_opt.solve( self.rnlp, tee=config.nlp_solver_tee, @@ -2236,6 +2235,17 @@ def check_config(self): config.logger.info("Solution pool does not support APPSI solver.") config.mip_solver = 'cplex_persistent' + # related to https://github.com/Pyomo/pyomo/issues/2363 + if ( + 'appsi' in config.mip_solver + or 'appsi' in config.nlp_solver + or ( + config.mip_regularization_solver is not None + and 'appsi' in config.mip_regularization_solver + ) + ): + config.load_solutions = False + ################################################################################################################################ # Feasibility Pump From 30771b332f866bd861abf339c042114a88e7e0c1 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 14:35:29 -0400 Subject: [PATCH 12/32] recover numpy and scipy import --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 3684a6c3234..8fd728a7c9b 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,6 +11,8 @@ import abc import logging +import numpy as np +from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From 72b142baa8053644b2eca30e21db9f01d71fce68 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 14:43:05 -0400 Subject: [PATCH 13/32] change numpy and scipy import --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 8fd728a7c9b..1594098069c 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,8 +11,8 @@ import abc import logging -import numpy as np -from scipy.sparse import coo_matrix +from pyomo.common.dependencies import numpy as np +from pyomo.common.dependencies.scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From ff401df0a9e732f80635a1a7af5abc43c2176bff Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 22:34:31 -0400 Subject: [PATCH 14/32] change scipy import --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 4 ++-- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 00b78e7b89f..186db3bb5a2 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -1,5 +1,5 @@ from pyomo.common.dependencies import numpy as np -from pyomo.common.dependencies.scipy.sparse import coo_matrix +import pyomo.common.dependencies.scipy.sparse as scipy_sparse from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxModel from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock @@ -135,4 +135,4 @@ def evaluate_jacobian_outputs(self): row[0], col[4], data[4] = (0, 4, 0.5) # y3 # sparse matrix - return coo_matrix((data, (row, col)), shape=(1, 5)) + return scipy_sparse.coo_matrix((data, (row, col)), shape=(1, 5)) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 1594098069c..8fd728a7c9b 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,8 +11,8 @@ import abc import logging -from pyomo.common.dependencies import numpy as np -from pyomo.common.dependencies.scipy.sparse import coo_matrix +import numpy as np +from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From d82dcde63ebcb402d626b85608f02ae4325cdc4d Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 21 Sep 2023 17:59:11 -0400 Subject: [PATCH 15/32] fix import error --- pyomo/contrib/mindtpy/algorithm_base_class.py | 7 +++---- pyomo/contrib/mindtpy/tests/MINLP_simple.py | 5 +++-- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index f3877304adb..533b4daa88f 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -34,7 +34,6 @@ SolutionStatus, SolverStatus, ) -from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock from pyomo.core import ( minimize, maximize, @@ -85,7 +84,7 @@ single_tree, single_tree_available = attempt_import('pyomo.contrib.mindtpy.single_tree') tabu_list, tabu_list_available = attempt_import('pyomo.contrib.mindtpy.tabu_list') - +egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] class _MindtPyAlgorithm(object): def __init__(self, **kwds): @@ -326,7 +325,7 @@ def build_ordered_component_lists(self, model): ) util_block.grey_box_list = list( model.component_data_objects( - ctype=ExternalGreyBoxBlock, active=True, descend_into=(Block) + ctype=egb.ExternalGreyBoxBlock, active=True, descend_into=(Block) ) ) util_block.linear_constraint_list = list( @@ -359,7 +358,7 @@ def build_ordered_component_lists(self, model): util_block.variable_list = list( v for v in model.component_data_objects( - ctype=Var, descend_into=(Block, ExternalGreyBoxBlock) + ctype=Var, descend_into=(Block, egb.ExternalGreyBoxBlock) ) if v in var_set ) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 91976997c34..7498b65adad 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -39,12 +39,13 @@ ) from pyomo.common.collections import ComponentMap from pyomo.contrib.mindtpy.tests.MINLP_simple_grey_box import GreyBoxModel -from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock +from pyomo.common.dependencies import attempt_import +egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] def build_model_external(m): ex_model = GreyBoxModel(initial={"X1": 0, "X2": 0, "Y1": 0, "Y2": 1, "Y3": 1}) - m.egb = ExternalGreyBoxBlock() + m.egb = egb.ExternalGreyBoxBlock() m.egb.set_external_model(ex_model) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 186db3bb5a2..d6af495d504 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -1,10 +1,10 @@ from pyomo.common.dependencies import numpy as np import pyomo.common.dependencies.scipy.sparse as scipy_sparse -from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxModel -from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock +from pyomo.common.dependencies import attempt_import +egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] -class GreyBoxModel(ExternalGreyBoxModel): +class GreyBoxModel(egb.ExternalGreyBoxModel): """Greybox model to compute the example OF.""" def __init__(self, initial, use_exact_derivatives=True, verbose=True): From cb1c2a94f5d79a937cdfcc91512a34e244767923 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 21 Sep 2023 17:59:44 -0400 Subject: [PATCH 16/32] black format --- pyomo/contrib/mindtpy/algorithm_base_class.py | 1 + pyomo/contrib/mindtpy/tests/MINLP_simple.py | 1 + pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 1 + 3 files changed, 3 insertions(+) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 533b4daa88f..d562c924a7d 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -86,6 +86,7 @@ tabu_list, tabu_list_available = attempt_import('pyomo.contrib.mindtpy.tabu_list') egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] + class _MindtPyAlgorithm(object): def __init__(self, **kwds): """ diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 7498b65adad..04315f59458 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -40,6 +40,7 @@ from pyomo.common.collections import ComponentMap from pyomo.contrib.mindtpy.tests.MINLP_simple_grey_box import GreyBoxModel from pyomo.common.dependencies import attempt_import + egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index d6af495d504..9fccf1e7108 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -1,6 +1,7 @@ from pyomo.common.dependencies import numpy as np import pyomo.common.dependencies.scipy.sparse as scipy_sparse from pyomo.common.dependencies import attempt_import + egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] From e80c6dcf607ac82277a89f64ae42b826dd5d9319 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 21 Sep 2023 18:59:29 -0400 Subject: [PATCH 17/32] update mindtpy import in pynumero --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 8fd728a7c9b..8b815b84335 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,7 +11,7 @@ import abc import logging -import numpy as np +from pyomo.common.dependencies import numpy as np from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass From 89596661fb641287e64263d95925cb1d065a3d85 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 21 Sep 2023 19:28:09 -0400 Subject: [PATCH 18/32] remove redundant scipy import --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 8b815b84335..a1a01d751e7 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -12,7 +12,6 @@ import abc import logging from pyomo.common.dependencies import numpy as np -from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From e0c245bb9b4b9b0433e422a280866d5b62945718 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 25 Sep 2023 12:36:16 -0400 Subject: [PATCH 19/32] add skip --- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 12 ++++++++++-- .../contrib/pynumero/interfaces/external_grey_box.py | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index d9ba683d198..f3310e2f1c8 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -15,7 +15,7 @@ from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP from pyomo.environ import SolverFactory, value, maximize from pyomo.opt import TerminationCondition - +from pyomo.common.dependencies import numpy_available, scipy_available model_list = [SimpleMINLP(grey_box=True)] required_solvers = ('cyipopt', 'glpk') @@ -24,7 +24,6 @@ else: subsolvers_available = False - @unittest.skipIf( not subsolvers_available, 'Required subsolvers %s are not available' % (required_solvers,), @@ -32,6 +31,15 @@ @unittest.skipIf( not differentiate_available, 'Symbolic differentiation is not available' ) +@unittest.skipIf( + not numpy_available, + 'Required numpy %s is not available', +) +@unittest.skipIf( + not scipy_available, + 'Required scipy %s is not available', +) + class TestMindtPy(unittest.TestCase): """Tests for the MindtPy solver plugin.""" diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index a1a01d751e7..642fd3bf310 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,6 +11,7 @@ import abc import logging +from scipy.sparse import coo_matrix from pyomo.common.dependencies import numpy as np from pyomo.common.deprecation import RenamedClass From 5ffaeb11aa0f09e3dad9ab2d2ddc154a67c6c542 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 25 Sep 2023 12:36:38 -0400 Subject: [PATCH 20/32] black format --- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index f3310e2f1c8..0618c447104 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -24,6 +24,7 @@ else: subsolvers_available = False + @unittest.skipIf( not subsolvers_available, 'Required subsolvers %s are not available' % (required_solvers,), @@ -31,15 +32,8 @@ @unittest.skipIf( not differentiate_available, 'Symbolic differentiation is not available' ) -@unittest.skipIf( - not numpy_available, - 'Required numpy %s is not available', -) -@unittest.skipIf( - not scipy_available, - 'Required scipy %s is not available', -) - +@unittest.skipIf(not numpy_available, 'Required numpy %s is not available') +@unittest.skipIf(not scipy_available, 'Required scipy %s is not available') class TestMindtPy(unittest.TestCase): """Tests for the MindtPy solver plugin.""" From 260b2d637d8b99b6066fc25d1b8580dc5cdc498e Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 25 Sep 2023 13:12:11 -0400 Subject: [PATCH 21/32] move numpy and scipy check forward --- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index 0618c447104..5360cfab687 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -12,10 +12,12 @@ """Tests for the MindtPy solver.""" from pyomo.core.expr.calculus.diff_with_sympy import differentiate_available import pyomo.common.unittest as unittest -from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP from pyomo.environ import SolverFactory, value, maximize from pyomo.opt import TerminationCondition from pyomo.common.dependencies import numpy_available, scipy_available +@unittest.skipIf(not numpy_available, 'Required numpy %s is not available') +@unittest.skipIf(not scipy_available, 'Required scipy %s is not available') +from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP model_list = [SimpleMINLP(grey_box=True)] required_solvers = ('cyipopt', 'glpk') @@ -32,8 +34,7 @@ @unittest.skipIf( not differentiate_available, 'Symbolic differentiation is not available' ) -@unittest.skipIf(not numpy_available, 'Required numpy %s is not available') -@unittest.skipIf(not scipy_available, 'Required scipy %s is not available') + class TestMindtPy(unittest.TestCase): """Tests for the MindtPy solver plugin.""" From 46cf8a58174d866cf8c0db28527726d17717e08a Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 14:25:22 -0400 Subject: [PATCH 22/32] fix --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 8 +++----- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 9fccf1e7108..19a637744a9 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -8,7 +8,7 @@ class GreyBoxModel(egb.ExternalGreyBoxModel): """Greybox model to compute the example OF.""" - def __init__(self, initial, use_exact_derivatives=True, verbose=True): + def __init__(self, initial, use_exact_derivatives=True, verbose=False): """ Parameters @@ -85,7 +85,6 @@ def set_input_values(self, input_values): def evaluate_equality_constraints(self): """Evaluate the equality constraints.""" - # Not sure what this function should return with no equality constraints return None def evaluate_outputs(self): @@ -101,9 +100,8 @@ def evaluate_outputs(self): z = x1**2 + x2**2 + y1 + 1.5 * y2 + 0.5 * y3 if self.verbose: - pass - # print("\n Consider inputs [x1,x2,y1,y2,y3] =\n",x1, x2, y1, y2, y3) - # print(" z = ",z,"\n") + print("\n Consider inputs [x1,x2,y1,y2,y3] =\n",x1, x2, y1, y2, y3) + print(" z = ",z,"\n") return np.asarray([z], dtype=np.float64) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index 5360cfab687..95e42065122 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -15,8 +15,6 @@ from pyomo.environ import SolverFactory, value, maximize from pyomo.opt import TerminationCondition from pyomo.common.dependencies import numpy_available, scipy_available -@unittest.skipIf(not numpy_available, 'Required numpy %s is not available') -@unittest.skipIf(not scipy_available, 'Required scipy %s is not available') from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP model_list = [SimpleMINLP(grey_box=True)] @@ -34,6 +32,8 @@ @unittest.skipIf( not differentiate_available, 'Symbolic differentiation is not available' ) +@unittest.skipIf(not numpy_available, 'Required numpy %s is not available') +@unittest.skipIf(not scipy_available, 'Required scipy %s is not available') class TestMindtPy(unittest.TestCase): """Tests for the MindtPy solver plugin.""" From ba135b631a7cf5d2d6555fded604ceebc054ee5d Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 14:31:04 -0400 Subject: [PATCH 23/32] black format --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 4 ++-- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 19a637744a9..db37c4390c9 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -100,8 +100,8 @@ def evaluate_outputs(self): z = x1**2 + x2**2 + y1 + 1.5 * y2 + 0.5 * y3 if self.verbose: - print("\n Consider inputs [x1,x2,y1,y2,y3] =\n",x1, x2, y1, y2, y3) - print(" z = ",z,"\n") + print("\n Consider inputs [x1,x2,y1,y2,y3] =\n", x1, x2, y1, y2, y3) + print(" z = ", z, "\n") return np.asarray([z], dtype=np.float64) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index 95e42065122..70ae881abb2 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -34,7 +34,6 @@ ) @unittest.skipIf(not numpy_available, 'Required numpy %s is not available') @unittest.skipIf(not scipy_available, 'Required scipy %s is not available') - class TestMindtPy(unittest.TestCase): """Tests for the MindtPy solver plugin.""" From 8eacf0b73aada09f5d187dd34b47c7d0d622a634 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 18:50:02 -0400 Subject: [PATCH 24/32] fix import bug --- pyomo/contrib/mindtpy/tests/MINLP_simple.py | 18 +- .../mindtpy/tests/MINLP_simple_grey_box.py | 280 +++++++++--------- .../mindtpy/tests/test_mindtpy_grey_box.py | 3 +- 3 files changed, 156 insertions(+), 145 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 04315f59458..7454b595986 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -38,16 +38,10 @@ Block, ) from pyomo.common.collections import ComponentMap -from pyomo.contrib.mindtpy.tests.MINLP_simple_grey_box import GreyBoxModel -from pyomo.common.dependencies import attempt_import - -egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] - - -def build_model_external(m): - ex_model = GreyBoxModel(initial={"X1": 0, "X2": 0, "Y1": 0, "Y2": 1, "Y3": 1}) - m.egb = egb.ExternalGreyBoxBlock() - m.egb.set_external_model(ex_model) +from pyomo.contrib.mindtpy.tests.MINLP_simple_grey_box import ( + GreyBoxModel, + build_model_external, +) class SimpleMINLP(ConcreteModel): @@ -56,6 +50,10 @@ class SimpleMINLP(ConcreteModel): def __init__(self, grey_box=False, *args, **kwargs): """Create the problem.""" kwargs.setdefault('name', 'SimpleMINLP') + if grey_box and GreyBoxModel is None: + m = None + return + super(SimpleMINLP, self).__init__(*args, **kwargs) m = self diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index db37c4390c9..547efc0a74c 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -2,136 +2,150 @@ import pyomo.common.dependencies.scipy.sparse as scipy_sparse from pyomo.common.dependencies import attempt_import -egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] - - -class GreyBoxModel(egb.ExternalGreyBoxModel): - """Greybox model to compute the example OF.""" - - def __init__(self, initial, use_exact_derivatives=True, verbose=False): - """ - Parameters - - use_exact_derivatives: bool - If True, the exact derivatives are used. If False, the finite difference - approximation is used. - verbose: bool - If True, print information about the model. - """ - self._use_exact_derivatives = use_exact_derivatives - self.verbose = verbose - self.initial = initial - - # For use with exact Hessian - self._output_con_mult_values = np.zeros(1) - - if not use_exact_derivatives: - raise NotImplementedError("use_exact_derivatives == False not supported") - - def input_names(self): - """Return the names of the inputs.""" - self.input_name_list = ["X1", "X2", "Y1", "Y2", "Y3"] - - return self.input_name_list - - def equality_constraint_names(self): - """Return the names of the equality constraints.""" - # no equality constraints - return [] - - def output_names(self): - """Return the names of the outputs.""" - return ['z'] - - def set_output_constraint_multipliers(self, output_con_multiplier_values): - """Set the values of the output constraint multipliers.""" - # because we only have one output constraint - assert len(output_con_multiplier_values) == 1 - np.copyto(self._output_con_mult_values, output_con_multiplier_values) - - def finalize_block_construction(self, pyomo_block): - """Finalize the construction of the ExternalGreyBoxBlock.""" - if self.initial is not None: - print("initialized") - pyomo_block.inputs["X1"].value = self.initial["X1"] - pyomo_block.inputs["X2"].value = self.initial["X2"] - pyomo_block.inputs["Y1"].value = self.initial["Y1"] - pyomo_block.inputs["Y2"].value = self.initial["Y2"] - pyomo_block.inputs["Y3"].value = self.initial["Y3"] - - else: - print("uninitialized") - for n in self.input_name_list: - pyomo_block.inputs[n].value = 1 - - pyomo_block.inputs["X1"].setub(4) - pyomo_block.inputs["X1"].setlb(0) - - pyomo_block.inputs["X2"].setub(4) - pyomo_block.inputs["X2"].setlb(0) - - pyomo_block.inputs["Y1"].setub(1) - pyomo_block.inputs["Y1"].setlb(0) - - pyomo_block.inputs["Y2"].setub(1) - pyomo_block.inputs["Y2"].setlb(0) - - pyomo_block.inputs["Y3"].setub(1) - pyomo_block.inputs["Y3"].setlb(0) - - def set_input_values(self, input_values): - """Set the values of the inputs.""" - self._input_values = list(input_values) - - def evaluate_equality_constraints(self): - """Evaluate the equality constraints.""" - return None - - def evaluate_outputs(self): - """Evaluate the output of the model.""" - # form matrix as a list of lists - # M = self._extract_and_assemble_fim() - x1 = self._input_values[0] - x2 = self._input_values[1] - y1 = self._input_values[2] - y2 = self._input_values[3] - y3 = self._input_values[4] - # z - z = x1**2 + x2**2 + y1 + 1.5 * y2 + 0.5 * y3 - - if self.verbose: - print("\n Consider inputs [x1,x2,y1,y2,y3] =\n", x1, x2, y1, y2, y3) - print(" z = ", z, "\n") - - return np.asarray([z], dtype=np.float64) - - def evaluate_jacobian_equality_constraints(self): - """Evaluate the Jacobian of the equality constraints.""" - return None - - ''' - def _extract_and_assemble_fim(self): - M = np.zeros((self.n_parameters, self.n_parameters)) - for i in range(self.n_parameters): - for k in range(self.n_parameters): - M[i,k] = self._input_values[self.ele_to_order[(i,k)]] - - return M - ''' - - def evaluate_jacobian_outputs(self): - """Evaluate the Jacobian of the outputs.""" - if self._use_exact_derivatives: - # compute gradient of log determinant - row = np.zeros(5) # to store row index - col = np.zeros(5) # to store column index - data = np.zeros(5) # to store data - - row[0], col[0], data[0] = (0, 0, 2 * self._input_values[0]) # x1 - row[0], col[1], data[1] = (0, 1, 2 * self._input_values[1]) # x2 - row[0], col[2], data[2] = (0, 2, 1) # y1 - row[0], col[3], data[3] = (0, 3, 1.5) # y2 - row[0], col[4], data[4] = (0, 4, 0.5) # y3 - - # sparse matrix - return scipy_sparse.coo_matrix((data, (row, col)), shape=(1, 5)) +egb, egb_available = attempt_import( + 'pyomo.contrib.pynumero.interfaces.external_grey_box' +) + +if egb_available: + + class GreyBoxModel(egb.ExternalGreyBoxModel): + """Greybox model to compute the example objective function.""" + + def __init__(self, initial, use_exact_derivatives=True, verbose=False): + """ + Parameters + + use_exact_derivatives: bool + If True, the exact derivatives are used. If False, the finite difference + approximation is used. + verbose: bool + If True, print information about the model. + """ + self._use_exact_derivatives = use_exact_derivatives + self.verbose = verbose + self.initial = initial + + # For use with exact Hessian + self._output_con_mult_values = np.zeros(1) + + if not use_exact_derivatives: + raise NotImplementedError( + "use_exact_derivatives == False not supported" + ) + + def input_names(self): + """Return the names of the inputs.""" + self.input_name_list = ["X1", "X2", "Y1", "Y2", "Y3"] + + return self.input_name_list + + def equality_constraint_names(self): + """Return the names of the equality constraints.""" + # no equality constraints + return [] + + def output_names(self): + """Return the names of the outputs.""" + return ['z'] + + def set_output_constraint_multipliers(self, output_con_multiplier_values): + """Set the values of the output constraint multipliers.""" + # because we only have one output constraint + assert len(output_con_multiplier_values) == 1 + np.copyto(self._output_con_mult_values, output_con_multiplier_values) + + def finalize_block_construction(self, pyomo_block): + """Finalize the construction of the ExternalGreyBoxBlock.""" + if self.initial is not None: + print("initialized") + pyomo_block.inputs["X1"].value = self.initial["X1"] + pyomo_block.inputs["X2"].value = self.initial["X2"] + pyomo_block.inputs["Y1"].value = self.initial["Y1"] + pyomo_block.inputs["Y2"].value = self.initial["Y2"] + pyomo_block.inputs["Y3"].value = self.initial["Y3"] + + else: + print("uninitialized") + for n in self.input_name_list: + pyomo_block.inputs[n].value = 1 + + pyomo_block.inputs["X1"].setub(4) + pyomo_block.inputs["X1"].setlb(0) + + pyomo_block.inputs["X2"].setub(4) + pyomo_block.inputs["X2"].setlb(0) + + pyomo_block.inputs["Y1"].setub(1) + pyomo_block.inputs["Y1"].setlb(0) + + pyomo_block.inputs["Y2"].setub(1) + pyomo_block.inputs["Y2"].setlb(0) + + pyomo_block.inputs["Y3"].setub(1) + pyomo_block.inputs["Y3"].setlb(0) + + def set_input_values(self, input_values): + """Set the values of the inputs.""" + self._input_values = list(input_values) + + def evaluate_equality_constraints(self): + """Evaluate the equality constraints.""" + return None + + def evaluate_outputs(self): + """Evaluate the output of the model.""" + # form matrix as a list of lists + # M = self._extract_and_assemble_fim() + x1 = self._input_values[0] + x2 = self._input_values[1] + y1 = self._input_values[2] + y2 = self._input_values[3] + y3 = self._input_values[4] + # z + z = x1**2 + x2**2 + y1 + 1.5 * y2 + 0.5 * y3 + + if self.verbose: + print("\n Consider inputs [x1,x2,y1,y2,y3] =\n", x1, x2, y1, y2, y3) + print(" z = ", z, "\n") + + return np.asarray([z], dtype=np.float64) + + def evaluate_jacobian_equality_constraints(self): + """Evaluate the Jacobian of the equality constraints.""" + return None + + ''' + def _extract_and_assemble_fim(self): + M = np.zeros((self.n_parameters, self.n_parameters)) + for i in range(self.n_parameters): + for k in range(self.n_parameters): + M[i,k] = self._input_values[self.ele_to_order[(i,k)]] + + return M + ''' + + def evaluate_jacobian_outputs(self): + """Evaluate the Jacobian of the outputs.""" + if self._use_exact_derivatives: + # compute gradient of log determinant + row = np.zeros(5) # to store row index + col = np.zeros(5) # to store column index + data = np.zeros(5) # to store data + + row[0], col[0], data[0] = (0, 0, 2 * self._input_values[0]) # x1 + row[0], col[1], data[1] = (0, 1, 2 * self._input_values[1]) # x2 + row[0], col[2], data[2] = (0, 2, 1) # y1 + row[0], col[3], data[3] = (0, 3, 1.5) # y2 + row[0], col[4], data[4] = (0, 4, 0.5) # y3 + + # sparse matrix + return scipy_sparse.coo_matrix((data, (row, col)), shape=(1, 5)) + + def build_model_external(m): + ex_model = GreyBoxModel(initial={"X1": 0, "X2": 0, "Y1": 0, "Y2": 1, "Y3": 1}) + m.egb = egb.ExternalGreyBoxBlock() + m.egb.set_external_model(ex_model) + +else: + GreyBoxModel = None + build_model_external = None diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index 70ae881abb2..f84136ca6bf 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -25,6 +25,7 @@ subsolvers_available = False +@unittest.skipIf(model_list[0] is None, 'Unable to generate the Grey Box model.') @unittest.skipIf( not subsolvers_available, 'Required subsolvers %s are not available' % (required_solvers,), @@ -32,8 +33,6 @@ @unittest.skipIf( not differentiate_available, 'Symbolic differentiation is not available' ) -@unittest.skipIf(not numpy_available, 'Required numpy %s is not available') -@unittest.skipIf(not scipy_available, 'Required scipy %s is not available') class TestMindtPy(unittest.TestCase): """Tests for the MindtPy solver plugin.""" From bd1266445419daa6adbfaba8f98b06e42b215540 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 20:02:24 -0400 Subject: [PATCH 25/32] fix import bug --- pyomo/contrib/mindtpy/algorithm_base_class.py | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index d562c924a7d..55609b60132 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -84,7 +84,7 @@ single_tree, single_tree_available = attempt_import('pyomo.contrib.mindtpy.single_tree') tabu_list, tabu_list_available = attempt_import('pyomo.contrib.mindtpy.tabu_list') -egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] +egb, egb_available = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box') class _MindtPyAlgorithm(object): @@ -324,11 +324,12 @@ def build_ordered_component_lists(self, model): ctype=Constraint, active=True, descend_into=(Block) ) ) - util_block.grey_box_list = list( - model.component_data_objects( - ctype=egb.ExternalGreyBoxBlock, active=True, descend_into=(Block) + if egb_available: + util_block.grey_box_list = list( + model.component_data_objects( + ctype=egb.ExternalGreyBoxBlock, active=True, descend_into=(Block) + ) ) - ) util_block.linear_constraint_list = list( c for c in util_block.constraint_list @@ -356,13 +357,22 @@ def build_ordered_component_lists(self, model): # We use component_data_objects rather than list(var_set) in order to # preserve a deterministic ordering. - util_block.variable_list = list( - v - for v in model.component_data_objects( - ctype=Var, descend_into=(Block, egb.ExternalGreyBoxBlock) + if egb_available: + util_block.variable_list = list( + v + for v in model.component_data_objects( + ctype=Var, descend_into=(Block, egb.ExternalGreyBoxBlock) + ) + if v in var_set + ) + else: + util_block.variable_list = list( + v + for v in model.component_data_objects( + ctype=Var, descend_into=(Block) + ) + if v in var_set ) - if v in var_set - ) util_block.discrete_variable_list = list( v for v in util_block.variable_list if v in var_set and v.is_integer() ) From ebe91a6b16f7a14be18dc6bc7f8f96be5e792e81 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 20:25:52 -0400 Subject: [PATCH 26/32] black format --- pyomo/contrib/mindtpy/algorithm_base_class.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 55609b60132..e4dea716178 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -84,7 +84,9 @@ single_tree, single_tree_available = attempt_import('pyomo.contrib.mindtpy.single_tree') tabu_list, tabu_list_available = attempt_import('pyomo.contrib.mindtpy.tabu_list') -egb, egb_available = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box') +egb, egb_available = attempt_import( + 'pyomo.contrib.pynumero.interfaces.external_grey_box' +) class _MindtPyAlgorithm(object): @@ -368,9 +370,7 @@ def build_ordered_component_lists(self, model): else: util_block.variable_list = list( v - for v in model.component_data_objects( - ctype=Var, descend_into=(Block) - ) + for v in model.component_data_objects(ctype=Var, descend_into=(Block)) if v in var_set ) util_block.discrete_variable_list = list( From 2b4575645d73bed7d73730433e83dbdb141d27e3 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 23:57:06 -0400 Subject: [PATCH 27/32] fix import bug --- pyomo/contrib/mindtpy/algorithm_base_class.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index e4dea716178..03fcc12fbac 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -332,6 +332,8 @@ def build_ordered_component_lists(self, model): ctype=egb.ExternalGreyBoxBlock, active=True, descend_into=(Block) ) ) + else: + util_block.grey_box_list = [] util_block.linear_constraint_list = list( c for c in util_block.constraint_list From 73f2e16ce76bfb6e84bcb0183b523d2340ff477c Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 14 Nov 2023 15:23:58 -0500 Subject: [PATCH 28/32] change load_solutions to attribute --- pyomo/contrib/mindtpy/algorithm_base_class.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 03fcc12fbac..5f7bc438fd0 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -143,6 +143,8 @@ def __init__(self, **kwds): self.last_iter_cuts = False # Store the OA cuts generated in the mip_start_process. self.mip_start_lazy_oa_cuts = [] + # Whether to load solutions in solve() function + self.load_solutions = True # Support use as a context manager under current solver API def __enter__(self): @@ -292,7 +294,7 @@ def model_is_valid(self): results = self.mip_opt.solve( self.original_model, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **config.mip_solver_args, ) if len(results.solution) > 0: @@ -832,7 +834,7 @@ def init_rNLP(self, add_oa_cuts=True): results = self.nlp_opt.solve( self.rnlp, tee=config.nlp_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -962,7 +964,7 @@ def init_max_binaries(self): results = self.mip_opt.solve( m, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **mip_args, ) if len(results.solution) > 0: @@ -1082,7 +1084,7 @@ def solve_subproblem(self): results = self.nlp_opt.solve( self.fixed_nlp, tee=config.nlp_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -1520,7 +1522,7 @@ def fix_dual_bound(self, last_iter_cuts): main_mip_results = self.mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **mip_args, ) if len(main_mip_results.solution) > 0: @@ -1607,7 +1609,7 @@ def solve_main(self): main_mip_results = self.mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. @@ -1663,7 +1665,7 @@ def solve_fp_main(self): main_mip_results = self.mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. @@ -1706,7 +1708,7 @@ def solve_regularization_main(self): main_mip_results = self.regularization_mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **dict(config.mip_solver_args), ) if len(main_mip_results.solution) > 0: @@ -1918,7 +1920,7 @@ def handle_main_unbounded(self, main_mip): main_mip_results = self.mip_opt.solve( main_mip, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **config.mip_solver_args, ) if len(main_mip_results.solution) > 0: @@ -2256,7 +2258,7 @@ def check_config(self): and 'appsi' in config.mip_regularization_solver ) ): - config.load_solutions = False + self.load_solutions = False ################################################################################################################################ # Feasibility Pump @@ -2323,7 +2325,7 @@ def solve_fp_subproblem(self): results = self.nlp_opt.solve( fp_nlp, tee=config.nlp_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **nlp_args, ) if len(results.solution) > 0: From a08fd36825f4fb3383668d74b0d3a18c513b7237 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 14 Nov 2023 15:48:35 -0500 Subject: [PATCH 29/32] black format --- pyomo/contrib/mindtpy/algorithm_base_class.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 5f7bc438fd0..c9169ab8c62 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -962,10 +962,7 @@ def init_max_binaries(self): mip_args = dict(config.mip_solver_args) update_solver_timelimit(self.mip_opt, config.mip_solver, self.timing, config) results = self.mip_opt.solve( - m, - tee=config.mip_solver_tee, - load_solutions=self.load_solutions, - **mip_args, + m, tee=config.mip_solver_tee, load_solutions=self.load_solutions, **mip_args ) if len(results.solution) > 0: m.solutions.load_from(results) From ae8455821f6f36c56ddb23bcad357b23f661c230 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 15 Nov 2023 13:11:37 -0500 Subject: [PATCH 30/32] remove load_solutions configuration --- pyomo/contrib/mindtpy/config_options.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index 507cbd995f8..ed0c86baae9 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -494,14 +494,6 @@ def _add_common_configs(CONFIG): domain=bool, ), ) - CONFIG.declare( - 'load_solutions', - ConfigValue( - default=True, - description='Whether to load solutions in solve() function', - domain=bool, - ), - ) def _add_subsolver_configs(CONFIG): From 7bf0268d47004164ef0f1c07bb2e166d5d3611b0 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 15 Nov 2023 13:12:02 -0500 Subject: [PATCH 31/32] add comments to enumerate over values() --- pyomo/contrib/mindtpy/cut_generation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index dd60b004830..a8d6948ac1d 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -196,6 +196,7 @@ def add_oa_cuts_for_grey_box( .evaluate_jacobian_outputs() .toarray() ) + # Enumerate over values works well now. However, it might be stable if the values() method changes. for index, output in enumerate(target_model_grey_box.outputs.values()): dual_value = jacobians_model.dual[jacobian_model_grey_box][ output.name.replace("outputs", "output_constraints") From 993290f777ee5d92192a0d3fbce8ef60b627ed0b Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 15 Nov 2023 13:12:56 -0500 Subject: [PATCH 32/32] black format --- pyomo/contrib/mindtpy/cut_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index a8d6948ac1d..28d302104a3 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -196,7 +196,7 @@ def add_oa_cuts_for_grey_box( .evaluate_jacobian_outputs() .toarray() ) - # Enumerate over values works well now. However, it might be stable if the values() method changes. + # Enumerate over values works well now. However, it might be stable if the values() method changes. for index, output in enumerate(target_model_grey_box.outputs.values()): dual_value = jacobians_model.dual[jacobian_model_grey_box][ output.name.replace("outputs", "output_constraints")