def makePTDF(baseMVA, bus, branch, slack=None): """Builds the DC PTDF matrix for a given choice of slack. Returns the DC PTDF matrix for a given choice of slack. The matrix is C{nbr x nb}, where C{nbr} is the number of branches and C{nb} is the number of buses. The C{slack} can be a scalar (single slack bus) or an C{nb x 1} column vector of weights specifying the proportion of the slack taken up at each bus. If the C{slack} is not specified the reference bus is used by default. For convenience, C{slack} can also be an C{nb x nb} matrix, where each column specifies how the slack should be handled for injections at that bus. @see: L{makeLODF} @author: Ray Zimmerman (PSERC Cornell) """ ## use reference bus for slack by default if slack is None: slack = find(bus[:, BUS_TYPE] == REF) slack = slack[0] ## set the slack bus to be used to compute initial PTDF if isscalar(slack): slack_bus = slack else: slack_bus = 0 ## use bus 1 for temp slack bus nb = bus.shape[0] nbr = branch.shape[0] noref = arange(1, nb) ## use bus 1 for voltage angle reference noslack = find(arange(nb) != slack_bus) ## check that bus numbers are equal to indices to bus (one set of bus numbers) if any(bus[:, BUS_I] != arange(nb)): stderr.write('makePTDF: buses must be numbered consecutively') ## compute PTDF for single slack_bus Bbus, Bf, _, _ = makeBdc(baseMVA, bus, branch) Bbus, Bf = Bbus.todense(), Bf.todense() H = zeros((nbr, nb)) H[:, noslack] = solve( Bbus[ix_(noslack, noref)].T, Bf[:, noref].T ).T # = Bf[:, noref] * inv(Bbus[ix_(noslack, noref)]) ## distribute slack, if requested if not isscalar(slack): if len(slack.shape) == 1: ## slack is a vector of weights slack = slack / sum(slack) ## normalize weights ## conceptually, we want to do ... ## H = H * (eye(nb, nb) - slack * ones((1, nb))) ## ... we just do it more efficiently v = dot(H, slack) for k in range(nb): H[:, k] = H[:, k] - v else: H = dot(H, slack) return H
def userfcn_iflims_formulation(om, *args): """This is the 'formulation' stage userfcn callback that defines the user costs and constraints for interface flow limits. It expects to find an 'if' field in the ppc stored in om, as described above. The optional args are not currently used. """ ## initialize some things ppc = om.get_ppc() baseMVA, bus, branch = ppc['baseMVA'], ppc['bus'], ppc['branch'] ifmap = ppc['if']['map'] iflims = ppc['if']['lims'] ## form B matrices for DC model _, Bf, _, Pfinj = makeBdc(baseMVA, bus, branch) n = Bf.shape[1] ## dim of theta ## form constraints ifidx = unique(iflims[:, 0]) ## interface number list nifs = len(ifidx) ## number of interfaces Aif = lil_matrix((nifs, n)) lif = zeros(nifs) uif = zeros(nifs) for k in range(nifs): ## extract branch indices br = ifmap[ifmap[:, 0] == ifidx[k], 1] if len(br) == 0: stderr.write( 'userfcn_iflims_formulation: interface %d has no in-service branches\n' % k) d = sign(br) br = abs(br) Ak = sparse((1, n)) ## Ak = sum( d(i) * Bf(i, :) ) bk = 0 ## bk = sum( d(i) * Pfinj(i) ) for i in range(len(br)): Ak = Ak + d[i] * Bf[br[i], :] bk = bk + d[i] * Pfinj[br[i]] Aif[k, :] = Ak lif[k] = iflims[k, 1] / baseMVA - bk uif[k] = iflims[k, 2] / baseMVA - bk ## add interface constraint om.add_constraints('iflims', Aif, lif, uif, ['Va']) ## nifs return om
def userfcn_iflims_formulation(om, *args): """This is the 'formulation' stage userfcn callback that defines the user costs and constraints for interface flow limits. It expects to find an 'if' field in the ppc stored in om, as described above. The optional args are not currently used. """ ## initialize some things ppc = om.get_ppc() baseMVA, bus, branch = ppc['baseMVA'], ppc['bus'], ppc['branch'] ifmap = ppc['if']['map'] iflims = ppc['if']['lims'] ## form B matrices for DC model _, Bf, _, Pfinj = makeBdc(baseMVA, bus, branch) n = Bf.shape[1] ## dim of theta ## form constraints ifidx = unique(iflims[:, 0]) ## interface number list nifs = len(ifidx) ## number of interfaces Aif = lil_matrix((nifs, n)) lif = zeros(nifs) uif = zeros(nifs) for k in range(nifs): ## extract branch indices br = ifmap[ifmap[:, 0] == ifidx[k], 1] if len(br) == 0: stderr.write('userfcn_iflims_formulation: interface %d has no in-service branches\n' % k) d = sign(br) br = abs(br) Ak = sparse((1, n)) ## Ak = sum( d(i) * Bf(i, :) ) bk = 0 ## bk = sum( d(i) * Pfinj(i) ) for i in range(len(br)): Ak = Ak + d[i] * Bf[br[i], :] bk = bk + d[i] * Pfinj[br[i]] Aif[k, :] = Ak lif[k] = iflims[k, 1] / baseMVA - bk uif[k] = iflims[k, 2] / baseMVA - bk ## add interface constraint om.add_constraints('iflims', Aif, lif, uif, ['Va']) ## nifs return om
def runpf(casedata=None, ppopt=None, fname='', solvedcase=''): """Runs a power flow. Runs a power flow [full AC Newton's method by default] and optionally returns the solved values in the data matrices, a flag which is C{True} if the algorithm was successful in finding a solution, and the elapsed time in seconds. All input arguments are optional. If C{casename} is provided it specifies the name of the input data file or dict containing the power flow data. The default value is 'case9'. If the ppopt is provided it overrides the default PYPOWER options vector and can be used to specify the solution algorithm and output options among other things. If the 3rd argument is given the pretty printed output will be appended to the file whose name is given in C{fname}. If C{solvedcase} is specified the solved case will be written to a case file in PYPOWER format with the specified name. If C{solvedcase} ends with '.mat' it saves the case as a MAT-file otherwise it saves it as a Python-file. If the C{ENFORCE_Q_LIMS} options is set to C{True} [default is false] then if any generator reactive power limit is violated after running the AC power flow, the corresponding bus is converted to a PQ bus, with Qg at the limit, and the case is re-run. The voltage magnitude at the bus will deviate from the specified value in order to satisfy the reactive power limit. If the reference bus is converted to PQ, the first remaining PV bus will be used as the slack bus for the next iteration. This may result in the real power output at this generator being slightly off from the specified values. Enforcing of generator Q limits inspired by contributions from Mu Lin, Lincoln University, New Zealand (1/14/05). @author: Ray Zimmerman (PSERC Cornell) """ ## default arguments if casedata is None: casedata = join(dirname(__file__), 'case9') ppopt = ppoption(ppopt) ## options verbose = ppopt["VERBOSE"] qlim = ppopt["ENFORCE_Q_LIMS"] ## enforce Q limits on gens? dc = ppopt["PF_DC"] ## use DC formulation? ## read data ppc = loadcase(casedata) ## add zero columns to branch for flows if needed if ppc["branch"].shape[1] < QT: ppc["branch"] = c_[ppc["branch"], zeros((ppc["branch"].shape[0], QT - ppc["branch"].shape[1] + 1))] ## convert to internal indexing ppc = ext2int(ppc) baseMVA, bus, gen, branch = \ ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"] ## get bus index lists of each type of bus ref, pv, pq = bustypes(bus, gen) ## generator info on = find(gen[:, GEN_STATUS] > 0) ## which generators are on? gbus = gen[on, GEN_BUS].astype(int) ## what buses are they at? ##----- run the power flow ----- t0 = time() if verbose > 0: v = ppver('all') stdout.write('PYPOWER Version %s, %s' % (v["Version"], v["Date"])) if dc: # DC formulation if verbose: stdout.write(' -- DC Power Flow\n') ## initial state Va0 = bus[:, VA] * (pi / 180) ## build B matrices and phase shift injections B, Bf, Pbusinj, Pfinj = makeBdc(baseMVA, bus, branch) ## compute complex bus power injections [generation - load] ## adjusted for phase shifters and real shunts Pbus = makeSbus(baseMVA, bus, gen).real - Pbusinj - bus[:, GS] / baseMVA ## "run" the power flow Va = dcpf(B, Pbus, Va0, ref, pv, pq) ## update data matrices with solution branch[:, [QF, QT]] = zeros((branch.shape[0], 2)) branch[:, PF] = (Bf * Va + Pfinj) * baseMVA branch[:, PT] = -branch[:, PF] bus[:, VM] = ones(bus.shape[0]) bus[:, VA] = Va * (180 / pi) ## update Pg for slack generator (1st gen at ref bus) ## (note: other gens at ref bus are accounted for in Pbus) ## Pg = Pinj + Pload + Gs ## newPg = oldPg + newPinj - oldPinj refgen = zeros(len(ref), dtype=int) for k in range(len(ref)): temp = find(gbus == ref[k]) refgen[k] = on[temp[0]] gen[refgen, PG] = gen[refgen, PG] + (B[ref, :] * Va - Pbus[ref]) * baseMVA success = 1 else: ## AC formulation alg = ppopt['PF_ALG'] if verbose > 0: if alg == 1: solver = 'Newton' elif alg == 2: solver = 'fast-decoupled, XB' elif alg == 3: solver = 'fast-decoupled, BX' elif alg == 4: solver = 'Gauss-Seidel' else: solver = 'unknown' print(' -- AC Power Flow (%s)\n' % solver) ## initial state # V0 = ones(bus.shape[0]) ## flat start V0 = bus[:, VM] * exp(1j * pi / 180 * bus[:, VA]) V0[gbus] = gen[on, VG] / abs(V0[gbus]) * V0[gbus] if qlim: ref0 = ref ## save index and angle of Varef0 = bus[ref0, VA] ## original reference bus(es) limited = [] ## list of indices of gens @ Q lims fixedQg = zeros(gen.shape[0]) ## Qg of gens at Q limits repeat = True while repeat: ## build admittance matrices Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) ## compute complex bus power injections [generation - load] Sbus = makeSbus(baseMVA, bus, gen) ## run the power flow alg = ppopt["PF_ALG"] if alg == 1: V, success, _ = newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppopt) elif alg == 2 or alg == 3: Bp, Bpp = makeB(baseMVA, bus, branch, alg) V, success, _ = fdpf(Ybus, Sbus, V0, Bp, Bpp, ref, pv, pq, ppopt) elif alg == 4: V, success, _ = gausspf(Ybus, Sbus, V0, ref, pv, pq, ppopt) else: stderr.write('Only Newton' 's method, fast-decoupled, and ' 'Gauss-Seidel power flow algorithms currently ' 'implemented.\n') ## update data matrices with solution bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, pv, pq) if qlim: ## enforce generator Q limits ## find gens with violated Q constraints gen_status = gen[:, GEN_STATUS] > 0 qg_max_lim = gen[:, QG] > gen[:, QMAX] qg_min_lim = gen[:, QG] < gen[:, QMIN] mx = find(gen_status & qg_max_lim) mn = find(gen_status & qg_min_lim) if len(mx) > 0 or len( mn) > 0: ## we have some Q limit violations # No PV generators if len(pv) == 0: if verbose: if len(mx) > 0: print( 'Gen %d [only one left] exceeds upper Q limit : INFEASIBLE PROBLEM\n' % mx + 1) else: print( 'Gen %d [only one left] exceeds lower Q limit : INFEASIBLE PROBLEM\n' % mn + 1) success = 0 break ## one at a time? if qlim == 2: ## fix largest violation, ignore the rest k = argmax(r_[gen[mx, QG] - gen[mx, QMAX], gen[mn, QMIN] - gen[mn, QG]]) if k > len(mx): mn = mn[k - len(mx)] mx = [] else: mx = mx[k] mn = [] if verbose and len(mx) > 0: for i in range(len(mx)): print('Gen ' + str(mx[i] + 1) + ' at upper Q limit, converting to PQ bus\n') if verbose and len(mn) > 0: for i in range(len(mn)): print('Gen ' + str(mn[i] + 1) + ' at lower Q limit, converting to PQ bus\n') ## save corresponding limit values fixedQg[mx] = gen[mx, QMAX] fixedQg[mn] = gen[mn, QMIN] mx = r_[mx, mn].astype(int) ## convert to PQ bus gen[mx, QG] = fixedQg[mx] ## set Qg to binding for i in range( len(mx) ): ## [one at a time, since they may be at same bus] gen[mx[i], GEN_STATUS] = 0 ## temporarily turn off gen, bi = gen[mx[i], GEN_BUS] ## adjust load accordingly, bus[bi, [PD, QD]] = (bus[bi, [PD, QD]] - gen[mx[i], [PG, QG]]) if len(ref) > 1 and any(bus[gen[mx, GEN_BUS], BUS_TYPE] == REF): raise ValueError('Sorry, PYPOWER cannot enforce Q ' 'limits for slack buses in systems ' 'with multiple slacks.') bus[gen[mx, GEN_BUS].astype(int), BUS_TYPE] = PQ ## & set bus type to PQ ## update bus index lists of each type of bus ref_temp = ref ref, pv, pq = bustypes(bus, gen) if verbose and ref != ref_temp: print('Bus %d is new slack bus\n' % ref) limited = r_[limited, mx].astype(int) else: repeat = 0 ## no more generator Q limits violated else: repeat = 0 ## don't enforce generator Q limits, once is enough if qlim and len(limited) > 0: ## restore injections from limited gens [those at Q limits] gen[limited, QG] = fixedQg[limited] ## restore Qg value, for i in range( len(limited )): ## [one at a time, since they may be at same bus] bi = gen[limited[i], GEN_BUS] ## re-adjust load, bus[bi, [PD, QD]] = bus[bi, [PD, QD]] + gen[limited[i], [PG, QG]] gen[limited[i], GEN_STATUS] = 1 ## and turn gen back on if ref != ref0: ## adjust voltage angles to make original ref bus correct bus[:, VA] = bus[:, VA] - bus[ref0, VA] + Varef0 ppc["et"] = time() - t0 ppc["success"] = success ##----- output results ----- ## convert back to original bus numbering & print results ppc["bus"], ppc["gen"], ppc["branch"] = bus, gen, branch results = int2ext(ppc) ## zero out result fields of out-of-service gens & branches if len(results["order"]["gen"]["status"]["off"]) > 0: results["gen"][ix_(results["order"]["gen"]["status"]["off"], [PG, QG])] = 0 if len(results["order"]["branch"]["status"]["off"]) > 0: results["branch"][ix_(results["order"]["branch"]["status"]["off"], [PF, QF, PT, QT])] = 0 if fname: fd = None try: fd = open(fname, "a") except Exception as detail: stderr.write("Error opening %s: %s.\n" % (fname, detail)) finally: if fd is not None: printpf(results, fd, ppopt) fd.close() else: printpf(results, stdout, ppopt) ## save solved case if solvedcase: savecase(solvedcase, results) return results, success
def opf_setup(ppc, ppopt): """Constructs an OPF model object from a PYPOWER case dict. Assumes that ppc is a PYPOWER case dict with internal indexing, all equipment in-service, etc. @see: L{opf}, L{ext2int}, L{opf_execute} @author: Ray Zimmerman (PSERC Cornell) @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) @author: Richard Lincoln Modified by University of Kassel (Friederike Meier): Bugfix in line 110 """ ## options dc = ppopt['PF_DC'] ## 1 = DC OPF, 0 = AC OPF alg = ppopt['OPF_ALG'] verbose = ppopt['VERBOSE'] ## data dimensions nb = ppc['bus'].shape[0] ## number of buses nl = ppc['branch'].shape[0] ## number of branches ng = ppc['gen'].shape[0] ## number of dispatchable injections if 'A' in ppc: nusr = ppc['A'].shape[0] ## number of linear user constraints else: nusr = 0 if 'N' in ppc: nw = ppc['N'].shape[0] ## number of general cost vars, w else: nw = 0 if dc: ## ignore reactive costs for DC ppc['gencost'], _ = pqcost(ppc['gencost'], ng) ## reduce A and/or N from AC dimensions to DC dimensions, if needed if nusr or nw: # pragma: no cover acc = r_[nb + arange(nb), 2 * nb + ng + arange(ng)] ## Vm and Qg columns if nusr and (ppc['A'].shape[1] >= 2*nb + 2*ng): ## make sure there aren't any constraints on Vm or Qg if ppc['A'][:, acc].nnz > 0: stderr.write('opf_setup: attempting to solve DC OPF with user constraints on Vm or Qg\n') # FIXME: delete sparse matrix columns bcc = delete(arange(ppc['A'].shape[1]), acc) ppc['A'] = ppc['A'].tolil()[:, bcc].tocsr() ## delete Vm and Qg columns if nw and (ppc['N'].shape[1] >= 2*nb + 2*ng): ## make sure there aren't any costs on Vm or Qg if ppc['N'][:, acc].nnz > 0: ii, _ = nonzero(ppc['N'][:, acc]) _, ii = unique(ii, return_index=True) ## indices of w with potential non-zero cost terms from Vm or Qg if any(ppc['Cw'][ii]) | ( ('H' in ppc) & (len(ppc['H']) > 0) & any(any(ppc['H'][:, ii])) ): stderr.write('opf_setup: attempting to solve DC OPF with user costs on Vm or Qg\n') # FIXME: delete sparse matrix columns bcc = delete(arange(ppc['N'].shape[1]), acc) ppc['N'] = ppc['N'].tolil()[:, bcc].tocsr() ## delete Vm and Qg columns ## convert single-block piecewise-linear costs into linear polynomial cost pwl1 = find((ppc['gencost'][:, MODEL] == PW_LINEAR) & (ppc['gencost'][:, NCOST] == 2)) # p1 = array([]) if len(pwl1) > 0: x0 = ppc['gencost'][pwl1, COST] y0 = ppc['gencost'][pwl1, COST + 1] x1 = ppc['gencost'][pwl1, COST + 2] y1 = ppc['gencost'][pwl1, COST + 3] m = (y1 - y0) / (x1 - x0) b = y0 - m * x0 ppc['gencost'][pwl1, MODEL] = POLYNOMIAL ppc['gencost'][pwl1, NCOST] = 2 ppc['gencost'][pwl1, COST:COST + 2] = r_['1',m.reshape(len(m),1), b.reshape(len(b),1)] # changed from ppc['gencost'][pwl1, COST:COST + 2] = r_[m, b] because we need to make sure, that m and b have the same shape, resulted in a value error due to shape mismatch before ## create (read-only) copies of individual fields for convenience baseMVA, bus, gen, branch, gencost, _, lbu, ubu, ppopt, \ _, fparm, H, Cw, z0, zl, zu, userfcn, _ = opf_args(ppc, ppopt) ## warn if there is more than one reference bus refs = find(bus[:, BUS_TYPE] == REF) if len(refs) > 1 and verbose > 0: errstr = '\nopf_setup: Warning: Multiple reference buses.\n' + \ ' For a system with islands, a reference bus in each island\n' + \ ' may help convergence, but in a fully connected system such\n' + \ ' a situation is probably not reasonable.\n\n' stdout.write(errstr) ## set up initial variables and bounds gbus = gen[:, GEN_BUS].astype(int) Va = bus[:, VA] * (pi / 180.0) Vm = bus[:, VM].copy() Vm[gbus] = gen[:, VG] ## buses with gens, init Vm from gen data Pg = gen[:, PG] / baseMVA Qg = gen[:, QG] / baseMVA Pmin = gen[:, PMIN] / baseMVA Pmax = gen[:, PMAX] / baseMVA Qmin = gen[:, QMIN] / baseMVA Qmax = gen[:, QMAX] / baseMVA if dc: ## DC model ## more problem dimensions nv = 0 ## number of voltage magnitude vars nq = 0 ## number of Qg vars q1 = array([]) ## index of 1st Qg column in Ay ## power mismatch constraints B, Bf, Pbusinj, Pfinj = makeBdc(baseMVA, bus, branch) neg_Cg = sparse((-ones(ng), (gen[:, GEN_BUS], arange(ng))), (nb, ng)) ## Pbus w.r.t. Pg Amis = hstack([B, neg_Cg], 'csr') bmis = -(bus[:, PD] + bus[:, GS]) / baseMVA - Pbusinj ## branch flow constraints il = find((branch[:, RATE_A] != 0) & (branch[:, RATE_A] < 1e10)) nl2 = len(il) ## number of constrained lines lpf = -Inf * ones(nl2) upf = branch[il, RATE_A] / baseMVA - Pfinj[il] upt = branch[il, RATE_A] / baseMVA + Pfinj[il] user_vars = ['Va', 'Pg'] ycon_vars = ['Pg', 'y'] else: ## AC model ## more problem dimensions nv = nb ## number of voltage magnitude vars nq = ng ## number of Qg vars q1 = ng ## index of 1st Qg column in Ay ## dispatchable load, constant power factor constraints Avl, lvl, uvl, _ = makeAvl(baseMVA, gen) ## generator PQ capability curve constraints Apqh, ubpqh, Apql, ubpql, Apqdata = makeApq(baseMVA, gen) user_vars = ['Va', 'Vm', 'Pg', 'Qg'] ycon_vars = ['Pg', 'Qg', 'y'] ## voltage angle reference constraints Vau = Inf * ones(nb) Val = -Vau Vau[refs] = Va[refs] Val[refs] = Va[refs] ## branch voltage angle difference limits Aang, lang, uang, iang = makeAang(baseMVA, branch, nb, ppopt) ## basin constraints for piece-wise linear gen cost variables if alg == 545 or alg == 550: ## SC-PDIPM or TRALM, no CCV cost vars # pragma: no cover ny = 0 Ay = None by = array([]) else: ipwl = find(gencost[:, MODEL] == PW_LINEAR) ## piece-wise linear costs ny = ipwl.shape[0] ## number of piece-wise linear cost vars Ay, by = makeAy(baseMVA, ng, gencost, 1, q1, 1+ng+nq) if any((gencost[:, MODEL] != POLYNOMIAL) & (gencost[:, MODEL] != PW_LINEAR)): stderr.write('opf_setup: some generator cost rows have invalid MODEL value\n') ## more problem dimensions nx = nb+nv + ng+nq ## number of standard OPF control variables if nusr: # pragma: no cover nz = ppc['A'].shape[1] - nx ## number of user z variables if nz < 0: stderr.write('opf_setup: user supplied A matrix must have at least %d columns.\n' % nx) else: nz = 0 ## number of user z variables if nw: ## still need to check number of columns of N if ppc['N'].shape[1] != nx: stderr.write('opf_setup: user supplied N matrix must have %d columns.\n' % nx) ## construct OPF model object om = opf_model(ppc) if len(pwl1) > 0: om.userdata('pwl1', pwl1) if dc: om.userdata('Bf', Bf) om.userdata('Pfinj', Pfinj) om.userdata('iang', iang) om.add_vars('Va', nb, Va, Val, Vau) om.add_vars('Pg', ng, Pg, Pmin, Pmax) om.add_constraints('Pmis', Amis, bmis, bmis, ['Va', 'Pg']) ## nb om.add_constraints('Pf', Bf[il, :], lpf, upf, ['Va']) ## nl om.add_constraints('Pt', -Bf[il, :], lpf, upt, ['Va']) ## nl om.add_constraints('ang', Aang, lang, uang, ['Va']) ## nang else: om.userdata('Apqdata', Apqdata) om.userdata('iang', iang) om.add_vars('Va', nb, Va, Val, Vau) om.add_vars('Vm', nb, Vm, bus[:, VMIN], bus[:, VMAX]) om.add_vars('Pg', ng, Pg, Pmin, Pmax) om.add_vars('Qg', ng, Qg, Qmin, Qmax) om.add_constraints('Pmis', nb, 'nonlinear') om.add_constraints('Qmis', nb, 'nonlinear') om.add_constraints('Sf', nl, 'nonlinear') om.add_constraints('St', nl, 'nonlinear') om.add_constraints('PQh', Apqh, array([]), ubpqh, ['Pg', 'Qg']) ## npqh om.add_constraints('PQl', Apql, array([]), ubpql, ['Pg', 'Qg']) ## npql om.add_constraints('vl', Avl, lvl, uvl, ['Pg', 'Qg']) ## nvl om.add_constraints('ang', Aang, lang, uang, ['Va']) ## nang ## y vars, constraints for piece-wise linear gen costs if ny > 0: om.add_vars('y', ny) om.add_constraints('ycon', Ay, array([]), by, ycon_vars) ## ncony ## add user vars, constraints and costs (as specified via A, ..., N, ...) if nz > 0: # pragma: no cover om.add_vars('z', nz, z0, zl, zu) user_vars.append('z') if nusr: # pragma: no cover om.add_constraints('usr', ppc['A'], lbu, ubu, user_vars) ## nusr if nw: # pragma: no cover user_cost = {} user_cost['N'] = ppc['N'] user_cost['Cw'] = Cw if len(fparm) > 0: user_cost['dd'] = fparm[:, 0] user_cost['rh'] = fparm[:, 1] user_cost['kk'] = fparm[:, 2] user_cost['mm'] = fparm[:, 3] # if len(H) > 0: user_cost['H'] = H om.add_costs('usr', user_cost, user_vars) ## execute userfcn callbacks for 'formulation' stage run_userfcn(userfcn, 'formulation', om) return om
def runpf(casedata=None, ppopt=None, fname='', solvedcase=''): """Runs a power flow. Runs a power flow [full AC Newton's method by default] and optionally returns the solved values in the data matrices, a flag which is C{True} if the algorithm was successful in finding a solution, and the elapsed time in seconds. All input arguments are optional. If C{casename} is provided it specifies the name of the input data file or dict containing the power flow data. The default value is 'case9'. If the ppopt is provided it overrides the default PYPOWER options vector and can be used to specify the solution algorithm and output options among other things. If the 3rd argument is given the pretty printed output will be appended to the file whose name is given in C{fname}. If C{solvedcase} is specified the solved case will be written to a case file in PYPOWER format with the specified name. If C{solvedcase} ends with '.mat' it saves the case as a MAT-file otherwise it saves it as a Python-file. If the C{ENFORCE_Q_LIMS} options is set to C{True} [default is false] then if any generator reactive power limit is violated after running the AC power flow, the corresponding bus is converted to a PQ bus, with Qg at the limit, and the case is re-run. The voltage magnitude at the bus will deviate from the specified value in order to satisfy the reactive power limit. If the reference bus is converted to PQ, the first remaining PV bus will be used as the slack bus for the next iteration. This may result in the real power output at this generator being slightly off from the specified values. Enforcing of generator Q limits inspired by contributions from Mu Lin, Lincoln University, New Zealand (1/14/05). @author: Ray Zimmerman (PSERC Cornell) """ ## default arguments if casedata is None: casedata = join(dirname(__file__), 'case9') ppopt = ppoption(ppopt) ## options verbose = ppopt["VERBOSE"] qlim = ppopt["ENFORCE_Q_LIMS"] ## enforce Q limits on gens? dc = ppopt["PF_DC"] ## use DC formulation? ## read data ppc = loadcase(casedata) ## add zero columns to branch for flows if needed if ppc["branch"].shape[1] < QT: ppc["branch"] = c_[ppc["branch"], zeros((ppc["branch"].shape[0], QT - ppc["branch"].shape[1] + 1))] ## convert to internal indexing ppc = ext2int(ppc) baseMVA, bus, gen, branch = \ ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"] ## get bus index lists of each type of bus ref, pv, pq = bustypes(bus, gen) ## generator info on = find(gen[:, GEN_STATUS] > 0) ## which generators are on? gbus = gen[on, GEN_BUS].astype(int) ## what buses are they at? ##----- run the power flow ----- t0 = time() if verbose > 0: v = ppver('all') stdout.write('PYPOWER Version %s, %s' % (v["Version"], v["Date"])) if dc: # DC formulation if verbose: stdout.write(' -- DC Power Flow\n') ## initial state Va0 = bus[:, VA] * (pi / 180) ## build B matrices and phase shift injections B, Bf, Pbusinj, Pfinj = makeBdc(baseMVA, bus, branch) ## compute complex bus power injections [generation - load] ## adjusted for phase shifters and real shunts Pbus = makeSbus(baseMVA, bus, gen).real - Pbusinj - bus[:, GS] / baseMVA ## "run" the power flow Va = dcpf(B, Pbus, Va0, ref, pv, pq) ## update data matrices with solution branch[:, [QF, QT]] = zeros((branch.shape[0], 2)) branch[:, PF] = (Bf * Va + Pfinj) * baseMVA branch[:, PT] = -branch[:, PF] bus[:, VM] = ones(bus.shape[0]) bus[:, VA] = Va * (180 / pi) ## update Pg for slack generator (1st gen at ref bus) ## (note: other gens at ref bus are accounted for in Pbus) ## Pg = Pinj + Pload + Gs ## newPg = oldPg + newPinj - oldPinj refgen = zeros(len(ref), dtype=int) for k in range(len(ref)): temp = find(gbus == ref[k]) refgen[k] = on[temp[0]] gen[refgen, PG] = gen[refgen, PG] + (B[ref, :] * Va - Pbus[ref]) * baseMVA success = 1 else: ## AC formulation alg = ppopt['PF_ALG'] if verbose > 0: if alg == 1: solver = 'Newton' elif alg == 2: solver = 'fast-decoupled, XB' elif alg == 3: solver = 'fast-decoupled, BX' elif alg == 4: solver = 'Gauss-Seidel' else: solver = 'unknown' print(' -- AC Power Flow (%s)\n' % solver) ## initial state # V0 = ones(bus.shape[0]) ## flat start V0 = bus[:, VM] * exp(1j * pi/180 * bus[:, VA]) V0[gbus] = gen[on, VG] / abs(V0[gbus]) * V0[gbus] if qlim: ref0 = ref ## save index and angle of Varef0 = bus[ref0, VA] ## original reference bus(es) limited = [] ## list of indices of gens @ Q lims fixedQg = zeros(gen.shape[0]) ## Qg of gens at Q limits repeat = True while repeat: ## build admittance matrices Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) ## compute complex bus power injections [generation - load] Sbus = makeSbus(baseMVA, bus, gen) ## run the power flow alg = ppopt["PF_ALG"] if alg == 1: V, success, _ = newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppopt) elif alg == 2 or alg == 3: Bp, Bpp = makeB(baseMVA, bus, branch, alg) V, success, _ = fdpf(Ybus, Sbus, V0, Bp, Bpp, ref, pv, pq, ppopt) elif alg == 4: V, success, _ = gausspf(Ybus, Sbus, V0, ref, pv, pq, ppopt) else: stderr.write('Only Newton''s method, fast-decoupled, and ' 'Gauss-Seidel power flow algorithms currently ' 'implemented.\n') ## update data matrices with solution bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, pv, pq) if qlim: ## enforce generator Q limits ## find gens with violated Q constraints gen_status = gen[:, GEN_STATUS] > 0 qg_max_lim = gen[:, QG] > gen[:, QMAX] qg_min_lim = gen[:, QG] < gen[:, QMIN] mx = find( gen_status & qg_max_lim ) mn = find( gen_status & qg_min_lim ) if len(mx) > 0 or len(mn) > 0: ## we have some Q limit violations # No PV generators if len(pv) == 0: if verbose: if len(mx) > 0: print('Gen %d [only one left] exceeds upper Q limit : INFEASIBLE PROBLEM\n' % mx + 1) else: print('Gen %d [only one left] exceeds lower Q limit : INFEASIBLE PROBLEM\n' % mn + 1) success = 0 break ## one at a time? if qlim == 2: ## fix largest violation, ignore the rest k = argmax(r_[gen[mx, QG] - gen[mx, QMAX], gen[mn, QMIN] - gen[mn, QG]]) if k > len(mx): mn = mn[k - len(mx)] mx = [] else: mx = mx[k] mn = [] if verbose and len(mx) > 0: for i in range(len(mx)): print('Gen ' + str(mx[i] + 1) + ' at upper Q limit, converting to PQ bus\n') if verbose and len(mn) > 0: for i in range(len(mn)): print('Gen ' + str(mn[i] + 1) + ' at lower Q limit, converting to PQ bus\n') ## save corresponding limit values fixedQg[mx] = gen[mx, QMAX] fixedQg[mn] = gen[mn, QMIN] mx = r_[mx, mn].astype(int) ## convert to PQ bus gen[mx, QG] = fixedQg[mx] ## set Qg to binding for i in range(len(mx)): ## [one at a time, since they may be at same bus] gen[mx[i], GEN_STATUS] = 0 ## temporarily turn off gen, bi = gen[mx[i], GEN_BUS] ## adjust load accordingly, bus[bi, [PD, QD]] = (bus[bi, [PD, QD]] - gen[mx[i], [PG, QG]]) if len(ref) > 1 and any(bus[gen[mx, GEN_BUS], BUS_TYPE] == REF): raise ValueError('Sorry, PYPOWER cannot enforce Q ' 'limits for slack buses in systems ' 'with multiple slacks.') bus[gen[mx, GEN_BUS].astype(int), BUS_TYPE] = PQ ## & set bus type to PQ ## update bus index lists of each type of bus ref_temp = ref ref, pv, pq = bustypes(bus, gen) if verbose and ref != ref_temp: print('Bus %d is new slack bus\n' % ref) limited = r_[limited, mx].astype(int) else: repeat = 0 ## no more generator Q limits violated else: repeat = 0 ## don't enforce generator Q limits, once is enough if qlim and len(limited) > 0: ## restore injections from limited gens [those at Q limits] gen[limited, QG] = fixedQg[limited] ## restore Qg value, for i in range(len(limited)): ## [one at a time, since they may be at same bus] bi = gen[limited[i], GEN_BUS] ## re-adjust load, bus[bi, [PD, QD]] = bus[bi, [PD, QD]] + gen[limited[i], [PG, QG]] gen[limited[i], GEN_STATUS] = 1 ## and turn gen back on if ref != ref0: ## adjust voltage angles to make original ref bus correct bus[:, VA] = bus[:, VA] - bus[ref0, VA] + Varef0 ppc["et"] = time() - t0 ppc["success"] = success ##----- output results ----- ## convert back to original bus numbering & print results ppc["bus"], ppc["gen"], ppc["branch"] = bus, gen, branch results = int2ext(ppc) ## zero out result fields of out-of-service gens & branches if len(results["order"]["gen"]["status"]["off"]) > 0: results["gen"][ix_(results["order"]["gen"]["status"]["off"], [PG, QG])] = 0 if len(results["order"]["branch"]["status"]["off"]) > 0: results["branch"][ix_(results["order"]["branch"]["status"]["off"], [PF, QF, PT, QT])] = 0 if fname: fd = None try: fd = open(fname, "a") except Exception as detail: stderr.write("Error opening %s: %s.\n" % (fname, detail)) finally: if fd is not None: printpf(results, fd, ppopt) fd.close() else: printpf(results, stdout, ppopt) ## save solved case if solvedcase: savecase(solvedcase, results) return results, success
def opf_setup(ppc, ppopt): """Constructs an OPF model object from a PYPOWER case dict. Assumes that ppc is a PYPOWER case dict with internal indexing, all equipment in-service, etc. @see: L{opf}, L{ext2int}, L{opf_execute} @author: Ray Zimmerman (PSERC Cornell) @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) @author: Richard Lincoln """ ## options dc = ppopt['PF_DC'] ## 1 = DC OPF, 0 = AC OPF alg = ppopt['OPF_ALG'] verbose = ppopt['VERBOSE'] ## data dimensions nb = ppc['bus'].shape[0] ## number of buses nl = ppc['branch'].shape[0] ## number of branches ng = ppc['gen'].shape[0] ## number of dispatchable injections if 'A' in ppc: nusr = ppc['A'].shape[0] ## number of linear user constraints else: nusr = 0 if 'N' in ppc: nw = ppc['N'].shape[0] ## number of general cost vars, w else: nw = 0 if dc: ## ignore reactive costs for DC ppc['gencost'], _ = pqcost(ppc['gencost'], ng) ## reduce A and/or N from AC dimensions to DC dimensions, if needed if nusr or nw: acc = r_[nb + arange(nb), 2 * nb + ng + arange(ng)] ## Vm and Qg columns if nusr and (ppc['A'].shape[1] >= 2*nb + 2*ng): ## make sure there aren't any constraints on Vm or Qg if ppc['A'][:, acc].nnz > 0: stderr.write('opf_setup: attempting to solve DC OPF with user constraints on Vm or Qg\n') # FIXME: delete sparse matrix columns bcc = delete(arange(ppc['A'].shape[1]), acc) ppc['A'] = ppc['A'].tolil()[:, bcc].tocsr() ## delete Vm and Qg columns if nw and (ppc['N'].shape[1] >= 2*nb + 2*ng): ## make sure there aren't any costs on Vm or Qg if ppc['N'][:, acc].nnz > 0: ii, _ = nonzero(ppc['N'][:, acc]) _, ii = unique(ii, return_index=True) ## indices of w with potential non-zero cost terms from Vm or Qg if any(ppc['Cw'][ii]) | ( ('H' in ppc) & (len(ppc['H']) > 0) & any(any(ppc['H'][:, ii])) ): stderr.write('opf_setup: attempting to solve DC OPF with user costs on Vm or Qg\n') # FIXME: delete sparse matrix columns bcc = delete(arange(ppc['N'].shape[1]), acc) ppc['N'] = ppc['N'].tolil()[:, bcc].tocsr() ## delete Vm and Qg columns ## convert single-block piecewise-linear costs into linear polynomial cost pwl1 = find((ppc['gencost'][:, MODEL] == PW_LINEAR) & (ppc['gencost'][:, NCOST] == 2)) # p1 = array([]) if len(pwl1) > 0: x0 = ppc['gencost'][pwl1, COST] y0 = ppc['gencost'][pwl1, COST + 1] x1 = ppc['gencost'][pwl1, COST + 2] y1 = ppc['gencost'][pwl1, COST + 3] m = (y1 - y0) / (x1 - x0) b = y0 - m * x0 ppc['gencost'][pwl1, MODEL] = POLYNOMIAL ppc['gencost'][pwl1, NCOST] = 2 ppc['gencost'][pwl1, COST:COST + 2] = r_[m, b] ## create (read-only) copies of individual fields for convenience baseMVA, bus, gen, branch, gencost, _, lbu, ubu, ppopt, \ _, fparm, H, Cw, z0, zl, zu, userfcn, _ = opf_args(ppc, ppopt) ## warn if there is more than one reference bus refs = find(bus[:, BUS_TYPE] == REF) if len(refs) > 1 and verbose > 0: errstr = '\nopf_setup: Warning: Multiple reference buses.\n' + \ ' For a system with islands, a reference bus in each island\n' + \ ' may help convergence, but in a fully connected system such\n' + \ ' a situation is probably not reasonable.\n\n' stdout.write(errstr) ## set up initial variables and bounds gbus = gen[:, GEN_BUS].astype(int) Va = bus[:, VA] * (pi / 180.0) Vm = bus[:, VM].copy() Vm[gbus] = gen[:, VG] ## buses with gens, init Vm from gen data Pg = gen[:, PG] / baseMVA Qg = gen[:, QG] / baseMVA Pmin = gen[:, PMIN] / baseMVA Pmax = gen[:, PMAX] / baseMVA Qmin = gen[:, QMIN] / baseMVA Qmax = gen[:, QMAX] / baseMVA if dc: ## DC model ## more problem dimensions nv = 0 ## number of voltage magnitude vars nq = 0 ## number of Qg vars q1 = array([]) ## index of 1st Qg column in Ay ## power mismatch constraints B, Bf, Pbusinj, Pfinj = makeBdc(baseMVA, bus, branch) neg_Cg = sparse((-ones(ng), (gen[:, GEN_BUS], arange(ng))), (nb, ng)) ## Pbus w.r.t. Pg Amis = hstack([B, neg_Cg], 'csr') bmis = -(bus[:, PD] + bus[:, GS]) / baseMVA - Pbusinj ## branch flow constraints il = find((branch[:, RATE_A] != 0) & (branch[:, RATE_A] < 1e10)) nl2 = len(il) ## number of constrained lines lpf = -Inf * ones(nl2) upf = branch[il, RATE_A] / baseMVA - Pfinj[il] upt = branch[il, RATE_A] / baseMVA + Pfinj[il] user_vars = ['Va', 'Pg'] ycon_vars = ['Pg', 'y'] else: ## AC model ## more problem dimensions nv = nb ## number of voltage magnitude vars nq = ng ## number of Qg vars q1 = ng ## index of 1st Qg column in Ay ## dispatchable load, constant power factor constraints Avl, lvl, uvl, _ = makeAvl(baseMVA, gen) ## generator PQ capability curve constraints Apqh, ubpqh, Apql, ubpql, Apqdata = makeApq(baseMVA, gen) user_vars = ['Va', 'Vm', 'Pg', 'Qg'] ycon_vars = ['Pg', 'Qg', 'y'] ## voltage angle reference constraints Vau = Inf * ones(nb) Val = -Vau Vau[refs] = Va[refs] Val[refs] = Va[refs] ## branch voltage angle difference limits Aang, lang, uang, iang = makeAang(baseMVA, branch, nb, ppopt) ## basin constraints for piece-wise linear gen cost variables if alg == 545 or alg == 550: ## SC-PDIPM or TRALM, no CCV cost vars ny = 0 Ay = None by = array([]) else: ipwl = find(gencost[:, MODEL] == PW_LINEAR) ## piece-wise linear costs ny = ipwl.shape[0] ## number of piece-wise linear cost vars Ay, by = makeAy(baseMVA, ng, gencost, 1, q1, 1+ng+nq) if any((gencost[:, MODEL] != POLYNOMIAL) & (gencost[:, MODEL] != PW_LINEAR)): stderr.write('opf_setup: some generator cost rows have invalid MODEL value\n') ## more problem dimensions nx = nb+nv + ng+nq; ## number of standard OPF control variables if nusr: nz = ppc['A'].shape[1] - nx ## number of user z variables if nz < 0: stderr.write('opf_setup: user supplied A matrix must have at least %d columns.\n' % nx) else: nz = 0 ## number of user z variables if nw: ## still need to check number of columns of N if ppc['N'].shape[1] != nx: stderr.write('opf_setup: user supplied N matrix must have %d columns.\n' % nx) ## construct OPF model object om = opf_model(ppc) if len(pwl1) > 0: om.userdata('pwl1', pwl1) if dc: om.userdata('Bf', Bf) om.userdata('Pfinj', Pfinj) om.userdata('iang', iang) om.add_vars('Va', nb, Va, Val, Vau) om.add_vars('Pg', ng, Pg, Pmin, Pmax) om.add_constraints('Pmis', Amis, bmis, bmis, ['Va', 'Pg']) ## nb om.add_constraints('Pf', Bf[il, :], lpf, upf, ['Va']) ## nl om.add_constraints('Pt', -Bf[il, :], lpf, upt, ['Va']) ## nl om.add_constraints('ang', Aang, lang, uang, ['Va']) ## nang else: om.userdata('Apqdata', Apqdata) om.userdata('iang', iang) om.add_vars('Va', nb, Va, Val, Vau) om.add_vars('Vm', nb, Vm, bus[:, VMIN], bus[:, VMAX]) om.add_vars('Pg', ng, Pg, Pmin, Pmax) om.add_vars('Qg', ng, Qg, Qmin, Qmax) om.add_constraints('Pmis', nb, 'nonlinear') om.add_constraints('Qmis', nb, 'nonlinear') om.add_constraints('Sf', nl, 'nonlinear') om.add_constraints('St', nl, 'nonlinear') om.add_constraints('PQh', Apqh, array([]), ubpqh, ['Pg', 'Qg']) ## npqh om.add_constraints('PQl', Apql, array([]), ubpql, ['Pg', 'Qg']) ## npql om.add_constraints('vl', Avl, lvl, uvl, ['Pg', 'Qg']) ## nvl om.add_constraints('ang', Aang, lang, uang, ['Va']) ## nang ## y vars, constraints for piece-wise linear gen costs if ny > 0: om.add_vars('y', ny) om.add_constraints('ycon', Ay, array([]), by, ycon_vars) ## ncony ## add user vars, constraints and costs (as specified via A, ..., N, ...) if nz > 0: om.add_vars('z', nz, z0, zl, zu) user_vars.append('z') if nusr: om.add_constraints('usr', ppc['A'], lbu, ubu, user_vars) ## nusr if nw: user_cost = {} user_cost['N'] = ppc['N'] user_cost['Cw'] = Cw if len(fparm) > 0: user_cost['dd'] = fparm[:, 0] user_cost['rh'] = fparm[:, 1] user_cost['kk'] = fparm[:, 2] user_cost['mm'] = fparm[:, 3] # if len(H) > 0: user_cost['H'] = H om.add_costs('usr', user_cost, user_vars) ## execute userfcn callbacks for 'formulation' stage run_userfcn(userfcn, 'formulation', om) return om
def _runpf(casedata=None, init='flat', ac=True, Numba=True, ppopt=None): """Runs a power flow. Similar to runpf() from pypower. See Pypower documentation for more information. Changes by University of Kassel (Florian Schaefer): Numba can be used for pf calculations. Changes in structure (AC as well as DC PF can be calculated) """ ## default arguments if casedata is None: casedata = join(dirname(__file__), 'case9') ppopt = ppoption(ppopt) ## options verbose = ppopt["VERBOSE"] ## read data ppci = loadcase(casedata) # get data for calc baseMVA, bus, gen, branch = \ ppci["baseMVA"], ppci["bus"], ppci["gen"], ppci["branch"] ## get bus index lists of each type of bus ref, pv, pq = bustypes(bus, gen) ## generator info on = find(gen[:, GEN_STATUS] > 0) ## which generators are on? gbus = gen[on, GEN_BUS].astype(int) ## what buses are they at? ##----- run the power flow ----- t0 = time() if not ac or (ac and init == 'dc'): # DC formulation if verbose: print(' -- DC Power Flow\n') ## initial state Va0 = bus[:, VA] * (pi / 180) ## build B matrices and phase shift injections B, Bf, Pbusinj, Pfinj = makeBdc(baseMVA, bus, branch) ## compute complex bus power injections [generation - load] ## adjusted for phase shifters and real shunts Pbus = makeSbus(baseMVA, bus, gen) - Pbusinj - bus[:, GS] / baseMVA ## "run" the power flow Va = dcpf(B, Pbus, Va0, ref, pv, pq) ## update data matrices with solution branch[:, [QF, QT]] = zeros((branch.shape[0], 2)) branch[:, PF] = (Bf * Va + Pfinj) * baseMVA branch[:, PT] = -branch[:, PF] bus[:, VM] = ones(bus.shape[0]) bus[:, VA] = Va * (180 / pi) ## update Pg for slack generator (1st gen at ref bus) ## (note: other gens at ref bus are accounted for in Pbus) ## Pg = Pinj + Pload + Gs ## newPg = oldPg + newPinj - oldPinj refgen = zeros(len(ref), dtype=int) for k in range(len(ref)): temp = find(gbus == ref[k]) refgen[k] = on[temp[0]] gen[refgen, PG] = gen[refgen, PG] + (B[ref, :] * Va - Pbus[ref]) * baseMVA success = 1 if ac and init == 'dc': # get results from DC powerflow for AC powerflow ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch if ac: ## AC formulation # options qlim = ppopt["ENFORCE_Q_LIMS"] ## enforce Q limits on gens? ## check if numba is available and the corresponding flag try: from numba import _version as nb_version # get Numba Version (in order to use it it must be > 0.25) nbVersion = float(nb_version.version_version[:4]) if nbVersion < 0.25: print( 'Warning: Numba version too old -> Upgrade to a version > 0.25. Numba is disabled\n' ) Numba = False except ImportError: # raise UserWarning('Numba cannot be imported. Call runpp() with Numba=False!') print( 'Warning: Numba cannot be imported. Numba is disabled. Call runpp() with Numba=False!\n' ) Numba = False if Numba: from pandapower.pypower_extensions.makeYbus import makeYbus else: from pypower.makeYbus import makeYbus alg = ppopt['PF_ALG'] if verbose > 0: if alg == 1: solver = 'Newton' elif alg == 2: solver = 'fast-decoupled, XB' elif alg == 3: solver = 'fast-decoupled, BX' elif alg == 4: solver = 'Gauss-Seidel' else: solver = 'unknown' print(' -- AC Power Flow (%s)\n' % solver) ## initial state # V0 = ones(bus.shape[0]) ## flat start V0 = bus[:, VM] * exp(1j * pi / 180 * bus[:, VA]) V0[gbus] = gen[on, VG] / abs(V0[gbus]) * V0[gbus] if qlim: ref0 = ref ## save index and angle of Varef0 = bus[ref0, VA] ## original reference bus(es) limited = [] ## list of indices of gens @ Q lims fixedQg = zeros(gen.shape[0]) ## Qg of gens at Q limits repeat = True while repeat: ## build admittance matrices Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) ## compute complex bus power injections [generation - load] Sbus = makeSbus(baseMVA, bus, gen) ## run the power flow alg = ppopt["PF_ALG"] if alg == 1: V, success, _ = newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppopt, Numba) elif alg == 2 or alg == 3: Bp, Bpp = makeB(baseMVA, bus, branch, alg) V, success, _ = fdpf(Ybus, Sbus, V0, Bp, Bpp, ref, pv, pq, ppopt) elif alg == 4: V, success, _ = gausspf(Ybus, Sbus, V0, ref, pv, pq, ppopt) else: raise ValueError( 'Only Newton' 's method, fast-decoupled, and ' 'Gauss-Seidel power flow algorithms currently ' 'implemented.\n') ## update data matrices with solution bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, pv, pq) if qlim: ## enforce generator Q limits ## find gens with violated Q constraints gen_status = gen[:, GEN_STATUS] > 0 qg_max_lim = gen[:, QG] > gen[:, QMAX] qg_min_lim = gen[:, QG] < gen[:, QMIN] mx = find(gen_status & qg_max_lim) mn = find(gen_status & qg_min_lim) if len(mx) > 0 or len( mn) > 0: ## we have some Q limit violations # No PV generators if len(pv) == 0: if verbose: if len(mx) > 0: print( 'Gen %d [only one left] exceeds upper Q limit : INFEASIBLE PROBLEM\n' % mx + 1) else: print( 'Gen %d [only one left] exceeds lower Q limit : INFEASIBLE PROBLEM\n' % mn + 1) success = 0 break ## one at a time? if qlim == 2: ## fix largest violation, ignore the rest k = argmax(r_[gen[mx, QG] - gen[mx, QMAX], gen[mn, QMIN] - gen[mn, QG]]) if k > len(mx): mn = mn[k - len(mx)] mx = [] else: mx = mx[k] mn = [] if verbose and len(mx) > 0: for i in range(len(mx)): print('Gen ' + str(mx[i] + 1) + ' at upper Q limit, converting to PQ bus\n') if verbose and len(mn) > 0: for i in range(len(mn)): print('Gen ' + str(mn[i] + 1) + ' at lower Q limit, converting to PQ bus\n') ## save corresponding limit values fixedQg[mx] = gen[mx, QMAX] fixedQg[mn] = gen[mn, QMIN] mx = r_[mx, mn].astype(int) ## convert to PQ bus gen[mx, QG] = fixedQg[mx] ## set Qg to binding for i in range( len(mx) ): ## [one at a time, since they may be at same bus] gen[mx[i], GEN_STATUS] = 0 ## temporarily turn off gen, bi = gen[mx[i], GEN_BUS] ## adjust load accordingly, bus[bi, [PD, QD]] = (bus[bi, [PD, QD]] - gen[mx[i], [PG, QG]]) if len(ref) > 1 and any(bus[gen[mx, GEN_BUS].astype(int), BUS_TYPE] == REF): raise ValueError('Sorry, PYPOWER cannot enforce Q ' 'limits for slack buses in systems ' 'with multiple slacks.') bus[gen[mx, GEN_BUS].astype(int), BUS_TYPE] = PQ ## & set bus type to PQ ## update bus index lists of each type of bus ref_temp = ref ref, pv, pq = bustypes(bus, gen) if verbose and ref != ref_temp: print('Bus %d is new slack bus\n' % ref) limited = r_[limited, mx].astype(int) else: repeat = 0 ## no more generator Q limits violated else: repeat = 0 ## don't enforce generator Q limits, once is enough if qlim and len(limited) > 0: ## restore injections from limited gens [those at Q limits] gen[limited, QG] = fixedQg[limited] ## restore Qg value, for i in range( len(limited )): ## [one at a time, since they may be at same bus] bi = gen[limited[i], GEN_BUS] ## re-adjust load, bus[bi, [PD, QD]] = bus[bi, [PD, QD]] + gen[limited[i], [PG, QG]] gen[limited[i], GEN_STATUS] = 1 ## and turn gen back on # if ref != ref0: # ## adjust voltage angles to make original ref bus correct # bus[:, VA] = bus[:, VA] - bus[ref0, VA] + Varef0 ppci["et"] = time() - t0 ppci["success"] = success ##----- output results ----- ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch results = ppci return results, success