Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate allocation input #972

Merged
merged 7 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"
BasicModelInterface = "59605e27-edc0-445a-b93d-c09a3a50b330"
Configurations = "5218b696-f38b-4ac9-8b61-a12ec717816d"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
DocumenterMarkdown = "997ab1e6-3595-5248-9280-8efb232c3433"
Expand Down
4 changes: 3 additions & 1 deletion core/src/Ribasim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ using Graphs:
inneighbors,
nv,
outneighbors,
rem_edge!
rem_edge!,
induced_subgraph,
is_connected

using Legolas: Legolas, @schema, @version, validate, SchemaVersion, declared
using Logging: with_logger, LogLevel, AbstractLogger
Expand Down
8 changes: 7 additions & 1 deletion core/src/create.jl
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@
function generate_allocation_models!(p::Parameters, config::Config)::Nothing
(; graph, allocation_models) = p

errors = non_positive_id(graph)
Jingru923 marked this conversation as resolved.
Show resolved Hide resolved

if errors
error("Allocation network initialization failed.")

Check warning on line 205 in core/src/create.jl

View check run for this annotation

Codecov / codecov/patch

core/src/create.jl#L205

Added line #L205 was not covered by tests
end

for allocation_network_id in keys(graph[].node_ids)
push!(
allocation_models,
Expand Down Expand Up @@ -691,7 +697,7 @@
first_row = time[first_row_idx]
is_active = true
else
@error "User node #$node_id data not in any table."
@error "User node $node_id data not in any table."

Check warning on line 700 in core/src/create.jl

View check run for this annotation

Codecov / codecov/patch

core/src/create.jl#L700

Added line #L700 was not covered by tests
SouthEndMusic marked this conversation as resolved.
Show resolved Hide resolved
errors = true
Jingru923 marked this conversation as resolved.
Show resolved Hide resolved
end

Expand Down
26 changes: 26 additions & 0 deletions core/src/solve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,32 @@ struct User <: AbstractParameterNode
allocated::Vector{Float64},
abstracted::Vector{Float64},
}

function User(
node_id,
active,
demand,
allocated,
return_factor,
min_level,
priorities,
record,
)
if valid_demand(node_id, demand, priorities)
return new(
node_id,
active,
demand,
allocated,
return_factor,
min_level,
priorities,
record,
)
else
error("Invalid demand")
end
end
end

"Subgrid linearly interpolates basin levels."
Expand Down
4 changes: 4 additions & 0 deletions core/src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@
end
end

if incomplete_subnetwork(graph, node_ids)
error("Incomplete connectivity in subnetwork")

Check warning on line 105 in core/src/utils.jl

View check run for this annotation

Codecov / codecov/patch

core/src/utils.jl#L105

Added line #L105 was not covered by tests
end

flow = zeros(flow_counter)
flow_vertical = zeros(flow_vertical_counter)
if config.solver.autodiff
Expand Down
47 changes: 47 additions & 0 deletions core/src/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -636,3 +636,50 @@

return !errors
end

function valid_demand(
node_id::Vector{NodeID},
demand::Vector{
Vector{LinearInterpolation{Vector{Float64}, Vector{Float64}, true, Float64}},
},
priorities::Vector{Int},
)::Bool
errors = false

for (col, id) in zip(demand, node_id)
for (demand_p_itp, p_itp) in zip(col, priorities)
if any(demand_p_itp.u .< 0.0)
@error "Demand of user node $id with priority $p_itp should be non-negative"
errors = true
end
end
end
return !errors
end

function valid_connection(graph::MetaGraph)::Bool
return is_connected(graph)

Check warning on line 661 in core/src/validation.jl

View check run for this annotation

Codecov / codecov/patch

core/src/validation.jl#L660-L661

Added lines #L660 - L661 were not covered by tests
end
Jingru923 marked this conversation as resolved.
Show resolved Hide resolved

function incomplete_subnetwork(graph::MetaGraph, node_ids::Dict{Int, Set{NodeID}})::Bool
errors = false
for (allocation_network_id, subnetwork_node_ids) in node_ids
subnetwork, _ = induced_subgraph(graph, code_for.(Ref(graph), subnetwork_node_ids))
if (!is_connected(subnetwork))
Jingru923 marked this conversation as resolved.
Show resolved Hide resolved
@error "All nodes in subnetwork $allocation_network_id should be connected"
errors = true
end
end
return errors
end

function non_positive_id(graph::MetaGraph)::Bool
Jingru923 marked this conversation as resolved.
Show resolved Hide resolved
errors = false
for allocation_network_id in keys(graph[].node_ids)
if (allocation_network_id <= 0)
@error "Allocation network id $allocation_network_id needs to be a positive integer."
errors = true
end
end
return errors
end
100 changes: 100 additions & 0 deletions core/test/create_test.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
@testitem "Non-positive allocation network ID" begin
using MetaGraphsNext
using Graphs
using Logging
using Ribasim
using Accessors: @set

struct NodeMetadata
type::Symbol
allocation_network_id::Int
end
Jingru923 marked this conversation as resolved.
Show resolved Hide resolved

graph = MetaGraph(
DiGraph();
label_type = Ribasim.NodeID,
vertex_data_type = NodeMetadata,
edge_data_type = Symbol,
graph_data = Tuple,
)

graph[Ribasim.NodeID(1)] = NodeMetadata(Symbol(:delft), 1)
graph[Ribasim.NodeID(2)] = NodeMetadata(Symbol(:denhaag), -1)

graph[1, 2] = :yes

node_ids = Dict{Int, Set{Ribasim.NodeID}}()
node_ids[0] = Set{Ribasim.NodeID}()
node_ids[-1] = Set{Ribasim.NodeID}()
push!(node_ids[0], Ribasim.NodeID(1))
push!(node_ids[-1], Ribasim.NodeID(2))

graph_data = (; node_ids,)
graph = @set graph.graph_data = graph_data

logger = TestLogger()
with_logger(logger) do
Ribasim.non_positive_id(graph)
end

@test length(logger.logs) == 2
@test logger.logs[1].level == Error
@test logger.logs[1].message ==
"Allocation network id 0 needs to be a positive integer."
@test logger.logs[2].level == Error
@test logger.logs[2].message ==
"Allocation network id -1 needs to be a positive integer."
end

@testitem "Incomplete subnetwork" begin
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding this test: It is good practice to create your own test model to do this, but I thought of an easier way: load the parameters of an existing model, say

toml_path = normpath(@__DIR__, "../../generated_testmodels/allocation_example/ribasim.toml")
p = Ribasim.Model(toml_path).integrator.p

remove an edge to make it invalid and run your validation test on that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This morning I tried to load an existing model and change one of the allocation id to negative number. It gave error saying the immutable struct of type cannot be changed. I remembered you mentioned this yesterday, it probably can't be changed or maybe I didn't do it the correct way. Anyhow, in my new code I created a graph that has the required feature and used it as the test model.

using MetaGraphsNext
using Graphs
using Logging
using Ribasim

struct NodeMetadata
type::Symbol
allocation_network_id::Int
end
Jingru923 marked this conversation as resolved.
Show resolved Hide resolved

graph = MetaGraph(
DiGraph();
label_type = Ribasim.NodeID,
vertex_data_type = NodeMetadata,
edge_data_type = Symbol,
graph_data = Tuple,
)

node_ids = Dict{Int, Set{Ribasim.NodeID}}()
node_ids[1] = Set{Ribasim.NodeID}()
push!(node_ids[1], Ribasim.NodeID(1))
push!(node_ids[1], Ribasim.NodeID(2))
push!(node_ids[1], Ribasim.NodeID(3))
node_ids[2] = Set{Ribasim.NodeID}()
push!(node_ids[2], Ribasim.NodeID(4))
push!(node_ids[2], Ribasim.NodeID(5))
push!(node_ids[2], Ribasim.NodeID(5))
Jingru923 marked this conversation as resolved.
Show resolved Hide resolved
#node_ids = Dict([(1, Set(NodeID(1))), (2, Set(NodeID(2)))])

graph[Ribasim.NodeID(1)] = NodeMetadata(Symbol(:delft), 1)
graph[Ribasim.NodeID(2)] = NodeMetadata(Symbol(:denhaag), 1)
graph[Ribasim.NodeID(3)] = NodeMetadata(Symbol(:rdam), 1)
graph[Ribasim.NodeID(4)] = NodeMetadata(Symbol(:adam), 2)
graph[Ribasim.NodeID(5)] = NodeMetadata(Symbol(:utrecht), 2)
graph[Ribasim.NodeID(6)] = NodeMetadata(Symbol(:leiden), 2)

graph[Ribasim.NodeID(1), Ribasim.NodeID(2)] = :yes
graph[Ribasim.NodeID(1), Ribasim.NodeID(3)] = :yes
graph[4, 5] = :yes

logger = TestLogger()

with_logger(logger) do
errors = Ribasim.incomplete_subnetwork(graph, node_ids)
@test errors == true
end

@test length(logger.logs) == 1
@test logger.logs[1].level == Error
@test logger.logs[1].message == "All nodes in subnetwork 2 should be connected"
end
24 changes: 24 additions & 0 deletions core/test/validation_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -401,3 +401,27 @@ end
@test logger.logs[2].message ==
"Basin / subgrid_level subgrid_id 1 has repeated element levels, this cannot be interpolated."
end

@testitem "negative demand" begin
using Logging
using DataInterpolations: LinearInterpolation
logger = TestLogger()

with_logger(logger) do
@test_throws "Invalid demand" Ribasim.User(
[Ribasim.NodeID(1)],
[true],
[[LinearInterpolation([-5.0, -5.0], [-1.8, 1.8])]],
[0.0, -0.0],
[0.9],
[0.9],
[1],
[],
)
end

@test length(logger.logs) == 1
@test logger.logs[1].level == Error
@test logger.logs[1].message ==
"Demand of user node #1 with priority 1 should be non-negative"
end
Loading