"""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, InvalidLicense, 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: # invalid license return codes: # expired: 233 (linux) # missing: 240 (linux) if e.returncode in [233, 240]: raise InvalidLicense() from 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