Coverage for gpkit\solvers\mosek_cli.py: 0%
65 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-05 22:34 -0500
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-05 22:34 -0500
1"""Module for using the MOSEK EXPOPT command line interface
3 Example
4 -------
5 ``result = _mosek.cli_expopt.imize(cs, A, p_idxs, "gpkit_mosek")``
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)
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
26def optimize_generator(path=None, **_):
27 """Constructor for the MOSEK CLI solver function.
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"]
43 def optimize(*, c, A, p_idxs, **_):
44 """Interface to the MOSEK "mskexpopt" command line solver
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
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
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.
75 Raises
76 ------
77 RuntimeWarning
78 If the format of mskexpopt's output file is unexpected.
80 """
81 write_output_file(filename, c, A, p_idxs)
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, encoding="UTF-8") 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)
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)
117 if tmpdir:
118 shutil.rmtree(path, ignore_errors=False, onerror=remove_read_only)
120 return {"status": solsta[:-1],
121 "objective": objective_val,
122 "primal": primal_vals,
123 "nu": dual_vals}
125 return optimize
128def write_output_file(filename, c, A, p_idxs):
129 "Writes a mosekexpopt compatible GP description to `filename`."
130 with open(filename, "w", encoding="UTF-8") as f:
131 numcon = p_idxs[-1]
132 numter, numvar = map(int, A.shape)
133 for n in [numcon, numvar, numter]:
134 f.write(f"{n:d}\n")
136 f.write("\n*c\n")
137 f.writelines([f"{x:.20e}\n" for x in c])
139 f.write("\n*p_idxs\n")
140 f.writelines([f"{x:d}\n" for x in p_idxs])
142 f.write("\n*t j A_tj\n")
143 f.writelines([f"{arow:d} {acol:d} {adata:.20e}\n"
144 for arow, acol, adata in zip(A.row, A.col, A.data)])
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)
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