diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 79ee2f922..f1f3785d6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,7 @@ Change Log [upcoming release] - 2023-..-.. ------------------------------- +- [ADDED] function to search std_types from the basic standard type library - [ADDED] Documentation for running powerflow using power-grid-model - [ADDED] exporting to :code:`GeoJSON` with all properties from :code:`bus`, :code:`res_bus` and :code:`line`, :code:`res_line` - [ADDED] function to run powerflow using the power-grid-model library @@ -34,8 +35,12 @@ Change Log - [ADDED] option to use a second tap changer for the trafo element - [CHANGED] parameters of function merge_internal_net_and_equivalent_external_net() - [FIXED] :code:`convert_format.py`: update the attributes of the characteristic objects to match the new characteristic +- [FIXED] fixed the wrong id numbers for pypower powerflow algorithms fdxb and fdbx - [FIXED] additional arguments from mpc saved to net._options: create "_options" if it does not exist - [CHANGED] cim2pp: extracted getting default classes, added generic setting datatypes from CGMES XMI schema +- [ADDED] function :code:`getOTDF` to obtain Outage Transfer Distribution Factors, that can be used to analyse outages using the DC approximation of the power system +- [ADDED] function :code:`outage_results_OTDF` to obtain the matrix of results for all outage scenarios, with rows as outage scenarios and columns as branch power flows in that scenario +- [FIXED] add some safeguards for TDPF to avoid numerical issues in some cases [2.13.1] - 2023-05-12 diff --git a/doc/plotting/matplotlib/simple_plot.rst b/doc/plotting/matplotlib/simple_plot.rst index a9ad3b031..5c85069a0 100644 --- a/doc/plotting/matplotlib/simple_plot.rst +++ b/doc/plotting/matplotlib/simple_plot.rst @@ -2,7 +2,7 @@ Simple Plotting ============================= -The function simple_plot() can be used for simple plotting. For advanced possibilities see the tutorials +The function simple_plot() can be used for simple plotting. For advanced possibilities see the `tutorial `_. .. _simple_plot: diff --git a/pandapower/control/controller/trafo/ContinuousTapControl.py b/pandapower/control/controller/trafo/ContinuousTapControl.py index b1bb7c5d5..7c77e2714 100644 --- a/pandapower/control/controller/trafo/ContinuousTapControl.py +++ b/pandapower/control/controller/trafo/ContinuousTapControl.py @@ -4,6 +4,7 @@ # and Energy System Technology (IEE), Kassel. All rights reserved. import numpy as np + from pandapower.auxiliary import read_from_net, write_to_net from pandapower.control.controller.trafo_control import TrafoController @@ -97,6 +98,8 @@ def is_converged(self, net): return True vm_pu = read_from_net(net, "res_bus", self.controlled_bus, "vm_pu", self._read_write_flag) + # this is possible in case the trafo is set out of service by the connectivity check + is_nan = np.isnan(vm_pu) self.tap_pos = read_from_net(net, self.trafotable, self.controlled_tid, "tap_pos", self._read_write_flag) difference = 1 - self.vm_set_pu / vm_pu @@ -110,4 +113,4 @@ def is_converged(self, net): else: converged = np.abs(difference) < self.tol - return np.all(converged) + return np.all(np.logical_or(converged, is_nan)) diff --git a/pandapower/control/controller/trafo/DiscreteTapControl.py b/pandapower/control/controller/trafo/DiscreteTapControl.py index 48ef2bb07..cf4aa2353 100644 --- a/pandapower/control/controller/trafo/DiscreteTapControl.py +++ b/pandapower/control/controller/trafo/DiscreteTapControl.py @@ -3,9 +3,11 @@ # Copyright (c) 2016-2023 by University of Kassel and Fraunhofer Institute for Energy Economics # and Energy System Technology (IEE), Kassel. All rights reserved. import numpy as np + from pandapower.auxiliary import read_from_net, write_to_net from pandapower.control.controller.trafo_control import TrafoController + class DiscreteTapControl(TrafoController): """ Trafo Controller with local tap changer voltage control. @@ -115,6 +117,8 @@ def is_converged(self, net): return True vm_pu = read_from_net(net, "res_bus", self.controlled_bus, "vm_pu", self._read_write_flag) + # this is possible in case the trafo is set out of service by the connectivity check + is_nan = np.isnan(vm_pu) self.tap_pos = read_from_net(net, self.trafotable, self.controlled_tid, "tap_pos", self._read_write_flag) reached_limit = np.where(self.tap_side_coeff * self.tap_sign == 1, @@ -125,5 +129,4 @@ def is_converged(self, net): converged = np.logical_or(reached_limit, np.logical_and(self.vm_lower_pu < vm_pu, vm_pu < self.vm_upper_pu)) - return np.all(converged) - + return np.all(np.logical_or(converged, is_nan)) diff --git a/pandapower/pf/create_jacobian_tdpf.py b/pandapower/pf/create_jacobian_tdpf.py index efae27ed6..b89eeb010 100644 --- a/pandapower/pf/create_jacobian_tdpf.py +++ b/pandapower/pf/create_jacobian_tdpf.py @@ -115,7 +115,7 @@ def calc_r_theta(t_air_pu, a0, a1, a2, i_square_pu, p_loss_pu): 2019, pp. 1-6, doi: 10.1109/EEEIC.2019.8783234. """ t_rise_pu = a0 + a1 * i_square_pu + a2 * np.square(i_square_pu) - t_air_pu - r_theta_pu = t_rise_pu / p_loss_pu + r_theta_pu = t_rise_pu / np.where(p_loss_pu == 0, 1e-6, p_loss_pu) return r_theta_pu diff --git a/pandapower/pf/runpf_pypower.py b/pandapower/pf/runpf_pypower.py index 6972a7739..5b912eb22 100644 --- a/pandapower/pf/runpf_pypower.py +++ b/pandapower/pf/runpf_pypower.py @@ -78,7 +78,7 @@ def _get_options(options, **kwargs): max_iteration = options["max_iteration"] # algorithms implemented within pypower - algorithm_pypower_dict = {'nr': 1, 'fdbx': 2, 'fdxb': 3, 'gs': 4} + algorithm_pypower_dict = {'nr': 1, 'fdxb': 2, 'fdbx': 3, 'gs': 4} ppopt = ppoption(ENFORCE_Q_LIMS=enforce_q_lims, PF_TOL=tolerance_mva, PF_ALG=algorithm_pypower_dict[algorithm], **kwargs) diff --git a/pandapower/plotting/plotly/traces.py b/pandapower/plotting/plotly/traces.py index 798b5beeb..9e698ef71 100644 --- a/pandapower/plotting/plotly/traces.py +++ b/pandapower/plotting/plotly/traces.py @@ -1094,15 +1094,25 @@ def draw_traces(traces, on_map=False, map_style='basic', showlegend=True, figsiz for trace in traces: xs += trace.get('x') or trace['lon'] ys += trace.get('y') or trace['lat'] - x_dropna = pd.Series(xs).dropna() - y_dropna = pd.Series(ys).dropna() - xrange = x_dropna.max() - x_dropna.min() - yrange = y_dropna.max() - y_dropna.min() - ratio = xrange / yrange - if ratio < 1: - aspectratio = (ratio, 1.) + xs_arr = np.array(xs) + ys_arr = np.array(ys) + xrange = np.nanmax(xs_arr) - np.nanmin(xs_arr) + yrange = np.nanmax(ys_arr) - np.nanmin(ys_arr) + + # the ratio only makes sense, if xrange and yrange != 0 + if xrange == 0 and yrange == 0: + aspectratio = (1, 1) + elif xrange == 0: + aspectratio = (0.35, 1) + elif yrange == 0: + aspectratio = (1, 0.35) + else: - aspectratio = (1., 1 / ratio) + ratio = xrange / yrange + if ratio < 1: + aspectratio = (ratio, 1.) + else: + aspectratio = (1., 1 / ratio) aspectratio = np.array(aspectratio) / max(aspectratio) fig['layout']['width'], fig['layout']['height'] = ([ar * figsize * 700 for ar in aspectratio]) diff --git a/pandapower/pypower/makeLODF.py b/pandapower/pypower/makeLODF.py index 65d960b9c..97b5fc93e 100644 --- a/pandapower/pypower/makeLODF.py +++ b/pandapower/pypower/makeLODF.py @@ -66,3 +66,118 @@ def makeLODF(branch, PTDF): update_LODF_diag(LODF) return LODF + + +def makeOTDF(PTDF, LODF, outage_branches): + """ + Compute the Outage Transfer Distribution Factors (OTDF) matrix. + + This function creates the OTDF matrix that relates bus power injections + to branch flows for specified outage scenarios. It's essential that + outage branches do not lead to isolated nodes or disconnected islands + in the grid. + + The grid cannot have isolated nodes or disconnected islands. Use the + pandapower.topology module to identify branches that, if outaged, would + lead to isolated nodes (determine_stubs) or islands (find_graph_characteristics). + + The resulting matrix has a width equal to the number of nodes and a length + equal to the number of outage branches multiplied by the total number of branches. + The dot product of OTDF and the bus power vector in generation reference frame + (positive for generation, negative for consumption - the opposite of res_bus.p_mw) + yields an array with outage branch power flows for every outage scenario, + facilitating the analysis of all outage scenarios under a DC + power flow approximation. + + Parameters + ---------- + PTDF : numpy.ndarray + The Power Transfer Distribution Factor matrix, defining the sensitivity + of branch flows to bus power injections. + LODF : numpy.ndarray + The Line Outage Distribution Factor matrix, describing how branch flows + are affected by outages of other branches. + outage_branches : list or numpy.ndarray + Indices of branches for which outage scenarios are to be considered. + + Returns + ------- + OTDF : numpy.ndarray + The Outage Transfer Distribution Factor matrix. Rows correspond to + outage scenarios, and columns correspond to branch flows. + + Examples + -------- + >>> H = makePTDF(baseMVA, bus, branch) + >>> LODF = makeLODF(branch, H) + >>> outage_branches = [0, 2] # Example branch indices for outage scenarios + >>> OTDF = makeOTDF(H, LODF, outage_branches) + >>> # To obtain a 2D array with the outage results: + >>> outage_results = (OTDF @ Pbus).reshape(len(outage_branches), -1) + + Notes + ----- + - The function assumes a DC power flow model. + - Ensure that the specified outage branches do not lead to grid + disconnection or isolated nodes. + """ + OTDF = np.vstack([PTDF + LODF[:, [i]] @ PTDF[[i], :] for i in outage_branches]) + return OTDF + + +def outage_results_OTDF(OTDF, Pbus, outage_branches): + """ + Calculate the branch power flows for each outage scenario based on the given + Outage Transfer Distribution Factors (OTDF), bus power injections (Pbus), and + specified outage branches. + + This function computes how branch flows are affected under N-1 contingency + scenarios (i.e., for each branch outage specified). It uses the OTDF matrix and + the bus power vector (Pbus) to determine the branch flows in each outage scenario. + + Pbus should represent the net power at each bus in the generation reference case. + + Parameters + ---------- + OTDF : numpy.ndarray + The Outage Transfer Distribution Factor matrix, which relates bus power + injections to branch flows under specific outage scenarios. Its shape + should be (num_outage_scenarios * num_branches, num_buses). + Pbus : numpy.ndarray + A vector representing the net power injections at each bus. Positive values + for generation, negative for consumption. Its length should be equal to + the total number of buses. + outage_branches : numpy.ndarray + An array of indices representing the branches that are outaged in each + scenario. Its length should be equal to the number of outage scenarios. + + Returns + ------- + numpy.ndarray + A 2D array where each row corresponds to an outage scenario and each column + represents the resulting power flow in a branch. The number of rows is equal + to the number of outage scenarios, and the number of columns is equal to the + number of branches. + + Examples + -------- + >>> OTDF = np.array([...]) # example OTDF matrix + >>> Pbus = np.array([...]) # example bus power vector + >>> outage_branches = np.array([...]) # example outage branches + >>> branch_flows = outage_results_OTDF(OTDF,Pbus,outage_branches) + + Notes + ----- + The function assumes a linear relationship between bus power injections and + branch flows, which is typical in DC power flow models. + """ + # get branch flows as an array first: + nminus1_otdf = (OTDF @ Pbus.reshape(-1, 1)) + # reshape to a 2D array with rows relating to outage scenarios and columns to + # the resulting branch power flows + nminus1_otdf = nminus1_otdf.reshape(outage_branches.shape[0], -1) + return nminus1_otdf + + + + diff --git a/pandapower/pypower/newtonpf.py b/pandapower/pypower/newtonpf.py index a0f836fbf..a2bddb8a2 100644 --- a/pandapower/pypower/newtonpf.py +++ b/pandapower/pypower/newtonpf.py @@ -298,7 +298,8 @@ def newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppci, options, makeYbus=None): if tdpf: # update the R, g, b for the tdpf_lines, and the Y-matrices - branch[tdpf_lines, BR_R] = r = r_ref_pu * (1 + alpha_pu * (T - t_ref_pu)) + # here: limit the change of the R to reflect a realistic range of values for T to avoid numerical issues + branch[tdpf_lines, BR_R] = r = r_ref_pu * (1 + alpha_pu * np.clip(np.nan_to_num(T - t_ref_pu), -50, 250 / T_base)) # todo expansion with SSC and VSC (that are not controllable) Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) g, b = calc_g_b(r, x) diff --git a/pandapower/std_types.py b/pandapower/std_types.py index 0d8197fed..20699ade7 100644 --- a/pandapower/std_types.py +++ b/pandapower/std_types.py @@ -107,7 +107,6 @@ def create_std_types(net, data, element="line", overwrite=True, check_required=T INPUT: **net** - The pandapower network - **data** - dictionary of standard type parameter sets **element** - "line", "trafo" or "trafo3w" @@ -224,7 +223,7 @@ def available_std_types(net, element="line"): return std_types.convert_objects() -def parameter_from_std_type(net, parameter, element="line", fill=None): +def parameter_from_std_type(net, parameter, element="line",fill=None): """ Loads standard types data for a parameter, which can be used to add an additional parameter, that is not included in the original pandapower datastructure but is available in the standard @@ -301,8 +300,8 @@ def find_std_type_by_parameter(net, data, element="line", epsilon=0.): OUTPUT: **fitting_types** - list of fitting types or empty list """ - assert epsilon >= 0 fitting_types = [] + assert epsilon >= 0 for name, stp in net.std_types[element].items(): for p, v in list(data.items()): if isinstance(v, float): @@ -314,6 +313,43 @@ def find_std_type_by_parameter(net, data, element="line", epsilon=0.): fitting_types.append(name) return fitting_types +def find_std_type_alternative(net, data, element = "line", voltage_rating = "", epsilon = 0.): + """ + Searches for a std_type that fits all values given in the standard types library with the margin of + epsilon. + + INPUT: + **net** - pandapower network + + **data** - dictionary of standard type parameters + + **element** - type of element ("line" or "trafo") + + **voltage_rating** - voltage rating of the cable ("HV" or "MV" or "LV") + + **epsilon** - tolerance margin for parameter comparison + + OUTPUT: + **fitting_types** - list of fitting types or empty list + """ + + assert epsilon >= 0 + linetypes = basic_line_std_types() + possible_alternatives = [] + fitting_types = [] + for p, v in linetypes.items(): + if voltage_rating == v.get("voltage_rating"): + possible_alternatives.append((p, v)) + for name, stp in possible_alternatives: + for p, v in list(data.items()): + if isinstance(v, float): + if abs(v - stp[p]) > epsilon: + break + elif stp[p] != v: + break + else: + fitting_types.append(name) + return fitting_types def add_zero_impedance_parameters(net): """ @@ -380,7 +416,8 @@ def basic_line_std_types(): "max_i_ka": 0.142, "type": "cs", "q_mm2": 50, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "LV"}, "NAYY 4x120 SE": {"c_nf_per_km": 264, "r_ohm_per_km": 0.225, @@ -388,7 +425,8 @@ def basic_line_std_types(): "max_i_ka": 0.242, "type": "cs", "q_mm2": 120, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "LV"}, "NAYY 4x150 SE": {"c_nf_per_km": 261, "r_ohm_per_km": 0.208, @@ -396,7 +434,8 @@ def basic_line_std_types(): "max_i_ka": 0.270, "type": "cs", "q_mm2": 150, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "LV"}, # Medium Voltage "NA2XS2Y 1x95 RM/25 12/20 kV": @@ -406,7 +445,8 @@ def basic_line_std_types(): "max_i_ka": 0.252, "type": "cs", "q_mm2": 95, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "NA2XS2Y 1x185 RM/25 12/20 kV": {"c_nf_per_km": 273, "r_ohm_per_km": 0.161, @@ -414,7 +454,8 @@ def basic_line_std_types(): "max_i_ka": 0.362, "type": "cs", "q_mm2": 185, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "NA2XS2Y 1x240 RM/25 12/20 kV": {"c_nf_per_km": 304, "r_ohm_per_km": 0.122, @@ -422,7 +463,8 @@ def basic_line_std_types(): "max_i_ka": 0.421, "type": "cs", "q_mm2": 240, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "NA2XS2Y 1x95 RM/25 6/10 kV": {"c_nf_per_km": 315, "r_ohm_per_km": 0.313, @@ -430,7 +472,8 @@ def basic_line_std_types(): "max_i_ka": 0.249, "type": "cs", "q_mm2": 95, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "NA2XS2Y 1x185 RM/25 6/10 kV": {"c_nf_per_km": 406, "r_ohm_per_km": 0.161, @@ -438,7 +481,8 @@ def basic_line_std_types(): "max_i_ka": 0.358, "type": "cs", "q_mm2": 185, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "NA2XS2Y 1x240 RM/25 6/10 kV": {"c_nf_per_km": 456, "r_ohm_per_km": 0.122, @@ -446,7 +490,8 @@ def basic_line_std_types(): "max_i_ka": 0.416, "type": "cs", "q_mm2": 240, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, # additional MV cables "NA2XS2Y 1x150 RM/25 12/20 kV": {"c_nf_per_km": 250, @@ -455,7 +500,8 @@ def basic_line_std_types(): "max_i_ka": 0.319, "type": "cs", "q_mm2": 150, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "NA2XS2Y 1x120 RM/25 12/20 kV": {"c_nf_per_km": 230, "r_ohm_per_km": 0.253, @@ -463,7 +509,8 @@ def basic_line_std_types(): "max_i_ka": 0.283, "type": "cs", "q_mm2": 120, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "NA2XS2Y 1x70 RM/25 12/20 kV": {"c_nf_per_km": 190, "r_ohm_per_km": 0.443, @@ -471,7 +518,8 @@ def basic_line_std_types(): "max_i_ka": 0.220, "type": "cs", "q_mm2": 70, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "NA2XS2Y 1x150 RM/25 6/10 kV": {"c_nf_per_km": 360, "r_ohm_per_km": 0.206, @@ -479,7 +527,8 @@ def basic_line_std_types(): "max_i_ka": 0.315, "type": "cs", "q_mm2": 150, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "NA2XS2Y 1x120 RM/25 6/10 kV": {"c_nf_per_km": 340, "r_ohm_per_km": 0.253, @@ -487,7 +536,8 @@ def basic_line_std_types(): "max_i_ka": 0.280, "type": "cs", "q_mm2": 120, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "NA2XS2Y 1x70 RM/25 6/10 kV": {"c_nf_per_km": 280, "r_ohm_per_km": 0.443, @@ -495,7 +545,8 @@ def basic_line_std_types(): "max_i_ka": 0.217, "type": "cs", "q_mm2": 70, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, # High Voltage "N2XS(FL)2Y 1x120 RM/35 64/110 kV": @@ -505,7 +556,8 @@ def basic_line_std_types(): "max_i_ka": 0.366, "type": "cs", "q_mm2": 120, - "alpha": alpha_cu}, + "alpha": alpha_cu, + "voltage_rating": "HV"}, "N2XS(FL)2Y 1x185 RM/35 64/110 kV": {"c_nf_per_km": 125, "r_ohm_per_km": 0.099, @@ -513,7 +565,8 @@ def basic_line_std_types(): "max_i_ka": 0.457, "type": "cs", "q_mm2": 185, - "alpha": alpha_cu}, + "alpha": alpha_cu, + "voltage_rating": "HV"}, "N2XS(FL)2Y 1x240 RM/35 64/110 kV": {"c_nf_per_km": 135, "r_ohm_per_km": 0.075, @@ -521,7 +574,8 @@ def basic_line_std_types(): "max_i_ka": 0.526, "type": "cs", "q_mm2": 240, - "alpha": alpha_cu}, + "alpha": alpha_cu, + "voltage_rating": "HV"}, "N2XS(FL)2Y 1x300 RM/35 64/110 kV": {"c_nf_per_km": 144, "r_ohm_per_km": 0.060, @@ -529,7 +583,8 @@ def basic_line_std_types(): "max_i_ka": 0.588, "type": "cs", "q_mm2": 300, - "alpha": alpha_cu}, + "alpha": alpha_cu, + "voltage_rating": "HV"}, # Overhead Lines, all from S.742f, Heuck: Elektrische Energieversorgung - # Vierweg+Teubner 2013 @@ -543,7 +598,8 @@ def basic_line_std_types(): "max_i_ka": 0.105, "type": "ol", "q_mm2": 16, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "LV"}, "24-AL1/4-ST1A 0.4": {"c_nf_per_km": 11.25, "r_ohm_per_km": 1.2012, @@ -551,7 +607,8 @@ def basic_line_std_types(): "max_i_ka": 0.140, "type": "ol", "q_mm2": 24, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "LV"}, "48-AL1/8-ST1A 0.4": {"c_nf_per_km": 12.2, "r_ohm_per_km": 0.5939, @@ -559,7 +616,8 @@ def basic_line_std_types(): "max_i_ka": .210, "type": "ol", "q_mm2": 48, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "LV"}, "94-AL1/15-ST1A 0.4": {"c_nf_per_km": 13.2, "r_ohm_per_km": 0.3060, @@ -567,7 +625,8 @@ def basic_line_std_types(): "max_i_ka": 0.350, "type": "ol", "q_mm2": 94, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "LV"}, # Medium Voltage "34-AL1/6-ST1A 10.0": @@ -577,7 +636,8 @@ def basic_line_std_types(): "max_i_ka": 0.170, "type": "ol", "q_mm2": 34, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "48-AL1/8-ST1A 10.0": {"c_nf_per_km": 10.1, "r_ohm_per_km": 0.5939, @@ -585,7 +645,8 @@ def basic_line_std_types(): "max_i_ka": 0.210, "type": "ol", "q_mm2": 48, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "70-AL1/11-ST1A 10.0": {"c_nf_per_km": 10.4, "r_ohm_per_km": 0.4132, @@ -593,7 +654,8 @@ def basic_line_std_types(): "max_i_ka": 0.290, "type": "ol", "q_mm2": 70, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "94-AL1/15-ST1A 10.0": {"c_nf_per_km": 10.75, "r_ohm_per_km": 0.3060, @@ -601,7 +663,8 @@ def basic_line_std_types(): "max_i_ka": 0.350, "type": "ol", "q_mm2": 94, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "122-AL1/20-ST1A 10.0": {"c_nf_per_km": 11.1, "r_ohm_per_km": 0.2376, @@ -609,7 +672,8 @@ def basic_line_std_types(): "max_i_ka": 0.410, "type": "ol", "q_mm2": 122, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "149-AL1/24-ST1A 10.0": {"c_nf_per_km": 11.25, "r_ohm_per_km": 0.1940, @@ -617,7 +681,8 @@ def basic_line_std_types(): "max_i_ka": 0.470, "type": "ol", "q_mm2": 149, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "34-AL1/6-ST1A 20.0": {"c_nf_per_km": 9.15, "r_ohm_per_km": 0.8342, @@ -625,7 +690,8 @@ def basic_line_std_types(): "max_i_ka": 0.170, "type": "ol", "q_mm2": 34, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "48-AL1/8-ST1A 20.0": {"c_nf_per_km": 9.5, "r_ohm_per_km": 0.5939, @@ -633,7 +699,8 @@ def basic_line_std_types(): "max_i_ka": 0.210, "type": "ol", "q_mm2": 48, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "70-AL1/11-ST1A 20.0": {"c_nf_per_km": 9.7, "r_ohm_per_km": 0.4132, @@ -641,7 +708,8 @@ def basic_line_std_types(): "max_i_ka": 0.290, "type": "ol", "q_mm2": 70, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "94-AL1/15-ST1A 20.0": {"c_nf_per_km": 10, "r_ohm_per_km": 0.3060, @@ -649,7 +717,8 @@ def basic_line_std_types(): "max_i_ka": 0.350, "type": "ol", "q_mm2": 94, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "122-AL1/20-ST1A 20.0": {"c_nf_per_km": 10.3, "r_ohm_per_km": 0.2376, @@ -657,7 +726,8 @@ def basic_line_std_types(): "max_i_ka": 0.410, "type": "ol", "q_mm2": 122, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "149-AL1/24-ST1A 20.0": {"c_nf_per_km": 10.5, "r_ohm_per_km": 0.1940, @@ -665,7 +735,8 @@ def basic_line_std_types(): "max_i_ka": 0.470, "type": "ol", "q_mm2": 149, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "184-AL1/30-ST1A 20.0": {"c_nf_per_km": 10.75, "r_ohm_per_km": 0.1571, @@ -673,7 +744,8 @@ def basic_line_std_types(): "max_i_ka": 0.535, "type": "ol", "q_mm2": 184, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, "243-AL1/39-ST1A 20.0": {"c_nf_per_km": 11, "r_ohm_per_km": 0.1188, @@ -681,7 +753,8 @@ def basic_line_std_types(): "max_i_ka": 0.645, "type": "ol", "q_mm2": 243, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "MV"}, # High Voltage # c acd x values are estimated for 4 m conductor distance, single bundle and "Donaumast" @@ -692,7 +765,8 @@ def basic_line_std_types(): "max_i_ka": 0.210, "type": "ol", "q_mm2": 48, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, "70-AL1/11-ST1A 110.0": {"c_nf_per_km": 8.4, "r_ohm_per_km": 0.4132, @@ -700,7 +774,8 @@ def basic_line_std_types(): "max_i_ka": 0.290, "type": "ol", "q_mm2": 70, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, "94-AL1/15-ST1A 110.0": {"c_nf_per_km": 8.65, "r_ohm_per_km": 0.3060, @@ -708,7 +783,8 @@ def basic_line_std_types(): "max_i_ka": 0.350, "type": "ol", "q_mm2": 94, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, "122-AL1/20-ST1A 110.0": {"c_nf_per_km": 8.5, "r_ohm_per_km": 0.2376, @@ -716,7 +792,8 @@ def basic_line_std_types(): "max_i_ka": 0.410, "type": "ol", "q_mm2": 122, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, "149-AL1/24-ST1A 110.0": {"c_nf_per_km": 8.75, "r_ohm_per_km": 0.1940, @@ -724,7 +801,8 @@ def basic_line_std_types(): "max_i_ka": 0.470, "type": "ol", "q_mm2": 149, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, "184-AL1/30-ST1A 110.0": {"c_nf_per_km": 8.8, "r_ohm_per_km": 0.1571, @@ -732,7 +810,8 @@ def basic_line_std_types(): "max_i_ka": 0.535, "type": "ol", "q_mm2": 184, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, "243-AL1/39-ST1A 110.0": {"c_nf_per_km": 9, "r_ohm_per_km": 0.1188, @@ -740,7 +819,8 @@ def basic_line_std_types(): "max_i_ka": 0.645, "type": "ol", "q_mm2": 243, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, "305-AL1/39-ST1A 110.0": {"c_nf_per_km": 9.2, "r_ohm_per_km": 0.0949, @@ -748,7 +828,8 @@ def basic_line_std_types(): "max_i_ka": 0.74, "type": "ol", "q_mm2": 305, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, "490-AL1/64-ST1A 110.0": {"c_nf_per_km": 9.75, "r_ohm_per_km": 0.059, @@ -756,7 +837,8 @@ def basic_line_std_types(): "max_i_ka": 0.960, "type": "ol", "q_mm2": 490, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, "679-AL1/86-ST1A 110.0": {"c_nf_per_km": 9.95, "r_ohm_per_km": 0.042, @@ -764,7 +846,8 @@ def basic_line_std_types(): "max_i_ka": 1.150, "type": "ol", "q_mm2": 679, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, # Transmission System # The following values of c and x depend on the geometries of the overhead line @@ -777,7 +860,8 @@ def basic_line_std_types(): "max_i_ka": 0.96, "type": "ol", "q_mm2": 490, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, "679-AL1/86-ST1A 220.0": {"c_nf_per_km": 11.7, "r_ohm_per_km": 0.042, @@ -785,7 +869,8 @@ def basic_line_std_types(): "max_i_ka": 1.150, "type": "ol", "q_mm2": 679, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, "490-AL1/64-ST1A 380.0": {"c_nf_per_km": 11, "r_ohm_per_km": 0.059, @@ -793,7 +878,8 @@ def basic_line_std_types(): "max_i_ka": 0.96, "type": "ol", "q_mm2": 490, - "alpha": alpha_al}, + "alpha": alpha_al, + "voltage_rating": "HV"}, "679-AL1/86-ST1A 380.0": {"c_nf_per_km": 14.6, "r_ohm_per_km": 0.042, @@ -801,7 +887,8 @@ def basic_line_std_types(): "max_i_ka": 1.150, "type": "ol", "q_mm2": 679, - "alpha": alpha_al} + "alpha": alpha_al, + "voltage_rating": "HV"} } return linetypes diff --git a/pandapower/test/api/test_std_types.py b/pandapower/test/api/test_std_types.py index 2b371347c..ff4f60940 100644 --- a/pandapower/test/api/test_std_types.py +++ b/pandapower/test/api/test_std_types.py @@ -227,6 +227,7 @@ def test_find_line_type(): name = "test_line1" typdata = {"c_nf_per_km": c, "r_ohm_per_km": r, "x_ohm_per_km": x, "max_i_ka": i} pp.create_std_type(net, data=typdata, name=name, element="line") + fitting_type = pp.find_std_type_by_parameter(net, typdata) assert len(fitting_type) == 1 assert fitting_type[0] == name @@ -235,7 +236,25 @@ def test_find_line_type(): assert len(fitting_type) == 1 assert fitting_type[0] == name - fitting_type = pp.find_std_type_by_parameter(net, {"r_ohm_per_km":r+0.07}, epsilon=.06) +def test_find_std_alternative(): + net = pp.create_empty_network() + c = 210 + r = 0.642 + x = 0.083 + i = 0.142 + vr = "LV" + ## {'NAYY 4x50 SE': {'c_nf_per_km': 210, 'r_ohm_per_km': 0.642, 'x_ohm_per_km': 0.083, 'max_i_ka': 0.142, 'voltage_rating': 'LV'} + # Assuming we are looking for the cable NAYY 4X50 SE with a maximum ampacity of 0.142 A + name ='NAYY 4x50 SE' + typdata = {"c_nf_per_km": c, "r_ohm_per_km": r, "x_ohm_per_km": x, "max_i_ka": i, "voltage_rating": vr} + fitting_type = pp.find_std_type_alternative(net, {"r_ohm_per_km":r+0.05}, voltage_rating ="LV", epsilon=0.06) + assert len(fitting_type) == 1 + assert fitting_type[0] == name + + fitting_type = pp.find_std_type_alternative(net, {"r_ohm_per_km":r+0.07}, voltage_rating ="LV", epsilon=0.06) + assert len(fitting_type) == 0 + + fitting_type = pp.find_std_type_alternative(net, {"r_ohm_per_km":r+0.07}, voltage_rating ="MV", epsilon=0.06) assert len(fitting_type) == 0 def test_change_type_line(): diff --git a/pandapower/test/loadflow/test_PTDF_LODF.py b/pandapower/test/loadflow/test_PTDF_LODF.py index f0ac6e58f..7e42fa507 100644 --- a/pandapower/test/loadflow/test_PTDF_LODF.py +++ b/pandapower/test/loadflow/test_PTDF_LODF.py @@ -11,7 +11,7 @@ import pandapower.networks as nw from pandapower.pd2ppc import _pd2ppc from pandapower.pypower.makePTDF import makePTDF -from pandapower.pypower.makeLODF import makeLODF +from pandapower.pypower.makeLODF import makeLODF, makeOTDF, outage_results_OTDF from pandapower.test.loadflow.result_test_network_generator import result_test_network_generator_dcpp from pandapower.test.helper_functions import add_grid_connection, create_test_line, assert_net_equal @@ -67,5 +67,69 @@ def test_LODF(): raise AssertionError("LODF has wrong dimension") +def test_OTDF(): + net = nw.case9() + mg = pp.topology.create_nxgraph(net, respect_switches=True) + # roots = np.r_[net.ext_grid.bus.values, net.gen.bus.values] + # stubs = pp.topology.determine_stubs(net, roots=roots, mg=mg, respect_switches=True) # no lines are stubs here? + # stubs = pp.toolbox.get_connected_elements(net, "line", roots) # because not n-1 lines here are those + c = pp.topology.find_graph_characteristics(g=mg, roots=net.ext_grid.bus.values, characteristics=["bridges"]) + bridges = np.array([pp.topology.lines_on_path(mg, p) for p in c["bridges"]]).flatten() + # outage_lines = [i for i in net.line.index.values if i not in stubs and i not in bridges] + outage_lines = np.array([i for i in net.line.index.values if i not in bridges]) + pp.rundcpp(net) + _, ppci = _pd2ppc(net) + ptdf = makePTDF(ppci["baseMVA"], ppci["bus"], ppci["branch"]) + lodf = makeLODF(ppci["branch"], ptdf) + OTDF = makeOTDF(ptdf, lodf, outage_lines) + Pbus = -net.res_bus.p_mw.values # must be in generation reference frame + nminus1_otdf = (OTDF @ Pbus.reshape(-1, 1)).reshape(outage_lines.shape[0], -1) + + # Test selected outages + n_lines = len(net.line) + for outage, line in enumerate(outage_lines): + otdf_outage_result = (OTDF[outage * n_lines:outage * n_lines + n_lines, :] @ Pbus) + + # Run power flow for the outage scenario + net.line.at[line, "in_service"] = False + pp.rundcpp(net) + pf_outage_result = net.res_line.p_from_mw.values + net.line.at[line, "in_service"] = True + + # Compare the results + assert np.allclose(otdf_outage_result, pf_outage_result, rtol=0, atol=1e-12) + + +def test_OTDF_outage_results(): + net = nw.case9() + mg = pp.topology.create_nxgraph(net, respect_switches=True) + # roots = np.r_[net.ext_grid.bus.values, net.gen.bus.values] + # stubs = pp.topology.determine_stubs(net, roots=roots, mg=mg, respect_switches=True) # no lines are stubs here? + # stubs = pp.toolbox.get_connected_elements(net, "line", roots) # because not n-1 lines here are those + c = pp.topology.find_graph_characteristics(g=mg, roots=net.ext_grid.bus.values, characteristics=["bridges"]) + bridges = np.array([pp.topology.lines_on_path(mg, p) for p in c["bridges"]]).flatten() + # outage_lines = [i for i in net.line.index.values if i not in stubs and i not in bridges] + outage_lines = np.array([i for i in net.line.index.values if i not in bridges]) + pp.rundcpp(net) + _, ppci = _pd2ppc(net) + ptdf = makePTDF(ppci["baseMVA"], ppci["bus"], ppci["branch"]) + lodf = makeLODF(ppci["branch"], ptdf) + OTDF = makeOTDF(ptdf, lodf, outage_lines) + Pbus = -net.res_bus.p_mw.values # must be in generation reference frame + nminus1_otdf = outage_results_OTDF(OTDF, Pbus, outage_lines) + + # now obtain the outage results by performing power flow calculations: + nminus1_pf = [] + for i in outage_lines: + net.line.at[i, "in_service"] = False + pp.rundcpp(net) + nminus1_pf.append(net.res_line.p_from_mw.values.copy()) + net.line.at[i, "in_service"] = True + + nminus1_pf = np.vstack(nminus1_pf) + + assert np.allclose(nminus1_otdf, nminus1_pf, rtol=0, atol=1e-12) + + if __name__ == "__main__": pytest.main([__file__, "-xs"]) diff --git a/pandapower/topology/graph_searches.py b/pandapower/topology/graph_searches.py index 832641f70..55e002f47 100644 --- a/pandapower/topology/graph_searches.py +++ b/pandapower/topology/graph_searches.py @@ -87,7 +87,7 @@ def connected_components(mg, notravbuses=set()): def calc_distance_to_bus(net, bus, respect_switches=True, nogobuses=None, - notravbuses=None, weight='weight'): + notravbuses=None, weight='weight', g=None): """ Calculates the shortest distance between a source bus and all buses connected to it. @@ -110,6 +110,8 @@ def calc_distance_to_bus(net, bus, respect_switches=True, nogobuses=None, **weight** (string, None) – Edge data key corresponding to the edge weight. + **g** (nx.MultiGraph, None) – MultiGraph of the network. If None, the graph will be created. + OUTPUT: **dist** - Returns a pandas series with containing all distances to the source bus in km. If weight=None dist is the topological distance (int). @@ -120,8 +122,9 @@ def calc_distance_to_bus(net, bus, respect_switches=True, nogobuses=None, dist = top.calc_distance_to_bus(net, 5) """ - g = create_nxgraph(net, respect_switches=respect_switches, nogobuses=nogobuses, - notravbuses=notravbuses) + if g is None: + g = create_nxgraph(net, respect_switches=respect_switches, nogobuses=nogobuses, + notravbuses=notravbuses) return pd.Series(nx.single_source_dijkstra_path_length(g, bus, weight=weight)) diff --git a/tutorials/sc_1ph_vector_group_trafo3w.ipynb b/tutorials/shortcircuit/sc_1ph_vector_group_trafo3w.ipynb similarity index 100% rename from tutorials/sc_1ph_vector_group_trafo3w.ipynb rename to tutorials/shortcircuit/sc_1ph_vector_group_trafo3w.ipynb diff --git a/tutorials/sc_power_station_unit.ipynb b/tutorials/shortcircuit/sc_power_station_unit.ipynb similarity index 100% rename from tutorials/sc_power_station_unit.ipynb rename to tutorials/shortcircuit/sc_power_station_unit.ipynb diff --git a/tutorials/sc_wind_power_station_units.ipynb b/tutorials/shortcircuit/sc_wind_power_station_units.ipynb similarity index 100% rename from tutorials/sc_wind_power_station_units.ipynb rename to tutorials/shortcircuit/sc_wind_power_station_units.ipynb diff --git a/tutorials/shortcircuit.ipynb b/tutorials/shortcircuit/shortcircuit.ipynb similarity index 100% rename from tutorials/shortcircuit.ipynb rename to tutorials/shortcircuit/shortcircuit.ipynb diff --git a/tutorials/shortcircuit_renewables.ipynb b/tutorials/shortcircuit/shortcircuit_renewables.ipynb similarity index 100% rename from tutorials/shortcircuit_renewables.ipynb rename to tutorials/shortcircuit/shortcircuit_renewables.ipynb