/
mosek_cli.py
157 lines (131 loc) · 5.41 KB
/
mosek_cli.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"""Module for using the MOSEK EXPOPT command line interface
Example
-------
``result = _mosek.cli_expopt.imize(cs, A, p_idxs, "gpkit_mosek")``
"""
import os
import shutil
import tempfile
import errno
import stat
from subprocess import check_output, CalledProcessError
from .. import settings
from ..exceptions import (UnknownInfeasible,
PrimalInfeasible, DualInfeasible)
def remove_read_only(func, path, exc):
"If we can't remove a file/directory, change permissions and try again."
if func in (os.rmdir, os.remove) and exc[1].errno == errno.EACCES:
# change the file to be readable,writable,executable: 0777
os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
func(path) # try again
def optimize_generator(path=None):
"""Constructor for the MOSEK CLI solver function.
Arguments
---------
path : str (optional)
The directory in which to put the MOSEK CLI input/output files.
By default uses a system-appropriate temp directory.
"""
tmpdir = path is None
if tmpdir:
path = tempfile.mkdtemp()
filename = path + os.sep + "gpkit_mosek"
if "mosek_bin_dir" in settings:
if settings["mosek_bin_dir"] not in os.environ["PATH"]:
os.environ["PATH"] += ":" + settings["mosek_bin_dir"]
def optimize(*, c, A, p_idxs, **_):
"""Interface to the MOSEK "mskexpopt" command line solver
Definitions
-----------
n is the number of monomials in the gp
m is the number of variables in the gp
p is the number of posynomials in the gp
Arguments
---------
c : floats array of shape n
Coefficients of each monomial
A: floats array of shape (m,n)
Exponents of the various free variables for each monomial.
p_idxs: ints array of shape n
Posynomial index of each monomial
filename: str
Filename prefix for temporary files
Returns
-------
dict
Contains the following keys
"success": bool
"objective_sol" float
Optimal value of the objective
"primal_sol": floats array of size m
Optimal value of the free variables. Note: not in logspace.
"dual_sol": floats array of size p
Optimal value of the dual variables, in logspace.
Raises
------
RuntimeWarning
If the format of mskexpopt's output file is unexpected.
"""
write_output_file(filename, c, A, p_idxs)
# run mskexpopt and print stdout
solution_filename = filename + ".sol"
try:
for logline in check_output(["mskexpopt", filename, "-sol",
solution_filename]).split(b"\n"):
print(logline)
except CalledProcessError as e:
raise UnknownInfeasible() from e
with open(solution_filename) as f:
_, probsta = f.readline().split("PROBLEM STATUS : ")
if probsta == "PRIMAL_INFEASIBLE\n":
raise PrimalInfeasible()
if probsta == "DUAL_INFEASIBLE\n":
raise DualInfeasible()
if probsta != "PRIMAL_AND_DUAL_FEASIBLE\n":
raise UnknownInfeasible("PROBLEM STATUS: " + probsta[:-1])
_, solsta = f.readline().split("SOLUTION STATUS : ")
# line looks like "OBJECTIVE : 2.763550e+002"
objective_val = float(f.readline().split()[2])
assert_equal(f.readline(), "\n")
assert_equal(f.readline(), "PRIMAL VARIABLES\n")
assert_equal(f.readline(), "INDEX ACTIVITY\n")
primal_vals = read_vals(f)
# read_vals reads the dividing blank line as well
assert_equal(f.readline(), "DUAL VARIABLES\n")
assert_equal(f.readline(), "INDEX ACTIVITY\n")
dual_vals = read_vals(f)
if tmpdir:
shutil.rmtree(path, ignore_errors=False, onerror=remove_read_only)
return dict(status=solsta[:-1],
objective=objective_val,
primal=primal_vals,
nu=dual_vals)
return optimize
def write_output_file(filename, c, A, p_idxs):
"Writes a mosekexpopt compatible GP description to `filename`."
with open(filename, "w") as f:
numcon = p_idxs[-1]
numter, numvar = map(int, A.shape)
for n in [numcon, numvar, numter]:
f.write("%d\n" % n)
f.write("\n*c\n")
f.writelines(["%.20e\n" % x for x in c])
f.write("\n*p_idxs\n")
f.writelines(["%d\n" % x for x in p_idxs])
f.write("\n*t j A_tj\n")
f.writelines(["%d %d %.20e\n" % tuple(x)
for x in zip(A.row, A.col, A.data)])
def assert_equal(received, expected):
"Asserts that a file's next line is as expected."
if tuple(expected[:-1].split()) != tuple(received[:-1].split()):
errstr = repr(expected)+" is not the same as "+repr(received)
raise RuntimeWarning("could not read mskexpopt output file: "+errstr)
def read_vals(fil):
"Read numeric values until a blank line occurs."
vals = []
line = fil.readline()
while line not in ["", "\n"]:
# lines look like "1 2.390776e+000 \n"
vals.append(float(line.split()[1]))
line = fil.readline()
return vals