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

T3D header not correct #356

Merged
merged 48 commits into from
Oct 14, 2022
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d47cf4e
Added ZSurf option for verticalPositionType.
tim-vd-aardweg Sep 29, 2022
1b48656
Added a child class that inherrits from QuantityUnitPair that also ha…
tim-vd-aardweg Sep 29, 2022
7282233
Fixed the aliases to adhere to the 2D3D manual.
tim-vd-aardweg Sep 29, 2022
0e70a0a
Reverted Time Interpolation back to the original timeInterpolation. U…
tim-vd-aardweg Sep 29, 2022
1a1163e
Added support for the timeInterpolation keyword when creating a T3D f…
tim-vd-aardweg Sep 29, 2022
096b2b8
Removed the QuantityUnitPositionPair class again, in favour of a bett…
tim-vd-aardweg Sep 30, 2022
4164906
Fixed failing tests as a result of making the QuantityUnitPair a Base…
tim-vd-aardweg Oct 3, 2022
105c9e8
Added new test for the QuantityUnitPair.
tim-vd-aardweg Oct 3, 2022
1c05236
Added documentation to the VerticalInterpolation enum.
tim-vd-aardweg Oct 3, 2022
15499a9
Added documentation to the TimeInterpolation enum.
tim-vd-aardweg Oct 3, 2022
91ddeef
Added documentation to the QuantityUnitPair class.
tim-vd-aardweg Oct 3, 2022
ea938a7
Updated documentation of ForcingBase to the new documentation style.
tim-vd-aardweg Oct 3, 2022
5d691e0
Added documenation to the T3D class.
tim-vd-aardweg Oct 3, 2022
c37deeb
Added validation: Ensure the first QuantityUnitPair is for `time`.
tim-vd-aardweg Oct 3, 2022
7d7a6cb
Intermediate commit of the new validation.
tim-vd-aardweg Oct 3, 2022
b417154
Intermediate commit of the new validation.
tim-vd-aardweg Oct 3, 2022
c50c71f
Fixed an error in the validator and fixed formatting.
tim-vd-aardweg Oct 3, 2022
735a3c8
Fixed some testcases and added a couple of testcases.
tim-vd-aardweg Oct 3, 2022
1a10026
Added additional testcases
tim-vd-aardweg Oct 4, 2022
bbde372
Added additional testcases
tim-vd-aardweg Oct 4, 2022
8d4d6f3
Added additional test cases.
tim-vd-aardweg Oct 4, 2022
fdebf78
Updated reference data for T3D
tim-vd-aardweg Oct 4, 2022
3a44fe3
Updated the remaining documentation for this file.
tim-vd-aardweg Oct 4, 2022
82b386a
autoformat: isort & black
tim-vd-aardweg Oct 4, 2022
3e24262
Fixed a bug with the `number_of_verticalpositions`. Implemented revie…
tim-vd-aardweg Oct 6, 2022
2cb275a
Use `Vertical Position` instead of `verticalpositionindex`
tim-vd-aardweg Oct 7, 2022
83c11f0
Updated keywords for the T3D header and added a test to ensure loadin…
tim-vd-aardweg Oct 7, 2022
87e78bd
Fixed code smells.
tim-vd-aardweg Oct 10, 2022
0694680
Fixed code smells.
tim-vd-aardweg Oct 10, 2022
f140d80
Add failing testcase that should pass
tim-vd-aardweg Oct 11, 2022
93e4f4b
Added a way to support the 'Vertical Position Specification' keyword …
tim-vd-aardweg Oct 11, 2022
aee2917
autoformat: isort & black
tim-vd-aardweg Oct 11, 2022
72c8d49
Merge branch 'main' into fix/317_t3d_header_not_correct
priscavdsluis Oct 12, 2022
0e1048a
Updated the keywords to the new keywords.
tim-vd-aardweg Oct 13, 2022
864ce5b
Merge branch 'fix/317_t3d_header_not_correct' of https://github.com/D…
tim-vd-aardweg Oct 13, 2022
5489c15
Updated error message to be more clear
tim-vd-aardweg Oct 13, 2022
88fb9fc
Added backwards compatibility for the keywords in the .bc file that u…
tim-vd-aardweg Oct 13, 2022
64b972c
autoformat: isort & black
tim-vd-aardweg Oct 13, 2022
f64a069
Added backwards compatibility for the `Time Interpolation` keyword in…
tim-vd-aardweg Oct 13, 2022
ae1d1a4
Added error when the user forgot to specify the vertpositions field t…
tim-vd-aardweg Oct 13, 2022
2e6bc19
Fixed the default enum values for timeinterpolation and verticalinter…
tim-vd-aardweg Oct 13, 2022
68a6cc5
Removed the check for this unit as it causes problems on the build se…
tim-vd-aardweg Oct 13, 2022
49c1575
Removed the ForcingBackwardsCompatibilityHelper and instead added a g…
tim-vd-aardweg Oct 13, 2022
dd24bf0
autoformat: isort & black
tim-vd-aardweg Oct 13, 2022
9f55295
Update hydrolib/core/io/bc/models.py
arthurvd Oct 13, 2022
5a9339a
Implemented review comments.
tim-vd-aardweg Oct 14, 2022
6c436bd
Merge branch 'main' into fix/317_t3d_header_not_correct
priscavdsluis Oct 14, 2022
0061257
autoformat: isort & black
priscavdsluis Oct 14, 2022
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
248 changes: 227 additions & 21 deletions hydrolib/core/io/bc/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,19 @@
import logging
from enum import Enum
from pathlib import Path
from typing import Callable, List, Literal, NamedTuple, Optional, Set, Union
from typing import Callable, List, Literal, Optional, Set, Union

from pydantic import Extra
from pydantic.class_validators import root_validator, validator
from pydantic.fields import Field

from hydrolib.core.io.ini.io_models import Property, Section
from hydrolib.core.io.ini.models import DataBlockINIBasedModel, INIGeneral, INIModel
from hydrolib.core.io.ini.models import (
BaseModel,
DataBlockINIBasedModel,
INIGeneral,
INIModel,
)
from hydrolib.core.io.ini.parser import Parser, ParserConfig
from hydrolib.core.io.ini.serializer import SerializerConfig, write_ini
from hydrolib.core.io.ini.util import (
Expand All @@ -32,9 +37,16 @@


class VerticalInterpolation(str, Enum):
"""Enum class containing the valid values for the vertical interpolation."""

linear = "linear"
"""str: Linear interpolation between vertical positions."""

log = "log"
"""str: Logarithmic interpolation between vertical positions (e.g. vertical velocity profiles)."""

block = "block"
"""str: Equal to the value at the directly lower specified vertical position."""


class VerticalPositionType(str, Enum):
priscavdsluis marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -49,22 +61,42 @@ class VerticalPositionType(str, Enum):
z_datum = "ZDatum"
"""str: z-coordinate with respect to the reference level of the model."""

z_surf = "ZSurf"
"""str: Absolute distance from the free surface downward."""


class TimeInterpolation(str, Enum):
"""Enum class containing the valid values for the time interpolation."""

linear = "linear"
"""str: Linear interpolation between times."""

block_from = "blockFrom"
"""str: Equal to that at the start of the time interval (latest specified time value)."""

block_to = "blockTo"
"""str: Equal to that at the end of the time interval (upcoming specified time value)."""


class QuantityUnitPair(NamedTuple):
"""A .bc file header lines tuple containing a quantity name and its unit."""
class QuantityUnitPair(BaseModel):
"""A .bc file header lines tuple containing a quantity name, its unit and optionally a vertical position index."""

quantity: str
"""str: Name of quantity."""

unit: str
"""str: Unit of quantity."""

verticalpositionindex: Optional[int]
"""int (optional): This is a (one-based) index into the verticalposition-specification, assigning a vertical position to the quantity (t3D-blocks only)."""

def _to_properties(self):
yield Property(key="quantity", value=self.quantity)
yield Property(key="unit", value=self.unit)
if self.verticalpositionindex is not None:
yield Property(
key="verticalpositionindex", value=self.verticalpositionindex
)


class ForcingBase(DataBlockINIBasedModel):
Expand All @@ -74,18 +106,17 @@ class ForcingBase(DataBlockINIBasedModel):
Typically subclassed, for the specific types of forcing data, e.g, TimeSeries.
This model is for example referenced under a
[ForcingModel][hydrolib.core.io.bc.models.ForcingModel]`.forcing[..]`.

Attributes:
name (str): Unique identifier that identifies the location for this forcing data.
function (str): Function type of the data in the actual datablock.
quantityunitpair (List[QuantityUnitPair]): list of header line tuples for one or
more quantities + their unit. Describes the columns in the actual datablock.
"""

_header: Literal["Forcing"] = "Forcing"
name: str = Field(alias="name")
"""str: Unique identifier that identifies the location for this forcing data."""

function: str = Field(alias="function")
"""str: Function type of the data in the actual datablock."""

quantityunitpair: List[QuantityUnitPair]
"""List[QuantityUnitPair]: List of header lines for one or more quantities and their unit. Describes the columns in the actual datablock."""

def _exclude_fields(self) -> Set:
return {"quantityunitpair"}.union(super()._exclude_fields())
Expand Down Expand Up @@ -113,7 +144,9 @@ def _validate_quantityunitpair(cls, values):
raise ValueError("unit is not provided")

if isinstance(quantities, str) and isinstance(units, str):
values[quantityunitpairkey] = [(quantities, units)]
values[quantityunitpairkey] = [
QuantityUnitPair(quantity=quantities, unit=units)
]
return values

if isinstance(quantities, list) and isinstance(units, list):
Expand All @@ -123,7 +156,8 @@ def _validate_quantityunitpair(cls, values):
)

values[quantityunitpairkey] = [
(quantity, unit) for quantity, unit in zip(quantities, units)
QuantityUnitPair(quantity=quantity, unit=unit)
for quantity, unit in zip(quantities, units)
]
return values

Expand Down Expand Up @@ -179,9 +213,15 @@ class TimeSeries(ForcingBase):
"""Subclass for a .bc file [Forcing] block with timeseries data."""

function: Literal["timeseries"] = "timeseries"

timeinterpolation: TimeInterpolation = Field(alias="timeInterpolation")
"""TimeInterpolation: The type of time interpolation."""

offset: float = Field(0.0, alias="offset")
"""float: All values in the table are increased by the offset (after multiplication by factor). Defaults to 0.0."""

factor: float = Field(1.0, alias="factor")
"""float: All values in the table are multiplied with the factor. Defaults to 1.0."""

_timeinterpolation_validator = get_enum_validator(
"timeinterpolation", enum=TimeInterpolation
Expand All @@ -192,14 +232,18 @@ class Harmonic(ForcingBase):
"""Subclass for a .bc file [Forcing] block with harmonic components data."""

function: Literal["harmonic"] = "harmonic"

factor: float = Field(1.0, alias="factor")
"""float: All values in the table are multiplied with the factor. Defaults to 1.0."""


class Astronomic(ForcingBase):
"""Subclass for a .bc file [Forcing] block with astronomic components data."""

function: Literal["astronomic"] = "astronomic"

factor: float = Field(1.0, alias="factor")
"""float: All values in the table are multiplied with the factor. Defaults to 1.0."""


class HarmonicCorrection(ForcingBase):
Expand All @@ -220,11 +264,22 @@ class T3D(ForcingBase):
function: Literal["t3d"] = "t3d"

offset: float = Field(0.0, alias="offset")
"""float: All values in the table are increased by the offset (after multiplication by factor). Defaults to 0.0."""

factor: float = Field(1.0, alias="factor")
"""float: All values in the table are multiplied with the factor. Defaults to 1.0."""

verticalpositions: List[float] = Field(alias="Vertical Position Specification")
"""List[float]: The specification of the vertical positions."""

verticalpositions: List[float] = Field(alias="verticalPositions")
verticalinterpolation: VerticalInterpolation = Field(alias="verticalInterpolation")
verticalpositiontype: VerticalPositionType = Field(alias="verticalPositionType")
verticalinterpolation: VerticalInterpolation = Field(alias="Vertical Interpolation")
arthurvd marked this conversation as resolved.
Show resolved Hide resolved
"""VerticalInterpolation: The type of vertical interpolation."""

verticalpositiontype: VerticalPositionType = Field(alias="Vertical Position Type")
"""VerticalPositionType: The vertical position type."""
arthurvd marked this conversation as resolved.
Show resolved Hide resolved

timeinterpolation: TimeInterpolation = Field(alias="timeInterpolation")
arthurvd marked this conversation as resolved.
Show resolved Hide resolved
"""TimeInterpolation: The type of time interpolation."""

_split_to_list = get_split_string_on_delimiter_validator(
"verticalpositions",
Expand All @@ -236,6 +291,152 @@ class T3D(ForcingBase):
_verticalpositiontype_validator = get_enum_validator(
"verticalpositiontype", enum=VerticalPositionType
)
_timeinterpolation_validator = get_enum_validator(
"timeinterpolation", enum=TimeInterpolation
)

@root_validator(pre=True)
def _validate_quantityunitpair(cls, values):
super()._validate_quantityunitpair(values)

quantityunitpairkey = "quantityunitpair"
arthurvd marked this conversation as resolved.
Show resolved Hide resolved
quantityunitpairs = values[quantityunitpairkey]

T3D._validate_that_first_unit_is_time_and_has_no_verticalpositionindex(
arthurvd marked this conversation as resolved.
Show resolved Hide resolved
quantityunitpairs
)

verticalpositionindexes = values.get("verticalpositionindex")
number_of_verticalpositions = len(values["verticalpositions"])

if verticalpositionindexes is None:
T3D._validate_that_all_non_time_quantityunitpairs_have_valid_verticalpositionindex(
quantityunitpairs, number_of_verticalpositions
)
return values

T3D._validate_verticalpositionindexes_and_update_quantityunitpairs(
arthurvd marked this conversation as resolved.
Show resolved Hide resolved
verticalpositionindexes,
number_of_verticalpositions,
quantityunitpairs,
)

return values

@staticmethod
def _validate_that_first_unit_is_time_and_has_no_verticalpositionindex(
quantityunitpairs: List[QuantityUnitPair],
) -> None:
if quantityunitpairs[0].quantity.lower() != "time":
raise ValueError("First quantity should be `time`")
if quantityunitpairs[0].verticalpositionindex is not None:
raise ValueError("`time` quantity cannot have vertical position index")

@staticmethod
def _validate_that_all_non_time_quantityunitpairs_have_valid_verticalpositionindex(
quantityunitpairs: List[QuantityUnitPair], maximum_verticalpositionindex: int
) -> None:
for quantityunitpair in quantityunitpairs:
priscavdsluis marked this conversation as resolved.
Show resolved Hide resolved
quantity = quantityunitpair.quantity.lower()
verticalpositionindex = quantityunitpair.verticalpositionindex

if quantity == "time":
continue
if not T3D._is_valid_verticalpositionindex(
verticalpositionindex, maximum_verticalpositionindex
):
raise ValueError(
f"Vertical position index should be between 1 and {maximum_verticalpositionindex}"
)

@staticmethod
def _validate_verticalpositionindexes_and_update_quantityunitpairs(
verticalpositionindexes: List[int],
number_of_verticalpositions: int,
quantityunitpairs: List[QuantityUnitPair],
) -> None:
if verticalpositionindexes is None:
raise ValueError("verticalpositionindex is not provided")

number_of_quantityunitpairs = len(quantityunitpairs)
number_of_verticalpositionindexes = len(verticalpositionindexes)

if number_of_verticalpositionindexes == number_of_quantityunitpairs:
T3D._validate_verticalpositionindexes_and_add_them_to_quantityunitpairs(
verticalpositionindexes[1:],
arthurvd marked this conversation as resolved.
Show resolved Hide resolved
number_of_verticalpositions,
quantityunitpairs[1:],
)
return

if number_of_verticalpositionindexes == number_of_quantityunitpairs - 1:
T3D._validate_verticalpositionindexes_and_add_them_to_quantityunitpairs(
verticalpositionindexes,
number_of_verticalpositions,
quantityunitpairs[1:],
)
return

raise ValueError(
"Number of vertical positions should be equal to the number of units or equal to the number of units - 1"
)

@staticmethod
def _validate_verticalpositionindexes_and_add_them_to_quantityunitpairs(
verticalpositionindexes: List[int],
number_of_verticalpositions: int,
quantityunitpairs: List[QuantityUnitPair],
) -> None:
T3D._validate_that_verticalpositionindexes_are_valid(
verticalpositionindexes, number_of_verticalpositions
)

T3D._add_verticalpositionindex_to_quantityunitpairs(
quantityunitpairs, verticalpositionindexes
)

@staticmethod
def _validate_that_verticalpositionindexes_are_valid(
verticalpositionindexes: List[int], number_of_vertical_positions: int
) -> None:
for verticalpositionindexstring in verticalpositionindexes:
verticalpositionindex = (
int(verticalpositionindexstring)
if verticalpositionindexstring
else None
)
if not T3D._is_valid_verticalpositionindex(
verticalpositionindex, number_of_vertical_positions
):
raise ValueError(
f"Vertical position index should be between 1 and {number_of_vertical_positions}"
)

@staticmethod
def _is_valid_verticalpositionindex(
verticalpositionindex: int, number_of_vertical_positions: int
) -> bool:
one_based_index_offset = 1
arthurvd marked this conversation as resolved.
Show resolved Hide resolved

return (
verticalpositionindex is not None
and verticalpositionindex >= one_based_index_offset
and verticalpositionindex <= number_of_vertical_positions
)

@staticmethod
def _add_verticalpositionindex_to_quantityunitpairs(
quantityunitpairs: List[QuantityUnitPair], verticalpositionindexes: List[int]
) -> None:
if len(quantityunitpairs) != len(verticalpositionindexes):
raise ValueError(
"Number of quantityunitpairs and verticalpositionindexes should be equal"
)

for (quantityunitpair, verticalpositionindex) in zip(
quantityunitpairs, verticalpositionindexes
):
quantityunitpair.verticalpositionindex = verticalpositionindex


class QHTable(ForcingBase):
Expand All @@ -250,13 +451,18 @@ class Constant(ForcingBase):
function: Literal["constant"] = "constant"

offset: float = Field(0.0, alias="offset")
"""float: All values in the table are increased by the offset (after multiplication by factor). Defaults to 0.0."""

factor: float = Field(1.0, alias="factor")
"""float: All values in the table are multiplied with the factor. Defaults to 1.0."""


class ForcingGeneral(INIGeneral):
"""`[General]` section with .bc file metadata."""

fileversion: str = Field("1.01", alias="fileVersion")
"""str: The file version."""

filetype: Literal["boundConds"] = Field("boundConds", alias="fileType")


Expand All @@ -266,16 +472,16 @@ class ForcingModel(INIModel):

This model is for example referenced under a
[ExtModel][hydrolib.core.io.ext.models.ExtModel]`.boundary[..].forcingfile[..]`.

Attributes:
general (ForcingGeneral): `[General]` block with file metadata.
forcing (List[ForcingBase]): List of `[Forcing]` blocks for all forcing
definitions in a single .bc file. Actual data is stored in
forcing[..].datablock from [hydrolib.core.io.ini.models.DataBlockINIBasedModel.datablock] or [hydrolib.core.io.ini.models.DataBlockINIBasedModel].
"""

general: ForcingGeneral = ForcingGeneral()
"""ForcingGeneral: `[General]` block with file metadata."""

forcing: List[ForcingBase] = []
"""List[ForcingBase]: List of `[Forcing]` blocks for all forcing
definitions in a single .bc file. Actual data is stored in
forcing[..].datablock from [hydrolib.core.io.ini.models.DataBlockINIBasedModel.datablock]
or [hydrolib.core.io.ini.models.DataBlockINIBasedModel]."""
priscavdsluis marked this conversation as resolved.
Show resolved Hide resolved

_split_to_list = make_list_validator("forcing")

Expand Down
Loading