-
Notifications
You must be signed in to change notification settings - Fork 40
support for mosek 9 (local branch) #1454
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
Merged
Merged
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
8d97565
gitignore
eacc720
fixed docstring error
739a33c
fixed inline comment error
4c464d9
more docstring changes
1b3dd6c
basic, untested mosek 9+ interface
c4a4bf8
working mosek interface, but dual variables dont match up with gpkit …
b83c85d
make sure this class can see the mosek_conif solver
39e3b56
add finding mosek python interface to the build process
d29905d
specify solvers in all tests. minor additions for mosek_conif interface
80a8fde
precision in some tests. line spacing issues.
rileyjmurray 517a267
switched to a log-sum-exp formulation; dual variables recovering corr…
rileyjmurray 152511c
precision requirements
rileyjmurray 791d58a
precision and API changes to some tests. Remaining failing tests are …
rileyjmurray 3685157
gitignore
rileyjmurray 6efe3e5
status handling
rileyjmurray 9807278
improved mosek implementation; monomial posynomial constraints are pa…
rileyjmurray 8169e36
test precision
a302f6e
improved documentation for mskoptimize in mosek_conif.py
17be878
mosek_conif: check if an installed mosek python package is associated…
rileyjmurray 1d79b07
pylint
rileyjmurray 09d8bb6
lint in mosek_conif.
1ozturkbe 4d3431e
small cruft edits, fix on some large SPs
bqpd 3d7ce71
simplified implementation, by taking cues from bqpd's mosek9 branch (…
d0d51ff
lint
bqpd 8eb8491
Merge remote-tracking branch 'rileyjmurray/master' into mosek9riley
bqpd 229aae5
default verbosity is True
bqpd 738b022
minor speedups on problem construction
bqpd fc395f2
fix test
bqpd ae24bef
Merge branch 'master' into mosek9riley
1ozturkbe 0fc8ae3
lint
bqpd ee8f999
lint
bqpd 7a50d0a
Merge branch 'master' into mosek9riley
bqpd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
"""Implements the GPkit interface to MOSEK (version >= 9) | ||
through python-based Optimizer API""" | ||
|
||
import numpy as np | ||
import mosek | ||
|
||
def mskoptimize(c, A, k, p_idxs, *args, **kwargs): | ||
# pylint: disable=unused-argument | ||
# pylint: disable-msg=too-many-locals | ||
# pylint: disable-msg=too-many-statements | ||
""" | ||
Definitions | ||
----------- | ||
"[a,b] array of floats" indicates array-like data with shape [a,b] | ||
n is the number of monomials in the gp | ||
m is the number of variables in the gp | ||
p is the number of posynomial constraints in the gp | ||
|
||
Arguments | ||
--------- | ||
c : floats array of shape n | ||
Coefficients of each monomial | ||
A : gpkit.small_classes.CootMatrix, of shape (n, m) | ||
Exponents of the various free variables for each monomial. | ||
k : ints array of shape p+1 | ||
k[0] is the number of monomials (rows of A) present in the objective | ||
k[1:] is the number of monomials (rows of A) present in each constraint | ||
p_idxs : ints array of shape n. | ||
sel = p_idxs == i selects rows of A | ||
and entries of c of the i-th posynomial | ||
fi(x) = c[sel] @ exp(A[sel,:] @ x). | ||
The 0-th posynomial gives the objective function, and the | ||
remaining posynomials should be constrained to be <= 1. | ||
|
||
Returns | ||
------- | ||
dict | ||
Contains the following keys | ||
"status": string | ||
"optimal", "infeasible", "unbounded", or "unknown". | ||
"primal" np.ndarray or None | ||
The values of the ``m`` primal variables. | ||
"la": np.ndarray or None | ||
The dual variables to the ``p`` posynomial constraints, when | ||
those constraints are represented in log-sum-exp ("LSE") form. | ||
""" | ||
# | ||
# Initial transformations of problem data. | ||
# | ||
# separate monomial constraints (call them "lin"), | ||
# from those which require | ||
# an LSE representation (call those "lse"). | ||
# | ||
# NOTE: the epigraph of the objective function always gets an "lse" | ||
# representation, even if the objective is a monomial. | ||
# | ||
log_c = np.log(np.array(c)) | ||
lse_posys = [0] | ||
lin_posys = [] | ||
for i, val in enumerate(k[1:]): | ||
if val > 1: | ||
lse_posys.append(i+1) | ||
else: | ||
lin_posys.append(i+1) | ||
if lin_posys: | ||
A = A.tocsr() | ||
lin_posys_set = frozenset(lin_posys) | ||
lin_idxs = [i for i, p in enumerate(p_idxs) if p in lin_posys_set] | ||
lse_idxs = np.ones(A.shape[0], dtype=bool) | ||
lse_idxs[lin_idxs] = False | ||
A_lse = A[lse_idxs, :].tocoo() | ||
log_c_lse = log_c[lse_idxs] | ||
A_lin = A[lin_idxs, :].tocoo() | ||
log_c_lin = log_c[lin_idxs] | ||
else: | ||
log_c_lin = np.array([]) # A_lin won't be referenced later, | ||
A_lse = A # so no need to define it. | ||
log_c_lse = log_c | ||
k_lse = [k[i] for i in lse_posys] | ||
n_lse = sum(k_lse) | ||
p_lse = len(k_lse) | ||
lse_p_idx = [] | ||
for i, ki in enumerate(k_lse): | ||
lse_p_idx.extend([i] * ki) | ||
lse_p_idx = np.array(lse_p_idx) | ||
# | ||
# Create MOSEK task. Add variables, bound constraints, and conic | ||
# constraints. | ||
# | ||
# Say that MOSEK's optimization variable is a block vector, | ||
# [x;t;z], where ... | ||
# x is the user-defined primal variable (length m), | ||
# t is an aux variable for exponential cones (length 3 * n_lse), | ||
# z is an epigraph variable for LSE terms (length p_lse). | ||
# | ||
# The variable z[0] is special, | ||
# because it's the epigraph of the objective function | ||
# in LSE form. The sign of this variable is not constrained. | ||
# | ||
# The variables z[1:] are epigraph terms for "log", | ||
# in constraints that naturally write as | ||
# LSE(Ai @ x + log_ci) <= 0. These variables need to be <= 0. | ||
# | ||
# The main constraints on (x, t, z) are described | ||
# in next comment block. | ||
# | ||
env = mosek.Env() | ||
task = env.Task(0, 0) | ||
m = A.shape[1] | ||
msk_nvars = m + 3 * n_lse + p_lse | ||
task.appendvars(msk_nvars) | ||
# "x" is free | ||
task.putvarboundlist(np.arange(m), [mosek.boundkey.fr] * m, | ||
np.zeros(m), np.zeros(m)) | ||
# t[3 * i + i] == 1, other components are free. | ||
bound_types = [mosek.boundkey.fr, mosek.boundkey.fx, mosek.boundkey.fr] | ||
task.putvarboundlist(np.arange(m, m + 3*n_lse), bound_types * n_lse, | ||
np.ones(3*n_lse), np.ones(3*n_lse)) | ||
# z[0] is free; z[1:] <= 0. | ||
bound_types = [mosek.boundkey.fr] + [mosek.boundkey.up] * (p_lse - 1) | ||
task.putvarboundlist(np.arange(m + 3*n_lse, msk_nvars), bound_types, | ||
np.zeros(p_lse), np.zeros(p_lse)) | ||
# t[3*i], t[3*i + 1], t[3*i + 2] belongs to the exponential cone | ||
task.appendconesseq([mosek.conetype.pexp] * n_lse, [0.0] * n_lse, | ||
[3] * n_lse, m) | ||
# | ||
# Exponential cone affine constraints (other than t[3*i + 1] == 1). | ||
# | ||
# For each i in {0, ..., n_lse - 1}, we need | ||
# t[3*i + 2] == A_lse[i, :] @ x + log_c_lse[i] - z[lse_p_idx[i]]. | ||
# | ||
# For each j from {0, ..., p_lse - 1}, the "t" should also satisfy | ||
# sum(t[3*i] for i where i == lse_p_idx[j]) <= 1. | ||
# | ||
# When combined with bound constraints ``t[3*i + 1] == 1``, the | ||
# above constraints imply | ||
# LSE(A_lse[sel, :] @ x + log_c_lse[sel]) <= z[i] | ||
# for ``sel = lse_p_idx == i``. | ||
# | ||
task.appendcons(n_lse + p_lse) | ||
# Linear equations between (x,t,z). | ||
# start with coefficients on "x" | ||
rows = list(A_lse.row) | ||
cols = list(A_lse.col) | ||
vals = list(A_lse.data) | ||
# add coefficients on "t" | ||
rows += list(range(n_lse)) | ||
cols += (m + 3*np.arange(n_lse) + 2).tolist() | ||
vals += [-1.0] * n_lse | ||
# add coefficients on "z" | ||
rows += list(range(n_lse)) | ||
cols += [m + 3*n_lse + lse_p_idx[i] for i in range(n_lse)] | ||
vals += [-1.0] * n_lse | ||
task.putaijlist(rows, cols, vals) | ||
cur_con_idx = n_lse | ||
# Linear inequalities on certain sums of "t". | ||
rows, cols, vals = [], [], [] | ||
for i in range(p_lse): | ||
sels = np.nonzero(lse_p_idx == i)[0] | ||
rows.extend([cur_con_idx] * sels.size) | ||
cols.extend(m + 3 * sels) | ||
vals.extend([1] * sels.size) | ||
cur_con_idx += 1 | ||
task.putaijlist(rows, cols, vals) | ||
# Build the right-hand-sides of the [in]equality constraints | ||
type_constraint = [mosek.boundkey.fx] * n_lse + [mosek.boundkey.up] * p_lse | ||
h = np.concatenate([-log_c_lse, np.ones(p_lse)]) | ||
task.putconboundlist(np.arange(h.size, dtype=int), type_constraint, h, h) | ||
# | ||
# Affine constraints, not needing the exponential cone | ||
# | ||
# Require A_lin @ x <= -log_c_lin. | ||
# | ||
if log_c_lin.size > 0: | ||
task.appendcons(log_c_lin.size) | ||
rows = cur_con_idx + np.array(A_lin.row) | ||
task.putaijlist(rows, A_lin.col, A_lin.data) | ||
type_constraint = [mosek.boundkey.up] * log_c_lin.size | ||
con_indices = np.arange(cur_con_idx, cur_con_idx + log_c_lin.size) | ||
h = -log_c_lin | ||
task.putconboundlist(con_indices, type_constraint, h, h) | ||
cur_con_idx += log_c_lin.size | ||
# | ||
# Set the objective function | ||
# | ||
task.putclist([int(m + 3*n_lse)], [1]) | ||
task.putobjsense(mosek.objsense.minimize) | ||
# | ||
# Set solver parameters, and call .solve(). | ||
# | ||
verbose = kwargs.get("verbose", True) | ||
if verbose: | ||
def streamprinter(text): | ||
""" Stream printer for output from mosek. """ | ||
print(text) | ||
|
||
env.set_Stream(mosek.streamtype.log, streamprinter) | ||
task.set_Stream(mosek.streamtype.log, streamprinter) | ||
task.putintparam(mosek.iparam.infeas_report_auto, mosek.onoffkey.on) | ||
task.putintparam(mosek.iparam.log_presolve, 0) | ||
|
||
task.optimize() | ||
|
||
if verbose: | ||
task.solutionsummary(mosek.streamtype.msg) | ||
# | ||
# Recover the solution | ||
# | ||
msk_solsta = task.getsolsta(mosek.soltype.itr) | ||
if msk_solsta == mosek.solsta.optimal: | ||
# recover primal variables | ||
x = [0.] * m | ||
task.getxxslice(mosek.soltype.itr, 0, m, x) | ||
x = np.array(x) | ||
# recover dual variables for log-sum-exp epigraph constraints | ||
# (skip epigraph of the objective function). | ||
z_duals = [0.] * (p_lse - 1) | ||
task.getsuxslice(mosek.soltype.itr, m + 3*n_lse + 1, | ||
msk_nvars, z_duals) | ||
z_duals = np.array(z_duals) | ||
z_duals[z_duals < 0] = 0 | ||
# recover dual variables for the remaining user-provided constraints | ||
if log_c_lin.size > 0: | ||
aff_duals = [0.] * log_c_lin.size | ||
task.getsucslice(mosek.soltype.itr, n_lse + p_lse, cur_con_idx, | ||
aff_duals) | ||
aff_duals = np.array(aff_duals) | ||
aff_duals[aff_duals < 0] = 0 | ||
# merge z_duals with aff_duals | ||
merged_duals = np.zeros(len(k)) | ||
merged_duals[lse_posys[1:]] = z_duals | ||
merged_duals[lin_posys] = aff_duals | ||
merged_duals = merged_duals[1:] | ||
else: | ||
merged_duals = z_duals | ||
# wrap things up in a dictionary | ||
solution = {'status': 'optimal', 'primal': x, 'la': merged_duals} | ||
elif msk_solsta == mosek.solsta.prim_infeas_cer: | ||
solution = {'status': 'infeasible', 'primal': None, 'la': None} | ||
elif msk_solsta == mosek.solsta.dual_infeas_cer: | ||
solution = {'status': 'unbounded', 'primal': None, 'la': None} | ||
else: | ||
solution = {'status': 'unknown', 'primal': None, 'la': None} | ||
task.__exit__(None, None, None) | ||
env.__exit__(None, None, None) | ||
return solution |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
This step has time complexity
p_lse * n_lse
. It's possible to pre-compute thesels
selectors in timen_lse
. @bqpd -- since you mentioned there's still a mosek 8 vs mosek 9 speed difference.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.
Is the speed difference purely a result of the c versus optimizer interface though @bqpd?
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 actually noticed a slight speedup on my machine using mosek_conif.
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.
@rileyjmurray I profiled it and took out the lines that were causing slowdowns in practice, so now around 94% of the time is spent in the optimize() call on my computer; that's where the speed difference is.
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.
@1ozturkbe I saw SPaircraft sped up quite a bit on my computer, but some other codes slowed down considerably
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.
makes sense.