From c425cb6679aaf6835e314af60c7dea66c95eec1b Mon Sep 17 00:00:00 2001 From: dlohmeier Date: Mon, 4 Dec 2023 10:47:07 +0100 Subject: [PATCH] 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 )