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

Increase performance by reducing lookups #1457

Merged
merged 19 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
55e598c
Precalculate neighboring edges for TabulatedRatingCurve
SouthEndMusic May 7, 2024
6425d61
Precalculate neighboring edges for LinearResistance
SouthEndMusic May 8, 2024
a09d4a5
Precalculate neighboring edges for ManningResistance
SouthEndMusic May 8, 2024
9ce0e28
Precalculate neighboring edges for FractionalFlow
SouthEndMusic May 8, 2024
5d0c4dd
Precalculate neighboring edges for Pump
SouthEndMusic May 8, 2024
d2fef95
Precalculate neighboring edges for Outlet
SouthEndMusic May 8, 2024
ad544bb
Precalculate neighboring edges for UserDemand
SouthEndMusic May 8, 2024
715fffd
Precompute basin bottoms for Manningresistance
SouthEndMusic May 8, 2024
da045ce
Avoid lookups in formulate_du!
SouthEndMusic May 8, 2024
30cbbf4
Fix tests
SouthEndMusic May 8, 2024
acf434f
Avoid lookups in get_level for UserDemand, ManningResistance
SouthEndMusic May 8, 2024
ef2ec90
Merge branch 'main' into even-more-perf
SouthEndMusic May 8, 2024
57b7015
Add basin index test
SouthEndMusic May 8, 2024
c52dfbd
Merge branch 'main' into even-more-perf
SouthEndMusic May 8, 2024
f75367b
Merge branch 'main' into even-more-perf
SouthEndMusic May 8, 2024
e88f0e0
Don't lookup basin index for bottoms for UserDemand, Linearresistance…
SouthEndMusic May 8, 2024
f18cd51
Comments adressed + avoid lookups for low_storage_factor and other sm…
SouthEndMusic May 13, 2024
74d26f1
Merge branch 'main' into even-more-perf
SouthEndMusic May 13, 2024
4e6a085
Revert https://github.com/Deltares/Ribasim/pull/1458, causes CI to fa…
SouthEndMusic May 13, 2024
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
2 changes: 1 addition & 1 deletion core/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ ComponentArrays = "0.13,0.14,0.15"
Configurations = "0.17"
DBInterface = "2.4"
DataFrames = "1.4"
DataInterpolations = "4.4, 5"
DataInterpolations = "4.4"
DataStructures = "0.18"
Dates = "<0.0.1,1"
Dictionaries = "0.3.25, 0.4"
Expand Down
4 changes: 1 addition & 3 deletions core/src/callback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,7 @@ function integrate_flows!(u, t, integrator)::Nothing
else
# Horizontal flows
value[] +=
0.5 *
(get_flow(graph, edge..., 0) + get_flow(graph, edge..., 0; prev = true)) *
dt
0.5 * (get_flow(graph, edge..., 0) + get_flow_prev(graph, edge..., 0)) * dt
end
end

Expand Down
70 changes: 55 additions & 15 deletions core/src/graph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
node_ids = Dict{Int32, Set{NodeID}}()
# Source edges per subnetwork
edges_source = Dict{Int32, Set{EdgeMetadata}}()
# The number of flow edges
# The flow counter gives a unique consecutive id to the
# flow edges to index the flow vectors
flow_counter = 0
# Dictionary from flow edge to index in flow vector
flow_dict = Dict{Tuple{NodeID, NodeID}, Int}()
flow_dict = Dict{Tuple{NodeID, NodeID}, Int32}()
graph = MetaGraph(
DiGraph();
label_type = NodeID,
Expand Down Expand Up @@ -65,7 +66,15 @@
if ismissing(subnetwork_id)
subnetwork_id = 0
end
edge_metadata = EdgeMetadata(fid, edge_type, subnetwork_id, (id_src, id_dst))
edge_metadata = EdgeMetadata(
fid,
edge_type == EdgeType.flow ? flow_counter + 1 : 0,
SouthEndMusic marked this conversation as resolved.
Show resolved Hide resolved
edge_type,
subnetwork_id,
(id_src, id_dst),
-1,
-1,
)
if haskey(graph, id_src, id_dst)
errors = true
@error "Duplicate edge" id_src id_dst
Expand Down Expand Up @@ -169,24 +178,55 @@
Set the given flow q over the edge between the given nodes.
"""
function set_flow!(graph::MetaGraph, id_src::NodeID, id_dst::NodeID, q::Number)::Nothing
(; flow_dict, flow) = graph[]
get_tmp(flow, q)[flow_dict[(id_src, id_dst)]] = q
(; flow_dict) = graph[]
flow_idx = flow_dict[(id_src, id_dst)]
set_flow!(graph, flow_idx, q)
return nothing
end

function set_flow!(graph::MetaGraph, edge_metadata::EdgeMetadata, q::Number)::Nothing
set_flow!(graph, edge_metadata.flow_idx, q)
return nothing
end

function set_flow!(graph, flow_idx::Int32, q::Number)::Nothing
(; flow) = graph[]
get_tmp(flow, q)[flow_idx] = q
return nothing
end

"""
Get the flow over the given edge (val is needed for get_tmp from ForwardDiff.jl).
"""
function get_flow(
graph::MetaGraph,
id_src::NodeID,
id_dst::NodeID,
val;
prev::Bool = false,
)::Number
(; flow_dict, flow, flow_prev) = graph[]
flow_vector = prev ? flow_prev : flow
return get_tmp(flow_vector, val)[flow_dict[id_src, id_dst]]
function get_flow(graph::MetaGraph, id_src::NodeID, id_dst::NodeID, val)::Number
(; flow_dict) = graph[]
flow_idx = flow_dict[id_src, id_dst]
return get_flow(graph, flow_idx, val)
end

function get_flow(graph, edge_metadata::EdgeMetadata, val)::Number
return get_flow(graph, edge_metadata.flow_idx, val)
end

function get_flow(graph::MetaGraph, flow_idx::Integer, val)
return get_tmp(graph[].flow, val)[flow_idx]
end

function get_flow_prev(graph, id_src::NodeID, id_dst::NodeID, val)::Number
# Note: Can be removed after https://github.com/Deltares/Ribasim/pull/1444
(; flow_dict) = graph[]
flow_idx = flow_dict[id_src, id_dst]
return get_flow(graph, flow_idx, val)
end

function get_flow_prev(graph, edge_metadata::EdgeMetadata, val)::Number

Check warning on line 222 in core/src/graph.jl

View check run for this annotation

Codecov / codecov/patch

core/src/graph.jl#L222

Added line #L222 was not covered by tests
# Note: Can be removed after https://github.com/Deltares/Ribasim/pull/1444
return get_flow_prev(graph, edge_metadata.flow_idx, val)

Check warning on line 224 in core/src/graph.jl

View check run for this annotation

Codecov / codecov/patch

core/src/graph.jl#L224

Added line #L224 was not covered by tests
end

function get_flow_prev(graph::MetaGraph, flow_idx::Integer, val)

Check warning on line 227 in core/src/graph.jl

View check run for this annotation

Codecov / codecov/patch

core/src/graph.jl#L227

Added line #L227 was not covered by tests
# Note: Can be removed after https://github.com/Deltares/Ribasim/pull/1444
return get_tmp(graph[].flow_prev, val)[flow_idx]

Check warning on line 229 in core/src/graph.jl

View check run for this annotation

Codecov / codecov/patch

core/src/graph.jl#L229

Added line #L229 was not covered by tests
end

"""
Expand Down
96 changes: 56 additions & 40 deletions core/src/parameter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,22 @@ end
"""
Type for storing metadata of edges in the graph:
id: ID of the edge (only used for labeling flow output)
flow_idx: Index in the vector of flows
type: type of the edge
subnetwork_id_source: ID of subnetwork where this edge is a source
(0 if not a source)
edge: (from node ID, to node ID)
basin_idx_src: Basin index of source node (0 if not a basin)
basin_idx_dst: Basin index of destination node (0 if not a basin)
"""
struct EdgeMetadata
id::Int32
flow_idx::Int32
type::EdgeType.T
subnetwork_id_source::Int32
edge::Tuple{NodeID, NodeID}
basin_idx_src::Int32
basin_idx_dst::Int32
end

abstract type AbstractParameterNode end
Expand Down Expand Up @@ -233,17 +239,19 @@ Type parameter C indicates the content backing the StructVector, which can be a
of Vectors or Arrow Primitives, and is added to avoid type instabilities.

node_id: node ID of the TabulatedRatingCurve node
inflow_id: node ID across the incoming flow edge
outflow_ids: node IDs across the outgoing flow edges
inflow_edge: incoming flow edge metadata
The ID of the destination node is always the ID of the TabulatedRatingCurve node
outflow_edges: outgoing flow edges metadata
The ID of the source node is always the ID of the TabulatedRatingCurve node
active: whether this node is active and thus contributes flows
tables: The current Q(h) relationships
time: The time table used for updating the tables
control_mapping: dictionary from (node_id, control_state) to Q(h) and/or active state
"""
struct TabulatedRatingCurve{C} <: AbstractParameterNode
node_id::Vector{NodeID}
inflow_id::Vector{NodeID}
outflow_ids::Vector{Vector{NodeID}}
inflow_edge::Vector{EdgeMetadata}
SouthEndMusic marked this conversation as resolved.
Show resolved Hide resolved
outflow_edges::Vector{Vector{EdgeMetadata}}
active::BitVector
tables::Vector{ScalarInterpolation}
time::StructVector{TabulatedRatingCurveTimeV1, C, Int}
Expand All @@ -252,17 +260,19 @@ end

"""
node_id: node ID of the LinearResistance node
inflow_id: node ID across the incoming flow edge
outflow_id: node ID across the outgoing flow edge
inflow_edge: incoming flow edge metadata
The ID of the destination node is always the ID of the LinearResistance node
outflow_edge: outgoing flow edge metadata
The ID of the source node is always the ID of the LinearResistance node
active: whether this node is active and thus contributes flows
resistance: the resistance to flow; `Q_unlimited = Δh/resistance`
max_flow_rate: the maximum flow rate allowed through the node; `Q = clamp(Q_unlimited, -max_flow_rate, max_flow_rate)`
control_mapping: dictionary from (node_id, control_state) to resistance and/or active state
"""
struct LinearResistance <: AbstractParameterNode
node_id::Vector{NodeID}
inflow_id::Vector{NodeID}
outflow_id::Vector{NodeID}
inflow_edge::Vector{EdgeMetadata}
outflow_edge::Vector{EdgeMetadata}
active::BitVector
resistance::Vector{Float64}
max_flow_rate::Vector{Float64}
Expand All @@ -273,8 +283,10 @@ end
This is a simple Manning-Gauckler reach connection.

node_id: node ID of the ManningResistance node
inflow_id: node ID across the incoming flow edge
outflow_id: node ID across the outgoing flow edge
inflow_edge: incoming flow edge metadata
The ID of the destination node is always the ID of the ManningResistance node
outflow_edge: outgoing flow edge metadata
The ID of the source node is always the ID of the ManningResistance node
length: reach length
manning_n: roughness; Manning's n in (SI units).

Expand Down Expand Up @@ -307,33 +319,31 @@ Requirements:
"""
struct ManningResistance <: AbstractParameterNode
node_id::Vector{NodeID}
inflow_id::Vector{NodeID}
outflow_id::Vector{NodeID}
inflow_edge::Vector{EdgeMetadata}
outflow_edge::Vector{EdgeMetadata}
active::BitVector
length::Vector{Float64}
manning_n::Vector{Float64}
profile_width::Vector{Float64}
profile_slope::Vector{Float64}
upstream_bottom::Vector{Float64}
downstream_bottom::Vector{Float64}
control_mapping::Dict{Tuple{NodeID, String}, NamedTuple}
end

"""
Requirements:

* from: must be (TabulatedRatingCurve,) node
* to: must be (Basin,) node
* fraction must be positive.

node_id: node ID of the TabulatedRatingCurve node
inflow_id: node ID across the incoming flow edge
outflow_id: node ID across the outgoing flow edge
node_id: node ID of the FractionalFlow node
inflow_edge: incoming flow edge metadata
The ID of the destination node is always the ID of the FractionalFlow node
outflow_edge: outgoing flow edge metadata
The ID of the source node is always the ID of the FractionalFlow node
fraction: The fraction in [0,1] of flow the node lets through
control_mapping: dictionary from (node_id, control_state) to fraction
"""
struct FractionalFlow <: AbstractParameterNode
node_id::Vector{NodeID}
inflow_id::Vector{NodeID}
outflow_id::Vector{NodeID}
inflow_edge::Vector{EdgeMetadata}
outflow_edge::Vector{EdgeMetadata}
fraction::Vector{Float64}
control_mapping::Dict{Tuple{NodeID, String}, NamedTuple}
end
Expand Down Expand Up @@ -362,8 +372,10 @@ end

"""
node_id: node ID of the Pump node
inflow_id: node ID across the incoming flow edge
outflow_ids: node IDs across the outgoing flow edges
inflow_edge: incoming flow edge metadata
The ID of the destination node is always the ID of the Pump node
outflow_edges: outgoing flow edges metadata
The ID of the source node is always the ID of the Pump node
active: whether this node is active and thus contributes flow
flow_rate: target flow rate
min_flow_rate: The minimal flow rate of the pump
Expand All @@ -373,8 +385,8 @@ is_pid_controlled: whether the flow rate of this pump is governed by PID control
"""
struct Pump{T} <: AbstractParameterNode
node_id::Vector{NodeID}
inflow_id::Vector{NodeID}
outflow_ids::Vector{Vector{NodeID}}
inflow_edge::Vector{EdgeMetadata}
outflow_edges::Vector{Vector{EdgeMetadata}}
active::BitVector
flow_rate::T
min_flow_rate::Vector{Float64}
Expand All @@ -384,8 +396,8 @@ struct Pump{T} <: AbstractParameterNode

function Pump(
node_id,
inflow_id,
outflow_ids,
inflow_edge,
outflow_edges,
active,
flow_rate::T,
min_flow_rate,
Expand All @@ -396,8 +408,8 @@ struct Pump{T} <: AbstractParameterNode
if valid_flow_rates(node_id, get_tmp(flow_rate, 0), control_mapping)
return new{T}(
node_id,
inflow_id,
outflow_ids,
inflow_edge,
outflow_edges,
active,
flow_rate,
min_flow_rate,
Expand All @@ -413,8 +425,10 @@ end

"""
node_id: node ID of the Outlet node
inflow_id: node ID across the incoming flow edge
outflow_ids: node IDs across the outgoing flow edges
inflow_edge: incoming flow edge metadata.
The ID of the destination node is always the ID of the Outlet node
outflow_edges: outgoing flow edges metadata.
The ID of the source node is always the ID of the Outlet node
active: whether this node is active and thus contributes flow
flow_rate: target flow rate
min_flow_rate: The minimal flow rate of the outlet
Expand All @@ -424,8 +438,8 @@ is_pid_controlled: whether the flow rate of this outlet is governed by PID contr
"""
struct Outlet{T} <: AbstractParameterNode
node_id::Vector{NodeID}
inflow_id::Vector{NodeID}
outflow_ids::Vector{Vector{NodeID}}
inflow_edge::Vector{EdgeMetadata}
outflow_edges::Vector{Vector{EdgeMetadata}}
active::BitVector
flow_rate::T
min_flow_rate::Vector{Float64}
Expand Down Expand Up @@ -528,8 +542,10 @@ end

"""
node_id: node ID of the UserDemand node
inflow_id: node ID across the incoming flow edge
outflow_id: node ID across the outgoing flow edge
inflow_edge: incoming flow edge
The ID of the destination node is always the ID of the UserDemand node
outflow_edge: outgoing flow edge metadata
The ID of the source node is always the ID of the UserDemand node
active: whether this node is active and thus demands water
realized_bmi: Cumulative inflow volume, for read or reset by BMI only
demand: water flux demand of UserDemand per priority over time
Expand All @@ -545,8 +561,8 @@ min_level: The level of the source basin below which the UserDemand does not abs
"""
struct UserDemand <: AbstractParameterNode
node_id::Vector{NodeID}
inflow_id::Vector{NodeID}
outflow_id::Vector{NodeID}
inflow_edge::Vector{EdgeMetadata}
outflow_edge::Vector{EdgeMetadata}
active::BitVector
realized_bmi::Vector{Float64}
demand::Matrix{Float64}
Expand Down Expand Up @@ -632,7 +648,7 @@ struct Parameters{T, C1, C2, V1, V2, V3}
@NamedTuple{
node_ids::Dict{Int32, Set{NodeID}},
edges_source::Dict{Int32, Set{EdgeMetadata}},
flow_dict::Dict{Tuple{NodeID, NodeID}, Int},
flow_dict::Dict{Tuple{NodeID, NodeID}, Int32},
flow::T,
flow_prev::Vector{Float64},
flow_integrated::Vector{Float64},
Expand Down
Loading