Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/hydrology/rainfall_runoff.tex
51 views
unlisted
1
% Rainfall-Runoff Modeling Template
2
% Topics: Unit hydrograph theory, SCS Curve Number method, Nash cascade, watershed response
3
% Style: Engineering hydrology report with computational modeling
4
5
\documentclass[a4paper, 11pt]{article}
6
\usepackage[utf8]{inputenc}
7
\usepackage[T1]{fontenc}
8
\usepackage{amsmath, amssymb}
9
\usepackage{graphicx}
10
\usepackage{siunitx}
11
\usepackage{booktabs}
12
\usepackage{subcaption}
13
\usepackage[makestderr]{pythontex}
14
\usepackage{geometry}
15
\geometry{margin=1in}
16
17
% Theorem environments
18
\newtheorem{definition}{Definition}[section]
19
\newtheorem{theorem}{Theorem}[section]
20
\newtheorem{example}{Example}[section]
21
\newtheorem{remark}{Remark}[section]
22
23
\title{Rainfall-Runoff Modeling: Unit Hydrograph Theory and SCS Curve Number Method}
24
\author{Hydrology Research Group}
25
\date{\today}
26
27
\begin{document}
28
\maketitle
29
30
\begin{abstract}
31
This engineering report presents a comprehensive analysis of rainfall-runoff transformation
32
using the unit hydrograph approach and the Soil Conservation Service (SCS) Curve Number method.
33
We develop synthetic unit hydrographs using the Nash cascade model, compute excess rainfall
34
from design storms using the SCS-CN method, and apply discrete convolution to generate direct
35
runoff hydrographs. The analysis demonstrates watershed response characteristics including
36
time of concentration, peak discharge estimation, and the effects of antecedent moisture
37
conditions and land use on runoff generation.
38
\end{abstract}
39
40
\section{Introduction}
41
42
Rainfall-runoff modeling is fundamental to hydrologic engineering, providing the basis
43
for flood forecasting, drainage design, and water resources planning. The transformation
44
of precipitation into streamflow involves complex processes including interception,
45
infiltration, surface detention, and channel routing. The unit hydrograph approach,
46
introduced by Sherman (1932), provides an elegant framework for this transformation
47
by treating the watershed as a linear time-invariant system.
48
49
\begin{definition}[Unit Hydrograph]
50
A unit hydrograph (UH) is the direct runoff hydrograph resulting from 1 unit (typically
51
1 inch or 1 cm) of excess rainfall generated uniformly over the drainage area at a constant
52
rate for an effective duration $D$. The direct runoff excludes baseflow and represents
53
only the surface and rapid subsurface response.
54
\end{definition}
55
56
The SCS Curve Number method, developed by the USDA Natural Resources Conservation Service,
57
provides a practical approach to estimating rainfall excess from total precipitation based
58
on watershed characteristics including soil type, land use, and antecedent moisture conditions.
59
60
\section{Theoretical Framework}
61
62
\subsection{Unit Hydrograph Theory}
63
64
The unit hydrograph method relies on three fundamental principles:
65
66
\begin{enumerate}
67
\item \textbf{Time invariance}: The UH for a given watershed is time-invariant
68
\item \textbf{Proportionality}: Direct runoff ordinates are proportional to excess rainfall depth
69
\item \textbf{Superposition}: Runoff hydrographs from successive storms can be superposed
70
\end{enumerate}
71
72
\begin{theorem}[Convolution Equation]
73
For a watershed with unit hydrograph $u(t)$ and excess rainfall intensity $i_e(t)$,
74
the direct runoff $Q(t)$ is given by the convolution integral:
75
\begin{equation}
76
Q(t) = \int_0^t i_e(\tau) \cdot u(t - \tau) \, d\tau
77
\end{equation}
78
In discrete form with time intervals $\Delta t$:
79
\begin{equation}
80
Q_n = \sum_{m=1}^{n} P_m \cdot U_{n-m+1}
81
\end{equation}
82
where $P_m$ is excess rainfall in period $m$ and $U_n$ is the unit hydrograph ordinate.
83
\end{theorem}
84
85
\subsection{SCS Curve Number Method}
86
87
The SCS-CN method estimates direct runoff depth from rainfall depth based on the curve number $CN$,
88
which ranges from 0 (no runoff potential) to 100 (complete runoff).
89
90
\begin{definition}[SCS Runoff Equation]
91
The direct runoff depth $Q$ (inches) from rainfall depth $P$ (inches) is:
92
\begin{equation}
93
Q = \frac{(P - I_a)^2}{P - I_a + S}
94
\end{equation}
95
where $I_a$ is initial abstraction (interception + infiltration before runoff begins)
96
and $S$ is potential maximum retention after runoff begins. The relationships are:
97
\begin{equation}
98
S = \frac{1000}{CN} - 10 \quad \text{(inches)}
99
\end{equation}
100
\begin{equation}
101
I_a = 0.2S \quad \text{(standard assumption)}
102
\end{equation}
103
\end{definition}
104
105
\begin{remark}[Curve Number Selection]
106
Curve numbers depend on:
107
\begin{itemize}
108
\item \textbf{Hydrologic Soil Group}: A (low runoff), B, C, D (high runoff)
109
\item \textbf{Land Use/Cover}: Urban areas have higher CN than forests
110
\item \textbf{Antecedent Moisture Condition}: AMC I (dry), II (normal), III (wet)
111
\end{itemize}
112
\end{remark}
113
114
\subsection{Nash Cascade Model}
115
116
The Nash cascade represents the watershed as a series of $N$ identical linear reservoirs,
117
each with storage coefficient $K$.
118
119
\begin{theorem}[Nash Instantaneous Unit Hydrograph]
120
The instantaneous unit hydrograph (IUH) for a Nash cascade is:
121
\begin{equation}
122
u(t) = \frac{1}{K \Gamma(N)} \left(\frac{t}{K}\right)^{N-1} e^{-t/K}
123
\end{equation}
124
where $\Gamma(N)$ is the gamma function. The parameters relate to watershed properties:
125
\begin{equation}
126
K = \frac{t_p}{N} \quad \text{and} \quad N = \left(\frac{t_p}{\sigma}\right)^2
127
\end{equation}
128
where $t_p$ is time to peak and $\sigma$ is standard deviation of the IUH.
129
\end{theorem}
130
131
\section{Computational Analysis}
132
133
\subsection{Watershed Characteristics and Design Storm}
134
135
We analyze a small urban watershed to demonstrate rainfall-runoff transformation.
136
The watershed has mixed land use with moderate infiltration characteristics, typical
137
of suburban development with 40\% impervious cover. We apply a 6-hour design storm
138
with total precipitation depth of 4.5 inches, distributed using the SCS Type II
139
temporal distribution commonly used in the eastern United States.
140
141
\begin{pycode}
142
import numpy as np
143
import matplotlib.pyplot as plt
144
from scipy.special import gamma
145
from scipy.interpolate import interp1d
146
147
np.random.seed(42)
148
149
# Watershed characteristics
150
drainage_area = 2.5 # square miles
151
time_of_concentration = 1.2 # hours
152
CN = 75 # Curve number for suburban residential (1/4 acre lots, 38% impervious)
153
154
# Calculate storage parameters
155
S_inches = 1000/CN - 10 # Maximum retention (inches)
156
Ia_inches = 0.2 * S_inches # Initial abstraction (inches)
157
158
# Design storm parameters
159
total_rainfall = 4.5 # inches
160
storm_duration = 6.0 # hours
161
dt = 0.25 # time step (hours)
162
163
# SCS Type II storm distribution (cumulative fractions)
164
# Time fractions of storm duration
165
time_fractions = np.array([0, 0.1, 0.2, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6,
166
0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1.0])
167
# Cumulative rainfall fractions (Type II distribution)
168
cum_rainfall_fractions = np.array([0, 0.035, 0.076, 0.125, 0.156, 0.194, 0.219,
169
0.663, 0.735, 0.772, 0.799, 0.820, 0.844,
170
0.871, 0.898, 0.926, 0.963, 1.0])
171
172
# Create time series
173
time_storm = time_fractions * storm_duration
174
cum_rainfall = cum_rainfall_fractions * total_rainfall
175
176
# Interpolate to finer time step
177
time_series = np.arange(0, storm_duration + dt, dt)
178
interp_func = interp1d(time_storm, cum_rainfall, kind='linear', fill_value='extrapolate')
179
cum_rainfall_series = interp_func(time_series)
180
cum_rainfall_series = np.maximum(cum_rainfall_series, 0)
181
cum_rainfall_series = np.minimum(cum_rainfall_series, total_rainfall)
182
183
# Incremental rainfall depth
184
rainfall_incremental = np.diff(cum_rainfall_series, prepend=0)
185
186
# Calculate excess rainfall using SCS-CN method
187
excess_rainfall = np.zeros_like(cum_rainfall_series)
188
for i in range(len(cum_rainfall_series)):
189
P = cum_rainfall_series[i]
190
if P > Ia_inches:
191
Q = ((P - Ia_inches)**2) / (P - Ia_inches + S_inches)
192
excess_rainfall[i] = Q
193
194
# Incremental excess rainfall
195
excess_incremental = np.diff(excess_rainfall, prepend=0)
196
197
# Calculate runoff coefficient
198
total_excess = excess_rainfall[-1]
199
runoff_coefficient = total_excess / total_rainfall if total_rainfall > 0 else 0
200
201
# Plot rainfall and excess rainfall
202
fig1, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))
203
204
# Rainfall hyetograph
205
ax1.bar(time_series, rainfall_incremental / dt, width=dt*0.9,
206
color='steelblue', edgecolor='black', alpha=0.7, label='Total rainfall')
207
ax1.bar(time_series, excess_incremental / dt, width=dt*0.9,
208
color='darkred', edgecolor='black', alpha=0.7, label='Excess rainfall')
209
ax1.set_xlabel('Time (hours)', fontsize=11)
210
ax1.set_ylabel('Rainfall intensity (in/hr)', fontsize=11)
211
ax1.set_title(f'SCS Type II Design Storm (Total P = {total_rainfall} in, CN = {CN})', fontsize=12)
212
ax1.legend(fontsize=10, loc='upper left')
213
ax1.grid(True, alpha=0.3)
214
ax1.set_xlim(0, storm_duration)
215
216
# Cumulative rainfall and runoff
217
ax2.plot(time_series, cum_rainfall_series, 'b-', linewidth=2.5, label='Cumulative rainfall')
218
ax2.plot(time_series, excess_rainfall, 'r-', linewidth=2.5, label='Cumulative excess rainfall')
219
ax2.axhline(y=Ia_inches, color='gray', linestyle='--', linewidth=1.5,
220
label=f'Initial abstraction = {Ia_inches:.2f} in')
221
ax2.fill_between(time_series, 0, excess_rainfall, color='darkred', alpha=0.2)
222
ax2.set_xlabel('Time (hours)', fontsize=11)
223
ax2.set_ylabel('Cumulative depth (inches)', fontsize=11)
224
ax2.set_title(f'Cumulative Rainfall and Runoff (Runoff coefficient = {runoff_coefficient:.3f})', fontsize=12)
225
ax2.legend(fontsize=10, loc='upper left')
226
ax2.grid(True, alpha=0.3)
227
ax2.set_xlim(0, storm_duration)
228
ax2.set_ylim(0, total_rainfall * 1.1)
229
230
plt.tight_layout()
231
plt.savefig('rainfall_runoff_scs_cn.pdf', dpi=150, bbox_inches='tight')
232
plt.close()
233
\end{pycode}
234
235
\begin{figure}[htbp]
236
\centering
237
\includegraphics[width=0.95\textwidth]{rainfall_runoff_scs_cn.pdf}
238
\caption{SCS Curve Number method applied to Type II design storm showing (top) rainfall
239
hyetograph with incremental rainfall intensity and excess rainfall intensity, and (bottom)
240
cumulative rainfall and runoff depths. The curve number CN = \py{CN} represents suburban
241
residential land use with moderate runoff potential. Initial abstraction $I_a$ = \py{f"{Ia_inches:.2f}"}
242
inches must be satisfied before runoff begins, resulting in a runoff coefficient of
243
\py{f"{runoff_coefficient:.3f}"}, indicating that \py{f"{runoff_coefficient*100:.1f}"}\%
244
of total precipitation becomes direct runoff. The nonlinear relationship between rainfall
245
and runoff is evident in the cumulative plot, with runoff generation accelerating once
246
initial abstraction is exceeded.}
247
\label{fig:scs_cn}
248
\end{figure}
249
250
\subsection{Synthetic Unit Hydrograph Development}
251
252
Using the time of concentration and drainage area, we develop a synthetic unit hydrograph
253
based on the Nash cascade model. The Nash model parameters are calibrated to match typical
254
watershed response characteristics for small urban basins. We use $N = 3$ reservoirs to
255
represent the storage-routing behavior of the watershed, which provides a realistic shape
256
with moderate skewness. The storage coefficient $K$ is derived from the time of concentration
257
using empirical relationships for urban watersheds.
258
259
\begin{pycode}
260
# Nash cascade parameters
261
N_reservoirs = 3 # Number of linear reservoirs
262
lag_time = 0.6 * time_of_concentration # Lag time approximation
263
264
# Storage coefficient K from time to peak relationship
265
# For Nash model: tp K(N-1) for N > 1
266
K_hours = lag_time / (N_reservoirs - 1) if N_reservoirs > 1 else lag_time
267
268
# Time array for unit hydrograph (extended beyond storm)
269
time_uh = np.arange(0, 12, dt)
270
271
# Nash IUH function
272
def nash_iuh(t, K, N):
273
"""Nash instantaneous unit hydrograph"""
274
if t <= 0:
275
return 0
276
return (1 / (K * gamma(N))) * ((t / K)**(N - 1)) * np.exp(-t / K)
277
278
# Generate instantaneous unit hydrograph
279
iuh = np.array([nash_iuh(t, K_hours, N_reservoirs) for t in time_uh])
280
281
# Convert IUH to unit hydrograph for duration D = dt
282
# UH(t) IUH(t) * dt for small dt
283
unit_hydrograph = iuh * dt
284
285
# Normalize to ensure sum equals 1 inch over drainage area
286
uh_sum = np.sum(unit_hydrograph)
287
if uh_sum > 0:
288
unit_hydrograph = unit_hydrograph / uh_sum
289
290
# Convert to flow units (cfs per inch of excess rainfall)
291
# Q (cfs) = Depth (in) * Area (sq mi) * 640 acres/sq mi * 43560 sq ft/acre / (duration * 3600 s/hr)
292
conversion_factor = drainage_area * 640 * 43560 / (dt * 3600)
293
unit_hydrograph_cfs = unit_hydrograph * conversion_factor
294
295
# Calculate unit hydrograph properties
296
peak_uh = np.max(unit_hydrograph_cfs)
297
time_to_peak_uh = time_uh[np.argmax(unit_hydrograph_cfs)]
298
base_time_idx = np.where(unit_hydrograph_cfs > 0.01 * peak_uh)[0]
299
base_time_uh = time_uh[base_time_idx[-1]] if len(base_time_idx) > 0 else time_uh[-1]
300
301
# Plot unit hydrograph
302
fig2, ax = plt.subplots(figsize=(12, 7))
303
ax.fill_between(time_uh, 0, unit_hydrograph_cfs, color='steelblue', alpha=0.3, label='UH area')
304
ax.plot(time_uh, unit_hydrograph_cfs, 'b-', linewidth=2.5, label=f'Unit hydrograph (D = {dt} hr)')
305
ax.axvline(x=time_to_peak_uh, color='red', linestyle='--', linewidth=1.5,
306
label=f'Time to peak = {time_to_peak_uh:.2f} hr')
307
ax.axhline(y=peak_uh, color='red', linestyle=':', linewidth=1.5, alpha=0.7)
308
ax.scatter([time_to_peak_uh], [peak_uh], s=100, c='red', zorder=5, edgecolor='black')
309
310
# Add annotations
311
ax.annotate(f'Peak = {peak_uh:.0f} cfs',
312
xy=(time_to_peak_uh, peak_uh), xytext=(time_to_peak_uh + 1.5, peak_uh + 50),
313
arrowprops=dict(arrowstyle='->', color='red', lw=1.5),
314
fontsize=11, color='red', weight='bold')
315
316
ax.set_xlabel('Time (hours)', fontsize=12)
317
ax.set_ylabel('Discharge (cfs per inch of excess rainfall)', fontsize=12)
318
ax.set_title(f'Synthetic Unit Hydrograph - Nash Cascade Model (N = {N_reservoirs}, K = {K_hours:.2f} hr)',
319
fontsize=13)
320
ax.legend(fontsize=11, loc='upper right')
321
ax.grid(True, alpha=0.3)
322
ax.set_xlim(0, 10)
323
ax.set_ylim(0, peak_uh * 1.2)
324
325
plt.tight_layout()
326
plt.savefig('rainfall_runoff_unit_hydrograph.pdf', dpi=150, bbox_inches='tight')
327
plt.close()
328
\end{pycode}
329
330
\begin{figure}[htbp]
331
\centering
332
\includegraphics[width=0.95\textwidth]{rainfall_runoff_unit_hydrograph.pdf}
333
\caption{Synthetic unit hydrograph derived from Nash cascade model with N = \py{N_reservoirs}
334
linear reservoirs and storage coefficient K = \py{f"{K_hours:.2f}"} hours. The unit hydrograph
335
represents the direct runoff response to 1 inch of excess rainfall uniformly distributed over
336
the \py{f"{drainage_area:.1f}"} square mile watershed during a \py{dt}-hour duration. Peak
337
discharge occurs at \py{f"{time_to_peak_uh:.2f}"} hours with magnitude \py{f"{peak_uh:.0f}"}
338
cfs per inch of excess rainfall. The base time is approximately \py{f"{base_time_uh:.1f}"}
339
hours, indicating the duration of direct runoff. The shape exhibits characteristic rising limb,
340
sharp peak, and gradual recession typical of small urban watersheds with rapid concentration
341
and moderate storage effects.}
342
\label{fig:unit_hydrograph}
343
\end{figure}
344
345
\subsection{Direct Runoff Hydrograph by Convolution}
346
347
We now apply discrete convolution of the excess rainfall hyetograph with the unit hydrograph
348
to generate the complete direct runoff hydrograph. This demonstrates the superposition principle,
349
where each increment of excess rainfall generates its own hydrograph following the unit hydrograph
350
shape, and all individual responses are summed to produce the total watershed response. The
351
convolution process accounts for the temporal distribution of rainfall intensity and the storage-
352
routing characteristics of the watershed.
353
354
\begin{pycode}
355
# Extend time series to accommodate full hydrograph
356
time_total = np.arange(0, storm_duration + base_time_uh + dt, dt)
357
358
# Extend excess rainfall array with zeros
359
excess_extended = np.zeros(len(time_total))
360
excess_extended[:len(excess_incremental)] = excess_incremental
361
362
# Extend unit hydrograph to match length
363
uh_extended = np.zeros(len(time_total))
364
uh_length = min(len(unit_hydrograph_cfs), len(uh_extended))
365
uh_extended[:uh_length] = unit_hydrograph_cfs[:uh_length]
366
367
# Perform discrete convolution
368
# Q(n) = sum(P(m) * U(n-m+1)) for m = 1 to n
369
direct_runoff = np.zeros(len(time_total))
370
for n in range(len(time_total)):
371
for m in range(n + 1):
372
if m < len(excess_extended) and (n - m) < len(uh_extended):
373
direct_runoff[n] += excess_extended[m] * uh_extended[n - m]
374
375
# Calculate peak discharge and time to peak
376
peak_discharge = np.max(direct_runoff)
377
time_to_peak = time_total[np.argmax(direct_runoff)]
378
379
# Calculate runoff volume
380
runoff_volume_cf = np.sum(direct_runoff) * dt * 3600 # cubic feet
381
runoff_volume_inches = runoff_volume_cf / (drainage_area * 640 * 43560)
382
383
# Add baseflow (simplified constant baseflow)
384
baseflow = 15.0 # cfs
385
total_streamflow = direct_runoff + baseflow
386
387
# Calculate centroid of excess rainfall and runoff
388
excess_centroid = np.sum(time_series[:len(excess_incremental)] * excess_incremental) / np.sum(excess_incremental) if np.sum(excess_incremental) > 0 else 0
389
runoff_centroid = np.sum(time_total * direct_runoff * dt) / np.sum(direct_runoff * dt) if np.sum(direct_runoff * dt) > 0 else 0
390
lag_time_computed = runoff_centroid - excess_centroid
391
392
# Plot direct runoff hydrograph
393
fig3, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))
394
395
# Rainfall and runoff
396
ax1_twin = ax1.twinx()
397
ax1_twin.bar(time_series, rainfall_incremental / dt, width=dt*0.9,
398
color='steelblue', alpha=0.4, edgecolor='black', linewidth=0.5)
399
ax1_twin.bar(time_series, excess_incremental / dt, width=dt*0.9,
400
color='darkred', alpha=0.5, edgecolor='black', linewidth=0.7)
401
ax1_twin.set_ylabel('Rainfall intensity (in/hr)', fontsize=11, color='steelblue')
402
ax1_twin.tick_params(axis='y', labelcolor='steelblue')
403
ax1_twin.set_ylim(0, np.max(rainfall_incremental / dt) * 2.5)
404
ax1_twin.invert_yaxis()
405
406
ax1.fill_between(time_total, 0, direct_runoff, color='navy', alpha=0.3, label='Direct runoff')
407
ax1.plot(time_total, direct_runoff, 'b-', linewidth=2.5, label='Direct runoff hydrograph')
408
ax1.axvline(x=time_to_peak, color='red', linestyle='--', linewidth=1.5,
409
label=f'Time to peak = {time_to_peak:.2f} hr')
410
ax1.axhline(y=peak_discharge, color='red', linestyle=':', linewidth=1.5, alpha=0.7)
411
ax1.scatter([time_to_peak], [peak_discharge], s=120, c='red', zorder=5,
412
edgecolor='black', linewidth=1.5)
413
ax1.annotate(f'$Q_p$ = {peak_discharge:.0f} cfs',
414
xy=(time_to_peak, peak_discharge), xytext=(time_to_peak + 1.5, peak_discharge * 0.85),
415
arrowprops=dict(arrowstyle='->', color='red', lw=1.5),
416
fontsize=12, color='red', weight='bold')
417
418
ax1.set_xlabel('Time (hours)', fontsize=11)
419
ax1.set_ylabel('Discharge (cfs)', fontsize=11)
420
ax1.set_title('Direct Runoff Hydrograph from Convolution of Excess Rainfall with Unit Hydrograph',
421
fontsize=13)
422
ax1.legend(fontsize=10, loc='upper right')
423
ax1.grid(True, alpha=0.3)
424
ax1.set_xlim(0, storm_duration + base_time_uh)
425
ax1.set_ylim(0, peak_discharge * 1.3)
426
427
# Total streamflow with baseflow
428
ax2.fill_between(time_total, baseflow, total_streamflow, color='navy', alpha=0.3, label='Direct runoff')
429
ax2.fill_between(time_total, 0, baseflow, color='lightblue', alpha=0.5, label='Baseflow')
430
ax2.plot(time_total, total_streamflow, 'b-', linewidth=2.5, label='Total streamflow')
431
ax2.plot(time_total, np.ones_like(time_total) * baseflow, 'c--', linewidth=1.5, label=f'Baseflow = {baseflow} cfs')
432
ax2.axvline(x=time_to_peak, color='red', linestyle='--', linewidth=1.5, alpha=0.7)
433
ax2.scatter([time_to_peak], [peak_discharge + baseflow], s=120, c='red', zorder=5,
434
edgecolor='black', linewidth=1.5)
435
436
ax2.set_xlabel('Time (hours)', fontsize=11)
437
ax2.set_ylabel('Discharge (cfs)', fontsize=11)
438
ax2.set_title(f'Total Streamflow Hydrograph (Direct Runoff + Baseflow)', fontsize=13)
439
ax2.legend(fontsize=10, loc='upper right')
440
ax2.grid(True, alpha=0.3)
441
ax2.set_xlim(0, storm_duration + base_time_uh)
442
ax2.set_ylim(0, (peak_discharge + baseflow) * 1.2)
443
444
plt.tight_layout()
445
plt.savefig('rainfall_runoff_hydrograph.pdf', dpi=150, bbox_inches='tight')
446
plt.close()
447
\end{pycode}
448
449
\begin{figure}[htbp]
450
\centering
451
\includegraphics[width=0.95\textwidth]{rainfall_runoff_hydrograph.pdf}
452
\caption{Direct runoff hydrograph generated by discrete convolution of excess rainfall
453
with the unit hydrograph (top panel) and complete streamflow hydrograph including baseflow
454
(bottom panel). The peak discharge $Q_p$ = \py{f"{peak_discharge:.0f}"} cfs occurs at
455
$t_p$ = \py{f"{time_to_peak:.2f}"} hours, demonstrating the lag between peak rainfall
456
intensity and peak watershed response. The computed lag time is \py{f"{lag_time_computed:.2f}"}
457
hours, measured between the centroids of excess rainfall and direct runoff. Total runoff
458
volume is \py{f"{runoff_volume_inches:.2f}"} inches, which matches the excess rainfall
459
calculated by the SCS-CN method, validating the convolution procedure. The hydrograph shape
460
exhibits rapid rise following intense rainfall, sustained peak during the main storm period,
461
and exponential recession characteristic of the Nash cascade routing. Baseflow contribution
462
of \py{baseflow} cfs represents the pre-storm groundwater contribution to streamflow.}
463
\label{fig:runoff_hydrograph}
464
\end{figure}
465
466
\section{Sensitivity Analysis}
467
468
\subsection{Effect of Curve Number on Runoff}
469
470
The curve number exerts strong control on runoff generation, with small changes in CN
471
producing significant changes in total runoff volume and peak discharge. We analyze
472
watershed response across a range of curve numbers representing different land use
473
conditions, from forested areas (low CN) to highly urbanized basins (high CN). This
474
sensitivity is particularly important for assessing the hydrologic impacts of land
475
development and urban sprawl.
476
477
\begin{pycode}
478
# Analyze sensitivity to curve number
479
CN_values = [60, 65, 70, 75, 80, 85, 90] # Range from forest to urban
480
colors_cn = plt.cm.viridis(np.linspace(0.1, 0.9, len(CN_values)))
481
482
fig4, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(14, 10))
483
484
# Store results for each CN
485
peak_discharges_cn = []
486
runoff_coeffs = []
487
times_to_peak_cn = []
488
489
for i, CN_test in enumerate(CN_values):
490
# Calculate excess rainfall for this CN
491
S_test = 1000/CN_test - 10
492
Ia_test = 0.2 * S_test
493
494
excess_test = np.zeros_like(cum_rainfall_series)
495
for j in range(len(cum_rainfall_series)):
496
P = cum_rainfall_series[j]
497
if P > Ia_test:
498
Q = ((P - Ia_test)**2) / (P - Ia_test + S_test)
499
excess_test[j] = Q
500
501
excess_incr_test = np.diff(excess_test, prepend=0)
502
503
# Convolution
504
excess_ext_test = np.zeros(len(time_total))
505
excess_ext_test[:len(excess_incr_test)] = excess_incr_test
506
507
runoff_test = np.zeros(len(time_total))
508
for n in range(len(time_total)):
509
for m in range(n + 1):
510
if m < len(excess_ext_test) and (n - m) < len(uh_extended):
511
runoff_test[n] += excess_ext_test[m] * uh_extended[n - m]
512
513
peak_q = np.max(runoff_test)
514
peak_discharges_cn.append(peak_q)
515
runoff_coeffs.append(excess_test[-1] / total_rainfall if total_rainfall > 0 else 0)
516
times_to_peak_cn.append(time_total[np.argmax(runoff_test)])
517
518
# Plot hydrograph
519
ax1.plot(time_total, runoff_test, color=colors_cn[i], linewidth=2,
520
label=f'CN = {CN_test}', alpha=0.8)
521
522
# Plot cumulative runoff
523
ax2.plot(time_total, np.cumsum(runoff_test) * dt * 3600 / (drainage_area * 640 * 43560),
524
color=colors_cn[i], linewidth=2, label=f'CN = {CN_test}', alpha=0.8)
525
526
# Peak discharge vs CN
527
ax3.plot(CN_values, peak_discharges_cn, 'o-', color='darkblue', linewidth=2.5,
528
markersize=8, markerfacecolor='steelblue', markeredgecolor='black', markeredgewidth=1.5)
529
ax3.set_xlabel('Curve Number (CN)', fontsize=11)
530
ax3.set_ylabel('Peak discharge (cfs)', fontsize=11)
531
ax3.set_title('Peak Discharge vs. Curve Number', fontsize=12)
532
ax3.grid(True, alpha=0.3)
533
534
# Runoff coefficient vs CN
535
ax4.plot(CN_values, runoff_coeffs, 's-', color='darkred', linewidth=2.5,
536
markersize=8, markerfacecolor='coral', markeredgecolor='black', markeredgewidth=1.5)
537
ax4.set_xlabel('Curve Number (CN)', fontsize=11)
538
ax4.set_ylabel('Runoff coefficient', fontsize=11)
539
ax4.set_title('Runoff Coefficient vs. Curve Number', fontsize=12)
540
ax4.grid(True, alpha=0.3)
541
ax4.set_ylim(0, 1)
542
543
ax1.set_xlabel('Time (hours)', fontsize=11)
544
ax1.set_ylabel('Discharge (cfs)', fontsize=11)
545
ax1.set_title('Effect of Curve Number on Hydrograph Shape', fontsize=12)
546
ax1.legend(fontsize=9, loc='upper right')
547
ax1.grid(True, alpha=0.3)
548
ax1.set_xlim(0, 15)
549
550
ax2.set_xlabel('Time (hours)', fontsize=11)
551
ax2.set_ylabel('Cumulative runoff depth (inches)', fontsize=11)
552
ax2.set_title('Cumulative Direct Runoff Depth', fontsize=12)
553
ax2.legend(fontsize=9, loc='lower right')
554
ax2.grid(True, alpha=0.3)
555
ax2.set_xlim(0, 15)
556
557
plt.tight_layout()
558
plt.savefig('rainfall_runoff_cn_sensitivity.pdf', dpi=150, bbox_inches='tight')
559
plt.close()
560
561
# Calculate CN sensitivity metrics
562
CN_mid_idx = len(CN_values) // 2
563
dQ_dCN = (peak_discharges_cn[-1] - peak_discharges_cn[0]) / (CN_values[-1] - CN_values[0])
564
relative_change = (peak_discharges_cn[-1] - peak_discharges_cn[0]) / peak_discharges_cn[0] * 100
565
\end{pycode}
566
567
\begin{figure}[htbp]
568
\centering
569
\includegraphics[width=0.98\textwidth]{rainfall_runoff_cn_sensitivity.pdf}
570
\caption{Sensitivity of watershed response to curve number variation from CN = 60 (forested,
571
permeable soils) to CN = 90 (heavily urbanized, impervious surfaces). Top left panel shows
572
direct runoff hydrographs demonstrating dramatic increases in peak discharge and runoff volume
573
with increasing CN. Top right panel displays cumulative runoff depth, illustrating how
574
urbanization increases total runoff. Bottom panels quantify peak discharge sensitivity
575
(slope = \py{f"{dQ_dCN:.1f}"} cfs per CN unit) and runoff coefficient progression from
576
\py{f"{runoff_coeffs[0]:.2f}"} to \py{f"{runoff_coeffs[-1]:.2f}"}. The \py{f"{relative_change:.0f}"}\%
577
increase in peak discharge from CN = 60 to CN = 90 demonstrates the profound hydrologic impact
578
of urbanization, with corresponding increases in flood risk and reduced groundwater recharge.}
579
\label{fig:cn_sensitivity}
580
\end{figure}
581
582
\subsection{Effect of Time of Concentration}
583
584
Time of concentration affects both the unit hydrograph shape and watershed response timing.
585
Shorter times of concentration, typical of urbanized or steep watersheds, produce higher,
586
sharper peaks with reduced lag time. Longer times of concentration, characteristic of flat
587
rural watersheds with longer flow paths, produce attenuated hydrographs with delayed peaks.
588
We examine this sensitivity by varying the Nash model storage coefficient while maintaining
589
constant excess rainfall.
590
591
\begin{pycode}
592
# Analyze sensitivity to time of concentration
593
tc_values = [0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8] # hours
594
colors_tc = plt.cm.plasma(np.linspace(0.1, 0.9, len(tc_values)))
595
596
fig5, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(14, 10))
597
598
peak_discharges_tc = []
599
times_to_peak_tc = []
600
601
for i, tc_test in enumerate(tc_values):
602
# Recalculate unit hydrograph with new tc
603
lag_test = 0.6 * tc_test
604
K_test = lag_test / (N_reservoirs - 1) if N_reservoirs > 1 else lag_test
605
606
iuh_test = np.array([nash_iuh(t, K_test, N_reservoirs) for t in time_uh])
607
uh_test = iuh_test * dt
608
uh_sum_test = np.sum(uh_test)
609
if uh_sum_test > 0:
610
uh_test = uh_test / uh_sum_test
611
uh_test_cfs = uh_test * conversion_factor
612
613
# Extend for convolution
614
uh_ext_test = np.zeros(len(time_total))
615
uh_length_test = min(len(uh_test_cfs), len(uh_ext_test))
616
uh_ext_test[:uh_length_test] = uh_test_cfs[:uh_length_test]
617
618
# Convolution with original excess rainfall
619
runoff_tc_test = np.zeros(len(time_total))
620
for n in range(len(time_total)):
621
for m in range(n + 1):
622
if m < len(excess_extended) and (n - m) < len(uh_ext_test):
623
runoff_tc_test[n] += excess_extended[m] * uh_ext_test[n - m]
624
625
peak_q_tc = np.max(runoff_tc_test)
626
peak_discharges_tc.append(peak_q_tc)
627
times_to_peak_tc.append(time_total[np.argmax(runoff_tc_test)])
628
629
# Plot unit hydrograph
630
ax1.plot(time_uh, uh_test_cfs, color=colors_tc[i], linewidth=2,
631
label=f'$t_c$ = {tc_test} hr', alpha=0.8)
632
633
# Plot runoff hydrograph
634
ax2.plot(time_total, runoff_tc_test, color=colors_tc[i], linewidth=2,
635
label=f'$t_c$ = {tc_test} hr', alpha=0.8)
636
637
# Peak discharge vs tc
638
ax3.plot(tc_values, peak_discharges_tc, 'o-', color='darkblue', linewidth=2.5,
639
markersize=8, markerfacecolor='steelblue', markeredgecolor='black', markeredgewidth=1.5)
640
ax3.set_xlabel('Time of concentration (hours)', fontsize=11)
641
ax3.set_ylabel('Peak discharge (cfs)', fontsize=11)
642
ax3.set_title('Peak Discharge vs. Time of Concentration', fontsize=12)
643
ax3.grid(True, alpha=0.3)
644
645
# Time to peak vs tc
646
ax4.plot(tc_values, times_to_peak_tc, 's-', color='darkred', linewidth=2.5,
647
markersize=8, markerfacecolor='coral', markeredgecolor='black', markeredgewidth=1.5)
648
ax4.plot(tc_values, tc_values, 'k--', linewidth=1.5, alpha=0.5, label='1:1 line')
649
ax4.set_xlabel('Time of concentration (hours)', fontsize=11)
650
ax4.set_ylabel('Time to peak discharge (hours)', fontsize=11)
651
ax4.set_title('Time to Peak vs. Time of Concentration', fontsize=12)
652
ax4.legend(fontsize=10)
653
ax4.grid(True, alpha=0.3)
654
655
ax1.set_xlabel('Time (hours)', fontsize=11)
656
ax1.set_ylabel('Discharge (cfs per inch)', fontsize=11)
657
ax1.set_title('Unit Hydrographs for Different $t_c$', fontsize=12)
658
ax1.legend(fontsize=9, loc='upper right')
659
ax1.grid(True, alpha=0.3)
660
ax1.set_xlim(0, 8)
661
662
ax2.set_xlabel('Time (hours)', fontsize=11)
663
ax2.set_ylabel('Discharge (cfs)', fontsize=11)
664
ax2.set_title('Runoff Hydrographs for Different $t_c$', fontsize=12)
665
ax2.legend(fontsize=9, loc='upper right')
666
ax2.grid(True, alpha=0.3)
667
ax2.set_xlim(0, 15)
668
669
plt.tight_layout()
670
plt.savefig('rainfall_runoff_tc_sensitivity.pdf', dpi=150, bbox_inches='tight')
671
plt.close()
672
673
# Calculate inverse relationship strength
674
from scipy.stats import linregress
675
slope_tc, intercept_tc, r_tc, p_tc, se_tc = linregress(tc_values, peak_discharges_tc)
676
\end{pycode}
677
678
\begin{figure}[htbp]
679
\centering
680
\includegraphics[width=0.98\textwidth]{rainfall_runoff_tc_sensitivity.pdf}
681
\caption{Sensitivity of watershed response to time of concentration $t_c$ ranging from
682
0.6 hours (highly urbanized, steep watershed) to 1.8 hours (rural, mild slopes). Top left
683
shows unit hydrographs becoming more attenuated and delayed with increasing $t_c$. Top right
684
displays corresponding runoff hydrographs exhibiting the inverse relationship between $t_c$
685
and peak discharge. Bottom left quantifies this relationship with regression slope
686
\py{f"{slope_tc:.1f}"} cfs/hour (R² = \py{f"{r_tc**2:.3f}"}), demonstrating that shorter
687
concentration times produce \py{f"{abs(peak_discharges_tc[0] - peak_discharges_tc[-1]):.0f}"}
688
cfs higher peaks. Bottom right shows time to peak increases approximately linearly with $t_c$,
689
though storm timing also influences this relationship. These results have critical implications
690
for urban drainage design and flood mitigation.}
691
\label{fig:tc_sensitivity}
692
\end{figure}
693
694
\section{Results Summary}
695
696
\begin{pycode}
697
print(r"\begin{table}[htbp]")
698
print(r"\centering")
699
print(r"\caption{Summary of Rainfall-Runoff Analysis Results}")
700
print(r"\begin{tabular}{lcc}")
701
print(r"\toprule")
702
print(r"\textbf{Parameter} & \textbf{Value} & \textbf{Units} \\")
703
print(r"\midrule")
704
print(r"\multicolumn{3}{l}{\textit{Watershed Characteristics}} \\")
705
print(f"Drainage area & {drainage_area:.2f} & sq mi \\\\")
706
print(f"Time of concentration & {time_of_concentration:.2f} & hr \\\\")
707
print(f"Curve number (CN) & {CN} & --- \\\\")
708
print(f"Maximum retention (S) & {S_inches:.2f} & in \\\\")
709
print(f"Initial abstraction ($I_a$) & {Ia_inches:.2f} & in \\\\")
710
print(r"\midrule")
711
print(r"\multicolumn{3}{l}{\textit{Design Storm}} \\")
712
print(f"Total rainfall depth & {total_rainfall:.2f} & in \\\\")
713
print(f"Storm duration & {storm_duration:.1f} & hr \\\\")
714
print(f"Distribution type & SCS Type II & --- \\\\")
715
print(r"\midrule")
716
print(r"\multicolumn{3}{l}{\textit{Runoff Generation}} \\")
717
print(f"Total excess rainfall & {total_excess:.2f} & in \\\\")
718
print(f"Runoff coefficient & {runoff_coefficient:.3f} & --- \\\\")
719
print(f"Runoff volume & {runoff_volume_cf:.0f} & cu ft \\\\")
720
print(r"\midrule")
721
print(r"\multicolumn{3}{l}{\textit{Unit Hydrograph}} \\")
722
print(f"Nash N parameter & {N_reservoirs} & --- \\\\")
723
print(f"Storage coefficient (K) & {K_hours:.2f} & hr \\\\")
724
print(f"Peak UH discharge & {peak_uh:.0f} & cfs/in \\\\")
725
print(f"Time to peak (UH) & {time_to_peak_uh:.2f} & hr \\\\")
726
print(f"Base time & {base_time_uh:.1f} & hr \\\\")
727
print(r"\midrule")
728
print(r"\multicolumn{3}{l}{\textit{Direct Runoff Hydrograph}} \\")
729
print(f"Peak discharge & {peak_discharge:.0f} & cfs \\\\")
730
print(f"Time to peak & {time_to_peak:.2f} & hr \\\\")
731
print(f"Lag time & {lag_time_computed:.2f} & hr \\\\")
732
print(r"\bottomrule")
733
print(r"\end{tabular}")
734
print(r"\label{tab:results}")
735
print(r"\end{table}")
736
\end{pycode}
737
738
\section{Discussion}
739
740
\subsection{Hydrologic Insights}
741
742
The analysis demonstrates several key principles of rainfall-runoff transformation:
743
744
\begin{enumerate}
745
\item \textbf{Nonlinear losses}: The SCS-CN method captures the nonlinear relationship
746
between precipitation and runoff, with runoff coefficient increasing from near-zero for
747
small storms to asymptotically approaching $(1 - I_a/P)$ for large storms.
748
749
\item \textbf{Watershed routing}: The Nash cascade model provides a parsimonious representation
750
of watershed storage and routing effects with physically meaningful parameters (N and K)
751
related to watershed geomorphology.
752
753
\item \textbf{Peak attenuation}: The convolution process demonstrates how watershed storage
754
attenuates and delays the runoff response relative to rainfall input, with peak discharge
755
occurring \py{f"{lag_time_computed:.2f}"} hours after the rainfall centroid.
756
757
\item \textbf{Land use sensitivity}: The CN sensitivity analysis quantifies the dramatic
758
impact of urbanization on flood potential, with peak discharge increasing by
759
\py{f"{relative_change:.0f}"}\% when CN increases from 60 to 90.
760
\end{enumerate}
761
762
\subsection{Engineering Applications}
763
764
These methods provide the foundation for:
765
766
\begin{itemize}
767
\item \textbf{Flood frequency analysis}: Design hydrographs for culvert and bridge sizing
768
\item \textbf{Stormwater management}: Detention basin sizing and outlet design
769
\item \textbf{Urban drainage}: Storm sewer network design and analysis
770
\item \textbf{Regulatory compliance}: Post-development runoff not exceeding pre-development conditions
771
\item \textbf{Watershed planning}: Assessing hydrologic impacts of land use changes
772
\end{itemize}
773
774
\subsection{Method Limitations}
775
776
Important assumptions and limitations include:
777
778
\begin{remark}[Model Assumptions]
779
\begin{itemize}
780
\item \textbf{Spatial uniformity}: Rainfall and watershed properties assumed spatially uniform
781
\item \textbf{Temporal invariance}: Unit hydrograph assumed constant for all storm events
782
\item \textbf{Linearity}: Superposition assumes linear watershed response
783
\item \textbf{Empiricism}: SCS-CN method is empirical, calibrated to agricultural watersheds
784
\item \textbf{Initial conditions}: Antecedent moisture significantly affects CN selection
785
\end{itemize}
786
\end{remark}
787
788
For large or heterogeneous watersheds, distributed hydrologic models may be more appropriate.
789
790
\section{Conclusions}
791
792
This comprehensive analysis of rainfall-runoff transformation demonstrates the coupled
793
application of the SCS Curve Number method and unit hydrograph theory for watershed response
794
prediction. Key findings include:
795
796
\begin{enumerate}
797
\item For the \py{f"{drainage_area:.1f}"} square mile urban watershed with CN = \py{CN},
798
the \py{total_rainfall}-inch design storm produces peak discharge $Q_p$ = \py{f"{peak_discharge:.0f}"}
799
cfs occurring at $t_p$ = \py{f"{time_to_peak:.2f}"} hours.
800
801
\item The runoff coefficient of \py{f"{runoff_coefficient:.3f}"} indicates that
802
\py{f"{runoff_coefficient*100:.1f}"}\% of total precipitation becomes direct runoff, with
803
\py{f"{Ia_inches:.2f}"} inches lost to initial abstraction and \py{f"{S_inches:.2f}"}
804
inches to continuing infiltration and retention.
805
806
\item The Nash cascade unit hydrograph with N = \py{N_reservoirs} reservoirs and K =
807
\py{f"{K_hours:.2f}"} hours accurately represents the storage-routing characteristics
808
of small urban watersheds, producing realistic hydrograph shape and timing.
809
810
\item Sensitivity analysis reveals peak discharge varies from \py{f"{peak_discharges_cn[0]:.0f}"}
811
to \py{f"{peak_discharges_cn[-1]:.0f}"} cfs across the CN range 60-90, emphasizing the
812
critical importance of land use in flood generation and the need for effective stormwater
813
management in developing areas.
814
815
\item Time of concentration exerts strong control on hydrograph attenuation, with the
816
inverse relationship between $t_c$ and peak discharge (slope = \py{f"{slope_tc:.1f}"}
817
cfs/hour) demonstrating why urbanization (which reduces $t_c$) amplifies flood peaks.
818
\end{enumerate}
819
820
These methods provide essential tools for hydrologic engineering practice, enabling
821
quantitative assessment of watershed response to design storms for infrastructure planning,
822
flood mitigation, and environmental protection.
823
824
\section*{References}
825
826
\begin{enumerate}
827
\item Sherman, L.K. (1932). Streamflow from rainfall by the unit-graph method.
828
\textit{Engineering News-Record}, 108, 501-505.
829
830
\item Soil Conservation Service (1985). \textit{National Engineering Handbook, Section 4: Hydrology}.
831
U.S. Department of Agriculture, Washington, D.C.
832
833
\item Chow, V.T., Maidment, D.R., and Mays, L.W. (1988). \textit{Applied Hydrology}.
834
McGraw-Hill, New York.
835
836
\item Nash, J.E. (1957). The form of the instantaneous unit hydrograph.
837
\textit{International Association of Scientific Hydrology, Publication}, 45(3), 114-121.
838
839
\item Hawkins, R.H., Ward, T.J., Woodward, D.E., and Van Mullem, J.A. (2009).
840
\textit{Curve Number Hydrology: State of the Practice}. American Society of Civil Engineers.
841
842
\item Natural Resources Conservation Service (2010). \textit{National Engineering Handbook,
843
Part 630: Hydrology}. U.S. Department of Agriculture.
844
845
\item Dooge, J.C.I. (1973). Linear theory of hydrologic systems. \textit{Technical Bulletin
846
No. 1468}, Agricultural Research Service, U.S. Department of Agriculture.
847
848
\item McCuen, R.H. (2016). \textit{Hydrologic Analysis and Design}, 4th ed. Pearson, Upper Saddle River, NJ.
849
850
\item Singh, V.P. (1988). \textit{Hydrologic Systems: Rainfall-Runoff Modeling}, Volume I.
851
Prentice Hall, Englewood Cliffs, NJ.
852
853
\item Viessman, W. and Lewis, G.L. (2003). \textit{Introduction to Hydrology}, 5th ed.
854
Pearson Education, Upper Saddle River, NJ.
855
856
\item Ponce, V.M. (1989). \textit{Engineering Hydrology: Principles and Practices}.
857
Prentice Hall, Englewood Cliffs, NJ.
858
859
\item USDA-NRCS (2004). Estimation of direct runoff from storm rainfall. In \textit{National
860
Engineering Handbook Part 630}, Chapter 10. U.S. Department of Agriculture.
861
862
\item Mishra, S.K. and Singh, V.P. (2003). \textit{Soil Conservation Service Curve Number (SCS-CN)
863
Methodology}. Kluwer Academic Publishers, Dordrecht.
864
865
\item Singh, V.P. (1992). \textit{Elementary Hydrology}. Prentice Hall, Englewood Cliffs, NJ.
866
867
\item Hjelmfelt, A.T. (1991). Investigation of curve number procedure. \textit{Journal of
868
Hydraulic Engineering}, 117(6), 725-737.
869
870
\item Mockus, V. (1949). Estimation of total (and peak rates of) surface runoff for individual
871
storms. \textit{Exhibit A of Appendix B, Interim Survey Report, Grand (Neosho) River Watershed},
872
U.S. Department of Agriculture.
873
874
\item Snyder, F.F. (1938). Synthetic unit-graphs. \textit{Transactions of the American Geophysical
875
Union}, 19, 447-454.
876
877
\item U.S. Army Corps of Engineers (2000). \textit{Hydrologic Modeling System HEC-HMS: Technical
878
Reference Manual}. Hydrologic Engineering Center, Davis, CA.
879
880
\item Singh, V.P. and Frevert, D.K. (2006). \textit{Watershed Models}. CRC Press, Boca Raton, FL.
881
882
\item Beven, K.J. (2012). \textit{Rainfall-Runoff Modelling: The Primer}, 2nd ed. Wiley-Blackwell,
883
Chichester, UK.
884
\end{enumerate}
885
886
\end{document}
887
888