From db6af070ae66ff006e81ba3860481fc6d8889aa1 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Tue, 6 Aug 2024 11:28:09 +0200
Subject: [PATCH 01/50] home charging points residential load

---
 edisgo/network/topology.py | 61 ++++++++++++++++++++++++++------------
 1 file changed, 42 insertions(+), 19 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 2462314f0..6795729b8 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -1997,6 +1997,20 @@ def connect_to_mv(self, edisgo_object, comp_data, comp_type="generator"):
 
         return comp_name
 
+    def get_component_at_bus(self, lv_bus, comp_type):
+        if comp_type == "generator":
+            comps_at_bus = self.generators_df[self.generators_df.bus == lv_bus]
+        elif comp_type == "charging_point":
+            comps_at_bus = self.charging_points_df[
+                self.charging_points_df.bus == lv_bus
+            ]
+        elif comp_type == "heat_pump":
+            hp_df = self.loads_df[self.loads_df.type == "heat_pump"]
+            comps_at_bus = hp_df[hp_df.bus == lv_bus]
+        else:
+            comps_at_bus = self.storage_units_df[self.storage_units_df.bus == lv_bus]
+        return comps_at_bus
+
     def connect_to_lv(
         self,
         edisgo_object,
@@ -2155,7 +2169,7 @@ def _choose_random_substation_id():
             # if substation ID (= LV grid ID) is given but it does not match an
             # existing LV grid ID a random LV grid to connect in is chosen
             else:
-                # ToDo
+                # TODO:
                 # lv_grid = _choose_random_substation_id()
                 # logger.warning(
                 #     "Given mvlv_subst_id does not exist, wherefore a random "
@@ -2291,20 +2305,7 @@ def _choose_random_substation_id():
                 lv_bus = lv_buses_rnd.pop()
 
                 # determine number of components of the same type at LV bus
-                if comp_type == "generator":
-                    comps_at_bus = self.generators_df[self.generators_df.bus == lv_bus]
-                elif comp_type == "charging_point":
-                    comps_at_bus = self.charging_points_df[
-                        self.charging_points_df.bus == lv_bus
-                    ]
-                elif comp_type == "heat_pump":
-                    hp_df = self.loads_df[self.loads_df.type == "heat_pump"]
-                    comps_at_bus = hp_df[hp_df.bus == lv_bus]
-                else:
-                    comps_at_bus = self.storage_units_df[
-                        self.storage_units_df.bus == lv_bus
-                    ]
-
+                comps_at_bus = self.get_component_at_bus(lv_bus, comp_type)
                 # ToDo: Increase number of generators/charging points
                 #  allowed at one load in case all loads already have one
                 #  generator/charging point
@@ -2331,10 +2332,11 @@ def _choose_random_substation_id():
     def connect_to_lv_based_on_geolocation(
         self,
         edisgo_object,
-        comp_data,
-        comp_type,
-        max_distance_from_target_bus=0.02,
-    ):
+        comp_data: dict,
+        comp_type: str,
+        max_distance_from_target_bus: float = 0.02,
+        allowed_number_of_comp_per_bus=2,
+    ) -> str:
         """
         Add and connect new component to LV grid topology based on its geolocation.
 
@@ -2381,6 +2383,8 @@ def connect_to_lv_based_on_geolocation(
             before a new bus is created. If the new component is closer to the target
             bus than the maximum specified distance, it is directly connected to that
             target bus. Default: 0.1.
+        allowed_number_of_comp_per_bus : int
+            Specifies, how many components of the same type are at most allowed to be
 
         Returns
         -------
@@ -2422,11 +2426,30 @@ def connect_to_lv_based_on_geolocation(
         # find the nearest substation or LV bus
         if voltage_level == 6:
             substations = self.buses_df.loc[self.transformers_df.bus1.unique()]
+            if comp_type == "charging_point":
+                if comp_data["sector"] == "home":
+                    lv_loads = self.loads_df[self.loads_df.sector == "residential"]
+                    lv_buses = lv_loads[lv_loads.index.isin(lv_loads.bus)]
             target_bus, target_bus_distance = geo.find_nearest_bus(
                 geolocation, substations
             )
         else:
             lv_buses = self.buses_df.drop(self.mv_grid.buses_df.index)
+            if comp_type == "charging_point":
+                if comp_data["sector"] == "home":
+                    lv_loads = self.loads_df[self.loads_df.sector == "residential"]
+                    lv_buses = lv_buses[lv_buses.index.isin(lv_loads.bus)]
+                elif comp_data["sector"] == "work":
+                    lv_loads = self.loads_df[self.loads_df.sector == "cts"]
+                    lv_buses = lv_buses[lv_buses.index.isin(lv_loads.bus)]
+
+            lv_buses.sort_index()
+            lv_buses["components_at_bus"] = lv_buses.index.map(
+                lambda bus: len(self.get_component_at_bus(bus, comp_type))
+            )
+            lv_buses = lv_buses[
+                lv_buses.components_at_bus <= allowed_number_of_comp_per_bus
+            ]
             target_bus, target_bus_distance = geo.find_nearest_bus(
                 geolocation, lv_buses
             )

From 01ba22abc3fe7796349ef5aac012a08c62f6af4c Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Tue, 6 Aug 2024 11:29:41 +0200
Subject: [PATCH 02/50] add test_connect_to_lv_based_on_geolocation

---
 tests/network/test_topology.py | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/tests/network/test_topology.py b/tests/network/test_topology.py
index 0baf02f34..8c4f0b198 100644
--- a/tests/network/test_topology.py
+++ b/tests/network/test_topology.py
@@ -948,7 +948,11 @@ class TestTopologyWithEdisgoObject:
     @pytest.yield_fixture(autouse=True)
     def setup_class(self):
         self.edisgo = EDisGo(ding0_grid=pytest.ding0_test_network_path)
+        self.edisgo3 = EDisGo(
+            ding0_grid=pytest.ding0_test_network_3_path, legacy_ding0_grids=False
+        )
         self.edisgo.set_time_series_worst_case_analysis()
+        self.edisgo3.set_time_series_worst_case_analysis()
 
     def test_to_geopandas(self):
         geopandas_container = self.edisgo.topology.to_geopandas()
@@ -1787,6 +1791,32 @@ def test_connect_to_lv(self):
         # check new storage
         assert self.edisgo.topology.storage_units_df.at[comp_name, "p_nom"] == 0.03
 
+    def test_connect_to_lv_based_on_geolocation(self):
+        # ######### ChargingPoint #############
+        # create directories to save ding0 example grid into
+        # lines_before = self.edisgo3.topology.lines_df
+        # buses_before = self.edisgo3.topology.buses_df
+        # generators_before = self.edisgo3.topology.generators_df
+
+        # add CHargingPoint
+        x = self.edisgo3.topology.buses_df.at["Busbar_mvgd_33535_MV", "x"]
+        y = self.edisgo3.topology.buses_df.at["Busbar_mvgd_33535_MV", "y"]
+        geom = Point((x, y))
+        test_cp = {
+            "p_set": 0.01,
+            "geom": geom,
+            "sector": "home",
+            "voltage_level": 7,
+            "mvlv_subst_id": 3.0,
+        }
+        neu = self.edisgo3.topology.connect_to_lv_based_on_geolocation(
+            edisgo_object=self.edisgo3,
+            comp_data=test_cp,
+            comp_type="charging_point",
+            max_distance_from_target_bus=0.1,
+        )
+        assert neu == "Charging_Point_LVGrid_1170540000_home_1"
+
     def test_check_integrity(self, caplog):
         """Test of validation of grids."""
         comps_dict = {

From c3ff9f927eb643757dd2a0a0d10ee7799bb05667 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Thu, 8 Aug 2024 09:05:16 +0200
Subject: [PATCH 03/50] update connect_to_lv_based_on_geolocation function

---
 edisgo/network/topology.py | 154 +++++++++++++++++++++++++------------
 1 file changed, 103 insertions(+), 51 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 6795729b8..d53615aae 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2334,8 +2334,10 @@ def connect_to_lv_based_on_geolocation(
         edisgo_object,
         comp_data: dict,
         comp_type: str,
-        max_distance_from_target_bus: float = 0.02,
-        allowed_number_of_comp_per_bus=2,
+        max_distance_from_target_bus: float = 0.1,
+        allowed_number_of_comp_per_bus: int = 2,
+        allow_mv_connection: bool = False,
+        factor_mv_connection: float = 3.0,
     ) -> str:
         """
         Add and connect new component to LV grid topology based on its geolocation.
@@ -2385,6 +2387,14 @@ def connect_to_lv_based_on_geolocation(
             target bus. Default: 0.1.
         allowed_number_of_comp_per_bus : int
             Specifies, how many components of the same type are at most allowed to be
+            placed at the same bus. Default: 2.
+        allow_mv_connection : bool
+            Specifies whether the component can be connected to the MV grid in case
+            the closest LV bus is too far away. Default: False.
+        factor_mv_connection : float
+            Specifies the factor by which the distance to the closest MV bus is
+            multiplied to decide whether the component is connected to the MV grid
+            instead of the LV grid. Default: 3.0.
 
         Returns
         -------
@@ -2394,81 +2404,123 @@ def connect_to_lv_based_on_geolocation(
             :attr:`~.network.topology.Topology.loads_df` or
             :attr:`~.network.topology.Topology.storage_units_df`, depending on component
             type.
-
         """
 
-        if "p" not in comp_data.keys():
-            comp_data["p"] = (
-                comp_data["p_set"]
-                if "p_set" in comp_data.keys()
-                else comp_data["p_nom"]
-            )
+        def get_add_func(comp_type):
+            if comp_type == "generator":
+                return self.add_generator
+            elif comp_type in ["charging_point", "heat_pump"]:
+                comp_data["type"] = comp_type
+                return self.add_load
+            elif comp_type == "storage_unit":
+                return self.add_storage_unit
+            else:
+                logger.error(f"Component type {comp_type} is not a valid option.")
+                return None
+
+        # TODO:
+        # - Calculate the distance for each bus using Geopy
+        # and store it in a separate column.
+        # - Sort the buses based on the distance.
+        # - Delete all buses outside the boundary.
+        # - Take the first bus that has 0 components.
+        # - If a bus with fewer components is found, take that bus.
+        # - If no suitable bus is found, search for the nearest
+        # MV bus and connect there.
+        # - If no MV bus is found, connect to the MV station.
+        # - If that is not possible, search outside the radius
+        # and connect to a new bus.
+        # - Perform the entire process throughout the network,
+        # the LV grid is not relevant.
+
+        if "p" not in comp_data:
+            comp_data["p"] = comp_data.get("p_set", comp_data.get("p_nom"))
 
         voltage_level = comp_data.pop("voltage_level")
         if voltage_level not in [6, 7]:
             raise ValueError(
-                f"Voltage level must either be 6 or 7 but given voltage level "
-                f"is {voltage_level}."
+                f"Voltage level must either be 6 or 7 but given"
+                f" voltage level is {voltage_level}."
             )
-        geolocation = comp_data.get("geom")
 
-        if comp_type == "generator":
-            add_func = self.add_generator
-        elif comp_type == "charging_point" or comp_type == "heat_pump":
-            add_func = self.add_load
-            comp_data["type"] = comp_type
-        elif comp_type == "storage_unit":
-            add_func = self.add_storage_unit
-        else:
-            logger.error(f"Component type {comp_type} is not a valid option.")
+        geolocation = comp_data.get("geom")
+        add_func = get_add_func(comp_type)
+        if not add_func:
             return
 
-        # find the nearest substation or LV bus
         if voltage_level == 6:
             substations = self.buses_df.loc[self.transformers_df.bus1.unique()]
-            if comp_type == "charging_point":
-                if comp_data["sector"] == "home":
-                    lv_loads = self.loads_df[self.loads_df.sector == "residential"]
-                    lv_buses = lv_loads[lv_loads.index.isin(lv_loads.bus)]
             target_bus, target_bus_distance = geo.find_nearest_bus(
                 geolocation, substations
             )
         else:
-            lv_buses = self.buses_df.drop(self.mv_grid.buses_df.index)
+            all_buses = self.buses_df
+            # Count components of the same type at each bus
+            comp_counts = (
+                self.loads_df[self.loads_df.type == comp_type].groupby("bus").size()
+            )
+            all_buses = all_buses.join(
+                comp_counts.rename("comp_count"), on="Bus"
+            ).fillna({"comp_count": 0})
+
             if comp_type == "charging_point":
-                if comp_data["sector"] == "home":
+                sector = comp_data.get("sector")
+                if sector == "home":
                     lv_loads = self.loads_df[self.loads_df.sector == "residential"]
-                    lv_buses = lv_buses[lv_buses.index.isin(lv_loads.bus)]
-                elif comp_data["sector"] == "work":
+                elif sector == "work":
                     lv_loads = self.loads_df[self.loads_df.sector == "cts"]
-                    lv_buses = lv_buses[lv_buses.index.isin(lv_loads.bus)]
-
-            lv_buses.sort_index()
-            lv_buses["components_at_bus"] = lv_buses.index.map(
-                lambda bus: len(self.get_component_at_bus(bus, comp_type))
-            )
-            lv_buses = lv_buses[
-                lv_buses.components_at_bus <= allowed_number_of_comp_per_bus
+                else:
+                    lv_loads = self.loads_df
+                lv_buses = all_buses[all_buses.index.isin(lv_loads.bus)]
+            else:
+                lv_buses = all_buses
+
+            # # Count components of the same type at each bus
+            # comp_counts = self.loads_df[self.loads_df.type == comp_type].
+            # groupby('bus').size()
+            # all_buses = all_buses.join(comp_counts.rename('comp_count'),
+            # on='Bus').fillna({'comp_count': 0})
+            all_buses = all_buses[
+                all_buses.comp_count <= allowed_number_of_comp_per_bus
             ]
+            lv_buses = lv_buses[lv_buses.comp_count <= allowed_number_of_comp_per_bus]
+            if lv_buses.empty:
+                target_bus, target_bus_distance = geo.find_nearest_bus(
+                    geolocation, all_buses
+                )
+            else:
+                target_bus, target_bus_distance = geo.find_nearest_bus(
+                    geolocation, lv_buses
+                )
             target_bus, target_bus_distance = geo.find_nearest_bus(
-                geolocation, lv_buses
+                geolocation, all_buses
             )
+            if allow_mv_connection:
+                target_bus_all, target_bus_distance_all = geo.find_nearest_bus(
+                    geolocation, self.mv_grid.buses_df
+                )
+            else:
+                target_bus_all, target_bus_distance_all = None, np.nan
+
+        if target_bus_distance_all < factor_mv_connection * target_bus_distance:
+            target_bus, target_bus_distance = target_bus_all, target_bus_distance_all
 
-        # check distance from target bus
         if target_bus_distance > max_distance_from_target_bus:
-            # if target bus is too far away from the component, connect the component
-            # via a new bus
-            bus = self._connect_to_lv_bus(
-                edisgo_object, target_bus, comp_type, comp_data
-            )
+            if target_bus_distance_all < max_distance_from_target_bus:
+                comp_data["voltage_level"] = 5
+                comp_data.pop("type", None)
+                return self.connect_to_mv(edisgo_object, comp_data, comp_type=comp_type)
+            else:
+                bus = self._connect_to_lv_bus(
+                    edisgo_object, target_bus, comp_type, comp_data
+                )
+                self.buses_df.loc[bus, "components_at_bus"] = 1
         else:
-            # if target bus is very close to the component, the component is directly
-            # connected at the target bus
             bus = target_bus
-        comp_data.pop("geom")
-        comp_data.pop("p")
-        comp_name = add_func(bus=bus, **comp_data)
-        return comp_name
+
+        comp_data.pop("geom", None)
+        comp_data.pop("p", None)
+        return add_func(bus=bus, **comp_data)
 
     def _connect_mv_bus_to_target_object(
         self, edisgo_object, bus, target_obj, line_type, number_parallel_lines

From ae9f79a9bd2195f7b884d101a8e299cc0e90e7c5 Mon Sep 17 00:00:00 2001
From: Kilian Helfenbein <Kilian.Helfenbein@rl-institut.de>
Date: Mon, 12 Aug 2024 11:14:42 +0200
Subject: [PATCH 04/50] add docstring and update comments
 assure_minimum_potential_charging_parks function

---
 edisgo/io/electromobility_import.py | 81 +++++++++++++++++++++--------
 1 file changed, 60 insertions(+), 21 deletions(-)

diff --git a/edisgo/io/electromobility_import.py b/edisgo/io/electromobility_import.py
index 888529599..35ad174c0 100644
--- a/edisgo/io/electromobility_import.py
+++ b/edisgo/io/electromobility_import.py
@@ -371,10 +371,49 @@ def assure_minimum_potential_charging_parks(
     edisgo_obj: EDisGo,
     potential_charging_parks_gdf: gpd.GeoDataFrame,
     **kwargs,
-):
-    # ensure minimum number of potential charging parks per car
+) -> gpd.GeoDataFrame:
+    """
+    Ensures that there is a minimum number of potential charging parks
+    relative to the number of electric vehicles for each use case (home, work,
+    public, and high-power charging). This function adjusts the potential
+    charging parks by duplicating entries if necessary.
+
+    Parameters
+    ----------
+    edisgo_obj : EDisGo
+        The eDisGo object containing grid and electromobility data.
+    potential_charging_parks_gdf : gpd.GeoDataFrame
+        A GeoDataFrame containing potential charging park locations
+        and their attributes.
+    **kwargs : dict, optional
+        Additional keyword arguments to specify the grid connection to car
+        rate for different use cases. Expected keys:
+        - 'gc_to_car_rate_home' : float, optional, default 0.5
+        - 'gc_to_car_rate_work' : float, optional, default 0.25
+        - 'gc_to_car_rate_public' : float, optional, default 0.1
+        - 'gc_to_car_rate_hpc' : float, optional, default 0.005
+
+    Returns
+    -------
+    gpd.GeoDataFrame
+        A GeoDataFrame with adjusted potential charging park locations,
+        ensuring the minimum required number of parks per use case.
+
+    Notes
+    -----
+    - The function may duplicate potential charging park entries if the
+      current number does not meet the required rate relative to the number
+      of cars for each use case.
+    - If no charging parks are available for a use case, a random sample of
+      10% of public charging parks will be duplicated and assigned to the
+      missing use case.
+    """
+
+    # Calculate the total number of unique cars
     num_cars = len(edisgo_obj.electromobility.charging_processes_df.car_id.unique())
 
+    # Iterate over each use case to ensure the minimum number of potential charging
+    # parks
     for use_case in USECASES:
         if use_case == "home":
             gc_to_car_rate = kwargs.get("gc_to_car_rate_home", 0.5)
@@ -385,30 +424,33 @@ def assure_minimum_potential_charging_parks(
         elif use_case == "hpc":
             gc_to_car_rate = kwargs.get("gc_to_car_rate_hpc", 0.005)
 
+        # Filter the GeoDataFrame for the current use case
         use_case_gdf = potential_charging_parks_gdf.loc[
             potential_charging_parks_gdf.use_case == use_case
         ]
 
+        # Count the number of potential grid connections (GCs) for the use case
         num_gcs = len(use_case_gdf)
 
-        # if tracbev doesn't provide possible grid connections choose a
-        # random public potential charging park and duplicate
+        # Handle cases where no potential charging parks exist for a specific use case
         if num_gcs == 0:
             logger.warning(
-                f"There are no potential charging parks for use case {use_case}. "
-                f"Therefore 10 % of public potential charging parks are duplicated "
-                f"randomly and assigned to use case {use_case}."
+                f"No potential charging parks found for use case '{use_case}'. "
+                f"Duplicating 10% of public potential charging parks and assigning "
+                f"them to use case '{use_case}'."
             )
 
+            # Randomly sample 10% of public charging parks and assign to the current use
+            # case
             public_gcs = potential_charging_parks_gdf.loc[
                 potential_charging_parks_gdf.use_case == "public"
             ]
-
             random_gcs = public_gcs.sample(
                 int(np.ceil(len(public_gcs) / 10)),
                 random_state=edisgo_obj.topology.mv_grid.id,
             ).assign(use_case=use_case)
 
+            # Append the new entries to the GeoDataFrame
             potential_charging_parks_gdf = pd.concat(
                 [
                     potential_charging_parks_gdf,
@@ -421,21 +463,22 @@ def assure_minimum_potential_charging_parks(
             ]
             num_gcs = len(use_case_gdf)
 
-        # escape zero division
+        # Calculate the actual grid connection to car rate
         actual_gc_to_car_rate = np.Infinity if num_cars == 0 else num_gcs / num_cars
 
-        # duplicate potential charging parks until desired quantity is ensured
+        # Duplicate potential charging parks until the required rate is met
         max_it = 50
         n = 0
-
         while actual_gc_to_car_rate < gc_to_car_rate and n < max_it:
             logger.info(
                 f"Duplicating potential charging parks to meet the desired grid "
-                f"connections to cars rate of {gc_to_car_rate*100:.2f} % for use case "
-                f"{use_case}. Iteration: {n+1}."
+                f"connections to cars rate of {gc_to_car_rate * 100:.2f}% for use case "
+                f"'{use_case}'. Iteration: {n + 1}."
             )
 
             if actual_gc_to_car_rate * 2 < gc_to_car_rate:
+                # Double the number of potential charging parks by duplicating the
+                # entire subset
                 potential_charging_parks_gdf = pd.concat(
                     [
                         potential_charging_parks_gdf,
@@ -443,17 +486,15 @@ def assure_minimum_potential_charging_parks(
                     ],
                     ignore_index=True,
                 )
-
             else:
+                # Calculate the number of extra grid connections needed and sample them
                 extra_gcs = (
                     int(np.ceil(num_gcs * gc_to_car_rate / actual_gc_to_car_rate))
                     - num_gcs
                 )
-
                 extra_gdf = use_case_gdf.sample(
                     n=extra_gcs, random_state=edisgo_obj.topology.mv_grid.id
                 )
-
                 potential_charging_parks_gdf = pd.concat(
                     [
                         potential_charging_parks_gdf,
@@ -462,23 +503,21 @@ def assure_minimum_potential_charging_parks(
                     ignore_index=True,
                 )
 
+            # Recalculate the grid connection to car rate after duplication
             use_case_gdf = potential_charging_parks_gdf.loc[
                 potential_charging_parks_gdf.use_case == use_case
             ]
-
             num_gcs = len(use_case_gdf)
-
             actual_gc_to_car_rate = num_gcs / num_cars
 
             n += 1
 
-    # sort GeoDataFrame
+    # Sort the GeoDataFrame by use case, AGS, and user-centric weight
     potential_charging_parks_gdf = potential_charging_parks_gdf.sort_values(
         by=["use_case", "ags", "user_centric_weight"], ascending=[True, True, False]
     ).reset_index(drop=True)
 
-    # in case of polygons use the centroid as potential charging parks point
-    # and set crs to match edisgo object
+    # For polygon geometries, use the centroid as the charging park point and match CRS
     return (
         potential_charging_parks_gdf.assign(
             geometry=potential_charging_parks_gdf.geometry.representative_point()

From de013e8f79fadbe74713fe66329394674eda9231 Mon Sep 17 00:00:00 2001
From: Kilian Helfenbein <Kilian.Helfenbein@rl-institut.de>
Date: Mon, 12 Aug 2024 11:35:51 +0200
Subject: [PATCH 05/50] update docstring and comments get_weights_df function

---
 edisgo/io/electromobility_import.py | 123 ++++++++++++++++------------
 1 file changed, 72 insertions(+), 51 deletions(-)

diff --git a/edisgo/io/electromobility_import.py b/edisgo/io/electromobility_import.py
index 35ad174c0..e3769ae93 100644
--- a/edisgo/io/electromobility_import.py
+++ b/edisgo/io/electromobility_import.py
@@ -380,9 +380,9 @@ def assure_minimum_potential_charging_parks(
 
     Parameters
     ----------
-    edisgo_obj : EDisGo
+    edisgo_obj : :class:`~.EDisGo`
         The eDisGo object containing grid and electromobility data.
-    potential_charging_parks_gdf : gpd.GeoDataFrame
+    potential_charging_parks_gdf : :geopandas:`geopandas.GeoDataFrame<GeoDataFrame>`
         A GeoDataFrame containing potential charging park locations
         and their attributes.
     **kwargs : dict, optional
@@ -395,7 +395,7 @@ def assure_minimum_potential_charging_parks(
 
     Returns
     -------
-    gpd.GeoDataFrame
+    :geopandas:`geopandas.GeoDataFrame<GeoDataFrame>`
         A GeoDataFrame with adjusted potential charging park locations,
         ensuring the minimum required number of parks per use case.
 
@@ -562,70 +562,83 @@ def distribute_charging_demand(edisgo_obj, **kwargs):
 
 def get_weights_df(edisgo_obj, potential_charging_park_indices, **kwargs):
     """
-    Get weights per potential charging point for a given set of grid connection indices.
+    Calculate the weights for potential charging points based on user and grid
+    preferences.
+
+    This function determines the attractiveness of potential charging parks by
+    calculating weights that reflect either user convenience or grid stability,
+    or a combination of both. The weights are adjusted based on specified
+    parameters and are returned as a DataFrame.
 
     Parameters
     ----------
     edisgo_obj : :class:`~.EDisGo`
+        The eDisGo object containing relevant grid and electromobility data.
     potential_charging_park_indices : list
-        List of potential charging parks indices
+        A list of indices identifying the potential charging parks to be
+        evaluated.
 
     Other Parameters
-    -----------------
-    mode : str
-        Only use user friendly weights ("user_friendly") or combine with
-        grid friendly weights ("grid_friendly"). Default: "user_friendly".
-    user_friendly_weight : float
-        Weight of user friendly weight if mode "grid_friendly". Default: 0.5.
-    distance_weight: float
-        Grid friendly weight is a combination of the installed capacity of
-        generators and loads within a LV grid and the distance towards the
-        nearest substation. This parameter sets the weight for the distance
-        parameter. Default: 1/3.
+    ----------------
+    mode : str, optional
+        Determines the weighting approach. "user_friendly" uses only user-centric
+        weights, while "grid_friendly" combines user-centric and grid-centric
+        weights. Default is "user_friendly".
+    user_friendly_weight : float, optional
+        The weight assigned to user-centric preferences when using the
+        "grid_friendly" mode. Default is 0.5.
+    distance_weight : float, optional
+        Weighting factor for the distance to the nearest substation when using
+        the "grid_friendly" mode. This affects the grid-centric weight
+        calculation. Default is 1/3.
 
     Returns
     -------
     :pandas:`pandas.DataFrame<DataFrame>`
-        DataFrame with numeric weights
+        A DataFrame containing the calculated weights for each potential charging
+        park, reflecting either user or grid preferences, or a combination thereof.
 
+    Notes
+    -----
+    - The grid-friendly weighting takes into account the installed capacity of
+      generators and loads within a low-voltage (LV) grid, as well as the
+      distance to the nearest substation.
+    - If the mode is set to "grid_friendly", the function will combine user
+      preferences with grid stability considerations to determine the final
+      weights.
+
+    Raises
+    ------
+    ValueError
+        If an invalid mode is provided, an exception is raised.
     """
 
     def _get_lv_grid_weights():
         """
-        DataFrame containing technical data of LV grids.
+        Calculate technical weights for LV grids.
+
+        This internal function creates a DataFrame containing technical
+        information about LV grids, which is used to calculate grid-centric
+        weights for potential charging parks.
 
         Returns
-        --------
+        -------
         :pandas:`pandas.DataFrame<DataFrame>`
-            Columns of the DataFrame are:
-                peak_generation_capacity : float
-                    Cumulative peak generation capacity of generators in the network in
-                    MW.
-
-                p_set : float
-                    Cumulative peak load of loads in the network in MW.
-
-                substation_capacity : float
-                    Cumulative capacity of transformers to overlaying network.
-
-                generators_weight : float
-                    Weighting used in grid friendly siting of public charging points.
-                    In the case of generators the weight is defined by dividing the
-                    peak_generation_capacity by substation_capacity and norming the
-                    results from 0 .. 1. A higher weight is more attractive.
-
-                loads_weight : float
-                    Weighting used in grid friendly siting of public charging points.
-                    In the case of loads the weight is defined by dividing the
-                    p_set by substation_capacity and norming the results from 0 .. 1.
-                    The result is then substracted from 1 as the higher the p_set is
-                    in relation to the substation_capacity the less attractive this LV
-                    grid is for new loads from a grid perspective. A higher weight is
-                    more attractive.
+            A DataFrame with the following columns:
+            - peak_generation_capacity: Cumulative peak generation capacity of
+              generators in the LV grid (in MW).
+            - p_set: Cumulative peak load of loads in the LV grid (in MW).
+            - substation_capacity: Cumulative capacity of transformers
+              connecting the LV grid to the overlaying network.
+            - generators_weight: Weight reflecting the attractiveness of the
+              LV grid for new generation capacity.
+            - loads_weight: Weight reflecting the attractiveness of the LV grid
+              for new loads, from a grid stability perspective.
 
         """
         lv_grids = list(edisgo_obj.topology.mv_grid.lv_grids)
 
+        # Create a DataFrame to store LV grid weights
         lv_grids_df = pd.DataFrame(
             index=[_._id for _ in lv_grids],
             columns=[
@@ -637,14 +650,15 @@ def _get_lv_grid_weights():
             ],
         )
 
+        # Populate the DataFrame with relevant data
         lv_grids_df.peak_generation_capacity = [
             _.peak_generation_capacity for _ in lv_grids
         ]
-
         lv_grids_df.substation_capacity = [
             _.transformers_df.s_nom.sum() for _ in lv_grids
         ]
 
+        # Normalize generator weights
         min_max_scaler = preprocessing.MinMaxScaler()
         lv_grids_df.generators_weight = lv_grids_df.peak_generation_capacity.divide(
             lv_grids_df.substation_capacity
@@ -655,23 +669,27 @@ def _get_lv_grid_weights():
 
         lv_grids_df.p_set = [_.p_set for _ in lv_grids]
 
+        # Normalize load weights and normalize them
         lv_grids_df.loads_weight = lv_grids_df.p_set.divide(
             lv_grids_df.substation_capacity
         )
         lv_grids_df.loads_weight = 1 - min_max_scaler.fit_transform(
             lv_grids_df.loads_weight.values.reshape(-1, 1)
         )
+
         return lv_grids_df
 
     mode = kwargs.get("mode", "user_friendly")
 
     if mode == "user_friendly":
+        # Retrieve user-centric weights for the selected charging parks
         weights = [
             _.user_centric_weight
             for _ in edisgo_obj.electromobility.potential_charging_parks
             if _.id in potential_charging_park_indices
         ]
     elif mode == "grid_friendly":
+        # Prepare data for grid-centric weight calculation
         potential_charging_parks = list(
             edisgo_obj.electromobility.potential_charging_parks
         )
@@ -687,28 +705,30 @@ def _get_lv_grid_weights():
         generators_weight_factor = kwargs.get("generators_weight_factor", 0.5)
         loads_weight_factor = 1 - generators_weight_factor
 
+        # Combine generator and load weights to calculate grid-centric weights
         combined_weights = (
             generators_weight_factor * lv_grids_df["generators_weight"]
             + loads_weight_factor * lv_grids_df["loads_weight"]
         )
 
+        # Retrieve the IDs of the nearest LV grids for each potential charging park
         lv_grid_ids = [
             _.nearest_substation["lv_grid_id"] for _ in potential_charging_parks
         ]
 
+        # Map the combined weights to the corresponding charging parks
         load_and_generator_capacity_weights = [
             combined_weights.at[lv_grid_id] for lv_grid_id in lv_grid_ids
         ]
 
-        # fmt: off
+        # Extract the distance weights from the eDisGo object
         distance_weights = (
-            edisgo_obj.electromobility._potential_charging_parks_df.distance_weight
-            .tolist()
+            edisgo_obj.electromobility._potential_charging_parks_df.distance_weight.tolist()  # noqa: E501
         )
-        # fmt: on
 
         distance_weight = kwargs.get("distance_weight", 1 / 3)
 
+        # Combine the distance weights with the load and generator capacity weights
         grid_friendly_weights = [
             (1 - distance_weight) * load_and_generator_capacity_weights[i]
             + distance_weight * distance_weights[i]
@@ -717,17 +737,18 @@ def _get_lv_grid_weights():
 
         user_friendly_weight = kwargs.get("user_friendly_weight", 0.5)
 
+        # Final combined weights considering both user and grid preferences
         weights = [
             (1 - user_friendly_weight) * grid_friendly_weights[i]
             + user_friendly_weight * user_friendly_weights[i]
             for i in range(len(grid_friendly_weights))
         ]
-
     else:
         raise ValueError(
-            "Provided mode is not valid, needs to be 'user_friendly' or "
+            "Provided mode is not valid. It must be either 'user_friendly' or "
             "'grid_friendly'."
         )
+
     return pd.DataFrame(weights)
 
 

From d68e4d4c609bcc36f0fbd4ed49526107f527a6e7 Mon Sep 17 00:00:00 2001
From: Kilian Helfenbein <Kilian.Helfenbein@rl-institut.de>
Date: Mon, 12 Aug 2024 13:51:08 +0200
Subject: [PATCH 06/50] small bug fix in topology connect_to_lv

---
 edisgo/network/topology.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index d53615aae..910c7be75 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2289,7 +2289,9 @@ def _choose_random_substation_id():
                     "component is therefore connected to random LV bus."
                 )
                 bus = random.choice(
-                    lv_grid.buses_df[~lv_grid.buses_df.in_building.astype(bool)].index
+                    lv_grid.buses_df[
+                        ~lv_grid.buses_df.in_building.astype(bool)
+                    ].index.tolist()
                 )
                 comp_data.pop("geom", None)
                 comp_data.pop("p")

From fe662bddfedaeab653fc78a94f448918f2d8bfcb Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Wed, 14 Aug 2024 13:59:18 +0200
Subject: [PATCH 07/50] Revert "update connect_to_lv_based_on_geolocation
 function"

This reverts commit c3ff9f927eb643757dd2a0a0d10ee7799bb05667.
---
 edisgo/network/topology.py | 154 ++++++++++++-------------------------
 1 file changed, 51 insertions(+), 103 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index d53615aae..6795729b8 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2334,10 +2334,8 @@ def connect_to_lv_based_on_geolocation(
         edisgo_object,
         comp_data: dict,
         comp_type: str,
-        max_distance_from_target_bus: float = 0.1,
-        allowed_number_of_comp_per_bus: int = 2,
-        allow_mv_connection: bool = False,
-        factor_mv_connection: float = 3.0,
+        max_distance_from_target_bus: float = 0.02,
+        allowed_number_of_comp_per_bus=2,
     ) -> str:
         """
         Add and connect new component to LV grid topology based on its geolocation.
@@ -2387,14 +2385,6 @@ def connect_to_lv_based_on_geolocation(
             target bus. Default: 0.1.
         allowed_number_of_comp_per_bus : int
             Specifies, how many components of the same type are at most allowed to be
-            placed at the same bus. Default: 2.
-        allow_mv_connection : bool
-            Specifies whether the component can be connected to the MV grid in case
-            the closest LV bus is too far away. Default: False.
-        factor_mv_connection : float
-            Specifies the factor by which the distance to the closest MV bus is
-            multiplied to decide whether the component is connected to the MV grid
-            instead of the LV grid. Default: 3.0.
 
         Returns
         -------
@@ -2404,123 +2394,81 @@ def connect_to_lv_based_on_geolocation(
             :attr:`~.network.topology.Topology.loads_df` or
             :attr:`~.network.topology.Topology.storage_units_df`, depending on component
             type.
+
         """
 
-        def get_add_func(comp_type):
-            if comp_type == "generator":
-                return self.add_generator
-            elif comp_type in ["charging_point", "heat_pump"]:
-                comp_data["type"] = comp_type
-                return self.add_load
-            elif comp_type == "storage_unit":
-                return self.add_storage_unit
-            else:
-                logger.error(f"Component type {comp_type} is not a valid option.")
-                return None
-
-        # TODO:
-        # - Calculate the distance for each bus using Geopy
-        # and store it in a separate column.
-        # - Sort the buses based on the distance.
-        # - Delete all buses outside the boundary.
-        # - Take the first bus that has 0 components.
-        # - If a bus with fewer components is found, take that bus.
-        # - If no suitable bus is found, search for the nearest
-        # MV bus and connect there.
-        # - If no MV bus is found, connect to the MV station.
-        # - If that is not possible, search outside the radius
-        # and connect to a new bus.
-        # - Perform the entire process throughout the network,
-        # the LV grid is not relevant.
-
-        if "p" not in comp_data:
-            comp_data["p"] = comp_data.get("p_set", comp_data.get("p_nom"))
+        if "p" not in comp_data.keys():
+            comp_data["p"] = (
+                comp_data["p_set"]
+                if "p_set" in comp_data.keys()
+                else comp_data["p_nom"]
+            )
 
         voltage_level = comp_data.pop("voltage_level")
         if voltage_level not in [6, 7]:
             raise ValueError(
-                f"Voltage level must either be 6 or 7 but given"
-                f" voltage level is {voltage_level}."
+                f"Voltage level must either be 6 or 7 but given voltage level "
+                f"is {voltage_level}."
             )
-
         geolocation = comp_data.get("geom")
-        add_func = get_add_func(comp_type)
-        if not add_func:
+
+        if comp_type == "generator":
+            add_func = self.add_generator
+        elif comp_type == "charging_point" or comp_type == "heat_pump":
+            add_func = self.add_load
+            comp_data["type"] = comp_type
+        elif comp_type == "storage_unit":
+            add_func = self.add_storage_unit
+        else:
+            logger.error(f"Component type {comp_type} is not a valid option.")
             return
 
+        # find the nearest substation or LV bus
         if voltage_level == 6:
             substations = self.buses_df.loc[self.transformers_df.bus1.unique()]
+            if comp_type == "charging_point":
+                if comp_data["sector"] == "home":
+                    lv_loads = self.loads_df[self.loads_df.sector == "residential"]
+                    lv_buses = lv_loads[lv_loads.index.isin(lv_loads.bus)]
             target_bus, target_bus_distance = geo.find_nearest_bus(
                 geolocation, substations
             )
         else:
-            all_buses = self.buses_df
-            # Count components of the same type at each bus
-            comp_counts = (
-                self.loads_df[self.loads_df.type == comp_type].groupby("bus").size()
-            )
-            all_buses = all_buses.join(
-                comp_counts.rename("comp_count"), on="Bus"
-            ).fillna({"comp_count": 0})
-
+            lv_buses = self.buses_df.drop(self.mv_grid.buses_df.index)
             if comp_type == "charging_point":
-                sector = comp_data.get("sector")
-                if sector == "home":
+                if comp_data["sector"] == "home":
                     lv_loads = self.loads_df[self.loads_df.sector == "residential"]
-                elif sector == "work":
+                    lv_buses = lv_buses[lv_buses.index.isin(lv_loads.bus)]
+                elif comp_data["sector"] == "work":
                     lv_loads = self.loads_df[self.loads_df.sector == "cts"]
-                else:
-                    lv_loads = self.loads_df
-                lv_buses = all_buses[all_buses.index.isin(lv_loads.bus)]
-            else:
-                lv_buses = all_buses
-
-            # # Count components of the same type at each bus
-            # comp_counts = self.loads_df[self.loads_df.type == comp_type].
-            # groupby('bus').size()
-            # all_buses = all_buses.join(comp_counts.rename('comp_count'),
-            # on='Bus').fillna({'comp_count': 0})
-            all_buses = all_buses[
-                all_buses.comp_count <= allowed_number_of_comp_per_bus
+                    lv_buses = lv_buses[lv_buses.index.isin(lv_loads.bus)]
+
+            lv_buses.sort_index()
+            lv_buses["components_at_bus"] = lv_buses.index.map(
+                lambda bus: len(self.get_component_at_bus(bus, comp_type))
+            )
+            lv_buses = lv_buses[
+                lv_buses.components_at_bus <= allowed_number_of_comp_per_bus
             ]
-            lv_buses = lv_buses[lv_buses.comp_count <= allowed_number_of_comp_per_bus]
-            if lv_buses.empty:
-                target_bus, target_bus_distance = geo.find_nearest_bus(
-                    geolocation, all_buses
-                )
-            else:
-                target_bus, target_bus_distance = geo.find_nearest_bus(
-                    geolocation, lv_buses
-                )
             target_bus, target_bus_distance = geo.find_nearest_bus(
-                geolocation, all_buses
+                geolocation, lv_buses
             )
-            if allow_mv_connection:
-                target_bus_all, target_bus_distance_all = geo.find_nearest_bus(
-                    geolocation, self.mv_grid.buses_df
-                )
-            else:
-                target_bus_all, target_bus_distance_all = None, np.nan
-
-        if target_bus_distance_all < factor_mv_connection * target_bus_distance:
-            target_bus, target_bus_distance = target_bus_all, target_bus_distance_all
 
+        # check distance from target bus
         if target_bus_distance > max_distance_from_target_bus:
-            if target_bus_distance_all < max_distance_from_target_bus:
-                comp_data["voltage_level"] = 5
-                comp_data.pop("type", None)
-                return self.connect_to_mv(edisgo_object, comp_data, comp_type=comp_type)
-            else:
-                bus = self._connect_to_lv_bus(
-                    edisgo_object, target_bus, comp_type, comp_data
-                )
-                self.buses_df.loc[bus, "components_at_bus"] = 1
+            # if target bus is too far away from the component, connect the component
+            # via a new bus
+            bus = self._connect_to_lv_bus(
+                edisgo_object, target_bus, comp_type, comp_data
+            )
         else:
+            # if target bus is very close to the component, the component is directly
+            # connected at the target bus
             bus = target_bus
-
-        comp_data.pop("geom", None)
-        comp_data.pop("p", None)
-        return add_func(bus=bus, **comp_data)
+        comp_data.pop("geom")
+        comp_data.pop("p")
+        comp_name = add_func(bus=bus, **comp_data)
+        return comp_name
 
     def _connect_mv_bus_to_target_object(
         self, edisgo_object, bus, target_obj, line_type, number_parallel_lines

From 5bf5696096487fed7e7bb0a424e46fbcf67be4a7 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Wed, 14 Aug 2024 13:59:35 +0200
Subject: [PATCH 08/50] Revert "add test_connect_to_lv_based_on_geolocation"

This reverts commit 01ba22abc3fe7796349ef5aac012a08c62f6af4c.
---
 tests/network/test_topology.py | 30 ------------------------------
 1 file changed, 30 deletions(-)

diff --git a/tests/network/test_topology.py b/tests/network/test_topology.py
index 8c4f0b198..0baf02f34 100644
--- a/tests/network/test_topology.py
+++ b/tests/network/test_topology.py
@@ -948,11 +948,7 @@ class TestTopologyWithEdisgoObject:
     @pytest.yield_fixture(autouse=True)
     def setup_class(self):
         self.edisgo = EDisGo(ding0_grid=pytest.ding0_test_network_path)
-        self.edisgo3 = EDisGo(
-            ding0_grid=pytest.ding0_test_network_3_path, legacy_ding0_grids=False
-        )
         self.edisgo.set_time_series_worst_case_analysis()
-        self.edisgo3.set_time_series_worst_case_analysis()
 
     def test_to_geopandas(self):
         geopandas_container = self.edisgo.topology.to_geopandas()
@@ -1791,32 +1787,6 @@ def test_connect_to_lv(self):
         # check new storage
         assert self.edisgo.topology.storage_units_df.at[comp_name, "p_nom"] == 0.03
 
-    def test_connect_to_lv_based_on_geolocation(self):
-        # ######### ChargingPoint #############
-        # create directories to save ding0 example grid into
-        # lines_before = self.edisgo3.topology.lines_df
-        # buses_before = self.edisgo3.topology.buses_df
-        # generators_before = self.edisgo3.topology.generators_df
-
-        # add CHargingPoint
-        x = self.edisgo3.topology.buses_df.at["Busbar_mvgd_33535_MV", "x"]
-        y = self.edisgo3.topology.buses_df.at["Busbar_mvgd_33535_MV", "y"]
-        geom = Point((x, y))
-        test_cp = {
-            "p_set": 0.01,
-            "geom": geom,
-            "sector": "home",
-            "voltage_level": 7,
-            "mvlv_subst_id": 3.0,
-        }
-        neu = self.edisgo3.topology.connect_to_lv_based_on_geolocation(
-            edisgo_object=self.edisgo3,
-            comp_data=test_cp,
-            comp_type="charging_point",
-            max_distance_from_target_bus=0.1,
-        )
-        assert neu == "Charging_Point_LVGrid_1170540000_home_1"
-
     def test_check_integrity(self, caplog):
         """Test of validation of grids."""
         comps_dict = {

From 6fdaf749cc0694276d6c996aa53935902a30a6c1 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Wed, 14 Aug 2024 13:59:51 +0200
Subject: [PATCH 09/50] Revert "home charging points residential load"

This reverts commit db6af070ae66ff006e81ba3860481fc6d8889aa1.
---
 edisgo/network/topology.py | 61 ++++++++++++--------------------------
 1 file changed, 19 insertions(+), 42 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 6795729b8..2462314f0 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -1997,20 +1997,6 @@ def connect_to_mv(self, edisgo_object, comp_data, comp_type="generator"):
 
         return comp_name
 
-    def get_component_at_bus(self, lv_bus, comp_type):
-        if comp_type == "generator":
-            comps_at_bus = self.generators_df[self.generators_df.bus == lv_bus]
-        elif comp_type == "charging_point":
-            comps_at_bus = self.charging_points_df[
-                self.charging_points_df.bus == lv_bus
-            ]
-        elif comp_type == "heat_pump":
-            hp_df = self.loads_df[self.loads_df.type == "heat_pump"]
-            comps_at_bus = hp_df[hp_df.bus == lv_bus]
-        else:
-            comps_at_bus = self.storage_units_df[self.storage_units_df.bus == lv_bus]
-        return comps_at_bus
-
     def connect_to_lv(
         self,
         edisgo_object,
@@ -2169,7 +2155,7 @@ def _choose_random_substation_id():
             # if substation ID (= LV grid ID) is given but it does not match an
             # existing LV grid ID a random LV grid to connect in is chosen
             else:
-                # TODO:
+                # ToDo
                 # lv_grid = _choose_random_substation_id()
                 # logger.warning(
                 #     "Given mvlv_subst_id does not exist, wherefore a random "
@@ -2305,7 +2291,20 @@ def _choose_random_substation_id():
                 lv_bus = lv_buses_rnd.pop()
 
                 # determine number of components of the same type at LV bus
-                comps_at_bus = self.get_component_at_bus(lv_bus, comp_type)
+                if comp_type == "generator":
+                    comps_at_bus = self.generators_df[self.generators_df.bus == lv_bus]
+                elif comp_type == "charging_point":
+                    comps_at_bus = self.charging_points_df[
+                        self.charging_points_df.bus == lv_bus
+                    ]
+                elif comp_type == "heat_pump":
+                    hp_df = self.loads_df[self.loads_df.type == "heat_pump"]
+                    comps_at_bus = hp_df[hp_df.bus == lv_bus]
+                else:
+                    comps_at_bus = self.storage_units_df[
+                        self.storage_units_df.bus == lv_bus
+                    ]
+
                 # ToDo: Increase number of generators/charging points
                 #  allowed at one load in case all loads already have one
                 #  generator/charging point
@@ -2332,11 +2331,10 @@ def _choose_random_substation_id():
     def connect_to_lv_based_on_geolocation(
         self,
         edisgo_object,
-        comp_data: dict,
-        comp_type: str,
-        max_distance_from_target_bus: float = 0.02,
-        allowed_number_of_comp_per_bus=2,
-    ) -> str:
+        comp_data,
+        comp_type,
+        max_distance_from_target_bus=0.02,
+    ):
         """
         Add and connect new component to LV grid topology based on its geolocation.
 
@@ -2383,8 +2381,6 @@ def connect_to_lv_based_on_geolocation(
             before a new bus is created. If the new component is closer to the target
             bus than the maximum specified distance, it is directly connected to that
             target bus. Default: 0.1.
-        allowed_number_of_comp_per_bus : int
-            Specifies, how many components of the same type are at most allowed to be
 
         Returns
         -------
@@ -2426,30 +2422,11 @@ def connect_to_lv_based_on_geolocation(
         # find the nearest substation or LV bus
         if voltage_level == 6:
             substations = self.buses_df.loc[self.transformers_df.bus1.unique()]
-            if comp_type == "charging_point":
-                if comp_data["sector"] == "home":
-                    lv_loads = self.loads_df[self.loads_df.sector == "residential"]
-                    lv_buses = lv_loads[lv_loads.index.isin(lv_loads.bus)]
             target_bus, target_bus_distance = geo.find_nearest_bus(
                 geolocation, substations
             )
         else:
             lv_buses = self.buses_df.drop(self.mv_grid.buses_df.index)
-            if comp_type == "charging_point":
-                if comp_data["sector"] == "home":
-                    lv_loads = self.loads_df[self.loads_df.sector == "residential"]
-                    lv_buses = lv_buses[lv_buses.index.isin(lv_loads.bus)]
-                elif comp_data["sector"] == "work":
-                    lv_loads = self.loads_df[self.loads_df.sector == "cts"]
-                    lv_buses = lv_buses[lv_buses.index.isin(lv_loads.bus)]
-
-            lv_buses.sort_index()
-            lv_buses["components_at_bus"] = lv_buses.index.map(
-                lambda bus: len(self.get_component_at_bus(bus, comp_type))
-            )
-            lv_buses = lv_buses[
-                lv_buses.components_at_bus <= allowed_number_of_comp_per_bus
-            ]
             target_bus, target_bus_distance = geo.find_nearest_bus(
                 geolocation, lv_buses
             )

From 6c333800b08a93988c843fc006e9bb6ca0263e50 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Thu, 15 Aug 2024 14:15:33 +0200
Subject: [PATCH 10/50] update connect_to_lv_based_on_geolocation function

---
 edisgo/network/topology.py | 199 ++++++++++++++++++++++++++++++++-----
 1 file changed, 176 insertions(+), 23 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 2462314f0..76d2a3443 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -7,6 +7,7 @@
 
 from zipfile import ZipFile
 
+import geopy.distance
 import networkx as nx
 import numpy as np
 import pandas as pd
@@ -2331,10 +2332,13 @@ def _choose_random_substation_id():
     def connect_to_lv_based_on_geolocation(
         self,
         edisgo_object,
-        comp_data,
-        comp_type,
-        max_distance_from_target_bus=0.02,
-    ):
+        comp_data: dict,
+        comp_type: str,
+        max_distance_from_target_bus: float = 0.1,
+        allowed_number_of_comp_per_bus: int = 2,
+        allow_mv_connection: bool = False,
+        factor_mv_connection: float = 3.0,
+    ) -> str:
         """
         Add and connect new component to LV grid topology based on its geolocation.
 
@@ -2381,6 +2385,16 @@ def connect_to_lv_based_on_geolocation(
             before a new bus is created. If the new component is closer to the target
             bus than the maximum specified distance, it is directly connected to that
             target bus. Default: 0.1.
+        allowed_number_of_comp_per_bus : int
+            Specifies, how many components of the same type are at most allowed to be
+            placed at the same bus. Default: 2.
+        allow_mv_connection : bool
+            Specifies whether the component can be connected to the MV grid in case
+            the closest LV bus is too far away. Default: False.
+        factor_mv_connection : float
+            Specifies the factor by which the distance to the closest MV bus is
+            multiplied to decide whether the component is connected to the MV grid
+            instead of the LV grid. Default: 3.0.
 
         Returns
         -------
@@ -2390,9 +2404,9 @@ def connect_to_lv_based_on_geolocation(
             :attr:`~.network.topology.Topology.loads_df` or
             :attr:`~.network.topology.Topology.storage_units_df`, depending on component
             type.
-
         """
 
+        # Ensure 'p' is in comp_data, defaulting to 'p_set' or 'p_nom'
         if "p" not in comp_data.keys():
             comp_data["p"] = (
                 comp_data["p_set"]
@@ -2400,6 +2414,7 @@ def connect_to_lv_based_on_geolocation(
                 else comp_data["p_nom"]
             )
 
+        # Extract and validate voltage level
         voltage_level = comp_data.pop("voltage_level")
         if voltage_level not in [6, 7]:
             raise ValueError(
@@ -2408,6 +2423,7 @@ def connect_to_lv_based_on_geolocation(
             )
         geolocation = comp_data.get("geom")
 
+        # Determine the appropriate add function based on component type
         if comp_type == "generator":
             add_func = self.add_generator
         elif comp_type == "charging_point" or comp_type == "heat_pump":
@@ -2419,34 +2435,171 @@ def connect_to_lv_based_on_geolocation(
             logger.error(f"Component type {comp_type} is not a valid option.")
             return
 
-        # find the nearest substation or LV bus
+        # Find the nearest substation or LV bus
         if voltage_level == 6:
             substations = self.buses_df.loc[self.transformers_df.bus1.unique()]
             target_bus, target_bus_distance = geo.find_nearest_bus(
                 geolocation, substations
             )
+            # Check distance from target bus
+            if target_bus_distance > max_distance_from_target_bus:
+                # If target bus is too far away, connect via a new bus
+                bus = self._connect_to_lv_bus(
+                    edisgo_object, target_bus, comp_type, comp_data
+                )
+            else:
+                # If target bus is close, connect directly to the target bus
+                bus = target_bus
+            comp_data.pop("geom")
+            comp_data.pop("p")
+            comp_name = add_func(bus=bus, **comp_data)
         else:
+            # For voltage level 7, find the nearest LV bus
+            mv_buses = self.buses_df.loc[self.mv_grid.buses_df.index]
             lv_buses = self.buses_df.drop(self.mv_grid.buses_df.index)
-            target_bus, target_bus_distance = geo.find_nearest_bus(
-                geolocation, lv_buses
-            )
+            if comp_type == "charging_point":
+                if comp_data["sector"] == "home":
+                    lv_loads = self.loads_df[self.loads_df.sector == "residential"]
+                elif comp_data["sector"] == "work":
+                    lv_loads = self.loads_df[self.loads_df.sector == "cts"]
+                else:
+                    lv_loads = self.loads_df
+                lv_buses = lv_buses.loc[lv_loads.bus]
+            else:
+                lv_buses = lv_buses.loc[self.loads_df.bus]
+
+            # Calculate distances to MV and LV buses
+            mv_buses = self.calculate_distance_to_bus(mv_buses, geolocation)
+            lv_buses = self.calculate_distance_to_bus(lv_buses, geolocation)
+
+            # Filter buses within the max distance
+            mv_buses_masked = mv_buses.loc[
+                mv_buses.distance < max_distance_from_target_bus
+            ]
+            lv_buses_masked = lv_buses.loc[
+                lv_buses.distance < max_distance_from_target_bus
+            ]
+
+            # Handle cases where no LV buses are within the max distance
+            if len(lv_buses_masked) == 0:
+                if (
+                    allow_mv_connection
+                    and len(mv_buses_masked) > 0
+                    and mv_buses.distance.min()
+                    < factor_mv_connection * lv_buses.distance.min()
+                ):
+                    mv_buses_masked = mv_buses[
+                        mv_buses.distance == mv_buses.distance.min()
+                    ]
+                else:
+                    lv_buses_masked = lv_buses[
+                        lv_buses.distance == lv_buses.distance.min()
+                    ]
+                    mv_buses_masked = pd.DataFrame()
+            else:
+                mv_buses_masked = pd.DataFrame()
+
+            # Check how many components of the same type are already connected
+            # to the target bus
+            if not mv_buses_masked.empty:
+                target_bus = mv_buses_masked.loc[mv_buses_masked.distance.idxmin()]
+            else:
+                if comp_type == "charging_point":
+                    charging_points_count = (
+                        self.charging_points_df[
+                            self.charging_points_df.bus.isin(lv_buses_masked.index)
+                        ]
+                        .groupby("bus")
+                        .size()
+                    )
+                    lv_buses_masked.loc[:, "num_comps"] = (
+                        lv_buses_masked.index.map(charging_points_count)
+                        .fillna(0)
+                        .astype(int)
+                    )
+                if comp_type == "generator":
+                    generators_count = (
+                        self.generators_df[
+                            self.generators_df.bus.isin(lv_buses_masked.index)
+                        ]
+                        .groupby("bus")
+                        .size()
+                    )
+                    lv_buses_masked.loc[:, "num_comps"] = (
+                        lv_buses_masked.index.map(generators_count)
+                        .fillna(0)
+                        .astype(int)
+                    )
+                if comp_type == "heat_pump":
+                    heat_pumps_count = (
+                        self.loads_df[self.loads_df.type == "heat_pump"][
+                            self.loads_df.bus.isin(lv_buses_masked.index)
+                        ]
+                        .groupby("bus")
+                        .size()
+                    )
+                    lv_buses_masked.loc[:, "num_comps"] = (
+                        lv_buses_masked.index.map(heat_pumps_count)
+                        .fillna(0)
+                        .astype(int)
+                    )
+                if comp_type == "storage_unit":
+                    storage_units_count = (
+                        self.storage_units_df[
+                            self.storage_units_df.bus.isin(lv_buses_masked.index)
+                        ]
+                        .groupby("bus")
+                        .size()
+                    )
+                    lv_buses_masked.loc[:, "num_comps"] = (
+                        lv_buses_masked.index.map(storage_units_count)
+                        .fillna(0)
+                        .astype(int)
+                    )
+                lv_buses_masked = lv_buses_masked[
+                    lv_buses_masked.num_comps == lv_buses_masked.num_comps.min()
+                ]
+                target_bus = lv_buses_masked.loc[lv_buses_masked.distance.idxmin()]
+
+            # Remove unnecessary keys from comp_data
+            comp_data.pop("geom")
+            comp_data.pop("p")
+
+            # Add the component to the grid
+            comp_name = add_func(bus=target_bus.name, **comp_data)
 
-        # check distance from target bus
-        if target_bus_distance > max_distance_from_target_bus:
-            # if target bus is too far away from the component, connect the component
-            # via a new bus
-            bus = self._connect_to_lv_bus(
-                edisgo_object, target_bus, comp_type, comp_data
-            )
-        else:
-            # if target bus is very close to the component, the component is directly
-            # connected at the target bus
-            bus = target_bus
-        comp_data.pop("geom")
-        comp_data.pop("p")
-        comp_name = add_func(bus=bus, **comp_data)
         return comp_name
 
+    def calculate_distance_to_bus(
+        self, bus_df: pd.DataFrame, geom: Point
+    ) -> pd.DataFrame:
+        """
+        Calculate the distance between a bus and a given geometry.
+
+        Parameters
+        ----------
+        bus_df : pandas.DataFrame
+            Data of bus.
+            DataFrame has same rows as columns of
+            :attr:`~.network.topology.Topology.buses_df`.
+        geom : shapely.geometry.Point
+            Geometry to calculate distance to.
+
+        Returns
+        -------
+        pandas.DataFrame
+            Data of bus with additional column 'distance' containing the distance
+            to the given geometry
+        """
+        distances = bus_df.apply(
+            lambda row: geopy.distance.distance(
+                (row["x"], row["y"]), (geom.x, geom.y)
+            ).km,
+            axis=1,
+        )
+        bus_df.loc[:, "distance"] = distances
+        return bus_df
+
     def _connect_mv_bus_to_target_object(
         self, edisgo_object, bus, target_obj, line_type, number_parallel_lines
     ):

From d678279ea1582cdeaa54dc1cb089aff80b5aaa7c Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Thu, 15 Aug 2024 14:37:18 +0200
Subject: [PATCH 11/50] Add warning maximum components per bus

---
 edisgo/network/topology.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 76d2a3443..cb31877e6 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2559,6 +2559,8 @@ def connect_to_lv_based_on_geolocation(
                 lv_buses_masked = lv_buses_masked[
                     lv_buses_masked.num_comps == lv_buses_masked.num_comps.min()
                 ]
+                if lv_buses_masked.num_comps.min() >= allowed_number_of_comp_per_bus:
+                    warnings.warn("Maximum number of components per bus exceeded")
                 target_bus = lv_buses_masked.loc[lv_buses_masked.distance.idxmin()]
 
             # Remove unnecessary keys from comp_data

From 944ab2ce24aae3166e8aebf2a49fa4a099daf3ef Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Thu, 15 Aug 2024 15:27:30 +0200
Subject: [PATCH 12/50] mv_buses only if allowed

---
 edisgo/network/topology.py | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index cb31877e6..f40d5b632 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2455,7 +2455,13 @@ def connect_to_lv_based_on_geolocation(
             comp_name = add_func(bus=bus, **comp_data)
         else:
             # For voltage level 7, find the nearest LV bus
-            mv_buses = self.buses_df.loc[self.mv_grid.buses_df.index]
+            if allow_mv_connection:
+                mv_buses = self.buses_df.loc[self.mv_grid.buses_df.index]
+                mv_buses = self.calculate_distance_to_bus(mv_buses, geolocation)
+                mv_buses_masked = mv_buses.loc[
+                    mv_buses.distance < max_distance_from_target_bus
+                ]
+
             lv_buses = self.buses_df.drop(self.mv_grid.buses_df.index)
             if comp_type == "charging_point":
                 if comp_data["sector"] == "home":
@@ -2468,14 +2474,10 @@ def connect_to_lv_based_on_geolocation(
             else:
                 lv_buses = lv_buses.loc[self.loads_df.bus]
 
-            # Calculate distances to MV and LV buses
-            mv_buses = self.calculate_distance_to_bus(mv_buses, geolocation)
+            # Calculate distances to LV buses
             lv_buses = self.calculate_distance_to_bus(lv_buses, geolocation)
 
             # Filter buses within the max distance
-            mv_buses_masked = mv_buses.loc[
-                mv_buses.distance < max_distance_from_target_bus
-            ]
             lv_buses_masked = lv_buses.loc[
                 lv_buses.distance < max_distance_from_target_bus
             ]

From 32dce1d619c59451609b4fe17b607ce0f273f446 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Mon, 19 Aug 2024 13:04:06 +0200
Subject: [PATCH 13/50] move 'calculate_distance_to_buses_df' to geo.py

---
 edisgo/network/topology.py | 31 -------------------------------
 edisgo/tools/geo.py        | 30 ++++++++++++++++++++++++++++++
 2 files changed, 30 insertions(+), 31 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index f40d5b632..427521515 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -7,7 +7,6 @@
 
 from zipfile import ZipFile
 
-import geopy.distance
 import networkx as nx
 import numpy as np
 import pandas as pd
@@ -2574,36 +2573,6 @@ def connect_to_lv_based_on_geolocation(
 
         return comp_name
 
-    def calculate_distance_to_bus(
-        self, bus_df: pd.DataFrame, geom: Point
-    ) -> pd.DataFrame:
-        """
-        Calculate the distance between a bus and a given geometry.
-
-        Parameters
-        ----------
-        bus_df : pandas.DataFrame
-            Data of bus.
-            DataFrame has same rows as columns of
-            :attr:`~.network.topology.Topology.buses_df`.
-        geom : shapely.geometry.Point
-            Geometry to calculate distance to.
-
-        Returns
-        -------
-        pandas.DataFrame
-            Data of bus with additional column 'distance' containing the distance
-            to the given geometry
-        """
-        distances = bus_df.apply(
-            lambda row: geopy.distance.distance(
-                (row["x"], row["y"]), (geom.x, geom.y)
-            ).km,
-            axis=1,
-        )
-        bus_df.loc[:, "distance"] = distances
-        return bus_df
-
     def _connect_mv_bus_to_target_object(
         self, edisgo_object, bus, target_obj, line_type, number_parallel_lines
     ):
diff --git a/edisgo/tools/geo.py b/edisgo/tools/geo.py
index 79b880a6b..55d6523cc 100755
--- a/edisgo/tools/geo.py
+++ b/edisgo/tools/geo.py
@@ -5,6 +5,9 @@
 
 from typing import TYPE_CHECKING
 
+import geopy.distance
+import pandas as pd
+
 from geopy.distance import geodesic
 from pyproj import Transformer
 
@@ -324,3 +327,30 @@ def mv_grid_gdf(edisgo_obj: EDisGo):
         geometry=[edisgo_obj.topology.grid_district["geom"]],
         crs=f"EPSG:{edisgo_obj.topology.grid_district['srid']}",
     )
+
+
+def calculate_distance_to_buses_df(bus_df: pd.DataFrame, geom: Point) -> pd.DataFrame:
+    """
+    Calculate the distance between a bus and a given geometry.
+
+    Parameters
+    ----------
+    bus_df : pandas.DataFrame
+        Data of bus.
+        DataFrame has same rows as columns of
+        :attr:`~.network.topology.Topology.buses_df`.
+    geom : shapely.geometry.Point
+        Geometry to calculate distance to.
+
+    Returns
+    -------
+    pandas.DataFrame
+        Data of bus with additional column 'distance' containing the distance
+        to the given geometry
+    """
+    distances = bus_df.apply(
+        lambda row: geopy.distance.distance((row["x"], row["y"]), (geom.x, geom.y)).km,
+        axis=1,
+    )
+    bus_df.loc[:, "distance"] = distances
+    return bus_df

From 333a37be327eb769b23279035172bbd4f11e5534 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Mon, 19 Aug 2024 13:06:34 +0200
Subject: [PATCH 14/50] Adjusting docstring to new method

---
 edisgo/network/topology.py | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 427521515..fb13a2c8c 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2403,6 +2403,36 @@ def connect_to_lv_based_on_geolocation(
             :attr:`~.network.topology.Topology.loads_df` or
             :attr:`~.network.topology.Topology.storage_units_df`, depending on component
             type.
+
+        Method
+        ------
+        1. Extract and validate voltage level
+        - Voltage level 6:
+            If the voltage level is 6, the component is connected to the closest
+            MV/LV substation or MV bus. Therefore, the distance to the substations
+            and MV buses is calculated and the closest one is chosen as target bus.
+            If the distance is greater than the specified maximum distance, a new
+            bus is created for the component.
+        - Voltage level 7:
+            If the voltage level is 7, the component is connected to the closest
+            LV bus.
+            - No MV connection allowed:
+                If the distance to the closest LV bus is less than the specified
+                maximum distance, the component is connected to the closest LV bus.
+                If the distance is greater, a new bus is created for the component.
+                If there are already components of the same type connected to the
+                target bus, the component is connected to the closest LV bus with
+                fewer connected components of the same type within the maximum
+                distance. If no such bus is found, the component is connected to
+                the closest LV bus again. If all buses within the allowed distance
+                have equal or more components of the same type connected to them
+                than allowed, the component is connected to a new LV bus.
+            - MV connection:
+                If the distance to the closest LV bus is less than the specified
+                maximum distance, the component is connected to the closest LV bus.
+                If the distance is greater, the distance to the closest MV bus is
+                calculated. If the distance to the closest MV bus is less than the
+                specified factor multiplied by the distance to the closest LV bus,
         """
 
         # Ensure 'p' is in comp_data, defaulting to 'p_set' or 'p_nom'

From 9013d10421ceafaaccee60a8511da5f9e63e99a4 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Mon, 19 Aug 2024 13:07:18 +0200
Subject: [PATCH 15/50] changes to new method in
 'connect_to_lv_based_on_geolocation'

---
 edisgo/network/topology.py | 148 ++++++++++++++++++-------------------
 1 file changed, 74 insertions(+), 74 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index fb13a2c8c..9d5751d00 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2452,21 +2452,29 @@ def connect_to_lv_based_on_geolocation(
             )
         geolocation = comp_data.get("geom")
 
-        # Determine the appropriate add function based on component type
-        if comp_type == "generator":
-            add_func = self.add_generator
-        elif comp_type == "charging_point" or comp_type == "heat_pump":
-            add_func = self.add_load
+        # Dictionary to map component types to their corresponding add functions
+        add_func_map = {
+            "generator": self.add_generator,
+            "charging_point": self.add_load,
+            "heat_pump": self.add_load,
+            "storage_unit": self.add_storage_unit,
+        }
+        # Set the component type in comp_data if necessary
+        if comp_type in ["charging_point", "heat_pump"]:
             comp_data["type"] = comp_type
-        elif comp_type == "storage_unit":
-            add_func = self.add_storage_unit
-        else:
+
+        # Determine the appropriate add function based on component type
+        add_func = add_func_map.get(comp_type)
+
+        if add_func is None:
             logger.error(f"Component type {comp_type} is not a valid option.")
             return
 
         # Find the nearest substation or LV bus
         if voltage_level == 6:
             substations = self.buses_df.loc[self.transformers_df.bus1.unique()]
+            mv_buses = self.buses_df.loc[self.mv_grid.buses_df.index]
+            substations = pd.concat([substations, mv_buses])
             target_bus, target_bus_distance = geo.find_nearest_bus(
                 geolocation, substations
             )
@@ -2485,8 +2493,9 @@ def connect_to_lv_based_on_geolocation(
         else:
             # For voltage level 7, find the nearest LV bus
             if allow_mv_connection:
+                # find MV buses within the max distance
                 mv_buses = self.buses_df.loc[self.mv_grid.buses_df.index]
-                mv_buses = self.calculate_distance_to_bus(mv_buses, geolocation)
+                mv_buses = geo.calculate_distance_to_buses_df(mv_buses, geolocation)
                 mv_buses_masked = mv_buses.loc[
                     mv_buses.distance < max_distance_from_target_bus
                 ]
@@ -2494,22 +2503,28 @@ def connect_to_lv_based_on_geolocation(
             lv_buses = self.buses_df.drop(self.mv_grid.buses_df.index)
             if comp_type == "charging_point":
                 if comp_data["sector"] == "home":
-                    lv_loads = self.loads_df[self.loads_df.sector == "residential"]
+                    lv_loads = self.loads_df[
+                        self.loads_df.sector.isin(["residential", "home"])
+                    ]
                 elif comp_data["sector"] == "work":
-                    lv_loads = self.loads_df[self.loads_df.sector == "cts"]
+                    lv_loads = self.loads_df[
+                        self.loads_df.sector.isin(
+                            ["industrial", "cts", "agricultural", "work"]
+                        )
+                    ]
                 else:
+                    # public charging points should not be in buildings
                     lv_loads = self.loads_df
+                    lv_buses = lv_buses.loc[~lv_buses.in_building]
                 lv_buses = lv_buses.loc[lv_loads.bus]
-            else:
-                lv_buses = lv_buses.loc[self.loads_df.bus]
 
             # Calculate distances to LV buses
-            lv_buses = self.calculate_distance_to_bus(lv_buses, geolocation)
+            lv_buses = geo.calculate_distance_to_buses_df(lv_buses, geolocation)
 
             # Filter buses within the max distance
             lv_buses_masked = lv_buses.loc[
                 lv_buses.distance < max_distance_from_target_bus
-            ]
+            ].copy()
 
             # Handle cases where no LV buses are within the max distance
             if len(lv_buses_masked) == 0:
@@ -2523,77 +2538,62 @@ def connect_to_lv_based_on_geolocation(
                         mv_buses.distance == mv_buses.distance.min()
                     ]
                 else:
-                    lv_buses_masked = lv_buses[
-                        lv_buses.distance == lv_buses.distance.min()
-                    ]
+                    # If no LV buses are within the max distance, connect via a new bus
+                    target_bus = self._connect_to_lv_bus(
+                        edisgo_object=edisgo_object,
+                        target_bus=lv_buses.distance.idxmin(),
+                        comp_type=comp_type,
+                        comp_data=comp_data,
+                    )
+                    lv_buses_masked = pd.DataFrame(self.buses_df.loc[target_bus]).T
+                    lv_buses_masked["distance"] = 0
                     mv_buses_masked = pd.DataFrame()
             else:
                 mv_buses_masked = pd.DataFrame()
 
-            # Check how many components of the same type are already connected
-            # to the target bus
             if not mv_buses_masked.empty:
                 target_bus = mv_buses_masked.loc[mv_buses_masked.distance.idxmin()]
+            # Check how many components of the same type are already connected
+            # to the target bus
             else:
-                if comp_type == "charging_point":
-                    charging_points_count = (
-                        self.charging_points_df[
-                            self.charging_points_df.bus.isin(lv_buses_masked.index)
-                        ]
-                        .groupby("bus")
-                        .size()
-                    )
-                    lv_buses_masked.loc[:, "num_comps"] = (
-                        lv_buses_masked.index.map(charging_points_count)
-                        .fillna(0)
-                        .astype(int)
-                    )
-                if comp_type == "generator":
-                    generators_count = (
-                        self.generators_df[
-                            self.generators_df.bus.isin(lv_buses_masked.index)
-                        ]
-                        .groupby("bus")
-                        .size()
-                    )
-                    lv_buses_masked.loc[:, "num_comps"] = (
-                        lv_buses_masked.index.map(generators_count)
-                        .fillna(0)
-                        .astype(int)
-                    )
-                if comp_type == "heat_pump":
-                    heat_pumps_count = (
-                        self.loads_df[self.loads_df.type == "heat_pump"][
-                            self.loads_df.bus.isin(lv_buses_masked.index)
-                        ]
-                        .groupby("bus")
-                        .size()
-                    )
-                    lv_buses_masked.loc[:, "num_comps"] = (
-                        lv_buses_masked.index.map(heat_pumps_count)
-                        .fillna(0)
-                        .astype(int)
-                    )
-                if comp_type == "storage_unit":
-                    storage_units_count = (
-                        self.storage_units_df[
-                            self.storage_units_df.bus.isin(lv_buses_masked.index)
-                        ]
-                        .groupby("bus")
-                        .size()
-                    )
-                    lv_buses_masked.loc[:, "num_comps"] = (
-                        lv_buses_masked.index.map(storage_units_count)
-                        .fillna(0)
-                        .astype(int)
-                    )
+                comp_df = {
+                    "charging_point": self.charging_points_df,
+                    "generator": self.generators_df,
+                    "heat_pump": self.loads_df[self.loads_df.type == "heat_pump"],
+                    "storage_unit": self.storage_units_df,
+                }.get(comp_type)
+
+                comp_type_counts = (
+                    comp_df.loc[comp_df.bus.isin(lv_buses_masked.index)]
+                    .groupby("bus")
+                    .size()
+                )
+
+                lv_buses_masked.loc[:, "num_comps"] = (
+                    lv_buses_masked.index.map(comp_type_counts).fillna(0).astype(int)
+                )
+
                 lv_buses_masked = lv_buses_masked[
                     lv_buses_masked.num_comps == lv_buses_masked.num_comps.min()
                 ]
+
                 if lv_buses_masked.num_comps.min() >= allowed_number_of_comp_per_bus:
-                    warnings.warn("Maximum number of components per bus exceeded")
+                    target_bus = self._connect_to_lv_bus(
+                        edisgo_object=edisgo_object,
+                        target_bus=lv_buses.distance.idxmin(),
+                        comp_type=comp_type,
+                        comp_data=comp_data,
+                    )
+                    lv_buses_masked = pd.DataFrame(self.buses_df.loc[target_bus]).T
+                    lv_buses_masked["distance"] = 0
+                    mv_buses_masked = pd.DataFrame()
+                    warnings.warn(
+                        "Maximum number of components per bus exceeded, "
+                        "connecting to new bus."
+                    )
                 target_bus = lv_buses_masked.loc[lv_buses_masked.distance.idxmin()]
-
+                if isinstance(target_bus, pd.DataFrame):
+                    target_bus = target_bus.iloc[0]
             # Remove unnecessary keys from comp_data
             comp_data.pop("geom")
             comp_data.pop("p")

From 0822f25c6e4d90610dda90869b58592ebeb0f743 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Mon, 19 Aug 2024 13:58:37 +0200
Subject: [PATCH 16/50] Adjusting docstring to new method

---
 edisgo/network/topology.py | 29 ++++++++++++++++++-----------
 1 file changed, 18 insertions(+), 11 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 85e09bcbd..0ef4f497a 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2433,8 +2433,12 @@ def connect_to_lv_based_on_geolocation(
                 If the distance to the closest LV bus is less than the specified
                 maximum distance, the component is connected to the closest LV bus.
                 If the distance is greater, the distance to the closest MV bus is
-                calculated. If the distance to the closest MV bus is less than the
-                specified factor multiplied by the distance to the closest LV bus,
+                calculated. If the distance to the closest MV bus multiplied with
+                the factor is less than the distance to the closest LV bus, the
+                component is connected to the closest MV bus. The is no restriction
+                on the number of components of the same type connected to
+                the MV bus. If the distance is greater, the component
+                is connected to a new LV bus.
         """
 
         # Ensure 'p' is in comp_data, defaulting to 'p_set' or 'p_nom'
@@ -2472,7 +2476,7 @@ def connect_to_lv_based_on_geolocation(
             logger.error(f"Component type {comp_type} is not a valid option.")
             return
 
-        # Find the nearest substation or LV bus
+        # Find the nearest substation or MV bus
         if voltage_level == 6:
             substations = self.buses_df.loc[self.transformers_df.bus1.unique()]
             mv_buses = self.buses_df.loc[self.mv_grid.buses_df.index]
@@ -2492,7 +2496,7 @@ def connect_to_lv_based_on_geolocation(
             comp_data.pop("geom")
             comp_data.pop("p")
             comp_name = add_func(bus=bus, **comp_data)
-        else:
+        elif voltage_level == 7:
             # For voltage level 7, find the nearest LV bus
             if allow_mv_connection:
                 # find MV buses within the max distance
@@ -2530,17 +2534,23 @@ def connect_to_lv_based_on_geolocation(
 
             # Handle cases where no LV buses are within the max distance
             if len(lv_buses_masked) == 0:
+                # If MV connection is allowed, connect to the nearest MV bus
+                # if it is closer than the factor multiplied by the distance
+                # to the nearest LV bus and there are MV buses within the
+                # max distance
                 if (
                     allow_mv_connection
                     and len(mv_buses_masked) > 0
-                    and mv_buses.distance.min()
-                    < factor_mv_connection * lv_buses.distance.min()
+                    and mv_buses.distance.min() * factor_mv_connection
+                    < lv_buses.distance.min()
                 ):
                     mv_buses_masked = mv_buses[
                         mv_buses.distance == mv_buses.distance.min()
                     ]
                 else:
-                    # If no LV buses are within the max distance, connect via a new bus
+                    # If no LV buses are within the max distance,
+                    # and MV connection is not allowed or no MV buses are
+                    # within the max distance, connect via a new bus
                     target_bus = self._connect_to_lv_bus(
                         edisgo_object=edisgo_object,
                         target_bus=lv_buses.distance.idxmin(),
@@ -2589,10 +2599,7 @@ def connect_to_lv_based_on_geolocation(
                     lv_buses_masked = pd.DataFrame(self.buses_df.loc[target_bus]).T
                     lv_buses_masked["distance"] = 0
                     mv_buses_masked = pd.DataFrame()
-                    warnings.warn(
-                        "Maximum number of components per bus exceeded, "
-                        "connecting to new bus."
-                    )
+
                 target_bus = lv_buses_masked.loc[lv_buses_masked.distance.idxmin()]
                 if isinstance(target_bus, pd.DataFrame):
                     target_bus = target_bus.iloc[0]

From ad48999adc5b3bc9dbb79c048aefb8e6a08bd889 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Mon, 19 Aug 2024 16:42:45 +0200
Subject: [PATCH 17/50] changes to new method in
 'connect_to_lv_based_on_geolocation'

---
 edisgo/network/topology.py | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 0ef4f497a..69216fe9f 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2468,13 +2468,17 @@ def connect_to_lv_based_on_geolocation(
         # Set the component type in comp_data if necessary
         if comp_type in ["charging_point", "heat_pump"]:
             comp_data["type"] = comp_type
+        elif comp_type in ["generator", "storage_unit"]:
+            comp_data["p_nom"] = comp_data["p"]
 
         # Determine the appropriate add function based on component type
         add_func = add_func_map.get(comp_type)
 
         if add_func is None:
-            logger.error(f"Component type {comp_type} is not a valid option.")
-            return
+            raise ValueError(
+                f"Provided component type {comp_type} is not valid. Must either be"
+                f"'generator', 'charging_point', 'heat_pump' or 'storage_unit'."
+            )
 
         # Find the nearest substation or MV bus
         if voltage_level == 6:
@@ -2512,16 +2516,27 @@ def connect_to_lv_based_on_geolocation(
                     lv_loads = self.loads_df[
                         self.loads_df.sector.isin(["residential", "home"])
                     ]
+                    lv_loads = lv_loads.loc[
+                        ~lv_loads.bus.isin(self.mv_grid.buses_df.index)
+                    ]
                 elif comp_data["sector"] == "work":
                     lv_loads = self.loads_df[
                         self.loads_df.sector.isin(
                             ["industrial", "cts", "agricultural", "work"]
                         )
                     ]
+                    lv_loads = lv_loads.loc[
+                        ~lv_loads.bus.isin(self.mv_grid.buses_df.index)
+                    ]
+
                 else:
                     # public charging points should not be in buildings
                     lv_loads = self.loads_df
+                    lv_loads = lv_loads.loc[
+                        ~lv_loads.bus.isin(self.mv_grid.buses_df.index)
+                    ]
                     lv_buses = lv_buses.loc[~lv_buses.in_building]
+
                 lv_buses = lv_buses.loc[lv_loads.bus]
 
             # Calculate distances to LV buses

From 46cc8656ff29a46d7bcccac7fa38e7b56718ab80 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Mon, 19 Aug 2024 16:43:17 +0200
Subject: [PATCH 18/50] add tests for 'connect_to_lv_based_on_geolocation'

---
 tests/network/test_topology.py | 124 ++++++++++++++++++++++++++++++++-
 1 file changed, 123 insertions(+), 1 deletion(-)

diff --git a/tests/network/test_topology.py b/tests/network/test_topology.py
index 0baf02f34..751474620 100644
--- a/tests/network/test_topology.py
+++ b/tests/network/test_topology.py
@@ -945,10 +945,14 @@ class TestTopologyWithEdisgoObject:
 
     """
 
-    @pytest.yield_fixture(autouse=True)
+    @pytest.fixture(autouse=True)
     def setup_class(self):
         self.edisgo = EDisGo(ding0_grid=pytest.ding0_test_network_path)
         self.edisgo.set_time_series_worst_case_analysis()
+        self.edisgo3 = EDisGo(
+            ding0_grid=pytest.ding0_test_network_3_path, legacy_ding0_grids=False
+        )
+        self.edisgo3.set_time_series_worst_case_analysis()
 
     def test_to_geopandas(self):
         geopandas_container = self.edisgo.topology.to_geopandas()
@@ -1909,3 +1913,121 @@ def test_check_integrity(self, caplog):
         assert "There are lines with very short line lengths" in caplog.text
         assert "Very small values for impedance of lines" and line in caplog.text
         caplog.clear()
+
+    # Define the parameters
+    sector_values = ["home", "work"]
+    comp_type_values = ["charging_point", "heat_pump", "storage_unit", "generator"]
+    voltage_level_values = [6, 7]
+    max_distance_from_target_bus_values = [0.01]
+    allowed_number_of_comp_per_bus_values = [2]
+    allow_mv_connection_values = [True, False]
+
+    # Parametrize the test function
+    @pytest.mark.parametrize("sector", sector_values)
+    @pytest.mark.parametrize("comp_type", comp_type_values)
+    @pytest.mark.parametrize("voltage_level", voltage_level_values)
+    @pytest.mark.parametrize(
+        "max_distance_from_target_bus", max_distance_from_target_bus_values
+    )
+    @pytest.mark.parametrize(
+        "allowed_number_of_comp_per_bus", allowed_number_of_comp_per_bus_values
+    )
+    @pytest.mark.parametrize("allow_mv_connection", allow_mv_connection_values)
+    def test_connect_to_lv_based_on_geolocation(
+        self,
+        sector,
+        comp_type,
+        voltage_level,
+        max_distance_from_target_bus,
+        allowed_number_of_comp_per_bus,
+        allow_mv_connection,
+    ):
+        x = self.edisgo3.topology.buses_df.at["Busbar_mvgd_33535_MV", "x"]
+        y = self.edisgo3.topology.buses_df.at["Busbar_mvgd_33535_MV", "y"]
+        test_cp = {
+            "p_set": 0.01,
+            "geom": Point((x, y)),
+            "sector": sector,
+            "voltage_level": voltage_level,
+            "mvlv_subst_id": 3.0,
+        }
+        if comp_type == "generator":
+            test_cp["generator_type"] = "solar"
+            test_cp["generator_id"] = 23456
+
+        if (
+            comp_type == "charging_point"
+            and sector == "home"
+            and voltage_level == 7
+            and not allow_mv_connection
+            and max_distance_from_target_bus == 0.01
+            and allowed_number_of_comp_per_bus == 2
+        ):
+            for _ in range(5):
+                test_cp = {
+                    "p_set": 0.01,
+                    "geom": Point((x, y)),
+                    "sector": sector,
+                    "voltage_level": voltage_level,
+                    "mvlv_subst_id": 3.0,
+                }
+                comp_name = self.edisgo3.topology.connect_to_lv_based_on_geolocation(
+                    edisgo_object=self.edisgo3,
+                    comp_data=test_cp,
+                    comp_type=comp_type,
+                    max_distance_from_target_bus=max_distance_from_target_bus,
+                    allowed_number_of_comp_per_bus=allowed_number_of_comp_per_bus,
+                    allow_mv_connection=allow_mv_connection,
+                )
+                assert comp_name.startswith("Charging_Point_")
+            assert len(self.edisgo3.topology.charging_points_df) == 5
+            assert len(self.edisgo3.topology.charging_points_df.bus.unique()) == 3
+
+        else:
+            comp_name = self.edisgo3.topology.connect_to_lv_based_on_geolocation(
+                edisgo_object=self.edisgo3,
+                comp_data=test_cp,
+                comp_type=comp_type,
+                max_distance_from_target_bus=max_distance_from_target_bus,
+                allowed_number_of_comp_per_bus=allowed_number_of_comp_per_bus,
+                allow_mv_connection=allow_mv_connection,
+            )
+            if comp_type == "charging_point":
+                assert comp_name.startswith("Charging_Point_")
+            elif comp_type == "heat_pump":
+                assert comp_name.startswith("Heat_Pump_")
+            elif comp_type == "storage_unit":
+                assert comp_name.startswith("StorageUnit")
+            else:
+                assert comp_name.startswith("Generator_")
+
+    # Separate test cases for specific combinations
+    @pytest.mark.parametrize(
+        "sector, comp_type, voltage_level",
+        [
+            ("public", "charging_point", 8),
+            ("public", "failing_test", 6),
+            ("public", "failing_test", 8),
+        ],
+    )
+    def test_connect_to_lv_based_on_geolocation_value_error(
+        self, sector, comp_type, voltage_level
+    ):
+        x = self.edisgo3.topology.buses_df.at["Busbar_mvgd_33535_MV", "x"]
+        y = self.edisgo3.topology.buses_df.at["Busbar_mvgd_33535_MV", "y"]
+        test_cp = {
+            "p_set": 0.01,
+            "geom": Point((x, y)),
+            "sector": sector,
+            "voltage_level": voltage_level,
+            "mvlv_subst_id": 3.0,
+        }
+        with pytest.raises(ValueError):
+            self.edisgo3.topology.connect_to_lv_based_on_geolocation(
+                edisgo_object=self.edisgo3,
+                comp_data=test_cp,
+                comp_type=comp_type,
+                max_distance_from_target_bus=0.01,
+                allowed_number_of_comp_per_bus=2,
+                allow_mv_connection=False,
+            )

From f53063705e739ce83b2e35a18679cabd7301e1c0 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Tue, 20 Aug 2024 10:10:40 +0200
Subject: [PATCH 19/50] adding tests

---
 tests/network/test_topology.py | 77 +++++++++++++++++++++++++++++++++-
 1 file changed, 75 insertions(+), 2 deletions(-)

diff --git a/tests/network/test_topology.py b/tests/network/test_topology.py
index 751474620..f530b53e4 100644
--- a/tests/network/test_topology.py
+++ b/tests/network/test_topology.py
@@ -1933,7 +1933,7 @@ def test_check_integrity(self, caplog):
         "allowed_number_of_comp_per_bus", allowed_number_of_comp_per_bus_values
     )
     @pytest.mark.parametrize("allow_mv_connection", allow_mv_connection_values)
-    def test_connect_to_lv_based_on_geolocation(
+    def test_connect_to_lv_based_on_geolocation_parametrized(
         self,
         sector,
         comp_type,
@@ -2010,7 +2010,7 @@ def test_connect_to_lv_based_on_geolocation(
             ("public", "failing_test", 8),
         ],
     )
-    def test_connect_to_lv_based_on_geolocation_value_error(
+    def test_connect_to_lv_based_on_geolocation_value_error_parametrized(
         self, sector, comp_type, voltage_level
     ):
         x = self.edisgo3.topology.buses_df.at["Busbar_mvgd_33535_MV", "x"]
@@ -2031,3 +2031,76 @@ def test_connect_to_lv_based_on_geolocation_value_error(
                 allowed_number_of_comp_per_bus=2,
                 allow_mv_connection=False,
             )
+
+    # Define the parameters
+    comp_types = ["charging_point", "heat_pump", "storage_unit", "generator"]
+
+    @pytest.mark.parametrize("comp_type", comp_types)
+    def test_connect_to_lv_based_on_geolocation(self, comp_type):
+        x = self.edisgo3.topology.buses_df.at[
+            "BranchTee_mvgd_33535_lvgd_1150640000_building_430863", "x"
+        ]
+        y = self.edisgo3.topology.buses_df.at[
+            "BranchTee_mvgd_33535_lvgd_1150640000_building_430863", "y"
+        ]
+        test_cp = {
+            "p_set": 0.01,
+            "geom": Point((x, y)),
+            "sector": "home",
+            "voltage_level": 7,
+            "mvlv_subst_id": 3.0,
+        }
+        if comp_type == "generator":
+            test_cp["generator_type"] = "solar"
+            test_cp["generator_id"] = 23456
+        comp_name = self.edisgo3.topology.connect_to_lv_based_on_geolocation(
+            edisgo_object=self.edisgo3,
+            comp_data=test_cp,
+            comp_type=comp_type,
+            max_distance_from_target_bus=0.01,
+            allowed_number_of_comp_per_bus=2,
+            allow_mv_connection=False,
+        )
+
+        if comp_type == "charging_point":
+            assert comp_name.startswith("Charging_Point_")
+            assert (
+                self.edisgo3.topology.charging_points_df.at[comp_name, "bus"]
+                == "BranchTee_mvgd_33535_lvgd_1150640000_building_430863"
+            )
+            assert (
+                self.edisgo3.topology.charging_points_df.at[comp_name, "p_set"] == 0.01
+            )
+            assert (
+                self.edisgo3.topology.charging_points_df.at[comp_name, "sector"]
+                == "home"
+            )
+        elif comp_type == "heat_pump":
+            assert comp_name.startswith("Heat_Pump_")
+            assert (
+                self.edisgo3.topology.loads_df.at[comp_name, "bus"]
+                == "BranchTee_mvgd_33535_lvgd_1150640000_building_430863"
+            )
+            assert self.edisgo3.topology.loads_df.at[comp_name, "p_set"] == 0.01
+            assert self.edisgo3.topology.loads_df.at[comp_name, "type"] == "heat_pump"
+            assert self.edisgo3.topology.loads_df.at[comp_name, "sector"] == "home"
+        elif comp_type == "storage_unit":
+            assert comp_name.startswith("StorageUnit")
+            assert (
+                self.edisgo3.topology.storage_units_df.at[comp_name, "bus"]
+                == "BranchTee_mvgd_33535_lvgd_1150640000_building_430863"
+            )
+            assert (
+                self.edisgo3.topology.storage_units_df.at[comp_name, "control"] == "PQ"
+            )
+            assert self.edisgo3.topology.storage_units_df.at[comp_name, "p_nom"] == 0.01
+            assert self.edisgo3.topology.storage_units_df.at[comp_name, "p_set"] == 0.01
+        else:
+            assert comp_name.startswith("Generator_")
+            assert (
+                self.edisgo3.topology.generators_df.at[comp_name, "bus"]
+                == "BranchTee_mvgd_33535_lvgd_1150640000_building_430863"
+            )
+            assert self.edisgo3.topology.generators_df.at[comp_name, "type"] == "solar"
+            assert self.edisgo3.topology.generators_df.at[comp_name, "p_nom"] == 0.01
+            assert self.edisgo3.topology.generators_df.at[comp_name, "control"] == "PQ"

From 8dbf59f81596ddcfb96fb5178fd1b9437f0bdc4d Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Tue, 20 Aug 2024 14:07:52 +0200
Subject: [PATCH 20/50] fixing tests

---
 edisgo/network/topology.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 69216fe9f..5b677663d 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2483,7 +2483,10 @@ def connect_to_lv_based_on_geolocation(
         # Find the nearest substation or MV bus
         if voltage_level == 6:
             substations = self.buses_df.loc[self.transformers_df.bus1.unique()]
-            mv_buses = self.buses_df.loc[self.mv_grid.buses_df.index]
+            if comp_type == "charging_point":
+                mv_buses = self.buses_df.loc[self.mv_grid.buses_df.index]
+            else:
+                mv_buses = pd.DataFrame()
             substations = pd.concat([substations, mv_buses])
             target_bus, target_bus_distance = geo.find_nearest_bus(
                 geolocation, substations

From 00d3d582be292a4abc89ac1f74e6fdc79112d38d Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Wed, 21 Aug 2024 10:05:37 +0200
Subject: [PATCH 21/50] improve readability of
 'connect_to_lv_based_on_geolocation'

---
 edisgo/network/topology.py | 154 +++++++++++++++++--------------------
 1 file changed, 70 insertions(+), 84 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 5b677663d..cd9383864 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2441,72 +2441,52 @@ def connect_to_lv_based_on_geolocation(
                 is connected to a new LV bus.
         """
 
-        # Ensure 'p' is in comp_data, defaulting to 'p_set' or 'p_nom'
-        if "p" not in comp_data.keys():
-            comp_data["p"] = (
-                comp_data["p_set"]
-                if "p_set" in comp_data.keys()
-                else comp_data["p_nom"]
-            )
-
-        # Extract and validate voltage level
-        voltage_level = comp_data.pop("voltage_level")
-        if voltage_level not in [6, 7]:
-            raise ValueError(
-                f"Voltage level must either be 6 or 7 but given voltage level "
-                f"is {voltage_level}."
-            )
-        geolocation = comp_data.get("geom")
+        def validate_voltage_level(voltage_level):
+            if voltage_level not in [6, 7]:
+                raise ValueError(
+                    f"Voltage level must either be 6 or 7 but given voltage level "
+                    f"is {voltage_level}."
+                )
 
-        # Dictionary to map component types to their corresponding add functions
-        add_func_map = {
-            "generator": self.add_generator,
-            "charging_point": self.add_load,
-            "heat_pump": self.add_load,
-            "storage_unit": self.add_storage_unit,
-        }
-        # Set the component type in comp_data if necessary
-        if comp_type in ["charging_point", "heat_pump"]:
-            comp_data["type"] = comp_type
-        elif comp_type in ["generator", "storage_unit"]:
-            comp_data["p_nom"] = comp_data["p"]
+        def get_add_function(comp_type):
+            add_func_map = {
+                "generator": self.add_generator,
+                "charging_point": self.add_load,
+                "heat_pump": self.add_load,
+                "storage_unit": self.add_storage_unit,
+            }
+            add_func = add_func_map.get(comp_type)
+            if add_func is None:
+                raise ValueError(
+                    f"Provided component type {comp_type} is not valid. Must either be"
+                    f"'generator', 'charging_point', 'heat_pump' or 'storage_unit'."
+                )
+            return add_func
 
-        # Determine the appropriate add function based on component type
-        add_func = add_func_map.get(comp_type)
+        def find_nearest_bus(geolocation, buses):
+            return geo.find_nearest_bus(geolocation, buses)
 
-        if add_func is None:
-            raise ValueError(
-                f"Provided component type {comp_type} is not valid. Must either be"
-                f"'generator', 'charging_point', 'heat_pump' or 'storage_unit'."
+        def connect_to_lv_bus(edisgo_object, target_bus, comp_type, comp_data):
+            return self._connect_to_lv_bus(
+                edisgo_object, target_bus, comp_type, comp_data
             )
 
-        # Find the nearest substation or MV bus
-        if voltage_level == 6:
+        def handle_voltage_level_6():
             substations = self.buses_df.loc[self.transformers_df.bus1.unique()]
             if comp_type == "charging_point":
                 mv_buses = self.buses_df.loc[self.mv_grid.buses_df.index]
             else:
                 mv_buses = pd.DataFrame()
             substations = pd.concat([substations, mv_buses])
-            target_bus, target_bus_distance = geo.find_nearest_bus(
-                geolocation, substations
-            )
-            # Check distance from target bus
+            target_bus, target_bus_distance = find_nearest_bus(geolocation, substations)
             if target_bus_distance > max_distance_from_target_bus:
-                # If target bus is too far away, connect via a new bus
-                bus = self._connect_to_lv_bus(
-                    edisgo_object, target_bus, comp_type, comp_data
-                )
+                bus = connect_to_lv_bus(edisgo_object, target_bus, comp_type, comp_data)
             else:
-                # If target bus is close, connect directly to the target bus
                 bus = target_bus
-            comp_data.pop("geom")
-            comp_data.pop("p")
-            comp_name = add_func(bus=bus, **comp_data)
-        elif voltage_level == 7:
-            # For voltage level 7, find the nearest LV bus
+            return bus
+
+        def handle_voltage_level_7():
             if allow_mv_connection:
-                # find MV buses within the max distance
                 mv_buses = self.buses_df.loc[self.mv_grid.buses_df.index]
                 mv_buses = geo.calculate_distance_to_buses_df(mv_buses, geolocation)
                 mv_buses_masked = mv_buses.loc[
@@ -2531,31 +2511,20 @@ def connect_to_lv_based_on_geolocation(
                     lv_loads = lv_loads.loc[
                         ~lv_loads.bus.isin(self.mv_grid.buses_df.index)
                     ]
-
                 else:
-                    # public charging points should not be in buildings
                     lv_loads = self.loads_df
                     lv_loads = lv_loads.loc[
                         ~lv_loads.bus.isin(self.mv_grid.buses_df.index)
                     ]
                     lv_buses = lv_buses.loc[~lv_buses.in_building]
-
                 lv_buses = lv_buses.loc[lv_loads.bus]
 
-            # Calculate distances to LV buses
             lv_buses = geo.calculate_distance_to_buses_df(lv_buses, geolocation)
-
-            # Filter buses within the max distance
             lv_buses_masked = lv_buses.loc[
                 lv_buses.distance < max_distance_from_target_bus
             ].copy()
 
-            # Handle cases where no LV buses are within the max distance
             if len(lv_buses_masked) == 0:
-                # If MV connection is allowed, connect to the nearest MV bus
-                # if it is closer than the factor multiplied by the distance
-                # to the nearest LV bus and there are MV buses within the
-                # max distance
                 if (
                     allow_mv_connection
                     and len(mv_buses_masked) > 0
@@ -2566,14 +2535,8 @@ def connect_to_lv_based_on_geolocation(
                         mv_buses.distance == mv_buses.distance.min()
                     ]
                 else:
-                    # If no LV buses are within the max distance,
-                    # and MV connection is not allowed or no MV buses are
-                    # within the max distance, connect via a new bus
-                    target_bus = self._connect_to_lv_bus(
-                        edisgo_object=edisgo_object,
-                        target_bus=lv_buses.distance.idxmin(),
-                        comp_type=comp_type,
-                        comp_data=comp_data,
+                    target_bus = connect_to_lv_bus(
+                        edisgo_object, lv_buses.distance.idxmin(), comp_type, comp_data
                     )
                     lv_buses_masked = pd.DataFrame(self.buses_df.loc[target_bus]).T
                     lv_buses_masked["distance"] = 0
@@ -2583,8 +2546,6 @@ def connect_to_lv_based_on_geolocation(
 
             if not mv_buses_masked.empty:
                 target_bus = mv_buses_masked.loc[mv_buses_masked.distance.idxmin()]
-            # Check how many components of the same type are already connected
-            # to the target bus
             else:
                 comp_df = {
                     "charging_point": self.charging_points_df,
@@ -2598,21 +2559,16 @@ def connect_to_lv_based_on_geolocation(
                     .groupby("bus")
                     .size()
                 )
-
                 lv_buses_masked.loc[:, "num_comps"] = (
                     lv_buses_masked.index.map(comp_type_counts).fillna(0).astype(int)
                 )
-
                 lv_buses_masked = lv_buses_masked[
                     lv_buses_masked.num_comps == lv_buses_masked.num_comps.min()
                 ]
 
                 if lv_buses_masked.num_comps.min() >= allowed_number_of_comp_per_bus:
-                    target_bus = self._connect_to_lv_bus(
-                        edisgo_object=edisgo_object,
-                        target_bus=lv_buses.distance.idxmin(),
-                        comp_type=comp_type,
-                        comp_data=comp_data,
+                    target_bus = connect_to_lv_bus(
+                        edisgo_object, lv_buses.distance.idxmin(), comp_type, comp_data
                     )
                     lv_buses_masked = pd.DataFrame(self.buses_df.loc[target_bus]).T
                     lv_buses_masked["distance"] = 0
@@ -2621,12 +2577,42 @@ def connect_to_lv_based_on_geolocation(
                 target_bus = lv_buses_masked.loc[lv_buses_masked.distance.idxmin()]
                 if isinstance(target_bus, pd.DataFrame):
                     target_bus = target_bus.iloc[0]
-            # Remove unnecessary keys from comp_data
-            comp_data.pop("geom")
-            comp_data.pop("p")
+            return target_bus.name
+
+        # Ensure 'p' is in comp_data, defaulting to 'p_set' or 'p_nom'
+        if "p" not in comp_data.keys():
+            comp_data["p"] = (
+                comp_data["p_set"]
+                if "p_set" in comp_data.keys()
+                else comp_data["p_nom"]
+            )
+
+        # Extract and validate voltage level
+        voltage_level = comp_data.pop("voltage_level")
+        validate_voltage_level(voltage_level)
+        geolocation = comp_data.get("geom")
+
+        # Determine the appropriate add function based on component type
+        add_func = get_add_function(comp_type)
+
+        # Set the component type in comp_data if necessary
+        if comp_type in ["charging_point", "heat_pump"]:
+            comp_data["type"] = comp_type
+        elif comp_type in ["generator", "storage_unit"]:
+            comp_data["p_nom"] = comp_data["p"]
+
+        # Handle different voltage levels
+        if voltage_level == 6:
+            bus = handle_voltage_level_6()
+        elif voltage_level == 7:
+            bus = handle_voltage_level_7()
+
+        # Remove unnecessary keys from comp_data
+        comp_data.pop("geom")
+        comp_data.pop("p")
 
-            # Add the component to the grid
-            comp_name = add_func(bus=target_bus.name, **comp_data)
+        # Add the component to the grid
+        comp_name = add_func(bus=bus, **comp_data)
 
         return comp_name
 

From 962c9e387d9bec405b99264602fcaa4d93f57c32 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Wed, 21 Aug 2024 10:27:15 +0200
Subject: [PATCH 22/50] fixing pandas warning

---
 edisgo/io/ding0_import.py | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/edisgo/io/ding0_import.py b/edisgo/io/ding0_import.py
index 326553111..7e9a5e7df 100644
--- a/edisgo/io/ding0_import.py
+++ b/edisgo/io/ding0_import.py
@@ -72,8 +72,12 @@ def sort_hvmv_transformer_buses(transformers_df):
     grid.import_from_csv_folder(path)
 
     # write dataframes to edisgo_obj
-    edisgo_obj.topology.buses_df = grid.buses[edisgo_obj.topology.buses_df.columns]
-    edisgo_obj.topology.lines_df = grid.lines[edisgo_obj.topology.lines_df.columns]
+    edisgo_obj.topology.buses_df = grid.buses[
+        edisgo_obj.topology.buses_df.columns
+    ].copy()
+    edisgo_obj.topology.lines_df = grid.lines[
+        edisgo_obj.topology.lines_df.columns
+    ].copy()
     if legacy_ding0_grids:
         logger.debug("Use ding0 legacy grid import.")
         # rename column peak_load to p_set

From 5e72bccf579e25a6df73622ea8e4ca88cfa97742 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Wed, 21 Aug 2024 11:50:53 +0200
Subject: [PATCH 23/50] using .loc instead of .copy

---
 edisgo/io/ding0_import.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/edisgo/io/ding0_import.py b/edisgo/io/ding0_import.py
index 7e9a5e7df..d14ee5046 100644
--- a/edisgo/io/ding0_import.py
+++ b/edisgo/io/ding0_import.py
@@ -72,12 +72,12 @@ def sort_hvmv_transformer_buses(transformers_df):
     grid.import_from_csv_folder(path)
 
     # write dataframes to edisgo_obj
-    edisgo_obj.topology.buses_df = grid.buses[
-        edisgo_obj.topology.buses_df.columns
-    ].copy()
-    edisgo_obj.topology.lines_df = grid.lines[
-        edisgo_obj.topology.lines_df.columns
-    ].copy()
+    edisgo_obj.topology.buses_df = grid.buses.loc[
+        :, edisgo_obj.topology.buses_df.columns
+    ]
+    edisgo_obj.topology.lines_df = grid.lines.loc[
+        :, edisgo_obj.topology.lines_df.columns
+    ]
     if legacy_ding0_grids:
         logger.debug("Use ding0 legacy grid import.")
         # rename column peak_load to p_set

From 5ab74f454b79f71d5c8b17e2dfafea05395ed7ef Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Fri, 23 Aug 2024 11:03:04 +0200
Subject: [PATCH 24/50] Refactor calculate_distance_to_buses_df arguments for
 consistency

---
 edisgo/network/topology.py |  4 ++--
 edisgo/tools/geo.py        | 33 +++++++++++++++++++--------------
 2 files changed, 21 insertions(+), 16 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index cd9383864..3066022ff 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2488,7 +2488,7 @@ def handle_voltage_level_6():
         def handle_voltage_level_7():
             if allow_mv_connection:
                 mv_buses = self.buses_df.loc[self.mv_grid.buses_df.index]
-                mv_buses = geo.calculate_distance_to_buses_df(mv_buses, geolocation)
+                mv_buses = geo.calculate_distance_to_buses_df(geolocation, mv_buses)
                 mv_buses_masked = mv_buses.loc[
                     mv_buses.distance < max_distance_from_target_bus
                 ]
@@ -2519,7 +2519,7 @@ def handle_voltage_level_7():
                     lv_buses = lv_buses.loc[~lv_buses.in_building]
                 lv_buses = lv_buses.loc[lv_loads.bus]
 
-            lv_buses = geo.calculate_distance_to_buses_df(lv_buses, geolocation)
+            lv_buses = geo.calculate_distance_to_buses_df(geolocation, lv_buses)
             lv_buses_masked = lv_buses.loc[
                 lv_buses.distance < max_distance_from_target_bus
             ].copy()
diff --git a/edisgo/tools/geo.py b/edisgo/tools/geo.py
index 55d6523cc..115877369 100755
--- a/edisgo/tools/geo.py
+++ b/edisgo/tools/geo.py
@@ -329,28 +329,33 @@ def mv_grid_gdf(edisgo_obj: EDisGo):
     )
 
 
-def calculate_distance_to_buses_df(bus_df: pd.DataFrame, geom: Point) -> pd.DataFrame:
+def calculate_distance_to_buses_df(
+    point: Point, busses_df: pd.DataFrame
+) -> pd.DataFrame:
     """
-    Calculate the distance between a bus and a given geometry.
+    Calculate the distance between buses and a given geometry.
 
     Parameters
     ----------
-    bus_df : pandas.DataFrame
-        Data of bus.
-        DataFrame has same rows as columns of
+    point : :shapely:`shapely.Point<Point>`
+        Geolocation to calculate distance to.
+    busses_df : :pandas:`pandas.DataFrame<DataFrame>`
+        Dataframe with buses and their positions given in 'x' and 'y'
+        columns. The dataframe has the same format as
         :attr:`~.network.topology.Topology.buses_df`.
-    geom : shapely.geometry.Point
-        Geometry to calculate distance to.
 
     Returns
     -------
-    pandas.DataFrame
-        Data of bus with additional column 'distance' containing the distance
-        to the given geometry
+    :pandas:`pandas.DataFrame<DataFrame>`
+        Data of `bus_df` with additional column 'distance' containing the distance
+        to the given geometry in km.
+
     """
-    distances = bus_df.apply(
-        lambda row: geopy.distance.distance((row["x"], row["y"]), (geom.x, geom.y)).km,
+    distances = busses_df.apply(
+        lambda row: geopy.distance.distance(
+            (row["x"], row["y"]), (point.x, point.y)
+        ).km,
         axis=1,
     )
-    bus_df.loc[:, "distance"] = distances
-    return bus_df
+    busses_df.loc[:, "distance"] = distances
+    return busses_df

From 9fca33a478d8313338382ff80ceec7fddcb2777b Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Mon, 26 Aug 2024 11:55:50 +0200
Subject: [PATCH 25/50] fixing failing tests

---
 doc/whatsnew/v0-3-0.rst                     |   1 +
 edisgo/config/config_grid_default.cfg       |   5 +
 edisgo/config/config_timeseries_default.cfg |  42 +--
 edisgo/flex_opt/q_control.py                |  66 ++---
 edisgo/io/powermodels_io.py                 |  39 ++-
 edisgo/io/timeseries_import.py              |   4 +
 edisgo/network/grids.py                     |  16 +-
 edisgo/network/overlying_grid.py            |  10 +
 edisgo/network/timeseries.py                |  40 +--
 edisgo/network/topology.py                  | 128 ++++++++--
 edisgo/opf/eDisGo_OPF.jl/src/core/data.jl   |   3 -
 edisgo/opf/eDisGo_OPF.jl/src/form/bf.jl     |  17 ++
 edisgo/opf/powermodels_opf.py               |  24 +-
 edisgo/tools/config.py                      |   2 +-
 edisgo/tools/geopandas_helper.py            |  47 ++--
 edisgo/tools/tools.py                       | 227 +++++++++++++++--
 rtd_requirements.txt                        |   2 +-
 setup.py                                    |   2 +-
 tests/flex_opt/test_q_control.py            |  20 +-
 tests/io/test_powermodels_io.py             |   6 +-
 tests/io/test_timeseries_import.py          |  10 +-
 tests/network/test_timeseries.py            |   8 +-
 tests/network/test_topology.py              |  67 ++++-
 tests/tools/test_geopandas_helper.py        |  65 +++++
 tests/tools/test_tools.py                   | 269 +++++++++++++++++++-
 25 files changed, 891 insertions(+), 229 deletions(-)
 create mode 100644 tests/tools/test_geopandas_helper.py

diff --git a/doc/whatsnew/v0-3-0.rst b/doc/whatsnew/v0-3-0.rst
index 7cb375542..7cf4110a1 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 f48c68d8f..1cd829259 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 bfb97351c..84c6074bc 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]
 
@@ -129,6 +129,8 @@ week_day = 0.8
 week_night = 0.6
 weekend_day = 0.6
 weekend_night = 0.6
+holiday_day = 0.6
+holiday_night = 0.6
 # tuple specifying the beginning/end of a workday (e.g. 18:00)
 day_start = 6:00
 day_end = 22:00
diff --git a/edisgo/flex_opt/q_control.py b/edisgo/flex_opt/q_control.py
index a6e98578e..07183a7d5 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 b0fb22781..541e01ffc 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/io/timeseries_import.py b/edisgo/io/timeseries_import.py
index 5d154b965..6c66f33af 100644
--- a/edisgo/io/timeseries_import.py
+++ b/edisgo/io/timeseries_import.py
@@ -297,6 +297,10 @@ def load_time_series_demandlib(edisgo_obj, timeindex=None):
                 "day": edisgo_obj.config["demandlib"]["weekend_day"],
                 "night": edisgo_obj.config["demandlib"]["weekend_night"],
             },
+            "holiday": {
+                "day": edisgo_obj.config["demandlib"]["holiday_day"],
+                "night": edisgo_obj.config["demandlib"]["holiday_night"],
+            },
         },
     )
 
diff --git a/edisgo/network/grids.py b/edisgo/network/grids.py
index 7f466b3dc..20d50ed74 100644
--- a/edisgo/network/grids.py
+++ b/edisgo/network/grids.py
@@ -90,20 +90,19 @@ def graph(self):
     @property
     def geopandas(self):
         """
-        Returns components as :geopandas:`GeoDataFrame`\\ s
+        Returns components as :geopandas:`GeoDataFrame`\\ s.
 
         Returns container with :geopandas:`GeoDataFrame`\\ s containing all
         georeferenced components within the grid.
 
         Returns
         -------
-        :class:`~.tools.geopandas_helper.GeoPandasGridContainer` or \
-            list(:class:`~.tools.geopandas_helper.GeoPandasGridContainer`)
+        :class:`~.tools.geopandas_helper.GeoPandasGridContainer`
             Data container with GeoDataFrames containing all georeferenced components
-            within the grid(s).
+            within the grid.
 
         """
-        return to_geopandas(self)
+        return to_geopandas(self, srid=self.edisgo_obj.topology.grid_district["srid"])
 
     @property
     def station(self):
@@ -650,10 +649,3 @@ def draw(
         else:
             plt.savefig(filename, dpi=150, bbox_inches="tight", pad_inches=0.1)
             plt.close()
-
-    @property
-    def geopandas(self):
-        """
-        TODO: Remove this as soon as LVGrids are georeferenced
-        """
-        raise NotImplementedError("LV Grids are not georeferenced yet.")
diff --git a/edisgo/network/overlying_grid.py b/edisgo/network/overlying_grid.py
index 77c9ccadd..241768a20 100644
--- a/edisgo/network/overlying_grid.py
+++ b/edisgo/network/overlying_grid.py
@@ -385,6 +385,16 @@ def distribute_overlying_grid_requirements(edisgo_obj):
             scaling_df_min = (
                 edisgo_obj.dsm.p_min.transpose() / edisgo_obj.dsm.p_min.sum(axis=1)
             )
+            # in case p_max/p_min of all DSM loads is zero in an hour but there is
+            # positive/negative DSM from the overlying grid, this is not correctly
+            # distributed and may lead to large errors in the time series with the
+            # distributed DSM
+            # in the following this is corrected by assuming an equal distribution
+            # during those hours
+            equal_dist_factor = 1 / len(dsm_loads)
+            scaling_df_max.fillna(equal_dist_factor, inplace=True)
+            scaling_df_min.fillna(equal_dist_factor, inplace=True)
+
             edisgo_copy.timeseries._loads_active_power.loc[:, dsm_loads] = (
                 edisgo_obj.timeseries._loads_active_power.loc[:, dsm_loads]
                 + (
diff --git a/edisgo/network/timeseries.py b/edisgo/network/timeseries.py
index 15337f06b..6cf4a7b47 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 3066022ff..5a1c10bb3 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -5,6 +5,7 @@
 import random
 import warnings
 
+from typing import TYPE_CHECKING
 from zipfile import ZipFile
 
 import networkx as nx
@@ -15,7 +16,7 @@
 
 from edisgo.network.components import Switch
 from edisgo.network.grids import LVGrid, MVGrid
-from edisgo.tools import geo, networkx_helper
+from edisgo.tools import geo, geopandas_helper, networkx_helper
 from edisgo.tools.tools import (
     calculate_apparent_power,
     calculate_line_reactance,
@@ -30,6 +31,9 @@
     from shapely.ops import transform
     from shapely.wkt import loads as wkt_loads
 
+if TYPE_CHECKING:
+    from edisgo.tools.geopandas_helper import GeoPandasGridContainer
+
 logger = logging.getLogger(__name__)
 
 COLUMNS = {
@@ -1928,7 +1932,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 +1985,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:
@@ -2617,7 +2626,12 @@ def handle_voltage_level_7():
         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).
@@ -2646,11 +2660,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
         -------
@@ -2767,6 +2782,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
@@ -2775,8 +2797,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(
@@ -2793,7 +2815,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,
@@ -2802,6 +2824,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
@@ -2811,8 +2840,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
@@ -2890,7 +2919,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,
@@ -2925,7 +2960,9 @@ def to_graph(self):
             self.transformers_df,
         )
 
-    def to_geopandas(self, mode: str = "mv"):
+    def to_geopandas(
+        self, mode: str | None = None, lv_grid_id: int | None = None
+    ) -> GeoPandasGridContainer:
         """
         Returns components as :geopandas:`GeoDataFrame`\\ s.
 
@@ -2935,23 +2972,29 @@ def to_geopandas(self, mode: str = "mv"):
         Parameters
         ----------
         mode : str
-            Return mode. If mode is "mv" the mv components are returned. If mode is "lv"
-            a generator with a container per lv grid is returned. Default: "mv"
+            If `mode` is None, GeoDataFrames for the MV grid and underlying LV grids is
+            returned. If `mode` is "mv", GeoDataFrames for only the MV grid are
+            returned. If `mode` is "lv", GeoDataFrames for the LV grid specified through
+            `lv_grid_id` are returned.
+            Default: None.
+        lv_grid_id : int
+            Only needs to be provided in case `mode` is "lv". In that case `lv_grid_id`
+            gives the LV grid ID as integer of the LV grid for which to return the
+            geodataframes.
 
         Returns
         -------
-        :class:`~.tools.geopandas_helper.GeoPandasGridContainer` or \
-            list(:class:`~.tools.geopandas_helper.GeoPandasGridContainer`)
+        :class:`~.tools.geopandas_helper.GeoPandasGridContainer`
             Data container with GeoDataFrames containing all georeferenced components
-            within the grid(s).
+            within the grid.
 
         """
-        if mode == "mv":
+        if mode is None:
+            return geopandas_helper.to_geopandas(self, srid=self.grid_district["srid"])
+        elif mode == "mv":
             return self.mv_grid.geopandas
         elif mode == "lv":
-            raise NotImplementedError("LV Grids are not georeferenced yet.")
-            # for lv_grid in self.mv_grid.lv_grids:
-            #     yield lv_grid.geopandas
+            return self.get_lv_grid(name=lv_grid_id).geopandas
         else:
             raise ValueError(f"{mode} is not valid. See docstring for more info.")
 
@@ -3261,6 +3304,9 @@ def check_integrity(self):
                 f"optimisation."
             )
 
+        # check for meshed grid
+        self.find_meshes()
+
     def assign_feeders(self, mode: str = "grid_feeder"):
         """
         Assigns MV or LV feeder to each bus and line, depending on the `mode`.
@@ -3329,3 +3375,31 @@ def aggregate_lv_grid_at_station(self, lv_grid_id: int | str) -> None:
 
     def __repr__(self):
         return f"Network topology {self.id}"
+
+    def find_meshes(edisgo_obj) -> list[list[int]] | None:
+        """
+        Find all meshes in the grid.
+
+        Parameters
+        ----------
+        edisgo_obj : EDisGo
+            EDisGo object.
+
+        Returns
+        -------
+        Optional[List[List[int]]]
+            List of all meshes in the grid.
+            Each mesh is represented as a list of node indices.
+            If no meshes are found, None is returned.
+        """
+        meshes = nx.cycle_basis(edisgo_obj.to_graph())
+        if meshes:
+            logger.warning(
+                "Grid contains mesh(es). Be aware, that the grid expansion methodology "
+                "is currently not able to handle meshes. Further, the optimisation of "
+                "flexibility dispatch is not exact in case of meshed grids, but can "
+                "still be used."
+            )
+            return meshes
+        else:
+            return None
diff --git a/edisgo/opf/eDisGo_OPF.jl/src/core/data.jl b/edisgo/opf/eDisGo_OPF.jl/src/core/data.jl
index 1063cc1f4..f180f831a 100644
--- a/edisgo/opf/eDisGo_OPF.jl/src/core/data.jl
+++ b/edisgo/opf/eDisGo_OPF.jl/src/core/data.jl
@@ -1,7 +1,4 @@
 function set_ac_bf_start_values!(network::Dict{String,<:Any})
-    for (i,bus) in network["bus"]
-        bus["w_start"] = bus["w"]
-    end
 
     for (i,gen) in network["gen_nd"]
         gen["pgc_start"] = gen["pgc"]
diff --git a/edisgo/opf/eDisGo_OPF.jl/src/form/bf.jl b/edisgo/opf/eDisGo_OPF.jl/src/form/bf.jl
index e1db2d1bb..1245fe7bf 100644
--- a/edisgo/opf/eDisGo_OPF.jl/src/form/bf.jl
+++ b/edisgo/opf/eDisGo_OPF.jl/src/form/bf.jl
@@ -118,6 +118,23 @@ function constraint_max_line_loading(pm::AbstractSOCBFModelEdisgo, n::Int)
 end
 
 
+function constraint_max_line_loading(pm::AbstractNCBFModelEdisgo, n::Int)
+    p  = PowerModels.var(pm, n, :p)
+    q  = PowerModels.var(pm, n, :q)
+    ll = PowerModels.var(pm, 1, :ll)
+    s_nom = Dict(i => get(branch, "rate_a", 1.0) for (i,branch) in PowerModels.ref(pm, n, :branch))
+
+    for (i,branch) in PowerModels.ref(pm, n, :branch)
+        f_bus = branch["f_bus"]
+        t_bus = branch["t_bus"]
+        f_idx = (i, f_bus, t_bus)
+        if !(branch["storage"])
+            JuMP.@constraint(pm.model, (p[f_idx]^2 + q[f_idx]^2)/s_nom[i]^2 <= ll[f_idx])
+        end
+    end
+end
+
+
 function constraint_power_balance(pm::AbstractBFModelEdisgo, n::Int, i, bus_gens, bus_gens_nd, bus_gens_slack, bus_loads, bus_arcs_to, bus_arcs_from, bus_lines_to, bus_storage, bus_pg, bus_qg, bus_pg_nd, bus_qg_nd, bus_pd, bus_qd, branch_r, branch_x, bus_dsm, bus_hps, bus_cps, bus_storage_pf, bus_dsm_pf, bus_hps_pf, bus_cps_pf, bus_gen_nd_pf, bus_gen_d_pf, bus_loads_pf, branch_strg_pf)
     pt   = get(PowerModels.var(pm, n),  :p, Dict()); PowerModels._check_var_keys(pt, bus_arcs_to, "active power", "branch")
     qt   = get(PowerModels.var(pm, n),  :q, Dict()); PowerModels._check_var_keys(qt, bus_arcs_to, "reactive power", "branch")
diff --git a/edisgo/opf/powermodels_opf.py b/edisgo/opf/powermodels_opf.py
index db4925d3b..85da160a8 100644
--- a/edisgo/opf/powermodels_opf.py
+++ b/edisgo/opf/powermodels_opf.py
@@ -4,26 +4,29 @@
 import subprocess
 import sys
 
+from typing import Optional
+
 import numpy as np
 
 from edisgo.flex_opt import exceptions
 from edisgo.io.powermodels_io import from_powermodels
+from edisgo.network.topology import Topology
 
 logger = logging.getLogger(__name__)
 
 
 def pm_optimize(
     edisgo_obj,
-    s_base=1,
-    flexible_cps=None,
-    flexible_hps=None,
-    flexible_loads=None,
-    flexible_storage_units=None,
-    opf_version=1,
-    method="soc",
-    warm_start=False,
-    silence_moi=False,
-):
+    s_base: int = 1,
+    flexible_cps: Optional[np.ndarray] = None,
+    flexible_hps: Optional[np.ndarray] = None,
+    flexible_loads: Optional[np.ndarray] = None,
+    flexible_storage_units: Optional[np.ndarray] = None,
+    opf_version: int = 1,
+    method: str = "soc",
+    warm_start: bool = False,
+    silence_moi: bool = False,
+) -> None:
     """
     Run OPF for edisgo object in julia subprocess and write results of OPF to edisgo
     object. Results of OPF are time series of operation schedules of flexibilities.
@@ -105,6 +108,7 @@ def pm_optimize(
         Default: True.
 
     """
+    Topology.find_meshes(edisgo_obj)
     opf_dir = os.path.dirname(os.path.abspath(__file__))
     solution_dir = os.path.join(opf_dir, "opf_solutions")
     pm, hv_flex_dict = edisgo_obj.to_powermodels(
diff --git a/edisgo/tools/config.py b/edisgo/tools/config.py
index 54fc08a33..7494943a3 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/geopandas_helper.py b/edisgo/tools/geopandas_helper.py
index 6c48a62e6..14066d560 100644
--- a/edisgo/tools/geopandas_helper.py
+++ b/edisgo/tools/geopandas_helper.py
@@ -11,6 +11,7 @@
 
 if TYPE_CHECKING:
     from edisgo.network.grids import Grid
+    from edisgo.network.topology import Topology
 
 COMPONENTS: list[str] = [
     "generators_df",
@@ -162,14 +163,17 @@ def plot(self):
             raise NotImplementedError
 
 
-def to_geopandas(grid_obj: Grid):
+def to_geopandas(grid_obj: Grid | Topology, srid: int) -> GeoPandasGridContainer:
     """
-    Translates all DataFrames with geolocations within a Grid class to GeoDataFrames.
+    Translates all DataFrames with geolocations within a grid topology to GeoDataFrames.
 
     Parameters
     ----------
-    grid_obj : :class:`~.network.grids.Grid`
-        Grid object to transform.
+    grid_obj : :class:`~.network.grids.Grid` or :class:`~.network.topology.Topology`
+        Grid or Topology object to transform.
+    srid : int
+        SRID (spatial reference ID) of x and y coordinates of buses. Usually given in
+        Topology.grid_district["srid"].
 
     Returns
     -------
@@ -178,9 +182,6 @@ def to_geopandas(grid_obj: Grid):
         their geolocation.
 
     """
-    # get srid id
-    srid = grid_obj._edisgo_obj.topology.grid_district["srid"]
-
     # convert buses_df
     buses_df = grid_obj.buses_df
     buses_df = buses_df.assign(
@@ -204,25 +205,27 @@ def to_geopandas(grid_obj: Grid):
             crs=f"EPSG:{srid}",
         )
         if components_dict[component.replace("_df", "_gdf")].empty:
-            components_dict[component.replace("_df", "_gdf")].index = components_dict[
-                component.replace("_df", "_gdf")
-            ].index.astype(object)
+            components_dict[component.replace("_df", "_gdf")].index = attr.index
 
     # convert lines_df
     lines_df = grid_obj.lines_df
 
-    geom_0 = lines_df.merge(
-        buses_gdf[["geometry"]], left_on="bus0", right_index=True
-    ).geometry
-    geom_1 = lines_df.merge(
-        buses_gdf[["geometry"]], left_on="bus1", right_index=True
-    ).geometry
-
-    geometry = [
-        LineString([point_0, point_1]) for point_0, point_1 in list(zip(geom_0, geom_1))
-    ]
-
-    lines_gdf = gpd.GeoDataFrame(lines_df.assign(geometry=geometry), crs=f"EPSG:{srid}")
+    lines_gdf = lines_df.merge(
+        buses_gdf[["geometry", "v_nom"]].rename(columns={"geometry": "geom_0"}),
+        left_on="bus0",
+        right_index=True,
+    )
+    lines_gdf = lines_gdf.merge(
+        buses_gdf[["geometry"]].rename(columns={"geometry": "geom_1"}),
+        left_on="bus1",
+        right_index=True,
+    )
+    lines_gdf["geometry"] = lines_gdf.apply(
+        lambda _: LineString([_["geom_0"], _["geom_1"]]), axis=1
+    )
+    lines_gdf = gpd.GeoDataFrame(
+        lines_gdf.drop(columns=["geom_0", "geom_1"]), crs=f"EPSG:{srid}"
+    )
 
     return GeoPandasGridContainer(
         crs=f"EPSG:{srid}",
diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py
index d05fe1b86..061e02ed5 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 "
diff --git a/rtd_requirements.txt b/rtd_requirements.txt
index 3900ea862..3344a499e 100644
--- a/rtd_requirements.txt
+++ b/rtd_requirements.txt
@@ -1,5 +1,5 @@
 dash < 2.9.0
-demandlib < 0.2.0
+demandlib
 egoio >= 0.4.7
 geopy >= 2.0.0
 jupyter_dash
diff --git a/setup.py b/setup.py
index f04a4b97c..082fbe8b3 100644
--- a/setup.py
+++ b/setup.py
@@ -33,7 +33,7 @@ def read(fname):
 requirements = [
     "contextily",
     "dash < 2.9.0",
-    "demandlib < 0.2.0",
+    "demandlib",
     "descartes",
     "egoio >= 0.4.7",
     "geoalchemy2 < 0.7.0",
diff --git a/tests/flex_opt/test_q_control.py b/tests/flex_opt/test_q_control.py
index 9595ec1c6..a028c1544 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 4d0d7842a..b3bfab036 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/io/test_timeseries_import.py b/tests/io/test_timeseries_import.py
index 17340163b..c3e0662d3 100644
--- a/tests/io/test_timeseries_import.py
+++ b/tests/io/test_timeseries_import.py
@@ -89,16 +89,20 @@ def test_feedin_oedb(self):
 
     def test_load_time_series_demandlib(self):
         edisgo = EDisGo(ding0_grid=pytest.ding0_test_network_path)
-        timeindex = pd.date_range("1/1/2018", periods=7000, freq="H")
+        timeindex = pd.date_range("1/1/2018", periods=8760, freq="H")
         load = timeseries_import.load_time_series_demandlib(edisgo, timeindex)
         assert (
             load.columns == ["cts", "residential", "agricultural", "industrial"]
         ).all()
-        assert len(load) == 7000
+        assert len(load) == 8760
         assert np.isclose(load.loc[timeindex[453], "cts"], 8.33507e-05)
         assert np.isclose(load.loc[timeindex[13], "residential"], 1.73151e-04)
         assert np.isclose(load.loc[timeindex[6328], "agricultural"], 1.01346e-04)
-        assert np.isclose(load.loc[timeindex[4325], "industrial"], 9.91768e-05)
+        assert np.isclose(load.loc[timeindex[4325], "industrial"], 9.91768322919766e-05)
+        assert np.isclose(load.sum()["cts"], 1.0)
+        assert np.isclose(load.sum()["residential"], 1.0)
+        assert np.isclose(load.sum()["agricultural"], 1.0)
+        assert np.isclose(load.sum()["industrial"], 1.0)
 
     @pytest.mark.local
     def test_cop_oedb(self):
diff --git a/tests/network/test_timeseries.py b/tests/network/test_timeseries.py
index 4666a836a..c744af3fe 100644
--- a/tests/network/test_timeseries.py
+++ b/tests/network/test_timeseries.py
@@ -1565,9 +1565,9 @@ def test_predefined_conventional_loads_by_sector(self, caplog):
             index=index,
             columns=["cts", "residential", "agricultural", "industrial"],
             data=[
-                [0.0000597, 0.0000782, 0.0000654, 0.0000992],
-                [0.0000526, 0.0000563, 0.0000611, 0.0000992],
-                [0.0000459, 0.0000451, 0.0000585, 0.0000992],
+                [0.000059711, 0.0000782190, 0.00006540, 0.000099176],
+                [0.000052590, 0.0000563428, 0.00006110, 0.000099176],
+                [0.000045927, 0.0000451043, 0.00005843, 0.000099176],
             ],
         )
 
@@ -1656,7 +1656,7 @@ def test_predefined_conventional_loads_by_sector(self, caplog):
             self.edisgo.timeseries.loads_active_power[
                 "Load_industrial_LVGrid_6_1"
             ].values,
-            [0.05752256] * 3,
+            [0.05752256272934643] * 3,
         ).all()
         assert np.isclose(
             self.edisgo.timeseries.loads_active_power.loc[
diff --git a/tests/network/test_topology.py b/tests/network/test_topology.py
index f530b53e4..62e04ee4c 100644
--- a/tests/network/test_topology.py
+++ b/tests/network/test_topology.py
@@ -955,9 +955,17 @@ def setup_class(self):
         self.edisgo3.set_time_series_worst_case_analysis()
 
     def test_to_geopandas(self):
-        geopandas_container = self.edisgo.topology.to_geopandas()
+        # further tests of to_geopandas are conducted in test_geopandas_helper.py
 
-        assert isinstance(geopandas_container, GeoPandasGridContainer)
+        # set up edisgo object with georeferenced LV
+        edisgo_geo = EDisGo(
+            ding0_grid=pytest.ding0_test_network_3_path, legacy_ding0_grids=False
+        )
+        test_suits = {
+            "mv": {"edisgo_obj": self.edisgo, "mode": "mv", "lv_grid_id": None},
+            "lv": {"edisgo_obj": edisgo_geo, "mode": "lv", "lv_grid_id": 1164120002},
+            "mv+lv": {"edisgo_obj": edisgo_geo, "mode": None, "lv_grid_id": None},
+        }
 
         attrs = [
             "buses_gdf",
@@ -968,19 +976,30 @@ def test_to_geopandas(self):
             "transformers_gdf",
         ]
 
-        for attr_str in attrs:
-            attr = getattr(geopandas_container, attr_str)
-            grid_attr = getattr(
-                self.edisgo.topology.mv_grid, attr_str.replace("_gdf", "_df")
+        for test_suit, params in test_suits.items():
+            # call to_geopandas() function with different settings
+            geopandas_container = params["edisgo_obj"].topology.to_geopandas(
+                mode=params["mode"], lv_grid_id=params["lv_grid_id"]
             )
 
-            assert isinstance(attr, GeoDataFrame)
+            assert isinstance(geopandas_container, GeoPandasGridContainer)
 
-            common_cols = list(set(attr.columns).intersection(grid_attr.columns))
+            # check that content of geodataframes is the same as content of original
+            # dataframes
+            for attr_str in attrs:
+                grid = getattr(geopandas_container, "grid")
+                attr = getattr(geopandas_container, attr_str)
+                grid_attr = getattr(grid, attr_str.replace("_gdf", "_df"))
 
-            assert_frame_equal(
-                attr[common_cols], grid_attr[common_cols], check_names=False
-            )
+                assert isinstance(attr, GeoDataFrame)
+
+                common_cols = list(set(attr.columns).intersection(grid_attr.columns))
+
+                assert_frame_equal(
+                    attr[common_cols].sort_index(),
+                    grid_attr[common_cols].sort_index(),
+                    check_names=False,
+                )
 
     def test_from_csv(self):
         """
@@ -1724,7 +1743,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,
@@ -1755,7 +1774,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)
@@ -1914,6 +1933,28 @@ def test_check_integrity(self, caplog):
         assert "Very small values for impedance of lines" and line in caplog.text
         caplog.clear()
 
+    def test_find_meshes(self, caplog: pytest.LogCaptureFixture):
+        meshes = Topology.find_meshes(self.edisgo)
+        assert not meshes
+        self.edisgo.topology.add_line(
+            "Bus_GeneratorFluctuating_2",
+            "Bus_GeneratorFluctuating_6",
+            0.1,
+            x=0.1,
+            r=0.1,
+        )
+        meshes = Topology.find_meshes(self.edisgo)
+        assert len(meshes) == 1
+        assert "Bus_GeneratorFluctuating_2" in meshes[0]
+        assert "Bus_GeneratorFluctuating_6" in meshes[0]
+        self.edisgo.topology.add_line(
+            "Bus_BranchTee_LVGrid_2_3", "Bus_BranchTee_LVGrid_3_3", 0.1, x=0.1, r=0.1
+        )
+        meshes = Topology.find_meshes(self.edisgo)
+        assert len(meshes) == 2
+        assert "Bus_BranchTee_LVGrid_2_3" in meshes[1]
+        assert "Grid contains mesh(es)." in caplog.text
+
     # Define the parameters
     sector_values = ["home", "work"]
     comp_type_values = ["charging_point", "heat_pump", "storage_unit", "generator"]
diff --git a/tests/tools/test_geopandas_helper.py b/tests/tools/test_geopandas_helper.py
new file mode 100644
index 000000000..a9aca9427
--- /dev/null
+++ b/tests/tools/test_geopandas_helper.py
@@ -0,0 +1,65 @@
+import pytest
+
+from edisgo import EDisGo
+from edisgo.tools import geopandas_helper
+
+
+class TestGeopandasHelper:
+    @classmethod
+    def setup_class(self):
+        self.edisgo = EDisGo(ding0_grid=pytest.ding0_test_network_path)
+
+    def test_to_geopandas(self):
+        # further tests of this function are conducted in test_topology.py
+        # test MV grid
+        data = geopandas_helper.to_geopandas(self.edisgo.topology.mv_grid, 4326)
+        assert data.buses_gdf.shape[0] == self.edisgo.topology.mv_grid.buses_df.shape[0]
+        assert (
+            data.buses_gdf.shape[1]
+            == self.edisgo.topology.mv_grid.buses_df.shape[1] + 1 - 2
+        )
+        assert "geometry" in data.buses_gdf.columns
+
+        assert data.lines_gdf.shape[0] == self.edisgo.topology.mv_grid.lines_df.shape[0]
+        assert (
+            data.lines_gdf.shape[1]
+            == self.edisgo.topology.mv_grid.lines_df.shape[1] + 2
+        )
+        assert "geometry" in data.lines_gdf.columns
+
+        assert data.loads_gdf.shape[0] == self.edisgo.topology.mv_grid.loads_df.shape[0]
+        assert (
+            data.loads_gdf.shape[1]
+            == self.edisgo.topology.mv_grid.loads_df.shape[1] + 2
+        )
+        assert "geometry" in data.loads_gdf.columns
+
+        assert (
+            data.generators_gdf.shape[0]
+            == self.edisgo.topology.mv_grid.generators_df.shape[0]
+        )
+        assert (
+            data.generators_gdf.shape[1]
+            == self.edisgo.topology.mv_grid.generators_df.shape[1] + 2
+        )
+        assert "geometry" in data.generators_gdf.columns
+
+        assert (
+            data.storage_units_gdf.shape[0]
+            == self.edisgo.topology.mv_grid.storage_units_df.shape[0]
+        )
+        assert (
+            data.storage_units_gdf.shape[1]
+            == self.edisgo.topology.mv_grid.storage_units_df.shape[1] + 2
+        )
+        assert "geometry" in data.storage_units_gdf.columns
+
+        assert (
+            data.transformers_gdf.shape[0]
+            == self.edisgo.topology.mv_grid.transformers_df.shape[0]
+        )
+        assert (
+            data.transformers_gdf.shape[1]
+            == self.edisgo.topology.mv_grid.transformers_df.shape[1] + 2
+        )
+        assert "geometry" in data.transformers_gdf.columns
diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py
index 40c34a63b..66216ca6d 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(

From 412cb9f6e4f6799f49ff90d22c7b1632ee863ff9 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Mon, 26 Aug 2024 16:19:24 +0200
Subject: [PATCH 26/50] fixing failing tests

---
 tests/io/test_timeseries_import.py | 4 +++-
 tests/network/test_timeseries.py   | 8 ++++----
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/tests/io/test_timeseries_import.py b/tests/io/test_timeseries_import.py
index c3e0662d3..e6050edc6 100644
--- a/tests/io/test_timeseries_import.py
+++ b/tests/io/test_timeseries_import.py
@@ -98,7 +98,9 @@ def test_load_time_series_demandlib(self):
         assert np.isclose(load.loc[timeindex[453], "cts"], 8.33507e-05)
         assert np.isclose(load.loc[timeindex[13], "residential"], 1.73151e-04)
         assert np.isclose(load.loc[timeindex[6328], "agricultural"], 1.01346e-04)
-        assert np.isclose(load.loc[timeindex[4325], "industrial"], 9.91768322919766e-05)
+        assert np.isclose(
+            load.loc[timeindex[4325], "industrial"], 9.876543209876541e-05
+        )
         assert np.isclose(load.sum()["cts"], 1.0)
         assert np.isclose(load.sum()["residential"], 1.0)
         assert np.isclose(load.sum()["agricultural"], 1.0)
diff --git a/tests/network/test_timeseries.py b/tests/network/test_timeseries.py
index c744af3fe..ed17d00e7 100644
--- a/tests/network/test_timeseries.py
+++ b/tests/network/test_timeseries.py
@@ -1565,9 +1565,9 @@ def test_predefined_conventional_loads_by_sector(self, caplog):
             index=index,
             columns=["cts", "residential", "agricultural", "industrial"],
             data=[
-                [0.000059711, 0.0000782190, 0.00006540, 0.000099176],
-                [0.000052590, 0.0000563428, 0.00006110, 0.000099176],
-                [0.000045927, 0.0000451043, 0.00005843, 0.000099176],
+                [0.000059711, 0.0000782190, 0.00006540, 0.000098765],
+                [0.000052590, 0.0000563428, 0.00006110, 0.000098765],
+                [0.000045927, 0.0000451043, 0.00005843, 0.000098765],
             ],
         )
 
@@ -1656,7 +1656,7 @@ def test_predefined_conventional_loads_by_sector(self, caplog):
             self.edisgo.timeseries.loads_active_power[
                 "Load_industrial_LVGrid_6_1"
             ].values,
-            [0.05752256272934643] * 3,
+            [0.05728395] * 3,
         ).all()
         assert np.isclose(
             self.edisgo.timeseries.loads_active_power.loc[

From f502ca99137c023bcd7acd94966be4af7669efee Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Mon, 26 Aug 2024 16:42:44 +0200
Subject: [PATCH 27/50] fixing failing tests

---
 tests/io/test_timeseries_import.py | 4 +---
 tests/network/test_timeseries.py   | 6 +++---
 2 files changed, 4 insertions(+), 6 deletions(-)

diff --git a/tests/io/test_timeseries_import.py b/tests/io/test_timeseries_import.py
index e6050edc6..c6abb872e 100644
--- a/tests/io/test_timeseries_import.py
+++ b/tests/io/test_timeseries_import.py
@@ -98,9 +98,7 @@ def test_load_time_series_demandlib(self):
         assert np.isclose(load.loc[timeindex[453], "cts"], 8.33507e-05)
         assert np.isclose(load.loc[timeindex[13], "residential"], 1.73151e-04)
         assert np.isclose(load.loc[timeindex[6328], "agricultural"], 1.01346e-04)
-        assert np.isclose(
-            load.loc[timeindex[4325], "industrial"], 9.876543209876541e-05
-        )
+        assert np.isclose(load.loc[timeindex[4325], "industrial"], 9.87654320e-05)
         assert np.isclose(load.sum()["cts"], 1.0)
         assert np.isclose(load.sum()["residential"], 1.0)
         assert np.isclose(load.sum()["agricultural"], 1.0)
diff --git a/tests/network/test_timeseries.py b/tests/network/test_timeseries.py
index ed17d00e7..2e8b717b4 100644
--- a/tests/network/test_timeseries.py
+++ b/tests/network/test_timeseries.py
@@ -1565,9 +1565,9 @@ def test_predefined_conventional_loads_by_sector(self, caplog):
             index=index,
             columns=["cts", "residential", "agricultural", "industrial"],
             data=[
-                [0.000059711, 0.0000782190, 0.00006540, 0.000098765],
-                [0.000052590, 0.0000563428, 0.00006110, 0.000098765],
-                [0.000045927, 0.0000451043, 0.00005843, 0.000098765],
+                [0.000059711, 0.0000782190, 0.00006540, 0.00009876],
+                [0.000052590, 0.0000563428, 0.00006110, 0.00009876],
+                [0.000045927, 0.0000451043, 0.00005843, 0.00009876],
             ],
         )
 

From 46732068b663c738988c5cb04d3825e69d7e8e0f Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Mon, 26 Aug 2024 16:45:16 +0200
Subject: [PATCH 28/50] fixing failing tests

---
 tests/network/test_topology.py | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/tests/network/test_topology.py b/tests/network/test_topology.py
index 04a98e2d1..8bac2dc66 100644
--- a/tests/network/test_topology.py
+++ b/tests/network/test_topology.py
@@ -1016,7 +1016,6 @@ def test_to_geopandas(self):
                 attr = getattr(geopandas_container, attr_str)
                 grid_attr = getattr(grid, attr_str.replace("_gdf", "_df"))
 
-                assert isinstance(attr, GeoDataFrame)
                 assert isinstance(attr, GeoDataFrame)
 
                 common_cols = list(set(attr.columns).intersection(grid_attr.columns))
@@ -1027,11 +1026,6 @@ def test_to_geopandas(self):
                     grid_attr[common_cols].sort_index(),
                     check_names=False,
                 )
-                assert_frame_equal(
-                    attr[common_cols].sort_index(),
-                    grid_attr[common_cols].sort_index(),
-                    check_names=False,
-                )
 
     def test_from_csv(self):
         """
@@ -1807,7 +1801,6 @@ def test_connect_to_lv(self):
         )
         # check new heat pump
         assert self.edisgo.topology.loads_df.at[comp_name, "p_set"] == 0.1
-        assert self.edisgo.topology.loads_df.at[comp_name, "p_set"] == 0.1
 
         # ############# storage unit #################
         # test existing substation ID (voltage level 7)

From 744535a1c8068773f8c4eedc88cd4521cff6877e Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Tue, 27 Aug 2024 08:40:14 +0200
Subject: [PATCH 29/50] fixing failing links

---
 edisgo/edisgo.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/edisgo/edisgo.py b/edisgo/edisgo.py
index 3930a5659..d47eb9de4 100755
--- a/edisgo/edisgo.py
+++ b/edisgo/edisgo.py
@@ -1004,7 +1004,7 @@ def analyze(
 
         Conducts a static, non-linear power flow analysis using
         `PyPSA <https://pypsa.readthedocs.io/en/latest/user-guide/power-flow.html#\
-        full-non-linear-power-flow>`_
+        non-linear-power-flow>`_
         and writes results (active, reactive and apparent power as well as
         current on lines and voltages at buses) to :class:`~.network.results.Results`
         (e.g. :attr:`~.network.results.Results.v_res` for voltages).

From 5ae69341adc323407d1969571d5b14b855b389fd Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Wed, 4 Sep 2024 10:23:48 +0200
Subject: [PATCH 30/50] adjusting test parametrization

---
 tests/network/test_topology.py | 22 ++++++----------------
 1 file changed, 6 insertions(+), 16 deletions(-)

diff --git a/tests/network/test_topology.py b/tests/network/test_topology.py
index 8bac2dc66..140d4b3c0 100644
--- a/tests/network/test_topology.py
+++ b/tests/network/test_topology.py
@@ -1981,25 +1981,15 @@ def test_find_meshes(self, caplog: pytest.LogCaptureFixture):
         assert "Bus_BranchTee_LVGrid_2_3" in meshes[1]
         assert "Grid contains mesh(es)." in caplog.text
 
-    # Define the parameters
-    sector_values = ["home", "work"]
-    comp_type_values = ["charging_point", "heat_pump", "storage_unit", "generator"]
-    voltage_level_values = [6, 7]
-    max_distance_from_target_bus_values = [0.01]
-    allowed_number_of_comp_per_bus_values = [2]
-    allow_mv_connection_values = [True, False]
-
     # Parametrize the test function
-    @pytest.mark.parametrize("sector", sector_values)
-    @pytest.mark.parametrize("comp_type", comp_type_values)
-    @pytest.mark.parametrize("voltage_level", voltage_level_values)
-    @pytest.mark.parametrize(
-        "max_distance_from_target_bus", max_distance_from_target_bus_values
-    )
+    @pytest.mark.parametrize("sector", ["home", "work"])
     @pytest.mark.parametrize(
-        "allowed_number_of_comp_per_bus", allowed_number_of_comp_per_bus_values
+        "comp_type", ["charging_point", "heat_pump", "storage_unit", "generator"]
     )
-    @pytest.mark.parametrize("allow_mv_connection", allow_mv_connection_values)
+    @pytest.mark.parametrize("voltage_level", [6, 7])
+    @pytest.mark.parametrize("max_distance_from_target_bus", [0.01])
+    @pytest.mark.parametrize("allowed_number_of_comp_per_bus", [2])
+    @pytest.mark.parametrize("allow_mv_connection", [True, False])
     def test_connect_to_lv_based_on_geolocation_parametrized(
         self,
         sector,

From 5c11cd0eb26b1d4336b57147a1937627af520084 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Wed, 4 Sep 2024 10:24:36 +0200
Subject: [PATCH 31/50] adjusting readability

---
 edisgo/network/topology.py | 81 +++++++++++++++++---------------------
 1 file changed, 37 insertions(+), 44 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 5a1c10bb3..a2808cbe2 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2472,14 +2472,6 @@ def get_add_function(comp_type):
                 )
             return add_func
 
-        def find_nearest_bus(geolocation, buses):
-            return geo.find_nearest_bus(geolocation, buses)
-
-        def connect_to_lv_bus(edisgo_object, target_bus, comp_type, comp_data):
-            return self._connect_to_lv_bus(
-                edisgo_object, target_bus, comp_type, comp_data
-            )
-
         def handle_voltage_level_6():
             substations = self.buses_df.loc[self.transformers_df.bus1.unique()]
             if comp_type == "charging_point":
@@ -2487,9 +2479,13 @@ def handle_voltage_level_6():
             else:
                 mv_buses = pd.DataFrame()
             substations = pd.concat([substations, mv_buses])
-            target_bus, target_bus_distance = find_nearest_bus(geolocation, substations)
+            target_bus, target_bus_distance = geo.find_nearest_bus(
+                geolocation, substations
+            )
             if target_bus_distance > max_distance_from_target_bus:
-                bus = connect_to_lv_bus(edisgo_object, target_bus, comp_type, comp_data)
+                bus = self._connect_to_lv_bus(
+                    edisgo_object, target_bus, comp_type, comp_data
+                )
             else:
                 bus = target_bus
             return bus
@@ -2543,49 +2539,46 @@ def handle_voltage_level_7():
                     mv_buses_masked = mv_buses[
                         mv_buses.distance == mv_buses.distance.min()
                     ]
+                    target_bus = mv_buses_masked.loc[mv_buses_masked.distance.idxmin()]
+                    return target_bus.name
                 else:
-                    target_bus = connect_to_lv_bus(
+                    target_bus = self._connect_to_lv_bus(
                         edisgo_object, lv_buses.distance.idxmin(), comp_type, comp_data
                     )
                     lv_buses_masked = pd.DataFrame(self.buses_df.loc[target_bus]).T
                     lv_buses_masked["distance"] = 0
                     mv_buses_masked = pd.DataFrame()
-            else:
-                mv_buses_masked = pd.DataFrame()
+                    return target_bus
+            comp_df = {
+                "charging_point": self.charging_points_df,
+                "generator": self.generators_df,
+                "heat_pump": self.loads_df[self.loads_df.type == "heat_pump"],
+                "storage_unit": self.storage_units_df,
+            }.get(comp_type)
+
+            comp_type_counts = (
+                comp_df.loc[comp_df.bus.isin(lv_buses_masked.index)]
+                .groupby("bus")
+                .size()
+            )
+            lv_buses_masked.loc[:, "num_comps"] = (
+                lv_buses_masked.index.map(comp_type_counts).fillna(0).astype(int)
+            )
+            lv_buses_masked = lv_buses_masked[
+                lv_buses_masked.num_comps == lv_buses_masked.num_comps.min()
+            ]
 
-            if not mv_buses_masked.empty:
-                target_bus = mv_buses_masked.loc[mv_buses_masked.distance.idxmin()]
-            else:
-                comp_df = {
-                    "charging_point": self.charging_points_df,
-                    "generator": self.generators_df,
-                    "heat_pump": self.loads_df[self.loads_df.type == "heat_pump"],
-                    "storage_unit": self.storage_units_df,
-                }.get(comp_type)
-
-                comp_type_counts = (
-                    comp_df.loc[comp_df.bus.isin(lv_buses_masked.index)]
-                    .groupby("bus")
-                    .size()
-                )
-                lv_buses_masked.loc[:, "num_comps"] = (
-                    lv_buses_masked.index.map(comp_type_counts).fillna(0).astype(int)
+            if lv_buses_masked.num_comps.min() >= allowed_number_of_comp_per_bus:
+                target_bus = self._connect_to_lv_bus(
+                    edisgo_object, lv_buses.distance.idxmin(), comp_type, comp_data
                 )
-                lv_buses_masked = lv_buses_masked[
-                    lv_buses_masked.num_comps == lv_buses_masked.num_comps.min()
-                ]
-
-                if lv_buses_masked.num_comps.min() >= allowed_number_of_comp_per_bus:
-                    target_bus = connect_to_lv_bus(
-                        edisgo_object, lv_buses.distance.idxmin(), comp_type, comp_data
-                    )
-                    lv_buses_masked = pd.DataFrame(self.buses_df.loc[target_bus]).T
-                    lv_buses_masked["distance"] = 0
-                    mv_buses_masked = pd.DataFrame()
+                lv_buses_masked = pd.DataFrame(self.buses_df.loc[target_bus]).T
+                lv_buses_masked["distance"] = 0
+                mv_buses_masked = pd.DataFrame()
 
-                target_bus = lv_buses_masked.loc[lv_buses_masked.distance.idxmin()]
-                if isinstance(target_bus, pd.DataFrame):
-                    target_bus = target_bus.iloc[0]
+            target_bus = lv_buses_masked.loc[lv_buses_masked.distance.idxmin()]
+            if isinstance(target_bus, pd.DataFrame):
+                target_bus = target_bus.iloc[0]
             return target_bus.name
 
         # Ensure 'p' is in comp_data, defaulting to 'p_set' or 'p_nom'

From 29d2fe5536689bfb4701662d409bb0a6c1b737b2 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Wed, 4 Sep 2024 13:33:29 +0200
Subject: [PATCH 32/50] Improve connection of charging points to the grid

---
 edisgo/network/topology.py | 39 ++++++++++++++++++++++++++------------
 1 file changed, 27 insertions(+), 12 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index a2808cbe2..ceffb1bc1 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2473,22 +2473,30 @@ def get_add_function(comp_type):
             return add_func
 
         def handle_voltage_level_6():
+            comp_data["voltage_level"] = 6
+            # get substations and MV buses
             substations = self.buses_df.loc[self.transformers_df.bus1.unique()]
-            if comp_type == "charging_point":
+            if allow_mv_connection:
                 mv_buses = self.buses_df.loc[self.mv_grid.buses_df.index]
             else:
                 mv_buses = pd.DataFrame()
-            substations = pd.concat([substations, mv_buses])
+            all_buses = pd.concat([substations, mv_buses])
             target_bus, target_bus_distance = geo.find_nearest_bus(
-                geolocation, substations
+                geolocation, all_buses
             )
-            if target_bus_distance > max_distance_from_target_bus:
-                bus = self._connect_to_lv_bus(
-                    edisgo_object, target_bus, comp_type, comp_data
-                )
+            if target_bus in substations.index:
+                if target_bus_distance > max_distance_from_target_bus:
+                    bus = self._connect_to_lv_bus(
+                        edisgo_object, target_bus, comp_type, comp_data
+                    )
+                else:
+                    mvlv_subst_id = self.buses_df.loc[target_bus].loc["lv_grid_id"]
+                    comp_data["mvlv_subst_id"] = mvlv_subst_id
+                    comp_name = self.connect_to_lv(edisgo_object, comp_data, comp_type)
+                    return None, comp_name
             else:
                 bus = target_bus
-            return bus
+            return bus, None
 
         def handle_voltage_level_7():
             if allow_mv_connection:
@@ -2549,6 +2557,7 @@ def handle_voltage_level_7():
                     lv_buses_masked["distance"] = 0
                     mv_buses_masked = pd.DataFrame()
                     return target_bus
+
             comp_df = {
                 "charging_point": self.charging_points_df,
                 "generator": self.generators_df,
@@ -2569,6 +2578,9 @@ def handle_voltage_level_7():
             ]
 
             if lv_buses_masked.num_comps.min() >= allowed_number_of_comp_per_bus:
+                # if all buses within the allowed distance have equal or more
+                # components of the same type connected to them than allowed,
+                # connect to new bus
                 target_bus = self._connect_to_lv_bus(
                     edisgo_object, lv_buses.distance.idxmin(), comp_type, comp_data
                 )
@@ -2590,7 +2602,7 @@ def handle_voltage_level_7():
             )
 
         # Extract and validate voltage level
-        voltage_level = comp_data.pop("voltage_level")
+        voltage_level = comp_data.get("voltage_level")
         validate_voltage_level(voltage_level)
         geolocation = comp_data.get("geom")
 
@@ -2605,13 +2617,16 @@ def handle_voltage_level_7():
 
         # Handle different voltage levels
         if voltage_level == 6:
-            bus = handle_voltage_level_6()
+            bus, comp_name = handle_voltage_level_6()
+            if comp_name is not None:
+                return comp_name
         elif voltage_level == 7:
             bus = handle_voltage_level_7()
 
         # Remove unnecessary keys from comp_data
-        comp_data.pop("geom")
-        comp_data.pop("p")
+        comp_data.pop("geom", None)
+        comp_data.pop("p", None)
+        comp_data.pop("voltage_level", None)
 
         # Add the component to the grid
         comp_name = add_func(bus=bus, **comp_data)

From b54a29e3b2b27829c7bfca0813f1927919f1c25a Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Fri, 6 Sep 2024 09:15:22 +0200
Subject: [PATCH 33/50] adding comments

---
 edisgo/network/topology.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index ceffb1bc1..bd40e34fe 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2473,7 +2473,6 @@ def get_add_function(comp_type):
             return add_func
 
         def handle_voltage_level_6():
-            comp_data["voltage_level"] = 6
             # get substations and MV buses
             substations = self.buses_df.loc[self.transformers_df.bus1.unique()]
             if allow_mv_connection:
@@ -2537,6 +2536,7 @@ def handle_voltage_level_7():
                 lv_buses.distance < max_distance_from_target_bus
             ].copy()
 
+            # if no bus is within the allowed distance, connect to new bus
             if len(lv_buses_masked) == 0:
                 if (
                     allow_mv_connection

From 64aa8339fc33e54a23134b757a7d92c7af8bfb32 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Fri, 6 Sep 2024 10:09:02 +0200
Subject: [PATCH 34/50] adjusting docstring

---
 edisgo/network/topology.py | 70 ++++++++++++++++++++------------------
 1 file changed, 36 insertions(+), 34 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index bd40e34fe..e94ad9966 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2369,6 +2369,42 @@ def connect_to_lv_based_on_geolocation(
         `max_distance_from_target_bus`. Otherwise, the new component is directly
         connected to the nearest bus.
 
+        * Method:
+            * Voltage level 6:
+                If the voltage level is 6, the component is connected to the closest
+                MV/LV substation or MV bus, depending whether MV connection is allowed
+                or not. Therefore, the distance to the substations and MV buses is
+                calculated and the closest one is chosen as target bus.
+                If the distance is greater than the specified maximum distance, a new
+                bus is created for the component.
+
+            * Voltage level 7:
+                If the voltage level is 7, the component is connected to the closest
+                LV bus.
+
+                * No MV connection allowed:
+                    If the distance to the closest LV bus is less than the specified
+                    maximum distance, the component is connected to the closest LV bus.
+                    If the distance is greater, a new bus is created for the component.
+                    If there are already components of the same type connected to the
+                    target bus, the component is connected to the closest LV bus with
+                    fewer connected components of the same type within the maximum
+                    distance. If no such bus is found, the component is connected to
+                    the closest LV bus again. If all buses within the allowed distance
+                    have equal or more components of the same type connected to them
+                    than allowed, the component is connected to a new LV bus.
+
+                * MV connection allowed:
+                    If the distance to the closest LV bus is less than the specified
+                    maximum distance, the component is connected to the closest LV bus.
+                    If the distance is greater, the distance to the closest MV bus is
+                    calculated. If the distance to the closest MV bus multiplied with
+                    the factor is less than the distance to the closest LV bus, the
+                    component is connected to the closest MV bus. The is no restriction
+                    on the number of components of the same type connected to
+                    the MV bus. If the distance is greater, the component
+                    is connected to a new LV bus.
+
         Parameters
         ----------
         edisgo_object : :class:`~.EDisGo`
@@ -2414,40 +2450,6 @@ def connect_to_lv_based_on_geolocation(
             :attr:`~.network.topology.Topology.loads_df` or
             :attr:`~.network.topology.Topology.storage_units_df`, depending on component
             type.
-
-        Method
-        ------
-        1. Extract and validate voltage level
-        - Voltage level 6:
-            If the voltage level is 6, the component is connected to the closest
-            MV/LV substation or MV bus. Therefore, the distance to the substations
-            and MV buses is calculated and the closest one is chosen as target bus.
-            If the distance is greater than the specified maximum distance, a new
-            bus is created for the component.
-        - Voltage level 7:
-            If the voltage level is 7, the component is connected to the closest
-            LV bus.
-            - No MV connection allowed:
-                If the distance to the closest LV bus is less than the specified
-                maximum distance, the component is connected to the closest LV bus.
-                If the distance is greater, a new bus is created for the component.
-                If there are already components of the same type connected to the
-                target bus, the component is connected to the closest LV bus with
-                fewer connected components of the same type within the maximum
-                distance. If no such bus is found, the component is connected to
-                the closest LV bus again. If all buses within the allowed distance
-                have equal or more components of the same type connected to them
-                than allowed, the component is connected to a new LV bus.
-            - MV connection:
-                If the distance to the closest LV bus is less than the specified
-                maximum distance, the component is connected to the closest LV bus.
-                If the distance is greater, the distance to the closest MV bus is
-                calculated. If the distance to the closest MV bus multiplied with
-                the factor is less than the distance to the closest LV bus, the
-                component is connected to the closest MV bus. The is no restriction
-                on the number of components of the same type connected to
-                the MV bus. If the distance is greater, the component
-                is connected to a new LV bus.
         """
 
         def validate_voltage_level(voltage_level):

From 962f495c9ff257bcbacbb4fdbc4a2e452f85c623 Mon Sep 17 00:00:00 2001
From: birgits <birgit.schachler@rl-institut.de>
Date: Fri, 13 Sep 2024 10:47:58 +0200
Subject: [PATCH 35/50] Fix typo

---
 edisgo/tools/geo.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/edisgo/tools/geo.py b/edisgo/tools/geo.py
index 115877369..4a813cfde 100755
--- a/edisgo/tools/geo.py
+++ b/edisgo/tools/geo.py
@@ -330,7 +330,7 @@ def mv_grid_gdf(edisgo_obj: EDisGo):
 
 
 def calculate_distance_to_buses_df(
-    point: Point, busses_df: pd.DataFrame
+    point: Point, buses_df: pd.DataFrame
 ) -> pd.DataFrame:
     """
     Calculate the distance between buses and a given geometry.
@@ -339,7 +339,7 @@ def calculate_distance_to_buses_df(
     ----------
     point : :shapely:`shapely.Point<Point>`
         Geolocation to calculate distance to.
-    busses_df : :pandas:`pandas.DataFrame<DataFrame>`
+    buses_df : :pandas:`pandas.DataFrame<DataFrame>`
         Dataframe with buses and their positions given in 'x' and 'y'
         columns. The dataframe has the same format as
         :attr:`~.network.topology.Topology.buses_df`.
@@ -347,15 +347,15 @@ def calculate_distance_to_buses_df(
     Returns
     -------
     :pandas:`pandas.DataFrame<DataFrame>`
-        Data of `bus_df` with additional column 'distance' containing the distance
+        Data of `buses_df` with additional column 'distance' containing the distance
         to the given geometry in km.
 
     """
-    distances = busses_df.apply(
+    distances = buses_df.apply(
         lambda row: geopy.distance.distance(
             (row["x"], row["y"]), (point.x, point.y)
         ).km,
         axis=1,
     )
-    busses_df.loc[:, "distance"] = distances
-    return busses_df
+    buses_df.loc[:, "distance"] = distances
+    return buses_df

From 6a72db4950d80a0f8439e19b435063f57e797133 Mon Sep 17 00:00:00 2001
From: birgits <birgit.schachler@rl-institut.de>
Date: Fri, 13 Sep 2024 10:48:20 +0200
Subject: [PATCH 36/50] Use new function to avoid doubling of code

---
 edisgo/tools/geo.py | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/edisgo/tools/geo.py b/edisgo/tools/geo.py
index 4a813cfde..2a21fdd1e 100755
--- a/edisgo/tools/geo.py
+++ b/edisgo/tools/geo.py
@@ -215,12 +215,9 @@ def find_nearest_bus(point, bus_target):
         Tuple that contains the name of the nearest bus and its distance in km.
 
     """
-    bus_target["dist"] = [
-        geodesic((point.y, point.x), (y, x)).km
-        for (x, y) in zip(bus_target["x"], bus_target["y"])
-    ]
+    bus_target = calculate_distance_to_buses_df(point, bus_target)
 
-    return bus_target["dist"].idxmin(), bus_target["dist"].min()
+    return bus_target["distance"].idxmin(), bus_target["distance"].min()
 
 
 def find_nearest_conn_objects(grid_topology, bus, lines, conn_diff_tolerance=0.0001):

From d490810bf6903f192ae8b3bfdb8571aac3b609f7 Mon Sep 17 00:00:00 2001
From: birgits <birgit.schachler@rl-institut.de>
Date: Fri, 13 Sep 2024 11:16:41 +0200
Subject: [PATCH 37/50] Add test for find_nearest_bus

---
 tests/tools/test_geo.py | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)
 create mode 100644 tests/tools/test_geo.py

diff --git a/tests/tools/test_geo.py b/tests/tools/test_geo.py
new file mode 100644
index 000000000..b7888245b
--- /dev/null
+++ b/tests/tools/test_geo.py
@@ -0,0 +1,34 @@
+import numpy as np
+import pytest
+
+from shapely.geometry import Point
+
+from edisgo import EDisGo
+from edisgo.tools import geo
+
+
+class TestTools:
+    @classmethod
+    def setup_class(self):
+        self.edisgo = EDisGo(
+            ding0_grid=pytest.ding0_test_network_3_path, legacy_ding0_grids=False
+        )
+
+    def test_find_nearest_bus(self):
+        # test with coordinates of existing bus
+        bus = self.edisgo.topology.buses_df.index[5]
+        point = Point(
+            (
+                self.edisgo.topology.buses_df.at[bus, "x"],
+                self.edisgo.topology.buses_df.at[bus, "y"],
+            )
+        )
+        nearest_bus, dist = geo.find_nearest_bus(point, self.edisgo.topology.buses_df)
+        assert nearest_bus == bus
+        assert dist == 0.0
+
+        # test with random coordinates
+        point = Point((10.002736, 47.5426))
+        nearest_bus, dist = geo.find_nearest_bus(point, self.edisgo.topology.buses_df)
+        assert nearest_bus == "BranchTee_mvgd_33535_lvgd_1163360000_building_431698"
+        assert np.isclose(dist, 0.0008622, atol=1e-6)

From 44708e1a45bfc8eb5bc1594039a59f8bb1359b2a Mon Sep 17 00:00:00 2001
From: birgits <birgit.schachler@rl-institut.de>
Date: Fri, 13 Sep 2024 11:26:49 +0200
Subject: [PATCH 38/50] Minor doc changes

---
 edisgo/network/topology.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index e94ad9966..4fb508ee4 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2372,9 +2372,9 @@ def connect_to_lv_based_on_geolocation(
         * Method:
             * Voltage level 6:
                 If the voltage level is 6, the component is connected to the closest
-                MV/LV substation or MV bus, depending whether MV connection is allowed
-                or not. Therefore, the distance to the substations and MV buses is
-                calculated and the closest one is chosen as target bus.
+                MV/LV substation or MV bus, depending on whether MV connection is
+                allowed or not. Therefore, the distance to the substations and MV buses
+                is calculated and the closest one is chosen as target bus.
                 If the distance is greater than the specified maximum distance, a new
                 bus is created for the component.
 
@@ -2469,7 +2469,7 @@ def get_add_function(comp_type):
             add_func = add_func_map.get(comp_type)
             if add_func is None:
                 raise ValueError(
-                    f"Provided component type {comp_type} is not valid. Must either be"
+                    f"Provided component type {comp_type} is not valid. Must either be "
                     f"'generator', 'charging_point', 'heat_pump' or 'storage_unit'."
                 )
             return add_func

From aba78ef05b0f96db416ae1931926b201fd490f63 Mon Sep 17 00:00:00 2001
From: birgits <birgit.schachler@rl-institut.de>
Date: Fri, 13 Sep 2024 13:30:34 +0200
Subject: [PATCH 39/50] Add to docstring

---
 edisgo/network/topology.py | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 4fb508ee4..fd4b9e6e3 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2369,6 +2369,8 @@ def connect_to_lv_based_on_geolocation(
         `max_distance_from_target_bus`. Otherwise, the new component is directly
         connected to the nearest bus.
 
+        In the following are some more details on the methodology:
+
         * Method:
             * Voltage level 6:
                 If the voltage level is 6, the component is connected to the closest
@@ -2380,11 +2382,12 @@ def connect_to_lv_based_on_geolocation(
 
             * Voltage level 7:
                 If the voltage level is 7, the component is connected to the closest
-                LV bus.
+                LV bus. Two main cases can be distinguished:
 
                 * No MV connection allowed:
                     If the distance to the closest LV bus is less than the specified
-                    maximum distance, the component is connected to the closest LV bus.
+                    maximum distance `max_distance_from_target_bus`, the component is
+                    connected to the closest LV bus.
                     If the distance is greater, a new bus is created for the component.
                     If there are already components of the same type connected to the
                     target bus, the component is connected to the closest LV bus with
@@ -2396,13 +2399,14 @@ def connect_to_lv_based_on_geolocation(
 
                 * MV connection allowed:
                     If the distance to the closest LV bus is less than the specified
-                    maximum distance, the component is connected to the closest LV bus.
+                    maximum distance `max_distance_from_target_bus`, the component is
+                    connected to the closest LV bus.
                     If the distance is greater, the distance to the closest MV bus is
                     calculated. If the distance to the closest MV bus multiplied with
-                    the factor is less than the distance to the closest LV bus, the
-                    component is connected to the closest MV bus. The is no restriction
-                    on the number of components of the same type connected to
-                    the MV bus. If the distance is greater, the component
+                    the factor `factor_mv_connection` is less than the distance to the
+                    closest LV bus, the component is connected to the closest MV bus.
+                    There is no restriction on the number of components of the same type
+                    connected to the MV bus. If the distance is greater, the component
                     is connected to a new LV bus.
 
         Parameters

From 11597b35d2b9c8c7ef50336a51e077411af285f8 Mon Sep 17 00:00:00 2001
From: birgits <birgit.schachler@rl-institut.de>
Date: Fri, 13 Sep 2024 13:33:40 +0200
Subject: [PATCH 40/50] Add comments to code

---
 edisgo/network/topology.py | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index fd4b9e6e3..294e8e3c8 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2486,10 +2486,13 @@ def handle_voltage_level_6():
             else:
                 mv_buses = pd.DataFrame()
             all_buses = pd.concat([substations, mv_buses])
+            # calculate distance to possible buses
             target_bus, target_bus_distance = geo.find_nearest_bus(
                 geolocation, all_buses
             )
             if target_bus in substations.index:
+                # if distance is larger than allowed, create new bus and connect to
+                # station via a new line
                 if target_bus_distance > max_distance_from_target_bus:
                     bus = self._connect_to_lv_bus(
                         edisgo_object, target_bus, comp_type, comp_data
@@ -2556,6 +2559,8 @@ def handle_voltage_level_7():
                     target_bus = mv_buses_masked.loc[mv_buses_masked.distance.idxmin()]
                     return target_bus.name
                 else:
+                    # if distance is larger than allowed, create new bus and connect to
+                    # closest bus via a new line
                     target_bus = self._connect_to_lv_bus(
                         edisgo_object, lv_buses.distance.idxmin(), comp_type, comp_data
                     )
@@ -2564,13 +2569,16 @@ def handle_voltage_level_7():
                     mv_buses_masked = pd.DataFrame()
                     return target_bus
 
+            # if LV bus is within distance, where no new bus needs to be created, check
+            # the number of already connected buses, so to not connect too many of the
+            # same components to that bus
             comp_df = {
                 "charging_point": self.charging_points_df,
                 "generator": self.generators_df,
                 "heat_pump": self.loads_df[self.loads_df.type == "heat_pump"],
                 "storage_unit": self.storage_units_df,
             }.get(comp_type)
-
+            # determine number of connected components per bus
             comp_type_counts = (
                 comp_df.loc[comp_df.bus.isin(lv_buses_masked.index)]
                 .groupby("bus")
@@ -2579,6 +2587,8 @@ def handle_voltage_level_7():
             lv_buses_masked.loc[:, "num_comps"] = (
                 lv_buses_masked.index.map(comp_type_counts).fillna(0).astype(int)
             )
+            # get buses where the least number of components of the given type is
+            # connected
             lv_buses_masked = lv_buses_masked[
                 lv_buses_masked.num_comps == lv_buses_masked.num_comps.min()
             ]

From dca6b45ef20ea6db4367c4969ea87adcfcf2f148 Mon Sep 17 00:00:00 2001
From: birgits <birgit.schachler@rl-institut.de>
Date: Fri, 13 Sep 2024 13:46:18 +0200
Subject: [PATCH 41/50] Change indent

---
 edisgo/network/topology.py | 75 +++++++++++++++++++-------------------
 1 file changed, 38 insertions(+), 37 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 294e8e3c8..02d637f32 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2371,43 +2371,44 @@ def connect_to_lv_based_on_geolocation(
 
         In the following are some more details on the methodology:
 
-        * Method:
-            * Voltage level 6:
-                If the voltage level is 6, the component is connected to the closest
-                MV/LV substation or MV bus, depending on whether MV connection is
-                allowed or not. Therefore, the distance to the substations and MV buses
-                is calculated and the closest one is chosen as target bus.
-                If the distance is greater than the specified maximum distance, a new
-                bus is created for the component.
-
-            * Voltage level 7:
-                If the voltage level is 7, the component is connected to the closest
-                LV bus. Two main cases can be distinguished:
-
-                * No MV connection allowed:
-                    If the distance to the closest LV bus is less than the specified
-                    maximum distance `max_distance_from_target_bus`, the component is
-                    connected to the closest LV bus.
-                    If the distance is greater, a new bus is created for the component.
-                    If there are already components of the same type connected to the
-                    target bus, the component is connected to the closest LV bus with
-                    fewer connected components of the same type within the maximum
-                    distance. If no such bus is found, the component is connected to
-                    the closest LV bus again. If all buses within the allowed distance
-                    have equal or more components of the same type connected to them
-                    than allowed, the component is connected to a new LV bus.
-
-                * MV connection allowed:
-                    If the distance to the closest LV bus is less than the specified
-                    maximum distance `max_distance_from_target_bus`, the component is
-                    connected to the closest LV bus.
-                    If the distance is greater, the distance to the closest MV bus is
-                    calculated. If the distance to the closest MV bus multiplied with
-                    the factor `factor_mv_connection` is less than the distance to the
-                    closest LV bus, the component is connected to the closest MV bus.
-                    There is no restriction on the number of components of the same type
-                    connected to the MV bus. If the distance is greater, the component
-                    is connected to a new LV bus.
+        Noch dokumentieren, welche Busse infrage kommen
+
+        * Voltage level 6:
+            If the voltage level is 6, the component is connected to the closest
+            MV/LV substation or MV bus, depending on whether MV connection is
+            allowed or not. Therefore, the distance to the substations and MV buses
+            is calculated and the closest one is chosen as target bus.
+            If the distance is greater than the specified maximum distance, a new
+            bus is created for the component.
+
+        * Voltage level 7:
+            If the voltage level is 7, the component is connected to the closest
+            LV bus. Two main cases can be distinguished:
+
+            * No MV connection allowed:
+                If the distance to the closest LV bus is less than the specified
+                maximum distance `max_distance_from_target_bus`, the component is
+                connected to the closest LV bus.
+                If the distance is greater, a new bus is created for the component.
+                If there are already components of the same type connected to the
+                target bus, the component is connected to the closest LV bus with
+                fewer connected components of the same type within the maximum
+                distance. If no such bus is found, the component is connected to
+                the closest LV bus again. If all buses within the allowed distance
+                have equal or more components of the same type connected to them
+                than allowed, the component is connected to a new LV bus.
+
+            * MV connection allowed:
+                If the distance to the closest LV bus is less than the specified
+                maximum distance `max_distance_from_target_bus`, the component is
+                connected to the closest LV bus.
+                If the distance is greater, the distance to the closest MV bus is
+                calculated. If the distance to the closest MV bus multiplied with
+                the factor `factor_mv_connection` is less than the distance to the
+                closest LV bus, the component is connected to the closest MV bus.
+                There is no restriction on the number of components of the same type
+                connected to the MV bus. If the distance is greater, the component
+                is connected to a new LV bus.
 
         Parameters
         ----------

From f30740fc67a9fd0dc79ebeba834a405bcfff838e Mon Sep 17 00:00:00 2001
From: birgits <birgit.schachler@rl-institut.de>
Date: Fri, 13 Sep 2024 13:54:26 +0200
Subject: [PATCH 42/50] Remove mistakenly added comment

---
 edisgo/network/topology.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 02d637f32..c9be560f2 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2371,8 +2371,6 @@ def connect_to_lv_based_on_geolocation(
 
         In the following are some more details on the methodology:
 
-        Noch dokumentieren, welche Busse infrage kommen
-
         * Voltage level 6:
             If the voltage level is 6, the component is connected to the closest
             MV/LV substation or MV bus, depending on whether MV connection is

From e66995371a32dff461b01c6647bfc4ab74e8e73c Mon Sep 17 00:00:00 2001
From: birgits <birgit.schachler@rl-institut.de>
Date: Fri, 13 Sep 2024 14:07:10 +0200
Subject: [PATCH 43/50] Add comment

---
 edisgo/network/topology.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index c9be560f2..a76135881 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2546,6 +2546,7 @@ def handle_voltage_level_7():
 
             # if no bus is within the allowed distance, connect to new bus
             if len(lv_buses_masked) == 0:
+                # connect to MV if this is the best option
                 if (
                     allow_mv_connection
                     and len(mv_buses_masked) > 0
@@ -2559,7 +2560,7 @@ def handle_voltage_level_7():
                     return target_bus.name
                 else:
                     # if distance is larger than allowed, create new bus and connect to
-                    # closest bus via a new line
+                    # the closest bus via a new line
                     target_bus = self._connect_to_lv_bus(
                         edisgo_object, lv_buses.distance.idxmin(), comp_type, comp_data
                     )

From a8360f4719fe5582dbc78e2ae9da21f788da6d19 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Tue, 17 Sep 2024 13:52:57 +0200
Subject: [PATCH 44/50] Fix incorrect geodesic function call by correcting
 latitude and longitude order

---
 edisgo/tools/geo.py     | 5 +----
 tests/tools/test_geo.py | 2 +-
 2 files changed, 2 insertions(+), 5 deletions(-)

diff --git a/edisgo/tools/geo.py b/edisgo/tools/geo.py
index 2a21fdd1e..f6541a563 100755
--- a/edisgo/tools/geo.py
+++ b/edisgo/tools/geo.py
@@ -5,7 +5,6 @@
 
 from typing import TYPE_CHECKING
 
-import geopy.distance
 import pandas as pd
 
 from geopy.distance import geodesic
@@ -349,9 +348,7 @@ def calculate_distance_to_buses_df(
 
     """
     distances = buses_df.apply(
-        lambda row: geopy.distance.distance(
-            (row["x"], row["y"]), (point.x, point.y)
-        ).km,
+        lambda row: geodesic((row["y"], row["x"]), (point.y, point.x)).km,
         axis=1,
     )
     buses_df.loc[:, "distance"] = distances
diff --git a/tests/tools/test_geo.py b/tests/tools/test_geo.py
index b7888245b..a6c7229c6 100644
--- a/tests/tools/test_geo.py
+++ b/tests/tools/test_geo.py
@@ -31,4 +31,4 @@ def test_find_nearest_bus(self):
         point = Point((10.002736, 47.5426))
         nearest_bus, dist = geo.find_nearest_bus(point, self.edisgo.topology.buses_df)
         assert nearest_bus == "BranchTee_mvgd_33535_lvgd_1163360000_building_431698"
-        assert np.isclose(dist, 0.0008622, atol=1e-6)
+        assert np.isclose(dist, 0.000806993475812168, atol=1e-6)

From 0b4989f785b2512eed97ed1d88229699ffdc5417 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Wed, 18 Sep 2024 09:39:30 +0200
Subject: [PATCH 45/50] fixing issues in connect to bus logic

---
 edisgo/network/topology.py | 29 +++++++++++++++++------------
 1 file changed, 17 insertions(+), 12 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index a76135881..6f860bf35 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -1910,12 +1910,16 @@ def connect_to_mv(self, edisgo_object, comp_data, comp_type="generator"):
         # add component to newly created bus
         comp_data.pop("geom")
         if comp_type == "generator":
+            comp_data.pop("type", None)
             comp_name = self.add_generator(bus=bus, **comp_data)
         elif comp_type == "charging_point":
+            comp_data.pop("type", None)
             comp_name = self.add_load(bus=bus, type="charging_point", **comp_data)
         elif comp_type == "heat_pump":
+            comp_data.pop("type", None)
             comp_name = self.add_load(bus=bus, type="heat_pump", **comp_data)
         else:
+            comp_data.pop("type", None)
             comp_name = self.add_storage_unit(bus=bus, **comp_data)
 
         # ===== voltage level 4: component is connected to MV station =====
@@ -2344,7 +2348,7 @@ def connect_to_lv_based_on_geolocation(
         edisgo_object,
         comp_data: dict,
         comp_type: str,
-        max_distance_from_target_bus: float = 0.1,
+        max_distance_from_target_bus: float = 0.02,
         allowed_number_of_comp_per_bus: int = 2,
         allow_mv_connection: bool = False,
         factor_mv_connection: float = 3.0,
@@ -2499,10 +2503,11 @@ def handle_voltage_level_6():
                 else:
                     mvlv_subst_id = self.buses_df.loc[target_bus].loc["lv_grid_id"]
                     comp_data["mvlv_subst_id"] = mvlv_subst_id
-                    comp_name = self.connect_to_lv(edisgo_object, comp_data, comp_type)
+                    comp_name = add_func(bus=target_bus, **comp_data)
                     return None, comp_name
             else:
-                bus = target_bus
+                comp_name = self.connect_to_mv(edisgo_object, comp_data, comp_type)
+                return None, comp_name
             return bus, None
 
         def handle_voltage_level_7():
@@ -2524,9 +2529,7 @@ def handle_voltage_level_7():
                     ]
                 elif comp_data["sector"] == "work":
                     lv_loads = self.loads_df[
-                        self.loads_df.sector.isin(
-                            ["industrial", "cts", "agricultural", "work"]
-                        )
+                        self.loads_df.sector.isin(["industrial", "cts", "agricultural"])
                     ]
                     lv_loads = lv_loads.loc[
                         ~lv_loads.bus.isin(self.mv_grid.buses_df.index)
@@ -2557,17 +2560,17 @@ def handle_voltage_level_7():
                         mv_buses.distance == mv_buses.distance.min()
                     ]
                     target_bus = mv_buses_masked.loc[mv_buses_masked.distance.idxmin()]
-                    return target_bus.name
+                    comp_name = self.connect_to_mv(edisgo_object, comp_data, comp_type)
+                    return None, comp_name
                 else:
                     # if distance is larger than allowed, create new bus and connect to
                     # the closest bus via a new line
+
                     target_bus = self._connect_to_lv_bus(
                         edisgo_object, lv_buses.distance.idxmin(), comp_type, comp_data
                     )
                     lv_buses_masked = pd.DataFrame(self.buses_df.loc[target_bus]).T
-                    lv_buses_masked["distance"] = 0
-                    mv_buses_masked = pd.DataFrame()
-                    return target_bus
+                    return target_bus, None
 
             # if LV bus is within distance, where no new bus needs to be created, check
             # the number of already connected buses, so to not connect too many of the
@@ -2607,7 +2610,7 @@ def handle_voltage_level_7():
             target_bus = lv_buses_masked.loc[lv_buses_masked.distance.idxmin()]
             if isinstance(target_bus, pd.DataFrame):
                 target_bus = target_bus.iloc[0]
-            return target_bus.name
+            return target_bus.name, None
 
         # Ensure 'p' is in comp_data, defaulting to 'p_set' or 'p_nom'
         if "p" not in comp_data.keys():
@@ -2637,7 +2640,9 @@ def handle_voltage_level_7():
             if comp_name is not None:
                 return comp_name
         elif voltage_level == 7:
-            bus = handle_voltage_level_7()
+            bus, comp_name = handle_voltage_level_7()
+            if comp_name is not None:
+                return comp_name
 
         # Remove unnecessary keys from comp_data
         comp_data.pop("geom", None)

From b2d6cffa624feea8bf5c56b12113860db4c05979 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Thu, 26 Sep 2024 17:04:56 +0200
Subject: [PATCH 46/50] remove unnecessary lines

---
 edisgo/network/topology.py | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 6f860bf35..23c87aea1 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2603,14 +2603,11 @@ def handle_voltage_level_7():
                 target_bus = self._connect_to_lv_bus(
                     edisgo_object, lv_buses.distance.idxmin(), comp_type, comp_data
                 )
-                lv_buses_masked = pd.DataFrame(self.buses_df.loc[target_bus]).T
-                lv_buses_masked["distance"] = 0
-                mv_buses_masked = pd.DataFrame()
-
-            target_bus = lv_buses_masked.loc[lv_buses_masked.distance.idxmin()]
-            if isinstance(target_bus, pd.DataFrame):
-                target_bus = target_bus.iloc[0]
-            return target_bus.name, None
+                return target_bus, ModuleNotFoundError
+
+            else:
+                target_bus = lv_buses_masked.loc[lv_buses_masked.distance.idxmin()]
+                return target_bus.name, None
 
         # Ensure 'p' is in comp_data, defaulting to 'p_set' or 'p_nom'
         if "p" not in comp_data.keys():

From 0c8b17568c295c2db92a9487b8fd654737c0174e Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Fri, 27 Sep 2024 14:40:05 +0200
Subject: [PATCH 47/50] Fix connect_to_lv_bus return type and error handling

---
 edisgo/network/topology.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 23c87aea1..633c508ff 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2603,7 +2603,10 @@ def handle_voltage_level_7():
                 target_bus = self._connect_to_lv_bus(
                     edisgo_object, lv_buses.distance.idxmin(), comp_type, comp_data
                 )
-                return target_bus, ModuleNotFoundError
+                if isinstance(target_bus, str):
+                    return target_bus, None
+                elif isinstance(target_bus, pd.DataFrame):
+                    return target_bus.index[0], None
 
             else:
                 target_bus = lv_buses_masked.loc[lv_buses_masked.distance.idxmin()]

From 1fe6ed1a0b9a9a5d9f9ea0e6afe80ff8a3fc10d4 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Tue, 1 Oct 2024 11:13:24 +0200
Subject: [PATCH 48/50] Update docstring

---
 edisgo/network/topology.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 633c508ff..b2509e869 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2412,6 +2412,14 @@ def connect_to_lv_based_on_geolocation(
                 connected to the MV bus. If the distance is greater, the component
                 is connected to a new LV bus.
 
+            * Buses the components can be connected to:
+                - For voltage level 6: Components can be connected to MV/LV substations.
+                    If MV connection is allowed, components can also be connected to
+                    MV buses.
+                - For voltage level 7: Components can be connected to LV buses. If MV
+                    connection is allowed, components can also be connected
+                    to MV buses and MV/LV substations.
+
         Parameters
         ----------
         edisgo_object : :class:`~.EDisGo`

From 00b8d42494c822086bd356baeeaac479c745a474 Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Tue, 1 Oct 2024 11:15:23 +0200
Subject: [PATCH 49/50] Refactor component data handling in Topology class

---
 edisgo/network/topology.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index b2509e869..67c9965d0 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2640,7 +2640,7 @@ def handle_voltage_level_7():
         if comp_type in ["charging_point", "heat_pump"]:
             comp_data["type"] = comp_type
         elif comp_type in ["generator", "storage_unit"]:
-            comp_data["p_nom"] = comp_data["p"]
+            comp_data["p_nom"] = comp_data.pop("p")
 
         # Handle different voltage levels
         if voltage_level == 6:

From be272cab1fc4b2019a3e2270c6634c822f14767e Mon Sep 17 00:00:00 2001
From: joda9 <jonas.danke@rl-institut.de>
Date: Tue, 1 Oct 2024 11:46:23 +0200
Subject: [PATCH 50/50] Revert "Refactor component data handling in Topology
 class"

This reverts commit 00b8d42494c822086bd356baeeaac479c745a474.
---
 edisgo/network/topology.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py
index 67c9965d0..b2509e869 100755
--- a/edisgo/network/topology.py
+++ b/edisgo/network/topology.py
@@ -2640,7 +2640,7 @@ def handle_voltage_level_7():
         if comp_type in ["charging_point", "heat_pump"]:
             comp_data["type"] = comp_type
         elif comp_type in ["generator", "storage_unit"]:
-            comp_data["p_nom"] = comp_data.pop("p")
+            comp_data["p_nom"] = comp_data["p"]
 
         # Handle different voltage levels
         if voltage_level == 6: