def _run_bfsw_ppc(ppc, ppopt=None): """ SPARSE version of distribution power flow solution according to [1] :References: [1] Jen-Hao Teng, "A Direct Approach for Distribution System Load Flow Solutions", IEEE Transactions on Power Delivery, vol. 18, no. 3, pp. 882-887, July 2003. :param ppc: matpower-style case data :return: results (pypower style), success (flag about PF convergence) """ ppci = ppc ppopt = ppoption(ppopt) baseMVA, bus, gen, branch = \ ppci["baseMVA"], ppci["bus"], ppci["gen"], ppci["branch"] nbus = bus.shape[0] # get bus index lists of each type of bus ref, pv, pq = bustypes(bus, gen) # depth-first-search bus ordering and generating Direct Load Flow matrix DLF = BCBV * BIBC DLF, ppc_bfsw, buses_ordered_bfsw = bibc_bcbv(ppci) baseMVA_bfsw, bus_bfsw, gen_bfsw, branch_bfsw = \ ppc_bfsw["baseMVA"], ppc_bfsw["bus"], ppc_bfsw["gen"], ppc_bfsw["branch"] time_start = time() # starting pf calculation timing # initialize voltages to flat start and buses with gens to their setpoints V0 = np.ones(nbus, dtype=complex) V0[gen[:, GEN_BUS].astype(int)] = gen[:, VG] Sbus_bfsw = makeSbus(baseMVA_bfsw, bus_bfsw, gen_bfsw) # update data matrices with solution Ybus_bfsw, Yf_bfsw, Yt_bfsw = makeYbus(baseMVA_bfsw, bus_bfsw, branch_bfsw) ## get bus index lists of each type of bus ref_bfsw, pv_bfsw, pq_bfsw = bustypes(bus_bfsw, gen_bfsw) # #----- run the power flow ----- V_final, success = bfsw(DLF, bus_bfsw, gen_bfsw, branch_bfsw, baseMVA_bfsw, Ybus_bfsw, Sbus_bfsw, V0, ref_bfsw, pv_bfsw, pq_bfsw, ppopt=ppopt) V_final = V_final[np.argsort(buses_ordered_bfsw)] # return bus voltages in original bus order # #----- output results to ppc ------ ppci["et"] = time() - time_start # pf time end # generate results for original bus ordering Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V_final, ref, pv, pq) ppci["success"] = success ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch return ppci, success
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'] Ybus = self.region['Ybus'] 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] # ---- 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 h = None dh = None return h, g, dh, dg
def _run_dc_pf(ppci): t0 = time() baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, _ = _get_pf_variables_from_ppci( ppci) ppci["bus"][:, VM] = 1.0 ## initial state Va0 = bus[:, VA] * (pi / 180.) ## build B matrices and phase shift injections B, Bf, Pbusinj, Pfinj = makeBdc(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] = real(gen[refgen, PG] + (B[ref, :] * Va - Pbus[ref]) * baseMVA) success = True # store results from DC powerflow for AC powerflow ppci = _store_results_from_pf_in_ppci(ppci, bus, gen, branch) ppci["et"] = time() - t0 ppci["success"] = success return ppci
def _run_ac_pf_without_qlims_enforced(ppci, recycle, makeYbus, ppopt): baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, V0 = _get_pf_variables_from_ppci( ppci) ppci, Ybus, Yf, Yt = _get_Y_bus(ppci, recycle, makeYbus, baseMVA, bus, branch) ## compute complex bus power injections [generation - load] Sbus = makeSbus(baseMVA, bus, gen) ## run the power flow V, success = _call_power_flow_function(baseMVA, bus, branch, Ybus, Sbus, V0, ref, pv, pq, ppopt) ## update data matrices with solution bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, pv, pq) return ppci, success, bus, gen, branch
def _run_ac_pf_without_qlims_enforced(ppci, options): numba, makeYbus = _import_numba_extensions_if_flag_is_true( options["numba"]) baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, V0 = _get_pf_variables_from_ppci( ppci) ppci, Ybus, Yf, Yt = _get_Y_bus(ppci, options, makeYbus, baseMVA, bus, branch) ## compute complex bus power injections [generation - load] Sbus = makeSbus(baseMVA, bus, gen) ## run the newton power flow V, success, _ = newtonpf(Ybus, Sbus, V0, pv, pq, options, numba) ## update data matrices with solution bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, pv, pq) return ppci, success, bus, gen, branch
def _run_fbsw_dense(ppc, ppopt=None): """ DENSE version of distribution power flow solution according to [1] :References: [1] Jen-Hao Teng, "A Direct Approach for Distribution System Load Flow Solutions", IEEE Transactions on Power Delivery, vol. 18, no. 3, pp. 882-887, July 2003. :param ppc: matpower-style case data :return: results (pypower style), success (flag about PF convergence) """ # time_start = time() # path_search = nx.shortest_path # ppci = ext2int(ppc) ppci = ppc ppopt = ppoption(ppopt) baseMVA, bus, gen, branch = \ ppci["baseMVA"], ppci["bus"], ppci["gen"], ppci["branch"] nbus = bus.shape[0] # get bus index lists of each type of bus ref, pv, pq = bustypes(bus, gen) root_bus = ref[ 0] # reference bus is assumed as root bus for a radial network DLF, ppc_fbsw, buses_ordered_fbsw = bibc_bcbv_dense(ppci) baseMVA_fbsw, bus_fbsw, gen_fbsw, branch_fbsw = \ ppc_fbsw["baseMVA"], ppc_fbsw["bus"], ppc_fbsw["gen"], ppc_fbsw["branch"] time_start = time() # initialize voltages to flat start and buses with gens to their setpoints V0 = np.ones(nbus, dtype=complex) V0[gen[:, GEN_BUS].astype(int)] = gen[:, VG] Sbus_fbsw = makeSbus(baseMVA_fbsw, bus_fbsw, gen_fbsw) # update data matrices with solution Ybus_fbsw, Yf_fbsw, Yt_fbsw = makeYbus(baseMVA_fbsw, bus_fbsw, branch_fbsw) ## get bus index lists of each type of bus ref_fbsw, pv_fbsw, pq_fbsw = bustypes(bus_fbsw, gen_fbsw) ##----- run the power flow ----- # ### # LF initialization and calculation V_final, success = fbsw_dense(DLF, bus_fbsw, gen_fbsw, branch_fbsw, baseMVA_fbsw, Ybus_fbsw, Sbus_fbsw, V0, ref_fbsw, pv_fbsw, pq_fbsw, ppopt=ppopt) V_final = V_final[np.argsort( buses_ordered_fbsw)] # return bus voltages in original bus order ppci["et"] = time() - time_start Sbus = makeSbus(baseMVA, bus, gen) # update data matrices with solution Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V_final, ref, pv, pq) ppci["success"] = success ##----- output results ----- ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch results = ppci return results, success
def fbsw(DLF, bus, gen, branch, baseMVA, Ybus, Sbus, V0, ref, pv, pq, ppopt=None, tol_inner=1e-2): """ distribution power flow solution according to [1] :param DLF: direct-Load-Flow matrix which relates bus current injections to voltage drops from the root bus :param bus: buses martix :param gen: generators matrix :param branch: branches matrix :param baseMVA: :param Ybus: bus admittance matrix :param Sbus: vector of power injections :param V0: initial voltage state vector :param ref: reference bus index :param pv: PV buses indices :param pq: PQ buses indices :return: power flow result :References: [1] Jen-Hao Teng, "A Direct Approach for Distribution System Load Flow Solutions", IEEE Transactions on Power Delivery, vol. 18, no. 3, pp. 882-887, July 2003. """ ## options if ppopt is None: ppopt = ppoption() tol = ppopt['PF_TOL'] max_it = ppopt['PF_MAX_IT_GS'] # maximum iterations from Gauss-Seidel verbose = ppopt['VERBOSE'] nbus = bus.shape[0] mask_root = ~(bus[:, BUS_TYPE] == 3) # mask for eliminating root bus root_bus_i = ref Vref = V0[ref] bus_ind_mask_dict = dict(zip(bus[mask_root, BUS_I], range(nbus - 1))) # detect PV buses busPV_ind_mask = [bus_ind_mask_dict[pvbus] for pvbus in pv] genPV_ind = np.where(np.in1d(gen[:, GEN_BUS], pv))[0].flatten() # compute shunt admittance # if Psh is the real power consumed by the shunt at V = 1.0 p.u. and Qsh is the reactive power injected by # the shunt at V = 1.0 p.u. then Psh - j Qsh = V * conj(Ysh * V) = conj(Ysh) = Gs - j Bs, # vector of shunt admittances Ysh = (bus[:, GS] + 1j * bus[:, BS]) / baseMVA # Line charging susceptance BR_B is also added as shunt admittance: # summation of charging susceptances per each bus Gch_f = -np.bincount(branch[:, F_BUS].real.astype(int), weights=branch[:, BR_B].imag / 2, minlength=nbus) Bch_f = np.bincount(branch[:, F_BUS].real.astype(int), weights=branch[:, BR_B].real / 2, minlength=nbus) Gch_t = -np.bincount(branch[:, T_BUS].real.astype(int), weights=branch[:, BR_B].imag / 2, minlength=nbus) Bch_t = np.bincount(branch[:, T_BUS].real.astype(int), weights=branch[:, BR_B].real / 2, minlength=nbus) Ysh += (Gch_f + Gch_t) + 1j * ( Bch_f + Bch_t) # adding line charging to shunt impedance vector V_iter = V0[mask_root].copy() # initial voltage vector without root bus V = V0.copy() Iinj = np.conj( Sbus[mask_root] / V_iter) - Ysh[mask_root] * V_iter # Initial current injections n_iter = 0 converged = 0 while not converged and n_iter < max_it: n_iter_inner = 0 n_iter += 1 deltaV = DLF * Iinj V_new = np.ones(nbus - 1) * Vref + deltaV # ## # inner loop for considering PV buses inner_loop_converged = False V_inner = V_new.copy() success_inner = 1 while not inner_loop_converged and len(pv) > 0: Vmis = (np.abs(gen[genPV_ind, VG]))**2 - (np.abs( V_inner[busPV_ind_mask]))**2 dQ = (Vmis / (2 * DLF[busPV_ind_mask, busPV_ind_mask].A1.imag)).flatten() gen[genPV_ind, QG] += dQ if (gen[genPV_ind, QG] < gen[genPV_ind, QMIN]).any(): Qviol_ind = np.argwhere( (gen[genPV_ind, QG] < gen[genPV_ind, QMIN])).flatten() gen[genPV_ind[Qviol_ind], QG] = gen[genPV_ind[Qviol_ind], QMIN] elif (gen[genPV_ind, QG] > gen[genPV_ind, QMAX]).any(): Qviol_ind = np.argwhere( (gen[genPV_ind, QG] < gen[genPV_ind, QMIN])).flatten() gen[genPV_ind[Qviol_ind], QG] = gen[genPV_ind[Qviol_ind], QMAX] Sbus = makeSbus(baseMVA, bus, gen) Iinj = np.conj( Sbus[mask_root] / V_inner) - Ysh[mask_root] * V_inner deltaV = DLF * Iinj V_inner = np.ones(nbus - 1) * V0[root_bus_i] + deltaV if n_iter_inner > 20 or np.any( np.abs(V_inner[busPV_ind_mask]) > 2): success_inner = 0 break # raise ConvergenceError("\n\t inner iterations (PV nodes) did not converge!!!") n_iter_inner += 1 if np.all(np.abs(dQ) < tol_inner ): # inner loop termination criterion inner_loop_converged = True V_new = V_inner.copy() if not success_inner: if verbose: sys.stdout.write( "\nFwd-back sweep power flow did not converge in " "{0} iterations.\n".format(n_iter)) break # testing termination criterion - V = np.insert(V_new, root_bus_i, Vref) mis = V * np.conj(Ybus * V) - Sbus F = np.r_[mis[pv].real, mis[pq].real, mis[pq].imag] # check tolerance normF = np.linalg.norm(F, np.Inf) # deltaVmax = np.max(np.abs(V_new - V_iter)) if normF < tol: converged = 1 if verbose: sys.stdout.write("\nFwd-back sweep power flow converged in " "{0} iterations.\n".format(n_iter)) V_iter = V_new.copy() # update iterating complex voltage vector # updating injected currents Iinj = np.conj(Sbus[mask_root] / V_iter) - Ysh[mask_root] * V_iter return V, converged
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 runcpf(basecasedata=None, targetcasedata=None, ppopt=None, fname='', solvedcase=''): # default arguments if basecasedata is None: basecasedata = join(dirname(__file__), 'case9') if targetcasedata is None: targetcasedata = join(dirname(__file__), 'case9target') ppopt = ppoption(ppopt) # options verbose = ppopt["VERBOSE"] step = ppopt["CPF_STEP"] parameterization = ppopt["CPF_PARAMETERIZATION"] adapt_step = ppopt["CPF_ADAPT_STEP"] cb_args = ppopt["CPF_USER_CALLBACK_ARGS"] # set up callbacks callback_names = ["cpf_default_callback"] if len(ppopt["CPF_USER_CALLBACK"]) > 0: if isinstance(ppopt["CPF_USER_CALLBACK"], list): callback_names = r_[callback_names, ppopt["CPF_USER_CALLBACK"]] else: callback_names.append(ppopt["CPF_USER_CALLBACK"]) callbacks = [] for callback_name in callback_names: callbacks.append(getattr(cpf_callbacks, callback_name)) # read base case data ppcbase = loadcase(basecasedata) nb = ppcbase["bus"].shape[0] # add zero columns to branch for flows if needed if ppcbase["branch"].shape[1] < QT: ppcbase["branch"] = c_[ppcbase["branch"], zeros((ppcbase["branch"].shape[0], QT - ppcbase["branch"].shape[1] + 1))] # convert to internal indexing ppcbase = ext2int(ppcbase) baseMVAb, busb, genb, branchb = \ ppcbase["baseMVA"], ppcbase["bus"], ppcbase["gen"], ppcbase["branch"] # get bus index lists of each type of bus ref, pv, pq = bustypes(busb, genb) # generator info onb = find(genb[:, GEN_STATUS] > 0) # which generators are on? gbusb = genb[onb, GEN_BUS].astype(int) # what buses are they at? # read target case data ppctarget = loadcase(targetcasedata) # add zero columns to branch for flows if needed if ppctarget["branch"].shape[1] < QT: ppctarget["branch"] = c_[ppctarget["branch"], zeros( (ppctarget["branch"].shape[0], QT - ppctarget["branch"].shape[1] + 1))] # convert to internal indexing ppctarget = ext2int(ppctarget) baseMVAt, bust, gent, brancht = \ ppctarget["baseMVA"], ppctarget["bus"], ppctarget["gen"], ppctarget["branch"] # get bus index lists of each type of bus # ref, pv, pq = bustypes(bust, gent) # generator info ont = find(gent[:, GEN_STATUS] > 0) # which generators are on? gbust = gent[ont, 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"])) stdout.write(' -- AC Continuation Power Flow\n') # initial state # V0 = ones(bus.shape[0]) ## flat start V0 = busb[:, VM] * exp(1j * pi / 180 * busb[:, VA]) vcb = ones(V0.shape) # create mask of voltage-controlled buses vcb[pq] = 0 # exclude PQ buses k = find(vcb[gbusb]) # in-service gens at v-c buses V0[gbusb[k]] = genb[onb[k], VG] / abs(V0[gbusb[k]]) * V0[gbusb[k]] # build admittance matrices Ybus, Yf, Yt = makeYbus(baseMVAb, busb, branchb) # compute base case complex bus power injections (generation - load) Sbusb = makeSbus(baseMVAb, busb, genb) # compute target case complex bus power injections (generation - load) Sbust = makeSbus(baseMVAt, bust, gent) # scheduled transfer Sxfr = Sbust - Sbusb # Run the base case power flow solution if verbose > 2: ppopt_pf = ppoption(ppopt, VERBOSE=max(0, verbose - 1)) else: ppopt_pf = ppoption(ppopt, VERBOSE=max(0, verbose - 2)) lam = 0 V, success, iterations = newtonpf(Ybus, Sbusb, V0, ref, pv, pq, ppopt_pf) if verbose > 2: print('step %3d : lambda = %6.3f\n' % (0, 0)) elif verbose > 1: print('step %3d : lambda = %6.3f, %2d Newton steps\n', (0, 0, iterations)) lamprv = lam # lam at previous step Vprv = V # V at previous step continuation = 1 cont_steps = 0 # input args for callbacks cb_data = { "ppc_base": ppcbase, "ppc_target": ppctarget, "Sxfr": Sxfr, "Ybus": Ybus, "Yf": Yf, "Yt": Yt, "ref": ref, "pv": pv, "pq": pq, "ppopt": ppopt } cb_state = {} # invoke callbacks for k in range(len(callbacks)): cb_state, _ = callbacks[k](cont_steps, V, lam, V, lam, cb_data, cb_state, cb_args) if linalg.norm(Sxfr) == 0: if verbose: print( 'base case and target case have identical load and generation\n' ) continuation = 0 V0 = V lam0 = lam # tangent predictor z = [dx;dlam] z = zeros(2 * len(V) + 1) z[-1] = 1.0 while continuation: cont_steps = cont_steps + 1 # prediction for next step V0, lam0, z = cpf_predictor(V, lam, Ybus, Sxfr, pv, pq, step, z, Vprv, lamprv, parameterization) # save previous voltage, lambda before updating Vprv = V lamprv = lam # correction V, success, i, lam = cpf_corrector(Ybus, Sbusb, V0, ref, pv, pq, lam0, Sxfr, Vprv, lamprv, z, step, parameterization, ppopt_pf) if not success: continuation = 0 if verbose: print( 'step %3d : lambda = %6.3f, corrector did not converge in %d iterations\n' % (cont_steps, lam, i)) break if verbose > 2: print('step %3d : lambda = %6.3f\n' % (cont_steps, lam)) elif verbose > 1: print('step %3d : lambda = %6.3f, %2d corrector Newton steps\n' % (cont_steps, lam, i)) # invoke callbacks for k in range(len(callbacks)): cb_state, _ = callbacks[k](cont_steps, V, lam, V0, lam0, cb_data, cb_state, cb_args) if isinstance(ppopt["CPF_STOP_AT"], str): if ppopt["CPF_STOP_AT"].upper() == "FULL": if abs(lam) < 1e-8: # traced the full continuation curve if verbose: print( '\nTraced full continuation curve in %d continuation steps\n' % cont_steps) continuation = 0 elif lam < lamprv and lam - step < 0: # next step will overshoot step = lam # modify step-size parameterization = 1 # change to natural parameterization adapt_step = False # disable step-adaptivity else: # == 'NOSE' if lam < lamprv: # reached the nose point if verbose: print( '\nReached steady state loading limit in %d continuation steps\n' % cont_steps) continuation = 0 else: if lam < lamprv: if verbose: print( '\nReached steady state loading limit in %d continuation steps\n' % cont_steps) continuation = 0 elif abs(ppopt["CPF_STOP_AT"] - lam) < 1e-8: # reached desired lambda if verbose: print( '\nReached desired lambda %3.2f in %d continuation steps\n' % (ppopt["CPF_STOP_AT"], cont_steps)) continuation = 0 # will reach desired lambda in next step elif lam + step > ppopt["CPF_STOP_AT"]: step = ppopt["CPF_STOP_AT"] - lam # modify step-size parameterization = 1 # change to natural parameterization adapt_step = False # disable step-adaptivity if adapt_step and continuation: pvpq = r_[pv, pq] # Adapt stepsize cpf_error = linalg.norm( r_[angle(V[pq]), abs(V[pvpq]), lam] - r_[angle(V0[pq]), abs(V0[pvpq]), lam0], inf) if cpf_error < ppopt["CPF_ERROR_TOL"]: # Increase stepsize step = step * ppopt["CPF_ERROR_TOL"] / cpf_error if step > ppopt["CPF_STEP_MAX"]: step = ppopt["CPF_STEP_MAX"] else: # decrese stepsize step = step * ppopt["CPF_ERROR_TOL"] / cpf_error if step < ppopt["CPF_STEP_MIN"]: step = ppopt["CPF_STEP_MIN"] # invoke callbacks if success: cpf_results = {} for k in range(len(callbacks)): cb_state, cpf_results = callbacks[k](cont_steps, V, lam, V0, lam0, cb_data, cb_state, cb_args, results=cpf_results, is_final=True) else: cpf_results["iterations"] = i # update bus and gen matrices to reflect the loading and generation # at the noise point bust[:, PD] = busb[:, PD] + lam * (bust[:, PD] - busb[:, PD]) bust[:, QD] = busb[:, QD] + lam * (bust[:, QD] - busb[:, QD]) gent[:, PG] = genb[:, PG] + lam * (gent[:, PG] - genb[:, PG]) # update data matrices with solution bust, gent, brancht = pfsoln(baseMVAt, bust, gent, brancht, Ybus, Yf, Yt, V, ref, pv, pq) ppctarget["et"] = time() - t0 ppctarget["success"] = success # ----- output results ----- # convert back to original bus numbering & print results ppctarget["bus"], ppctarget["gen"], ppctarget[ "branch"] = bust, gent, brancht if success: n = cpf_results["iterations"] + 1 cpf_results["V_p"] = i2e_data(ppctarget, cpf_results["V_p"], full((nb, n), nan), "bus", 0) cpf_results["V_c"] = i2e_data(ppctarget, cpf_results["V_c"], full((nb, n), nan), "bus", 0) results = int2ext(ppctarget) results["cpf"] = cpf_results # 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 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
def _bfswpf(DLF, bus, gen, branch, baseMVA, Ybus, Sbus, Ibus, V0, ref, pv, pq, buses_ordered_bfs_nets, enforce_q_lims, tolerance_kva, max_iteration, **kwargs): """ distribution power flow solution according to [1] :param DLF: direct-Load-Flow matrix which relates bus current injections to voltage drops from the root bus :param bus: buses martix :param gen: generators matrix :param branch: branches matrix :param baseMVA: :param Ybus: bus admittance matrix :param Sbus: vector of power injections :param V0: initial voltage state vector :param ref: reference bus index :param pv: PV buses indices :param pq: PQ buses indices :param buses_ordered_bfs_nets: buses ordered according to breadth-first search :return: power flow result """ # setting options tolerance_mva = tolerance_kva * 1e-3 max_it = max_iteration # maximum iterations verbose = kwargs["VERBOSE"] # verbose is set in run._runpppf() # # tolerance for the inner loop for PV nodes if 'tolerance_kva_pv' in kwargs: tol_mva_inner = kwargs['tolerance_kva_pv'] * 1e-3 else: tol_mva_inner = 1.e-2 if 'max_iter_pv' in kwargs: max_iter_pv = kwargs['max_iter_pv'] else: max_iter_pv = 20 nobus = bus.shape[0] ngen = gen.shape[0] mask_root = ~(bus[:, BUS_TYPE] == 3) # mask for eliminating root bus norefs = len(ref) Ysh = _makeYsh_bfsw(bus, branch, baseMVA) # detect generators on PV buses which have status ON gen_pv = np.in1d(gen[:, GEN_BUS], pv) & (gen[:, GEN_STATUS] > 0) qg_lim = np.zeros( ngen, dtype=bool) # initialize generators which violated Q limits Iinj = np.conj(Sbus / V0) - Ysh * V0 + Ibus # Initial current injections # initiate reference voltage vector V_ref = np.ones(nobus, dtype=complex) for neti, buses_ordered_bfs in enumerate(buses_ordered_bfs_nets): V_ref[buses_ordered_bfs] *= V0[ref[neti]] V = V0.copy() n_iter = 0 converged = 0 if verbose: print(' -- AC Power Flow (Backward/Forward sweep)\n') while not converged and n_iter < max_it: n_iter_inner = 0 n_iter += 1 deltaV = DLF * Iinj[mask_root] V[mask_root] = V_ref[mask_root] + deltaV # ## # inner loop for considering PV buses # TODO improve PV buses inner loop inner_loop_converged = False while not inner_loop_converged and len(pv) > 0: pvi = pv - norefs # internal PV buses indices, assuming reference node is always 0 Vmis = (np.abs(gen[gen_pv, VG]))**2 - (np.abs(V[pv]))**2 # TODO improve getting values from sparse DLF matrix - DLF[pvi, pvi] is unefficient dQ = (Vmis / (2 * DLF[pvi, pvi].A1.imag)).flatten() gen[gen_pv, QG] += dQ if enforce_q_lims: # check Q violation limits ## find gens with violated Q constraints qg_max_lim = (gen[:, QG] > gen[:, QMAX]) & gen_pv qg_min_lim = (gen[:, QG] < gen[:, QMIN]) & gen_pv if qg_min_lim.any(): gen[qg_min_lim, QG] = gen[qg_min_lim, QMIN] bus[gen[qg_min_lim, GEN_BUS].astype(int), BUS_TYPE] = 1 # convert to PQ bus if qg_max_lim.any(): gen[qg_max_lim, QG] = gen[qg_max_lim, QMAX] bus[gen[qg_max_lim, GEN_BUS].astype(int), BUS_TYPE] = 1 # convert to PQ bus # TODO: correct: once all the PV buses are converted to PQ buses, conversion back to PV is not possible qg_lim_new = qg_min_lim | qg_max_lim if qg_lim_new.any(): pq2pv = (qg_lim != qg_lim_new) & qg_lim # convert PQ to PV bus if pq2pv.any(): bus[gen[qg_max_lim, GEN_BUS].astype(int), BUS_TYPE] = 2 # convert to PV bus qg_lim = qg_lim_new.copy() ref, pv, pq = bustypes(bus, gen) # avoid calling makeSbus, update only Sbus for pv nodes Sbus = makeSbus(baseMVA, bus, gen) Iinj = np.conj(Sbus / V) - Ysh * V + Ibus deltaV = DLF * Iinj[mask_root] V[mask_root] = V_ref[mask_root] + deltaV if n_iter_inner > max_iter_pv: raise LoadflowNotConverged( " FBSW Power Flow did not converge - inner iterations for PV nodes " "reached maximum value of {0}!".format(max_iter_pv)) n_iter_inner += 1 if np.all(np.abs(dQ) < tol_mva_inner ): # inner loop termination criterion inner_loop_converged = True # testing termination criterion - F = _evaluate_Fx(Ybus, V, Sbus, pv, pq, Ibus=Ibus) # check tolerance converged = _check_for_convergence(F, tolerance_mva) if converged and verbose: print("\nFwd-back sweep power flow converged in " "{0} iterations.\n".format(n_iter)) # updating injected currents Iinj = np.conj(Sbus / V) - Ysh * V + Ibus return V, converged
def _bfswpf(DLF, bus, gen, branch, baseMVA, Ybus, bus_ord_bfsw, Sbus, V0, ref, pv, pq, enforce_q_lims, tolerance_kva, max_iteration, **kwargs): """ distribution power flow solution according to [1] :param DLF: direct-Load-Flow matrix which relates bus current injections to voltage drops from the root bus :param bus: buses martix :param gen: generators matrix :param branch: branches matrix :param baseMVA: :param Ybus: bus admittance matrix :param Sbus: vector of power injections :param V0: initial voltage state vector :param ref: reference bus index :param pv: PV buses indices :param pq: PQ buses indices :return: power flow result :References: [1] Jen-Hao Teng, "A Direct Approach for Distribution System Load Flow Solutions", IEEE Transactions on Power Delivery, vol. 18, no. 3, pp. 882-887, July 2003. """ # setting options tolerance_mva = tolerance_kva * 1e-3 max_it = max_iteration # maximum iterations verbose = kwargs["VERBOSE"] # verbose is set in run._runpppf() # # tolerance for the inner loop for PV nodes if 'tolerance_kva_pv' in kwargs: tol_mva_inner = kwargs['tolerance_kva_pv'] * 1e-3 else: tol_mva_inner = 1.e-2 if 'max_iter_pv' in kwargs: max_iter_pv = kwargs['max_iter_pv'] else: max_iter_pv = 20 nbus = bus.shape[0] ngen = gen.shape[0] mask_root = ~(bus[:, BUS_TYPE] == 3) # mask for eliminating root bus root_bus_i = ref Vref = V0[ref] # bus_ind_mask_dict = dict(zip(bus[mask_root, BUS_I], range(nbus - 1))) bus_ord_bfsw_inv = np.argsort(bus_ord_bfsw) # compute shunt admittance # if Psh is the real power consumed by the shunt at V = 1.0 p.u. and Qsh is the reactive power injected by # the shunt at V = 1.0 p.u. then Psh - j Qsh = V * conj(Ysh * V) = conj(Ysh) = Gs - j Bs, # vector of shunt admittances Ysh = (bus[:, GS] + 1j * bus[:, BS]) / baseMVA # Line charging susceptance BR_B is also added as shunt admittance: # summation of charging susceptances per each bus stat = branch[:, BR_STATUS] ## ones at in-service branches Ys = stat / (branch[:, BR_R] + 1j * branch[:, BR_X]) ysh = (-branch[:, BR_B].imag + 1j * (branch[:, BR_B].real)) / 2 tap = branch[:, TAP] # * np.exp(1j * np.pi / 180 * branch[:, SHIFT]) ysh_f = Ys * (1 - tap) / (tap * np.conj(tap)) + ysh / (tap * np.conj(tap)) ysh_t = Ys * (tap - 1) / tap + ysh Gch = (np.bincount( branch[:, F_BUS].real.astype(int), weights=ysh_f.real, minlength=nbus) + np.bincount(branch[:, T_BUS].real.astype(int), weights=ysh_t.real, minlength=nbus)) Bch = (np.bincount( branch[:, F_BUS].real.astype(int), weights=ysh_f.imag, minlength=nbus) + np.bincount(branch[:, T_BUS].real.astype(int), weights=ysh_t.imag, minlength=nbus)) Ysh += Gch + 1j * Bch # Gch_f = - np.bincount(branch[:, F_BUS].real.astype(int), weights=branch[:, BR_B].imag / 2, minlength=nbus) # Bch_f = np.bincount(branch[:, F_BUS].real.astype(int), weights=branch[:, BR_B].real / 2, minlength=nbus) # Gch_t = - np.bincount(branch[:, T_BUS].real.astype(int), weights=branch[:, BR_B].imag / 2, minlength=nbus) # Bch_t = np.bincount(branch[:, T_BUS].real.astype(int), weights=branch[:, BR_B].real / 2, minlength=nbus) # Ysh += (Gch_f + Gch_t) + 1j * (Bch_f + Bch_t) # adding line charging to shunt impedance vector # detect generators on PV buses which have status ON gen_pv = np.in1d(gen[:, GEN_BUS], pv) & (gen[:, GEN_STATUS] > 0) qg_lim = np.zeros( ngen, dtype=bool) #initialize generators which violated Q limits V_iter = V0[mask_root].copy() # initial voltage vector without root bus V = V0.copy() Iinj = np.conj(Sbus / V) - Ysh * V # Initial current injections n_iter = 0 converged = 0 if verbose: print(' -- AC Power Flow Backward/Forward sweep\n') while not converged and n_iter < max_it: n_iter_inner = 0 n_iter += 1 deltaV = DLF * Iinj[mask_root] V_iter = np.ones(nbus - 1) * Vref + deltaV # ## # inner loop for considering PV buses inner_loop_converged = False while not inner_loop_converged and len(pv) > 0: pvi = pv - 1 # internal PV buses indices, assuming reference node is always 0 Vmis = (np.abs(gen[gen_pv, VG]))**2 - (np.abs(V_iter[pvi]))**2 dQ = (Vmis / (2 * DLF[pvi, pvi].A1.imag)).flatten() gen[gen_pv, QG] += dQ if enforce_q_lims: #check Q violation limits ## find gens with violated Q constraints qg_max_lim = (gen[:, QG] > gen[:, QMAX]) & gen_pv qg_min_lim = (gen[:, QG] < gen[:, QMIN]) & gen_pv if qg_min_lim.any(): gen[qg_min_lim, QG] = gen[qg_min_lim, QMIN] bus[gen[qg_min_lim, GEN_BUS].astype(int), BUS_TYPE] = 1 # convert to PQ bus if qg_max_lim.any(): gen[qg_max_lim, QG] = gen[qg_max_lim, QMAX] bus[gen[qg_max_lim, GEN_BUS].astype(int), BUS_TYPE] = 1 # convert to PQ bus # TODO: correct: once all the PV buses are converted to PQ buses, conversion back to PV is not possible qg_lim_new = qg_min_lim | qg_max_lim if qg_lim_new.any(): pq2pv = (qg_lim != qg_lim_new) & qg_lim # convert PQ to PV bus if pq2pv.any(): bus[gen[qg_max_lim, GEN_BUS].astype(int), BUS_TYPE] = 2 # convert to PV bus qg_lim = qg_lim_new.copy() ref, pv, pq = bustypes(bus, gen) Sbus = makeSbus(baseMVA, bus, gen) V = np.insert(V_iter, root_bus_i, Vref) Iinj = np.conj(Sbus / V) - Ysh * V deltaV = DLF * Iinj[mask_root] V_iter = np.ones(nbus - 1) * V0[root_bus_i] + deltaV if n_iter_inner > max_iter_pv: raise LoadflowNotConverged( " FBSW Power Flow did not converge - inner iterations for PV nodes " "reached maximum value of {0}!".format(max_iter_pv)) n_iter_inner += 1 if np.all(np.abs(dQ) < tol_mva_inner ): # inner loop termination criterion inner_loop_converged = True # testing termination criterion - V = np.insert(V_iter, root_bus_i, Vref) mis = V * np.conj(Ybus * V) - Sbus F = np.r_[mis[pv].real, mis[pq].real, mis[pq].imag] # check tolerance normF = np.linalg.norm(F, np.Inf) if normF < tolerance_mva: converged = 1 if verbose: print("\nFwd-back sweep power flow converged in " "{0} iterations.\n".format(n_iter)) elif n_iter == max_it: raise LoadflowNotConverged( " FBSW Power Flow did not converge - " "reached maximum iterations = {0}!".format(max_it)) # updating injected currents Iinj = np.conj(Sbus / V) - Ysh * V return V, converged
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 _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
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 _run_bfswpf(ppc, options, **kwargs): """ SPARSE version of distribution power flow solution according to [1] :References: [1] Jen-Hao Teng, "A Direct Approach for Distribution System Load Flow Solutions", IEEE Transactions on Power Delivery, vol. 18, no. 3, pp. 882-887, July 2003. :param ppc: matpower-style case data :return: results (pypower style), success (flag about PF convergence) """ time_start = time() # starting pf calculation timing tap_shift = ppc['branch'][:, SHIFT].real enforce_q_lims, tolerance_kva, max_iteration, calculate_voltage_angles, numba = _get_options( options) numba, makeYbus = _import_numba_extensions_if_flag_is_true(numba) ppci = ppc baseMVA, bus, gen, branch = \ ppci["baseMVA"], ppci["bus"], ppci["gen"], ppci["branch"] nbus = bus.shape[0] # generate results for original bus ordering Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) # get bus index lists of each type of bus ref, pv, pq = bustypes(bus, gen) # creating networkx graph from list of branches G = nx.Graph() G.add_edges_from((int(fb), int(tb), { "shift": float(shift) }) for fb, tb, shift in list( zip(branch[:, F_BUS].real, branch[:, T_BUS].real, tap_shift))) if not nx.is_connected(G): Graphs = list(nx.connected_component_subgraphs(G)) else: Graphs = [G] V_final = np.zeros(nbus, dtype=complex) V_tapshifts = np.zeros(nbus) for subi, G in enumerate(Graphs): ppci_sub = _cut_ppc(ppci, G.nodes()) nbus_sub = len(G) # depth-first-search bus ordering and generating Direct Load Flow matrix DLF = BCBV * BIBC DLF, ppc_bfsw, buses_ordered_bfsw = _bibc_bcbv(ppci_sub, G) ppc_bfsw['branch'][:, SHIFT] = 0 baseMVA_bfsw, bus_bfsw, gen_bfsw, branch_bfsw, ref_bfsw, pv_bfsw, pq_bfsw,\ on, gbus, V0 = _get_pf_variables_from_ppci(ppc_bfsw) Sbus_bfsw = makeSbus(baseMVA_bfsw, bus_bfsw, gen_bfsw) Ybus_bfsw, Yf_bfsw, Yt_bfsw = makeYbus(baseMVA_bfsw, bus_bfsw, branch_bfsw) # #----- run the power flow ----- V_res, success = _bfswpf(DLF, bus_bfsw, gen_bfsw, branch_bfsw, baseMVA, Ybus_bfsw, buses_ordered_bfsw, Sbus_bfsw, V0, ref_bfsw, pv_bfsw, pq_bfsw, enforce_q_lims, tolerance_kva, max_iteration, **kwargs) V_final[ buses_ordered_bfsw] = V_res # return bus voltages in original bus order # TODO: find the better way to consider transformer phase shift and remove this workaround if calculate_voltage_angles: predecessors = nx.bfs_predecessors(G, ref[subi]) branches = list(zip(branch[:, F_BUS].real, branch[:, T_BUS].real)) for bus_start in predecessors.iterkeys(): bus_pred = bus_start bus_next = bus_start while predecessors.get(bus_next) is not None: bus_next = predecessors.get(bus_pred) shift_angle = G.get_edge_data(bus_pred, bus_next)['shift'] if (bus_pred, bus_next) in branches: V_tapshifts[bus_start] += shift_angle else: V_tapshifts[bus_start] -= shift_angle bus_pred = bus_next V_final *= np.exp(1j * np.pi / 180 * V_tapshifts) # #----- output results to ppc ------ ppci["et"] = time() - time_start # pf time end bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V_final, ref, pv, pq) ppci["success"] = success ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch return ppci, success
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 runpf_fast(Ybus, Yf, Yt, ref, pv, pq, on, ppc, 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 ## options ## read data #ppc = loadcase(casedata) ## convert to internal indexing ppc["branch"][:, [0, 1]] -= 1 ppc["bus"][:, 0] -= 1 ppc["gen"][:, 0] -= 1 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 #print(gen[:, GEN_STATUS]) #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() V0 = bus[:, VM] * exp(1j * 0.017453292519943295 * bus[:, VA]) V0[gbus] = gen[on, VG] / abs(V0[gbus]) * V0[gbus] ## 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 V, success, i = newtonpf_fast(Ybus, Sbus, V0, ref, pv, pq, ppopt) ## update data matrices with solution #bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, pv, pq) bus[:, VM] = abs(V) bus[:, VA] = angle(V) * 180 / pi #UNTIL HERE 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 ppc["branch"][:, [0, 1]] += 1 ppc["bus"][:, 0] += 1 ppc["gen"][:, 0] += 1 return ppc, success, i
def _run_bfswpf(ppci, options, **kwargs): """ SPARSE version of distribution power flow solution according to [1] :References: [1] Jen-Hao Teng, "A Direct Approach for Distribution System Load Flow Solutions", IEEE Transactions on Power Delivery, vol. 18, no. 3, pp. 882-887, July 2003. :param ppci: matpower-style case data :param options: pf options :return: results (pypower style), success (flag about PF convergence) """ time_start = time() # starting pf calculation timing baseMVA, bus, gen, branch, ref, pv, pq, \ on, gbus, V0 = _get_pf_variables_from_ppci(ppci) enforce_q_lims, tolerance_kva, max_iteration, calculate_voltage_angles, numba = _get_options( options) numba, makeYbus = _import_numba_extensions_if_flag_is_true(numba) nobus = bus.shape[0] nobranch = branch.shape[0] # generate Sbus Sbus = makeSbus(baseMVA, bus, gen) # generate results for original bus ordering # Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) ppci, Ybus, Yf, Yt = _get_Y_bus(ppci, options, makeYbus, baseMVA, bus, branch) # creating network graph from list of branches bus_from = branch[:, F_BUS].real.astype(int) bus_to = branch[:, T_BUS].real.astype(int) G = csr_matrix((np.ones(nobranch), (bus_from, bus_to)), shape=(nobus, nobus)) # create spanning trees using breadth-first-search # TODO add efficiency warning if a network is heavy-meshed G_trees = [] for refbus in ref: G_trees.append(csgraph.breadth_first_tree(G, refbus, directed=False)) # depth-first-search bus ordering and generating Direct Load Flow matrix DLF = BCBV * BIBC ppci, DLF, buses_ordered_bfs_nets = _get_bibc_bcbv( ppci, options, bus, branch, G) # if there are trafos with phase-shift calculate Ybus without phase-shift for bfswpf any_trafo_shift = (branch[:, SHIFT] != 0).any() if any_trafo_shift: branch_noshift = branch.copy() branch_noshift[:, SHIFT] = 0 Ybus_noshift, Yf_noshift, _ = makeYbus(baseMVA, bus, branch_noshift) else: Ybus_noshift = Ybus.copy() # get current injections for constant-current loads Ibus = _get_ibus(ppci) # #----- run the power flow ----- V_final, success = _bfswpf(DLF, bus, gen, branch, baseMVA, Ybus_noshift, Sbus, Ibus, V0, ref, pv, pq, buses_ordered_bfs_nets, enforce_q_lims, tolerance_kva, max_iteration, **kwargs) # if phase-shifting trafos are present adjust final state vector angles accordingly if calculate_voltage_angles and any_trafo_shift: brch_shift_mask = branch[:, SHIFT] != 0 trafos_shift = dict( list( zip( list( zip(branch[brch_shift_mask, F_BUS].real.astype(int), branch[brch_shift_mask, T_BUS].real.astype(int))), branch[brch_shift_mask, SHIFT].real))) for trafo_ind, shift_degree in iteritems(trafos_shift): neti = 0 # if multiple reference nodes, find in which network trafo is located if len(ref) > 0: for refbusi in range(len(ref)): if trafo_ind[0] in buses_ordered_bfs_nets[refbusi]: neti = refbusi break G_tree = G_trees[neti] buses_ordered_bfs = buses_ordered_bfs_nets[neti] if (np.argwhere(buses_ordered_bfs == trafo_ind[0]) < np.argwhere(buses_ordered_bfs == trafo_ind[1])): lv_bus = trafo_ind[1] shift_degree *= -1 else: lv_bus = trafo_ind[0] buses_shifted_from_root = csgraph.breadth_first_order( G_tree, lv_bus, directed=True, return_predecessors=False) V_final[buses_shifted_from_root] *= np.exp(1j * np.pi / 180 * shift_degree) # #----- output results to ppc ------ ppci["et"] = time() - time_start # pf time end bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V_final, ref) # bus, gen, branch = pfsoln_bfsw(baseMVA, bus, gen, branch, V_final, ref, pv, pq, BIBC, ysh_f,ysh_t,Iinj, Sbus) ppci["success"] = success ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch return ppci, success
def _bfswpf(DLF, bus, gen, branch, baseMVA, Ybus, Sbus, V0, ref, pv, pq, buses_ordered_bfs_nets, enforce_q_lims, tolerance_kva, max_iteration, **kwargs): """ distribution power flow solution according to [1] :param DLF: direct-Load-Flow matrix which relates bus current injections to voltage drops from the root bus :param bus: buses martix :param gen: generators matrix :param branch: branches matrix :param baseMVA: :param Ybus: bus admittance matrix :param Sbus: vector of power injections :param V0: initial voltage state vector :param ref: reference bus index :param pv: PV buses indices :param pq: PQ buses indices :return: power flow result """ # setting options tolerance_mva = tolerance_kva * 1e-3 max_it = max_iteration # maximum iterations verbose = kwargs["VERBOSE"] # verbose is set in run._runpppf() # # tolerance for the inner loop for PV nodes if 'tolerance_kva_pv' in kwargs: tol_mva_inner = kwargs['tolerance_kva_pv'] * 1e-3 else: tol_mva_inner = 1.e-2 if 'max_iter_pv' in kwargs: max_iter_pv = kwargs['max_iter_pv'] else: max_iter_pv = 20 nobus = bus.shape[0] ngen = gen.shape[0] mask_root = ~(bus[:, BUS_TYPE] == 3) # mask for eliminating root bus norefs = len(ref) # compute shunt admittance # if Psh is the real power consumed by the shunt at V = 1.0 p.u. and Qsh is the reactive power injected by # the shunt at V = 1.0 p.u. then Psh - j Qsh = V * conj(Ysh * V) = conj(Ysh) = Gs - j Bs, # vector of shunt admittances Ysh = (bus[:, GS] + 1j * bus[:, BS]) / baseMVA # Line charging susceptance BR_B is also added as shunt admittance: # summation of charging susceptances per each bus stat = branch[:, BR_STATUS] ## ones at in-service branches Ys = stat / (branch[:, BR_R] + 1j * branch[:, BR_X]) ysh = (-branch[:, BR_B].imag + 1j * (branch[:, BR_B].real)) / 2 tap = branch[:, TAP] # * np.exp(1j * np.pi / 180 * branch[:, SHIFT]) ysh_f = Ys * (1 - tap) / (tap * np.conj(tap)) + ysh / (tap * np.conj(tap)) ysh_t = Ys * (tap - 1) / tap + ysh Gch = (np.bincount( branch[:, F_BUS].real.astype(int), weights=ysh_f.real, minlength=nobus) + np.bincount(branch[:, T_BUS].real.astype(int), weights=ysh_t.real, minlength=nobus)) Bch = (np.bincount( branch[:, F_BUS].real.astype(int), weights=ysh_f.imag, minlength=nobus) + np.bincount(branch[:, T_BUS].real.astype(int), weights=ysh_t.imag, minlength=nobus)) Ysh += Gch + 1j * Bch # detect generators on PV buses which have status ON gen_pv = np.in1d(gen[:, GEN_BUS], pv) & (gen[:, GEN_STATUS] > 0) qg_lim = np.zeros( ngen, dtype=bool) #initialize generators which violated Q limits Iinj = np.conj(Sbus / V0) - Ysh * V0 # Initial current injections # initiate reference voltage vector V_ref = np.ones(nobus, dtype=complex) for neti, buses_ordered_bfs in enumerate(buses_ordered_bfs_nets): V_ref[buses_ordered_bfs] *= V0[ref[neti]] V = V0.copy() n_iter = 0 converged = 0 if verbose: print(' -- AC Power Flow (Backward/Forward sweep)\n') while not converged and n_iter < max_it: n_iter_inner = 0 n_iter += 1 deltaV = DLF * Iinj[mask_root] V[mask_root] = V_ref[mask_root] + deltaV # ## # inner loop for considering PV buses # TODO improve PV buses inner loop inner_loop_converged = False while not inner_loop_converged and len(pv) > 0: pvi = pv - norefs # internal PV buses indices, assuming reference node is always 0 Vmis = (np.abs(gen[gen_pv, VG]))**2 - (np.abs(V[pv]))**2 # TODO improve getting values from sparse DLF matrix - DLF[pvi, pvi] is unefficient dQ = (Vmis / (2 * DLF[pvi, pvi].A1.imag)).flatten() gen[gen_pv, QG] += dQ if enforce_q_lims: #check Q violation limits ## find gens with violated Q constraints qg_max_lim = (gen[:, QG] > gen[:, QMAX]) & gen_pv qg_min_lim = (gen[:, QG] < gen[:, QMIN]) & gen_pv if qg_min_lim.any(): gen[qg_min_lim, QG] = gen[qg_min_lim, QMIN] bus[gen[qg_min_lim, GEN_BUS].astype(int), BUS_TYPE] = 1 # convert to PQ bus if qg_max_lim.any(): gen[qg_max_lim, QG] = gen[qg_max_lim, QMAX] bus[gen[qg_max_lim, GEN_BUS].astype(int), BUS_TYPE] = 1 # convert to PQ bus # TODO: correct: once all the PV buses are converted to PQ buses, conversion back to PV is not possible qg_lim_new = qg_min_lim | qg_max_lim if qg_lim_new.any(): pq2pv = (qg_lim != qg_lim_new) & qg_lim # convert PQ to PV bus if pq2pv.any(): bus[gen[qg_max_lim, GEN_BUS].astype(int), BUS_TYPE] = 2 # convert to PV bus qg_lim = qg_lim_new.copy() ref, pv, pq = bustypes(bus, gen) # avoid calling makeSbus, update only Sbus for pv nodes Sbus = makeSbus(baseMVA, bus, gen) Iinj = np.conj(Sbus / V) - Ysh * V deltaV = DLF * Iinj[mask_root] V[mask_root] = V_ref[mask_root] + deltaV if n_iter_inner > max_iter_pv: raise LoadflowNotConverged( " FBSW Power Flow did not converge - inner iterations for PV nodes " "reached maximum value of {0}!".format(max_iter_pv)) n_iter_inner += 1 if np.all(np.abs(dQ) < tol_mva_inner ): # inner loop termination criterion inner_loop_converged = True # testing termination criterion - mis = V * np.conj(Ybus * V) - Sbus F = np.r_[mis[pv].real, mis[pq].real, mis[pq].imag] # check tolerance normF = np.linalg.norm(F, np.Inf) if normF < tolerance_mva: converged = 1 if verbose: print("\nFwd-back sweep power flow converged in " "{0} iterations.\n".format(n_iter)) elif n_iter == max_it: raise LoadflowNotConverged( " FBSW Power Flow did not converge - " "reached maximum iterations = {0}!".format(max_it)) # updating injected currents Iinj = np.conj(Sbus / V) - Ysh * V return V, converged