Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix setup channels and 2d1d links #133

Merged
merged 25 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ec96e8e
typo in check of crs_type in setup_channels
shartgring Aug 2, 2024
1ea5c19
typo when writing friction type to mdu
shartgring Aug 2, 2024
333ff68
fixing typo in writing friction values to mdu
shartgring Aug 2, 2024
9568ff8
fixing keyword, first letter must be lowercas
shartgring Aug 2, 2024
8195859
spacing default set to inf instead of None
shartgring Aug 5, 2024
2dbceef
debugging of 2d_to_1d links
shartgring Aug 5, 2024
ccc33df
running pre-commit hooks
shartgring Aug 6, 2024
ab17892
fix linting
shartgring Aug 6, 2024
7fc80c4
Merge remote-tracking branch 'origin/main' into bugfix_setup_channels
shartgring Sep 23, 2024
1ca70bc
import split_by from hydrolib-core
shartgring Sep 25, 2024
d52512d
changing savedir of mesh in write_mesh
shartgring Sep 25, 2024
25de86f
include split_by from hydrolib core in imports
shartgring Sep 25, 2024
dafa9cb
remove dimension dropping 1d2d
shartgring Sep 25, 2024
f05d8cf
enabled mesh test
veenstrajelmer Sep 25, 2024
9b95d96
fixed 1d2dlinks in hydrolib-core network (and thus in written network…
veenstrajelmer Sep 25, 2024
80c39c1
linting
veenstrajelmer Sep 25, 2024
136d4e4
linting
veenstrajelmer Sep 25, 2024
25a0ac2
add durations
veenstrajelmer Sep 25, 2024
22131da
no filterwarning
veenstrajelmer Sep 25, 2024
4989c68
no filterwarning
veenstrajelmer Sep 25, 2024
192e2bd
codecov link update
veenstrajelmer Sep 25, 2024
a59b424
increased test coverage
veenstrajelmer Sep 26, 2024
cd95d61
update friction type to be same as in PR 139
shartgring Sep 26, 2024
a6dbf8b
Update changelog.rst
shartgring Sep 26, 2024
6e1b214
Nicer formatting in changelog.rst
shartgring Sep 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ happy to discuss how it can be implemented for your model.
:target: https://github.com/Deltares/hydromt_delft3dfm/actions/workflows/ci.yml

.. |codecov| image:: https://codecov.io/gh/Deltares/hydromt_delft3dfm/branch/main/graph/badge.svg?token=ss3EgmwHhH
:target: https://codecov.io/gh/Deltares/hydromt_delft3dfm
:target: https://codecov.io/gh/Deltares/hydromt_delft3dfm?displayType=list

.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
:alt: Formatter
Expand Down
4 changes: 3 additions & 1 deletion docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ This makes the code more robust and future-proof.
Added
-----
- Setup 1D laterals at branches from points and polygons. (PR #81)
- Adding tests for Mesh workflows. (PR #133)

Changed
-------

- Change default spacing in setup_channels from ``None`` to ``np.inf``. (PR #133)
Fixed
-----
- Bugfixing of reading of frictions (global), crosssections and boundaries when update. (PR #81)
- Fixing bug related to changes to pandas TimeDelta formatting, see also https://pandas.pydata.org/docs/whatsnew/v2.2.0.html#other-deprecations. (PR #129)
- Fixing setup_links1d2d for 2d to 1d direction. (PR #133)

v0.2.0 (20 November 2023)
=========================
Expand Down
12 changes: 5 additions & 7 deletions hydromt_delft3dfm/dflowfm.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def setup_channels(
friction_value: float = 0.023,
crosssections_fn: str = None,
crosssections_type: str = None,
spacing: int = None,
spacing: float = np.inf,
snap_offset: float = 0.0,
allow_intersection_snapping: bool = True,
):
Expand Down Expand Up @@ -3068,7 +3068,7 @@ def _prepare_inifields(da_dict, da):
# update config if frcition
if "frictype" in self._MAPS[name]:
self.set_config(
"physics.UniFrictType", self._MAPS[name]["frictype"]
"physics.uniffricttype", self._MAPS[name]["frictype"]
)
# update config if infiltration
if name == "infiltcap":
Expand Down Expand Up @@ -3342,7 +3342,7 @@ def read_mesh(self):
def write_mesh(self, write_gui=True):
"""Write 1D branches and 2D mesh at <root/dflowfm/fm_net.nc>."""
self._assert_write_mode()
savedir = dirname(join(self.root, self._config_fn))
savedir = join(self.root, "dflowfm")
mesh_filename = "fm_net.nc"

# write mesh
Expand Down Expand Up @@ -3710,13 +3710,11 @@ def set_link1d2d(
self._mesh = self._mesh.drop_vars(
[
"link1d2d",
"link1d2d_ids",
"link1d2d_long_names",
"link1d2d_id",
"link1d2d_long_name",
"link1d2d_contact_type",
]
)
# Remove unused dims nLink1D2D_edge and Two
self._mesh = self._mesh.drop_dims(["nLink1D2D_edge", "Two"])

# Add link1d2d to mesh
self._mesh = self._mesh.merge(link1d2d)
Expand Down
12 changes: 6 additions & 6 deletions hydromt_delft3dfm/mesh_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
from pyproj import CRS
from shapely.geometry import LineString

# TODO: maybe move this function here instead of under workflows?
from hydromt_delft3dfm.workflows.mesh import _set_link1d2d

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -69,13 +72,10 @@ def hydrolib_network_from_mesh(
dfm_network._mesh1d._set_mesh1d() # TODO: avoid this private function
# dfm_network._mesh1d.meshkernel.mesh1d_set(grids["mesh1d"].mesh)

# add 1d2dlinks
_link1d2d_attrs = dfm_network._link1d2d.__dict__.keys()
if "link1d2d" in mesh:
for var, val in mesh.variables.items():
if var in _link1d2d_attrs:
# use hydrolib-core conventions as it does harmonization when reading.
setattr(dfm_network._link1d2d, var, val.values)
# add 1d2dlinks to network
link1d2d_arr = mesh["link1d2d"].to_numpy()
_set_link1d2d(dfm_network, link1d2d_arr)

return dfm_network

Expand Down
61 changes: 42 additions & 19 deletions hydromt_delft3dfm/workflows/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import xarray as xr
import xugrid as xu
from hydrolib.core.dflowfm import Branch, Network
from hydrolib.core.dflowfm.net.models import split_by
from meshkernel import Contacts, GeometryList
from shapely.geometry import (
LineString,
Expand Down Expand Up @@ -321,27 +322,47 @@
return loads(dumps(geometry, rounding_precision=rounding_precision))


# below function is a copy of functions from hydrolib, to avoid installation error.


# The function below is an altered version of the from_polygon method from hydrolib
# hydrolib > dhydamo > geometry > models.py > GeometryList > from_polygon
# hydrolib uses class methods to access inner_outer_separator; here an object is used
def polygon_to_geometrylist(polygon):
# Create a list of coordinate lists
cls = GeometryList
geometrylist = GeometryList()
# Add exterior
x_ext, y_ext = np.array(polygon.exterior.coords[:]).T
x_crds = [x_ext]
y_crds = [y_ext]
# Add interiors, seperated by inner_outer_separator
for interior in polygon.interiors:
x_int, y_int = np.array(interior.coords[:]).T
x_crds.append([cls.inner_outer_separator])
x_crds.append([geometrylist.inner_outer_separator])
x_crds.append(x_int)
y_crds.append([cls.inner_outer_separator])
y_crds.append([geometrylist.inner_outer_separator])
y_crds.append(y_int)
gl = cls(x_coordinates=np.concatenate(x_crds), y_coordinates=np.concatenate(y_crds))
gl = GeometryList(
x_coordinates=np.concatenate(x_crds), y_coordinates=np.concatenate(y_crds)
)
return gl


# The function below is an altered version of the from_polygon method from hydrolib
# hydrolib > dhydamo > geometry > models.py > GeometryList > _to_polygon, to_geometry
def polygon_to_geometry(geometrylist):
geometries = [
geo
for geo in split_by(geometrylist, geometrylist.geometry_separator)
if geo.x_coordinates.size > 0
]
polygons = []
for geometry in geometries:
parts = [
np.stack([p.x_coordinates, p.y_coordinates], axis=1)
for p in split_by(geometry, geometrylist.inner_outer_separator)
]
polygons.append(Polygon(shell=parts[0], holes=parts[1:]))
return MultiPolygon(polygons)


def links1d2d_add_links_1d_to_2d(
mesh: xu.UgridDataset,
branchids: List[str] = None,
Expand Down Expand Up @@ -406,7 +427,6 @@
network._link1d2d.meshkernel.contacts_compute_single(
node_mask=node_mask, polygons=geometrylist, projection_factor=1.0
)

# Filter the links that are longer than the max distance
id1d = network._link1d2d.link1d2d[npresent:, 0]
id2d = network._link1d2d.link1d2d[npresent:, 1]
Expand All @@ -430,16 +450,20 @@
return link1d2d


def _filter_links_on_idx(network: Network, keep: np.ndarray) -> None:
# Select the remaining links
link1d2d_arr = network._link1d2d.link1d2d[keep]
def _set_link1d2d(network, link1d2d_arr):
# set contacts on meshkernel, use .copy() to avoid strided arrays
mesh1d_indices = link1d2d_arr[:, 0].copy()
mesh2d_indices = link1d2d_arr[:, 1].copy()
contacts = Contacts(mesh1d_indices=mesh1d_indices, mesh2d_indices=mesh2d_indices)
network._link1d2d.meshkernel.contacts_set(contacts)


def _filter_links_on_idx(network: Network, keep: np.ndarray) -> None:
# Select the remaining links
link1d2d_arr = network._link1d2d.link1d2d[keep]
_set_link1d2d(network, link1d2d_arr)


def links1d2d_add_links_2d_to_1d_embedded(
mesh: xu.UgridDataset,
branchids: List[str] = None,
Expand Down Expand Up @@ -506,7 +530,7 @@
mpgl = GeometryList(*faces2d.T.copy())
idx = np.zeros(len(faces2d), dtype=bool)
if isinstance(area, MultiPolygon):
area = [a for a in area]
area = list(area.geoms)

Check warning on line 533 in hydromt_delft3dfm/workflows/mesh.py

View check run for this annotation

Codecov / codecov/patch

hydromt_delft3dfm/workflows/mesh.py#L533

Added line #L533 was not covered by tests
else:
area = [area]
for subarea in area:
Expand All @@ -532,7 +556,7 @@

# Generate links
network._link1d2d.meshkernel.contacts_compute_with_points(
node_mask=node_mask, points=multipoint
node_mask=node_mask, polygons=multipoint
)

# extract links from network object
Expand Down Expand Up @@ -584,9 +608,9 @@
"""
# Initialise hydrolib network object
network = mutils.hydrolib_network_from_mesh(mesh)

print(within)
geometrylist = network.meshkernel.mesh2d_get_mesh_boundaries_as_polygons()
mpboundaries = GeometryList(**geometrylist.__dict__).to_geometry()
mpboundaries = polygon_to_geometry(geometrylist)
if within is not None:
# If a 'within' polygon was provided, get the intersection with
# the meshboundaries and convert it to a geometrylist
Expand All @@ -607,15 +631,14 @@
network._link1d2d._link_from_2d_to_1d_lateral(
node_mask, polygon=geometrylist, search_radius=max_length
)

# If the provided distance factor was None,
# no further selection is needed, all links are kept.
if dist_factor is None:
return
if dist_factor is None or dist_factor == "None":
link1d2d = mutils.links1d2d_from_hydrolib_network(network)
return link1d2d

# Create multilinestring
multilinestring = MultiLineString([poly.exterior for poly in mpboundaries.geoms])

# Find the links that intersect the boundary close to the origin
id1d = network._link1d2d.link1d2d[npresent:, 0]
id2d = network._link1d2d.link1d2d[npresent:, 1]
Expand Down
4 changes: 1 addition & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,4 @@ include = ["hydromt_delft3dfm"]
exclude = ["docs", "examples", "envs", "tests", ".github"]

[tool.pytest.ini_options]
filterwarnings = [
"ignore:distutils Version classes are deprecated:DeprecationWarning",
]
addopts = "--durations=0"
58 changes: 39 additions & 19 deletions tests/test_workflows_mesh.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
from os.path import abspath, dirname, join
from hydromt_delft3dfm import DFlowFMModel
from hydromt_delft3dfm.mesh_utils import hydrolib_network_from_mesh

EXAMPLEDIR = join(dirname(abspath(__file__)), "..", "examples")


def test_setup_inks1d2d_add_links(tmpdir):
"""
to increase code coverage, we are not actually doing anything here since all options fail
nevertheless a convenient setup for a sort of unit test,
so good to keep somewhere and improve in the future
"""
# Instantiate an empty model
def test_hydrolib_network_from_mesh(tmpdir):
model = DFlowFMModel(root=join(EXAMPLEDIR, "dflowfm_piave"), mode="r")
model.read()
model.set_root(tmpdir, mode="w")
# TODO: uncomment all options below
# ValueError: These variables cannot be found in this dataset: ['link1d2d_long_names', 'link1d2d_ids']
# model.setup_link1d2d(link_direction= "1d_to_2d", link_type="embedded")
# AttributeError: type object 'GeometryList' has no attribute 'inner_outer_separator'
# model.setup_link1d2d(link_direction= "2d_to_1d", link_type="embedded")
# ValueError: These variables cannot be found in this dataset: ['link1d2d_long_names', 'link1d2d_ids']
# model.setup_link1d2d(link_direction= "1d_to_2d", link_type="lateral")
# AttributeError: 'GeometryList' object has no attribute 'to_geometry'
# model.setup_link1d2d(link_direction= "2d_to_1d", link_type="lateral")

# add checks with assertions

# check if all expected grids and link1d2 are present in mesh
assert set(model.mesh_names) == set(['mesh1d', 'network1d', 'mesh2d'])
assert "link1d2d" in model.mesh.data_vars
assert model.mesh["link1d2d"].shape == (1734,2)

# check 1d2dlinks are properly converted to hydrolib-core network/link1d2d instance
network = hydrolib_network_from_mesh(model.mesh)
link1d2d_arr = network._link1d2d.link1d2d
assert link1d2d_arr.shape == (1734, 2)

# call this since it does some checks
model.write_mesh()


def test_setup_links1d2d_add_links(tmpdir):
# Instantiate an empty model
model = DFlowFMModel(root=join(EXAMPLEDIR, "dflowfm_local"), mode="r")
model.read()
model.set_root(tmpdir, mode="w")

# TODO: this test takes >6 seconds, speed it up by making it more unittest-like

# TODO: add checks with assertions, but at the moment nothing much seems to change
# compared to the initial model (eg the shape of 1d2dlinks is still (102,2))

# these lines below are still useful to increase code coverage
# at least the functions are executed and raise no errors
model.setup_link1d2d(link_direction="1d_to_2d", link_type="embedded")
model.setup_link1d2d(link_direction="2d_to_1d", link_type="embedded")
model.setup_link1d2d(link_direction="1d_to_2d", link_type="lateral")
# TODO: below does not work with dist_factor enabled
model.setup_link1d2d(link_direction="2d_to_1d", link_type="lateral", dist_factor="None")

# much of the validation is done upon writing, so maybe write the result also
model.write_mesh()
model.write()
# TODO: writing the model fails with "pydantic.v1.error_wrappers.ValidationError: 12 validation errors for StructureModel"
# model.write()
Loading