Repository: e2nIEE/pandapower

Issue:
determine stubs returns wrong stub bus 
I have  a simple grid, which looks like this:

            b0
            |
            trafo
            |
        b6--b1--b2
        |   |   |
        b5--b4--b3

The function `topology.determine_stubs` returns that b6 is a stub bus and the connected lines are on a stub. This seems to be a bug in `find_graph_characteristics` 

@jkupka could you have a look at this? I'll add a test


Discussion:
> I have a simple grid, which looks like this:
> 
> ```
>         b0
>         |
>         trafo
>         |
>     b6--b1--b2
>     |   |   |
>     b5--b4--b3
> ```
> 
> The function `topology.determine_stubs` returns that b6 is a stub bus and the connected lines are on a stub. This seems to be a bug in `find_graph_characteristics`
> 
> @jkupka could you have a look at this? I'll add a test

Here is the test:
https://github.com/FlorianShepherd/pandapower/commit/db18a2fcb8c50d92a4390e527921804c554dc45c
I'm not able to reproduce the result you described with the network you're using for the test.

For me `determine_stubs` returns that all buses except b0 are stub buses, which is expected behaviour. There is only one connection from b0 to b1 so all buses after this connection are determined as stub buses.
If i use b1 as root bus (`determine_stubs(meshed_network, roots=[1]`) b1-b6 are not determined as stubs anymore. 

If no root buses are declared, `determine_stubs` will use the connection buses of external grids per default. One could question if this is the proper approach. 

Resolving patch:
diff --git a/pandapower/topology/graph_searches.py b/pandapower/topology/graph_searches.py
--- a/pandapower/topology/graph_searches.py
+++ b/pandapower/topology/graph_searches.py
@@ -191,13 +191,14 @@ def find_basic_graph_characteristics(g, roots, characteristics):
     low = {root: 0 for root in roots}
     visited = set(roots)
     path = []
-    stack = [(root, root, iter(sorted(g[root]))) for root in roots]
+    stack = [(root, root, iter(g[root])) for root in roots]
     while stack:
         grandparent, parent, children = stack[-1]
         try:
             child = next(children)
             if stub_buses:
-                path.append(child)  # keep track of movement through the graph
+                if child not in visited:
+                    path.append(child)  # keep track of movement through the graph
             if grandparent == child:
                 continue
             if child in visited:
@@ -206,9 +207,10 @@ def find_basic_graph_characteristics(g, roots, characteristics):
             else:
                 low[child] = discovery[child] = len(discovery)
                 visited.add(child)
-                stack.append((parent, child, iter(sorted(g[child]))))
+                stack.append((parent, child, iter(g[child])))
         except StopIteration:
-            stack.pop()
+            back = stack.pop()
+            path.append(back[0])
             if low[parent] >= discovery[grandparent]:
                 # Articulation points and start of not n-1 safe buses
                 if grandparent not in roots:
@@ -223,14 +225,12 @@ def find_basic_graph_characteristics(g, roots, characteristics):
 
                     # Stub buses
                     if stub_buses:
-                        if path[-1] == grandparent:
-                            path.pop()
+                        stub = path.pop()
+                        if stub != grandparent:
+                            char_dict['stub_buses'].add(stub)
+                        while path and path[-1] != grandparent and path[-1] not in roots:
                             stub = path.pop()
                             char_dict['stub_buses'].add(stub)
-                        else:
-                            while path[-1] != grandparent:
-                                stub = path.pop()
-                                char_dict['stub_buses'].add(stub)
             low[grandparent] = min(low[parent], low[grandparent])
 
     if connected:


Test patch:
diff --git a/pandapower/test/topology/test_graph_searches.py b/pandapower/test/topology/test_graph_searches.py
--- a/pandapower/test/topology/test_graph_searches.py
+++ b/pandapower/test/topology/test_graph_searches.py
@@ -52,6 +52,17 @@ def meshed_network():
     pp.create_line(net, b[6], b[1], length_km=100., std_type="149-AL1/24-ST1A 110.0", name="line7")
     return net
 
+@pytest.fixture
+def mixed_network():
+    net = pp.create_empty_network()
+    pp.create_buses(net, nr_buses=5, vn_kv=20.)
+    connections = [(0, 1), (1, 2), (2, 3), (2, 4)]
+    for fb, tb in connections:
+        pp.create_line(net, fb, tb, length_km=1, std_type="NA2XS2Y 1x185 RM/25 12/20 kV")
+    for b in [1, 4, 3]:
+        pp.create_ext_grid(net, b)
+    return net
+
 
 def test_determine_stubs(feeder_network):
     net = feeder_network
@@ -63,7 +74,6 @@ def test_determine_stubs(feeder_network):
     assert net.bus.on_stub.at[sec_bus]
     assert net.line.is_stub.at[sec_line]
 
-
 def test_determine_stubs_meshed(meshed_network):
     net = meshed_network
     # root == LV side of trafo at ext_grid. Then ext_grid bus itself (0) == stub
@@ -71,6 +81,12 @@ def test_determine_stubs_meshed(meshed_network):
     assert len(stubs) == 1
     assert stubs.pop() == 0
 
+def test_determine_stubs_mixed(mixed_network):
+    net = mixed_network
+    stubs = top.determine_stubs(net, roots=[1, 4, 3])
+    assert stubs == {0}
+    stubs = top.determine_stubs(net, roots=[4, 3, 1])
+    assert stubs == {0}
 
 def test_distance(feeder_network):
     net = feeder_network