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.

SCS dimensionless unit hydrograph and its scaling to a basin A curvilinear unit hydrograph rising to a peak at t/Tp = 1, q/qp = 1, then receding to zero near t/Tp = 5. Axes are labelled t/Tp horizontally and q/qp vertically. Annotations show that the horizontal axis multiplies by Tp to become real time and the vertical axis multiplies by qp to become real discharge. peak (1, 1) t / Tp (× Tp → real time, hours) q / qp (× qp → discharge) 1 2 3 5 1

Step 1 — Basin Lag and Time to Peak

The time to peak follows the standard relation:

text
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:

text
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:

text
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.

python
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.

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.