Validation

The tables below show the validation rules applied to the input to the Julia core before running the model.

1 Connectivity

In the table below, each column shows which node types are allowed to be downstream (or ‘down-control’) of the node type at the top of the column.

Code
using Ribasim
using DataFrames: DataFrame
using MarkdownTables

node_names_snake_case = Symbol[]
node_names_camel_case = Symbol[]
for (node_name, node_type) in zip(fieldnames(Ribasim.Parameters), fieldtypes(Ribasim.Parameters))
    if node_type <: Ribasim.AbstractParameterNode
        push!(node_names_snake_case, node_name)
        push!(node_names_camel_case, nameof(node_type))
    end
end

function to_symbol(b::Bool)::String
    return b ? "✓" : "x"
end


df = DataFrame()
df[!, :downstream] = node_names_snake_case

for node_name in node_names_snake_case
    df[!, node_name] =
        [(to_symbol(node_name_ in Ribasim.neighbortypes(node_name))) for node_name_ in node_names_snake_case]
end

markdown_table(df)
downstream basin linear_resistance manning_resistance tabulated_rating_curve level_boundary flow_boundary pump outlet terminal discrete_control continuous_control pid_control user_demand level_demand flow_demand
basin x x x x x x x
linear_resistance x x x x x x x x x x x
manning_resistance x x x x x x x x x x x x
tabulated_rating_curve x x x x x x x x x x x
level_boundary x x x x x x x x x
flow_boundary x x x x x x x x x x x x x x x
pump x x x x x x x x x
outlet x x x x x x x x x
terminal x x x x x x x x x x
discrete_control x x x x x x x x x x x x x x x
continuous_control x x x x x x x x x x x x x x x
pid_control x x x x x x x x x x x x x x
user_demand x x x x x x x x x x x x x x
level_demand x x x x x x x x x x x x x x x
flow_demand x x x x x x x x x x x x x x x

2 Neighbor amounts

The table below shows for each node type between which bounds the amount of in- and outneighbors must be, for both flow and control edges.

Code
flow_in_min = Vector{String}()
flow_in_max = Vector{String}()
flow_out_min = Vector{String}()
flow_out_max = Vector{String}()
control_in_min = Vector{String}()
control_in_max = Vector{String}()
control_out_min = Vector{String}()
control_out_max = Vector{String}()

function unbounded(i::Int)::String
    return i == typemax(Int) ? "∞" : string(i)
end

for node_name in node_names_camel_case
    bounds_flow = Ribasim.n_neighbor_bounds_flow(node_name)
    push!(flow_in_min, string(bounds_flow.in_min))
    push!(flow_in_max, unbounded(bounds_flow.in_max))
    push!(flow_out_min, string(bounds_flow.out_min))
    push!(flow_out_max, unbounded(bounds_flow.out_max))

    bounds_control = Ribasim.n_neighbor_bounds_control(node_name)
    push!(control_in_min, string(bounds_control.in_min))
    push!(control_in_max, unbounded(bounds_control.in_max))
    push!(control_out_min, string(bounds_control.out_min))
    push!(control_out_max, unbounded(bounds_control.out_max))

end

df = DataFrame(
    ;
    node_type = node_names_snake_case,
    flow_in_min,
    flow_in_max,
    flow_out_min,
    flow_out_max,
    control_in_min,
    control_in_max,
    control_out_min,
    control_out_max,
)

markdown_table(df)
node_type flow_in_min flow_in_max flow_out_min flow_out_max control_in_min control_in_max control_out_min control_out_max
basin 0 0 0 1 0 0
linear_resistance 1 1 1 1 0 1 0 0
manning_resistance 1 1 1 1 0 1 0 0
tabulated_rating_curve 1 1 1 1 0 1 0 0
level_boundary 0 0 0 0 0 0
flow_boundary 0 0 1 0 0 0 0
pump 1 1 1 1 0 1 0 0
outlet 1 1 1 1 0 1 0 0
terminal 1 0 0 0 0 0 0
discrete_control 0 0 0 0 0 0 1
continuous_control 0 0 0 0 0 0 1
pid_control 0 0 0 0 0 1 1 1
user_demand 1 1 1 1 0 0 0 0
level_demand 0 0 0 0 0 0 1
flow_demand 0 0 0 0 0 0 1 1