def _get_Sbus(ppci, recycle=None): baseMVA, bus, gen = ppci["baseMVA"], ppci["bus"], ppci["gen"] if not isinstance(recycle, dict) or "Sbus" not in ppci["internal"]: return makeSbus(baseMVA, bus, gen) if recycle["bus_pq"] or recycle["gen"]: return makeSbus(baseMVA, bus, gen) return ppci["internal"]["Sbus"]
def _run_ac_pf_without_qlims_enforced(ppci, recycle, makeYbus, ppopt): baseMVA, bus, gen, branch, ref, pv, pq, _, gbus, V0, ref_gens = _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, it = _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, ref_gens) return ppci, success, bus, gen, branch, it
def _run_dc_pf(ppci): t0 = time() baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, _, refgen = _get_pf_variables_from_ppci( ppci) ## initial state Va0 = bus[:, VA] * (pi / 180.) ## build B matrices and phase shift injections B, Bf, Pbusinj, Pfinj = makeBdc(bus, branch) ## updates Bbus matrix ppci['internal']['Bbus'] = B ## 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[:, VA] = Va * (180. / pi) ## update Pg for slack generators ## (note: other gens at ref bus are accounted for in Pbus) ## Pg = Pinj + Pload + Gs ## newPg = oldPg + newPinj - oldPinj ## ext_grid (refgen) buses refgenbus = gen[refgen, GEN_BUS].astype(int) ## number of ext_grids (refgen) at those buses ext_grids_bus = bincount(refgenbus) gen[refgen, PG] = real(gen[refgen, PG] + (B[refgenbus, :] * Va - Pbus[refgenbus]) * baseMVA / ext_grids_bus[refgenbus]) # store results from DC powerflow for AC powerflow et = time() - t0 success = True iterations = 1 ppci = _store_results_from_pf_in_ppci(ppci, bus, gen, branch, success, iterations, et) return ppci
def _run_ac_pf_without_qlims_enforced(ppci, options): makeYbus, pfsoln = _get_numba_functions(ppci, options) baseMVA, bus, gen, branch, ref, pv, pq, _, _, V0, ref_gens = _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, iterations, ppci["internal"]["J"], ppci["internal"][ "Vm_it"], ppci["internal"]["Va_it"] = newtonpf(Ybus, Sbus, V0, pv, pq, ppci, options) ## update data matrices with solution bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, ref_gens) return ppci, success, iterations, bus, gen, branch
def newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppci, options): """Solves the power flow using a full Newton's method. Solves for bus voltages given the full system admittance matrix (for all buses), the complex bus power injection vector (for all buses), the initial vector of complex bus voltages, and column vectors with the lists of bus indices for the swing bus, PV buses, and PQ buses, respectively. The bus voltage vector contains the set point for generator (including ref bus) buses, and the reference angle of the swing bus, as well as an initial guess for remaining magnitudes and angles. @see: L{runpf} @author: Ray Zimmerman (PSERC Cornell) @author: Richard Lincoln Modified by University of Kassel (Florian Schaefer) to use numba """ # options tol = options['tolerance_mva'] max_it = options["max_iteration"] numba = options["numba"] iwamoto = options["algorithm"] == "iwamoto_nr" voltage_depend_loads = options["voltage_depend_loads"] dist_slack = options["distributed_slack"] v_debug = options["v_debug"] use_umfpack = options["use_umfpack"] permc_spec = options["permc_spec"] baseMVA = ppci['baseMVA'] bus = ppci['bus'] gen = ppci['gen'] slack_weights = bus[:, SL_FAC].astype(float64) ## contribution factors for distributed slack # initialize i = 0 V = V0 Va = angle(V) Vm = abs(V) dVa, dVm = None, None if iwamoto: dVm, dVa = zeros_like(Vm), zeros_like(Va) if v_debug: Vm_it = Vm.copy() Va_it = Va.copy() else: Vm_it = None Va_it = None # set up indexing for updating V if dist_slack and len(ref) > 1: pv = r_[ref[1:], pv] ref = ref[[0]] pvpq = r_[pv, pq] # reference buses are always at the top, no matter where they are in the grid (very confusing...) # so in the refpvpq, the indices must be adjusted so that ref bus(es) starts with 0 # todo: is it possible to simplify the indices/lookups and make the code clearer? # for columns: columns are in the normal order in Ybus; column numbers for J are reduced by 1 internally refpvpq = r_[ref, pvpq] # generate lookup pvpq -> index pvpq (used in createJ): # shows for a given row from Ybus, which row in J it becomes # e.g. the first row in J is a PV bus. If the first PV bus in Ybus is in the row 2, the index of the row in Jbus must be 0. # pvpq_lookup will then have a 0 at the index 2 pvpq_lookup = zeros(max(Ybus.indices) + 1, dtype=int) if dist_slack: # slack bus is relevant for the function createJ_ds pvpq_lookup[refpvpq] = arange(len(refpvpq)) else: pvpq_lookup[pvpq] = arange(len(pvpq)) # get jacobian function createJ = get_fastest_jacobian_function(pvpq, pq, numba, dist_slack) nref = len(ref) npv = len(pv) npq = len(pq) j1 = 0 j2 = npv # j1:j2 - V angle of pv buses j3 = j2 j4 = j2 + npq # j3:j4 - V angle of pq buses j5 = j4 j6 = j4 + npq # j5:j6 - V mag of pq buses j7 = j6 j8 = j6 + nref # j7:j8 - slacks # make initial guess for the slack slack = (gen[:, PG].sum() - bus[:, PD].sum()) / baseMVA # evaluate F(x0) F = _evaluate_Fx(Ybus, V, Sbus, ref, pv, pq, slack_weights, dist_slack, slack) converged = _check_for_convergence(F, tol) Ybus = Ybus.tocsr() J = None # do Newton iterations while (not converged and i < max_it): # update iteration counter i = i + 1 J = create_jacobian_matrix(Ybus, V, ref, refpvpq, pvpq, pq, createJ, pvpq_lookup, nref, npv, npq, numba, slack_weights, dist_slack) dx = -1 * spsolve(J, F, permc_spec=permc_spec, use_umfpack=use_umfpack) # update voltage if npv and not iwamoto: Va[pv] = Va[pv] + dx[j1:j2] if npq and not iwamoto: Va[pq] = Va[pq] + dx[j3:j4] Vm[pq] = Vm[pq] + dx[j5:j6] if dist_slack: slack = slack + dx[j7:j8] # iwamoto multiplier to increase convergence if iwamoto: Vm, Va = _iwamoto_step(Ybus, J, F, dx, pq, npv, npq, dVa, dVm, Vm, Va, pv, j1, j2, j3, j4, j5, j6) V = Vm * exp(1j * Va) Vm = abs(V) # update Vm and Va again in case Va = angle(V) # we wrapped around with a negative Vm if v_debug: Vm_it = column_stack((Vm_it, Vm)) Va_it = column_stack((Va_it, Va)) if voltage_depend_loads: Sbus = makeSbus(baseMVA, bus, gen, vm=Vm) F = _evaluate_Fx(Ybus, V, Sbus, ref, pv, pq, slack_weights, dist_slack, slack) converged = _check_for_convergence(F, tol) return V, converged, i, J, Vm_it, Va_it
def newtonpf(Ybus, Sbus, V0, pv, pq, ppci, options): """Solves the power flow using a full Newton's method. Solves for bus voltages given the full system admittance matrix (for all buses), the complex bus power injection vector (for all buses), the initial vector of complex bus voltages, and column vectors with the lists of bus indices for the swing bus, PV buses, and PQ buses, respectively. The bus voltage vector contains the set point for generator (including ref bus) buses, and the reference angle of the swing bus, as well as an initial guess for remaining magnitudes and angles. @see: L{runpf} @author: Ray Zimmerman (PSERC Cornell) @author: Richard Lincoln Modified by University of Kassel (Florian Schaefer) to use numba """ ## options tol = options['tolerance_mva'] max_it = options["max_iteration"] numba = options["numba"] iwamoto = options["algorithm"] == "iwamoto_nr" voltage_depend_loads = options["voltage_depend_loads"] v_debug = options["v_debug"] baseMVA = ppci['baseMVA'] bus = ppci['bus'] gen = ppci['gen'] ## initialize i = 0 V = V0 Va = angle(V) Vm = abs(V) dVa, dVm = None, None if iwamoto: dVm, dVa = zeros_like(Vm), zeros_like(Va) if v_debug: Vm_it = Vm.copy() Va_it = Va.copy() else: Vm_it = None Va_it = None ## set up indexing for updating V pvpq = r_[pv, pq] # generate lookup pvpq -> index pvpq (used in createJ) pvpq_lookup = zeros(max(Ybus.indices) + 1, dtype=int) pvpq_lookup[pvpq] = arange(len(pvpq)) # get jacobian function createJ = get_fastest_jacobian_function(pvpq, pq, numba) npv = len(pv) npq = len(pq) j1 = 0 j2 = npv ## j1:j2 - V angle of pv buses j3 = j2 j4 = j2 + npq ## j3:j4 - V angle of pq buses j5 = j4 j6 = j4 + npq ## j5:j6 - V mag of pq buses ## evaluate F(x0) F = _evaluate_Fx(Ybus, V, Sbus, pv, pq) converged = _check_for_convergence(F, tol) Ybus = Ybus.tocsr() J = None ## do Newton iterations while (not converged and i < max_it): ## update iteration counter i = i + 1 J = create_jacobian_matrix(Ybus, V, pvpq, pq, createJ, pvpq_lookup, npv, npq, numba) dx = -1 * spsolve(J, F) ## update voltage if npv and not iwamoto: Va[pv] = Va[pv] + dx[j1:j2] if npq and not iwamoto: Va[pq] = Va[pq] + dx[j3:j4] Vm[pq] = Vm[pq] + dx[j5:j6] # iwamoto multiplier to increase convergence if iwamoto: Vm, Va = _iwamoto_step(Ybus, J, F, dx, pq, npv, npq, dVa, dVm, Vm, Va, pv, j1, j2, j3, j4, j5, j6) V = Vm * exp(1j * Va) Vm = abs(V) ## update Vm and Va again in case Va = angle(V) ## we wrapped around with a negative Vm if v_debug: Vm_it = column_stack((Vm_it, Vm)) Va_it = column_stack((Va_it, Va)) if voltage_depend_loads: Sbus = makeSbus(baseMVA, bus, gen, vm=Vm) F = _evaluate_Fx(Ybus, V, Sbus, pv, pq) converged = _check_for_convergence(F, tol) return V, converged, i, J, Vm_it, Va_it
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, _, gbus, V0, ref_gens = _get_pf_variables_from_ppci(ppci) enforce_q_lims, tolerance_mva, 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() # #----- run the power flow ----- V_final, success = _bfswpf(DLF, bus, gen, branch, baseMVA, Ybus_noshift, Sbus, V0, ref, pv, pq, buses_ordered_bfs_nets, options, **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, ref_gens) # 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, options, **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 """ enforce_q_lims = options["enforce_q_lims"] tolerance_mva = options["tolerance_mva"] max_iteration = options["max_iteration"] voltage_depend_loads = options["voltage_depend_loads"] # setting options 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_mva_pv' in kwargs: tol_mva_inner = kwargs['tolerance_mva_pv'] 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 # 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, vm=abs(V)) if voltage_depend_loads else 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 - if voltage_depend_loads: Sbus = makeSbus(baseMVA, bus, gen, vm=abs(V)) F = _evaluate_Fx(Ybus, V, Sbus, pv, pq) # 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 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], 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 decoupledpf(Ybus, Sbus, V0, pv, pq, ppci, options): """Solves the power flow using a fast decoupled method. Solves for bus voltages given the full system admittance matrix (for all buses), the complex bus power injection vector (for all buses), the initial vector of complex bus voltages, the FDPF matrices B prime and B double prime, and column vectors with the lists of bus indices for the swing bus, PV buses, and PQ buses, respectively. The bus voltage vector contains the set point for generator (including ref bus) buses, and the reference angle of the swing bus, as well as an initial guess for remaining magnitudes and angles. C{ppopt} is a PYPOWER options vector which can be used to set the termination tolerance, maximum number of iterations, and output options (see L{ppoption} for details). Uses default options if this parameter is not given. Returns the final complex voltages, a flag which indicates whether it converged or not, and the number of iterations performed. @see: L{runpf} @author: Ray Zimmerman (PSERC Cornell) Modified to consider voltage_depend_loads """ # old algortihm options to the new ones pp2pypower_algo = {'fdbx': 2, 'fdxb': 3} # options tol = options["tolerance_mva"] max_it = options["max_iteration"] # No use currently for numba. TODO: Check if can be applied in Bp and Bpp # numba = options["numba"] # NOTE: options["algorithm"] is either 'fdbx' or 'fdxb'. Otherwise, error algorithm = pp2pypower_algo[options["algorithm"]] voltage_depend_loads = options["voltage_depend_loads"] v_debug = options["v_debug"] baseMVA = ppci["baseMVA"] bus = ppci["bus"] branch = ppci["branch"] gen = ppci["gen"] # initialize i = 0 V = V0 Va = angle(V) Vm = abs(V) dVa, dVm = None, None if v_debug: Vm_it = Vm.copy() Va_it = Va.copy() else: Vm_it = None Va_it = None # set up indexing for updating V pvpq = r_[pv, pq] # evaluate initial mismatch P, Q = _evaluate_mis(Ybus, V, Sbus, pvpq, pq) # check tolerance converged = _check_for_convergence(P, Q, tol) # create and reduce B matrices Bp, Bpp = makeB(baseMVA, bus, real(branch), algorithm) # splu requires a CSC matrix Bp = Bp[array([pvpq]).T, pvpq].tocsc() Bpp = Bpp[array([pq]).T, pq].tocsc() # factor B matrices Bp_solver = splu(Bp) Bpp_solver = splu(Bpp) # do P and Q iterations while (not converged and i < max_it): # update iteration counter i = i + 1 # ----- do P iteration, update Va ----- dVa = -Bp_solver.solve(P) # update voltage Va[pvpq] = Va[pvpq] + dVa V = Vm * exp(1j * Va) # evalute mismatch P, Q = _evaluate_mis(Ybus, V, Sbus, pvpq, pq) # check tolerance if _check_for_convergence(P, Q, tol): converged = True break # ----- do Q iteration, update Vm ----- dVm = -Bpp_solver.solve(Q) # update voltage Vm[pq] = Vm[pq] + dVm V = Vm * exp(1j * Va) if v_debug: Vm_it = column_stack((Vm_it, Vm)) Va_it = column_stack((Va_it, Va)) if voltage_depend_loads: Sbus = makeSbus(baseMVA, bus, gen, vm=Vm) # evalute mismatch P, Q = _evaluate_mis(Ybus, V, Sbus, pvpq, pq) # check tolerance if _check_for_convergence(P, Q, tol): converged = True break # the newtonpf/newtonpf funtion returns J. We are returning Bp and Bpp return V, converged, i, Bp, Bpp, Vm_it, Va_it
def ts_newtonpf(self, net): options = net["_options"] bus = self.ppci["bus"] branch = self.ppci["branch"] gen = self.ppci["gen"] # compute complex bus power injections [generation - load] # self.Cg = _get_Cg(gen_on, bus) # Sbus = _get_Sbus(self.baseMVA, bus, gen, self.Cg) Sbus = makeSbus(self.baseMVA, bus, gen) # run the newton power flow V, success, _, _, _, _ = nr_pf.newtonpf(self.Ybus, Sbus, self.V, self.pv, self.pq, self.ppci, options) if not success: logger.warning("Loadflow not converged") logger.info("Lines of of service:") logger.info(net.line[~net.line.in_service]) raise LoadflowNotConverged("Power Flow did not converge after") if self.ppci["gen"].shape[ 0] == 1 and not options["voltage_depend_loads"]: pfsoln = pf_solution_single_slack else: pfsoln = pfsoln_full bus, gen, branch = pfsoln(self.baseMVA, bus, gen, branch, self.Ybus, self.Yf, self.Yt, V, self.ref, self.ref_gens, Ibus=self.Ibus) self.ppci["bus"] = bus self.ppci["branch"] = branch self.ppci["gen"] = gen self.ppci["success"] = success self.ppci["et"] = None # ppci doesn't contain out of service elements, but ppc does -> copy results accordingly self.ppc = _copy_results_ppci_to_ppc(self.ppci, self.ppc, options["mode"]) # raise if PF was not successful. If DC -> success is always 1 if self.ppc["success"] != 1: _clean_up(net, res=False) else: net["_ppc"] = self.ppc net["converged"] = True self.V = V _extract_results(net, self.ppc) return net
pp_net._pd2ppc_lookups = { "bus": np.array([], dtype=int), "ext_grid": np.array([], dtype=int), "gen": np.array([], dtype=int), "branch": np.array([], dtype=int) } # convert pandapower net to ppc ppc, ppci = _pd2ppc(pp_net) baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, _, refgen = _get_pf_variables_from_ppci( ppci) Va0 = bus[:, VA] * (np.pi / 180.) B, Bf, Pbusinj, Pfinj = makeBdc(bus, branch) from pandapower.pypower.idx_brch import F_BUS, T_BUS, BR_X, TAP, SHIFT, BR_STATUS Pbus = makeSbus(baseMVA, bus, gen) - Pbusinj - bus[:, GS] / baseMVA Pbus_pp_ro = Pbus[pp_vect_converter] error_p = np.abs(np.real(Sdc_me) - np.real(Pbus_pp_ro)) test_ok = True #### pandapower DC algo (yet another one) Va = copy.deepcopy(Va0) pvpq = np.r_[pv, pq] pvpq_matrix = B[pvpq.T, :].tocsc()[:, pvpq] ref_matrix = np.transpose(Pbus[pvpq] - B[pvpq.T, :].tocsc()[:, ref] * Va0[ref]) Va[pvpq] = np.real(scipy.sparse.linalg.spsolve(pvpq_matrix, ref_matrix)) #### if np.max(error_p) > tol: test_ok = False print(
def _aux_test(self, pn_net): with tempfile.TemporaryDirectory() as path: case_name = os.path.join(path, "this_case.json") pp.to_json(pn_net, case_name) real_init_file = pp.from_json(case_name) backend = LightSimBackend() with warnings.catch_warnings(): warnings.filterwarnings("ignore") backend.load_grid(case_name) nb_sub = backend.n_sub pp_net = backend.init_pp_backend._grid # first i deactivate all slack bus in pp that are connected but not handled in ls pp_net.ext_grid["in_service"].loc[:] = False pp_net.ext_grid["in_service"].iloc[0] = True conv = backend.runpf() conv_pp = backend.init_pp_backend.runpf() assert conv_pp, "Error: pandapower do not converge, impossible to perform the necessary checks" assert conv, "Error: lightsim do not converge" por_pp, qor_pp, vor_pp, aor_pp = copy.deepcopy( backend.init_pp_backend.lines_or_info()) pex_pp, qex_pp, vex_pp, aex_pp = copy.deepcopy( backend.init_pp_backend.lines_ex_info()) # I- Check for divergence and equality of flows" por_ls, qor_ls, vor_ls, aor_ls = backend.lines_or_info() max_mis = np.max(np.abs(por_ls - por_pp)) assert max_mis <= self.tol, f"Error: por do not match, maximum absolute error is {max_mis:.5f} MW" max_mis = np.max(np.abs(qor_ls - qor_pp)) assert max_mis <= self.tol, f"Error: qor do not match, maximum absolute error is {max_mis:.5f} MVAr" max_mis = np.max(np.abs(vor_ls - vor_pp)) assert max_mis <= self.tol, f"Error: vor do not match, maximum absolute error is {max_mis:.5f} kV" max_mis = np.max(np.abs(aor_ls - aor_pp)) assert max_mis <= self.tol, f"Error: aor do not match, maximum absolute error is {max_mis:.5f} A" # "II - Check for possible solver issues" with warnings.catch_warnings(): warnings.filterwarnings("ignore") pp.runpp(backend.init_pp_backend._grid, v_debug=True) v_tmp = backend.init_pp_backend._grid.res_bus[ "vm_pu"].values[:nb_sub] + 0j v_tmp *= np.exp( 1j * np.pi / 180. * backend.init_pp_backend._grid.res_bus["va_degree"].values[:nb_sub]) v_tmp = np.concatenate((v_tmp, v_tmp)) backend._grid.ac_pf(v_tmp, 1000, 1e-5) Y_pp = backend.init_pp_backend._grid._ppc["internal"]["Ybus"] Sbus = backend.init_pp_backend._grid._ppc["internal"]["Sbus"] pv_ = backend.init_pp_backend._grid._ppc["internal"]["pv"] pq_ = backend.init_pp_backend._grid._ppc["internal"]["pq"] max_iter = 10 tol_this = 1e-8 All_Vms = backend.init_pp_backend._grid._ppc["internal"]["Vm_it"] AllVas = backend.init_pp_backend._grid._ppc["internal"]["Va_it"] for index_V in range(All_Vms.shape[1] - 1, -1, -1): nb_iter = All_Vms.shape[1] - 1 # i check from easiest to hardest, so from the last iteartion of pandapower to the first iteration of pandapower # take the same V as pandapower V_init = All_Vms[:, index_V] * (np.cos(AllVas[:, index_V]) + 1j * np.sin(AllVas[:, index_V])) # V_init *= np.exp(1j * AllVas[:, 0]) V_init_ref = copy.deepcopy(V_init) solver = ClassSolver() solver.solve(scipy.sparse.csc_matrix(Y_pp), V_init, Sbus, pv_, pq_, max_iter, tol_this) time_for_nr = solver.get_timers()[3] if TIMER_INFO: print( f"\t Info: Time to perform {nb_iter - index_V} NR iterations for a grid with {nb_sub} " f"buses: {1000. * time_for_nr:.2f}ms") error_va = np.abs( solver.get_Va() - np.angle(backend.init_pp_backend._grid._ppc["internal"]["V"])) assert np.max(error_va) <= self.tol, f"Error: VA do not match for iteration {index_V}, maximum absolute " \ f"error is {np.max(error_va):.5f} rad" error_vm = np.abs( np.abs(solver.get_Vm() - np.abs( backend.init_pp_backend._grid._ppc["internal"]["V"]))) assert np.max(error_vm) <= self.tol, f"\t Error: VM do not match for iteration {index_V}, maximum absolute " \ f"error is {np.max(error_vm):.5f} pu" solver.reset() if TIMER_INFO: print("") # 'III - Check the data conversion' pp_vect_converter = backend.init_pp_backend._grid._pd2ppc_lookups[ "bus"][:nb_sub] pp_net = backend.init_pp_backend._grid # 1) Checking Sbus conversion Sbus_pp = backend.init_pp_backend._grid._ppc["internal"]["Sbus"] Sbus_pp_right_order = Sbus_pp[pp_vect_converter] Sbus_me = backend._grid.get_Sbus() error_p = np.abs(np.real(Sbus_me) - np.real(Sbus_pp_right_order)) assert np.max(error_p) <= self.tol, f"\t Error: P do not match for Sbus, maximum absolute error is " \ f"{np.max(error_p):.5f} MW, \t Error: significative difference for bus " \ f"index (lightsim): {np.where(error_p > self.tol)[0]}" error_q = np.abs(np.imag(Sbus_me) - np.imag(Sbus_pp_right_order)) assert np.max(error_q) <= self.tol, f"\t Error: Q do not match for Sbus, maximum absolute error is " \ f"{np.max(error_q):.5f} MVAr, \t Error: significative difference for bus " \ f"index (lightsim): {np.where(error_q > self.tol)[0]}" # 2) Checking Ybus conversion" Y_me = backend._grid.get_Ybus() Y_pp = backend.init_pp_backend._grid._ppc["internal"]["Ybus"] Y_pp_right_order = Y_pp[pp_vect_converter.reshape(nb_sub, 1), pp_vect_converter.reshape(1, nb_sub)] error_p = np.abs(np.real(Y_me) - np.real(Y_pp_right_order)) assert np.max(error_p) <= self.tol, f"Error: P do not match for Ybus, maximum absolute error " \ f"is {np.max(error_p):.5f}" error_q = np.abs(np.imag(Y_me) - np.imag(Y_pp_right_order)) assert np.max(error_q) <= self.tol, f"\t Error: Q do not match for Ybus, maximum absolute error is " \ f"{np.max(error_q):.5f}" # "IV - Check for the initialization (dc powerflow)" # 1) check that the results are same for dc lightsim and dc pandapower Vinit = np.ones(backend.nb_bus_total, dtype=np.complex_) * pp_net["_options"]["init_vm_pu"] backend._grid.deactivate_result_computation() Vdc = backend._grid.dc_pf(Vinit, max_iter, tol_this) backend._grid.reactivate_result_computation() Ydc_me = backend._grid.get_Ybus() Sdc_me = backend._grid.get_Sbus() assert np.max(np.abs(V_init_ref[pp_vect_converter] - Vdc[:nb_sub])) <= 100.*self.tol,\ f"\t Error for the DC approximation: resulting voltages are different " \ f"{np.max(np.abs(V_init_ref[pp_vect_converter] - Vdc[:nb_sub])):.5f}pu" if np.max(np.abs(V_init_ref[pp_vect_converter] - Vdc[:nb_sub])) >= self.tol: warnings.warn( "\t Warning: maximum difference after DC approximation is " "{np.max(np.abs(V_init_ref[pp_vect_converter] - Vdc[:nb_sub])):.5f} which is higher than " "the tolerance (this is just a warning because we noticed this could happen even if the " "results match perfectly. Probably some conversion issue with complex number and " "radian / degree.") # "2) check that the Sbus vector is same for PP and lightisim in DC" from pandapower.pd2ppc import _pd2ppc from pandapower.pf.run_newton_raphson_pf import _get_pf_variables_from_ppci from pandapower.pypower.idx_brch import F_BUS, T_BUS, BR_X, TAP, SHIFT, BR_STATUS from pandapower.pypower.idx_bus import VA, GS from pandapower.pypower.makeBdc import makeBdc from pandapower.pypower.makeSbus import makeSbus pp_net._pd2ppc_lookups = { "bus": np.array([], dtype=int), "ext_grid": np.array([], dtype=int), "gen": np.array([], dtype=int), "branch": np.array([], dtype=int) } # convert pandapower net to ppc ppc, ppci = _pd2ppc(pp_net) baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, _, refgen = _get_pf_variables_from_ppci( ppci) Va0 = bus[:, VA] * (np.pi / 180.) B, Bf, Pbusinj, Pfinj = makeBdc(bus, branch) Pbus = makeSbus(baseMVA, bus, gen) - Pbusinj - bus[:, GS] / baseMVA Pbus_pp_ro = Pbus[pp_vect_converter] error_p = np.abs(np.real(Sdc_me) - np.real(Pbus_pp_ro)) test_ok = True #### pandapower DC algo (yet another one) Va = copy.deepcopy(Va0) pvpq = np.r_[pv, pq] pvpq_matrix = B[pvpq.T, :].tocsc()[:, pvpq] ref_matrix = np.transpose(Pbus[pvpq] - B[pvpq.T, :].tocsc()[:, ref] * Va0[ref]) Va[pvpq] = np.real(scipy.sparse.linalg.spsolve(pvpq_matrix, ref_matrix)) #### assert np.max(error_p) <= self.tol, f"\t Error: P do not match for Sbus (dc), maximum absolute error is " \ f"{np.max(error_p):.5f} MW, \nError: significative difference for bus " \ f"index (lightsim): {np.where(error_p > self.tol)[0]}" error_q = np.abs(np.imag(Sdc_me) - np.imag(Pbus_pp_ro)) assert np.max(error_q) <= self.tol, f"\t Error: Q do not match for Sbus (dc), maximum absolute error is " \ f"{np.max(error_q):.5f} MVAr, \n\t Error: significative difference for " \ f"bus index (lightsim): {np.where(error_q > self.tol)[0]}" # "3) check that the Ybus matrix is same for PP and lightisim in DC" with warnings.catch_warnings(): warnings.filterwarnings("ignore") pp.rundcpp(pp_net) Ydc_pp = backend.init_pp_backend._grid._ppc["internal"]["Bbus"] Ydc_pp_right_order = Ydc_pp[pp_vect_converter.reshape(nb_sub, 1), pp_vect_converter.reshape(1, nb_sub)] error_p = np.abs(np.real(Ydc_me) - np.real(Ydc_pp_right_order)) assert np.max(error_p) <= self.tol, f"Error: P do not match for Ybus (dc mode), maximum absolute error " \ f"is {np.max(error_p):.5f}" error_q = np.abs(np.imag(Ydc_me) - np.imag(Ydc_pp_right_order)) assert np.max(error_q) <= self.tol, f"\t Error: Q do not match for Ybus (dc mdoe), maximum absolute error " \ f"is {np.max(error_q):.5f}" # "3) check that lightsim ac pf init with pp dc pf give same results (than pp)" Vinit = np.ones(backend.nb_bus_total, dtype=np.complex_) * pp_net["_options"]["init_vm_pu"] Vinit[:nb_sub] = V_init_ref[pp_vect_converter] conv = backend._grid.ac_pf(Vinit, max_iter, tol_this) assert conv.shape[ 0] > 0, "\t Error: the lightsim diverge when initialized with pandapower Vinit_dc" lpor, lqor, lvor, laor = backend._grid.get_lineor_res() tpor, tqor, tvor, taor = backend._grid.get_trafohv_res() tpex, tqex, tvex, taex = backend._grid.get_trafolv_res() nb_trafo = tpor.shape[0] nb_powerline = lpor.shape[0] p_or_me2 = np.concatenate((lpor, tpor)) q_or_me2 = np.concatenate((lqor, tqor)) v_or_me2 = np.concatenate((lvor, tvor)) a_or_me2 = 1000. * np.concatenate((laor, taor)) test_ok = True # pdb.set_trace() max_mis = np.max(np.abs(p_or_me2 - por_pp)) assert np.max( error_q ) <= self.tol, f"\t Error: por do not match, maximum absolute error is {max_mis:.5f} MW" max_mis = np.max(np.abs(q_or_me2 - qor_pp)) assert np.max( error_q ) <= self.tol, f"\t Error: qor do not match, maximum absolute error is {max_mis:.5f} MVAr" max_mis = np.max(np.abs(v_or_me2 - vor_pp)) assert np.max( error_q ) <= self.tol, f"\t Error: vor do not match, maximum absolute error is {max_mis:.5f} kV" max_mis = np.max(np.abs(a_or_me2 - aor_pp)) assert np.max( error_q ) <= self.tol, f"\t Error: aor do not match, maximum absolute error is {max_mis:.5f} A" # "V - Check trafo proper conversion to r,x, b" from lightsim2grid_cpp import GridModel, PandaPowerConverter, SolverType from pandapower.build_branch import _calc_branch_values_from_trafo_df, get_trafo_values from pandapower.build_branch import _calc_nominal_ratio_from_dataframe, _calc_r_x_y_from_dataframe from pandapower.build_branch import _calc_tap_from_dataframe, BASE_KV, _calc_r_x_from_dataframe # my trafo parameters converter = PandaPowerConverter() converter.set_sn_mva(pp_net.sn_mva) converter.set_f_hz(pp_net.f_hz) tap_neutral = 1.0 * pp_net.trafo["tap_neutral"].values tap_neutral[~np.isfinite(tap_neutral)] = 0. if np.any(tap_neutral != 0.): raise RuntimeError( "lightsim converter supposes that tap_neutral is 0 for the transformers" ) tap_step_pct = 1.0 * pp_net.trafo["tap_step_percent"].values tap_step_pct[~np.isfinite(tap_step_pct)] = 0. tap_pos = 1.0 * pp_net.trafo["tap_pos"].values tap_pos[~np.isfinite(tap_pos)] = 0. shift_ = 1.0 * pp_net.trafo["shift_degree"].values shift_[~np.isfinite(shift_)] = 0. is_tap_hv_side = pp_net.trafo["tap_side"].values == "hv" is_tap_hv_side[~np.isfinite(is_tap_hv_side)] = True if np.any(pp_net.trafo["tap_phase_shifter"].values): raise RuntimeError( "ideal phase shifter are not modeled. Please remove all trafo with " "pp_net.trafo[\"tap_phase_shifter\"] set to True.") tap_angles_ = 1.0 * pp_net.trafo["tap_step_degree"].values tap_angles_[~np.isfinite(tap_angles_)] = 0. tap_angles_ = np.deg2rad(tap_angles_) trafo_r, trafo_x, trafo_b = \ converter.get_trafo_param(tap_step_pct, tap_pos, tap_angles_, # in radian ! is_tap_hv_side, pp_net.bus.loc[pp_net.trafo["hv_bus"]]["vn_kv"], pp_net.bus.loc[pp_net.trafo["lv_bus"]]["vn_kv"], pp_net.trafo["vk_percent"].values, pp_net.trafo["vkr_percent"].values, pp_net.trafo["sn_mva"].values, pp_net.trafo["pfe_kw"].values, pp_net.trafo["i0_percent"].values, ) # pandapower trafo parameters ppc = copy.deepcopy(pp_net._ppc) bus_lookup = pp_net["_pd2ppc_lookups"]["bus"] trafo_df = pp_net["trafo"] lv_bus = get_trafo_values(trafo_df, "lv_bus") vn_lv = ppc["bus"][bus_lookup[lv_bus], BASE_KV] vn_trafo_hv, vn_trafo_lv, shift_pp = _calc_tap_from_dataframe( pp_net, trafo_df) ratio = _calc_nominal_ratio_from_dataframe(ppc, trafo_df, vn_trafo_hv, vn_trafo_lv, bus_lookup) r_t, x_t, b_t = _calc_r_x_y_from_dataframe(pp_net, trafo_df, vn_trafo_lv, vn_lv, pp_net.sn_mva) # check where there are mismatch if any val_r_pp = r_t val_r_me = trafo_r all_equals_r = np.abs(val_r_pp - val_r_me) <= self.tol if not np.all(all_equals_r): test_ok = False print( f"\t Error: some trafo resistance are not equal, max error: {np.max(np.abs(val_r_pp - val_r_me)):.5f}" ) val_x_pp = x_t val_x_me = trafo_x all_equals_x = np.abs(val_x_pp - val_x_me) <= self.tol assert np.all(all_equals_x), f"\t Error: some trafo x are not equal, max error: " \ f"{np.max(np.abs(val_x_pp - val_x_me)):.5f}" val_ib_pp = np.imag(b_t) val_ib_me = np.imag(trafo_b) all_equals_imag_b = np.abs(val_ib_pp - val_ib_me) <= self.tol assert np.all(all_equals_imag_b), f"\t Error: some trafo (imag) b are not equal, max error: " \ f"{np.max(np.abs(val_ib_pp - val_ib_me)):.5f}" val_reb_pp = np.real(b_t) val_reb_me = np.real(trafo_b) all_equals_real_b = np.abs(val_reb_pp - val_reb_me) <= self.tol assert np.all(all_equals_real_b), f"\t Error: some trafo (real) b are not equal, max error: " \ f"{np.max(np.abs(val_reb_pp - val_reb_me)):.5f}"