From 2f722dc2d9797b59c52732d1c699b50468de294a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 28 Oct 2024 22:55:54 -0600 Subject: [PATCH 1/4] Remove pin to Gurobi 10.0.3 --- .github/workflows/test_branches.yml | 4 ++-- .github/workflows/test_pr_and_main.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index c1029ff3d7b..d6c3c5e37db 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -286,7 +286,7 @@ jobs: if test -z "${{matrix.slim}}"; then python -m pip install --cache-dir cache/pip cplex docplex \ || echo "WARNING: CPLEX Community Edition is not available" - python -m pip install --cache-dir cache/pip gurobipy==10.0.3 \ + python -m pip install --cache-dir cache/pip gurobipy \ || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" @@ -369,7 +369,7 @@ jobs: if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" - for PKG in 'cplex>=12.10' docplex 'gurobi=10.0.3' xpress cyipopt pymumps scip; do + for PKG in 'cplex>=12.10' docplex gurobi xpress cyipopt pymumps scip; do echo "" echo "*** Install $PKG ***" # conda can literally take an hour to determine that a diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 33aacaa9e35..0eba6d96310 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -309,7 +309,7 @@ jobs: if test -z "${{matrix.slim}}"; then python -m pip install --cache-dir cache/pip cplex docplex \ || echo "WARNING: CPLEX Community Edition is not available" - python -m pip install --cache-dir cache/pip gurobipy==10.0.3 \ + python -m pip install --cache-dir cache/pip gurobipy \ || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" @@ -392,7 +392,7 @@ jobs: if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" - for PKG in 'cplex>=12.10' docplex 'gurobi=10.0.3' xpress cyipopt pymumps scip; do + for PKG in 'cplex>=12.10' docplex gurobi xpress cyipopt pymumps scip; do echo "" echo "*** Install $PKG ***" # conda can literally take an hour to determine that a From bbe0324f87964b73537047f86aa952194886eb32 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 16 Oct 2024 10:22:43 -0600 Subject: [PATCH 2/4] Update contrib (appsi+solver) tests to reflect change in Gurobi behavior --- .../solvers/tests/test_gurobi_persistent.py | 35 +++++++++++++------ .../tests/solvers/test_gurobi_persistent.py | 29 +++++++++++---- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index 2f674a2eb6a..458d4bf29e6 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -191,12 +191,16 @@ def test_lp(self): class TestGurobiPersistent(unittest.TestCase): def test_nonconvex_qcp_objective_bound_1(self): - # the goal of this test is to ensure we can get an objective bound - # for nonconvex but continuous problems even if a feasible solution - # is not found + # the goal of this test is to ensure we can get an objective + # bound for nonconvex but continuous problems even if a feasible + # solution is not found # - # This is a fragile test because it could fail if Gurobi's algorithms improve - # (e.g., a heuristic solution is found before an objective bound of -8 is reached + # This is a fragile test because it could fail if Gurobi's + # algorithms improve (e.g., a heuristic solution is found before + # an objective bound of -8 is reached + # + # Update: as of Gurobi 11, this test no longer tests the + # intended behavior (the solver has improved) m = pe.ConcreteModel() m.x = pe.Var(bounds=(-5, 5)) m.y = pe.Var(bounds=(-5, 5)) @@ -208,14 +212,22 @@ def test_nonconvex_qcp_objective_bound_1(self): opt.gurobi_options['BestBdStop'] = -8 opt.config.load_solution = False res = opt.solve(m) - self.assertEqual(res.best_feasible_objective, None) + if opt.version() < (11, 0): + self.assertEqual(res.incumbent_objective, None) + else: + self.assertEqual(res.incumbent_objective, -4) self.assertAlmostEqual(res.best_objective_bound, -8) def test_nonconvex_qcp_objective_bound_2(self): - # the goal of this test is to ensure we can best_objective_bound properly - # for nonconvex but continuous problems when the solver terminates with a nonzero gap + # the goal of this test is to ensure we can best_objective_bound + # properly for nonconvex but continuous problems when the solver + # terminates with a nonzero gap + # + # This is a fragile test because it could fail if Gurobi's + # algorithms change # - # This is a fragile test because it could fail if Gurobi's algorithms change + # Update: as of Gurobi 11, this test no longer tests the + # intended behavior (the solver has improved) m = pe.ConcreteModel() m.x = pe.Var(bounds=(-5, 5)) m.y = pe.Var(bounds=(-5, 5)) @@ -227,7 +239,10 @@ def test_nonconvex_qcp_objective_bound_2(self): opt.gurobi_options['MIPGap'] = 0.5 res = opt.solve(m) self.assertAlmostEqual(res.best_feasible_objective, -4) - self.assertAlmostEqual(res.best_objective_bound, -6) + if opt.version() < (11, 0): + self.assertAlmostEqual(res.objective_bound, -6) + else: + self.assertAlmostEqual(res.objective_bound, -4) def test_range_constraints(self): m = pe.ConcreteModel() diff --git a/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py b/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py index 2f281e2abf0..5992b435b55 100644 --- a/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py +++ b/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py @@ -192,8 +192,12 @@ def test_nonconvex_qcp_objective_bound_1(self): # for nonconvex but continuous problems even if a feasible solution # is not found # - # This is a fragile test because it could fail if Gurobi's algorithms improve - # (e.g., a heuristic solution is found before an objective bound of -8 is reached + # This is a fragile test because it could fail if Gurobi's + # algorithms improve (e.g., a heuristic solution is found before + # an objective bound of -8 is reached + # + # Update: as of Gurobi 11, this test no longer tests the + # intended behavior (the solver has improved) m = pe.ConcreteModel() m.x = pe.Var(bounds=(-5, 5)) m.y = pe.Var(bounds=(-5, 5)) @@ -206,14 +210,22 @@ def test_nonconvex_qcp_objective_bound_1(self): opt.config.load_solutions = False opt.config.raise_exception_on_nonoptimal_result = False res = opt.solve(m) - self.assertEqual(res.incumbent_objective, None) + if opt.version() < (11, 0): + self.assertEqual(res.incumbent_objective, None) + else: + self.assertEqual(res.incumbent_objective, -4) self.assertAlmostEqual(res.objective_bound, -8) def test_nonconvex_qcp_objective_bound_2(self): - # the goal of this test is to ensure we can objective_bound properly - # for nonconvex but continuous problems when the solver terminates with a nonzero gap + # the goal of this test is to ensure we can objective_bound + # properly for nonconvex but continuous problems when the solver + # terminates with a nonzero gap + # + # This is a fragile test because it could fail if Gurobi's + # algorithms change # - # This is a fragile test because it could fail if Gurobi's algorithms change + # Update: as of Gurobi 11, this test no longer tests the + # intended behavior (the solver has improved) m = pe.ConcreteModel() m.x = pe.Var(bounds=(-5, 5)) m.y = pe.Var(bounds=(-5, 5)) @@ -225,7 +237,10 @@ def test_nonconvex_qcp_objective_bound_2(self): opt.config.solver_options['MIPGap'] = 0.5 res = opt.solve(m) self.assertAlmostEqual(res.incumbent_objective, -4) - self.assertAlmostEqual(res.objective_bound, -6) + if opt.version() < (11, 0): + self.assertAlmostEqual(res.objective_bound, -6) + else: + self.assertAlmostEqual(res.objective_bound, -4) def test_range_constraints(self): m = pe.ConcreteModel() From 19612e59e33623cb2327c1d8dabcad1a6bca1a5c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 16 Oct 2024 11:11:41 -0600 Subject: [PATCH 3/4] Bugfix to test (copy/paste error) --- pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index 458d4bf29e6..56094392262 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -240,9 +240,9 @@ def test_nonconvex_qcp_objective_bound_2(self): res = opt.solve(m) self.assertAlmostEqual(res.best_feasible_objective, -4) if opt.version() < (11, 0): - self.assertAlmostEqual(res.objective_bound, -6) + self.assertAlmostEqual(res.best_objective_bound, -6) else: - self.assertAlmostEqual(res.objective_bound, -4) + self.assertAlmostEqual(res.best_objective_bound, -4) def test_range_constraints(self): m = pe.ConcreteModel() From cd931030295f1086a59e5c5049df7e6d32a516ec Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 16 Oct 2024 12:28:22 -0600 Subject: [PATCH 4/4] fix typo (copy/paste error) --- pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index 56094392262..d7893464b1a 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -213,9 +213,9 @@ def test_nonconvex_qcp_objective_bound_1(self): opt.config.load_solution = False res = opt.solve(m) if opt.version() < (11, 0): - self.assertEqual(res.incumbent_objective, None) + self.assertEqual(res.best_feasible_objective, None) else: - self.assertEqual(res.incumbent_objective, -4) + self.assertEqual(res.best_feasible_objective, -4) self.assertAlmostEqual(res.best_objective_bound, -8) def test_nonconvex_qcp_objective_bound_2(self):