From c37c563034c61b225f69a00442859247e35a59aa Mon Sep 17 00:00:00 2001 From: Daniel Tollenaar Date: Wed, 14 Aug 2024 16:01:51 +0200 Subject: [PATCH 1/6] fix network --- notebooks/de_dommel/get_model.py | 9 ++++++ notebooks/de_dommel/get_verwerkt.py | 9 ++++++ notebooks/de_dommel/modify_model.py | 46 +++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 notebooks/de_dommel/get_model.py create mode 100644 notebooks/de_dommel/get_verwerkt.py create mode 100644 notebooks/de_dommel/modify_model.py diff --git a/notebooks/de_dommel/get_model.py b/notebooks/de_dommel/get_model.py new file mode 100644 index 0000000..56538ad --- /dev/null +++ b/notebooks/de_dommel/get_model.py @@ -0,0 +1,9 @@ +# %% +from ribasim_nl import CloudStorage + +cloud = CloudStorage() + +dommel_url = cloud.joinurl("DeDommel", "modellen", "DeDommel_2024_6_3") + +# %% +cloud.download_content(dommel_url) diff --git a/notebooks/de_dommel/get_verwerkt.py b/notebooks/de_dommel/get_verwerkt.py new file mode 100644 index 0000000..f6d7d10 --- /dev/null +++ b/notebooks/de_dommel/get_verwerkt.py @@ -0,0 +1,9 @@ +# %% +from ribasim_nl import CloudStorage + +cloud = CloudStorage() + +dommel_url = cloud.joinurl("DeDommel", "verwerkt") + +# %% +cloud.download_content(dommel_url) diff --git a/notebooks/de_dommel/modify_model.py b/notebooks/de_dommel/modify_model.py new file mode 100644 index 0000000..733b50d --- /dev/null +++ b/notebooks/de_dommel/modify_model.py @@ -0,0 +1,46 @@ +# %% +import sqlite3 + +import pandas as pd +from ribasim import Model +from ribasim_nl import CloudStorage + +cloud = CloudStorage() + +ribasim_toml = cloud.joinpath("DeDommel", "modellen", "DeDommel_2024_6_3", "model.toml") +database_gpkg = ribasim_toml.with_name("database.gpkg") + +# %% remove urban_runoff +# Connect to the SQLite database + +conn = sqlite3.connect(database_gpkg) + +# get table into DataFrame +table = "Basin / static" +df = pd.read_sql_query(f"SELECT * FROM '{table}'", conn) + +# drop urban runoff column if exists +if "urban_runoff" in df.columns: + df.drop(columns="urban_runoff", inplace=True) + + # Write the DataFrame back to the SQLite table + df.to_sql(table, conn, if_exists="replace", index=False) + +# # Close the connection +conn.close() + + +# %% read model +model = Model.read(ribasim_toml) + +# %% verwijder Basin to Basin edges +model.edge.df = model.edge.df[~((model.edge.df.from_node_type == "Basin") & (model.edge.df.to_node_type == "Basin"))] + +# %% write model +ribasim_toml = ribasim_toml.parents[1].joinpath("DeDommel", ribasim_toml.name) +model.write(ribasim_toml) + +# %% upload model + +# cloud.upload_model("DeDommel", "DeDommel") +# %% From 7ede4725205bd9a5786649dc3667f47078a03308 Mon Sep 17 00:00:00 2001 From: Daniel Tollenaar Date: Thu, 15 Aug 2024 12:51:47 +0200 Subject: [PATCH 2/6] model-validator --- notebooks/de_dommel/modify_model.py | 17 ++- src/ribasim_nl/ribasim_nl/__init__.py | 3 +- .../ribasim_nl/network_validator.py | 100 ++++++++++++++++++ 3 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 src/ribasim_nl/ribasim_nl/network_validator.py diff --git a/notebooks/de_dommel/modify_model.py b/notebooks/de_dommel/modify_model.py index 733b50d..7bd4bd5 100644 --- a/notebooks/de_dommel/modify_model.py +++ b/notebooks/de_dommel/modify_model.py @@ -2,8 +2,7 @@ import sqlite3 import pandas as pd -from ribasim import Model -from ribasim_nl import CloudStorage +from ribasim_nl import CloudStorage, Model, NetworkValidator cloud = CloudStorage() @@ -33,12 +32,20 @@ # %% read model model = Model.read(ribasim_toml) +network_validator = NetworkValidator(model) + # %% verwijder Basin to Basin edges -model.edge.df = model.edge.df[~((model.edge.df.from_node_type == "Basin") & (model.edge.df.to_node_type == "Basin"))] +duplicated_edges_df = network_validator.edge_duplicated() + +if not duplicated_edges_df.empty: + model.edge.df = model.edge.df[ + ~((model.edge.df.from_node_type == "Basin") & (model.edge.df.to_node_type == "Basin")) + ] + # %% write model -ribasim_toml = ribasim_toml.parents[1].joinpath("DeDommel", ribasim_toml.name) -model.write(ribasim_toml) +# ribasim_toml = ribasim_toml.parents[1].joinpath("DeDommel", ribasim_toml.name) +# model.write(ribasim_toml) # %% upload model diff --git a/src/ribasim_nl/ribasim_nl/__init__.py b/src/ribasim_nl/ribasim_nl/__init__.py index 593c2b2..ea8fb34 100644 --- a/src/ribasim_nl/ribasim_nl/__init__.py +++ b/src/ribasim_nl/ribasim_nl/__init__.py @@ -3,6 +3,7 @@ from ribasim_nl.cloud import CloudStorage from ribasim_nl.model import Model from ribasim_nl.network import Network +from ribasim_nl.network_validator import NetworkValidator from ribasim_nl.reset_index import reset_index -__all__ = ["CloudStorage", "Network", "reset_index", "Model"] +__all__ = ["CloudStorage", "Network", "reset_index", "Model", "NetworkValidator"] diff --git a/src/ribasim_nl/ribasim_nl/network_validator.py b/src/ribasim_nl/ribasim_nl/network_validator.py new file mode 100644 index 0000000..c476309 --- /dev/null +++ b/src/ribasim_nl/ribasim_nl/network_validator.py @@ -0,0 +1,100 @@ +from dataclasses import dataclass + +from ribasim import Model + + +def within_distance(row, gdf, tolerance=1.0) -> bool: + distance = gdf[gdf.index != row.name].distance(row.geometry) + return (distance < tolerance).any() + + +def check_node_connectivity(row, node_df, tolerance=1.0) -> bool: + invalid = True + + # check if from_node_id is valid + if row.from_node_id in node_df.index: + distance = row.geometry.boundary.geoms[0].distance(node_df.at[row.from_node_id, "geometry"]) + invalid = distance > tolerance + + # if valid, check if to_node_id is valid + if (not invalid) and (row.to_node_id in node_df.index): + distance = row.geometry.boundary.geoms[1].distance(node_df.at[row.to_node_id, "geometry"]) + invalid = distance > tolerance + + return invalid + + +def check_internal_basin(row, edge_df) -> bool: + if row.node_type == "Basin": + return row.node_id not in edge_df.from_node_id.to_numpy() + else: + return False + + +@dataclass +class NetworkValidator: + model: Model + tolerance: float = 1 + + @property + def node_df(self): + return self.model.node_table().df + + @property + def edge_df(self): + return self.model.edge.df + + def node_overlapping(self): + """Check if the node-geometry overlaps another node within tolerance (default=1m)""" + return self.node_df[self.node_df.apply(lambda row: within_distance(row, self.node_df, self.tolerance), axis=1)] + + def node_duplicated(self): + """Check if node_id is duplicated""" + return self.node_df[self.node_df.node_id.duplicated()] + + def node_internal_basin(self): + """Check if a Node with node_type Basin is not connected to another node""" + mask = self.node_df.apply(lambda row: check_internal_basin(row, self.edge_df), axis=1) + return self.node_df[mask] + + def edge_duplicated(self): + """Check if the `from_node_id` and `to_node_id` in the edge-table is duplicated""" + return self.edge_df[self.edge_df.duplicated(subset=["from_node_id", "to_node_id"], keep=False)] + + def edge_missing_nodes(self): + """Check if the `from_node_id` and `to_node_id` in the edge-table are both as node-id in the node-table""" + mask = ~( + self.edge_df.from_node_id.isin(self.node_df.node_id) & self.edge_df.to_node_id.isin(self.node_df.node_id) + ) + return self.edge_df[mask] + + def edge_incorrect_from_node(self): + """Check if the `from_node_type` in edge-table in matches the `node_type` of the corresponding node in the node-table""" + node_df = self.node_df.set_index("node_id") + mask = ~self.edge_df.apply( + lambda row: node_df.at[row["from_node_id"], "node_type"] == row["from_node_type"] + if row["from_node_id"] in node_df.index + else False, + axis=1, + ) + return self.edge_df[mask] + + def edge_incorrect_to_node(self): + """Check if the `to_node_type` in edge-table in matches the `node_type` of the corresponding node in the node-table""" + node_df = self.node_df.set_index("node_id") + mask = ~self.edge_df.apply( + lambda row: node_df.at[row["to_node_id"], "node_type"] == row["to_node_type"] + if row["to_node_id"] in node_df.index + else False, + axis=1, + ) + return self.edge_df[mask] + + def edge_incorrect_connectivity(self): + """Check if the geometries of the `from_node_id` and `to_node_id` are on the start and end vertices of the edge-geometry within tolerance (default=1m)""" + node_df = self.node_df.set_index("node_id") + mask = self.edge_df.apply( + lambda row: check_node_connectivity(row=row, node_df=node_df, tolerance=self.tolerance), axis=1 + ) + + return self.edge_df[mask] From 25eef21531d7926de6231ff16f0f926832141480 Mon Sep 17 00:00:00 2001 From: Daniel Tollenaar Date: Thu, 15 Aug 2024 21:59:02 +0200 Subject: [PATCH 3/6] progress! lots of code for #102 --- notebooks/aaenmaas/get_model.py | 9 ++ notebooks/de_dommel/modify_model.py | 123 ++++++++++++++++++++++++++-- src/ribasim_nl/ribasim_nl/model.py | 46 +++++++++++ 3 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 notebooks/aaenmaas/get_model.py diff --git a/notebooks/aaenmaas/get_model.py b/notebooks/aaenmaas/get_model.py new file mode 100644 index 0000000..29dd5a3 --- /dev/null +++ b/notebooks/aaenmaas/get_model.py @@ -0,0 +1,9 @@ +# %% +from ribasim_nl import CloudStorage + +cloud = CloudStorage() + +aaenmaas_url = cloud.joinurl("AaenMaas", "modellen", "AaenMaas_2024_6_3") + +# %% +cloud.download_content(aaenmaas_url) diff --git a/notebooks/de_dommel/modify_model.py b/notebooks/de_dommel/modify_model.py index 7bd4bd5..9824d92 100644 --- a/notebooks/de_dommel/modify_model.py +++ b/notebooks/de_dommel/modify_model.py @@ -1,7 +1,10 @@ # %% import sqlite3 +import geopandas as gpd import pandas as pd +from ribasim import Node +from ribasim.nodes import basin, flow_boundary, level_boundary, manning_resistance from ribasim_nl import CloudStorage, Model, NetworkValidator cloud = CloudStorage() @@ -34,18 +37,124 @@ network_validator = NetworkValidator(model) -# %% verwijder Basin to Basin edges +# %% verwijder duplicated edges duplicated_edges_df = network_validator.edge_duplicated() -if not duplicated_edges_df.empty: - model.edge.df = model.edge.df[ - ~((model.edge.df.from_node_type == "Basin") & (model.edge.df.to_node_type == "Basin")) - ] +# see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2288780504 +model.edge.df = model.edge.df[~((model.edge.df.from_node_type == "Basin") & (model.edge.df.to_node_type == "Basin"))] +# see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2291081244 +duplicated_fids = network_validator.edge_duplicated().index.to_list() +model.edge.df = model.edge.df.drop_duplicates(subset=["from_node_id", "to_node_id"]) + +if not network_validator.edge_duplicated().empty: + raise Exception("nog steeds duplicated edges") + +# %% toevoegen bovenstroomse knopen + +# see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2291091067 +edge_mask = model.edge.df.index.isin(duplicated_fids) +node_id = model.next_node_id +edge_fid = next(i for i in duplicated_fids if i in model.edge.df.index) +model.edge.df.loc[edge_mask, ["from_node_type"]] = "FlowBoundary" +model.edge.df.loc[edge_mask, ["from_node_id"]] = node_id + +node = Node(node_id, model.edge.df.at[edge_fid, "geometry"].boundary.geoms[0]) +data = flow_boundary.Static(flow_rate=[0]) +model.flow_boundary.add(node, [data]) + +# see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2291111647 + +data = [ + basin.Profile(level=[0.0, 1.0], area=[0.01, 1000.0]), + basin.Static( + drainage=[0.0], + potential_evaporation=[0.001 / 86400], + infiltration=[0.0], + precipitation=[0.005 / 86400], + ), + basin.State(level=[0]), +] + +for row in network_validator.edge_incorrect_connectivity().itertuples(): + area = basin.Area(geometry=model.basin.area[row.from_node_id].geometry.to_list()) + node = Node(row.from_node_id, row.geometry.boundary.geoms[0]) + model.edge.df.loc[row.Index, ["from_node_type"]] = "Basin" + model.basin.add(node, data + [area]) + +if not network_validator.edge_incorrect_connectivity().empty: + raise Exception("nog steeds edges zonder knopen") + +# %% verwijderen internal basins + +# see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2291271525 +for row in network_validator.node_internal_basin().itertuples(): + if row.node_id not in model.basin.area.df.node_id.to_numpy(): # remove or change to level-boundary + edge_select_df = model.edge.df[model.edge.df.to_node_id == row.node_id] + if len(edge_select_df) == 1: + if edge_select_df.iloc[0]["from_node_type"] == "FlowBoundary": + model.remove_node(row.node_id) + model.remove_node(edge_select_df.iloc[0]["from_node_id"]) + model.edge.df.drop(index=edge_select_df.index[0], inplace=True) + +df = model.node_table().df[model.node_table().df.node_type == "Basin"] + +# see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2291876800 +boundary_node = Node(node_id=28, geometry=model.flow_boundary[28].geometry) + +for edge_id in [1960, 798, 1579, 1959, 797]: + model.remove_edge(edge_id) + +# see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2292014475 +model.remove_edge(edge_id=953) + +# see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2292017813 +model.remove_edge(edge_id=1913) +model.remove_edge(edge_id=951) + +# see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2291988317 +for edge_id in [799, 1580, 625, 1123, 597, 978]: + model.reverse_edge(edge_id) + +data = level_boundary.Static(level=[0]) +model.level_boundary.add(boundary_node, [data]) + +model.edge.add(model.tabulated_rating_curve[614], model.level_boundary[28]) + +# see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2292050862 + +gdf = gpd.read_file( + cloud.joinpath("DeDommel", "verwerkt", "1_ontvangen_data", "Geodata", "data_Q42018.gpkg"), + layer="HydroObject", + engine="pyogrio", + fid_as_index=True, +) + +geometry = gdf.loc[2751].geometry.interpolate(0.5, normalized=True) +node_id = model.next_node_id +data = manning_resistance.Static(length=[100], manning_n=[0.04], profile_width=[10], profile_slope=[1]) + +model.manning_resistance.add(Node(node_id=node_id, geometry=geometry), [data]) + +model.edge.df = model.edge.df[~((model.edge.df.from_node_id == 611) & (model.edge.df.to_node_id == 1643))] +model.edge.add(model.basin[1643], model.manning_resistance[node_id]) +model.edge.add(model.manning_resistance[node_id], model.basin[1182]) +model.edge.add(model.tabulated_rating_curve[611], model.basin[1182]) + +# for row in df.itertuples(): +# area_select_df = model.basin.area.df[model.basin.area.df.geometry.contains(row.geometry)] +# if len(area_select_df) == 1: +# area_id = area_select_df.iloc[0]["node_id"] +# if row.node_id != area_id: +# print(f"{row.node_id} == {area_id}") +# elif area_select_df.empty: +# raise Exception(f"Basin {row.node_id} not within Area") +# else: +# raise Exception(f"Basin {row.node_id} contained by multiple areas") # %% write model -# ribasim_toml = ribasim_toml.parents[1].joinpath("DeDommel", ribasim_toml.name) -# model.write(ribasim_toml) +ribasim_toml = ribasim_toml.parents[1].joinpath("DeDommel", ribasim_toml.name) +model.write(ribasim_toml) # %% upload model diff --git a/src/ribasim_nl/ribasim_nl/model.py b/src/ribasim_nl/ribasim_nl/model.py index 0bea2f8..03abd0f 100644 --- a/src/ribasim_nl/ribasim_nl/model.py +++ b/src/ribasim_nl/ribasim_nl/model.py @@ -92,6 +92,19 @@ def get_node(self, node_id: int): node_type = self.get_node_type(node_id) return getattr(self, pascal_to_snake_case(node_type))[node_id] + def remove_node(self, node_id: int): + """Remove node from model""" + node_type = self.get_node_type(node_id) + + # read existing table + table = getattr(self, pascal_to_snake_case(node_type)) + + # remove node from all tables + for attr in table.model_fields.keys(): + df = getattr(table, attr).df + if df is not None: + getattr(table, attr).df = df[df.node_id != node_id] + def update_node(self, node_id, node_type, data, node_properties: dict = {}): """Update a node type and/or data""" # get existing network node_type @@ -178,6 +191,39 @@ def add_control_node( for _to_node_id in to_node_id: self.edge.add(table[node_id], self.get_node(_to_node_id)) + def reverse_edge(self, edge_id: int): + """Reverse an edge""" + if self.edge.df is not None: + # get original edge-data + edge_data = dict(self.edge.df.loc[edge_id]) + + # revert node ids + self.edge.df.loc[edge_id, ["from_node_id"]] = edge_data["to_node_id"] + self.edge.df.loc[edge_id, ["to_node_id"]] = edge_data["from_node_id"] + + # revert node types + self.edge.df.loc[edge_id, ["from_node_type"]] = edge_data["to_node_type"] + self.edge.df.loc[edge_id, ["to_node_type"]] = edge_data["from_node_type"] + + # revert geometry + self.edge.df.loc[edge_id, ["geometry"]] = edge_data["geometry"].reverse() + + return self.edge.df.loc[edge_id] + + def remove_edge(self, edge_id: int): + """Remove an edge and disconnected nodes""" + if self.edge.df is not None: + # get original edge-data + edge_data = dict(self.edge.df.loc[edge_id]) + + # remove edge from edge-table + self.edge.df = self.edge.df[self.edge.df.index != edge_id] + + # remove disconnected nodes + for node_id in [edge_data["from_node_id"], edge_data["to_node_id"]]: + if node_id not in self.edge.df[["from_node_id", "to_node_id"]].to_numpy().ravel(): + self.remove_node(node_id) + def find_closest_basin(self, geometry: BaseGeometry, max_distance: float | None) -> NodeData: """Find the closest basin_node.""" # only works when basin area are defined From 9539070623ee4953d3967515539a4439ab848259 Mon Sep 17 00:00:00 2001 From: Daniel Tollenaar Date: Fri, 16 Aug 2024 15:24:45 +0200 Subject: [PATCH 4/6] all for model-version 2024.8.0 --- notebooks/de_dommel/modify_model.py | 109 +++++++++++++----- src/ribasim_nl/ribasim_nl/model.py | 38 ++++-- .../ribasim_nl/network_validator.py | 5 + 3 files changed, 111 insertions(+), 41 deletions(-) diff --git a/notebooks/de_dommel/modify_model.py b/notebooks/de_dommel/modify_model.py index 9824d92..fc47695 100644 --- a/notebooks/de_dommel/modify_model.py +++ b/notebooks/de_dommel/modify_model.py @@ -4,7 +4,7 @@ import geopandas as gpd import pandas as pd from ribasim import Node -from ribasim.nodes import basin, flow_boundary, level_boundary, manning_resistance +from ribasim.nodes import basin, level_boundary, manning_resistance, outlet from ribasim_nl import CloudStorage, Model, NetworkValidator cloud = CloudStorage() @@ -12,6 +12,18 @@ ribasim_toml = cloud.joinpath("DeDommel", "modellen", "DeDommel_2024_6_3", "model.toml") database_gpkg = ribasim_toml.with_name("database.gpkg") + +basin_data = [ + basin.Profile(level=[0.0, 1.0], area=[0.01, 1000.0]), + basin.Static( + drainage=[0.0], + potential_evaporation=[0.001 / 86400], + infiltration=[0.0], + precipitation=[0.005 / 86400], + ), + basin.State(level=[0]), +] + # %% remove urban_runoff # Connect to the SQLite database @@ -56,31 +68,53 @@ edge_mask = model.edge.df.index.isin(duplicated_fids) node_id = model.next_node_id edge_fid = next(i for i in duplicated_fids if i in model.edge.df.index) -model.edge.df.loc[edge_mask, ["from_node_type"]] = "FlowBoundary" +model.edge.df.loc[edge_mask, ["from_node_type"]] = "Basin" model.edge.df.loc[edge_mask, ["from_node_id"]] = node_id node = Node(node_id, model.edge.df.at[edge_fid, "geometry"].boundary.geoms[0]) -data = flow_boundary.Static(flow_rate=[0]) -model.flow_boundary.add(node, [data]) +model.basin.area.df.loc[model.basin.area.df.node_id == 1009, ["node_id"]] = node_id +area = basin.Area(geometry=model.basin.area[node_id].geometry.to_list()) +model.basin.add(node, basin_data + [area]) # see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2291111647 -data = [ - basin.Profile(level=[0.0, 1.0], area=[0.01, 1000.0]), - basin.Static( - drainage=[0.0], - potential_evaporation=[0.001 / 86400], - infiltration=[0.0], - precipitation=[0.005 / 86400], - ), - basin.State(level=[0]), -] for row in network_validator.edge_incorrect_connectivity().itertuples(): + # drop edge from model + model.remove_edge(row.from_node_id, row.to_node_id, remove_disconnected_nodes=False) + + # add basin_node area = basin.Area(geometry=model.basin.area[row.from_node_id].geometry.to_list()) - node = Node(row.from_node_id, row.geometry.boundary.geoms[0]) - model.edge.df.loc[row.Index, ["from_node_type"]] = "Basin" - model.basin.add(node, data + [area]) + basin_node = Node(row.from_node_id, row.geometry.boundary.geoms[0]) + model.basin.add(basin_node, basin_data + [area]) + + # eindhovensch kanaal we need to add manning a 99% of the length + if row.to_node_id == 2: + geometry = row.geometry.interpolate(0.99, normalized=True) + name = "" + if row.to_node_id == 14: + gdf = gpd.read_file( + cloud.joinpath("DeDommel", "verwerkt", "1_ontvangen_data", "Geodata", "data_Q42018.gpkg"), + layer="DuikerSifonHevel", + engine="pyogrio", + fid_as_index=True, + ) + kdu = gdf.loc[5818] + geometry = kdu.geometry.interpolate(0.5, normalized=True) + name = kdu.objectId + + # add manning-node + manning_node_id = model.next_node_id + manning_data = manning_resistance.Static(length=[100], manning_n=[0.04], profile_width=[10], profile_slope=[1]) + model.manning_resistance.add( + Node(node_id=manning_node_id, geometry=geometry, name=name), + [manning_data], + ) + + # add edges + model.edge.add(model.basin[row.from_node_id], model.manning_resistance[manning_node_id]) + model.edge.add(model.manning_resistance[manning_node_id], model.level_boundary[row.to_node_id]) + if not network_validator.edge_incorrect_connectivity().empty: raise Exception("nog steeds edges zonder knopen") @@ -102,24 +136,25 @@ # see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2291876800 boundary_node = Node(node_id=28, geometry=model.flow_boundary[28].geometry) -for edge_id in [1960, 798, 1579, 1959, 797]: - model.remove_edge(edge_id) +for node_id in [29, 1828, 615, 28, 1329]: + model.remove_node(node_id, remove_edges=True) + +level_data = level_boundary.Static(level=[0]) +model.level_boundary.add(boundary_node, [level_data]) + +model.edge.add(model.tabulated_rating_curve[614], model.level_boundary[28]) # see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2292014475 -model.remove_edge(edge_id=953) +model.remove_node(node_id=1898, remove_edges=True) # see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2292017813 -model.remove_edge(edge_id=1913) -model.remove_edge(edge_id=951) +for node_id in [1891, 989, 1058]: + model.remove_node(node_id, remove_edges=True) # see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2291988317 -for edge_id in [799, 1580, 625, 1123, 597, 978]: - model.reverse_edge(edge_id) - -data = level_boundary.Static(level=[0]) -model.level_boundary.add(boundary_node, [data]) - -model.edge.add(model.tabulated_rating_curve[614], model.level_boundary[28]) +# for from_node_id, to_node_id in [799, 1580, 625, 1123, 597, 978]: +for from_node_id, to_node_id in [[616, 1032], [1030, 616], [393, 1242], [1852, 393], [353, 1700], [1253, 353]]: + model.reverse_edge(from_node_id, to_node_id) # see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2292050862 @@ -134,13 +169,27 @@ node_id = model.next_node_id data = manning_resistance.Static(length=[100], manning_n=[0.04], profile_width=[10], profile_slope=[1]) -model.manning_resistance.add(Node(node_id=node_id, geometry=geometry), [data]) +model.manning_resistance.add(Node(node_id=node_id, geometry=geometry), [manning_data]) model.edge.df = model.edge.df[~((model.edge.df.from_node_id == 611) & (model.edge.df.to_node_id == 1643))] model.edge.add(model.basin[1643], model.manning_resistance[node_id]) model.edge.add(model.manning_resistance[node_id], model.basin[1182]) model.edge.add(model.tabulated_rating_curve[611], model.basin[1182]) +# see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2293457160 +model.update_node(417, "Outlet", [outlet.Static(flow_rate=[0])]) + +if not network_validator.node_internal_basin().empty: + raise Exception("nog steeds interne basins") + +df = network_validator.edge_incorrect_type_connectivity( + from_node_type="ManningResistance", to_node_type="LevelBoundary" +) + +# see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2293486609 +for node_id in df.from_node_id: + model.update_node(node_id, "Outlet", [outlet.Static(flow_rate=[100])]) + # for row in df.itertuples(): # area_select_df = model.basin.area.df[model.basin.area.df.geometry.contains(row.geometry)] # if len(area_select_df) == 1: diff --git a/src/ribasim_nl/ribasim_nl/model.py b/src/ribasim_nl/ribasim_nl/model.py index 03abd0f..3141aa2 100644 --- a/src/ribasim_nl/ribasim_nl/model.py +++ b/src/ribasim_nl/ribasim_nl/model.py @@ -92,7 +92,7 @@ def get_node(self, node_id: int): node_type = self.get_node_type(node_id) return getattr(self, pascal_to_snake_case(node_type))[node_id] - def remove_node(self, node_id: int): + def remove_node(self, node_id: int, remove_edges: bool = False): """Remove node from model""" node_type = self.get_node_type(node_id) @@ -105,6 +105,16 @@ def remove_node(self, node_id: int): if df is not None: getattr(table, attr).df = df[df.node_id != node_id] + if remove_edges and (self.edge.df is not None): + for row in self.edge.df[self.edge.df.from_node_id == node_id].itertuples(): + self.remove_edge( + from_node_id=row.from_node_id, to_node_id=row.to_node_id, remove_disconnected_nodes=False + ) + for row in self.edge.df[self.edge.df.to_node_id == node_id].itertuples(): + self.remove_edge( + from_node_id=row.from_node_id, to_node_id=row.to_node_id, remove_disconnected_nodes=False + ) + def update_node(self, node_id, node_type, data, node_properties: dict = {}): """Update a node type and/or data""" # get existing network node_type @@ -115,6 +125,7 @@ def update_node(self, node_id, node_type, data, node_properties: dict = {}): # save node, so we can add it later node_dict = table.node.df[table.node.df["node_id"] == node_id].iloc[0].to_dict() + node_dict.pop("node_type") # remove node from all tables for attr in table.model_fields.keys(): @@ -191,11 +202,15 @@ def add_control_node( for _to_node_id in to_node_id: self.edge.add(table[node_id], self.get_node(_to_node_id)) - def reverse_edge(self, edge_id: int): + def reverse_edge(self, from_node_id: int, to_node_id: int): """Reverse an edge""" if self.edge.df is not None: # get original edge-data - edge_data = dict(self.edge.df.loc[edge_id]) + df = self.edge.df.copy() + df.loc[:, ["edge_id"]] = df.index + df = df.set_index(["from_node_id", "to_node_id"], drop=False) + edge_data = dict(df.loc[from_node_id, to_node_id]) + edge_id = edge_data["edge_id"] # revert node ids self.edge.df.loc[edge_id, ["from_node_id"]] = edge_data["to_node_id"] @@ -208,21 +223,22 @@ def reverse_edge(self, edge_id: int): # revert geometry self.edge.df.loc[edge_id, ["geometry"]] = edge_data["geometry"].reverse() - return self.edge.df.loc[edge_id] - - def remove_edge(self, edge_id: int): + def remove_edge(self, from_node_id: int, to_node_id: int, remove_disconnected_nodes=True): """Remove an edge and disconnected nodes""" if self.edge.df is not None: # get original edge-data - edge_data = dict(self.edge.df.loc[edge_id]) + indices = self.edge.df[ + (self.edge.df.from_node_id == from_node_id) & (self.edge.df.to_node_id == to_node_id) + ].index # remove edge from edge-table - self.edge.df = self.edge.df[self.edge.df.index != edge_id] + self.edge.df = self.edge.df[~self.edge.df.index.isin(indices)] # remove disconnected nodes - for node_id in [edge_data["from_node_id"], edge_data["to_node_id"]]: - if node_id not in self.edge.df[["from_node_id", "to_node_id"]].to_numpy().ravel(): - self.remove_node(node_id) + if remove_disconnected_nodes: + for node_id in [from_node_id, to_node_id]: + if node_id not in self.edge.df[["from_node_id", "to_node_id"]].to_numpy().ravel(): + self.remove_node(node_id) def find_closest_basin(self, geometry: BaseGeometry, max_distance: float | None) -> NodeData: """Find the closest basin_node.""" diff --git a/src/ribasim_nl/ribasim_nl/network_validator.py b/src/ribasim_nl/ribasim_nl/network_validator.py index c476309..a184997 100644 --- a/src/ribasim_nl/ribasim_nl/network_validator.py +++ b/src/ribasim_nl/ribasim_nl/network_validator.py @@ -98,3 +98,8 @@ def edge_incorrect_connectivity(self): ) return self.edge_df[mask] + + def edge_incorrect_type_connectivity(self, from_node_type="ManningResistance", to_node_type="LevelBoundary"): + """Check edges that contain wrong connectivity""" + mask = (self.edge_df.from_node_type == from_node_type) & (self.edge_df.to_node_type == to_node_type) + return self.edge_df[mask] From 51904a8a34840c1f9e5d517e2b6e355a411038a4 Mon Sep 17 00:00:00 2001 From: Daniel Tollenaar Date: Tue, 20 Aug 2024 11:12:25 +0200 Subject: [PATCH 5/6] Update notebooks/de_dommel/modify_model.py Co-authored-by: Martijn Visser --- notebooks/de_dommel/modify_model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/notebooks/de_dommel/modify_model.py b/notebooks/de_dommel/modify_model.py index fc47695..8e636d5 100644 --- a/notebooks/de_dommel/modify_model.py +++ b/notebooks/de_dommel/modify_model.py @@ -50,7 +50,6 @@ network_validator = NetworkValidator(model) # %% verwijder duplicated edges -duplicated_edges_df = network_validator.edge_duplicated() # see: https://github.com/Deltares/Ribasim-NL/issues/102#issuecomment-2288780504 model.edge.df = model.edge.df[~((model.edge.df.from_node_type == "Basin") & (model.edge.df.to_node_type == "Basin"))] From f5401b5dbdd68457edd6d22354942afb95cbbdf2 Mon Sep 17 00:00:00 2001 From: Daniel Tollenaar Date: Tue, 20 Aug 2024 11:54:41 +0200 Subject: [PATCH 6/6] Update network_validator.py --- src/ribasim_nl/ribasim_nl/network_validator.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ribasim_nl/ribasim_nl/network_validator.py b/src/ribasim_nl/ribasim_nl/network_validator.py index a184997..ba95378 100644 --- a/src/ribasim_nl/ribasim_nl/network_validator.py +++ b/src/ribasim_nl/ribasim_nl/network_validator.py @@ -33,6 +33,17 @@ def check_internal_basin(row, edge_df) -> bool: @dataclass class NetworkValidator: + """Contains some methods for validating a RIBASIM-model + + Parameters + ---------- + model: ribasim.Model + Ribasim model + tolerance: float (default=1) + Tolerance to use for snapping. Should be in the units of the model.crs + + """ + model: Model tolerance: float = 1