Repository files navigation

Streamfall.jl

A graph-based streamflow modelling system written in Julialang.

DOI

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
  • Support for modelling and assessment of interacting systems
  • A wide range of performance metrics

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

This package includes implementations of the following (with naive timings using the @time macro):

  • GR4J [0.015274 seconds (224.75 k allocations: 5.584 MiB)]
  • HyMod [0.016502 seconds (469.25 k allocations: 12.902 MiB)]
  • IHACRES [0.021734 seconds (675.63 k allocations: 17.773 MiB)]
  • SIMHYD [0.022818 seconds (600.39 k allocations: 16.768 MiB)]

Timings were taken using an example dataset spanning 1963-07-05 - 2014-12-31 (18808 days, approximately 51.5 years)

The IHACRES rainfall-runoff model was previously implemented with ihacres_nim but has since been ported to pure Julia.

Graphs and MetaGraphs are used underneath for network traversal/analysis.

Installation

Streamfall is now registered! The latest release version can be installed with:

] add Streamfall

or the latest development version from GitHub with dev or add:

# Editable install
] dev Streamfall#main
] add https://github.com/ConnectedSystems/Streamfall.jl#main

Development

Local development should follow the usual process of git cloning the repository.

To build locally:

$ julia --project=.
julia>] build

To run tests:

julia>] test

Usage

The examples below use data from the CAMEL-AUS dataset, available here:

Fowler, K. J. A., Acharya, S. C., Addor, N., Chou, C., and Peel, M. C.: CAMELS-AUS: hydrometeorological time series and landscape attributes for 222 catchments in Australia, Earth Syst. Sci. Data, 13, 3847–3867, https://doi.org/10.5194/essd-13-3847-2021, 2021.

Note that since start of development, an updated dataset is incoming (currently under review):

Fowler, K. J. A., Zhang, Z., and Hou, X.: CAMELS-AUS v2: updated hydrometeorological timeseries and landscape attributes for an enlarged set of catchments in Australia, Earth Syst. Sci. Data Discuss. [preprint], https://doi.org/10.5194/essd-2024-263, in review, 2024.

Climate data was sourced from the Climate Change in Australia data service. Additional data was extracted from the Long Paddock data silo.

Quick start (single node)

The examples below are run from the examples directory.

using Statistics
using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
# Load data file which holds observed streamflow, precipitation and PET data
obs_data = CSV.read(
joinpath(data_dir, "cotter/climate/CAMELS-AUS_410730.csv"), DataFrame; comment="#"
)
# 18808×8 DataFrame# Row │ year month day Date 410730_P 410730_PET 410730_max_T 410730_Q# │ Int64 Int64 Int64 Date Float64 Float64 Float64 Float64# ───────┼─────────────────────────────────────────────────────────────────────────────────# 1 │ 1963 7 5 1963-07-05 0.204475 1.02646 6.80409 127.322# 2 │ 1963 7 6 1963-07-06 4.24377 0.790078 5.91556 110.224# 3 │ 1963 7 7 1963-07-07 5.20097 0.400584 3.02218 117.653# By default, Streamfall expects Date, Precipitation (P), Evapotranspiration (ET) and# Flow (Q) columns for each gauge as a minimum.# Some rainfall-runoff models may also require Temperature (T) data.
Qo =extract_flow(obs_data, "410730")
climate =extract_climate(obs_data)
# Create a node (node type, node id, catchment/watershed area)
hymod_node =create_node(SimpleHyModNode, "410730", 129.2);
# Attempt to fit model parameters for 30 seconds# Here, we use RMSE (Root Mean Square Error) as the objective function# Note that Streamfall assumes any objective function is to be minimized.calibrate!(hymod_node, climate, Qo, Streamfall.RMSE; MaxTime=30)
# Run the fitted modelrun_node!(hymod_node, climate)
# Display a basic overview plot (shows time series and Q-Q plot)# using a 366 day offset (e.g., ~1 year burn-in period)quickplot(Qo, hymod_node, climate, "HyMod"; burn_in=366)
# Save figuresavefig("quick_example.png")

Quick start (network of nodes)

# Load and generate stream network
network_spec = YAML.load_file("network.yml")
sn =create_network("Example Network", network_spec)
# Show figure of networkplot_network(sn)
# Calibrate network using the BlackBoxOptim package# keyword arguments will be passed to the `bboptimize()` functioncalibrate!(sn, climate, Qo, Streamfall.RMSE; MaxTime=180.0)
# Run stream network# There is also `run_catchment!()` which does the same thingrun_basin!(sn, climate)
# Get a specific node in network
node = sn[1] # get the first node in the network ("node1")# Nodes can also be retrieved by name# which will also return its position in the network:# nid, node = sn["node1"]# Compare "goodness-of-fit"
Streamfall.RMSE(obs_streamflow, node.outflow)
# Save calibrated network spec to file
Streamfall.save_network(sn, "calibrated_example.yml")

To display an overview of a node or network:

julia> node
Name:406219 [IHACRESBilinearNode]
Area:1985.73
┌──────────────┬───────────┬─────────────┬─────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Parameter │ Value │ Lower Bound │ Upper Bound │ Description │
├──────────────┼───────────┼─────────────┼─────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ d │ 84.280210.0550.0 │ Catchment moisture deficit threshold, higher values indicate the catchment can hold more water before generating runoff. │
│ d2 │ 2.422410.000110.0 │ Scaling factor (d*d2) which creates a second threshold, changing the shape of effective rainfall response. │
│ e │ 0.8129590.11.5 │ PET conversion factor, controls the rate of evapotranspiration losses, converts temperature to PET. │
│ f │ 2.579280.013.0 │ Plant stress threshold, controls at what moisture deficit plants begin to experience stress. │
│ a │ 5.923380.110.0 │ Quickflow storage coefficient, where higher values lead to faster quickflow response. │
│ b │ 0.09899260.0010.1 │ Slowflow storage coefficient, lower values lead to slower baseflow recession. │
│ storage_coef │ 1.861341.0e-1010.0 │ Groundwater interaction factor, controlling how water is exchanged with deeper groundwater. │
│ alpha │ 0.7279051.0e-51.0 │ Effective rainfall scaling factor, partitions rainfall into runoff. │
└──────────────┴───────────┴─────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Network specification

Stream networks are specified as Dictionaries, with an entry for each node.

An example spec from a YAML file is shown here, with connectivity between nodes defined by their names.

# ... Partial snippet of stream definition as an example ...Node3:
node_type: IHACRESBilinearNode # node type, typically tied to the attached modelinlets: # nodes that contribute incoming streamflow
- Node1
- Node2outlets: Node4 # node that this node flows toarea: 150.0# subcatchment area in km^2parameters:
# model specific parameters defined here...

A full example of the spec is available here. The snippet above defines Node 3 in the diagram below.

Each node defines a subcatchment and holds the relevant parameter values for the associated model.

Running a network

using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
# Load a network from a file, providing a name for the network and the file path.# Creates a graph representation of the stream with associated metadata.
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
sn =load_network("Example Network", joinpath(data_dir, "campaspe/campaspe_network.yml"))
# Load climate data, in this case from a CSV file with data for all nodes.# Indicate which columns are precipitation and evaporation data based on partial identifiers
climate =Climate(joinpath(data_dir, "campaspe/climate/climate.csv"), "_rain", "_evap")
# This runs an entire stream network@info"Running an example stream..."run_catchment!(sn, climate)
@info"Displaying outflow from node 406219"
node_id, node = sn["406219"]
plot(node.outflow)

Individual nodes can be run for more fine-grain control.

# Run up to a point in the stream for all time steps.# All nodes upstream will be run as well (but not those downstream)
node_id, node = sn["406219"]
run_node!(sn, node_id, climate)
# Reset a node (clears stored states)reset!(node)
# Run a specific node, and only a specific node, for all time steps
inflow =...# inflows for each time step
extractions =...# extractions from stream for each time step
gw_flux =...# forced groundwater interactions for each time steprun_node!(node, climate; inflow=inflow, extraction=extractions, exchange=gw_flux)

Another approach is to identify the outlets for a given network...

inlets, outlets =find_inlets_and_outlets(sn)

... and call run_node! for each outlet (with relevant climate data), which will recurse through all relevant nodes upstream.

@info"Running example stream..."
timesteps =sim_length(climate)
reset!(sn)
prep_state!(sn, timesteps)
for ts in (1:timesteps)
for outlet in outlets
run_node!(sn, outlet, climate, ts)
endend

See the docs for an overview and example applications.

Further preliminary usage examples are provided in the examples directory.

About

A graph-based streamflow modelling system in Julialang

Resources

Stars

15 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Streamfall.jl

A graph-based streamflow modelling system written in Julialang.

DOI

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
  • Support for modelling and assessment of interacting systems
  • A wide range of performance metrics

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

This package includes implementations of the following (with naive timings using the @time macro):

  • GR4J [0.015274 seconds (224.75 k allocations: 5.584 MiB)]
  • HyMod [0.016502 seconds (469.25 k allocations: 12.902 MiB)]
  • IHACRES [0.021734 seconds (675.63 k allocations: 17.773 MiB)]
  • SIMHYD [0.022818 seconds (600.39 k allocations: 16.768 MiB)]

Timings were taken using an example dataset spanning 1963-07-05 - 2014-12-31 (18808 days, approximately 51.5 years)

The IHACRES rainfall-runoff model was previously implemented with ihacres_nim but has since been ported to pure Julia.

Graphs and MetaGraphs are used underneath for network traversal/analysis.

Installation

Streamfall is now registered! The latest release version can be installed with:

] add Streamfall

or the latest development version from GitHub with dev or add:

# Editable install
] dev Streamfall#main
] add https://github.com/ConnectedSystems/Streamfall.jl#main

Development

Local development should follow the usual process of git cloning the repository.

To build locally:

$ julia --project=.
julia>] build

To run tests:

julia>] test

Usage

The examples below use data from the CAMEL-AUS dataset, available here:

Fowler, K. J. A., Acharya, S. C., Addor, N., Chou, C., and Peel, M. C.: CAMELS-AUS: hydrometeorological time series and landscape attributes for 222 catchments in Australia, Earth Syst. Sci. Data, 13, 3847–3867, https://doi.org/10.5194/essd-13-3847-2021, 2021.

Note that since start of development, an updated dataset is incoming (currently under review):

Fowler, K. J. A., Zhang, Z., and Hou, X.: CAMELS-AUS v2: updated hydrometeorological timeseries and landscape attributes for an enlarged set of catchments in Australia, Earth Syst. Sci. Data Discuss. [preprint], https://doi.org/10.5194/essd-2024-263, in review, 2024.

Climate data was sourced from the Climate Change in Australia data service. Additional data was extracted from the Long Paddock data silo.

Quick start (single node)

The examples below are run from the examples directory.

using Statistics
using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
# Load data file which holds observed streamflow, precipitation and PET data
obs_data = CSV.read(
joinpath(data_dir, "cotter/climate/CAMELS-AUS_410730.csv"), DataFrame; comment="#"
)
# 18808×8 DataFrame# Row │ year month day Date 410730_P 410730_PET 410730_max_T 410730_Q# │ Int64 Int64 Int64 Date Float64 Float64 Float64 Float64# ───────┼─────────────────────────────────────────────────────────────────────────────────# 1 │ 1963 7 5 1963-07-05 0.204475 1.02646 6.80409 127.322# 2 │ 1963 7 6 1963-07-06 4.24377 0.790078 5.91556 110.224# 3 │ 1963 7 7 1963-07-07 5.20097 0.400584 3.02218 117.653# By default, Streamfall expects Date, Precipitation (P), Evapotranspiration (ET) and# Flow (Q) columns for each gauge as a minimum.# Some rainfall-runoff models may also require Temperature (T) data.
Qo =extract_flow(obs_data, "410730")
climate =extract_climate(obs_data)
# Create a node (node type, node id, catchment/watershed area)
hymod_node =create_node(SimpleHyModNode, "410730", 129.2);
# Attempt to fit model parameters for 30 seconds# Here, we use RMSE (Root Mean Square Error) as the objective function# Note that Streamfall assumes any objective function is to be minimized.calibrate!(hymod_node, climate, Qo, Streamfall.RMSE; MaxTime=30)
# Run the fitted modelrun_node!(hymod_node, climate)
# Display a basic overview plot (shows time series and Q-Q plot)# using a 366 day offset (e.g., ~1 year burn-in period)quickplot(Qo, hymod_node, climate, "HyMod"; burn_in=366)
# Save figuresavefig("quick_example.png")

Quick start (network of nodes)

# Load and generate stream network
network_spec = YAML.load_file("network.yml")
sn =create_network("Example Network", network_spec)
# Show figure of networkplot_network(sn)
# Calibrate network using the BlackBoxOptim package# keyword arguments will be passed to the `bboptimize()` functioncalibrate!(sn, climate, Qo, Streamfall.RMSE; MaxTime=180.0)
# Run stream network# There is also `run_catchment!()` which does the same thingrun_basin!(sn, climate)
# Get a specific node in network
node = sn[1] # get the first node in the network ("node1")# Nodes can also be retrieved by name# which will also return its position in the network:# nid, node = sn["node1"]# Compare "goodness-of-fit"
Streamfall.RMSE(obs_streamflow, node.outflow)
# Save calibrated network spec to file
Streamfall.save_network(sn, "calibrated_example.yml")

To display an overview of a node or network:

julia> node
Name:406219 [IHACRESBilinearNode]
Area:1985.73
┌──────────────┬───────────┬─────────────┬─────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Parameter │ Value │ Lower Bound │ Upper Bound │ Description │
├──────────────┼───────────┼─────────────┼─────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ d │ 84.280210.0550.0 │ Catchment moisture deficit threshold, higher values indicate the catchment can hold more water before generating runoff. │
│ d2 │ 2.422410.000110.0 │ Scaling factor (d*d2) which creates a second threshold, changing the shape of effective rainfall response. │
│ e │ 0.8129590.11.5 │ PET conversion factor, controls the rate of evapotranspiration losses, converts temperature to PET. │
│ f │ 2.579280.013.0 │ Plant stress threshold, controls at what moisture deficit plants begin to experience stress. │
│ a │ 5.923380.110.0 │ Quickflow storage coefficient, where higher values lead to faster quickflow response. │
│ b │ 0.09899260.0010.1 │ Slowflow storage coefficient, lower values lead to slower baseflow recession. │
│ storage_coef │ 1.861341.0e-1010.0 │ Groundwater interaction factor, controlling how water is exchanged with deeper groundwater. │
│ alpha │ 0.7279051.0e-51.0 │ Effective rainfall scaling factor, partitions rainfall into runoff. │
└──────────────┴───────────┴─────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Network specification

Stream networks are specified as Dictionaries, with an entry for each node.

An example spec from a YAML file is shown here, with connectivity between nodes defined by their names.

# ... Partial snippet of stream definition as an example ...Node3:
node_type: IHACRESBilinearNode # node type, typically tied to the attached modelinlets: # nodes that contribute incoming streamflow
- Node1
- Node2outlets: Node4 # node that this node flows toarea: 150.0# subcatchment area in km^2parameters:
# model specific parameters defined here...

A full example of the spec is available here. The snippet above defines Node 3 in the diagram below.

Each node defines a subcatchment and holds the relevant parameter values for the associated model.

Running a network

using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
# Load a network from a file, providing a name for the network and the file path.# Creates a graph representation of the stream with associated metadata.
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
sn =load_network("Example Network", joinpath(data_dir, "campaspe/campaspe_network.yml"))
# Load climate data, in this case from a CSV file with data for all nodes.# Indicate which columns are precipitation and evaporation data based on partial identifiers
climate =Climate(joinpath(data_dir, "campaspe/climate/climate.csv"), "_rain", "_evap")
# This runs an entire stream network@info"Running an example stream..."run_catchment!(sn, climate)
@info"Displaying outflow from node 406219"
node_id, node = sn["406219"]
plot(node.outflow)

Individual nodes can be run for more fine-grain control.

# Run up to a point in the stream for all time steps.# All nodes upstream will be run as well (but not those downstream)
node_id, node = sn["406219"]
run_node!(sn, node_id, climate)
# Reset a node (clears stored states)reset!(node)
# Run a specific node, and only a specific node, for all time steps
inflow =...# inflows for each time step
extractions =...# extractions from stream for each time step
gw_flux =...# forced groundwater interactions for each time steprun_node!(node, climate; inflow=inflow, extraction=extractions, exchange=gw_flux)

Another approach is to identify the outlets for a given network...

inlets, outlets =find_inlets_and_outlets(sn)

... and call run_node! for each outlet (with relevant climate data), which will recurse through all relevant nodes upstream.

@info"Running example stream..."
timesteps =sim_length(climate)
reset!(sn)
prep_state!(sn, timesteps)
for ts in (1:timesteps)
for outlet in outlets
run_node!(sn, outlet, climate, ts)
endend

See the docs for an overview and example applications.

Further preliminary usage examples are provided in the examples directory.

About

A graph-based streamflow modelling system in Julialang

Resources

Stars

15 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Streamfall.jl

A graph-based streamflow modelling system written in Julialang.

DOI

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
  • Support for modelling and assessment of interacting systems
  • A wide range of performance metrics

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

This package includes implementations of the following (with naive timings using the @time macro):

  • GR4J [0.015274 seconds (224.75 k allocations: 5.584 MiB)]
  • HyMod [0.016502 seconds (469.25 k allocations: 12.902 MiB)]
  • IHACRES [0.021734 seconds (675.63 k allocations: 17.773 MiB)]
  • SIMHYD [0.022818 seconds (600.39 k allocations: 16.768 MiB)]

Timings were taken using an example dataset spanning 1963-07-05 - 2014-12-31 (18808 days, approximately 51.5 years)

The IHACRES rainfall-runoff model was previously implemented with ihacres_nim but has since been ported to pure Julia.

Graphs and MetaGraphs are used underneath for network traversal/analysis.

Installation

Streamfall is now registered! The latest release version can be installed with:

] add Streamfall

or the latest development version from GitHub with dev or add:

# Editable install
] dev Streamfall#main
] add https://github.com/ConnectedSystems/Streamfall.jl#main

Development

Local development should follow the usual process of git cloning the repository.

To build locally:

$ julia --project=.
julia>] build

To run tests:

julia>] test

Usage

The examples below use data from the CAMEL-AUS dataset, available here:

Fowler, K. J. A., Acharya, S. C., Addor, N., Chou, C., and Peel, M. C.: CAMELS-AUS: hydrometeorological time series and landscape attributes for 222 catchments in Australia, Earth Syst. Sci. Data, 13, 3847–3867, https://doi.org/10.5194/essd-13-3847-2021, 2021.

Note that since start of development, an updated dataset is incoming (currently under review):

Fowler, K. J. A., Zhang, Z., and Hou, X.: CAMELS-AUS v2: updated hydrometeorological timeseries and landscape attributes for an enlarged set of catchments in Australia, Earth Syst. Sci. Data Discuss. [preprint], https://doi.org/10.5194/essd-2024-263, in review, 2024.

Climate data was sourced from the Climate Change in Australia data service. Additional data was extracted from the Long Paddock data silo.

Quick start (single node)

The examples below are run from the examples directory.

using Statistics
using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
# Load data file which holds observed streamflow, precipitation and PET data
obs_data = CSV.read(
joinpath(data_dir, "cotter/climate/CAMELS-AUS_410730.csv"), DataFrame; comment="#"
)
# 18808×8 DataFrame# Row │ year month day Date 410730_P 410730_PET 410730_max_T 410730_Q# │ Int64 Int64 Int64 Date Float64 Float64 Float64 Float64# ───────┼─────────────────────────────────────────────────────────────────────────────────# 1 │ 1963 7 5 1963-07-05 0.204475 1.02646 6.80409 127.322# 2 │ 1963 7 6 1963-07-06 4.24377 0.790078 5.91556 110.224# 3 │ 1963 7 7 1963-07-07 5.20097 0.400584 3.02218 117.653# By default, Streamfall expects Date, Precipitation (P), Evapotranspiration (ET) and# Flow (Q) columns for each gauge as a minimum.# Some rainfall-runoff models may also require Temperature (T) data.
Qo =extract_flow(obs_data, "410730")
climate =extract_climate(obs_data)
# Create a node (node type, node id, catchment/watershed area)
hymod_node =create_node(SimpleHyModNode, "410730", 129.2);
# Attempt to fit model parameters for 30 seconds# Here, we use RMSE (Root Mean Square Error) as the objective function# Note that Streamfall assumes any objective function is to be minimized.calibrate!(hymod_node, climate, Qo, Streamfall.RMSE; MaxTime=30)
# Run the fitted modelrun_node!(hymod_node, climate)
# Display a basic overview plot (shows time series and Q-Q plot)# using a 366 day offset (e.g., ~1 year burn-in period)quickplot(Qo, hymod_node, climate, "HyMod"; burn_in=366)
# Save figuresavefig("quick_example.png")

Quick start (network of nodes)

# Load and generate stream network
network_spec = YAML.load_file("network.yml")
sn =create_network("Example Network", network_spec)
# Show figure of networkplot_network(sn)
# Calibrate network using the BlackBoxOptim package# keyword arguments will be passed to the `bboptimize()` functioncalibrate!(sn, climate, Qo, Streamfall.RMSE; MaxTime=180.0)
# Run stream network# There is also `run_catchment!()` which does the same thingrun_basin!(sn, climate)
# Get a specific node in network
node = sn[1] # get the first node in the network ("node1")# Nodes can also be retrieved by name# which will also return its position in the network:# nid, node = sn["node1"]# Compare "goodness-of-fit"
Streamfall.RMSE(obs_streamflow, node.outflow)
# Save calibrated network spec to file
Streamfall.save_network(sn, "calibrated_example.yml")

To display an overview of a node or network:

julia> node
Name:406219 [IHACRESBilinearNode]
Area:1985.73
┌──────────────┬───────────┬─────────────┬─────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Parameter │ Value │ Lower Bound │ Upper Bound │ Description │
├──────────────┼───────────┼─────────────┼─────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ d │ 84.280210.0550.0 │ Catchment moisture deficit threshold, higher values indicate the catchment can hold more water before generating runoff. │
│ d2 │ 2.422410.000110.0 │ Scaling factor (d*d2) which creates a second threshold, changing the shape of effective rainfall response. │
│ e │ 0.8129590.11.5 │ PET conversion factor, controls the rate of evapotranspiration losses, converts temperature to PET. │
│ f │ 2.579280.013.0 │ Plant stress threshold, controls at what moisture deficit plants begin to experience stress. │
│ a │ 5.923380.110.0 │ Quickflow storage coefficient, where higher values lead to faster quickflow response. │
│ b │ 0.09899260.0010.1 │ Slowflow storage coefficient, lower values lead to slower baseflow recession. │
│ storage_coef │ 1.861341.0e-1010.0 │ Groundwater interaction factor, controlling how water is exchanged with deeper groundwater. │
│ alpha │ 0.7279051.0e-51.0 │ Effective rainfall scaling factor, partitions rainfall into runoff. │
└──────────────┴───────────┴─────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Network specification

Stream networks are specified as Dictionaries, with an entry for each node.

An example spec from a YAML file is shown here, with connectivity between nodes defined by their names.

# ... Partial snippet of stream definition as an example ...Node3:
node_type: IHACRESBilinearNode # node type, typically tied to the attached modelinlets: # nodes that contribute incoming streamflow
- Node1
- Node2outlets: Node4 # node that this node flows toarea: 150.0# subcatchment area in km^2parameters:
# model specific parameters defined here...

A full example of the spec is available here. The snippet above defines Node 3 in the diagram below.

Each node defines a subcatchment and holds the relevant parameter values for the associated model.

Running a network

using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
# Load a network from a file, providing a name for the network and the file path.# Creates a graph representation of the stream with associated metadata.
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
sn =load_network("Example Network", joinpath(data_dir, "campaspe/campaspe_network.yml"))
# Load climate data, in this case from a CSV file with data for all nodes.# Indicate which columns are precipitation and evaporation data based on partial identifiers
climate =Climate(joinpath(data_dir, "campaspe/climate/climate.csv"), "_rain", "_evap")
# This runs an entire stream network@info"Running an example stream..."run_catchment!(sn, climate)
@info"Displaying outflow from node 406219"
node_id, node = sn["406219"]
plot(node.outflow)

Individual nodes can be run for more fine-grain control.

# Run up to a point in the stream for all time steps.# All nodes upstream will be run as well (but not those downstream)
node_id, node = sn["406219"]
run_node!(sn, node_id, climate)
# Reset a node (clears stored states)reset!(node)
# Run a specific node, and only a specific node, for all time steps
inflow =...# inflows for each time step
extractions =...# extractions from stream for each time step
gw_flux =...# forced groundwater interactions for each time steprun_node!(node, climate; inflow=inflow, extraction=extractions, exchange=gw_flux)

Another approach is to identify the outlets for a given network...

inlets, outlets =find_inlets_and_outlets(sn)

... and call run_node! for each outlet (with relevant climate data), which will recurse through all relevant nodes upstream.

@info"Running example stream..."
timesteps =sim_length(climate)
reset!(sn)
prep_state!(sn, timesteps)
for ts in (1:timesteps)
for outlet in outlets
run_node!(sn, outlet, climate, ts)
endend

See the docs for an overview and example applications.

Further preliminary usage examples are provided in the examples directory.

About

A graph-based streamflow modelling system in Julialang

Resources

Stars

15 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Streamfall.jl

A graph-based streamflow modelling system written in Julialang.

DOI

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
  • Support for modelling and assessment of interacting systems
  • A wide range of performance metrics

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

This package includes implementations of the following (with naive timings using the @time macro):

  • GR4J [0.015274 seconds (224.75 k allocations: 5.584 MiB)]
  • HyMod [0.016502 seconds (469.25 k allocations: 12.902 MiB)]
  • IHACRES [0.021734 seconds (675.63 k allocations: 17.773 MiB)]
  • SIMHYD [0.022818 seconds (600.39 k allocations: 16.768 MiB)]

Timings were taken using an example dataset spanning 1963-07-05 - 2014-12-31 (18808 days, approximately 51.5 years)

The IHACRES rainfall-runoff model was previously implemented with ihacres_nim but has since been ported to pure Julia.

Graphs and MetaGraphs are used underneath for network traversal/analysis.

Installation

Streamfall is now registered! The latest release version can be installed with:

] add Streamfall

or the latest development version from GitHub with dev or add:

# Editable install
] dev Streamfall#main
] add https://github.com/ConnectedSystems/Streamfall.jl#main

Development

Local development should follow the usual process of git cloning the repository.

To build locally:

$ julia --project=.
julia>] build

To run tests:

julia>] test

Usage

The examples below use data from the CAMEL-AUS dataset, available here:

Fowler, K. J. A., Acharya, S. C., Addor, N., Chou, C., and Peel, M. C.: CAMELS-AUS: hydrometeorological time series and landscape attributes for 222 catchments in Australia, Earth Syst. Sci. Data, 13, 3847–3867, https://doi.org/10.5194/essd-13-3847-2021, 2021.

Note that since start of development, an updated dataset is incoming (currently under review):

Fowler, K. J. A., Zhang, Z., and Hou, X.: CAMELS-AUS v2: updated hydrometeorological timeseries and landscape attributes for an enlarged set of catchments in Australia, Earth Syst. Sci. Data Discuss. [preprint], https://doi.org/10.5194/essd-2024-263, in review, 2024.

Climate data was sourced from the Climate Change in Australia data service. Additional data was extracted from the Long Paddock data silo.

Quick start (single node)

The examples below are run from the examples directory.

using Statistics
using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
# Load data file which holds observed streamflow, precipitation and PET data
obs_data = CSV.read(
joinpath(data_dir, "cotter/climate/CAMELS-AUS_410730.csv"), DataFrame; comment="#"
)
# 18808×8 DataFrame# Row │ year month day Date 410730_P 410730_PET 410730_max_T 410730_Q# │ Int64 Int64 Int64 Date Float64 Float64 Float64 Float64# ───────┼─────────────────────────────────────────────────────────────────────────────────# 1 │ 1963 7 5 1963-07-05 0.204475 1.02646 6.80409 127.322# 2 │ 1963 7 6 1963-07-06 4.24377 0.790078 5.91556 110.224# 3 │ 1963 7 7 1963-07-07 5.20097 0.400584 3.02218 117.653# By default, Streamfall expects Date, Precipitation (P), Evapotranspiration (ET) and# Flow (Q) columns for each gauge as a minimum.# Some rainfall-runoff models may also require Temperature (T) data.
Qo =extract_flow(obs_data, "410730")
climate =extract_climate(obs_data)
# Create a node (node type, node id, catchment/watershed area)
hymod_node =create_node(SimpleHyModNode, "410730", 129.2);
# Attempt to fit model parameters for 30 seconds# Here, we use RMSE (Root Mean Square Error) as the objective function# Note that Streamfall assumes any objective function is to be minimized.calibrate!(hymod_node, climate, Qo, Streamfall.RMSE; MaxTime=30)
# Run the fitted modelrun_node!(hymod_node, climate)
# Display a basic overview plot (shows time series and Q-Q plot)# using a 366 day offset (e.g., ~1 year burn-in period)quickplot(Qo, hymod_node, climate, "HyMod"; burn_in=366)
# Save figuresavefig("quick_example.png")

Quick start (network of nodes)

# Load and generate stream network
network_spec = YAML.load_file("network.yml")
sn =create_network("Example Network", network_spec)
# Show figure of networkplot_network(sn)
# Calibrate network using the BlackBoxOptim package# keyword arguments will be passed to the `bboptimize()` functioncalibrate!(sn, climate, Qo, Streamfall.RMSE; MaxTime=180.0)
# Run stream network# There is also `run_catchment!()` which does the same thingrun_basin!(sn, climate)
# Get a specific node in network
node = sn[1] # get the first node in the network ("node1")# Nodes can also be retrieved by name# which will also return its position in the network:# nid, node = sn["node1"]# Compare "goodness-of-fit"
Streamfall.RMSE(obs_streamflow, node.outflow)
# Save calibrated network spec to file
Streamfall.save_network(sn, "calibrated_example.yml")

To display an overview of a node or network:

julia> node
Name:406219 [IHACRESBilinearNode]
Area:1985.73
┌──────────────┬───────────┬─────────────┬─────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Parameter │ Value │ Lower Bound │ Upper Bound │ Description │
├──────────────┼───────────┼─────────────┼─────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ d │ 84.280210.0550.0 │ Catchment moisture deficit threshold, higher values indicate the catchment can hold more water before generating runoff. │
│ d2 │ 2.422410.000110.0 │ Scaling factor (d*d2) which creates a second threshold, changing the shape of effective rainfall response. │
│ e │ 0.8129590.11.5 │ PET conversion factor, controls the rate of evapotranspiration losses, converts temperature to PET. │
│ f │ 2.579280.013.0 │ Plant stress threshold, controls at what moisture deficit plants begin to experience stress. │
│ a │ 5.923380.110.0 │ Quickflow storage coefficient, where higher values lead to faster quickflow response. │
│ b │ 0.09899260.0010.1 │ Slowflow storage coefficient, lower values lead to slower baseflow recession. │
│ storage_coef │ 1.861341.0e-1010.0 │ Groundwater interaction factor, controlling how water is exchanged with deeper groundwater. │
│ alpha │ 0.7279051.0e-51.0 │ Effective rainfall scaling factor, partitions rainfall into runoff. │
└──────────────┴───────────┴─────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Network specification

Stream networks are specified as Dictionaries, with an entry for each node.

An example spec from a YAML file is shown here, with connectivity between nodes defined by their names.

# ... Partial snippet of stream definition as an example ...Node3:
node_type: IHACRESBilinearNode # node type, typically tied to the attached modelinlets: # nodes that contribute incoming streamflow
- Node1
- Node2outlets: Node4 # node that this node flows toarea: 150.0# subcatchment area in km^2parameters:
# model specific parameters defined here...

A full example of the spec is available here. The snippet above defines Node 3 in the diagram below.

Each node defines a subcatchment and holds the relevant parameter values for the associated model.

Running a network

using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
# Load a network from a file, providing a name for the network and the file path.# Creates a graph representation of the stream with associated metadata.
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
sn =load_network("Example Network", joinpath(data_dir, "campaspe/campaspe_network.yml"))
# Load climate data, in this case from a CSV file with data for all nodes.# Indicate which columns are precipitation and evaporation data based on partial identifiers
climate =Climate(joinpath(data_dir, "campaspe/climate/climate.csv"), "_rain", "_evap")
# This runs an entire stream network@info"Running an example stream..."run_catchment!(sn, climate)
@info"Displaying outflow from node 406219"
node_id, node = sn["406219"]
plot(node.outflow)

Individual nodes can be run for more fine-grain control.

# Run up to a point in the stream for all time steps.# All nodes upstream will be run as well (but not those downstream)
node_id, node = sn["406219"]
run_node!(sn, node_id, climate)
# Reset a node (clears stored states)reset!(node)
# Run a specific node, and only a specific node, for all time steps
inflow =...# inflows for each time step
extractions =...# extractions from stream for each time step
gw_flux =...# forced groundwater interactions for each time steprun_node!(node, climate; inflow=inflow, extraction=extractions, exchange=gw_flux)

Another approach is to identify the outlets for a given network...

inlets, outlets =find_inlets_and_outlets(sn)

... and call run_node! for each outlet (with relevant climate data), which will recurse through all relevant nodes upstream.

@info"Running example stream..."
timesteps =sim_length(climate)
reset!(sn)
prep_state!(sn, timesteps)
for ts in (1:timesteps)
for outlet in outlets
run_node!(sn, outlet, climate, ts)
endend

See the docs for an overview and example applications.

Further preliminary usage examples are provided in the examples directory.

About

A graph-based streamflow modelling system in Julialang

Resources

Stars

15 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Streamfall.jl

A graph-based streamflow modelling system written in Julialang.

DOI

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
  • Support for modelling and assessment of interacting systems
  • A wide range of performance metrics

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

This package includes implementations of the following (with naive timings using the @time macro):

  • GR4J [0.015274 seconds (224.75 k allocations: 5.584 MiB)]
  • HyMod [0.016502 seconds (469.25 k allocations: 12.902 MiB)]
  • IHACRES [0.021734 seconds (675.63 k allocations: 17.773 MiB)]
  • SIMHYD [0.022818 seconds (600.39 k allocations: 16.768 MiB)]

Timings were taken using an example dataset spanning 1963-07-05 - 2014-12-31 (18808 days, approximately 51.5 years)

The IHACRES rainfall-runoff model was previously implemented with ihacres_nim but has since been ported to pure Julia.

Graphs and MetaGraphs are used underneath for network traversal/analysis.

Installation

Streamfall is now registered! The latest release version can be installed with:

] add Streamfall

or the latest development version from GitHub with dev or add:

# Editable install
] dev Streamfall#main
] add https://github.com/ConnectedSystems/Streamfall.jl#main

Development

Local development should follow the usual process of git cloning the repository.

To build locally:

$ julia --project=.
julia>] build

To run tests:

julia>] test

Usage

The examples below use data from the CAMEL-AUS dataset, available here:

Fowler, K. J. A., Acharya, S. C., Addor, N., Chou, C., and Peel, M. C.: CAMELS-AUS: hydrometeorological time series and landscape attributes for 222 catchments in Australia, Earth Syst. Sci. Data, 13, 3847–3867, https://doi.org/10.5194/essd-13-3847-2021, 2021.

Note that since start of development, an updated dataset is incoming (currently under review):

Fowler, K. J. A., Zhang, Z., and Hou, X.: CAMELS-AUS v2: updated hydrometeorological timeseries and landscape attributes for an enlarged set of catchments in Australia, Earth Syst. Sci. Data Discuss. [preprint], https://doi.org/10.5194/essd-2024-263, in review, 2024.

Climate data was sourced from the Climate Change in Australia data service. Additional data was extracted from the Long Paddock data silo.

Quick start (single node)

The examples below are run from the examples directory.

using Statistics
using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
# Load data file which holds observed streamflow, precipitation and PET data
obs_data = CSV.read(
joinpath(data_dir, "cotter/climate/CAMELS-AUS_410730.csv"), DataFrame; comment="#"
)
# 18808×8 DataFrame# Row │ year month day Date 410730_P 410730_PET 410730_max_T 410730_Q# │ Int64 Int64 Int64 Date Float64 Float64 Float64 Float64# ───────┼─────────────────────────────────────────────────────────────────────────────────# 1 │ 1963 7 5 1963-07-05 0.204475 1.02646 6.80409 127.322# 2 │ 1963 7 6 1963-07-06 4.24377 0.790078 5.91556 110.224# 3 │ 1963 7 7 1963-07-07 5.20097 0.400584 3.02218 117.653# By default, Streamfall expects Date, Precipitation (P), Evapotranspiration (ET) and# Flow (Q) columns for each gauge as a minimum.# Some rainfall-runoff models may also require Temperature (T) data.
Qo =extract_flow(obs_data, "410730")
climate =extract_climate(obs_data)
# Create a node (node type, node id, catchment/watershed area)
hymod_node =create_node(SimpleHyModNode, "410730", 129.2);
# Attempt to fit model parameters for 30 seconds# Here, we use RMSE (Root Mean Square Error) as the objective function# Note that Streamfall assumes any objective function is to be minimized.calibrate!(hymod_node, climate, Qo, Streamfall.RMSE; MaxTime=30)
# Run the fitted modelrun_node!(hymod_node, climate)
# Display a basic overview plot (shows time series and Q-Q plot)# using a 366 day offset (e.g., ~1 year burn-in period)quickplot(Qo, hymod_node, climate, "HyMod"; burn_in=366)
# Save figuresavefig("quick_example.png")

Quick start (network of nodes)

# Load and generate stream network
network_spec = YAML.load_file("network.yml")
sn =create_network("Example Network", network_spec)
# Show figure of networkplot_network(sn)
# Calibrate network using the BlackBoxOptim package# keyword arguments will be passed to the `bboptimize()` functioncalibrate!(sn, climate, Qo, Streamfall.RMSE; MaxTime=180.0)
# Run stream network# There is also `run_catchment!()` which does the same thingrun_basin!(sn, climate)
# Get a specific node in network
node = sn[1] # get the first node in the network ("node1")# Nodes can also be retrieved by name# which will also return its position in the network:# nid, node = sn["node1"]# Compare "goodness-of-fit"
Streamfall.RMSE(obs_streamflow, node.outflow)
# Save calibrated network spec to file
Streamfall.save_network(sn, "calibrated_example.yml")

To display an overview of a node or network:

julia> node
Name:406219 [IHACRESBilinearNode]
Area:1985.73
┌──────────────┬───────────┬─────────────┬─────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Parameter │ Value │ Lower Bound │ Upper Bound │ Description │
├──────────────┼───────────┼─────────────┼─────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ d │ 84.280210.0550.0 │ Catchment moisture deficit threshold, higher values indicate the catchment can hold more water before generating runoff. │
│ d2 │ 2.422410.000110.0 │ Scaling factor (d*d2) which creates a second threshold, changing the shape of effective rainfall response. │
│ e │ 0.8129590.11.5 │ PET conversion factor, controls the rate of evapotranspiration losses, converts temperature to PET. │
│ f │ 2.579280.013.0 │ Plant stress threshold, controls at what moisture deficit plants begin to experience stress. │
│ a │ 5.923380.110.0 │ Quickflow storage coefficient, where higher values lead to faster quickflow response. │
│ b │ 0.09899260.0010.1 │ Slowflow storage coefficient, lower values lead to slower baseflow recession. │
│ storage_coef │ 1.861341.0e-1010.0 │ Groundwater interaction factor, controlling how water is exchanged with deeper groundwater. │
│ alpha │ 0.7279051.0e-51.0 │ Effective rainfall scaling factor, partitions rainfall into runoff. │
└──────────────┴───────────┴─────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Network specification

Stream networks are specified as Dictionaries, with an entry for each node.

An example spec from a YAML file is shown here, with connectivity between nodes defined by their names.

# ... Partial snippet of stream definition as an example ...Node3:
node_type: IHACRESBilinearNode # node type, typically tied to the attached modelinlets: # nodes that contribute incoming streamflow
- Node1
- Node2outlets: Node4 # node that this node flows toarea: 150.0# subcatchment area in km^2parameters:
# model specific parameters defined here...

A full example of the spec is available here. The snippet above defines Node 3 in the diagram below.

Each node defines a subcatchment and holds the relevant parameter values for the associated model.

Running a network

using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
# Load a network from a file, providing a name for the network and the file path.# Creates a graph representation of the stream with associated metadata.
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
sn =load_network("Example Network", joinpath(data_dir, "campaspe/campaspe_network.yml"))
# Load climate data, in this case from a CSV file with data for all nodes.# Indicate which columns are precipitation and evaporation data based on partial identifiers
climate =Climate(joinpath(data_dir, "campaspe/climate/climate.csv"), "_rain", "_evap")
# This runs an entire stream network@info"Running an example stream..."run_catchment!(sn, climate)
@info"Displaying outflow from node 406219"
node_id, node = sn["406219"]
plot(node.outflow)

Individual nodes can be run for more fine-grain control.

# Run up to a point in the stream for all time steps.# All nodes upstream will be run as well (but not those downstream)
node_id, node = sn["406219"]
run_node!(sn, node_id, climate)
# Reset a node (clears stored states)reset!(node)
# Run a specific node, and only a specific node, for all time steps
inflow =...# inflows for each time step
extractions =...# extractions from stream for each time step
gw_flux =...# forced groundwater interactions for each time steprun_node!(node, climate; inflow=inflow, extraction=extractions, exchange=gw_flux)

Another approach is to identify the outlets for a given network...

inlets, outlets =find_inlets_and_outlets(sn)

... and call run_node! for each outlet (with relevant climate data), which will recurse through all relevant nodes upstream.

@info"Running example stream..."
timesteps =sim_length(climate)
reset!(sn)
prep_state!(sn, timesteps)
for ts in (1:timesteps)
for outlet in outlets
run_node!(sn, outlet, climate, ts)
endend

See the docs for an overview and example applications.

Further preliminary usage examples are provided in the examples directory.

About

A graph-based streamflow modelling system in Julialang

Resources

Stars

15 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Streamfall.jl

A graph-based streamflow modelling system written in Julialang.

DOI

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
  • Support for modelling and assessment of interacting systems
  • A wide range of performance metrics

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

This package includes implementations of the following (with naive timings using the @time macro):

  • GR4J [0.015274 seconds (224.75 k allocations: 5.584 MiB)]
  • HyMod [0.016502 seconds (469.25 k allocations: 12.902 MiB)]
  • IHACRES [0.021734 seconds (675.63 k allocations: 17.773 MiB)]
  • SIMHYD [0.022818 seconds (600.39 k allocations: 16.768 MiB)]

Timings were taken using an example dataset spanning 1963-07-05 - 2014-12-31 (18808 days, approximately 51.5 years)

The IHACRES rainfall-runoff model was previously implemented with ihacres_nim but has since been ported to pure Julia.

Graphs and MetaGraphs are used underneath for network traversal/analysis.

Installation

Streamfall is now registered! The latest release version can be installed with:

] add Streamfall

or the latest development version from GitHub with dev or add:

# Editable install
] dev Streamfall#main
] add https://github.com/ConnectedSystems/Streamfall.jl#main

Development

Local development should follow the usual process of git cloning the repository.

To build locally:

$ julia --project=.
julia>] build

To run tests:

julia>] test

Usage

The examples below use data from the CAMEL-AUS dataset, available here:

Fowler, K. J. A., Acharya, S. C., Addor, N., Chou, C., and Peel, M. C.: CAMELS-AUS: hydrometeorological time series and landscape attributes for 222 catchments in Australia, Earth Syst. Sci. Data, 13, 3847–3867, https://doi.org/10.5194/essd-13-3847-2021, 2021.

Note that since start of development, an updated dataset is incoming (currently under review):

Fowler, K. J. A., Zhang, Z., and Hou, X.: CAMELS-AUS v2: updated hydrometeorological timeseries and landscape attributes for an enlarged set of catchments in Australia, Earth Syst. Sci. Data Discuss. [preprint], https://doi.org/10.5194/essd-2024-263, in review, 2024.

Climate data was sourced from the Climate Change in Australia data service. Additional data was extracted from the Long Paddock data silo.

Quick start (single node)

The examples below are run from the examples directory.

using Statistics
using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
# Load data file which holds observed streamflow, precipitation and PET data
obs_data = CSV.read(
joinpath(data_dir, "cotter/climate/CAMELS-AUS_410730.csv"), DataFrame; comment="#"
)
# 18808×8 DataFrame# Row │ year month day Date 410730_P 410730_PET 410730_max_T 410730_Q# │ Int64 Int64 Int64 Date Float64 Float64 Float64 Float64# ───────┼─────────────────────────────────────────────────────────────────────────────────# 1 │ 1963 7 5 1963-07-05 0.204475 1.02646 6.80409 127.322# 2 │ 1963 7 6 1963-07-06 4.24377 0.790078 5.91556 110.224# 3 │ 1963 7 7 1963-07-07 5.20097 0.400584 3.02218 117.653# By default, Streamfall expects Date, Precipitation (P), Evapotranspiration (ET) and# Flow (Q) columns for each gauge as a minimum.# Some rainfall-runoff models may also require Temperature (T) data.
Qo =extract_flow(obs_data, "410730")
climate =extract_climate(obs_data)
# Create a node (node type, node id, catchment/watershed area)
hymod_node =create_node(SimpleHyModNode, "410730", 129.2);
# Attempt to fit model parameters for 30 seconds# Here, we use RMSE (Root Mean Square Error) as the objective function# Note that Streamfall assumes any objective function is to be minimized.calibrate!(hymod_node, climate, Qo, Streamfall.RMSE; MaxTime=30)
# Run the fitted modelrun_node!(hymod_node, climate)
# Display a basic overview plot (shows time series and Q-Q plot)# using a 366 day offset (e.g., ~1 year burn-in period)quickplot(Qo, hymod_node, climate, "HyMod"; burn_in=366)
# Save figuresavefig("quick_example.png")

Quick start (network of nodes)

# Load and generate stream network
network_spec = YAML.load_file("network.yml")
sn =create_network("Example Network", network_spec)
# Show figure of networkplot_network(sn)
# Calibrate network using the BlackBoxOptim package# keyword arguments will be passed to the `bboptimize()` functioncalibrate!(sn, climate, Qo, Streamfall.RMSE; MaxTime=180.0)
# Run stream network# There is also `run_catchment!()` which does the same thingrun_basin!(sn, climate)
# Get a specific node in network
node = sn[1] # get the first node in the network ("node1")# Nodes can also be retrieved by name# which will also return its position in the network:# nid, node = sn["node1"]# Compare "goodness-of-fit"
Streamfall.RMSE(obs_streamflow, node.outflow)
# Save calibrated network spec to file
Streamfall.save_network(sn, "calibrated_example.yml")

To display an overview of a node or network:

julia> node
Name:406219 [IHACRESBilinearNode]
Area:1985.73
┌──────────────┬───────────┬─────────────┬─────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Parameter │ Value │ Lower Bound │ Upper Bound │ Description │
├──────────────┼───────────┼─────────────┼─────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ d │ 84.280210.0550.0 │ Catchment moisture deficit threshold, higher values indicate the catchment can hold more water before generating runoff. │
│ d2 │ 2.422410.000110.0 │ Scaling factor (d*d2) which creates a second threshold, changing the shape of effective rainfall response. │
│ e │ 0.8129590.11.5 │ PET conversion factor, controls the rate of evapotranspiration losses, converts temperature to PET. │
│ f │ 2.579280.013.0 │ Plant stress threshold, controls at what moisture deficit plants begin to experience stress. │
│ a │ 5.923380.110.0 │ Quickflow storage coefficient, where higher values lead to faster quickflow response. │
│ b │ 0.09899260.0010.1 │ Slowflow storage coefficient, lower values lead to slower baseflow recession. │
│ storage_coef │ 1.861341.0e-1010.0 │ Groundwater interaction factor, controlling how water is exchanged with deeper groundwater. │
│ alpha │ 0.7279051.0e-51.0 │ Effective rainfall scaling factor, partitions rainfall into runoff. │
└──────────────┴───────────┴─────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Network specification

Stream networks are specified as Dictionaries, with an entry for each node.

An example spec from a YAML file is shown here, with connectivity between nodes defined by their names.

# ... Partial snippet of stream definition as an example ...Node3:
node_type: IHACRESBilinearNode # node type, typically tied to the attached modelinlets: # nodes that contribute incoming streamflow
- Node1
- Node2outlets: Node4 # node that this node flows toarea: 150.0# subcatchment area in km^2parameters:
# model specific parameters defined here...

A full example of the spec is available here. The snippet above defines Node 3 in the diagram below.

Each node defines a subcatchment and holds the relevant parameter values for the associated model.

Running a network

using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
# Load a network from a file, providing a name for the network and the file path.# Creates a graph representation of the stream with associated metadata.
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
sn =load_network("Example Network", joinpath(data_dir, "campaspe/campaspe_network.yml"))
# Load climate data, in this case from a CSV file with data for all nodes.# Indicate which columns are precipitation and evaporation data based on partial identifiers
climate =Climate(joinpath(data_dir, "campaspe/climate/climate.csv"), "_rain", "_evap")
# This runs an entire stream network@info"Running an example stream..."run_catchment!(sn, climate)
@info"Displaying outflow from node 406219"
node_id, node = sn["406219"]
plot(node.outflow)

Individual nodes can be run for more fine-grain control.

# Run up to a point in the stream for all time steps.# All nodes upstream will be run as well (but not those downstream)
node_id, node = sn["406219"]
run_node!(sn, node_id, climate)
# Reset a node (clears stored states)reset!(node)
# Run a specific node, and only a specific node, for all time steps
inflow =...# inflows for each time step
extractions =...# extractions from stream for each time step
gw_flux =...# forced groundwater interactions for each time steprun_node!(node, climate; inflow=inflow, extraction=extractions, exchange=gw_flux)

Another approach is to identify the outlets for a given network...

inlets, outlets =find_inlets_and_outlets(sn)

... and call run_node! for each outlet (with relevant climate data), which will recurse through all relevant nodes upstream.

@info"Running example stream..."
timesteps =sim_length(climate)
reset!(sn)
prep_state!(sn, timesteps)
for ts in (1:timesteps)
for outlet in outlets
run_node!(sn, outlet, climate, ts)
endend

See the docs for an overview and example applications.

Further preliminary usage examples are provided in the examples directory.

About

A graph-based streamflow modelling system in Julialang

Resources

Stars

15 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Streamfall.jl

A graph-based streamflow modelling system written in Julialang.

DOI

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
  • Support for modelling and assessment of interacting systems
  • A wide range of performance metrics

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

This package includes implementations of the following (with naive timings using the @time macro):

  • GR4J [0.015274 seconds (224.75 k allocations: 5.584 MiB)]
  • HyMod [0.016502 seconds (469.25 k allocations: 12.902 MiB)]
  • IHACRES [0.021734 seconds (675.63 k allocations: 17.773 MiB)]
  • SIMHYD [0.022818 seconds (600.39 k allocations: 16.768 MiB)]

Timings were taken using an example dataset spanning 1963-07-05 - 2014-12-31 (18808 days, approximately 51.5 years)

The IHACRES rainfall-runoff model was previously implemented with ihacres_nim but has since been ported to pure Julia.

Graphs and MetaGraphs are used underneath for network traversal/analysis.

Installation

Streamfall is now registered! The latest release version can be installed with:

] add Streamfall

or the latest development version from GitHub with dev or add:

# Editable install
] dev Streamfall#main
] add https://github.com/ConnectedSystems/Streamfall.jl#main

Development

Local development should follow the usual process of git cloning the repository.

To build locally:

$ julia --project=.
julia>] build

To run tests:

julia>] test

Usage

The examples below use data from the CAMEL-AUS dataset, available here:

Fowler, K. J. A., Acharya, S. C., Addor, N., Chou, C., and Peel, M. C.: CAMELS-AUS: hydrometeorological time series and landscape attributes for 222 catchments in Australia, Earth Syst. Sci. Data, 13, 3847–3867, https://doi.org/10.5194/essd-13-3847-2021, 2021.

Note that since start of development, an updated dataset is incoming (currently under review):

Fowler, K. J. A., Zhang, Z., and Hou, X.: CAMELS-AUS v2: updated hydrometeorological timeseries and landscape attributes for an enlarged set of catchments in Australia, Earth Syst. Sci. Data Discuss. [preprint], https://doi.org/10.5194/essd-2024-263, in review, 2024.

Climate data was sourced from the Climate Change in Australia data service. Additional data was extracted from the Long Paddock data silo.

Quick start (single node)

The examples below are run from the examples directory.

using Statistics
using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
# Load data file which holds observed streamflow, precipitation and PET data
obs_data = CSV.read(
joinpath(data_dir, "cotter/climate/CAMELS-AUS_410730.csv"), DataFrame; comment="#"
)
# 18808×8 DataFrame# Row │ year month day Date 410730_P 410730_PET 410730_max_T 410730_Q# │ Int64 Int64 Int64 Date Float64 Float64 Float64 Float64# ───────┼─────────────────────────────────────────────────────────────────────────────────# 1 │ 1963 7 5 1963-07-05 0.204475 1.02646 6.80409 127.322# 2 │ 1963 7 6 1963-07-06 4.24377 0.790078 5.91556 110.224# 3 │ 1963 7 7 1963-07-07 5.20097 0.400584 3.02218 117.653# By default, Streamfall expects Date, Precipitation (P), Evapotranspiration (ET) and# Flow (Q) columns for each gauge as a minimum.# Some rainfall-runoff models may also require Temperature (T) data.
Qo =extract_flow(obs_data, "410730")
climate =extract_climate(obs_data)
# Create a node (node type, node id, catchment/watershed area)
hymod_node =create_node(SimpleHyModNode, "410730", 129.2);
# Attempt to fit model parameters for 30 seconds# Here, we use RMSE (Root Mean Square Error) as the objective function# Note that Streamfall assumes any objective function is to be minimized.calibrate!(hymod_node, climate, Qo, Streamfall.RMSE; MaxTime=30)
# Run the fitted modelrun_node!(hymod_node, climate)
# Display a basic overview plot (shows time series and Q-Q plot)# using a 366 day offset (e.g., ~1 year burn-in period)quickplot(Qo, hymod_node, climate, "HyMod"; burn_in=366)
# Save figuresavefig("quick_example.png")

Quick start (network of nodes)

# Load and generate stream network
network_spec = YAML.load_file("network.yml")
sn =create_network("Example Network", network_spec)
# Show figure of networkplot_network(sn)
# Calibrate network using the BlackBoxOptim package# keyword arguments will be passed to the `bboptimize()` functioncalibrate!(sn, climate, Qo, Streamfall.RMSE; MaxTime=180.0)
# Run stream network# There is also `run_catchment!()` which does the same thingrun_basin!(sn, climate)
# Get a specific node in network
node = sn[1] # get the first node in the network ("node1")# Nodes can also be retrieved by name# which will also return its position in the network:# nid, node = sn["node1"]# Compare "goodness-of-fit"
Streamfall.RMSE(obs_streamflow, node.outflow)
# Save calibrated network spec to file
Streamfall.save_network(sn, "calibrated_example.yml")

To display an overview of a node or network:

julia> node
Name:406219 [IHACRESBilinearNode]
Area:1985.73
┌──────────────┬───────────┬─────────────┬─────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Parameter │ Value │ Lower Bound │ Upper Bound │ Description │
├──────────────┼───────────┼─────────────┼─────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ d │ 84.280210.0550.0 │ Catchment moisture deficit threshold, higher values indicate the catchment can hold more water before generating runoff. │
│ d2 │ 2.422410.000110.0 │ Scaling factor (d*d2) which creates a second threshold, changing the shape of effective rainfall response. │
│ e │ 0.8129590.11.5 │ PET conversion factor, controls the rate of evapotranspiration losses, converts temperature to PET. │
│ f │ 2.579280.013.0 │ Plant stress threshold, controls at what moisture deficit plants begin to experience stress. │
│ a │ 5.923380.110.0 │ Quickflow storage coefficient, where higher values lead to faster quickflow response. │
│ b │ 0.09899260.0010.1 │ Slowflow storage coefficient, lower values lead to slower baseflow recession. │
│ storage_coef │ 1.861341.0e-1010.0 │ Groundwater interaction factor, controlling how water is exchanged with deeper groundwater. │
│ alpha │ 0.7279051.0e-51.0 │ Effective rainfall scaling factor, partitions rainfall into runoff. │
└──────────────┴───────────┴─────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Network specification

Stream networks are specified as Dictionaries, with an entry for each node.

An example spec from a YAML file is shown here, with connectivity between nodes defined by their names.

# ... Partial snippet of stream definition as an example ...Node3:
node_type: IHACRESBilinearNode # node type, typically tied to the attached modelinlets: # nodes that contribute incoming streamflow
- Node1
- Node2outlets: Node4 # node that this node flows toarea: 150.0# subcatchment area in km^2parameters:
# model specific parameters defined here...

A full example of the spec is available here. The snippet above defines Node 3 in the diagram below.

Each node defines a subcatchment and holds the relevant parameter values for the associated model.

Running a network

using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
# Load a network from a file, providing a name for the network and the file path.# Creates a graph representation of the stream with associated metadata.
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
sn =load_network("Example Network", joinpath(data_dir, "campaspe/campaspe_network.yml"))
# Load climate data, in this case from a CSV file with data for all nodes.# Indicate which columns are precipitation and evaporation data based on partial identifiers
climate =Climate(joinpath(data_dir, "campaspe/climate/climate.csv"), "_rain", "_evap")
# This runs an entire stream network@info"Running an example stream..."run_catchment!(sn, climate)
@info"Displaying outflow from node 406219"
node_id, node = sn["406219"]
plot(node.outflow)

Individual nodes can be run for more fine-grain control.

# Run up to a point in the stream for all time steps.# All nodes upstream will be run as well (but not those downstream)
node_id, node = sn["406219"]
run_node!(sn, node_id, climate)
# Reset a node (clears stored states)reset!(node)
# Run a specific node, and only a specific node, for all time steps
inflow =...# inflows for each time step
extractions =...# extractions from stream for each time step
gw_flux =...# forced groundwater interactions for each time steprun_node!(node, climate; inflow=inflow, extraction=extractions, exchange=gw_flux)

Another approach is to identify the outlets for a given network...

inlets, outlets =find_inlets_and_outlets(sn)

... and call run_node! for each outlet (with relevant climate data), which will recurse through all relevant nodes upstream.

@info"Running example stream..."
timesteps =sim_length(climate)
reset!(sn)
prep_state!(sn, timesteps)
for ts in (1:timesteps)
for outlet in outlets
run_node!(sn, outlet, climate, ts)
endend

See the docs for an overview and example applications.

Further preliminary usage examples are provided in the examples directory.

About

A graph-based streamflow modelling system in Julialang

Resources

Stars

15 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Streamfall.jl

A graph-based streamflow modelling system written in Julialang.

DOI

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
  • Support for modelling and assessment of interacting systems
  • A wide range of performance metrics

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

This package includes implementations of the following (with naive timings using the @time macro):

  • GR4J [0.015274 seconds (224.75 k allocations: 5.584 MiB)]
  • HyMod [0.016502 seconds (469.25 k allocations: 12.902 MiB)]
  • IHACRES [0.021734 seconds (675.63 k allocations: 17.773 MiB)]
  • SIMHYD [0.022818 seconds (600.39 k allocations: 16.768 MiB)]

Timings were taken using an example dataset spanning 1963-07-05 - 2014-12-31 (18808 days, approximately 51.5 years)

The IHACRES rainfall-runoff model was previously implemented with ihacres_nim but has since been ported to pure Julia.

Graphs and MetaGraphs are used underneath for network traversal/analysis.

Installation

Streamfall is now registered! The latest release version can be installed with:

] add Streamfall

or the latest development version from GitHub with dev or add:

# Editable install
] dev Streamfall#main
] add https://github.com/ConnectedSystems/Streamfall.jl#main

Development

Local development should follow the usual process of git cloning the repository.

To build locally:

$ julia --project=.
julia>] build

To run tests:

julia>] test

Usage

The examples below use data from the CAMEL-AUS dataset, available here:

Fowler, K. J. A., Acharya, S. C., Addor, N., Chou, C., and Peel, M. C.: CAMELS-AUS: hydrometeorological time series and landscape attributes for 222 catchments in Australia, Earth Syst. Sci. Data, 13, 3847–3867, https://doi.org/10.5194/essd-13-3847-2021, 2021.

Note that since start of development, an updated dataset is incoming (currently under review):

Fowler, K. J. A., Zhang, Z., and Hou, X.: CAMELS-AUS v2: updated hydrometeorological timeseries and landscape attributes for an enlarged set of catchments in Australia, Earth Syst. Sci. Data Discuss. [preprint], https://doi.org/10.5194/essd-2024-263, in review, 2024.

Climate data was sourced from the Climate Change in Australia data service. Additional data was extracted from the Long Paddock data silo.

Quick start (single node)

The examples below are run from the examples directory.

using Statistics
using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
# Load data file which holds observed streamflow, precipitation and PET data
obs_data = CSV.read(
joinpath(data_dir, "cotter/climate/CAMELS-AUS_410730.csv"), DataFrame; comment="#"
)
# 18808×8 DataFrame# Row │ year month day Date 410730_P 410730_PET 410730_max_T 410730_Q# │ Int64 Int64 Int64 Date Float64 Float64 Float64 Float64# ───────┼─────────────────────────────────────────────────────────────────────────────────# 1 │ 1963 7 5 1963-07-05 0.204475 1.02646 6.80409 127.322# 2 │ 1963 7 6 1963-07-06 4.24377 0.790078 5.91556 110.224# 3 │ 1963 7 7 1963-07-07 5.20097 0.400584 3.02218 117.653# By default, Streamfall expects Date, Precipitation (P), Evapotranspiration (ET) and# Flow (Q) columns for each gauge as a minimum.# Some rainfall-runoff models may also require Temperature (T) data.
Qo =extract_flow(obs_data, "410730")
climate =extract_climate(obs_data)
# Create a node (node type, node id, catchment/watershed area)
hymod_node =create_node(SimpleHyModNode, "410730", 129.2);
# Attempt to fit model parameters for 30 seconds# Here, we use RMSE (Root Mean Square Error) as the objective function# Note that Streamfall assumes any objective function is to be minimized.calibrate!(hymod_node, climate, Qo, Streamfall.RMSE; MaxTime=30)
# Run the fitted modelrun_node!(hymod_node, climate)
# Display a basic overview plot (shows time series and Q-Q plot)# using a 366 day offset (e.g., ~1 year burn-in period)quickplot(Qo, hymod_node, climate, "HyMod"; burn_in=366)
# Save figuresavefig("quick_example.png")

Quick start (network of nodes)

# Load and generate stream network
network_spec = YAML.load_file("network.yml")
sn =create_network("Example Network", network_spec)
# Show figure of networkplot_network(sn)
# Calibrate network using the BlackBoxOptim package# keyword arguments will be passed to the `bboptimize()` functioncalibrate!(sn, climate, Qo, Streamfall.RMSE; MaxTime=180.0)
# Run stream network# There is also `run_catchment!()` which does the same thingrun_basin!(sn, climate)
# Get a specific node in network
node = sn[1] # get the first node in the network ("node1")# Nodes can also be retrieved by name# which will also return its position in the network:# nid, node = sn["node1"]# Compare "goodness-of-fit"
Streamfall.RMSE(obs_streamflow, node.outflow)
# Save calibrated network spec to file
Streamfall.save_network(sn, "calibrated_example.yml")

To display an overview of a node or network:

julia> node
Name:406219 [IHACRESBilinearNode]
Area:1985.73
┌──────────────┬───────────┬─────────────┬─────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Parameter │ Value │ Lower Bound │ Upper Bound │ Description │
├──────────────┼───────────┼─────────────┼─────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ d │ 84.280210.0550.0 │ Catchment moisture deficit threshold, higher values indicate the catchment can hold more water before generating runoff. │
│ d2 │ 2.422410.000110.0 │ Scaling factor (d*d2) which creates a second threshold, changing the shape of effective rainfall response. │
│ e │ 0.8129590.11.5 │ PET conversion factor, controls the rate of evapotranspiration losses, converts temperature to PET. │
│ f │ 2.579280.013.0 │ Plant stress threshold, controls at what moisture deficit plants begin to experience stress. │
│ a │ 5.923380.110.0 │ Quickflow storage coefficient, where higher values lead to faster quickflow response. │
│ b │ 0.09899260.0010.1 │ Slowflow storage coefficient, lower values lead to slower baseflow recession. │
│ storage_coef │ 1.861341.0e-1010.0 │ Groundwater interaction factor, controlling how water is exchanged with deeper groundwater. │
│ alpha │ 0.7279051.0e-51.0 │ Effective rainfall scaling factor, partitions rainfall into runoff. │
└──────────────┴───────────┴─────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Network specification

Stream networks are specified as Dictionaries, with an entry for each node.

An example spec from a YAML file is shown here, with connectivity between nodes defined by their names.

# ... Partial snippet of stream definition as an example ...Node3:
node_type: IHACRESBilinearNode # node type, typically tied to the attached modelinlets: # nodes that contribute incoming streamflow
- Node1
- Node2outlets: Node4 # node that this node flows toarea: 150.0# subcatchment area in km^2parameters:
# model specific parameters defined here...

A full example of the spec is available here. The snippet above defines Node 3 in the diagram below.

Each node defines a subcatchment and holds the relevant parameter values for the associated model.

Running a network

using CSV, DataFrames, YAML
using StatsPlots
using Streamfall
# Load a network from a file, providing a name for the network and the file path.# Creates a graph representation of the stream with associated metadata.
data_dir =joinpath(dirname(dirname(pathof(Streamfall))), "test/data")
sn =load_network("Example Network", joinpath(data_dir, "campaspe/campaspe_network.yml"))
# Load climate data, in this case from a CSV file with data for all nodes.# Indicate which columns are precipitation and evaporation data based on partial identifiers
climate =Climate(joinpath(data_dir, "campaspe/climate/climate.csv"), "_rain", "_evap")
# This runs an entire stream network@info"Running an example stream..."run_catchment!(sn, climate)
@info"Displaying outflow from node 406219"
node_id, node = sn["406219"]
plot(node.outflow)

Individual nodes can be run for more fine-grain control.

# Run up to a point in the stream for all time steps.# All nodes upstream will be run as well (but not those downstream)
node_id, node = sn["406219"]
run_node!(sn, node_id, climate)
# Reset a node (clears stored states)reset!(node)
# Run a specific node, and only a specific node, for all time steps
inflow =...# inflows for each time step
extractions =...# extractions from stream for each time step
gw_flux =...# forced groundwater interactions for each time steprun_node!(node, climate; inflow=inflow, extraction=extractions, exchange=gw_flux)

Another approach is to identify the outlets for a given network...

inlets, outlets =find_inlets_and_outlets(sn)

... and call run_node! for each outlet (with relevant climate data), which will recurse through all relevant nodes upstream.

@info"Running example stream..."
timesteps =sim_length(climate)
reset!(sn)
prep_state!(sn, timesteps)
for ts in (1:timesteps)
for outlet in outlets
run_node!(sn, outlet, climate, ts)
endend

See the docs for an overview and example applications.

Further preliminary usage examples are provided in the examples directory.

About

A graph-based streamflow modelling system in Julialang

Resources

Stars

15 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages