def calc_branch_results(net, ppci, V): Ybus = ppci["internal"]["Ybus"] Yf = ppci["internal"]["Yf"] Yt = ppci["internal"]["Yt"] baseMVA, bus, gen, branch, ref, _, _, _, _, _, ref_gens = _get_pf_variables_from_ppci(ppci) bus, gen, branch = pfsoln_pypower(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, ref_gens) ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch
def pm_results_to_ppc_results(net, ppc, ppci, result_pm): V = np.zeros(len(ppci["bus"]), dtype="complex") for i, bus in result_pm["solution"]["bus"].items(): V[int(i) - 1] = bus["vm"] * np.exp(1j * bus["va"]) for i, gen in result_pm["solution"]["gen"].items(): ppci["gen"][int(i) - 1, PG] = gen["pg"] ppci["gen"][int(i) - 1, QG] = gen["qg"] ppci["obj"] = result_pm["objective"] ppci["success"] = result_pm["status"] == "LocalOptimal" ppci["et"] = result_pm["solve_time"] ppci["f"] = result_pm["objective"] ppci["internal_gencost"] = result_pm["objective"] makeYbus, pfsoln = _get_numba_functions(ppci, net._options) baseMVA, bus, gen, branch, ref, pv, pq, _, gbus, V0, ref_gens = _get_pf_variables_from_ppci( ppci) Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, ref_gens) ppc["bus"][:, VM] = np.nan ppc["bus"][:, VA] = np.nan result = _copy_results_ppci_to_ppc(ppci, ppc, "opf") return result
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 = _get_Sbus(ppci, options["recycle"])
def _pf_without_branches(ppci, options): Ybus, Yf, Yt = makeYbus_pypower(ppci["baseMVA"], ppci["bus"], ppci["branch"]) baseMVA, bus, gen, branch, ref, _, pq, _, _, V0, ref_gens = _get_pf_variables_from_ppci(ppci) V = ppci["bus"][:, VM] bus, gen, branch = pfsoln_pypower(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, ref_gens) ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch ppci["success"] = True ppci["iterations"] = 1 ppci["et"] = 0 return ppci
def _calc_power_flow(ppci, V): # store results for all elements # calculate branch results (in ppc_i) baseMVA, bus, gen, branch, ref, pv, pq, _, _, _, ref_gens = _get_pf_variables_from_ppci( ppci) Ybus, Yf, Yt = ppci['internal']['Ybus'], ppci['internal']['Yf'], ppci[ 'internal']['Yt'] ppci['bus'], ppci['gen'], ppci['branch'] =\ pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, ref_gens) # calculate bus power injections Sbus = np.multiply(V, np.conj(Ybus * V)) * baseMVA ppci["bus"][:, PD] = -Sbus.real # saved in MW, injection -> demand ppci["bus"][:, QD] = -Sbus.imag # saved in Mvar, injection -> demand return ppci
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 = _get_Sbus(ppci, options["recycle"]) # run the newton power flow if options["lightsim2grid"]: V, success, iterations, J, Vm_it, Va_it = newton_ls( Ybus.tocsc(), Sbus, V0, ref, pv, pq, ppci, options) else: V, success, iterations, J, Vm_it, Va_it = newtonpf( Ybus, Sbus, V0, ref, pv, pq, ppci, options) # keep "internal" variables in memory / net["_ppc"]["internal"] -> needed for recycle. ppci = _store_internal( ppci, { "J": J, "Vm_it": Vm_it, "Va_it": Va_it, "bus": bus, "gen": gen, "branch": branch, "baseMVA": baseMVA, "V": V, "pv": pv, "pq": pq, "ref": ref, "Sbus": Sbus, "ref_gens": ref_gens, "Ybus": Ybus, "Yf": Yf, "Yt": Yt }) return ppci, success, iterations
def _run_dc_pf(ppci): t0 = time() baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, _, _ = _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) ## 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 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) # 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 _run_ac_pf_with_qlims_enforced(ppci, options): baseMVA, bus, gen, branch, ref, pv, pq, on, _, V0, ref_gens = _get_pf_variables_from_ppci(ppci) qlim = options["enforce_q_lims"] limited = [] # list of indices of gens @ Q lims fixedQg = zeros(gen.shape[0]) # Qg of gens at Q limits while True: ppci, success, iterations = _run_ac_pf_without_qlims_enforced(ppci, options) bus, gen, branch = ppci_to_pfsoln(ppci, options) # 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 = setdiff1d(find(gen_status & qg_max_lim), ref_gens) mn = setdiff1d(find(gen_status & qg_min_lim), ref_gens) if len(mx) > 0 or len(mn) > 0: # we have some Q limit violations # 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 = [] # 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].astype(int) # 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, pandapower cannot enforce Q ' # 'limits for slack buses in systems ' # 'with multiple slacks.') changed_gens = gen[mx, GEN_BUS].astype(int) bus[setdiff1d(changed_gens, ref), BUS_TYPE] = PQ # & set bus type to PQ # update bus index lists of each type of bus ref, pv, pq = bustypes(bus, gen) limited = r_[limited, mx].astype(int) else: break # no more generator Q limits violated if 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].astype(int) # 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 return ppci, success, iterations, bus, gen, branch
def runpp(self, net, max_iteration=10, need_reset=True, **kwargs): net_orig = copy.deepcopy(net) pp.runpp(net_orig) V_orig = net_orig._ppc["internal"]["V"] # ---------- pp.run.runpp() ----------------- t0_start = time() t0_options = time() passed_parameters = _passed_runpp_parameters(locals()) _init_runpp_options(net, algorithm="nr", calculate_voltage_angles="auto", init="auto", max_iteration=max_iteration, tolerance_mva=1e-8, trafo_model="t", trafo_loading="current", enforce_q_lims=False, check_connectivity=False, voltage_depend_loads=True, consider_line_temperature=False, passed_parameters=passed_parameters, numba=True, **kwargs) _check_bus_index_and_print_warning_if_high(net) _check_gen_index_and_print_warning_if_high(net) et_options = time() - t0_options # ---------- pp.powerflow._powerflow() ----------------- """ Gets called by runpp or rundcpp with different arguments. """ # get infos from options t0_early_init = time() init_results = net["_options"]["init_results"] ac = net["_options"]["ac"] algorithm = net["_options"]["algorithm"] net["converged"] = False net["OPF_converged"] = False _add_auxiliary_elements(net) if not ac or init_results: verify_results(net) else: reset_results(net, all_empty=False) # TODO remove this when zip loads are integrated for all PF algorithms if algorithm not in ['nr', 'bfsw']: net["_options"]["voltage_depend_loads"] = False _add_auxiliary_elements(net) # convert pandapower net to ppc ppc, self.ppci = _pd2ppc(net) # pdb.set_trace() # store variables net["_ppc"] = ppc if not "VERBOSE" in kwargs: kwargs["VERBOSE"] = 0 # ----- run the powerflow ----- options = net["_options"] et_early_init = time() - t0_early_init # ---------- pp.powerflow._run_pf_algorithm() ---------------- # ---------- pp.pf.run_newton_raphson_pf.run_newton_raphson_pf() ---------------- t0 = time() t0_init = t0 et_init_dc = 0. if need_reset: if isinstance(options["init_va_degree"], str) and options["init_va_degree"] == "dc": self.ppci = _run_dc_pf(self.ppci) et_init_dc = time() - t0 if options["enforce_q_lims"]: raise NotImplementedError("enforce_q_lims not yet implemented") t0_init = time() # ---------- pp.pf.run_newton_raphson_pf._run_ac_pf_without_qlims_enforced ---------- # ppci, success, iterations = _run_ac_pf_without_qlims_enforced(ppci, options) makeYbus, pfsoln = _get_numba_functions(self.ppci, options) self.baseMVA, self.bus, self.gen, self.branch, self.ref, self.pv, self.pq, _, _, V0, self.ref_gens = _get_pf_variables_from_ppci( self.ppci) self.ppci, self.Ybus, self.Yf, self.Yt = _get_Y_bus( self.ppci, options, makeYbus, self.baseMVA, self.bus, self.branch) # TODO i have a problem here for the order of the bus / id of bus tmp_bus_ind = np.argsort(net.bus.index) model = DataModel() # model.set_sn_mva(net.sn_mva) # model.set_f_hz(net.f_hz) # TODO set that elsewhere self.converter.set_sn_mva(net.sn_mva) self.converter.set_f_hz(net.f_hz) # init_but should be called first among all the rest model.init_bus(net.bus.iloc[tmp_bus_ind]["vn_kv"].values, net.line.shape[0], net.trafo.shape[0]) # init the shunts line_r, line_x, line_h = self.converter.get_line_param( net.line["r_ohm_per_km"].values * net.line["length_km"].values, net.line["x_ohm_per_km"].values * net.line["length_km"].values, net.line["c_nf_per_km"].values * net.line["length_km"].values, net.line["g_us_per_km"].values * net.line["length_km"].values, net.bus.loc[net.line["from_bus"]]["vn_kv"], net.bus.loc[net.line["to_bus"]]["vn_kv"]) model.init_powerlines(line_r, line_x, line_h, net.line["from_bus"].values, net.line["to_bus"].values) # init the shunts model.init_shunt(net.shunt["p_mw"].values, net.shunt["q_mvar"].values, net.shunt["bus"].values) # init trafo if net.trafo.shape[0]: trafo_r, trafo_x, trafo_b = self.converter.get_trafo_param( net.trafo["vn_hv_kv"].values, net.trafo["vn_lv_kv"].values, net.trafo["vk_percent"].values, net.trafo["vkr_percent"].values, net.trafo["sn_mva"].values, net.trafo["pfe_kw"].values, net.trafo["i0_percent"].values, net.bus.loc[net.trafo["lv_bus"]]["vn_kv"]) # trafo_branch = ppc["branch"][net.line.shape[0]:, :] tap_step_pct = net.trafo["tap_step_percent"].values tap_step_pct[~np.isfinite(tap_step_pct)] = 0. tap_pos = net.trafo["tap_pos"].values tap_pos[~np.isfinite(tap_pos)] = 0. is_tap_hv_side = net.trafo["tap_side"].values == "hv" is_tap_hv_side[~np.isfinite(tap_pos)] = True model.init_trafo(trafo_r, trafo_x, trafo_b, tap_step_pct, tap_pos, is_tap_hv_side, net.trafo["hv_bus"].values, net.trafo["lv_bus"].values) model.init_loads(net.load["p_mw"].values, net.load["q_mvar"].values, net.load["bus"].values) model.init_generators(net.gen["p_mw"].values, net.gen["vm_pu"].values, net.gen["bus"].values) # TODO better way here! model.add_slackbus(net.ext_grid["bus"].values) # model.init_Ybus() # Ybus = model.get_Ybus() # be careful, the order is not the same between this and pandapower, you need to change it # Ybus_proper_oder = Ybus[np.array([net.bus.index]).T, np.array([net.bus.index])] # self.Ybus_proper_oder = self.Ybus else: pass # TODO update self.ppci with new values of generation - load such that Sbus is properly udpated # compute complex bus power injections [generation - load] Sbus = _get_Sbus(self.ppci, False) # Sbus_me = model.get_Sbus() # pdb.set_trace() # Sbus_me_r = np.real(Sbus_me) # Va0 = np.full(net.bus.shape[0], fill_value=net["_options"]["init_vm_pu"], dtype=np.complex_) # Va0[net.ext_grid["bus"].values] = net.ext_grid["vm_pu"].values * np.exp(1j * net.ext_grid["va_degree"].values / 360. * 2 * np.pi) #dctheta = model.dc_pf(Sbus_me_r, Va0) # self.dctheta = V0[tmp_bus_ind] # self.dcYbus = self.ppci["internal"]['Bbus'][np.array([tmp_bus_ind]).T, np.array([tmp_bus_ind])] # tmpdc = np.abs(dcYbus - self.dcYbus) # pv_me = model.get_pv() # pq_me = model.get_pq() # pdb.set_trace() # run the newton power flow # ------------------- pp.pypower.newtonpf --------------------- max_it = options["max_iteration"] tol = options['tolerance_mva'] self.Ybus = sparse.csc_matrix(self.Ybus) et_init = time() - t0_init t0__ = time() if need_reset: # reset the solver self.solver.reset() self.V = 1.0 * copy.deepcopy(V0) else: # reuse previous voltages pass self.solver.solve(self.Ybus, self.V, Sbus, self.pv, self.pq, max_it, tol) et__ = time() - t0__ t0_ = time() Va = self.solver.get_Va() Vm = self.solver.get_Vm() self.V = Vm * np.exp(1j * Va) J = self.solver.get_J() success = self.solver.converged() iterations = self.solver.get_nb_iter() # timer_Fx_, timer_solve_, timer_initialize_, timer_check_, timer_dSbus_, timer_fillJ_, timer_total_nr_ timers = self.solver.get_timers() et_ = time() - t0_ # ---------------------- pp.pypower.newtonpf --------------------- self.ppci = _store_internal( self.ppci, { "J": J, "Vm_it": None, "Va_it": None, "bus": self.bus, "gen": self.gen, "branch": self.branch, "baseMVA": self.baseMVA, "V": self.V, "pv": self.pv, "pq": self.pq, "ref": self.ref, "Sbus": Sbus, "ref_gens": self.ref_gens, "Ybus": self.Ybus, "Yf": self.Yf, "Yt": self.Yt, "timers": timers, "time_get_res": et_, "time_solve": et__, "time_init": et_init, "time_init_dc": et_init_dc, "time_early_init": et_early_init, "time_options": et_options }) t0_ppci_to_pfsoln = time() # update data matrices with solution store in ppci # ---------- pp.pf.run_newton_raphson_pf._run_ac_pf_without_qlims_enforced ---------- self.bus, self.gen, self.branch = ppci_to_pfsoln(self.ppci, options) te_ppci_to_pfsoln = time() - t0_ppci_to_pfsoln # these are the values from pypower / matlab t0_store_res = time() et = t0_store_res - t0 result = _store_results_from_pf_in_ppci(self.ppci, self.bus, self.gen, self.branch, success, iterations, et) t0_to_net = time() et_store_res = t0_to_net - t0_store_res # ---------- pp.pf.run_newton_raphson_pf.run_newton_raphson_pf() ---------------- # ---------- pp.powerflow._run_pf_algorithm() ---------------- # read the results (=ppci with results) to net _ppci_to_net(result, net) et_to_net = time() - t0_to_net # ---------- pp.powerflow._powerflow() ---------------- # ---------- pp.run.runpp() ----------------- # added et_start = time() - t0_start self.ppci = _store_internal( self.ppci, { "time_store_res": et_store_res, "time_to_net": et_to_net, "time_all": et_start, "time_ppci_to_pfsoln": te_ppci_to_pfsoln }) has_conv = model.compute_newton(V0[tmp_bus_ind], max_it, tol) # check the results results_solver = np.max(np.abs(V_orig - self.V)) Ybus = model.get_Ybus() Ybus_proper_oder = Ybus self.Ybus_proper_oder = self.Ybus[np.array([tmp_bus_ind]).T, np.array([tmp_bus_ind])] tmp = np.abs(Ybus_proper_oder - self.Ybus_proper_oder) # > 1e-7 por, qor, vor, aor = model.get_lineor_res() pex, qex, vex, aex = model.get_lineex_res() load_p, load_q, load_v = model.get_loads_res() np.max(np.abs(por - net_orig.res_line["p_from_mw"])) np.max(np.abs(qor - net_orig.res_line["q_from_mvar"])) a_or_pp = np.sqrt(net.res_line["p_from_mw"].values**2 + net.res_line["q_from_mvar"].values**2) a_or_pp /= np.sqrt(3) * net.bus.loc[net.line["from_bus"].values][ "vn_kv"].values * net.res_line["vm_from_pu"].values np.max(np.abs(a_or_pp - aor)) np.max(np.abs(a_or_pp - net.res_line["i_from_ka"])) np.max(np.abs(a_or_pp - net.res_line["i_from_ka"])) Va_me2 = model.get_Va() Vm_me2 = model.get_Vm() res_vm = np.abs(Vm_me2 - Vm[tmp_bus_ind]) res_va = np.abs(Va_me2 - Va[tmp_bus_ind]) # check that if i start the solver on the data Sbus_me = model.get_Sbus() pv_me = model.get_pv() pq_me = model.get_pq() np.all(sorted(pv_me) == sorted(net.gen["bus"])) np.all(sorted(pq_me) == sorted(tmp_bus_ind[self.pq])) plv, qlv, vlv, alv = model.get_trafolv_res() phv, qhv, vhv, ahv = model.get_trafohv_res() res_trafo = np.abs(plv - net_orig.res_trafo["p_lv_mw"].values) res_trafo_q = np.abs(qlv - net_orig.res_trafo["q_lv_mvar"].values) # self.solver.reset() # self.solver.solve(Ybus, V0, Sbus, pv_me, pq_me, max_it, tol) # Va2 = self.solver.get_Va() # Vm2 = self.solver.get_Vm() pdb.set_trace()
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 estimate(self, v_start=None, delta_start=None, calculate_voltage_angles=True): """ The function estimate is the main function of the module. It takes up to three input arguments: v_start, delta_start and calculate_voltage_angles. The first two are the initial state variables for the estimation process. Usually they can be initialized in a "flat-start" condition: All voltages being 1.0 pu and all voltage angles being 0 degrees. In this case, the parameters can be left at their default values (None). If the estimation is applied continuously, using the results from the last estimation as the starting condition for the current estimation can decrease the amount of iterations needed to estimate the current state. The third parameter defines whether all voltage angles are calculated absolutely, including phase shifts from transformers. If only the relative differences between buses are required, this parameter can be set to False. Returned is a boolean value, which is true after a successful estimation and false otherwise. The resulting complex voltage will be written into the pandapower network. The result fields are found res_bus_est of the pandapower network. INPUT: **net** - The net within this line should be created **v_start** (np.array, shape=(1,), optional) - Vector with initial values for all voltage magnitudes in p.u. (sorted by bus index) **delta_start** (np.array, shape=(1,), optional) - Vector with initial values for all voltage angles in degrees (sorted by bus index) OPTIONAL: **calculate_voltage_angles** - (bool) - Take into account absolute voltage angles and phase shifts in transformers Default is True. OUTPUT: **successful** (boolean) - True if the estimation process was successful Optional estimation variables: The bus power injections can be accessed with *se.s_node_powers* and the estimated values corresponding to the (noisy) measurement values with *se.hx*. (*hx* denotes h(x)) EXAMPLE: success = estimate(np.array([1.0, 1.0, 1.0]), np.array([0.0, 0.0, 0.0])) """ if self.net is None: raise UserWarning("Component was not initialized with a network.") t0 = time() # add initial values for V and delta # node voltages # V<delta if v_start is None: v_start = np.ones(self.net.bus.shape[0]) if delta_start is None: delta_start = np.zeros(self.net.bus.shape[0]) # initialize result tables if not existent _copy_power_flow_results(self.net) # initialize ppc ppc, ppci = _init_ppc(self.net, v_start, delta_start, calculate_voltage_angles) # add measurements to ppci structure ppci = _add_measurements_to_ppc(self.net, ppci, self.s_ref) # calculate relevant vectors from ppci measurements z, self.pp_meas_indices, r_cov = _build_measurement_vectors(ppci) # number of nodes n_active = len(np.where(ppci["bus"][:, 1] != 4)[0]) slack_buses = np.where(ppci["bus"][:, 1] == 3)[0] # Check if observability criterion is fulfilled and the state estimation is possible if len(z) < 2 * n_active - 1: self.logger.error("System is not observable (cancelling)") self.logger.error( "Measurements available: %d. Measurements required: %d" % (len(z), 2 * n_active - 1)) return False # set the starting values for all active buses v_m = ppci["bus"][:, 7] delta = ppci["bus"][:, 8] * np.pi / 180 # convert to rad delta_masked = np.ma.array(delta, mask=False) delta_masked.mask[slack_buses] = True non_slack_buses = np.arange(len(delta))[~delta_masked.mask] # matrix calculation object sem = wls_matrix_ops(ppci, slack_buses, non_slack_buses, self.s_ref) # state vector E = np.concatenate((delta_masked.compressed(), v_m)) # invert covariance matrix r_inv = csr_matrix(np.linalg.inv(np.diagflat(r_cov)**2)) current_error = 100. cur_it = 0 G_m, r, H, h_x = None, None, None, None while current_error > self.tolerance and cur_it < self.max_iterations: self.logger.debug(" Starting iteration %d" % (1 + cur_it)) try: # create h(x) for the current iteration h_x = sem.create_hx(v_m, delta) # residual r r = csr_matrix(z - h_x).T # jacobian matrix H H = csr_matrix(sem.create_jacobian(v_m, delta)) # gain matrix G_m # G_m = H^t * R^-1 * H G_m = H.T * (r_inv * H) # state vector difference d_E # d_E = G_m^-1 * (H' * R^-1 * r) d_E = spsolve(G_m, H.T * (r_inv * r)) E += d_E # update V/delta delta[non_slack_buses] = E[:len(non_slack_buses)] v_m = np.squeeze(E[len(non_slack_buses):]) # prepare next iteration cur_it += 1 current_error = np.max(np.abs(d_E)) self.logger.debug("Current error: %.7f" % current_error) except np.linalg.linalg.LinAlgError: self.logger.error( "A problem appeared while using the linear algebra methods." "Check and change the measurement set.") return False # print output for results if current_error <= self.tolerance: successful = True self.logger.debug( "WLS State Estimation successful (%d iterations)" % cur_it) else: successful = False self.logger.debug( "WLS State Estimation not successful (%d/%d iterations)" % (cur_it, self.max_iterations)) # store results for all elements # calculate bus power injections v_cpx = v_m * np.exp(1j * delta) bus_powers_conj = np.zeros(len(v_cpx), dtype=np.complex128) for i in range(len(v_cpx)): bus_powers_conj[i] = np.dot(sem.Y_bus[i, :], v_cpx) * np.conjugate( v_cpx[i]) ppci["bus"][:, 2] = bus_powers_conj.real # saved in per unit ppci["bus"][:, 3] = -bus_powers_conj.imag # saved in per unit ppci["bus"][:, 7] = v_m ppci["bus"][:, 8] = delta * 180 / np.pi # convert to degree # calculate line results (in ppc_i) s_ref, bus, gen, branch = _get_pf_variables_from_ppci(ppci)[0:4] out = np.flatnonzero(branch[:, BR_STATUS] == 0) # out-of-service branches br = np.flatnonzero(branch[:, BR_STATUS]).astype( int) # in-service branches # complex power at "from" bus Sf = v_cpx[np.real(branch[br, F_BUS]).astype(int)] * np.conj( sem.Yf[br, :] * v_cpx) * s_ref # complex power injected at "to" bus St = v_cpx[np.real(branch[br, T_BUS]).astype(int)] * np.conj( sem.Yt[br, :] * v_cpx) * s_ref branch[np.ix_(br, [PF, QF, PT, QT])] = np.c_[Sf.real, Sf.imag, St.real, St.imag] branch[np.ix_(out, [PF, QF, PT, QT])] = np.zeros((len(out), 4)) et = time() - t0 ppci = _store_results_from_pf_in_ppci(ppci, bus, gen, branch, successful, cur_it, et) # convert to pandapower indices ppc = _copy_results_ppci_to_ppc(ppci, ppc, mode="se") # extract results from ppc _add_pf_options(self.net, tolerance_kva=1e-5, trafo_loading="current", numba=True, ac=True, algorithm='nr', max_iteration="auto") # writes res_bus.vm_pu / va_degree and res_line _extract_results_se(self.net, ppc) # restore backup of previous results _rename_results(self.net) # additionally, write bus power injection results (these are not written in _extract_results) mapping_table = self.net["_pd2ppc_lookups"]["bus"] self.net.res_bus_est.p_kw = -get_values( ppc["bus"][:, 2], self.net.bus.index.values, mapping_table) * self.s_ref / 1e3 self.net.res_bus_est.q_kvar = -get_values( ppc["bus"][:, 3], self.net.bus.index.values, mapping_table) * self.s_ref / 1e3 # store variables required for chi^2 and r_N_max test: self.R_inv = r_inv.toarray() self.Gm = G_m.toarray() self.r = r.toarray() self.H = H.toarray() self.Ht = self.H.T self.hx = h_x self.V = v_m self.delta = delta # delete results which are not correctly calculated for k in list(self.net.keys()): if k.startswith("res_") and k.endswith("_est") and \ k not in ("res_bus_est", "res_line_est", "res_trafo_est", "res_trafo3w_est"): del self.net[k] return successful
def _run_ac_pf_with_qlims_enforced(ppci, recycle, makeYbus, ppopt): baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, V0, _ = _get_pf_variables_from_ppci( ppci) qlim = ppopt["ENFORCE_Q_LIMS"] limited = [] ## list of indices of gens @ Q lims fixedQg = zeros(gen.shape[0]) ## Qg of gens at Q limits it = 0 while True: ppci, success, bus, gen, branch, it_inner = _run_ac_pf_without_qlims_enforced( ppci, recycle, makeYbus, ppopt) it += it_inner ## 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] non_refs = (gen[:, QMAX] != 0.) & (gen[:, QMIN] != 0.) mx = find(gen_status & qg_max_lim & non_refs) mn = find(gen_status & qg_min_lim & non_refs) if len(mx) > 0 or len(mn) > 0: ## we have some Q limit violations ## 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 = [] ## 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 mx: ## [one at a time, since they may be at same bus] gen[i, GEN_STATUS] = 0 ## temporarily turn off gen, bi = gen[i, GEN_BUS].astype(int) ## adjust load accordingly, bus[bi, [PD, QD]] = (bus[bi, [PD, QD]] - gen[i, [PG, QG]]) if len(ref) > 1 and any(bus[gen[mx, GEN_BUS].astype(int), BUS_TYPE] == REF): raise ValueError('Sorry, pandapower cannot enforce Q ' 'limits for slack buses in systems ' 'with multiple slacks.') changed_gens = gen[mx, GEN_BUS].astype(int) bus[setdiff1d(changed_gens, ref), BUS_TYPE] = PQ ## & set bus type to PQ ## update bus index lists of each type of bus ref, pv, pq = bustypes(bus, gen) limited = r_[limited, mx].astype(int) else: break ## no more generator Q limits violated if len(limited) > 0: ## restore injections from limited gens [those at Q limits] gen[limited, QG] = fixedQg[limited] ## restore Qg value, for i in limited: ## [one at a time, since they may be at same bus] bi = gen[i, GEN_BUS].astype(int) ## re-adjust load, bus[bi, [PD, QD]] = bus[bi, [PD, QD]] + gen[i, [PG, QG]] gen[i, GEN_STATUS] = 1 ## and turn gen back on return ppci, success, bus, gen, branch, it
def estimate(self, v_start='flat', delta_start='flat', calculate_voltage_angles=True, zero_injection=None, fuse_buses_with_bb_switch='all'): """ The function estimate is the main function of the module. It takes up to three input arguments: v_start, delta_start and calculate_voltage_angles. The first two are the initial state variables for the estimation process. Usually they can be initialized in a "flat-start" condition: All voltages being 1.0 pu and all voltage angles being 0 degrees. In this case, the parameters can be left at their default values (None). If the estimation is applied continuously, using the results from the last estimation as the starting condition for the current estimation can decrease the amount of iterations needed to estimate the current state. The third parameter defines whether all voltage angles are calculated absolutely, including phase shifts from transformers. If only the relative differences between buses are required, this parameter can be set to False. Returned is a boolean value, which is true after a successful estimation and false otherwise. The resulting complex voltage will be written into the pandapower network. The result fields are found res_bus_est of the pandapower network. INPUT: **net** - The net within this line should be created **v_start** (np.array, shape=(1,), optional) - Vector with initial values for all voltage magnitudes in p.u. (sorted by bus index) **delta_start** (np.array, shape=(1,), optional) - Vector with initial values for all voltage angles in degrees (sorted by bus index) OPTIONAL: **calculate_voltage_angles** - (bool) - Take into account absolute voltage angles and phase shifts in transformers Default is True **zero_injection** - (str, iterable, None) - Defines which buses are zero injection bus or the method to identify zero injection bus, with 'wls_estimator' virtual measurements will be added, with 'wls_estimator with zero constraints' the buses will be handled as constraints "auto": all bus without p,q measurement, without p, q value (load, sgen...) and aux buses will be identified as zero injection bus "aux_bus": only aux bus will be identified as zero injection bus None: no bus will be identified as zero injection bus iterable: the iterable should contain index of the zero injection bus and also aux bus will be identified as zero-injection bus **fuse_buses_with_bb_switch** - (str, iterable, None) - Defines how buses with closed bb switches should be handled, if fuse buses will only fused to one for calculation, if not fuse, an auxiliary bus and auxiliary line will be automatically added to the network to make the buses with different p,q injection measurements identifieble "all": all buses with bb-switches will be fused, the same as the default behaviour in load flow None: buses with bb-switches and individual p,q measurements will be reconfigurated by auxiliary elements iterable: the iterable should contain the index of buses to be fused, the behaviour is contigous e.g. if one of the bus among the buses connected through bb switch is given, then all of them will still be fused OUTPUT: **successful** (boolean) - True if the estimation process was successful Optional estimation variables: The bus power injections can be accessed with *se.s_node_powers* and the estimated values corresponding to the (noisy) measurement values with *se.hx*. (*hx* denotes h(x)) EXAMPLE: success = estimate(np.array([1.0, 1.0, 1.0]), np.array([0.0, 0.0, 0.0])) """ if self.net is None: raise UserWarning("Component was not initialized with a network.") t0 = time() # change the configuration of the pp net to avoid auto fusing of buses connected # through bb switch with elements on each bus if this feature enabled bus_to_be_fused = None if fuse_buses_with_bb_switch != 'all' and not self.net.switch.empty: if isinstance(fuse_buses_with_bb_switch, str): raise UserWarning( "fuse_buses_with_bb_switch parameter is not correctly initialized" ) elif hasattr(fuse_buses_with_bb_switch, '__iter__'): bus_to_be_fused = fuse_buses_with_bb_switch _add_aux_elements_for_bb_switch(self.net, bus_to_be_fused) # add initial values for V and delta # node voltages # V<delta if v_start is None: v_start = "flat" if delta_start is None: delta_start = "flat" # initialize result tables if not existent _copy_power_flow_results(self.net) # initialize ppc ppc, ppci = _init_ppc(self.net, v_start, delta_start, calculate_voltage_angles) # add measurements to ppci structure ppci = _add_measurements_to_ppc(self.net, ppci, zero_injection) # Finished converting pandapower network to ppci # Estimate voltage magnitude and angle with the given estimator delta, v_m = self.estimator.estimate(ppci) # store results for all elements # calculate bus power injections v_cpx = v_m * np.exp(1j * delta) bus_powers_conj = np.zeros(len(v_cpx), dtype=np.complex128) for i in range(len(v_cpx)): bus_powers_conj[i] = np.dot(ppci['internal']['Y_bus'][i, :], v_cpx) * np.conjugate(v_cpx[i]) ppci["bus"][:, 2] = bus_powers_conj.real # saved in per unit ppci["bus"][:, 3] = -bus_powers_conj.imag # saved in per unit ppci["bus"][:, 7] = v_m ppci["bus"][:, 8] = delta * 180 / np.pi # convert to degree # calculate line results (in ppc_i) s_ref, bus, gen, branch = _get_pf_variables_from_ppci(ppci)[0:4] out = np.flatnonzero(branch[:, BR_STATUS] == 0) # out-of-service branches br = np.flatnonzero(branch[:, BR_STATUS]).astype( int) # in-service branches # complex power at "from" bus Sf = v_cpx[np.real(branch[br, F_BUS]).astype(int)] * np.conj( ppci['internal']['Yf'][br, :] * v_cpx) * s_ref # complex power injected at "to" bus St = v_cpx[np.real(branch[br, T_BUS]).astype(int)] * np.conj( ppci['internal']['Yt'][br, :] * v_cpx) * s_ref branch[np.ix_(br, [PF, QF, PT, QT])] = np.c_[Sf.real, Sf.imag, St.real, St.imag] branch[np.ix_(out, [PF, QF, PT, QT])] = np.zeros((len(out), 4)) et = time() - t0 ppci = _store_results_from_pf_in_ppci(ppci, bus, gen, branch, self.estimator.successful, self.estimator.iterations, et) # convert to pandapower indices ppc = _copy_results_ppci_to_ppc(ppci, ppc, mode="se") # extract results from ppc _add_pf_options(self.net, tolerance_mva=1e-8, trafo_loading="current", numba=True, ac=True, algorithm='nr', max_iteration="auto") # writes res_bus.vm_pu / va_degree and res_line _extract_results_se(self.net, ppc) # restore backup of previous results _rename_results(self.net) # additionally, write bus power injection results (these are not written in _extract_results) mapping_table = self.net["_pd2ppc_lookups"]["bus"] self.net.res_bus_est.p_mw = -get_values( ppc["bus"][:, 2], self.net.bus.index.values, mapping_table) self.net.res_bus_est.q_mvar = -get_values( ppc["bus"][:, 3], self.net.bus.index.values, mapping_table) _clean_up(self.net) # clear the aux elements and calculation results created for the substitution of bb switches if fuse_buses_with_bb_switch != 'all' and not self.net.switch.empty: _drop_aux_elements_for_bb_switch(self.net) # delete results which are not correctly calculated for k in list(self.net.keys()): if k.startswith("res_") and k.endswith("_est") and \ k not in ("res_bus_est", "res_line_est", "res_trafo_est", "res_trafo3w_est"): del self.net[k] return self.estimator.successful