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

feat: sources sinks converter (UNST-8528) #721

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
203 commits
Select commit Hold shift + click to select a range
3f33e92
test initializing the `ExtOldModel`
MAfarrag Nov 15, 2024
2ad0ca8
remove unused imported modules
MAfarrag Nov 17, 2024
3227ab6
remove unused imported modules
MAfarrag Nov 17, 2024
3527c41
reformat the test_ext_old_to_new.py and test the printed messages
MAfarrag Nov 17, 2024
5d143b8
reformat the test_ext_old_to_new.py and test the printed messages
MAfarrag Nov 17, 2024
50e5475
add `InitialCondInterpolationMethod` class and test
MAfarrag Nov 17, 2024
546a58f
add `InitialCondFileType` class and test
MAfarrag Nov 17, 2024
de01cb7
fix initial condition tests
MAfarrag Nov 17, 2024
858c86e
create `test_meteo_forcing_file_type` to test `MeteoForcingFileType`
MAfarrag Nov 17, 2024
0e2b075
change tests to check for the parameter values not the parameter name
MAfarrag Nov 17, 2024
6411cd2
test `MeteoInterpolationMethod` in the `test_meteo_interpolation_meth…
MAfarrag Nov 17, 2024
4df1b16
reformat and test the Meteo class
MAfarrag Nov 17, 2024
b6ff6b1
clean tests that were moved to the TestMeteo test class
MAfarrag Nov 17, 2024
0efb1d1
move `test_missing_required_fields` to the separate `TestMeteo` class
MAfarrag Nov 17, 2024
77a4f78
solve the compare floating point values
MAfarrag Nov 17, 2024
a3b335b
rename the old `TestMeteo` to `TestExtModel`
MAfarrag Nov 17, 2024
1a17cd2
reformat the `TestExtModel`
MAfarrag Nov 17, 2024
ed71a0b
add the cases for time_series/boundary condition files as forcing fil…
MAfarrag Nov 18, 2024
16e4ee1
correct field names in the `InitialCondition` class
MAfarrag Nov 18, 2024
50e9ef2
test the `InitialConditions` class for all possible behaviours
MAfarrag Nov 18, 2024
f5a7834
fix issue in assigning mutable object (list) as a default
MAfarrag Nov 18, 2024
9af79d1
test `construct_filemodel_new_or_existing` function
MAfarrag Nov 18, 2024
fbad133
reformat the test_ext_old_to_new.py file to use fixtures instead of t…
MAfarrag Nov 18, 2024
9d516a8
import the used function directly at the top of the file do not use "…
MAfarrag Nov 18, 2024
f5af0fe
name test files by their respective modules not inner functions or cl…
MAfarrag Nov 18, 2024
ed30f2c
reformat test file
MAfarrag Nov 18, 2024
8403ebb
move check values to the conftest file
MAfarrag Nov 18, 2024
6c80dbc
test the `_read_ext_old_data` function
MAfarrag Nov 18, 2024
f99841e
test the `InitialConditionConverter` class
MAfarrag Nov 18, 2024
8e88a9b
reformat test
MAfarrag Nov 18, 2024
5fb2ce0
move the initial condition fields tests from the test_ext.py to the t…
MAfarrag Nov 20, 2024
a41f591
remove duplicated initial condition fields classes
MAfarrag Nov 20, 2024
714ef29
autoformat: isort & black
MAfarrag Nov 20, 2024
b2a9f28
move the InitialCondition tests to the tests_inifield.py
MAfarrag Nov 20, 2024
fcac5af
fix use mutable as default value
MAfarrag Nov 20, 2024
a78c1f4
create and test `InitialConditionConverter`
MAfarrag Nov 20, 2024
f18cbab
merge formated files by CI
MAfarrag Nov 20, 2024
6cdc8e0
autoformat: isort & black
MAfarrag Nov 20, 2024
86142e7
user snake case parameter names
MAfarrag Nov 21, 2024
a1ccf9c
integrate the Initial_Condition converter to the `ext_old_to_new`
MAfarrag Nov 21, 2024
b6f4e51
autoformat: isort & black
MAfarrag Nov 21, 2024
456a502
delete test results
MAfarrag Nov 21, 2024
b667ff4
Merge branch 'feature/621_extforce_converter-initial-condition-file' …
MAfarrag Nov 21, 2024
17753e7
fix test error
MAfarrag Nov 21, 2024
cbb2d16
clean
MAfarrag Nov 21, 2024
6d24267
autoformat: isort & black
MAfarrag Nov 25, 2024
92e453b
remove ignored tests
MAfarrag Nov 25, 2024
629a24c
use default_factory=list instead of [] to prevent using mutable objec…
MAfarrag Nov 25, 2024
1c15e87
use fixtures instead of variables declared in the `tests.utils` module
MAfarrag Nov 25, 2024
1c41a77
convert relative imports to absolute imports
MAfarrag Nov 25, 2024
04bf366
exclude python=3.12.5 bcause of black warning about memory safety issue.
MAfarrag Nov 25, 2024
e3f9746
create separate group for the docs dependencies
MAfarrag Nov 26, 2024
a523f58
create `ExternalForcingConverter` class to include all converter func…
MAfarrag Nov 26, 2024
0ada611
create class method for the `ExternalForcingConverter` from the old e…
MAfarrag Nov 26, 2024
bcd5694
autoformat: isort & black
MAfarrag Nov 26, 2024
e23fa07
convert the `ext_old_to_new` function into a method called `update` i…
MAfarrag Nov 26, 2024
b70fd6f
Merge branch 'feature/unst-8490-621_extforce_converter-boundary-condi…
MAfarrag Nov 26, 2024
64dc374
first step to move out the `construct_filemodel_new_or_existing` call…
MAfarrag Nov 28, 2024
846342d
create setter and getter properties for each model, and make abstract…
MAfarrag Nov 28, 2024
199241e
reformat the converter tests
MAfarrag Nov 28, 2024
78eebc3
separate tests for the update, save, and add default paths to the mod…
MAfarrag Nov 29, 2024
8ffb639
fix the error of using mutables as a default value.
MAfarrag Dec 2, 2024
6539245
create separate test class for the update method in the converter
MAfarrag Dec 2, 2024
67fd3fa
test files for the update meteo test
MAfarrag Dec 2, 2024
215c05e
rename the meteo_converter into converters and merge the boundary cov…
MAfarrag Dec 2, 2024
6c5590e
move the initial_condition_converter to the converters module
MAfarrag Dec 2, 2024
6dc8db7
move the initial_condition_converter to the converters module
MAfarrag Dec 2, 2024
f7f2536
autoformat: isort & black
MAfarrag Dec 2, 2024
ed20778
add the `BoundaryConditionConverter` to the converter_factory.py
MAfarrag Dec 2, 2024
33db751
fix the bug in using mutable as default value
MAfarrag Dec 2, 2024
4162b0e
create a `converters.BoundaryConditionConverter` class
MAfarrag Dec 2, 2024
efa5fec
fix using mutables as default value
MAfarrag Dec 3, 2024
07a5cb8
fix using mutables as default value
MAfarrag Dec 3, 2024
0c0d2a6
use the getattr to fetch attributes from the object
MAfarrag Dec 3, 2024
6d19ab4
create subfolders in the test directory
MAfarrag Dec 3, 2024
908fefa
get rid of relative paths
MAfarrag Dec 3, 2024
69d18cc
add init file to the polyline test dir
MAfarrag Dec 3, 2024
f2b4c57
add tests for different polyline cases (basic case, and with label)
MAfarrag Dec 3, 2024
5322456
add tests for different polyline cases (basic case, and with label)
MAfarrag Dec 3, 2024
cd9389b
split the test_ext.py to a separate testing modules
MAfarrag Dec 4, 2024
a01f8fd
fix error in Boundary object instantiation in the `BoundaryConditionC…
MAfarrag Dec 4, 2024
0a17562
test the `ext.models.boundary` with an existing polyline
MAfarrag Dec 4, 2024
9ff2e91
test the `ExternalForcingConverter.update` with only boundary conditi…
MAfarrag Dec 4, 2024
4c5bfb2
replace float point comparison with np.isclose
MAfarrag Dec 4, 2024
13dead8
replace float point comparison with np.isclose
MAfarrag Dec 4, 2024
5587390
remove unused imported function
MAfarrag Dec 5, 2024
5211d96
add a pull request template
MAfarrag Dec 5, 2024
1726a38
correct test folder name from extold to ext
MAfarrag Dec 5, 2024
d0b183e
optimize the `ext.models.Boundary.forcing` property to have one retur…
MAfarrag Dec 5, 2024
d68e75d
test the `ExtOldParametersQuantity` class
MAfarrag Dec 6, 2024
5db0ceb
correct assert statement is `test_ext_old_initial_condition_quantity`…
MAfarrag Dec 6, 2024
99d47d4
move the TestModels.TestLateral from thr test_ext.py to a separate te…
MAfarrag Dec 6, 2024
8e5314f
correct the test file description
MAfarrag Dec 6, 2024
f4246d2
reformat the test_boundary.py file
MAfarrag Dec 6, 2024
b3c92b1
clean the test file from unused imported functions
MAfarrag Dec 6, 2024
eef3351
remove unused class `ext.models.InitialConditions`
MAfarrag Dec 9, 2024
de14bf0
add reference to the initial condition documentation in the `InitialF…
MAfarrag Dec 9, 2024
d0ee415
add `ParametersConverter` class and test
MAfarrag Dec 9, 2024
656b112
merge the `BaseConverter` class to the `converters` module
MAfarrag Dec 9, 2024
1e3552a
test `MeteoConverter`
MAfarrag Dec 9, 2024
31a0a99
merge `converter_factory` module to the `converters` module
MAfarrag Dec 9, 2024
7387036
merge the `enum_converters` module to `utils`
MAfarrag Dec 9, 2024
c4e5892
merge the `__contains__` to the `ConverterFactory` class
MAfarrag Dec 9, 2024
540f4e4
abstract duplicate lines in the parameter and initial condition conve…
MAfarrag Dec 9, 2024
06c3411
add try, except clause to avoid PermissionError in Windows
MAfarrag Dec 9, 2024
0ae3726
update MetroForcingFileType class with updates types
MAfarrag Dec 9, 2024
15553f1
silence dimr and serializer tests
MAfarrag Dec 10, 2024
a2280fe
autoformat: isort & black
MAfarrag Dec 10, 2024
3f7a75c
fix error in using mutables as default value
MAfarrag Dec 10, 2024
dad5451
remove the `initialsalinitytopuse` quantity from the `ExtOldInitialCo…
MAfarrag Dec 10, 2024
2ac138a
add missing initial condition quantities and add `__missing__` method…
MAfarrag Dec 10, 2024
1fd561a
adjust the `tracer` quantity to `initialtracer`
MAfarrag Dec 10, 2024
20175ab
add list of old initial conditions quantities that comes with a suffi…
MAfarrag Dec 10, 2024
638a8cc
Merge branch 'feature/621_extforce_converter-initial-condition-file' …
MAfarrag Dec 10, 2024
938ac05
update docstring
MAfarrag Dec 10, 2024
7f30c43
remove duplicate `InitialConditions` class (duplicate if `InitialField`)
MAfarrag Dec 10, 2024
8cd2665
fix floating point comparison
MAfarrag Dec 10, 2024
9b33474
fix floating point comparison
MAfarrag Dec 10, 2024
fcde9f1
remove un-used parameter `postfix and fix sonar warnings`
MAfarrag Dec 10, 2024
c4f9645
merge changes from the `origin/feature/621_extforce_converter-initial…
MAfarrag Dec 11, 2024
f563c97
remove the `locationtype` and convert the value of the extrapolation …
MAfarrag Dec 11, 2024
457473d
the `ExternalForcingConverter` class can be instantiated by `ExtOldMo…
MAfarrag Dec 11, 2024
bec5e13
rename boundary polylines files to `boundary-polyline-` prefix
MAfarrag Dec 11, 2024
1d4b3c8
update converter command line
MAfarrag Dec 11, 2024
25bd8c5
merge changes from base branch
MAfarrag Dec 11, 2024
56930f9
add `ExtOldSourcesSinks` class for the source and sinks quantities
MAfarrag Dec 12, 2024
5092c75
test `ExtOldSourcesSinks` class for the source and sinks quantities
MAfarrag Dec 12, 2024
4c02cd9
merge changes from the base branch
MAfarrag Dec 12, 2024
b366a14
return the correct return type hint
MAfarrag Dec 12, 2024
7556303
Merge branch 'feature/unst-8490-621_extforce_converter-boundary-condi…
MAfarrag Dec 12, 2024
75988fb
fix using mutables as default value, and use absolute imports
MAfarrag Dec 12, 2024
726f01b
abstract the conversion of the interpolation related info in a separa…
MAfarrag Dec 16, 2024
8779cb7
add docstring and test for the `convert_interpolation_data` method
MAfarrag Dec 16, 2024
6d6dad0
add reference to documentation for the tim model
MAfarrag Dec 16, 2024
f17b36c
remove unused fields in the `TimParser`
MAfarrag Dec 16, 2024
55ad838
move the `convert_interpolation_data` from the converters.py to the u…
MAfarrag Dec 16, 2024
ffa8a95
move the `convert_initial_cond_param_dict` from the converters.py to …
MAfarrag Dec 16, 2024
51e5fdb
add docs for the `UnknownKeywordErrorManager`
MAfarrag Dec 18, 2024
b811686
refactor the `UnknownKeywordErrorManager`
MAfarrag Dec 18, 2024
fe27aee
use absolute imports
MAfarrag Dec 18, 2024
297d919
add a class method `_exclude_from_validation` to enable bypassing the…
MAfarrag Dec 18, 2024
16fa483
create a `SourceSink` class and tests
MAfarrag Dec 18, 2024
8394df1
refactor the `TestSourceSink`
MAfarrag Dec 18, 2024
11d654c
change the type of discharge in the `SourceSink` class to float or li…
MAfarrag Dec 18, 2024
6be8c44
add `SourceSinkConverter` and tests
MAfarrag Dec 19, 2024
304cd39
test discharge constant value in the `SourceSink`
MAfarrag Dec 19, 2024
33a467e
add utility function for locate the temperature and salinity in the q…
MAfarrag Dec 19, 2024
cf3a3a4
add missing test files for the `test_converters.py::TestSourceSinkCon…
MAfarrag Dec 23, 2024
4cdcdd4
the `find_temperature_salinity_in_quantities` no returns a dictionary…
MAfarrag Dec 23, 2024
33e6cd2
add default test case for the SourceSinkConverter
MAfarrag Dec 23, 2024
3768ecb
reformat the polyfile tests
MAfarrag Dec 23, 2024
3e8ed26
add testcase for polyfile with 2*3
MAfarrag Dec 23, 2024
a6a99c9
add testcase for the polyfile with 2*5 dimensions
MAfarrag Dec 23, 2024
c1a70ae
add `SourceSinkConverter.get_z_sources_sinks` method and tests
MAfarrag Dec 23, 2024
b50b823
add `initialsedfrac` quantity name to the `SourcesSink` model
MAfarrag Dec 23, 2024
3e477e1
test `SourceSinkConverter` with a 5 column polyline file
MAfarrag Dec 23, 2024
4f8aeb5
test `SourceSinkConverter.parse_tim_model` with quantities_list and t…
MAfarrag Dec 23, 2024
5934485
test `SourceSinkConverter.parse_tim_model` with default case
MAfarrag Dec 23, 2024
8847cc0
move the `SourceSinkConverter` tests to a separate test file
MAfarrag Dec 23, 2024
0396cb4
add missing test files
MAfarrag Dec 23, 2024
3575aba
add all cases of the temperature/salinity in the external forcing fil…
MAfarrag Dec 23, 2024
966f8d0
add no temperature no salinity to the SourceSink converter
MAfarrag Dec 23, 2024
6b530a5
make the `discharge` attribute not optional
MAfarrag Dec 23, 2024
0c2b34e
replace equality be `is` to check None
MAfarrag Dec 24, 2024
545a532
move test_extold.py to a sub dir
MAfarrag Dec 24, 2024
d2bceaa
move TestExtForcing to test_ext_forcing.py
MAfarrag Dec 24, 2024
7c92028
reformat tests
MAfarrag Dec 24, 2024
ebbf589
add `quantities` property to the `ExtOldModel`
MAfarrag Dec 24, 2024
f822a9b
add `x` and `y` properties to the `PolyFile` class
MAfarrag Dec 24, 2024
f06fc1f
move the `get_z_sources_sinks` method from the `SourceSinkConverter` …
MAfarrag Dec 24, 2024
12663a7
remove the `discharge_salinity_temperature_sorsin` from the old meteo…
MAfarrag Dec 24, 2024
314c8a1
add `root_dir` as a property to the `SourceSinkConverter`
MAfarrag Dec 24, 2024
6253b9b
add `source_sink` to the attributes of the `ExtModel` as a list of `S…
MAfarrag Dec 24, 2024
2128eee
add `SourceSinkConverter` and default test case
MAfarrag Dec 24, 2024
2e4ca9e
add `SourceSinkConverter` and default test case
MAfarrag Dec 24, 2024
8ee1bf6
reformat the `test_main_converter.py::TestUpdate` and create separate…
MAfarrag Dec 25, 2024
e29c1d9
rename test
MAfarrag Dec 25, 2024
845950f
add an example
MAfarrag Dec 25, 2024
17819da
add documentations
MAfarrag Dec 25, 2024
edaad5f
use absolute imports
MAfarrag Dec 25, 2024
5656560
change the backup function to keep the whole name with the extension …
MAfarrag Dec 25, 2024
348c8d9
clean
MAfarrag Dec 25, 2024
7267c60
convert the `ext_old_to_new_from_mdu` function to a method `from_mdu`…
MAfarrag Dec 25, 2024
c9465d9
add a `verbose` property the `ExternalForcingConverter` class
MAfarrag Dec 26, 2024
c296fa3
move logging to separate method
MAfarrag Dec 26, 2024
eea08a1
refactor the `update` method
MAfarrag Dec 26, 2024
bfe81f6
add lines to parse the temperature and salinity in the mdu file
MAfarrag Jan 6, 2025
de31910
update packages
MAfarrag Jan 6, 2025
f086597
compare the temperature and salinity between the mdu vs ext vs tim file
MAfarrag Jan 7, 2025
3367e61
merge base branch
MAfarrag Jan 8, 2025
b1600f4
more descriptive variable name
MAfarrag Jan 9, 2025
c372121
add missing function parameter docstring
MAfarrag Jan 9, 2025
604af44
correct docstrings and type hints
MAfarrag Jan 9, 2025
d246cd1
add example and test for the `PolyFile.get_z_source_sinks`method in c…
MAfarrag Jan 9, 2025
89b55a0
use `ForcingData` class as a type for the discharge, temperature, and…
MAfarrag Jan 9, 2025
a074a44
remove `interpolationmethod` and `operand` from the attributes of the…
MAfarrag Jan 9, 2025
861616e
the order is `salinity` then `temperature` in the tim file
MAfarrag Jan 9, 2025
ec978a7
rename the `kwargs` to a descriptive name `mdu_quantities`
MAfarrag Jan 9, 2025
d9ae51d
test the combination of mdu and ext file
MAfarrag Jan 10, 2025
2c7e5f3
abstract the lines for merging the ext_quantities and the mdu_quantities
MAfarrag Jan 10, 2025
27cc78d
update docstring
MAfarrag Jan 10, 2025
856fa6c
correct type hint
MAfarrag Jan 13, 2025
b2eda43
Revert "use `ForcingData` class as a type for the discharge, temperat…
MAfarrag Jan 15, 2025
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
72 changes: 70 additions & 2 deletions hydrolib/core/dflowfm/ext/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Dict, List, Literal, Optional, Union
from typing import Dict, List, Literal, Optional, Set, Union

from pydantic.v1 import Field, root_validator, validator
from strenum import StrEnum
Expand All @@ -24,6 +24,13 @@
from hydrolib.core.dflowfm.tim.models import TimModel
from hydrolib.core.utils import str_is_empty_or_none

SOURCE_SINKS_QUANTITIES_VALID_PREFIXES = (
"initialtracer",
"tracerbnd",
"sedfracbnd",
"initialsedfrac",
)


class Boundary(INIBasedModel):
"""
Expand Down Expand Up @@ -181,6 +188,65 @@ def validate_location_type(cls, v: str) -> str:
return v


class SourceSink(INIBasedModel):
"""
A `[SourceSink]` block for use inside an external forcings file,
i.e., a [ExtModel][hydrolib.core.dflowfm.ext.models.SourceSink].

All lowercased attributes match with the source-sink input as described in
[UM Sec.C.5.2.4](https://content.oss.deltares.nl/delft3dfm1d2d/D-Flow_FM_User_Manual_1D2D.pdf#subsection.C.5.2.4).
"""

_header: Literal["SourceSink"] = "SourceSink"
id: str = Field(alias="id")
name: str = Field("", alias="name")
locationfile: DiskOnlyFileModel = Field(
default_factory=lambda: DiskOnlyFileModel(None), alias="locationFile"
)

numcoordinates: Optional[int] = Field(alias="numCoordinates")
xcoordinates: Optional[List[float]] = Field(alias="xCoordinates")
ycoordinates: Optional[List[float]] = Field(alias="yCoordinates")

zsource: Optional[Union[float, List[float]]] = Field(alias="zSource")
zsink: Optional[Union[float, List[float]]] = Field(alias="zSink")
discharge: Union[float, List[float]] = Field(alias="discharge")
MAfarrag marked this conversation as resolved.
Show resolved Hide resolved
area: Optional[float] = Field(alias="Area")

salinitydelta: Optional[Union[List[float], float]] = Field(alias="SalinityDelta")
temperaturedelta: Optional[Union[List[float], float]] = Field(
MAfarrag marked this conversation as resolved.
Show resolved Hide resolved
alias="TemperatureDelta"
)

@classmethod
def _exclude_from_validation(cls, input_data: Optional[dict] = None) -> Set:
fields = cls.__fields__
unknown_keywords = [
key
for key in input_data.keys()
if key not in fields
and key.startswith(SOURCE_SINKS_QUANTITIES_VALID_PREFIXES)
]
return set(unknown_keywords)

class Config:
"""
Config class to tell Pydantic to accept fields not explicitly declared in the model.
"""

# Allow dynamic fields
extra = "allow"

def __init__(self, **data):
super().__init__(**data)
# Add dynamic attributes for fields starting with 'tracer'
for key, value in data.items():
if isinstance(key, str) and key.startswith(
SOURCE_SINKS_QUANTITIES_VALID_PREFIXES
):
setattr(self, key, value)


class MeteoForcingFileType(StrEnum):
"""
Enum class containing the valid values for the forcingFileType
Expand Down Expand Up @@ -318,7 +384,7 @@ def is_intermediate_link(self) -> bool:


class ExtGeneral(INIGeneral):
"""The external forcing file's `[General]` section with file meta data."""
"""The external forcing file's `[General]` section with file meta-data."""

_header: Literal["General"] = "General"
fileversion: str = Field("2.01", alias="fileVersion")
Expand All @@ -335,12 +401,14 @@ class ExtModel(INIModel):
general (ExtGeneral): `[General]` block with file metadata.
boundary (List[Boundary]): List of `[Boundary]` blocks for all boundary conditions.
lateral (List[Lateral]): List of `[Lateral]` blocks for all lateral discharges.
source_sink (List[SourceSink]): List of `[SourceSink]` blocks for all source/sink terms.
meteo (List[Meteo]): List of `[Meteo]` blocks for all meteorological forcings.
"""

general: ExtGeneral = ExtGeneral()
boundary: List[Boundary] = Field(default_factory=list)
lateral: List[Lateral] = Field(default_factory=list)
source_sink: List[SourceSink] = Field(default_factory=list)
meteo: List[Meteo] = Field(default_factory=list)
serializer_config: INISerializerConfig = INISerializerConfig(
section_indent=0, property_indent=0
Expand Down
13 changes: 11 additions & 2 deletions hydrolib/core/dflowfm/extold/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,6 @@ class ExtOldMeteoQuantity(StrEnum):
"""Long wave radiation"""
SolarRadiation = "solarradiation"
"""Solar radiation"""
DischargeSalinityTemperatureSorSin = "discharge_salinity_temperature_sorsin"
"""Discharge, salinity temperature source-sinks"""
NudgeSalinityTemperature = "nudge_salinity_temperature"
"""Nudging salinity and temperature"""
AirPressure = "airpressure"
Expand Down Expand Up @@ -302,6 +300,12 @@ def _missing_(cls, value):
)


class ExtOldSourcesSinks(StrEnum):
"""Source and sink quantities"""

DischargeSalinityTemperatureSorSin = "discharge_salinity_temperature_sorsin"


class ExtOldQuantity(StrEnum):
"""Enum class containing the valid values for the boundary conditions category
of the external forcings.
Expand Down Expand Up @@ -789,3 +793,8 @@ def _get_serializer(
@classmethod
def _get_parser(cls) -> Callable[[Path], Dict]:
return Parser.parse

@property
def quantities(self) -> List[str]:
"""List all the quantities in the external forcings file."""
return [forcing.quantity for forcing in self.forcing]
25 changes: 19 additions & 6 deletions hydrolib/core/dflowfm/ini/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,22 @@
ModelSaveSettings,
ParsableFileModel,
)

from ..ini.io_models import CommentBlock, Document, Property, Section
from .parser import Parser
from .serializer import (
from hydrolib.core.dflowfm.ini.io_models import (
CommentBlock,
Document,
Property,
Section,
)
from hydrolib.core.dflowfm.ini.parser import Parser
from hydrolib.core.dflowfm.ini.serializer import (
DataBlockINIBasedSerializerConfig,
INISerializerConfig,
write_ini,
)
from .util import UnknownKeywordErrorManager, make_list_validator
from hydrolib.core.dflowfm.ini.util import (
UnknownKeywordErrorManager,
make_list_validator,
)

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -121,12 +128,13 @@ class Config:
@root_validator(pre=True)
def _validate_unknown_keywords(cls, values):
unknown_keyword_error_manager = cls._get_unknown_keyword_error_manager()
do_not_validate = cls._exclude_from_validation(values)
if unknown_keyword_error_manager:
unknown_keyword_error_manager.raise_error_for_unknown_keywords(
values,
cls._header,
cls.__fields__,
cls._exclude_fields(),
cls._exclude_fields() | do_not_validate,
)
return values

Expand Down Expand Up @@ -181,6 +189,11 @@ def validate(cls: Type["INIBasedModel"], value: Any) -> "INIBasedModel":

return super().validate(value)

@classmethod
def _exclude_from_validation(cls, input_data: Optional = None) -> Set:
"""Fields that should not be checked when validating existing fields as they will be dynamically added."""
return set()

@classmethod
def _exclude_fields(cls) -> Set:
return {"comments", "datablock", "_header"}
Expand Down
58 changes: 42 additions & 16 deletions hydrolib/core/dflowfm/ini/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def validate_forbidden_fields(
return values

for field in field_names:
if values.get(field) != None:
if values.get(field) is not None:
raise ValueError(
f"{field} is forbidden when {conditional_field_name} {operator_str(comparison_func)} {conditional_value}"
)
Expand Down Expand Up @@ -228,7 +228,7 @@ def validate_required_fields(
return values

for field in field_names:
if values.get(field) == None:
if values.get(field) is None:
raise ValueError(
f"{field} should be provided when {conditional_field_name} {operator_str(comparison_func)} {conditional_value}"
)
Expand Down Expand Up @@ -654,28 +654,54 @@ def raise_error_for_unknown_keywords(
"""
unknown_keywords = self._get_all_unknown_keywords(data, fields, excluded_fields)

if len(unknown_keywords) == 0:
return

raise ValueError(
f"Unknown keywords are detected in section: '{section_header}', '{unknown_keywords}'"
)
if len(unknown_keywords) > 0:
raise ValueError(
f"Unknown keywords are detected in section: '{section_header}', '{unknown_keywords}'"
)

def _get_all_unknown_keywords(
self, data: Dict[str, Any], fields: Dict[str, ModelField], excluded_fields: Set
) -> List[str]:
"""
Get all unknown keywords in the data.

Args:
data: Dict[str, Any]: Input data containing all properties which are checked on unknown keywords.
fields: Dict[str, ModelField]: Known fields of the Model.
excluded_fields: Set[str]: Fields which should be excluded from the check for unknown keywords.

Returns:
List[str]: List of unknown keywords.
"""
list_of_unknown_keywords = []
for name in data:
if self._is_unknown_keyword(name, fields, excluded_fields):
list_of_unknown_keywords.append(name)
for keyword in data:
if self._is_unknown_keyword(keyword, fields, excluded_fields):
list_of_unknown_keywords.append(keyword)

return list_of_unknown_keywords

@staticmethod
def _is_unknown_keyword(
self, name: str, fields: Dict[str, ModelField], excluded_fields: Set
keyword: str, fields: Dict[str, ModelField], excluded_fields: Set
):
for model_field in fields.values():
if name == model_field.name or name == model_field.alias:
return False
"""
Check if the given field name equals to any of the model field names or aliases, if not, the function checks if
the field is not in the excluded_fields parameter.

Args:
keyword: str: Name of the field.
fields: Dict[str, ModelField]: Known fields of the Model.
excluded_fields: Set[str]: Fields which should be excluded from the check for unknown keywords.

Returns:
bool: True if the field is unknown (not a field name or alias and and not in the exclude list),
False otherwise
"""
exists = any(
keyword == model_field.name or keyword == model_field.alias
for model_field in fields.values()
)
# the field is not in the known fields, check if it should be excluded
unknown = not exists and keyword not in excluded_fields

return name not in excluded_fields
return unknown
85 changes: 82 additions & 3 deletions hydrolib/core/dflowfm/polyfile/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""models.py defines all classes and functions related to representing pol/pli(z) files.
"""

from typing import Callable, List, Optional, Sequence
from typing import Callable, List, Optional, Sequence, Tuple

from pydantic.v1 import Field

Expand Down Expand Up @@ -80,7 +80,30 @@ class PolyObject(BaseModel):


class PolyFile(ParsableFileModel):
"""Poly-file (.pol/.pli/.pliz) representation."""
"""
Poly-file (.pol/.pli/.pliz) representation.

Notes:
- The `has_z_values` attribute is used to determine if the PolyFile contains z-values.
- The `has_z_values` is false by default and should be set to true if the PolyFile path ends with `.pliz`.
- The `***.pliz` file should have a 2*3 structure, where the third column contains the z-values, otherwise
(the parser will give an error).
- If there is a label in the file, the parser will ignore the label and read the file as a normal polyline file.
```
tfl_01
2 2
0.00 1.00 #zee
0.00 2.00 #zee
```
- if the file is .pliz, and the dimensions are 2*5 the first three columns will be considered as x, y, z values
and the last two columns will be considered as data values.
```
L1
2 5
63.35 12.95 -4.20 -5.35 0
45.20 6.35 -3.00 -2.90 0
```
"""

has_z_values: bool = False
objects: Sequence[PolyObject] = Field(default_factory=list)
Expand All @@ -106,7 +129,63 @@ def _get_serializer(cls) -> Callable:

@classmethod
def _get_parser(cls) -> Callable:
# TODO Prevent circular dependency in Parser
# Prevent circular dependency in Parser
from .parser import read_polyfile

return read_polyfile

@property
def x(self) -> List[float]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this used for? You typically want to keep the individual PolyObjects separated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used in the SourceSinkConverter.convert to to get the xcoordinates

"""X-coordinates of all points in the PolyFile."""
return [point.x for obj in self.objects for point in obj.points]

@property
def y(self) -> List[float]:
"""Y-coordinates of all points in the PolyFile."""
return [point.y for obj in self.objects for point in obj.points]

def get_z_sources_sinks(self) -> Tuple[List[float], List[float]]:
"""
Get the z values of the source and sink points from the polyline file.

Returns:
z_source, z_sinkA: Tuple[List[float]]:
MAfarrag marked this conversation as resolved.
Show resolved Hide resolved
If the polyline has data (more than 3 columns), then both the z_source and z_sink will be a list of two values.
Otherwise, the z_source and the z_sink will be a single value each.

Note:
- calling this method on a polyline file that does not have z-values will return a list of None.

Examples:
in case the polyline has 3 columns:
>>> polyline = PolyFile("tests/data/input/source-sink/leftsor.pliz")
>>> z_source, z_sink = polyline.get_z_sources_sinks()
>>> print(z_source, z_sink)
[-3] [-4.2]

in case the polyline has more than 3 columns:
>>> polyline = PolyFile("tests/data/input/source-sink/leftsor-5-columns.pliz") #Doctest: +SKIP
>>> z_source, z_sink = polyline.get_z_sources_sinks()
>>> print(z_source, z_sink)
[-3, -2.9] [-4.2, -5.35]

in case the polyline does not have z-values:
>>> root_dir = "tests/data/input/dflowfm_individual_files/polylines"
>>> polyline = PolyFile(f"{root_dir}/boundary-polyline-no-z-no-label.pli")
>>> z_source, z_sink = polyline.get_z_sources_sinks()
>>> print(z_source, z_sink)
[None] [None]
"""
has_data = True if self.objects[0].points[0].data else False
MAfarrag marked this conversation as resolved.
Show resolved Hide resolved

z_source_sink = []
for elem in [0, -1]:
point = self.objects[0].points[elem]
if has_data:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also safeguard against not have_z

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a property has_z_value which can be used outside the method where the user can know if the polyline has a z value if yes the get_zsource_sink then can be used

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the get_z_sources_sinks will return a [None], [None] if the polyline does not have a z value as the get_z_sources_sinks just calls the point.z and for a polyline that does not have a z value the z value is assigned to None

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added documentation (Note in the PolyFile.get_zsources_sinks, also an examples) and a test tests/dflowfm/poolyfole/test_polyline_models.py::TestGetZSourcesSinks.test_get_z_sources_sinks_no_z_values

z_source_sink.append([point.z, point.data[0]])
else:
z_source_sink.append([point.z])

z_sink: list[float | None] = z_source_sink[0]
z_source: list[float | None] = z_source_sink[1]
return z_source, z_sink
Loading
Loading