diff --git a/pandapower/_version.py b/pandapower/_version.py index a3a3d8636..e5a5ce769 100644 --- a/pandapower/_version.py +++ b/pandapower/_version.py @@ -1,4 +1,4 @@ import importlib.metadata __version__ = importlib.metadata.version("pandapower") -__format_version__ = "3.0.0" +__format_version__ = "3.1.0" diff --git a/pandapower/build_branch.py b/pandapower/build_branch.py index 8d5930bc9..be7155d9e 100644 --- a/pandapower/build_branch.py +++ b/pandapower/build_branch.py @@ -12,8 +12,8 @@ import pandas as pd from pandapower.auxiliary import get_values -from pandapower.pypower.idx_brch import F_BUS, T_BUS, BR_R, BR_X, BR_B, BR_G, TAP, SHIFT, BR_STATUS, RATE_A, \ - BR_R_ASYM, BR_X_ASYM, BR_G_ASYM, BR_B_ASYM, branch_cols +from pandapower.pypower.idx_brch import F_BUS, T_BUS, BR_R, BR_X, BR_B, BR_G, TAP, SHIFT, BR_STATUS, ANGMAX, ANGMIN, \ + RATE_A, BR_R_ASYM, BR_X_ASYM, BR_G_ASYM, BR_B_ASYM, branch_cols from pandapower.pypower.idx_brch_dc import branch_dc_cols, DC_RATE_A, DC_RATE_B, DC_RATE_C, DC_BR_STATUS, DC_F_BUS, \ DC_T_BUS, DC_BR_R, DC_BR_G from pandapower.pypower.idx_brch_tdpf import BR_R_REF_OHM_PER_KM, BR_LENGTH_KM, RATE_I_KA, T_START_C, R_THETA, \ @@ -207,6 +207,8 @@ def _calc_line_parameter(net, ppc, elm="line", ppc_elm="branch"): branch[f:t, T_BUS] = to_bus branch[f:t, BR_R] = line["r_ohm_per_km"].values * length_km / baseR / parallel branch[f:t, BR_X] = line["x_ohm_per_km"].values * length_km / baseR / parallel + branch[f:t, ANGMAX] = line["max_theta_deg"].values + branch[f:t, ANGMIN] = line["min_theta_deg"].values if net._options["tdpf"]: branch[f:t, TDPF] = line["in_service"].values & line["tdpf"].fillna(False).values.astype(bool) @@ -360,6 +362,8 @@ def _calc_trafo_parameter(net, ppc, update_vk_values: bool=True): branch[f:t, TAP] = ratio branch[f:t, SHIFT] = shift branch[f:t, BR_STATUS] = trafo["in_service"].values + branch[f:t, ANGMAX] = trafo["max_theta_deg"].values + branch[f:t, ANGMIN] = trafo["min_theta_deg"].values if any(trafo.df.values <= 0): raise UserWarning("Rating factor df must be positive. Transformers with false " "rating factors: %s" % trafo.query('df<=0').index.tolist()) diff --git a/pandapower/convert_format.py b/pandapower/convert_format.py index 227c2fe9a..3ac4fbec2 100644 --- a/pandapower/convert_format.py +++ b/pandapower/convert_format.py @@ -315,6 +315,10 @@ def _add_missing_columns(net, elements_to_deserialize): for element in update_elements: if "df" not in net[element]: net[element]["df"] = 1.0 + if "max_theta_deg" not in net[element]: + net[element]["max_theta_deg"] = 360.0 + if "min_theta_deg" not in net[element]: + net[element]["min_theta_deg"] = -360.0 if _check_elements_to_deserialize('bus', elements_to_deserialize) \ and _check_elements_to_deserialize('bus_geodata', elements_to_deserialize) \ diff --git a/pandapower/converter/pypower/from_ppc.py b/pandapower/converter/pypower/from_ppc.py index b851934e7..4f632ff85 100644 --- a/pandapower/converter/pypower/from_ppc.py +++ b/pandapower/converter/pypower/from_ppc.py @@ -230,6 +230,8 @@ def _from_ppc_branch(net, ppc, f_hz, **kwargs): c_nf_per_km=(ppc['branch'][is_line, BR_B]/Zni[is_line]/omega*1e9/2), g_us_per_km=(br_g[is_line]/Zni[is_line]*1e6/2), max_i_ka=max_i_ka[is_line], type='ol', max_loading_percent=100, + max_theta_deg=ppc['branch'][is_line, ANGMAX], + min_theta_deg=ppc['branch'][is_line, ANGMIN], in_service=ppc['branch'][is_line, BR_STATUS].astype(bool)) # --- create transformer @@ -290,6 +292,7 @@ def _from_ppc_branch(net, ppc, f_hz, **kwargs): net, hv_buses=net.bus.index[hv_bus], lv_buses=net.bus.index[lv_bus], sn_mva=sn, vn_hv_kv=vn_hv_kv, vn_lv_kv=vn_lv_kv, name=bra_name[is_trafo], vk_percent=vk_percent, vkr_percent=vkr_percent, + max_theta_deg=ppc['branch'][is_trafo, ANGMAX], min_theta_deg=ppc['branch'][is_trafo, ANGMIN], max_loading_percent=100, pfe_kw=pfe_kw, i0_percent=i0_percent, shift_degree=ppc['branch'][is_trafo, SHIFT], tap_step_percent=np.abs(ratio_1)*100, tap_pos=np.sign(ratio_1), diff --git a/pandapower/create.py b/pandapower/create.py index 7e98c7954..1e379dd6d 100644 --- a/pandapower/create.py +++ b/pandapower/create.py @@ -207,6 +207,8 @@ def create_empty_network(name="", f_hz=50., sn_mva=1, add_stdtypes=True): ("c_nf_per_km", "f8"), ("g_us_per_km", "f8"), ("max_i_ka", "f8"), + ("max_theta_deg", "f8"), + ("min_theta_deg", "f8"), ("df", "f8"), ("parallel", "u4"), ("type", dtype(object)), @@ -245,6 +247,8 @@ def create_empty_network(name="", f_hz=50., sn_mva=1, add_stdtypes=True): ("tap_step_degree", "f8"), ("tap_pos", "i4"), ("tap_phase_shifter", 'bool'), + ("max_theta_deg", "f8"), + ("min_theta_deg", "f8"), ("parallel", "u4"), ("df", "f8"), ("in_service", 'bool')], @@ -2306,7 +2310,8 @@ def create_line(net, from_bus, to_bus, length_km, std_type, name=None, index=Non v = { "name": name, "length_km": length_km, "from_bus": from_bus, "to_bus": to_bus, "in_service": bool(in_service), "std_type": std_type, - "df": df, "parallel": parallel + "df": df, "parallel": parallel, + "max_theta_deg": 360., "min_theta_deg": -360., } lineparam = load_std_type(net, std_type, "line") @@ -2569,7 +2574,8 @@ def create_lines(net, from_buses, to_buses, length_km, std_type, name=None, inde entries = {"from_bus": from_buses, "to_bus": to_buses, "length_km": length_km, "std_type": std_type, "name": name, "df": df, "parallel": parallel, - "in_service": in_service} + "in_service": in_service, + "max_theta_deg": 360., "min_theta_deg": -360.} # add std type data if isinstance(std_type, str): @@ -2739,6 +2745,7 @@ def create_lines_dc(net, from_buses_dc, to_buses_dc, length_km, std_type, name=N def create_line_from_parameters(net, from_bus, to_bus, length_km, r_ohm_per_km, x_ohm_per_km, c_nf_per_km, max_i_ka, name=None, index=None, type=None, + max_theta_deg=360., min_theta_deg=-360., geodata=None, in_service=True, df=1., parallel=1, g_us_per_km=0., max_loading_percent=nan, alpha=nan, temperature_degree_celsius=nan, r0_ohm_per_km=nan, @@ -2780,6 +2787,10 @@ def create_line_from_parameters(net, from_bus, to_bus, length_km, r_ohm_per_km, **type** (str, None) - type of line ("ol" for overhead line or "cs" for cable system) + **max_theta_deg** (float, 360) - maximal phase angle difference (only needed for OPF) + + **min_theta_deg** (float, -360) - minimal phase angle difference (only needed for OPF) + **df** (float, 1) - derating factor: maximal current of line in relation to nominal current\ of line (from 0 to 1) @@ -2847,7 +2858,8 @@ def create_line_from_parameters(net, from_bus, to_bus, length_km, r_ohm_per_km, "to_bus": to_bus, "in_service": bool(in_service), "std_type": None, "df": df, "r_ohm_per_km": r_ohm_per_km, "x_ohm_per_km": x_ohm_per_km, "c_nf_per_km": c_nf_per_km, "max_i_ka": max_i_ka, "parallel": parallel, "type": type, - "g_us_per_km": g_us_per_km + "g_us_per_km": g_us_per_km, + "max_theta_deg": max_theta_deg, "min_theta_deg": min_theta_deg } tdpf_columns = ("wind_speed_m_per_s", "wind_angle_degree", "conductor_outer_diameter_m", @@ -3011,6 +3023,7 @@ def create_line_dc_from_parameters(net, from_bus_dc, to_bus_dc, length_km, r_ohm def create_lines_from_parameters(net, from_buses, to_buses, length_km, r_ohm_per_km, x_ohm_per_km, c_nf_per_km, max_i_ka, name=None, index=None, type=None, + max_theta_deg=360., min_theta_deg=-360., geodata=None, in_service=True, df=1., parallel=1, g_us_per_km=0., max_loading_percent=nan, alpha=nan, temperature_degree_celsius=nan, r0_ohm_per_km=nan, @@ -3055,6 +3068,10 @@ def create_lines_from_parameters(net, from_buses, to_buses, length_km, r_ohm_per **type** (list of string, None) - type of line ("ol" for overhead line or "cs" for cable system) + **max_theta_deg** (list of float, 360) - maximal phase angle difference (only needed for OPF) + + **min_theta_deg** (list of float, -360) - minimal phase angle difference (only needed for OPF) + **df** (list of float, 1) - derating factor: maximal current of line in relation to nominal current\ of line (from 0 to 1) @@ -3117,7 +3134,8 @@ def create_lines_from_parameters(net, from_buses, to_buses, length_km, r_ohm_per entries = {"from_bus": from_buses, "to_bus": to_buses, "length_km": length_km, "type": type, "r_ohm_per_km": r_ohm_per_km, "x_ohm_per_km": x_ohm_per_km, "c_nf_per_km": c_nf_per_km, "max_i_ka": max_i_ka, "g_us_per_km": g_us_per_km, - "name": name, "df": df, "parallel": parallel, "in_service": in_service} + "name": name, "df": df, "parallel": parallel, "in_service": in_service, + "max_theta_deg": max_theta_deg, "min_theta_deg": min_theta_deg} _add_to_entries_if_not_nan(net, "line", entries, index, "max_loading_percent", max_loading_percent) @@ -3359,7 +3377,8 @@ def create_transformer(net, hv_bus, lv_bus, std_type, name=None, tap_pos=nan, in v = { "name": name, "hv_bus": hv_bus, "lv_bus": lv_bus, - "in_service": bool(in_service), "std_type": std_type + "in_service": bool(in_service), "std_type": std_type, + "max_theta_deg": 360., "min_theta_deg": -360. } ti = load_std_type(net, std_type, "trafo") @@ -3425,6 +3444,7 @@ def create_transformer_from_parameters(net, hv_bus, lv_bus, sn_mva, vn_hv_kv, vn tap_min=nan, tap_step_percent=nan, tap_step_degree=nan, tap_pos=nan, tap_phase_shifter=False, in_service=True, name=None, vector_group=None, index=None, + max_theta_deg=360., min_theta_deg=-360., max_loading_percent=nan, parallel=1, df=1., vk0_percent=nan, vkr0_percent=nan, mag0_percent=nan, mag0_rx=nan, @@ -3510,6 +3530,10 @@ def create_transformer_from_parameters(net, hv_bus, lv_bus, sn_mva, vn_hv_kv, vn **index** (int, None) - Force a specified ID if it is available. If None, the index one \ higher than the highest already existing index is selected. + **max_theta_deg** (float, 360) - maximal phase angle difference (only needed for OPF) + + **min_theta_deg** (float, -360) - minimal phase angle difference (only needed for OPF) + **max_loading_percent (float)** - maximum current loading (only needed for OPF) **df** (float) - derating factor: maximal current of transformer in relation to nominal \ @@ -3588,7 +3612,9 @@ def create_transformer_from_parameters(net, hv_bus, lv_bus, sn_mva, vn_hv_kv, vn "tap_max": tap_max, "tap_min": tap_min, "shift_degree": shift_degree, "tap_side": tap_side, "tap_step_percent": tap_step_percent, "tap_step_degree": tap_step_degree, - "tap_phase_shifter": tap_phase_shifter, "parallel": parallel, "df": df} + "tap_phase_shifter": tap_phase_shifter, "parallel": parallel, "df": df, + "max_theta_deg": max_theta_deg, "min_theta_deg": min_theta_deg + } if ("tap_neutral" in v) and (tap_pos is nan): v["tap_pos"] = v["tap_neutral"] @@ -3638,7 +3664,9 @@ def create_transformers_from_parameters(net, hv_buses, lv_buses, sn_mva, vn_hv_k tap_side=None, tap_neutral=nan, tap_max=nan, tap_min=nan, tap_step_percent=nan, tap_step_degree=nan, tap_pos=nan, tap_phase_shifter=False, in_service=True, name=None, - vector_group=None, index=None, max_loading_percent=nan, + vector_group=None, index=None, + max_theta_deg=360., min_theta_deg=-360., + max_loading_percent=nan, parallel=1, df=1., vk0_percent=nan, vkr0_percent=nan, mag0_percent=nan, mag0_rx=nan, si0_hv_partial=nan, pt_percent=nan, oltc=nan, tap_dependent_impedance=nan, @@ -3723,6 +3751,10 @@ def create_transformers_from_parameters(net, hv_buses, lv_buses, sn_mva, vn_hv_k **index** (int, None) - Force a specified ID if it is available. If None, the index one \ higher than the highest already existing index is selected. + **max_theta_deg** (float, 360) - maximal phase angle difference (only needed for OPF) + + **min_theta_deg** (float, -360) - minimal phase angle difference (only needed for OPF) + **max_loading_percent (float)** - maximum current loading (only needed for OPF) **df** (float) - derating factor: maximal current of transformer in relation to nominal \ diff --git a/pandapower/test/plotting/test_geo.py b/pandapower/test/plotting/test_geo.py index a3aac1c1e..40a801b21 100644 --- a/pandapower/test/plotting/test_geo.py +++ b/pandapower/test/plotting/test_geo.py @@ -271,12 +271,12 @@ def test_dump_to_geojson(): # test exporting branches result = geo.dump_to_geojson(_net, branches=True) assert isinstance(result, FeatureCollection) - assert dumps(result, sort_keys=True) == '{"features": [{"geometry": {"coordinates": [[1.0, 2.0], [3.0, 4.0]], "type": "LineString"}, "id": "line-0", "properties": {"c_nf_per_km": 720.0, "df": 1.0, "from_bus": 1, "g_us_per_km": 0.0, "ices": 0.389985, "in_service": true, "length_km": 1.0, "max_i_ka": 0.328, "name": "line1", "parallel": 1, "pp_index": 0, "pp_type": "line", "r_ohm_per_km": 0.2067, "std_type": null, "to_bus": 7, "type": null, "x_ohm_per_km": 0.1897522}, "type": "Feature"}], "type": "FeatureCollection"}' + assert dumps(result, sort_keys=True) == '{"features": [{"geometry": {"coordinates": [[1.0, 2.0], [3.0, 4.0]], "type": "LineString"}, "id": "line-0", "properties": {"c_nf_per_km": 720.0, "df": 1.0, "from_bus": 1, "g_us_per_km": 0.0, "ices": 0.389985, "in_service": true, "length_km": 1.0, "max_i_ka": 0.328, "max_theta_deg": 360.0, "min_theta_deg": -360.0, "name": "line1", "parallel": 1, "pp_index": 0, "pp_type": "line", "r_ohm_per_km": 0.2067, "std_type": null, "to_bus": 7, "type": null, "x_ohm_per_km": 0.1897522}, "type": "Feature"}], "type": "FeatureCollection"}' # test exporting both result = geo.dump_to_geojson(_net, nodes=True, branches=True) assert isinstance(result, FeatureCollection) - assert dumps(result, sort_keys=True) == '{"features": [{"geometry": {"coordinates": [1.0, 2.0], "type": "Point"}, "id": "bus-1", "properties": {"in_service": true, "name": "bus2", "pp_index": 1, "pp_type": "bus", "type": "b", "vn_kv": 0.4, "zone": null}, "type": "Feature"}, {"geometry": {"coordinates": [1.0, 3.0], "type": "Point"}, "id": "bus-7", "properties": {"in_service": true, "name": "bus3", "pp_index": 7, "pp_type": "bus", "type": "b", "vn_kv": 0.4, "zone": null}, "type": "Feature"}, {"geometry": {"coordinates": [[1.0, 2.0], [3.0, 4.0]], "type": "LineString"}, "id": "line-0", "properties": {"c_nf_per_km": 720.0, "df": 1.0, "from_bus": 1, "g_us_per_km": 0.0, "ices": 0.389985, "in_service": true, "length_km": 1.0, "max_i_ka": 0.328, "name": "line1", "parallel": 1, "pp_index": 0, "pp_type": "line", "r_ohm_per_km": 0.2067, "std_type": null, "to_bus": 7, "type": null, "x_ohm_per_km": 0.1897522}, "type": "Feature"}], "type": "FeatureCollection"}' + assert dumps(result, sort_keys=True) == '{"features": [{"geometry": {"coordinates": [1.0, 2.0], "type": "Point"}, "id": "bus-1", "properties": {"in_service": true, "name": "bus2", "pp_index": 1, "pp_type": "bus", "type": "b", "vn_kv": 0.4, "zone": null}, "type": "Feature"}, {"geometry": {"coordinates": [1.0, 3.0], "type": "Point"}, "id": "bus-7", "properties": {"in_service": true, "name": "bus3", "pp_index": 7, "pp_type": "bus", "type": "b", "vn_kv": 0.4, "zone": null}, "type": "Feature"}, {"geometry": {"coordinates": [[1.0, 2.0], [3.0, 4.0]], "type": "LineString"}, "id": "line-0", "properties": {"c_nf_per_km": 720.0, "df": 1.0, "from_bus": 1, "g_us_per_km": 0.0, "ices": 0.389985, "in_service": true, "length_km": 1.0, "max_i_ka": 0.328, "max_theta_deg": 360.0, "min_theta_deg": -360.0, "name": "line1", "parallel": 1, "pp_index": 0, "pp_type": "line", "r_ohm_per_km": 0.2067, "std_type": null, "to_bus": 7, "type": null, "x_ohm_per_km": 0.1897522}, "type": "Feature"}], "type": "FeatureCollection"}' # test exporting specific nodes result = geo.dump_to_geojson(_net, nodes=[1]) @@ -286,7 +286,7 @@ def test_dump_to_geojson(): # test exporting specific branches result = geo.dump_to_geojson(_net, branches=[0]) assert isinstance(result, FeatureCollection) - assert dumps(result, sort_keys=True) == '{"features": [{"geometry": {"coordinates": [[1.0, 2.0], [3.0, 4.0]], "type": "LineString"}, "id": "line-0", "properties": {"c_nf_per_km": 720.0, "df": 1.0, "from_bus": 1, "g_us_per_km": 0.0, "ices": 0.389985, "in_service": true, "length_km": 1.0, "max_i_ka": 0.328, "name": "line1", "parallel": 1, "pp_index": 0, "pp_type": "line", "r_ohm_per_km": 0.2067, "std_type": null, "to_bus": 7, "type": null, "x_ohm_per_km": 0.1897522}, "type": "Feature"}], "type": "FeatureCollection"}' + assert dumps(result, sort_keys=True) == '{"features": [{"geometry": {"coordinates": [[1.0, 2.0], [3.0, 4.0]], "type": "LineString"}, "id": "line-0", "properties": {"c_nf_per_km": 720.0, "df": 1.0, "from_bus": 1, "g_us_per_km": 0.0, "ices": 0.389985, "in_service": true, "length_km": 1.0, "max_i_ka": 0.328, "max_theta_deg": 360.0, "min_theta_deg": -360.0, "name": "line1", "parallel": 1, "pp_index": 0, "pp_type": "line", "r_ohm_per_km": 0.2067, "std_type": null, "to_bus": 7, "type": null, "x_ohm_per_km": 0.1897522}, "type": "Feature"}], "type": "FeatureCollection"}' # test exporting props from bus and res_bus _net.res_bus.loc[1, ["vm_pu", "va_degree", "p_mw", "q_mvar"]] = [1.0, 1.0, 1.0, 1.0] @@ -298,7 +298,7 @@ def test_dump_to_geojson(): _net.res_line.loc[0, _net.res_line.columns] = [7.0]*len(_net.res_line.columns) result = geo.dump_to_geojson(_net, branches=[0]) assert isinstance(result, FeatureCollection) - assert dumps(result, sort_keys=True) == '{"features": [{"geometry": {"coordinates": [[1.0, 2.0], [3.0, 4.0]], "type": "LineString"}, "id": "line-0", "properties": {"c_nf_per_km": 720.0, "df": 1.0, "from_bus": 1, "g_us_per_km": 0.0, "i_from_ka": 7.0, "i_ka": 7.0, "i_to_ka": 7.0, "ices": 0.389985, "in_service": true, "length_km": 1.0, "loading_percent": 7.0, "max_i_ka": 0.328, "name": "line1", "p_from_mw": 7.0, "p_to_mw": 7.0, "parallel": 1, "pl_mw": 7.0, "pp_index": 0, "pp_type": "line", "q_from_mvar": 7.0, "q_to_mvar": 7.0, "ql_mvar": 7.0, "r_ohm_per_km": 0.2067, "std_type": null, "to_bus": 7, "type": null, "va_from_degree": 7.0, "va_to_degree": 7.0, "vm_from_pu": 7.0, "vm_to_pu": 7.0, "x_ohm_per_km": 0.1897522}, "type": "Feature"}], "type": "FeatureCollection"}' + assert dumps(result, sort_keys=True) == '{"features": [{"geometry": {"coordinates": [[1.0, 2.0], [3.0, 4.0]], "type": "LineString"}, "id": "line-0", "properties": {"c_nf_per_km": 720.0, "df": 1.0, "from_bus": 1, "g_us_per_km": 0.0, "i_from_ka": 7.0, "i_ka": 7.0, "i_to_ka": 7.0, "ices": 0.389985, "in_service": true, "length_km": 1.0, "loading_percent": 7.0, "max_i_ka": 0.328, "max_theta_deg": 360.0, "min_theta_deg": -360.0, "name": "line1", "p_from_mw": 7.0, "p_to_mw": 7.0, "parallel": 1, "pl_mw": 7.0, "pp_index": 0, "pp_type": "line", "q_from_mvar": 7.0, "q_to_mvar": 7.0, "ql_mvar": 7.0, "r_ohm_per_km": 0.2067, "std_type": null, "to_bus": 7, "type": null, "va_from_degree": 7.0, "va_to_degree": 7.0, "vm_from_pu": 7.0, "vm_to_pu": 7.0, "x_ohm_per_km": 0.1897522}, "type": "Feature"}], "type": "FeatureCollection"}' def test_convert_geodata_to_geojson(): diff --git a/pyproject.toml b/pyproject.toml index 98b08cea7..063492a9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pandapower" -version = "3.0.0" # File format version '__format_version__' is tracked in _version.py +version = "3.1.0" # File format version '__format_version__' is tracked in _version.py authors = [ { name = "Leon Thurner", email = "leon.thurner@retoflow.de" }, { name = "Alexander Scheidler", email = "alexander.scheidler@iee.fraunhofer.de" }