From f9972bc6f37cedb55f8a435a960082b49e94d580 Mon Sep 17 00:00:00 2001 From: joda9 Date: Mon, 22 Jul 2024 10:42:07 +0200 Subject: [PATCH 01/29] Update select_cable to consider voltage drop constraints --- edisgo/tools/tools.py | 109 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 6 deletions(-) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index d05fe1b8..3d462f18 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -193,7 +193,72 @@ def drop_duplicated_columns(df, keep="last"): return df.loc[:, ~df.columns.duplicated(keep=keep)] -def select_cable(edisgo_obj, level, apparent_power): +def calculate_voltage_drop(s_max, r_total, x_total, v_nom, cos_phi=0.95): + """ + Calculate voltage drop in kV. + + Parameters + ---------- + s_max : float or array-like + Apparent power in kVA. + r_total : float or array-like + Total resistance in Ohm. + x_total : float or array-like + Total reactance in Ohm. + v_nom : float or array-like + Nominal voltage in kV. + cos_phi : float + Cosine phi of the load or generator. Default: 0.95. + Returns + ------- + float + Voltage drop in kV. + """ + return np.abs( + s_max / v_nom * (r_total * cos_phi + x_total * sqrt(1 - (cos_phi) ** 2)) * 1e-3 + ) + + +def voltage_drop_percentage( + R_per_km, L_per_km, length, num_parallel, v_nom, s_max, cos_phi=0.95 +): + """ + Calculate the voltage drop percentage. + + Parameters + ---------- + R_per_km : float or array-like + Resistance per kilometer of the cable in ohm/km. + L_per_km : float or array-like + Inductance per kilometer of the cable in mH/km. + length : float + Length of the cable in km. + num_parallel : int + Number of parallel cables. + v_nom : int + Nominal voltage in kV. + s_max : float + Apparent power in kVA. + cos_phi : float + Cosine phi of the load or generator. Default: 0.95. + Returns + ------- + float + Voltage drop in percentage of nominal voltage. + """ + # Calculate resistance and reactance for the given length and + # number of parallel cables + r_total = calculate_line_resistance(R_per_km, length, num_parallel) + x_total = calculate_line_reactance(L_per_km, length, num_parallel) + + # Calculate the voltage drop or increase + delta_v = calculate_voltage_drop(s_max, r_total, x_total, v_nom, cos_phi) + return delta_v / v_nom + + +def select_cable( + edisgo_obj, level, apparent_power, length=0, max_voltage_drop=None, max_cables=7 +): """ Selects suitable cable type and quantity using given apparent power. @@ -219,7 +284,15 @@ def select_cable(edisgo_obj, level, apparent_power): Number of necessary parallel cables. """ - + if not max_voltage_drop: + if level == "mv": + max_voltage_drop = edisgo_obj.config._data[ + "grid_expansion_allowed_voltage_deviations" + ]["mv_max_v_drop"] + elif level == "lv": + max_voltage_drop = edisgo_obj.config._data[ + "grid_expansion_allowed_voltage_deviations" + ]["lv_max_v_drop"] cable_count = 1 if level == "mv": @@ -240,18 +313,42 @@ def select_cable(edisgo_obj, level, apparent_power): ) > apparent_power ] + if length != 0: + suitable_cables = suitable_cables[ + voltage_drop_percentage( + R_per_km=available_cables["R_per_km"], + L_per_km=available_cables["L_per_km"], + length=length, + num_parallel=cable_count, + v_nom=available_cables["U_n"], + s_max=apparent_power, + cos_phi=0.9, + ) + < max_voltage_drop + ] # increase cable count until appropriate cable type is found - while suitable_cables.empty and cable_count < 7: + while suitable_cables.empty and cable_count < max_cables: # parameter cable_count += 1 suitable_cables = available_cables[ calculate_apparent_power( - available_cables["U_n"], - available_cables["I_max_th"], - cable_count, + available_cables["U_n"], available_cables["I_max_th"], cable_count ) > apparent_power ] + if length != 0: + suitable_cables = suitable_cables[ + voltage_drop_percentage( + R_per_km=available_cables["R_per_km"], + L_per_km=available_cables["L_per_km"], + length=length, + num_parallel=cable_count, + v_nom=available_cables["U_n"], + s_max=apparent_power, + cos_phi=0.9, + ) + < max_voltage_drop + ] if suitable_cables.empty: raise exceptions.MaximumIterationError( "Could not find a suitable cable for apparent power of " From 3dc762b32a37c3f8906eea54fe8fbd9c67bdfab8 Mon Sep 17 00:00:00 2001 From: joda9 Date: Mon, 22 Jul 2024 10:43:01 +0200 Subject: [PATCH 02/29] adding test for cable voltage drop --- tests/tools/test_tools.py | 74 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py index 40c34a63..d2baaf22 100644 --- a/tests/tools/test_tools.py +++ b/tests/tools/test_tools.py @@ -30,6 +30,54 @@ def test_calculate_line_reactance(self): data = tools.calculate_line_reactance(np.array([2, 3]), 3, 2) assert_allclose(data, np.array([1.88496 / 2, 2.82743 / 2]), rtol=1e-5) + def test_voltage_drop(self): + data = tools.calculate_voltage_drop(50, 0.125, 0.36, 20, 0.9) + assert np.isclose(data, 0.67355 * 1e-3) + data = tools.calculate_voltage_drop( + np.array([50, 50]), + np.array([0.125, 0.125]), + np.array([0.36, 0.36]), + 20, + 0.9, + ) + assert_allclose(data, np.array([0.67355 * 1e-3, 0.67355 * 1e-3]), rtol=1e-5) + data = tools.calculate_voltage_drop(50, 0.125, 0.36, 40, 0.9) + assert np.isclose(data, 0.67355 * 1e-3 / 2) + data = tools.calculate_voltage_drop(100, 0.125, 0.36, 20, 0.9) + assert np.isclose(data, 0.67355 * 1e-3 * 2) + data = tools.calculate_voltage_drop( + np.array([100, 100]), + np.array([0.125, 0.125]), + np.array([0.36, 0.36]), + np.array([20, 20]), + 0.9, + ) + assert_allclose( + data, np.array([0.67355 * 1e-3 * 2, 0.67355 * 1e-3 * 2]), rtol=1e-5 + ) + + def test_voltage_drop_percentage(self): + data = tools.voltage_drop_percentage(0.152, 0.360, 1, 1, 20, 50, 0.9) + assert np.isclose(data, 2.326224820444546e-5) + data = tools.voltage_drop_percentage( + np.array([0.152, 0.152]), np.array([0.360, 0.360]), 1, 1, 20, 50, 0.9 + ) + assert_allclose( + data, np.array([2.326224820444546e-5, 2.326224820444546e-5]), rtol=1e-5 + ) + data = tools.voltage_drop_percentage(0.152, 0.360, 2, 1, 20, 50, 0.9) + assert np.isclose(data, 2 * 2.326224820444546e-5) + data = tools.voltage_drop_percentage( + np.array([0.152, 0.152]), np.array([0.360, 0.360]), 2, 1, 20, 50, 0.9 + ) + assert_allclose( + data, + np.array([2 * 2.326224820444546e-5, 2 * 2.326224820444546e-5]), + rtol=1e-5, + ) + data = tools.voltage_drop_percentage(0.152, 0.360, 1, 2, 20, 50, 0.9) + assert np.isclose(data, 2.326224820444546e-5 / 2) + def test_calculate_line_resistance(self): # test single line data = tools.calculate_line_resistance(2, 3, 1) @@ -97,6 +145,7 @@ def test_drop_duplicated_columns(self): assert (check_df.loc[:, "a"] == [4, 5, 6]).all() def test_select_cable(self): + # no length given cable_data, num_parallel_cables = tools.select_cable(self.edisgo, "mv", 5.1) assert cable_data.name == "NA2XS2Y 3x1x150 RE/25" assert num_parallel_cables == 1 @@ -109,6 +158,31 @@ def test_select_cable(self): assert cable_data.name == "NAYY 4x1x150" assert num_parallel_cables == 1 + # length given + cable_data, num_parallel_cables = tools.select_cable( + self.edisgo, "mv", 5.1, 1000 + ) + assert cable_data.name == "NA2XS2Y 3x1x150 RE/25" + assert num_parallel_cables == 1 + + cable_data, num_parallel_cables = tools.select_cable( + self.edisgo, "mv", 40, 1000 + ) + assert cable_data.name == "NA2XS(FL)2Y 3x1x500 RM/35" + assert num_parallel_cables == 2 + + cable_data, num_parallel_cables = tools.select_cable( + self.edisgo, "lv", 0.18, 1000 + ) + assert cable_data.name == "NAYY 4x1x240" + assert num_parallel_cables == 3 + + cable_data, num_parallel_cables = tools.select_cable( + self.edisgo, "lv", 0.18, 1000, max_voltage_drop=0.01, max_cables=100 + ) + assert cable_data.name == "NAYY 4x1x300" + assert num_parallel_cables == 15 + def test_get_downstream_buses(self): # ######## test with LV bus ######## buses_downstream = tools.get_downstream_buses( From 9d0411a53f3d51174d18dbe7299311041001c365 Mon Sep 17 00:00:00 2001 From: joda9 Date: Mon, 22 Jul 2024 13:29:23 +0200 Subject: [PATCH 03/29] Adding default values for adding new components --- edisgo/config/config_grid_default.cfg | 6 ++++++ edisgo/tools/tools.py | 20 +++++++++----------- tests/tools/test_tools.py | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/edisgo/config/config_grid_default.cfg b/edisgo/config/config_grid_default.cfg index f48c68d8..7cab549d 100644 --- a/edisgo/config/config_grid_default.cfg +++ b/edisgo/config/config_grid_default.cfg @@ -55,3 +55,9 @@ upper_limit_voltage_level_4 = 20.0 # Positioning of disconnecting points: Can be position at location of most # balanced load or generation. Choose load, generation, loadgen position = load + +[new_components] +# Define the default values for adding new components to the grid + +lv_max_voltage_deviation = 0.035 +mv_max_voltage_deviation = 0.015 diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index 3d462f18..e37ba3fa 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -284,29 +284,27 @@ def select_cable( Number of necessary parallel cables. """ - if not max_voltage_drop: - if level == "mv": - max_voltage_drop = edisgo_obj.config._data[ - "grid_expansion_allowed_voltage_deviations" - ]["mv_max_v_drop"] - elif level == "lv": - max_voltage_drop = edisgo_obj.config._data[ - "grid_expansion_allowed_voltage_deviations" - ]["lv_max_v_drop"] - cable_count = 1 if level == "mv": cable_data = edisgo_obj.topology.equipment_data["mv_cables"] available_cables = cable_data[ cable_data["U_n"] == edisgo_obj.topology.mv_grid.nominal_voltage ] + if not max_voltage_drop: + max_voltage_drop = edisgo_obj.config._data["new_components"][ + "mv_max_voltage_deviation" + ] elif level == "lv": available_cables = edisgo_obj.topology.equipment_data["lv_cables"] + if not max_voltage_drop: + max_voltage_drop = edisgo_obj.config._data["new_components"][ + "lv_max_voltage_deviation" + ] else: raise ValueError( "Specified voltage level is not valid. Must either be 'mv' or 'lv'." ) - + cable_count = 1 suitable_cables = available_cables[ calculate_apparent_power( available_cables["U_n"], available_cables["I_max_th"], cable_count diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py index d2baaf22..996f70b9 100644 --- a/tests/tools/test_tools.py +++ b/tests/tools/test_tools.py @@ -175,7 +175,7 @@ def test_select_cable(self): self.edisgo, "lv", 0.18, 1000 ) assert cable_data.name == "NAYY 4x1x240" - assert num_parallel_cables == 3 + assert num_parallel_cables == 5 cable_data, num_parallel_cables = tools.select_cable( self.edisgo, "lv", 0.18, 1000, max_voltage_drop=0.01, max_cables=100 From b8a8baacc4b57af02571bc8e077bd4121a54ff1d Mon Sep 17 00:00:00 2001 From: joda9 Date: Mon, 22 Jul 2024 15:11:13 +0200 Subject: [PATCH 04/29] Update default voltage deviation values for new components --- edisgo/config/config_grid_default.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/edisgo/config/config_grid_default.cfg b/edisgo/config/config_grid_default.cfg index 7cab549d..1f4f9d58 100644 --- a/edisgo/config/config_grid_default.cfg +++ b/edisgo/config/config_grid_default.cfg @@ -59,5 +59,5 @@ position = load [new_components] # Define the default values for adding new components to the grid -lv_max_voltage_deviation = 0.035 -mv_max_voltage_deviation = 0.015 +lv_max_voltage_deviation = 0.03 # nach VDE-AR-N 4100 (VDE-AR-N 4100) Anwendungsregel: 2019-04 +mv_max_voltage_deviation = 0.02 # nach VDE-AR-N 4110 (VDE-AR-N 4110) Anwendungsregel: 2023-09 From bab29bceae9079a965aa66c3bfd3ca75c30d482e Mon Sep 17 00:00:00 2001 From: joda9 Date: Wed, 24 Jul 2024 13:02:36 +0200 Subject: [PATCH 05/29] Update default voltage deviation values for new components --- edisgo/config/config_grid_default.cfg | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/edisgo/config/config_grid_default.cfg b/edisgo/config/config_grid_default.cfg index 1f4f9d58..d1f082ca 100644 --- a/edisgo/config/config_grid_default.cfg +++ b/edisgo/config/config_grid_default.cfg @@ -50,14 +50,13 @@ upper_limit_voltage_level_6 = 0.2 upper_limit_voltage_level_5 = 5.5 upper_limit_voltage_level_4 = 20.0 +lv_max_voltage_deviation = 0.03 +# from VDE-AR-N 4100 (VDE-AR-N 4100) Anwendungsregel: 2019-04 +mv_max_voltage_deviation = 0.02 +# from VDE-AR-N 4110 (VDE-AR-N 4110) Anwendungsregel: 2023-09 + [disconnecting_point] # Positioning of disconnecting points: Can be position at location of most # balanced load or generation. Choose load, generation, loadgen position = load - -[new_components] -# Define the default values for adding new components to the grid - -lv_max_voltage_deviation = 0.03 # nach VDE-AR-N 4100 (VDE-AR-N 4100) Anwendungsregel: 2019-04 -mv_max_voltage_deviation = 0.02 # nach VDE-AR-N 4110 (VDE-AR-N 4110) Anwendungsregel: 2023-09 From f31886aa4e4821103cca4be2a68516dc0b858a3c Mon Sep 17 00:00:00 2001 From: joda9 Date: Wed, 24 Jul 2024 13:03:09 +0200 Subject: [PATCH 06/29] Refactor calculate_voltage_drop function for clarity and readability --- edisgo/tools/tools.py | 124 ++++++++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 35 deletions(-) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index e37ba3fa..2e1a8771 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -193,37 +193,57 @@ def drop_duplicated_columns(df, keep="last"): return df.loc[:, ~df.columns.duplicated(keep=keep)] -def calculate_voltage_drop(s_max, r_total, x_total, v_nom, cos_phi=0.95): +def calculate_voltage_diff_per_line( + s_max: float | np.ndarray, + r_total: float | np.ndarray, + x_total: float | np.ndarray, + v_nom: float | np.ndarray, + sign: int = -1, + cos_phi: float = 0.95, +) -> float | np.ndarray: """ - Calculate voltage drop in kV. + Calculate the voltage drop across a line in kV. Parameters ---------- s_max : float or array-like - Apparent power in kVA. + Apparent power the cable must carry in MVA. r_total : float or array-like - Total resistance in Ohm. + Total resistance in Ohms. x_total : float or array-like - Total reactance in Ohm. + Total reactance in Ohms. v_nom : float or array-like Nominal voltage in kV. - cos_phi : float - Cosine phi of the load or generator. Default: 0.95. + sign : int, optional + Sign of the reactance. -1 for inductive and +1 for capacitive. Default is -1. + cos_phi : float, optional + Power factor (cosine of the phase angle) of the load or generator. + Default is 0.95. + Returns ------- - float + float or array-like Voltage drop in kV. """ - return np.abs( - s_max / v_nom * (r_total * cos_phi + x_total * sqrt(1 - (cos_phi) ** 2)) * 1e-3 + sin_phi = np.sqrt(1 - cos_phi**2) + voltage_diff = np.abs( + (s_max * 1e6 / (v_nom * 1e3)) * (r_total * cos_phi + sign * x_total * sin_phi) ) - - -def voltage_drop_percentage( - R_per_km, L_per_km, length, num_parallel, v_nom, s_max, cos_phi=0.95 -): + return voltage_diff / 1e3 # Convert to kV + + +def voltage_diff_pu( + R_per_km: float | np.ndarray, + L_per_km: float | np.ndarray, + length: float, + num_parallel: int, + v_nom: float | np.ndarray, + s_max: float | np.ndarray, + cos_phi: float = 0.95, + sign: int = -1, +) -> float | np.ndarray: """ - Calculate the voltage drop percentage. + Calculate the voltage drop per unit of nominal voltage. Parameters ---------- @@ -238,27 +258,43 @@ def voltage_drop_percentage( v_nom : int Nominal voltage in kV. s_max : float - Apparent power in kVA. - cos_phi : float + Apparent power the cable must carry in MVA. + cos_phi : float, optional Cosine phi of the load or generator. Default: 0.95. + sign : int, optional + Sign of the reactance. -1 for inductive and +1 for capacitive. Default is -1. + Returns ------- float - Voltage drop in percentage of nominal voltage. + Voltage drop in per unit of nominal voltage. """ - # Calculate resistance and reactance for the given length and + # Calculate total resistance and reactance for the given length and # number of parallel cables r_total = calculate_line_resistance(R_per_km, length, num_parallel) x_total = calculate_line_reactance(L_per_km, length, num_parallel) # Calculate the voltage drop or increase - delta_v = calculate_voltage_drop(s_max, r_total, x_total, v_nom, cos_phi) - return delta_v / v_nom + delta_v = calculate_voltage_diff_per_line( + s_max, r_total, x_total, v_nom, sign=sign, cos_phi=cos_phi + ) + + # Convert voltage drop to per unit of nominal voltage + voltage_drop_pu = delta_v / v_nom + + return voltage_drop_pu def select_cable( - edisgo_obj, level, apparent_power, length=0, max_voltage_drop=None, max_cables=7 -): + edisgo_obj: EDisGo, + level: str, + apparent_power: float, + length: float = 0, + max_voltage_diff: float | None = None, + max_cables: int = 7, + cos_phi: float | None = 0.95, + inductive_reactance: bool = True, +) -> tuple[pd.Series, int]: """ Selects suitable cable type and quantity using given apparent power. @@ -274,6 +310,17 @@ def select_cable( 'lv'. apparent_power : float Apparent power the cable must carry in MVA. + length : float + Length of the cable in km. Default: 0. + max_voltage_diff : float + Maximum voltage drop in pu. Default: None. + max_cables : int + Maximum number of parallel cables to consider. Default is 7. + cos_phi : float + Cosine phi of the load or generator. Default: 0.95. + inductive_reactance : bool + If True, inductive reactance is considered. Default + is True. If False, capacitive reactance is considered. Returns ------- @@ -284,20 +331,25 @@ def select_cable( Number of necessary parallel cables. """ - + if not cos_phi: + cos_phi = 0.95 + if inductive_reactance: + sign = -1 + else: + sign = 1 if level == "mv": cable_data = edisgo_obj.topology.equipment_data["mv_cables"] available_cables = cable_data[ cable_data["U_n"] == edisgo_obj.topology.mv_grid.nominal_voltage ] - if not max_voltage_drop: - max_voltage_drop = edisgo_obj.config._data["new_components"][ + if not max_voltage_diff: + max_voltage_diff = edisgo_obj.config["grid_connection"][ "mv_max_voltage_deviation" ] elif level == "lv": available_cables = edisgo_obj.topology.equipment_data["lv_cables"] - if not max_voltage_drop: - max_voltage_drop = edisgo_obj.config._data["new_components"][ + if not max_voltage_diff: + max_voltage_diff = edisgo_obj.config["grid_connection"][ "lv_max_voltage_deviation" ] else: @@ -313,16 +365,17 @@ def select_cable( ] if length != 0: suitable_cables = suitable_cables[ - voltage_drop_percentage( + voltage_diff_pu( R_per_km=available_cables["R_per_km"], L_per_km=available_cables["L_per_km"], length=length, num_parallel=cable_count, v_nom=available_cables["U_n"], s_max=apparent_power, - cos_phi=0.9, + cos_phi=cos_phi, + sign=sign, ) - < max_voltage_drop + < max_voltage_diff ] # increase cable count until appropriate cable type is found @@ -336,16 +389,17 @@ def select_cable( ] if length != 0: suitable_cables = suitable_cables[ - voltage_drop_percentage( + voltage_diff_pu( R_per_km=available_cables["R_per_km"], L_per_km=available_cables["L_per_km"], length=length, num_parallel=cable_count, v_nom=available_cables["U_n"], s_max=apparent_power, - cos_phi=0.9, + cos_phi=cos_phi, + sign=sign, ) - < max_voltage_drop + < max_voltage_diff ] if suitable_cables.empty: raise exceptions.MaximumIterationError( From 8adc37270a916c3f7202012f75a69f091d9d314b Mon Sep 17 00:00:00 2001 From: joda9 Date: Wed, 24 Jul 2024 13:03:52 +0200 Subject: [PATCH 07/29] update tests select cables --- tests/tools/test_tools.py | 83 ++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py index 996f70b9..646cbe03 100644 --- a/tests/tools/test_tools.py +++ b/tests/tools/test_tools.py @@ -30,53 +30,55 @@ def test_calculate_line_reactance(self): data = tools.calculate_line_reactance(np.array([2, 3]), 3, 2) assert_allclose(data, np.array([1.88496 / 2, 2.82743 / 2]), rtol=1e-5) - def test_voltage_drop(self): - data = tools.calculate_voltage_drop(50, 0.125, 0.36, 20, 0.9) - assert np.isclose(data, 0.67355 * 1e-3) - data = tools.calculate_voltage_drop( + def test_voltage_diff(self): + data = tools.calculate_voltage_diff_per_line(50, 0.125, 0.36, 20, -1, 0.9) + correct_value = 0.11105090491866049 + assert np.isclose(data, correct_value) + data = tools.calculate_voltage_diff_per_line( np.array([50, 50]), np.array([0.125, 0.125]), np.array([0.36, 0.36]), 20, + -1, 0.9, ) - assert_allclose(data, np.array([0.67355 * 1e-3, 0.67355 * 1e-3]), rtol=1e-5) - data = tools.calculate_voltage_drop(50, 0.125, 0.36, 40, 0.9) - assert np.isclose(data, 0.67355 * 1e-3 / 2) - data = tools.calculate_voltage_drop(100, 0.125, 0.36, 20, 0.9) - assert np.isclose(data, 0.67355 * 1e-3 * 2) - data = tools.calculate_voltage_drop( + assert_allclose(data, np.array([correct_value, correct_value]), rtol=1e-5) + data = tools.calculate_voltage_diff_per_line(50, 0.125, 0.36, 40, -1, 0.9) + assert np.isclose(data, correct_value / 2) + data = tools.calculate_voltage_diff_per_line(100, 0.125, 0.36, 20, -1, 0.9) + assert np.isclose(data, correct_value * 2) + data = tools.calculate_voltage_diff_per_line( np.array([100, 100]), np.array([0.125, 0.125]), np.array([0.36, 0.36]), np.array([20, 20]), + -1, 0.9, ) assert_allclose( - data, np.array([0.67355 * 1e-3 * 2, 0.67355 * 1e-3 * 2]), rtol=1e-5 + data, np.array([correct_value * 2, correct_value * 2]), rtol=1e-5 ) - def test_voltage_drop_percentage(self): - data = tools.voltage_drop_percentage(0.152, 0.360, 1, 1, 20, 50, 0.9) - assert np.isclose(data, 2.326224820444546e-5) - data = tools.voltage_drop_percentage( - np.array([0.152, 0.152]), np.array([0.360, 0.360]), 1, 1, 20, 50, 0.9 + def test_voltage_drop_pu(self): + data = tools.voltage_diff_pu(0.1, 0.350, 1, 1, 20, 50, 0.9, -1) + correct_value = 0.52589253567891375 * 1e-2 + assert np.isclose(data, correct_value) + data = tools.voltage_diff_pu( + np.array([0.1, 0.1]), np.array([0.35, 0.35]), 1, 1, 20, 50, 0.9, -1 ) - assert_allclose( - data, np.array([2.326224820444546e-5, 2.326224820444546e-5]), rtol=1e-5 - ) - data = tools.voltage_drop_percentage(0.152, 0.360, 2, 1, 20, 50, 0.9) - assert np.isclose(data, 2 * 2.326224820444546e-5) - data = tools.voltage_drop_percentage( - np.array([0.152, 0.152]), np.array([0.360, 0.360]), 2, 1, 20, 50, 0.9 + assert_allclose(data, np.array([correct_value, correct_value]), rtol=1e-5) + data = tools.voltage_diff_pu(0.1, 0.35, 2, 1, 20, 50, 0.9, -1) + assert np.isclose(data, 2 * correct_value) + data = tools.voltage_diff_pu( + np.array([0.1, 0.1]), np.array([0.35, 0.35]), 2, 1, 20, 50, 0.9, -1 ) assert_allclose( data, - np.array([2 * 2.326224820444546e-5, 2 * 2.326224820444546e-5]), + np.array([2 * correct_value, 2 * correct_value]), rtol=1e-5, ) - data = tools.voltage_drop_percentage(0.152, 0.360, 1, 2, 20, 50, 0.9) - assert np.isclose(data, 2.326224820444546e-5 / 2) + data = tools.voltage_diff_pu(0.1, 0.35, 1, 2, 20, 50, 0.9, -1) + assert np.isclose(data, correct_value / 2) def test_calculate_line_resistance(self): # test single line @@ -159,29 +161,36 @@ def test_select_cable(self): assert num_parallel_cables == 1 # length given - cable_data, num_parallel_cables = tools.select_cable( - self.edisgo, "mv", 5.1, 1000 - ) + cable_data, num_parallel_cables = tools.select_cable(self.edisgo, "mv", 5.1, 2) assert cable_data.name == "NA2XS2Y 3x1x150 RE/25" assert num_parallel_cables == 1 - cable_data, num_parallel_cables = tools.select_cable( - self.edisgo, "mv", 40, 1000 - ) + cable_data, num_parallel_cables = tools.select_cable(self.edisgo, "mv", 40, 1) assert cable_data.name == "NA2XS(FL)2Y 3x1x500 RM/35" assert num_parallel_cables == 2 + cable_data, num_parallel_cables = tools.select_cable(self.edisgo, "lv", 0.18, 1) + assert cable_data.name == "NAYY 4x1x300" + assert num_parallel_cables == 3 + cable_data, num_parallel_cables = tools.select_cable( - self.edisgo, "lv", 0.18, 1000 + self.edisgo, "lv", 0.18, 1, max_voltage_diff=0.01, max_cables=100 ) - assert cable_data.name == "NAYY 4x1x240" - assert num_parallel_cables == 5 + assert cable_data.name == "NAYY 4x1x300" + assert num_parallel_cables == 8 cable_data, num_parallel_cables = tools.select_cable( - self.edisgo, "lv", 0.18, 1000, max_voltage_drop=0.01, max_cables=100 + self.edisgo, + "lv", + 0.18, + 1, + max_voltage_diff=0.01, + max_cables=100, + cos_phi=1, + inductive_reactance=False, ) assert cable_data.name == "NAYY 4x1x300" - assert num_parallel_cables == 15 + assert num_parallel_cables == 12 def test_get_downstream_buses(self): # ######## test with LV bus ######## From a37c4d5c27e624cca3efc9f5667a8892d4e245d7 Mon Sep 17 00:00:00 2001 From: joda9 Date: Wed, 24 Jul 2024 13:42:59 +0200 Subject: [PATCH 08/29] use "difference" instead of "drop" in the variable names --- edisgo/tools/tools.py | 16 ++++++++-------- tests/tools/test_tools.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index 2e1a8771..76a7cd64 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -202,7 +202,7 @@ def calculate_voltage_diff_per_line( cos_phi: float = 0.95, ) -> float | np.ndarray: """ - Calculate the voltage drop across a line in kV. + Calculate the voltage difference across a line in kV. Parameters ---------- @@ -223,7 +223,7 @@ def calculate_voltage_diff_per_line( Returns ------- float or array-like - Voltage drop in kV. + Voltage difference in kV. """ sin_phi = np.sqrt(1 - cos_phi**2) voltage_diff = np.abs( @@ -243,7 +243,7 @@ def voltage_diff_pu( sign: int = -1, ) -> float | np.ndarray: """ - Calculate the voltage drop per unit of nominal voltage. + Calculate the voltage difference per unit of nominal voltage. Parameters ---------- @@ -267,7 +267,7 @@ def voltage_diff_pu( Returns ------- float - Voltage drop in per unit of nominal voltage. + Voltage difference in per unit of nominal voltage. """ # Calculate total resistance and reactance for the given length and # number of parallel cables @@ -279,10 +279,10 @@ def voltage_diff_pu( s_max, r_total, x_total, v_nom, sign=sign, cos_phi=cos_phi ) - # Convert voltage drop to per unit of nominal voltage - voltage_drop_pu = delta_v / v_nom + # Convert voltage difference to per unit of nominal voltage + voltage_difference_pu = delta_v / v_nom - return voltage_drop_pu + return voltage_difference_pu def select_cable( @@ -313,7 +313,7 @@ def select_cable( length : float Length of the cable in km. Default: 0. max_voltage_diff : float - Maximum voltage drop in pu. Default: None. + Maximum voltage difference in pu. Default: None. max_cables : int Maximum number of parallel cables to consider. Default is 7. cos_phi : float diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py index 646cbe03..a2bf3975 100644 --- a/tests/tools/test_tools.py +++ b/tests/tools/test_tools.py @@ -59,7 +59,7 @@ def test_voltage_diff(self): data, np.array([correct_value * 2, correct_value * 2]), rtol=1e-5 ) - def test_voltage_drop_pu(self): + def test_voltage_diff_pu(self): data = tools.voltage_diff_pu(0.1, 0.350, 1, 1, 20, 50, 0.9, -1) correct_value = 0.52589253567891375 * 1e-2 assert np.isclose(data, correct_value) From 1b2301a5d7eea4a5c7baae4fb1da4cac1530dfc0 Mon Sep 17 00:00:00 2001 From: joda9 Date: Thu, 25 Jul 2024 09:00:16 +0200 Subject: [PATCH 09/29] source of formula added --- edisgo/tools/tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index 76a7cd64..dea5d673 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -226,9 +226,10 @@ def calculate_voltage_diff_per_line( Voltage difference in kV. """ sin_phi = np.sqrt(1 - cos_phi**2) + # Calculate the voltage difference using the formula from VDE-AR-N 4105 voltage_diff = np.abs( (s_max * 1e6 / (v_nom * 1e3)) * (r_total * cos_phi + sign * x_total * sin_phi) - ) + ) # in V return voltage_diff / 1e3 # Convert to kV From a0f01fb21b21c34f4f50d896406af247e7faedf0 Mon Sep 17 00:00:00 2001 From: joda9 Date: Tue, 30 Jul 2024 14:25:03 +0200 Subject: [PATCH 10/29] specified source for values --- edisgo/config/config_grid_default.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/edisgo/config/config_grid_default.cfg b/edisgo/config/config_grid_default.cfg index d1f082ca..1cd82925 100644 --- a/edisgo/config/config_grid_default.cfg +++ b/edisgo/config/config_grid_default.cfg @@ -50,10 +50,10 @@ upper_limit_voltage_level_6 = 0.2 upper_limit_voltage_level_5 = 5.5 upper_limit_voltage_level_4 = 20.0 +# from VDE-AR-N 4100 (VDE-AR-N 4100) Anwendungsregel: 2019-04, table 3 lv_max_voltage_deviation = 0.03 -# from VDE-AR-N 4100 (VDE-AR-N 4100) Anwendungsregel: 2019-04 +# from VDE-AR-N 4110 (VDE-AR-N 4110) Anwendungsregel: 2023-09, 5.3.2 Zulässige Spannungsänderung mv_max_voltage_deviation = 0.02 -# from VDE-AR-N 4110 (VDE-AR-N 4110) Anwendungsregel: 2023-09 [disconnecting_point] From 3a23e5e488e261ddfcead7e2ef0539529bf3681f Mon Sep 17 00:00:00 2001 From: joda9 Date: Tue, 30 Jul 2024 14:30:31 +0200 Subject: [PATCH 11/29] update tools to consistency variable names and minor fixes --- edisgo/tools/tools.py | 103 ++++++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 44 deletions(-) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index dea5d673..789f3053 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -14,7 +14,7 @@ from sqlalchemy.engine.base import Engine -from edisgo.flex_opt import exceptions +from edisgo.flex_opt import exceptions, q_control from edisgo.io.db import session_scope_egon_data, sql_grid_geom, sql_intersects from edisgo.tools import session_scope @@ -198,8 +198,8 @@ def calculate_voltage_diff_per_line( r_total: float | np.ndarray, x_total: float | np.ndarray, v_nom: float | np.ndarray, - sign: int = -1, - cos_phi: float = 0.95, + reactive_power_mode: str = "inductive", + power_factor: float = 0.95, ) -> float | np.ndarray: """ Calculate the voltage difference across a line in kV. @@ -209,14 +209,14 @@ def calculate_voltage_diff_per_line( s_max : float or array-like Apparent power the cable must carry in MVA. r_total : float or array-like - Total resistance in Ohms. + Total resistance of the line in Ohms. x_total : float or array-like - Total reactance in Ohms. + Total reactance of the line in Ohms. v_nom : float or array-like - Nominal voltage in kV. + Nominal voltage of the line in kV. sign : int, optional Sign of the reactance. -1 for inductive and +1 for capacitive. Default is -1. - cos_phi : float, optional + power_factor : float, optional Power factor (cosine of the phase angle) of the load or generator. Default is 0.95. @@ -225,12 +225,13 @@ def calculate_voltage_diff_per_line( float or array-like Voltage difference in kV. """ - sin_phi = np.sqrt(1 - cos_phi**2) + sign = q_control.get_q_sign_generator(reactive_power_mode) + sin_phi = np.sqrt(1 - power_factor**2) # Calculate the voltage difference using the formula from VDE-AR-N 4105 - voltage_diff = np.abs( - (s_max * 1e6 / (v_nom * 1e3)) * (r_total * cos_phi + sign * x_total * sin_phi) - ) # in V - return voltage_diff / 1e3 # Convert to kV + voltage_diff = (s_max / (v_nom)) * ( + r_total * power_factor + sign * x_total * sin_phi + ) + return voltage_diff # in kV def voltage_diff_pu( @@ -240,8 +241,8 @@ def voltage_diff_pu( num_parallel: int, v_nom: float | np.ndarray, s_max: float | np.ndarray, - cos_phi: float = 0.95, - sign: int = -1, + power_factor: float = 0.95, + reactive_power_mode: str = "inductive", ) -> float | np.ndarray: """ Calculate the voltage difference per unit of nominal voltage. @@ -260,7 +261,7 @@ def voltage_diff_pu( Nominal voltage in kV. s_max : float Apparent power the cable must carry in MVA. - cos_phi : float, optional + power_factor : float, optional Cosine phi of the load or generator. Default: 0.95. sign : int, optional Sign of the reactance. -1 for inductive and +1 for capacitive. Default is -1. @@ -277,7 +278,12 @@ def voltage_diff_pu( # Calculate the voltage drop or increase delta_v = calculate_voltage_diff_per_line( - s_max, r_total, x_total, v_nom, sign=sign, cos_phi=cos_phi + s_max, + r_total, + x_total, + v_nom, + reactive_power_mode=reactive_power_mode, + power_factor=power_factor, ) # Convert voltage difference to per unit of nominal voltage @@ -293,15 +299,17 @@ def select_cable( length: float = 0, max_voltage_diff: float | None = None, max_cables: int = 7, - cos_phi: float | None = 0.95, - inductive_reactance: bool = True, + power_factor: float | None = None, + component_type: str | None = None, + reactive_power_mode: str = "inductive", ) -> tuple[pd.Series, int]: """ - Selects suitable cable type and quantity using given apparent power. + Selects suitable cable type and quantity based on apparent power and + voltage deviation. - Cable is selected to be able to carry the given `apparent_power`, no load - factor is considered. Overhead lines are not considered in choosing a - suitable cable. + The cable is selected to carry the given `apparent_power` and to ensure + acceptable voltage deviation over the cable length. No load factor is + considered. Overhead lines are not considered in choosing a suitable cable. Parameters ---------- @@ -314,30 +322,37 @@ def select_cable( length : float Length of the cable in km. Default: 0. max_voltage_diff : float - Maximum voltage difference in pu. Default: None. + Maximum allowed voltage difference (p.u. of nominal voltage). + If None, it defaults to the value specified in the configuration file + under the `grid_connection` section for the respective voltage level. + Default: None. max_cables : int - Maximum number of parallel cables to consider. Default is 7. - cos_phi : float - Cosine phi of the load or generator. Default: 0.95. - inductive_reactance : bool - If True, inductive reactance is considered. Default - is True. If False, capacitive reactance is considered. + Maximum number of cables to consider. Default: 7. + power_factor : float + Power factor of the load. + component_type : str + Type of the component to be connected, used to obtain the default power factor + from the configuration. + possible options are 'gen', 'load', 'cp', 'hp' + reactive_power_mode : str + Mode of the reactive power. Default: 'inductive' Returns ------- - :pandas:`pandas.Series` - Series with attributes of selected cable as in equipment data and - cable type as series name. - int - Number of necessary parallel cables. - + tuple[pd.Series, int] + A tuple containing the selected cable type and the quantity needed. """ - if not cos_phi: - cos_phi = 0.95 - if inductive_reactance: - sign = -1 + if component_type is None: + component_type = level + "_load" + elif component_type in ["gen", "load", "cp", "hp"]: + component_type = level + "_" + component_type else: - sign = 1 + raise ValueError( + "Specified component type is not valid. " + "Must either be 'gen', 'load', 'cp' or 'hp'." + ) + if power_factor is None: + power_factor = edisgo_obj.config["reactive_power_factor"][component_type] if level == "mv": cable_data = edisgo_obj.topology.equipment_data["mv_cables"] available_cables = cable_data[ @@ -373,8 +388,8 @@ def select_cable( num_parallel=cable_count, v_nom=available_cables["U_n"], s_max=apparent_power, - cos_phi=cos_phi, - sign=sign, + power_factor=power_factor, + reactive_power_mode=reactive_power_mode, ) < max_voltage_diff ] @@ -397,8 +412,8 @@ def select_cable( num_parallel=cable_count, v_nom=available_cables["U_n"], s_max=apparent_power, - cos_phi=cos_phi, - sign=sign, + power_factor=power_factor, + reactive_power_mode=reactive_power_mode, ) < max_voltage_diff ] From 6e3cd06b1ea94629893783180d1d314a31349d05 Mon Sep 17 00:00:00 2001 From: joda9 Date: Tue, 30 Jul 2024 14:31:00 +0200 Subject: [PATCH 12/29] adding tests for cable_selection --- tests/tools/test_tools.py | 41 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py index a2bf3975..ec5d1277 100644 --- a/tests/tools/test_tools.py +++ b/tests/tools/test_tools.py @@ -183,14 +183,49 @@ def test_select_cable(self): self.edisgo, "lv", 0.18, - 1, + length=1, max_voltage_diff=0.01, max_cables=100, - cos_phi=1, - inductive_reactance=False, + power_factor=1, + reactive_power_mode="inductive", ) assert cable_data.name == "NAYY 4x1x300" assert num_parallel_cables == 12 + cable_data, num_parallel_cables = tools.select_cable( + self.edisgo, "lv", 0.18, length=1 + ) + assert cable_data.name == "NAYY 4x1x300" + assert num_parallel_cables == 3 + cable_data, num_parallel_cables = tools.select_cable( + self.edisgo, + "lv", + 0.18, + length=1, + max_voltage_diff=0.01, + max_cables=100, + power_factor=None, + reactive_power_mode="inductive", + component_type="gen", + ) + assert cable_data.name == "NAYY 4x1x300" + assert num_parallel_cables == 8 + try: + cable_data, num_parallel_cables = tools.select_cable( + self.edisgo, + "lv", + 0.18, + length=1, + max_voltage_diff=0.01, + max_cables=100, + power_factor=None, + reactive_power_mode="inductive", + component_type="fail", + ) + except ValueError as e: + assert ( + str(e) == "Specified component type is not valid. " + "Must either be 'gen', 'load', 'cp' or 'hp'." + ) def test_get_downstream_buses(self): # ######## test with LV bus ######## From 84047488ba0d4747ee809ab7f7953d4be8f4a22e Mon Sep 17 00:00:00 2001 From: joda9 Date: Fri, 2 Aug 2024 13:00:18 +0200 Subject: [PATCH 13/29] adding component type to select cable function --- edisgo/tools/tools.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index 789f3053..93295609 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -200,6 +200,7 @@ def calculate_voltage_diff_per_line( v_nom: float | np.ndarray, reactive_power_mode: str = "inductive", power_factor: float = 0.95, + component_type: str = "load", ) -> float | np.ndarray: """ Calculate the voltage difference across a line in kV. @@ -214,18 +215,28 @@ def calculate_voltage_diff_per_line( Total reactance of the line in Ohms. v_nom : float or array-like Nominal voltage of the line in kV. - sign : int, optional - Sign of the reactance. -1 for inductive and +1 for capacitive. Default is -1. + reactive_power_mode : str, optional + Mode of the reactive power. Default: 'inductive'. + alternative: 'capacitive' power_factor : float, optional Power factor (cosine of the phase angle) of the load or generator. Default is 0.95. + component_type : str, optional + Type of the component to be connected, used to obtain the default reactive power + mode from the configuration. Default: 'load'. + alternative: 'gen' Returns ------- float or array-like Voltage difference in kV. """ - sign = q_control.get_q_sign_generator(reactive_power_mode) + if "gen" in component_type: + sign = q_control.get_q_sign_generator(reactive_power_mode) + elif "load" in component_type or "cp" in component_type or "hp" in component_type: + sign = q_control.get_q_sign_load(reactive_power_mode) + else: + raise ValueError("Component type not supported.") sin_phi = np.sqrt(1 - power_factor**2) # Calculate the voltage difference using the formula from VDE-AR-N 4105 voltage_diff = (s_max / (v_nom)) * ( @@ -243,6 +254,7 @@ def voltage_diff_pu( s_max: float | np.ndarray, power_factor: float = 0.95, reactive_power_mode: str = "inductive", + component_type: str = "load", ) -> float | np.ndarray: """ Calculate the voltage difference per unit of nominal voltage. @@ -263,8 +275,10 @@ def voltage_diff_pu( Apparent power the cable must carry in MVA. power_factor : float, optional Cosine phi of the load or generator. Default: 0.95. - sign : int, optional - Sign of the reactance. -1 for inductive and +1 for capacitive. Default is -1. + component_type : str, optional + Type of the component to be connected, used to obtain the default reactive power + mode from the configuration. Default: 'load'. + alternative: 'gen' Returns ------- @@ -284,6 +298,7 @@ def voltage_diff_pu( v_nom, reactive_power_mode=reactive_power_mode, power_factor=power_factor, + component_type=component_type, ) # Convert voltage difference to per unit of nominal voltage @@ -300,7 +315,7 @@ def select_cable( max_voltage_diff: float | None = None, max_cables: int = 7, power_factor: float | None = None, - component_type: str | None = None, + component_type: str | None = "load", reactive_power_mode: str = "inductive", ) -> tuple[pd.Series, int]: """ @@ -334,6 +349,7 @@ def select_cable( Type of the component to be connected, used to obtain the default power factor from the configuration. possible options are 'gen', 'load', 'cp', 'hp' + Default: 'load'. reactive_power_mode : str Mode of the reactive power. Default: 'inductive' @@ -344,6 +360,7 @@ def select_cable( """ if component_type is None: component_type = level + "_load" + elif component_type in ["gen", "load", "cp", "hp"]: component_type = level + "_" + component_type else: @@ -390,6 +407,7 @@ def select_cable( s_max=apparent_power, power_factor=power_factor, reactive_power_mode=reactive_power_mode, + component_type=component_type, ) < max_voltage_diff ] @@ -414,6 +432,7 @@ def select_cable( s_max=apparent_power, power_factor=power_factor, reactive_power_mode=reactive_power_mode, + component_type=component_type, ) < max_voltage_diff ] From f1a02e25133fac19ca9a97017cd255bcac814683 Mon Sep 17 00:00:00 2001 From: joda9 Date: Fri, 2 Aug 2024 13:00:55 +0200 Subject: [PATCH 14/29] adding component type to tests --- tests/tools/test_tools.py | 228 +++++++++++++++++++++++++++++++------- 1 file changed, 191 insertions(+), 37 deletions(-) diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py index ec5d1277..9116e5cc 100644 --- a/tests/tools/test_tools.py +++ b/tests/tools/test_tools.py @@ -30,54 +30,142 @@ def test_calculate_line_reactance(self): data = tools.calculate_line_reactance(np.array([2, 3]), 3, 2) assert_allclose(data, np.array([1.88496 / 2, 2.82743 / 2]), rtol=1e-5) - def test_voltage_diff(self): - data = tools.calculate_voltage_diff_per_line(50, 0.125, 0.36, 20, -1, 0.9) - correct_value = 0.11105090491866049 + def test_calculate_voltage_diff_per_line(self): + data = tools.calculate_voltage_diff_per_line( + s_max=50, + r_total=0.125, + x_total=0.36, + v_nom=20, + reactive_power_mode="inductive", + power_factor=0.9, + component_type="gen", + ) + correct_value = -0.11105090491866049 assert np.isclose(data, correct_value) data = tools.calculate_voltage_diff_per_line( - np.array([50, 50]), - np.array([0.125, 0.125]), - np.array([0.36, 0.36]), - 20, - -1, - 0.9, + s_max=np.array([50, 50]), + r_total=np.array([0.125, 0.125]), + x_total=np.array([0.36, 0.36]), + v_nom=20, + reactive_power_mode="inductive", + power_factor=0.9, + component_type="gen", ) assert_allclose(data, np.array([correct_value, correct_value]), rtol=1e-5) - data = tools.calculate_voltage_diff_per_line(50, 0.125, 0.36, 40, -1, 0.9) + data = tools.calculate_voltage_diff_per_line( + s_max=50, + r_total=0.125, + x_total=0.36, + v_nom=40, + reactive_power_mode="inductive", + power_factor=0.9, + component_type="gen", + ) assert np.isclose(data, correct_value / 2) - data = tools.calculate_voltage_diff_per_line(100, 0.125, 0.36, 20, -1, 0.9) + data = tools.calculate_voltage_diff_per_line( + s_max=100, + r_total=0.125, + x_total=0.36, + v_nom=20, + reactive_power_mode="inductive", + power_factor=0.9, + component_type="gen", + ) assert np.isclose(data, correct_value * 2) data = tools.calculate_voltage_diff_per_line( - np.array([100, 100]), - np.array([0.125, 0.125]), - np.array([0.36, 0.36]), - np.array([20, 20]), - -1, - 0.9, + s_max=np.array([100, 100]), + r_total=np.array([0.125, 0.125]), + x_total=np.array([0.36, 0.36]), + v_nom=np.array([20, 20]), + reactive_power_mode="inductive", + power_factor=0.9, + component_type="gen", ) assert_allclose( data, np.array([correct_value * 2, correct_value * 2]), rtol=1e-5 ) + Phi = np.pi / 6 + arctanphi = np.arctan(Phi) + R = 0.125 + X = arctanphi * R + data = tools.calculate_voltage_diff_per_line( + s_max=-0.027, # 27 kW generator + r_total=R, + x_total=X, + v_nom=0.23, + reactive_power_mode="capacitive", + power_factor=0.95, + component_type="gen", + ) + data = data / 0.23 # convert to pu + # assert np.isclose(data, 0.2) def test_voltage_diff_pu(self): - data = tools.voltage_diff_pu(0.1, 0.350, 1, 1, 20, 50, 0.9, -1) + data = tools.voltage_diff_pu( + R_per_km=0.1, + L_per_km=0.350, + length=1, + num_parallel=1, + v_nom=20, + s_max=50, + power_factor=0.9, + reactive_power_mode="inductive", + component_type="gen", + ) correct_value = 0.52589253567891375 * 1e-2 assert np.isclose(data, correct_value) data = tools.voltage_diff_pu( - np.array([0.1, 0.1]), np.array([0.35, 0.35]), 1, 1, 20, 50, 0.9, -1 + R_per_km=np.array([0.1, 0.1]), + L_per_km=np.array([0.35, 0.35]), + length=1, + num_parallel=1, + v_nom=20, + s_max=50, + power_factor=0.9, + reactive_power_mode="inductive", + component_type="gen", ) assert_allclose(data, np.array([correct_value, correct_value]), rtol=1e-5) - data = tools.voltage_diff_pu(0.1, 0.35, 2, 1, 20, 50, 0.9, -1) + data = tools.voltage_diff_pu( + R_per_km=0.1, + L_per_km=0.35, + length=2, + num_parallel=1, + v_nom=20, + s_max=50, + power_factor=0.9, + reactive_power_mode="inductive", + component_type="gen", + ) assert np.isclose(data, 2 * correct_value) data = tools.voltage_diff_pu( - np.array([0.1, 0.1]), np.array([0.35, 0.35]), 2, 1, 20, 50, 0.9, -1 + R_per_km=np.array([0.1, 0.1]), + L_per_km=np.array([0.35, 0.35]), + length=2, + num_parallel=1, + v_nom=20, + s_max=50, + power_factor=0.9, + reactive_power_mode="inductive", + component_type="gen", ) assert_allclose( data, np.array([2 * correct_value, 2 * correct_value]), rtol=1e-5, ) - data = tools.voltage_diff_pu(0.1, 0.35, 1, 2, 20, 50, 0.9, -1) + + data = tools.voltage_diff_pu( + R_per_km=0.1, + L_per_km=0.35, + length=1, + num_parallel=2, + v_nom=20, + s_max=50, + power_factor=0.9, + reactive_power_mode="inductive", + component_type="gen", + ) assert np.isclose(data, correct_value / 2) def test_calculate_line_resistance(self): @@ -148,36 +236,90 @@ def test_drop_duplicated_columns(self): def test_select_cable(self): # no length given - cable_data, num_parallel_cables = tools.select_cable(self.edisgo, "mv", 5.1) + cable_data, num_parallel_cables = tools.select_cable( + self.edisgo, + "mv", + 5.1, + length=0, + max_voltage_diff=None, + max_cables=7, + power_factor=None, + component_type="load", + reactive_power_mode="inductive", + ) assert cable_data.name == "NA2XS2Y 3x1x150 RE/25" assert num_parallel_cables == 1 - cable_data, num_parallel_cables = tools.select_cable(self.edisgo, "mv", 40) + cable_data, num_parallel_cables = tools.select_cable( + self.edisgo, + "mv", + 40, + length=0, + max_voltage_diff=None, + max_cables=7, + power_factor=None, + component_type="load", + reactive_power_mode="inductive", + ) assert cable_data.name == "NA2XS(FL)2Y 3x1x500 RM/35" assert num_parallel_cables == 2 - cable_data, num_parallel_cables = tools.select_cable(self.edisgo, "lv", 0.18) + cable_data, num_parallel_cables = tools.select_cable( + self.edisgo, + "lv", + 0.18, + length=0, + max_voltage_diff=None, + max_cables=7, + power_factor=None, + component_type="load", + reactive_power_mode="inductive", + ) assert cable_data.name == "NAYY 4x1x150" assert num_parallel_cables == 1 # length given - cable_data, num_parallel_cables = tools.select_cable(self.edisgo, "mv", 5.1, 2) + cable_data, num_parallel_cables = tools.select_cable( + self.edisgo, + "mv", + 5.1, + length=2, + max_voltage_diff=None, + max_cables=7, + power_factor=None, + component_type="load", + reactive_power_mode="inductive", + ) assert cable_data.name == "NA2XS2Y 3x1x150 RE/25" assert num_parallel_cables == 1 - cable_data, num_parallel_cables = tools.select_cable(self.edisgo, "mv", 40, 1) + cable_data, num_parallel_cables = tools.select_cable( + self.edisgo, + "mv", + 40, + length=1, + max_voltage_diff=None, + max_cables=7, + power_factor=None, + component_type="load", + reactive_power_mode="inductive", + ) assert cable_data.name == "NA2XS(FL)2Y 3x1x500 RM/35" assert num_parallel_cables == 2 - cable_data, num_parallel_cables = tools.select_cable(self.edisgo, "lv", 0.18, 1) - assert cable_data.name == "NAYY 4x1x300" - assert num_parallel_cables == 3 - cable_data, num_parallel_cables = tools.select_cable( - self.edisgo, "lv", 0.18, 1, max_voltage_diff=0.01, max_cables=100 + self.edisgo, + "lv", + 0.18, + length=1, + max_voltage_diff=None, + max_cables=7, + power_factor=None, + component_type="load", + reactive_power_mode="inductive", ) assert cable_data.name == "NAYY 4x1x300" - assert num_parallel_cables == 8 + assert num_parallel_cables == 5 cable_data, num_parallel_cables = tools.select_cable( self.edisgo, @@ -187,15 +329,26 @@ def test_select_cable(self): max_voltage_diff=0.01, max_cables=100, power_factor=1, + component_type="load", reactive_power_mode="inductive", ) assert cable_data.name == "NAYY 4x1x300" assert num_parallel_cables == 12 + cable_data, num_parallel_cables = tools.select_cable( - self.edisgo, "lv", 0.18, length=1 + self.edisgo, + "lv", + 0.18, + length=1, + max_voltage_diff=0.01, + max_cables=100, + power_factor=None, + component_type="load", + reactive_power_mode="inductive", ) assert cable_data.name == "NAYY 4x1x300" - assert num_parallel_cables == 3 + assert num_parallel_cables == 14 + cable_data, num_parallel_cables = tools.select_cable( self.edisgo, "lv", @@ -204,11 +357,12 @@ def test_select_cable(self): max_voltage_diff=0.01, max_cables=100, power_factor=None, - reactive_power_mode="inductive", component_type="gen", + reactive_power_mode="inductive", ) assert cable_data.name == "NAYY 4x1x300" assert num_parallel_cables == 8 + try: cable_data, num_parallel_cables = tools.select_cable( self.edisgo, @@ -218,8 +372,8 @@ def test_select_cable(self): max_voltage_diff=0.01, max_cables=100, power_factor=None, - reactive_power_mode="inductive", component_type="fail", + reactive_power_mode="inductive", ) except ValueError as e: assert ( From 9c9f4561970d7deeb1178ecff2fbcb1dcaa3fb2e Mon Sep 17 00:00:00 2001 From: joda9 Date: Mon, 5 Aug 2024 11:35:34 +0200 Subject: [PATCH 15/29] defining return values --- edisgo/tools/tools.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index 93295609..60a80ab7 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -229,7 +229,10 @@ def calculate_voltage_diff_per_line( Returns ------- float or array-like - Voltage difference in kV. + Voltage difference in kV. If positive, the voltage difference behaves like + expected, it rises for generators and drops for loads. If negative, + the voltage difference behaves counterintuitively, it drops for generators + and rises for loads. """ if "gen" in component_type: sign = q_control.get_q_sign_generator(reactive_power_mode) From bd9c87e107c1caec775babe9bae1e2e2e61f4c3b Mon Sep 17 00:00:00 2001 From: joda9 Date: Mon, 5 Aug 2024 11:36:23 +0200 Subject: [PATCH 16/29] change name voltage_diff_pu --- edisgo/tools/tools.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index 60a80ab7..e18d8c84 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -242,13 +242,11 @@ def calculate_voltage_diff_per_line( raise ValueError("Component type not supported.") sin_phi = np.sqrt(1 - power_factor**2) # Calculate the voltage difference using the formula from VDE-AR-N 4105 - voltage_diff = (s_max / (v_nom)) * ( - r_total * power_factor + sign * x_total * sin_phi - ) + voltage_diff = (s_max / v_nom) * (r_total * power_factor + sign * x_total * sin_phi) return voltage_diff # in kV -def voltage_diff_pu( +def voltage_diff_pu_per_line( R_per_km: float | np.ndarray, L_per_km: float | np.ndarray, length: float, @@ -401,7 +399,7 @@ def select_cable( ] if length != 0: suitable_cables = suitable_cables[ - voltage_diff_pu( + voltage_diff_pu_per_line( R_per_km=available_cables["R_per_km"], L_per_km=available_cables["L_per_km"], length=length, @@ -426,7 +424,7 @@ def select_cable( ] if length != 0: suitable_cables = suitable_cables[ - voltage_diff_pu( + voltage_diff_pu_per_line( R_per_km=available_cables["R_per_km"], L_per_km=available_cables["L_per_km"], length=length, From 803d7960c7837e0b58081994f672fe2a1c38fbb7 Mon Sep 17 00:00:00 2001 From: joda9 Date: Mon, 5 Aug 2024 11:36:39 +0200 Subject: [PATCH 17/29] adding tests for coverage --- tests/tools/test_tools.py | 170 ++++++++++++++++++++++++++++++-------- 1 file changed, 135 insertions(+), 35 deletions(-) diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py index 9116e5cc..b183e0c7 100644 --- a/tests/tools/test_tools.py +++ b/tests/tools/test_tools.py @@ -31,77 +31,133 @@ def test_calculate_line_reactance(self): assert_allclose(data, np.array([1.88496 / 2, 2.82743 / 2]), rtol=1e-5) def test_calculate_voltage_diff_per_line(self): + correct_value_positive_sign = 0.6523893665569375 + correct_value_negative_sign = 1.2016106334430623 + r_total = 0.412 + x_total = 0.252 data = tools.calculate_voltage_diff_per_line( s_max=50, - r_total=0.125, - x_total=0.36, + r_total=r_total, + x_total=x_total, v_nom=20, reactive_power_mode="inductive", power_factor=0.9, component_type="gen", ) - correct_value = -0.11105090491866049 - assert np.isclose(data, correct_value) + assert np.isclose(data, correct_value_positive_sign) data = tools.calculate_voltage_diff_per_line( s_max=np.array([50, 50]), - r_total=np.array([0.125, 0.125]), - x_total=np.array([0.36, 0.36]), + r_total=np.array([r_total, r_total]), + x_total=np.array([x_total, x_total]), v_nom=20, reactive_power_mode="inductive", power_factor=0.9, component_type="gen", ) - assert_allclose(data, np.array([correct_value, correct_value]), rtol=1e-5) + assert_allclose( + data, + np.array([correct_value_positive_sign, correct_value_positive_sign]), + rtol=1e-5, + ) data = tools.calculate_voltage_diff_per_line( s_max=50, - r_total=0.125, - x_total=0.36, + r_total=r_total, + x_total=x_total, v_nom=40, reactive_power_mode="inductive", power_factor=0.9, component_type="gen", ) - assert np.isclose(data, correct_value / 2) + assert np.isclose(data, correct_value_positive_sign / 2) data = tools.calculate_voltage_diff_per_line( s_max=100, - r_total=0.125, - x_total=0.36, + r_total=r_total, + x_total=x_total, v_nom=20, reactive_power_mode="inductive", power_factor=0.9, component_type="gen", ) - assert np.isclose(data, correct_value * 2) + assert np.isclose(data, correct_value_positive_sign * 2) data = tools.calculate_voltage_diff_per_line( s_max=np.array([100, 100]), - r_total=np.array([0.125, 0.125]), - x_total=np.array([0.36, 0.36]), + r_total=np.array([r_total, r_total]), + x_total=np.array([x_total, x_total]), v_nom=np.array([20, 20]), reactive_power_mode="inductive", power_factor=0.9, component_type="gen", ) assert_allclose( - data, np.array([correct_value * 2, correct_value * 2]), rtol=1e-5 + data, + np.array( + [correct_value_positive_sign * 2, correct_value_positive_sign * 2] + ), + rtol=1e-5, ) + data = tools.calculate_voltage_diff_per_line( + s_max=100, + r_total=r_total, + x_total=x_total, + v_nom=20, + reactive_power_mode="capacitive", + power_factor=0.9, + component_type="gen", + ) + assert np.isclose(data, correct_value_negative_sign * 2) + data = tools.calculate_voltage_diff_per_line( + s_max=100, + r_total=r_total, + x_total=x_total, + v_nom=20, + reactive_power_mode="inductive", + power_factor=0.9, + component_type="load", + ) + assert np.isclose(data, correct_value_negative_sign * 2) + data = tools.calculate_voltage_diff_per_line( + s_max=100, + r_total=r_total, + x_total=x_total, + v_nom=20, + reactive_power_mode="capacitive", + power_factor=0.9, + component_type="load", + ) + assert np.isclose(data, correct_value_positive_sign * 2) + try: + data = tools.calculate_voltage_diff_per_line( + s_max=100, + r_total=r_total, + x_total=x_total, + v_nom=20, + reactive_power_mode="inductive", + power_factor=0.9, + component_type="fail", + ) + except ValueError as e: + assert str(e) == "Component type not supported." + Phi = np.pi / 6 arctanphi = np.arctan(Phi) - R = 0.125 + R = r_total X = arctanphi * R + v_nom = 0.4 data = tools.calculate_voltage_diff_per_line( - s_max=-0.027, # 27 kW generator + s_max=0.027, # 27 kW generator r_total=R, x_total=X, - v_nom=0.23, - reactive_power_mode="capacitive", + v_nom=v_nom, + reactive_power_mode="inductive", power_factor=0.95, component_type="gen", ) - data = data / 0.23 # convert to pu - # assert np.isclose(data, 0.2) + assert np.isclose(data / v_nom, 0.022230950086158 / v_nom) - def test_voltage_diff_pu(self): - data = tools.voltage_diff_pu( + def test_voltage_diff_pu_per_line(self): + correct_value_negative_sign = 0.52589253567891375 * 1e-2 + correct_value_positive_sign = 0.017241074643210865 + data = tools.voltage_diff_pu_per_line( R_per_km=0.1, L_per_km=0.350, length=1, @@ -112,9 +168,8 @@ def test_voltage_diff_pu(self): reactive_power_mode="inductive", component_type="gen", ) - correct_value = 0.52589253567891375 * 1e-2 - assert np.isclose(data, correct_value) - data = tools.voltage_diff_pu( + assert np.isclose(data, correct_value_negative_sign) + data = tools.voltage_diff_pu_per_line( R_per_km=np.array([0.1, 0.1]), L_per_km=np.array([0.35, 0.35]), length=1, @@ -125,8 +180,12 @@ def test_voltage_diff_pu(self): reactive_power_mode="inductive", component_type="gen", ) - assert_allclose(data, np.array([correct_value, correct_value]), rtol=1e-5) - data = tools.voltage_diff_pu( + assert_allclose( + data, + np.array([correct_value_negative_sign, correct_value_negative_sign]), + rtol=1e-5, + ) + data = tools.voltage_diff_pu_per_line( R_per_km=0.1, L_per_km=0.35, length=2, @@ -137,8 +196,8 @@ def test_voltage_diff_pu(self): reactive_power_mode="inductive", component_type="gen", ) - assert np.isclose(data, 2 * correct_value) - data = tools.voltage_diff_pu( + assert np.isclose(data, 2 * correct_value_negative_sign) + data = tools.voltage_diff_pu_per_line( R_per_km=np.array([0.1, 0.1]), L_per_km=np.array([0.35, 0.35]), length=2, @@ -151,11 +210,26 @@ def test_voltage_diff_pu(self): ) assert_allclose( data, - np.array([2 * correct_value, 2 * correct_value]), + np.array( + [2 * correct_value_negative_sign, 2 * correct_value_negative_sign] + ), rtol=1e-5, ) - data = tools.voltage_diff_pu( + data = tools.voltage_diff_pu_per_line( + R_per_km=0.1, + L_per_km=0.35, + length=1, + num_parallel=2, + v_nom=20, + s_max=50, + power_factor=0.9, + reactive_power_mode="inductive", + component_type="gen", + ) + assert np.isclose(data, correct_value_negative_sign / 2) + + data = tools.voltage_diff_pu_per_line( R_per_km=0.1, L_per_km=0.35, length=1, @@ -164,9 +238,35 @@ def test_voltage_diff_pu(self): s_max=50, power_factor=0.9, reactive_power_mode="inductive", + component_type="load", + ) + assert np.isclose(data, correct_value_positive_sign / 2) + + data = tools.voltage_diff_pu_per_line( + R_per_km=0.1, + L_per_km=0.35, + length=1, + num_parallel=2, + v_nom=20, + s_max=50, + power_factor=0.9, + reactive_power_mode="capacitive", + component_type="load", + ) + assert np.isclose(data, correct_value_negative_sign / 2) + + data = tools.voltage_diff_pu_per_line( + R_per_km=0.1, + L_per_km=0.35, + length=1, + num_parallel=2, + v_nom=20, + s_max=50, + power_factor=0.9, + reactive_power_mode="capacitive", component_type="gen", ) - assert np.isclose(data, correct_value / 2) + assert np.isclose(data, correct_value_positive_sign / 2) def test_calculate_line_resistance(self): # test single line @@ -244,7 +344,7 @@ def test_select_cable(self): max_voltage_diff=None, max_cables=7, power_factor=None, - component_type="load", + component_type=None, reactive_power_mode="inductive", ) assert cable_data.name == "NA2XS2Y 3x1x150 RE/25" From 2115254263b7a351d7ebf678807a60aa215a4ed1 Mon Sep 17 00:00:00 2001 From: joda9 Date: Mon, 5 Aug 2024 13:53:54 +0200 Subject: [PATCH 18/29] Refactor voltage difference calculation to use per unit (pu) instead of kilovolts (kV) --- edisgo/tools/tools.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index e18d8c84..257230e0 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -193,7 +193,7 @@ def drop_duplicated_columns(df, keep="last"): return df.loc[:, ~df.columns.duplicated(keep=keep)] -def calculate_voltage_diff_per_line( +def calculate_voltage_diff_pu_per_line( s_max: float | np.ndarray, r_total: float | np.ndarray, x_total: float | np.ndarray, @@ -229,7 +229,7 @@ def calculate_voltage_diff_per_line( Returns ------- float or array-like - Voltage difference in kV. If positive, the voltage difference behaves like + Voltage difference in pu. If positive, the voltage difference behaves like expected, it rises for generators and drops for loads. If negative, the voltage difference behaves counterintuitively, it drops for generators and rises for loads. @@ -242,11 +242,13 @@ def calculate_voltage_diff_per_line( raise ValueError("Component type not supported.") sin_phi = np.sqrt(1 - power_factor**2) # Calculate the voltage difference using the formula from VDE-AR-N 4105 - voltage_diff = (s_max / v_nom) * (r_total * power_factor + sign * x_total * sin_phi) - return voltage_diff # in kV + voltage_diff = (s_max / (v_nom**2)) * ( + r_total * power_factor + sign * x_total * sin_phi + ) + return voltage_diff # in pu -def voltage_diff_pu_per_line( +def calculate_voltage_difference_pu_per_line_with_length( R_per_km: float | np.ndarray, L_per_km: float | np.ndarray, length: float, @@ -292,7 +294,7 @@ def voltage_diff_pu_per_line( x_total = calculate_line_reactance(L_per_km, length, num_parallel) # Calculate the voltage drop or increase - delta_v = calculate_voltage_diff_per_line( + delta_v = calculate_voltage_diff_pu_per_line( s_max, r_total, x_total, @@ -303,7 +305,7 @@ def voltage_diff_pu_per_line( ) # Convert voltage difference to per unit of nominal voltage - voltage_difference_pu = delta_v / v_nom + voltage_difference_pu = delta_v return voltage_difference_pu @@ -399,7 +401,7 @@ def select_cable( ] if length != 0: suitable_cables = suitable_cables[ - voltage_diff_pu_per_line( + calculate_voltage_difference_pu_per_line_with_length( R_per_km=available_cables["R_per_km"], L_per_km=available_cables["L_per_km"], length=length, @@ -424,7 +426,7 @@ def select_cable( ] if length != 0: suitable_cables = suitable_cables[ - voltage_diff_pu_per_line( + calculate_voltage_difference_pu_per_line_with_length( R_per_km=available_cables["R_per_km"], L_per_km=available_cables["L_per_km"], length=length, From e68b601b978de75af01a277510d0709b29e4f3f7 Mon Sep 17 00:00:00 2001 From: joda9 Date: Mon, 5 Aug 2024 13:54:33 +0200 Subject: [PATCH 19/29] Refactor voltage difference calculation to use per unit (pu) instead of kilovolts (kV) --- tests/tools/test_tools.py | 71 ++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py index b183e0c7..193ed4c5 100644 --- a/tests/tools/test_tools.py +++ b/tests/tools/test_tools.py @@ -30,12 +30,12 @@ def test_calculate_line_reactance(self): data = tools.calculate_line_reactance(np.array([2, 3]), 3, 2) assert_allclose(data, np.array([1.88496 / 2, 2.82743 / 2]), rtol=1e-5) - def test_calculate_voltage_diff_per_line(self): - correct_value_positive_sign = 0.6523893665569375 - correct_value_negative_sign = 1.2016106334430623 + def test_calculate_voltage_diff_pu_per_line(self): + correct_value_positive_sign = 0.03261946832784687 + correct_value_negative_sign = 0.06008053167215312 r_total = 0.412 x_total = 0.252 - data = tools.calculate_voltage_diff_per_line( + data = tools.calculate_voltage_diff_pu_per_line( s_max=50, r_total=r_total, x_total=x_total, @@ -45,7 +45,7 @@ def test_calculate_voltage_diff_per_line(self): component_type="gen", ) assert np.isclose(data, correct_value_positive_sign) - data = tools.calculate_voltage_diff_per_line( + data = tools.calculate_voltage_diff_pu_per_line( s_max=np.array([50, 50]), r_total=np.array([r_total, r_total]), x_total=np.array([x_total, x_total]), @@ -59,7 +59,7 @@ def test_calculate_voltage_diff_per_line(self): np.array([correct_value_positive_sign, correct_value_positive_sign]), rtol=1e-5, ) - data = tools.calculate_voltage_diff_per_line( + data = tools.calculate_voltage_diff_pu_per_line( s_max=50, r_total=r_total, x_total=x_total, @@ -68,8 +68,8 @@ def test_calculate_voltage_diff_per_line(self): power_factor=0.9, component_type="gen", ) - assert np.isclose(data, correct_value_positive_sign / 2) - data = tools.calculate_voltage_diff_per_line( + assert np.isclose(data, correct_value_positive_sign / 4) + data = tools.calculate_voltage_diff_pu_per_line( s_max=100, r_total=r_total, x_total=x_total, @@ -79,7 +79,7 @@ def test_calculate_voltage_diff_per_line(self): component_type="gen", ) assert np.isclose(data, correct_value_positive_sign * 2) - data = tools.calculate_voltage_diff_per_line( + data = tools.calculate_voltage_diff_pu_per_line( s_max=np.array([100, 100]), r_total=np.array([r_total, r_total]), x_total=np.array([x_total, x_total]), @@ -95,7 +95,7 @@ def test_calculate_voltage_diff_per_line(self): ), rtol=1e-5, ) - data = tools.calculate_voltage_diff_per_line( + data = tools.calculate_voltage_diff_pu_per_line( s_max=100, r_total=r_total, x_total=x_total, @@ -105,7 +105,7 @@ def test_calculate_voltage_diff_per_line(self): component_type="gen", ) assert np.isclose(data, correct_value_negative_sign * 2) - data = tools.calculate_voltage_diff_per_line( + data = tools.calculate_voltage_diff_pu_per_line( s_max=100, r_total=r_total, x_total=x_total, @@ -115,7 +115,7 @@ def test_calculate_voltage_diff_per_line(self): component_type="load", ) assert np.isclose(data, correct_value_negative_sign * 2) - data = tools.calculate_voltage_diff_per_line( + data = tools.calculate_voltage_diff_pu_per_line( s_max=100, r_total=r_total, x_total=x_total, @@ -126,7 +126,7 @@ def test_calculate_voltage_diff_per_line(self): ) assert np.isclose(data, correct_value_positive_sign * 2) try: - data = tools.calculate_voltage_diff_per_line( + data = tools.calculate_voltage_diff_pu_per_line( s_max=100, r_total=r_total, x_total=x_total, @@ -143,7 +143,7 @@ def test_calculate_voltage_diff_per_line(self): R = r_total X = arctanphi * R v_nom = 0.4 - data = tools.calculate_voltage_diff_per_line( + data = tools.calculate_voltage_diff_pu_per_line( s_max=0.027, # 27 kW generator r_total=R, x_total=X, @@ -152,12 +152,35 @@ def test_calculate_voltage_diff_per_line(self): power_factor=0.95, component_type="gen", ) - assert np.isclose(data / v_nom, 0.022230950086158 / v_nom) + assert np.isclose(data, 0.055577375215395) + + # test the examples from VDE-AR-N 4105 attachment D + data = tools.calculate_voltage_diff_pu_per_line( + s_max=0.02, + r_total=0.2001, + x_total=0.1258, + v_nom=0.4, + reactive_power_mode="inductive", + power_factor=1, + component_type="gen", + ) + assert np.isclose(data, 0.025, rtol=1e-2) + + data = tools.calculate_voltage_diff_pu_per_line( + s_max=0.022, + r_total=0.2001, + x_total=0.1258, + v_nom=0.4, + reactive_power_mode="inductive", + power_factor=0.9, + component_type="gen", + ) + assert np.isclose(data, 0.0173, rtol=1e-2) - def test_voltage_diff_pu_per_line(self): + def test_calculate_voltage_difference_pu_per_line_with_length(self): correct_value_negative_sign = 0.52589253567891375 * 1e-2 correct_value_positive_sign = 0.017241074643210865 - data = tools.voltage_diff_pu_per_line( + data = tools.calculate_voltage_difference_pu_per_line_with_length( R_per_km=0.1, L_per_km=0.350, length=1, @@ -169,7 +192,7 @@ def test_voltage_diff_pu_per_line(self): component_type="gen", ) assert np.isclose(data, correct_value_negative_sign) - data = tools.voltage_diff_pu_per_line( + data = tools.calculate_voltage_difference_pu_per_line_with_length( R_per_km=np.array([0.1, 0.1]), L_per_km=np.array([0.35, 0.35]), length=1, @@ -185,7 +208,7 @@ def test_voltage_diff_pu_per_line(self): np.array([correct_value_negative_sign, correct_value_negative_sign]), rtol=1e-5, ) - data = tools.voltage_diff_pu_per_line( + data = tools.calculate_voltage_difference_pu_per_line_with_length( R_per_km=0.1, L_per_km=0.35, length=2, @@ -197,7 +220,7 @@ def test_voltage_diff_pu_per_line(self): component_type="gen", ) assert np.isclose(data, 2 * correct_value_negative_sign) - data = tools.voltage_diff_pu_per_line( + data = tools.calculate_voltage_difference_pu_per_line_with_length( R_per_km=np.array([0.1, 0.1]), L_per_km=np.array([0.35, 0.35]), length=2, @@ -216,7 +239,7 @@ def test_voltage_diff_pu_per_line(self): rtol=1e-5, ) - data = tools.voltage_diff_pu_per_line( + data = tools.calculate_voltage_difference_pu_per_line_with_length( R_per_km=0.1, L_per_km=0.35, length=1, @@ -229,7 +252,7 @@ def test_voltage_diff_pu_per_line(self): ) assert np.isclose(data, correct_value_negative_sign / 2) - data = tools.voltage_diff_pu_per_line( + data = tools.calculate_voltage_difference_pu_per_line_with_length( R_per_km=0.1, L_per_km=0.35, length=1, @@ -242,7 +265,7 @@ def test_voltage_diff_pu_per_line(self): ) assert np.isclose(data, correct_value_positive_sign / 2) - data = tools.voltage_diff_pu_per_line( + data = tools.calculate_voltage_difference_pu_per_line_with_length( R_per_km=0.1, L_per_km=0.35, length=1, @@ -255,7 +278,7 @@ def test_voltage_diff_pu_per_line(self): ) assert np.isclose(data, correct_value_negative_sign / 2) - data = tools.voltage_diff_pu_per_line( + data = tools.calculate_voltage_difference_pu_per_line_with_length( R_per_km=0.1, L_per_km=0.35, length=1, From 93e3592e5e24c6144d2e00ce1ff78fe01229ab2e Mon Sep 17 00:00:00 2001 From: birgits Date: Wed, 7 Aug 2024 16:40:10 +0200 Subject: [PATCH 20/29] Rename config parameters to make wording more consistent --- edisgo/config/config_timeseries_default.cfg | 40 ++++++------- edisgo/flex_opt/q_control.py | 64 +++++++++------------ edisgo/io/powermodels_io.py | 31 +++++----- edisgo/network/timeseries.py | 40 +++++++------ edisgo/tools/config.py | 2 +- edisgo/tools/tools.py | 19 ++++-- tests/flex_opt/test_q_control.py | 20 +++---- tests/io/test_powermodels_io.py | 6 +- 8 files changed, 111 insertions(+), 111 deletions(-) diff --git a/edisgo/config/config_timeseries_default.cfg b/edisgo/config/config_timeseries_default.cfg index bfb97351..a1373e89 100644 --- a/edisgo/config/config_timeseries_default.cfg +++ b/edisgo/config/config_timeseries_default.cfg @@ -88,16 +88,16 @@ lv_load_case_hp = 1.0 # =========================== # power factors used to generate reactive power time series for loads and generators -mv_gen = 0.9 -mv_load = 0.9 -mv_storage = 0.9 -mv_cp = 1.0 -mv_hp = 1.0 -lv_gen = 0.95 -lv_load = 0.95 -lv_storage = 0.95 -lv_cp = 1.0 -lv_hp = 1.0 +mv_generator = 0.9 +mv_conventional_load = 0.9 +mv_storage_unit = 0.9 +mv_charging_point = 1.0 +mv_heat_pump = 1.0 +lv_generator = 0.95 +lv_conventional_load = 0.95 +lv_storage_unit = 0.95 +lv_charging_point = 1.0 +lv_heat_pump = 1.0 [reactive_power_mode] @@ -105,16 +105,16 @@ lv_hp = 1.0 # =========================== # power factor modes used to generate reactive power time series for loads and generators -mv_gen = inductive -mv_load = inductive -mv_storage = inductive -mv_cp = inductive -mv_hp = inductive -lv_gen = inductive -lv_load = inductive -lv_storage = inductive -lv_cp = inductive -lv_hp = inductive +mv_generator = inductive +mv_conventional_load = inductive +mv_storage_unit = inductive +mv_charging_point = inductive +mv_heat_pump = inductive +lv_generator = inductive +lv_conventional_load = inductive +lv_storage_unit = inductive +lv_charging_point = inductive +lv_heat_pump = inductive [demandlib] diff --git a/edisgo/flex_opt/q_control.py b/edisgo/flex_opt/q_control.py index a6e98578..cfc353fb 100644 --- a/edisgo/flex_opt/q_control.py +++ b/edisgo/flex_opt/q_control.py @@ -92,22 +92,6 @@ def fixed_cosphi(active_power, q_sign, power_factor): return active_power * q_sign * np.tan(np.arccos(power_factor)) -def _get_component_dict(): - """ - Helper function to translate from component type term used in function to the one - used in the config files. - - """ - comp_dict = { - "generators": "gen", - "storage_units": "storage", - "conventional_loads": "load", - "charging_points": "cp", - "heat_pumps": "hp", - } - return comp_dict - - def _fixed_cosphi_default_power_factor(comp_df, component_type, configs): """ Gets fixed cosphi default reactive power factor for each given component. @@ -123,8 +107,8 @@ def _fixed_cosphi_default_power_factor(comp_df, component_type, configs): All components must have the same `component_type`. component_type : str The component type determines the reactive power factor and mode used. - Possible options are 'generators', 'storage_units', 'conventional_loads', - 'charging_points', and 'heat_pumps'. + Possible options are 'generator', 'storage_unit', 'conventional_load', + 'charging_point', and 'heat_pump'. configs : :class:`~.tools.config.Config` eDisGo configuration data. @@ -136,22 +120,28 @@ def _fixed_cosphi_default_power_factor(comp_df, component_type, configs): """ reactive_power_factor = configs["reactive_power_factor"] - comp_dict = _get_component_dict() - - if component_type in comp_dict.keys(): - comp = comp_dict[component_type] + allowed_types = [ + "generator", + "storage_unit", + "conventional_load", + "charging_point", + "heat_pump", + ] + if component_type in allowed_types: # write series with power factor for each component power_factor = pd.Series(index=comp_df.index, dtype=float) for voltage_level in comp_df.voltage_level.unique(): cols = comp_df.index[comp_df.voltage_level == voltage_level] if len(cols) > 0: - power_factor[cols] = reactive_power_factor[f"{voltage_level}_{comp}"] + power_factor[cols] = reactive_power_factor[ + f"{voltage_level}_{component_type}" + ] return power_factor else: raise ValueError( "Given 'component_type' is not valid. Valid options are " - "'generators','storage_units', 'conventional_loads', 'charging_points', " - "and 'heat_pumps'." + "'generator', 'storage_unit', 'conventional_load', 'charging_point', " + "and 'heat_pump'." ) @@ -170,8 +160,8 @@ def _fixed_cosphi_default_reactive_power_sign(comp_df, component_type, configs): All components must have the same `component_type`. component_type : str The component type determines the reactive power factor and mode used. - Possible options are 'generators', 'storage_units', 'conventional_loads', - 'charging_points', and 'heat_pumps'. + Possible options are 'generator', 'storage_unit', 'conventional_load', + 'charging_point', and 'heat_pump'. configs : :class:`~.tools.config.Config` eDisGo configuration data. @@ -183,17 +173,15 @@ def _fixed_cosphi_default_reactive_power_sign(comp_df, component_type, configs): """ reactive_power_mode = configs["reactive_power_mode"] - comp_dict = _get_component_dict() q_sign_dict = { - "generators": get_q_sign_generator, - "storage_units": get_q_sign_generator, - "conventional_loads": get_q_sign_load, - "charging_points": get_q_sign_load, - "heat_pumps": get_q_sign_load, + "generator": get_q_sign_generator, + "storage_unit": get_q_sign_generator, + "conventional_load": get_q_sign_load, + "charging_point": get_q_sign_load, + "heat_pump": get_q_sign_load, } - if component_type in comp_dict.keys(): - comp = comp_dict[component_type] + if component_type in q_sign_dict.keys(): get_q_sign = q_sign_dict[component_type] # write series with power factor for each component q_sign = pd.Series(index=comp_df.index, dtype=float) @@ -201,12 +189,12 @@ def _fixed_cosphi_default_reactive_power_sign(comp_df, component_type, configs): cols = comp_df.index[comp_df.voltage_level == voltage_level] if len(cols) > 0: q_sign[cols] = get_q_sign( - reactive_power_mode[f"{voltage_level}_{comp}"] + reactive_power_mode[f"{voltage_level}_{component_type}"] ) return q_sign else: raise ValueError( "Given 'component_type' is not valid. Valid options are " - "'generators','storage_units', 'conventional_loads', 'charging_points', " - "and 'heat_pumps'." + "'generator', 'storage_unit', 'conventional_load', 'charging_point', " + "and 'heat_pump'." ) diff --git a/edisgo/io/powermodels_io.py b/edisgo/io/powermodels_io.py index b0fb2278..48b5c13c 100644 --- a/edisgo/io/powermodels_io.py +++ b/edisgo/io/powermodels_io.py @@ -667,7 +667,7 @@ def _build_gen(edisgo_obj, psa_net, pm, flexible_storage_units, s_base): gen.bus[gen_i], flexible_storage_units=flexible_storage_units, ) - pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "gen") + pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "generator") q = [ sign * np.tan(np.arccos(pf)) * gen.p_nom[gen_i], sign * np.tan(np.arccos(pf)) * gen.p_nom_min[gen_i], @@ -704,7 +704,7 @@ def _build_gen(edisgo_obj, psa_net, pm, flexible_storage_units, s_base): psa_net.storage_units.bus.loc[inflexible_storage_units[stor_i]], flexible_storage_units=flexible_storage_units, ) - pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "storage") + pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "storage_unit") p_g = max( [ psa_net.storage_units_t.p_set[inflexible_storage_units[stor_i]][0], @@ -837,7 +837,7 @@ def _build_branch(edisgo_obj, psa_net, pm, flexible_storage_units, s_base): flexible_storage_units=flexible_storage_units, ) # retrieve power factor from config - pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "storage") + pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "storage_unit") pm["branch"][str(stor_i + len(branches.index) + 1)] = { "name": "bss_branch_" + str(stor_i + 1), @@ -919,22 +919,22 @@ def _build_load( edisgo_obj.topology.loads_df.loc[loads_df.index[load_i]].type == "conventional_load" ): - pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "load") + pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "conventional_load") elif ( edisgo_obj.topology.loads_df.loc[loads_df.index[load_i]].type == "heat_pump" ): - pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "hp") + pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "heat_pump") elif ( edisgo_obj.topology.loads_df.loc[loads_df.index[load_i]].type == "charging_point" ): - pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "cp") + pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "charging_point") else: logger.warning( "No type specified for load {}. Power factor and sign will" "be set for conventional load.".format(loads_df.index[load_i]) ) - pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "load") + pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "conventional_load") p_d = psa_net.loads_t.p_set[loads_df.index[load_i]] q_d = psa_net.loads_t.q_set[loads_df.index[load_i]] pm["load"][str(load_i + 1)] = { @@ -955,7 +955,7 @@ def _build_load( psa_net.storage_units.bus.loc[inflexible_storage_units[stor_i]], flexible_storage_units=flexible_storage_units, ) - pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "storage") + pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "storage_unit") p_d = -min( [ psa_net.storage_units_t.p_set[inflexible_storage_units[stor_i]][0], @@ -1036,7 +1036,7 @@ def _build_battery_storage( flexible_storage_units=flexible_storage_units, ) # retrieve power factor from config - pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "storage") + pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "storage_unit") e_max = ( psa_net.storage_units.p_nom.loc[flexible_storage_units[stor_i]] * psa_net.storage_units.max_hours.loc[flexible_storage_units[stor_i]] @@ -1151,7 +1151,7 @@ def _build_electromobility(edisgo_obj, psa_net, pm, s_base, flexible_cps): eta = edisgo_obj.electromobility.simbev_config_df.eta_cp.values[0] except IndexError: eta = 0.9 - pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "cp") + pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "charging_point") q = ( sign * np.tan(np.arccos(pf)) @@ -1218,7 +1218,7 @@ def _build_heatpump(psa_net, pm, edisgo_obj, s_base, flexible_hps): for hp_i in np.arange(len(heat_df.index)): idx_bus = _mapping(psa_net, edisgo_obj, heat_df.bus[hp_i]) # retrieve power factor and sign from config - pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "hp") + pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "heat_pump") q = sign * np.tan(np.arccos(pf)) * heat_df.p_set[hp_i] p_d = heat_df2[heat_df.index[hp_i]] pm["heatpumps"][str(hp_i + 1)] = { @@ -1446,7 +1446,7 @@ def _build_dsm(edisgo_obj, psa_net, pm, s_base, flexible_loads): for dsm_i in np.arange(len(dsm_df.index)): idx_bus = _mapping(psa_net, edisgo_obj, dsm_df.bus[dsm_i]) # retrieve power factor and sign from config - pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "load") + pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "conventional_load") p_max = edisgo_obj.dsm.p_max[dsm_df.index[dsm_i]] p_min = edisgo_obj.dsm.p_min[dsm_df.index[dsm_i]] e_min = edisgo_obj.dsm.e_min[dsm_df.index[dsm_i]] @@ -2053,7 +2053,8 @@ def _get_pf(edisgo_obj, pm, idx_bus, kind): idx_bus : int Bus index from PowerModels bus dictionary. kind : str - Must be one of ["gen", "load", "storage", "hp", "cp"]. + Must be one of ["generator", "conventional_load", "storage_unit", "heat_pump", + "charging_point"]. Returns ------- @@ -2067,12 +2068,12 @@ def _get_pf(edisgo_obj, pm, idx_bus, kind): sign = edisgo_obj.config._data["reactive_power_mode"][ "{}_{}".format(grid_level, kind) ] - if kind in ["gen", "storage"]: + if kind in ["generator", "storage_unit"]: if sign == "inductive": sign = -1 else: sign = 1 - elif kind in ["load", "hp", "cp"]: + elif kind in ["conventional_load", "heat_pump", "charging_point"]: if sign == "inductive": sign = 1 else: diff --git a/edisgo/network/timeseries.py b/edisgo/network/timeseries.py index 15337f06..6cf4a7b4 100644 --- a/edisgo/network/timeseries.py +++ b/edisgo/network/timeseries.py @@ -821,10 +821,10 @@ def _worst_case_generators(self, cases, df, configs): # reactive power # get worst case configurations for each generator power_factor = q_control._fixed_cosphi_default_power_factor( - df, "generators", configs + df, "generator", configs ) q_sign = q_control._fixed_cosphi_default_reactive_power_sign( - df, "generators", configs + df, "generator", configs ) # write reactive power configuration to TimeSeriesRaw self.time_series_raw.q_control.drop(df.index, errors="ignore", inplace=True) @@ -899,10 +899,10 @@ def _worst_case_conventional_load(self, cases, df, configs): # reactive power # get worst case configurations for each load power_factor = q_control._fixed_cosphi_default_power_factor( - df, "conventional_loads", configs + df, "conventional_load", configs ) q_sign = q_control._fixed_cosphi_default_reactive_power_sign( - df, "conventional_loads", configs + df, "conventional_load", configs ) # write reactive power configuration to TimeSeriesRaw self.time_series_raw.q_control.drop(df.index, errors="ignore", inplace=True) @@ -999,10 +999,10 @@ def _worst_case_charging_points(self, cases, df, configs): # reactive power # get worst case configurations for each charging point power_factor = q_control._fixed_cosphi_default_power_factor( - df, "charging_points", configs + df, "charging_point", configs ) q_sign = q_control._fixed_cosphi_default_reactive_power_sign( - df, "charging_points", configs + df, "charging_point", configs ) # write reactive power configuration to TimeSeriesRaw self.time_series_raw.q_control.drop(df.index, errors="ignore", inplace=True) @@ -1077,10 +1077,10 @@ def _worst_case_heat_pumps(self, cases, df, configs): # reactive power # get worst case configurations for each heat pump power_factor = q_control._fixed_cosphi_default_power_factor( - df, "heat_pumps", configs + df, "heat_pump", configs ) q_sign = q_control._fixed_cosphi_default_reactive_power_sign( - df, "heat_pumps", configs + df, "heat_pump", configs ) # write reactive power configuration to TimeSeriesRaw self.time_series_raw.q_control.drop(df.index, errors="ignore", inplace=True) @@ -1153,10 +1153,10 @@ def _worst_case_storage_units(self, cases, df, configs): # reactive power # get worst case configurations for each load power_factor = q_control._fixed_cosphi_default_power_factor( - df, "storage_units", configs + df, "storage_unit", configs ) q_sign = q_control._fixed_cosphi_default_reactive_power_sign( - df, "storage_units", configs + df, "storage_unit", configs ) # write reactive power configuration to TimeSeriesRaw self.time_series_raw.q_control.drop(df.index, errors="ignore", inplace=True) @@ -1606,7 +1606,7 @@ def _get_q_sign_and_power_factor_per_component( q_sign, q_control._fixed_cosphi_default_reactive_power_sign( df[df["type"] == load_type], - f"{load_type}s", + load_type, edisgo_object.config, ), ] @@ -1616,17 +1616,17 @@ def _get_q_sign_and_power_factor_per_component( power_factor, q_control._fixed_cosphi_default_power_factor( df[df["type"] == load_type], - f"{load_type}s", + load_type, edisgo_object.config, ), ] ) else: q_sign = q_control._fixed_cosphi_default_reactive_power_sign( - df, type, edisgo_object.config + df, type[:-1], edisgo_object.config ) power_factor = q_control._fixed_cosphi_default_power_factor( - df, type, edisgo_object.config + df, type[:-1], edisgo_object.config ) elif isinstance(parametrisation, pd.DataFrame): # check if all given components exist in network and only use existing @@ -1659,7 +1659,7 @@ def _get_q_sign_and_power_factor_per_component( q_sign, default_func( df[df["type"] == load_type], - f"{load_type}s", + load_type, edisgo_object.config, ), ] @@ -1668,7 +1668,9 @@ def _get_q_sign_and_power_factor_per_component( q_sign = pd.concat( [ q_sign, - default_func(df, type, edisgo_object.config), + default_func( + df, type[:-1], edisgo_object.config + ), ] ) else: @@ -1692,7 +1694,7 @@ def _get_q_sign_and_power_factor_per_component( power_factor, default_func( df[df["type"] == load_type], - f"{load_type}s", + load_type, edisgo_object.config, ), ] @@ -1701,7 +1703,9 @@ def _get_q_sign_and_power_factor_per_component( power_factor = pd.concat( [ power_factor, - default_func(df, type, edisgo_object.config), + default_func( + df, type[:-1], edisgo_object.config + ), ] ) else: diff --git a/edisgo/tools/config.py b/edisgo/tools/config.py index 54fc08a3..7494943a 100644 --- a/edisgo/tools/config.py +++ b/edisgo/tools/config.py @@ -116,7 +116,7 @@ class Config: Get reactive power factor for generators in the MV network - >>> config['reactive_power_factor']['mv_gen'] + >>> config['reactive_power_factor']['mv_generator'] """ diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index 257230e0..11ef5670 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -234,9 +234,9 @@ def calculate_voltage_diff_pu_per_line( the voltage difference behaves counterintuitively, it drops for generators and rises for loads. """ - if "gen" in component_type: + if component_type in ["generator", "storage_unit"]: sign = q_control.get_q_sign_generator(reactive_power_mode) - elif "load" in component_type or "cp" in component_type or "hp" in component_type: + elif component_type in ["conventional_load", "heat_pump", "charging_point"]: sign = q_control.get_q_sign_load(reactive_power_mode) else: raise ValueError("Component type not supported.") @@ -362,14 +362,21 @@ def select_cable( A tuple containing the selected cable type and the quantity needed. """ if component_type is None: - component_type = level + "_load" - - elif component_type in ["gen", "load", "cp", "hp"]: + component_type = level + "conventional_load" + + elif component_type in [ + "generator", + "conventional_load", + "charging_point", + "heat_pump", + "storage_unit", + ]: component_type = level + "_" + component_type else: raise ValueError( "Specified component type is not valid. " - "Must either be 'gen', 'load', 'cp' or 'hp'." + "Must either be 'generator', 'conventional_load', 'charging_point', " + "'heat_pump' or 'storage_unit'." ) if power_factor is None: power_factor = edisgo_obj.config["reactive_power_factor"][component_type] diff --git a/tests/flex_opt/test_q_control.py b/tests/flex_opt/test_q_control.py index 9595ec1c..a028c154 100644 --- a/tests/flex_opt/test_q_control.py +++ b/tests/flex_opt/test_q_control.py @@ -101,7 +101,7 @@ def test__fixed_cosphi_default_power_factor( # test for component_type="generators" pf = q_control._fixed_cosphi_default_power_factor( - comp_df=df, component_type="generators", configs=config + comp_df=df, component_type="generator", configs=config ) assert pf.shape == (3,) @@ -112,7 +112,7 @@ def test__fixed_cosphi_default_power_factor( # test for component_type="loads" pf = q_control._fixed_cosphi_default_power_factor( - comp_df=df, component_type="conventional_loads", configs=config + comp_df=df, component_type="conventional_load", configs=config ) assert pf.shape == (3,) @@ -123,7 +123,7 @@ def test__fixed_cosphi_default_power_factor( # test for component_type="charging_points" pf = q_control._fixed_cosphi_default_power_factor( - comp_df=df, component_type="charging_points", configs=config + comp_df=df, component_type="charging_point", configs=config ) assert pf.shape == (3,) @@ -134,7 +134,7 @@ def test__fixed_cosphi_default_power_factor( # test for component_type="heat_pumps" pf = q_control._fixed_cosphi_default_power_factor( - comp_df=df, component_type="heat_pumps", configs=config + comp_df=df, component_type="heat_pump", configs=config ) assert pf.shape == (3,) @@ -145,7 +145,7 @@ def test__fixed_cosphi_default_power_factor( # test for component_type="storage_units" pf = q_control._fixed_cosphi_default_power_factor( - comp_df=df, component_type="storage_units", configs=config + comp_df=df, component_type="storage_unit", configs=config ) assert pf.shape == (3,) @@ -165,7 +165,7 @@ def test__fixed_cosphi_default_reactive_power_sign( # test for component_type="generators" pf = q_control._fixed_cosphi_default_reactive_power_sign( - comp_df=df, component_type="generators", configs=config + comp_df=df, component_type="generator", configs=config ) assert pf.shape == (3,) @@ -176,7 +176,7 @@ def test__fixed_cosphi_default_reactive_power_sign( # test for component_type="conventional_loads" pf = q_control._fixed_cosphi_default_reactive_power_sign( - comp_df=df, component_type="conventional_loads", configs=config + comp_df=df, component_type="conventional_load", configs=config ) assert pf.shape == (3,) @@ -187,7 +187,7 @@ def test__fixed_cosphi_default_reactive_power_sign( # test for component_type="charging_points" pf = q_control._fixed_cosphi_default_reactive_power_sign( - comp_df=df, component_type="charging_points", configs=config + comp_df=df, component_type="charging_point", configs=config ) assert pf.shape == (3,) @@ -198,7 +198,7 @@ def test__fixed_cosphi_default_reactive_power_sign( # test for component_type="heat_pumps" pf = q_control._fixed_cosphi_default_reactive_power_sign( - comp_df=df, component_type="heat_pumps", configs=config + comp_df=df, component_type="heat_pump", configs=config ) assert pf.shape == (3,) @@ -209,7 +209,7 @@ def test__fixed_cosphi_default_reactive_power_sign( # test for component_type="storage_units" pf = q_control._fixed_cosphi_default_reactive_power_sign( - comp_df=df, component_type="storage_units", configs=config + comp_df=df, component_type="storage_unit", configs=config ) assert pf.shape == (3,) diff --git a/tests/io/test_powermodels_io.py b/tests/io/test_powermodels_io.py index 4d0d7842..b3bfab03 100644 --- a/tests/io/test_powermodels_io.py +++ b/tests/io/test_powermodels_io.py @@ -310,7 +310,7 @@ def test__get_pf(self): # test mode None powermodels_network, hv_flex_dict = powermodels_io.to_powermodels(self.edisgo) - for component in ["gen", "storage"]: + for component in ["generator", "storage_unit"]: pf, sign = powermodels_io._get_pf( self.edisgo, powermodels_network, 1, component ) @@ -322,10 +322,10 @@ def test__get_pf(self): assert pf == 0.95 assert sign == -1 - for component in ["hp", "cp"]: + for component in ["heat_pump", "charging_point"]: for bus in [1, 29]: pf, sign = powermodels_io._get_pf( - self.edisgo, powermodels_network, 1, component + self.edisgo, powermodels_network, bus, component ) assert pf == 1 assert sign == 1 From 59f12c868c651aa2374e2fdd38558f171a8f2213 Mon Sep 17 00:00:00 2001 From: birgits Date: Wed, 7 Aug 2024 16:40:42 +0200 Subject: [PATCH 21/29] Minor docstring fix --- edisgo/flex_opt/q_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edisgo/flex_opt/q_control.py b/edisgo/flex_opt/q_control.py index cfc353fb..07183a7d 100644 --- a/edisgo/flex_opt/q_control.py +++ b/edisgo/flex_opt/q_control.py @@ -100,7 +100,7 @@ def _fixed_cosphi_default_power_factor(comp_df, component_type, configs): ----------- comp_df : :pandas:`pandas.DataFrame` Dataframe with component names (in the index) of all components - reactive power factor needs to be set. Only required column is + reactive power factor needs to be set for. Only required column is column 'voltage_level', giving the voltage level the component is in (the voltage level can be set using the function :func:`~.tools.tools.assign_voltage_level_to_component`). From ace46a93ce519a4642d40056b990b64040253e16 Mon Sep 17 00:00:00 2001 From: birgits Date: Wed, 7 Aug 2024 16:41:11 +0200 Subject: [PATCH 22/29] Simplify getting config values --- edisgo/io/powermodels_io.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/edisgo/io/powermodels_io.py b/edisgo/io/powermodels_io.py index 48b5c13c..541e01ff 100644 --- a/edisgo/io/powermodels_io.py +++ b/edisgo/io/powermodels_io.py @@ -2062,12 +2062,8 @@ def _get_pf(edisgo_obj, pm, idx_bus, kind): """ grid_level = pm["bus"][str(idx_bus)]["grid_level"] - pf = edisgo_obj.config._data["reactive_power_factor"][ - "{}_{}".format(grid_level, kind) - ] - sign = edisgo_obj.config._data["reactive_power_mode"][ - "{}_{}".format(grid_level, kind) - ] + pf = edisgo_obj.config["reactive_power_factor"]["{}_{}".format(grid_level, kind)] + sign = edisgo_obj.config["reactive_power_mode"]["{}_{}".format(grid_level, kind)] if kind in ["generator", "storage_unit"]: if sign == "inductive": sign = -1 From aa49064cb5ea4483a5bec828a6f6aeb8d69e39f2 Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 8 Aug 2024 13:58:15 +0200 Subject: [PATCH 23/29] Adapt calculate_voltage_diff_pu_per_line to have q_sign and power_factor as necessary inputs --- edisgo/tools/tools.py | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index 11ef5670..beb9fa51 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -14,7 +14,7 @@ from sqlalchemy.engine.base import Engine -from edisgo.flex_opt import exceptions, q_control +from edisgo.flex_opt import exceptions from edisgo.io.db import session_scope_egon_data, sql_grid_geom, sql_intersects from edisgo.tools import session_scope @@ -198,12 +198,11 @@ def calculate_voltage_diff_pu_per_line( r_total: float | np.ndarray, x_total: float | np.ndarray, v_nom: float | np.ndarray, - reactive_power_mode: str = "inductive", - power_factor: float = 0.95, - component_type: str = "load", + q_sign: int, + power_factor: float, ) -> float | np.ndarray: """ - Calculate the voltage difference across a line in kV. + Calculate the voltage difference across a line in p.u.. Parameters ---------- @@ -215,35 +214,27 @@ def calculate_voltage_diff_pu_per_line( Total reactance of the line in Ohms. v_nom : float or array-like Nominal voltage of the line in kV. - reactive_power_mode : str, optional - Mode of the reactive power. Default: 'inductive'. - alternative: 'capacitive' - power_factor : float, optional - Power factor (cosine of the phase angle) of the load or generator. - Default is 0.95. - component_type : str, optional - Type of the component to be connected, used to obtain the default reactive power - mode from the configuration. Default: 'load'. - alternative: 'gen' + q_sign : int + `q_sign` defines whether the reactive power is positive or + negative and must either be -1 or +1. In case of generators and storage units, + inductive reactive power is negative. In case of loads, inductive reactive + power is positive. + power_factor : :pandas:`pandas.Series` or float + Ratio of real to apparent power. Returns ------- float or array-like - Voltage difference in pu. If positive, the voltage difference behaves like + Voltage difference in p.u.. If positive, the voltage difference behaves like expected, it rises for generators and drops for loads. If negative, the voltage difference behaves counterintuitively, it drops for generators and rises for loads. + """ - if component_type in ["generator", "storage_unit"]: - sign = q_control.get_q_sign_generator(reactive_power_mode) - elif component_type in ["conventional_load", "heat_pump", "charging_point"]: - sign = q_control.get_q_sign_load(reactive_power_mode) - else: - raise ValueError("Component type not supported.") sin_phi = np.sqrt(1 - power_factor**2) # Calculate the voltage difference using the formula from VDE-AR-N 4105 voltage_diff = (s_max / (v_nom**2)) * ( - r_total * power_factor + sign * x_total * sin_phi + r_total * power_factor + q_sign * x_total * sin_phi ) return voltage_diff # in pu From 0d1683d49911de9d6ea3f58edc892ec89baca6dd Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 8 Aug 2024 14:00:08 +0200 Subject: [PATCH 24/29] Change function calculate_voltage_diff_pu_per_line_from_type to have line type and component type as inputs --- edisgo/tools/tools.py | 109 +++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 43 deletions(-) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index beb9fa51..31553ae0 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -14,7 +14,7 @@ from sqlalchemy.engine.base import Engine -from edisgo.flex_opt import exceptions +from edisgo.flex_opt import exceptions, q_control from edisgo.io.db import session_scope_egon_data, sql_grid_geom, sql_intersects from edisgo.tools import session_scope @@ -239,67 +239,94 @@ def calculate_voltage_diff_pu_per_line( return voltage_diff # in pu -def calculate_voltage_difference_pu_per_line_with_length( - R_per_km: float | np.ndarray, - L_per_km: float | np.ndarray, +def calculate_voltage_diff_pu_per_line_from_type( + edisgo_obj: EDisGo, + cable_names: str | np.ndarray, length: float, num_parallel: int, v_nom: float | np.ndarray, s_max: float | np.ndarray, - power_factor: float = 0.95, - reactive_power_mode: str = "inductive", - component_type: str = "load", + component_type: str, ) -> float | np.ndarray: """ - Calculate the voltage difference per unit of nominal voltage. + Calculate the voltage difference across a line in p.u. depending on line type + and component type. + + This function serves as a helper function for function + :py:func:`calculate_voltage_diff_pu_per_line`, as it automatically obtains the + equipment data per line type from the provided equipment data and default reactive + power data per component type from the configuration files. Parameters ---------- - R_per_km : float or array-like + edisgo_obj : :class:`~.EDisGo` + cable_names : str or array-like Resistance per kilometer of the cable in ohm/km. - L_per_km : float or array-like - Inductance per kilometer of the cable in mH/km. length : float Length of the cable in km. num_parallel : int Number of parallel cables. v_nom : int - Nominal voltage in kV. + Nominal voltage of the cable(s) in kV. s_max : float Apparent power the cable must carry in MVA. - power_factor : float, optional - Cosine phi of the load or generator. Default: 0.95. component_type : str, optional Type of the component to be connected, used to obtain the default reactive power - mode from the configuration. Default: 'load'. - alternative: 'gen' + mode and power factor from the configuration file. If this is given, + `reactive_power_mode` and `power_factor` are not considered. + Possible options are "generator", "conventional_load", "charging_point", + "heat_pump" and "storage_unit". Returns ------- - float - Voltage difference in per unit of nominal voltage. + float or array-like + Voltage difference in p.u.. If positive, the voltage difference behaves like + expected, it rises for generators and drops for loads. If negative, + the voltage difference behaves counterintuitively, it drops for generators + and rises for loads. + """ - # Calculate total resistance and reactance for the given length and - # number of parallel cables - r_total = calculate_line_resistance(R_per_km, length, num_parallel) - x_total = calculate_line_reactance(L_per_km, length, num_parallel) + # calculate total resistance and reactance for the given length and + # number of parallel cables for given cable types + config_type = "mv_cables" if v_nom > 1.0 else "lv_cables" + cable_data = edisgo_obj.topology.equipment_data[config_type] + r_total = calculate_line_resistance( + cable_data.loc[cable_names, "R_per_km"], length, num_parallel + ) + x_total = calculate_line_reactance( + cable_data.loc[cable_names, "L_per_km"], length, num_parallel + ) + + # get sign of reactive power based on component type + config_type = f"mv_{component_type}" if v_nom > 1.0 else f"lv_{component_type}" + if component_type in ["generator", "storage_unit"]: + q_sign = q_control.get_q_sign_generator( + edisgo_obj.config["reactive_power_mode"][config_type] + ) + elif component_type in ["conventional_load", "heat_pump", "charging_point"]: + q_sign = q_control.get_q_sign_load( + edisgo_obj.config["reactive_power_mode"][config_type] + ) + else: + raise ValueError( + "Specified component type is not valid. " + "Must either be 'generator', 'conventional_load', 'charging_point', " + "'heat_pump' or 'storage_unit'." + ) + + # get power factor based on component type + power_factor = edisgo_obj.config["reactive_power_factor"][config_type] # Calculate the voltage drop or increase - delta_v = calculate_voltage_diff_pu_per_line( + return calculate_voltage_diff_pu_per_line( s_max, r_total, x_total, v_nom, - reactive_power_mode=reactive_power_mode, - power_factor=power_factor, - component_type=component_type, + q_sign, + power_factor, ) - # Convert voltage difference to per unit of nominal voltage - voltage_difference_pu = delta_v - - return voltage_difference_pu - def select_cable( edisgo_obj: EDisGo, @@ -399,15 +426,13 @@ def select_cable( ] if length != 0: suitable_cables = suitable_cables[ - calculate_voltage_difference_pu_per_line_with_length( - R_per_km=available_cables["R_per_km"], - L_per_km=available_cables["L_per_km"], + calculate_voltage_diff_pu_per_line_from_type( + edisgo_obj=edisgo_obj, + cable_names=suitable_cables.index, length=length, num_parallel=cable_count, - v_nom=available_cables["U_n"], + v_nom=available_cables["U_n"].values[0], s_max=apparent_power, - power_factor=power_factor, - reactive_power_mode=reactive_power_mode, component_type=component_type, ) < max_voltage_diff @@ -424,15 +449,13 @@ def select_cable( ] if length != 0: suitable_cables = suitable_cables[ - calculate_voltage_difference_pu_per_line_with_length( - R_per_km=available_cables["R_per_km"], - L_per_km=available_cables["L_per_km"], + calculate_voltage_diff_pu_per_line_from_type( + edisgo_obj=edisgo_obj, + cable_names=available_cables.index, length=length, num_parallel=cable_count, - v_nom=available_cables["U_n"], + v_nom=available_cables["U_n"].values[0], s_max=apparent_power, - power_factor=power_factor, - reactive_power_mode=reactive_power_mode, component_type=component_type, ) < max_voltage_diff From 095e3d0bb13f582e00de87ec20f7cae8814c34cb Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 8 Aug 2024 14:00:51 +0200 Subject: [PATCH 25/29] Change function select_cable to retrieve reactive power behavior from configs always --- edisgo/tools/tools.py | 59 +++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index 31553ae0..061e02ed 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -332,20 +332,18 @@ def select_cable( edisgo_obj: EDisGo, level: str, apparent_power: float, - length: float = 0, + component_type: str | None = None, + length: float = 0.0, max_voltage_diff: float | None = None, max_cables: int = 7, - power_factor: float | None = None, - component_type: str | None = "load", - reactive_power_mode: str = "inductive", ) -> tuple[pd.Series, int]: """ Selects suitable cable type and quantity based on apparent power and voltage deviation. The cable is selected to carry the given `apparent_power` and to ensure - acceptable voltage deviation over the cable length. No load factor is - considered. Overhead lines are not considered in choosing a suitable cable. + acceptable voltage deviation over the cable. + Overhead lines are not considered in choosing a suitable cable. Parameters ---------- @@ -355,49 +353,33 @@ def select_cable( 'lv'. apparent_power : float Apparent power the cable must carry in MVA. + component_type : str + Type of the component to be connected. Possible options are "generator", + "conventional_load", "charging_point", "heat_pump" or "storage_unit". + Only needed in case a cable length is given and thus the voltage difference over + the cable can be taken into account for selecting a suitable cable. In that case + it is used to obtain the default power factor and reactive power mode from the + configuration files in sections `reactive_power_factor` and + `reactive_power_mode`. + Default: None. length : float Length of the cable in km. Default: 0. max_voltage_diff : float - Maximum allowed voltage difference (p.u. of nominal voltage). + Maximum allowed voltage difference in p.u.. If None, it defaults to the value specified in the configuration file - under the `grid_connection` section for the respective voltage level. + under the `grid_connection` section for the respective voltage level + (lv_max_voltage_deviation for LV and mv_max_voltage_deviation for MV). Default: None. max_cables : int Maximum number of cables to consider. Default: 7. - power_factor : float - Power factor of the load. - component_type : str - Type of the component to be connected, used to obtain the default power factor - from the configuration. - possible options are 'gen', 'load', 'cp', 'hp' - Default: 'load'. - reactive_power_mode : str - Mode of the reactive power. Default: 'inductive' Returns ------- - tuple[pd.Series, int] - A tuple containing the selected cable type and the quantity needed. + tuple[:pandas:`pandas.Series`, int] + A tuple containing information on the selected cable type and the quantity + needed. + """ - if component_type is None: - component_type = level + "conventional_load" - - elif component_type in [ - "generator", - "conventional_load", - "charging_point", - "heat_pump", - "storage_unit", - ]: - component_type = level + "_" + component_type - else: - raise ValueError( - "Specified component type is not valid. " - "Must either be 'generator', 'conventional_load', 'charging_point', " - "'heat_pump' or 'storage_unit'." - ) - if power_factor is None: - power_factor = edisgo_obj.config["reactive_power_factor"][component_type] if level == "mv": cable_data = edisgo_obj.topology.equipment_data["mv_cables"] available_cables = cable_data[ @@ -417,6 +399,7 @@ def select_cable( raise ValueError( "Specified voltage level is not valid. Must either be 'mv' or 'lv'." ) + cable_count = 1 suitable_cables = available_cables[ calculate_apparent_power( From 2875aa736681f649e2d90e8993f5f3b70618551c Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 8 Aug 2024 14:01:11 +0200 Subject: [PATCH 26/29] Adapt tests to changes --- tests/tools/test_tools.py | 240 +++++++++----------------------------- 1 file changed, 54 insertions(+), 186 deletions(-) diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py index 193ed4c5..66216ca6 100644 --- a/tests/tools/test_tools.py +++ b/tests/tools/test_tools.py @@ -35,58 +35,49 @@ def test_calculate_voltage_diff_pu_per_line(self): correct_value_negative_sign = 0.06008053167215312 r_total = 0.412 x_total = 0.252 + + # test generator, float data = tools.calculate_voltage_diff_pu_per_line( s_max=50, r_total=r_total, x_total=x_total, v_nom=20, - reactive_power_mode="inductive", + q_sign=-1, power_factor=0.9, - component_type="gen", ) assert np.isclose(data, correct_value_positive_sign) + # test generator, array data = tools.calculate_voltage_diff_pu_per_line( s_max=np.array([50, 50]), r_total=np.array([r_total, r_total]), x_total=np.array([x_total, x_total]), v_nom=20, - reactive_power_mode="inductive", + q_sign=-1, power_factor=0.9, - component_type="gen", ) assert_allclose( data, np.array([correct_value_positive_sign, correct_value_positive_sign]), rtol=1e-5, ) + # test generator, float, higher voltage data = tools.calculate_voltage_diff_pu_per_line( s_max=50, r_total=r_total, x_total=x_total, v_nom=40, - reactive_power_mode="inductive", + q_sign=-1, power_factor=0.9, - component_type="gen", ) assert np.isclose(data, correct_value_positive_sign / 4) - data = tools.calculate_voltage_diff_pu_per_line( - s_max=100, - r_total=r_total, - x_total=x_total, - v_nom=20, - reactive_power_mode="inductive", - power_factor=0.9, - component_type="gen", - ) - assert np.isclose(data, correct_value_positive_sign * 2) + # test generator, array, larger cable data = tools.calculate_voltage_diff_pu_per_line( s_max=np.array([100, 100]), r_total=np.array([r_total, r_total]), x_total=np.array([x_total, x_total]), v_nom=np.array([20, 20]), - reactive_power_mode="inductive", + q_sign=-1, power_factor=0.9, - component_type="gen", ) assert_allclose( data, @@ -95,64 +86,26 @@ def test_calculate_voltage_diff_pu_per_line(self): ), rtol=1e-5, ) + # test generator, capacitive data = tools.calculate_voltage_diff_pu_per_line( s_max=100, r_total=r_total, x_total=x_total, v_nom=20, - reactive_power_mode="capacitive", - power_factor=0.9, - component_type="gen", - ) - assert np.isclose(data, correct_value_negative_sign * 2) - data = tools.calculate_voltage_diff_pu_per_line( - s_max=100, - r_total=r_total, - x_total=x_total, - v_nom=20, - reactive_power_mode="inductive", + q_sign=1, power_factor=0.9, - component_type="load", ) assert np.isclose(data, correct_value_negative_sign * 2) + # test load, capacitive data = tools.calculate_voltage_diff_pu_per_line( s_max=100, r_total=r_total, x_total=x_total, v_nom=20, - reactive_power_mode="capacitive", + q_sign=-1, power_factor=0.9, - component_type="load", ) assert np.isclose(data, correct_value_positive_sign * 2) - try: - data = tools.calculate_voltage_diff_pu_per_line( - s_max=100, - r_total=r_total, - x_total=x_total, - v_nom=20, - reactive_power_mode="inductive", - power_factor=0.9, - component_type="fail", - ) - except ValueError as e: - assert str(e) == "Component type not supported." - - Phi = np.pi / 6 - arctanphi = np.arctan(Phi) - R = r_total - X = arctanphi * R - v_nom = 0.4 - data = tools.calculate_voltage_diff_pu_per_line( - s_max=0.027, # 27 kW generator - r_total=R, - x_total=X, - v_nom=v_nom, - reactive_power_mode="inductive", - power_factor=0.95, - component_type="gen", - ) - assert np.isclose(data, 0.055577375215395) # test the examples from VDE-AR-N 4105 attachment D data = tools.calculate_voltage_diff_pu_per_line( @@ -160,9 +113,8 @@ def test_calculate_voltage_diff_pu_per_line(self): r_total=0.2001, x_total=0.1258, v_nom=0.4, - reactive_power_mode="inductive", + q_sign=-1, power_factor=1, - component_type="gen", ) assert np.isclose(data, 0.025, rtol=1e-2) @@ -171,65 +123,60 @@ def test_calculate_voltage_diff_pu_per_line(self): r_total=0.2001, x_total=0.1258, v_nom=0.4, - reactive_power_mode="inductive", + q_sign=-1, power_factor=0.9, - component_type="gen", ) assert np.isclose(data, 0.0173, rtol=1e-2) - def test_calculate_voltage_difference_pu_per_line_with_length(self): - correct_value_negative_sign = 0.52589253567891375 * 1e-2 - correct_value_positive_sign = 0.017241074643210865 - data = tools.calculate_voltage_difference_pu_per_line_with_length( - R_per_km=0.1, - L_per_km=0.350, + def test_calculate_voltage_diff_pu_per_line_from_type(self): + correct_value_negative_sign = 0.4916578234319946 * 1e-2 + correct_value_positive_sign = 0.017583421765680056 + data = tools.calculate_voltage_diff_pu_per_line_from_type( + edisgo_obj=self.edisgo, + cable_names="NA2XS(FL)2Y 3x1x300 RM/25", length=1, num_parallel=1, v_nom=20, s_max=50, - power_factor=0.9, - reactive_power_mode="inductive", - component_type="gen", + component_type="generator", ) assert np.isclose(data, correct_value_negative_sign) - data = tools.calculate_voltage_difference_pu_per_line_with_length( - R_per_km=np.array([0.1, 0.1]), - L_per_km=np.array([0.35, 0.35]), + data = tools.calculate_voltage_diff_pu_per_line_from_type( + edisgo_obj=self.edisgo, + cable_names=np.array( + ["NA2XS(FL)2Y 3x1x300 RM/25", "NA2XS(FL)2Y 3x1x300 RM/25"] + ), length=1, num_parallel=1, v_nom=20, s_max=50, - power_factor=0.9, - reactive_power_mode="inductive", - component_type="gen", + component_type="generator", ) assert_allclose( data, np.array([correct_value_negative_sign, correct_value_negative_sign]), rtol=1e-5, ) - data = tools.calculate_voltage_difference_pu_per_line_with_length( - R_per_km=0.1, - L_per_km=0.35, + data = tools.calculate_voltage_diff_pu_per_line_from_type( + edisgo_obj=self.edisgo, + cable_names="NA2XS(FL)2Y 3x1x300 RM/25", length=2, num_parallel=1, v_nom=20, s_max=50, - power_factor=0.9, - reactive_power_mode="inductive", - component_type="gen", + component_type="generator", ) assert np.isclose(data, 2 * correct_value_negative_sign) - data = tools.calculate_voltage_difference_pu_per_line_with_length( - R_per_km=np.array([0.1, 0.1]), - L_per_km=np.array([0.35, 0.35]), + data = tools.calculate_voltage_diff_pu_per_line_from_type( + edisgo_obj=self.edisgo, + cable_names=np.array( + ["NA2XS(FL)2Y 3x1x300 RM/25", "NA2XS(FL)2Y 3x1x300 RM/25"] + ), length=2, num_parallel=1, v_nom=20, s_max=50, - power_factor=0.9, - reactive_power_mode="inductive", - component_type="gen", + component_type="generator", ) assert_allclose( data, @@ -239,55 +186,25 @@ def test_calculate_voltage_difference_pu_per_line_with_length(self): rtol=1e-5, ) - data = tools.calculate_voltage_difference_pu_per_line_with_length( - R_per_km=0.1, - L_per_km=0.35, + data = tools.calculate_voltage_diff_pu_per_line_from_type( + edisgo_obj=self.edisgo, + cable_names="NA2XS(FL)2Y 3x1x300 RM/25", length=1, num_parallel=2, v_nom=20, s_max=50, - power_factor=0.9, - reactive_power_mode="inductive", - component_type="gen", + component_type="generator", ) assert np.isclose(data, correct_value_negative_sign / 2) - data = tools.calculate_voltage_difference_pu_per_line_with_length( - R_per_km=0.1, - L_per_km=0.35, + data = tools.calculate_voltage_diff_pu_per_line_from_type( + edisgo_obj=self.edisgo, + cable_names="NA2XS(FL)2Y 3x1x300 RM/25", length=1, num_parallel=2, v_nom=20, s_max=50, - power_factor=0.9, - reactive_power_mode="inductive", - component_type="load", - ) - assert np.isclose(data, correct_value_positive_sign / 2) - - data = tools.calculate_voltage_difference_pu_per_line_with_length( - R_per_km=0.1, - L_per_km=0.35, - length=1, - num_parallel=2, - v_nom=20, - s_max=50, - power_factor=0.9, - reactive_power_mode="capacitive", - component_type="load", - ) - assert np.isclose(data, correct_value_negative_sign / 2) - - data = tools.calculate_voltage_difference_pu_per_line_with_length( - R_per_km=0.1, - L_per_km=0.35, - length=1, - num_parallel=2, - v_nom=20, - s_max=50, - power_factor=0.9, - reactive_power_mode="capacitive", - component_type="gen", + component_type="conventional_load", ) assert np.isclose(data, correct_value_positive_sign / 2) @@ -363,12 +280,6 @@ def test_select_cable(self): self.edisgo, "mv", 5.1, - length=0, - max_voltage_diff=None, - max_cables=7, - power_factor=None, - component_type=None, - reactive_power_mode="inductive", ) assert cable_data.name == "NA2XS2Y 3x1x150 RE/25" assert num_parallel_cables == 1 @@ -377,12 +288,6 @@ def test_select_cable(self): self.edisgo, "mv", 40, - length=0, - max_voltage_diff=None, - max_cables=7, - power_factor=None, - component_type="load", - reactive_power_mode="inductive", ) assert cable_data.name == "NA2XS(FL)2Y 3x1x500 RM/35" assert num_parallel_cables == 2 @@ -391,12 +296,6 @@ def test_select_cable(self): self.edisgo, "lv", 0.18, - length=0, - max_voltage_diff=None, - max_cables=7, - power_factor=None, - component_type="load", - reactive_power_mode="inductive", ) assert cable_data.name == "NAYY 4x1x150" assert num_parallel_cables == 1 @@ -407,11 +306,7 @@ def test_select_cable(self): "mv", 5.1, length=2, - max_voltage_diff=None, - max_cables=7, - power_factor=None, - component_type="load", - reactive_power_mode="inductive", + component_type="conventional_load", ) assert cable_data.name == "NA2XS2Y 3x1x150 RE/25" assert num_parallel_cables == 1 @@ -421,11 +316,7 @@ def test_select_cable(self): "mv", 40, length=1, - max_voltage_diff=None, - max_cables=7, - power_factor=None, - component_type="load", - reactive_power_mode="inductive", + component_type="conventional_load", ) assert cable_data.name == "NA2XS(FL)2Y 3x1x500 RM/35" assert num_parallel_cables == 2 @@ -435,11 +326,7 @@ def test_select_cable(self): "lv", 0.18, length=1, - max_voltage_diff=None, - max_cables=7, - power_factor=None, - component_type="load", - reactive_power_mode="inductive", + component_type="conventional_load", ) assert cable_data.name == "NAYY 4x1x300" assert num_parallel_cables == 5 @@ -451,23 +338,7 @@ def test_select_cable(self): length=1, max_voltage_diff=0.01, max_cables=100, - power_factor=1, - component_type="load", - reactive_power_mode="inductive", - ) - assert cable_data.name == "NAYY 4x1x300" - assert num_parallel_cables == 12 - - cable_data, num_parallel_cables = tools.select_cable( - self.edisgo, - "lv", - 0.18, - length=1, - max_voltage_diff=0.01, - max_cables=100, - power_factor=None, - component_type="load", - reactive_power_mode="inductive", + component_type="conventional_load", ) assert cable_data.name == "NAYY 4x1x300" assert num_parallel_cables == 14 @@ -479,29 +350,26 @@ def test_select_cable(self): length=1, max_voltage_diff=0.01, max_cables=100, - power_factor=None, - component_type="gen", - reactive_power_mode="inductive", + component_type="generator", ) assert cable_data.name == "NAYY 4x1x300" assert num_parallel_cables == 8 try: - cable_data, num_parallel_cables = tools.select_cable( + tools.select_cable( self.edisgo, "lv", 0.18, length=1, max_voltage_diff=0.01, max_cables=100, - power_factor=None, component_type="fail", - reactive_power_mode="inductive", ) except ValueError as e: assert ( str(e) == "Specified component type is not valid. " - "Must either be 'gen', 'load', 'cp' or 'hp'." + "Must either be 'generator', 'conventional_load', 'charging_point', " + "'heat_pump' or 'storage_unit'." ) def test_get_downstream_buses(self): From ec36c0b71af0c0b8ad9d1232fd5461faf4bcddb2 Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 8 Aug 2024 14:53:19 +0200 Subject: [PATCH 27/29] Consider voltage drop when selecting suitable cable in connect functions --- edisgo/network/topology.py | 63 ++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py index 2462314f..d7694476 100755 --- a/edisgo/network/topology.py +++ b/edisgo/network/topology.py @@ -1928,7 +1928,13 @@ def connect_to_mv(self, edisgo_object, comp_data, comp_type="generator"): # avoid very short lines by limiting line length to at least 1m line_length = max(line_length, 0.001) - line_type, num_parallel = select_cable(edisgo_object, "mv", power) + line_type, num_parallel = select_cable( + edisgo_obj=edisgo_object, + level="mv", + apparent_power=power, + length=line_length, + component_type=comp_type, + ) line_name = self.add_line( bus0=self.mv_grid.station.index[0], @@ -1975,13 +1981,12 @@ def connect_to_mv(self, edisgo_object, comp_data, comp_type="generator"): for dist_min_obj in conn_objects_min_stack: # do not allow connection to virtual busses if "virtual" not in dist_min_obj["repr"]: - line_type, num_parallel = select_cable(edisgo_object, "mv", power) target_obj_result = self._connect_mv_bus_to_target_object( edisgo_object=edisgo_object, bus=self.buses_df.loc[bus, :], target_obj=dist_min_obj, - line_type=line_type.name, - number_parallel_lines=num_parallel, + comp_type=comp_type, + power=power, ) if target_obj_result is not None: @@ -2448,7 +2453,12 @@ def connect_to_lv_based_on_geolocation( return comp_name def _connect_mv_bus_to_target_object( - self, edisgo_object, bus, target_obj, line_type, number_parallel_lines + self, + edisgo_object, + bus, + target_obj, + comp_type, + power, ): """ Connects given MV bus to given target object (MV line or bus). @@ -2477,11 +2487,12 @@ def _connect_mv_bus_to_target_object( * shp : :shapely:`Shapely Point object` or \ :shapely:`Shapely Line object` Geometry of line or bus to connect to. - - line_type : str - Line type to use to connect new component with. - number_parallel_lines : int - Number of parallel lines to connect new component with. + comp_type : str + Type of added component. Can be 'generator', 'charging_point', 'heat_pump' + or 'storage_unit'. + Default: 'generator'. + power : float + Nominal power of the new component to be connected. Returns ------- @@ -2598,6 +2609,13 @@ def _connect_mv_bus_to_target_object( "branch_detour_factor" ], ) + line_type, num_parallel = select_cable( + edisgo_obj=edisgo_object, + level="mv", + apparent_power=power, + length=line_length, + component_type=comp_type, + ) # avoid very short lines by limiting line length to at least 1m if line_length < 0.001: line_length = 0.001 @@ -2606,8 +2624,8 @@ def _connect_mv_bus_to_target_object( bus1=bus.name, length=line_length, kind="cable", - type_info=line_type, - num_parallel=number_parallel_lines, + type_info=line_type.name, + num_parallel=num_parallel, ) # add line to equipment changes edisgo_object.results._add_line_to_equipment_changes( @@ -2624,7 +2642,7 @@ def _connect_mv_bus_to_target_object( # bus is the nearest connection point else: - # add new branch for satellite (station to station) + # add new line between new bus and closest bus line_length = geo.calc_geo_dist_vincenty( grid_topology=self, bus_source=bus.name, @@ -2633,6 +2651,13 @@ def _connect_mv_bus_to_target_object( "branch_detour_factor" ], ) + line_type, num_parallel = select_cable( + edisgo_obj=edisgo_object, + level="mv", + apparent_power=power, + length=line_length, + component_type=comp_type, + ) # avoid very short lines by limiting line length to at least 1m if line_length < 0.001: line_length = 0.001 @@ -2642,8 +2667,8 @@ def _connect_mv_bus_to_target_object( bus1=bus.name, length=line_length, kind="cable", - type_info=line_type, - num_parallel=number_parallel_lines, + type_info=line_type.name, + num_parallel=num_parallel, ) # add line to equipment changes @@ -2721,7 +2746,13 @@ def _connect_to_lv_bus(self, edisgo_object, target_bus, comp_type, comp_data): line_length = max(line_length, 0.001) # get suitable line type - line_type, num_parallel = select_cable(edisgo_object, "lv", comp_data["p"]) + line_type, num_parallel = select_cable( + edisgo_obj=edisgo_object, + level="lv", + apparent_power=comp_data["p"], + component_type=comp_type, + length=line_length, + ) line_name = self.add_line( bus0=target_bus, bus1=b, From a49d90746de549888d97d9b824cac360c4e75c7f Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 8 Aug 2024 14:53:45 +0200 Subject: [PATCH 28/29] Adapt test that is now failing because voltage drop is too high to find suitable cable --- tests/network/test_topology.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/network/test_topology.py b/tests/network/test_topology.py index 0baf02f3..e977a5bd 100644 --- a/tests/network/test_topology.py +++ b/tests/network/test_topology.py @@ -1720,7 +1720,7 @@ def test_connect_to_lv(self): loads_before = self.edisgo.topology.loads_df test_hp = { - "p_set": 0.3, + "p_set": 0.1, "geom": geom, "voltage_level": 6, "mvlv_subst_id": 6, @@ -1751,7 +1751,7 @@ def test_connect_to_lv(self): new_line_df.loc[new_line_df.index[0], ["bus0", "bus1"]] ) # check new heat pump - assert self.edisgo.topology.loads_df.at[comp_name, "p_set"] == 0.3 + assert self.edisgo.topology.loads_df.at[comp_name, "p_set"] == 0.1 # ############# storage unit ################# # test existing substation ID (voltage level 7) From 455272905e96563a821ef0ac629b80160a1da89f Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 8 Aug 2024 15:44:42 +0200 Subject: [PATCH 29/29] Add changes to whatsnew --- doc/whatsnew/v0-3-0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/whatsnew/v0-3-0.rst b/doc/whatsnew/v0-3-0.rst index 7cb37554..7cf4110a 100644 --- a/doc/whatsnew/v0-3-0.rst +++ b/doc/whatsnew/v0-3-0.rst @@ -27,3 +27,4 @@ Changes * Added a new reinforcement method that separate lv grids when the overloading is very high `#380 `_ * Move function to assign feeder to Topology class and add methods to the Grid class to get information on the feeders `#360 `_ * Added a storage operation strategy where the storage is charged when PV feed-in is higher than electricity demand of the household and discharged when electricity demand exceeds PV generation `#386 `_ +* Added an estimation of the voltage deviation over a cable when selecting a suitable cable to connect a new component `#411 `_