From b00b314834e567bd9c9aa037e6b06de7823600e9 Mon Sep 17 00:00:00 2001 From: hofer_jn Date: Tue, 23 Jan 2024 10:05:11 +0100 Subject: [PATCH 1/6] TeamCity change in 'Ribasim / Linux' project: build features of 'Build ribasim_cli' build configuration were updated --- .../buildTypes/Ribasim_Linux_BuildRibasimCli.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.teamcity/Ribasim_Linux/buildTypes/Ribasim_Linux_BuildRibasimCli.xml b/.teamcity/Ribasim_Linux/buildTypes/Ribasim_Linux_BuildRibasimCli.xml index 2e30ad363..f203da184 100644 --- a/.teamcity/Ribasim_Linux/buildTypes/Ribasim_Linux_BuildRibasimCli.xml +++ b/.teamcity/Ribasim_Linux/buildTypes/Ribasim_Linux_BuildRibasimCli.xml @@ -90,12 +90,6 @@ pixi run build-ribasim-cli]]> - - - - - - From 9f6eb8c145f0fe3e71a9db8ec0aceb38ef69bf6a Mon Sep 17 00:00:00 2001 From: hofer_jn Date: Tue, 23 Jan 2024 10:05:56 +0100 Subject: [PATCH 2/6] TeamCity change in 'Ribasim / Linux' project: triggers of 'Test ribasim_api' build configuration were updated --- .../Ribasim_Linux/buildTypes/Ribasim_Linux_TestRibasimApi.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.teamcity/Ribasim_Linux/buildTypes/Ribasim_Linux_TestRibasimApi.xml b/.teamcity/Ribasim_Linux/buildTypes/Ribasim_Linux_TestRibasimApi.xml index 7849428d2..8fb65f23d 100644 --- a/.teamcity/Ribasim_Linux/buildTypes/Ribasim_Linux_TestRibasimApi.xml +++ b/.teamcity/Ribasim_Linux/buildTypes/Ribasim_Linux_TestRibasimApi.xml @@ -53,7 +53,7 @@ pixi run test-ribasim-api]]> - + From 839c0e69af7865a34edb4d033b4a1b5ed5438cd7 Mon Sep 17 00:00:00 2001 From: hofer_jn Date: Tue, 23 Jan 2024 10:06:26 +0100 Subject: [PATCH 3/6] TeamCity change in 'Ribasim / Linux' project: triggers of 'Test ribasim_api' build configuration were updated --- .../Ribasim_Linux/buildTypes/Ribasim_Linux_TestRibasimApi.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.teamcity/Ribasim_Linux/buildTypes/Ribasim_Linux_TestRibasimApi.xml b/.teamcity/Ribasim_Linux/buildTypes/Ribasim_Linux_TestRibasimApi.xml index 8fb65f23d..8ba314363 100644 --- a/.teamcity/Ribasim_Linux/buildTypes/Ribasim_Linux_TestRibasimApi.xml +++ b/.teamcity/Ribasim_Linux/buildTypes/Ribasim_Linux_TestRibasimApi.xml @@ -53,7 +53,7 @@ pixi run test-ribasim-api]]> - + From eb7f512d003bdc4810c9503bce18423d151aab65 Mon Sep 17 00:00:00 2001 From: Maarten Pronk <8655030+evetion@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:07:52 +0100 Subject: [PATCH 4/6] Refactor `sort_keys` implementation (#970) Fixes #956 While ideally we set the sort_keys on the Schema of a TableModel, or on the TableModel themselves, this is not directly possible AFAIK: - Schemas are autogenerated, and sort_keys is not something we can serialize or parse in a JSONSchema - The TableModels are generic (TableModel[T]), so specific sort_keys can't be hardcoded to a class. The previous approach was to hold the sort keys in a `Dict[table, sort_keys]` on a NodeModel (which holds multiple tables), and passing them on when save/sort was called from the Node. This PR improves on that approach by introducing a private _sort_keys on the TableModel, which is always set (from an empty list by default) on initialization from the NodeModel. Instead of keeping a separate Dict with sort_keys on a NodeModel, we now set them in the `Field` attribute `json_schema_extra` (the only customizable attribute of a Field) for each field. Each field of a NodeModel is now validated by default (also on assignment), and if a sort_keys is present for a fieldtype, it is set on the TableModel. Based on the initial exploration by @deltamarnix in https://github.com/Deltares/Ribasim/tree/feat/sort-keys-in-tablemodel --- python/ribasim/ribasim/config.py | 122 +++++++++++---------------- python/ribasim/ribasim/input_base.py | 31 +++++-- python/ribasim/tests/test_io.py | 10 ++- 3 files changed, 79 insertions(+), 84 deletions(-) diff --git a/python/ribasim/ribasim/config.py b/python/ribasim/ribasim/config.py index e992e811b..167f8fb64 100644 --- a/python/ribasim/ribasim/config.py +++ b/python/ribasim/ribasim/config.py @@ -79,154 +79,132 @@ class Logging(ChildModel): class Terminal(NodeModel): static: TableModel[TerminalStaticSchema] = Field( - default_factory=TableModel[TerminalStaticSchema] + default_factory=TableModel[TerminalStaticSchema], + json_schema_extra={"sort_keys": ["node_id"]}, ) - _sort_keys: dict[str, list[str]] = {"static": ["node_id"]} - class PidControl(NodeModel): static: TableModel[PidControlStaticSchema] = Field( - default_factory=TableModel[PidControlStaticSchema] + default_factory=TableModel[PidControlStaticSchema], + json_schema_extra={"sort_keys": ["node_id", "control_state"]}, ) time: TableModel[PidControlTimeSchema] = Field( - default_factory=TableModel[PidControlTimeSchema] + default_factory=TableModel[PidControlTimeSchema], + json_schema_extra={"sort_keys": ["node_id", "time"]}, ) - _sort_keys: dict[str, list[str]] = { - "static": ["node_id", "control_state"], - "time": ["node_id", "time"], - } - class LevelBoundary(NodeModel): static: TableModel[LevelBoundaryStaticSchema] = Field( - default_factory=TableModel[LevelBoundaryStaticSchema] + default_factory=TableModel[LevelBoundaryStaticSchema], + json_schema_extra={"sort_keys": ["node_id"]}, ) time: TableModel[LevelBoundaryTimeSchema] = Field( - default_factory=TableModel[LevelBoundaryTimeSchema] + default_factory=TableModel[LevelBoundaryTimeSchema], + json_schema_extra={"sort_keys": ["node_id", "time"]}, ) - _sort_keys: dict[str, list[str]] = { - "static": ["node_id"], - "time": ["node_id", "time"], - } - class Pump(NodeModel): static: TableModel[PumpStaticSchema] = Field( - default_factory=TableModel[PumpStaticSchema] + default_factory=TableModel[PumpStaticSchema], + json_schema_extra={"sort_keys": ["node_id", "control_state"]}, ) - _sort_keys: dict[str, list[str]] = {"static": ["node_id", "control_state"]} - class TabulatedRatingCurve(NodeModel): static: TableModel[TabulatedRatingCurveStaticSchema] = Field( - default_factory=TableModel[TabulatedRatingCurveStaticSchema] + default_factory=TableModel[TabulatedRatingCurveStaticSchema], + json_schema_extra={"sort_keys": ["node_id", "control_state", "level"]}, ) time: TableModel[TabulatedRatingCurveTimeSchema] = Field( - default_factory=TableModel[TabulatedRatingCurveTimeSchema] + default_factory=TableModel[TabulatedRatingCurveTimeSchema], + json_schema_extra={"sort_keys": ["node_id", "time", "level"]}, ) - _sort_keys: dict[str, list[str]] = { - "static": ["node_id", "control_state", "level"], - "time": ["node_id", "time", "level"], - } class User(NodeModel): static: TableModel[UserStaticSchema] = Field( - default_factory=TableModel[UserStaticSchema] + default_factory=TableModel[UserStaticSchema], + json_schema_extra={"sort_keys": ["node_id", "priority"]}, + ) + time: TableModel[UserTimeSchema] = Field( + default_factory=TableModel[UserTimeSchema], + json_schema_extra={"sort_keys": ["node_id", "priority", "time"]}, ) - time: TableModel[UserTimeSchema] = Field(default_factory=TableModel[UserTimeSchema]) - - _sort_keys: dict[str, list[str]] = { - "static": ["node_id", "priority"], - "time": ["node_id", "priority", "time"], - } class FlowBoundary(NodeModel): static: TableModel[FlowBoundaryStaticSchema] = Field( - default_factory=TableModel[FlowBoundaryStaticSchema] + default_factory=TableModel[FlowBoundaryStaticSchema], + json_schema_extra={"sort_keys": ["node_id"]}, ) time: TableModel[FlowBoundaryTimeSchema] = Field( - default_factory=TableModel[FlowBoundaryTimeSchema] + default_factory=TableModel[FlowBoundaryTimeSchema], + json_schema_extra={"sort_keys": ["node_id", "time"]}, ) - _sort_keys: dict[str, list[str]] = { - "static": ["node_id"], - "time": ["node_id", "time"], - } - class Basin(NodeModel): profile: TableModel[BasinProfileSchema] = Field( - default_factory=TableModel[BasinProfileSchema] + default_factory=TableModel[BasinProfileSchema], + json_schema_extra={"sort_keys": ["node_id", "level"]}, ) state: TableModel[BasinStateSchema] = Field( - default_factory=TableModel[BasinStateSchema] + default_factory=TableModel[BasinStateSchema], + json_schema_extra={"sort_keys": ["node_id"]}, ) static: TableModel[BasinStaticSchema] = Field( - default_factory=TableModel[BasinStaticSchema] + default_factory=TableModel[BasinStaticSchema], + json_schema_extra={"sort_keys": ["node_id"]}, ) time: TableModel[BasinTimeSchema] = Field( - default_factory=TableModel[BasinTimeSchema] + default_factory=TableModel[BasinTimeSchema], + json_schema_extra={"sort_keys": ["node_id", "time"]}, ) subgrid: TableModel[BasinSubgridSchema] = Field( - default_factory=TableModel[BasinSubgridSchema] + default_factory=TableModel[BasinSubgridSchema], + json_schema_extra={"sort_keys": ["subgrid_id", "basin_level"]}, ) - _sort_keys: dict[str, list[str]] = { - "static": ["node_id"], - "state": ["node_id"], - "profile": ["node_id", "level"], - "time": ["node_id", "time"], - "subgrid": ["subgrid_id", "basin_level"], - } - class ManningResistance(NodeModel): static: TableModel[ManningResistanceStaticSchema] = Field( - default_factory=TableModel[ManningResistanceStaticSchema] + default_factory=TableModel[ManningResistanceStaticSchema], + json_schema_extra={"sort_keys": ["node_id", "control_state"]}, ) - _sort_keys: dict[str, list[str]] = {"static": ["node_id", "control_state"]} - class DiscreteControl(NodeModel): condition: TableModel[DiscreteControlConditionSchema] = Field( - default_factory=TableModel[DiscreteControlConditionSchema] + default_factory=TableModel[DiscreteControlConditionSchema], + json_schema_extra={ + "sort_keys": ["node_id", "listen_feature_id", "variable", "greater_than"] + }, ) logic: TableModel[DiscreteControlLogicSchema] = Field( - default_factory=TableModel[DiscreteControlLogicSchema] + default_factory=TableModel[DiscreteControlLogicSchema], + json_schema_extra={"sort_keys": ["node_id", "truth_state"]}, ) - _sort_keys: dict[str, list[str]] = { - "condition": ["node_id", "listen_feature_id", "variable", "greater_than"], - "logic": ["node_id", "truth_state"], - } - class Outlet(NodeModel): static: TableModel[OutletStaticSchema] = Field( - default_factory=TableModel[OutletStaticSchema] + default_factory=TableModel[OutletStaticSchema], + json_schema_extra={"sort_keys": ["node_id", "control_state"]}, ) - _sort_keys: dict[str, list[str]] = {"static": ["node_id", "control_state"]} - class LinearResistance(NodeModel): static: TableModel[LinearResistanceStaticSchema] = Field( - default_factory=TableModel[LinearResistanceStaticSchema] + default_factory=TableModel[LinearResistanceStaticSchema], + json_schema_extra={"sort_keys": ["node_id", "control_state"]}, ) - _sort_keys: dict[str, list[str]] = {"static": ["node_id", "control_state"]} - class FractionalFlow(NodeModel): static: TableModel[FractionalFlowStaticSchema] = Field( - default_factory=TableModel[FractionalFlowStaticSchema] + default_factory=TableModel[FractionalFlowStaticSchema], + json_schema_extra={"sort_keys": ["node_id", "control_state"]}, ) - - _sort_keys: dict[str, list[str]] = {"static": ["node_id", "control_state"]} diff --git a/python/ribasim/ribasim/input_base.py b/python/ribasim/ribasim/input_base.py index 1538bfafa..17b066d01 100644 --- a/python/ribasim/ribasim/input_base.py +++ b/python/ribasim/ribasim/input_base.py @@ -9,6 +9,7 @@ Any, Generic, TypeVar, + cast, ) import geopandas as gpd @@ -20,6 +21,8 @@ ConfigDict, DirectoryPath, Field, + PrivateAttr, + ValidationInfo, field_validator, model_serializer, model_validator, @@ -158,6 +161,7 @@ def _load(cls, filepath: Path | None) -> dict[str, Any]: class TableModel(FileModel, Generic[TableT]): df: DataFrame[TableT] | None = Field(default=None, exclude=True, repr=False) + _sort_keys: list[str] = PrivateAttr(default=[]) @field_validator("df") @classmethod @@ -219,15 +223,14 @@ def _save( self, directory: DirectoryPath, input_dir: DirectoryPath, - sort_keys: list[str] = ["node_id"], ) -> None: # TODO directory could be used to save an arrow file db_path = context_file_loading.get().get("database") if self.df is not None and self.filepath is not None: - self.sort(sort_keys) + self.sort() self._write_arrow(self.filepath, directory, input_dir) elif self.df is not None and db_path is not None: - self.sort(sort_keys) + self.sort() self._write_table(db_path) def _write_table(self, temp_path: Path) -> None: @@ -289,13 +292,13 @@ def _from_arrow(cls, path: FilePath) -> pd.DataFrame: directory = context_file_loading.get().get("directory", Path(".")) return pd.read_feather(directory / path) - def sort(self, sort_keys: list[str]): + def sort(self): """Sort the table as required. Sorting is done automatically before writing the table. """ if self.df is not None: - self.df.sort_values(sort_keys, ignore_index=True, inplace=True) + self.df.sort_values(self._sort_keys, ignore_index=True, inplace=True) @classmethod def tableschema(cls) -> TableT: @@ -374,7 +377,7 @@ def _write_table(self, path: FilePath) -> None: gdf.to_file(path, layer=self.tablename(), driver="GPKG") - def sort(self, sort_keys: list[str]): + def sort(self): self.df.sort_index(inplace=True) @@ -392,8 +395,6 @@ def check_parent(self) -> "ChildModel": class NodeModel(ChildModel): """Base class to handle combining the tables for a single node type.""" - _sort_keys: dict[str, list[str]] = {} - @model_serializer(mode="wrap") def set_modeld( self, serializer: Callable[[type["NodeModel"]], dict[str, Any]] @@ -401,6 +402,19 @@ def set_modeld( content = serializer(self) return dict(filter(lambda x: x[1], content.items())) + @field_validator("*") + @classmethod + def set_sort_keys(cls, v: Any, info: ValidationInfo) -> Any: + """Set sort keys for all TableModels if present in FieldInfo.""" + if isinstance(v, (TableModel,)): + field = cls.model_fields[getattr(info, "field_name")] + extra = field.json_schema_extra + if extra is not None and isinstance(extra, dict): + # We set sort_keys ourselves as list[str] in json_schema_extra + # but mypy doesn't know. + v._sort_keys = cast(list[str], extra.get("sort_keys", [])) + return v + @classmethod def get_input_type(cls): return cls.__name__ @@ -434,7 +448,6 @@ def _save(self, directory: DirectoryPath, input_dir: DirectoryPath, **kwargs): getattr(self, field)._save( directory, input_dir, - sort_keys=self._sort_keys[field], ) def _repr_content(self) -> str: diff --git a/python/ribasim/tests/test_io.py b/python/ribasim/tests/test_io.py index b1ac52056..e86b9456b 100644 --- a/python/ribasim/tests/test_io.py +++ b/python/ribasim/tests/test_io.py @@ -111,9 +111,13 @@ def test_sort(level_setpoint_with_minmax, tmp_path): # apply a wrong sort, then call the sort method to restore order table.df.sort_values("greater_than", ascending=False, inplace=True) assert table.df.iloc[0]["greater_than"] == 15.0 - sort_keys = model.discrete_control._sort_keys["condition"] - assert sort_keys == ["node_id", "listen_feature_id", "variable", "greater_than"] - table.sort(sort_keys) + assert table._sort_keys == [ + "node_id", + "listen_feature_id", + "variable", + "greater_than", + ] + table.sort() assert table.df.iloc[0]["greater_than"] == 5.0 # re-apply wrong sort, then check if it gets sorted on write From ec7234d41462bf4a12e77f77814cd54984d4adfb Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Tue, 23 Jan 2024 10:44:14 +0100 Subject: [PATCH 5/6] Always require 'Basin / state' as an initial condition (#977) Before we put a volume of 1 m3 in if no initial condition was given. But since this will not always work well, and there is no other number that makes a good default, it is best to remove this feature and request an explicit initial condition. --- core/src/bmi.jl | 12 ++----- core/src/utils.jl | 26 +++++++++++----- core/src/validation.jl | 24 -------------- core/test/bmi_test.jl | 2 +- core/test/run_models_test.jl | 2 +- core/test/utils_test.jl | 4 +-- .../ribasim_testmodels/backwater.py | 8 +++-- .../ribasim_testmodels/basic.py | 11 +++++-- .../ribasim_testmodels/discrete_control.py | 4 ++- .../ribasim_testmodels/invalid.py | 31 ++++++++++++++++--- .../ribasim_testmodels/time.py | 9 +++++- .../ribasim_testmodels/trivial.py | 4 ++- 12 files changed, 78 insertions(+), 59 deletions(-) diff --git a/core/src/bmi.jl b/core/src/bmi.jl index d2d7f43bb..7f11ee34d 100644 --- a/core/src/bmi.jl +++ b/core/src/bmi.jl @@ -73,16 +73,8 @@ function BMI.initialize(T::Type{Model}, config::Config)::Model end @debug "Read database into memory." - storage = if isempty(state) - # default to nearly empty basins, perhaps make required input - fill(1.0, n) - else - storages, errors = get_storages_from_levels(parameters.basin, state.level) - if errors - error("Encountered errors while parsing the initial levels of basins.") - end - storages - end + storage = get_storages_from_levels(parameters.basin, state.level) + # Synchronize level with storage set_current_basin_properties!(parameters.basin, storage) diff --git a/core/src/utils.jl b/core/src/utils.jl index 5040f786c..20ff9f75c 100644 --- a/core/src/utils.jl +++ b/core/src/utils.jl @@ -374,16 +374,28 @@ function get_storage_from_level(basin::Basin, state_idx::Int, level::Float64)::F end """Compute the storages of the basins based on the water level of the basins.""" -function get_storages_from_levels( - basin::Basin, - levels::Vector, -)::Tuple{Vector{Float64}, Bool} - storages = Float64[] +function get_storages_from_levels(basin::Basin, levels::Vector)::Vector{Float64} + errors = false + state_length = length(levels) + basin_length = length(basin.level) + if state_length != basin_length + @error "Unexpected 'Basin / state' length." state_length basin_length + errors = true + end + storages = zeros(state_length) for (i, level) in enumerate(levels) - push!(storages, get_storage_from_level(basin, i, level)) + storage = get_storage_from_level(basin, i, level) + if isnan(storage) + errors = true + end + storages[i] = storage + end + if errors + error("Encountered errors while parsing the initial levels of basins.") end - return storages, any(isnan.(storages)) + + return storages end """ diff --git a/core/src/validation.jl b/core/src/validation.jl index d49dc3506..922b02fe2 100644 --- a/core/src/validation.jl +++ b/core/src/validation.jl @@ -338,30 +338,6 @@ function variable_nt(s::Any) NamedTuple{names}((getfield(s, x) for x in names)) end -function is_consistent(node, edge, state, static, profile, time) - - # Check that node ids exist - # TODO Do we need to check the reverse as well? All ids in use? - ids = node.fid - @assert edge.from_node_id ⊆ ids "Edge from_node_id not in node ids" - @assert edge.to_node_id ⊆ ids "Edge to_node_id not in node ids" - @assert state.node_id ⊆ ids "State id not in node ids" - @assert static.node_id ⊆ ids "Static id not in node ids" - @assert profile.node_id ⊆ ids "Profile id not in node ids" - @assert time.node_id ⊆ ids "Time id not in node ids" - - # Check edges for uniqueness - @assert allunique(edge, [:from_node_id, :to_node_id]) "Duplicate edge found" - - # TODO Check states - - # TODO Check statics - - # TODO Check forcings - - true -end - # functions used by sort(x; by) sort_by_fid(row) = row.fid sort_by_id(row) = row.node_id diff --git a/core/test/bmi_test.jl b/core/test/bmi_test.jl index 8c6c95dc7..39599eeac 100644 --- a/core/test/bmi_test.jl +++ b/core/test/bmi_test.jl @@ -49,7 +49,7 @@ end toml_path = normpath(@__DIR__, "../../generated_testmodels/basic/ribasim.toml") model = BMI.initialize(Ribasim.Model, toml_path) storage0 = BMI.get_value_ptr(model, "volume") - @test storage0 == ones(4) + @test storage0 ≈ ones(4) @test_throws "Unknown variable foo" BMI.get_value_ptr(model, "foo") BMI.update_until(model, 86400.0) storage = BMI.get_value_ptr(model, "volume") diff --git a/core/test/run_models_test.jl b/core/test/run_models_test.jl index 995b07032..227ec010f 100644 --- a/core/test/run_models_test.jl +++ b/core/test/run_models_test.jl @@ -73,7 +73,7 @@ @test flow.from_node_id[1:4] == [6, typemax(Int), 0, 6] @test flow.to_node_id[1:4] == [6, typemax(Int), typemax(Int), 0] - @test basin.storage[1] == 1.0 + @test basin.storage[1] ≈ 1.0 @test basin.level[1] ≈ 0.044711584 # The exporter interpolates 1:1 for three subgrid elements, but shifted by 1.0 meter. diff --git a/core/test/utils_test.jl b/core/test/utils_test.jl index ed2bc1a1e..d4263a878 100644 --- a/core/test/utils_test.jl +++ b/core/test/utils_test.jl @@ -125,9 +125,7 @@ end logger = TestLogger() with_logger(logger) do - storages, errors = Ribasim.get_storages_from_levels(basin, [-1.0]) - @test isnan(storages[1]) - @test errors + @test_throws ErrorException Ribasim.get_storages_from_levels(basin, [-1.0]) end @test length(logger.logs) == 1 diff --git a/python/ribasim_testmodels/ribasim_testmodels/backwater.py b/python/ribasim_testmodels/ribasim_testmodels/backwater.py index e9adc4f07..42e950ec9 100644 --- a/python/ribasim_testmodels/ribasim_testmodels/backwater.py +++ b/python/ribasim_testmodels/ribasim_testmodels/backwater.py @@ -52,16 +52,17 @@ def backwater_model(): ) # Rectangular profile, width of 1.0 m. + basin_ids = ids[node_type == "Basin"] profile = pd.DataFrame( data={ - "node_id": np.repeat(ids[node_type == "Basin"], 2), + "node_id": np.repeat(basin_ids, 2), "area": [20.0, 20.0] * n_basin, "level": [0.0, 1.0] * n_basin, } ) static = pd.DataFrame( data={ - "node_id": ids[node_type == "Basin"], + "node_id": basin_ids, "drainage": 0.0, "potential_evaporation": 0.0, "infiltration": 0.0, @@ -69,7 +70,8 @@ def backwater_model(): "urban_runoff": 0.0, } ) - basin = ribasim.Basin(profile=profile, static=static) + state = pd.DataFrame(data={"node_id": basin_ids, "level": 0.05}) + basin = ribasim.Basin(profile=profile, static=static, state=state) manning_resistance = ribasim.ManningResistance( static=pd.DataFrame( diff --git a/python/ribasim_testmodels/ribasim_testmodels/basic.py b/python/ribasim_testmodels/ribasim_testmodels/basic.py index 7a3982173..657314b95 100644 --- a/python/ribasim_testmodels/ribasim_testmodels/basic.py +++ b/python/ribasim_testmodels/ribasim_testmodels/basic.py @@ -36,8 +36,10 @@ def basic_model() -> ribasim.Model: ) static = static.iloc[[0, 0, 0, 0]] static["node_id"] = [1, 3, 6, 9] - - basin = ribasim.Basin(profile=profile, static=static) + state = pd.DataFrame( + data={"node_id": static["node_id"], "level": 0.04471158417652035} + ) + basin = ribasim.Basin(profile=profile, static=static, state=state) # Setup linear resistance: linear_resistance = ribasim.LinearResistance( @@ -303,8 +305,11 @@ def tabulated_rating_curve_model() -> ribasim.Model: "urban_runoff": 0.0, } ) + state = pd.DataFrame( + data={"node_id": static["node_id"], "level": 0.04471158417652035} + ) - basin = ribasim.Basin(profile=profile, static=static) + basin = ribasim.Basin(profile=profile, static=static, state=state) # Set up a rating curve node: # Discharge: lose 1% of storage volume per day at storage = 1000.0. diff --git a/python/ribasim_testmodels/ribasim_testmodels/discrete_control.py b/python/ribasim_testmodels/ribasim_testmodels/discrete_control.py index 6481214ad..e82b606a0 100644 --- a/python/ribasim_testmodels/ribasim_testmodels/discrete_control.py +++ b/python/ribasim_testmodels/ribasim_testmodels/discrete_control.py @@ -511,7 +511,9 @@ def tabulated_rating_curve_control_model() -> ribasim.Model: } ) - basin = ribasim.Basin(profile=profile, static=static) + state = pd.DataFrame(data={"node_id": [1], "level": 0.04471158417652035}) + + basin = ribasim.Basin(profile=profile, static=static, state=state) # Set up a rating curve node: # Discharge: lose 1% of storage volume per day at storage = 100.0. diff --git a/python/ribasim_testmodels/ribasim_testmodels/invalid.py b/python/ribasim_testmodels/ribasim_testmodels/invalid.py index 19cee6613..7301f7f2a 100644 --- a/python/ribasim_testmodels/ribasim_testmodels/invalid.py +++ b/python/ribasim_testmodels/ribasim_testmodels/invalid.py @@ -61,7 +61,9 @@ def invalid_qh_model(): } ) - basin = ribasim.Basin(profile=profile, static=static) + state = pd.DataFrame(data={"node_id": [3], "level": 1.4112729908597084}) + + basin = ribasim.Basin(profile=profile, static=static, state=state) rating_curve_static = pd.DataFrame( # Invalid: levels must not be repeated @@ -171,7 +173,14 @@ def invalid_fractional_flow_model(): } ) - basin = ribasim.Basin(profile=profile, static=static) + state = pd.DataFrame( + data={ + "node_id": [1, 2], + "level": 1.4112729908597084, + } + ) + + basin = ribasim.Basin(profile=profile, static=static, state=state) # Setup terminal: terminal = ribasim.Terminal(static=pd.DataFrame(data={"node_id": [5, 6]})) @@ -263,7 +272,14 @@ def invalid_discrete_control_model(): } ) - basin = ribasim.Basin(profile=profile, static=static) + state = pd.DataFrame( + data={ + "node_id": [1, 3], + "level": 1.4112729908597084, + } + ) + + basin = ribasim.Basin(profile=profile, static=static, state=state) # Setup pump: pump = ribasim.Pump( @@ -391,7 +407,14 @@ def invalid_edge_types_model(): } ) - basin = ribasim.Basin(profile=profile, static=static) + state = pd.DataFrame( + data={ + "node_id": [1, 3], + "level": 0.04471158417652035, + } + ) + + basin = ribasim.Basin(profile=profile, static=static, state=state) # Setup pump: pump = ribasim.Pump( diff --git a/python/ribasim_testmodels/ribasim_testmodels/time.py b/python/ribasim_testmodels/ribasim_testmodels/time.py index bb34dfea7..42a0bdd6a 100644 --- a/python/ribasim_testmodels/ribasim_testmodels/time.py +++ b/python/ribasim_testmodels/ribasim_testmodels/time.py @@ -66,7 +66,14 @@ def flow_boundary_time_model(): } ) - basin = ribasim.Basin(profile=profile, static=static) + state = pd.DataFrame( + data={ + "node_id": [2], + "level": 0.04471158417652035, + } + ) + + basin = ribasim.Basin(profile=profile, static=static, state=state) n_times = 100 time = pd.date_range( diff --git a/python/ribasim_testmodels/ribasim_testmodels/trivial.py b/python/ribasim_testmodels/ribasim_testmodels/trivial.py index 664b69197..99b5067cf 100644 --- a/python/ribasim_testmodels/ribasim_testmodels/trivial.py +++ b/python/ribasim_testmodels/ribasim_testmodels/trivial.py @@ -74,6 +74,8 @@ def trivial_model() -> ribasim.Model: } ) + state = pd.DataFrame(data={"node_id": [6], "level": 0.04471158417652035}) + # Create a subgrid level interpolation from one basin to three elements. Scale one to one, but: # # 22. start at -1.0 @@ -88,7 +90,7 @@ def trivial_model() -> ribasim.Model: "subgrid_level": [-1.0, 0.0, 0.0, 1.0, 1.0, 2.0], } ) - basin = ribasim.Basin(profile=profile, static=static, subgrid=subgrid) + basin = ribasim.Basin(profile=profile, static=static, state=state, subgrid=subgrid) # Set up a rating curve node: # Discharge: lose 1% of storage volume per day at storage = 1000.0. From 8032975bc157f07371d2f922c97fd416ee23e9b7 Mon Sep 17 00:00:00 2001 From: Hofer-Julian <30049909+Hofer-Julian@users.noreply.github.com> Date: Tue, 23 Jan 2024 14:07:41 +0100 Subject: [PATCH 6/6] Add cron job to auto-update pixi lock file (#978) This way there will be a PR that updates the lock file every month --- .github/workflows/core_tests.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/pixi_auto_update.yml | 32 ++++++++++++++++++++++++++ .github/workflows/pre-commit_check.yml | 2 +- .github/workflows/python_lint.yml | 2 +- .github/workflows/python_tests.yml | 2 +- .github/workflows/qgis.yml | 2 +- 7 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/pixi_auto_update.yml diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index a5020269f..50015322d 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -1,7 +1,7 @@ name: Julia Tests on: push: - branches: [main] + branches: [main, update/pixi-lock] paths-ignore: [".teamcity/**"] tags: ["*"] pull_request: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ef6eea4c1..4497abdda 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,7 +1,7 @@ name: Docs on: push: - branches: [main] + branches: [main, update/pixi-lock] paths-ignore: [".teamcity/**"] pull_request: merge_group: diff --git a/.github/workflows/pixi_auto_update.yml b/.github/workflows/pixi_auto_update.yml new file mode 100644 index 000000000..96e7c513a --- /dev/null +++ b/.github/workflows/pixi_auto_update.yml @@ -0,0 +1,32 @@ +name: Pixi auto update + +on: + schedule: + # At 03:00 on day 1 of the month + - cron: "0 3 1 * *" + # on demand + workflow_dispatch: + +jobs: + auto-update: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} + - uses: prefix-dev/setup-pixi@v0.4.3 + with: + pixi-version: "latest" + cache: false + - name: Update pixi lock file + run: | + rm pixi.lock + pixi install + - uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: update/pixi-lock + title: Update pixi lock file + commit-message: "Update `pixi.lock`" + body: Update pixi dependencies to the latest version. + author: "GitHub " diff --git a/.github/workflows/pre-commit_check.yml b/.github/workflows/pre-commit_check.yml index 17d1ae7a2..fb5f88949 100644 --- a/.github/workflows/pre-commit_check.yml +++ b/.github/workflows/pre-commit_check.yml @@ -4,7 +4,7 @@ on: pull_request: merge_group: push: - branches: [main, update/pre-commit-hooks] + branches: [main, update/pre-commit-hooks, update/pixi-lock] jobs: check: diff --git a/.github/workflows/python_lint.yml b/.github/workflows/python_lint.yml index 47310710d..a5a08c9a5 100644 --- a/.github/workflows/python_lint.yml +++ b/.github/workflows/python_lint.yml @@ -1,7 +1,7 @@ name: Python Lint on: push: - branches: [main] + branches: [main, update/pixi-lock] paths-ignore: [".teamcity/**"] tags: ["*"] pull_request: diff --git a/.github/workflows/python_tests.yml b/.github/workflows/python_tests.yml index 07022ddde..6089785e8 100644 --- a/.github/workflows/python_tests.yml +++ b/.github/workflows/python_tests.yml @@ -1,7 +1,7 @@ name: Ribasim Python Tests on: push: - branches: [main] + branches: [main, update/pixi-lock] paths-ignore: [".teamcity/**"] tags: ["*"] pull_request: diff --git a/.github/workflows/qgis.yml b/.github/workflows/qgis.yml index 67f159a35..608820001 100644 --- a/.github/workflows/qgis.yml +++ b/.github/workflows/qgis.yml @@ -2,7 +2,7 @@ name: QGIS Tests on: push: - branches: [main] + branches: [main, update/pixi-lock] paths-ignore: [".teamcity/**"] tags: ["*"] pull_request: