From 95c8f7c87d705e04f865c9b93ae9c04b186b64b9 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Fri, 13 Dec 2024 21:25:40 +0100 Subject: [PATCH 01/14] Add `Basin / subgrid_time` table --- core/src/read.jl | 6 ++++ core/src/schema.jl | 13 +++++++ docs/reference/node/basin.qmd | 15 ++++++-- python/ribasim/ribasim/config.py | 5 +++ python/ribasim/ribasim/nodes/basin.py | 6 ++++ python/ribasim/ribasim/schemas.py | 19 ++++++++++ .../ribasim_testmodels/two_basin.py | 8 +++-- ribasim_qgis/core/nodes.py | 35 ++++++++++++++----- 8 files changed, 93 insertions(+), 14 deletions(-) diff --git a/core/src/read.jl b/core/src/read.jl index e99d207e0..6c9e2a34c 100644 --- a/core/src/read.jl +++ b/core/src/read.jl @@ -187,6 +187,12 @@ function parse_static_and_time( return out, !errors end +""" +Validate the split of node IDs between static and time tables. + +For node types that can have a part of the parameters defined statically and a part dynamically, +this checks if each ID is defined exactly once in either table. +""" function static_and_time_node_ids( db::DB, static::StructVector, diff --git a/core/src/schema.jl b/core/src/schema.jl index e03d8e852..58b32088e 100644 --- a/core/src/schema.jl +++ b/core/src/schema.jl @@ -10,6 +10,7 @@ @schema "ribasim.basin.profile" BasinProfile @schema "ribasim.basin.state" BasinState @schema "ribasim.basin.subgrid" BasinSubgrid +@schema "ribasim.basin.subgridtime" BasinSubgridTime @schema "ribasim.basin.concentration" BasinConcentration @schema "ribasim.basin.concentrationexternal" BasinConcentrationExternal @schema "ribasim.basin.concentrationstate" BasinConcentrationState @@ -58,9 +59,13 @@ function nodetype( type_string = string(T) elements = split(type_string, '.'; limit = 3) last_element = last(elements) + # Special case last elements that need an underscore if startswith(last_element, "concentration") && length(last_element) > 13 elements[end] = "concentration_$(last_element[14:end])" end + if last_element == "subgridtime" + elements[end] = "subgrid_time" + end if isnode(sv) n = elements[2] k = Symbol(elements[3]) @@ -150,6 +155,14 @@ end subgrid_level::Float64 end +@version BasinSubgridTimeV1 begin + subgrid_id::Int32 + node_id::Int32 + time::DateTime + basin_level::Float64 + subgrid_level::Float64 +end + @version LevelBoundaryStaticV1 begin node_id::Int32 active::Union{Missing, Bool} diff --git a/docs/reference/node/basin.qmd b/docs/reference/node/basin.qmd index ee87fb99a..62abafde9 100644 --- a/docs/reference/node/basin.qmd +++ b/docs/reference/node/basin.qmd @@ -228,7 +228,7 @@ ax.legend() for converting the initial state in terms of levels to an initial state in terms of storages used in the core. -#### Interactive basin example +#### Interactive Basin example The profile data is not detailed enough to create a full 3D picture of the basin. However, if we assume the profile data is for a stretch of canal of given length, the following plot shows a cross section of the basin. ```{python} @@ -391,6 +391,15 @@ Water levels beyond the last `basin_level` are linearly extrapolated. Note that the interpolation to subgrid water level is not constrained by any water balance within Ribasim. Generally, to create physically meaningful subgrid water levels, the subgrid table must be parametrized properly such that the spatially integrated water volume of the subgrid elements agrees with the total storage volume of the basin. +## Subgrid time + +This table is the transient form of the Subgrid table. +The only difference is that a time column is added. +The table must by sorted by time, and per time it must be sorted by `subgrid_id`. +With this the subgrid relations can be updated over time. +Note that a `node_id` can be either in this table or in the static one, but not both. +That means for each Basin all subgrid relations are either static or dynamic. + ## Concentration {#sec-basin-conc} This table defines the concentration of substances for the inflow boundaries of a Basin node. @@ -402,7 +411,7 @@ substance | String | | can correspond to known De drainage | Float64 | $\text{g}/\text{m}^3$ | (optional) precipitation | Float64 | $\text{g}/\text{m}^3$ | (optional) -## ConcentrationState {#sec-basin-conc-state} +## Concentration state {#sec-basin-conc-state} This table defines the concentration of substances in the Basin at the start of the simulation. column | type | unit | restriction @@ -411,7 +420,7 @@ node_id | Int32 | - | sorted substance | String | - | can correspond to known Delwaq substances concentration | Float64 | $\text{g}/\text{m}^3$ | -## ConcentrationExternal +## Concentration external This table is used for (external) concentrations, that can be used for Control lookups. column | type | unit | restriction diff --git a/python/ribasim/ribasim/config.py b/python/ribasim/ribasim/config.py index a9acd3350..49060b2b9 100644 --- a/python/ribasim/ribasim/config.py +++ b/python/ribasim/ribasim/config.py @@ -23,6 +23,7 @@ BasinStateSchema, BasinStaticSchema, BasinSubgridSchema, + BasinSubgridTimeSchema, BasinTimeSchema, ContinuousControlFunctionSchema, ContinuousControlVariableSchema, @@ -405,6 +406,10 @@ class Basin(MultiNodeModel): default_factory=TableModel[BasinSubgridSchema], json_schema_extra={"sort_keys": ["subgrid_id", "basin_level"]}, ) + subgrid_time: TableModel[BasinSubgridTimeSchema] = Field( + default_factory=TableModel[BasinSubgridTimeSchema], + json_schema_extra={"sort_keys": ["subgrid_id", "time", "basin_level"]}, + ) area: SpatialTableModel[BasinAreaSchema] = Field( default_factory=SpatialTableModel[BasinAreaSchema], json_schema_extra={"sort_keys": ["node_id"]}, diff --git a/python/ribasim/ribasim/nodes/basin.py b/python/ribasim/ribasim/nodes/basin.py index 1beb21336..9da7ee1a6 100644 --- a/python/ribasim/ribasim/nodes/basin.py +++ b/python/ribasim/ribasim/nodes/basin.py @@ -8,6 +8,7 @@ BasinStateSchema, BasinStaticSchema, BasinSubgridSchema, + BasinSubgridTimeSchema, BasinTimeSchema, ) @@ -18,6 +19,7 @@ "State", "Static", "Subgrid", + "SubgridTime", "Time", ] @@ -42,6 +44,10 @@ class Subgrid(TableModel[BasinSubgridSchema]): pass +class SubgridTime(TableModel[BasinSubgridTimeSchema]): + pass + + class Area(SpatialTableModel[BasinAreaSchema]): pass diff --git a/python/ribasim/ribasim/schemas.py b/python/ribasim/ribasim/schemas.py index 2bcd22222..b80a5d466 100644 --- a/python/ribasim/ribasim/schemas.py +++ b/python/ribasim/ribasim/schemas.py @@ -116,6 +116,25 @@ class BasinStaticSchema(_BaseSchema): ) +class BasinSubgridTimeSchema(_BaseSchema): + fid: Index[Int32] = pa.Field(default=1, check_name=True, coerce=True) + subgrid_id: Series[Annotated[pd.ArrowDtype, pyarrow.int32()]] = pa.Field( + nullable=False + ) + node_id: Series[Annotated[pd.ArrowDtype, pyarrow.int32()]] = pa.Field( + nullable=False, default=0 + ) + time: Series[Annotated[pd.ArrowDtype, pyarrow.timestamp("ms")]] = pa.Field( + nullable=False + ) + basin_level: Series[Annotated[pd.ArrowDtype, pyarrow.float64()]] = pa.Field( + nullable=False + ) + subgrid_level: Series[Annotated[pd.ArrowDtype, pyarrow.float64()]] = pa.Field( + nullable=False + ) + + class BasinSubgridSchema(_BaseSchema): fid: Index[Int32] = pa.Field(default=1, check_name=True, coerce=True) subgrid_id: Series[Annotated[pd.ArrowDtype, pyarrow.int32()]] = pa.Field( diff --git a/python/ribasim_testmodels/ribasim_testmodels/two_basin.py b/python/ribasim_testmodels/ribasim_testmodels/two_basin.py index bbdab74e1..acc52c4aa 100644 --- a/python/ribasim_testmodels/ribasim_testmodels/two_basin.py +++ b/python/ribasim_testmodels/ribasim_testmodels/two_basin.py @@ -44,10 +44,12 @@ def two_basin_model() -> Model: Node(3, Point(750, 0)), [ *basin_shared, - basin.Subgrid( + # Raise the subgrid levels by a meter after a month + basin.SubgridTime( subgrid_id=2, - basin_level=[0.0, 1.0], - subgrid_level=[0.0, 1.0], + time=["2020-01-01", "2020-01-01", "2020-02-01", "2020-02-01"], + basin_level=[0.0, 1.0, 0.0, 1.0], + subgrid_level=[0.0, 1.0, 1.0, 2.0], meta_x=750.0, meta_y=0.0, ), diff --git a/ribasim_qgis/core/nodes.py b/ribasim_qgis/core/nodes.py index 35d8f3970..d2274b3ee 100644 --- a/ribasim_qgis/core/nodes.py +++ b/ribasim_qgis/core/nodes.py @@ -348,8 +348,8 @@ def geometry_type(cls) -> str: @classmethod def attributes(cls) -> list[QgsField]: return [ - QgsField("time", QVariant.DateTime), QgsField("node_id", QVariant.Int), + QgsField("time", QVariant.DateTime), QgsField("drainage", QVariant.Double), QgsField("potential_evaporation", QVariant.Double), QgsField("infiltration", QVariant.Double), @@ -369,8 +369,8 @@ def geometry_type(cls) -> str: @classmethod def attributes(cls) -> list[QgsField]: return [ - QgsField("time", QVariant.DateTime), QgsField("node_id", QVariant.Int), + QgsField("time", QVariant.DateTime), QgsField("substance", QVariant.String), QgsField("concentration", QVariant.Double), ] @@ -388,8 +388,8 @@ def geometry_type(cls) -> str: @classmethod def attributes(cls) -> list[QgsField]: return [ - QgsField("time", QVariant.DateTime), QgsField("node_id", QVariant.Int), + QgsField("time", QVariant.DateTime), QgsField("substance", QVariant.String), QgsField("concentration", QVariant.Double), ] @@ -407,15 +407,15 @@ def geometry_type(cls) -> str: @classmethod def attributes(cls) -> list[QgsField]: return [ - QgsField("time", QVariant.DateTime), QgsField("node_id", QVariant.Int), + QgsField("time", QVariant.DateTime), QgsField("substance", QVariant.String), QgsField("drainage", QVariant.Double), QgsField("precipitation", QVariant.Double), ] -class BasinSubgridLevel(Input): +class BasinSubgrid(Input): @classmethod def input_type(cls) -> str: return "Basin / subgrid" @@ -434,6 +434,26 @@ def attributes(cls) -> list[QgsField]: ] +class BasinSubgridTime(Input): + @classmethod + def input_type(cls) -> str: + return "Basin / subgrid_time" + + @classmethod + def geometry_type(cls) -> str: + return "No Geometry" + + @classmethod + def attributes(cls) -> list[QgsField]: + return [ + QgsField("subgrid_id", QVariant.Int), + QgsField("node_id", QVariant.Int), + QgsField("time", QVariant.DateTime), + QgsField("basin_level", QVariant.Double), + QgsField("subgrid_level", QVariant.Double), + ] + + class BasinArea(Input): @classmethod def input_type(cls) -> str: @@ -505,8 +525,8 @@ def geometry_type(cls) -> str: @classmethod def attributes(cls) -> list[QgsField]: return [ - QgsField("time", QVariant.DateTime), QgsField("node_id", QVariant.Int), + QgsField("time", QVariant.DateTime), QgsField("level", QVariant.Double), QgsField("flow_rate", QVariant.Double), ] @@ -583,7 +603,6 @@ def geometry_type(cls) -> str: @classmethod def attributes(cls) -> list[QgsField]: return [ - QgsField("time", QVariant.DateTime), QgsField("node_id", QVariant.Int), QgsField("time", QVariant.DateTime), QgsField("level", QVariant.Double), @@ -663,8 +682,8 @@ def geometry_type(cls) -> str: @classmethod def attributes(cls) -> list[QgsField]: return [ - QgsField("time", QVariant.DateTime), QgsField("node_id", QVariant.Int), + QgsField("time", QVariant.DateTime), QgsField("flow_rate", QVariant.Double), ] From f4c2139a7d375dc2125c789a741d67eb315e2594 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 16 Dec 2024 13:17:46 +0100 Subject: [PATCH 02/14] Read new table into Subgrid struct --- core/src/Ribasim.jl | 1 + core/src/parameter.jl | 17 ++++++- core/src/read.jl | 101 ++++++++++++++++++++++++++++++++++++++--- core/src/validation.jl | 4 +- 4 files changed, 114 insertions(+), 9 deletions(-) diff --git a/core/src/Ribasim.jl b/core/src/Ribasim.jl index 6beebf38c..e43bc892d 100644 --- a/core/src/Ribasim.jl +++ b/core/src/Ribasim.jl @@ -65,6 +65,7 @@ using PreallocationTools: LazyBufferCache # basin profiles and TabulatedRatingCurve. See also the node # references in the docs. using DataInterpolations: + ConstantInterpolation, LinearInterpolation, LinearInterpolationIntInv, invert_integral, diff --git a/core/src/parameter.jl b/core/src/parameter.jl index 03b909799..a72b5796e 100644 --- a/core/src/parameter.jl +++ b/core/src/parameter.jl @@ -105,6 +105,7 @@ end Base.to_index(id::NodeID) = Int(id.value) +"LinearInterpolation from a Float64 to a Float64" const ScalarInterpolation = LinearInterpolation{ Vector{Float64}, Vector{Float64}, @@ -114,6 +115,10 @@ const ScalarInterpolation = LinearInterpolation{ (1,), } +"ConstantInterpolation from a Float64 to an Int, used to look up indices" +const IndexLookup = + ConstantInterpolation{Vector{Int64}, Vector{Float64}, Vector{Float64}, Int64, (1,)} + set_zero!(v) = v .= zero(eltype(v)) const Cache = LazyBufferCache{Returns{Int}, typeof(set_zero!)} @@ -876,10 +881,20 @@ end "Subgrid linearly interpolates basin levels." @kwdef struct Subgrid + # cache the current level for static subgrids followed by dynamic subgrids + level::Vector{Float64} + # static subgrid_id::Vector{Int32} basin_index::Vector{Int32} + # per subgrid one relation interpolations::Vector{ScalarInterpolation} - level::Vector{Float64} + # dynamic + subgrid_id_time::Vector{Int32} + basin_index_time::Vector{Int32} + # per subgrid n relations, n being the number of timesteps for that subgrid + interpolations_time::Vector{ScalarInterpolation} + # per subgrid 1 lookup from t to an index in interpolations_time + current_interpolation_index::Vector{IndexLookup} end """ diff --git a/core/src/read.jl b/core/src/read.jl index 6c9e2a34c..f9d5b2fe2 100644 --- a/core/src/read.jl +++ b/core/src/read.jl @@ -1232,17 +1232,40 @@ function FlowDemand(db::DB, config::Config)::FlowDemand ) end +function push_lookup!( + current_interpolation_index::Vector{IndexLookup}, + lookup_index::Vector{Int}, + lookup_time::Vector{Float64}, +) + index_lookup = ConstantInterpolation( + lookup_index, + lookup_time; + extrapolate = true, + cache_parameters = true, + ) + push!(current_interpolation_index, index_lookup) +end + function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid - node_to_basin = Dict(node_id => index for (index, node_id) in enumerate(basin.node_id)) - tables = load_structvector(db, config, BasinSubgridV1) + time = load_structvector(db, config, BasinSubgridTimeV1) + static = load_structvector(db, config, BasinSubgridV1) + + _, _, _, valid = static_and_time_node_ids(db, static, time, "Basin") + if !valid + error("Problems encountered when parsing Subgrid static and time node IDs.") + end + node_to_basin = Dict{Int32, Int}( + Int32(node_id) => index for (index, node_id) in enumerate(basin.node_id) + ) subgrid_ids = Int32[] basin_index = Int32[] interpolations = ScalarInterpolation[] has_error = false - for group in IterTools.groupby(row -> row.subgrid_id, tables) + + for group in IterTools.groupby(row -> row.subgrid_id, static) subgrid_id = first(getproperty.(group, :subgrid_id)) - node_id = NodeID(NodeType.Basin, first(getproperty.(group, :node_id)), db) + node_id = first(getproperty.(group, :node_id)) basin_level = getproperty.(group, :basin_level) subgrid_level = getproperty.(group, :subgrid_level) @@ -1268,9 +1291,75 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid end has_error && error("Invalid Basin / subgrid table.") - level = fill(NaN, length(subgrid_ids)) - return Subgrid(; subgrid_id = subgrid_ids, basin_index, interpolations, level) + subgrid_id_time = Int32[first(time.subgrid_id)] + basin_index_time = Int32[node_to_basin[first(time.node_id)]] + interpolations_time = ScalarInterpolation[] + current_interpolation_index = IndexLookup[] + + # Initialize index_lookup contents + lookup_time = Float64[] + lookup_index = Int[] + + interpolation_index = 0 + for group in IterTools.groupby(row -> (row.subgrid_id, row.time), time) + interpolation_index += 1 + subgrid_id = first(getproperty.(group, :subgrid_id)) + time_group = seconds_since(first(getproperty.(group, :time)), config.starttime) + node_id = first(getproperty.(group, :node_id)) + basin_level = getproperty.(group, :basin_level) + subgrid_level = getproperty.(group, :subgrid_level) + + is_valid = + valid_subgrid(subgrid_id, node_id, node_to_basin, basin_level, subgrid_level) + + if is_valid + # Ensure it doesn't extrapolate before the first value. + pushfirst!(subgrid_level, first(subgrid_level)) + pushfirst!(basin_level, nextfloat(-Inf)) + new_interp = LinearInterpolation( + subgrid_level, + basin_level; + extrapolate = true, + cache_parameters = true, + ) + # # These should only be pushed when the subgrid_id has changed + if subgrid_id_time[end] != subgrid_id + # Push the completed index_lookup of the previous subgrid_id + push_lookup!(current_interpolation_index, lookup_index, lookup_time) + # Push the new subgrid_id and basin_index + push!(subgrid_id_time, subgrid_id) + push!(basin_index_time, node_to_basin[node_id]) + # Start new index_lookup contents + lookup_time = Float64[] + lookup_index = Int[] + end + push!(lookup_index, interpolation_index) + push!(lookup_time, time_group) + push!(interpolations_time, new_interp) + else + has_error = true + end + end + + # Push completed IndexLookup of the last group + if interpolation_index > 0 + push_lookup!(current_interpolation_index, lookup_index, lookup_time) + end + + has_error && error("Invalid Basin / subgrid_time table.") + level = fill(NaN, length(subgrid_ids) + length(subgrid_id_time)) + + return Subgrid(; + level, + subgrid_id = subgrid_ids, + basin_index, + interpolations, + subgrid_id_time, + basin_index_time, + interpolations_time, + current_interpolation_index, + ) end function Allocation(db::DB, config::Config, graph::MetaGraph)::Allocation diff --git a/core/src/validation.jl b/core/src/validation.jl index 81a372421..fb0777e37 100644 --- a/core/src/validation.jl +++ b/core/src/validation.jl @@ -321,8 +321,8 @@ Validate the entries for a single subgrid element. """ function valid_subgrid( subgrid_id::Int32, - node_id::NodeID, - node_to_basin::Dict{NodeID, Int}, + node_id::Int32, + node_to_basin::Dict{Int32, Int}, basin_level::Vector{Float64}, subgrid_level::Vector{Float64}, )::Bool From 24b8fff438be1b325356ca2d4f8bd1242f3c432b Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 16 Dec 2024 13:26:10 +0100 Subject: [PATCH 03/14] update_subgrid_level! --- core/src/callback.jl | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/core/src/callback.jl b/core/src/callback.jl index 467e71060..4e2440210 100644 --- a/core/src/callback.jl +++ b/core/src/callback.jl @@ -671,12 +671,23 @@ function apply_parameter_update!(parameter_update)::Nothing end function update_subgrid_level!(integrator)::Nothing - (; p) = integrator + (; p, t) = integrator du = get_du(integrator) basin_level = p.basin.current_properties.current_level[parent(du)] subgrid = integrator.p.subgrid - for (i, (index, interp)) in enumerate(zip(subgrid.basin_index, subgrid.interpolations)) - subgrid.level[i] = interp(basin_level[index]) + + i = 0 + # First update the all the subgrids with static h(h) relations + for (index, hh_itp) in zip(subgrid.basin_index, subgrid.interpolations) + i += 1 + subgrid.level[i] = hh_itp(basin_level[index]) + end + # Then update the subgrids with dynamic h(h) relations + for (index, lookup) in zip(subgrid.basin_index_time, current_interpolation_index) + i += 1 + itp_index = lookup(t) + hh_itp = subgrid.interpolations_time[itp_index] + subgrid.level[i] = hh_itp(basin_level[index]) end end From 1a86893d9e8eba3178bea214a9c075e873db4ab3 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 16 Dec 2024 15:02:53 +0100 Subject: [PATCH 04/14] Keep subgrid.level vector in order of subgrid_id --- core/src/callback.jl | 19 +++++---- core/src/parameter.jl | 16 +++++--- core/src/read.jl | 41 ++++++++++++------- core/src/write.jl | 11 +++-- .../ribasim_testmodels/two_basin.py | 9 +++- 5 files changed, 64 insertions(+), 32 deletions(-) diff --git a/core/src/callback.jl b/core/src/callback.jl index 4e2440210..d332d7a00 100644 --- a/core/src/callback.jl +++ b/core/src/callback.jl @@ -676,18 +676,23 @@ function update_subgrid_level!(integrator)::Nothing basin_level = p.basin.current_properties.current_level[parent(du)] subgrid = integrator.p.subgrid - i = 0 # First update the all the subgrids with static h(h) relations - for (index, hh_itp) in zip(subgrid.basin_index, subgrid.interpolations) - i += 1 - subgrid.level[i] = hh_itp(basin_level[index]) + for (level_index, basin_index, hh_itp) in zip( + subgrid.level_index_static, + subgrid.basin_index_static, + subgrid.interpolations_static, + ) + subgrid.level[level_index] = hh_itp(basin_level[basin_index]) end # Then update the subgrids with dynamic h(h) relations - for (index, lookup) in zip(subgrid.basin_index_time, current_interpolation_index) - i += 1 + for (level_index, basin_index, lookup) in zip( + subgrid.level_index_time, + subgrid.basin_index_time, + subgrid.current_interpolation_index, + ) itp_index = lookup(t) hh_itp = subgrid.interpolations_time[itp_index] - subgrid.level[i] = hh_itp(basin_level[index]) + subgrid.level[level_index] = hh_itp(basin_level[basin_index]) end end diff --git a/core/src/parameter.jl b/core/src/parameter.jl index a72b5796e..2c4b895d9 100644 --- a/core/src/parameter.jl +++ b/core/src/parameter.jl @@ -881,16 +881,22 @@ end "Subgrid linearly interpolates basin levels." @kwdef struct Subgrid - # cache the current level for static subgrids followed by dynamic subgrids + # level of each subgrid (static and dynamic) ordered by subgrid_id level::Vector{Float64} # static - subgrid_id::Vector{Int32} - basin_index::Vector{Int32} + subgrid_id_static::Vector{Int32} + # index into the basin.current_level vector for each static subgrid_id + basin_index_static::Vector{Int} + # index into the subgrid.level vector for each static subgrid_id + level_index_static::Vector{Int} # per subgrid one relation - interpolations::Vector{ScalarInterpolation} + interpolations_static::Vector{ScalarInterpolation} # dynamic subgrid_id_time::Vector{Int32} - basin_index_time::Vector{Int32} + # index into the basin.current_level vector for each dynamic subgrid_id + basin_index_time::Vector{Int} + # index into the subgrid.level vector for each dynamic subgrid_id + level_index_time::Vector{Int} # per subgrid n relations, n being the number of timesteps for that subgrid interpolations_time::Vector{ScalarInterpolation} # per subgrid 1 lookup from t to an index in interpolations_time diff --git a/core/src/read.jl b/core/src/read.jl index f9d5b2fe2..7e068a906 100644 --- a/core/src/read.jl +++ b/core/src/read.jl @@ -1258,9 +1258,9 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid node_to_basin = Dict{Int32, Int}( Int32(node_id) => index for (index, node_id) in enumerate(basin.node_id) ) - subgrid_ids = Int32[] - basin_index = Int32[] - interpolations = ScalarInterpolation[] + subgrid_id_static = Int32[] + basin_index_static = Int[] + interpolations_static = ScalarInterpolation[] has_error = false for group in IterTools.groupby(row -> row.subgrid_id, static) @@ -1276,15 +1276,15 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid # Ensure it doesn't extrapolate before the first value. pushfirst!(subgrid_level, first(subgrid_level)) pushfirst!(basin_level, nextfloat(-Inf)) - new_interp = LinearInterpolation( + hh_itp = LinearInterpolation( subgrid_level, basin_level; extrapolate = true, cache_parameters = true, ) - push!(subgrid_ids, subgrid_id) - push!(basin_index, node_to_basin[node_id]) - push!(interpolations, new_interp) + push!(subgrid_id_static, subgrid_id) + push!(basin_index_static, node_to_basin[node_id]) + push!(interpolations_static, hh_itp) else has_error = true end @@ -1293,7 +1293,7 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid has_error && error("Invalid Basin / subgrid table.") subgrid_id_time = Int32[first(time.subgrid_id)] - basin_index_time = Int32[node_to_basin[first(time.node_id)]] + basin_index_time = Int[node_to_basin[first(time.node_id)]] interpolations_time = ScalarInterpolation[] current_interpolation_index = IndexLookup[] @@ -1317,7 +1317,7 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid # Ensure it doesn't extrapolate before the first value. pushfirst!(subgrid_level, first(subgrid_level)) pushfirst!(basin_level, nextfloat(-Inf)) - new_interp = LinearInterpolation( + hh_itp = LinearInterpolation( subgrid_level, basin_level; extrapolate = true, @@ -1336,7 +1336,7 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid end push!(lookup_index, interpolation_index) push!(lookup_time, time_group) - push!(interpolations_time, new_interp) + push!(interpolations_time, hh_itp) else has_error = true end @@ -1348,15 +1348,28 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid end has_error && error("Invalid Basin / subgrid_time table.") - level = fill(NaN, length(subgrid_ids) + length(subgrid_id_time)) + level = fill(NaN, length(subgrid_id_static) + length(subgrid_id_time)) + + # Find the level indices + level_index_static = zeros(Int, length(subgrid_id_static)) + level_index_time = zeros(Int, length(subgrid_id_time)) + subgrid_ids = sort(vcat(subgrid_id_static, subgrid_id_time)) + for (i, subgrid_id) in enumerate(subgrid_id_static) + level_index_static[i] = findsorted(subgrid_ids, subgrid_id) + end + for (i, subgrid_id) in enumerate(subgrid_id_time) + level_index_time[i] = findsorted(subgrid_ids, subgrid_id) + end return Subgrid(; level, - subgrid_id = subgrid_ids, - basin_index, - interpolations, + subgrid_id_static, + basin_index_static, + level_index_static, + interpolations_static, subgrid_id_time, basin_index_time, + level_index_time, interpolations_time, current_interpolation_index, ) diff --git a/core/src/write.jl b/core/src/write.jl index b81e5d01d..4ccc91312 100644 --- a/core/src/write.jl +++ b/core/src/write.jl @@ -376,11 +376,14 @@ function subgrid_level_table( (; t, saveval) = saved.subgrid_level subgrid = integrator.p.subgrid - nelem = length(subgrid.subgrid_id) + nelem = length(subgrid.level) ntsteps = length(t) time = repeat(datetime_since.(t, config.starttime); inner = nelem) - subgrid_id = repeat(subgrid.subgrid_id; outer = ntsteps) + subgrid_id = repeat( + sort(vcat(subgrid.subgrid_id_static, subgrid.subgrid_id_time)); + outer = ntsteps, + ) subgrid_level = FlatVector(saveval) return (; time, subgrid_id, subgrid_level) end @@ -412,9 +415,9 @@ function write_arrow( mkpath(dirname(path)) try Arrow.write(path, table; compress, metadata) - catch + catch e @error "Failed to write results, file may be locked." path - error("Failed to write results.") + rethrow(e) end return nothing end diff --git a/python/ribasim_testmodels/ribasim_testmodels/two_basin.py b/python/ribasim_testmodels/ribasim_testmodels/two_basin.py index acc52c4aa..0aecdc47e 100644 --- a/python/ribasim_testmodels/ribasim_testmodels/two_basin.py +++ b/python/ribasim_testmodels/ribasim_testmodels/two_basin.py @@ -1,6 +1,6 @@ from typing import Any -from ribasim.config import Node +from ribasim.config import Node, Results from ribasim.input_base import TableModel from ribasim.model import Model from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve @@ -18,7 +18,12 @@ def two_basin_model() -> Model: infiltrates in the left basin, and exfiltrates in the right basin. The right basin fills up and discharges over the rating curve. """ - model = Model(starttime="2020-01-01", endtime="2021-01-01", crs="EPSG:28992") + model = Model( + starttime="2020-01-01", + endtime="2021-01-01", + crs="EPSG:28992", + results=Results(subgrid=True), + ) model.flow_boundary.add( Node(1, Point(0, 0)), [flow_boundary.Static(flow_rate=[1e-2])] From d217094a22b6d43b5b7f7400859ae511ab060914 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 16 Dec 2024 15:27:18 +0100 Subject: [PATCH 05/14] Add a test --- core/test/run_models_test.jl | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/core/test/run_models_test.jl b/core/test/run_models_test.jl index 72c541320..ce8593cef 100644 --- a/core/test/run_models_test.jl +++ b/core/test/run_models_test.jl @@ -583,3 +583,28 @@ end @test all(isapprox.(Δinf[1:2:end], 25.0; atol = 1e-10)) @test all(Δinf[2:2:end] .== 0.0) end + +@testitem "two_basin" begin + using DataFrames: DataFrame, nrow + using Dates: DateTime + import BasicModelInterface as BMI + + toml_path = normpath(@__DIR__, "../../generated_testmodels/two_basin/ribasim.toml") + model = Ribasim.run(toml_path) + df = DataFrame(Ribasim.subgrid_level_table(model)) + + ntime = 367 + @test nrow(df) == ntime * 2 + @test df.subgrid_id == repeat(1:2; outer = ntime) + @test extrema(df.time) == (DateTime(2020), DateTime(2021)) + @test all(df.subgrid_level[1:2] .== 0.01) + + # After a month the h(h) of subgrid_id 2 increases by a meter + i_change = searchsortedfirst(df.time, DateTime(2020, 2)) + @test df.subgrid_level[i_change + 1] - df.subgrid_level[i_change - 1] ≈ 1.0f0 + + # Besides the 1 meter shift the h(h) relations are 1:1 + basin_level = copy(BMI.get_value_ptr(model, "basin.level")) + basin_level[2] += 1 + @test basin_level ≈ df.subgrid_level[(end - 1):end] +end From e62c0aab0ac02e6ff48252e19d91a412a5fab62c Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 16 Dec 2024 15:40:00 +0100 Subject: [PATCH 06/14] More --- core/test/run_models_test.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/core/test/run_models_test.jl b/core/test/run_models_test.jl index 6e791b79a..d63697ea4 100644 --- a/core/test/run_models_test.jl +++ b/core/test/run_models_test.jl @@ -607,4 +607,5 @@ end basin_level = copy(BMI.get_value_ptr(model, "basin.level")) basin_level[2] += 1 @test basin_level ≈ df.subgrid_level[(end - 1):end] + @test basin_level ≈ model.integrator.p.subgrid.level end From 8e07c468ccb7c160ef7581ae67b2ad425f1a30ad Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 16 Dec 2024 16:19:15 +0100 Subject: [PATCH 07/14] Make this work for non subgrid models --- core/src/read.jl | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/core/src/read.jl b/core/src/read.jl index 7e068a906..a54f3515e 100644 --- a/core/src/read.jl +++ b/core/src/read.jl @@ -197,7 +197,8 @@ function static_and_time_node_ids( db::DB, static::StructVector, time::StructVector, - node_type::String, + node_type::String; + is_complete::Bool = true, )::Tuple{Set{NodeID}, Set{NodeID}, Vector{NodeID}, Bool} ids = get_ids(db, node_type) idx = searchsortedfirst.(Ref(ids), static.node_id) @@ -211,7 +212,7 @@ function static_and_time_node_ids( errors = true @error "$node_type cannot be in both static and time tables, found these node IDs in both: $doubles." end - if !issetequal(node_ids, union(static_node_ids, time_node_ids)) + if is_complete && !issetequal(node_ids, union(static_node_ids, time_node_ids)) errors = true @error "$node_type node IDs don't match." end @@ -1250,7 +1251,9 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid time = load_structvector(db, config, BasinSubgridTimeV1) static = load_structvector(db, config, BasinSubgridV1) - _, _, _, valid = static_and_time_node_ids(db, static, time, "Basin") + # Since not all Basins need to have subgrids, don't enforce completeness. + _, _, _, valid = + static_and_time_node_ids(db, static, time, "Basin"; is_complete = false) if !valid error("Problems encountered when parsing Subgrid static and time node IDs.") end @@ -1291,12 +1294,17 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid end has_error && error("Invalid Basin / subgrid table.") - - subgrid_id_time = Int32[first(time.subgrid_id)] - basin_index_time = Int[node_to_basin[first(time.node_id)]] + subgrid_id_time = Int32[] + basin_index_time = Int[] interpolations_time = ScalarInterpolation[] current_interpolation_index = IndexLookup[] + # Push the first subgrid_id and basin_index + if length(time) > 0 + push!(subgrid_id_time, first(time.subgrid_id)) + push!(basin_index_time, node_to_basin[first(time.node_id)]) + end + # Initialize index_lookup contents lookup_time = Float64[] lookup_index = Int[] From 0362381172b13553b6ed509b4d4c435efdd65439 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 16 Dec 2024 16:24:48 +0100 Subject: [PATCH 08/14] Update subgrid validation test --- core/test/validation_test.jl | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/core/test/validation_test.jl b/core/test/validation_test.jl index e46c0ef74..cf8fc4bf6 100644 --- a/core/test/validation_test.jl +++ b/core/test/validation_test.jl @@ -285,30 +285,24 @@ end using Ribasim: valid_subgrid, NodeID using Logging - node_to_basin = Dict(NodeID(:Basin, 9, 1) => 1) + node_to_basin = Dict(Int32(9) => 1) logger = TestLogger() with_logger(logger) do - @test !valid_subgrid( - Int32(1), - NodeID(:Basin, 10, 1), - node_to_basin, - [-1.0, 0.0], - [-1.0, 0.0], - ) + @test !valid_subgrid(Int32(1), Int32(10), node_to_basin, [-1.0, 0.0], [-1.0, 0.0]) end @test length(logger.logs) == 1 @test logger.logs[1].level == Error @test logger.logs[1].message == "The node_id of the Basin / subgrid does not exist." - @test logger.logs[1].kwargs[:node_id] == NodeID(:Basin, 10, 1) + @test logger.logs[1].kwargs[:node_id] == Int32(10) @test logger.logs[1].kwargs[:subgrid_id] == 1 logger = TestLogger() with_logger(logger) do @test !valid_subgrid( Int32(1), - NodeID(:Basin, 9, 1), + Int32(9), node_to_basin, [-1.0, 0.0, 0.0], [-1.0, 0.0, 0.0], From d633a16525731a9ef9ee5a8739e9282ff1418a7d Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Tue, 17 Dec 2024 16:35:42 +0100 Subject: [PATCH 09/14] Address more comments --- core/src/read.jl | 88 +++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/core/src/read.jl b/core/src/read.jl index a54f3515e..d3dbb3acf 100644 --- a/core/src/read.jl +++ b/core/src/read.jl @@ -192,6 +192,10 @@ Validate the split of node IDs between static and time tables. For node types that can have a part of the parameters defined statically and a part dynamically, this checks if each ID is defined exactly once in either table. + +The `is_complete` argument allows disabling the check that all Node IDs of type `node_type` +are either in the `static` or `time` table. +This is not required for Subgrid since not all Basins need to have subgrids. """ function static_and_time_node_ids( db::DB, @@ -1264,7 +1268,6 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid subgrid_id_static = Int32[] basin_index_static = Int[] interpolations_static = ScalarInterpolation[] - has_error = false for group in IterTools.groupby(row -> row.subgrid_id, static) subgrid_id = first(getproperty.(group, :subgrid_id)) @@ -1274,26 +1277,22 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid is_valid = valid_subgrid(subgrid_id, node_id, node_to_basin, basin_level, subgrid_level) - - if is_valid - # Ensure it doesn't extrapolate before the first value. - pushfirst!(subgrid_level, first(subgrid_level)) - pushfirst!(basin_level, nextfloat(-Inf)) - hh_itp = LinearInterpolation( - subgrid_level, - basin_level; - extrapolate = true, - cache_parameters = true, - ) - push!(subgrid_id_static, subgrid_id) - push!(basin_index_static, node_to_basin[node_id]) - push!(interpolations_static, hh_itp) - else - has_error = true - end + !is_valid && error("Invalid Basin / subgrid table.") + + # Ensure it doesn't extrapolate before the first value. + pushfirst!(subgrid_level, first(subgrid_level)) + pushfirst!(basin_level, nextfloat(-Inf)) + hh_itp = LinearInterpolation( + subgrid_level, + basin_level; + extrapolate = true, + cache_parameters = true, + ) + push!(subgrid_id_static, subgrid_id) + push!(basin_index_static, node_to_basin[node_id]) + push!(interpolations_static, hh_itp) end - has_error && error("Invalid Basin / subgrid table.") subgrid_id_time = Int32[] basin_index_time = Int[] interpolations_time = ScalarInterpolation[] @@ -1320,34 +1319,31 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid is_valid = valid_subgrid(subgrid_id, node_id, node_to_basin, basin_level, subgrid_level) - - if is_valid - # Ensure it doesn't extrapolate before the first value. - pushfirst!(subgrid_level, first(subgrid_level)) - pushfirst!(basin_level, nextfloat(-Inf)) - hh_itp = LinearInterpolation( - subgrid_level, - basin_level; - extrapolate = true, - cache_parameters = true, - ) - # # These should only be pushed when the subgrid_id has changed - if subgrid_id_time[end] != subgrid_id - # Push the completed index_lookup of the previous subgrid_id - push_lookup!(current_interpolation_index, lookup_index, lookup_time) - # Push the new subgrid_id and basin_index - push!(subgrid_id_time, subgrid_id) - push!(basin_index_time, node_to_basin[node_id]) - # Start new index_lookup contents - lookup_time = Float64[] - lookup_index = Int[] - end - push!(lookup_index, interpolation_index) - push!(lookup_time, time_group) - push!(interpolations_time, hh_itp) - else - has_error = true + !is_valid && error("Invalid Basin / subgrid_time table.") + + # Ensure it doesn't extrapolate before the first value. + pushfirst!(subgrid_level, first(subgrid_level)) + pushfirst!(basin_level, nextfloat(-Inf)) + hh_itp = LinearInterpolation( + subgrid_level, + basin_level; + extrapolate = true, + cache_parameters = true, + ) + # # These should only be pushed when the subgrid_id has changed + if subgrid_id_time[end] != subgrid_id + # Push the completed index_lookup of the previous subgrid_id + push_lookup!(current_interpolation_index, lookup_index, lookup_time) + # Push the new subgrid_id and basin_index + push!(subgrid_id_time, subgrid_id) + push!(basin_index_time, node_to_basin[node_id]) + # Start new index_lookup contents + lookup_time = Float64[] + lookup_index = Int[] end + push!(lookup_index, interpolation_index) + push!(lookup_time, time_group) + push!(interpolations_time, hh_itp) end # Push completed IndexLookup of the last group From ebe7ddaae5df96b2706b12079f829c28ea93f93a Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Fri, 20 Dec 2024 11:49:15 +0100 Subject: [PATCH 10/14] Rest of review comments --- core/src/read.jl | 1 - core/src/schema.jl | 3 +-- core/test/run_models_test.jl | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/read.jl b/core/src/read.jl index d3dbb3acf..7bc6c361d 100644 --- a/core/src/read.jl +++ b/core/src/read.jl @@ -1351,7 +1351,6 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid push_lookup!(current_interpolation_index, lookup_index, lookup_time) end - has_error && error("Invalid Basin / subgrid_time table.") level = fill(NaN, length(subgrid_id_static) + length(subgrid_id_time)) # Find the level indices diff --git a/core/src/schema.jl b/core/src/schema.jl index 58b32088e..4e5d926bb 100644 --- a/core/src/schema.jl +++ b/core/src/schema.jl @@ -62,8 +62,7 @@ function nodetype( # Special case last elements that need an underscore if startswith(last_element, "concentration") && length(last_element) > 13 elements[end] = "concentration_$(last_element[14:end])" - end - if last_element == "subgridtime" + elseif last_element == "subgridtime" elements[end] = "subgrid_time" end if isnode(sv) diff --git a/core/test/run_models_test.jl b/core/test/run_models_test.jl index d63697ea4..2dc929c73 100644 --- a/core/test/run_models_test.jl +++ b/core/test/run_models_test.jl @@ -597,6 +597,7 @@ end @test nrow(df) == ntime * 2 @test df.subgrid_id == repeat(1:2; outer = ntime) @test extrema(df.time) == (DateTime(2020), DateTime(2021)) + @test allunique(df.time[1:2:(end - 1)]) @test all(df.subgrid_level[1:2] .== 0.01) # After a month the h(h) of subgrid_id 2 increases by a meter From 6234c1b135ef8bf0d06f57aee3b689cd2b57b6fe Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Fri, 20 Dec 2024 11:53:56 +0100 Subject: [PATCH 11/14] Apply suggestions from code review Co-authored-by: Maarten Pronk --- core/src/parameter.jl | 12 ++++++++---- core/src/read.jl | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/core/src/parameter.jl b/core/src/parameter.jl index 2c4b895d9..e74c8d147 100644 --- a/core/src/parameter.jl +++ b/core/src/parameter.jl @@ -115,7 +115,7 @@ const ScalarInterpolation = LinearInterpolation{ (1,), } -"ConstantInterpolation from a Float64 to an Int, used to look up indices" +"ConstantInterpolation from a Float64 to an Int, used to look up indices over time" const IndexLookup = ConstantInterpolation{Vector{Int64}, Vector{Float64}, Vector{Float64}, Int64, (1,)} @@ -881,9 +881,11 @@ end "Subgrid linearly interpolates basin levels." @kwdef struct Subgrid - # level of each subgrid (static and dynamic) ordered by subgrid_id + # current level of each subgrid (static and dynamic) ordered by subgrid_id level::Vector{Float64} - # static + + # Static part + # Static subgrid ids subgrid_id_static::Vector{Int32} # index into the basin.current_level vector for each static subgrid_id basin_index_static::Vector{Int} @@ -891,7 +893,9 @@ end level_index_static::Vector{Int} # per subgrid one relation interpolations_static::Vector{ScalarInterpolation} - # dynamic + + # Dynamic part + # Dynamic subgrid ids subgrid_id_time::Vector{Int32} # index into the basin.current_level vector for each dynamic subgrid_id basin_index_time::Vector{Int} diff --git a/core/src/read.jl b/core/src/read.jl index 7bc6c361d..df4fadfe2 100644 --- a/core/src/read.jl +++ b/core/src/read.jl @@ -188,7 +188,7 @@ function parse_static_and_time( end """ -Validate the split of node IDs between static and time tables. +Retrieve and validate the split of node IDs between static and time tables. For node types that can have a part of the parameters defined statically and a part dynamically, this checks if each ID is defined exactly once in either table. From 7cd1ded3b9a4417e29bb60c7d0c9245e50028915 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Fri, 20 Dec 2024 15:17:15 +0100 Subject: [PATCH 12/14] Fix bad resolve --- core/src/read.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/read.jl b/core/src/read.jl index 773ad7d1d..0cd02a706 100644 --- a/core/src/read.jl +++ b/core/src/read.jl @@ -201,7 +201,7 @@ function static_and_time_node_ids( db::DB, static::StructVector, time::StructVector, - node_type::NodeType.T, + node_type::NodeType.T; is_complete::Bool = true, )::Tuple{Set{NodeID}, Set{NodeID}, Vector{NodeID}, Bool} node_ids = get_node_ids(db, node_type) From dcf6182ecb93e324f43609f956bc4e3189eb92aa Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Fri, 20 Dec 2024 15:57:09 +0100 Subject: [PATCH 13/14] Better --- core/src/read.jl | 10 ++++++++-- docs/reference/node/basin.qmd | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/read.jl b/core/src/read.jl index 0cd02a706..abe277bb3 100644 --- a/core/src/read.jl +++ b/core/src/read.jl @@ -1255,7 +1255,6 @@ end function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid time = load_structvector(db, config, BasinSubgridTimeV1) static = load_structvector(db, config, BasinSubgridV1) - node_table = get_node_ids(db, NodeType.Basin) # Since not all Basins need to have subgrids, don't enforce completeness. _, _, _, valid = @@ -1271,6 +1270,8 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid basin_index_static = Int[] interpolations_static = ScalarInterpolation[] + # In the static table, each subgrid ID has 1 h(h) relation. We process one relation + # at a time and push the results to the respective vectors. for group in IterTools.groupby(row -> row.subgrid_id, static) subgrid_id = first(getproperty.(group, :subgrid_id)) node_id = first(getproperty.(group, :node_id)) @@ -1311,6 +1312,11 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid lookup_index = Int[] interpolation_index = 0 + # In the time table, each subgrid ID can have a different number of relations over time. + # We group over the combination of subgrid ID and time such that this group has 1 h(h) relation. + # We process one relation at a time and push the results to the respective vectors. + # Some vectors are pushed only when the subgrid_id has changed. This can be done in + # sequence since it is first sorted by subgrid_id and then by time. for group in IterTools.groupby(row -> (row.subgrid_id, row.time), time) interpolation_index += 1 subgrid_id = first(getproperty.(group, :subgrid_id)) @@ -1332,7 +1338,7 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid extrapolate = true, cache_parameters = true, ) - # # These should only be pushed when the subgrid_id has changed + # These should only be pushed when the subgrid_id has changed if subgrid_id_time[end] != subgrid_id # Push the completed index_lookup of the previous subgrid_id push_lookup!(current_interpolation_index, lookup_index, lookup_time) diff --git a/docs/reference/node/basin.qmd b/docs/reference/node/basin.qmd index 62abafde9..a7fea057b 100644 --- a/docs/reference/node/basin.qmd +++ b/docs/reference/node/basin.qmd @@ -395,7 +395,7 @@ Generally, to create physically meaningful subgrid water levels, the subgrid tab This table is the transient form of the Subgrid table. The only difference is that a time column is added. -The table must by sorted by time, and per time it must be sorted by `subgrid_id`. +The table must by sorted by `subgrid_id`, and per `subgrid_id` it must be sorted by `time`. With this the subgrid relations can be updated over time. Note that a `node_id` can be either in this table or in the static one, but not both. That means for each Basin all subgrid relations are either static or dynamic. From 207406d2e0b26218f563165b3c36b32e6cfdcacc Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Fri, 20 Dec 2024 16:08:57 +0100 Subject: [PATCH 14/14] Work! --- core/src/read.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/read.jl b/core/src/read.jl index abe277bb3..475634ea7 100644 --- a/core/src/read.jl +++ b/core/src/read.jl @@ -1258,7 +1258,7 @@ function Subgrid(db::DB, config::Config, basin::Basin)::Subgrid # Since not all Basins need to have subgrids, don't enforce completeness. _, _, _, valid = - static_and_time_node_ids(db, static, time, "Basin"; is_complete = false) + static_and_time_node_ids(db, static, time, NodeType.Basin; is_complete = false) if !valid error("Problems encountered when parsing Subgrid static and time node IDs.") end