Skip to content

Commit

Permalink
Rename TargetLevel again: LevelDemand (#1172)
Browse files Browse the repository at this point in the history
Follow up of #1082 and #1141.

As suggested by @gijsber, to go with FlowDemand of
#78, and renaming User to
UserDemand.

Not breaking since the TargetLevel node has not yet been in a release.
  • Loading branch information
visr authored Feb 23, 2024
1 parent 34c90b4 commit de2203f
Show file tree
Hide file tree
Showing 19 changed files with 101 additions and 101 deletions.
32 changes: 16 additions & 16 deletions core/src/allocation_optim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,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 target_level node (0 if such a
- The index of the connected level_demand node (0 if such a
node does not exist)
- The index of the basin
"""
Expand All @@ -354,27 +354,27 @@ function get_basin_data(
u::ComponentVector,
node_id::NodeID,
)
(; graph, basin, target_level) = p
(; graph, basin, level_demand) = p
(; Δt_allocation) = allocation_model
@assert node_id.type == NodeType.Basin
influx = get_flow(graph, node_id, 0.0)
_, basin_idx = id_index(basin.node_id, node_id)
storage_basin = u.storage[basin_idx]
control_inneighbors = inneighbor_labels_type(graph, node_id, EdgeType.control)
if isempty(control_inneighbors)
target_level_idx = 0
level_demand_idx = 0
else
target_level_node_id = first(control_inneighbors)
target_level_idx = findsorted(target_level.node_id, target_level_node_id)
level_demand_node_id = first(control_inneighbors)
level_demand_idx = findsorted(level_demand.node_id, level_demand_node_id)
end
return storage_basin, Δt_allocation, influx, target_level_idx, basin_idx
return storage_basin, Δt_allocation, influx, level_demand_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 target_level node).
a level_demand node).
Storages are converted to flows by dividing by the allocation timestep.
"""
function get_basin_capacity(
Expand All @@ -384,14 +384,14 @@ function get_basin_capacity(
t::Float64,
node_id::NodeID,
)::Float64
(; target_level) = p
(; level_demand) = p
@assert node_id.type == NodeType.Basin
storage_basin, Δt_allocation, influx, target_level_idx, basin_idx =
storage_basin, Δt_allocation, influx, level_demand_idx, basin_idx =
get_basin_data(allocation_model, p, u, node_id)
if iszero(target_level_idx)
if iszero(level_demand_idx)
return 0.0
else
level_max = target_level.max_level[target_level_idx](t)
level_max = level_demand.max_level[level_demand_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
Expand All @@ -400,7 +400,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 target_level node).
reference levels are provided by a level_demand node).
Storages are converted to flows by dividing by the allocation timestep.
"""
function get_basin_demand(
Expand All @@ -410,14 +410,14 @@ function get_basin_demand(
t::Float64,
node_id::NodeID,
)::Float64
(; target_level) = p
(; level_demand) = p
@assert node_id.type == NodeType.Basin
storage_basin, Δt_allocation, influx, target_level_idx, basin_idx =
storage_basin, Δt_allocation, influx, level_demand_idx, basin_idx =
get_basin_data(allocation_model, p, u, node_id)
if iszero(target_level_idx)
if iszero(level_demand_idx)
return 0.0
else
level_min = target_level.min_level[target_level_idx](t)
level_min = level_demand.min_level[level_demand_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
Expand Down
6 changes: 3 additions & 3 deletions core/src/parameter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -524,12 +524,12 @@ struct User <: AbstractParameterNode
end

"""
node_id: node ID of the TargetLevel node
node_id: node ID of the LevelDemand 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 TargetLevel
struct LevelDemand
node_id::Vector{NodeID}
min_level::Vector{LinearInterpolation}
max_level::Vector{LinearInterpolation}
Expand Down Expand Up @@ -578,6 +578,6 @@ struct Parameters{T, C1, C2}
discrete_control::DiscreteControl
pid_control::PidControl{T}
user::User
target_level::TargetLevel
level_demand::LevelDemand
subgrid::Subgrid
end
18 changes: 9 additions & 9 deletions core/src/read.jl
Original file line number Diff line number Diff line change
Expand Up @@ -757,25 +757,25 @@ function User(db::DB, config::Config)::User
)
end

function TargetLevel(db::DB, config::Config)::TargetLevel
static = load_structvector(db, config, TargetLevelStaticV1)
time = load_structvector(db, config, TargetLevelTimeV1)
function LevelDemand(db::DB, config::Config)::LevelDemand
static = load_structvector(db, config, LevelDemandStaticV1)
time = load_structvector(db, config, LevelDemandTimeV1)

parsed_parameters, valid = parse_static_and_time(
db,
config,
"TargetLevel";
"LevelDemand";
static,
time,
time_interpolatables = [:min_level, :max_level],
)

if !valid
error("Errors occurred when parsing TargetLevel data.")
error("Errors occurred when parsing LevelDemand data.")
end

return TargetLevel(
NodeID.(NodeType.TargetLevel, parsed_parameters.node_id),
return LevelDemand(
NodeID.(NodeType.LevelDemand, parsed_parameters.node_id),
parsed_parameters.min_level,
parsed_parameters.max_level,
parsed_parameters.priority,
Expand Down Expand Up @@ -889,7 +889,7 @@ function Parameters(db::DB, config::Config)::Parameters
discrete_control = DiscreteControl(db, config)
pid_control = PidControl(db, config, chunk_sizes)
user = User(db, config)
target_level = TargetLevel(db, config)
level_demand = LevelDemand(db, config)

basin = Basin(db, config, chunk_sizes)
subgrid_level = Subgrid(db, config, basin)
Expand All @@ -911,7 +911,7 @@ function Parameters(db::DB, config::Config)::Parameters
discrete_control,
pid_control,
user,
target_level,
level_demand,
subgrid_level,
)

Expand Down
8 changes: 4 additions & 4 deletions core/src/schema.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
@schema "ribasim.outlet.static" OutletStatic
@schema "ribasim.user.static" UserStatic
@schema "ribasim.user.time" UserTime
@schema "ribasim.targetlevel.static" TargetLevelStatic
@schema "ribasim.targetlevel.time" TargetLevelTime
@schema "ribasim.leveldemand.static" LevelDemandStatic
@schema "ribasim.leveldemand.time" LevelDemandTime

const delimiter = " / "
tablename(sv::Type{SchemaVersion{T, N}}) where {T, N} = tablename(sv())
Expand Down Expand Up @@ -238,14 +238,14 @@ end
priority::Int
end

@version TargetLevelStaticV1 begin
@version LevelDemandStaticV1 begin
node_id::Int
min_level::Float64
max_level::Float64
priority::Int
end

@version TargetLevelTimeV1 begin
@version LevelDemandTimeV1 begin
node_id::Int
time::DateTime
min_level::Float64
Expand Down
4 changes: 2 additions & 2 deletions core/src/solve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,12 @@ function continuous_control!(
end

if !iszero(K_d)
dtarget_level = scalar_interpolation_derivative(target[i], t)
dlevel_demand = scalar_interpolation_derivative(target[i], t)
du_listened_basin_old = du.storage[listened_node_idx]
# The expression below is the solution to an implicit equation for
# du_listened_basin. This equation results from the fact that if the derivative
# term in the PID controller is used, the controlled pump flow rate depends on itself.
flow_rate += K_d * (dtarget_level - du_listened_basin_old / area) / D
flow_rate += K_d * (dlevel_demand - du_listened_basin_old / area) / D
end

# Clip values outside pump flow rate bounds
Expand Down
8 changes: 4 additions & 4 deletions core/src/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -646,21 +646,21 @@ 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, TargetLevelStaticV1, TargetLevelTimeV1]
for type in [UserStaticV1, UserTimeV1, LevelDemandStaticV1, LevelDemandTimeV1]
union!(priorities, load_structvector(db, config, type).priority)
end
return sort(unique(priorities))
end

function get_basin_priority_idx(p::Parameters, node_id::NodeID)::Int
(; graph, target_level, allocation) = p
(; graph, level_demand, allocation) = 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(target_level.node_id, only(inneighbors_control))
priority = target_level.priority[idx]
idx = findsorted(level_demand.node_id, only(inneighbors_control))
priority = level_demand.priority[idx]
return findsorted(allocation.priorities, priority)
end
end
Expand Down
6 changes: 3 additions & 3 deletions core/src/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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{:target_level}) = Set((:basin,))
neighbortypes(::Val{:level_demand}) = Set((:basin,))
neighbortypes(::Val{:basin}) = Set((
:linear_resistance,
:tabulated_rating_curve,
Expand Down Expand Up @@ -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{:TargetLevel}) = n_neighbor_bounds(0, 0, 0, 0)
n_neighbor_bounds_flow(::Val{:LevelDemand}) = n_neighbor_bounds(0, 0, 0, 0)
n_neighbor_bounds_flow(nodetype) =
error("'n_neighbor_bounds_flow' not defined for $nodetype.")

Expand All @@ -77,7 +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{:TargetLevel}) = n_neighbor_bounds(0, 0, 1, typemax(Int))
n_neighbor_bounds_control(::Val{:LevelDemand}) = n_neighbor_bounds(0, 0, 1, typemax(Int))
n_neighbor_bounds_control(nodetype) =
error("'n_neighbor_bounds_control' not defined for $nodetype.")

Expand Down
6 changes: 3 additions & 3 deletions core/test/allocation_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -325,15 +325,15 @@ end
end

@testitem "Allocation level control" begin
toml_path = normpath(@__DIR__, "../../generated_testmodels/target_level/ribasim.toml")
toml_path = normpath(@__DIR__, "../../generated_testmodels/level_demand/ribasim.toml")
@test ispath(toml_path)
model = Ribasim.run(toml_path)

storage = Ribasim.get_storages_and_levels(model).storage[1, :]
t = Ribasim.timesteps(model)

p = model.integrator.p
(; user, graph, allocation, basin, target_level) = p
(; user, graph, allocation, basin, level_demand) = p

d = user.demand_itp[1][2](0)
ϕ = 1e-3 # precipitation
Expand All @@ -344,7 +344,7 @@ end
0,
)
A = basin.area[1][1]
l_max = target_level.max_level[1](0)
l_max = level_demand.max_level[1](0)
Δt_allocation = allocation.allocation_models[1].Δt_allocation

# Until the first allocation solve, the user abstracts fully
Expand Down
8 changes: 4 additions & 4 deletions core/test/control_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,13 @@ end
idx_target_change = searchsortedlast(timesteps, t_target_change)

K_p, K_i, _ = pid_control.pid_params[2](0)
target_level = pid_control.target[2](0)
level_demand = pid_control.target[2](0)

A = basin.area[1][1]
initial_level = level[1]
flow_rate = flow_boundary.flow_rate[1].u[1]
du0 = flow_rate + K_p * (target_level - initial_level)
Δlevel = initial_level - target_level
du0 = flow_rate + K_p * (level_demand - initial_level)
Δlevel = initial_level - level_demand
alpha = -K_p / (2 * A)
omega = sqrt(4 * K_i / A - (K_i / A)^2) / 2
phi = atan(du0 / (A * Δlevel) - alpha) / omega
Expand All @@ -122,7 +122,7 @@ end
bound = @. a * exp(alpha * timesteps[1:idx_target_change])
eps = 5e-3
# Initial convergence to target level
@test all(@. abs(level[1:idx_target_change] - target_level) < bound + eps)
@test all(@. abs(level[1:idx_target_change] - level_demand) < bound + eps)
# Later closeness to target level
@test all(
@. abs(
Expand Down
20 changes: 10 additions & 10 deletions docs/core/usage.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -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
- TargetLevel: Indicates minimum and maximum target level of connected basins for allocation
- `TargetLevel / static`: static target levels
- `TargetLevel / time`: dynamic target levels
- LevelDemand: Indicates minimum and maximum target level of connected basins for allocation
- `LevelDemand / static`: static target levels
- `LevelDemand / 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)
Expand Down Expand Up @@ -466,12 +466,12 @@ demand | Float64 | $m^3 s^{-1}$ | -
return_factor | Float64 | - | between [0 - 1]
min_level | Float64 | $m$ | -

# TargetLevel {#sec-target_level}
# LevelDemand {#sec-level_demand}

An `TargetLevel` node associates a minimum and a maximum level with connected basins to be used by the allocation algorithm.
An `LevelDemand` 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 `TargetLevel` node can be used for basins in different subnetworks.
The same `LevelDemand` node can be used for basins in different subnetworks.

column | type | unit | restriction
------------- | ------- | ------------ | -----------
Expand All @@ -480,10 +480,10 @@ min_level | Float64 | $m$ | -
max_level | Float64 | $m$ | -
priority | Int | - | positive

## TargetLevel / time
## LevelDemand / time

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.
This table is the transient form of the `LevelDemand` table, in which time-dependent minimum and maximum levels can be supplied.
Similar to the static version, only a single priority per `LevelDemand` node can be provided.

column | type | unit | restriction
------------- | ------- | ------------ | -----------
Expand Down Expand Up @@ -723,7 +723,7 @@ demand | Float64
allocated | Float64
realized | Float64

For Basins the values `demand`, `allocated` and `realized` are positive if the Basin level is below the minimum level given by a `TargetLevel` node.
For Basins the values `demand`, `allocated` and `realized` are positive if the Basin level is below the minimum level given by a `LevelDemand` node.
The values are negative if the Basin supplies due to a surplus of water.

::: {.callout-note}
Expand Down
Loading

0 comments on commit de2203f

Please sign in to comment.