diff --git a/core/src/Ribasim.jl b/core/src/Ribasim.jl index d98d5a609..ca675a366 100644 --- a/core/src/Ribasim.jl +++ b/core/src/Ribasim.jl @@ -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 diff --git a/core/src/create.jl b/core/src/create.jl index 46a5f9ada..66e331e06 100644 --- a/core/src/create.jl +++ b/core/src/create.jl @@ -199,6 +199,12 @@ const nonconservative_nodetypes = function generate_allocation_models!(p::Parameters, config::Config)::Nothing (; graph, allocation_models) = p + errors = non_positive_allocation_network_id(graph) + + if errors + error("Allocation network initialization failed.") + end + for allocation_network_id in keys(graph[].node_ids) push!( allocation_models, diff --git a/core/src/solve.jl b/core/src/solve.jl index f93a76ad5..2087f2f35 100644 --- a/core/src/solve.jl +++ b/core/src/solve.jl @@ -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." diff --git a/core/src/utils.jl b/core/src/utils.jl index daeb1ca00..7d2bd8e64 100644 --- a/core/src/utils.jl +++ b/core/src/utils.jl @@ -101,6 +101,10 @@ function create_graph(db::DB, config::Config, chunk_sizes::Vector{Int})::MetaGra end end + if incomplete_subnetwork(graph, node_ids) + error("Incomplete connectivity in subnetwork") + end + flow = zeros(flow_counter) flow_vertical = zeros(flow_vertical_counter) if config.solver.autodiff diff --git a/core/src/validation.jl b/core/src/validation.jl index 8ff6e59cc..f74d03eb8 100644 --- a/core/src/validation.jl +++ b/core/src/validation.jl @@ -636,3 +636,46 @@ function valid_subgrid( 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 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) + @error "All nodes in subnetwork $allocation_network_id should be connected" + errors = true + end + end + return errors +end + +function non_positive_allocation_network_id(graph::MetaGraph)::Bool + 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 diff --git a/core/test/create_test.jl b/core/test/create_test.jl new file mode 100644 index 000000000..946d6664f --- /dev/null +++ b/core/test/create_test.jl @@ -0,0 +1,89 @@ +@testitem "Non-positive allocation network ID" begin + using MetaGraphsNext + using Graphs + using Logging + using Ribasim + using Accessors: @set + + graph = MetaGraph( + DiGraph(); + label_type = Ribasim.NodeID, + vertex_data_type = Ribasim.NodeMetadata, + edge_data_type = Symbol, + graph_data = Tuple, + ) + + graph[Ribasim.NodeID(1)] = Ribasim.NodeMetadata(Symbol(:delft), 1) + graph[Ribasim.NodeID(2)] = Ribasim.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_allocation_network_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 + using MetaGraphsNext + using Graphs + using Logging + using Ribasim + + graph = MetaGraph( + DiGraph(); + label_type = Ribasim.NodeID, + vertex_data_type = Ribasim.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(6)) + + graph[Ribasim.NodeID(1)] = Ribasim.NodeMetadata(Symbol(:delft), 1) + graph[Ribasim.NodeID(2)] = Ribasim.NodeMetadata(Symbol(:denhaag), 1) + graph[Ribasim.NodeID(3)] = Ribasim.NodeMetadata(Symbol(:rdam), 1) + graph[Ribasim.NodeID(4)] = Ribasim.NodeMetadata(Symbol(:adam), 2) + graph[Ribasim.NodeID(5)] = Ribasim.NodeMetadata(Symbol(:utrecht), 2) + graph[Ribasim.NodeID(6)] = Ribasim.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 diff --git a/core/test/validation_test.jl b/core/test/validation_test.jl index 97b782b6d..62d8cfeb7 100644 --- a/core/test/validation_test.jl +++ b/core/test/validation_test.jl @@ -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