Coverage for gpkit/solvers/mosek_cli.py: 97%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

65 statements  

1"""Module for using the MOSEK EXPOPT command line interface 

2 

3 Example 

4 ------- 

5 ``result = _mosek.cli_expopt.imize(cs, A, p_idxs, "gpkit_mosek")`` 

6 

7""" 

8import os 

9import shutil 

10import tempfile 

11import errno 

12import stat 

13from subprocess import check_output, CalledProcessError 

14from .. import settings 

15from ..exceptions import (UnknownInfeasible, InvalidLicense, 

16 PrimalInfeasible, DualInfeasible) 

17 

18def remove_read_only(func, path, exc): # pragma: no cover 

19 "If we can't remove a file/directory, change permissions and try again." 

20 if func in (os.rmdir, os.remove) and exc[1].errno == errno.EACCES: 

21 # change the file to be readable,writable,executable: 0777 

22 os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) 

23 func(path) # try again 

24 

25 

26def optimize_generator(path=None, **_): 

27 """Constructor for the MOSEK CLI solver function. 

28 

29 Arguments 

30 --------- 

31 path : str (optional) 

32 The directory in which to put the MOSEK CLI input/output files. 

33 By default uses a system-appropriate temp directory. 

34 """ 

35 tmpdir = path is None 

36 if tmpdir: 

37 path = tempfile.mkdtemp() 

38 filename = path + os.sep + "gpkit_mosek" 

39 if "mosek_bin_dir" in settings: 

40 if settings["mosek_bin_dir"] not in os.environ["PATH"]: 

41 os.environ["PATH"] += ":" + settings["mosek_bin_dir"] 

42 

43 def optimize(*, c, A, p_idxs, **_): 

44 """Interface to the MOSEK "mskexpopt" command line solver 

45 

46 Definitions 

47 ----------- 

48 n is the number of monomials in the gp 

49 m is the number of variables in the gp 

50 p is the number of posynomials in the gp 

51 

52 Arguments 

53 --------- 

54 c : floats array of shape n 

55 Coefficients of each monomial 

56 A: floats array of shape (m,n) 

57 Exponents of the various free variables for each monomial. 

58 p_idxs: ints array of shape n 

59 Posynomial index of each monomial 

60 filename: str 

61 Filename prefix for temporary files 

62 

63 Returns 

64 ------- 

65 dict 

66 Contains the following keys 

67 "success": bool 

68 "objective_sol" float 

69 Optimal value of the objective 

70 "primal_sol": floats array of size m 

71 Optimal value of the free variables. Note: not in logspace. 

72 "dual_sol": floats array of size p 

73 Optimal value of the dual variables, in logspace. 

74 

75 Raises 

76 ------ 

77 RuntimeWarning 

78 If the format of mskexpopt's output file is unexpected. 

79 

80 """ 

81 write_output_file(filename, c, A, p_idxs) 

82 

83 # run mskexpopt and print stdout 

84 solution_filename = filename + ".sol" 

85 try: 

86 for logline in check_output(["mskexpopt", filename, "-sol", 

87 solution_filename]).split(b"\n"): 

88 print(logline) 

89 except CalledProcessError as e: 

90 # invalid license return codes: 

91 # expired: 233 (linux) 

92 # missing: 240 (linux) 

93 if e.returncode in [233, 240]: # pragma: no cover 

94 raise InvalidLicense() from e 

95 raise UnknownInfeasible() from e 

96 with open(solution_filename) as f: 

97 _, probsta = f.readline()[:-1].split("PROBLEM STATUS : ") 

98 if probsta == "PRIMAL_INFEASIBLE": 

99 raise PrimalInfeasible() 

100 if probsta == "DUAL_INFEASIBLE": 

101 raise DualInfeasible() 

102 if probsta != "PRIMAL_AND_DUAL_FEASIBLE": 

103 raise UnknownInfeasible("PROBLEM STATUS: " + probsta) 

104 

105 _, solsta = f.readline().split("SOLUTION STATUS : ") 

106 # line looks like "OBJECTIVE : 2.763550e+002" 

107 objective_val = float(f.readline().split()[2]) 

108 assert_equal(f.readline(), "") 

109 assert_equal(f.readline(), "PRIMAL VARIABLES") 

110 assert_equal(f.readline(), "INDEX ACTIVITY") 

111 primal_vals = read_vals(f) 

112 # read_vals reads the dividing blank line as well 

113 assert_equal(f.readline(), "DUAL VARIABLES") 

114 assert_equal(f.readline(), "INDEX ACTIVITY") 

115 dual_vals = read_vals(f) 

116 

117 if tmpdir: 

118 shutil.rmtree(path, ignore_errors=False, onerror=remove_read_only) 

119 

120 return dict(status=solsta[:-1], 

121 objective=objective_val, 

122 primal=primal_vals, 

123 nu=dual_vals) 

124 

125 return optimize 

126 

127 

128def write_output_file(filename, c, A, p_idxs): 

129 "Writes a mosekexpopt compatible GP description to `filename`." 

130 with open(filename, "w") as f: 

131 numcon = p_idxs[-1] 

132 numter, numvar = map(int, A.shape) 

133 for n in [numcon, numvar, numter]: 

134 f.write("%d\n" % n) 

135 

136 f.write("\n*c\n") 

137 f.writelines(["%.20e\n" % x for x in c]) 

138 

139 f.write("\n*p_idxs\n") 

140 f.writelines(["%d\n" % x for x in p_idxs]) 

141 

142 f.write("\n*t j A_tj\n") 

143 f.writelines(["%d %d %.20e\n" % tuple(x) 

144 for x in zip(A.row, A.col, A.data)]) 

145 

146 

147def assert_equal(received, expected): 

148 "Asserts that a file's next line is as expected." 

149 if expected.rstrip() != received.rstrip(): # pragma: no cover 

150 errstr = repr(expected)+" is not the same as "+repr(received) 

151 raise RuntimeWarning("could not read mskexpopt output file: "+errstr) 

152 

153 

154def read_vals(fil): 

155 "Read numeric values until a blank line occurs." 

156 vals = [] 

157 line = fil.readline() 

158 while line not in ["", "\n"]: 

159 # lines look like "1 2.390776e+000 \n" 

160 vals.append(float(line.split()[1])) 

161 line = fil.readline() 

162 return vals