def _get_y_bus(ppci0, ppci1, ppci2, recycle): if recycle and recycle["Ybus"] and ppci0["internal"]["Ybus"].size and \ ppci1["internal"]["Ybus"].size and ppci2["internal"]["Ybus"].size: y_0_bus, y_0_f, y_0_t = ppci0["internal"]['Ybus'], ppci0["internal"][ 'Yf'], ppci0["internal"]['Yt'] y_1_bus, y_1_f, y_1_t = ppci1["internal"]['Ybus'], ppci1["internal"][ 'Yf'], ppci1["internal"]['Yt'] y_2_bus, y_2_f, y_2_t = ppci2["internal"]['Ybus'], ppci2["internal"][ 'Yf'], ppci2["internal"]['Yt'] else: # build admittance matrices y_0_bus, y_0_f, y_0_t = makeYbus(ppci0["baseMVA"], ppci0["bus"], ppci0["branch"]) y_1_bus, y_1_f, y_1_t = makeYbus(ppci1["baseMVA"], ppci1["bus"], ppci1["branch"]) y_2_bus, y_2_f, y_2_t = makeYbus(ppci2["baseMVA"], ppci2["bus"], ppci2["branch"]) if recycle and recycle["Ybus"]: ppci0["internal"]['Ybus'], ppci0["internal"]['Yf'], ppci0[ "internal"]['Yt'] = y_0_bus, y_0_f, y_0_t ppci1["internal"]['Ybus'], ppci1["internal"]['Yf'], ppci1[ "internal"]['Yt'] = y_1_bus, y_1_f, y_1_t ppci2["internal"]['Ybus'], ppci2["internal"]['Yf'], ppci2[ "internal"]['Yt'] = y_2_bus, y_2_f, y_2_t return ppci0, ppci1, ppci2, y_0_bus, y_1_bus, y_2_bus, y_0_f, y_1_f, y_2_f, y_0_t, y_1_t, y_2_t
def makeB(baseMVA, bus, branch, alg): """Builds the FDPF matrices, B prime and B double prime. Returns the two matrices B prime and B double prime used in the fast decoupled power flow. Does appropriate conversions to p.u. C{alg} is the value of the C{PF_ALG} option specifying the power flow algorithm. @see: L{fdpf} @author: Ray Zimmerman (PSERC Cornell) """ ## constants nb = bus.shape[0] ## number of buses nl = branch.shape[0] ## number of lines ##----- form Bp (B prime) ----- temp_branch = copy(branch) ## modify a copy of branch temp_bus = copy(bus) ## modify a copy of bus temp_bus[:, BS] = zeros(nb) ## zero out shunts at buses temp_branch[:, BR_B] = zeros(nl) ## zero out line charging shunts temp_branch[:, TAP] = ones(nl) ## cancel out taps if alg == 2: ## if XB method temp_branch[:, BR_R] = zeros(nl) ## zero out line resistance Bp = -1 * makeYbus(baseMVA, temp_bus, temp_branch)[0].imag ##----- form Bpp (B double prime) ----- temp_branch = copy(branch) ## modify a copy of branch temp_branch[:, SHIFT] = zeros(nl) ## zero out phase shifters if alg == 3: ## if BX method temp_branch[:, BR_R] = zeros(nl) ## zero out line resistance Bpp = -1 * makeYbus(baseMVA, bus, temp_branch)[0].imag return Bp, Bpp
def get_Y(self): # Using recycled version if available if "Ybus" in self["internal"] and self["internal"]["Ybus"].size: Ybus, Yf, Yt = self["internal"]['Ybus'], self["internal"]['Yf'], self["internal"]['Yt'] else: ## build admittance matrices Ybus, Yf, Yt = makeYbus(self['baseMVA'], self['bus'], self['branch']) self["internal"]['Ybus'], self["internal"]['Yf'], self["internal"]['Yt'] = Ybus, Yf, Yt return Ybus, Yf, Yt
def initialize_Y(self): with warnings.catch_warnings(): warnings.simplefilter("ignore") Ybus, Yf, Yt = makeYbus(self.eppci["baseMVA"], self.eppci["bus"], self.eppci["branch"]) self.eppci['internal']['Yf'], self.eppci['internal']['Yt'],\ self.eppci['internal']['Ybus'] = Yf, Yt, Ybus # create relevant matrices in sparse form self.Ybus, self.Yf, self.Yt = Ybus, Yf, Yt
def opf_execute(om, ppopt): """Executes the OPF specified by an OPF model object. C{results} are returned with internal indexing, all equipment in-service, etc. @see: L{opf}, L{opf_setup} @author: Ray Zimmerman (PSERC Cornell) @author: Richard Lincoln """ ##----- setup ----- ## options dc = ppopt['PF_DC'] ## 1 = DC OPF, 0 = AC OPF alg = ppopt['OPF_ALG'] verbose = ppopt['VERBOSE'] ## build user-defined costs om.build_cost_params() ## get indexing vv, ll, nn, _ = om.get_idx() if verbose > 0: v = ppver('all') stdout.write('PYPOWER Version %s, %s' % (v['Version'], v['Date'])) ##----- run DC OPF solver ----- if dc: if verbose > 0: stdout.write(' -- DC Optimal Power Flow\n') results, success, raw = dcopf_solver(om, ppopt) else: ##----- run AC OPF solver ----- if verbose > 0: stdout.write(' -- AC Optimal Power Flow\n') ## if OPF_ALG not set, choose best available option if alg == 0: alg = 560 ## MIPS ## update deprecated algorithm codes to new, generalized formulation equivalents if alg == 100 | alg == 200: ## CONSTR alg = 300 elif alg == 120 | alg == 220: ## dense LP alg = 320 elif alg == 140 | alg == 240: ## sparse (relaxed) LP alg = 340 elif alg == 160 | alg == 260: ## sparse (full) LP alg = 360 ppopt['OPF_ALG_POLY'] = alg ## run specific AC OPF solver if alg == 560 or alg == 565: ## PIPS results, success, raw = pipsopf_solver(om, ppopt) # elif alg == 580: ## IPOPT # pragma: no cover # try: # __import__('pyipopt') # results, success, raw = ipoptopf_solver(om, ppopt) # except ImportError: # raise ImportError('OPF_ALG %d requires IPOPT ' # '(see https://projects.coin-or.org/Ipopt/)' % # alg) else: stderr.write( 'opf_execute: OPF_ALG %d is not a valid algorithm code\n' % alg) if ('output' not in raw) or ('alg' not in raw['output']): raw['output']['alg'] = alg if success: if not dc: ## copy bus voltages back to gen matrix results['gen'][:, VG] = results['bus'][ results['gen'][:, GEN_BUS].astype(int), VM] ## gen PQ capability curve multipliers if (ll['N']['PQh'] > 0) | (ll['N']['PQl'] > 0): # pragma: no cover mu_PQh = results['mu']['lin']['l'][ ll['i1']['PQh']:ll['iN']['PQh']] - results['mu']['lin'][ 'u'][ll['i1']['PQh']:ll['iN']['PQh']] mu_PQl = results['mu']['lin']['l'][ ll['i1']['PQl']:ll['iN']['PQl']] - results['mu']['lin'][ 'u'][ll['i1']['PQl']:ll['iN']['PQl']] Apqdata = om.userdata('Apqdata') results['gen'] = update_mupq(results['baseMVA'], results['gen'], mu_PQh, mu_PQl, Apqdata) ## compute g, dg, f, df, d2f if requested by RETURN_RAW_DER = 1 if ppopt['RETURN_RAW_DER']: # pragma: no cover ## move from results to raw if using v4.0 of MINOPF or TSPOPF if 'dg' in results: raw = {} raw['dg'] = results['dg'] raw['g'] = results['g'] ## compute g, dg, unless already done by post-v4.0 MINOPF or TSPOPF if 'dg' not in raw: ppc = om.get_ppc() Ybus, Yf, Yt = makeYbus(ppc['baseMVA'], ppc['bus'], ppc['branch']) g, geq, dg, dgeq = opf_consfcn(results['x'], om, Ybus, Yf, Yt, ppopt) raw['g'] = r_[geq, g] raw['dg'] = r_[dgeq.T, dg.T] ## true Jacobian organization ## compute df, d2f _, df, d2f = opf_costfcn(results['x'], om, True) raw['df'] = df raw['d2f'] = d2f ## delete g and dg fieldsfrom results if using v4.0 of MINOPF or TSPOPF if 'dg' in results: del results['dg'] del results['g'] ## angle limit constraint multipliers if ll['N']['ang'] > 0: iang = om.userdata('iang') results['branch'][iang, MU_ANGMIN] = results['mu']['lin']['l'][ ll['i1']['ang']:ll['iN']['ang']] * pi / 180 results['branch'][iang, MU_ANGMAX] = results['mu']['lin']['u'][ ll['i1']['ang']:ll['iN']['ang']] * pi / 180 else: ## assign empty g, dg, f, df, d2f if requested by RETURN_RAW_DER = 1 if not dc and ppopt['RETURN_RAW_DER']: raw['dg'] = array([]) raw['g'] = array([]) raw['df'] = array([]) raw['d2f'] = array([]) ## assign values and limit shadow prices for variables if om.var['order']: results['var'] = {'val': {}, 'mu': {'l': {}, 'u': {}}} for name in om.var['order']: if om.getN('var', name): idx = arange(vv['i1'][name], vv['iN'][name]) results['var']['val'][name] = results['x'][idx] results['var']['mu']['l'][name] = results['mu']['var']['l'][idx] results['var']['mu']['u'][name] = results['mu']['var']['u'][idx] ## assign shadow prices for linear constraints if om.lin['order']: results['lin'] = {'mu': {'l': {}, 'u': {}}} for name in om.lin['order']: if om.getN('lin', name): idx = arange(ll['i1'][name], ll['iN'][name]) results['lin']['mu']['l'][name] = results['mu']['lin']['l'][idx] results['lin']['mu']['u'][name] = results['mu']['lin']['u'][idx] ## assign shadow prices for nonlinear constraints if not dc: if om.nln['order']: results['nln'] = {'mu': {'l': {}, 'u': {}}} for name in om.nln['order']: if om.getN('nln', name): idx = arange(nn['i1'][name], nn['iN'][name]) results['nln']['mu']['l'][name] = results['mu']['nln']['l'][ idx] results['nln']['mu']['u'][name] = results['mu']['nln']['u'][ idx] ## assign values for components of user cost if om.cost['order']: results['cost'] = {} for name in om.cost['order']: if om.getN('cost', name): results['cost'][name] = om.compute_cost(results['x'], name) ## if single-block PWL costs were converted to POLY, insert dummy y into x ## Note: The "y" portion of x will be nonsense, but everything should at ## least be in the expected locations. pwl1 = om.userdata('pwl1') if (len(pwl1) > 0) and (alg != 545) and (alg != 550): ## get indexing vv, _, _, _ = om.get_idx() if dc: nx = vv['iN']['Pg'] else: nx = vv['iN']['Qg'] y = zeros(len(pwl1)) raw['xr'] = r_[raw['xr'][:nx], y, raw['xr'][nx:]] results['x'] = r_[results['x'][:nx], y, results['x'][nx:]] return results, success, raw
def pipsopf_solver(om, ppopt, out_opt=None): """Solves AC optimal power flow using PIPS. Inputs are an OPF model object, a PYPOWER options vector and a dict containing keys (can be empty) for each of the desired optional output fields. outputs are a C{results} dict, C{success} flag and C{raw} output dict. C{results} is a PYPOWER case dict (ppc) with the usual baseMVA, bus branch, gen, gencost fields, along with the following additional fields: - C{order} see 'help ext2int' for details of this field - C{x} final value of optimization variables (internal order) - C{f} final objective function value - C{mu} shadow prices on ... - C{var} - C{l} lower bounds on variables - C{u} upper bounds on variables - C{nln} - C{l} lower bounds on nonlinear constraints - C{u} upper bounds on nonlinear constraints - C{lin} - C{l} lower bounds on linear constraints - C{u} upper bounds on linear constraints C{success} is C{True} if solver converged successfully, C{False} otherwise C{raw} is a raw output dict in form returned by MINOS - xr final value of optimization variables - pimul constraint multipliers - info solver specific termination code - output solver specific output information @see: L{opf}, L{pips} @author: Ray Zimmerman (PSERC Cornell) @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) @author: Richard Lincoln """ ##----- initialization ----- ## optional output if out_opt is None: out_opt = {} ## options verbose = ppopt['VERBOSE'] feastol = ppopt['PDIPM_FEASTOL'] gradtol = ppopt['PDIPM_GRADTOL'] comptol = ppopt['PDIPM_COMPTOL'] costtol = ppopt['PDIPM_COSTTOL'] max_it = ppopt['PDIPM_MAX_IT'] max_red = ppopt['SCPDIPM_RED_IT'] init = ppopt['INIT'] step_control = (ppopt['OPF_ALG'] == 565) ## OPF_ALG == 565, PIPS-sc if feastol == 0: feastol = ppopt['OPF_VIOLATION'] opt = { 'feastol': feastol, 'gradtol': gradtol, 'comptol': comptol, 'costtol': costtol, 'max_it': max_it, 'max_red': max_red, 'step_control': step_control, 'cost_mult': 1e-4, 'verbose': verbose } ## unpack data ppc = om.get_ppc() baseMVA, bus, gen, branch, gencost = \ ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"], ppc["gencost"] vv, _, nn, _ = om.get_idx() ## problem dimensions nb = bus.shape[0] ## number of buses nl = branch.shape[0] ## number of branches ny = om.getN('var', 'y') ## number of piece-wise linear costs ## linear constraints A, l, u = om.linear_constraints() ## bounds on optimization vars x0, xmin, xmax = om.getv() ## build admittance matrices Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) ## try to select an interior initial point if init is not available from a previous powerflow if init != "pf": ll, uu = xmin.copy(), xmax.copy() ll[xmin == -Inf] = -1e10 ## replace Inf with numerical proxies uu[xmax == Inf] = 1e10 x0 = (ll + uu) / 2 Varefs = bus[bus[:, BUS_TYPE] == REF, VA] * (pi / 180) ## angles set to first reference angle x0[vv["i1"]["Va"]:vv["iN"]["Va"]] = Varefs[0] if ny > 0: ipwl = find(gencost[:, MODEL] == PW_LINEAR) # PQ = r_[gen[:, PMAX], gen[:, QMAX]] # c = totcost(gencost[ipwl, :], PQ[ipwl]) c = gencost.flatten('F')[sub2ind(gencost.shape, ipwl, NCOST+2*gencost[ipwl, NCOST])] ## largest y-value in CCV data x0[vv["i1"]["y"]:vv["iN"]["y"]] = max(c) + 0.1 * abs(max(c)) # x0[vv["i1"]["y"]:vv["iN"]["y"]] = c + 0.1 * abs(c) ## find branches with flow limits il = find((branch[:, RATE_A] != 0) & (branch[:, RATE_A] < 1e10)) nl2 = len(il) ## number of constrained lines ##----- run opf ----- f_fcn = lambda x, return_hessian=False: opf_costfcn(x, om, return_hessian) gh_fcn = lambda x: opf_consfcn(x, om, Ybus, Yf[il, :], Yt[il,:], ppopt, il) hess_fcn = lambda x, lmbda, cost_mult: opf_hessfcn(x, lmbda, om, Ybus, Yf[il, :], Yt[il, :], ppopt, il, cost_mult) solution = pips(f_fcn, x0, A, l, u, xmin, xmax, gh_fcn, hess_fcn, opt) x, f, info, lmbda, output = solution["x"], solution["f"], \ solution["eflag"], solution["lmbda"], solution["output"] success = (info > 0) ## update solution data Va = x[vv["i1"]["Va"]:vv["iN"]["Va"]] Vm = x[vv["i1"]["Vm"]:vv["iN"]["Vm"]] Pg = x[vv["i1"]["Pg"]:vv["iN"]["Pg"]] Qg = x[vv["i1"]["Qg"]:vv["iN"]["Qg"]] V = Vm * exp(1j * Va) ##----- calculate return values ----- ## update voltages & generator outputs bus[:, VA] = Va * 180 / pi bus[:, VM] = Vm gen[:, PG] = Pg * baseMVA gen[:, QG] = Qg * baseMVA gen[:, VG] = Vm[ gen[:, GEN_BUS].astype(int) ] ## compute branch flows Sf = V[ branch[:, F_BUS].astype(int) ] * conj(Yf * V) ## cplx pwr at "from" bus, p["u"]. St = V[ branch[:, T_BUS].astype(int) ] * conj(Yt * V) ## cplx pwr at "to" bus, p["u"]. branch[:, PF] = Sf.real * baseMVA branch[:, QF] = Sf.imag * baseMVA branch[:, PT] = St.real * baseMVA branch[:, QT] = St.imag * baseMVA ## line constraint is actually on square of limit ## so we must fix multipliers muSf = zeros(nl) muSt = zeros(nl) if len(il) > 0: muSf[il] = \ 2 * lmbda["ineqnonlin"][:nl2] * branch[il, RATE_A] / baseMVA muSt[il] = \ 2 * lmbda["ineqnonlin"][nl2:nl2+nl2] * branch[il, RATE_A] / baseMVA ## update Lagrange multipliers bus[:, MU_VMAX] = lmbda["upper"][vv["i1"]["Vm"]:vv["iN"]["Vm"]] bus[:, MU_VMIN] = lmbda["lower"][vv["i1"]["Vm"]:vv["iN"]["Vm"]] gen[:, MU_PMAX] = lmbda["upper"][vv["i1"]["Pg"]:vv["iN"]["Pg"]] / baseMVA gen[:, MU_PMIN] = lmbda["lower"][vv["i1"]["Pg"]:vv["iN"]["Pg"]] / baseMVA gen[:, MU_QMAX] = lmbda["upper"][vv["i1"]["Qg"]:vv["iN"]["Qg"]] / baseMVA gen[:, MU_QMIN] = lmbda["lower"][vv["i1"]["Qg"]:vv["iN"]["Qg"]] / baseMVA bus[:, LAM_P] = \ lmbda["eqnonlin"][nn["i1"]["Pmis"]:nn["iN"]["Pmis"]] / baseMVA bus[:, LAM_Q] = \ lmbda["eqnonlin"][nn["i1"]["Qmis"]:nn["iN"]["Qmis"]] / baseMVA branch[:, MU_SF] = muSf / baseMVA branch[:, MU_ST] = muSt / baseMVA ## package up results nlnN = om.getN('nln') ## extract multipliers for nonlinear constraints kl = find(lmbda["eqnonlin"] < 0) ku = find(lmbda["eqnonlin"] > 0) nl_mu_l = zeros(nlnN) nl_mu_u = r_[zeros(2*nb), muSf, muSt] nl_mu_l[kl] = -lmbda["eqnonlin"][kl] nl_mu_u[ku] = lmbda["eqnonlin"][ku] mu = { 'var': {'l': lmbda["lower"], 'u': lmbda["upper"]}, 'nln': {'l': nl_mu_l, 'u': nl_mu_u}, 'lin': {'l': lmbda["mu_l"], 'u': lmbda["mu_u"]} } results = ppc results["bus"], results["branch"], results["gen"], \ results["om"], results["x"], results["mu"], results["f"] = \ bus, branch, gen, om, x, mu, f pimul = r_[ results["mu"]["nln"]["l"] - results["mu"]["nln"]["u"], results["mu"]["lin"]["l"] - results["mu"]["lin"]["u"], -ones(int(ny > 0)), results["mu"]["var"]["l"] - results["mu"]["var"]["u"], ] raw = {'xr': x, 'pimul': pimul, 'info': info, 'output': output} return results, success, raw
def runpp_3ph(net, calculate_voltage_angles=True, init="auto", max_iteration="auto", tolerance_mva=1e-8, trafo_model='t', trafo_loading="current", enforce_q_lims=False, numba=True, recycle=None, check_connectivity=True, switch_rx_ratio=2.0, delta_q=0, v_debug=False, **kwargs): """ runpp_3ph: Performs Unbalanced/Asymmetric/Three Phase Load flow INPUT: **net** - The pandapower format network OPTIONAL: **algorithm** (str, "nr") - algorithm that is used to solve the power flow problem. The following algorithms are available: - "nr" Newton-Raphson (pypower implementation with numba accelerations) Used only for positive sequence network Zero and Negative sequence networks use Current Injection method Vnew = Y.inv * Ispecified ( from s_abc/v_abc old) Icalculated = Y * Vnew **calculate_voltage_angles** (bool, "auto") - consider voltage angles in loadflow calculation If True, voltage angles of ext_grids and transformer shifts are considered in the loadflow calculation. Considering the voltage angles is only necessary in meshed networks that are usually found in higher voltage levels. calculate_voltage_angles in "auto" mode defaults to: - True, if the network voltage level is above 70 kV - False otherwise The network voltage level is defined as the maximum rated voltage of any bus in the network that is connected to a line. **max_iteration** (int, "auto") - maximum number of iterations carried out in the power flow algorithm. In "auto" mode, the default value depends on the power flow solver: - 10 for "nr" For three phase calculations, its extended to 3 * max_iteration **tolerance_mva** (float, 1e-8) - loadflow termination condition referring to P / Q mismatch of node power in MVA **trafo_model** - transformer equivalent models - "t" - transformer is modeled as equivalent with the T-model. - "pi" - This is not recommended, since it is less exact than the T-model. So, for three phase load flow, its not implemented **trafo_loading** (str, "current") - mode of calculation for transformer loading Transformer loading can be calculated relative to the rated current or the rated power. In both cases the overall transformer loading is defined as the maximum loading on the two sides of the transformer. - "current"- transformer loading is given as ratio of current flow and rated current of the transformer. This is the recommended setting, since thermal as well as magnetic effects in the transformer depend on the current. - "power" - transformer loading is given as ratio of apparent power flow to the rated apparent power of the transformer. **enforce_q_lims** (bool, False) (Not tested with 3 Phase load flow) - respect generator reactive power limits If True, the reactive power limits in net.gen.max_q_mvar/min_q_mvar are respected in the loadflow. This is done by running a second loadflow if reactive power limits are violated at any generator, so that the runtime for the loadflow will increase if reactive power has to be curtailed. Note: enforce_q_lims only works if algorithm="nr"! **check_connectivity** (bool, True) - Perform an extra connectivity test after the conversion from pandapower to PYPOWER. If True, an extra connectivity test based on SciPy Compressed Sparse Graph Routines is perfomed. If check finds unsupplied buses, they are set out of service in the ppc **voltage_depend_loads** (bool, True) (Not tested with 3 Phase load flow) - consideration of voltage-dependent loads. If False, ``net.load.const_z_percent`` and ``net.load.const_i_percent`` are not considered, i.e. ``net.load.p_mw`` and ``net.load.q_mvar`` are considered as constant-power loads. **consider_line_temperature** (bool, False) (Not tested with 3 Phase load flow) - adjustment of line impedance based on provided line temperature. If True, ``net.line`` must contain a column ``temperature_degree_celsius``. The temperature dependency coefficient alpha must be provided in the ``net.line.alpha`` column, otherwise the default value of 0.004 is used. **KWARGS**: **numba** (bool, True) - Activation of numba JIT compiler in the newton solver If set to True, the numba JIT compiler is used to generate matrices for the powerflow, which leads to significant speed improvements. **switch_rx_ratio** (float, 2) (Not tested with 3 Phase load flow) - rx_ratio of bus-bus-switches. If impedance is zero, buses connected by a closed bus-bus switch are fused to model an ideal bus. Otherwise, they are modelled as branches with resistance defined as z_ohm column in switch table and this parameter **delta_q** (Not tested with 3 Phase load flow) - Reactive power tolerance for option "enforce_q_lims" in kvar - helps convergence in some cases. **trafo3w_losses** (Not tested with 3 Phase load flow) - defines where open loop losses of three-winding transformers are considered. Valid options are "hv", "mv", "lv" for HV/MV/LV side or "star" for the star point. **v_debug** (bool, False) (Not tested with 3 Phase load flow) - if True, voltage values in each newton-raphson iteration are logged in the ppc. **init_vm_pu** (string/float/array/Series, None) (Not tested with 3 Phase load flow) - Allows to define initialization specifically for voltage magnitudes. Only works with ``init == "auto"``! - "auto": all buses are initialized with the mean value of all voltage controlled elements in the grid - "flat" for flat start from 1.0 - "results": voltage magnitude vector is taken from result table - a float with which all voltage magnitudes are initialized - an iterable with a voltage magnitude value for each bus (length and order has to match with the buses in net.bus) - a pandas Series with a voltage magnitude value for each bus (indexes have to match the indexes in net.bus) **init_va_degree** (string/float/array/Series, None) (Not tested with 3 Phase load flow) - Allows to define initialization specifically for voltage angles. Only works with ``init == "auto"``! - "auto": voltage angles are initialized from DC power flow if angles are calculated or as 0 otherwise - "dc": voltage angles are initialized from DC power flow - "flat" for flat start from 0 - "results": voltage angle vector is taken from result table - a float with which all voltage angles are initialized - an iterable with a voltage angle value for each bus (length and order has to match with the buses in net.bus) - a pandas Series with a voltage angle value for each bus (indexes have to match the indexes in net.bus) **recycle** (dict, none) - Reuse of internal powerflow variables for time series calculation. Contains a dict with the following parameters: bus_pq: If True PQ values of buses are updated gen: If True Sbus and the gen table in the ppc are recalculated Ybus: If True the admittance matrix (Ybus, Yf, Yt) is taken from ppc["internal"] and not reconstructed **neglect_open_switch_branches** (bool, False) (Not tested with 3 Phase load flow) - If True no auxiliary buses are created for branches when switches are opened at the branch. Instead branches are set out of service SEE ALSO: pp.add_zero_impedance_parameters(net): To add zero sequence parameters into network from the standard type EXAMPLES: Use this module like this: .. code-block:: python from pandapower.pf.runpp_3ph import runpp_3ph runpp_3ph(net) NOTES: - Three phase load flow uses Sequence Frame for power flow solution. - Three phase system is modelled with earth return. - PH-E load type is called as wye since Neutral and Earth are considered same - This solver has proved successful only for Earthed transformers (i.e Dyn,Yyn,YNyn & Yzn vector groups) """ # ============================================================================= # pandapower settings # ============================================================================= overrule_options = {} if "user_pf_options" in net.keys() and len(net.user_pf_options) > 0: passed_parameters = _passed_runpp_parameters(locals()) overrule_options = { key: val for key, val in net.user_pf_options.items() if key not in passed_parameters.keys() } if numba: numba = _check_if_numba_is_installed(numba) ac = True mode = "pf_3ph" # TODO: Make valid modes (pf, pf_3ph, se, etc.) available in seperate file (similar to idx_bus.py) # v_debug = kwargs.get("v_debug", False) copy_constraints_to_ppc = False if trafo_model == 'pi': raise Not_implemented("Three phase Power Flow doesnot support pi model\ because of lack of accuracy") # if calculate_voltage_angles == "auto": # calculate_voltage_angles = False # hv_buses = np.where(net.bus.vn_kv.values > 70)[0] # Todo: Where does that number come from? # if len(hv_buses) > 0: # line_buses = net.line[["from_bus", "to_bus"]].values.flatten() # if len(set(net.bus.index[hv_buses]) & set(line_buses)) > 0: # scipy spsolve options in NR power flow use_umfpack = kwargs.get("use_umfpack", True) permc_spec = kwargs.get("permc_spec", None) calculate_voltage_angles = True if init == "results" and net.res_bus_3ph.empty: init = "auto" if init == "auto": init = "dc" if calculate_voltage_angles else "flat" default_max_iteration = { "nr": 10, "bfsw": 10, "gs": 10000, "fdxb": 30, "fdbx": 30 } if max_iteration == "auto": max_iteration = default_max_iteration["nr"] neglect_open_switch_branches = kwargs.get("neglect_open_switch_branches", False) only_v_results = kwargs.get("only_v_results", False) net._options = {} _add_ppc_options(net, calculate_voltage_angles=calculate_voltage_angles, trafo_model=trafo_model, check_connectivity=check_connectivity, mode=mode, switch_rx_ratio=switch_rx_ratio, init_vm_pu=init, init_va_degree=init, enforce_q_lims=enforce_q_lims, recycle=None, voltage_depend_loads=False, delta=delta_q,\ neglect_open_switch_branches=neglect_open_switch_branches ) _add_pf_options(net, tolerance_mva=tolerance_mva, trafo_loading=trafo_loading, numba=numba, ac=ac, algorithm="nr", max_iteration=max_iteration,\ only_v_results=only_v_results,v_debug=v_debug, use_umfpack=use_umfpack, permc_spec=permc_spec) net._options.update(overrule_options) _check_bus_index_and_print_warning_if_high(net) _check_gen_index_and_print_warning_if_high(net) # ========================================================================= # pd2ppc conversion # ========================================================================= _, ppci1 = _pd2ppc_recycle(net, 1, recycle=recycle) _, ppci2 = _pd2ppc_recycle(net, 2, recycle=recycle) gs_eg, bs_eg = _add_ext_grid_sc_impedance(net, ppci2) _, ppci0 = _pd2ppc_recycle(net, 0, recycle=recycle) _, bus0, gen0, branch0, _, _, _ = _get_pf_variables_from_ppci(ppci0) base_mva, bus1, gen1, branch1, sl_bus, _, pq_bus = _get_pf_variables_from_ppci( ppci1) _, bus2, gen2, branch2, _, _, _ = _get_pf_variables_from_ppci(ppci2) # initialize the results after the conversion to ppc is done, otherwise init=results does not work init_results(net, "pf_3ph") # ============================================================================= # P Q values aggragated and summed up for each bus to make s_abc matrix # s_abc for wye connections ; s_abc_delta for delta connection # ============================================================================= s_abc_delta, s_abc = _load_mapping(net, ppci1) # ========================================================================= # Construct Sequence Frame Bus admittance matrices Ybus # ========================================================================= ppci0, ppci1, ppci2, y_0_pu, y_1_pu, y_2_pu, y_0_f, y_1_f, _,\ y_0_t, y_1_t, _ = _get_y_bus(ppci0, ppci1, ppci2, recycle) # ========================================================================= # Initial voltage values # ========================================================================= nb = ppci1["bus"].shape[0] # make sure flat start is always respected, even with other voltage data in recycled ppc if init == "flat": v_012_it = np.zeros((3, nb), dtype=np.complex128) v_012_it[1, :] = 1.0 else: v_012_it = np.concatenate([ np.array(ppc["bus"][:, VM] * np.exp(1j * np.deg2rad(ppc["bus"][:, VA]))).reshape( (1, nb)) for ppc in (ppci0, ppci1, ppci2) ], axis=0).astype(np.complex128) # For Delta transformation: # Voltage changed from line-earth to line-line using V_T # s_abc/v_abc will now give line-line currents. This is converted to line-earth # current using I-T v_del_xfmn = np.array([[1, -1, 0], [0, 1, -1], [-1, 0, 1]]) i_del_xfmn = np.array([[1, 0, -1], [-1, 1, 0], [0, -1, 1]]) v_abc_it = sequence_to_phase(v_012_it) # ========================================================================= # Iteration using Power mismatch criterion # ========================================================================= outer_tolerance_mva = 3e-8 count = 0 s_mismatch = np.array([[True], [True]], dtype=bool) t0 = perf_counter() while (s_mismatch > outer_tolerance_mva).any() and count < 30 * max_iteration: # ===================================================================== # Voltages and Current transformation for PQ and Slack bus # ===================================================================== s_abc_pu = -np.divide(s_abc, ppci1["baseMVA"]) s_abc_delta_pu = -np.divide(s_abc_delta, ppci1["baseMVA"]) i_abc_it_wye = (np.divide(s_abc_pu, v_abc_it)).conjugate() i_abc_it_delta = np.matmul(i_del_xfmn, (np.divide( s_abc_delta_pu, np.matmul(v_del_xfmn, v_abc_it))).conjugate()) # For buses with both delta and wye loads we need to sum of their currents # to sum up the currents i_abc_it = i_abc_it_wye + i_abc_it_delta i012_it = phase_to_sequence(i_abc_it) v1_for_s1 = v_012_it[1, :] i1_for_s1 = -i012_it[1, :] v0_pu_it = X012_to_X0(v_012_it) v2_pu_it = X012_to_X2(v_012_it) i0_pu_it = X012_to_X0(i012_it) i2_pu_it = X012_to_X2(i012_it) s1 = np.multiply(v1_for_s1, i1_for_s1.conjugate()) # ============================================================================= # Current used to find S1 Positive sequence power # ============================================================================= ppci1["bus"][pq_bus, PD] = np.real(s1[pq_bus]) * ppci1["baseMVA"] ppci1["bus"][pq_bus, QD] = np.imag(s1[pq_bus]) * ppci1["baseMVA"] # ============================================================================= # Conduct Positive sequence power flow # ============================================================================= _run_newton_raphson_pf(ppci1, net._options) # ============================================================================= # Conduct Negative and Zero sequence power flow # ============================================================================= v0_pu_it = V_from_I(y_0_pu, i0_pu_it) v2_pu_it = V_from_I(y_2_pu, i2_pu_it) # ============================================================================= # Evaluate Positive Sequence Power Mismatch # ============================================================================= i1_from_v_it = I1_from_V012(v_012_it, y_1_pu).flatten() s_from_voltage = S_from_VI_elementwise(v1_for_s1, i1_from_v_it) v1_pu_it = V1_from_ppc(ppci1) v_012_new = combine_X012(v0_pu_it, v1_pu_it, v2_pu_it) s_mismatch = np.abs( np.abs(s1[pq_bus]) - np.abs(s_from_voltage[pq_bus])) v_012_it = v_012_new v_abc_it = sequence_to_phase(v_012_it) count += 1 et = perf_counter() - t0 success = (count < 30 * max_iteration) for ppc in [ppci0, ppci1, ppci2]: ppc["et"] = et ppc["success"] = success # TODO: Add reference to paper to explain the following steps # This is required since the ext_grid power results are not correct if its # not done ref, pv, pq = bustypes(ppci0["bus"], ppci0["gen"]) ref_gens = ppci0["internal"]["ref_gens"] ppci0["bus"][ref, GS] -= gs_eg ppci0["bus"][ref, BS] -= bs_eg y_0_pu, y_0_f, y_0_t = makeYbus(ppci0["baseMVA"], ppci0["bus"], ppci0["branch"]) # revert the change, otherwise repeated calculation with recycled elements will fail ppci0["bus"][ref, GS] += gs_eg ppci0["bus"][ref, BS] += bs_eg # Bus, Branch, and Gen power values bus0, gen0, branch0 = pfsoln(base_mva, bus0, gen0, branch0, y_0_pu, y_0_f, y_0_t, v_012_it[0, :].flatten(), sl_bus, ref_gens) bus1, gen1, branch1 = pfsoln(base_mva, bus1, gen1, branch1, y_1_pu, y_1_f, y_1_t, v_012_it[1, :].flatten(), sl_bus, ref_gens) bus2, gen2, branch2 = pfsoln(base_mva, bus2, gen2, branch2, y_1_pu, y_1_f, y_1_t, v_012_it[2, :].flatten(), sl_bus, ref_gens) ppci0 = _store_results_from_pf_in_ppci(ppci0, bus0, gen0, branch0) ppci1 = _store_results_from_pf_in_ppci(ppci1, bus1, gen1, branch1) ppci2 = _store_results_from_pf_in_ppci(ppci2, bus2, gen2, branch2) i_012_res = _current_from_voltage_results(y_0_pu, y_1_pu, v_012_it) s_012_res = S_from_VI_elementwise(v_012_it, i_012_res) * ppci1["baseMVA"] eg_is_mask = net["_is_elements"]['ext_grid'] ext_grid_lookup = net["_pd2ppc_lookups"]["ext_grid"] eg_is_idx = net["ext_grid"].index.values[eg_is_mask] eg_idx_ppc = ext_grid_lookup[eg_is_idx] """ # 2 ext_grids Fix: Instead of the generator index, bus indices of the generators are used""" eg_bus_idx_ppc = np.real(ppci1["gen"][eg_idx_ppc, GEN_BUS]).astype(int) ppci0["gen"][eg_idx_ppc, PG] = s_012_res[0, eg_bus_idx_ppc].real ppci1["gen"][eg_idx_ppc, PG] = s_012_res[1, eg_bus_idx_ppc].real ppci2["gen"][eg_idx_ppc, PG] = s_012_res[2, eg_bus_idx_ppc].real ppci0["gen"][eg_idx_ppc, QG] = s_012_res[0, eg_bus_idx_ppc].imag ppci1["gen"][eg_idx_ppc, QG] = s_012_res[1, eg_bus_idx_ppc].imag ppci2["gen"][eg_idx_ppc, QG] = s_012_res[2, eg_bus_idx_ppc].imag ppc0 = net["_ppc0"] ppc1 = net["_ppc1"] ppc2 = net["_ppc2"] # ppci doesn't contain out of service elements, but ppc does -> copy results accordingly ppc0 = _copy_results_ppci_to_ppc(ppci0, ppc0, mode=mode) ppc1 = _copy_results_ppci_to_ppc(ppci1, ppc1, mode=mode) ppc2 = _copy_results_ppci_to_ppc(ppci2, ppc2, mode=mode) _extract_results_3ph(net, ppc0, ppc1, ppc2) # Raise error if PF was not successful. If DC -> success is always 1 if not ppci0["success"]: net["converged"] = False _clean_up(net, res=False) raise LoadflowNotConverged("Power Flow {0} did not converge after\ {1} iterations!".format("nr", count)) else: net["converged"] = True _clean_up(net)