def dcopf_solver(om, ppopt, out_opt=None): """Solves a DC optimal power flow. Inputs are an OPF model object, a PYPOWER options dict and a dict containing fields (can be empty) for each of the desired optional output fields. Outputs are a C{results} dict, C{success} flag and C{raw} output dict. C{results} is a PYPOWER case dict (ppc) with the usual baseMVA, bus branch, gen, gencost fields, along with the following additional fields: - C{order} see 'help ext2int' for details of this field - C{x} final value of optimization variables (internal order) - C{f} final objective function value - C{mu} shadow prices on ... - C{var} - C{l} lower bounds on variables - C{u} upper bounds on variables - C{lin} - C{l} lower bounds on linear constraints - C{u} upper bounds on linear constraints - C{g} (optional) constraint values - C{dg} (optional) constraint 1st derivatives - C{df} (optional) obj fun 1st derivatives (not yet implemented) - C{d2f} (optional) obj fun 2nd derivatives (not yet implemented) C{success} is C{True} if solver converged successfully, C{False} otherwise. C{raw} is a raw output dict in form returned by MINOS - C{xr} final value of optimization variables - C{pimul} constraint multipliers - C{info} solver specific termination code - C{output} solver specific output information @see: L{opf}, L{qps_pypower} @author: Ray Zimmerman (PSERC Cornell) @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) @author: Richard Lincoln """ if out_opt is None: out_opt = {} ## options verbose = ppopt['VERBOSE'] alg = ppopt['OPF_ALG_DC'] if alg == 0: if have_fcn('cplex'): ## use CPLEX by default, if available alg = 500 elif have_fcn('mosek'): ## if not, then MOSEK, if available alg = 600 elif have_fcn('gurobi'): ## if not, then Gurobi, if available alg = 700 else: ## otherwise PIPS alg = 200 ## unpack data ppc = om.get_ppc() baseMVA, bus, gen, branch, gencost = \ ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"], ppc["gencost"] cp = om.get_cost_params() N, H, Cw = cp["N"], cp["H"], cp["Cw"] fparm = array(c_[cp["dd"], cp["rh"], cp["kk"], cp["mm"]]) Bf = om.userdata('Bf') Pfinj = om.userdata('Pfinj') vv, ll, _, _ = om.get_idx() ## problem dimensions ipol = find(gencost[:, MODEL] == POLYNOMIAL) ## polynomial costs ipwl = find(gencost[:, MODEL] == PW_LINEAR) ## piece-wise linear costs nb = bus.shape[0] ## number of buses nl = branch.shape[0] ## number of branches nw = N.shape[0] ## number of general cost vars, w ny = om.getN('var', 'y') ## number of piece-wise linear costs nxyz = om.getN('var') ## total number of control vars of all types ## linear constraints & variable bounds A, l, u = om.linear_constraints() x0, xmin, xmax = om.getv() ## set up objective function of the form: f = 1/2 * X'*HH*X + CC'*X ## where X = [x;y;z]. First set up as quadratic function of w, ## f = 1/2 * w'*HHw*w + CCw'*w, where w = diag(M) * (N*X - Rhat). We ## will be building on the (optionally present) user supplied parameters. ## piece-wise linear costs any_pwl = int(ny > 0) if any_pwl: # Sum of y vars. Npwl = sparse( (ones(ny), (zeros(ny), arange(vv["i1"]["y"], vv["iN"]["y"]))), (1, nxyz)) Hpwl = sparse((1, 1)) Cpwl = array([1]) fparm_pwl = array([[1, 0, 0, 1]]) else: Npwl = None #zeros((0, nxyz)) Hpwl = None #array([]) Cpwl = array([]) fparm_pwl = zeros((0, 4)) ## quadratic costs npol = len(ipol) if any(find(gencost[ipol, NCOST] > 3)): stderr.write('DC opf cannot handle polynomial costs with higher ' 'than quadratic order.\n') iqdr = find(gencost[ipol, NCOST] == 3) ilin = find(gencost[ipol, NCOST] == 2) polycf = zeros((npol, 3)) ## quadratic coeffs for Pg if len(iqdr) > 0: polycf[iqdr, :] = gencost[ipol[iqdr], COST:COST + 3] if npol: polycf[ilin, 1:3] = gencost[ipol[ilin], COST:COST + 2] polycf = dot(polycf, diag([baseMVA**2, baseMVA, 1])) ## convert to p.u. if npol: Npol = sparse((ones(npol), (arange(npol), vv["i1"]["Pg"] + ipol)), (npol, nxyz)) # Pg vars Hpol = sparse((2 * polycf[:, 0], (arange(npol), arange(npol))), (npol, npol)) else: Npol = None Hpol = None Cpol = polycf[:, 1] fparm_pol = ones((npol, 1)) * array([[1, 0, 0, 1]]) ## combine with user costs NN = vstack( [n for n in [Npwl, Npol, N] if n is not None and n.shape[0] > 0], "csr") # FIXME: Zero dimension sparse matrices. if (Hpwl is not None) and any_pwl and (npol + nw): Hpwl = hstack([Hpwl, sparse((any_pwl, npol + nw))]) if Hpol is not None: if any_pwl and npol: Hpol = hstack([sparse((npol, any_pwl)), Hpol]) if npol and nw: Hpol = hstack([Hpol, sparse((npol, nw))]) if (H is not None) and nw and (any_pwl + npol): H = hstack([sparse((nw, any_pwl + npol)), H]) HHw = vstack( [h for h in [Hpwl, Hpol, H] if h is not None and h.shape[0] > 0], "csr") CCw = r_[Cpwl, Cpol, Cw] ffparm = r_[fparm_pwl, fparm_pol, fparm] ## transform quadratic coefficients for w into coefficients for X nnw = any_pwl + npol + nw M = sparse((ffparm[:, 3], (range(nnw), range(nnw)))) MR = M * ffparm[:, 1] HMR = HHw * MR MN = M * NN HH = MN.T * HHw * MN CC = MN.T * (CCw - HMR) C0 = 0.5 * dot(MR, HMR) + sum(polycf[:, 2]) # Constant term of cost. ## set up input for QP solver opt = {'alg': alg, 'verbose': verbose} if (alg == 200) or (alg == 250): ## try to select an interior initial point Varefs = bus[bus[:, BUS_TYPE] == REF, VA] * (pi / 180.0) lb, ub = xmin.copy(), xmax.copy() lb[xmin == -Inf] = -1e10 ## replace Inf with numerical proxies ub[xmax == Inf] = 1e10 x0 = (lb + ub) / 2 # angles set to first reference angle x0[vv["i1"]["Va"]:vv["iN"]["Va"]] = Varefs[0] if ny > 0: ipwl = find(gencost[:, MODEL] == PW_LINEAR) # largest y-value in CCV data c = gencost.flatten('F')[sub2ind(gencost.shape, ipwl, NCOST + 2 * gencost[ipwl, NCOST])] x0[vv["i1"]["y"]:vv["iN"]["y"]] = max(c) + 0.1 * abs(max(c)) ## set up options feastol = ppopt['PDIPM_FEASTOL'] gradtol = ppopt['PDIPM_GRADTOL'] comptol = ppopt['PDIPM_COMPTOL'] costtol = ppopt['PDIPM_COSTTOL'] max_it = ppopt['PDIPM_MAX_IT'] max_red = ppopt['SCPDIPM_RED_IT'] if feastol == 0: feastol = ppopt['OPF_VIOLATION'] ## = OPF_VIOLATION by default opt["pips_opt"] = { 'feastol': feastol, 'gradtol': gradtol, 'comptol': comptol, 'costtol': costtol, 'max_it': max_it, 'max_red': max_red, 'cost_mult': 1 } elif alg == 400: opt['ipopt_opt'] = ipopt_options([], ppopt) elif alg == 500: opt['cplex_opt'] = cplex_options([], ppopt) elif alg == 600: opt['mosek_opt'] = mosek_options([], ppopt) elif alg == 700: opt['grb_opt'] = gurobi_options([], ppopt) else: raise ValueError("Unrecognised solver [%d]." % alg) ##----- run opf ----- x, f, info, output, lmbda = \ qps_pypower(HH, CC, A, l, u, xmin, xmax, x0, opt) success = (info == 1) ##----- calculate return values ----- if not any(isnan(x)): ## update solution data Va = x[vv["i1"]["Va"]:vv["iN"]["Va"]] Pg = x[vv["i1"]["Pg"]:vv["iN"]["Pg"]] f = f + C0 ## update voltages & generator outputs bus[:, VA] = Va * 180 / pi gen[:, PG] = Pg * baseMVA ## compute branch flows branch[:, [QF, QT]] = zeros((nl, 2)) branch[:, PF] = (Bf * Va + Pfinj) * baseMVA branch[:, PT] = -branch[:, PF] ## package up results mu_l = lmbda["mu_l"] mu_u = lmbda["mu_u"] muLB = lmbda["lower"] muUB = lmbda["upper"] ## update Lagrange multipliers il = find((branch[:, RATE_A] != 0) & (branch[:, RATE_A] < 1e10)) bus[:, [LAM_P, LAM_Q, MU_VMIN, MU_VMAX]] = zeros((nb, 4)) gen[:, [MU_PMIN, MU_PMAX, MU_QMIN, MU_QMAX]] = zeros((gen.shape[0], 4)) branch[:, [MU_SF, MU_ST]] = zeros((nl, 2)) bus[:, LAM_P] = (mu_u[ll["i1"]["Pmis"]:ll["iN"]["Pmis"]] - mu_l[ll["i1"]["Pmis"]:ll["iN"]["Pmis"]]) / baseMVA branch[il, MU_SF] = mu_u[ll["i1"]["Pf"]:ll["iN"]["Pf"]] / baseMVA branch[il, MU_ST] = mu_u[ll["i1"]["Pt"]:ll["iN"]["Pt"]] / baseMVA gen[:, MU_PMIN] = muLB[vv["i1"]["Pg"]:vv["iN"]["Pg"]] / baseMVA gen[:, MU_PMAX] = muUB[vv["i1"]["Pg"]:vv["iN"]["Pg"]] / baseMVA pimul = r_[mu_l - mu_u, -ones( (ny > 0)), ## dummy entry corresponding to linear cost row in A muLB - muUB] mu = {'var': {'l': muLB, 'u': muUB}, 'lin': {'l': mu_l, 'u': mu_u}} results = deepcopy(ppc) results["bus"], results["branch"], results["gen"], \ results["om"], results["x"], results["mu"], results["f"] = \ bus, branch, gen, om, x, mu, f raw = {'xr': x, 'pimul': pimul, 'info': info, 'output': output} return results, success, raw
def dcopf_solver(om, ppopt, out_opt=None): """Solves a DC optimal power flow. Inputs are an OPF model object, a PYPOWER options dict and a dict containing fields (can be empty) for each of the desired optional output fields. Outputs are a C{results} dict, C{success} flag and C{raw} output dict. C{results} is a PYPOWER case dict (ppc) with the usual baseMVA, bus branch, gen, gencost fields, along with the following additional fields: - C{order} see 'help ext2int' for details of this field - C{x} final value of optimization variables (internal order) - C{f} final objective function value - C{mu} shadow prices on ... - C{var} - C{l} lower bounds on variables - C{u} upper bounds on variables - C{lin} - C{l} lower bounds on linear constraints - C{u} upper bounds on linear constraints - C{g} (optional) constraint values - C{dg} (optional) constraint 1st derivatives - C{df} (optional) obj fun 1st derivatives (not yet implemented) - C{d2f} (optional) obj fun 2nd derivatives (not yet implemented) C{success} is C{True} if solver converged successfully, C{False} otherwise. C{raw} is a raw output dict in form returned by MINOS - C{xr} final value of optimization variables - C{pimul} constraint multipliers - C{info} solver specific termination code - C{output} solver specific output information @see: L{opf}, L{qps_pypower} @author: Ray Zimmerman (PSERC Cornell) @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) """ if out_opt is None: out_opt = {} ## options verbose = ppopt['VERBOSE'] alg = ppopt['OPF_ALG_DC'] if alg == 0: if have_fcn('cplex'): ## use CPLEX by default, if available alg = 500 elif have_fcn('mosek'): ## if not, then MOSEK, if available alg = 600 elif have_fcn('gurobi'): ## if not, then Gurobi, if available alg = 700 else: ## otherwise PIPS alg = 200 ## unpack data ppc = om.get_ppc() baseMVA, bus, gen, branch, gencost = \ ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"], ppc["gencost"] cp = om.get_cost_params() N, H, Cw = cp["N"], cp["H"], cp["Cw"] fparm = array(c_[cp["dd"], cp["rh"], cp["kk"], cp["mm"]]) Bf = om.userdata('Bf') Pfinj = om.userdata('Pfinj') vv, ll, _, _ = om.get_idx() ## problem dimensions ipol = find(gencost[:, MODEL] == POLYNOMIAL) ## polynomial costs ipwl = find(gencost[:, MODEL] == PW_LINEAR) ## piece-wise linear costs nb = bus.shape[0] ## number of buses nl = branch.shape[0] ## number of branches nw = N.shape[0] ## number of general cost vars, w ny = om.getN('var', 'y') ## number of piece-wise linear costs nxyz = om.getN('var') ## total number of control vars of all types ## linear constraints & variable bounds A, l, u = om.linear_constraints() x0, xmin, xmax = om.getv() ## set up objective function of the form: f = 1/2 * X'*HH*X + CC'*X ## where X = [x;y;z]. First set up as quadratic function of w, ## f = 1/2 * w'*HHw*w + CCw'*w, where w = diag(M) * (N*X - Rhat). We ## will be building on the (optionally present) user supplied parameters. ## piece-wise linear costs any_pwl = int(ny > 0) if any_pwl: # Sum of y vars. Npwl = sparse((ones(ny), (zeros(ny), arange(vv["i1"]["y"], vv["iN"]["y"]))), (1, nxyz)) Hpwl = sparse((1, 1)) Cpwl = array([1]) fparm_pwl = array([[1, 0, 0, 1]]) else: Npwl = None#zeros((0, nxyz)) Hpwl = None#array([]) Cpwl = array([]) fparm_pwl = zeros((0, 4)) ## quadratic costs npol = len(ipol) if any(find(gencost[ipol, NCOST] > 3)): stderr.write('DC opf cannot handle polynomial costs with higher ' 'than quadratic order.\n') iqdr = find(gencost[ipol, NCOST] == 3) ilin = find(gencost[ipol, NCOST] == 2) polycf = zeros((npol, 3)) ## quadratic coeffs for Pg if len(iqdr) > 0: polycf[iqdr, :] = gencost[ipol[iqdr], COST:COST + 3] if npol: polycf[ilin, 1:3] = gencost[ipol[ilin], COST:COST + 2] polycf = dot(polycf, diag([ baseMVA**2, baseMVA, 1])) ## convert to p.u. if npol: Npol = sparse((ones(npol), (arange(npol), vv["i1"]["Pg"] + ipol)), (npol, nxyz)) # Pg vars Hpol = sparse((2 * polycf[:, 0], (arange(npol), arange(npol))), (npol, npol)) else: Npol = None Hpol = None Cpol = polycf[:, 1] fparm_pol = ones((npol, 1)) * array([[1, 0, 0, 1]]) ## combine with user costs NN = vstack([n for n in [Npwl, Npol, N] if n is not None and n.shape[0] > 0], "csr") # FIXME: Zero dimension sparse matrices. if (Hpwl is not None) and any_pwl and (npol + nw): Hpwl = hstack([Hpwl, sparse((any_pwl, npol + nw))]) if Hpol is not None: if any_pwl and npol: Hpol = hstack([sparse((npol, any_pwl)), Hpol]) if npol and nw: Hpol = hstack([Hpol, sparse((npol, nw))]) if (H is not None) and nw and (any_pwl + npol): H = hstack([sparse((nw, any_pwl + npol)), H]) HHw = vstack([h for h in [Hpwl, Hpol, H] if h is not None and h.shape[0] > 0], "csr") CCw = r_[Cpwl, Cpol, Cw] ffparm = r_[fparm_pwl, fparm_pol, fparm] ## transform quadratic coefficients for w into coefficients for X nnw = any_pwl + npol + nw M = sparse((ffparm[:, 3], (range(nnw), range(nnw)))) MR = M * ffparm[:, 1] HMR = HHw * MR MN = M * NN HH = MN.T * HHw * MN CC = MN.T * (CCw - HMR) C0 = 0.5 * dot(MR, HMR) + sum(polycf[:, 2]) # Constant term of cost. ## set up input for QP solver opt = {'alg': alg, 'verbose': verbose} if (alg == 200) or (alg == 250): ## try to select an interior initial point Varefs = bus[bus[:, BUS_TYPE] == REF, VA] * (pi / 180.0) lb, ub = xmin.copy(), xmax.copy() lb[xmin == -Inf] = -1e10 ## replace Inf with numerical proxies ub[xmax == Inf] = 1e10 x0 = (lb + ub) / 2; # angles set to first reference angle x0[vv["i1"]["Va"]:vv["iN"]["Va"]] = Varefs[0] if ny > 0: ipwl = find(gencost[:, MODEL] == PW_LINEAR) # largest y-value in CCV data c = gencost.flatten('F')[sub2ind(gencost.shape, ipwl, NCOST + 2 * gencost[ipwl, NCOST])] x0[vv["i1"]["y"]:vv["iN"]["y"]] = max(c) + 0.1 * abs(max(c)) ## set up options feastol = ppopt['PDIPM_FEASTOL'] gradtol = ppopt['PDIPM_GRADTOL'] comptol = ppopt['PDIPM_COMPTOL'] costtol = ppopt['PDIPM_COSTTOL'] max_it = ppopt['PDIPM_MAX_IT'] max_red = ppopt['SCPDIPM_RED_IT'] if feastol == 0: feastol = ppopt['OPF_VIOLATION'] ## = OPF_VIOLATION by default opt["pips_opt"] = { 'feastol': feastol, 'gradtol': gradtol, 'comptol': comptol, 'costtol': costtol, 'max_it': max_it, 'max_red': max_red, 'cost_mult': 1 } elif alg == 400: opt['ipopt_opt'] = ipopt_options([], ppopt) elif alg == 500: opt['cplex_opt'] = cplex_options([], ppopt) elif alg == 600: opt['mosek_opt'] = mosek_options([], ppopt) elif alg == 700: opt['grb_opt'] = gurobi_options([], ppopt) else: raise ValueError("Unrecognised solver [%d]." % alg) ##----- run opf ----- x, f, info, output, lmbda = \ qps_pypower(HH, CC, A, l, u, xmin, xmax, x0, opt) success = (info == 1) ##----- calculate return values ----- if not any(isnan(x)): ## update solution data Va = x[vv["i1"]["Va"]:vv["iN"]["Va"]] Pg = x[vv["i1"]["Pg"]:vv["iN"]["Pg"]] f = f + C0 ## update voltages & generator outputs bus[:, VA] = Va * 180 / pi gen[:, PG] = Pg * baseMVA ## compute branch flows branch[:, [QF, QT]] = zeros((nl, 2)) branch[:, PF] = (Bf * Va + Pfinj) * baseMVA branch[:, PT] = -branch[:, PF] ## package up results mu_l = lmbda["mu_l"] mu_u = lmbda["mu_u"] muLB = lmbda["lower"] muUB = lmbda["upper"] ## update Lagrange multipliers il = find((branch[:, RATE_A] != 0) & (branch[:, RATE_A] < 1e10)) bus[:, [LAM_P, LAM_Q, MU_VMIN, MU_VMAX]] = zeros((nb, 4)) gen[:, [MU_PMIN, MU_PMAX, MU_QMIN, MU_QMAX]] = zeros((gen.shape[0], 4)) branch[:, [MU_SF, MU_ST]] = zeros((nl, 2)) bus[:, LAM_P] = (mu_u[ll["i1"]["Pmis"]:ll["iN"]["Pmis"]] - mu_l[ll["i1"]["Pmis"]:ll["iN"]["Pmis"]]) / baseMVA branch[il, MU_SF] = mu_u[ll["i1"]["Pf"]:ll["iN"]["Pf"]] / baseMVA branch[il, MU_ST] = mu_u[ll["i1"]["Pt"]:ll["iN"]["Pt"]] / baseMVA gen[:, MU_PMIN] = muLB[vv["i1"]["Pg"]:vv["iN"]["Pg"]] / baseMVA gen[:, MU_PMAX] = muUB[vv["i1"]["Pg"]:vv["iN"]["Pg"]] / baseMVA pimul = r_[ mu_l - mu_u, -ones(int(ny > 0)), ## dummy entry corresponding to linear cost row in A muLB - muUB ] mu = { 'var': {'l': muLB, 'u': muUB}, 'lin': {'l': mu_l, 'u': mu_u} } results = deepcopy(ppc) results["bus"], results["branch"], results["gen"], \ results["om"], results["x"], results["mu"], results["f"] = \ bus, branch, gen, om, x, mu, f raw = {'xr': x, 'pimul': pimul, 'info': info, 'output': output} return results, success, raw
def qps_gurobi(H, c, A, l, u, xmin, xmax, x0, opt): """Quadratic Program Solver based on GUROBI. A wrapper function providing a PYPOWER standardized interface for using gurobipy to solve the following QP (quadratic programming) problem: min 1/2 x'*H*x + c'*x x subject to l <= A*x <= u (linear constraints) xmin <= x <= xmax (variable bounds) Inputs (all optional except H, c, A and l): H : matrix (possibly sparse) of quadratic cost coefficients c : vector of linear cost coefficients A, l, u : define the optional linear constraints. Default values for the elements of l and u are -Inf and Inf, respectively. xmin, xmax : optional lower and upper bounds on the C{x} variables, defaults are -Inf and Inf, respectively. x0 : optional starting value of optimization vector C{x} opt : optional options structure with the following fields, all of which are also optional (default values shown in parentheses) verbose (0) - controls level of progress output displayed 0 = no progress output 1 = some progress output 2 = verbose progress output grb_opt - options dict for Gurobi, value in verbose overrides these options problem : The inputs can alternatively be supplied in a single PROBLEM dict with fields corresponding to the input arguments described above: H, c, A, l, u, xmin, xmax, x0, opt Outputs: x : solution vector f : final objective function value exitflag : gurobipy exit flag 1 = converged 0 or negative values = negative of GUROBI_MEX exit flag (see gurobipy documentation for details) output : gurobipy output dict (see gurobipy documentation for details) lmbda : dict containing the Langrange and Kuhn-Tucker multipliers on the constraints, with fields: mu_l - lower (left-hand) limit on linear constraints mu_u - upper (right-hand) limit on linear constraints lower - lower bound on optimization variables upper - upper bound on optimization variables Note the calling syntax is almost identical to that of QUADPROG from MathWorks' Optimization Toolbox. The main difference is that the linear constraints are specified with A, l, u instead of A, b, Aeq, beq. Calling syntax options: x, f, exitflag, output, lmbda = ... qps_gurobi(H, c, A, l, u, xmin, xmax, x0, opt) r = qps_gurobi(H, c, A, l, u) r = qps_gurobi(H, c, A, l, u, xmin, xmax) r = qps_gurobi(H, c, A, l, u, xmin, xmax, x0) r = qps_gurobi(H, c, A, l, u, xmin, xmax, x0, opt) r = qps_gurobi(problem), where problem is a dict with fields: H, c, A, l, u, xmin, xmax, x0, opt all fields except 'c', 'A' and 'l' or 'u' are optional Example: (problem from from http://www.jmu.edu/docs/sasdoc/sashtml/iml/chap8/sect12.htm) H = [ 1003.1 4.3 6.3 5.9; 4.3 2.2 2.1 3.9; 6.3 2.1 3.5 4.8; 5.9 3.9 4.8 10 ] c = zeros((4, 1)) A = [ [1 1 1 1] [0.17 0.11 0.10 0.18] ] l = [1; 0.10] u = [1; Inf] xmin = zeros((4, 1)) x0 = [1; 0; 0; 1] opt = {'verbose': 2} x, f, s, out, lmbda = qps_gurobi(H, c, A, l, u, xmin, [], x0, opt) @see: L{gurobipy}. """ import gurobipy ##----- input argument handling ----- ## gather inputs if isinstance(H, dict): ## problem struct p = H if 'opt' in p: opt = p['opt'] if 'x0' in p: x0 = p['x0'] if 'xmax' in p: xmax = p['xmax'] if 'xmin' in p: xmin = p['xmin'] if 'u' in p: u = p['u'] if 'l' in p: l = p['l'] if 'A' in p: A = p['A'] if 'c' in p: c = p['c'] if 'H' in p: H = p['H'] else: ## individual args assert H is not None assert c is not None assert A is not None assert l is not None if opt is None: opt = {} # if x0 is None: # x0 = array([]) # if xmax is None: # xmax = array([]) # if xmin is None: # xmin = array([]) ## define nx, set default values for missing optional inputs if len(H) == 0 or not any(any(H)): if len(A) == 0 and len(xmin) == 0 and len(xmax) == 0: stderr.write('qps_gurobi: LP problem must include constraints or variable bounds\n') else: if len(A) > 0: nx = shape(A)[1] elif len(xmin) > 0: nx = len(xmin) else: # if len(xmax) > 0 nx = len(xmax) H = sparse((nx, nx)) else: nx = shape(H)[0] if len(c) == 0: c = zeros(nx) if len(A) > 0 and (len(l) == 0 or all(l == -Inf)) and \ (len(u) == 0 or all(u == Inf)): A = None ## no limits => no linear constraints nA = shape(A)[0] ## number of original linear constraints if nA: if len(u) == 0: ## By default, linear inequalities are ... u = Inf * ones(nA) ## ... unbounded above and ... if len(l) == 0: l = -Inf * ones(nA) ## ... unbounded below. if len(x0) == 0: x0 = zeros(nx) ## default options if 'verbose' in opt: verbose = opt['verbose'] else: verbose = 0 # if 'max_it' in opt: # max_it = opt['max_it'] # else: # max_it = 0 ## set up options struct for Gurobi if 'grb_opt' in opt: g_opt = gurobi_options(opt['grb_opt']) else: g_opt = gurobi_options() g_opt['Display'] = min(verbose, 3) if verbose: g_opt['DisplayInterval'] = 1 else: g_opt['DisplayInterval'] = Inf if not issparse(A): A = sparse(A) ## split up linear constraints ieq = find( abs(u-l) <= EPS ) ## equality igt = find( u >= 1e10 & l > -1e10 ) ## greater than, unbounded above ilt = find( l <= -1e10 & u < 1e10 ) ## less than, unbounded below ibx = find( (abs(u-l) > EPS) & (u < 1e10) & (l > -1e10) ) ## grab some dimensions nlt = len(ilt) ## number of upper bounded linear inequalities ngt = len(igt) ## number of lower bounded linear inequalities nbx = len(ibx) ## number of doubly bounded linear inequalities neq = len(ieq) ## number of equalities niq = nlt + ngt + 2 * nbx ## number of inequalities AA = [ A[ieq, :], A[ilt, :], -A[igt, :], A[ibx, :], -A[ibx, :] ] bb = [ u[ieq], u[ilt], -l[igt], u[ibx], -l[ibx] ] contypes = '=' * neq + '<' * niq ## call the solver if len(H) == 0 or not any(any(H)): lpqp = 'LP' else: lpqp = 'QP' rr, cc, vv = find(H) g_opt['QP']['qrow'] = int(rr.T - 1) g_opt['QP']['qcol'] = int(cc.T - 1) g_opt['QP']['qval'] = 0.5 * vv.T if verbose: methods = [ 'primal simplex', 'dual simplex', 'interior point', 'concurrent', 'deterministic concurrent' ] print('Gurobi Version %s -- %s %s solver\n' '<unknown>' % (methods[g_opt['Method'] + 1], lpqp)) x, f, eflag, output, lmbda = \ gurobipy(c.T, 1, AA, bb, contypes, xmin, xmax, 'C', g_opt) pi = lmbda['Pi'] rc = lmbda['RC'] output['flag'] = eflag if eflag == 2: eflag = 1 ## optimal solution found else: eflag = -eflag ## failed somehow ## check for empty results (in case optimization failed) lam = {} if len(x) == 0: x = NaN(nx, 1); lam['lower'] = NaN(nx) lam['upper'] = NaN(nx) else: lam['lower'] = zeros(nx) lam['upper'] = zeros(nx) if len(f) == 0: f = NaN if len(pi) == 0: pi = NaN(len(bb)) kl = find(rc > 0); ## lower bound binding ku = find(rc < 0); ## upper bound binding lam['lower'][kl] = rc[kl] lam['upper'][ku] = -rc[ku] lam['eqlin'] = pi[:neq + 1] lam['ineqlin'] = pi[neq + range(niq + 1)] mu_l = zeros(nA) mu_u = zeros(nA) ## repackage lmbdas kl = find(lam['eqlin'] > 0) ## lower bound binding ku = find(lam['eqlin'] < 0) ## upper bound binding mu_l[ieq[kl]] = lam['eqlin'][kl] mu_l[igt] = -lam['ineqlin'][nlt + range(ngt + 1)] mu_l[ibx] = -lam['ineqlin'][nlt + ngt + nbx + range(nbx)] mu_u[ieq[ku]] = -lam['eqlin'][ku] mu_u[ilt] = -lam['ineqlin'][:nlt + 1] mu_u[ibx] = -lam['ineqlin'][nlt + ngt + range(nbx + 1)] lmbda = { 'mu_l': mu_l, 'mu_u': mu_u, 'lower': lam['lower'], 'upper': lam['upper'] } return x, f, eflag, output, lmbda
def qps_gurobi(H, c, A, l, u, xmin, xmax, x0, opt): """Quadratic Program Solver based on GUROBI. A wrapper function providing a PYPOWER standardized interface for using gurobipy to solve the following QP (quadratic programming) problem: min 1/2 x'*H*x + c'*x x subject to l <= A*x <= u (linear constraints) xmin <= x <= xmax (variable bounds) Inputs (all optional except H, c, A and l): H : matrix (possibly sparse) of quadratic cost coefficients c : vector of linear cost coefficients A, l, u : define the optional linear constraints. Default values for the elements of l and u are -Inf and Inf, respectively. xmin, xmax : optional lower and upper bounds on the C{x} variables, defaults are -Inf and Inf, respectively. x0 : optional starting value of optimization vector C{x} opt : optional options structure with the following fields, all of which are also optional (default values shown in parentheses) verbose (0) - controls level of progress output displayed 0 = no progress output 1 = some progress output 2 = verbose progress output grb_opt - options dict for Gurobi, value in verbose overrides these options problem : The inputs can alternatively be supplied in a single PROBLEM dict with fields corresponding to the input arguments described above: H, c, A, l, u, xmin, xmax, x0, opt Outputs: x : solution vector f : final objective function value exitflag : gurobipy exit flag 1 = converged 0 or negative values = negative of GUROBI_MEX exit flag (see gurobipy documentation for details) output : gurobipy output dict (see gurobipy documentation for details) lmbda : dict containing the Langrange and Kuhn-Tucker multipliers on the constraints, with fields: mu_l - lower (left-hand) limit on linear constraints mu_u - upper (right-hand) limit on linear constraints lower - lower bound on optimization variables upper - upper bound on optimization variables Note the calling syntax is almost identical to that of QUADPROG from MathWorks' Optimization Toolbox. The main difference is that the linear constraints are specified with A, l, u instead of A, b, Aeq, beq. Calling syntax options: x, f, exitflag, output, lmbda = ... qps_gurobi(H, c, A, l, u, xmin, xmax, x0, opt) r = qps_gurobi(H, c, A, l, u) r = qps_gurobi(H, c, A, l, u, xmin, xmax) r = qps_gurobi(H, c, A, l, u, xmin, xmax, x0) r = qps_gurobi(H, c, A, l, u, xmin, xmax, x0, opt) r = qps_gurobi(problem), where problem is a dict with fields: H, c, A, l, u, xmin, xmax, x0, opt all fields except 'c', 'A' and 'l' or 'u' are optional Example: (problem from from http://www.jmu.edu/docs/sasdoc/sashtml/iml/chap8/sect12.htm) H = [ 1003.1 4.3 6.3 5.9; 4.3 2.2 2.1 3.9; 6.3 2.1 3.5 4.8; 5.9 3.9 4.8 10 ] c = zeros((4, 1)) A = [ [1 1 1 1] [0.17 0.11 0.10 0.18] ] l = [1; 0.10] u = [1; Inf] xmin = zeros((4, 1)) x0 = [1; 0; 0; 1] opt = {'verbose': 2} x, f, s, out, lmbda = qps_gurobi(H, c, A, l, u, xmin, [], x0, opt) @see: L{gurobipy}. """ import gurobipy ##----- input argument handling ----- ## gather inputs if isinstance(H, dict): ## problem struct p = H if 'opt' in p: opt = p['opt'] if 'x0' in p: x0 = p['x0'] if 'xmax' in p: xmax = p['xmax'] if 'xmin' in p: xmin = p['xmin'] if 'u' in p: u = p['u'] if 'l' in p: l = p['l'] if 'A' in p: A = p['A'] if 'c' in p: c = p['c'] if 'H' in p: H = p['H'] else: ## individual args assert H is not None assert c is not None assert A is not None assert l is not None if opt is None: opt = {} # if x0 is None: # x0 = array([]) # if xmax is None: # xmax = array([]) # if xmin is None: # xmin = array([]) ## define nx, set default values for missing optional inputs if len(H) == 0 or not any(any(H)): if len(A) == 0 and len(xmin) == 0 and len(xmax) == 0: stderr.write('qps_gurobi: LP problem must include constraints or variable bounds\n') else: if len(A) > 0: nx = shape(A)[1] elif len(xmin) > 0: nx = len(xmin) else: # if len(xmax) > 0 nx = len(xmax) H = sparse((nx, nx)) else: nx = shape(H)[0] if len(c) == 0: c = zeros(nx) if len(A) > 0 and (len(l) == 0 or all(l == -Inf)) and \ (len(u) == 0 or all(u == Inf)): A = None ## no limits => no linear constraints nA = shape(A)[0] ## number of original linear constraints if nA: if len(u) == 0: ## By default, linear inequalities are ... u = Inf * ones(nA) ## ... unbounded above and ... if len(l) == 0: l = -Inf * ones(nA) ## ... unbounded below. if len(x0) == 0: x0 = zeros(nx) ## default options if 'verbose' in opt: verbose = opt['verbose'] else: verbose = 0 # if 'max_it' in opt: # max_it = opt['max_it'] # else: # max_it = 0 ## set up options struct for Gurobi if 'grb_opt' in opt: g_opt = gurobi_options(opt['grb_opt']) else: g_opt = gurobi_options() g_opt['Display'] = min(verbose, 3) if verbose: g_opt['DisplayInterval'] = 1 else: g_opt['DisplayInterval'] = Inf if not issparse(A): A = sparse(A) ## split up linear constraints ieq = find( abs(u-l) <= EPS ) ## equality igt = find( u >= 1e10 & l > -1e10 ) ## greater than, unbounded above ilt = find( l <= -1e10 & u < 1e10 ) ## less than, unbounded below ibx = find( (abs(u-l) > EPS) & (u < 1e10) & (l > -1e10) ) ## grab some dimensions nlt = len(ilt) ## number of upper bounded linear inequalities ngt = len(igt) ## number of lower bounded linear inequalities nbx = len(ibx) ## number of doubly bounded linear inequalities neq = len(ieq) ## number of equalities niq = nlt + ngt + 2 * nbx ## number of inequalities AA = [ A[ieq, :], A[ilt, :], -A[igt, :], A[ibx, :], -A[ibx, :] ] bb = [ u[ieq], u[ilt], -l[igt], u[ibx], -l[ibx] ] contypes = '=' * neq + '<' * niq ## call the solver if len(H) == 0 or not any(any(H)): lpqp = 'LP' else: lpqp = 'QP' rr, cc, vv = find(H) g_opt['QP']['qrow'] = int(rr.T - 1) g_opt['QP']['qcol'] = int(cc.T - 1) g_opt['QP']['qval'] = 0.5 * vv.T if verbose: methods = [ 'primal simplex', 'dual simplex', 'interior point', 'concurrent', 'deterministic concurrent' ] print(('Gurobi Version %s -- %s %s solver\n' '<unknown>' % (methods[g_opt['Method'] + 1], lpqp))) x, f, eflag, output, lmbda = \ gurobipy(c.T, 1, AA, bb, contypes, xmin, xmax, 'C', g_opt) pi = lmbda['Pi'] rc = lmbda['RC'] output['flag'] = eflag if eflag == 2: eflag = 1 ## optimal solution found else: eflag = -eflag ## failed somehow ## check for empty results (in case optimization failed) lam = {} if len(x) == 0: x = NaN(nx, 1); lam['lower'] = NaN(nx) lam['upper'] = NaN(nx) else: lam['lower'] = zeros(nx) lam['upper'] = zeros(nx) if len(f) == 0: f = NaN if len(pi) == 0: pi = NaN(len(bb)) kl = find(rc > 0); ## lower bound binding ku = find(rc < 0); ## upper bound binding lam['lower'][kl] = rc[kl] lam['upper'][ku] = -rc[ku] lam['eqlin'] = pi[:neq + 1] lam['ineqlin'] = pi[neq + list(range(niq + 1))] mu_l = zeros(nA) mu_u = zeros(nA) ## repackage lmbdas kl = find(lam['eqlin'] > 0) ## lower bound binding ku = find(lam['eqlin'] < 0) ## upper bound binding mu_l[ieq[kl]] = lam['eqlin'][kl] mu_l[igt] = -lam['ineqlin'][nlt + list(range(ngt + 1))] mu_l[ibx] = -lam['ineqlin'][nlt + ngt + nbx + list(range(nbx))] mu_u[ieq[ku]] = -lam['eqlin'][ku] mu_u[ilt] = -lam['ineqlin'][:nlt + 1] mu_u[ibx] = -lam['ineqlin'][nlt + ngt + list(range(nbx + 1))] lmbda = { 'mu_l': mu_l, 'mu_u': mu_u, 'lower': lam['lower'], 'upper': lam['upper'] } return x, f, eflag, output, lmbda