From 373ac0eaee237bbd47e3248405c51425b1b7f847 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Thu, 16 Oct 2025 19:04:50 +0200 Subject: [PATCH 1/4] Improve extraction of multiple point observations --- src/modelskill/model/dfsu.py | 31 ++++++++++++++++++++++++++++--- tests/model/test_dfsu.py | 12 ++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/modelskill/model/dfsu.py b/src/modelskill/model/dfsu.py index 7ab7cf041..f47c56377 100644 --- a/src/modelskill/model/dfsu.py +++ b/src/modelskill/model/dfsu.py @@ -73,7 +73,9 @@ def __init__( item = data.name self.sel_items = SelectedItems(values=data.name, aux=[]) data = mikeio.Dataset({data.name: data}) - elif isinstance(data, (mikeio.dfsu.Dfsu2DH, mikeio.dfsu.Dfsu3D)): + elif isinstance( + data, (mikeio.dfsu.Dfsu2DH, mikeio.dfsu.Dfsu3D, mikeio.Dataset) + ): item_names = [i.name for i in data.items] idx = _get_idx(x=item, valid_names=item_names) item_info = data.items[idx] @@ -82,8 +84,8 @@ def __init__( item_names, item=item, aux_items=aux_items ) item = self.sel_items.values - if isinstance(data, mikeio.Dataset): - data = data[self.sel_items.all] + # if isinstance(data, mikeio.Dataset): + # item_info = data.items[idx] assert isinstance( data, (mikeio.dfsu.Dfsu2DH, mikeio.dfsu.Dfsu3D, mikeio.Dataset) @@ -144,6 +146,29 @@ def extract( f"Extraction from {type(self.data)} to {type(observation)} is not implemented." ) + def extract_points(self, observations: list[PointObservation]) -> mikeio.Dataset: + """Extract multiple PointObservations at once. + + Parameters + ---------- + observations : list[PointObservation] + List of PointObservations to extract. + + Returns + ------- + mikeio.Dataset + Dataset with extracted points. + """ + x = [obs.x for obs in observations] + y = [obs.y for obs in observations] + if not isinstance(self.data, mikeio.dfsu.Dfsu2DH): + raise NotImplementedError( + "extract_points is only implemented for DfsuModelResult with Dfsu2DH data." + ) + elemids = self.data.geometry.find_index(x=x, y=y) + ds = self.data.read(elements=elemids, items=self.sel_items.all) + return ds + @staticmethod def _parse_spatial_method(method: str | None) -> str | None: METHOD_MAP = { diff --git a/tests/model/test_dfsu.py b/tests/model/test_dfsu.py index 6d9efa53f..db528a40a 100644 --- a/tests/model/test_dfsu.py +++ b/tests/model/test_dfsu.py @@ -195,6 +195,18 @@ def test_dfsu_extract_point(sw_dutch_coast, Hm0_EPL): assert list(mr_extr_1.data.data_vars) == list(mr_extr_2.data.data_vars) +def test_dfsu_extract_points(hd_oresund_2d, klagshamn, drogden): + mr = ms.DfsuModelResult(hd_oresund_2d, item=0) + obs = [klagshamn, drogden] + ds = mr.extract_points(obs) + assert ds.geometry.n_elements == 2 + + # once we have a small dataset with multiple points, we can match + cc = ms.match(obs, ds) + assert "dmi_30357_Drogden_Fyr" in cc + assert "Klagshamn" in cc + + def test_dfsu_extract_point_aux(sw_dutch_coast, Hm0_EPL): mr1 = ms.model_result( sw_dutch_coast, item=0, aux_items=["Peak Wave Direction"], name="SW1" From 59f4dd5ce00ec30acddbf360867869334c70c3e2 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Thu, 16 Oct 2025 19:15:32 +0200 Subject: [PATCH 2/4] Remove unused --- src/modelskill/model/dfsu.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modelskill/model/dfsu.py b/src/modelskill/model/dfsu.py index f47c56377..cf8a7ff34 100644 --- a/src/modelskill/model/dfsu.py +++ b/src/modelskill/model/dfsu.py @@ -84,8 +84,6 @@ def __init__( item_names, item=item, aux_items=aux_items ) item = self.sel_items.values - # if isinstance(data, mikeio.Dataset): - # item_info = data.items[idx] assert isinstance( data, (mikeio.dfsu.Dfsu2DH, mikeio.dfsu.Dfsu3D, mikeio.Dataset) From b16044bdd8c262069f6157e9c036d1306e7dd3eb Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Fri, 17 Oct 2025 08:10:38 +0200 Subject: [PATCH 3/4] extract_points returns DfsuModelResult --- src/modelskill/model/dfsu.py | 28 +++++++++++++++------------- tests/model/test_dfsu.py | 15 +++++++++------ 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/modelskill/model/dfsu.py b/src/modelskill/model/dfsu.py index cf8a7ff34..0be84c78e 100644 --- a/src/modelskill/model/dfsu.py +++ b/src/modelskill/model/dfsu.py @@ -2,6 +2,7 @@ import inspect from pathlib import Path from typing import Literal, Optional, get_args, cast +from collections.abc import Sequence, Iterable import mikeio import numpy as np @@ -29,8 +30,6 @@ class DfsuModelResult(SpatialField): item : str | int | None, optional If multiple items/arrays are present in the input an item must be given (as either an index or a string), by default None - quantity : Quantity, optional - Model quantity, for MIKE files this is inferred from the EUM information aux_items : Optional[list[int | str]], optional Auxiliary items, by default None """ @@ -39,10 +38,9 @@ def __init__( self, data: UnstructuredType, *, - name: Optional[str] = None, + name: str | None = None, item: str | int | None = None, - quantity: Optional[Quantity] = None, - aux_items: Optional[list[int | str]] = None, + aux_items: Sequence[int | str] | None = None, ) -> None: filename = None @@ -90,9 +88,8 @@ def __init__( ) self.data = data self.name = name or str(item) - self.quantity = ( - Quantity.from_mikeio_iteminfo(item_info) if quantity is None else quantity - ) + self.quantity = Quantity.from_mikeio_iteminfo(item_info) + self.filename = filename # TODO: remove? backward compatibility def __repr__(self) -> str: @@ -144,18 +141,18 @@ def extract( f"Extraction from {type(self.data)} to {type(observation)} is not implemented." ) - def extract_points(self, observations: list[PointObservation]) -> mikeio.Dataset: + def extract_points(self, observations: Iterable[PointObservation]) -> DfsuModelResult: """Extract multiple PointObservations at once. Parameters ---------- - observations : list[PointObservation] + observations : Iterable[PointObservation] List of PointObservations to extract. Returns ------- - mikeio.Dataset - Dataset with extracted points. + DfsuModelResult + A DfsuModelResult containing the extracted points. """ x = [obs.x for obs in observations] y = [obs.y for obs in observations] @@ -165,7 +162,12 @@ def extract_points(self, observations: list[PointObservation]) -> mikeio.Dataset ) elemids = self.data.geometry.find_index(x=x, y=y) ds = self.data.read(elements=elemids, items=self.sel_items.all) - return ds + return DfsuModelResult( + data=ds, + name=self.name, + item=self.sel_items.values, + aux_items=self.sel_items.aux, + ) @staticmethod def _parse_spatial_method(method: str | None) -> str | None: diff --git a/tests/model/test_dfsu.py b/tests/model/test_dfsu.py index db528a40a..69af3df99 100644 --- a/tests/model/test_dfsu.py +++ b/tests/model/test_dfsu.py @@ -196,15 +196,18 @@ def test_dfsu_extract_point(sw_dutch_coast, Hm0_EPL): def test_dfsu_extract_points(hd_oresund_2d, klagshamn, drogden): - mr = ms.DfsuModelResult(hd_oresund_2d, item=0) + mr = ms.DfsuModelResult(name="HD", data=hd_oresund_2d, item=0, aux_items=[2]) obs = [klagshamn, drogden] - ds = mr.extract_points(obs) - assert ds.geometry.n_elements == 2 + mr_sub = mr.extract_points(obs) + assert mr_sub.name == "HD" + assert mr_sub.sel_items.values == "Surface elevation" + assert "U velocity" in mr_sub.sel_items.aux + assert mr_sub.data.geometry.n_elements == 2 # once we have a small dataset with multiple points, we can match - cc = ms.match(obs, ds) - assert "dmi_30357_Drogden_Fyr" in cc - assert "Klagshamn" in cc + #cc = ms.match(obs, ds) + #assert "dmi_30357_Drogden_Fyr" in cc + #assert "Klagshamn" in cc def test_dfsu_extract_point_aux(sw_dutch_coast, Hm0_EPL): From eadfd28125956fcf301be01dc861eb156f3ea3a8 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Fri, 17 Oct 2025 10:35:46 +0200 Subject: [PATCH 4/4] Note on avoiding interpolation --- src/modelskill/model/dfsu.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/modelskill/model/dfsu.py b/src/modelskill/model/dfsu.py index 0be84c78e..50bcf9350 100644 --- a/src/modelskill/model/dfsu.py +++ b/src/modelskill/model/dfsu.py @@ -153,6 +153,12 @@ def extract_points(self, observations: Iterable[PointObservation]) -> DfsuModelR ------- DfsuModelResult A DfsuModelResult containing the extracted points. + + Notes + ----- + The extracted result will only contain selected elements and is not suitable for interpolation in space. + Use `match(..., spatial_method='contained')` to avoid interpolation. + """ x = [obs.x for obs in observations] y = [obs.y for obs in observations]