AI-Hydro Interactive Module · Baseflow Analysis

Splitting the Flow
Baseflow Separation

The stream you see is two flows in disguise: quick storm runoff and slow, steady groundwater seeping in. Drag a slider, watch the code rewrite itself, and see the hydrograph split before your eyes.

Read the concept · Drag α to rewrite the code · Run the kernel · Wipe the compare · Pass the checkpoint

Your path through this module

💡
Concept
📐
Filter
⚡
Live Code
↔️
Compare
✓
Check
📜
Sources

1 · Two flows, one stream

When it rains, two very different processes contribute to the stream at the same time:

⚡

Quickflow (stormflow)

Overland flow and shallow subsurface flow that responds rapidly to rain. Rises fast, peaks in hours to days, then recedes.

🌱

Baseflow

Groundwater seeping slowly from the saturated zone into the stream. Sustains the stream between storms. Changes on a seasonal timescale.

The Baseflow Index (BFI) is the fraction of total streamflow that is baseflow:

BFI = Σ baseflow / Σ total Q

A BFI near 1 means the stream is groundwater-dominated (spring-fed rivers in chalk catchments). A BFI near 0 means flashy, ephemeral flow with almost no groundwater contribution. Most temperate rivers have a BFI between 0.4 and 0.8.

2 · The Lyne–Hollick digital filter

Lyne & Hollick (1979) proposed the simplest viable digital filter for automated baseflow separation. For each time step t:

f(t) = α · f(t−1) + (1+α)/2 · [Q(t) − Q(t−1)]

b(t) = Q(t) − max(0, f(t))

where f(t) = high-frequency (quickflow) component  ·  b(t) = baseflow  ·  α = filter parameter (0 < α < 1)

The filter runs 3 passes: forward → backward → forward. This removes phase lag and makes the separation symmetric in time. The parameter α controls the smoothing:

α → 1

More smoothing → baseflow tracks the slow trend only. BFI tends lower.

α → 0

Less smoothing → baseflow follows Q more closely. BFI tends higher.

α = 0.925

Nathan & McMahon (1990) recommended value for daily streamflow data.

Key insight α is a calibration parameter, not a physical constant. The right value varies by catchment and data resolution. In practice 0.90–0.95 is used for daily data; lower values for sub-daily.

3 · Drag α — watch the code rewrite itself

Move the slider below. As you drag, the value of alpha in the Python cell updates in real time — this is live code editing via aihydro.rewriteCell. Click Run to execute with the current α and see the separated hydrograph.

Code updates live The highlighted line in the cell below changes as you drag
Estimated BFI ~0.55
0 (all quickflow)1 (all baseflow)
Python Lyne–Hollick baseflow separation — α is updated by the slider above
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

alpha = 0.925  # ← drag the slider to change this

# ── Synthetic 365-day hydrograph ─────────────────────────
rng  = np.random.default_rng(42)
days = np.arange(365)
b_true   = 2.0 + 1.2 * np.sin(2 * np.pi * (days - 80) / 365)
storms   = np.zeros(365)
idx      = rng.choice(365, 15, replace=False)
storms[idx] = rng.exponential(4.0, 15)
uh       = np.exp(-np.arange(20) / 4.0); uh /= uh.sum()
qf       = np.convolve(storms, uh, mode='full')[:365]
Q        = np.clip(b_true + qf + rng.normal(0, 0.1, 365), 0.05, None)

# ── Lyne–Hollick filter (3-pass) ─────────────────────────
def lyne_hollick(Q, alpha, passes=3):
    b = Q.copy()
    for pi in range(passes):
        f   = np.zeros_like(Q)
        fwd = (pi % 2 == 0)
        if fwd:
            for i in range(1, len(Q)):
                f[i] = alpha * f[i-1] + (1+alpha)/2 * (Q[i] - Q[i-1])
        else:
            for i in range(len(Q)-2, -1, -1):
                f[i] = alpha * f[i+1] + (1+alpha)/2 * (Q[i] - Q[i+1])
        b = np.minimum(b, Q - np.clip(f, 0, None))
        b = np.clip(b, 0, None)
    return b

base = lyne_hollick(Q, alpha)
BFI  = base.sum() / Q.sum()

# ── Plot ─────────────────────────────────────────────────
fig, ax = plt.subplots(figsize=(9, 4.2))
ax.fill_between(days, 0, base, color='#34d399', alpha=0.55, label='Baseflow')
ax.fill_between(days, base, Q,  color='#00A3FF', alpha=0.28, label='Quickflow')
ax.plot(days, Q,    color='#00DDFF', lw=1.6, label='Total Q')
ax.plot(days, base, color='#34d399', lw=1.1, ls='--')
ax.set(xlabel='Day of year', ylabel='Q (m³/s)',
       title=f'Lyne–Hollick separation  ·  α = {alpha:.3f}  ·  BFI = {BFI:.3f}')
ax.set_facecolor('#0f0f1e'); fig.patch.set_facecolor('#0a0a15')
ax.tick_params(colors='#7dd3fc'); ax.yaxis.label.set_color('#7dd3fc')
ax.xaxis.label.set_color('#7dd3fc'); ax.title.set_color('white')
for sp in ax.spines.values(): sp.set_color('#334466')
ax.legend(framealpha=0.4, fontsize=9, labelcolor='white')
plt.tight_layout()

print(f"α = {alpha:.3f}")
print(f"BFI = {BFI:.4f}  ({BFI*100:.1f}% of flow is baseflow)")
print(f"Mean Q = {Q.mean():.2f} m³/s  |  Mean base = {base.mean():.2f} m³/s")
What to try Set α = 0.800 and run → wide, generous baseflow. Set α = 0.975 → tight, conservative baseflow that barely responds to storms. The BFI bar above updates instantly; the chart only updates on Run.

4 · Before and after — drag to compare

The wipe control below shows the same synthetic hydrograph: left = raw total discharge only; right = baseflow separated (green = baseflow; blue = quickflow; teal line = total Q). Drag the divider left and right to compare.

Total Q (unseparated) Day of year →
Separated — Baseflow + Quickflow Day of year →
↔
Before
After

Synthetic data (α = 0.925, BFI ≈ 0.55). The real split depends on the actual gauge record — Module 5 fetches live USGS data you can run through this filter.

5 · Checkpoint

Three questions — pass all three to mark this module complete.

1 · The Baseflow Index (BFI) is defined as…

That's a flood-frequency ratio, not the BFI.
The recession constant describes how quickly flow falls, not the fraction that is baseflow.

2 · In the Lyne–Hollick filter, increasing α closer to 1 will…

Higher α means stronger smoothing — baseflow hugs the slow trend and doesn't respond to storms, so the estimate is smaller, not larger.
α is the key sensitivity parameter; changing it always changes the BFI estimate.

3 · Which type of catchment would you expect to have a high BFI (close to 1)?

Urban and impervious catchments route almost all rain to the stream rapidly — very low BFI.
Snowmelt is a large quickflow pulse — it tends to lower BFI, not raise it.

Sources & Further Reading

  1. Lyne, V. & Hollick, M. (1979). Stochastic time-variable rainfall-runoff modelling. Inst. Eng. Australia Nat. Conf. Publ. 79/10, 89–93. — Original filter formulation.
  2. Nathan, R. J. & McMahon, T. A. (1990). Evaluation of automated techniques for baseflow and recession analyses. Water Resources Research, 26(7), 1465–1473. — Recommends α = 0.925 for daily data; systematic evaluation of the Lyne–Hollick filter.
  3. Dingman, S. L. (2015). Physical Hydrology, 3rd ed. Waveland Press. — Baseflow and groundwater–stream exchange, Chapter 6.
Author
Mohammad Galib · Purdue University
License
CC-BY-4.0 — free to reuse with attribution
Module ID
baseflow-separation · v0.1.1
Course
Introduction to Hydrology · intro-hydro-2026
Data
Synthetic hydrograph (NumPy random seed 42); no real gauge data
Dependencies
numpy, matplotlib (kernel cell); compare wipe is pure DOM
CC-BY-4.0