From dc00b1c1bfe34d2c236502fe9136d8162193eda0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Mon, 2 Dec 2024 08:10:10 +0000 Subject: [PATCH 01/28] Drop support of deprecated files --- epyt_flow/simulation/scada/scada_data.py | 9 +-------- epyt_flow/simulation/sensor_config.py | 18 ++++++------------ 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/epyt_flow/simulation/scada/scada_data.py b/epyt_flow/simulation/scada/scada_data.py index 59eeaed..6a51205 100644 --- a/epyt_flow/simulation/scada/scada_data.py +++ b/epyt_flow/simulation/scada/scada_data.py @@ -142,8 +142,6 @@ def __init__(self, sensor_config: SensorConfig, sensor_readings_time: np.ndarray dict[int, bsr_array]] = None, bulk_species_link_concentration_raw: Union[np.ndarray, dict[int, bsr_array]] = None, - pump_energy_usage_data = None, - pump_efficiency_data = None, pumps_energy_usage_data_raw: np.ndarray = None, pumps_efficiency_data_raw: np.ndarray = None, sensor_faults: list[SensorFault] = [], @@ -289,11 +287,6 @@ def __init__(self, sensor_config: SensorConfig, sensor_readings_time: np.ndarray raise TypeError("'frozen_sensor_config' must be an instance of 'bool' " + f"but not of '{type(frozen_sensor_config)}'") - if pump_efficiency_data is not None or pump_energy_usage_data is not None: - warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" + - " -- support of such old files will be removed in the next release!", - DeprecationWarning) - def __raise_shape_mismatch(var_name: str) -> None: raise ValueError(f"Shape mismatch in '{var_name}' -- " + "i.e number of time steps in 'sensor_readings_time' " + @@ -2668,7 +2661,7 @@ def plot_pumps_efficiency(self, sensor_locations: list[str] = None, show: bool = return plot_timeseries_data(data.T, labels=[f"Pump {n_id}" for n_id in pump_efficiency_sensors], x_axis_label=self.__get_x_axis_label(), - y_axis_label="Efficiency in $\%$", + y_axis_label="Efficiency in $%$", show=show, save_to_file=save_to_file, ax=ax) def get_data_pumps_energyconsumption(self, sensor_locations: list[str] = None) -> np.ndarray: diff --git a/epyt_flow/simulation/sensor_config.py b/epyt_flow/simulation/sensor_config.py index 4835e6d..e59a8f4 100644 --- a/epyt_flow/simulation/sensor_config.py +++ b/epyt_flow/simulation/sensor_config.py @@ -2,7 +2,6 @@ Module provides a class for implementing sensor configurations. """ from copy import deepcopy -import warnings import itertools import numpy as np import epyt @@ -465,7 +464,7 @@ class SensorConfig(JsonSerializable): """ def __init__(self, nodes: list[str], links: list[str], valves: list[str], pumps: list[str], tanks: list[str], bulk_species: list[str], surface_species: list[str], - flow_unit: int = None, + flow_unit: int, pressure_sensors: list[str] = [], flow_sensors: list[str] = [], demand_sensors: list[str] = [], @@ -685,16 +684,11 @@ def __init__(self, nodes: list[str], links: list[str], valves: list[str], pumps: if any(s not in surface_species for s in surfacespecies_id_to_idx.keys()): raise ValueError("Unknown surface species ID in 'surfacespecies_id_to_idx'") - if flow_unit is not None: - if not isinstance(flow_unit, int): - raise TypeError("'flow_unit' must be a an instance of 'int' " + - f"but not of '{type(flow_unit)}'") - if flow_unit not in range(10): - raise ValueError("Invalid value of 'flow_unit'") - else: - warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" + - " -- support of such old files will be removed in the next release!", - DeprecationWarning) + if not isinstance(flow_unit, int): + raise TypeError("'flow_unit' must be a an instance of 'int' " + + f"but not of '{type(flow_unit)}'") + if flow_unit not in range(10): + raise ValueError("Invalid value of 'flow_unit'") if quality_unit is not None: if not isinstance(quality_unit, int): From ad476e665cfe1a049c0400fbf6ecf6525406c9b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Tue, 3 Dec 2024 08:19:23 +0000 Subject: [PATCH 02/28] Remove metadata from notebooks --- docs/examples/abrupt_leakage.ipynb | 10 ++-------- docs/examples/basic_usage.ipynb | 8 +------- docs/examples/chlorine_injection.ipynb | 8 +------- docs/examples/control_example.ipynb | 8 +------- docs/examples/net2-cl2_example.ipynb | 8 +------- docs/examples/uncertainties.ipynb | 8 +------- docs/examples/water_age.ipynb | 8 +------- 7 files changed, 8 insertions(+), 50 deletions(-) diff --git a/docs/examples/abrupt_leakage.ipynb b/docs/examples/abrupt_leakage.ipynb index 9263cc4..a4a59c6 100644 --- a/docs/examples/abrupt_leakage.ipynb +++ b/docs/examples/abrupt_leakage.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "data": {}, "source": [ "# Abrupt Leakage Example" ] @@ -180,14 +180,8 @@ } ], "metadata": { - "kernelspec": { - "display_name": "epytflow2", - "language": "python", - "name": "python3" - }, "language_info": { - "name": "python", - "version": "3.9.18" + "name": "python" } }, "nbformat": 4, diff --git a/docs/examples/basic_usage.ipynb b/docs/examples/basic_usage.ipynb index c856f11..b7c5c74 100644 --- a/docs/examples/basic_usage.ipynb +++ b/docs/examples/basic_usage.ipynb @@ -206,14 +206,8 @@ } ], "metadata": { - "kernelspec": { - "display_name": "epytflow2", - "language": "python", - "name": "python3" - }, "language_info": { - "name": "python", - "version": "3.9.18" + "name": "python" } }, "nbformat": 4, diff --git a/docs/examples/chlorine_injection.ipynb b/docs/examples/chlorine_injection.ipynb index 67bdd53..1df5ae9 100644 --- a/docs/examples/chlorine_injection.ipynb +++ b/docs/examples/chlorine_injection.ipynb @@ -194,14 +194,8 @@ } ], "metadata": { - "kernelspec": { - "display_name": "epytflow2", - "language": "python", - "name": "python3" - }, "language_info": { - "name": "python", - "version": "3.9.18" + "name": "python" } }, "nbformat": 4, diff --git a/docs/examples/control_example.ipynb b/docs/examples/control_example.ipynb index cf429d6..9fe1ebe 100644 --- a/docs/examples/control_example.ipynb +++ b/docs/examples/control_example.ipynb @@ -219,14 +219,8 @@ } ], "metadata": { - "kernelspec": { - "display_name": "epytflow2", - "language": "python", - "name": "python3" - }, "language_info": { - "name": "python", - "version": "3.9.18" + "name": "python" } }, "nbformat": 4, diff --git a/docs/examples/net2-cl2_example.ipynb b/docs/examples/net2-cl2_example.ipynb index 393f185..e05feb6 100644 --- a/docs/examples/net2-cl2_example.ipynb +++ b/docs/examples/net2-cl2_example.ipynb @@ -141,14 +141,8 @@ } ], "metadata": { - "kernelspec": { - "display_name": "epytflow2", - "language": "python", - "name": "python3" - }, "language_info": { - "name": "python", - "version": "3.9.18" + "name": "python" } }, "nbformat": 4, diff --git a/docs/examples/uncertainties.ipynb b/docs/examples/uncertainties.ipynb index b4923eb..142d228 100644 --- a/docs/examples/uncertainties.ipynb +++ b/docs/examples/uncertainties.ipynb @@ -158,14 +158,8 @@ } ], "metadata": { - "kernelspec": { - "display_name": "epytflow2", - "language": "python", - "name": "python3" - }, "language_info": { - "name": "python", - "version": "3.9.18" + "name": "python" } }, "nbformat": 4, diff --git a/docs/examples/water_age.ipynb b/docs/examples/water_age.ipynb index d3080fe..1371b94 100644 --- a/docs/examples/water_age.ipynb +++ b/docs/examples/water_age.ipynb @@ -159,14 +159,8 @@ } ], "metadata": { - "kernelspec": { - "display_name": "epytflow2", - "language": "python", - "name": "python3" - }, "language_info": { - "name": "python", - "version": "3.9.18" + "name": "python" } }, "nbformat": 4, From de62cf8b9827698fd6f4257ed67b7ed02da25459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Tue, 3 Dec 2024 08:32:59 +0000 Subject: [PATCH 03/28] Add __version__ attribute --- epyt_flow/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/epyt_flow/__init__.py b/epyt_flow/__init__.py index 9e02e2d..f1203b5 100644 --- a/epyt_flow/__init__.py +++ b/epyt_flow/__init__.py @@ -6,6 +6,7 @@ with open(os.path.join(os.path.dirname(__file__), 'VERSION'), encoding="utf-8") as f: VERSION = f.read().strip() + __version__ = VERSION def compile_libraries_unix(lib_epanet_name: str, compile_script_name: str, From 873f4858326324b2ff35b2af3bb13c61e12c2031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Tue, 3 Dec 2024 10:06:08 +0000 Subject: [PATCH 04/28] Add language metainfo to notebooks --- docs/examples/abrupt_leakage.ipynb | 20 ++++++++++++++++++-- docs/examples/basic_usage.ipynb | 18 +++++++++++++++++- docs/examples/chlorine_injection.ipynb | 11 ++++++++++- docs/examples/control_example.ipynb | 11 ++++++++++- docs/examples/event_detection.ipynb | 11 ++++++++++- docs/examples/net2-cl2_example.ipynb | 11 ++++++++++- docs/examples/network_topology.ipynb | 11 ++++++++++- docs/examples/plot_network.ipynb | 11 ++++++++++- docs/examples/pump_states.ipynb | 11 ++++++++++- docs/examples/sensor_fault.ipynb | 11 ++++++++++- docs/examples/sensor_override_attack.ipynb | 11 ++++++++++- docs/examples/sensor_replay_attack.ipynb | 11 ++++++++++- docs/examples/uncertainties.ipynb | 11 ++++++++++- docs/examples/visualization.ipynb | 15 +++++++++------ docs/examples/water_age.ipynb | 11 ++++++++++- 15 files changed, 164 insertions(+), 21 deletions(-) diff --git a/docs/examples/abrupt_leakage.ipynb b/docs/examples/abrupt_leakage.ipynb index a4a59c6..96a655a 100644 --- a/docs/examples/abrupt_leakage.ipynb +++ b/docs/examples/abrupt_leakage.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "data": {}, + "metadata": {}, "source": [ "# Abrupt Leakage Example" ] @@ -177,11 +177,27 @@ "source": [ "sim.close()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/basic_usage.ipynb b/docs/examples/basic_usage.ipynb index b7c5c74..ae31080 100644 --- a/docs/examples/basic_usage.ipynb +++ b/docs/examples/basic_usage.ipynb @@ -203,11 +203,27 @@ "source": [ "sim.close()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/chlorine_injection.ipynb b/docs/examples/chlorine_injection.ipynb index 1df5ae9..03940ba 100644 --- a/docs/examples/chlorine_injection.ipynb +++ b/docs/examples/chlorine_injection.ipynb @@ -195,7 +195,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/control_example.ipynb b/docs/examples/control_example.ipynb index 9fe1ebe..8f8599d 100644 --- a/docs/examples/control_example.ipynb +++ b/docs/examples/control_example.ipynb @@ -220,7 +220,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/event_detection.ipynb b/docs/examples/event_detection.ipynb index 172ad6e..09b5ad1 100644 --- a/docs/examples/event_detection.ipynb +++ b/docs/examples/event_detection.ipynb @@ -265,7 +265,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/net2-cl2_example.ipynb b/docs/examples/net2-cl2_example.ipynb index e05feb6..e29abdc 100644 --- a/docs/examples/net2-cl2_example.ipynb +++ b/docs/examples/net2-cl2_example.ipynb @@ -142,7 +142,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/network_topology.ipynb b/docs/examples/network_topology.ipynb index d62a531..c9ad688 100644 --- a/docs/examples/network_topology.ipynb +++ b/docs/examples/network_topology.ipynb @@ -290,7 +290,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/plot_network.ipynb b/docs/examples/plot_network.ipynb index d842104..f1de2c8 100644 --- a/docs/examples/plot_network.ipynb +++ b/docs/examples/plot_network.ipynb @@ -110,7 +110,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/pump_states.ipynb b/docs/examples/pump_states.ipynb index 1980613..f6b5bc5 100644 --- a/docs/examples/pump_states.ipynb +++ b/docs/examples/pump_states.ipynb @@ -184,7 +184,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/sensor_fault.ipynb b/docs/examples/sensor_fault.ipynb index 597895c..0f8058d 100644 --- a/docs/examples/sensor_fault.ipynb +++ b/docs/examples/sensor_fault.ipynb @@ -167,7 +167,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/sensor_override_attack.ipynb b/docs/examples/sensor_override_attack.ipynb index 71cf0a2..92074e0 100644 --- a/docs/examples/sensor_override_attack.ipynb +++ b/docs/examples/sensor_override_attack.ipynb @@ -176,7 +176,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/sensor_replay_attack.ipynb b/docs/examples/sensor_replay_attack.ipynb index 9121a79..24ddd95 100644 --- a/docs/examples/sensor_replay_attack.ipynb +++ b/docs/examples/sensor_replay_attack.ipynb @@ -219,7 +219,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/uncertainties.ipynb b/docs/examples/uncertainties.ipynb index 142d228..f60f413 100644 --- a/docs/examples/uncertainties.ipynb +++ b/docs/examples/uncertainties.ipynb @@ -159,7 +159,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/visualization.ipynb b/docs/examples/visualization.ipynb index 054335d..d50bf37 100644 --- a/docs/examples/visualization.ipynb +++ b/docs/examples/visualization.ipynb @@ -258,14 +258,17 @@ "source": [ "wdn.close()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df3db290", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", @@ -276,7 +279,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.4" + "version": "3.9.20" } }, "nbformat": 4, diff --git a/docs/examples/water_age.ipynb b/docs/examples/water_age.ipynb index 1371b94..dcd3432 100644 --- a/docs/examples/water_age.ipynb +++ b/docs/examples/water_age.ipynb @@ -160,7 +160,16 @@ ], "metadata": { "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" } }, "nbformat": 4, From 2435a950a0dcb7ff0aecf6ea705f2c522ad17534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Wed, 4 Dec 2024 08:41:09 +0000 Subject: [PATCH 05/28] load_inp: More informative error mesage --- epyt_flow/data/networks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epyt_flow/data/networks.py b/epyt_flow/data/networks.py index 1eba8ec..df51588 100644 --- a/epyt_flow/data/networks.py +++ b/epyt_flow/data/networks.py @@ -102,7 +102,7 @@ def load_inp(f_in: str, include_empty_sensor_config: bool = True, Scenario configuration for the .inp file. """ if not os.path.isfile(f_in): - raise ValueError("Can not find 'f_in'") + raise ValueError(f"Can not find {f_in}") if include_empty_sensor_config is True: return ScenarioConfig(f_inp_in=f_in, sensor_config=create_empty_sensor_config(f_inp=f_in), From 5bc5558198dde32c3a16fd0d1a9653c17dc3d7f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Thu, 5 Dec 2024 08:00:31 +0000 Subject: [PATCH 06/28] RTD bug: Replace GitHub links with links to own file server --- epyt_flow/data/benchmarks/leakdb.py | 4 +-- epyt_flow/data/networks.py | 40 ++++++++++------------------- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/epyt_flow/data/benchmarks/leakdb.py b/epyt_flow/data/benchmarks/leakdb.py index 8b2e712..3ce17b2 100644 --- a/epyt_flow/data/benchmarks/leakdb.py +++ b/epyt_flow/data/benchmarks/leakdb.py @@ -491,9 +491,9 @@ def gen_dem(download_dir): return dem_final - week_pattern_url = "https://github.com/KIOS-Research/LeakDB/raw/master/CCWI-WDSA2018/" +\ + week_pattern_url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/CCWI-WDSA2018/" +\ "Dataset_Generator_Py3/weekPat_30min.mat" - year_offset_url = "https://github.com/KIOS-Research/LeakDB/raw/master/CCWI-WDSA2018/" +\ + year_offset_url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/CCWI-WDSA2018/" +\ "Dataset_Generator_Py3/yearOffset_30min.mat" download_if_necessary(os.path.join(download_dir, "weekPat_30min.mat"), diff --git a/epyt_flow/data/networks.py b/epyt_flow/data/networks.py index df51588..facb2b1 100644 --- a/epyt_flow/data/networks.py +++ b/epyt_flow/data/networks.py @@ -153,8 +153,7 @@ def load_net1(download_dir: str = get_temp_folder(), verbose: bool = True, :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "Net1.inp") - url = "https://raw.githubusercontent.com/OpenWaterAnalytics/EPyT/main/epyt/networks/" + \ - "asce-tf-wdst/Net1.inp" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/Net1.inp" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -201,8 +200,7 @@ def load_net2(download_dir: str = get_temp_folder(), verbose: bool = True, :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "Net2.inp") - url = "https://raw.githubusercontent.com/OpenWaterAnalytics/EPyT/main/epyt/networks/" + \ - "asce-tf-wdst/Net2.inp" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/Net2.inp" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -249,8 +247,7 @@ def load_net3(download_dir: str = get_temp_folder(), verbose: bool = True, :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "Net3.inp") - url = "https://raw.githubusercontent.com/OpenWaterAnalytics/EPyT/main/epyt/networks/" + \ - "asce-tf-wdst/Net3.inp" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/Net3.inp" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -297,7 +294,7 @@ def load_net6(download_dir: str = get_temp_folder(), verbose: bool = True, :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "Net6.inp") - url = "https://github.com/OpenWaterAnalytics/WNTR/raw/main/examples/networks/Net6.inp" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/Net6.inp" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -344,8 +341,7 @@ def load_richmond(download_dir: str = get_temp_folder(), verbose: bool = True, :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "Richmond_standard.inp") - url = "https://raw.githubusercontent.com/OpenWaterAnalytics/EPyT/main/epyt/networks/" + \ - "exeter-benchmarks/Richmond_standard.inp" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/Richmond_standard.inp" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -392,8 +388,7 @@ def load_micropolis(download_dir: str = get_temp_folder(), verbose: bool = True, :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "MICROPOLIS_v1.inp") - url = "https://github.com/OpenWaterAnalytics/EPyT/raw/main/epyt/networks/asce-tf-wdst/" + \ - "MICROPOLIS_v1.inp" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/MICROPOLIS_v1.inp" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -440,8 +435,7 @@ def load_balerma(download_dir: str = get_temp_folder(), verbose: bool = True, :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "Balerma.inp") - url = "https://github.com/OpenWaterAnalytics/EPyT/raw/main/epyt/networks/" + \ - "asce-tf-wdst/Balerma.inp" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/Balerma.inp" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -488,8 +482,7 @@ def load_rural(download_dir: str = get_temp_folder(), verbose: bool = True, :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "RuralNetwork.inp") - url = "https://github.com/OpenWaterAnalytics/EPyT/raw/main/epyt/networks/" + \ - "asce-tf-wdst/RuralNetwork.inp" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/RuralNetwork.inp" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -536,8 +529,7 @@ def load_bwsn1(download_dir: str = get_temp_folder(), verbose: bool = True, :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "BWSN_Network_1.inp") - url = "https://github.com/OpenWaterAnalytics/EPyT/raw/main/epyt/networks/" + \ - "asce-tf-wdst/BWSN_Network_1.inp" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/BWSN_Network_1.inp" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -584,8 +576,7 @@ def load_bwsn2(download_dir: str = get_temp_folder(), verbose: bool = True, :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "BWSN_Network_2.inp") - url = "https://github.com/OpenWaterAnalytics/EPyT/raw/main/epyt/networks/" + \ - "asce-tf-wdst/BWSN_Network_2.inp" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/BWSN_Network_2.inp" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -632,8 +623,7 @@ def load_anytown(download_dir: str = get_temp_folder(), verbose: bool = True, :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "Anytown.inp") - url = "https://raw.githubusercontent.com/OpenWaterAnalytics/EPyT/main/epyt/networks/" + \ - "asce-tf-wdst/Anytown.inp" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/Anytown.inp" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -727,7 +717,7 @@ def load_ctown(download_dir: str = get_temp_folder(), verbose: bool = True, :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "CTOWN.INP") - url = "https://github.com/scy-phy/www.batadal.net/raw/master/data/CTOWN.INP" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/CTOWN.INP" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -783,8 +773,7 @@ def load_kentucky(wdn_id: int = 1, download_dir: str = get_temp_folder(), raise ValueError(f"Unknown network 'ky{wdn_id}.inp'") f_in = os.path.join(download_dir, f"ky{wdn_id}.inp") - url = "https://raw.githubusercontent.com/OpenWaterAnalytics/EPyT/main/epyt/networks/" + \ - f"asce-tf-wdst/ky{wdn_id}.inp" + url = f"https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/ky{wdn_id}.inp" download_if_necessary(f_in, url, verbose) return load_inp(f_in, flow_units_id=flow_units_id) @@ -836,8 +825,7 @@ def load_hanoi(download_dir: str = get_temp_folder(), :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ f_in = os.path.join(download_dir, "Hanoi.inp") - url = "https://raw.githubusercontent.com/OpenWaterAnalytics/EPyT/main/epyt/networks/" + \ - "asce-tf-wdst/Hanoi.inp" + url = "https://filedn.com/lumBFq2P9S74PNoLPWtzxG4/EPyT-Flow/Networks/Hanoi.inp" download_if_necessary(f_in, url, verbose) config = load_inp(f_in, flow_units_id=flow_units_id) From bc7e5df6286902ca61d5b53ededea68fae472ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Fri, 6 Dec 2024 08:11:30 +0000 Subject: [PATCH 07/28] Docs: Add links to external documentations (e.g. numpy, pandas, etc.) --- epyt_flow/metrics.py | 54 +++---- .../models/sensor_interpolation_detector.py | 6 +- epyt_flow/rest_api/base_handler.py | 8 +- .../rest_api/scada_data/data_handlers.py | 22 +-- .../rest_api/scada_data/export_handlers.py | 4 +- epyt_flow/rest_api/scada_data/handlers.py | 18 +-- epyt_flow/rest_api/scenario/event_handlers.py | 12 +- epyt_flow/rest_api/scenario/handlers.py | 30 ++-- .../rest_api/scenario/simulation_handlers.py | 14 +- .../rest_api/scenario/uncertainty_handlers.py | 12 +- epyt_flow/serialization.py | 6 +- epyt_flow/simulation/events/leakages.py | 2 +- epyt_flow/simulation/events/quality_events.py | 2 +- .../events/sensor_reading_attack.py | 2 +- .../simulation/events/sensor_reading_event.py | 6 +- epyt_flow/simulation/events/system_event.py | 2 +- .../simulation/scada/advanced_control.py | 4 +- epyt_flow/simulation/scada/scada_data.py | 138 +++++++++--------- .../simulation/scada/scada_data_export.py | 2 +- epyt_flow/simulation/sensor_config.py | 32 ++-- epyt_flow/topology.py | 4 +- epyt_flow/uncertainty/model_uncertainty.py | 2 +- epyt_flow/uncertainty/sensor_noise.py | 4 +- epyt_flow/uncertainty/uncertainties.py | 8 +- epyt_flow/uncertainty/utils.py | 14 +- epyt_flow/utils.py | 16 +- 26 files changed, 213 insertions(+), 211 deletions(-) diff --git a/epyt_flow/metrics.py b/epyt_flow/metrics.py index e2c7ead..c8fce62 100644 --- a/epyt_flow/metrics.py +++ b/epyt_flow/metrics.py @@ -12,7 +12,7 @@ def r2_score(y_pred: np.ndarray, y: np.ndarray) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted outputs. y : `numpy.ndarray` Ground truth outputs. @@ -31,9 +31,9 @@ def running_r2_score(y_pred: np.ndarray, y: np.ndarray) -> list[float]: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted outputs. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth outputs. Returns @@ -55,9 +55,9 @@ def mean_squared_error(y_pred: np.ndarray, y: np.ndarray) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted outputs. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth outputs. Returns @@ -74,9 +74,9 @@ def running_mse(y_pred: np.ndarray, y: np.ndarray) -> list[float]: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted outputs. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth outputs. Returns @@ -112,9 +112,9 @@ def mape(y_pred: np.ndarray, y: np.ndarray, epsilon: float = .05) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted outputs. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth outputs. epsilon : `float`, optional Small number added to predictions and ground truth to avoid division-by-zero. @@ -153,9 +153,9 @@ def smape(y_pred: np.ndarray, y: np.ndarray, epsilon: float = .05) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted outputs. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth outputs. epsilon : `float`, optional Small number added to predictions and ground truth to avoid division-by-zero. @@ -194,9 +194,9 @@ def mase(y_pred: np.ndarray, y: np.ndarray, epsilon: float = .05) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted outputs. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth outputs. epsilon : `float`, optional Small number added to predictions and ground truth to avoid division-by-zero. @@ -242,9 +242,9 @@ def f1_micro_score(y_pred: np.ndarray, y: np.ndarray) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted labels. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth labels. Returns @@ -270,9 +270,9 @@ def roc_auc_score(y_pred: np.ndarray, y: np.ndarray) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted labels. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth labels. Returns @@ -298,9 +298,9 @@ def true_positive_rate(y_pred: np.ndarray, y: np.ndarray) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted labels. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth labels. Returns @@ -335,9 +335,9 @@ def true_negative_rate(y_pred: np.ndarray, y: np.ndarray) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted labels. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth labels. Returns @@ -372,9 +372,9 @@ def precision_score(y_pred: np.ndarray, y: np.ndarray) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted labels. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth labels. Returns @@ -405,9 +405,9 @@ def accuracy_score(y_pred: np.ndarray, y: np.ndarray) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted labels. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth labels. Returns @@ -434,9 +434,9 @@ def f1_score(y_pred: np.ndarray, y: np.ndarray) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted labels. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth labels. Returns diff --git a/epyt_flow/models/sensor_interpolation_detector.py b/epyt_flow/models/sensor_interpolation_detector.py index 857cb1d..5c840a2 100644 --- a/epyt_flow/models/sensor_interpolation_detector.py +++ b/epyt_flow/models/sensor_interpolation_detector.py @@ -20,7 +20,7 @@ class SensorInterpolationDetector(EventDetector): Regressor class that will be used for the sensor interpolation. Must implement the usual `fit` and `predict` functions. - The default is `sklearn.linear_model.LinearRegression` + The default is `sklearn.linear_model.LinearRegression `_ """ def __init__(self, regressor_type: Any = LinearRegression, **kwds): self.__regressor_type = regressor_type @@ -63,7 +63,7 @@ def fit(self, scada_data: Union[ScadaData, np.ndarray]) -> None: Parameters ---------- - scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or `numpy.ndarray` + scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or `numpy.ndarray `_ SCADA data to fit this detector. """ if isinstance(scada_data, ScadaData): @@ -93,7 +93,7 @@ def apply(self, scada_data: Union[ScadaData, np.ndarray]) -> list[int]: Parameters ---------- - scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or `numpy.ndarray` + scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or `numpy.ndarray `_ SCADA data in which to look for events/anomalies. Returns diff --git a/epyt_flow/rest_api/base_handler.py b/epyt_flow/rest_api/base_handler.py index c060f27..483557e 100644 --- a/epyt_flow/rest_api/base_handler.py +++ b/epyt_flow/rest_api/base_handler.py @@ -17,7 +17,7 @@ def send_error(self, resp: falcon.Response, error_msg: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. error_msg : `str` Error message. @@ -43,7 +43,7 @@ def send_json_parsing_error(self, resp: falcon.Response) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. """ resp.status = falcon.HTTP_BAD_REQUEST @@ -55,7 +55,7 @@ def load_json_data_from_request(self, req: falcon.Request) -> Any: Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. Returns @@ -74,7 +74,7 @@ def send_json_response(self, resp: falcon.Response, data: Any) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data : `Any` Data to be sent. diff --git a/epyt_flow/rest_api/scada_data/data_handlers.py b/epyt_flow/rest_api/scada_data/data_handlers.py index d43788b..89ff8a8 100644 --- a/epyt_flow/rest_api/scada_data/data_handlers.py +++ b/epyt_flow/rest_api/scada_data/data_handlers.py @@ -18,7 +18,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -45,7 +45,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -72,7 +72,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -100,7 +100,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -128,7 +128,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -156,7 +156,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -184,7 +184,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -212,7 +212,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -240,7 +240,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -270,7 +270,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -299,7 +299,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. diff --git a/epyt_flow/rest_api/scada_data/export_handlers.py b/epyt_flow/rest_api/scada_data/export_handlers.py index 8bc66d8..8b21dc1 100644 --- a/epyt_flow/rest_api/scada_data/export_handlers.py +++ b/epyt_flow/rest_api/scada_data/export_handlers.py @@ -41,7 +41,7 @@ def send_temp_file(self, resp: falcon.Response, tmp_file: str, Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. tmp_file : `str` Path to the temporary file to be send. @@ -71,7 +71,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. diff --git a/epyt_flow/rest_api/scada_data/handlers.py b/epyt_flow/rest_api/scada_data/handlers.py index ff84803..2524c97 100644 --- a/epyt_flow/rest_api/scada_data/handlers.py +++ b/epyt_flow/rest_api/scada_data/handlers.py @@ -38,7 +38,7 @@ def on_delete(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data instance. @@ -65,7 +65,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -87,9 +87,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, data_id: str) -> N Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -121,7 +121,7 @@ def on_get(self, _, resp: falcon.Response, data_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -143,9 +143,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, data_id: str) -> N Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. @@ -178,9 +178,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, data_id: str) -> N Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. data_id : `str` UUID of the SCADA data. diff --git a/epyt_flow/rest_api/scenario/event_handlers.py b/epyt_flow/rest_api/scenario/event_handlers.py index 470fa39..66f5688 100644 --- a/epyt_flow/rest_api/scenario/event_handlers.py +++ b/epyt_flow/rest_api/scenario/event_handlers.py @@ -18,7 +18,7 @@ def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -40,9 +40,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -73,7 +73,7 @@ def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -95,9 +95,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. diff --git a/epyt_flow/rest_api/scenario/handlers.py b/epyt_flow/rest_api/scenario/handlers.py index 46d0d46..aa510b1 100644 --- a/epyt_flow/rest_api/scenario/handlers.py +++ b/epyt_flow/rest_api/scenario/handlers.py @@ -54,7 +54,7 @@ def on_delete(self, _, resp: falcon.Response, scenario_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -95,7 +95,7 @@ def __send_temp_file(self, resp: falcon.Response, tmp_file: str, Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. tmp_file : `str` Path to the temporary file to be send. @@ -111,7 +111,7 @@ def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -152,7 +152,7 @@ def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -179,9 +179,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response) -> None: Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -205,7 +205,7 @@ def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -232,7 +232,7 @@ def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -255,9 +255,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Request` + resp : `falcon.Response `_ Request instance. scenario_id : `str` UUID of the scenario. @@ -288,7 +288,7 @@ def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -310,9 +310,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Request` + resp : `falcon.Response `_ Request instance. scenario_id : `str` UUID of the scenario. @@ -345,9 +345,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str, Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. diff --git a/epyt_flow/rest_api/scenario/simulation_handlers.py b/epyt_flow/rest_api/scenario/simulation_handlers.py index 33c7f82..c1d6885 100644 --- a/epyt_flow/rest_api/scenario/simulation_handlers.py +++ b/epyt_flow/rest_api/scenario/simulation_handlers.py @@ -33,9 +33,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -63,7 +63,7 @@ def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -104,9 +104,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -149,9 +149,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. diff --git a/epyt_flow/rest_api/scenario/uncertainty_handlers.py b/epyt_flow/rest_api/scenario/uncertainty_handlers.py index 3853a9b..98bac5f 100644 --- a/epyt_flow/rest_api/scenario/uncertainty_handlers.py +++ b/epyt_flow/rest_api/scenario/uncertainty_handlers.py @@ -18,7 +18,7 @@ def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -40,9 +40,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -73,7 +73,7 @@ def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None: Parameters ---------- - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. @@ -95,9 +95,9 @@ def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) Parameters ---------- - req : `falcon.Request` + req : `falcon.Request `_ Request instance. - resp : `falcon.Response` + resp : `falcon.Response `_ Response instance. scenario_id : `str` UUID of the scenario. diff --git a/epyt_flow/serialization.py b/epyt_flow/serialization.py index 6af74f8..c8b981f 100644 --- a/epyt_flow/serialization.py +++ b/epyt_flow/serialization.py @@ -52,14 +52,16 @@ def my_packb(data: Any) -> bytes: """ - Overriden `umsgpack.packb` method to support custom serialization handlers. + Overriden `umsgpack.packb `_ + method to support custom serialization handlers. """ return umsgpack.packb(data, ext_handlers=ext_handler_pack) def my_unpackb(data: Any) -> Any: """ - Overriden `umsgpack.unpackb` method to support custom serialization handlers. + Overriden `umsgpack.unpackb `_ + method to support custom serialization handlers. """ return umsgpack.unpackb(data, ext_handlers=ext_handler_unpack) diff --git a/epyt_flow/simulation/events/leakages.py b/epyt_flow/simulation/events/leakages.py index e26def1..88cc39b 100644 --- a/epyt_flow/simulation/events/leakages.py +++ b/epyt_flow/simulation/events/leakages.py @@ -37,7 +37,7 @@ class Leakage(SystemEvent, JsonSerializable): in this case, 'area' must be set to 'None'. The default is None. - profile : `numpy.ndarray` + profile : `numpy.ndarray `_ Pattern of this leak. node_id : `str`, optional ID of the node at which the leak is placed. diff --git a/epyt_flow/simulation/events/quality_events.py b/epyt_flow/simulation/events/quality_events.py index 2870f8c..20d0937 100644 --- a/epyt_flow/simulation/events/quality_events.py +++ b/epyt_flow/simulation/events/quality_events.py @@ -24,7 +24,7 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable): ID of the bulk species that is going to be injected. node_id : `str` ID of the node at which the injection is palced. - profile : `numpy.ndarray` + profile : `numpy.ndarray `_ Injection strength profile -- i.e. every entry corresponds to the strength of the injection at a point in time. Pattern will repeat if it is shorter than the total injection time. source_type : `int` diff --git a/epyt_flow/simulation/events/sensor_reading_attack.py b/epyt_flow/simulation/events/sensor_reading_attack.py index 9a16c06..c375d9e 100644 --- a/epyt_flow/simulation/events/sensor_reading_attack.py +++ b/epyt_flow/simulation/events/sensor_reading_attack.py @@ -26,7 +26,7 @@ class SensorOverrideAttack(SensorReadingAttack, JsonSerializable): Parameters ---------- - new_sensor_values : `numpy.ndarray` + new_sensor_values : `numpy.ndarray `_ New sensor reading values -- i.e. these values replace the true sensor reading values. """ def __init__(self, new_sensor_values: np.ndarray, **kwds): diff --git a/epyt_flow/simulation/events/sensor_reading_event.py b/epyt_flow/simulation/events/sensor_reading_event.py index ceedcb0..fc84f47 100644 --- a/epyt_flow/simulation/events/sensor_reading_event.py +++ b/epyt_flow/simulation/events/sensor_reading_event.py @@ -157,14 +157,14 @@ def apply(self, sensor_readings: numpy.ndarray, Parameters ---------- - sensor_readings : `numpy.ndarray` + sensor_readings : `numpy.ndarray `_ Original sensor readings. - sensor_readings_time : `numpy.ndarray` + sensor_readings_time : `numpy.ndarray `_ Time (seconds since simulation start) for each sensor reading row in 'sensor_readings'. Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Modified sensor readings. """ raise NotImplementedError() diff --git a/epyt_flow/simulation/events/system_event.py b/epyt_flow/simulation/events/system_event.py index 0586b4b..04df551 100644 --- a/epyt_flow/simulation/events/system_event.py +++ b/epyt_flow/simulation/events/system_event.py @@ -23,7 +23,7 @@ def init(self, epanet_api: epyt.epanet) -> None: Parameters ---------- - epanet_api : `epyt.epanet` + epanet_api : `epyt.epanet `_ API to EPANET and EPANET-MSX. """ self._epanet_api = epanet_api diff --git a/epyt_flow/simulation/scada/advanced_control.py b/epyt_flow/simulation/scada/advanced_control.py index 262f193..1ab21dc 100644 --- a/epyt_flow/simulation/scada/advanced_control.py +++ b/epyt_flow/simulation/scada/advanced_control.py @@ -15,7 +15,7 @@ class AdvancedControlModule(ABC): Attributes ---------- - epanet_api : `epyt.epanet` + epanet_api : `epyt.epanet `_ API to EPANET and EPANET-MSX. Is set in :func:`init`. """ def __init__(self, **kwds): @@ -29,7 +29,7 @@ def init(self, epanet_api: epyt.epanet) -> None: Parameters ---------- - epanet_api : `epyt.epanet` + epanet_api : `epyt.epanet `_ API to EPANET for implementing the control module. """ if not isinstance(epanet_api, epyt.epanet): diff --git a/epyt_flow/simulation/scada/scada_data.py b/epyt_flow/simulation/scada/scada_data.py index 6a51205..b3c5b0c 100644 --- a/epyt_flow/simulation/scada/scada_data.py +++ b/epyt_flow/simulation/scada/scada_data.py @@ -34,75 +34,75 @@ class ScadaData(Serializable): ---------- sensor_config : :class:`~epyt_flow.simulation.sensor_config.SensorConfig` Specifications of all sensors. - sensor_readings_time : `numpy.ndarray` + sensor_readings_time : `numpy.ndarray `_ Time (seconds since simulation start) for each sensor reading row in `sensor_readings_data_raw`. This parameter is expected to be a 1d array with the same size as the number of rows in `sensor_readings_data_raw`. - pressure_data_raw : `numpy.ndarray`, optional + pressure_data_raw : `numpy.ndarray `_, optional Raw pressure values of all nodes as a two-dimensional array -- first dimension encodes time, second dimension pressure at nodes. The default is None, - flow_data_raw : `numpy.ndarray`, optional + flow_data_raw : `numpy.ndarray `_, optional Raw flow values of all links/pipes -- first dimension encodes time, second dimension pressure at links/pipes. The default is None. - demand_data_raw : `numpy.ndarray`, optional + demand_data_raw : `numpy.ndarray `_, optional Raw demand values of all nodes -- first dimension encodes time, second dimension demand at nodes. The default is None. - node_quality_data_raw : `numpy.ndarray`, optional + node_quality_data_raw : `numpy.ndarray `_, optional Raw quality values of all nodes -- first dimension encodes time, second dimension quality at nodes. The default is None. - link_quality_data_raw : `numpy.ndarray`, optional + link_quality_data_raw : `numpy.ndarray `_, optional Raw quality values of all links/pipes -- first dimension encodes time, second dimension quality at links/pipes. The default is None. - pumps_state_data_raw : `numpy.ndarray`, optional + pumps_state_data_raw : `numpy.ndarray `_, optional States of all pumps -- first dimension encodes time, second dimension states of pumps. The default is None. - valves_state_data_raw : `numpy.ndarray`, optional + valves_state_data_raw : `numpy.ndarray `_, optional States of all valves -- first dimension encodes time, second dimension states of valves. The default is None. - tanks_volume_data_raw : `numpy.ndarray`, optional + tanks_volume_data_raw : `numpy.ndarray `_, optional Water volumes in all tanks -- first dimension encodes time, second dimension water volume in tanks. The default is None. - surface_species_concentration_raw : `numpy.ndarray`, optional + surface_species_concentration_raw : `numpy.ndarray `_, optional Raw concentrations of surface species as a tree dimensional array -- first dimension encodes time, second dimension denotes the different surface species, third dimension denotes species concentrations at links/pipes. The default is None. - bulk_species_node_concentration_raw : `numpy.ndarray`, optional + bulk_species_node_concentration_raw : `numpy.ndarray `_, optional Raw concentrations of bulk species at nodes as a tree dimensional array -- first dimension encodes time, second dimension denotes the different bulk species, third dimension denotes species concentrations at nodes. The default is None. - bulk_species_link_concentration_raw : `numpy.ndarray`, optional + bulk_species_link_concentration_raw : `numpy.ndarray `_, optional Raw concentrations of bulk species at links as a tree dimensional array -- first dimension encodes time, second dimension denotes the different bulk species, third dimension denotes species concentrations at nodes. The default is None. - pumps_energy_usage_data_raw : `numpy.ndarray`, optional + pumps_energy_usage_data_raw : `numpy.ndarray `_, optional Energy usage data of each pump. The default is None. - pumps_efficiency_data_raw : `numpy.ndarray`, optional + pumps_efficiency_data_raw : `numpy.ndarray `_, optional Pump efficiency data of each pump. The default is None. @@ -1153,7 +1153,7 @@ def pressure_data_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Raw pressure readings. """ return deepcopy(self.__pressure_data_raw) @@ -1165,7 +1165,7 @@ def flow_data_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Raw flow readings. """ return deepcopy(self.__flow_data_raw) @@ -1177,7 +1177,7 @@ def demand_data_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Raw demand readings. """ return deepcopy(self.__demand_data_raw) @@ -1189,7 +1189,7 @@ def node_quality_data_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Raw node quality readings. """ return deepcopy(self.__node_quality_data_raw) @@ -1201,7 +1201,7 @@ def link_quality_data_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Raw link quality readings. """ return deepcopy(self.__link_quality_data_raw) @@ -1213,7 +1213,7 @@ def sensor_readings_time(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Sensor readings time stamps. """ return deepcopy(self.__sensor_readings_time) @@ -1225,7 +1225,7 @@ def pumps_state_data_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Raw pump state readings. """ return deepcopy(self.__pumps_state_data_raw) @@ -1237,7 +1237,7 @@ def valves_state_data_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Raw valve state readings. """ return deepcopy(self.__valves_state_data_raw) @@ -1249,7 +1249,7 @@ def tanks_volume_data_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Raw tank volume readings. """ return deepcopy(self.__tanks_volume_data_raw) @@ -1261,7 +1261,7 @@ def surface_species_concentration_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Raw species concentrations. """ return deepcopy(self.__surface_species_concentration_raw) @@ -1273,7 +1273,7 @@ def bulk_species_node_concentration_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Raw species concentrations. """ return deepcopy(self.__bulk_species_node_concentration_raw) @@ -1285,7 +1285,7 @@ def bulk_species_link_concentration_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Raw species concentrations. """ return deepcopy(self.__bulk_species_link_concentration_raw) @@ -1297,7 +1297,7 @@ def pumps_energyconsumption_data_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Energy consumption of each pump. """ return deepcopy(self.__pumps_energy_usage_data_raw) @@ -1309,7 +1309,7 @@ def pumps_efficiency_data_raw(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Pumps' efficiency. """ return deepcopy(self.__pumps_efficiency_data_raw) @@ -1967,7 +1967,7 @@ def get_data(self) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Final sensor readings. """ # Comute clean sensor readings @@ -2068,7 +2068,7 @@ def get_data_pressures(self, sensor_locations: list[str] = None) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Pressure sensor readings. """ if self.__sensor_config.pressure_sensors == []: @@ -2118,14 +2118,14 @@ def plot_pressures(self, sensor_locations: list[str] = None, show: bool = True, i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_pressures(sensor_locations) @@ -2155,7 +2155,7 @@ def get_data_flows(self, sensor_locations: list[str] = None) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Flow sensor readings. """ if self.__sensor_config.flow_sensors == []: @@ -2205,14 +2205,14 @@ def plot_flows(self, sensor_locations: list[str] = None, show: bool = True, i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_flows(sensor_locations) @@ -2241,7 +2241,7 @@ def get_data_demands(self, sensor_locations: list[str] = None) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Demand sensor readings. """ if self.__sensor_config.demand_sensors == []: @@ -2291,14 +2291,14 @@ def plot_demands(self, sensor_locations: list[str] = None, show: bool = True, i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_demands(sensor_locations) @@ -2327,7 +2327,7 @@ def get_data_nodes_quality(self, sensor_locations: list[str] = None) -> np.ndarr Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Node quality sensor readings. """ if self.__sensor_config.quality_node_sensors == []: @@ -2379,14 +2379,14 @@ def plot_nodes_quality(self, sensor_locations: list[str] = None, show: bool = Tr i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_nodes_quality(sensor_locations) @@ -2416,7 +2416,7 @@ def get_data_links_quality(self, sensor_locations: list[str] = None) -> np.ndarr Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Link quality sensor readings. """ if self.__sensor_config.quality_link_sensors == []: @@ -2468,14 +2468,14 @@ def plot_links_quality(self, sensor_locations: list[str] = None, show: bool = Tr i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_links_quality(sensor_locations) @@ -2505,7 +2505,7 @@ def get_data_pumps_state(self, sensor_locations: list[str] = None) -> np.ndarray Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Pump state sensor readings. """ if self.__sensor_config.pump_state_sensors == []: @@ -2556,14 +2556,14 @@ def plot_pumps_state(self, sensor_locations: list[str] = None, show: bool = True i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_pumps_state(sensor_locations) @@ -2592,7 +2592,7 @@ def get_data_pumps_efficiency(self, sensor_locations: list[str] = None) -> np.nd Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Pump efficiency sensor readings. """ if self.__sensor_config.pump_efficiency_sensors == []: @@ -2644,14 +2644,14 @@ def plot_pumps_efficiency(self, sensor_locations: list[str] = None, show: bool = i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_pumps_efficiency(sensor_locations) @@ -2680,7 +2680,7 @@ def get_data_pumps_energyconsumption(self, sensor_locations: list[str] = None) - Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Pump energy consumption sensor readings. """ if self.__sensor_config.pump_energyconsumption_sensors == []: @@ -2732,14 +2732,14 @@ def plot_pumps_energyconsumption(self, sensor_locations: list[str] = None, show: i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_pumps_energyconsumption(sensor_locations) @@ -2767,7 +2767,7 @@ def get_data_valves_state(self, sensor_locations: list[str] = None) -> np.ndarra Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Valve state sensor readings. """ if self.__sensor_config.valve_state_sensors == []: @@ -2818,14 +2818,14 @@ def plot_valves_state(self, sensor_locations: list[str] = None, show: bool = Tru i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_valves_state(sensor_locations) @@ -2854,7 +2854,7 @@ def get_data_tanks_water_volume(self, sensor_locations: list[str] = None) -> np. Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Water tanks volume sensor readings. """ if self.__sensor_config.tank_volume_sensors == []: @@ -2905,14 +2905,14 @@ def plot_tanks_water_volume(self, sensor_locations: list[str] = None, show: bool i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_tanks_water_volume(sensor_locations) @@ -2946,7 +2946,7 @@ def get_data_surface_species_concentration(self, Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Surface species concentration sensor readings. """ if self.__sensor_config.surface_species_sensors == {}: @@ -3007,14 +3007,14 @@ def plot_surface_species_concentration(self, surface_species_sensor_locations: d i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_surface_species_concentration(surface_species_sensor_locations) @@ -3062,7 +3062,7 @@ def get_data_bulk_species_node_concentration(self, Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Bulk species concentration sensor readings. """ if self.__sensor_config.bulk_species_node_sensors == {}: @@ -3123,14 +3123,14 @@ def plot_bulk_species_node_concentration(self, bulk_species_node_sensors: dict = i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_bulk_species_node_concentration(bulk_species_node_sensors) @@ -3177,7 +3177,7 @@ def get_data_bulk_species_link_concentration(self, Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Bulk species concentration sensor readings. """ if self.__sensor_config.bulk_species_link_sensors == {}: @@ -3238,14 +3238,14 @@ def plot_bulk_species_link_concentration(self, bulk_species_link_sensors: dict = i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ data = self.get_data_bulk_species_link_concentration(bulk_species_link_sensors) @@ -3289,7 +3289,7 @@ def to_pandas_dataframe(self, export_raw_data: bool = False) -> pd.DataFrame: Returns ------- - `pandas.DataFrame` + `pandas.DataFrame `_ Exported data. """ from .scada_data_export import ScadaDataExport diff --git a/epyt_flow/simulation/scada/scada_data_export.py b/epyt_flow/simulation/scada/scada_data_export.py index 4ba5be2..4ac00e5 100644 --- a/epyt_flow/simulation/scada/scada_data_export.py +++ b/epyt_flow/simulation/scada/scada_data_export.py @@ -102,7 +102,7 @@ def create_column_desc(scada_data: ScadaData) -> np.ndarray: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ 3-dimensional array describing all columns of the sensor readings: The first dimension describes the sensor type, the second dimension describes the sensor location, and the third one describes the measurement units. diff --git a/epyt_flow/simulation/sensor_config.py b/epyt_flow/simulation/sensor_config.py index e59a8f4..d0d2d8d 100644 --- a/epyt_flow/simulation/sensor_config.py +++ b/epyt_flow/simulation/sensor_config.py @@ -1775,12 +1775,12 @@ def surface_species_sensors(self, surface_species_sensors: dict) -> None: @property def sensors_id_to_idx(self) -> dict: """ - Gets a mapping of sensor IDs to indices in the final Numpy array returned by `get_data()`. + Gets a mapping of sensor IDs to indices in the final `Numpy array `_ returned by `get_data()`. Returns ------- `dict` - Mapping of sensor IDs to indices in the final Numpy array. + Mapping of sensor IDs to indices in the final `Numpy array `_. """ return deepcopy(self.__sensors_id_to_idx) @@ -1981,39 +1981,39 @@ def compute_readings(self, pressures: np.ndarray, flows: np.ndarray, demands: np Parameters ---------- - pressures : `numpy.ndarray` + pressures : `numpy.ndarray `_ Pressure values at all nodes. - flows : `numpy.ndarray` + flows : `numpy.ndarray `_ Flow values at all links/pipes. - demands : `numpy.ndarray` + demands : `numpy.ndarray `_ Demand values at all nodes. - nodes_quality : `numpy.ndarray` + nodes_quality : `numpy.ndarray `_ Quality values at all nodes. - links_quality : `numpy.ndarray` + links_quality : `numpy.ndarray `_ Quality values at all links/pipes. - pumps_state : `numpy.ndarray` + pumps_state : `numpy.ndarray `_ States of all pumps. - pumps_efficiency : `numpy.ndarray` + pumps_efficiency : `numpy.ndarray `_ Efficiency of all pumps. - pumps_energyconsumption : `numpy.ndarray` + pumps_energyconsumption : `numpy.ndarray `_ Energy consumption of all pumps. - valves_state : `numpy.ndarray` + valves_state : `numpy.ndarray `_ States of all valves. - tanks_volume : `numpy.ndarray` + tanks_volume : `numpy.ndarray `_ Water volume in all tanks. - bulk_species_node_concentrations : `numpy.ndarray` + bulk_species_node_concentrations : `numpy.ndarray `_ Bulk species concentrations at all nodes. Expect a three-dimensional array: First dimension denotes time, second dimension corresponds to species ID, and third dimension contains the concentration. - bulk_species_link_concentrations : `numpy.ndarray` + bulk_species_link_concentrations : `numpy.ndarray `_ Bulk species concentrations at all links/pipes. Expect a three-dimensional array: First dimension denotes time, second dimension corresponds to species ID, and third dimension contains the concentration. - surface_species_concentrations : `numpy.ndarray` + surface_species_concentrations : `numpy.ndarray `_ Surface species concentrations at all links/pipes. Expect a three-dimensional array: First dimension denotes time, @@ -2022,7 +2022,7 @@ def compute_readings(self, pressures: np.ndarray, flows: np.ndarray, demands: np Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Sensor readings. """ data = [] diff --git a/epyt_flow/topology.py b/epyt_flow/topology.py index 2668611..56933c6 100644 --- a/epyt_flow/topology.py +++ b/epyt_flow/topology.py @@ -552,7 +552,7 @@ def to_gis(self, coord_reference_system: str = None, pumps_as_points: bool = Fal Returns ------- `dict` - Network topology as a dictionary of `geopandas.GeoDataFrames` instances. + Network topology as a dictionary of `geopandas.GeoDataFrames `_ instances. If a quantity does not exist, the data frame will be None. """ gis = {"nodes": None, "links": None, @@ -629,7 +629,7 @@ def get_adj_matrix(self) -> bsr_array: Returns ------- - `scipy.bsr_array` + `scipy.bsr_array `_ Adjacency matrix as a sparse array. """ nodes_id = [node_id for node_id, _ in self.__nodes] diff --git a/epyt_flow/uncertainty/model_uncertainty.py b/epyt_flow/uncertainty/model_uncertainty.py index 7356e48..626ae76 100644 --- a/epyt_flow/uncertainty/model_uncertainty.py +++ b/epyt_flow/uncertainty/model_uncertainty.py @@ -248,7 +248,7 @@ def apply(self, epanet_api: epyt.epanet) -> None: Parameters ---------- - epanet_api : `epyt.epanet` + epanet_api : `epyt.epanet `_ Interface to EPANET and EPANET-MSX. """ if self.__pipe_length is not None: diff --git a/epyt_flow/uncertainty/sensor_noise.py b/epyt_flow/uncertainty/sensor_noise.py index 6e8e1cf..355d312 100644 --- a/epyt_flow/uncertainty/sensor_noise.py +++ b/epyt_flow/uncertainty/sensor_noise.py @@ -63,11 +63,11 @@ def apply(self, sensor_readings: numpy.ndarray) -> numpy.ndarray: Parameters ---------- - sensor_readings : `numpy.ndarray` + sensor_readings : `numpy.ndarray `_ Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Perturbed sensor readings. """ return self.__uncertainty.apply_batch(sensor_readings) diff --git a/epyt_flow/uncertainty/uncertainties.py b/epyt_flow/uncertainty/uncertainties.py index 27c4e40..0421157 100644 --- a/epyt_flow/uncertainty/uncertainties.py +++ b/epyt_flow/uncertainty/uncertainties.py @@ -86,12 +86,12 @@ def clip(self, data: np.ndarray) -> np.ndarray: Parameters ---------- - data : `numpy.ndarray` + data : `numpy.ndarray `_ Array to be clipped. Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Clipped data. """ if self.__min_value is not None: @@ -124,12 +124,12 @@ def apply_batch(self, data: np.ndarray) -> np.ndarray: Parameters ---------- - data : `numpy.ndarray` + data : `numpy.ndarray `_ Array of values to which the uncertainty is applied. Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Uncertainty applied to `data`. """ for t in range(data.shape[0]): diff --git a/epyt_flow/uncertainty/utils.py b/epyt_flow/uncertainty/utils.py index dfc5c93..24ec52b 100644 --- a/epyt_flow/uncertainty/utils.py +++ b/epyt_flow/uncertainty/utils.py @@ -11,7 +11,7 @@ def smoothing(pattern: np.ndarray, sigma: float = 10.) -> np.ndarray: Parameters ---------- - pattern : `numpy.ndarray` + pattern : `numpy.ndarray `_ The original pattern sigma : `float`, optional Standard deviation for the Gaussian filter. @@ -32,7 +32,7 @@ def scale_to_range(pattern: np.ndarray, min_value: float, max_value: float) -> n Parameters ---------- - pattern : `numpy.ndarray` + pattern : `numpy.ndarray `_ The pattern to be scaled. min_value : `float` Lower bound of the pattern. @@ -41,7 +41,7 @@ def scale_to_range(pattern: np.ndarray, min_value: float, max_value: float) -> n Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ The scaled pattern. """ if min_value is None or max_value is None: @@ -65,7 +65,7 @@ def generate_random_gaussian_noise(n_samples: int): Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Gaussian noise. """ return np.random.normal(np.random.rand(), np.random.rand(), size=n_samples) @@ -87,7 +87,7 @@ def generate_deep_random_gaussian_noise(n_samples: int, mean: float = None): Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Random Gaussian noise. """ noise = [] @@ -127,7 +127,7 @@ def create_deep_random_pattern(n_samples: int, min_value: float = 0., max_value: Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Random pattern. """ pattern = [] @@ -179,7 +179,7 @@ def _create_deep_random_pattern(start_value: float = None, min_length: int = 2, Returns ------- - `numpy.ndarray` + `numpy.ndarray `_ Random pattern. """ pattern = [] diff --git a/epyt_flow/utils.py b/epyt_flow/utils.py index d81912d..89c460b 100644 --- a/epyt_flow/utils.py +++ b/epyt_flow/utils.py @@ -76,7 +76,7 @@ def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_labe Parameters ---------- - data : `numpy.ndarray` + data : `numpy.ndarray `_ Time series data -- each row in `data` corresponds to a complete time series. labels : `list[str]`, optional Labels for each time series in `data`. @@ -108,14 +108,14 @@ def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_labe i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'ax' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ if not isinstance(data, np.ndarray): @@ -198,11 +198,11 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray, Parameters ---------- - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth values. - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Predicted values. - confidence_interval : `numpy.ndarray`, optional + confidence_interval : `numpy.ndarray `_, optional Confidence interval (upper and lower value) for each prediction in `y_pred`. If not None, the confidence interval is plotted as well. @@ -232,14 +232,14 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray, i.e. a plot can not be shown and saved to a file at the same time! The default is None. - ax : `matplotlib.axes.Axes`, optional + ax : `matplotlib.axes.Axes `_, optional If not None, 'axes' is used for plotting. The default is None. Returns ------- - `matplotlib.axes.Axes` + `matplotlib.axes.Axes `_ Plot. """ if not isinstance(y_pred, np.ndarray): From 30291ec4406575ab8e0b91b3bdb5616e3d4910b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Fri, 6 Dec 2024 08:15:37 +0000 Subject: [PATCH 08/28] Doc: Fix error in EPANET-MSX example --- docs/tut.quality.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tut.quality.rst b/docs/tut.quality.rst index a52fd43..33ae938 100644 --- a/docs/tut.quality.rst +++ b/docs/tut.quality.rst @@ -192,10 +192,10 @@ Example of a scenario where we want to monitor chlorine in Net2: sim.set_general_parameters(simulation_duration=to_seconds(days=5)) # Monitor bulk species "CL2" at every node - sim.set_bulk_species_sensors(sensor_info={"CL2": sim.sensor_config.nodes}) + sim.set_bulk_species_node_sensors(sensor_info={"CL2": sim.sensor_config.nodes}) # Run entire simulation res = sim.run_simulation(verbose=True) # Show concentration of chlorine species at every node - print(res.get_data_bulk_species_concentration()) + print(res.get_data_bulk_species_node_concentration()) From c7999bc9e459dd60734da2f28030334c85daac90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Sat, 7 Dec 2024 10:23:39 +0000 Subject: [PATCH 09/28] ScadaData: Bugfix in concatenate function --- epyt_flow/simulation/scada/scada_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epyt_flow/simulation/scada/scada_data.py b/epyt_flow/simulation/scada/scada_data.py index b3c5b0c..0fc6db9 100644 --- a/epyt_flow/simulation/scada/scada_data.py +++ b/epyt_flow/simulation/scada/scada_data.py @@ -1952,7 +1952,7 @@ def concatenate(self, other) -> None: if self.__pumps_energy_usage_data_raw is not None: self.__pumps_energy_usage_data_raw = np.concatenate( - (self.__pumps_energy_usage_data_raw, other.pumps_energy_usage_data_raw), + (self.__pumps_energy_usage_data_raw, other.pumps_energyconsumption_data_raw), axis=0) if self.__pumps_efficiency_data_raw is not None: From 1537424b99b9f35ff058b6c5e087a6f2dd321675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Sun, 8 Dec 2024 16:55:32 +0100 Subject: [PATCH 10/28] Docs: Add links to external documentations --- epyt_flow/data/benchmarks/batadal.py | 2 +- epyt_flow/data/benchmarks/battledim.py | 7 ++++--- epyt_flow/data/benchmarks/gecco_water_quality.py | 8 ++++---- epyt_flow/data/benchmarks/leakdb.py | 8 ++++---- epyt_flow/data/benchmarks/water_usage.py | 4 ++-- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/epyt_flow/data/benchmarks/batadal.py b/epyt_flow/data/benchmarks/batadal.py index a365fd2..827e87a 100644 --- a/epyt_flow/data/benchmarks/batadal.py +++ b/epyt_flow/data/benchmarks/batadal.py @@ -91,7 +91,7 @@ def load_data(download_dir: str = None, return_X_y: bool = False, `dict` Dictionary of the loaded benchmark data. The dictionary contains the two training data sets ("train_1" and "train_2"), as well as the test data set ("test"). - If `return_X_y` is False, each dictionary entry is a Pandas dataframe. + If `return_X_y` is False, each dictionary entry is a `Pandas dataframe `_. Otherwise, it is a tuple of sensor readings and labels (except for the test set) -- if `return_ground_truth` is True or `return_features_desc` is True, the corresponding data is appended to the tuple. diff --git a/epyt_flow/data/benchmarks/battledim.py b/epyt_flow/data/benchmarks/battledim.py index 091f5bf..8dba2f2 100644 --- a/epyt_flow/data/benchmarks/battledim.py +++ b/epyt_flow/data/benchmarks/battledim.py @@ -286,7 +286,7 @@ def load_data(return_test_scenario: bool, download_dir: str = None, return_X_y: Returns ------- - Either a `pandas.DataFrame` instance or a tuple of Numpy arrays. + Either a `pandas.DataFrame `_ instance or a tuple of `Numpy arrays `_. Benchmark data set. """ # Download data files if necessary @@ -389,8 +389,9 @@ def load_scada_data(return_test_scenario: bool, download_dir: str = None, :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or `list[tuple[numpy.ndarray, numpy.ndarray]]` The simulated benchmark scenario as either a :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` instance or as a tuple of - (X, y) Numpy arrays. If 'return_leak_locations' is True, the leak locations are included - as an instance of `scipy.sparse.bsr_array` as well. + (X, y) `Numpy arrays `_. + If 'return_leak_locations' is True, the leak locations are included + as an instance of `scipy.sparse.bsr_array `_ as well. """ download_dir = download_dir if download_dir is not None else get_temp_folder() diff --git a/epyt_flow/data/benchmarks/gecco_water_quality.py b/epyt_flow/data/benchmarks/gecco_water_quality.py index 5306c57..d3e2059 100644 --- a/epyt_flow/data/benchmarks/gecco_water_quality.py +++ b/epyt_flow/data/benchmarks/gecco_water_quality.py @@ -31,9 +31,9 @@ def compute_evaluation_score(y_pred: np.ndarray, y: np.ndarray) -> float: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Event indication prediction over time - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth event indication over time. Returns @@ -85,7 +85,7 @@ def load_gecco2017_water_quality_data(download_dir: str = None, return_X_y: bool Returns ------- - `pandas.DataFrame` or `tuple[numpy.ndarray, numpy.ndarray]` + `pandas.DataFrame `_ or `tuple[numpy.ndarray, numpy.ndarray] `_ The benchmark data set as either a Pandas data frame or as a pair of (X, y) Numpy arrays. """ url_data = "https://zenodo.org/records/3884465/files/1_gecco2017_water_quality.csv?download=1" @@ -154,7 +154,7 @@ def load_gecco2018_water_quality_data(download_dir: str = None, return_X_y: bool Returns ------- - `pandas.DataFrame` or `tuple[numpy.ndarray, numpy.ndarray]` + `pandas.DataFrame `_ or `tuple[numpy.ndarray, numpy.ndarray] `_ The benchmark data set as either a Pandas data frame or as a pair of (X, y) Numpy arrays. """ # Download data if necessary diff --git a/epyt_flow/data/benchmarks/leakdb.py b/epyt_flow/data/benchmarks/leakdb.py index 3ce17b2..bdbaa36 100644 --- a/epyt_flow/data/benchmarks/leakdb.py +++ b/epyt_flow/data/benchmarks/leakdb.py @@ -97,7 +97,7 @@ def compute_evaluation_score(scenarios_id: list[int], use_net1: bool, List of scenarios ID that are to be evaluated -- there is a total number of 1000 scenarios. use_net1 : `bool` If True, Net1 LeakDB will be used for evaluation, otherwise the Hanoi LeakDB will be used. - y_pred_labels_per_scenario : `list[numpy.ndarray]` + y_pred_labels_per_scenario : `list[numpy.ndarray] `_ Predicted binary labels (over time) for each scenario in `scenarios_id`. Returns @@ -201,7 +201,7 @@ def load_data(scenarios_id: list[int], use_net1: bool, download_dir: str = None, The default is False. return_leak_locations : `bool` If True and if `return_X_y` is True, the leak locations are returned as well -- - as an instance of `scipy.sparse.bsr_array`. + as an instance of `scipy.sparse.bsr_array `_. The default is False. verbose : `bool`, optional @@ -327,7 +327,7 @@ def load_scada_data(scenarios_id: list[int], use_net1: bool = True, download_dir The default is False. return_leak_locations : `bool` If True, the leak locations are returned as well -- - as an instance of `scipy.sparse.bsr_array`. + as an instance of `scipy.sparse.bsr_array `_. The default is False. verbose : `bool`, optional @@ -341,7 +341,7 @@ def load_scada_data(scenarios_id: list[int], use_net1: bool = True, download_dir The simulated benchmark scenarios as either a list of :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` instances or as a list of (X, y) Numpy arrays. If 'return_leak_locations' is True, the leak locations are included - as an instance of `scipy.sparse.bsr_array` as well. + as an instance of `scipy.sparse.bsr_array `_ as well. """ download_dir = download_dir if download_dir is not None else get_temp_folder() diff --git a/epyt_flow/data/benchmarks/water_usage.py b/epyt_flow/data/benchmarks/water_usage.py index b952ca9..cfffe68 100644 --- a/epyt_flow/data/benchmarks/water_usage.py +++ b/epyt_flow/data/benchmarks/water_usage.py @@ -22,9 +22,9 @@ def compute_evaluation_score(y_pred: np.ndarray, y: np.ndarray) -> dict: Parameters ---------- - y_pred : `numpy.ndarray` + y_pred : `numpy.ndarray `_ Event indication prediction over time - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth event indication over time. Returns From 980df4976824e12993ab420a1f812432b95d6f57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Mon, 9 Dec 2024 09:50:25 +0100 Subject: [PATCH 11/28] Add WaterBenchmarkHub to documentation --- docs/tut.scenarios.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/tut.scenarios.rst b/docs/tut.scenarios.rst index 15a75c6..2696a23 100644 --- a/docs/tut.scenarios.rst +++ b/docs/tut.scenarios.rst @@ -428,6 +428,15 @@ several (WDN related) benchmark data sets from the literature: +--------------------------------+---------------------------------------------------------------------------------------------+ + +WaterBenchmarkHub ++++++++++++++++++ + +For more networks and benchmarks, check-out the +`WaterBenchmarkHub `_, +a platform for accessing and sharing Water Distribution Network (WDN) benchmarks and data sets. + + .. [1] Vrachimis et al. (2018) -- see https://github.com/KIOS-Research/LeakDB/ .. [2] Vrachmimis et al. (2020) -- see https://github.com/KIOS-Research/BattLeDIM .. [3] Taormina et al. (2017) -- see https://www.batadal.net/ From 01ec8d02a534b591eeb73c9f4d12080461919a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Tue, 10 Dec 2024 08:18:04 +0100 Subject: [PATCH 12/28] Bump version --- epyt_flow/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epyt_flow/VERSION b/epyt_flow/VERSION index a3df0a6..ac39a10 100644 --- a/epyt_flow/VERSION +++ b/epyt_flow/VERSION @@ -1 +1 @@ -0.8.0 +0.9.0 From a7eedc4c2f8e7b19cec5e3b6cf83248eaac3671f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Wed, 11 Dec 2024 09:20:04 +0100 Subject: [PATCH 13/28] Remove the effect of system events when saving a scenario to .inp --- epyt_flow/simulation/scenario_simulator.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/epyt_flow/simulation/scenario_simulator.py b/epyt_flow/simulation/scenario_simulator.py index be580f0..92ef7b7 100644 --- a/epyt_flow/simulation/scenario_simulator.py +++ b/epyt_flow/simulation/scenario_simulator.py @@ -477,7 +477,8 @@ def __exit__(self, *args): self.close() def save_to_epanet_file(self, inp_file_path: str, msx_file_path: str = None, - export_sensor_config: bool = True) -> None: + export_sensor_config: bool = True, undo_system_events: bool = True + ) -> None: """ Exports this scenario to EPANET files -- i.e. an .inp file and (optionally) a .msx file if EPANET-MSX was loaded. @@ -540,6 +541,10 @@ def __override_report_section(file_in: str, report_desc: str) -> None: if write_end_section is True: f_in.write("\n[END]") + if undo_system_events is True: + for event in self._system_events: + event.cleanup() + if inp_file_path is not None: self.epanet_api.saveInputFile(inp_file_path) self.__f_inp_in = inp_file_path @@ -641,6 +646,10 @@ def __override_report_section(file_in: str, report_desc: str) -> None: __override_report_section(msx_file_path, report_desc) + if undo_system_events is True: + for event in self._system_events: + event.init(self.epanet_api) + def get_flow_units(self) -> int: """ Gets the flow units. From f925106579bc17958c652ffb4abf90cf3ec00931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Wed, 11 Dec 2024 09:36:07 +0100 Subject: [PATCH 14/28] Bugfix: Multiple source pattern for the same species and node but at different time points --- epyt_flow/simulation/events/quality_events.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/epyt_flow/simulation/events/quality_events.py b/epyt_flow/simulation/events/quality_events.py index 20d0937..ae18ec7 100644 --- a/epyt_flow/simulation/events/quality_events.py +++ b/epyt_flow/simulation/events/quality_events.py @@ -136,7 +136,7 @@ def __str__(self) -> str: f"node_id: {self.__node_id} profile: {self.__profile} source_type: {self.__source_type}" def _get_pattern_id(self) -> str: - return f"{self.__species_id}_{self.__node_id}_{self.start_time}" + return f"{self.__species_id}_{self.__node_id}" def init(self, epanet_api: epyt.epanet) -> None: super().init(epanet_api) @@ -180,9 +180,20 @@ def init(self, epanet_api: epyt.epanet) -> None: pattern_id = self._get_pattern_id() if pattern_id in self._epanet_api.getMSXPatternsNameID(): - raise ValueError("Duplicated injection event") - - self._epanet_api.addMSXPattern(pattern_id, pattern) + node_idx = self._epanet_api.getNodeIndex(self.__node_id) + species_idx, = self._epanet_api.getMSXSpeciesIndex([self.__species_id]) + cur_source_type = self._epanet_api.msx.MSXgetsource(node_idx, species_idx) + if cur_source_type[0] != source_type_: + raise ValueError("Source type does not match existing source type") + + # Add new injection amount to existing injection -- + # i.e. two injection events at the same node + pattern_idx, = self._epanet_api.getMSXPatternsIndex([pattern_id]) + cur_pattern = self._epanet_api.getMSXPattern()[pattern_idx - 1] + cur_pattern += pattern + self._epanet_api.setMSXPattern(pattern_idx, cur_pattern) + else: + self._epanet_api.addMSXPattern(pattern_id, pattern) self._epanet_api.setMSXSources(self.__node_id, self.__species_id, source_type_, 1, pattern_id) From 89f74f1278cc9109ec0331e87267ae3ddf51a8a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Thu, 12 Dec 2024 09:00:41 +0100 Subject: [PATCH 15/28] Fix some docstrings and code style issues --- epyt_flow/gym/control_gyms.py | 4 ++-- epyt_flow/simulation/parallel_simulation.py | 2 +- epyt_flow/simulation/scenario_config.py | 2 +- epyt_flow/simulation/scenario_visualizer.py | 18 +++++++++--------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/epyt_flow/gym/control_gyms.py b/epyt_flow/gym/control_gyms.py index e57a8e0..39d3172 100644 --- a/epyt_flow/gym/control_gyms.py +++ b/epyt_flow/gym/control_gyms.py @@ -15,7 +15,7 @@ def register(env_name: str, env: ScenarioControlEnv) -> None: ---------- env_name : `str` Name of the environment -- must be unique among all environments. - env : :class:`epyt_flow.gym.scenario_control_env.ScenarioControlEnv` + env : :class:`~epyt_flow.gym.scenario_control_env.ScenarioControlEnv` Environment. """ if env_name in environments: @@ -38,7 +38,7 @@ def make(env_name: str, **kwds) -> ScenarioControlEnv: Returns ------- - :class:`epyt_flow.gym.scenario_control_env.ScenarioControlEnv` + :class:`~epyt_flow.gym.scenario_control_env.ScenarioControlEnv` Environment. """ if env_name not in environments: diff --git a/epyt_flow/simulation/parallel_simulation.py b/epyt_flow/simulation/parallel_simulation.py index ccd1f2b..4ef36ba 100644 --- a/epyt_flow/simulation/parallel_simulation.py +++ b/epyt_flow/simulation/parallel_simulation.py @@ -4,8 +4,8 @@ from typing import Callable, Any import os import warnings -from multiprocess import Pool, cpu_count import shutil +from multiprocess import Pool, cpu_count import psutil from .scenario_config import ScenarioConfig diff --git a/epyt_flow/simulation/scenario_config.py b/epyt_flow/simulation/scenario_config.py index c276a2a..7fc2fdd 100644 --- a/epyt_flow/simulation/scenario_config.py +++ b/epyt_flow/simulation/scenario_config.py @@ -5,8 +5,8 @@ from copy import deepcopy import os import json -import numpy as np from pathlib import Path +import numpy as np from ..uncertainty import AbsoluteGaussianUncertainty, RelativeGaussianUncertainty, \ AbsoluteUniformUncertainty, RelativeUniformUncertainty, ModelUncertainty, \ diff --git a/epyt_flow/simulation/scenario_visualizer.py b/epyt_flow/simulation/scenario_visualizer.py index 1f4d0fd..7e40598 100644 --- a/epyt_flow/simulation/scenario_visualizer.py +++ b/epyt_flow/simulation/scenario_visualizer.py @@ -147,7 +147,7 @@ def __init__(self, scenario: ScenarioSimulator) -> None: Parameters ---------- - scenario : :class:`epyt_flow.simulation.scenario_simulator.ScenarioSimulator` + scenario : :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator` An instance of the `ScenarioSimulator` class, used to simulate and retrieve the system topology. @@ -155,7 +155,7 @@ def __init__(self, scenario: ScenarioSimulator) -> None: ------ TypeError If `scenario` is not an instance of - :class:`epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. + :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`. """ if not isinstance(scenario, ScenarioSimulator): @@ -303,7 +303,7 @@ def __get_link_data( Parameters ---------- - scada_data : :class:`~epyt_flow.scada.scada_data.ScadaData`, optional + scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional The SCADA data object to retrieve link data from. If `None`, a simulation is run to generate the SCADA data. Default is `None`. parameter : `str`, optional @@ -644,7 +644,7 @@ def color_nodes( Parameters ---------- - scada_data : :class:`~epyt_flow.scada.scad_data.ScadaData`, optional + scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional The SCADA data object containing node data. If `None`, a simulation will be run to generate SCADA data. Default is `None`. parameter : `str`, optional @@ -761,7 +761,7 @@ def color_links( Parameters ---------- - scada_data : :class:`~epyt_flow.scada.scada_data.ScadaData`, optional + scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional The SCADA data object. If `None`, the method will run a simulation. Default is `None`. parameter : `str`, optional @@ -861,7 +861,7 @@ def color_pumps( Parameters ---------- - scada_data : :class:`~epyt_flow.scada.scada_data.ScadaData`, optional + scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional The SCADA data object containing the pump data. If `None`, a simulation will be run to generate SCADA data. Default is `None`. parameter : `str`, optional @@ -971,7 +971,7 @@ def color_tanks( Parameters ---------- - scada_data : :class:`~epyt_flow.scada.scada_data.ScadaData`, optional + scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional The SCADA data object containing tank volume data. If `None`, a simulation will be run to generate it. Default is `None`. @@ -1066,7 +1066,7 @@ def color_valves( Parameters ---------- - scada_data : :class:`~epyt_flow.scada.scada_data.ScadaData`, optional + scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional The SCADA data object containing valve state data. If `None`, a simulation is run to generate SCADA data. Default is `None`. statistic : `str`, optional @@ -1162,7 +1162,7 @@ def resize_links( Parameters ---------- - scada_data : :class:`~epyt_flow.scada.scada_data.ScadaData`, optional + scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional The SCADA data object. If `None`, a simulation will be run to generate it. Default is `None`. parameter : `str`, optional From 1b295b679a6ec7eef8b0e3891859bbadf46176b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Fri, 13 Dec 2024 08:31:23 +0100 Subject: [PATCH 16/28] Add local model uncertainties --- docs/examples/uncertainties.ipynb | 7 +- docs/tut.uncertainty.rst | 42 +- epyt_flow/data/benchmarks/leakdb.py | 6 +- epyt_flow/uncertainty/model_uncertainty.py | 648 ++++++++++++++++----- examples/uncertainties.py | 2 +- tests/test_uncertainty.py | 26 +- 6 files changed, 576 insertions(+), 155 deletions(-) diff --git a/docs/examples/uncertainties.ipynb b/docs/examples/uncertainties.ipynb index f60f413..ec5e63b 100644 --- a/docs/examples/uncertainties.ipynb +++ b/docs/examples/uncertainties.ipynb @@ -113,7 +113,7 @@ "source": [ "uc = RelativeUniformUncertainty(low=0.75, high=1.25)\n", "\n", - "sim.set_model_uncertainty(ModelUncertainty(demand_pattern_uncertainty=uc))" + "sim.set_model_uncertainty(ModelUncertainty(global_demand_pattern_uncertainty=uc))" ] }, { @@ -158,6 +158,11 @@ } ], "metadata": { + "kernelspec": { + "display_name": "epytflow2", + "language": "python", + "name": "python3" + }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/tut.uncertainty.rst b/docs/tut.uncertainty.rst index 7ded8f5..0e302c1 100644 --- a/docs/tut.uncertainty.rst +++ b/docs/tut.uncertainty.rst @@ -50,7 +50,7 @@ A complete list of pre-defined and implemented uncertainties is given in the fol Model Uncertainty +++++++++++++++++ -Model uncertainty refers to uncertainty in the WDN model -- i.e. uncertainty in pipe lengths, +Model uncertainty refers to uncertainties in the WDN model -- i.e. uncertainties in pipe lengths, pipe diameters, base demands, demand patterns, etc. EPyT-Flow allows the user to specify model uncertainties by instantiating @@ -58,10 +58,38 @@ EPyT-Flow allows the user to specify model uncertainties by instantiating to the scenario simulator (instance of :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`) by calling :func:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator.set_model_uncertainty` BEFORE -the simulation is run. - -Example of setting pipe length, and demand pattern uncertainty -- in both cases the uncertainty -corresponds to a uniform deviation of up to 10%: +the simulation is run. As a consequence, the simulation runs are no longer deterministic. +See below for a full list of all quantities that can be affected by uncertainties: + ++-------------------------------------------------+ +| Quantities that can be affected by uncertainties| ++=================================================+ +| Node elevation | ++-------------------------------------------------+ +| Pipe length | ++-------------------------------------------------+ +| Pipe diameter | ++-------------------------------------------------+ +| Pipe roughness coefficient | ++-------------------------------------------------+ +| Base demand | ++-------------------------------------------------+ +| Demand pattern | ++-------------------------------------------------+ +| EPANET-MSX constants | ++-------------------------------------------------+ +| EPANET-MSX parameters | ++-------------------------------------------------+ + +Uncertainties can be either on a global or local level. +In global uncertainties, a specific quantity (e.g. pipe length) is always affected by the +same uncertainty -- e.g. all pipe's length are affected by the same uncertainty. +On the other hand, local uncertainties allow to specify the uncertainties for each element +and quantity separately -- e.g. only a sub-set of pipes is affected by some uncertainty, +also, the type and magnitude of uncertainty could vary between the pipes. + +Example of setting pipe length, and demand pattern global uncertainty -- in both cases the +global uncertainty corresponds to a uniform deviation of up to 10%: .. code-block:: python @@ -70,8 +98,8 @@ corresponds to a uniform deviation of up to 10%: with ScenarioSimulator(scenario_config=network_config) as sim: # Specify pipe length and demand pattern uncertainty uncertainty = PercentageDeviationUncertainty(deviation_percentage=.1) - model_uncertainty = ModelUncertainty(pipe_length_uncertainty=uncertainty, - demand_pattern_uncertainty=uncertainty) + model_uncertainty = ModelUncertainty(global_pipe_length_uncertainty=uncertainty, + global_demand_pattern_uncertainty=uncertainty) sim.set_model_uncertainty(model_uncertainty) # Run the simulation diff --git a/epyt_flow/data/benchmarks/leakdb.py b/epyt_flow/data/benchmarks/leakdb.py index bdbaa36..cab4398 100644 --- a/epyt_flow/data/benchmarks/leakdb.py +++ b/epyt_flow/data/benchmarks/leakdb.py @@ -544,9 +544,9 @@ def apply(self, data: float) -> float: upper = data + z return lower + np.random.uniform() * (upper - lower) - my_uncertainties = {"pipe_length_uncertainty": MyUniformUncertainty(low=0, high=0.25), - "pipe_roughness_uncertainty": MyUniformUncertainty(low=0, high=0.25), - "base_demand_uncertainty": MyUniformUncertainty(low=0, high=0.25)} + my_uncertainties = {"global_pipe_length_uncertainty": MyUniformUncertainty(low=0, high=0.25), + "global_pipe_roughness_uncertainty": MyUniformUncertainty(low=0, high=0.25), + "global_base_demand_uncertainty": MyUniformUncertainty(low=0, high=0.25)} model_uncertainty = ModelUncertainty(**my_uncertainties) # Create sensor config (place pressure and flow sensors everywhere) diff --git a/epyt_flow/uncertainty/model_uncertainty.py b/epyt_flow/uncertainty/model_uncertainty.py index 626ae76..ad59a2c 100644 --- a/epyt_flow/uncertainty/model_uncertainty.py +++ b/epyt_flow/uncertainty/model_uncertainty.py @@ -4,6 +4,7 @@ from copy import deepcopy import warnings import epyt +from epyt.epanet import ToolkitConstants import numpy as np from ..serialization import serializable, JsonSerializable, MODEL_UNCERTAINTY_ID @@ -18,36 +19,86 @@ class ModelUncertainty(JsonSerializable): Parameters ---------- - pipe_length_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional - Uncertainty of pipe lengths. None, in the case of no uncertainty. + global_pipe_length_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional + Global uncertainty of pipe lengths. None, in the case of no uncertainty. The default is None. - pipe_roughness_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional - Uncertainty of pipe roughness coefficients. None, in the case of no uncertainty. + global_pipe_roughness_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional + Global uncertainty of pipe roughness coefficients. None, in the case of no uncertainty. The default is None. - pipe_diameter_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional - Uncertainty of pipe diameters. None, in the case of no uncertainty. + global_pipe_diameter_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional + Global uncertainty of pipe diameters. None, in the case of no uncertainty. The default is None. - base_demand_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional - Uncertainty of base demands. None, in the case of no uncertainty. + global_base_demand_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional + Global uncertainty of base demands. None, in the case of no uncertainty. The default is None. - demand_pattern_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional - Uncertainty of demand patterns. None, in the case of no uncertainty. + global_demand_pattern_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional + Global uncertainty of demand patterns. None, in the case of no uncertainty. The default is None. - elevation_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional - Uncertainty of elevations. None, in the case of no uncertainty. + global_elevation_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional + Global uncertainty of elevations. None, in the case of no uncertainty. The default is None. - constants_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional - Uncertainty of MSX constants. None, in the case of no uncertainty. + global_constants_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional + Global uncertainty of MSX constants. None, in the case of no uncertainty. The default is None. - parameters_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional - Uncertainty of MSX parameters. None, in the case of no uncertainty. + global_parameters_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional + Global uncertainty of MSX parameters. None, in the case of no uncertaint. + + The default is None. + local_pipe_length_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional + Local uncertainty of pipe lengths -- i.e. a dictionary of pipe IDs and uncertainties. + + None, in the case of no uncertainty. + + The default is None. + local_pipe_roughness_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional + Local uncertainty of pipe roughness coefficients -- i.e. a dictionary of pipe IDs and uncertainties. + + None, in the case of no uncertainty. + + The default is None. + local_pipe_diameter_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional + Local uncertainty of pipe diameters -- i.e. a dictionary of pipe IDs and uncertainties. + + None, in the case of no uncertainty. + + The default is None. + local_base_demand_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional + Local uncertainty of base demands -- i.e. a dictionary of node IDs and uncertainties. + + None, in the case of no uncertainty. + + The default is None. + local_demand_pattern_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional + Local uncertainty of demand patterns -- + i.e. a dictionary of demand pattern IDs and uncertainties. + + None, in the case of no uncertainty. + + The default is None. + local_elevation_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional + Local uncertainty of elevations -- i.e. a dictionary of node IDs and uncertainties. + + None, in the case of no uncertainty. + + The default is None. + local_constants_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional + Local uncertainty of MSX constants -- i.e. a dictionary of constant IDs and uncertainties. + + None, in the case of no uncertainty. + + The default is None. + local_parameters_uncertainty : dict[tuple[str, int, str] :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional + Local uncertainty of MSX parameters -- i.e. a dictionary of + (parameter ID, item type, item ID) and uncertainties. + + None, in the case of no uncertainty. The default is None. """ @@ -59,188 +110,450 @@ def __init__(self, pipe_length_uncertainty: Uncertainty = None, elevation_uncertainty: Uncertainty = None, constants_uncertainty: Uncertainty = None, parameters_uncertainty: Uncertainty = None, - demand_base_uncertainty: Uncertainty = None, **kwds): - if demand_base_uncertainty is not None: - warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" + - " -- support of such old files will be removed in the next release!", - DeprecationWarning) - + global_pipe_length_uncertainty: Uncertainty = None, + global_pipe_roughness_uncertainty: Uncertainty = None, + global_pipe_diameter_uncertainty: Uncertainty = None, + global_base_demand_uncertainty: Uncertainty = None, + global_demand_pattern_uncertainty: Uncertainty = None, + global_elevation_uncertainty: Uncertainty = None, + global_constants_uncertainty: Uncertainty = None, + global_parameters_uncertainty: Uncertainty = None, + local_pipe_length_uncertainty: dict[str, Uncertainty] = None, + local_pipe_roughness_uncertainty: dict[str, Uncertainty] = None, + local_pipe_diameter_uncertainty: dict[str, Uncertainty] = None, + local_base_demand_uncertainty: dict[str, Uncertainty] = None, + local_demand_pattern_uncertainty: dict[str, Uncertainty] = None, + local_elevation_uncertainty: dict[str, Uncertainty] = None, + local_constants_uncertainty: dict[str, Uncertainty] = None, + local_parameters_uncertainty: dict[str, int, Uncertainty] = None, + **kwds): if pipe_length_uncertainty is not None: - if not isinstance(pipe_length_uncertainty, Uncertainty): - raise TypeError("'pipe_length_uncertainty' must be an instance of " + - "'epyt_flow.uncertainty.Uncertainty' but not of " + - f"'{type(pipe_length_uncertainty)}'") + global_pipe_diameter_uncertainty = pipe_length_uncertainty + warnings.warn("'pipe_length_uncertainty' is deprecated and " + + "will be removed in future releases") if pipe_roughness_uncertainty is not None: - if not isinstance(pipe_roughness_uncertainty, Uncertainty): - raise TypeError("'pipe_roughness_uncertainty' must be an instance of " + - "'epyt_flow.uncertainty.Uncertainty' but not of " + - f"'{type(pipe_roughness_uncertainty)}'") + global_pipe_roughness_uncertainty = pipe_roughness_uncertainty + warnings.warn("'pipe_roughness_uncertainty' is deprecated and " + + "will be removed in future releases") if pipe_diameter_uncertainty is not None: - if not isinstance(pipe_diameter_uncertainty, Uncertainty): - raise TypeError("'pipe_diameter_uncertainty' must be an instance of " + - "'epyt_flow.uncertainty.Uncertainty' but not of " + - f"'{type(pipe_diameter_uncertainty)}'") + global_pipe_diameter_uncertainty = pipe_diameter_uncertainty + warnings.warn("'pipe_diameter_uncertainty' is deprecated and " + + "will be removed in future releases") if base_demand_uncertainty is not None: - if not isinstance(base_demand_uncertainty, Uncertainty): - raise TypeError("'base_demand_uncertainty' must be an instance of " + - "'epyt_flow.uncertainty.Uncertainty' but not of " + - f"'{type(base_demand_uncertainty)}'") + global_base_demand_uncertainty = base_demand_uncertainty + warnings.warn("'base_demand_uncertainty' is deprecated and " + + "will be removed in future releases") if demand_pattern_uncertainty is not None: - if not isinstance(demand_pattern_uncertainty, Uncertainty): - raise TypeError("'demand_pattern_uncertainty' must be an instance of " + - "'epyt_flow.uncertainty.Uncertainty' but not of " + - f"'{type(demand_pattern_uncertainty)}'") + global_demand_pattern_uncertainty = demand_pattern_uncertainty + warnings.warn("'demand_pattern_uncertainty' is deprecated and " + + "will be removed in future releases") if elevation_uncertainty is not None: - if not isinstance(elevation_uncertainty, Uncertainty): - raise TypeError("'elevation_uncertainty' must be an instance of " + - "'epyt_flow.uncertainty.Uncertainty' but not of " + - f"'{type(elevation_uncertainty)}'") + global_elevation_uncertainty = elevation_uncertainty + warnings.warn("'elevation_uncertainty' is deprecated and " + + "will be removed in future releases") if constants_uncertainty is not None: - if not isinstance(constants_uncertainty, Uncertainty): - raise TypeError("'constants_uncertainty' must be an instance of " + - "'epyt_flow.uncertainty.Uncertainty' but not of " + - f"'{type(constants_uncertainty)}'") + global_constants_uncertainty = constants_uncertainty + warnings.warn("'constants_uncertainty' is deprecated and " + + "will be removed in future releases") if parameters_uncertainty is not None: - if not isinstance(parameters_uncertainty, Uncertainty): - raise TypeError("'parameters_uncertainty' must be an instance of " + - "'epyt_flow.uncertainty.Uncertainty' but not of " + - f"'{type(parameters_uncertainty)}'") + global_parameters_uncertainty = parameters_uncertainty + warnings.warn("'parameters_uncertainty' is deprecated and " + + "will be removed in future releases") - self.__pipe_length = pipe_length_uncertainty - self.__pipe_roughness = pipe_roughness_uncertainty - self.__pipe_diameter = pipe_diameter_uncertainty - self.__base_demand = base_demand_uncertainty - self.__demand_pattern = demand_pattern_uncertainty - self.__elevation = elevation_uncertainty - self.__constants = constants_uncertainty - self.__parameters = parameters_uncertainty + if global_pipe_length_uncertainty is not None: + if not isinstance(global_pipe_length_uncertainty, Uncertainty): + raise TypeError("'global_pipe_length_uncertainty' must be an instance of " + + "'epyt_flow.uncertainty.Uncertainty' but not of " + + f"'{type(global_pipe_length_uncertainty)}'") + if global_pipe_roughness_uncertainty is not None: + if not isinstance(global_pipe_roughness_uncertainty, Uncertainty): + raise TypeError("'global_pipe_roughness_uncertainty' must be an instance of " + + "'epyt_flow.uncertainty.Uncertainty' but not of " + + f"'{type(global_pipe_roughness_uncertainty)}'") + if global_pipe_diameter_uncertainty is not None: + if not isinstance(global_pipe_diameter_uncertainty, Uncertainty): + raise TypeError("'global_pipe_diameter_uncertainty' must be an instance of " + + "'epyt_flow.uncertainty.Uncertainty' but not of " + + f"'{type(global_pipe_diameter_uncertainty)}'") + if global_base_demand_uncertainty is not None: + if not isinstance(global_base_demand_uncertainty, Uncertainty): + raise TypeError("'global_base_demand_uncertainty' must be an instance of " + + "'epyt_flow.uncertainty.Uncertainty' but not of " + + f"'{type(global_base_demand_uncertainty)}'") + if global_demand_pattern_uncertainty is not None: + if not isinstance(global_demand_pattern_uncertainty, Uncertainty): + raise TypeError("'global_demand_pattern_uncertainty' must be an instance of " + + "'epyt_flow.uncertainty.Uncertainty' but not of " + + f"'{type(global_demand_pattern_uncertainty)}'") + if global_elevation_uncertainty is not None: + if not isinstance(global_elevation_uncertainty, Uncertainty): + raise TypeError("'global_elevation_uncertainty' must be an instance of " + + "'epyt_flow.uncertainty.Uncertainty' but not of " + + f"'{type(global_elevation_uncertainty)}'") + if global_constants_uncertainty is not None: + if not isinstance(global_constants_uncertainty, Uncertainty): + raise TypeError("'global_constants_uncertainty' must be an instance of " + + "'epyt_flow.uncertainty.Uncertainty' but not of " + + f"'{type(global_constants_uncertainty)}'") + if global_parameters_uncertainty is not None: + if not isinstance(global_parameters_uncertainty, Uncertainty): + raise TypeError("'global_parameters_uncertainty' must be an instance of " + + "'epyt_flow.uncertainty.Uncertainty' but not of " + + f"'{type(global_parameters_uncertainty)}'") + + if local_pipe_length_uncertainty is not None: + if not isinstance(local_pipe_length_uncertainty, dict): + raise TypeError("'local_pipe_length_uncertainty' must be an instance of " + + "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + + f"'{type(local_pipe_length_uncertainty)}'") + if any(not isinstance(key, str) or not isinstance(val, Uncertainty) + for key, val in local_pipe_length_uncertainty.items()): + raise TypeError("'local_pipe_length_uncertainty': " + + "All keys must be instances of 'str' and all values must be " + + "instances of 'epyt_flow.uncertainty.Uncertainty'") + if local_pipe_roughness_uncertainty is not None: + if not isinstance(local_pipe_roughness_uncertainty, dict): + raise TypeError("'local_pipe_roughness_uncertainty' must be an instance of " + + "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + + f"'{type(local_pipe_roughness_uncertainty)}'") + if any(not isinstance(key, str) or not isinstance(val, Uncertainty) + for key, val in local_pipe_roughness_uncertainty.items()): + raise TypeError("'local_pipe_roughness_uncertainty': " + + "All keys must be instances of 'str' and all values must be " + + "instances of 'epyt_flow.uncertainty.Uncertainty'") + if local_pipe_diameter_uncertainty is not None: + if not isinstance(local_pipe_diameter_uncertainty, dict): + raise TypeError("'local_pipe_diameter_uncertainty' must be an instance of " + + "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + + f"'{type(local_pipe_diameter_uncertainty)}'") + if any(not isinstance(key, str) or not isinstance(val, Uncertainty) + for key, val in local_pipe_diameter_uncertainty.items()): + raise TypeError("'local_pipe_diameter_uncertainty': " + + "All keys must be instances of 'str' and all values must be " + + "instances of 'epyt_flow.uncertainty.Uncertainty'") + if local_base_demand_uncertainty is not None: + if not isinstance(local_base_demand_uncertainty, dict): + raise TypeError("'local_base_demand_uncertainty' must be an instance of " + + "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + + f"'{type(local_base_demand_uncertainty)}'") + if any(not isinstance(key, str) or not isinstance(val, Uncertainty) + for key, val in local_base_demand_uncertainty.items()): + raise TypeError("'local_base_demand_uncertainty': " + + "All keys must be instances of 'str' and all values must be " + + "instances of 'epyt_flow.uncertainty.Uncertainty'") + if local_demand_pattern_uncertainty is not None: + if not isinstance(local_demand_pattern_uncertainty, dict): + raise TypeError("'local_demand_pattern_uncertainty' must be an instance of " + + "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + + f"'{type(local_demand_pattern_uncertainty)}'") + if any(not isinstance(key, str) or not isinstance(val, Uncertainty) + for key, val in local_demand_pattern_uncertainty.items()): + raise TypeError("'local_demand_pattern_uncertainty': " + + "All keys must be instances of 'str' and all values must be " + + "instances of 'epyt_flow.uncertainty.Uncertainty'") + if local_elevation_uncertainty is not None: + if not isinstance(local_elevation_uncertainty, dict): + raise TypeError("'local_elevation_uncertainty' must be an instance of " + + "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + + f"'{type(local_elevation_uncertainty)}'") + if any(not isinstance(key, str) or not isinstance(val, Uncertainty) + for key, val in local_elevation_uncertainty.items()): + raise TypeError("'local_elevation_uncertainty': " + + "All keys must be instances of 'str' and all values must be " + + "instances of 'epyt_flow.uncertainty.Uncertainty'") + if local_constants_uncertainty is not None: + if not isinstance(local_constants_uncertainty, dict): + raise TypeError("'local_constants_uncertainty' must be an instance of " + + "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + + f"'{type(local_constants_uncertainty)}'") + if any(not isinstance(key, str) or not isinstance(val, Uncertainty) + for key, val in local_constants_uncertainty.items()): + raise TypeError("'local_constants_uncertainty': " + + "All keys must be instances of 'str' and all values must be " + + "instances of 'epyt_flow.uncertainty.Uncertainty'") + if local_parameters_uncertainty is not None: + if not isinstance(local_parameters_uncertainty, dict): + raise TypeError("'local_parameters_uncertainty' must be an instance of " + + "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + + f"'{type(local_parameters_uncertainty)}'") + if any(not isinstance(key,tuple) or not isinstance(key[0], str) or + not isinstance(key[1], int) or not isinstance(key[2], str) or + not isinstance(local_parameters_uncertainty[key], Uncertainty) + for key in local_parameters_uncertainty.keys()): + raise TypeError("'local_parameters_uncertainty': " + + "All keys must be instances of 'tuple[str, int, str]' and all " + + "values must be instances of 'epyt_flow.uncertainty.Uncertainty'") + + self.__global_pipe_length = global_pipe_length_uncertainty + self.__global_pipe_roughness = global_pipe_roughness_uncertainty + self.__global_pipe_diameter = global_pipe_diameter_uncertainty + self.__global_base_demand = global_base_demand_uncertainty + self.__global_demand_pattern = global_demand_pattern_uncertainty + self.__global_elevation = global_elevation_uncertainty + self.__global_constants = global_constants_uncertainty + self.__global_parameters = global_parameters_uncertainty + self.__local_pipe_length = local_pipe_length_uncertainty + self.__local_pipe_roughness = local_pipe_roughness_uncertainty + self.__local_pipe_diameter = local_pipe_diameter_uncertainty + self.__local_base_demand = local_base_demand_uncertainty + self.__local_demand_pattern = local_demand_pattern_uncertainty + self.__local_elevation = local_elevation_uncertainty + self.__local_constants = local_constants_uncertainty + self.__local_parameters = local_parameters_uncertainty super().__init__(**kwds) @property - def pipe_length(self) -> Uncertainty: + def global_pipe_length(self) -> Uncertainty: """ - Gets the pipe length uncertainty. + Returns the global pipe length uncertainty. Returns ------- :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` - Pipe length uncertainty. + Global pipe length uncertainty. """ - return deepcopy(self.__pipe_length) + return deepcopy(self.__global_pipe_length) @property - def pipe_roughness(self) -> Uncertainty: + def global_pipe_roughness(self) -> Uncertainty: """ - Gets the pipe roughness uncertainty. + Returns the global pipe roughness uncertainty. Returns ------- :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` - Pipe roughness uncertainty. + Global pipe roughness uncertainty. """ - return deepcopy(self.__pipe_roughness) + return deepcopy(self.__global_pipe_roughness) @property - def pipe_diameter(self) -> Uncertainty: + def global_pipe_diameter(self) -> Uncertainty: """ - Gets the pipe diameter uncertainty. + Returns the global pipe diameter uncertainty. Returns ------- :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` - Pipe diameter uncertainty. + Global pipe diameter uncertainty. """ - return deepcopy(self.__pipe_diameter) + return deepcopy(self.__global_pipe_diameter) @property - def base_demand(self) -> Uncertainty: + def global_base_demand(self) -> Uncertainty: """ - Gets the base demand uncertainty. + Returns the global base demand uncertainty. Returns ------- :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` - Demand base uncertainty. + Global base demand uncertainty. """ - return deepcopy(self.__base_demand) + return deepcopy(self.__global_base_demand) @property - def demand_pattern(self) -> Uncertainty: + def global_demand_pattern(self) -> Uncertainty: """ - Gets the demand pattern uncertainty. + Returns the global demand pattern uncertainty. Returns ------- :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` - Demand pattern uncertainty. + Global demand pattern uncertainty. """ - return deepcopy(self.__demand_pattern) + return deepcopy(self.__global_demand_pattern) @property - def elevation(self) -> Uncertainty: + def global_elevation(self) -> Uncertainty: """ - Gets the node elevation uncertainty. + Returns the global node elevation uncertainty. Returns ------- :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` - Node elevation uncertainty. + Global node elevation uncertainty. """ - return deepcopy(self.__elevation) + return deepcopy(self.__global_elevation) @property - def constants(self) -> Uncertainty: + def global_constants(self) -> Uncertainty: """ - Gets the MSX constant uncertainty. + Returns the global MSX constant uncertainty. Returns ------- :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` - MSX constant uncertainty. + Global MSX constant uncertainty. """ - return deepcopy(self.__constants) + return deepcopy(self.__global_constants) @property - def parameters(self) -> Uncertainty: + def global_parameters(self) -> Uncertainty: """ - Gets the MSX parameter uncertainty. + Returns the global MSX parameter uncertainty. Returns ------- :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` - MSX parameter uncertainty. + Global MSX parameter uncertainty. + """ + return deepcopy(self.__global_parameters) + + @property + def local_pipe_length(self) -> dict[str, Uncertainty]: + """ + Returns the local pipe length uncertainty. + + Returns + ------- + dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] + Local pipe length uncertainty. + """ + return deepcopy(self.__local_pipe_length) + + @property + def local_pipe_roughness(self) -> dict[str, Uncertainty]: """ - return deepcopy(self.__parameters) + Returns the local pipe roughness uncertainty. + + Returns + ------- + dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] + Local pipe roughness uncertainty. + """ + return deepcopy(self.__local_pipe_roughness) + + @property + def local_pipe_diameter(self) -> dict[str, Uncertainty]: + """ + Returns the local pipe diameter uncertainty. + + Returns + ------- + dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] + Local pipe diameter uncertainty. + """ + return deepcopy(self.__local_pipe_diameter) + + @property + def local_base_demand(self) -> dict[str, Uncertainty]: + """ + Returns the local base demand uncertainty. + + Returns + ------- + dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] + Local base demand uncertainty. + """ + return deepcopy(self.__local_base_demand) + + @property + def local_demand_pattern(self) -> dict[str, Uncertainty]: + """ + Returns the local demand pattern uncertainty. + + Returns + ------- + dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] + Local demand pattern uncertainty. + """ + return deepcopy(self.__local_demand_pattern) + + @property + def local_elevation(self) -> dict[str, Uncertainty]: + """ + Returns the local node elevation uncertainty. + + Returns + ------- + dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] + Local node elevation uncertainty. + """ + return deepcopy(self.__local_elevation) + + @property + def local_constants(self) -> dict[str, Uncertainty]: + """ + Returns the local MSX constant uncertainty. + + Returns + ------- + dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] + Local MSX constant uncertainty. + """ + return deepcopy(self.__local_constants) + + @property + def local_parameters(self) -> dict[tuple[str, int, str], Uncertainty]: + """ + Returns the local MSX parameter uncertainty. + + Returns + ------- + dict[tuple[str, int, str], :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] + Local MSX parameter uncertainty. + """ + return deepcopy(self.__local_parameters) def get_attributes(self) -> dict: - return super().get_attributes() | {"pipe_length_uncertainty": self.__pipe_length, - "pipe_roughness_uncertainty": self.__pipe_roughness, - "pipe_diameter_uncertainty": self.__pipe_diameter, - "base_demand_uncertainty": self.__base_demand, - "demand_pattern_uncertainty": self.__demand_pattern, - "elevation_uncertainty": self.__elevation, - "constants_uncertainty": self.__constants, - "parameters_uncertainty": self.__parameters} + attribs = {"global_pipe_length_uncertainty": self.__global_pipe_length, + "global_pipe_roughness_uncertainty": self.__global_pipe_roughness, + "global_pipe_diameter_uncertainty": self.__global_pipe_diameter, + "global_base_demand_uncertainty": self.__global_base_demand, + "global_demand_pattern_uncertainty": self.__global_demand_pattern, + "global_elevation_uncertainty": self.__global_elevation, + "global_constants_uncertainty": self.__global_constants, + "global_parameters_uncertainty": self.__global_parameters, + "local_pipe_length_uncertainty": self.__local_pipe_length, + "local_pipe_roughness_uncertainty": self.__local_pipe_roughness, + "local_pipe_diameter_uncertainty": self.__local_pipe_diameter, + "local_base_demand_uncertainty": self.__local_base_demand, + "local_demand_pattern_uncertainty": self.__local_demand_pattern, + "local_elevation_uncertainty": self.__local_elevation, + "local_constants_uncertainty": self.__local_constants, + "local_parameters_uncertainty": self.__local_parameters} + + return super().get_attributes() | attribs def __eq__(self, other) -> bool: if not isinstance(other, ModelUncertainty): raise TypeError("Can not compare 'ModelUncertainty' instance " + f"with '{type(other)}' instance") - return self.__pipe_length == other.pipe_length \ - and self.__pipe_roughness == other.pipe_roughness \ - and self.__pipe_diameter == other.pipe_diameter \ - and self.__base_demand == other.base_demand \ - and self.__demand_pattern == other.demand_pattern \ - and self.__elevation == other.elevation \ - and self.__parameters == other.parameters and self.__constants == other.constants + return self.__global_pipe_length == other.global_pipe_length \ + and self.__global_pipe_roughness == other.global_pipe_roughness \ + and self.__global_pipe_diameter == other.global_pipe_diameter \ + and self.__global_base_demand == other.global_base_demand \ + and self.__global_demand_pattern == other.global_demand_pattern \ + and self.__global_elevation == other.global_elevation \ + and self.__global_parameters == other.global_parameters \ + and self.__global_constants == other.global_constants \ + and self.__local_pipe_length == other.local_pipe_length \ + and self.__local_pipe_roughness == other.local_pipe_roughness \ + and self.__local_pipe_diameter == other.local_pipe_diameter \ + and self.__local_base_demand == other.local_base_demand \ + and self.__local_demand_pattern == other.local_demand_pattern \ + and self.__local_elevation == other.local_elevation \ + and self.__local_parameters == other.local_parameters \ + and self.__local_constants == other.local_constants def __str__(self) -> str: - return f"pipe_length: {self.__pipe_length} pipe_roughness: {self.__pipe_roughness} " + \ - f"pipe_diameter: {self.__pipe_diameter} demand_base: {self.__base_demand} " + \ - f"demand_pattern: {self.__demand_pattern} elevation: {self.__elevation} " + \ - f"constants: {self.__constants} parameters: {self.__parameters}" + return f"global_pipe_length: {self.__global_pipe_length} " +\ + f"global_pipe_roughness: {self.__global_pipe_roughness} " + \ + f"global_pipe_diameter: {self.__global_pipe_diameter} " + \ + f"global_demand_base: {self.__global_base_demand} " + \ + f"global_demand_pattern: {self.__global_demand_pattern} " + \ + f"global_elevation: {self.__global_elevation} " + \ + f"global_constants: {self.__global_constants} " + \ + f"global_parameters: {self.__global_parameters}" + \ + f"local_pipe_length: {self.__local_pipe_length} " +\ + f"local_pipe_roughness: {self.__local_pipe_roughness} " + \ + f"local_pipe_diameter: {self.__local_pipe_diameter} " + \ + f"local_demand_base: {self.__local_base_demand} " + \ + f"local_demand_pattern: {self.__local_demand_pattern} " + \ + f"local_elevation: {self.__local_elevation} " + \ + f"local_constants: {self.__local_constants} " + \ + f"local_parameters: {self.__local_parameters}" def apply(self, epanet_api: epyt.epanet) -> None: """ @@ -251,31 +564,61 @@ def apply(self, epanet_api: epyt.epanet) -> None: epanet_api : `epyt.epanet `_ Interface to EPANET and EPANET-MSX. """ - if self.__pipe_length is not None: + if self.__global_pipe_length is not None: link_length = epanet_api.getLinkLength() - link_length = self.__pipe_length.apply_batch(link_length) + link_length = self.__global_pipe_length.apply_batch(link_length) epanet_api.setLinkLength(link_length) - if self.__pipe_diameter is not None: + if self.__local_pipe_length is not None: + for pipe_id, uncertainty in self.__local_pipe_length.items(): + link_idx = epanet_api.getLinkIndex(pipe_id) + link_length = epanet_api.getLinkLength(link_idx) + link_length = uncertainty.apply(link_length) + epanet_api.setLinkLength(link_idx, link_length) + + if self.__global_pipe_diameter is not None: link_diameters = epanet_api.getLinkDiameter() - link_diameters = self.__pipe_diameter.apply_batch(link_diameters) + link_diameters = self.__global_pipe_diameter.apply_batch(link_diameters) epanet_api.setLinkDiameter(link_diameters) - if self.__pipe_roughness is not None: + if self.__local_pipe_diameter is not None: + for pipe_id, uncertainty in self.__local_pipe_diameter.items(): + link_idx = epanet_api.getLinkIndex(pipe_id) + link_diameter = epanet_api.getLinkDiameter(link_idx) + link_diameter = uncertainty.apply(link_diameter) + epanet_api.setLinkDiameter(link_idx, link_diameter) + + if self.__global_pipe_roughness is not None: coeffs = epanet_api.getLinkRoughnessCoeff() - coeffs = self.__pipe_roughness.apply_batch(coeffs) + coeffs = self.__global_pipe_roughness.apply_batch(coeffs) epanet_api.setLinkRoughnessCoeff(coeffs) - if self.__base_demand is not None: + if self.__local_pipe_roughness is not None: + for pipe_id, uncertainty in self.__local_pipe_roughness.items(): + link_idx = epanet_api.getLinkIndex(pipe_id) + link_roughness_coeff = epanet_api.getLinkRoughnessCoeff(link_idx) + link_roughness_coeff = uncertainty.apply(link_roughness_coeff) + epanet_api.setLinkRoughnessCoeff(link_idx, link_roughness_coeff) + + if self.__global_base_demand is not None: all_nodes_idx = epanet_api.getNodeIndex() for node_idx in all_nodes_idx: n_demand_categories = epanet_api.getNodeDemandCategoriesNumber(node_idx) for demand_category in range(n_demand_categories): base_demand = epanet_api.getNodeBaseDemands(node_idx)[demand_category + 1] - base_demand = self.__base_demand.apply(base_demand) + base_demand = self.__global_base_demand.apply(base_demand) + epanet_api.setNodeBaseDemands(node_idx, demand_category + 1, base_demand) + + if self.__local_base_demand is not None: + for node_id, uncertainty in self.__local_base_demand.items(): + node_idx = epanet_api.getNodeIndex(node_id) + n_demand_categories = epanet_api.getNodeDemandCategoriesNumber(node_idx) + for demand_category in range(n_demand_categories): + base_demand = epanet_api.getNodeBaseDemands(node_idx)[demand_category + 1] + base_demand = uncertainty.apply(base_demand) epanet_api.setNodeBaseDemands(node_idx, demand_category + 1, base_demand) - if self.__demand_pattern is not None: + if self.__global_demand_pattern is not None: demand_patterns_idx = epanet_api.getNodeDemandPatternIndex() demand_patterns_id = np.unique([demand_patterns_idx[k] for k in demand_patterns_idx.keys()]) @@ -285,25 +628,53 @@ def apply(self, epanet_api: epyt.epanet) -> None: pattern_length = epanet_api.getPatternLengths(pattern_id) for t in range(pattern_length): v = epanet_api.getPatternValue(pattern_id, t+1) - epanet_api.setPatternValue(pattern_id, t+1, self.__demand_pattern.apply(v)) + v_ = self.__global_demand_pattern.apply(v) + epanet_api.setPatternValue(pattern_id, t+1, v_) - if self.__elevation is not None: + if self.__local_demand_pattern is not None: + patterns_id = epanet_api.getPatternNameID() + paterns_idx = epanet_api.getPatternIndex() + + for pattern_id, uncertainty in self.__local_demand_pattern.items(): + pattern_idx = paterns_idx[patterns_id.index(pattern_id)] + pattern_length, = epanet_api.getPatternLengths(pattern_id) + for t in range(pattern_length): + v = epanet_api.getPatternValue(pattern_idx, t+1) + v_ = uncertainty.apply(v) + epanet_api.setPatternValue(pattern_idx, t+1, v_) + + if self.__global_elevation is not None: elevations = epanet_api.getNodeElevations() - elevations = self.__elevation.apply_batch(elevations) + elevations = self.__global_elevation.apply_batch(elevations) epanet_api.setNodeElevations(elevations) - if self.__constants is not None: + if self.__local_elevation is not None: + for node_id, uncertainty in self.__local_elevation.items(): + node_idx = epanet_api.getNodeIndex(node_id) + elevation = epanet_api.getNodeElevations(node_idx) + elevation = uncertainty.apply(elevation) + epanet_api.setNodeElevations(node_idx, elevation) + + if self.__global_constants is not None: constants = np.array(epanet_api.getMSXConstantsValue()) - constants = self.__constants.apply_batch(constants) + constants = self.__global_constants.apply_batch(constants) epanet_api.setMSXConstantsValue(constants) - if self.__parameters is not None: + if self.__local_constants: + for constant_id, uncertainty in self.__local_constants.items(): + idx = epanet_api.MSXgetindex(ToolkitConstants.MSX_CONSTANT, constant_id) + constant = epanet_api.msx.MSXgetconstant(idx) + constant = uncertainty.apply(constant) + epanet_api.msx.MSXsetconstant(idx, constant) + + if self.__global_parameters is not None: parameters_pipes = epanet_api.getMSXParametersPipesValue() for i, pipe_idx in enumerate(epanet_api.getLinkPipeIndex()): if len(parameters_pipes[i]) == 0: continue - parameters_pipes_val = self.__parameters.apply_batch(np.array(parameters_pipes[i])) + parameters_pipes_val = self.__global_parameters.apply_batch( + np.array(parameters_pipes[i])) epanet_api.setMSXParametersPipesValue(pipe_idx, parameters_pipes_val) parameters_tanks = epanet_api.getMSXParametersTanksValue() @@ -311,5 +682,22 @@ def apply(self, epanet_api: epyt.epanet) -> None: if parameters_tanks[i] is None or len(parameters_tanks[i]) == 0: continue - parameters_tanks_val = self.__parameters.apply_batch(np.array(parameters_tanks[i])) + parameters_tanks_val = self.__global_parameters.apply_batch( + np.array(parameters_tanks[i])) epanet_api.setMSXParametersTanksValue(tank_idx, parameters_tanks_val) + + if self.__local_parameters is not None: + for (param_id, item_type, item_id), uncertainty in self.__local_parameters.items(): + idx, = epanet_api.getMSXParametersIndex([param_id]) + + if item_type == ToolkitConstants.MSX_NODE: + item_idx = epanet_api.getNodeIndex(item_id) + elif item_type == ToolkitConstants.MSX_LINK: + item_idx = epanet_api.getLinkIndex(item_id) + else: + raise ValueError(f"Unknown item type '{item_type}' must be either " + + "ToolkitConstants.MSX_NODE or ToolkitConstants.MSX_LINK") + + parameter = epanet_api.msx.MSXgetparameter(item_type, item_idx, idx) + parameter = uncertainty.apply(parameter) + epanet_api.msx.MSXsetparameter(item_type, item_idx, idx, parameter) diff --git a/examples/uncertainties.py b/examples/uncertainties.py index fb7594e..9dac83a 100644 --- a/examples/uncertainties.py +++ b/examples/uncertainties.py @@ -21,7 +21,7 @@ # Consequently, the simulation is no longer deterministic and the results vary # from run to run. uc = RelativeUniformUncertainty(low=0.75, high=1.25) - sim.set_model_uncertainty(ModelUncertainty(demand_pattern_uncertainty=uc)) + sim.set_model_uncertainty(ModelUncertainty(global_demand_pattern_uncertainty=uc)) # Run simulation three times and retrieve sensor readings at node "n105" measurements = [] diff --git a/tests/test_uncertainty.py b/tests/test_uncertainty.py index bfa5910..221550a 100644 --- a/tests/test_uncertainty.py +++ b/tests/test_uncertainty.py @@ -21,19 +21,19 @@ def test_model_uncertainty(): "pressure_min": 0, "pressure_required": 0.1, "pressure_exponent": 0.5}) - sim.set_model_uncertainty( - ModelUncertainty(pipe_length_uncertainty=RelativeUniformUncertainty(low=0.9, - high=1.1), - pipe_roughness_uncertainty=RelativeUniformUncertainty(low=0.75, - high=1.25), - pipe_diameter_uncertainty=AbsoluteGaussianUncertainty(mean=0., - scale=.05), - base_demand_uncertainty=RelativeUniformUncertainty(low=0.75, - high=1.25), - demand_pattern_uncertainty=RelativeUniformUncertainty(low=0.75, - high=1.25), - elevation_uncertainty=AbsoluteGaussianUncertainty(mean=0., - scale=0.1))) + uncertainties = {"global_pipe_length_uncertainty": RelativeUniformUncertainty(low=0.9, + high=1.1), + "global_pipe_roughness_uncertainty": RelativeUniformUncertainty(low=0.75, + high=1.25), + "global_pipe_diameter_uncertainty": AbsoluteGaussianUncertainty(mean=0., + scale=.05), + "global_base_demand_uncertainty": RelativeUniformUncertainty(low=0.75, + high=1.25), + "global_demand_pattern_uncertainty": RelativeUniformUncertainty(low=0.75, + high=1.25), + "global_elevation_uncertainty": AbsoluteGaussianUncertainty(mean=0., + scale=0.1)} + sim.set_model_uncertainty(ModelUncertainty(**uncertainties)) res = sim.run_simulation() assert res.get_data() is not None From 29fb31ff5741bebeed841d4dcf166e4d55c92a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Sat, 14 Dec 2024 21:36:11 +0100 Subject: [PATCH 17/28] Use ASCII characters only in progress bars --- epyt_flow/simulation/scenario_simulator.py | 6 +++--- epyt_flow/utils.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/epyt_flow/simulation/scenario_simulator.py b/epyt_flow/simulation/scenario_simulator.py index 92ef7b7..e75c437 100644 --- a/epyt_flow/simulation/scenario_simulator.py +++ b/epyt_flow/simulation/scenario_simulator.py @@ -1663,7 +1663,7 @@ def run_advanced_quality_simulation_as_generator(self, hyd_file_in: str, verbose print("Running EPANET-MSX ...") n_iterations = math.ceil(self.epanet_api.getTimeSimulationDuration() / hyd_time_step) - progress_bar = iter(tqdm(range(n_iterations + 1), desc="Time steps")) + progress_bar = iter(tqdm(range(n_iterations + 1), ascii=True, desc="Time steps")) def __get_concentrations(init_qual=False): if init_qual is True: @@ -1905,7 +1905,7 @@ def run_basic_quality_simulation_as_generator(self, hyd_file_in: str, verbose: b print("Running basic quality analysis using EPANET ...") n_iterations = math.ceil(self.epanet_api.getTimeSimulationDuration() / requested_time_step) - progress_bar = iter(tqdm(range(n_iterations + 1), desc="Time steps")) + progress_bar = iter(tqdm(range(n_iterations + 1), ascii=True, desc="Time steps")) # Run simulation step by step total_time = 0 @@ -2090,7 +2090,7 @@ def run_hydraulic_simulation_as_generator(self, hyd_export: str = None, verbose: print("Running EPANET ...") n_iterations = math.ceil(self.epanet_api.getTimeSimulationDuration() / requested_time_step) - progress_bar = iter(tqdm(range(n_iterations + 1), desc="Time steps")) + progress_bar = iter(tqdm(range(n_iterations + 1), ascii=True, desc="Time steps")) try: # Run simulation step by step diff --git a/epyt_flow/utils.py b/epyt_flow/utils.py index 89c460b..9d7721c 100644 --- a/epyt_flow/utils.py +++ b/epyt_flow/utils.py @@ -342,6 +342,7 @@ def download_if_necessary(download_path: str, url: str, verbose: bool = True) -> content_length = int(response.headers.get('content-length', 0)) with open(download_path, "wb") as file, tqdm(desc=download_path, total=content_length, + ascii=True, unit='B', unit_scale=True, unit_divisor=1024) as progress_bar: From c79a69079c7ddb1820d9dc2d29ec98c588463a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Sun, 15 Dec 2024 10:22:02 +0100 Subject: [PATCH 18/28] Add co-authors to documentation --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index b2c9cda..8debcc3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ project = 'EPyT-Flow' copyright = 'EPyT-Flow Developers, 2024' -author = 'André Artelt, Marios S. Kyriakou, Stelios G. Vrachimis' +author = 'André Artelt, Marios S. Kyriakou, Stelios G. Vrachimis, et al.' # -- General configuration --------------------------------------------------- From f37255f19beb3051946fa082bfbc69d572d4177c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Sun, 15 Dec 2024 10:23:51 +0100 Subject: [PATCH 19/28] ScenarioSimulator: Add functions for getting (demand) patterns --- epyt_flow/simulation/scenario_simulator.py | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/epyt_flow/simulation/scenario_simulator.py b/epyt_flow/simulation/scenario_simulator.py index e75c437..7ceb254 100644 --- a/epyt_flow/simulation/scenario_simulator.py +++ b/epyt_flow/simulation/scenario_simulator.py @@ -947,6 +947,45 @@ def randomize_demands(self) -> None: for t in range(pattern_length): # Set shuffled/randomized pattern self.epanet_api.setPatternValue(pattern_id, t + 1, pattern[t]) + def get_pattern(self, pattern_id: str) -> np.ndarray: + """ + Returns the EPANET pattern (i.e. all multiplier factors over time) given its ID. + + Parameters + ---------- + pattern_id : `str` + ID of the pattern. + + Returns + ------- + `numpy.ndarray `_ + The pattern -- i.e. multiplier factors over time. + """ + pattern_idx = self.epanet_api.getPatternIndex(pattern_id) + pattern_length = self.epanet_api.getPatternLengths(pattern_idx) + return np.array([self.epanet_api.getPatternValue(pattern_idx, t+1) + for t in range(pattern_length)]) + + def get_node_demand_pattern(self, node_id: str) -> np.ndarray: + """ + Returns the values of the demand pattern of a given node -- + i.e. multiplier factors that are applied to the base demand. + + Parameters + ---------- + node_id : `str` + ID of the node. + + Returns + ------- + `numpy.ndarray `_ + The demand pattern -- i.e. multiplier factors over time. + """ + node_idx = self.epanet_api.getNodeIndex(node_id) + demand_category = self.epanet_api.getNodeDemandCategoriesNumber()[node_idx] + demand_pattern_id = self.epanet_api.getNodeDemandPatternNameID()[demand_category][node_idx] + return self.get_pattern(demand_pattern_id) + def set_node_demand_pattern(self, node_id: str, base_demand: float, demand_pattern_id: str, demand_pattern: np.ndarray) -> None: """ From fe22ecdf5abb1bbe169de3f66da3f11e122c31a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Mon, 16 Dec 2024 08:23:32 +0100 Subject: [PATCH 20/28] Fix external links in docstrings --- epyt_flow/metrics.py | 2 +- epyt_flow/simulation/scenario_simulator.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/epyt_flow/metrics.py b/epyt_flow/metrics.py index c8fce62..110403f 100644 --- a/epyt_flow/metrics.py +++ b/epyt_flow/metrics.py @@ -14,7 +14,7 @@ def r2_score(y_pred: np.ndarray, y: np.ndarray) -> float: ---------- y_pred : `numpy.ndarray `_ Predicted outputs. - y : `numpy.ndarray` + y : `numpy.ndarray `_ Ground truth outputs. Returns diff --git a/epyt_flow/simulation/scenario_simulator.py b/epyt_flow/simulation/scenario_simulator.py index 7ceb254..d87d3ac 100644 --- a/epyt_flow/simulation/scenario_simulator.py +++ b/epyt_flow/simulation/scenario_simulator.py @@ -999,7 +999,7 @@ def set_node_demand_pattern(self, node_id: str, base_demand: float, demand_patte Base demand. demand_pattern_id : `str` ID of the new demand pattern. - demand_pattern : `numpy.ndarray` + demand_pattern : `numpy.ndarray `_ Demand pattern over time. Final demand over time = base_demand * demand_pattern """ self._adapt_to_network_changes() @@ -2595,7 +2595,7 @@ def add_quality_source(self, node_id: str, pattern: np.ndarray, source_type: int ---------- node_id : `str` ID of the node at which this external water quality source is placed. - pattern : `numpy.ndarray` + pattern : `numpy.ndarray `_ 1d source pattern. source_type : `int`, Types of the external water quality source -- must be of the following @@ -2688,7 +2688,7 @@ def add_species_injection_source(self, species_id: str, node_id: str, pattern: n node_id : `str` ID of the node at which this external (bulk or surface) species injection source is placed. - pattern : `numpy.ndarray` + pattern : `numpy.ndarray `_ 1d source pattern. source_type : `int`, Type of the external (bulk or surface) species injection source -- must be one of From 40322739c611c66e7f38bc6b4f16c2f463717d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Mon, 16 Dec 2024 08:29:38 +0100 Subject: [PATCH 21/28] Bugfix in set_node_demand_pattern and get_node_demand_pattern --- epyt_flow/simulation/scenario_simulator.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/epyt_flow/simulation/scenario_simulator.py b/epyt_flow/simulation/scenario_simulator.py index d87d3ac..ff92d31 100644 --- a/epyt_flow/simulation/scenario_simulator.py +++ b/epyt_flow/simulation/scenario_simulator.py @@ -983,7 +983,7 @@ def get_node_demand_pattern(self, node_id: str) -> np.ndarray: """ node_idx = self.epanet_api.getNodeIndex(node_id) demand_category = self.epanet_api.getNodeDemandCategoriesNumber()[node_idx] - demand_pattern_id = self.epanet_api.getNodeDemandPatternNameID()[demand_category][node_idx] + demand_pattern_id = self.epanet_api.getNodeDemandPatternNameID()[demand_category][node_idx - 1] return self.get_pattern(demand_pattern_id) def set_node_demand_pattern(self, node_id: str, base_demand: float, demand_pattern_id: str, @@ -998,7 +998,7 @@ def set_node_demand_pattern(self, node_id: str, base_demand: float, demand_patte base_demand : `float` Base demand. demand_pattern_id : `str` - ID of the new demand pattern. + ID of the (new) demand pattern. Existing demand pattern will be overriden if it already exisits. demand_pattern : `numpy.ndarray `_ Demand pattern over time. Final demand over time = base_demand * demand_pattern """ @@ -1020,7 +1020,13 @@ def set_node_demand_pattern(self, node_id: str, base_demand: float, demand_patte "detected. Expected a one dimensional array!") node_idx = self.epanet_api.getNodeIndex(node_id) - self.epanet_api.addPattern(demand_pattern_id, demand_pattern) + + if demand_pattern_id not in self.epanet_api.getPatternNameID(): + self.epanet_api.addPattern(demand_pattern_id, demand_pattern) + else: + pattern_idx = self.epanet_api.getPatternIndex(demand_pattern_id) + self.epanet_api.setPattern(pattern_idx, demand_pattern) + self.epanet_api.setNodeJunctionData(node_idx, self.epanet_api.getNodeElevations(node_idx), base_demand, demand_pattern_id) From 45fa273ed52c2a60ce1eec730fea2307f401dff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Tue, 17 Dec 2024 08:24:18 +0100 Subject: [PATCH 22/28] ScenarioSimulator: Add function add_pattern --- epyt_flow/simulation/scenario_simulator.py | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/epyt_flow/simulation/scenario_simulator.py b/epyt_flow/simulation/scenario_simulator.py index ff92d31..66dd77d 100644 --- a/epyt_flow/simulation/scenario_simulator.py +++ b/epyt_flow/simulation/scenario_simulator.py @@ -966,6 +966,31 @@ def get_pattern(self, pattern_id: str) -> np.ndarray: return np.array([self.epanet_api.getPatternValue(pattern_idx, t+1) for t in range(pattern_length)]) + def add_pattern(self, pattern_id: str, pattern: np.ndarray) -> None: + """ + Adds a pattern to the EPANET scenario. + + Parameters + ---------- + pattern_id : `str` + ID of the pattern. + pattern : `numpy.ndarray `_ + Pattern of multipliers over time. + """ + self._adapt_to_network_changes() + + if not isinstance(pattern_id, str): + raise TypeError("'pattern_id' must be an instance of 'str' " + + f"but not of '{type(pattern_id)}'") + if not isinstance(pattern, np.ndarray): + raise TypeError("'pattern' must be an instance of 'numpy.ndarray' " + + f"but not of '{type(pattern)}'") + if len(pattern.shape) > 1: + raise ValueError(f"Inconsistent pattern shape '{pattern.shape}' " + + "detected. Expected a one dimensional array!") + + self.epanet_api.addPattern(pattern_id, pattern) + def get_node_demand_pattern(self, node_id: str) -> np.ndarray: """ Returns the values of the demand pattern of a given node -- From c175b2d542d597904e3439a2f437f354057a4cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Tue, 17 Dec 2024 08:37:30 +0100 Subject: [PATCH 23/28] Bugfix: Set pump speed --- epyt_flow/gym/scenario_control_env.py | 2 +- epyt_flow/simulation/events/actuator_events.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/epyt_flow/gym/scenario_control_env.py b/epyt_flow/gym/scenario_control_env.py index 3b8ea97..3edc337 100644 --- a/epyt_flow/gym/scenario_control_env.py +++ b/epyt_flow/gym/scenario_control_env.py @@ -194,7 +194,7 @@ def set_pump_speed(self, pump_id: str, speed: float) -> None: if pattern_idx == 0: warnings.warn(f"No pattern for pump '{pump_id}' found -- a new pattern is created") pattern_idx = self._scenario_sim.epanet_api.addPattern(f"pump_speed_{pump_id}") - self._scenario_sim.epanet_api.setLinkPumpPatternIndex(pattern_idx) + self._scenario_sim.epanet_api.setLinkPumpPatternIndex(pump_idx, pattern_idx) self._scenario_sim.epanet_api.setPattern(pattern_idx, np.array([speed])) diff --git a/epyt_flow/simulation/events/actuator_events.py b/epyt_flow/simulation/events/actuator_events.py index 3d29757..29f0a66 100644 --- a/epyt_flow/simulation/events/actuator_events.py +++ b/epyt_flow/simulation/events/actuator_events.py @@ -180,7 +180,7 @@ def apply(self, cur_time: int) -> None: if pattern_idx == 0: warnings.warn(f"No pattern for pump '{self.pump_id}' found -- a new pattern is created") pattern_idx = self._epanet_api.addPattern(f"pump_speed_{self.pump_id}") - self._epanet_api.setLinkPumpPatternIndex(pattern_idx) + self._epanet_api.setLinkPumpPatternIndex(pump_idx, pattern_idx) self._epanet_api.setPattern(pattern_idx, np.array([self.__pump_speed])) From bdfc9fef403373f8a528c32ead146d1334da01f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Wed, 18 Dec 2024 09:04:21 +0100 Subject: [PATCH 24/28] set_node_demand_pattern: Use existing demand pattern if requested --- epyt_flow/simulation/scenario_simulator.py | 57 ++++++++++++++-------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/epyt_flow/simulation/scenario_simulator.py b/epyt_flow/simulation/scenario_simulator.py index 66dd77d..82a86ef 100644 --- a/epyt_flow/simulation/scenario_simulator.py +++ b/epyt_flow/simulation/scenario_simulator.py @@ -1012,7 +1012,7 @@ def get_node_demand_pattern(self, node_id: str) -> np.ndarray: return self.get_pattern(demand_pattern_id) def set_node_demand_pattern(self, node_id: str, base_demand: float, demand_pattern_id: str, - demand_pattern: np.ndarray) -> None: + demand_pattern: np.ndarray = None) -> None: """ Sets the demand pattern (incl. base demand) at a given node. @@ -1024,8 +1024,11 @@ def set_node_demand_pattern(self, node_id: str, base_demand: float, demand_patte Base demand. demand_pattern_id : `str` ID of the (new) demand pattern. Existing demand pattern will be overriden if it already exisits. - demand_pattern : `numpy.ndarray `_ + demand_pattern : `numpy.ndarray `_, optional Demand pattern over time. Final demand over time = base_demand * demand_pattern + If None, the pattern demand_pattern_id is assumed to already exist. + + The default is None. """ self._adapt_to_network_changes() @@ -1037,20 +1040,25 @@ def set_node_demand_pattern(self, node_id: str, base_demand: float, demand_patte if not isinstance(demand_pattern_id, str): raise TypeError("'demand_pattern_id' must be an instance of 'str' " + f"but not of '{type(demand_pattern_id)}'") - if not isinstance(demand_pattern, np.ndarray): - raise TypeError("'demand_pattern' must be an instance of 'numpy.ndarray' " + - f"but not of '{type(demand_pattern)}'") - if len(demand_pattern.shape) > 1: - raise ValueError(f"Inconsistent demand pattern shape '{demand_pattern.shape}' " + - "detected. Expected a one dimensional array!") + if demand_pattern is not None: + if not isinstance(demand_pattern, np.ndarray): + raise TypeError("'demand_pattern' must be an instance of 'numpy.ndarray' " + + f"but not of '{type(demand_pattern)}'") + if len(demand_pattern.shape) > 1: + raise ValueError(f"Inconsistent demand pattern shape '{demand_pattern.shape}' " + + "detected. Expected a one dimensional array!") node_idx = self.epanet_api.getNodeIndex(node_id) if demand_pattern_id not in self.epanet_api.getPatternNameID(): + if demand_pattern is None: + raise ValueError("'demand_pattern' can not be None if " + + "'demand_pattern_id' does not already exist.") self.epanet_api.addPattern(demand_pattern_id, demand_pattern) else: - pattern_idx = self.epanet_api.getPatternIndex(demand_pattern_id) - self.epanet_api.setPattern(pattern_idx, demand_pattern) + if demand_pattern is not None: + pattern_idx = self.epanet_api.getPatternIndex(demand_pattern_id) + self.epanet_api.setPattern(pattern_idx, demand_pattern) self.epanet_api.setNodeJunctionData(node_idx, self.epanet_api.getNodeElevations(node_idx), base_demand, demand_pattern_id) @@ -2617,7 +2625,7 @@ def enable_chemical_analysis(self, chemical_name: str = "Chlorine", self.set_general_parameters(quality_model={"type": "CHEM", "chemical_name": chemical_name, "units": chemical_units}) - def add_quality_source(self, node_id: str, pattern: np.ndarray, source_type: int, + def add_quality_source(self, node_id: str, source_type: int, pattern: np.ndarray = None, pattern_id: str = None, source_strength: int = 1.) -> None: """ Adds a new external water quality source at a particular node. @@ -2626,8 +2634,6 @@ def add_quality_source(self, node_id: str, pattern: np.ndarray, source_type: int ---------- node_id : `str` ID of the node at which this external water quality source is placed. - pattern : `numpy.ndarray `_ - 1d source pattern. source_type : `int`, Types of the external water quality source -- must be of the following EPANET toolkit constants: @@ -2643,6 +2649,12 @@ def add_quality_source(self, node_id: str, pattern: np.ndarray, source_type: int - EN_MASS Injects a given mass/minute into a node - EN_SETPOINT Sets the concentration leaving a node to a given value - EN_FLOWPACED Adds a given value to the concentration leaving a node + pattern : `numpy.ndarray `_, optional + 1d source pattern multipiers over time -- i.e. quality-source = source_strength * pattern. + + If None, the pattern pattern_id is assume to already exist. + + The default is None. pattern_id : `str`, optional ID of the source pattern. @@ -2665,20 +2677,23 @@ def add_quality_source(self, node_id: str, pattern: np.ndarray, source_type: int "call 'enable_chemical_analysis()' before calling this function.") if node_id not in self._sensor_config.nodes: raise ValueError(f"Unknown node '{node_id}'") - if not isinstance(pattern, np.ndarray): - raise TypeError("'pattern' must be an instance of 'numpy.ndarray' " + - f"but not of '{type(pattern)}'") if not isinstance(source_type, int) or not 0 <= source_type <= 3: raise ValueError("Invalid type of water quality source") - + if pattern is not None: + if not isinstance(pattern, np.ndarray): + raise TypeError("'pattern' must be an instance of 'numpy.ndarray' " + + f"but not of '{type(pattern)}'") + if pattern is None and pattern_id is None: + raise ValueError("'pattern_id' and 'pattern' can not be None at the same time") if pattern_id is None: pattern_id = f"quality_source_pattern_node={node_id}" - if pattern_id in self.epanet_api.getPatternNameID(): - raise ValueError("Invalid 'pattern_id' -- " + - f"there already exists a pattern with ID '{pattern_id}'") node_idx = self.epanet_api.getNodeIndex(node_id) - pattern_idx = self.epanet_api.addPattern(pattern_id, pattern) + + if pattern is None: + pattern_idx = self.epanet_api.getPatternIndex(pattern_id) + else: + pattern_idx = self.epanet_api.addPattern(pattern_id, pattern) self.epanet_api.api.ENsetnodevalue(node_idx, ToolkitConstants.EN_SOURCETYPE, source_type) self.epanet_api.setNodeSourceQuality(node_idx, source_strength) From 395a9910316322260e6b3c1859bb6ce04c14fc2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Wed, 18 Dec 2024 09:04:55 +0100 Subject: [PATCH 25/28] Doc: Add PDF output --- .readthedocs.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index ed5efdc..6a06b9c 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -10,4 +10,7 @@ sphinx: python: install: - - requirements: docs/requirements.txt \ No newline at end of file + - requirements: docs/requirements.txt + +formats: + - pdf \ No newline at end of file From 9fc531f6711f91714793547905183f0009291925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Thu, 19 Dec 2024 08:43:23 +0100 Subject: [PATCH 26/28] Add implementation of local sensor noise --- epyt_flow/simulation/scada/scada_data.py | 103 ++++++++++----------- epyt_flow/uncertainty/sensor_noise.py | 109 +++++++++++++++++++---- tests/test_uncertainty.py | 16 ++-- 3 files changed, 148 insertions(+), 80 deletions(-) diff --git a/epyt_flow/simulation/scada/scada_data.py b/epyt_flow/simulation/scada/scada_data.py index 0fc6db9..a6e4725 100644 --- a/epyt_flow/simulation/scada/scada_data.py +++ b/epyt_flow/simulation/scada/scada_data.py @@ -1314,54 +1314,44 @@ def pumps_efficiency_data_raw(self) -> np.ndarray: """ return deepcopy(self.__pumps_efficiency_data_raw) + def __map_sensor_to_idx(self, sensor_type: int, sensor_id: str) -> int: + if sensor_type == SENSOR_TYPE_NODE_PRESSURE: + return self.__sensor_config.get_index_of_reading(pressure_sensor=sensor_id) + elif sensor_type == SENSOR_TYPE_NODE_QUALITY: + return self.__sensor_config.get_index_of_reading(node_quality_sensor=sensor_id) + elif sensor_type == SENSOR_TYPE_NODE_DEMAND: + return self.__sensor_config.get_index_of_reading(demand_sensor=sensor_id) + elif sensor_type == SENSOR_TYPE_LINK_FLOW: + return self.__sensor_config.get_index_of_reading(flow_sensor=sensor_id) + elif sensor_type == SENSOR_TYPE_LINK_QUALITY: + return self.__sensor_config.get_index_of_reading(link_quality_sensor=sensor_id) + elif sensor_type == SENSOR_TYPE_VALVE_STATE: + return self.__sensor_config.get_index_of_reading(valve_state_sensor=sensor_id) + elif sensor_type == SENSOR_TYPE_PUMP_STATE: + return self.__sensor_config.get_index_of_reading(pump_state_sensor=sensor_id) + elif sensor_type == SENSOR_TYPE_PUMP_EFFICIENCY: + return self.__sensor_config.get_index_of_reading(pump_efficiency_sensor=sensor_id) + elif sensor_type == SENSOR_TYPE_PUMP_ENERGYCONSUMPTION: + return self.__sensor_config.get_index_of_reading(pump_energyconsumption_sensor=sensor_id) + elif sensor_type == SENSOR_TYPE_TANK_VOLUME: + return self.__sensor_config.get_index_of_reading(tank_volume_sensor=sensor_id) + elif sensor_type == SENSOR_TYPE_NODE_BULK_SPECIES: + return self.__sensor_config.get_index_of_reading(bulk_species_node_sensor=sensor_id) + elif sensor_type == SENSOR_TYPE_LINK_BULK_SPECIES: + return self.__sensor_config.get_index_of_reading(bulk_species_link_sensor=sensor_id) + elif sensor_type == SENSOR_TYPE_SURFACE_SPECIES: + return self.__sensor_config.get_index_of_reading(surface_species_sensor=sensor_id) + else: + raise ValueError(f"Unknown sensor type '{sensor_type}'") + def __init(self): - self.__apply_sensor_noise = lambda x: x + self.__apply_global_sensor_noise = lambda x: x if self.__sensor_noise is not None: - self.__apply_sensor_noise = self.__sensor_noise.apply + self.__apply_global_sensor_noise = self.__sensor_noise.apply_global_uncertainty self.__apply_sensor_reading_events = [] for sensor_event in self.__sensor_reading_events: - idx = None - if sensor_event.sensor_type == SENSOR_TYPE_NODE_PRESSURE: - idx = self.__sensor_config.get_index_of_reading( - pressure_sensor=sensor_event.sensor_id) - elif sensor_event.sensor_type == SENSOR_TYPE_NODE_QUALITY: - idx = self.__sensor_config.get_index_of_reading( - node_quality_sensor=sensor_event.sensor_id) - elif sensor_event.sensor_type == SENSOR_TYPE_NODE_DEMAND: - idx = self.__sensor_config.get_index_of_reading( - demand_sensor=sensor_event.sensor_id) - elif sensor_event.sensor_type == SENSOR_TYPE_LINK_FLOW: - idx = self.__sensor_config.get_index_of_reading( - flow_sensor=sensor_event.sensor_id) - elif sensor_event.sensor_type == SENSOR_TYPE_LINK_QUALITY: - idx = self.__sensor_config.get_index_of_reading( - link_quality_sensor=sensor_event.sensor_id) - elif sensor_event.sensor_type == SENSOR_TYPE_VALVE_STATE: - idx = self.__sensor_config.get_index_of_reading( - valve_state_sensor=sensor_event.sensor_id) - elif sensor_event.sensor_type == SENSOR_TYPE_PUMP_STATE: - idx = self.__sensor_config.get_index_of_reading( - pump_state_sensor=sensor_event.sensor_id) - elif sensor_event.sensor_type == SENSOR_TYPE_PUMP_EFFICIENCY: - idx = self.__sensor_config.get_index_of_reading( - pump_efficiency_sensor=sensor_event.sensor_id) - elif sensor_event.sensor_type == SENSOR_TYPE_PUMP_ENERGYCONSUMPTION: - idx = self.__sensor_config.get_index_of_reading( - pump_energyconsumption_sensor=sensor_event.sensor_id) - elif sensor_event.sensor_type == SENSOR_TYPE_TANK_VOLUME: - idx = self.__sensor_config.get_index_of_reading( - tank_volume_sensor=sensor_event.sensor_id) - elif sensor_event.sensor_type == SENSOR_TYPE_NODE_BULK_SPECIES: - idx = self.__sensor_config.get_index_of_reading( - bulk_species_node_sensor=sensor_event.sensor_id) - elif sensor_event.sensor_type == SENSOR_TYPE_LINK_BULK_SPECIES: - idx = self.__sensor_config.get_index_of_reading( - bulk_species_link_sensor=sensor_event.sensor_id) - elif sensor_event.sensor_type == SENSOR_TYPE_SURFACE_SPECIES: - idx = self.__sensor_config.get_index_of_reading( - surface_species_sensor=sensor_event.sensor_id) - + idx = self.__map_sensor_to_idx(sensor_event.sensor_type, sensor_event.sensor_id) self.__apply_sensor_reading_events.append((idx, sensor_event.apply)) self.__sensor_readings = None @@ -2019,18 +2009,21 @@ def get_data(self) -> np.ndarray: sensor_readings = np.concatenate(data, axis=1) # Apply sensor uncertainties - state_sensors_idx = [] # Pump states and valve states are NOT affected! - for link_id in self.sensor_config.pump_state_sensors: - state_sensors_idx.append( - self.__sensor_config.get_index_of_reading(pump_state_sensor=link_id)) - for link_id in self.sensor_config.valve_state_sensors: - state_sensors_idx.append( - self.__sensor_config.get_index_of_reading(valve_state_sensor=link_id)) - - mask = np.ones(sensor_readings.shape[1], dtype=bool) - mask[state_sensors_idx] = False - - sensor_readings[:, mask] = self.__apply_sensor_noise(sensor_readings[:, mask]) + if self.__sensor_noise is not None: + state_sensors_idx = [] # Pump states and valve states are NOT affected! + for link_id in self.sensor_config.pump_state_sensors: + state_sensors_idx.append( + self.__sensor_config.get_index_of_reading(pump_state_sensor=link_id)) + for link_id in self.sensor_config.valve_state_sensors: + state_sensors_idx.append( + self.__sensor_config.get_index_of_reading(valve_state_sensor=link_id)) + + mask = np.ones(sensor_readings.shape[1], dtype=bool) + mask[state_sensors_idx] = False + sensor_readings[:, mask] = self.__apply_global_sensor_noise(sensor_readings[:, mask]) + + sensor_readings = self.__sensor_noise.apply_local_uncertainty(self.__map_sensor_to_idx, + sensor_readings) # Apply sensor faults for idx, f in self.__apply_sensor_reading_events: diff --git a/epyt_flow/uncertainty/sensor_noise.py b/epyt_flow/uncertainty/sensor_noise.py index 355d312..1b36f5e 100644 --- a/epyt_flow/uncertainty/sensor_noise.py +++ b/epyt_flow/uncertainty/sensor_noise.py @@ -2,6 +2,8 @@ Module provides a class for implementing sensor noise (e.g. uncertainty in sensor readings). """ from copy import deepcopy +import warnings +from typing import Callable import numpy from .uncertainties import Uncertainty @@ -15,46 +17,115 @@ class SensorNoise(JsonSerializable): Parameters ---------- - uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` - Sensor uncertainty. + global_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional + Global sensor uncertainty. If None, no global sensor uncertainties are applied. + + The default is None. + local_uncertainties : dict[tuple[int, str], :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional + Local (i.e. sensor specific) uncertainties. + If None, no local sensor uncertainties are applied. + + The default is None. """ - def __init__(self, uncertainty: Uncertainty, **kwds): - if not isinstance(uncertainty, Uncertainty): + def __init__(self, uncertainty: Uncertainty = None, + global_uncertainty: Uncertainty = None, + local_uncertainties: dict[int, str, Uncertainty] = None, + **kwds): + if uncertainty is not None: + global_uncertainty = uncertainty + warnings.warn("'uncertainty' is deprecated and will be removed in future releases. " + + "Use 'global_uncertainty' instead") + + if not isinstance(global_uncertainty, Uncertainty): raise TypeError("'uncertainty' must be an instance of " + - f"'epyt_flow.uncertainty.Uncertainty' not of {type(uncertainty)}") - - self.__uncertainty = uncertainty + "'epyt_flow.uncertainty.Uncertainty' but not of " + + f"'{type(global_uncertainty)}'") + if local_uncertainties is not None: + if not isinstance(local_uncertainties, dict): + raise TypeError("'local_uncertainties' must be an instance of " + + "'dict[tuple[int, str], epyt_flow.uncertainty.Uncertainty]' "+ + f"but not of '{type(local_uncertainties)}'") + if any(not isinstance(key[0], int) or not isinstance(key[1], str) or + not isinstance(local_uncertainties[key], Uncertainty) + for key in local_uncertainties.keys()): + raise TypeError("'local_uncertainties' must be an instance of " + + "'dict[tuple[int, str], epyt_flow.uncertainty.Uncertainty]'") + + self.__global_uncertainty = global_uncertainty + self.__local_uncertainties = local_uncertainties super().__init__(**kwds) @property - def uncertainty(self) -> Uncertainty: + def global_uncertainty(self) -> Uncertainty: """ - Gets the Sensor readings uncertainty. + Returns the global sensor readings uncertainty. Returns ------- :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` - Sensor readings uncertainty. + Global sensor readings uncertainty. + """ + return deepcopy(self.__global_uncertainty) + + @property + def local_uncertainties(self) -> dict[int, str, Uncertainty]: """ - return deepcopy(self.__uncertainty) + Returns the local (i.e. sensor specific) uncertainties. + + Returns + ------- + dict[tuple[int, str], :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] + Local (i.e. sensor specific) uncertainties. + """ + return deepcopy(self.__local_uncertainties) def get_attributes(self) -> dict: - return super().get_attributes() | {"uncertainty": self.__uncertainty} + return super().get_attributes() | {"global_uncertainty": self.__global_uncertainty, + "local_uncertainties": self.__local_uncertainties} def __eq__(self, other) -> bool: if not isinstance(other, SensorNoise): raise TypeError("Can not compare 'SensorNoise' instance " + f"with '{type(other)}' instance") - return self.__uncertainty == other.uncertainty + return super().__eq__(other) and self.__global_uncertainty == other.global_uncertainty and \ + self.__local_uncertainties == other.local_uncertainties def __str__(self) -> str: - return f"uncertainty: {self.__uncertainty}" + return f"global_uncertainty: {self.__global_uncertainty} " + \ + f"local_uncertainties: {self.__local_uncertainties}" - def apply(self, sensor_readings: numpy.ndarray) -> numpy.ndarray: + def apply_local_uncertainty(self, map_sensor_to_idx: Callable[[int, str], int], + sensor_readings: numpy.ndarray) -> numpy.ndarray: + """ + Applies the local (i.e. sensor specific) sensor uncertainties -- i.e. sensor readings + are perturbed according to the specified uncertainties. + + Parameters + ---------- + map_sensor_to_idx : `Callable[[int, str], int]` + Function mapping sensor type (int) and sensor id (e.g. node id, link id, etc.) to indices + in the final sensor readings. + sensor_readings : `numpy.ndarray `_ + All (global) sensor readings (no matter if ther). + + Returns + ------- + `numpy.ndarray `_ + Perturbed sensor readings. + """ + if self.__local_uncertainties is None: + return sensor_readings + else: + for (sensor_type, sensor_id), uncertainty in map_sensor_to_idx.items(): + idx = map_sensor_to_idx(sensor_type, sensor_id) + sensor_readings[:, idx] = uncertainty.apply_batch(sensor_readings[:, idx]) + return sensor_readings + + def apply_global_uncertainty(self, sensor_readings: numpy.ndarray) -> numpy.ndarray: """ - Applies the sensor uncertainty to given sensor readings -- i.e. sensor readings + Applies the global sensor uncertainty to given sensor readings -- i.e. sensor readings are perturbed according to the specified uncertainty. .. note:: @@ -64,10 +135,14 @@ def apply(self, sensor_readings: numpy.ndarray) -> numpy.ndarray: Parameters ---------- sensor_readings : `numpy.ndarray `_ + All (global) senor readings. Returns ------- `numpy.ndarray `_ Perturbed sensor readings. """ - return self.__uncertainty.apply_batch(sensor_readings) + if self.__global_uncertainty is None: + return sensor_readings + else: + return self.__global_uncertainty.apply_batch(sensor_readings) diff --git a/tests/test_uncertainty.py b/tests/test_uncertainty.py index 221550a..d0799b4 100644 --- a/tests/test_uncertainty.py +++ b/tests/test_uncertainty.py @@ -48,7 +48,7 @@ def test_sensor_noise(): "pressure_min": 0, "pressure_required": 0.1, "pressure_exponent": 0.5}) - sim.set_sensor_noise(SensorNoise(RelativeGaussianUncertainty(scale=1.))) + sim.set_sensor_noise(SensorNoise(global_uncertainty=RelativeGaussianUncertainty(scale=1.))) res = sim.run_simulation() assert res.get_data() is not None @@ -59,7 +59,7 @@ def test_sensor_noise(): "pressure_min": 0, "pressure_required": 0.1, "pressure_exponent": 0.5}) - sim.set_sensor_noise(SensorNoise(PercentageDeviationUncertainty(0.2))) + sim.set_sensor_noise(SensorNoise(global_uncertainty=PercentageDeviationUncertainty(0.2))) res = sim.run_simulation() assert res.get_data() is not None @@ -70,7 +70,7 @@ def test_sensor_noise(): "pressure_min": 0, "pressure_required": 0.1, "pressure_exponent": 0.5}) - sim.set_sensor_noise(SensorNoise(AbsoluteUniformUncertainty(0, .5))) + sim.set_sensor_noise(SensorNoise(global_uncertainty=AbsoluteUniformUncertainty(0, .5))) res = sim.run_simulation() assert res.get_data() is not None @@ -81,7 +81,7 @@ def test_sensor_noise(): "pressure_min": 0, "pressure_required": 0.1, "pressure_exponent": 0.5}) - sim.set_sensor_noise(SensorNoise(AbsoluteDeepUniformUncertainty())) + sim.set_sensor_noise(SensorNoise(global_uncertainty=AbsoluteDeepUniformUncertainty())) res = sim.run_simulation() assert res.get_data() is not None @@ -92,7 +92,7 @@ def test_sensor_noise(): "pressure_min": 0, "pressure_required": 0.1, "pressure_exponent": 0.5}) - sim.set_sensor_noise(SensorNoise(RelativeDeepUniformUncertainty())) + sim.set_sensor_noise(SensorNoise(global_uncertainty=RelativeDeepUniformUncertainty())) res = sim.run_simulation() assert res.get_data() is not None @@ -103,7 +103,7 @@ def test_sensor_noise(): "pressure_min": 0, "pressure_required": 0.1, "pressure_exponent": 0.5}) - sim.set_sensor_noise(SensorNoise(AbsoluteDeepUniformUncertainty())) + sim.set_sensor_noise(SensorNoise(global_uncertainty=AbsoluteDeepUniformUncertainty())) res = sim.run_simulation() assert res.get_data() is not None @@ -114,7 +114,7 @@ def test_sensor_noise(): "pressure_min": 0, "pressure_required": 0.1, "pressure_exponent": 0.5}) - sim.set_sensor_noise(SensorNoise(AbsoluteDeepGaussianUncertainty())) + sim.set_sensor_noise(SensorNoise(global_uncertainty=AbsoluteDeepGaussianUncertainty())) res = sim.run_simulation() assert res.get_data() is not None @@ -125,7 +125,7 @@ def test_sensor_noise(): "pressure_min": 0, "pressure_required": 0.1, "pressure_exponent": 0.5}) - sim.set_sensor_noise(SensorNoise(RelativeDeepGaussianUncertainty())) + sim.set_sensor_noise(SensorNoise(global_uncertainty=RelativeDeepGaussianUncertainty())) res = sim.run_simulation() assert res.get_data() is not None From 0a0e5531f8722f2cfd572a01c4871b3d3a5aa701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Fri, 20 Dec 2024 09:00:33 +0100 Subject: [PATCH 27/28] ModelUncertainty: Add local uncertainties for patterns --- epyt_flow/uncertainty/model_uncertainty.py | 189 +++++++++++++++------ 1 file changed, 139 insertions(+), 50 deletions(-) diff --git a/epyt_flow/uncertainty/model_uncertainty.py b/epyt_flow/uncertainty/model_uncertainty.py index ad59a2c..ef02282 100644 --- a/epyt_flow/uncertainty/model_uncertainty.py +++ b/epyt_flow/uncertainty/model_uncertainty.py @@ -100,6 +100,19 @@ class ModelUncertainty(JsonSerializable): None, in the case of no uncertainty. + The default is None. + local_patterns_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional + Local uncertainty of EPANET patterns -- i.e. a dictionary of pattern IDs and uncertainties. + + None, in the case of no uncertainty. + + The default is None. + local_msx_patterns_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional + Local uncertainty of EPANET-MSX patterns -- i.e. a dictionary of MSX pattern IDs + and uncertainties. + + None, in the case of no uncertainty. + The default is None. """ def __init__(self, pipe_length_uncertainty: Uncertainty = None, @@ -126,6 +139,8 @@ def __init__(self, pipe_length_uncertainty: Uncertainty = None, local_elevation_uncertainty: dict[str, Uncertainty] = None, local_constants_uncertainty: dict[str, Uncertainty] = None, local_parameters_uncertainty: dict[str, int, Uncertainty] = None, + local_patterns_uncertainty: dict[str, Uncertainty] = None, + local_msx_patterns_uncertainty: dict[str, Uncertainty] = None, **kwds): if pipe_length_uncertainty is not None: global_pipe_diameter_uncertainty = pipe_length_uncertainty @@ -276,13 +291,33 @@ def __init__(self, pipe_length_uncertainty: Uncertainty = None, raise TypeError("'local_parameters_uncertainty' must be an instance of " + "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + f"'{type(local_parameters_uncertainty)}'") - if any(not isinstance(key,tuple) or not isinstance(key[0], str) or + if any(not isinstance(key, tuple) or not isinstance(key[0], str) or not isinstance(key[1], int) or not isinstance(key[2], str) or not isinstance(local_parameters_uncertainty[key], Uncertainty) for key in local_parameters_uncertainty.keys()): raise TypeError("'local_parameters_uncertainty': " + "All keys must be instances of 'tuple[str, int, str]' and all " + "values must be instances of 'epyt_flow.uncertainty.Uncertainty'") + if local_patterns_uncertainty is not None: + if not isinstance(local_patterns_uncertainty, dict): + raise TypeError("'local_patterns_uncertainty' must be an instance of " + + "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + + f"'{type(local_patterns_uncertainty)}'") + if any(not isinstance(key, str) or not isinstance(val, Uncertainty) + for key, val in local_patterns_uncertainty.items()): + raise TypeError("'local_patterns_uncertainty': " + + "All keys must be instances of 'str' and all values must be " + + "instances of 'epyt_flow.uncertainty.Uncertainty'") + if local_msx_patterns_uncertainty is not None: + if not isinstance(local_msx_patterns_uncertainty, dict): + raise TypeError("'local_msx_patterns_uncertainty' must be an instance of " + + "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + + f"'{type(local_msx_patterns_uncertainty)}'") + if any(not isinstance(key, str) or not isinstance(val, Uncertainty) + for key, val in local_msx_patterns_uncertainty.items()): + raise TypeError("'local_msx_patterns_uncertainty': " + + "All keys must be instances of 'str' and all values must be " + + "instances of 'epyt_flow.uncertainty.Uncertainty'") self.__global_pipe_length = global_pipe_length_uncertainty self.__global_pipe_roughness = global_pipe_roughness_uncertainty @@ -300,6 +335,8 @@ def __init__(self, pipe_length_uncertainty: Uncertainty = None, self.__local_elevation = local_elevation_uncertainty self.__local_constants = local_constants_uncertainty self.__local_parameters = local_parameters_uncertainty + self.__local_patterns = local_patterns_uncertainty + self.__local_msx_patterns = local_msx_patterns_uncertainty super().__init__(**kwds) @@ -495,6 +532,30 @@ def local_parameters(self) -> dict[tuple[str, int, str], Uncertainty]: """ return deepcopy(self.__local_parameters) + @property + def local_patterns(self) -> dict[str, Uncertainty]: + """ + Returns the local EPANET patterns uncertainty. + + Returns + ------- + dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] + Local EPANET patterns uncertainty. + """ + return deepcopy(self.__local_patterns) + + @property + def local_msx_patterns(self) -> dict[str, Uncertainty]: + """ + Returns the local EPANET-MSX patterns uncertainty. + + Returns + ------- + dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] + Local EPANET-MSX patterns uncertainty. + """ + return deepcopy(self.__local_msx_patterns) + def get_attributes(self) -> dict: attribs = {"global_pipe_length_uncertainty": self.__global_pipe_length, "global_pipe_roughness_uncertainty": self.__global_pipe_roughness, @@ -511,7 +572,9 @@ def get_attributes(self) -> dict: "local_demand_pattern_uncertainty": self.__local_demand_pattern, "local_elevation_uncertainty": self.__local_elevation, "local_constants_uncertainty": self.__local_constants, - "local_parameters_uncertainty": self.__local_parameters} + "local_parameters_uncertainty": self.__local_parameters, + "local_patterns_uncertainty": self.__local_patterns, + "local_msx_patterns_uncertainty": self.__local_msx_patterns} return super().get_attributes() | attribs @@ -535,7 +598,9 @@ def __eq__(self, other) -> bool: and self.__local_demand_pattern == other.local_demand_pattern \ and self.__local_elevation == other.local_elevation \ and self.__local_parameters == other.local_parameters \ - and self.__local_constants == other.local_constants + and self.__local_constants == other.local_constants \ + and self.__local_patterns == other.local_patterns \ + and self.__local_msx_patterns == other.local_msx_patterns def __str__(self) -> str: return f"global_pipe_length: {self.__global_pipe_length} " +\ @@ -553,7 +618,9 @@ def __str__(self) -> str: f"local_demand_pattern: {self.__local_demand_pattern} " + \ f"local_elevation: {self.__local_elevation} " + \ f"local_constants: {self.__local_constants} " + \ - f"local_parameters: {self.__local_parameters}" + f"local_parameters: {self.__local_parameters} " + \ + f"local_patterns: {self.__local_patterns} " + \ + f"local_msx_patterns: {self.__local_msx_patterns}" def apply(self, epanet_api: epyt.epanet) -> None: """ @@ -655,49 +722,71 @@ def apply(self, epanet_api: epyt.epanet) -> None: elevation = uncertainty.apply(elevation) epanet_api.setNodeElevations(node_idx, elevation) - if self.__global_constants is not None: - constants = np.array(epanet_api.getMSXConstantsValue()) - constants = self.__global_constants.apply_batch(constants) - epanet_api.setMSXConstantsValue(constants) - - if self.__local_constants: - for constant_id, uncertainty in self.__local_constants.items(): - idx = epanet_api.MSXgetindex(ToolkitConstants.MSX_CONSTANT, constant_id) - constant = epanet_api.msx.MSXgetconstant(idx) - constant = uncertainty.apply(constant) - epanet_api.msx.MSXsetconstant(idx, constant) - - if self.__global_parameters is not None: - parameters_pipes = epanet_api.getMSXParametersPipesValue() - for i, pipe_idx in enumerate(epanet_api.getLinkPipeIndex()): - if len(parameters_pipes[i]) == 0: - continue - - parameters_pipes_val = self.__global_parameters.apply_batch( - np.array(parameters_pipes[i])) - epanet_api.setMSXParametersPipesValue(pipe_idx, parameters_pipes_val) - - parameters_tanks = epanet_api.getMSXParametersTanksValue() - for i, tank_idx in enumerate(epanet_api.getNodeTankIndex()): - if parameters_tanks[i] is None or len(parameters_tanks[i]) == 0: - continue - - parameters_tanks_val = self.__global_parameters.apply_batch( - np.array(parameters_tanks[i])) - epanet_api.setMSXParametersTanksValue(tank_idx, parameters_tanks_val) - - if self.__local_parameters is not None: - for (param_id, item_type, item_id), uncertainty in self.__local_parameters.items(): - idx, = epanet_api.getMSXParametersIndex([param_id]) - - if item_type == ToolkitConstants.MSX_NODE: - item_idx = epanet_api.getNodeIndex(item_id) - elif item_type == ToolkitConstants.MSX_LINK: - item_idx = epanet_api.getLinkIndex(item_id) - else: - raise ValueError(f"Unknown item type '{item_type}' must be either " + - "ToolkitConstants.MSX_NODE or ToolkitConstants.MSX_LINK") - - parameter = epanet_api.msx.MSXgetparameter(item_type, item_idx, idx) - parameter = uncertainty.apply(parameter) - epanet_api.msx.MSXsetparameter(item_type, item_idx, idx, parameter) + if self.__local_patterns is not None: + for pattern_id, uncertainty in self.__local_patterns.items(): + pattern_idx = epanet_api.getPatternIndex(pattern_id) + pattern_length = epanet_api.getPatternLengths(pattern_idx) + pattern = np.array([epanet_api.getPatternValue(pattern_idx, t+1) + for t in range(pattern_length)]) + pattern = uncertainty.apply_batch(pattern) + epanet_api.setPattern(pattern_idx, pattern) + + if epanet_api.MSXFile is not None: + if self.__global_constants is not None: + constants = np.array(epanet_api.getMSXConstantsValue()) + constants = self.__global_constants.apply_batch(constants) + epanet_api.setMSXConstantsValue(constants) + + if self.__local_constants: + for constant_id, uncertainty in self.__local_constants.items(): + idx = epanet_api.MSXgetindex(ToolkitConstants.MSX_CONSTANT, constant_id) + constant = epanet_api.msx.MSXgetconstant(idx) + constant = uncertainty.apply(constant) + epanet_api.msx.MSXsetconstant(idx, constant) + + if self.__global_parameters is not None: + parameters_pipes = epanet_api.getMSXParametersPipesValue() + for i, pipe_idx in enumerate(epanet_api.getLinkPipeIndex()): + if len(parameters_pipes[i]) == 0: + continue + + parameters_pipes_val = self.__global_parameters.apply_batch( + np.array(parameters_pipes[i])) + epanet_api.setMSXParametersPipesValue(pipe_idx, parameters_pipes_val) + + parameters_tanks = epanet_api.getMSXParametersTanksValue() + for i, tank_idx in enumerate(epanet_api.getNodeTankIndex()): + if parameters_tanks[i] is None or len(parameters_tanks[i]) == 0: + continue + + parameters_tanks_val = self.__global_parameters.apply_batch( + np.array(parameters_tanks[i])) + epanet_api.setMSXParametersTanksValue(tank_idx, parameters_tanks_val) + + if self.__local_parameters is not None: + for (param_id, item_type, item_id), uncertainty in self.__local_parameters.items(): + idx, = epanet_api.getMSXParametersIndex([param_id]) + + if item_type == ToolkitConstants.MSX_NODE: + item_idx = epanet_api.getNodeIndex(item_id) + elif item_type == ToolkitConstants.MSX_LINK: + item_idx = epanet_api.getLinkIndex(item_id) + else: + raise ValueError(f"Unknown item type '{item_type}' must be either " + + "ToolkitConstants.MSX_NODE or ToolkitConstants.MSX_LINK") + + parameter = epanet_api.msx.MSXgetparameter(item_type, item_idx, idx) + parameter = uncertainty.apply(parameter) + epanet_api.msx.MSXsetparameter(item_type, item_idx, idx, parameter) + + if self.__local_msx_patterns is not None: + for pattern_id, uncertainty in self.__local_msx_patterns.items(): + pattern_idx, = epanet_api.getMSXPatternsIndex([pattern_id]) + pattern = epanet_api.getMSXConstantsValue([pattern_idx]) + pattern = uncertainty.apply_batch(pattern) + epanet_api.setMSXPattern(pattern_idx, pattern) + else: + if self.__local_msx_patterns is not None or self.__local_parameters is not None or \ + self.__local_constants is not None or self.__global_constants is not None or \ + self.__global_parameters is not None: + warnings.warn("Ignoring EPANET-MSX uncertainties because not .msx file was loaded") From d5999449a3966841818c33280cb53c56a0e0feb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Artelt?= Date: Sat, 28 Dec 2024 15:17:21 +0100 Subject: [PATCH 28/28] Fix typo --- docs/tut.uncertainty.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/tut.uncertainty.rst b/docs/tut.uncertainty.rst index 0e302c1..2ac29f2 100644 --- a/docs/tut.uncertainty.rst +++ b/docs/tut.uncertainty.rst @@ -88,7 +88,7 @@ On the other hand, local uncertainties allow to specify the uncertainties for ea and quantity separately -- e.g. only a sub-set of pipes is affected by some uncertainty, also, the type and magnitude of uncertainty could vary between the pipes. -Example of setting pipe length, and demand pattern global uncertainty -- in both cases the +Example of setting global pipe length, and demand pattern uncertainty -- in both cases the global uncertainty corresponds to a uniform deviation of up to 10%: .. code-block:: python @@ -111,8 +111,9 @@ global uncertainty corresponds to a uniform deviation of up to 10%: Sensor Uncertainty ++++++++++++++++++ -Sensor uncertainty (also referred to as sensor noise) refers to uncertainty that affects **ALL** -sensor readings -- i.e. all sensor readings are perturbed by the given uncertainty. +Sensor uncertainty (also referred to as sensor noise) can either act on a global level -- +i.e. all sensor readings are perturbed by the given uncertainty --, +or on a local level by just affecting a sub-set of sensors. In EPyT-Flow, sensor uncertainties have to be :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` instances wrapped inside a :class:`~epyt_flow.uncertainty.sensor_noise.SensorNoise` instance. @@ -121,7 +122,7 @@ Sensor uncertainty/noise can be added BEFORE the simulation is run by calling :func:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator.set_sensor_noise` of a :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator` instance. -Example setting Gaussian uncertainty BEFORE the simulation is run: +Example setting a global Gaussian uncertainty BEFORE the simulation is run: .. code-block:: python @@ -130,7 +131,7 @@ Example setting Gaussian uncertainty BEFORE the simulation is run: with ScenarioSimulator(scenario_config=network_config) as sim: # Sensor readings are affected by relative Gaussian uncertainty with scale=1 uncertainty = RelativeGaussianUncertainty(scale=1.) - sim.set_sensor_noise(SensorNoise(uncertainty)) + sim.set_sensor_noise(SensorNoise(global_uncertainty=uncertainty)) # Run simulation # .... @@ -139,7 +140,7 @@ AFTERWARDS, the sensor uncertainty/noise can be set or changed by calling :func:`~epyt_flow.simulation.scada.scada_data.ScadaData.change_sensor_noise` of a :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` instance. -Example of setting/changing the sensor uniform deviation uncertainty AFTER the +Example of setting/changing a global sensor uniform deviation uncertainty AFTER the simulation was run: .. code-block:: python @@ -152,4 +153,4 @@ simulation was run: # Sensor readings deviate (uniformly) up to 10% from their original value uncertainty = PercentageDeviationUncertainty(deviation_percentage=.1) - scada_data.change_sensor_noise(SensorNoise(uncertainty)) \ No newline at end of file + scada_data.change_sensor_noise(SensorNoise(global_uncertainty=uncertainty)) \ No newline at end of file