From c425cb6679aaf6835e314af60c7dea66c95eec1b Mon Sep 17 00:00:00 2001
From: dlohmeier <daniel.lohmeier@retoflow.de>
Date: Mon, 4 Dec 2023 10:47:07 +0100
Subject: [PATCH 1/3] added option to guess slider valve types; some small
 adaptions

---
 .../converter/stanet/preparing_steps.py       |  47 +++--
 .../converter/stanet/stanet2pandapipes.py     |   8 +-
 pandapipes/converter/stanet/table_creation.py | 187 +++++++++++-------
 3 files changed, 146 insertions(+), 96 deletions(-)

diff --git a/pandapipes/converter/stanet/preparing_steps.py b/pandapipes/converter/stanet/preparing_steps.py
index 0050b643a..91e8b5b5e 100644
--- a/pandapipes/converter/stanet/preparing_steps.py
+++ b/pandapipes/converter/stanet/preparing_steps.py
@@ -25,6 +25,28 @@
 logger = logging.getLogger(__name__)
 
 
+DEFAULT_STANET_KEYWORDS = {
+    "pipes": ['REM Leitungsdaten'],
+    "house_pipes": ['REM HA Leitungsdaten'],
+    "nodes": ['REM Knotendaten'],
+    "house_nodes": ["REM HA Knotendaten"],
+    "valves": ['REM Ventiledaten'],
+    "pumps_gas": ['REM Kompressorendaten'],
+    "pumps_water": ['REM Pumpendaten'],
+    "net_parameters": ['REM Netzparameterdaten'],
+    "houses": ["REM Hausdaten"],
+    "house_connections": ["REM HA Verbindungsdaten"],
+    "meters": ["REM HA Zählerdaten"],
+    "controllers": ["REM Reglerdaten"],
+    "slider_valves": ["REM Schieberdaten"],
+    "inflexion_points": ["REM Knickpunktdaten"],
+    "heat_exchangers": ["REM Wärmetauscherdaten"],
+    "customers": ["REM Abnehmerdaten"],
+    "house_inflexion_points": ["REM HA Knickpunktdaten"],
+    "layers": ["REM Layerdaten"]
+}
+
+
 def get_stanet_raw_data(stanet_path, read_options=None, add_layers=True, return_line_info=False,
                         keywords=None):
     """
@@ -52,24 +74,7 @@ def get_stanet_raw_data(stanet_path, read_options=None, add_layers=True, return_
         # 4th line: STANET internal header of table (this is the first line to convert to pandas
         #           dataframe and return to the converter)
         # everything until the next empty line will be added to the dataframe
-        keywords = {"pipes": ['REM Leitungsdaten'],
-                    "house_pipes": ['REM HA Leitungsdaten'],
-                    "nodes": ['REM Knotendaten'],
-                    "house_nodes": ["REM HA Knotendaten"],
-                    "valves": ['REM Ventiledaten'],
-                    "pumps_gas": ['REM Kompressorendaten'],
-                    "pumps_water": ['REM Pumpendaten'],
-                    "net_parameters": ['REM Netzparameterdaten'],
-                    "houses": ["REM Hausdaten"],
-                    "house_connections": ["REM HA Verbindungsdaten"],
-                    "meters": ["REM HA Zählerdaten"],
-                    "controllers": ["REM Reglerdaten"],
-                    "slider_valves": ["REM Schieberdaten"],
-                    "inflexion_points": ["REM Knickpunktdaten"],
-                    "heat_exchangers": ["REM Wärmetauscherdaten"],
-                    "customers": ["REM Abnehmerdaten"],
-                    "house_inflexion_points": ["REM HA Knickpunktdaten"],
-                    "layers": ["REM Layerdaten"]}
+        keywords = DEFAULT_STANET_KEYWORDS
     stored_data = dict()
 
     logger.info("Reading STANET csv-file.")
@@ -230,9 +235,9 @@ def adapt_pipe_data_according_to_nodes(pipe_data, pipes_to_check, node_geo, pipe
     coord = "x" if is_x else "y"
     locat = "from" if is_start else "to"
     run = 0 if is_x else 2
-    run += 0 if is_start else 1
+    run += 1 - int(is_start)
     pipe_name = coord_names[run]
-    node_nr = node_cols[0] if is_start else node_cols[1]
+    node_nr = node_cols[1 - int(is_start)]
     node_val = node_geo.loc[pipe_data.loc[pipes_to_check, node_nr].values, node_name].values
 
     if pipe_name not in pipe_data.columns:
@@ -273,7 +278,7 @@ def adapt_pipe_data(stored_data, pipe_data, coord_names, use_clients):
 
         # the following code is just a check whether pipe and node geodata fit together
         # in case of deviations, the pipe geodata is adapted on the basis of the node geodata
-        pipe_rec = pipe_data.RECNO.values
+        pipe_rec = pipe_data.index.values
         for is_x, is_start in product([True, False], [True, False]):
             current_index_range = indices[0] if is_start else indices[1]
             current_pipe_nums = pipe_rec[current_index_range.values]
diff --git a/pandapipes/converter/stanet/stanet2pandapipes.py b/pandapipes/converter/stanet/stanet2pandapipes.py
index a0a4f6875..ed644f9f9 100644
--- a/pandapipes/converter/stanet/stanet2pandapipes.py
+++ b/pandapipes/converter/stanet/stanet2pandapipes.py
@@ -31,7 +31,8 @@
 #         - maybe it will be necessary to remove deleted data from the STANET tables, otherwise they
 #           might be inserted into the pandapipes net erroneously
 def stanet_to_pandapipes(stanet_path, name="net", remove_unused_household_connections=True,
-                         stanet_like_valves=False, read_options=None, add_layers=True, **kwargs):
+                         stanet_like_valves=False, read_options=None, add_layers=True,
+                         guess_slider_valve_types=False, **kwargs):
     """Converts STANET csv-file to pandapipesNet.
 
     :param stanet_path: path to csv-file exported from STANET
@@ -50,6 +51,9 @@ def stanet_to_pandapipes(stanet_path, name="net", remove_unused_household_connec
     :param add_layers: If True, adds information on layers of different components if provided by \
             STANET
     :type add_layers: bool, default True
+    :param guess_slider_valve_types: If set to True, the slider valve status (opened / closed) is \
+            guessed based on the logic "even number = opened; odd number = closed".
+    :type guess_slider_valve_types: bool, default False
     :return: net
     :rtype: pandapipesNet
     """
@@ -99,7 +103,7 @@ def stanet_to_pandapipes(stanet_path, name="net", remove_unused_household_connec
     # pandapipes
     create_valve_and_pipe(net, stored_data, index_mapping, net_params, stanet_like_valves, add_layers)
 
-    create_slider_valves(net, stored_data, index_mapping, add_layers)
+    create_slider_valves(net, stored_data, index_mapping, add_layers, guess_slider_valve_types)
 
     if "pumps_water" in stored_data:
         create_pumps(net, stored_data['pumps_water'], index_mapping, add_layers)
diff --git a/pandapipes/converter/stanet/table_creation.py b/pandapipes/converter/stanet/table_creation.py
index 553a0c3f4..ab974d142 100644
--- a/pandapipes/converter/stanet/table_creation.py
+++ b/pandapipes/converter/stanet/table_creation.py
@@ -65,8 +65,9 @@ def create_junctions_from_nodes(net, stored_data, net_params, index_mapping, add
     add_info = {"stanet_id": node_table.STANETID.astype(str).values
                 if "STANETID" in node_table.columns else knams,
                 "p_stanet": node_table.PRECH.values.astype(np.float64),
-                "stanet_valid": ~node_table.CALCBAD.values.astype(np.bool_),
-                "t_stanet": node_table.TEMP.values.astype(np.float64)}
+                "stanet_valid": ~node_table.CALCBAD.values.astype(np.bool_)}
+    if "TEMP" in node_table.columns:
+        add_info["t_stanet"] = node_table.TEMP.values.astype(np.float64)
     if hasattr(node_table, "KFAK"):
         add_info["K_stanet"] = node_table.KFAK.values.astype(np.float64)
     if add_layers:
@@ -173,62 +174,88 @@ def create_valve_and_pipe(net, stored_data, index_mapping, net_params, stanet_li
             )
 
 
-def create_slider_valves(net, stored_data, index_mapping, add_layers):
+def create_slider_valves(net, stored_data, index_mapping, add_layers,
+                         guess_opened_from_types=False):
     """
     Creates pandapipes slider valves from STANET data.
-    :param net:
-    :type net:
-    :param stored_data:
-    :type stored_data:
-    :param index_mapping:
-    :type index_mapping:
-    :param add_layers:
-    :type add_layers:
-    :return:
-    :rtype:
+
+    :param net: pandapipes net to which to add slider valves
+    :type net: pandapipesNet
+    :param stored_data: dictionary of STANET element tables
+    :type stored_data: dict
+    :param index_mapping: dictionary of mappings between STANET and pandapipes indices
+    :type index_mapping: dict
+    :param add_layers: if True, the layer info will be added to the slider valve table
+    :type add_layers: bool
+    :param guess_opened_from_types: if True, the status of slider valves with unknown types is \
+        guessed based on the logic "even type number = opened, odd type number = closed"
+    :type guess_opened_from_types: bool, default False
+    :return: No output
+    :rtype: None
     """
-    if "slider_valves" not in stored_data:
+    if "slider_valves" not in stored_data and "house_slider_valves" not in stored_data:
         return
     logger.info("Creating all slider valves.")
-    slider_valves = stored_data["slider_valves"]
-
-    # identify all junctions that are connected on each side of the slider valves
-    svf = index_mapping["slider_valves_from"]
-    svt = index_mapping["slider_valves_to"]
-    from_junctions = np.array([svf[sv] for sv in slider_valves.RECNO.values])
-    to_junctions = np.array([svt[sv] for sv in slider_valves.RECNO.values])
-
-    # these types can be converted to normal valves
-    # --> there are many types of slider valves in STANET, the behavior is not always clear, so
-    #     if you want to convert another type, identify the correct valve behavior in pandapipes
-    #     that matches this type.
-    opened_sv = [2, 6, 10, 18]
-    closed_sv = [3, 7, 11, 19]
-    opened_types = {o: True for o in opened_sv}
-    opened_types.update({c: False for c in closed_sv})
-    sv_types = set(slider_valves.TYP.values.astype(np.int32))
-    if len(sv_types - set(opened_types.keys())):
-        raise UserWarning("The slider valve types %s cannot be converted."
-                          % (sv_types - set(opened_types.keys())))
-
-    # create all slider valves --> most important are the opened and loss_coefficient entries
-    valve_system = slider_valves.CLIENTTYP.replace(CLIENT_TYPES_OF_PIPES).values
-    add_info = dict()
-    if add_layers:
-        add_info["stanet_layer"] = slider_valves.LAYER.values.astype(str)
-    # account for sliders with diameter 0 m
-    if any(slider_valves.DM == 0):
-        logger.warning(f"{sum(slider_valves.DM == 0)} sliders have a inner diameter of 0 m! "
-                       f"The diameter will be set to 1 m.")
-        slider_valves.DM[slider_valves.DM == 0] = 1e3
-    pandapipes.create_valves(
-        net, from_junctions, to_junctions, slider_valves.DM.values / 1000,
-        opened=slider_valves.TYP.astype(np.int32).replace(opened_types).values,
-        loss_coefficient=slider_valves.ZETA.values, name=slider_valves.STANETID.values,
-        type="slider_valve_" + valve_system, stanet_nr=slider_valves.RECNO.values.astype(np.int32),
-        stanet_id=slider_valves.STANETID.values.astype(str), stanet_system=valve_system,
-        stanet_active=slider_valves.ISACTIVE.values.astype(np.bool_), **add_info
-    )
+
+    for tbl_name in ("slider_valves", "house_slider_valves"):
+        if tbl_name not in stored_data:
+            continue
+        slider_valves = stored_data[tbl_name]
+
+        # identify all junctions that are connected on each side of the slider valves
+        svf = index_mapping["slider_valves_from"]
+        svt = index_mapping["slider_valves_to"]
+        from_junctions = np.array([svf[sv] for sv in slider_valves.RECNO.values])
+        to_junctions = np.array([svt[sv] for sv in slider_valves.RECNO.values])
+
+        # these types can be converted to normal valves
+        # --> there are many types of slider valves in STANET, the behavior is not always clear, so
+        #     if you want to convert another type, identify the correct valve behavior in pandapipes
+        #     that matches this type.
+        opened_sv = [2, 6, 10, 18]
+        closed_sv = [3, 7, 11, 19]
+        # TODO: Is it possible that there is always a "CONNECTED" column and it says whether the
+        #       valve is opened or closed? Maybe the type is only used for graphical purpose.
+        opened_types = {o: True for o in opened_sv}
+        opened_types.update({c: False for c in closed_sv})
+        sv_types = set(slider_valves.TYP.values.astype(np.int32))
+        if len(sv_types - set(opened_types.keys())):
+            if guess_opened_from_types:
+                logger.warning(
+                    "The slider valve types %s are not (yet) known. Their status (opened/closed) "
+                    "will be guessed based on the logic: even number = opened, odd number = closed."
+                    % (sv_types - set(opened_types.keys()))
+                )
+                opened_types.update(
+                    {t: bool(t % 2 + 1) for t in sv_types - set(opened_types.keys())}
+                )
+            else:
+                raise UserWarning("The slider valve types %s cannot be converted."
+                                  % (sv_types - set(opened_types.keys())))
+
+        # create all slider valves --> most important are the opened and loss_coefficient entries
+        valve_system = slider_valves.CLIENTTYP.replace(CLIENT_TYPES_OF_PIPES).values
+        add_info = dict()
+        if add_layers:
+            add_info["stanet_layer"] = slider_valves.LAYER.values.astype(str)
+        # account for sliders with diameter 0 m
+        if "DM" not in slider_valves.columns:
+            logger.warning(f"The table {tbl_name} does not contain the slider valve inner diameter!"
+                           f"The diameter will be set to 1 m.")
+            slider_valves["DM"] = 1e3
+        if any(slider_valves.DM == 0):
+            logger.warning(f"{sum(slider_valves.DM == 0)} sliders have an inner diameter of 0 m! "
+                           f"The diameter will be set to 1 m.")
+            slider_valves.DM[slider_valves.DM == 0] = 1e3
+        pandapipes.create_valves(
+            net, from_junctions, to_junctions, slider_valves.DM.values / 1000,
+            opened=slider_valves.TYP.astype(np.int32).replace(opened_types).values,
+            loss_coefficient=slider_valves.ZETA.values, name=slider_valves.STANETID.values,
+            type="slider_valve_" + valve_system,
+            stanet_nr=slider_valves.RECNO.values.astype(np.int32),
+            stanet_id=slider_valves.STANETID.values.astype(str), stanet_system=valve_system,
+            stanet_active=slider_valves.ISACTIVE.values.astype(np.bool_), **add_info
+        )
 
 
 # noinspection PyTypeChecker
@@ -317,7 +344,7 @@ def create_control_components(net, stored_data, index_mapping, net_params, add_l
 
     control_active = (control_table.AKTIV.values == "J").astype(np.bool_)
     if consider_controlled:
-        control_active &= fully_open
+        control_active &= ~fully_open
     in_service = control_table.ISACTIVE.values.astype(np.bool_)
     if consider_controlled:
         in_service &= ~(control_table.ZU.values == "J")
@@ -398,7 +425,7 @@ def get_connection_types(connection_table):
     :return:
     :rtype:
     """
-    extend_from_to = ["slider_valves"]
+    extend_from_to = ["slider_valves", "house_slider_valves"]
     connection_types = list(chain.from_iterable([
         [(ct, ct)] if ct not in extend_from_to else [(ct, ct + "_from"), (ct, ct + "_to")]
         for ct in set(connection_table.type)
@@ -432,6 +459,8 @@ def create_junctions_from_connections(net, connection_table, net_params, index_m
     extend_from_to, connection_types = get_connection_types(connection_table)
     for con_type, node_type in connection_types:
         cons = connection_table.loc[connection_table.type == con_type]
+        if cons.empty:
+            continue
         stanet_ids = cons.STANETID.astype(str).values
         stanet_nrs = cons.RECNO.astype(np.int32).values
         p_stanet = cons.PRECH.astype(np.float64).values if houses_in_calculation else np.NaN
@@ -566,11 +595,14 @@ def create_geodata_sections(row):
     text_k = 293
     if "TU" in pipes.columns:
         text_k = pipes.TU.values.astype(np.float64) + 273.15
+    alpha = 0
+    if "WDZAHL" in pipes.columns:
+        alpha = pipes.WDZAHL.values.astype(np.float64)
     pandapipes.create_pipes_from_parameters(
         net, pipe_sections.fj.values, pipe_sections.tj.values, pipe_sections.length.values / 1000,
         pipes.DM.values / 1000, pipes.RAU.values, pipes.ZETA.values, type="main_pipe",
         stanet_std_type=pipes.ROHRTYP.values, in_service=pipes.ISACTIVE.values, text_k=text_k,
-        alpha_w_per_m2k=pipes.WDZAHL.values.astype(np.float64),
+        alpha_w_per_m2k=alpha,
         name=["pipe_%s_%s_%s" % (nf, nt, sec) for nf, nt, sec in zip(
             pipes.ANFNAM.values, pipes.ENDNAM.values, pipe_sections.section_no.values)],
         stanet_nr=pipes.RECNO.values, stanet_id=pipes.STANETID.values,
@@ -694,14 +726,16 @@ def create_pipes_from_remaining_pipe_table(net, stored_data, connection_table, i
     text_k = 293
     if "TU" in p_tbl.columns:
         text_k = p_tbl.TU.values.astype(np.float64) + 273.15
+    alpha = 0
+    if "WDZAHL" in p_tbl.columns:
+        alpha = p_tbl.WDZAHL.values.astype(np.float64)
     pandapipes.create_pipes_from_parameters(
         net, from_junctions, to_junctions, length_km=p_tbl.RORL.values.astype(np.float64) / 1000,
         type="main_pipe", diameter_m=p_tbl.DM.values.astype(np.float64) / 1000,
         loss_coefficient=p_tbl.ZETA.values, stanet_std_type=p_tbl.ROHRTYP.values,
         k_mm=p_tbl.RAU.values, in_service=p_tbl.ISACTIVE.values.astype(np.bool_),
-        alpha_w_per_m2k=p_tbl.WDZAHL.values.astype(np.float64), text_k=text_k,
         name=["pipe_%s_%s" % (anf, end) for anf, end in zip(from_names[valid], to_names[valid])],
-        stanet_nr=p_tbl.RECNO.values.astype(np.int32),
+        alpha_w_per_m2k=alpha, text_k=text_k, stanet_nr=p_tbl.RECNO.values.astype(np.int32),
         stanet_id=p_tbl.STANETID.values.astype(str), v_stanet=p_tbl.VM.values, geodata=geodata,
         stanet_system=CLIENT_TYPES_OF_PIPES[MAIN_PIPE_TYPE],
         stanet_active=p_tbl.ISACTIVE.values.astype(np.bool_),
@@ -710,7 +744,8 @@ def create_pipes_from_remaining_pipe_table(net, stored_data, connection_table, i
     )
 
 
-def check_connection_client_types(hh_pipes, all_client_types, node_client_types):
+def check_connection_client_types(hh_pipes, all_client_types, node_client_types,
+                                  fail_on_connection_check=True):
     # create pipes for household connections (from house to supply pipe), which is a separate table
     # in the STANET CSV file
     # --> there are many ways how household connections can be created in STANET,
@@ -725,16 +760,20 @@ def check_connection_client_types(hh_pipes, all_client_types, node_client_types)
     clientnodetype = hh_pipes.CLIENTTYP.isin(node_client_types)
     client2nodetype = hh_pipes.CLIENT2TYP.isin(node_client_types)
     if not np.all(clientnodetype | client2nodetype):
-        raise UserWarning(
-            f"One of the household connection sides must be connected to a node (type {NODE_TYPE} element)\n"
-            f"or a connection (type {HOUSE_CONNECTION_TYPE} element with ID CON...) "
-            f"or a house node (type {HOUSE_CONNECTION_TYPE} element). \n"
-            f"Please check that the input data is correct. \n"
-            f"Check these CLIENTTYP / CLIENT2TYP: "
-            f"{set(hh_pipes.loc[~clientnodetype, 'CLIENTTYP'].values) | set(hh_pipes.loc[~client2nodetype, 'CLIENT2TYP'].values)} "
-            f"in the HA LEI table (max. 10 entries shown): \n "
-            f"{hh_pipes.loc[~clientnodetype & ~client2nodetype].head(10)}"
-            )
+        not_node_type = (set(hh_pipes.loc[~clientnodetype, 'CLIENTTYP'].values)
+                         | set(hh_pipes.loc[~client2nodetype, 'CLIENT2TYP'].values))
+        msg = (f"One of the household connection sides must be connected to a node (type "
+               f"{NODE_TYPE} element)\n or a connection (type {HOUSE_CONNECTION_TYPE} element with"
+               f" ID CON...) or a house node (type {HOUSE_CONNECTION_TYPE} element). \n"
+               f"Please check that the input data is correct. \n"
+               f"Check these CLIENTTYP / CLIENT2TYP: "
+               f"{not_node_type} in the HA LEI table (max. 10 entries shown): \n"
+               f"{hh_pipes.loc[~clientnodetype & ~client2nodetype].head(10)}")
+        if fail_on_connection_check:
+            raise UserWarning(msg)
+        else:
+            logger.warning(f"{msg} \nWill ignore this error and continue net setup, please check "
+                           f"your network configuration carefully!")
     return clientnodetype, client2nodetype
 
 
@@ -1009,16 +1048,18 @@ def create_geodata_sections(row):
     text_k = 293
     if "TU" in hp_data.columns:
         text_k = hp_data.TU.values.astype(np.float64) + 273.15
+    alpha = 0
+    if "WDZAHL" in hp_data.columns:
+        alpha = hp_data.WDZAHL.values.astype(np.float64)
     pandapipes.create_pipes_from_parameters(
         net, hp_data.fj.values, hp_data.tj.values, hp_data.length.values / 1000,
         hp_data.DM.values / 1000, hp_data.RAU.values, hp_data.ZETA.values, type="house_pipe",
-        stanet_std_type=hp_data.ROHRTYP.values,
         in_service=hp_data.ISACTIVE.values if houses_in_calculation else False, text_k=text_k,
-        alpha_w_per_m2k=hp_data.WDZAHL.values.astype(np.float64),
+        alpha_w_per_m2k=alpha, geodata=hp_data.section_geo.values,
         name=["pipe_%s_%s_%s" % (nf, nt, sec) for nf, nt, sec in zip(
             hp_data.CLIENTID.values, hp_data.CLIENT2ID.values, hp_data.section_no.values)],
-        stanet_nr=hp_data.RECNO.values, stanet_id=hp_data.STANETID.values,
-        geodata=hp_data.section_geo.values, v_stanet=hp_data.VM.values,
+        stanet_std_type=hp_data.ROHRTYP.values, stanet_nr=hp_data.RECNO.values,
+        stanet_id=hp_data.STANETID.values, v_stanet=hp_data.VM.values,
         stanet_active=hp_data.ISACTIVE.values.astype(np.bool_),
         stanet_valid=houses_in_calculation, **add_info
     )

From f1d9ba74413bd7f912f5f8446c00253f25cdf80d Mon Sep 17 00:00:00 2001
From: dlohmeier <daniel.lohmeier@retoflow.de>
Date: Thu, 14 Dec 2023 16:05:17 +0100
Subject: [PATCH 2/3] switch pump junctions in DHN tutorial; closes #580

---
 tutorials/circular_flow_in_a_district_heating_grid.ipynb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tutorials/circular_flow_in_a_district_heating_grid.ipynb b/tutorials/circular_flow_in_a_district_heating_grid.ipynb
index 5fe99d839..a06e23669 100644
--- a/tutorials/circular_flow_in_a_district_heating_grid.ipynb
+++ b/tutorials/circular_flow_in_a_district_heating_grid.ipynb
@@ -71,7 +71,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "pp.create_circ_pump_const_mass_flow(net, return_junction=j0, flow_junction=j3, p_flow_bar=5,\n",
+    "pp.create_circ_pump_const_mass_flow(net, return_junction=j3, flow_junction=j0, p_flow_bar=5,\n",
     "                                    mdot_flow_kg_per_s=20, t_flow_k=273.15+35)"
    ]
   },

From 6fec1cf1545929ec74a2d1a15b597b3575f5827c Mon Sep 17 00:00:00 2001
From: sdrauz <Simon.Ruben.Drauz@iee.fraunhofer.de>
Date: Fri, 15 Dec 2023 20:53:08 +0100
Subject: [PATCH 3/3] converged is explicit. Only under net defined.

---
 pandapipes/pf/pipeflow_setup.py              |  2 +-
 pandapipes/pipeflow.py                       | 38 ++++++++------------
 pandapipes/test/api/test_special_networks.py |  2 +-
 3 files changed, 17 insertions(+), 25 deletions(-)

diff --git a/pandapipes/pf/pipeflow_setup.py b/pandapipes/pf/pipeflow_setup.py
index 713f05be8..795fbabda 100644
--- a/pandapipes/pf/pipeflow_setup.py
+++ b/pandapipes/pf/pipeflow_setup.py
@@ -31,7 +31,7 @@
 
 logger = logging.getLogger(__name__)
 
-default_options = {"friction_model": "nikuradse", "converged": False, "tol_p": 1e-4, "tol_v": 1e-4,
+default_options = {"friction_model": "nikuradse", "tol_p": 1e-4, "tol_v": 1e-4,
                    "tol_T": 1e-3, "tol_res": 1e-3, "iter": 10, "error_flag": False, "alpha": 1,
                    "nonlinear_method": "constant", "mode": "hydraulics",
                    "ambient_temperature": 293, "check_connectivity": True,
diff --git a/pandapipes/pipeflow.py b/pandapipes/pipeflow.py
index aa9f49947..6f1184929 100644
--- a/pandapipes/pipeflow.py
+++ b/pandapipes/pipeflow.py
@@ -65,7 +65,7 @@ def pipeflow(net, sol_vec=None, **kwargs):
     init_options(net, local_params)
 
     # init result tables
-    net["converged"] = False
+    net.converged = False
     init_all_result_tables(net)
 
     create_lookups(net)
@@ -90,8 +90,8 @@ def pipeflow(net, sol_vec=None, **kwargs):
 
     if calculate_hydraulics:
         reduce_pit(net, node_pit, branch_pit, mode="hydraulics")
-        converged, _ = hydraulics(net)
-        if not converged:
+        hydraulics(net)
+        if not net.converged:
             raise PipeflowNotConverged("The hydraulic calculation did not converge to a solution.")
         extract_results_active_pit(net, mode="hydraulics")
 
@@ -99,8 +99,8 @@ def pipeflow(net, sol_vec=None, **kwargs):
         node_pit, branch_pit = net["_pit"]["node"], net["_pit"]["branch"]
         identify_active_nodes_branches(net, branch_pit, node_pit, False)
         reduce_pit(net, node_pit, branch_pit, mode="heat_transfer")
-        converged, _ = heat_transfer(net)
-        if not converged:
+        heat_transfer(net)
+        if not net.converged:
             raise PipeflowNotConverged("The heat transfer calculation did not converge to a "
                                        "solution.")
         extract_results_active_pit(net, mode="heat_transfer")
@@ -125,7 +125,7 @@ def hydraulics(net):
     error_v, error_p, residual_norm = [], [], None
 
     # This loop is left as soon as the solver converged
-    while not get_net_option(net, "converged") and niter <= max_iter:
+    while not net.converged and niter <= max_iter:
         logger.debug("niter %d" % niter)
 
         # solve_hydraulics is where the calculation takes place
@@ -145,17 +145,13 @@ def hydraulics(net):
     write_internal_results(net, iterations=niter, error_p=error_p[niter - 1],
                            error_v=error_v[niter - 1], residual_norm=residual_norm)
 
-    converged = get_net_option(net, "converged")
-    net['converged'] = converged
-    if converged:
+    if net.converged:
         set_user_pf_options(net, hyd_flag=True)
 
-    log_final_results(net, converged, niter, residual_norm)
+    log_final_results(net, niter, residual_norm)
     if not get_net_option(net, "reuse_internal_data"):
         net.pop("_internal_data", None)
 
-    return converged, niter
-
 
 def heat_transfer(net):
     max_iter, nonlinear_method, tol_t, tol_res = get_net_options(
@@ -170,11 +166,11 @@ def heat_transfer(net):
 
     error_t, error_t_out, residual_norm = [], [], None
 
-    set_net_option(net, "converged", False)
+    net.converged = False
     niter = 0
 
     # This loop is left as soon as the solver converged
-    while not get_net_option(net, "converged") and niter <= max_iter:
+    while not net.converged and niter <= max_iter:
         logger.debug("niter %d" % niter)
 
         # solve_hydraulics is where the calculation takes place
@@ -198,11 +194,7 @@ def heat_transfer(net):
     write_internal_results(net, iterations_T=niter, error_T=error_t[niter - 1],
                            residual_norm_T=residual_norm)
 
-    converged = get_net_option(net, "converged")
-    net['converged'] = converged
-    log_final_results(net, converged, niter, residual_norm, hyraulic_mode=False)
-
-    return converged, niter
+    log_final_results(net, niter, residual_norm, hyraulic_mode=False)
 
 
 def solve_hydraulics(net):
@@ -334,9 +326,9 @@ def finalize_iteration(net, niter, error_1, error_2, residual_norm, nonlinear_me
     # Setting convergence flag
     if error_2[niter] <= tol_2 and error_1[niter] <= tol_1 and residual_norm < tol_res:
         if nonlinear_method != "automatic":
-            set_net_option(net, "converged", True)
+            net.converged = True
         elif get_net_option(net, "alpha") == 1:
-            set_net_option(net, "converged", True)
+            net.converged = True
 
     if hydraulic_mode:
         logger.debug("errorv: %s" % error_1[niter])
@@ -347,7 +339,7 @@ def finalize_iteration(net, niter, error_1, error_2, residual_norm, nonlinear_me
         logger.debug("alpha: %s" % get_net_option(net, "alpha"))
 
 
-def log_final_results(net, converged, niter, residual_norm, hyraulic_mode=True):
+def log_final_results(net, niter, residual_norm, hyraulic_mode=True):
     if hyraulic_mode:
         solver = "hydraulics"
         outputs = ["tol_p", "tol_v"]
@@ -355,7 +347,7 @@ def log_final_results(net, converged, niter, residual_norm, hyraulic_mode=True):
         solver = "heat transfer"
         outputs = ["tol_T"]
     logger.debug("--------------------------------------------------------------------------------")
-    if not converged:
+    if not net.converged:
         logger.debug("Maximum number of iterations reached but %s solver did not converge."
                      % solver)
         logger.debug("Norm of residual: %s" % residual_norm)
diff --git a/pandapipes/test/api/test_special_networks.py b/pandapipes/test/api/test_special_networks.py
index 0015844e4..1b09cd661 100644
--- a/pandapipes/test/api/test_special_networks.py
+++ b/pandapipes/test/api/test_special_networks.py
@@ -117,7 +117,7 @@ def test_wild_indexing(create_net_changed_indices):
     net = copy.deepcopy(create_net_changed_indices)
 
     pandapipes.pipeflow(net)
-    assert net["converged"]
+    assert net.converged
 
 
 if __name__ == "__main__":