ExpHydro Model Tutorial

Overview

This tutorial demonstrates how to use the ExpHydro model for hydrological calculations, including:

  • Lumped calculation (Single HRU)
  • Distributed calculation (Multiple HRUs)

Dependencies

First, import the required Julia packages:

using CSV            
using DataFrames     
using ComponentArrays
using BenchmarkTools
using NamedTupleTools
using Plots

Model Setup

Parameter Configuration

The ExpHydro model contains 6 key parameters defined using ComponentVector:

# Define model parameters
params = ComponentVector(
    f = 0.01674478,    # Infiltration parameter
    Smax = 1709.461015, # Maximum soil water storage
    Qmax = 18.46996175, # Maximum discharge
    Df = 2.674548848,   # Degree-day factor
    Tmax = 0.175739196, # Maximum temperature threshold
    Tmin = -2.092959084 # Minimum temperature threshold
)

Initial States

Set initial states for the two calculation modules:

# Define initial states
inistates = ComponentVector(
    snowpack = 0.0,           # Snow accumulation
    soilwater = 1303.004248   # Soil water content
)

Data Preparation

The model uses hydrometeorological data from "data/exphydro/01013500.csv":

# Read input data
file_path = "data/exphydro/01013500.csv"
data = CSV.File(file_path)
df = DataFrame(data)
ts = collect(1:10000)  # Time series length

Input variables include:

  • Day length (dayl)
  • Mean temperature (tmean)
  • Precipitation (prcp)

Import the ExpHydro Model

include("../models/ExpHydro.jl")
println(exphydro_model)
HydroModel: exphydro
  Components: surface, soil
  Inputs: temp, lday, prcp
  Outputs: pet, snowfall, rainfall, melt, evap, baseflow, surfaceflow, flow
  Parameters: Tmin, Tmax, Df, Smax, Qmax, f
  States: snowpack, soilwater
  Components:
    Fluxes: 0 fluxes
    Buckets: 2 buckets
    Route: nothing

Model Testing

Single Node Testing

# Configure solver
solver = HydroModels.ManualSolver{true}()
pas = ComponentVector(params=params, inistates=inistates)
# Run model with benchmarking
result = exphydro_model(input, pas, config=(solver=solver, timeidx=ts))

# convert to NamedTuple
model_total_output_names = vcat(HydroModels.get_state_names(exphydro_model), HydroModels.get_output_names(exphydro_model))
result_ntp = NamedTuple{Tuple(model_total_output_names)}(eachslice(result, dims=1))

# Visualize results
plot(result_ntp.flow, label="Simulated")
plot!(df[ts, "flow(mm)"], label="Observed")

exphydro predict

The other hydrological fluxes are also available

output_df = DataFrame(result)
10000×10 DataFrame
   Row │ snowpack  soilwater  pet       snowfall  rainfall  melt       evap     baseflow  surfaceflow  flow      
       │ Float64   Float64    Float64   Float64   Float64   Float64    Float64  Float64   Float64      Float64
───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────
     1 │      0.0      0.0    1.13779       0.0       3.1   0.0204477      0.0   1303.0      0.867263  0.0204477
     2 │      0.0      0.0    1.51019       0.0       4.24  0.0211591      0.0   1305.05     1.15292   0.0211591
     3 │      0.0      0.0    1.63204       0.0       8.02  0.0228657      0.0   1309.68     1.25036   0.0228657
     4 │      0.0      0.0    1.21771       0.0      15.27  0.0274505      0.0   1320.59     0.940706  0.0274505
     5 │      0.0      0.0    1.02779       0.0       8.48  0.033228       0.0   1332.0      0.800846  0.033228
   ⋮   │    ⋮          ⋮         ⋮         ⋮         ⋮          ⋮         ⋮        ⋮           ⋮           ⋮
  9996 │      0.0    261.111  0.221762      0.0       0.0   0.495441      -0.0   1493.37     0.193729  0.495441
  9997 │      0.0    266.638  0.238907     12.06      0.0   0.49106       -0.0   1492.84     0.208632  0.49106
  9998 │      0.0    278.143  0.288362     11.55      0.0   0.486664      -0.0   1492.3      0.25173   0.486664
  9999 │      0.0    285.026  0.311022      1.84      0.0   0.482119      -0.0   1491.74     0.271409  0.482119
 10000 │      0.0    286.074  0.176836      0.14      0.0   0.477275      -0.0   1491.14     0.154251  0.477275

trial another solver (using HydroModelTools)

using HydroModelTools: ODESolver, DiscreteSolver
using OrdinalDiffEq

# using continuous solver
result1 = exphydro_model(input, pas, config=(solver=ODESolver(alg=Tsit5(), abstol=1e-3, reltol=1e-3), timeidx=ts))
# using discrete solver
result2 = exphydro_model(input, pas, config=(solver=DiscreteSolver(), timeidx=ts))

Single Node Benchmarking

# Performance testing using BenchmarkTools
@btime exphydro_model(input, params, config=(solver=solver, timeidx=ts), convert_to_ntp=true);
@btime exphydro_model(input, params, config=(solver=ODESolver(alg=Tsit5(), abstol=1e-3, reltol=1e-3), timeidx=ts), convert_to_ntp=true);
@btime exphydro_model(input, params, config=(solver=DiscreteSolver(), timeidx=ts), convert_to_ntp=true);

Efficiency of each solver

16.612 ms (328670 allocations: 35.08 MiB)
16.748 ms (328670 allocations: 35.08 MiB)
7.844 ms (160412 allocations: 20.11 MiB)

Multi-Node Testing

Multi-node setup for distributed computing:

# Setup multiple nodes
node_num = 10  # Number of nodes
# convert to 3D array (variables, node_num, time)
inputs = repeat(reshape(input, size(input)[1], 1, size(input)[2]), 1, node_num, 1)
ptypes = [Symbol(:node, i) for i in 1:node_num]

# Configure parameters and states for multiple nodes
params_multi = ComponentVector(NamedTuple{Tuple(ptypes)}(repeat([params], node_num)))
init_states_multi = ComponentVector(NamedTuple{Tuple(ptypes)}(repeat([inistates], node_num)))
pas_multi = ComponentVector(params=params_multi, initstates=init_states_multi)
# Run multi-node simulation
results = exphydro_model(inputs, pas_multi, config=(solver=solver, timeidx=ts))