Repository: openego/eDisGo (language: python)

Issue:
[BUG] Voltage drop should be considered when selecting cable to connect new components
In [select_cable function](https://github.com/openego/eDisGo/blob/dev/edisgo/tools/tools.py#L196) only the apparent power over a line is considered. However, voltage drop should be considered as well, as small, long cables can lead to infeasibilities in the power flow. Voltage drop could be estimated as done in [ding0](https://github.com/openego/ding0/blob/656f85b6f2a253ed747d2a3dea74e0b1c6df6044/ding0/flexopt/check_tech_constraints.py#L689). It needs to be checked if this is applicable for the MV as well.



Discussion / hints:

Fix patch:
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 <https://github.com/openego/eDisGo/pull/380>`_
 * Move function to assign feeder to Topology class and add methods to the Grid class to get information on the feeders `#360 <https://github.com/openego/eDisGo/pull/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 <https://github.com/openego/eDisGo/pull/386>`_
+* Added an estimation of the voltage deviation over a cable when selecting a suitable cable to connect a new component `#411 <https://github.com/openego/eDisGo/pull/411>`_
diff --git a/edisgo/config/config_grid_default.cfg b/edisgo/config/config_grid_default.cfg
index f48c68d8..1cd82925 100644
--- a/edisgo/config/config_grid_default.cfg
+++ b/edisgo/config/config_grid_default.cfg
@@ -50,6 +50,11 @@ 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 4110 (VDE-AR-N 4110) Anwendungsregel: 2023-09, 5.3.2 Zulässige Spannungsänderung
+mv_max_voltage_deviation = 0.02
+
 [disconnecting_point]
 
 # Positioning of disconnecting points: Can be position at location of most
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..07183a7d 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.
@@ -116,15 +100,15 @@ def _fixed_cosphi_default_power_factor(comp_df, component_type, configs):
     -----------
     comp_df : :pandas:`pandas.DataFrame<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`).
         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..541e01ff 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
     -------
@@ -2061,18 +2062,14 @@ 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)
-    ]
-    if kind in ["gen", "storage"]:
+    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
         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/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<points>` or \
                 :shapely:`Shapely Line object<linestrings>`
                     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,
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 d05fe1b8..061e02ed 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
 
@@ -193,13 +193,157 @@ 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_diff_pu_per_line(
+    s_max: float | np.ndarray,
+    r_total: float | np.ndarray,
+    x_total: float | np.ndarray,
+    v_nom: float | np.ndarray,
+    q_sign: int,
+    power_factor: float,
+) -> float | np.ndarray:
     """
-    Selects suitable cable type and quantity using given apparent power.
+    Calculate the voltage difference across a line in p.u..
 
-    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.
+    Parameters
+    ----------
+    s_max : float or array-like
+        Apparent power the cable must carry in MVA.
+    r_total : float or array-like
+        Total resistance of the line in Ohms.
+    x_total : float or array-like
+        Total reactance of the line in Ohms.
+    v_nom : float or array-like
+        Nominal voltage of the line in kV.
+    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<Series>` or float
+        Ratio of real to apparent power.
+
+    Returns
+    -------
+    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.
+
+    """
+    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 + q_sign * x_total * sin_phi
+    )
+    return voltage_diff  # in pu
+
+
+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,
+    component_type: str,
+) -> float | np.ndarray:
+    """
+    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
+    ----------
+    edisgo_obj : :class:`~.EDisGo`
+    cable_names : str or array-like
+        Resistance per kilometer of the cable in ohm/km.
+    length : float
+        Length of the cable in km.
+    num_parallel : int
+        Number of parallel cables.
+    v_nom : int
+        Nominal voltage of the cable(s) in kV.
+    s_max : float
+        Apparent power the cable must carry in MVA.
+    component_type : str, optional
+        Type of the component to be connected, used to obtain the default reactive power
+        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 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 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
+    return calculate_voltage_diff_pu_per_line(
+        s_max,
+        r_total,
+        x_total,
+        v_nom,
+        q_sign,
+        power_factor,
+    )
+
+
+def select_cable(
+    edisgo_obj: EDisGo,
+    level: str,
+    apparent_power: float,
+    component_type: str | None = None,
+    length: float = 0.0,
+    max_voltage_diff: float | None = None,
+    max_cables: int = 7,
+) -> 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.
+    Overhead lines are not considered in choosing a suitable cable.
 
     Parameters
     ----------
@@ -209,49 +353,96 @@ def select_cable(edisgo_obj, level, apparent_power):
         '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 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
+        (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.
 
     Returns
     -------
-    :pandas:`pandas.Series<Series>`
-        Series with attributes of selected cable as in equipment data and
-        cable type as series name.
-    int
-        Number of necessary parallel cables.
+    tuple[:pandas:`pandas.Series<Series>`, int]
+        A tuple containing information on the selected cable type and the quantity
+        needed.
 
     """
-
-    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_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_diff:
+            max_voltage_diff = edisgo_obj.config["grid_connection"][
+                "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
         )
         > apparent_power
     ]
+    if length != 0:
+        suitable_cables = suitable_cables[
+            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"].values[0],
+                s_max=apparent_power,
+                component_type=component_type,
+            )
+            < max_voltage_diff
+        ]
 
     # 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[
+                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"].values[0],
+                    s_max=apparent_power,
+                    component_type=component_type,
+                )
+                < max_voltage_diff
+            ]
     if suitable_cables.empty:
         raise exceptions.MaximumIterationError(
             "Could not find a suitable cable for apparent power of "


Test patch:
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
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)
diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py
index 40c34a63..66216ca6 100644
--- a/tests/tools/test_tools.py
+++ b/tests/tools/test_tools.py
@@ -30,6 +30,184 @@ 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_pu_per_line(self):
+        correct_value_positive_sign = 0.03261946832784687
+        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,
+            q_sign=-1,
+            power_factor=0.9,
+        )
+        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,
+            q_sign=-1,
+            power_factor=0.9,
+        )
+        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,
+            q_sign=-1,
+            power_factor=0.9,
+        )
+        assert np.isclose(data, correct_value_positive_sign / 4)
+        # 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]),
+            q_sign=-1,
+            power_factor=0.9,
+        )
+        assert_allclose(
+            data,
+            np.array(
+                [correct_value_positive_sign * 2, correct_value_positive_sign * 2]
+            ),
+            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,
+            q_sign=1,
+            power_factor=0.9,
+        )
+        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,
+            q_sign=-1,
+            power_factor=0.9,
+        )
+        assert np.isclose(data, correct_value_positive_sign * 2)
+
+        # 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,
+            q_sign=-1,
+            power_factor=1,
+        )
+        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,
+            q_sign=-1,
+            power_factor=0.9,
+        )
+        assert np.isclose(data, 0.0173, rtol=1e-2)
+
+    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,
+            component_type="generator",
+        )
+        assert np.isclose(data, correct_value_negative_sign)
+        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,
+            component_type="generator",
+        )
+        assert_allclose(
+            data,
+            np.array([correct_value_negative_sign, correct_value_negative_sign]),
+            rtol=1e-5,
+        )
+        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,
+            component_type="generator",
+        )
+        assert np.isclose(data, 2 * correct_value_negative_sign)
+        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,
+            component_type="generator",
+        )
+        assert_allclose(
+            data,
+            np.array(
+                [2 * correct_value_negative_sign, 2 * correct_value_negative_sign]
+            ),
+            rtol=1e-5,
+        )
+
+        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,
+            component_type="generator",
+        )
+        assert np.isclose(data, correct_value_negative_sign / 2)
+
+        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,
+            component_type="conventional_load",
+        )
+        assert np.isclose(data, correct_value_positive_sign / 2)
+
     def test_calculate_line_resistance(self):
         # test single line
         data = tools.calculate_line_resistance(2, 3, 1)
@@ -97,18 +275,103 @@ def test_drop_duplicated_columns(self):
         assert (check_df.loc[:, "a"] == [4, 5, 6]).all()
 
     def test_select_cable(self):
-        cable_data, num_parallel_cables = tools.select_cable(self.edisgo, "mv", 5.1)
+        # 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
 
-        cable_data, num_parallel_cables = tools.select_cable(self.edisgo, "mv", 40)
+        cable_data, num_parallel_cables = tools.select_cable(
+            self.edisgo,
+            "mv",
+            40,
+        )
         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,
+        )
         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,
+            length=2,
+            component_type="conventional_load",
+        )
+        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,
+            length=1,
+            component_type="conventional_load",
+        )
+        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,
+            length=1,
+            component_type="conventional_load",
+        )
+        assert cable_data.name == "NAYY 4x1x300"
+        assert num_parallel_cables == 5
+
+        cable_data, num_parallel_cables = tools.select_cable(
+            self.edisgo,
+            "lv",
+            0.18,
+            length=1,
+            max_voltage_diff=0.01,
+            max_cables=100,
+            component_type="conventional_load",
+        )
+        assert cable_data.name == "NAYY 4x1x300"
+        assert num_parallel_cables == 14
+
+        cable_data, num_parallel_cables = tools.select_cable(
+            self.edisgo,
+            "lv",
+            0.18,
+            length=1,
+            max_voltage_diff=0.01,
+            max_cables=100,
+            component_type="generator",
+        )
+        assert cable_data.name == "NAYY 4x1x300"
+        assert num_parallel_cables == 8
+
+        try:
+            tools.select_cable(
+                self.edisgo,
+                "lv",
+                0.18,
+                length=1,
+                max_voltage_diff=0.01,
+                max_cables=100,
+                component_type="fail",
+            )
+        except ValueError as e:
+            assert (
+                str(e) == "Specified component type is not valid. "
+                "Must either be 'generator', 'conventional_load', 'charging_point', "
+                "'heat_pump' or 'storage_unit'."
+            )
+
     def test_get_downstream_buses(self):
         # ######## test with LV bus ########
         buses_downstream = tools.get_downstream_buses(