Skip to main content

cnkit

CI PyPI Python License: MIT

Curve number hydrology with Earth Observation. cnkit implements the NRCS curve number method as it is actually used when the inputs come from satellite data: the runoff relations and the initial abstraction ratio conversions, the NLCD land cover by hydrologic soil group lookup with its assumptions stated rather than buried, storm event and baseflow separation, the Hawkins asymptotic curve number and its competing estimators, and the antecedent moisture conventions.

The argument the library is built around:

Earth Observation has moved the curve number from an unverifiable lookup to a measurable, spatially and temporally resolved, uncertainty-quantified index. In doing so it has made visible the fact that the remaining error is dominated by the 1954 equation, not by the inputs.

That is why the API returns disagreements instead of hiding them. Where a quantity has more than one defensible definition, cnkit computes all of them and hands you the spread: composite_runoff returns three runoff depths, compare_estimators returns four curve numbers, compare_conventions returns the antecedent classes that the 1972 rainfall rule and the satellite soil moisture percentile assign to the same storm.

Install

pip install cnkit

The core science needs only numpy, pandas and scipy. The cnkit.data fetchers (USGS NWIS, USDA Soil Data Access) need an HTTP client, which is an extra:

pip install "cnkit[data]"

From a clone, for development:

git clone https://github.com/skp703/cnkit.git
cd cnkit
pip install -e ".[dev,data]"

Python 3.9 or newer.

Quick start

>>> from cnkit import runoff, composite_runoff
>>> round(float(runoff(3.0, 75)), 4)      # 3 inches of rain on CN 75
0.9608
>>> # The weighting trap from Module 3.
>>> # 60 percent impervious at CN 98, 40 percent woods at CN 55, 1 inch storm.
>>> qd, qcn, qs = composite_runoff(1.0, [98, 55], [0.6, 0.4])
>>> [round(x, 4) for x in (qd, qcn, qs)]
[0.4745, 0.0949, 0.0277]

Those three numbers are all "the runoff from this watershed". The first computes runoff per subarea and then averages. The second averages the curve number and then computes runoff. The third averages the retention S and then computes runoff. They differ by a factor of five and seventeen.

The reason is that CN 55 has Ia = 1.64 inches, so under the distributed calculation the wooded 40 percent contributes exactly nothing to a 1 inch storm, while the lumped composite CN of 80.8 has Ia = 0.48 inches and quietly assumes the entire watershed is partly absorbing. The gap narrows as the storm grows, to roughly two-fold at 2 inches, which is why nobody notices the problem when they sanity-check their work against a 100-year event. Which of the three goes in the report is a real engineering decision, and almost nobody makes it consciously.

That example is a doctest. It runs in CI on every push.

API overview

Module What it does
cnkit.core S, Q, initial abstraction, back-calculation of CN from observed P and Q, the lambda 0.05 against lambda 0.20 conversions, the ARC 1 and ARC 3 adjustments, the Williams slope adjustment, and both composite CN weighting conventions.
cnkit.lookup NLCD Level II crossed with hydrologic soil group to CN, with the hydrologic condition assumption made explicit per class, plus the condition spread and area-weighted composites.
cnkit.events Storm event separation from a rainfall series, baseflow separation by local minimum and by the Lyne and Hollick filter, and the rainfall runoff event table the asymptotic fit consumes.
cnkit.asymptotic The Hawkins asymptotic CN, the standard, complacent and violent behaviour classification, and the four competing estimators that do not agree with each other.
cnkit.antecedent The 1972 five-day antecedent rainfall AMC class set against the satellite soil moisture percentile, with a day-of-year climatology and a disagreement table.

A sixth module, cnkit.data, holds no-authentication fetchers for USGS NWIS and USDA Soil Data Access plus the workshop watershed registry. It is separated from the science because it is the only part that touches the network, and it is the only part that needs the data extra.

Units and input validation

  • Rainfall, runoff and storage depths are in inches unless a function says otherwise. Drainage area arguments state their units in their names.
  • Curve numbers are dimensionless. S_from_CN deliberately clips values to [1, 100]; composite calculations reject values outside (0, 100] because silently weighting an invalid CN would contaminate the entire result.
  • Initial abstraction ratio lam must be a finite scalar between 0 and 1.
  • Missing numerical inputs propagate as NaN where a numerical result can be returned honestly. Structurally invalid inputs, such as negative rainfall, zero total area, incomplete antecedent windows or an unknown model name, raise ValueError.
  • Functions accept NumPy arrays where vector operation is meaningful. Event and climatology functions require pandas Series with a DatetimeIndex.

What this library will not do

cnkit computes runoff volume. It does not compute peak discharge, time to peak, time of concentration, or hydrographs. The curve number method is a volume method. TR-55 chapter 4, unit hydrograph convolution and every routing step downstream of that are out of scope, and pairing a volume from this library with a peak from somewhere else is your decision to document.

Two range limits are worth stating before anyone runs a design with these numbers:

  • CN below 30. The NRCS tables are not defined below CN 30 and the method is not reliable there. Very low curve numbers imply retention values so large that the initial abstraction alone exceeds most design storms, and the result is a runoff depth of zero that is an artefact of the equation rather than a hydrologic finding.
  • Runoff below 0.5 inches. Below roughly half an inch of computed runoff the method's error is the same size as its answer. Back-calculated curve numbers from small events are the main source of the apparent scatter in event data, which is exactly why the asymptotic fit exists.

Neither limit is enforced by the code. The functions will happily return a number outside them. Checking that you are inside them is the analyst's job.

Testing

The suite has five layers, and they fail for different reasons on purpose.

  1. Unit and validation tests on the public API. tests/test_cnkit.py and tests/test_public_api_validation.py cover textbook S and CN values, round-trip identities, clipping and validation at the bounds, the lambda and ARC conversions, the weighting divergence, event separation on synthetic series, empty inputs and estimator comparison. No network.
  2. Offline data-fetcher contract tests. Mock USGS NWIS and USDA Soil Data Access responses exercise response parsing, no-data behavior, HTTP request parameters and the optional-dependency error without making CI depend on a public service or the network.
  3. Regression tests pinned to the workshop data pack. Every number that appears on a workshop slide is asserted against the data it was computed from, so a slide claim and the code cannot silently diverge. These read tests/data.
  4. Notebook expectation tests. The values the Colab notebooks print for attendees are asserted here, so a change to the package that would alter what forty people see on their screens fails the build first.
  5. Data pack integrity tests. Row counts, column names, date ranges, units and checksums for the 1.8 MB pack in tests/data. If an input file is regenerated or truncated, this layer says so rather than letting layer 2 fail with a confusing numeric mismatch.

Run them:

pip install -e ".[test,data]"

pytest                              # every layer
pytest tests src/cnkit              # every layer plus the package doctests
pytest tests/test_cnkit.py tests/test_public_api_validation.py -q
pytest -m "not slow"                # skip the long fits and anything networked
pytest --cov=cnkit --cov-report=term-missing
flake8 src tests tools

Doctests are collected by default (--doctest-modules), so the quick start above and the examples in the module docstrings are executable specifications, not illustrations.

CI runs the whole matrix on Python 3.9 through 3.14 on Linux, plus macOS and Windows on 3.12, and separately verifies that the single-file notebook build is in sync:

python tools/flatten.py            # regenerate dist/cnkit_flat.py
python tools/flatten.py --check    # fails if the flat file has drifted

tools/flatten.py concatenates the five science modules into one importable file for Colab attendees who have no pip install step. It is generated, never edited by hand, and CI fails on drift.

Citation

If you use cnkit, cite the software and the workshop it was written for. Machine-readable metadata is in CITATION.cff; GitHub renders a formatted citation from it, and most reference managers import it directly.

Ramirez-Avila, J. J., and Kumar, S. (2026). cnkit: curve number hydrology with Earth Observation, version 1.0.0. Software accompanying the workshop "Modern Curve Number Hydrology: Fundamentals and Remote Sensing Applications", 2026 ASCE-EWRI Watershed Management Conference.

Provenance

This is the library behind the workshop "Modern Curve Number Hydrology: Fundamentals and Remote Sensing Applications" at the 2026 ASCE-EWRI Watershed Management Conference, developed for the EWRI Curve Number Hydrology and Remote Sensing Task Committees. It is released for workshop use and for anyone who wants to reproduce the workshop's results. There is no warranty. Verify before you design anything.

Licensed under the MIT License. See LICENSE and CHANGELOG.md.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cnkit-1.0.0.tar.gz (465.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cnkit-1.0.0-py3-none-any.whl (48.6 kB view details)

Uploaded Python 3

File details

Details for the file cnkit-1.0.0.tar.gz.

File metadata

  • Download URL: cnkit-1.0.0.tar.gz
  • Upload date:
  • Size: 465.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for cnkit-1.0.0.tar.gz
Algorithm Hash digest
SHA256 e6459dac5027e16f8d0734ea98ffb1fb34c0994caf187eeab4c1a204075007f9
MD5 cff271d33bab7284e963341b9bf981ad
BLAKE2b-256 a5bb0cd8fc3668a564f9b62fb837f9ee1087d4142a5e2d8d4efe2ca864ed61de

See more details on using hashes here.

Provenance

The following attestation bundles were made for cnkit-1.0.0.tar.gz:

Publisher: publish.yml on skp703/cnkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cnkit-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: cnkit-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 48.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for cnkit-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d795224ad67c45b694dcdc61941d9cdc7c7c715e854df4ec500aa276e893ef9d
MD5 a1400359608f023c6844cfe73096c600
BLAKE2b-256 362f4b3fa800a8409bf03bbb8675b8efb72cc214bc6a15e9820ebcaf04a8208e

See more details on using hashes here.

Provenance

The following attestation bundles were made for cnkit-1.0.0-py3-none-any.whl:

Publisher: publish.yml on skp703/cnkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.1.0

2 files

This release

1.0.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page