diff --git a/README.md b/README.md index f8f343bd..76bdb4e7 100644 --- a/README.md +++ b/README.md @@ -172,18 +172,18 @@ To display an overview of a node or network: julia> node Name: 406219 [IHACRESBilinearNode] Area: 1985.73 -┌──────────────┬───────────┬─────────────┬─────────────┐ -│ Parameter │ Value │ Lower Bound │ Upper Bound │ -├──────────────┼───────────┼─────────────┼─────────────┤ -│ d │ 84.2802 │ 10.0 │ 550.0 │ -│ d2 │ 2.42241 │ 0.0001 │ 10.0 │ -│ e │ 0.812959 │ 0.1 │ 1.5 │ -│ f │ 2.57928 │ 0.01 │ 3.0 │ -│ a │ 5.92338 │ 0.1 │ 10.0 │ -│ b │ 0.0989926 │ 0.001 │ 0.1 │ -│ storage_coef │ 1.86134 │ 1.0e-10 │ 10.0 │ -│ alpha │ 0.727905 │ 1.0e-5 │ 1.0 │ -└──────────────┴───────────┴─────────────┴─────────────┘ +┌──────────────┬───────────┬─────────────┬─────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Parameter │ Value │ Lower Bound │ Upper Bound │ Description │ +├──────────────┼───────────┼─────────────┼─────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ d │ 84.2802 │ 10.0 │ 550.0 │ Catchment moisture deficit threshold, higher values indicate the catchment can hold more water before generating runoff. │ +│ d2 │ 2.42241 │ 0.0001 │ 10.0 │ Scaling factor (d*d2) which creates a second threshold, changing the shape of effective rainfall response. │ +│ e │ 0.812959 │ 0.1 │ 1.5 │ PET conversion factor, controls the rate of evapotranspiration losses, converts temperature to PET. │ +│ f │ 2.57928 │ 0.01 │ 3.0 │ Plant stress threshold, controls at what moisture deficit plants begin to experience stress. │ +│ a │ 5.92338 │ 0.1 │ 10.0 │ Quickflow storage coefficient, where higher values lead to faster quickflow response. │ +│ b │ 0.0989926 │ 0.001 │ 0.1 │ Slowflow storage coefficient, lower values lead to slower baseflow recession. │ +│ storage_coef │ 1.86134 │ 1.0e-10 │ 10.0 │ Groundwater interaction factor, controling how water is exchanged with deeper groundwater. │ +│ alpha │ 0.727905 │ 1.0e-5 │ 1.0 │ Effective rainfall scaling factor, partitions rainfall into runoff. │ +└──────────────┴───────────┴─────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ``` ### Network specification diff --git a/docs/src/examples/node_creation.md b/docs/src/examples/node_creation.md index 62b73e53..17633d09 100644 --- a/docs/src/examples/node_creation.md +++ b/docs/src/examples/node_creation.md @@ -7,14 +7,39 @@ user-defined values. using Streamfall hymod_node = create_node(SimpleHyModNode, "410730", 129.2) +# Name: 410730 [SimpleHyModNode] +# Area: 129.2 +# ┌───────────┬───────┬─────────────┬─────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────┐ +# │ Parameter │ Value │ Lower Bound │ Upper Bound │ Description │ +# ├───────────┼───────┼─────────────┼─────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤ +# │ Sm_max │ 250.0 │ 1.0 │ 500.0 │ Maximum soil storage capacity. │ +# │ B │ 1.0 │ 0.0 │ 2.0 │ Controls how quickly the catchment becomes saturated as rainfall accumulates. │ +# │ alpha │ 0.2 │ 0.0 │ 1.0 │ The split between quick and slow flow components. Higher values direct more water through quickflow. │ +# │ Kf │ 0.5 │ 0.1 │ 0.9999 │ Quickflow recession. │ +# │ Ks │ 0.05 │ 0.001 │ 0.1 │ Slowflow recession. │ +# └───────────┴───────┴─────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘ # Hymod parameters ("hy_" prefix is simply to avoid any variable name conflicts) -hy_Sm_max = 250.0 -hy_B = 1.0 -hy_alpha = 0.2 -hy_Kf = 0.5 -hy_Ks = 0.05 +hy_Sm_max = 370.0 +hy_B = 0.5 +hy_alpha = 0.3 +hy_Kf = 0.25 +hy_Ks = 0.25 # Update parameters update_params!(hymod_node, hy_Sm_max, hy_B, hy_alpha, hy_Kf, hy_Ks) + +# The "Value" column indicates model parameters have been updated. +print(hymod_node) +# Name: 410730 [SimpleHyModNode] +# Area: 129.2 +# ┌───────────┬───────┬─────────────┬─────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────┐ +# │ Parameter │ Value │ Lower Bound │ Upper Bound │ Description │ +# ├───────────┼───────┼─────────────┼─────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤ +# │ Sm_max │ 370.0 │ 1.0 │ 500.0 │ Maximum soil storage capacity. │ +# │ B │ 0.5 │ 0.0 │ 2.0 │ Controls how quickly the catchment becomes saturated as rainfall accumulates. │ +# │ alpha │ 0.3 │ 0.0 │ 1.0 │ The split between quick and slow flow components. Higher values direct more water through quickflow. │ +# │ Kf │ 0.25 │ 0.1 │ 0.9999 │ Quickflow recession. │ +# │ Ks │ 0.25 │ 0.001 │ 0.1 │ Slowflow recession. │ +# └───────────┴───────┴─────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘ ``` diff --git a/docs/src/primer.md b/docs/src/primer.md index 53bcbaf8..f17020ce 100644 --- a/docs/src/primer.md +++ b/docs/src/primer.md @@ -35,7 +35,7 @@ The spec takes the following form: # The node type which defines which model is used for this node # In this case, it is the IHACRES with the bilinear formulation of the CMD module node_type: IHACRESBilinearNode - area: 130.0 # subcatchment area in km^2 (from BoM) + area: 130.0 # subcatchment area in km^2 (from the Australian Bureau of Meteorology) # This spec defines a single node system # so it has no nodes upstream (inlets) or downstream (outlets) @@ -76,18 +76,18 @@ Node 1 -------- Name: 410730 [IHACRESBilinearNode] Area: 130.0 -┌──────────────┬───────┬─────────────┬─────────────┐ -│ Parameter │ Value │ Lower Bound │ Upper Bound │ -├──────────────┼───────┼─────────────┼─────────────┤ -│ d │ 200.0 │ 10.0 │ 550.0 │ -│ d2 │ 2.0 │ 0.0001 │ 10.0 │ -│ e │ 1.0 │ 0.1 │ 1.5 │ -│ f │ 0.8 │ 0.01 │ 3.0 │ -│ a │ 0.9 │ 0.1 │ 10.0 │ -│ b │ 0.1 │ 0.001 │ 0.1 │ -│ storage_coef │ 2.9 │ 1.0e-10 │ 10.0 │ -│ alpha │ 0.95 │ 1.0e-5 │ 1.0 │ -└──────────────┴───────┴─────────────┴─────────────┘ +┌──────────────┬───────┬─────────────┬─────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Parameter │ Value │ Lower Bound │ Upper Bound │ Description │ +├──────────────┼───────┼─────────────┼─────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ d │ 200.0 │ 10.0 │ 550.0 │ Catchment moisture deficit threshold, higher values indicate the catchment can hold more water before generating runoff. │ +│ d2 │ 2.0 │ 0.0001 │ 10.0 │ Scaling factor (d*d2) which creates a second threshold, changing the shape of effective rainfall response. │ +│ e │ 1.0 │ 0.1 │ 1.5 │ PET conversion factor, controls the rate of evapotranspiration losses, converts temperature to PET. │ +│ f │ 0.8 │ 0.01 │ 3.0 │ Plant stress threshold, controls at what moisture deficit plants begin to experience stress. │ +│ a │ 0.9 │ 0.1 │ 10.0 │ Quickflow storage coefficient, where higher values lead to faster quickflow response. │ +│ b │ 0.1 │ 0.001 │ 0.1 │ Slowflow storage coefficient, lower values lead to slower baseflow recession. │ +│ storage_coef │ 2.9 │ 1.0e-10 │ 10.0 │ Groundwater interaction factor, controling how water is exchanged with deeper groundwater. │ +│ alpha │ 0.95 │ 1.0e-5 │ 1.0 │ Effective rainfall scaling factor, partitions rainfall into runoff. │ +└──────────────┴───────┴─────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ``` diff --git a/src/Nodes/DamNode.jl b/src/Nodes/DamNode.jl index 0518316b..4db4cb24 100644 --- a/src/Nodes/DamNode.jl +++ b/src/Nodes/DamNode.jl @@ -31,7 +31,7 @@ Base.@kwdef mutable struct DamNode{P,A<:AbstractFloat} <: NetworkNode area::A max_storage::A - storage_coef::P = Param(0.5, bounds=(0.00001, 10.0)) + storage_coef::P = Param(0.5, bounds=(0.00001, 10.0), desc="Storage coefficient.") # Dam storage volume to level calc_dam_level::Function = c_dam_level diff --git a/src/Nodes/GR4J/GR4JNode.jl b/src/Nodes/GR4J/GR4JNode.jl index fdd8c957..b3577539 100644 --- a/src/Nodes/GR4J/GR4JNode.jl +++ b/src/Nodes/GR4J/GR4JNode.jl @@ -71,14 +71,10 @@ Base.@kwdef mutable struct GR4JNode{P,A<:AbstractFloat} <: GRNJNode const area::A # Parameters - # x1 : maximum capacity of the production store (mm) (> 0) - # x2 : groundwater exchange coefficient (mm) (value < and > 0 possible) - # x3 : one day ahead maximum capacity of the routing store (mm, > 0) - # x4 : time base of unit hydrograph UH1 (days, > 0.5) - X1::P = Param(350.0, bounds=(1.0, 1500.0)) - X2::P = Param(0.0, bounds=(-10.0, 5.0)) - X3::P = Param(40.0, bounds=(1.0, 500.0)) - X4::P = Param(0.5, bounds=(0.5, 10.0)) + X1::P = Param(350.0, bounds=(1.0, 1500.0), desc="Maximum soil water storage capacity.") + X2::P = Param(0.0, bounds=(-10.0, 5.0), desc="Water exchange with deeper groundwater and adjacent catchments.") + X3::P = Param(40.0, bounds=(1.0, 500.0), desc="Maximum capacity of one day ahead of routing store, controls the baseflow component.") + X4::P = Param(0.5, bounds=(0.5, 10.0), desc="Time base of quickflow controlling the timing and shape of hydrograph.") # stores p_store::Vector{A} = [0.0] @@ -226,10 +222,13 @@ end Update parameters for GR4J. """ function update_params!(node::GR4JNode, X1::Float64, X2::Float64, X3::Float64, X4::Float64)::Nothing - node.X1 = Param(X1, bounds=node.X1.bounds) - node.X2 = Param(X2, bounds=node.X2.bounds) - node.X3 = Param(X3, bounds=node.X3.bounds) - node.X4 = Param(X4, bounds=node.X4.bounds) + param_pairs = zip([:X1, :X2, :X3, :X4], [X1, X2, X3, X4]) + + # First item will always be the set value, so it can be skipped + for (p, v) in param_pairs + param = getfield(node, p) + setfield!(node, p, Param(v; (keys(param)[2:end] .=> values(param)[2:end])...)) + end return nothing end diff --git a/src/Nodes/HyMod/HyModNode.jl b/src/Nodes/HyMod/HyModNode.jl index 30c0fae6..3598956f 100644 --- a/src/Nodes/HyMod/HyModNode.jl +++ b/src/Nodes/HyMod/HyModNode.jl @@ -25,11 +25,11 @@ Base.@kwdef mutable struct SimpleHyModNode{P,A<:AbstractFloat} <: HyModNode const area::A # parameters - Sm_max::P = Param(250.0, bounds=(1.0, 500.0)) - B::P = Param(1.0, bounds=(0.0, 2.0)) - alpha::P = Param(0.2, bounds=(0.0, 1.0)) - Kf::P = Param(0.5, bounds=(0.1, 0.9999)) - Ks::P = Param(0.05, bounds=(0.001, 0.1)) + Sm_max::P = Param(250.0, bounds=(1.0, 500.0), desc="Maximum soil storage capacity.") + B::P = Param(1.0, bounds=(0.0, 2.0), desc="Controls how quickly the catchment becomes saturated as rainfall accumulates.") + alpha::P = Param(0.2, bounds=(0.0, 1.0), desc="The split between quick and slow flow components. Higher values direct more water through quickflow.") + Kf::P = Param(0.5, bounds=(0.1, 0.9999), desc="Quickflow recession.") + Ks::P = Param(0.05, bounds=(0.001, 0.1), desc="Slowflow recession.") # stores Sm::Array{A} = [0.0] @@ -173,11 +173,13 @@ end Update parameters for HyMod. """ function update_params!(node::HyModNode, Sm_max::F, B::F, alpha::F, Kf::F, Ks::F) where {F<:Float64} - node.Sm_max = Param(Sm_max, bounds=node.Sm_max.bounds::Tuple) - node.B = Param(B, bounds=node.B.bounds::Tuple) - node.alpha = Param(alpha, bounds=node.alpha.bounds::Tuple) - node.Kf = Param(Kf, bounds=node.Kf.bounds::Tuple) - node.Ks = Param(Ks, bounds=node.Ks.bounds::Tuple) + param_pairs = zip([:Sm_max, :B, :alpha, :Kf, :Ks], [Sm_max, B, alpha, Kf, Ks]) + + # First item will always be the set value, so it can be skipped + for (p, v) in param_pairs + param = getfield(node, p) + setfield!(node, p, Param(v; (keys(param)[2:end] .=> values(param)[2:end])...)) + end end function update_state!( diff --git a/src/Nodes/IHACRES/IHACRESNode.jl b/src/Nodes/IHACRES/IHACRESNode.jl index 58a6a542..bcd2b628 100644 --- a/src/Nodes/IHACRES/IHACRESNode.jl +++ b/src/Nodes/IHACRES/IHACRESNode.jl @@ -13,15 +13,15 @@ Base.@kwdef mutable struct IHACRESBilinearNode{P,A<:AbstractFloat} <: IHACRESNod const area::A # https://wiki.ewater.org.au/display/SD41/IHACRES-CMD+-+SRG - d::P = Param(200.0, bounds=(10.0, 550.0)) # flow threshold - d2::P = Param(2.0, bounds=(0.0001, 10.0)) # flow threshold2 - e::P = Param(1.0, bounds=(0.999, 1.0)) # temperature to PET conversion factor - f::P = Param(0.8, bounds=(0.01, 3.0)) # plant stress threshold factor (multiplicative factor of d) - a::P = Param(0.9, bounds=(0.1, 10.0)) # quickflow storage coefficient == (1/tau_q) - b::P = Param(0.1, bounds=(1e-3, 0.1)) # slowflow storage coefficent == (1/tau_s) + d::P = Param(200.0, bounds=(10.0, 550.0), desc="Catchment moisture deficit threshold, higher values indicate the catchment can hold more water before generating runoff.") + d2::P = Param(2.0, bounds=(0.0001, 10.0), desc="Scaling factor (d*d2) which creates a second threshold, changing the shape of effective rainfall response.") + e::P = Param(1.0, bounds=(0.999, 1.0), desc="PET conversion factor, controls the rate of evapotranspiration losses, converts temperature to PET.") + f::P = Param(0.8, bounds=(0.01, 3.0), desc="Plant stress threshold, controls at what moisture deficit plants begin to experience stress.") + a::P = Param(0.9, bounds=(0.1, 10.0), desc="Quickflow storage coefficient, where higher values lead to faster quickflow response.") # quickflow storage coefficient == (1/tau_q) + b::P = Param(0.1, bounds=(1e-3, 0.1), desc="Slowflow storage coefficient, lower values lead to slower baseflow recession.") # slowflow storage coefficent == (1/tau_s) - storage_coef::P = Param(2.9, bounds=(1e-10, 10.0)) - alpha::P = Param(0.1, bounds=(1e-5, 1 - 1 / 10^9)) + storage_coef::P = Param(2.9, bounds=(1e-10, 10.0), desc="Groundwater interaction factor, controling how water is exchanged with deeper groundwater.") + alpha::P = Param(0.1, bounds=(1e-5, 1 - 1 / 10^9), desc="Effective rainfall scaling factor, partitions rainfall into runoff.") # const level_params::Array{P, 1} = [ # Param(-0.01, bounds=(-10.0, -0.01)), # p1 @@ -462,14 +462,16 @@ function update_params!( s_coef::F, alpha::F )::Nothing where {F<:Float64} - node.d = Param(d, bounds=node.d.bounds::Tuple) - node.d2 = Param(d2, bounds=node.d2.bounds::Tuple) - node.e = Param(e, bounds=node.e.bounds::Tuple) - node.f = Param(f, bounds=node.f.bounds::Tuple) - node.a = Param(a, bounds=node.a.bounds::Tuple) - node.b = Param(b, bounds=node.b.bounds::Tuple) - node.storage_coef = Param(s_coef, bounds=node.storage_coef.bounds::Tuple) - node.alpha = Param(alpha, bounds=node.alpha.bounds::Tuple) + param_pairs = zip( + [:d, :d2, :e, :f, :a, :b, :storage_coef, :alpha], + [d, d2, e, f, a, b, s_coef, alpha] + ) + + # First item will always be the set value, so it can be skipped + for (p, v) in param_pairs + param = getfield(node, p) + setfield!(node, p, Param(v; (keys(param)[2:end] .=> values(param)[2:end])...)) + end return nothing end diff --git a/src/Nodes/Node.jl b/src/Nodes/Node.jl index e4fa6b64..f6138a7a 100644 --- a/src/Nodes/Node.jl +++ b/src/Nodes/Node.jl @@ -19,7 +19,7 @@ end Create node of a given type. """ function create_node(node::Type{<:NetworkNode}, name::String, area::Float64) - return node{Param, Float64}(; name=name, area=area) + return node{Param,Float64}(; name=name, area=area) end @@ -55,7 +55,6 @@ function GenericDirectNode(name::String, spec::Dict) end - """ param_info(node::NetworkNode) @@ -72,7 +71,6 @@ function param_info(node::NetworkNode; kwargs...)::Tuple return param_names, values, bounds end - """ get_node_id(mg::MetaDiGraph, node_name::String)::Int64 @@ -160,10 +158,15 @@ function Base.show(io::IO, n::NetworkNode) println(io, "Name: $(n.name) [$(ntype_name)]") println(io, "Area: $(n.area)") - param_names, x0, bounds = param_info(n; with_level=false) + n_model = Model(n) + param_names = n_model[:fieldname] + x0 = n_model[:val] + bounds = n_model[:bounds] + descs = n_model[:desc] + lb, ub = zip(bounds...) - details = hcat(param_names, x0, [lb...], [ub...]) + details = hcat([param_names...], [x0...], [lb...], [ub...], [descs...]) - pretty_table(io, details, header=["Parameter", "Value", "Lower Bound", "Upper Bound"]) + pretty_table(io, details, header=["Parameter", "Value", "Lower Bound", "Upper Bound", "Description"]) print("\n") end diff --git a/src/Nodes/SIMHYD/SIMHYDNode.jl b/src/Nodes/SIMHYD/SIMHYDNode.jl index 0861134b..fcd39e7e 100644 --- a/src/Nodes/SIMHYD/SIMHYDNode.jl +++ b/src/Nodes/SIMHYD/SIMHYDNode.jl @@ -9,15 +9,15 @@ Base.@kwdef mutable struct SIMHYDNode{P,A<:AbstractFloat} <: NetworkNode area::A # parameters - baseflow_coef::P = Param(0.5, bounds=(0.0, 1.0)) - impervious_threshold::P = Param(2.5, bounds=(0.0, 5.0)) # mm - infiltration_coef::P = Param(200.0, bounds=(0.0, 400.0)) - infiltration_shape::P = Param(5.0, bounds=(0.0, 10.0)) - interflow_coef::P = Param(0.5, bounds=(0.0, 1.0)) - pervious_fraction::P = Param(0.5, bounds=(0.0, 1.0)) - risc::P = Param(2.5, bounds=(0.0, 5.0)) # rainfall interception store capacity (mm) - recharge_coef::P = Param(0.5, bounds=(0.0, 1.0)) - smsc::P = Param(250.0, bounds=(1.0, 500.0)) # Soil Moisture Store Capacity (mm) + baseflow_coef::P = Param(0.5, bounds=(0.0, 1.0), desc="Rate of release from groundwater.") + impervious_threshold::P = Param(2.5, bounds=(0.0, 5.0), desc="Threshold below which rainfall is lost to evaporation (in mm).") # mm + infiltration_coef::P = Param(200.0, bounds=(0.0, 400.0), desc="Maximum infiltration rate. Higher values allow more infiltration into soil.") + infiltration_shape::P = Param(5.0, bounds=(0.0, 10.0), desc="Controls decrease in infiltration with increasing soil moisture.") + interflow_coef::P = Param(0.5, bounds=(0.0, 1.0), desc="Movement of water laterally through soil.") + pervious_fraction::P = Param(0.5, bounds=(0.0, 1.0), desc="Indicates fraction of catchment that is pervious.") + risc::P = Param(2.5, bounds=(0.0, 5.0), desc="Represents canopy/vegetation interception capacity (in mm).") + recharge_coef::P = Param(0.5, bounds=(0.0, 1.0), desc="Rate of deep percolation from soil to groundwater.") + smsc::P = Param(250.0, bounds=(1.0, 500.0), desc="Maximum capacity of soil (in mm).") # stores sm_store::Array{A} = [0.0] @@ -183,15 +183,16 @@ function update_params!( recharge_coef::Float64, smsc::Float64 )::Nothing - node.baseflow_coef = Param(baseflow_coef, bounds=node.baseflow_coef.bounds) - node.impervious_threshold = Param(impervious_threshold, bounds=node.impervious_threshold.bounds) - node.infiltration_coef = Param(infiltration_coef, bounds=node.infiltration_coef.bounds) - node.infiltration_shape = Param(infiltration_shape, bounds=node.infiltration_shape.bounds) - node.interflow_coef = Param(interflow_coef, bounds=node.interflow_coef.bounds) - node.pervious_fraction = Param(pervious_fraction, bounds=node.pervious_fraction.bounds) - node.risc = Param(risc, bounds=node.risc.bounds) - node.recharge_coef = Param(recharge_coef, bounds=node.recharge_coef.bounds) - node.smsc = Param(smsc, bounds=node.smsc.bounds) + param_pairs = zip( + [:baseflow_coef, :impervious_threshold, :infiltration_coef, :infiltration_shape, :interflow_coef, :pervious_fraction, :risc, :recharge_coef, :smsc], + [baseflow_coef, impervious_threshold, infiltration_coef, infiltration_shape, interflow_coef, pervious_fraction, risc, recharge_coef, smsc] + ) + + # First item will always be the set value, so it can be skipped + for (p, v) in param_pairs + param = getfield(node, p) + setfield!(node, p, Param(v; (keys(param)[2:end] .=> values(param)[2:end])...)) + end return nothing end