Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
fail-fast: false
matrix:
version:
- '1.10' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'.
- '1.11' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'.
- '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia.
- 'nightly'
os:
Expand Down
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ CodecZlib = "0.7"
Compose = "0.9"
DataFrames = "1.6"
DataStructures = "0.18.10"
Dates = "1.11.0"
Distributions = "0.25.100"
GR = "0.72.9, 0.73"
GR_jll = "0.73"
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
Streamfall leverages the Julia language and ecosystem to provide:
- Quick heterogeneous modelling of a stream network
- Use of different rainfall-runoff models and their ensembles in tandem
- Modelling and assessment of interacting systems
- Support for modelling and assessment of interacting systems
- A wide range of performance metrics

This package includes implementations of the following:
Expand All @@ -20,12 +20,21 @@ This package includes implementations of the following:

Performance is expected to be similar to implementations in C and Fortran.

Naive timings (using `@time`) for an example dataset spanning 1963-07-05 - 2014-12-31 (18808 days, approximately 51.5 years)

- SimpleHyModNode \
0.016502 seconds (469.25 k allocations: 12.902 MiB)
- GR4JNode \
0.015274 seconds (224.75 k allocations: 5.584 MiB)
- SYMHYDNode \
0.039540 seconds (638.01 k allocations: 15.190 MiB, 46.99% gc time)
- IHACRESBilinearNode \
0.021734 seconds (675.63 k allocations: 17.773 MiB)

The IHACRES rainfall-runoff model was previously implemented with [ihacres_nim](https://github.com/ConnectedSystems/ihacres_nim) but has since been ported to pure Julia.

[Graphs](https://github.com/JuliaGraphs/Graphs.jl) and [MetaGraphs](https://github.com/JuliaGraphs/MetaGraphs.jl) are used underneath for network traversal/analysis.

> [NOTE] Streamfall is currently in its early stages and under active development. Although it is fairly usable for small networks and assessing/analyzing single sub-catchments, things may change drastically and unexpectedly.

## Installation

Streamfall is now registered! The latest release version can be installed with:
Expand Down
8 changes: 0 additions & 8 deletions docs/src/API/nodes/IHACRES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,3 @@ Modules = [Streamfall]
Order = [:function, :type]
Pages = ["Nodes/IHACRES/IHACRESNode.jl"]
```

### IHACRES - Expuh

```@autodocs
Modules = [Streamfall]
Order = [:function, :type]
Pages = ["IHACRESExpuhNode.jl"]
```
18 changes: 10 additions & 8 deletions examples/node_comparison.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Distributed, Plots, StatsPlots
using Distributed
using Plots


N = 4
if nworkers() < N
Expand All @@ -18,23 +20,23 @@ end
comment="#",
dateformat=date_format) |> DataFrame

hist_streamflow = obs_data[:, ["Date", "410730_Q"]]
hist_streamflow = extract_flow(obs_data, "410730")
climate_data = obs_data[:, ["Date", "410730_P", "410730_PET"]]
climate = Climate(climate_data, "_P", "_PET")

burn_in = 366 # 1 year burn-in period

# Create objective function to minimize (here we use Normalized KGE')
func = (obs, sim) -> 1.0 - Streamfall.NmKGE(obs[burn_in:end], sim[burn_in:end])
opt_func = (node) -> calibrate!(node, climate, hist_streamflow[:, "410730_Q"]; metric=func, MaxTime=900)
opt_func = (node) -> calibrate!(node, climate, hist_streamflow[:, "410730"], func; MaxTime=30)
end


# Create individual nodes
hymod_node = create_node(SimpleHyModNode, "410730", 129.2)
gr4j_node = create_node(GR4JNode, "410730", 129.2)
symhyd_node = create_node(SYMHYDNode, "410730", 129.2)
ihacres_node = create_node(BilinearNode, "410730", 129.2)
ihacres_node = create_node(IHACRESBilinearNode, "410730", 129.2)


# Calibrate each node separately using multiprocessing
Expand All @@ -44,14 +46,14 @@ result = pmap(opt_func, node_list)


# Create comparison plot
Qo = hist_streamflow[:, "410730_Q"]
Qo_burn = Qo[burn_in:end]
Qo = hist_streamflow[:, "410730"]
res_plots = []

for ((res, opt), node, n_name) in zip(result, node_list, node_names)
update_params!(node, best_candidate(res)...)
reset!(node)
run_node!(node, climate)
@info typeof(node)
@time run_node!(node, climate)

node_burn = node.outflow[burn_in:end]

Expand All @@ -68,4 +70,4 @@ combined_plot = plot(

display(combined_plot)

savefig("multi_model_comparison_updated.png")
# savefig("multi_model_comparison_updated.png")
4 changes: 3 additions & 1 deletion src/Nodes/GR4J/GR4JNode.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ abstract type GRNJNode <: NetworkNode end
"""
GR4J Node

GR4J: modèle du Génie Rural à 4 paramètres Journalier.

A four-parameter model with two stores.

# Parameters
Expand Down Expand Up @@ -287,7 +289,7 @@ end
p_store=0.0, r_store=0.0
)::Tuple where {F<:Float64}

Generated simulated streamflow for given rainfall and potential evaporation.
Generated simulated streamflow with GR4J for given rainfall and potential evaporation.

# Parameters
- `P` : Catchment average rainfall
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
"""
Current disabled.

Expuh node implementation needs to be updated to use pure julia methods.
"""

using Parameters
using ModelParameters


Base.@kwdef mutable struct ExpuhNode{P, A<:AbstractFloat} <: IHACRESNode
Base.@kwdef mutable struct ExpuhNode{P,A<:AbstractFloat} <: IHACRESNode
name::String
area::A

Expand Down Expand Up @@ -95,9 +101,9 @@ end


function ExpuhNode(name::String, area::Float64, d::Float64, d2::Float64, e::Float64, f::Float64,
tau_q::Float64, tau_s::Float64, v_s::Float64, s_coef::Float64,
store::Float64, quick::Float64, slow::Float64)
return ExpuhNode{Param, Float64}(
tau_q::Float64, tau_s::Float64, v_s::Float64, s_coef::Float64,
store::Float64, quick::Float64, slow::Float64)
return ExpuhNode{Param,Float64}(
name=name,
area=area,
d=d,
Expand Down Expand Up @@ -165,23 +171,21 @@ function run_node!(

flow_res = [0.0, 0.0, 0.0]
@ccall IHACRES.calc_flows(flow_res::Ptr{Cdouble}, prev_q::Cdouble, prev_s::Cdouble, s_node.v_s::Cdouble, e_rainfall::Cdouble,
s_node.area::Cdouble, s_node.tau_q::Cdouble, s_node.tau_s::Cdouble)::Cvoid
s_node.area::Cdouble, s_node.tau_q::Cdouble, s_node.tau_s::Cdouble)::Cvoid
(quick_store, slow_store, outflow) = flow_res

gw_store = s_node.gw_store[end]
routing_res = [0.0, 0.0]
@ccall IHACRES.routing(
routing_res::Ptr{Cdouble},
gw_store::Cdouble,
s_node.storage_coef::Cdouble,
inflow::Cdouble,
outflow::Cdouble,
ext::Cdouble,
gw_exchange::Cdouble)::Cvoid
routing_res::Ptr{Cdouble},
gw_store::Cdouble,
s_node.storage_coef::Cdouble,
inflow::Cdouble,
outflow::Cdouble,
ext::Cdouble,
gw_exchange::Cdouble)::Cvoid
(gw_store, outflow) = routing_res

# level::Float64 = @ccall IHACRES.calc_ft_level(outflow::Cdouble, s_node.level_params::Ptr{Cdouble})::Cdouble

update_state!(s_node, cmd, e_rainfall, et, quick_store, slow_store, outflow, gw_store)

return outflow
Expand Down Expand Up @@ -211,19 +215,19 @@ function run_node_with_temp!(s_node::ExpuhNode, rain::Float64, temp::Float64, in

flow_res = [0.0, 0.0, 0.0]
@ccall IHACRES.calc_flows(flow_res::Ptr{Cdouble}, prev_q::Cdouble, prev_s::Cdouble, s_node.v_s::Cdouble, e_rainfall::Cdouble,
s_node.area::Cdouble, s_node.tau_q::Cdouble, s_node.tau_s::Cdouble)::Cvoid
s_node.area::Cdouble, s_node.tau_q::Cdouble, s_node.tau_s::Cdouble)::Cvoid
(quick_store, slow_store, outflow) = flow_res

gw_store = s_node.gw_store[end]
routing_res = [0.0, 0.0]
@ccall IHACRES.routing(
routing_res::Ptr{Cdouble},
gw_store::Cdouble,
s_node.storage_coef::Cdouble,
inflow::Cdouble,
outflow::Cdouble,
ext::Cdouble,
gw_exchange::Cdouble)::Cvoid
routing_res::Ptr{Cdouble},
gw_store::Cdouble,
s_node.storage_coef::Cdouble,
inflow::Cdouble,
outflow::Cdouble,
ext::Cdouble,
gw_exchange::Cdouble)::Cvoid
(gw_store, outflow) = routing_res

# level::Float64 = @ccall IHACRES.calc_ft_level(outflow::Cdouble, s_node.level_params::Ptr{Cdouble})::Cdouble
Expand All @@ -235,7 +239,7 @@ end


function update_params!(node::ExpuhNode, d::Float64, d2::Float64, e::Float64, f::Float64,
tau_q::Float64, tau_s::Float64, v_s::Float64, s_coef::Float64)::Nothing
tau_q::Float64, tau_s::Float64, v_s::Float64, s_coef::Float64)::Nothing
node.d = Param(d, bounds=node.d.bounds)
node.d2 = Param(d2, bounds=node.d2.bounds)
node.e = Param(e, bounds=node.e.bounds)
Expand Down
32 changes: 8 additions & 24 deletions src/Streamfall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,13 @@ using Graphs, MetaGraphs, Distributed, DataFrames

const MODPATH = @__DIR__

if Sys.iswindows()
libext = ".dll"
elseif Sys.islinux()
libext = ".so"
elseif Sys.isapple()
libext = ".dynlib"
else
throw(DomainError("Unsupported platform"))
end

# Can't use string, DLL location has to be a const
# (which makes sense but still, many hours wasted!)
# https://github.com/JuliaLang/julia/issues/29602
const IHACRES = joinpath(MODPATH, "../deps", "usr", "lib", "ihacres$(libext)")


include("Network.jl")
include("Nodes/Node.jl")
include("Climate.jl")
include("metrics.jl")
include("calibration.jl")
include("Nodes/IHACRES/IHACRESNode.jl")
include("Nodes/IHACRES/IHACRESExpuhNode.jl")
# include("Nodes/IHACRES/IHACRESExpuhNode.jl")
include("Nodes/GR4J/GR4JNode.jl")
include("Nodes/HyMod/HyModNode.jl")
include("Nodes/SYMHYD/SYMHYDNode.jl")
Expand All @@ -37,9 +21,9 @@ include("Nodes/Ensembles/EnsembleNode.jl")


function timestep_value(ts::Int, gauge_id::String, col_partial::String, dataset::DataFrame)::Float64
target_col = filter(x -> occursin(gauge_id, x)
& occursin(col_partial, x),
names(dataset)
target_col = filter(
x -> occursin(gauge_id, x) & occursin(col_partial, x),
names(dataset)
)

amount = 0.0
Expand Down Expand Up @@ -107,7 +91,7 @@ julia> climate, streamflow = align_time_frame(climate, streamflow)
"""
function align_time_frame(timeseries::T...) where {T<:DataFrame}
min_date, max_date = find_common_timeframe(timeseries...)
modded = [t[min_date .<= t.Date .<= max_date, :] for t in timeseries]
modded = [t[min_date.<=t.Date.<=max_date, :] for t in timeseries]

for t in modded
ts_diff = diff(t.Date)
Expand Down Expand Up @@ -137,7 +121,7 @@ function run_basin!(
_, outlets = find_inlets_and_outlets(sn)
@inbounds for outlet_id in outlets
run_node!(sn, outlet_id, climate;
inflow=inflow, extraction=extraction, exchange=exchange)
inflow=inflow, extraction=extraction, exchange=exchange)
end
end

Expand Down Expand Up @@ -294,9 +278,9 @@ include("plotting.jl")

# Nodes
export NetworkNode, GenericNode, GenericDirectNode
export IHACRES, IHACRESNode, IHACRESBilinearNode, ExpuhNode, DamNode, Climate
export IHACRES, IHACRESNode, IHACRESBilinearNode, DamNode, Climate
export create_node, GR4JNode, HyModNode, SimpleHyModNode, SYMHYDNode
export EnsembleNode, WeightedEnsembleNode
export EnsembleNode, WeightedEnsembleNode, GREnsembleNode

# Network
export find_inlets_and_outlets, inlets, outlets
Expand Down
15 changes: 9 additions & 6 deletions src/metrics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ end
"""
NSE(obs, sim)

The Nash-Sutcliffe Efficiency score
The Nash-Sutcliffe Efficiency score.

Ranges from 1.0 to -∞, where values below zero indicate the mean of
observations would outperform a model.
"""
function NSE(obs, sim)
return 1.0 - sum((obs .- sim) .^ 2) / sum((obs .- mean(obs)) .^ 2)
Expand All @@ -164,7 +167,7 @@ end
"""
RMSE(obs, sim)

Root Mean Square Error
Root Mean Square Error.
"""
function RMSE(obs, sim)
return (sum((sim .- obs) .^ 2) / length(sim))^0.5
Expand All @@ -186,7 +189,7 @@ end
"""
ADJ_R2(obs, sim, p::Int64)::Float64

Adjusted R²
Adjusted R².

# Arguments
- `obs::Vector` : observations
Expand All @@ -204,15 +207,15 @@ end
"""
MAE(obs, sim)

Mean Absolute Error
Mean Absolute Error.
"""
MAE(obs, sim) = mean(abs.(sim .- obs))


"""
ME(obs, sim)

Mean Error
Mean Error.
"""
ME(obs, sim) = mean(sim .- obs)

Expand Down Expand Up @@ -471,7 +474,7 @@ function mKGE(obs, sim; scaling=(1.0, 1.0, 1.0))::Float64
# Timing (Pearson's correlation)
r = Statistics.cor(obs, sim)
if isnan(r)
# can happen if obs or sim is a constant (std of 0)
# Can happen if obs or sim is a constant (std of 0)
r = all(obs .== sim) ? 1.0 : 0.0
end

Expand Down
Loading
Loading