Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion pyomo/contrib/alternative_solutions/solnpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def gurobi_generate_solutions(
abs_opt_gap=None,
solver_options={},
tee=False,
pool_search_mode=2,
):
"""
Finds alternative optimal solutions for discrete variables using Gurobi's
Expand Down Expand Up @@ -56,6 +57,10 @@ def gurobi_generate_solutions(
Solver option-value pairs to be passed to the Gurobi solver.
tee : boolean
Boolean indicating that the solver output should be displayed.
pool_search_mode : 1 or 2
The generation method for filling the pool.
This parameter maps to the PoolSearchMode in gurobi.
Method designed to work with value 2 as optimality ordered.

Returns
-------
Expand All @@ -69,10 +74,20 @@ def gurobi_generate_solutions(
if not opt.available():
raise ApplicationError("Solver (gurobi) not available")

assert num_solutions >= 1, "num_solutions must be positive integer"
if num_solutions == 1:
logger.warning("Running alternative_solutions method to find only 1 solution!")

assert pool_search_mode in [1, 2], "pool_search_mode must be 1 or 2"
if pool_search_mode == 1:
logger.warning(
"Running gurobi_solnpool with PoolSearchMode=1, best effort search may lead to unexpected behavior"
)

opt.config.stream_solver = tee
opt.config.load_solution = False
opt.gurobi_options["PoolSolutions"] = num_solutions
opt.gurobi_options["PoolSearchMode"] = 2
opt.gurobi_options["PoolSearchMode"] = pool_search_mode
if rel_opt_gap is not None:
opt.gurobi_options["PoolGap"] = rel_opt_gap
if abs_opt_gap is not None:
Expand Down
20 changes: 20 additions & 0 deletions pyomo/contrib/alternative_solutions/tests/test_solnpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,26 @@ def test_ip_feasibility(self):
unique_solns_by_obj = [val for val in Counter(objectives).values()]
np.testing.assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj)

def test_invalid_search_mode(self):
"""
Confirm that an exception is thrown with pool_search_mode not in [1,2]
"""
m = tc.get_triangle_ip()
with self.assertRaisesRegex(AssertionError, "pool_search_mode must be 1 or 2"):
gurobi_generate_solutions(m, pool_search_mode=0)

@unittest.skipIf(not numpy_available, "Numpy not installed")
def test_ip_num_solutions_best_effort(self):
"""
Enumerate solutions for an ip: triangle_ip.
Test best effort mode in solution pool.

Check that the correct number of alternate solutions are found.
"""
m = tc.get_triangle_ip()
results = gurobi_generate_solutions(m, pool_search_mode=1, num_solutions=8)
assert len(results) >= 1, 'Need to find some solutions'

@unittest.skipIf(not numpy_available, "Numpy not installed")
def test_ip_num_solutions(self):
"""
Expand Down
Loading