From 62e08058b4dd8b1f3f5343543cf6281c5666305e Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Wed, 12 Jun 2024 16:52:28 +0200 Subject: [PATCH 1/8] Non-equidistant time axis for Dfsu2DH --- mikeio/dfs/_dfs.py | 5 +-- mikeio/dfsu/_dfsu.py | 72 ++++++++++++++++++++++++++++++----------- mikeio/dfsu/_layered.py | 2 +- tests/test_dfsu.py | 21 ++++-------- 4 files changed, 65 insertions(+), 35 deletions(-) diff --git a/mikeio/dfs/_dfs.py b/mikeio/dfs/_dfs.py index cf3b4f9fd..28e6dc830 100644 --- a/mikeio/dfs/_dfs.py +++ b/mikeio/dfs/_dfs.py @@ -46,8 +46,9 @@ def _read_item_time_step( it: int, error_bad_data: bool = True, fill_bad_data_value: float = np.nan, -) -> Tuple[DfsFile, np.ndarray]: +) -> Tuple[DfsFile, np.ndarray, float]: itemdata = dfs.ReadItemTimeStep(item_numbers[item] + 1, it) + t = itemdata.Time if itemdata is not None: d = itemdata.Data d[d == deletevalue] = np.nan @@ -60,7 +61,7 @@ def _read_item_time_step( d[:] = fill_bad_data_value dfs.Close() dfs = DfsFileFactory.DfsGenericOpen(filename) - return dfs, d + return dfs, d, t def _fuzzy_item_search( diff --git a/mikeio/dfsu/_dfsu.py b/mikeio/dfsu/_dfsu.py index 5e4f1e777..095fc3acd 100644 --- a/mikeio/dfsu/_dfsu.py +++ b/mikeio/dfsu/_dfsu.py @@ -1,11 +1,13 @@ from __future__ import annotations from dataclasses import dataclass +from datetime import datetime from pathlib import Path from typing import Any, Literal, Sequence, Tuple import numpy as np import pandas as pd +from mikecore.DfsFile import TimeAxisType from mikecore.DfsFactory import DfsFactory from mikecore.DfsuBuilder import DfsuBuilder from mikecore.DfsuFile import DfsuFile, DfsuFileType @@ -28,7 +30,7 @@ from ..spatial import Grid2D from .._track import _extract_track from ._common import get_elements_from_source, get_nodes_from_source -from ..eum import ItemInfo +from ..eum import ItemInfo, TimeStepUnit def write_dfsu(filename: str | Path, data: Dataset) -> None: @@ -43,8 +45,9 @@ def write_dfsu(filename: str | Path, data: Dataset) -> None: """ filename = str(filename) - if not data.is_equidistant: - raise ValueError("Non-equidistant time axis is not supported.") + # TODO warnings that this is not universally supported + # if not data.is_equidistant: + # raise ValueError("Non-equidistant time axis is not supported.") dt = data.timestep n_time_steps = len(data.time) @@ -71,7 +74,22 @@ def write_dfsu(filename: str | Path, data: Dataset) -> None: factory = DfsFactory() proj = factory.CreateProjection(geometry.projection_string) builder.SetProjection(proj) - builder.SetTimeInfo(data.time[0], dt) + # builder.SetTimeInfo(data.time[0], dt) + + if data.is_equidistant: + if len(data.time) == 1: + dt = data.timestep + else: + dt = (data.time[1] - data.time[0]).total_seconds() + + temporal_axis = factory.CreateTemporalEqCalendarAxis( + TimeStepUnit.SECOND, data.time[0], 0, dt + ) + else: + temporal_axis = factory.CreateTemporalNonEqCalendarAxis( + TimeStepUnit.SECOND, data.time[0] + ) + builder.SetTemporalAxis(timeAxis=temporal_axis) builder.SetZUnit(eumUnit.eumUmeter) if dfsu_filetype != DfsuFileType.Dfsu2D: @@ -84,6 +102,11 @@ def write_dfsu(filename: str | Path, data: Dataset) -> None: builder.ApplicationVersion = __dfs_version__ dfs = builder.CreateFile(filename) + if data.is_equidistant: + t_rel = np.zeros(data.n_timesteps) + else: + t_rel = (data.time - data.time[0]).total_seconds() + for i in range(n_time_steps): if geometry.is_layered: if "time" in data.dims: @@ -91,14 +114,14 @@ def write_dfsu(filename: str | Path, data: Dataset) -> None: zn = data._zn[i] else: zn = data._zn - dfs.WriteItemTimeStepNext(0, zn.astype(np.float32)) + dfs.WriteItemTimeStepNext(t_rel[i], zn.astype(np.float32)) for da in data: if "time" in data.dims: d = da.to_numpy()[i, :] else: d = da.to_numpy() d[np.isnan(d)] = data.deletevalue - dfs.WriteItemTimeStepNext(0, d.astype(np.float32)) + dfs.WriteItemTimeStepNext(t_rel[i], d.astype(np.float32)) dfs.Close() @@ -117,7 +140,8 @@ def _validate_elements_and_geometry_sel(elements: Any, **kwargs: Any) -> None: class _DfsuInfo: filename: str type: DfsuFileType - time: pd.DatetimeIndex + start_time: datetime + time: pd.DatetimeIndex # Not true if non-equidistant timestep: float items: list[ItemInfo] deletevalue: float @@ -132,11 +156,15 @@ def _get_dfsu_info(filename: str | Path) -> _DfsuInfo: type = DfsuFileType(dfs.DfsuFileType) deletevalue = dfs.DeleteValueFloat freq = pd.Timedelta(seconds=dfs.TimeStepInSeconds) - time = pd.date_range( - start=dfs.StartDateTime, - periods=dfs.NumberOfTimeSteps, - freq=freq, - ) + + if dfs.FileInfo.TimeAxis.TimeAxisType == TimeAxisType.CalendarNonEquidistant: + time = None + else: + time = pd.date_range( + start=dfs.StartDateTime, + periods=dfs.NumberOfTimeSteps, + freq=freq, + ) timestep = dfs.TimeStepInSeconds items = _get_item_info(dfs.ItemInfo) dfs.Close() @@ -146,6 +174,7 @@ def _get_dfsu_info(filename: str | Path) -> _DfsuInfo: time=time, timestep=timestep, items=items, + start_time=dfs.FileInfo.TimeAxis.StartDateTime, deletevalue=deletevalue, ) @@ -266,6 +295,7 @@ def __init__(self, filename: str | Path) -> None: self._type = info.type self._deletevalue = info.deletevalue self._time = info.time + self._start_time = info.start_time self._timestep = info.timestep self._items = info.items self._geometry = self._read_geometry(self._filename) @@ -310,13 +340,14 @@ def items(self) -> list[ItemInfo]: return self._items @property - def start_time(self) -> pd.Timestamp: + def start_time(self) -> datetime: """File start time""" - return self._time[0] + return self._start_time @property def n_timesteps(self) -> int: """Number of time steps""" + # TODO non-equidistant return len(self._time) @property @@ -331,6 +362,10 @@ def end_time(self) -> pd.Timestamp: @property def time(self) -> pd.DatetimeIndex: + if self._time is None: + raise ValueError( + "Non-equidistant time axis is not supported without first reading the data." + ) return self._time @staticmethod @@ -429,6 +464,8 @@ def read( shape: Tuple[int, ...] + t_seconds = np.zeros(len(time_steps)) + n_steps = len(time_steps) shape = ( (n_elems,) @@ -440,12 +477,10 @@ def read( data: np.ndarray = np.ndarray(shape=shape, dtype=dtype) data_list.append(data) - time = self.time - for i in trange(n_steps, disable=not self.show_progress): it = time_steps[i] for item in range(n_items): - dfs, d = _read_item_time_step( + dfs, d, t = _read_item_time_step( dfs=dfs, filename=self._filename, time=time, @@ -457,6 +492,7 @@ def read( error_bad_data=error_bad_data, fill_bad_data_value=fill_bad_data_value, ) + t_seconds[i] = t if elements is not None: d = d[elements] @@ -466,7 +502,7 @@ def read( else: data_list[item][i] = d - time = self.time[time_steps] + time = pd.to_datetime(t_seconds, unit="s", origin=self.start_time) dfs.Close() diff --git a/mikeio/dfsu/_layered.py b/mikeio/dfsu/_layered.py index 2519913cd..49dedb93d 100644 --- a/mikeio/dfsu/_layered.py +++ b/mikeio/dfsu/_layered.py @@ -302,7 +302,7 @@ def read( for i in trange(n_steps, disable=not self.show_progress): it = time_steps[i] for item in range(n_items): - dfs, d = _read_item_time_step( + dfs, d, t = _read_item_time_step( dfs=dfs, filename=self._filename, time=time, diff --git a/tests/test_dfsu.py b/tests/test_dfsu.py index f3092f907..311438ac7 100644 --- a/tests/test_dfsu.py +++ b/tests/test_dfsu.py @@ -587,13 +587,17 @@ def test_write_from_dfsu_2_time_steps(tmp_path): assert dfs.end_time != newdfs.end_time -def test_write_non_equidistant_is_not_possible(tmp_path): +def test_write_non_equidistant_is_possible(tmp_path): sourcefilename = "tests/testdata/HD2D.dfsu" fp = tmp_path / "simple.dfsu" ds = mikeio.read(sourcefilename, time=[0, 1, 3]) + assert not ds.is_equidistant - with pytest.raises(ValueError): - ds.to_dfs(fp) + ds.to_dfs(fp) + + ds2 = mikeio.read(fp) + + assert all(ds.time == ds2.time) def test_temporal_resample_by_reading_selected_timesteps(tmp_path): @@ -959,14 +963,3 @@ def test_interp_like_fm_dataset(): dsi = ds.interp_like(geometry) assert isinstance(dsi, Dataset) assert isinstance(dsi.geometry, GeometryFM2D) - - -def test_writing_non_equdistant_dfsu_is_not_possible(tmp_path): - ds = mikeio.read("tests/testdata/wind_north_sea.dfsu") - dss = ds.isel(time=[0, 2, 3]) - assert not dss.is_equidistant - - # TODO which exception should be raised, when trying to write non-equidistant dfsu? This operation is not supported - with pytest.raises(ValueError, match="equidistant"): - fp = tmp_path / "not_gonna_work.dfsu" - dss.to_dfs(fp) From 0cb90366cc7feb50c2b3dd324f3a34db0d280f3d Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Mon, 17 Jun 2024 14:21:54 +0200 Subject: [PATCH 2/8] Layered neq --- mikeio/dfsu/_layered.py | 14 +++++++++----- tests/test_dfsu_layered.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/mikeio/dfsu/_layered.py b/mikeio/dfsu/_layered.py index 49dedb93d..9ac18e73d 100644 --- a/mikeio/dfsu/_layered.py +++ b/mikeio/dfsu/_layered.py @@ -45,6 +45,7 @@ def __init__(self, filename: str | Path) -> None: self._type = info.type self._deletevalue = info.deletevalue self._time = info.time + self._start_time = info.start_time self._timestep = info.timestep self._geometry = self._read_geometry(self._filename) # 3d files have a zn item @@ -96,7 +97,7 @@ def items(self) -> list[ItemInfo]: @property def start_time(self) -> pd.Timestamp: """File start time""" - return self._time[0] + return self._start_time @property def n_timesteps(self) -> int: @@ -115,6 +116,10 @@ def end_time(self) -> pd.Timestamp: @property def time(self) -> pd.DatetimeIndex: + if self._time is None: + raise ValueError( + "Non-equidistant time axis is not supported without first reading the data." + ) return self._time @property @@ -283,7 +288,7 @@ def read( deletevalue = self.deletevalue data_list = [] - + t_seconds = np.zeros(len(time_steps)) n_steps = len(time_steps) item0_is_node_based = False for item in range(n_items): @@ -297,8 +302,6 @@ def read( if single_time_selected and not keepdims: data = data[0] - time = self.time - for i in trange(n_steps, disable=not self.show_progress): it = time_steps[i] for item in range(n_items): @@ -314,6 +317,7 @@ def read( error_bad_data=error_bad_data, fill_bad_data_value=fill_bad_data_value, ) + t_seconds[i] = t if elements is not None: if item == 0 and item0_is_node_based: @@ -326,7 +330,7 @@ def read( else: data_list[item][i] = d - time = self.time[time_steps] + time = pd.to_datetime(t_seconds, unit="s", origin=self.start_time) dfs.Close() diff --git a/tests/test_dfsu_layered.py b/tests/test_dfsu_layered.py index 5eaf93c1c..e21b477a1 100644 --- a/tests/test_dfsu_layered.py +++ b/tests/test_dfsu_layered.py @@ -1,4 +1,5 @@ import numpy as np +import pandas as pd import pytest import mikeio from mikeio import Mesh @@ -625,3 +626,34 @@ def test_read_elements_3d(): ds2 = mikeio.read("tests/testdata/oresund_sigma_z.dfsu", elements=[10, 0]) assert ds2.geometry.element_coordinates[1][0] == pytest.approx(354020.46382194717) assert ds2.Salinity.to_numpy()[0, 1] == pytest.approx(23.18906021118164) + + +# def test_write_non_equidistant_is_possible(tmp_path): +# sourcefilename = "tests/testdata/HD2D.dfsu" +# fp = tmp_path / "simple.dfsu" +# ds = mikeio.read(sourcefilename, time=[0, 1, 3]) +# assert not ds.is_equidistant + +# ds.to_dfs(fp) + +# ds2 = mikeio.read(fp) + +# assert all(ds.time == ds2.time) + + +def test_write_3d_non_equidistant(tmp_path): + sourcefilename = "tests/testdata/basin_3d.dfsu" + fp = tmp_path / "simple.dfsu" + ds = mikeio.read(sourcefilename) + + # manipulate time + ds.time = pd.DatetimeIndex(["2000-01-01", "2000-01-02", "2000-01-10"]) + + assert not ds.is_equidistant + + ds.to_dfs(fp) + + ds2 = mikeio.read(fp) + + assert all(ds.time == ds2.time) + assert not ds2.is_equidistant From 03bdf17348271b047fe676153313c8793d8228da Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Tue, 18 Jun 2024 13:44:17 +0200 Subject: [PATCH 3/8] Support .end_time for neq, but not .time for dfs objects. --- mikeio/dfsu/_dfsu.py | 45 +++++++++++++++++++++----------------- mikeio/dfsu/_layered.py | 28 +++++++++++++++++------- mikeio/dfsu/_spectral.py | 29 +++++++++++++++++++----- tests/test_dfsu.py | 11 ++++++++++ tests/test_dfsu_layered.py | 22 ++++++++----------- 5 files changed, 88 insertions(+), 47 deletions(-) diff --git a/mikeio/dfsu/_dfsu.py b/mikeio/dfsu/_dfsu.py index 095fc3acd..2aa9eb45e 100644 --- a/mikeio/dfsu/_dfsu.py +++ b/mikeio/dfsu/_dfsu.py @@ -1,6 +1,7 @@ from __future__ import annotations from dataclasses import dataclass from datetime import datetime +from functools import cached_property from pathlib import Path from typing import Any, Literal, Sequence, Tuple @@ -141,8 +142,9 @@ class _DfsuInfo: filename: str type: DfsuFileType start_time: datetime - time: pd.DatetimeIndex # Not true if non-equidistant + equidistant: bool timestep: float + n_timesteps: int items: list[ItemInfo] deletevalue: float @@ -155,24 +157,17 @@ def _get_dfsu_info(filename: str | Path) -> _DfsuInfo: dfs = DfsuFile.Open(filename) type = DfsuFileType(dfs.DfsuFileType) deletevalue = dfs.DeleteValueFloat - freq = pd.Timedelta(seconds=dfs.TimeStepInSeconds) - if dfs.FileInfo.TimeAxis.TimeAxisType == TimeAxisType.CalendarNonEquidistant: - time = None - else: - time = pd.date_range( - start=dfs.StartDateTime, - periods=dfs.NumberOfTimeSteps, - freq=freq, - ) timestep = dfs.TimeStepInSeconds items = _get_item_info(dfs.ItemInfo) + equidistant = dfs.FileInfo.TimeAxis.TimeAxisType == TimeAxisType.CalendarEquidistant dfs.Close() return _DfsuInfo( filename=filename, type=type, - time=time, timestep=timestep, + equidistant=equidistant, + n_timesteps=dfs.NumberOfTimeSteps, items=items, start_time=dfs.FileInfo.TimeAxis.StartDateTime, deletevalue=deletevalue, @@ -294,9 +289,10 @@ def __init__(self, filename: str | Path) -> None: self._filename = info.filename self._type = info.type self._deletevalue = info.deletevalue - self._time = info.time + self._equidistant = info.equidistant self._start_time = info.start_time self._timestep = info.timestep + self._n_timesteps = info.n_timesteps self._items = info.items self._geometry = self._read_geometry(self._filename) @@ -347,8 +343,7 @@ def start_time(self) -> datetime: @property def n_timesteps(self) -> int: """Number of time steps""" - # TODO non-equidistant - return len(self._time) + return self._n_timesteps @property def timestep(self) -> float: @@ -358,15 +353,25 @@ def timestep(self) -> float: @property def end_time(self) -> pd.Timestamp: """File end time""" - return self._time[-1] + if self._equidistant: + return self.time[-1] + else: + # read the last timestep + ds = self.read(items=0, time=-1) + return ds.time[-1] - @property + @cached_property def time(self) -> pd.DatetimeIndex: - if self._time is None: - raise ValueError( - "Non-equidistant time axis is not supported without first reading the data." + if self._equidistant: + return pd.date_range( + start=self.start_time, + periods=self.n_timesteps, + freq=f"{self.timestep}S", + ) + else: + raise NotImplementedError( + "Non-equidistant time axis. Read the data to get time." ) - return self._time @staticmethod def _read_geometry(filename: str) -> GeometryFM2D: diff --git a/mikeio/dfsu/_layered.py b/mikeio/dfsu/_layered.py index 9ac18e73d..b4292b7cf 100644 --- a/mikeio/dfsu/_layered.py +++ b/mikeio/dfsu/_layered.py @@ -1,4 +1,5 @@ from __future__ import annotations +from functools import cached_property from pathlib import Path from typing import Any, Sequence, Tuple, TYPE_CHECKING @@ -44,9 +45,10 @@ def __init__(self, filename: str | Path) -> None: self._filename = info.filename self._type = info.type self._deletevalue = info.deletevalue - self._time = info.time + self._equidistant = info.equidistant self._start_time = info.start_time self._timestep = info.timestep + self._n_timesteps = info.n_timesteps self._geometry = self._read_geometry(self._filename) # 3d files have a zn item self._items = self._read_items(self._filename) @@ -102,7 +104,7 @@ def start_time(self) -> pd.Timestamp: @property def n_timesteps(self) -> int: """Number of time steps""" - return len(self._time) + return self._n_timesteps @property def timestep(self) -> float: @@ -112,15 +114,25 @@ def timestep(self) -> float: @property def end_time(self) -> pd.Timestamp: """File end time""" - return self._time[-1] + if self._equidistant: + return self.time[-1] + else: + # read the last timestep + ds = self.read(items=0, time=-1) + return ds.time[-1] - @property + @cached_property def time(self) -> pd.DatetimeIndex: - if self._time is None: - raise ValueError( - "Non-equidistant time axis is not supported without first reading the data." + if self._equidistant: + return pd.date_range( + start=self.start_time, + periods=self.n_timesteps, + freq=f"{self.timestep}S", + ) + else: + raise NotImplementedError( + "Non-equidistant time axis. Read the data to get time." ) - return self._time @property def geometry(self) -> GeometryFM3D | GeometryFMVerticalProfile: diff --git a/mikeio/dfsu/_spectral.py b/mikeio/dfsu/_spectral.py index 128e7db3f..68f624553 100644 --- a/mikeio/dfsu/_spectral.py +++ b/mikeio/dfsu/_spectral.py @@ -1,4 +1,5 @@ from __future__ import annotations +from functools import cached_property from typing import Sequence, Sized, Tuple, Any from pathlib import Path @@ -32,8 +33,10 @@ def __init__(self, filename: str | Path) -> None: self._filename = info.filename self._type = info.type self._deletevalue = info.deletevalue - self._time = info.time + self._equidistant = info.equidistant + self._start_time = info.start_time self._timestep = info.timestep + self._n_timesteps = info.n_timesteps self._items = info.items self._geometry = self._read_geometry(self._filename) @@ -90,12 +93,12 @@ def items(self) -> list[ItemInfo]: @property def start_time(self) -> pd.Timestamp: """File start time""" - return self._time[0] + return self._start_time @property def n_timesteps(self) -> int: """Number of time steps""" - return len(self._time) + return self._n_timesteps @property def timestep(self) -> float: @@ -105,11 +108,25 @@ def timestep(self) -> float: @property def end_time(self) -> pd.Timestamp: """File end time""" - return self._time[-1] + if self._equidistant: + return self.time[-1] + else: + # read the last timestep + ds = self.read(items=0, time=-1) + return ds.time[-1] - @property + @cached_property def time(self) -> pd.DatetimeIndex: - return self._time + if self._equidistant: + return pd.date_range( + start=self.start_time, + periods=self.n_timesteps, + freq=f"{self.timestep}S", + ) + else: + raise NotImplementedError( + "Non-equidistant time axis. Read the data to get time." + ) @staticmethod def _read_geometry( diff --git a/tests/test_dfsu.py b/tests/test_dfsu.py index 311438ac7..bb58df4f1 100644 --- a/tests/test_dfsu.py +++ b/tests/test_dfsu.py @@ -599,6 +599,17 @@ def test_write_non_equidistant_is_possible(tmp_path): assert all(ds.time == ds2.time) + dfs = mikeio.open(fp) + + dfs = mikeio.open(fp) + + # it is not possible to get all time without reading the entire file + with pytest.raises(NotImplementedError): + dfs.time + + # but getting the end time is not that expensive + assert dfs.end_time == ds.time[-1] + def test_temporal_resample_by_reading_selected_timesteps(tmp_path): sourcefilename = "tests/testdata/HD2D.dfsu" diff --git a/tests/test_dfsu_layered.py b/tests/test_dfsu_layered.py index e21b477a1..af7dbddc3 100644 --- a/tests/test_dfsu_layered.py +++ b/tests/test_dfsu_layered.py @@ -628,19 +628,6 @@ def test_read_elements_3d(): assert ds2.Salinity.to_numpy()[0, 1] == pytest.approx(23.18906021118164) -# def test_write_non_equidistant_is_possible(tmp_path): -# sourcefilename = "tests/testdata/HD2D.dfsu" -# fp = tmp_path / "simple.dfsu" -# ds = mikeio.read(sourcefilename, time=[0, 1, 3]) -# assert not ds.is_equidistant - -# ds.to_dfs(fp) - -# ds2 = mikeio.read(fp) - -# assert all(ds.time == ds2.time) - - def test_write_3d_non_equidistant(tmp_path): sourcefilename = "tests/testdata/basin_3d.dfsu" fp = tmp_path / "simple.dfsu" @@ -657,3 +644,12 @@ def test_write_3d_non_equidistant(tmp_path): assert all(ds.time == ds2.time) assert not ds2.is_equidistant + + dfs = mikeio.open(fp) + + # it is not possible to get all time without reading the entire file + with pytest.raises(NotImplementedError): + dfs.time + + # but getting the end time is not that expensive + assert dfs.end_time == pd.Timestamp("2000-01-10") From cf681739747999f29e99c2e05a9cf7b79e410cc9 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Tue, 25 Jun 2024 14:07:41 +0200 Subject: [PATCH 4/8] Clean --- mikeio/dfsu/_dfsu.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/mikeio/dfsu/_dfsu.py b/mikeio/dfsu/_dfsu.py index 2aa9eb45e..d25a9b765 100644 --- a/mikeio/dfsu/_dfsu.py +++ b/mikeio/dfsu/_dfsu.py @@ -46,11 +46,6 @@ def write_dfsu(filename: str | Path, data: Dataset) -> None: """ filename = str(filename) - # TODO warnings that this is not universally supported - # if not data.is_equidistant: - # raise ValueError("Non-equidistant time axis is not supported.") - - dt = data.timestep n_time_steps = len(data.time) geometry = data.geometry @@ -75,22 +70,16 @@ def write_dfsu(filename: str | Path, data: Dataset) -> None: factory = DfsFactory() proj = factory.CreateProjection(geometry.projection_string) builder.SetProjection(proj) - # builder.SetTimeInfo(data.time[0], dt) if data.is_equidistant: - if len(data.time) == 1: - dt = data.timestep - else: - dt = (data.time[1] - data.time[0]).total_seconds() - temporal_axis = factory.CreateTemporalEqCalendarAxis( - TimeStepUnit.SECOND, data.time[0], 0, dt + TimeStepUnit.SECOND, data.time[0], 0, data.timestep ) else: temporal_axis = factory.CreateTemporalNonEqCalendarAxis( TimeStepUnit.SECOND, data.time[0] ) - builder.SetTemporalAxis(timeAxis=temporal_axis) + builder.SetTemporalAxis(temporal_axis) builder.SetZUnit(eumUnit.eumUmeter) if dfsu_filetype != DfsuFileType.Dfsu2D: @@ -469,7 +458,7 @@ def read( shape: Tuple[int, ...] - t_seconds = np.zeros(len(time_steps)) + t_rel = np.zeros(len(time_steps)) n_steps = len(time_steps) shape = ( @@ -497,7 +486,7 @@ def read( error_bad_data=error_bad_data, fill_bad_data_value=fill_bad_data_value, ) - t_seconds[i] = t + t_rel[i] = t if elements is not None: d = d[elements] @@ -507,7 +496,7 @@ def read( else: data_list[item][i] = d - time = pd.to_datetime(t_seconds, unit="s", origin=self.start_time) + time = pd.to_datetime(t_rel, unit="s", origin=self.start_time) dfs.Close() From f941e826d5c6d0592c2779fe2849f286ed4901ba Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Mon, 5 Aug 2024 11:48:23 +0200 Subject: [PATCH 5/8] Type checks use is --- mikeio/pfs/_pfssection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mikeio/pfs/_pfssection.py b/mikeio/pfs/_pfssection.py index f95be0cc4..f3b4cbbbc 100644 --- a/mikeio/pfs/_pfssection.py +++ b/mikeio/pfs/_pfssection.py @@ -275,7 +275,7 @@ def _yield_deep_dict(keys: Sequence[str], val: Any) -> Any: def _param_match(parampat: Any, v: Any, case: bool) -> Any: if parampat is None: return False - if type(v) != type(parampat): + if type(v) is not type(parampat): return False if isinstance(v, str): vv = str(v) if case else str(v).lower() From 36c39dceb7d69b4de2831ea737cccad7f09118a1 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Mon, 5 Aug 2024 12:06:15 +0200 Subject: [PATCH 6/8] Updates due to apppend functionality --- mikeio/dfsu/_dfsu.py | 13 +++++++------ mikeio/dfsu/_layered.py | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mikeio/dfsu/_dfsu.py b/mikeio/dfsu/_dfsu.py index 5c036a145..13fc38555 100644 --- a/mikeio/dfsu/_dfsu.py +++ b/mikeio/dfsu/_dfsu.py @@ -47,8 +47,6 @@ def write_dfsu(filename: str | Path, data: Dataset) -> None: """ filename = str(filename) - n_time_steps = len(data.time) - geometry = data.geometry dfsu_filetype = DfsuFileType.Dfsu2D @@ -101,6 +99,11 @@ def write_dfsu_data(dfs: DfsuFile, ds: Dataset, is_layered: bool) -> None: n_time_steps = len(ds.time) data = ds + if data.is_equidistant: + t_rel = np.zeros(data.n_timesteps) + else: + t_rel = (data.time - data.time[0]).total_seconds() + for i in range(n_time_steps): if is_layered: if "time" in data.dims: @@ -353,7 +356,7 @@ def end_time(self) -> pd.Timestamp: ds = self.read(items=0, time=-1) return ds.time[-1] - @cached_property + @property def time(self) -> pd.DatetimeIndex: if self._equidistant: return pd.date_range( @@ -526,7 +529,6 @@ def read( dt=self.timestep, ) - def append(self, ds: Dataset, validate: bool = True) -> None: """ Append data to an existing dfsu file @@ -551,7 +553,7 @@ def append(self, ds: Dataset, validate: bool = True) -> None: dfs = DfsFileFactory.DfsuFileOpenAppend(str(self._filename), parameters=None) write_dfsu_data(dfs=dfs, ds=ds, is_layered=False) info = _get_dfsu_info(self._filename) - self._time = info.time + self._n_timesteps = info.n_timesteps def _parse_geometry_sel( self, @@ -559,7 +561,6 @@ def _parse_geometry_sel( x: float | None, y: float | None, ) -> np.ndarray | None: - """Parse geometry selection Parameters diff --git a/mikeio/dfsu/_layered.py b/mikeio/dfsu/_layered.py index 9d1545cc7..cd510a3fd 100644 --- a/mikeio/dfsu/_layered.py +++ b/mikeio/dfsu/_layered.py @@ -123,7 +123,7 @@ def end_time(self) -> pd.Timestamp: ds = self.read(items=0, time=-1) return ds.time[-1] - @cached_property + @property def time(self) -> pd.DatetimeIndex: if self._equidistant: return pd.date_range( @@ -405,7 +405,7 @@ def append(self, ds: Dataset, validate: bool = True) -> None: dfs = DfsFileFactory.DfsuFileOpenAppend(str(self._filename), parameters=None) write_dfsu_data(dfs=dfs, ds=ds, is_layered=ds.geometry.is_layered) info = _get_dfsu_info(self._filename) - self._time = info.time + self._n_timesteps = info.n_timesteps class Dfsu2DV(DfsuLayered): From 2a93e8f9cd48df1a88d54e0b67b38852d8c0f3e0 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Mon, 5 Aug 2024 12:07:13 +0200 Subject: [PATCH 7/8] No cache --- mikeio/dfsu/_dfsu.py | 1 - mikeio/dfsu/_layered.py | 1 - 2 files changed, 2 deletions(-) diff --git a/mikeio/dfsu/_dfsu.py b/mikeio/dfsu/_dfsu.py index 13fc38555..8a0fcf656 100644 --- a/mikeio/dfsu/_dfsu.py +++ b/mikeio/dfsu/_dfsu.py @@ -1,7 +1,6 @@ from __future__ import annotations from dataclasses import dataclass from datetime import datetime -from functools import cached_property from pathlib import Path from typing import Any, Literal, Sequence, Tuple diff --git a/mikeio/dfsu/_layered.py b/mikeio/dfsu/_layered.py index cd510a3fd..604a987f9 100644 --- a/mikeio/dfsu/_layered.py +++ b/mikeio/dfsu/_layered.py @@ -1,5 +1,4 @@ from __future__ import annotations -from functools import cached_property from pathlib import Path from typing import Any, Sequence, Tuple, TYPE_CHECKING From 6fce5b05856fe2e30e2db26146136cc87e491cd0 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Mon, 5 Aug 2024 12:11:59 +0200 Subject: [PATCH 8/8] Consistent --- mikeio/dfsu/_spectral.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mikeio/dfsu/_spectral.py b/mikeio/dfsu/_spectral.py index 68f624553..aeb5d3c6d 100644 --- a/mikeio/dfsu/_spectral.py +++ b/mikeio/dfsu/_spectral.py @@ -1,5 +1,4 @@ from __future__ import annotations -from functools import cached_property from typing import Sequence, Sized, Tuple, Any from pathlib import Path @@ -115,7 +114,7 @@ def end_time(self) -> pd.Timestamp: ds = self.read(items=0, time=-1) return ds.time[-1] - @cached_property + @property def time(self) -> pd.DatetimeIndex: if self._equidistant: return pd.date_range(