Deriving SCS Dimensionless Unit Hydrographs in Python
The NRCS (formerly SCS) dimensionless unit hydrograph reduces an entire basin transfer function to two numbers — a time to peak and a peak discharge — applied to one fixed curve of t/Tp versus q/qp. That economy is why it is the default synthetic hydrograph for ungauged design work, and this guide shows how to scale it to a real basin in Python. It is a focused implementation reference under the Unit Hydrograph Methods guide, which itself sits within the broader Rainfall-Runoff Modeling & Hydrologic Simulation coverage on this site. The parent guide handles the convolution that consumes the UH you build here; this page is only about generating the ordinates correctly.
The method has exactly three moving parts: locate the time to peak Tp from basin lag, compute the peak discharge qp from a peak factor, and sample the standard dimensionless table onto your time step. Get those right and the resulting array drops straight into convolution.
The Standard Dimensionless Curve
The SCS dimensionless UH is a single curvilinear shape, tabulated as the ratio of discharge to peak discharge (q/qp) against the ratio of time to time-to-peak (t/Tp). It is normalized so that the area under it corresponds to one unit of runoff. The standard curve places 37.5 percent of the runoff volume under the rising limb and reaches a time base of five times Tp. A representative subset of the ordinates:
| t/Tp | q/qp | t/Tp | q/qp |
|---|---|---|---|
| 0.0 | 0.000 | 1.1 | 0.990 |
| 0.2 | 0.100 | 1.3 | 0.860 |
| 0.4 | 0.310 | 1.5 | 0.680 |
| 0.5 | 0.470 | 1.7 | 0.460 |
| 0.6 | 0.660 | 2.0 | 0.280 |
| 0.7 | 0.820 | 2.4 | 0.147 |
| 0.8 | 0.930 | 3.0 | 0.055 |
| 0.9 | 0.990 | 4.0 | 0.011 |
| 1.0 | 1.000 | 5.0 | 0.000 |
To turn this into a basin UH you multiply the t/Tp column by Tp (giving real time) and the q/qp column by qp (giving real discharge), then resample onto your computation time step.
Step 1 — Basin Lag and Time to Peak
The time to peak follows the standard relation:
Tp = D/2 + Tlag
where D is the unit-hydrograph duration and Tlag is the basin lag, the delay from the centroid of excess rainfall to the peak. The NRCS ties lag to the time of concentration:
Tlag = 0.6 * Tc
The time of concentration Tc itself is estimated by the segmental velocity method or by the NRCS lag equation from flow length, slope, and curve number. That estimation is a topic in its own right; for the velocity-based approach see estimating time of concentration with the NRCS velocity method. For consistency with convolution, choose D to match your rainfall time step. The NRCS also recommends D not exceed roughly 0.133·Tc for the standard shape; if your time step is coarser, derive the UH at a small D and convert its duration through the S-curve described in the parent guide.
Step 2 — Peak Discharge and the Peak Factor
The peak discharge for one unit of runoff is:
qp = C * A / Tp
In English units C = 484, with qp in cubic feet per second, A in square miles, Tp in hours, and the UH representing one inch of runoff. In SI units the equivalent constant is 2.08 with A in square kilometres, qp in cubic metres per second, Tp in hours, and one millimetre of runoff. The constant is called the peak rate factor (PRF) and it is not arbitrary: 484 corresponds to a hydrograph in which 37.5 percent of the runoff volume falls under the rising limb. Flatter, more attenuated basins put less volume under the rising limb and therefore take a lower PRF; steep flashy basins take a higher one.
Peak-Factor Variants and Terrain
| Peak factor (English) | SI equivalent | Rising-limb volume | Best-fit terrain |
|---|---|---|---|
| 600 | 2.58 | ~45% | Steep, mountainous, rapid response |
| 484 | 2.08 | 37.5% | Standard mixed / rolling terrain |
| 300 | 1.29 | ~23% | Flat agricultural, coastal plain |
| 100 | 0.43 | ~8% | Very flat, swampy, marshy |
The critical caveat: each peak factor comes with its own dimensionless t/Tp versus q/qp table. A lower PRF stretches the time base and flattens the peak, so you cannot keep the standard 484 table and merely change the constant in qp = C·A/Tp. Changing the terrain response means swapping both the constant and the shape table together, or the volume under the curve no longer equals one unit.
Step 3 — Sampling the Table in Python
The implementation scales the dimensionless ordinates, resamples them onto the computation time step with linear interpolation, and normalizes the result to conserve unit volume. Every non-trivial step carries an inline comment.
import logging
import numpy as np
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
# Standard SCS dimensionless UH (PRF 484): t/Tp and q/qp
T_RATIO = np.array([0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.6,
2.8, 3.0, 3.5, 4.0, 4.5, 5.0])
Q_RATIO = np.array([0.000, 0.030, 0.100, 0.190, 0.310, 0.470, 0.660, 0.820,
0.930, 0.990, 1.000, 0.990, 0.930, 0.860, 0.780, 0.680,
0.560, 0.390, 0.280, 0.207, 0.147, 0.107, 0.077, 0.055,
0.025, 0.011, 0.005, 0.000])
def scs_unit_hydrograph(area_km2: float, tc_hours: float, dt_hours: float,
peak_factor_si: float = 2.08) -> tuple[np.ndarray, np.ndarray]:
"""
Build an SCS dimensionless unit hydrograph scaled to a basin (SI units).
area_km2 : drainage area (km2)
tc_hours : time of concentration (h)
dt_hours : computation / unit duration D (h); UH duration equals dt_hours
peak_factor_si : PRF in SI form (2.08 standard; 1.29 flat; 0.43 swampy; 2.58 steep)
Returns (time_hours, ordinates_m3s_per_mm).
"""
t_lag = 0.6 * tc_hours # NRCS lag from time of concentration
t_peak = dt_hours / 2.0 + t_lag # Tp = D/2 + Tlag
q_peak = peak_factor_si * area_km2 / t_peak # qp = C*A/Tp, m3/s per mm of runoff
logging.info("Tlag=%.2f h Tp=%.2f h qp=%.3f m3/s/mm", t_lag, t_peak, q_peak)
# Scale dimensionless ordinates to real time and discharge
t_curve = T_RATIO * t_peak
q_curve = Q_RATIO * q_peak
# Resample onto the fixed computation time step
n_steps = int(np.ceil(t_curve[-1] / dt_hours)) + 1
times = np.arange(n_steps) * dt_hours
ordinates = np.interp(times, t_curve, q_curve, left=0.0, right=0.0)
# Normalise so the UH integrates to exactly 1 mm of runoff over the area
# 1 mm over area_km2 = area_km2 * 1000 m3
volume_m3 = ordinates.sum() * dt_hours * 3600.0
expected_m3 = area_km2 * 1000.0
ratio = volume_m3 / expected_m3
if not np.isclose(ratio, 1.0, atol=0.03):
logging.warning("Rescaling UH to conserve unit volume (ratio %.3f)", ratio)
ordinates = ordinates / ratio
logging.info("UH built: %d ordinates, peak %.3f m3/s at t=%.2f h",
ordinates.size, ordinates.max(), times[ordinates.argmax()])
return times, ordinates
if __name__ == "__main__":
t, uh = scs_unit_hydrograph(area_km2=25.0, tc_hours=4.0, dt_hours=1.0)
for ti, qi in zip(t, uh):
print(f"{ti:5.2f} h {qi:7.3f} m3/s/mm")
The np.interp resample is what makes the UH usable at an arbitrary time step, and the volume-normalization block is the non-negotiable safety check: the raw scaled ordinates rarely integrate to exactly one unit because the dimensionless table is sampled unevenly, so the rescale corrects the small residual before the UH ever reaches convolution.
Gotchas
- Peak factor and table must move together. Swapping 484 for 300 without also swapping the t/Tp versus q/qp table produces a UH whose volume is no longer one unit. Keep matched pairs.
- Unit systems. 484 is English (mi², inches, cfs); 2.08 is SI (km², mm, m³/s). Mixing a metric area with the English constant scales the peak by more than a factor of 600. Fix the unit system before touching the constant.
- Duration versus time step. Tp includes D/2, so the UH duration D is baked into the shape. If your rainfall time step differs from D, convert the UH duration through the S-curve rather than resampling naively.
- Coarse sampling near the peak. With a large dt the interpolation can step over the true peak, undersizing qp. Build the UH at a fine dt and aggregate, or confirm that dt is well below Tp/5.
- Lag error propagates twice. Because qp = C·A/Tp and Tp depends on lag, an error in Tc shifts both the timing and the height of the peak. Validate Tc before trusting the hydrograph.
Frequently Asked Questions
What does the peak factor 484 mean in the SCS unit hydrograph?
484 is the peak rate factor in English units: qp in cubic feet per second equals 484 times drainage area in square miles times runoff depth in inches, divided by the time to peak in hours. It corresponds to 37.5 percent of the runoff volume occurring under the rising limb, which is the standard shape for mixed, rolling terrain. The metric equivalent is 2.08 for area in square kilometres, depth in millimetres, and discharge in cubic metres per second.
When should I use a peak factor of 300 or 100 instead of 484?
Lower the peak factor for flatter, slower basins. Use around 300 for flat agricultural or coastal-plain basins and 100 for very flat, swampy or marshy watersheds where storage spreads the hydrograph out. Raise it toward 600 for steep, mountainous terrain with rapid response. Each peak factor comes with its own dimensionless t/Tp versus q/qp table, so you cannot simply swap the constant in qp = C*A/Tp without also changing the table.
How is time to peak related to basin lag in the SCS method?
Time to peak Tp equals D/2 plus the basin lag Tlag, where D is the unit-hydrograph duration and Tlag is the delay from the centroid of excess rainfall to the peak. The NRCS relates lag to the time of concentration as Tlag = 0.6Tc. Because Tp drives the peak discharge qp = CA/Tp, an error in lag propagates directly into both the timing and the magnitude of the peak.
Related Topics
- Unit Hydrograph Methods — parent guide covering LTI theory, the convolution equation, and the S-curve that consumes this UH
- Snyder vs Clark Unit Hydrograph: When to Use Each — a contrasting pair of synthetic methods for when the single-parameter SCS shape is too rigid
- Estimating Time of Concentration with the NRCS Velocity Method — how to compute the Tc that drives basin lag and Tp
- Curve Number Runoff Estimation — the loss model producing the excess rainfall this UH is convolved against