-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enabling the use of gurobipy directly, which should be faster #60
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,9 +50,17 @@ | |
except ImportError: | ||
logger.info('MOSEK solver not found.') | ||
|
||
try: | ||
import gurobipy as gp | ||
installed_solvers.add('gurobi') | ||
except ImportError: | ||
logger.info('GUROBI solver not found') | ||
|
||
|
||
# choose default from installed choices | ||
if 'glpk' in installed_solvers: | ||
if 'gurobi' in installed_solvers: | ||
default_solver = 'gurobi' | ||
elif 'glpk' in installed_solvers: | ||
default_solver = 'glpk' | ||
elif 'scipy' in installed_solvers: | ||
default_solver = 'scipy' | ||
|
@@ -62,7 +70,6 @@ | |
"`installed_solvers` wasn't empty above?") | ||
|
||
|
||
|
||
def lpsolve(c, G, h, solver=None): | ||
"""Try to solve linear program with given or default solver. | ||
|
||
|
@@ -88,6 +95,8 @@ def lpsolve(c, G, h, solver=None): | |
result = _solve_lp_using_cvxopt(c, G, h, solver=solver) | ||
elif solver == 'scipy': | ||
result = _solve_lp_using_scipy(c, G, h) | ||
elif solver == 'gurobi': | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Passing the selection |
||
result = _solve_lp_using_gurobi(c, G, h) | ||
else: | ||
raise Exception( | ||
'unknown LP solver "{s}".'.format(s=solver)) | ||
|
@@ -145,6 +154,34 @@ def _solve_lp_using_scipy(c, G, h): | |
fun=sol.fun) | ||
|
||
|
||
def _solve_lp_using_gurobi(c, G, h): | ||
"""Attempt linear optimization using gurobipy""" | ||
_assert_have_solver('gurobi') | ||
m = gp.Model() | ||
x = m.addMVar(G.shape[1], lb=-gp.GRB.INFINITY) | ||
m.addConstr(G@x <= h) | ||
m.setObjective(c@x) | ||
m.optimize() | ||
|
||
result = dict() | ||
if m.Status == gp.GRB.OPTIMAL: | ||
result['status'] = 0 | ||
result['x'] = x.x | ||
result['fun'] = m.ObjVal | ||
return result | ||
elif m.Status == gp.GRB.INFEASIBLE: | ||
result['status'] = 2 | ||
elif m.Status == gp.GRB.INF_OR_UNBD or m.Status == gp.GRB.UNBOUNDED: | ||
result['status'] = 3 | ||
else: | ||
raise ValueError(f'`gurobipy` returned unexpected status value: {m.Status}') | ||
|
||
result['x'] = None | ||
result['fun'] = None | ||
return result | ||
|
||
|
||
|
||
def _assert_have_solver(solver): | ||
"""Raise `RuntimeError` if `solver` is absent.""" | ||
if solver in installed_solvers: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
cvxopt==1.2.4 | ||
cvxopt==1.2.4 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line appears to have only whitespace changes. It seems that the line could be omitted from the changeset. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -486,5 +486,14 @@ def is_glpk_present(): | |
return False | ||
|
||
|
||
def test_gurobipy_return_same_result_as_scipy(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is good to test the added functionality. However, the absence of the solver There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
c, A, b = example_1d() | ||
result_gurobi = solvers.lpsolve(c, A, b, solver='gurobi') | ||
result_scipy = solvers.lpsolve(c, A, b, solver='scipy') | ||
nt.assert_equal(result_scipy['status'], result_gurobi['status']) | ||
nt.assert_almost_equal(result_scipy['x'][0], result_gurobi['x'][0]) | ||
nt.assert_almost_equal(result_scipy['fun'], result_gurobi['fun']) | ||
|
||
|
||
if __name__ == '__main__': | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I lean towards not making
gurobi
a default solver, becausegurobi
andgurobipy
are proprietary, closed-source software: https://www.gurobi.com/downloads/end-user-license-agreement-academic/, and: https://pypi.org/project/gurobipy/.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with not making
gurobi
a default solver. This is not a vote against Gurobi, but rather, automatically using a restrictive-license-encumbered dependency is an anti-pattern of open source code. Users should have to explicitly request it.Perhaps there is a more convenient way for users to do this, e.g., an environment variable.