diff --git a/core/src/allocation_optim.jl b/core/src/allocation_optim.jl index 812875639..9e51d441b 100644 --- a/core/src/allocation_optim.jl +++ b/core/src/allocation_optim.jl @@ -360,7 +360,7 @@ Get several variables associated with a basin: - Its current storage - The allocation update interval - The influx (sum of instantaneous vertical fluxes of the basin) -- The index of the connected allocation_target node (0 if such a +- The index of the connected target_level node (0 if such a node does not exist) - The index of the basin """ @@ -370,7 +370,7 @@ function get_basin_data( u::ComponentVector, node_id::NodeID, ) - (; graph, basin, allocation_target) = p + (; graph, basin, target_level) = p (; Δt_allocation) = allocation_model @assert node_id.type == NodeType.Basin influx = get_flow(graph, node_id, 0.0) @@ -378,20 +378,19 @@ function get_basin_data( storage_basin = u.storage[basin_idx] control_inneighbors = inneighbor_labels_type(graph, node_id, EdgeType.control) if isempty(control_inneighbors) - allocation_target_idx = 0 + target_level_idx = 0 else - allocation_target_node_id = first(control_inneighbors) - allocation_target_idx = - findsorted(allocation_target.node_id, allocation_target_node_id) + target_level_node_id = first(control_inneighbors) + target_level_idx = findsorted(target_level.node_id, target_level_node_id) end - return storage_basin, Δt_allocation, influx, allocation_target_idx, basin_idx + return storage_basin, Δt_allocation, influx, target_level_idx, basin_idx end """ Get the capacity of the basin, i.e. the maximum flow that can be abstracted from the basin if it is in a state of surplus storage (0 if no reference levels are provided by -a allocation_target node). +a target_level node). Storages are converted to flows by dividing by the allocation timestep. """ function get_basin_capacity( @@ -401,14 +400,14 @@ function get_basin_capacity( t::Float64, node_id::NodeID, )::Float64 - (; allocation_target) = p + (; target_level) = p @assert node_id.type == NodeType.Basin - storage_basin, Δt_allocation, influx, allocation_target_idx, basin_idx = + storage_basin, Δt_allocation, influx, target_level_idx, basin_idx = get_basin_data(allocation_model, p, u, node_id) - if iszero(allocation_target_idx) + if iszero(target_level_idx) return 0.0 else - level_max = allocation_target.max_level[allocation_target_idx](t) + level_max = target_level.max_level[target_level_idx](t) storage_max = get_storage_from_level(p.basin, basin_idx, level_max) return max(0.0, (storage_basin - storage_max) / Δt_allocation + influx) end @@ -417,7 +416,7 @@ end """ Get the demand of the basin, i.e. how large a flow the basin needs to get to its minimum target level (0 if no -reference levels are provided by a allocation_target node). +reference levels are provided by a target_level node). Storages are converted to flows by dividing by the allocation timestep. """ function get_basin_demand( @@ -427,14 +426,14 @@ function get_basin_demand( t::Float64, node_id::NodeID, )::Float64 - (; allocation_target) = p + (; target_level) = p @assert node_id.type == NodeType.Basin - storage_basin, Δt_allocation, influx, allocation_target_idx, basin_idx = + storage_basin, Δt_allocation, influx, target_level_idx, basin_idx = get_basin_data(allocation_model, p, u, node_id) - if iszero(allocation_target_idx) + if iszero(target_level_idx) return 0.0 else - level_min = allocation_target.min_level[allocation_target_idx](t) + level_min = target_level.min_level[target_level_idx](t) storage_min = get_storage_from_level(p.basin, basin_idx, level_min) return max(0.0, (storage_min - storage_basin) / Δt_allocation - influx) end diff --git a/core/src/parameter.jl b/core/src/parameter.jl index 1f58ac2b3..fc8c8feb7 100644 --- a/core/src/parameter.jl +++ b/core/src/parameter.jl @@ -519,12 +519,12 @@ struct User <: AbstractParameterNode end """ -node_id: node ID of the AllocationTarget node +node_id: node ID of the TargetLevel node min_level: The minimum target level of the connected basin(s) max_level: The maximum target level of the connected basin(s) priority: If in a shortage state, the priority of the demand of the connected basin(s) """ -struct AllocationTarget +struct TargetLevel node_id::Vector{NodeID} min_level::Vector{LinearInterpolation} max_level::Vector{LinearInterpolation} @@ -573,6 +573,6 @@ struct Parameters{T, C1, C2} discrete_control::DiscreteControl pid_control::PidControl{T} user::User - allocation_target::AllocationTarget + target_level::TargetLevel subgrid::Subgrid end diff --git a/core/src/read.jl b/core/src/read.jl index 85abc3651..c6df7eb85 100644 --- a/core/src/read.jl +++ b/core/src/read.jl @@ -765,25 +765,25 @@ function User(db::DB, config::Config)::User ) end -function AllocationTarget(db::DB, config::Config)::AllocationTarget - static = load_structvector(db, config, AllocationTargetStaticV1) - time = load_structvector(db, config, AllocationTargetTimeV1) +function TargetLevel(db::DB, config::Config)::TargetLevel + static = load_structvector(db, config, TargetLevelStaticV1) + time = load_structvector(db, config, TargetLevelTimeV1) parsed_parameters, valid = parse_static_and_time( db, config, - "AllocationTarget"; + "TargetLevel"; static, time, time_interpolatables = [:min_level, :max_level], ) if !valid - error("Errors occurred when parsing AllocationTarget data.") + error("Errors occurred when parsing TargetLevel data.") end - return AllocationTarget( - NodeID.(NodeType.AllocationTarget, parsed_parameters.node_id), + return TargetLevel( + NodeID.(NodeType.TargetLevel, parsed_parameters.node_id), parsed_parameters.min_level, parsed_parameters.max_level, parsed_parameters.priority, @@ -875,7 +875,7 @@ function Parameters(db::DB, config::Config)::Parameters discrete_control = DiscreteControl(db, config) pid_control = PidControl(db, config, chunk_sizes) user = User(db, config) - allocation_target = AllocationTarget(db, config) + target_level = TargetLevel(db, config) basin = Basin(db, config, chunk_sizes) subgrid_level = Subgrid(db, config, basin) @@ -913,7 +913,7 @@ function Parameters(db::DB, config::Config)::Parameters discrete_control, pid_control, user, - allocation_target, + target_level, subgrid_level, ) diff --git a/core/src/schema.jl b/core/src/schema.jl index 0cc806824..bb4004fe6 100644 --- a/core/src/schema.jl +++ b/core/src/schema.jl @@ -23,8 +23,8 @@ @schema "ribasim.outlet.static" OutletStatic @schema "ribasim.user.static" UserStatic @schema "ribasim.user.time" UserTime -@schema "ribasim.allocationtarget.static" AllocationTargetStatic -@schema "ribasim.allocationtarget.time" AllocationTargetTime +@schema "ribasim.targetlevel.static" TargetLevelStatic +@schema "ribasim.targetlevel.time" TargetLevelTime const delimiter = " / " tablename(sv::Type{SchemaVersion{T, N}}) where {T, N} = tablename(sv()) @@ -238,14 +238,14 @@ end priority::Int end -@version AllocationTargetStaticV1 begin +@version TargetLevelStaticV1 begin node_id::Int min_level::Float64 max_level::Float64 priority::Int end -@version AllocationTargetTimeV1 begin +@version TargetLevelTimeV1 begin node_id::Int time::DateTime min_level::Float64 diff --git a/core/src/util.jl b/core/src/util.jl index 12c26d59f..e9165203e 100644 --- a/core/src/util.jl +++ b/core/src/util.jl @@ -646,20 +646,20 @@ function get_all_priorities(db::DB, config::Config)::Vector{Int} priorities = Set{Int}() # TODO: Is there a way to automatically grab all tables with a priority column? - for type in [UserStaticV1, UserTimeV1, AllocationTargetStaticV1, AllocationTargetTimeV1] + for type in [UserStaticV1, UserTimeV1, TargetLevelStaticV1, TargetLevelTimeV1] union!(priorities, load_structvector(db, config, type).priority) end return sort(unique(priorities)) end function get_basin_priority(p::Parameters, node_id::NodeID)::Int - (; graph, allocation_target) = p + (; graph, target_level) = p @assert node_id.type == NodeType.Basin inneighbors_control = inneighbor_labels_type(graph, node_id, EdgeType.control) if isempty(inneighbors_control) return 0 else - idx = findsorted(allocation_target.node_id, only(inneighbors_control)) - return allocation_target.priority[idx] + idx = findsorted(target_level.node_id, only(inneighbors_control)) + return target_level.priority[idx] end end diff --git a/core/src/validation.jl b/core/src/validation.jl index caf0785c7..ef213b708 100644 --- a/core/src/validation.jl +++ b/core/src/validation.jl @@ -3,7 +3,7 @@ neighbortypes(nodetype::Symbol) = neighbortypes(Val(nodetype)) neighbortypes(::Val{:pump}) = Set((:basin, :fractional_flow, :terminal, :level_boundary)) neighbortypes(::Val{:outlet}) = Set((:basin, :fractional_flow, :terminal, :level_boundary)) neighbortypes(::Val{:user}) = Set((:basin, :fractional_flow, :terminal, :level_boundary)) -neighbortypes(::Val{:allocation_target}) = Set((:basin,)) +neighbortypes(::Val{:target_level}) = Set((:basin,)) neighbortypes(::Val{:basin}) = Set(( :linear_resistance, :tabulated_rating_curve, @@ -58,7 +58,7 @@ n_neighbor_bounds_flow(::Val{:Terminal}) = n_neighbor_bounds(1, typemax(Int), 0, n_neighbor_bounds_flow(::Val{:PidControl}) = n_neighbor_bounds(0, 0, 0, 0) n_neighbor_bounds_flow(::Val{:DiscreteControl}) = n_neighbor_bounds(0, 0, 0, 0) n_neighbor_bounds_flow(::Val{:User}) = n_neighbor_bounds(1, 1, 1, 1) -n_neighbor_bounds_flow(::Val{:AllocationTarget}) = n_neighbor_bounds(0, 0, 0, 0) +n_neighbor_bounds_flow(::Val{:TargetLevel}) = n_neighbor_bounds(0, 0, 0, 0) n_neighbor_bounds_flow(nodetype) = error("'n_neighbor_bounds_flow' not defined for $nodetype.") @@ -77,8 +77,7 @@ n_neighbor_bounds_control(::Val{:PidControl}) = n_neighbor_bounds(0, 1, 1, 1) n_neighbor_bounds_control(::Val{:DiscreteControl}) = n_neighbor_bounds(0, 0, 1, typemax(Int)) n_neighbor_bounds_control(::Val{:User}) = n_neighbor_bounds(0, 0, 0, 0) -n_neighbor_bounds_control(::Val{:AllocationTarget}) = - n_neighbor_bounds(0, 0, 1, typemax(Int)) +n_neighbor_bounds_control(::Val{:TargetLevel}) = n_neighbor_bounds(0, 0, 1, typemax(Int)) n_neighbor_bounds_control(nodetype) = error("'n_neighbor_bounds_control' not defined for $nodetype.") diff --git a/core/test/allocation_test.jl b/core/test/allocation_test.jl index 4a819dd16..cf12a35ca 100644 --- a/core/test/allocation_test.jl +++ b/core/test/allocation_test.jl @@ -321,8 +321,7 @@ end end @testitem "Allocation level control" begin - toml_path = - normpath(@__DIR__, "../../generated_testmodels/allocation_target/ribasim.toml") + toml_path = normpath(@__DIR__, "../../generated_testmodels/target_level/ribasim.toml") @test ispath(toml_path) model = Ribasim.run(toml_path) @@ -330,7 +329,7 @@ end t = Ribasim.timesteps(model) p = model.integrator.p - (; user, graph, allocation, basin, allocation_target) = p + (; user, graph, allocation, basin, target_level) = p d = user.demand_itp[1][2](0) ϕ = 1e-3 # precipitation @@ -341,7 +340,7 @@ end 0, ) A = basin.area[1][1] - l_max = allocation_target.max_level[1](0) + l_max = target_level.max_level[1](0) Δt_allocation = allocation.allocation_models[1].Δt_allocation # Until the first allocation solve, the user abstracts fully diff --git a/docs/core/usage.qmd b/docs/core/usage.qmd index 28cbcca9c..4720a0cae 100644 --- a/docs/core/usage.qmd +++ b/docs/core/usage.qmd @@ -198,9 +198,9 @@ name it must have in the database if it is stored there. - User: sets water usage demands at certain priorities - `User / static`: demands - `User / time`: dynamic demands -- AllocationTarget: Indicates minimum and maximum target level of connected basins for allocation - - `AllocationTarget / static`: static target levels - - `AllocationTarget / time`: dynamic target levels +- TargetLevel: Indicates minimum and maximum target level of connected basins for allocation + - `TargetLevel / static`: static target levels + - `TargetLevel / time`: dynamic target levels - Terminal: Water sink without state or properties - `Terminal / static`: - (only node IDs) - DiscreteControl: Set parameters of other nodes based on model state conditions (e.g. basin level) @@ -464,12 +464,12 @@ demand | Float64 | $m^3 s^{-1}$ | - return_factor | Float64 | - | between [0 - 1] min_level | Float64 | $m$ | - -# AllocationTarget {#sec-allocation_target} +# TargetLevel {#sec-target_level} -An `AllocationTarget` node associates a minimum and a maximum level with connected basins to be used by the allocation algorithm. +An `TargetLevel` node associates a minimum and a maximum level with connected basins to be used by the allocation algorithm. Below the minimum level the basin has a demand of the supplied priority, above the maximum level the basin acts as a source, used by all nodes with demands in order of priority. -The same `AllocationTarget` node can be used for basins in different subnetworks. +The same `TargetLevel` node can be used for basins in different subnetworks. column | type | unit | restriction ------------- | ------- | ------------ | ----------- @@ -478,10 +478,10 @@ min_level | Float64 | $m$ | - max_level | Float64 | $m$ | - priority | Int | - | positive -## AllocationTarget / time +## TargetLevel / time -This table is the transient form of the `AllocationTarget` table, in which time-dependent minimum and maximum levels can be supplied. -Similar to the static version, only a single priority per `AllocationTarget` node can be provided. +This table is the transient form of the `TargetLevel` table, in which time-dependent minimum and maximum levels can be supplied. +Similar to the static version, only a single priority per `TargetLevel` node can be provided. column | type | unit | restriction ------------- | ------- | ------------ | ----------- diff --git a/python/ribasim/ribasim/__init__.py b/python/ribasim/ribasim/__init__.py index 85322bdce..08c98d630 100644 --- a/python/ribasim/ribasim/__init__.py +++ b/python/ribasim/ribasim/__init__.py @@ -4,7 +4,6 @@ from ribasim import utils from ribasim.config import ( Allocation, - AllocationTarget, Basin, Compression, DiscreteControl, @@ -20,6 +19,7 @@ Results, Solver, TabulatedRatingCurve, + TargetLevel, Terminal, User, Verbosity, @@ -30,7 +30,7 @@ __all__ = [ "Allocation", - "AllocationTarget", + "TargetLevel", "Basin", "DiscreteControl", "Compression", diff --git a/python/ribasim/ribasim/config.py b/python/ribasim/ribasim/config.py index f53d21816..22b0a0060 100644 --- a/python/ribasim/ribasim/config.py +++ b/python/ribasim/ribasim/config.py @@ -6,8 +6,6 @@ # These schemas are autogenerated from ribasim.schemas import ( - AllocationTargetStaticSchema, - AllocationTargetTimeSchema, BasinProfileSchema, BasinStateSchema, BasinStaticSchema, @@ -28,6 +26,8 @@ PumpStaticSchema, TabulatedRatingCurveStaticSchema, TabulatedRatingCurveTimeSchema, + TargetLevelStaticSchema, + TargetLevelTimeSchema, TerminalStaticSchema, UserStaticSchema, UserTimeSchema, @@ -137,13 +137,13 @@ class User(NodeModel): ) -class AllocationTarget(NodeModel): - static: TableModel[AllocationTargetStaticSchema] = Field( - default_factory=TableModel[AllocationTargetStaticSchema], +class TargetLevel(NodeModel): + static: TableModel[TargetLevelStaticSchema] = Field( + default_factory=TableModel[TargetLevelStaticSchema], json_schema_extra={"sort_keys": ["node_id", "priority"]}, ) - time: TableModel[AllocationTargetTimeSchema] = Field( - default_factory=TableModel[AllocationTargetTimeSchema], + time: TableModel[TargetLevelTimeSchema] = Field( + default_factory=TableModel[TargetLevelTimeSchema], json_schema_extra={"sort_keys": ["node_id", "priority", "time"]}, ) diff --git a/python/ribasim/ribasim/geometry/node.py b/python/ribasim/ribasim/geometry/node.py index cb6044229..aa88ad865 100644 --- a/python/ribasim/ribasim/geometry/node.py +++ b/python/ribasim/ribasim/geometry/node.py @@ -205,7 +205,7 @@ def plot(self, ax=None, zorder=None) -> Any: "DiscreteControl": "*", "PidControl": "x", "User": "s", - "AllocationTarget": "o", + "TargetLevel": "o", "": "o", } @@ -223,7 +223,7 @@ def plot(self, ax=None, zorder=None) -> Any: "DiscreteControl": "k", "PidControl": "k", "User": "g", - "AllocationTarget": "k", + "TargetLevel": "k", "": "k", } assert self.df is not None diff --git a/python/ribasim/ribasim/model.py b/python/ribasim/ribasim/model.py index 9341dca4f..00d396b49 100644 --- a/python/ribasim/ribasim/model.py +++ b/python/ribasim/ribasim/model.py @@ -18,7 +18,6 @@ from ribasim.config import ( Allocation, - AllocationTarget, Basin, DiscreteControl, FlowBoundary, @@ -33,6 +32,7 @@ Results, Solver, TabulatedRatingCurve, + TargetLevel, Terminal, User, ) @@ -168,7 +168,7 @@ class Model(FileModel): logging: Logging = Logging() allocation: Allocation = Field(default_factory=Allocation) - allocation_target: AllocationTarget = Field(default_factory=AllocationTarget) + target_level: TargetLevel = Field(default_factory=TargetLevel) basin: Basin = Field(default_factory=Basin) fractional_flow: FractionalFlow = Field(default_factory=FractionalFlow) level_boundary: LevelBoundary = Field(default_factory=LevelBoundary) diff --git a/python/ribasim/ribasim/schemas.py b/python/ribasim/ribasim/schemas.py index 3ee2ad373..ca4dd37bd 100644 --- a/python/ribasim/ribasim/schemas.py +++ b/python/ribasim/ribasim/schemas.py @@ -11,14 +11,14 @@ class Config: coerce = True -class AllocationTargetStaticSchema(_BaseSchema): +class TargetLevelStaticSchema(_BaseSchema): node_id: Series[int] = pa.Field(nullable=False) min_level: Series[float] = pa.Field(nullable=False) max_level: Series[float] = pa.Field(nullable=False) priority: Series[int] = pa.Field(nullable=False) -class AllocationTargetTimeSchema(_BaseSchema): +class TargetLevelTimeSchema(_BaseSchema): node_id: Series[int] = pa.Field(nullable=False) time: Series[Timestamp] = pa.Field(nullable=False) min_level: Series[float] = pa.Field(nullable=False) diff --git a/python/ribasim_testmodels/ribasim_testmodels/__init__.py b/python/ribasim_testmodels/ribasim_testmodels/__init__.py index 107d6c3a5..aa8ceb180 100644 --- a/python/ribasim_testmodels/ribasim_testmodels/__init__.py +++ b/python/ribasim_testmodels/ribasim_testmodels/__init__.py @@ -7,12 +7,12 @@ import ribasim_testmodels from ribasim_testmodels.allocation import ( allocation_example_model, - allocation_target_model, fractional_flow_subnetwork_model, looped_subnetwork_model, main_network_with_subnetworks_model, minimal_subnetwork_model, subnetwork_model, + target_level_model, user_model, ) from ribasim_testmodels.backwater import backwater_model @@ -55,7 +55,7 @@ __all__ = [ "allocation_example_model", - "allocation_target_model", + "target_level_model", "backwater_model", "basic_arrow_model", "basic_model", diff --git a/python/ribasim_testmodels/ribasim_testmodels/allocation.py b/python/ribasim_testmodels/ribasim_testmodels/allocation.py index 285fa46e0..ac379f2b3 100644 --- a/python/ribasim_testmodels/ribasim_testmodels/allocation.py +++ b/python/ribasim_testmodels/ribasim_testmodels/allocation.py @@ -1642,20 +1642,20 @@ def main_network_with_subnetworks_model(): return model -def allocation_target_model(): +def target_level_model(): # Set up the nodes: xy = np.array( [ (0.0, 0.0), # 1: FlowBoundary (1.0, 0.0), # 2: Basin (2.0, 0.0), # 3: User - (1.0, -1.0), # 4: AllocationTarget + (1.0, -1.0), # 4: TargetLevel (2.0, -1.0), # 5: Basin ] ) node_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1]) - node_type = ["FlowBoundary", "Basin", "User", "AllocationTarget", "Basin"] + node_type = ["FlowBoundary", "Basin", "User", "TargetLevel", "Basin"] # Make sure the feature id starts at 1: explicitly give an index. node = ribasim.Node( @@ -1725,7 +1725,7 @@ def allocation_target_model(): ) # Setup allocation level control - allocation_target = ribasim.AllocationTarget( + target_level = ribasim.TargetLevel( static=pd.DataFrame( data={"node_id": [4], "priority": 1, "min_level": 1.0, "max_level": 1.5} ) @@ -1751,7 +1751,7 @@ def allocation_target_model(): network=ribasim.Network(node=node, edge=edge), basin=basin, flow_boundary=flow_boundary, - allocation_target=allocation_target, + target_level=target_level, user=user, allocation=allocation, starttime="2020-01-01 00:00:00", diff --git a/ribasim_qgis/core/nodes.py b/ribasim_qgis/core/nodes.py index e8b77a39a..0bee95579 100644 --- a/ribasim_qgis/core/nodes.py +++ b/ribasim_qgis/core/nodes.py @@ -215,9 +215,9 @@ def renderer(self) -> QgsCategorizedSymbolRenderer: "DiscreteControl": (QColor("black"), "DiscreteControl", shape.Star), "PidControl": (QColor("black"), "PidControl", shape.Cross2), "User": (QColor("green"), "User", shape.Square), - "AllocationTarget": ( + "TargetLevel": ( QColor("black"), - "AllocationTarget", + "TargetLevel", shape.Circle, ), # All other nodes, or incomplete input @@ -785,10 +785,10 @@ def attributes(cls) -> list[QgsField]: ] -class AllocationTargetStatic(Input): +class TargetLevelStatic(Input): @classmethod def input_type(cls) -> str: - return "AllocationTarget / static" + return "TargetLevel / static" @classmethod def geometry_type(cls) -> str: @@ -803,10 +803,10 @@ def attributes(cls) -> list[QgsField]: ] -class AllocationTargetTime(Input): +class TargetLevelTime(Input): @classmethod def input_type(cls) -> str: - return "AllocationTarget / time" + return "TargetLevel / time" @classmethod def geometry_type(cls) -> str: