From bb6bf5e67b31820760325f2f5f3e9c6c83094d3f Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 25 Aug 2023 13:58:03 -0400 Subject: [PATCH 01/12] add highs support --- pyomo/contrib/mindtpy/config_options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index ed0c86baae9..713ab539660 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -538,7 +538,7 @@ def _add_subsolver_configs(CONFIG): 'cplex_persistent', 'appsi_cplex', 'appsi_gurobi', - # 'appsi_highs', TODO: feasibility pump now fails with appsi_highs #2951 + 'appsi_highs' ] ), description='MIP subsolver name', @@ -620,7 +620,7 @@ def _add_subsolver_configs(CONFIG): 'cplex_persistent', 'appsi_cplex', 'appsi_gurobi', - # 'appsi_highs', + 'appsi_highs', ] ), description='MIP subsolver for regularization problem', From da8a56e837fb43dec321af088fd498c980429db5 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 25 Aug 2023 14:03:07 -0400 Subject: [PATCH 02/12] change test mip solver to highs --- pyomo/contrib/mindtpy/tests/test_mindtpy.py | 2 +- pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py | 2 +- pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy.py b/pyomo/contrib/mindtpy/tests/test_mindtpy.py index e872eccc670..4efd9493b8a 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy.py @@ -56,7 +56,7 @@ QCP_model._generate_model() extreme_model_list = [LP_model.model, QCP_model.model] -required_solvers = ('ipopt', 'glpk') +required_solvers = ('ipopt', 'appsi_highs') if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py index b5bfbe62553..95516af11fd 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py @@ -12,7 +12,7 @@ from pyomo.environ import SolverFactory, value from pyomo.opt import TerminationCondition -required_solvers = ('ipopt', 'glpk') +required_solvers = ('ipopt', 'appsi_highs') if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py index 697a63d17c8..18b7a420674 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py @@ -17,8 +17,7 @@ from pyomo.contrib.mindtpy.tests.feasibility_pump1 import FeasPump1 from pyomo.contrib.mindtpy.tests.feasibility_pump2 import FeasPump2 -required_solvers = ('ipopt', 'cplex') -# TODO: 'appsi_highs' will fail here. +required_solvers = ('ipopt', 'appsi_highs') if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: From 95f4cc2a281fdf235800ae9d82bc27c11d6f1f18 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 25 Aug 2023 14:13:24 -0400 Subject: [PATCH 03/12] black format --- pyomo/contrib/mindtpy/config_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index 713ab539660..2769d336e31 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -538,7 +538,7 @@ def _add_subsolver_configs(CONFIG): 'cplex_persistent', 'appsi_cplex', 'appsi_gurobi', - 'appsi_highs' + 'appsi_highs', ] ), description='MIP subsolver name', From 553c8bb45741c9a8c55a9ee68f4aa349aaa144aa Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 1 Sep 2023 15:34:14 -0400 Subject: [PATCH 04/12] add deactivate_trivial_constraints for feasibility subproblem --- pyomo/contrib/mindtpy/algorithm_base_class.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 7def1dcaab3..584d796d069 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -1311,6 +1311,20 @@ def solve_feasibility_subproblem(self): update_solver_timelimit( self.feasibility_nlp_opt, config.nlp_solver, self.timing, config ) + try: + TransformationFactory('contrib.deactivate_trivial_constraints').apply_to( + self.fixed_nlp, + tmp=True, + ignore_infeasible=False, + tolerance=config.constraint_tolerance, + ) + except InfeasibleConstraintException as e: + config.logger.error( + str(e) + '\nInfeasibility detected in deactivate_trivial_constraints.' + ) + results = SolverResults() + results.solver.termination_condition = tc.infeasible + return self.fixed_nlp, results with SuppressInfeasibleWarning(): try: with time_code(self.timing, 'feasibility subproblem'): @@ -1341,6 +1355,9 @@ def solve_feasibility_subproblem(self): self.handle_feasibility_subproblem_tc( feas_soln.solver.termination_condition, MindtPy ) + TransformationFactory('contrib.deactivate_trivial_constraints').revert( + self.fixed_nlp + ) MindtPy.feas_opt.deactivate() for constr in MindtPy.nonlinear_constraint_list: constr.activate() From d12d69b4b1a409ef7450ae5f22229d19d3401dc1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 12 Mar 2024 13:39:55 -0600 Subject: [PATCH 05/12] Temporarily pinning to highspy pre-release for testing --- .github/workflows/test_pr_and_main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 76ec6de951a..a2060240391 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -605,7 +605,8 @@ jobs: if: ${{ ! matrix.slim }} shell: bash run: | - $PYTHON_EXE -m pip install --cache-dir cache/pip highspy \ + echo "NOTE: temporarily pinning to highspy pre-release for testing" + $PYTHON_EXE -m pip install --cache-dir cache/pip highspy==1.7.1.dev1 \ || echo "WARNING: highspy is not available" - name: Set up coverage tracking From 81245460c156f187ead1baafaa58195c21389e60 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 27 Mar 2024 20:31:17 -0400 Subject: [PATCH 06/12] add highs version check and load_solutions attributes --- pyomo/contrib/mindtpy/algorithm_base_class.py | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 8d25f3c1d3a..0394110675d 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -152,7 +152,9 @@ def __init__(self, **kwds): # 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 + self.mip_load_solutions = True + self.nlp_load_solutions = True + self.regularization_mip_load_solutions = True # Support use as a context manager under current solver API def __enter__(self): @@ -302,7 +304,7 @@ def model_is_valid(self): results = self.mip_opt.solve( self.original_model, tee=config.mip_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.mip_load_solutions, **config.mip_solver_args, ) if len(results.solution) > 0: @@ -846,7 +848,7 @@ def init_rNLP(self, add_oa_cuts=True): results = self.nlp_opt.solve( self.rnlp, tee=config.nlp_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.nlp_load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -868,7 +870,7 @@ def init_rNLP(self, add_oa_cuts=True): results = self.nlp_opt.solve( self.rnlp, tee=config.nlp_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.nlp_load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -999,7 +1001,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=self.load_solutions, **mip_args + m, + tee=config.mip_solver_tee, + load_solutions=self.mip_load_solutions, + **mip_args, ) if len(results.solution) > 0: m.solutions.load_from(results) @@ -1119,7 +1124,7 @@ def solve_subproblem(self): results = self.nlp_opt.solve( self.fixed_nlp, tee=config.nlp_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.nlp_load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -1586,7 +1591,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=self.load_solutions, + load_solutions=self.mip_load_solutions, **mip_args, ) if len(main_mip_results.solution) > 0: @@ -1674,7 +1679,7 @@ def solve_main(self): main_mip_results = self.mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.mip_load_solutions, **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. @@ -1735,7 +1740,7 @@ def solve_fp_main(self): main_mip_results = self.mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.mip_load_solutions, **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. @@ -1778,7 +1783,7 @@ def solve_regularization_main(self): main_mip_results = self.regularization_mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.regularization_mip_load_solutions, **dict(config.mip_solver_args), ) if len(main_mip_results.solution) > 0: @@ -1994,7 +1999,7 @@ def handle_main_unbounded(self, main_mip): main_mip_results = self.mip_opt.solve( main_mip, tee=config.mip_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.mip_load_solutions, **config.mip_solver_args, ) if len(main_mip_results.solution) > 0: @@ -2277,6 +2282,11 @@ def check_subsolver_validity(self): raise ValueError(self.config.mip_solver + ' is not available.') if not self.mip_opt.license_is_valid(): raise ValueError(self.config.mip_solver + ' is not licensed.') + if self.config.mip_solver == "appsi_highs": + if self.mip_opt.version() < (1, 7, 0): + raise ValueError( + "MindtPy requires the use of HIGHS version 1.7.0 or higher for full compatibility." + ) if not self.nlp_opt.available(): raise ValueError(self.config.nlp_solver + ' is not available.') if not self.nlp_opt.license_is_valid(): @@ -2324,15 +2334,15 @@ def check_config(self): config.mip_solver = 'cplex_persistent' # related to https://github.com/Pyomo/pyomo/issues/2363 + if 'appsi' in config.mip_solver: + self.mip_load_solutions = False + if 'appsi' in config.nlp_solver: + self.nlp_load_solutions = False 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.mip_regularization_solver is not None + and 'appsi' in config.mip_regularization_solver ): - self.load_solutions = False + self.regularization_mip_load_solutions = False ################################################################################################################################ # Feasibility Pump @@ -2400,7 +2410,7 @@ def solve_fp_subproblem(self): results = self.nlp_opt.solve( fp_nlp, tee=config.nlp_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.nlp_load_solutions, **nlp_args, ) if len(results.solution) > 0: From 34c2c36c35260e8207a5850edcc5a985e8b3d55d Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 27 Mar 2024 20:39:26 -0400 Subject: [PATCH 07/12] add version check for highs in tests --- pyomo/contrib/mindtpy/tests/test_mindtpy.py | 7 ++++++- pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py | 8 +++++++- pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py | 8 +++++++- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 9 ++++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy.py b/pyomo/contrib/mindtpy/tests/test_mindtpy.py index 27c57370ba2..d0364378ed8 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy.py @@ -56,7 +56,12 @@ QCP_model._generate_model() extreme_model_list = [LP_model.model, QCP_model.model] -required_solvers = ('ipopt', 'appsi_highs') +if SolverFactory('appsi_highs').available(exception_flag=False) and SolverFactory( + 'appsi_highs' +).version() >= (1, 7, 0): + required_solvers = ('ipopt', 'appsi_highs') +else: + required_solvers = ('ipopt', 'glpk') if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py index fb78be6b2f1..dda0f74147e 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py @@ -23,7 +23,13 @@ from pyomo.environ import SolverFactory, value from pyomo.opt import TerminationCondition -required_solvers = ('ipopt', 'appsi_highs') +if SolverFactory('appsi_highs').available(exception_flag=False) and SolverFactory( + 'appsi_highs' +).version() >= (1, 7, 0): + required_solvers = ('ipopt', 'appsi_highs') +else: + required_solvers = ('ipopt', 'glpk') + if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py index b8f889e6920..0baa361910e 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py @@ -28,7 +28,13 @@ from pyomo.contrib.mindtpy.tests.feasibility_pump1 import FeasPump1 from pyomo.contrib.mindtpy.tests.feasibility_pump2 import FeasPump2 -required_solvers = ('ipopt', 'appsi_highs') +if SolverFactory('appsi_highs').available(exception_flag=False) and SolverFactory( + 'appsi_highs' +).version() >= (1, 7, 0): + required_solvers = ('ipopt', 'appsi_highs') +else: + required_solvers = ('ipopt', 'glpk') + if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index d50a41ad000..e01558d48ef 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -18,7 +18,14 @@ from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP model_list = [SimpleMINLP(grey_box=True)] -required_solvers = ('cyipopt', 'glpk') + +if SolverFactory('appsi_highs').available(exception_flag=False) and SolverFactory( + 'appsi_highs' +).version() >= (1, 7, 0): + required_solvers = ('cyipopt', 'appsi_highs') +else: + required_solvers = ('cyipopt', 'glpk') + if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: From b63358c5eb6578102917d07e00380fa3188973e3 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 16 Apr 2024 14:57:55 -0400 Subject: [PATCH 08/12] add highs version requirements for pr workflow --- .github/workflows/test_pr_and_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index a2060240391..28c72541a13 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -606,7 +606,7 @@ jobs: shell: bash run: | echo "NOTE: temporarily pinning to highspy pre-release for testing" - $PYTHON_EXE -m pip install --cache-dir cache/pip highspy==1.7.1.dev1 \ + $PYTHON_EXE -m pip install --cache-dir cache/pip highspy>=1.7.1.dev1 \ || echo "WARNING: highspy is not available" - name: Set up coverage tracking From 6b1e2960e94172a10802326b6e7b848c4c7b4ab4 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 17 Apr 2024 00:36:04 -0400 Subject: [PATCH 09/12] fix test pr highs version bug --- .github/workflows/test_pr_and_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 9eb6d362bb8..711e134e401 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -606,7 +606,7 @@ jobs: shell: bash run: | echo "NOTE: temporarily pinning to highspy pre-release for testing" - $PYTHON_EXE -m pip install --cache-dir cache/pip highspy>=1.7.1.dev1 \ + $PYTHON_EXE -m pip install --cache-dir cache/pip "highspy>=1.7.1.dev1" \ || echo "WARNING: highspy is not available" - name: Set up coverage tracking From 3165c9d67b2b47a8d7ff9e601781ed4e2eecc8b2 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 6 May 2024 16:14:10 -0600 Subject: [PATCH 10/12] Minor edit to APPSI Highs version method to support older versions of Highs --- pyomo/contrib/appsi/solvers/highs.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 3612b9d5014..29c3698b277 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -176,11 +176,23 @@ def available(self): return self.Availability.NotFound def version(self): - version = ( - highspy.HIGHS_VERSION_MAJOR, - highspy.HIGHS_VERSION_MINOR, - highspy.HIGHS_VERSION_PATCH, - ) + try: + version = ( + highspy.HIGHS_VERSION_MAJOR, + highspy.HIGHS_VERSION_MINOR, + highspy.HIGHS_VERSION_PATCH, + ) + except AttributeError: + # Older versions of Highs do not have the above attributes + # and the solver version can only be obtained by making + # an instance of the solver class. + tmp = highspy.Highs() + version = ( + tmp.versionMajor(), + tmp.versionMinor(), + tmp.versionPatch(), + ) + return version @property From b425c4db8e4b6edacf7352b5a0e85de0c69b1558 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 6 May 2024 16:21:03 -0600 Subject: [PATCH 11/12] Fixing black formatting in highs.py --- pyomo/contrib/appsi/solvers/highs.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 75975a4e0a8..87b9557269f 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -187,11 +187,7 @@ def version(self): # and the solver version can only be obtained by making # an instance of the solver class. tmp = highspy.Highs() - version = ( - tmp.versionMajor(), - tmp.versionMinor(), - tmp.versionPatch(), - ) + version = (tmp.versionMajor(), tmp.versionMinor(), tmp.versionPatch()) return version From c610a20cf594be2dcf34e847aaf63dfb08bfde2b Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 6 May 2024 16:22:49 -0600 Subject: [PATCH 12/12] Removing whitespace in highs.py --- pyomo/contrib/appsi/solvers/highs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 87b9557269f..c948444839d 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -176,7 +176,7 @@ def available(self): return self.Availability.NotFound def version(self): - try: + try: version = ( highspy.HIGHS_VERSION_MAJOR, highspy.HIGHS_VERSION_MINOR,