def network_observability(topology, meas_idx, V): from pypower.api import makeYbus from pypower.bustypes import bustypes from pypower.dSbus_dV import dSbus_dV from pypower.dSbr_dV import dSbr_dV # build admittances Ybus,Yfrom,Yto = makeYbus(topology["baseMVA"],topology["bus"],topology["branch"]) Ybus, Yfrom, Yto = Ybus.toarray(), Yfrom.toarray(), Yto.toarray() Nk = Ybus.shape[0] # get non-reference buses ref,pv,pq = bustypes(topology["bus"],topology["gen"]) nonref = np.r_[pv,pq] gbus = topology["gen"][:,GEN_BUS].astype(int) # calculate partial derivative dSbus_dVm, dSbus_dVa = dSbus_dV(Ybus,V) dSf_dVa, dSf_dVm, dSt_dVa, dSt_dVm, Sf, St = dSbr_dV(topology["branch"],Yfrom,Yto,V) # create Jacobian matrix ## submatrices related to line flow dPf_Va = np.real(dSf_dVa)[meas_idx["Pf"],:][:,nonref] dPf_Vm = np.real(dSf_dVm)[meas_idx["Pf"],:][:,nonref] dPt_Va = np.real(dSt_dVa)[meas_idx["Pt"],:][:,nonref] dPt_Vm = np.real(dSt_dVm)[meas_idx["Pt"],:][:,nonref] dQf_Va = np.imag(dSf_dVa)[meas_idx["Qf"],:][:,nonref] dQf_Vm = np.imag(dSf_dVm)[meas_idx["Qf"],:][:,nonref] dQt_Va = np.imag(dSt_dVa)[meas_idx["Qt"],:][:,nonref] dQt_Vm = np.imag(dSt_dVm)[meas_idx["Qt"],:][:,nonref] ## submatrix related to generator output dPg_Va = np.real(dSbus_dVa)[gbus,:][meas_idx["Pg"],:][:,nonref] dPg_Vm = np.real(dSbus_dVm)[gbus,:][meas_idx["Pg"],:][:,nonref] dQg_Va = np.imag(dSbus_dVa)[gbus,:][meas_idx["Qg"],:][:,nonref] dQg_Vm = np.imag(dSbus_dVm)[gbus,:][meas_idx["Qg"],:][:,nonref] ## submatrix related to bus injection dPk_Va = np.real(dSbus_dVa)[meas_idx["Pk"],:][:,nonref] dPk_Vm = np.real(dSbus_dVm)[meas_idx["Pk"],:][:,nonref] dQk_Va = np.imag(dSbus_dVa)[meas_idx["Qk"],:][:,nonref] dQk_Vm = np.imag(dSbus_dVm)[meas_idx["Qk"],:][:,nonref] ## submatrix related to voltage angle dVa_Va = np.eye(Nk)[meas_idx["Va"],:][:,nonref] dVa_Vm = np.zeros((Nk,Nk))[meas_idx["Va"],:][:,nonref] ## submatrix related to voltage magnitude dVm_Va = np.zeros((Nk,Nk))[meas_idx["Vm"],:][:,nonref] dVm_Vm = np.eye(Nk)[meas_idx["Vm"],:][:,nonref] H = np.r_[np.c_[dPf_Va, dPf_Vm],\ np.c_[dPt_Va, dPt_Vm],\ np.c_[dPg_Va, dPg_Vm],\ np.c_[dQf_Va, dQf_Vm],\ np.c_[dQt_Va, dQt_Vm],\ np.c_[dQg_Va, dQg_Vm],\ np.c_[dPk_Va, dPk_Vm],\ np.c_[dQk_Va, dQk_Vm],\ np.c_[dVa_Va, dVa_Vm],\ np.c_[dVm_Va, dVm_Vm]] return isobservable(H,pv,pq)
def voltage2power(V,topology): """Calculation of nodal and line power from complex nodal voltages Returns dictionary of corresponding powers and a dictionary with the partial derivatives :param V: numpy array of complex nodal voltages :param topology: dict in pypower casefile format :returns: dict of calculated power values, dict of jacobians if V is one-dimensional """ from pypower.idx_brch import F_BUS,T_BUS from pypower.idx_gen import GEN_BUS from pypower.makeYbus import makeYbus from pypower.idx_bus import PD,QD from pypower.dSbus_dV import dSbus_dV from pypower.dSbr_dV import dSbr_dV assert(str(V.dtype)[:7]=="complex") list_f = topology["branch"][:,F_BUS].tolist() list_t = topology["branch"][:,T_BUS].tolist() Ybus,Yfrom,Yto = makeYbus(topology["baseMVA"],topology["bus"],topology["branch"]) Ybus, Yfrom, Yto = Ybus.toarray(), Yfrom.toarray(), Yto.toarray() powers = {} if len(V.shape)==1: powers["Sf"] = V[list_f]*np.conj(np.dot(Yfrom,V)) powers["St"] = V[list_t]*np.conj(np.dot(Yto,V)) gbus = topology["gen"][:,GEN_BUS].astype(int) Sgbus= V[gbus]*np.conj(np.dot(Ybus[gbus,:],V)) powers["Sg"] = ( Sgbus*topology["baseMVA"] + topology["bus"][gbus,PD] + 1j*topology["bus"][gbus,QD] ) / topology["baseMVA"] powers["Sk"] = V*np.conj(np.dot(Ybus,V)) else: powers = dict([(name,[]) for name in ["Sf","St","Sg","Sk"]]) for k in range(V.shape[0]): powers["Sf"].append(V[k,list_f]*np.conj(np.dot(Yfrom,V[k,:]))) powers["St"].append(V[k,list_t]*np.conj(np.dot(Yto,V[k,:]))) gbus = topology["gen"][:,GEN_BUS].astype(int) Sgbus= V[k,gbus]*np.conj(np.dot(Ybus[gbus,:],V[k,:])) powers["Sg"].append(( Sgbus*topology["baseMVA"] + topology["bus"][gbus,PD] + 1j*topology["bus"][gbus,QD] ) / topology["baseMVA"]) powers["Sk"].append(V[k,:]*np.conj(np.dot(Ybus,V[k,:]))) for key in powers.keys(): powers[key] = np.asarray(powers[key]) # calculate partial derivative jacobians = dict([]) if len(V.shape)==1: jacobians["dSbus_dVm"], jacobians["dSbus_dVa"] = dSbus_dV(Ybus,V) jacobians["dSf_dVa"], jacobians["dSf_dVm"], jacobians["dSt_dVa"], \ jacobians["dSt_dVm"], jacobians["Sf"], jacobians["St"] = dSbr_dV(topology["branch"],Yfrom,Yto,V) return powers, jacobians
def opf_hessfcn(x, lmbda, om, Ybus, Yf, Yt, ppopt, il=None, cost_mult=1.0): """Evaluates Hessian of Lagrangian for AC OPF. Hessian evaluation function for AC optimal power flow, suitable for use with L{pips}. Examples:: Lxx = opf_hessfcn(x, lmbda, om, Ybus, Yf, Yt, ppopt) Lxx = opf_hessfcn(x, lmbda, om, Ybus, Yf, Yt, ppopt, il) Lxx = opf_hessfcn(x, lmbda, om, Ybus, Yf, Yt, ppopt, il, cost_mult) @param x: optimization vector @param lmbda: C{eqnonlin} - Lagrange multipliers on power balance equations. C{ineqnonlin} - Kuhn-Tucker multipliers on constrained branch flows. @param om: OPF model object @param Ybus: bus admittance matrix @param Yf: admittance matrix for "from" end of constrained branches @param Yt: admittance matrix for "to" end of constrained branches @param ppopt: PYPOWER options vector @param il: (optional) vector of branch indices corresponding to branches with flow limits (all others are assumed to be unconstrained). The default is C{range(nl)} (all branches). C{Yf} and C{Yt} contain only the rows corresponding to C{il}. @param cost_mult: (optional) Scale factor to be applied to the cost (default = 1). @return: Hessian of the Lagrangian. @see: L{opf_costfcn}, L{opf_consfcn} @author: Ray Zimmerman (PSERC Cornell) @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) """ ##----- initialize ----- ## 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, Cw, H, dd, rh, kk, mm = \ cp["N"], cp["Cw"], cp["H"], cp["dd"], cp["rh"], cp["kk"], cp["mm"] vv, _, _, _ = om.get_idx() ## unpack needed parameters nb = bus.shape[0] ## number of buses nl = branch.shape[0] ## number of branches ng = gen.shape[0] ## number of dispatchable injections nxyz = len(x) ## total number of control vars of all types ## set default constrained lines if il is None: il = arange(nl) ## all lines have limits by default nl2 = len(il) ## number of constrained lines ## grab Pg & Qg Pg = x[vv["i1"]["Pg"]:vv["iN"]["Pg"]] ## active generation in p.u. Qg = x[vv["i1"]["Qg"]:vv["iN"]["Qg"]] ## reactive generation in p.u. ## put Pg & Qg back in gen gen[:, PG] = Pg * baseMVA ## active generation in MW gen[:, QG] = Qg * baseMVA ## reactive generation in MVAr ## reconstruct V Va = x[vv["i1"]["Va"]:vv["iN"]["Va"]] Vm = x[vv["i1"]["Vm"]:vv["iN"]["Vm"]] V = Vm * exp(1j * Va) nxtra = nxyz - 2 * nb pcost = gencost[arange(ng), :] if gencost.shape[0] > ng: qcost = gencost[arange(ng, 2 * ng), :] else: qcost = array([]) ## ----- evaluate d2f ----- d2f_dPg2 = zeros(ng) #sparse((ng, 1)) ## w.r.t. p.u. Pg d2f_dQg2 = zeros(ng) #sparse((ng, 1)) ## w.r.t. p.u. Qg ipolp = find(pcost[:, MODEL] == POLYNOMIAL) d2f_dPg2[ipolp] = \ baseMVA**2 * polycost(pcost[ipolp, :], Pg[ipolp] * baseMVA, 2) if any(qcost): ## Qg is not free ipolq = find(qcost[:, MODEL] == POLYNOMIAL) d2f_dQg2[ipolq] = \ baseMVA**2 * polycost(qcost[ipolq, :], Qg[ipolq] * baseMVA, 2) i = r_[arange(vv["i1"]["Pg"], vv["iN"]["Pg"]), arange(vv["i1"]["Qg"], vv["iN"]["Qg"])] # d2f = sparse((vstack([d2f_dPg2, d2f_dQg2]).toarray().flatten(), # (i, i)), shape=(nxyz, nxyz)) d2f = sparse((r_[d2f_dPg2, d2f_dQg2], (i, i)), (nxyz, nxyz)) ## generalized cost if issparse(N) and N.nnz > 0: nw = N.shape[0] r = N * x - rh ## Nx - rhat iLT = find(r < -kk) ## below dead zone iEQ = find((r == 0) & (kk == 0)) ## dead zone doesn't exist iGT = find(r > kk) ## above dead zone iND = r_[iLT, iEQ, iGT] ## rows that are Not in the Dead region iL = find(dd == 1) ## rows using linear function iQ = find(dd == 2) ## rows using quadratic function LL = sparse((ones(len(iL)), (iL, iL)), (nw, nw)) QQ = sparse((ones(len(iQ)), (iQ, iQ)), (nw, nw)) kbar = sparse((r_[ones(len(iLT)), zeros(len(iEQ)), -ones(len(iGT))], (iND, iND)), (nw, nw)) * kk rr = r + kbar ## apply non-dead zone shift M = sparse((mm[iND], (iND, iND)), (nw, nw)) ## dead zone or scale diagrr = sparse((rr, (arange(nw), arange(nw))), (nw, nw)) ## linear rows multiplied by rr(i), quadratic rows by rr(i)^2 w = M * (LL + QQ * diagrr) * rr HwC = H * w + Cw AA = N.T * M * (LL + 2 * QQ * diagrr) d2f = d2f + AA * H * AA.T + 2 * N.T * M * QQ * \ sparse((HwC, (arange(nw), arange(nw))), (nw, nw)) * N d2f = d2f * cost_mult ##----- evaluate Hessian of power balance constraints ----- nlam = len(lmbda["eqnonlin"]) / 2 lamP = lmbda["eqnonlin"][:nlam] lamQ = lmbda["eqnonlin"][nlam:nlam + nlam] Gpaa, Gpav, Gpva, Gpvv = d2Sbus_dV2(Ybus, V, lamP) Gqaa, Gqav, Gqva, Gqvv = d2Sbus_dV2(Ybus, V, lamQ) d2G = vstack([ hstack([ vstack([hstack([Gpaa, Gpav]), hstack([Gpva, Gpvv])]).real + vstack([hstack([Gqaa, Gqav]), hstack([Gqva, Gqvv])]).imag, sparse((2 * nb, nxtra)) ]), hstack([sparse( (nxtra, 2 * nb)), sparse((nxtra, nxtra))]) ], "csr") ##----- evaluate Hessian of flow constraints ----- nmu = len(lmbda["ineqnonlin"]) / 2 muF = lmbda["ineqnonlin"][:nmu] muT = lmbda["ineqnonlin"][nmu:nmu + nmu] if ppopt['OPF_FLOW_LIM'] == 2: ## current dIf_dVa, dIf_dVm, dIt_dVa, dIt_dVm, If, It = dIbr_dV(branch, Yf, Yt, V) Hfaa, Hfav, Hfva, Hfvv = d2AIbr_dV2(dIf_dVa, dIf_dVm, If, Yf, V, muF) Htaa, Htav, Htva, Htvv = d2AIbr_dV2(dIt_dVa, dIt_dVm, It, Yt, V, muT) else: f = branch[il, F_BUS].astype(int) ## list of "from" buses t = branch[il, T_BUS].astype(int) ## list of "to" buses ## connection matrix for line & from buses Cf = sparse((ones(nl2), (arange(nl2), f)), (nl2, nb)) ## connection matrix for line & to buses Ct = sparse((ones(nl2), (arange(nl2), t)), (nl2, nb)) dSf_dVa, dSf_dVm, dSt_dVa, dSt_dVm, Sf, St = \ dSbr_dV(branch[il,:], Yf, Yt, V) if ppopt['OPF_FLOW_LIM'] == 1: ## real power Hfaa, Hfav, Hfva, Hfvv = d2ASbr_dV2(dSf_dVa.real, dSf_dVm.real, Sf.real, Cf, Yf, V, muF) Htaa, Htav, Htva, Htvv = d2ASbr_dV2(dSt_dVa.real, dSt_dVm.real, St.real, Ct, Yt, V, muT) else: ## apparent power Hfaa, Hfav, Hfva, Hfvv = \ d2ASbr_dV2(dSf_dVa, dSf_dVm, Sf, Cf, Yf, V, muF) Htaa, Htav, Htva, Htvv = \ d2ASbr_dV2(dSt_dVa, dSt_dVm, St, Ct, Yt, V, muT) d2H = vstack([ hstack([ vstack([hstack([Hfaa, Hfav]), hstack([Hfva, Hfvv])]) + vstack([hstack([Htaa, Htav]), hstack([Htva, Htvv])]), sparse((2 * nb, nxtra)) ]), hstack([sparse( (nxtra, 2 * nb)), sparse((nxtra, nxtra))]) ], "csr") ##----- do numerical check using (central) finite differences ----- if 0: nx = len(x) step = 1e-5 num_d2f = sparse((nx, nx)) num_d2G = sparse((nx, nx)) num_d2H = sparse((nx, nx)) for i in range(nx): xp = x xm = x xp[i] = x[i] + step / 2 xm[i] = x[i] - step / 2 # evaluate cost & gradients _, dfp = opf_costfcn(xp, om) _, dfm = opf_costfcn(xm, om) # evaluate constraints & gradients _, _, dHp, dGp = opf_consfcn(xp, om, Ybus, Yf, Yt, ppopt, il) _, _, dHm, dGm = opf_consfcn(xm, om, Ybus, Yf, Yt, ppopt, il) num_d2f[:, i] = cost_mult * (dfp - dfm) / step num_d2G[:, i] = (dGp - dGm) * lmbda["eqnonlin"] / step num_d2H[:, i] = (dHp - dHm) * lmbda["ineqnonlin"] / step d2f_err = max(max(abs(d2f - num_d2f))) d2G_err = max(max(abs(d2G - num_d2G))) d2H_err = max(max(abs(d2H - num_d2H))) if d2f_err > 1e-6: print('Max difference in d2f: %g' % d2f_err) if d2G_err > 1e-5: print('Max difference in d2G: %g' % d2G_err) if d2H_err > 1e-6: print('Max difference in d2H: %g' % d2H_err) return d2f + d2G + d2H
def t_hessian(quiet=False): """Numerical tests of 2nd derivative code. @author: Ray Zimmerman (PSERC Cornell) """ t_begin(44, quiet) ## run powerflow to get solved case ppopt = ppoption(VERBOSE=0, OUT_ALL=0) results, _ = runpf(case30(), ppopt) baseMVA, bus, gen, branch = \ results['baseMVA'], results['bus'], results['gen'], results['branch'] ## switch to internal bus numbering and build admittance matrices _, bus, gen, branch = ext2int1(bus, gen, branch) Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) Vm = bus[:, VM] Va = bus[:, VA] * (pi / 180) V = Vm * exp(1j * Va) f = branch[:, F_BUS] ## list of "from" buses t = branch[:, T_BUS] ## list of "to" buses nl = len(f) nb = len(V) Cf = sparse((ones(nl), (range(nl), f)), (nl, nb)) ## connection matrix for line & from buses Ct = sparse((ones(nl), (range(nl), t)), (nl, nb)) ## connection matrix for line & to buses pert = 1e-8 ##----- check d2Sbus_dV2 code ----- t = ' - d2Sbus_dV2 (complex power injections)' lam = 10 * random.rand(nb) num_Haa = zeros((nb, nb), complex) num_Hav = zeros((nb, nb), complex) num_Hva = zeros((nb, nb), complex) num_Hvv = zeros((nb, nb), complex) dSbus_dVm, dSbus_dVa = dSbus_dV(Ybus, V) Haa, Hav, Hva, Hvv = d2Sbus_dV2(Ybus, V, lam) for i in range(nb): Vap = V.copy() Vap[i] = Vm[i] * exp(1j * (Va[i] + pert)) dSbus_dVm_ap, dSbus_dVa_ap = dSbus_dV(Ybus, Vap) num_Haa[:, i] = (dSbus_dVa_ap - dSbus_dVa).T * lam / pert num_Hva[:, i] = (dSbus_dVm_ap - dSbus_dVm).T * lam / pert Vmp = V.copy() Vmp[i] = (Vm[i] + pert) * exp(1j * Va[i]) dSbus_dVm_mp, dSbus_dVa_mp = dSbus_dV(Ybus, Vmp) num_Hav[:, i] = (dSbus_dVa_mp - dSbus_dVa).T * lam / pert num_Hvv[:, i] = (dSbus_dVm_mp - dSbus_dVm).T * lam / pert t_is(Haa.todense(), num_Haa, 4, ['Haa', t]) t_is(Hav.todense(), num_Hav, 4, ['Hav', t]) t_is(Hva.todense(), num_Hva, 4, ['Hva', t]) t_is(Hvv.todense(), num_Hvv, 4, ['Hvv', t]) ##----- check d2Sbr_dV2 code ----- t = ' - d2Sbr_dV2 (complex power flows)' lam = 10 * random.rand(nl) # lam = [1 zeros(nl-1, 1)] num_Gfaa = zeros((nb, nb), complex) num_Gfav = zeros((nb, nb), complex) num_Gfva = zeros((nb, nb), complex) num_Gfvv = zeros((nb, nb), complex) num_Gtaa = zeros((nb, nb), complex) num_Gtav = zeros((nb, nb), complex) num_Gtva = zeros((nb, nb), complex) num_Gtvv = zeros((nb, nb), complex) dSf_dVa, dSf_dVm, dSt_dVa, dSt_dVm, _, _ = dSbr_dV(branch, Yf, Yt, V) Gfaa, Gfav, Gfva, Gfvv = d2Sbr_dV2(Cf, Yf, V, lam) Gtaa, Gtav, Gtva, Gtvv = d2Sbr_dV2(Ct, Yt, V, lam) for i in range(nb): Vap = V.copy() Vap[i] = Vm[i] * exp(1j * (Va[i] + pert)) dSf_dVa_ap, dSf_dVm_ap, dSt_dVa_ap, dSt_dVm_ap, Sf_ap, St_ap = \ dSbr_dV(branch, Yf, Yt, Vap) num_Gfaa[:, i] = (dSf_dVa_ap - dSf_dVa).T * lam / pert num_Gfva[:, i] = (dSf_dVm_ap - dSf_dVm).T * lam / pert num_Gtaa[:, i] = (dSt_dVa_ap - dSt_dVa).T * lam / pert num_Gtva[:, i] = (dSt_dVm_ap - dSt_dVm).T * lam / pert Vmp = V.copy() Vmp[i] = (Vm[i] + pert) * exp(1j * Va[i]) dSf_dVa_mp, dSf_dVm_mp, dSt_dVa_mp, dSt_dVm_mp, Sf_mp, St_mp = \ dSbr_dV(branch, Yf, Yt, Vmp) num_Gfav[:, i] = (dSf_dVa_mp - dSf_dVa).T * lam / pert num_Gfvv[:, i] = (dSf_dVm_mp - dSf_dVm).T * lam / pert num_Gtav[:, i] = (dSt_dVa_mp - dSt_dVa).T * lam / pert num_Gtvv[:, i] = (dSt_dVm_mp - dSt_dVm).T * lam / pert t_is(Gfaa.todense(), num_Gfaa, 4, ['Gfaa', t]) t_is(Gfav.todense(), num_Gfav, 4, ['Gfav', t]) t_is(Gfva.todense(), num_Gfva, 4, ['Gfva', t]) t_is(Gfvv.todense(), num_Gfvv, 4, ['Gfvv', t]) t_is(Gtaa.todense(), num_Gtaa, 4, ['Gtaa', t]) t_is(Gtav.todense(), num_Gtav, 4, ['Gtav', t]) t_is(Gtva.todense(), num_Gtva, 4, ['Gtva', t]) t_is(Gtvv.todense(), num_Gtvv, 4, ['Gtvv', t]) ##----- check d2Ibr_dV2 code ----- t = ' - d2Ibr_dV2 (complex currents)' lam = 10 * random.rand(nl) # lam = [1, zeros(nl-1)] num_Gfaa = zeros((nb, nb), complex) num_Gfav = zeros((nb, nb), complex) num_Gfva = zeros((nb, nb), complex) num_Gfvv = zeros((nb, nb), complex) num_Gtaa = zeros((nb, nb), complex) num_Gtav = zeros((nb, nb), complex) num_Gtva = zeros((nb, nb), complex) num_Gtvv = zeros((nb, nb), complex) dIf_dVa, dIf_dVm, dIt_dVa, dIt_dVm, _, _ = dIbr_dV(branch, Yf, Yt, V) Gfaa, Gfav, Gfva, Gfvv = d2Ibr_dV2(Yf, V, lam) Gtaa, Gtav, Gtva, Gtvv = d2Ibr_dV2(Yt, V, lam) for i in range(nb): Vap = V.copy() Vap[i] = Vm[i] * exp(1j * (Va[i] + pert)) dIf_dVa_ap, dIf_dVm_ap, dIt_dVa_ap, dIt_dVm_ap, If_ap, It_ap = \ dIbr_dV(branch, Yf, Yt, Vap) num_Gfaa[:, i] = (dIf_dVa_ap - dIf_dVa).T * lam / pert num_Gfva[:, i] = (dIf_dVm_ap - dIf_dVm).T * lam / pert num_Gtaa[:, i] = (dIt_dVa_ap - dIt_dVa).T * lam / pert num_Gtva[:, i] = (dIt_dVm_ap - dIt_dVm).T * lam / pert Vmp = V.copy() Vmp[i] = (Vm[i] + pert) * exp(1j * Va[i]) dIf_dVa_mp, dIf_dVm_mp, dIt_dVa_mp, dIt_dVm_mp, If_mp, It_mp = \ dIbr_dV(branch, Yf, Yt, Vmp) num_Gfav[:, i] = (dIf_dVa_mp - dIf_dVa).T * lam / pert num_Gfvv[:, i] = (dIf_dVm_mp - dIf_dVm).T * lam / pert num_Gtav[:, i] = (dIt_dVa_mp - dIt_dVa).T * lam / pert num_Gtvv[:, i] = (dIt_dVm_mp - dIt_dVm).T * lam / pert t_is(Gfaa.todense(), num_Gfaa, 4, ['Gfaa', t]) t_is(Gfav.todense(), num_Gfav, 4, ['Gfav', t]) t_is(Gfva.todense(), num_Gfva, 4, ['Gfva', t]) t_is(Gfvv.todense(), num_Gfvv, 4, ['Gfvv', t]) t_is(Gtaa.todense(), num_Gtaa, 4, ['Gtaa', t]) t_is(Gtav.todense(), num_Gtav, 4, ['Gtav', t]) t_is(Gtva.todense(), num_Gtva, 4, ['Gtva', t]) t_is(Gtvv.todense(), num_Gtvv, 4, ['Gtvv', t]) ##----- check d2ASbr_dV2 code ----- t = ' - d2ASbr_dV2 (squared apparent power flows)' lam = 10 * random.rand(nl) # lam = [1 zeros(nl-1, 1)] num_Gfaa = zeros((nb, nb), complex) num_Gfav = zeros((nb, nb), complex) num_Gfva = zeros((nb, nb), complex) num_Gfvv = zeros((nb, nb), complex) num_Gtaa = zeros((nb, nb), complex) num_Gtav = zeros((nb, nb), complex) num_Gtva = zeros((nb, nb), complex) num_Gtvv = zeros((nb, nb), complex) dSf_dVa, dSf_dVm, dSt_dVa, dSt_dVm, Sf, St = dSbr_dV(branch, Yf, Yt, V) dAf_dVa, dAf_dVm, dAt_dVa, dAt_dVm = \ dAbr_dV(dSf_dVa, dSf_dVm, dSt_dVa, dSt_dVm, Sf, St) Gfaa, Gfav, Gfva, Gfvv = d2ASbr_dV2(dSf_dVa, dSf_dVm, Sf, Cf, Yf, V, lam) Gtaa, Gtav, Gtva, Gtvv = d2ASbr_dV2(dSt_dVa, dSt_dVm, St, Ct, Yt, V, lam) for i in range(nb): Vap = V.copy() Vap[i] = Vm[i] * exp(1j * (Va[i] + pert)) dSf_dVa_ap, dSf_dVm_ap, dSt_dVa_ap, dSt_dVm_ap, Sf_ap, St_ap = \ dSbr_dV(branch, Yf, Yt, Vap) dAf_dVa_ap, dAf_dVm_ap, dAt_dVa_ap, dAt_dVm_ap = \ dAbr_dV(dSf_dVa_ap, dSf_dVm_ap, dSt_dVa_ap, dSt_dVm_ap, Sf_ap, St_ap) num_Gfaa[:, i] = (dAf_dVa_ap - dAf_dVa).T * lam / pert num_Gfva[:, i] = (dAf_dVm_ap - dAf_dVm).T * lam / pert num_Gtaa[:, i] = (dAt_dVa_ap - dAt_dVa).T * lam / pert num_Gtva[:, i] = (dAt_dVm_ap - dAt_dVm).T * lam / pert Vmp = V.copy() Vmp[i] = (Vm[i] + pert) * exp(1j * Va[i]) dSf_dVa_mp, dSf_dVm_mp, dSt_dVa_mp, dSt_dVm_mp, Sf_mp, St_mp = \ dSbr_dV(branch, Yf, Yt, Vmp) dAf_dVa_mp, dAf_dVm_mp, dAt_dVa_mp, dAt_dVm_mp = \ dAbr_dV(dSf_dVa_mp, dSf_dVm_mp, dSt_dVa_mp, dSt_dVm_mp, Sf_mp, St_mp) num_Gfav[:, i] = (dAf_dVa_mp - dAf_dVa).T * lam / pert num_Gfvv[:, i] = (dAf_dVm_mp - dAf_dVm).T * lam / pert num_Gtav[:, i] = (dAt_dVa_mp - dAt_dVa).T * lam / pert num_Gtvv[:, i] = (dAt_dVm_mp - dAt_dVm).T * lam / pert t_is(Gfaa.todense(), num_Gfaa, 2, ['Gfaa', t]) t_is(Gfav.todense(), num_Gfav, 2, ['Gfav', t]) t_is(Gfva.todense(), num_Gfva, 2, ['Gfva', t]) t_is(Gfvv.todense(), num_Gfvv, 2, ['Gfvv', t]) t_is(Gtaa.todense(), num_Gtaa, 2, ['Gtaa', t]) t_is(Gtav.todense(), num_Gtav, 2, ['Gtav', t]) t_is(Gtva.todense(), num_Gtva, 2, ['Gtva', t]) t_is(Gtvv.todense(), num_Gtvv, 2, ['Gtvv', t]) ##----- check d2ASbr_dV2 code ----- t = ' - d2ASbr_dV2 (squared real power flows)' lam = 10 * random.rand(nl) # lam = [1 zeros(nl-1, 1)] num_Gfaa = zeros((nb, nb), complex) num_Gfav = zeros((nb, nb), complex) num_Gfva = zeros((nb, nb), complex) num_Gfvv = zeros((nb, nb), complex) num_Gtaa = zeros((nb, nb), complex) num_Gtav = zeros((nb, nb), complex) num_Gtva = zeros((nb, nb), complex) num_Gtvv = zeros((nb, nb), complex) dSf_dVa, dSf_dVm, dSt_dVa, dSt_dVm, Sf, St = dSbr_dV(branch, Yf, Yt, V) dAf_dVa, dAf_dVm, dAt_dVa, dAt_dVm = \ dAbr_dV(dSf_dVa.real, dSf_dVm.real, dSt_dVa.real, dSt_dVm.real, Sf.real, St.real) Gfaa, Gfav, Gfva, Gfvv = d2ASbr_dV2(dSf_dVa.real, dSf_dVm.real, Sf.real, Cf, Yf, V, lam) Gtaa, Gtav, Gtva, Gtvv = d2ASbr_dV2(dSt_dVa.real, dSt_dVm.real, St.real, Ct, Yt, V, lam) for i in range(nb): Vap = V.copy() Vap[i] = Vm[i] * exp(1j * (Va[i] + pert)) dSf_dVa_ap, dSf_dVm_ap, dSt_dVa_ap, dSt_dVm_ap, Sf_ap, St_ap = \ dSbr_dV(branch, Yf, Yt, Vap) dAf_dVa_ap, dAf_dVm_ap, dAt_dVa_ap, dAt_dVm_ap = \ dAbr_dV(dSf_dVa_ap.real, dSf_dVm_ap.real, dSt_dVa_ap.real, dSt_dVm_ap.real, Sf_ap.real, St_ap.real) num_Gfaa[:, i] = (dAf_dVa_ap - dAf_dVa).T * lam / pert num_Gfva[:, i] = (dAf_dVm_ap - dAf_dVm).T * lam / pert num_Gtaa[:, i] = (dAt_dVa_ap - dAt_dVa).T * lam / pert num_Gtva[:, i] = (dAt_dVm_ap - dAt_dVm).T * lam / pert Vmp = V.copy() Vmp[i] = (Vm[i] + pert) * exp(1j * Va[i]) dSf_dVa_mp, dSf_dVm_mp, dSt_dVa_mp, dSt_dVm_mp, Sf_mp, St_mp = \ dSbr_dV(branch, Yf, Yt, Vmp) dAf_dVa_mp, dAf_dVm_mp, dAt_dVa_mp, dAt_dVm_mp = \ dAbr_dV(dSf_dVa_mp.real, dSf_dVm_mp.real, dSt_dVa_mp.real, dSt_dVm_mp.real, Sf_mp.real, St_mp.real) num_Gfav[:, i] = (dAf_dVa_mp - dAf_dVa).T * lam / pert num_Gfvv[:, i] = (dAf_dVm_mp - dAf_dVm).T * lam / pert num_Gtav[:, i] = (dAt_dVa_mp - dAt_dVa).T * lam / pert num_Gtvv[:, i] = (dAt_dVm_mp - dAt_dVm).T * lam / pert t_is(Gfaa.todense(), num_Gfaa, 2, ['Gfaa', t]) t_is(Gfav.todense(), num_Gfav, 2, ['Gfav', t]) t_is(Gfva.todense(), num_Gfva, 2, ['Gfva', t]) t_is(Gfvv.todense(), num_Gfvv, 2, ['Gfvv', t]) t_is(Gtaa.todense(), num_Gtaa, 2, ['Gtaa', t]) t_is(Gtav.todense(), num_Gtav, 2, ['Gtav', t]) t_is(Gtva.todense(), num_Gtva, 2, ['Gtva', t]) t_is(Gtvv.todense(), num_Gtvv, 2, ['Gtvv', t]) ##----- check d2AIbr_dV2 code ----- t = ' - d2AIbr_dV2 (squared current magnitudes)' lam = 10 * random.rand(nl) # lam = [1 zeros(nl-1, 1)] num_Gfaa = zeros((nb, nb), complex) num_Gfav = zeros((nb, nb), complex) num_Gfva = zeros((nb, nb), complex) num_Gfvv = zeros((nb, nb), complex) num_Gtaa = zeros((nb, nb), complex) num_Gtav = zeros((nb, nb), complex) num_Gtva = zeros((nb, nb), complex) num_Gtvv = zeros((nb, nb), complex) dIf_dVa, dIf_dVm, dIt_dVa, dIt_dVm, If, It = dIbr_dV(branch, Yf, Yt, V) dAf_dVa, dAf_dVm, dAt_dVa, dAt_dVm = \ dAbr_dV(dIf_dVa, dIf_dVm, dIt_dVa, dIt_dVm, If, It) Gfaa, Gfav, Gfva, Gfvv = d2AIbr_dV2(dIf_dVa, dIf_dVm, If, Yf, V, lam) Gtaa, Gtav, Gtva, Gtvv = d2AIbr_dV2(dIt_dVa, dIt_dVm, It, Yt, V, lam) for i in range(nb): Vap = V.copy() Vap[i] = Vm[i] * exp(1j * (Va[i] + pert)) dIf_dVa_ap, dIf_dVm_ap, dIt_dVa_ap, dIt_dVm_ap, If_ap, It_ap = \ dIbr_dV(branch, Yf, Yt, Vap) dAf_dVa_ap, dAf_dVm_ap, dAt_dVa_ap, dAt_dVm_ap = \ dAbr_dV(dIf_dVa_ap, dIf_dVm_ap, dIt_dVa_ap, dIt_dVm_ap, If_ap, It_ap) num_Gfaa[:, i] = (dAf_dVa_ap - dAf_dVa).T * lam / pert num_Gfva[:, i] = (dAf_dVm_ap - dAf_dVm).T * lam / pert num_Gtaa[:, i] = (dAt_dVa_ap - dAt_dVa).T * lam / pert num_Gtva[:, i] = (dAt_dVm_ap - dAt_dVm).T * lam / pert Vmp = V.copy() Vmp[i] = (Vm[i] + pert) * exp(1j * Va[i]) dIf_dVa_mp, dIf_dVm_mp, dIt_dVa_mp, dIt_dVm_mp, If_mp, It_mp = \ dIbr_dV(branch, Yf, Yt, Vmp) dAf_dVa_mp, dAf_dVm_mp, dAt_dVa_mp, dAt_dVm_mp = \ dAbr_dV(dIf_dVa_mp, dIf_dVm_mp, dIt_dVa_mp, dIt_dVm_mp, If_mp, It_mp) num_Gfav[:, i] = (dAf_dVa_mp - dAf_dVa).T * lam / pert num_Gfvv[:, i] = (dAf_dVm_mp - dAf_dVm).T * lam / pert num_Gtav[:, i] = (dAt_dVa_mp - dAt_dVa).T * lam / pert num_Gtvv[:, i] = (dAt_dVm_mp - dAt_dVm).T * lam / pert t_is(Gfaa.todense(), num_Gfaa, 3, ['Gfaa', t]) t_is(Gfav.todense(), num_Gfav, 3, ['Gfav', t]) t_is(Gfva.todense(), num_Gfva, 3, ['Gfva', t]) t_is(Gfvv.todense(), num_Gfvv, 2, ['Gfvv', t]) t_is(Gtaa.todense(), num_Gtaa, 3, ['Gtaa', t]) t_is(Gtav.todense(), num_Gtav, 3, ['Gtav', t]) t_is(Gtva.todense(), num_Gtva, 3, ['Gtva', t]) t_is(Gtvv.todense(), num_Gtvv, 2, ['Gtvv', t]) t_end()
def opf_consfcn(x, om, Ybus, Yf, Yt, ppopt, il=None, *args): """Evaluates nonlinear constraints and their Jacobian for OPF. Constraint evaluation function for AC optimal power flow, suitable for use with L{pips}. Computes constraint vectors and their gradients. @param x: optimization vector @param om: OPF model object @param Ybus: bus admittance matrix @param Yf: admittance matrix for "from" end of constrained branches @param Yt: admittance matrix for "to" end of constrained branches @param ppopt: PYPOWER options vector @param il: (optional) vector of branch indices corresponding to branches with flow limits (all others are assumed to be unconstrained). The default is C{range(nl)} (all branches). C{Yf} and C{Yt} contain only the rows corresponding to C{il}. @return: C{h} - vector of inequality constraint values (flow limits) limit^2 - flow^2, where the flow can be apparent power real power or current, depending on value of C{OPF_FLOW_LIM} in C{ppopt} (only for constrained lines). C{g} - vector of equality constraint values (power balances). C{dh} - (optional) inequality constraint gradients, column j is gradient of h(j). C{dg} - (optional) equality constraint gradients. @see: L{opf_costfcn}, L{opf_hessfcn} @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) @author: Ray Zimmerman (PSERC Cornell) @author: Richard Lincoln """ ##----- initialize ----- ## unpack data ppc = om.get_ppc() baseMVA, bus, gen, branch = \ ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"] vv, _, _, _ = om.get_idx() ## problem dimensions nb = bus.shape[0] ## number of buses nl = branch.shape[0] ## number of branches ng = gen.shape[0] ## number of dispatchable injections nxyz = len(x) ## total number of control vars of all types ## set default constrained lines if il is None: il = arange(nl) ## all lines have limits by default nl2 = len(il) ## number of constrained lines ## grab Pg & Qg Pg = x[vv["i1"]["Pg"]:vv["iN"]["Pg"]] ## active generation in p.u. Qg = x[vv["i1"]["Qg"]:vv["iN"]["Qg"]] ## reactive generation in p.u. ## put Pg & Qg back in gen gen[:, PG] = Pg * baseMVA ## active generation in MW gen[:, QG] = Qg * baseMVA ## reactive generation in MVAr ## rebuild Sbus Sbus = makeSbus(baseMVA, bus, gen) ## net injected power in p.u. ## ----- evaluate constraints ----- ## reconstruct V Va = x[vv["i1"]["Va"]:vv["iN"]["Va"]] Vm = x[vv["i1"]["Vm"]:vv["iN"]["Vm"]] V = Vm * exp(1j * Va) ## evaluate power flow equations mis = V * conj(Ybus * V) - Sbus ##----- evaluate constraint function values ----- ## first, the equality constraints (power flow) g = r_[ mis.real, ## active power mismatch for all buses mis.imag ] ## reactive power mismatch for all buses ## then, the inequality constraints (branch flow limits) if nl2 > 0: flow_max = (branch[il, RATE_A] / baseMVA)**2 flow_max[flow_max == 0] = Inf if ppopt['OPF_FLOW_LIM'] == 2: ## current magnitude limit, |I| If = Yf * V It = Yt * V h = r_[ If * conj(If) - flow_max, ## branch I limits (from bus) It * conj(It) - flow_max ].real ## branch I limits (to bus) else: ## compute branch power flows ## complex power injected at "from" bus (p.u.) Sf = V[ branch[il, F_BUS].astype(int) ] * conj(Yf * V) ## complex power injected at "to" bus (p.u.) St = V[ branch[il, T_BUS].astype(int) ] * conj(Yt * V) if ppopt['OPF_FLOW_LIM'] == 1: ## active power limit, P (Pan Wei) h = r_[ Sf.real**2 - flow_max, ## branch P limits (from bus) St.real**2 - flow_max ] ## branch P limits (to bus) else: ## apparent power limit, |S| h = r_[ Sf * conj(Sf) - flow_max, ## branch S limits (from bus) St * conj(St) - flow_max ].real ## branch S limits (to bus) else: h = zeros((0,1)) ##----- evaluate partials of constraints ----- ## index ranges iVa = arange(vv["i1"]["Va"], vv["iN"]["Va"]) iVm = arange(vv["i1"]["Vm"], vv["iN"]["Vm"]) iPg = arange(vv["i1"]["Pg"], vv["iN"]["Pg"]) iQg = arange(vv["i1"]["Qg"], vv["iN"]["Qg"]) iVaVmPgQg = r_[iVa, iVm, iPg, iQg].T ## compute partials of injected bus powers dSbus_dVm, dSbus_dVa = dSbus_dV(Ybus, V) ## w.r.t. V ## Pbus w.r.t. Pg, Qbus w.r.t. Qg neg_Cg = sparse((-ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng)) ## construct Jacobian of equality constraints (power flow) and transpose it dg = lil_matrix((2 * nb, nxyz)) blank = sparse((nb, ng)) dg[:, iVaVmPgQg] = vstack([ ## P mismatch w.r.t Va, Vm, Pg, Qg hstack([dSbus_dVa.real, dSbus_dVm.real, neg_Cg, blank]), ## Q mismatch w.r.t Va, Vm, Pg, Qg hstack([dSbus_dVa.imag, dSbus_dVm.imag, blank, neg_Cg]) ], "csr") dg = dg.T if nl2 > 0: ## compute partials of Flows w.r.t. V if ppopt['OPF_FLOW_LIM'] == 2: ## current dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft = \ dIbr_dV(branch[il, :], Yf, Yt, V) else: ## power dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft = \ dSbr_dV(branch[il, :], Yf, Yt, V) if ppopt['OPF_FLOW_LIM'] == 1: ## real part of flow (active power) dFf_dVa = dFf_dVa.real dFf_dVm = dFf_dVm.real dFt_dVa = dFt_dVa.real dFt_dVm = dFt_dVm.real Ff = Ff.real Ft = Ft.real ## squared magnitude of flow (of complex power or current, or real power) df_dVa, df_dVm, dt_dVa, dt_dVm = \ dAbr_dV(dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft) ## construct Jacobian of inequality constraints (branch limits) ## and transpose it. dh = lil_matrix((2 * nl2, nxyz)) dh[:, r_[iVa, iVm].T] = vstack([ hstack([df_dVa, df_dVm]), ## "from" flow limit hstack([dt_dVa, dt_dVm]) ## "to" flow limit ], "csr") dh = dh.T else: dh = None return h, g, dh, dg
def opf_hessfcn(x, lmbda, om, Ybus, Yf, Yt, ppopt, il=None, cost_mult=1.0): """Evaluates Hessian of Lagrangian for AC OPF. Hessian evaluation function for AC optimal power flow, suitable for use with L{pips}. Examples:: Lxx = opf_hessfcn(x, lmbda, om, Ybus, Yf, Yt, ppopt) Lxx = opf_hessfcn(x, lmbda, om, Ybus, Yf, Yt, ppopt, il) Lxx = opf_hessfcn(x, lmbda, om, Ybus, Yf, Yt, ppopt, il, cost_mult) @param x: optimization vector @param lmbda: C{eqnonlin} - Lagrange multipliers on power balance equations. C{ineqnonlin} - Kuhn-Tucker multipliers on constrained branch flows. @param om: OPF model object @param Ybus: bus admittance matrix @param Yf: admittance matrix for "from" end of constrained branches @param Yt: admittance matrix for "to" end of constrained branches @param ppopt: PYPOWER options vector @param il: (optional) vector of branch indices corresponding to branches with flow limits (all others are assumed to be unconstrained). The default is C{range(nl)} (all branches). C{Yf} and C{Yt} contain only the rows corresponding to C{il}. @param cost_mult: (optional) Scale factor to be applied to the cost (default = 1). @return: Hessian of the Lagrangian. @see: L{opf_costfcn}, L{opf_consfcn} @author: Ray Zimmerman (PSERC Cornell) @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) """ ##----- initialize ----- ## 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, Cw, H, dd, rh, kk, mm = \ cp["N"], cp["Cw"], cp["H"], cp["dd"], cp["rh"], cp["kk"], cp["mm"] vv, _, _, _ = om.get_idx() ## unpack needed parameters nb = bus.shape[0] ## number of buses nl = branch.shape[0] ## number of branches ng = gen.shape[0] ## number of dispatchable injections nxyz = len(x) ## total number of control vars of all types ## set default constrained lines if il is None: il = arange(nl) ## all lines have limits by default nl2 = len(il) ## number of constrained lines ## grab Pg & Qg Pg = x[vv["i1"]["Pg"]:vv["iN"]["Pg"]] ## active generation in p.u. Qg = x[vv["i1"]["Qg"]:vv["iN"]["Qg"]] ## reactive generation in p.u. ## put Pg & Qg back in gen gen[:, PG] = Pg * baseMVA ## active generation in MW gen[:, QG] = Qg * baseMVA ## reactive generation in MVAr ## reconstruct V Va = x[vv["i1"]["Va"]:vv["iN"]["Va"]] Vm = x[vv["i1"]["Vm"]:vv["iN"]["Vm"]] V = Vm * exp(1j * Va) nxtra = nxyz - 2 * nb pcost = gencost[arange(ng), :] if gencost.shape[0] > ng: qcost = gencost[arange(ng, 2 * ng), :] else: qcost = array([]) ## ----- evaluate d2f ----- d2f_dPg2 = zeros(ng)#sparse((ng, 1)) ## w.r.t. p.u. Pg d2f_dQg2 = zeros(ng)#sparse((ng, 1)) ## w.r.t. p.u. Qg ipolp = find(pcost[:, MODEL] == POLYNOMIAL) d2f_dPg2[ipolp] = \ baseMVA**2 * polycost(pcost[ipolp, :], Pg[ipolp] * baseMVA, 2) if any(qcost): ## Qg is not free ipolq = find(qcost[:, MODEL] == POLYNOMIAL) d2f_dQg2[ipolq] = \ baseMVA**2 * polycost(qcost[ipolq, :], Qg[ipolq] * baseMVA, 2) i = r_[arange(vv["i1"]["Pg"], vv["iN"]["Pg"]), arange(vv["i1"]["Qg"], vv["iN"]["Qg"])] # d2f = sparse((vstack([d2f_dPg2, d2f_dQg2]).toarray().flatten(), # (i, i)), shape=(nxyz, nxyz)) d2f = sparse((r_[d2f_dPg2, d2f_dQg2], (i, i)), (nxyz, nxyz)) ## generalized cost if issparse(N) and N.nnz > 0: nw = N.shape[0] r = N * x - rh ## Nx - rhat iLT = find(r < -kk) ## below dead zone iEQ = find((r == 0) & (kk == 0)) ## dead zone doesn't exist iGT = find(r > kk) ## above dead zone iND = r_[iLT, iEQ, iGT] ## rows that are Not in the Dead region iL = find(dd == 1) ## rows using linear function iQ = find(dd == 2) ## rows using quadratic function LL = sparse((ones(len(iL)), (iL, iL)), (nw, nw)) QQ = sparse((ones(len(iQ)), (iQ, iQ)), (nw, nw)) kbar = sparse((r_[ones(len(iLT)), zeros(len(iEQ)), -ones(len(iGT))], (iND, iND)), (nw, nw)) * kk rr = r + kbar ## apply non-dead zone shift M = sparse((mm[iND], (iND, iND)), (nw, nw)) ## dead zone or scale diagrr = sparse((rr, (arange(nw), arange(nw))), (nw, nw)) ## linear rows multiplied by rr(i), quadratic rows by rr(i)^2 w = M * (LL + QQ * diagrr) * rr HwC = H * w + Cw AA = N.T * M * (LL + 2 * QQ * diagrr) d2f = d2f + AA * H * AA.T + 2 * N.T * M * QQ * \ sparse((HwC, (arange(nw), arange(nw))), (nw, nw)) * N d2f = d2f * cost_mult ##----- evaluate Hessian of power balance constraints ----- nlam = len(lmbda["eqnonlin"]) / 2 lamP = lmbda["eqnonlin"][:nlam] lamQ = lmbda["eqnonlin"][nlam:nlam + nlam] Gpaa, Gpav, Gpva, Gpvv = d2Sbus_dV2(Ybus, V, lamP) Gqaa, Gqav, Gqva, Gqvv = d2Sbus_dV2(Ybus, V, lamQ) d2G = vstack([ hstack([ vstack([hstack([Gpaa, Gpav]), hstack([Gpva, Gpvv])]).real + vstack([hstack([Gqaa, Gqav]), hstack([Gqva, Gqvv])]).imag, sparse((2 * nb, nxtra))]), hstack([ sparse((nxtra, 2 * nb)), sparse((nxtra, nxtra)) ]) ], "csr") ##----- evaluate Hessian of flow constraints ----- nmu = len(lmbda["ineqnonlin"]) / 2 muF = lmbda["ineqnonlin"][:nmu] muT = lmbda["ineqnonlin"][nmu:nmu + nmu] if ppopt['OPF_FLOW_LIM'] == 2: ## current dIf_dVa, dIf_dVm, dIt_dVa, dIt_dVm, If, It = dIbr_dV(Yf, Yt, V) Hfaa, Hfav, Hfva, Hfvv = d2AIbr_dV2(dIf_dVa, dIf_dVm, If, Yf, V, muF) Htaa, Htav, Htva, Htvv = d2AIbr_dV2(dIt_dVa, dIt_dVm, It, Yt, V, muT) else: f = branch[il, F_BUS].astype(int) ## list of "from" buses t = branch[il, T_BUS].astype(int) ## list of "to" buses ## connection matrix for line & from buses Cf = sparse((ones(nl2), (arange(nl2), f)), (nl2, nb)) ## connection matrix for line & to buses Ct = sparse((ones(nl2), (arange(nl2), t)), (nl2, nb)) dSf_dVa, dSf_dVm, dSt_dVa, dSt_dVm, Sf, St = \ dSbr_dV(branch[il,:], Yf, Yt, V) if ppopt['OPF_FLOW_LIM'] == 1: ## real power Hfaa, Hfav, Hfva, Hfvv = d2ASbr_dV2(dSf_dVa.real, dSf_dVm.real, Sf.real, Cf, Yf, V, muF) Htaa, Htav, Htva, Htvv = d2ASbr_dV2(dSt_dVa.real, dSt_dVm.real, St.real, Ct, Yt, V, muT) else: ## apparent power Hfaa, Hfav, Hfva, Hfvv = \ d2ASbr_dV2(dSf_dVa, dSf_dVm, Sf, Cf, Yf, V, muF) Htaa, Htav, Htva, Htvv = \ d2ASbr_dV2(dSt_dVa, dSt_dVm, St, Ct, Yt, V, muT) d2H = vstack([ hstack([ vstack([hstack([Hfaa, Hfav]), hstack([Hfva, Hfvv])]) + vstack([hstack([Htaa, Htav]), hstack([Htva, Htvv])]), sparse((2 * nb, nxtra)) ]), hstack([ sparse((nxtra, 2 * nb)), sparse((nxtra, nxtra)) ]) ], "csr") ##----- do numerical check using (central) finite differences ----- if 0: nx = len(x) step = 1e-5 num_d2f = sparse((nx, nx)) num_d2G = sparse((nx, nx)) num_d2H = sparse((nx, nx)) for i in range(nx): xp = x xm = x xp[i] = x[i] + step / 2 xm[i] = x[i] - step / 2 # evaluate cost & gradients _, dfp = opf_costfcn(xp, om) _, dfm = opf_costfcn(xm, om) # evaluate constraints & gradients _, _, dHp, dGp = opf_consfcn(xp, om, Ybus, Yf, Yt, ppopt, il) _, _, dHm, dGm = opf_consfcn(xm, om, Ybus, Yf, Yt, ppopt, il) num_d2f[:, i] = cost_mult * (dfp - dfm) / step num_d2G[:, i] = (dGp - dGm) * lmbda["eqnonlin"] / step num_d2H[:, i] = (dHp - dHm) * lmbda["ineqnonlin"] / step d2f_err = max(max(abs(d2f - num_d2f))) d2G_err = max(max(abs(d2G - num_d2G))) d2H_err = max(max(abs(d2H - num_d2H))) if d2f_err > 1e-6: print('Max difference in d2f: %g' % d2f_err) if d2G_err > 1e-5: print('Max difference in d2G: %g' % d2G_err) if d2H_err > 1e-6: print('Max difference in d2H: %g' % d2H_err) return d2f + d2G + d2H
def opf_consfcn(x, om, Ybus, Yf, Yt, ppopt, il=None, *args): """Evaluates nonlinear constraints and their Jacobian for OPF. Constraint evaluation function for AC optimal power flow, suitable for use with L{pips}. Computes constraint vectors and their gradients. @param x: optimization vector @param om: OPF model object @param Ybus: bus admittance matrix @param Yf: admittance matrix for "from" end of constrained branches @param Yt: admittance matrix for "to" end of constrained branches @param ppopt: PYPOWER options vector @param il: (optional) vector of branch indices corresponding to branches with flow limits (all others are assumed to be unconstrained). The default is C{range(nl)} (all branches). C{Yf} and C{Yt} contain only the rows corresponding to C{il}. @return: C{h} - vector of inequality constraint values (flow limits) limit^2 - flow^2, where the flow can be apparent power real power or current, depending on value of C{OPF_FLOW_LIM} in C{ppopt} (only for constrained lines). C{g} - vector of equality constraint values (power balances). C{dh} - (optional) inequality constraint gradients, column j is gradient of h(j). C{dg} - (optional) equality constraint gradients. @see: L{opf_costfcn}, L{opf_hessfcn} @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) @author: Ray Zimmerman (PSERC Cornell) """ ##----- initialize ----- ## unpack data ppc = om.get_ppc() baseMVA, bus, gen, branch = \ ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"] vv, _, _, _ = om.get_idx() ## problem dimensions nb = bus.shape[0] ## number of buses nl = branch.shape[0] ## number of branches ng = gen.shape[0] ## number of dispatchable injections nxyz = len(x) ## total number of control vars of all types ## set default constrained lines if il is None: il = arange(nl) ## all lines have limits by default nl2 = len(il) ## number of constrained lines ## grab Pg & Qg Pg = x[vv["i1"]["Pg"]:vv["iN"]["Pg"]] ## active generation in p.u. Qg = x[vv["i1"]["Qg"]:vv["iN"]["Qg"]] ## reactive generation in p.u. ## put Pg & Qg back in gen gen[:, PG] = Pg * baseMVA ## active generation in MW gen[:, QG] = Qg * baseMVA ## reactive generation in MVAr ## rebuild Sbus Sbus = makeSbus(baseMVA, bus, gen) ## net injected power in p.u. ## ----- evaluate constraints ----- ## reconstruct V Va = x[vv["i1"]["Va"]:vv["iN"]["Va"]] Vm = x[vv["i1"]["Vm"]:vv["iN"]["Vm"]] V = Vm * exp(1j * Va) ## evaluate power flow equations mis = V * conj(Ybus * V) - Sbus ##----- evaluate constraint function values ----- ## first, the equality constraints (power flow) g = r_[mis.real, ## active power mismatch for all buses mis.imag] ## reactive power mismatch for all buses ## then, the inequality constraints (branch flow limits) if nl2 > 0: flow_max = (branch[il, RATE_A] / baseMVA)**2 flow_max[flow_max == 0] = Inf if ppopt['OPF_FLOW_LIM'] == 2: ## current magnitude limit, |I| If = Yf * V It = Yt * V h = r_[If * conj(If) - flow_max, ## branch I limits (from bus) It * conj(It) - flow_max].real ## branch I limits (to bus) else: ## compute branch power flows ## complex power injected at "from" bus (p.u.) Sf = V[branch[il, F_BUS].astype(int)] * conj(Yf * V) ## complex power injected at "to" bus (p.u.) St = V[branch[il, T_BUS].astype(int)] * conj(Yt * V) if ppopt['OPF_FLOW_LIM'] == 1: ## active power limit, P (Pan Wei) h = r_[Sf.real**2 - flow_max, ## branch P limits (from bus) St.real**2 - flow_max] ## branch P limits (to bus) else: ## apparent power limit, |S| h = r_[Sf * conj(Sf) - flow_max, ## branch S limits (from bus) St * conj(St) - flow_max].real ## branch S limits (to bus) else: h = zeros((0, 1)) ##----- evaluate partials of constraints ----- ## index ranges iVa = arange(vv["i1"]["Va"], vv["iN"]["Va"]) iVm = arange(vv["i1"]["Vm"], vv["iN"]["Vm"]) iPg = arange(vv["i1"]["Pg"], vv["iN"]["Pg"]) iQg = arange(vv["i1"]["Qg"], vv["iN"]["Qg"]) iVaVmPgQg = r_[iVa, iVm, iPg, iQg].T ## compute partials of injected bus powers dSbus_dVm, dSbus_dVa = dSbus_dV(Ybus, V) ## w.r.t. V ## Pbus w.r.t. Pg, Qbus w.r.t. Qg neg_Cg = sparse((-ones(ng), (gen[:, GEN_BUS], list(range(ng)))), (nb, ng)) ## construct Jacobian of equality constraints (power flow) and transpose it dg = lil_matrix((2 * nb, nxyz)) blank = sparse((nb, ng)) dg[:, iVaVmPgQg] = vstack( [ ## P mismatch w.r.t Va, Vm, Pg, Qg hstack([dSbus_dVa.real, dSbus_dVm.real, neg_Cg, blank]), ## Q mismatch w.r.t Va, Vm, Pg, Qg hstack([dSbus_dVa.imag, dSbus_dVm.imag, blank, neg_Cg]) ], "csr") dg = dg.T if nl2 > 0: ## compute partials of Flows w.r.t. V if ppopt['OPF_FLOW_LIM'] == 2: ## current dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft = \ dIbr_dV(branch[il, :], Yf, Yt, V) else: ## power dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft = \ dSbr_dV(branch[il, :], Yf, Yt, V) if ppopt['OPF_FLOW_LIM'] == 1: ## real part of flow (active power) dFf_dVa = dFf_dVa.real dFf_dVm = dFf_dVm.real dFt_dVa = dFt_dVa.real dFt_dVm = dFt_dVm.real Ff = Ff.real Ft = Ft.real ## squared magnitude of flow (of complex power or current, or real power) df_dVa, df_dVm, dt_dVa, dt_dVm = \ dAbr_dV(dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft) ## construct Jacobian of inequality constraints (branch limits) ## and transpose it. dh = lil_matrix((2 * nl2, nxyz)) dh[:, r_[iVa, iVm].T] = vstack( [ hstack([df_dVa, df_dVm]), ## "from" flow limit hstack([dt_dVa, dt_dVm]) ## "to" flow limit ], "csr") dh = dh.T else: dh = None return h, g, dh, dg
def t_jacobian(quiet=False): """Numerical tests of partial derivative code. @author: Ray Zimmerman (PSERC Cornell) """ t_begin(28, quiet) ## run powerflow to get solved case ppopt = ppoption(VERBOSE=0, OUT_ALL=0) ppc = loadcase(case30()) results, _ = runpf(ppc, ppopt) baseMVA, bus, gen, branch = \ results['baseMVA'], results['bus'], results['gen'], results['branch'] ## switch to internal bus numbering and build admittance matrices _, bus, gen, branch = ext2int1(bus, gen, branch) Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) Ybus_full = Ybus.todense() Yf_full = Yf.todense() Yt_full = Yt.todense() Vm = bus[:, VM] Va = bus[:, VA] * (pi / 180) V = Vm * exp(1j * Va) f = branch[:, F_BUS].astype(int) ## list of "from" buses t = branch[:, T_BUS].astype(int) ## list of "to" buses #nl = len(f) nb = len(V) pert = 1e-8 Vm = array([Vm]).T # column array Va = array([Va]).T # column array Vc = array([V]).T # column array ##----- check dSbus_dV code ----- ## full matrices dSbus_dVm_full, dSbus_dVa_full = dSbus_dV(Ybus_full, V) ## sparse matrices dSbus_dVm, dSbus_dVa = dSbus_dV(Ybus, V) dSbus_dVm_sp = dSbus_dVm.todense() dSbus_dVa_sp = dSbus_dVa.todense() ## compute numerically to compare Vmp = (Vm * ones((1, nb)) + pert*eye(nb)) * (exp(1j * Va) * ones((1, nb))) Vap = (Vm * ones((1, nb))) * (exp(1j * (Va*ones((1, nb)) + pert*eye(nb)))) num_dSbus_dVm = (Vmp * conj(Ybus * Vmp) - Vc * ones((1, nb)) * conj(Ybus * Vc * ones((1, nb)))) / pert num_dSbus_dVa = (Vap * conj(Ybus * Vap) - Vc * ones((1, nb)) * conj(Ybus * Vc * ones((1, nb)))) / pert t_is(dSbus_dVm_sp, num_dSbus_dVm, 5, 'dSbus_dVm (sparse)') t_is(dSbus_dVa_sp, num_dSbus_dVa, 5, 'dSbus_dVa (sparse)') t_is(dSbus_dVm_full, num_dSbus_dVm, 5, 'dSbus_dVm (full)') t_is(dSbus_dVa_full, num_dSbus_dVa, 5, 'dSbus_dVa (full)') ##----- check dSbr_dV code ----- ## full matrices dSf_dVa_full, dSf_dVm_full, dSt_dVa_full, dSt_dVm_full, _, _ = \ dSbr_dV(branch, Yf_full, Yt_full, V) ## sparse matrices dSf_dVa, dSf_dVm, dSt_dVa, dSt_dVm, Sf, St = dSbr_dV(branch, Yf, Yt, V) dSf_dVa_sp = dSf_dVa.todense() dSf_dVm_sp = dSf_dVm.todense() dSt_dVa_sp = dSt_dVa.todense() dSt_dVm_sp = dSt_dVm.todense() ## compute numerically to compare Vmpf = Vmp[f, :] Vapf = Vap[f, :] Vmpt = Vmp[t, :] Vapt = Vap[t, :] Sf2 = (Vc[f] * ones((1, nb))) * conj(Yf * Vc * ones((1, nb))) St2 = (Vc[t] * ones((1, nb))) * conj(Yt * Vc * ones((1, nb))) Smpf = Vmpf * conj(Yf * Vmp) Sapf = Vapf * conj(Yf * Vap) Smpt = Vmpt * conj(Yt * Vmp) Sapt = Vapt * conj(Yt * Vap) num_dSf_dVm = (Smpf - Sf2) / pert num_dSf_dVa = (Sapf - Sf2) / pert num_dSt_dVm = (Smpt - St2) / pert num_dSt_dVa = (Sapt - St2) / pert t_is(dSf_dVm_sp, num_dSf_dVm, 5, 'dSf_dVm (sparse)') t_is(dSf_dVa_sp, num_dSf_dVa, 5, 'dSf_dVa (sparse)') t_is(dSt_dVm_sp, num_dSt_dVm, 5, 'dSt_dVm (sparse)') t_is(dSt_dVa_sp, num_dSt_dVa, 5, 'dSt_dVa (sparse)') t_is(dSf_dVm_full, num_dSf_dVm, 5, 'dSf_dVm (full)') t_is(dSf_dVa_full, num_dSf_dVa, 5, 'dSf_dVa (full)') t_is(dSt_dVm_full, num_dSt_dVm, 5, 'dSt_dVm (full)') t_is(dSt_dVa_full, num_dSt_dVa, 5, 'dSt_dVa (full)') ##----- check dAbr_dV code ----- ## full matrices dAf_dVa_full, dAf_dVm_full, dAt_dVa_full, dAt_dVm_full = \ dAbr_dV(dSf_dVa_full, dSf_dVm_full, dSt_dVa_full, dSt_dVm_full, Sf, St) ## sparse matrices dAf_dVa, dAf_dVm, dAt_dVa, dAt_dVm = \ dAbr_dV(dSf_dVa, dSf_dVm, dSt_dVa, dSt_dVm, Sf, St) dAf_dVa_sp = dAf_dVa.todense() dAf_dVm_sp = dAf_dVm.todense() dAt_dVa_sp = dAt_dVa.todense() dAt_dVm_sp = dAt_dVm.todense() ## compute numerically to compare num_dAf_dVm = (abs(Smpf)**2 - abs(Sf2)**2) / pert num_dAf_dVa = (abs(Sapf)**2 - abs(Sf2)**2) / pert num_dAt_dVm = (abs(Smpt)**2 - abs(St2)**2) / pert num_dAt_dVa = (abs(Sapt)**2 - abs(St2)**2) / pert t_is(dAf_dVm_sp, num_dAf_dVm, 4, 'dAf_dVm (sparse)') t_is(dAf_dVa_sp, num_dAf_dVa, 4, 'dAf_dVa (sparse)') t_is(dAt_dVm_sp, num_dAt_dVm, 4, 'dAt_dVm (sparse)') t_is(dAt_dVa_sp, num_dAt_dVa, 4, 'dAt_dVa (sparse)') t_is(dAf_dVm_full, num_dAf_dVm, 4, 'dAf_dVm (full)') t_is(dAf_dVa_full, num_dAf_dVa, 4, 'dAf_dVa (full)') t_is(dAt_dVm_full, num_dAt_dVm, 4, 'dAt_dVm (full)') t_is(dAt_dVa_full, num_dAt_dVa, 4, 'dAt_dVa (full)') ##----- check dIbr_dV code ----- ## full matrices dIf_dVa_full, dIf_dVm_full, dIt_dVa_full, dIt_dVm_full, _, _ = \ dIbr_dV(branch, Yf_full, Yt_full, V) ## sparse matrices dIf_dVa, dIf_dVm, dIt_dVa, dIt_dVm, _, _ = dIbr_dV(branch, Yf, Yt, V) dIf_dVa_sp = dIf_dVa.todense() dIf_dVm_sp = dIf_dVm.todense() dIt_dVa_sp = dIt_dVa.todense() dIt_dVm_sp = dIt_dVm.todense() ## compute numerically to compare num_dIf_dVm = (Yf * Vmp - Yf * Vc * ones((1, nb))) / pert num_dIf_dVa = (Yf * Vap - Yf * Vc * ones((1, nb))) / pert num_dIt_dVm = (Yt * Vmp - Yt * Vc * ones((1, nb))) / pert num_dIt_dVa = (Yt * Vap - Yt * Vc * ones((1, nb))) / pert t_is(dIf_dVm_sp, num_dIf_dVm, 5, 'dIf_dVm (sparse)') t_is(dIf_dVa_sp, num_dIf_dVa, 5, 'dIf_dVa (sparse)') t_is(dIt_dVm_sp, num_dIt_dVm, 5, 'dIt_dVm (sparse)') t_is(dIt_dVa_sp, num_dIt_dVa, 5, 'dIt_dVa (sparse)') t_is(dIf_dVm_full, num_dIf_dVm, 5, 'dIf_dVm (full)') t_is(dIf_dVa_full, num_dIf_dVa, 5, 'dIf_dVa (full)') t_is(dIt_dVm_full, num_dIt_dVm, 5, 'dIt_dVm (full)') t_is(dIt_dVa_full, num_dIt_dVa, 5, 'dIt_dVa (full)') t_end()
def admmopf_consfcn(self, x=None): if x is None: x = self.pb['x0'] nx = len(x) nb = self.region['nb'] ng = self.region['ng'] iv = self.idx['var'] bus = self.region['bus'] gen = self.region['gen'] branch = self.region['branch'] Ybus = self.region['Ybus'] Yf = self.region['Yf'] Yt = self.region['Yt'] baseMVA = self.region['baseMVA'] ridx = self.idx['rbus']['int'] # idx ranges iVa = iv['iVa'] iVm = iv['iVm'] iPg = iv['iPg'] iQg = iv['iQg'] # grab Pg and Qg gen[:, PG] = x[iPg] gen[:, QG] = x[iQg] # rebuid Sbus Sbus = makeSbus(1, bus, gen) # reconstruct V Va, Vm = x[iVa], x[iVm] V = Vm * exp(1j * Va) # evaluate power flow equations mis = V * conj(Ybus * V) - Sbus g = r_[mis.real, mis.imag] row = ridx + [i + nb for i in ridx] g = g[row] il = find((branch[:, RATE_A] != 0) & (branch[:, RATE_A] < 1e10)) nl2 = len(il) Ybus2, Yf2, Yt2 = makeYbus(baseMVA, bus, branch) Yf = Yf2[il, :] Yt = Yt2[il, :] if nl2 > 0: flow_max = (branch[il, RATE_A] / baseMVA)**2 flow_max[flow_max == 0] = Inf ## compute branch power flows ## complex power injected at "from" bus (p.u.) Sf = V[branch[il, F_BUS].astype(int)] * conj(Yf * V) ## complex power injected at "to" bus (p.u.) St = V[branch[il, T_BUS].astype(int)] * conj(Yt * V) h = r_[Sf.real**2 - flow_max, ## branch P limits (from bus) St.real**2 - flow_max] ## branch P limits (to bus) else: h = array([]) # ---- evaluate constraint gradients ------- # compute partials of injected bus powers dSbus_dVm, dSbus_dVa = dSbus_dV(Ybus, V) # w.r.t. V neg_Cg = sparse((-ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng)) # construct Jacobian of equality constraints (power flow) and transpose it dg = lil_matrix((2 * nb, nx)) blank = sparse((nb, ng)) dg = vstack([ \ #P mismatch w.r.t Va, Vm, Pg, Qg hstack([dSbus_dVa.real, dSbus_dVm.real, neg_Cg, blank]), # Q mismatch w.r.t Va, Vm, Pg, Qg hstack([dSbus_dVa.imag, dSbus_dVm.imag, blank, neg_Cg]) ], "csr") dg = dg[row, :] dg = dg.T if nl2 > 0: ## compute partials of Flows w.r.t. V ## power dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft = \ dSbr_dV(branch[il, :], Yf, Yt, V) dFf_dVa = dFf_dVa.real dFf_dVm = dFf_dVm.real dFt_dVa = dFt_dVa.real dFt_dVm = dFt_dVm.real Ff = Ff.real Ft = Ft.real ## squared magnitude of flow (of complex power or current, or real power) df_dVa, df_dVm, dt_dVa, dt_dVm = \ dAbr_dV(dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft) ## construct Jacobian of inequality constraints (branch limits) ## and transpose it. dh = lil_matrix((2 * nl2, nx)) dh[:, r_[iVa, iVm].T] = vstack( [ hstack([df_dVa, df_dVm]), ## "from" flow limit hstack([dt_dVa, dt_dVm]) ## "to" flow limit ], "csr") dh = dh.T else: dh = None h = array([]) dh = None return h, g, dh, dg