Skip to content

API reference

Describing a test

pumptest.data

What a pumping test is: a well, a pumping schedule and the observations.

Units are never converted inside the package. Give lengths, times and rates in one consistent set (metres, days and cubic metres per day is the usual one) and the fitted parameters come back in it: transmissivity in square metres per day. :mod:pumptest.units converts field units before they get here.

Schedule dataclass

Pumping rate as a step function: (start_time, rate) pairs.

Recovery is a step to zero. A step-drawdown test is a list of rising steps. Rates are positive for pumping (abstraction) and the first step starts at the time origin of the observations.

constant(rate, *, stop=None) classmethod

A constant-rate test, optionally followed by recovery from stop.

rate_at(t)

The rate in force at time t (zero before the first step).

superpose(unit_response, t)

Drawdown at t from the drawdown per unit rate of a step at time zero.

Every solution here is linear in the rate, so a schedule is the sum of its rate changes, each delayed to its own start.

Well dataclass

The pumping well. casing_radius sets wellbore storage (defaults to radius).

Observation dataclass

Drawdown measured at one place: an observation well or the pumping well.

distance zero means the pumping well itself. screen is the observation interval as heights above the aquifer base, used only by the unconfined solution; None is a fully penetrating well.

PumpingTest dataclass

A whole test. thickness is required by the unconfined solution only.

radius_of(observation)

The radial distance a solution should use for observation.

Models

pumptest.models

The aquifer models a test is fitted to.

Each model knows its parameters, their bounds, and the drawdown per unit rate at one observation point. :meth:Model.drawdown superposes that over the pumping schedule, so recovery and step tests need nothing model-specific.

MODELS = {'theis': Theis, 'hantush-jacob': HantushJacob, 'papadopulos-cooper': PapadopulosCooper, 'neuman': Neuman} module-attribute

Model factories by name, for callers that pick a solution from a string.

Parameter dataclass

A fitted quantity. log parameters are estimated on a log10 scale.

Model

Bases: ABC

An analytical solution for drawdown around a pumped well.

unit_response(values, test, observation) abstractmethod

Drawdown per unit rate, for a rate switched on at time zero.

check(test)

Raise ValueError if test lacks something this model needs.

Most models need nothing beyond the test itself, so the default accepts.

derived(values, test)

Quantities that follow from the fitted ones, such as K = T / b.

drawdown(values, test, observation, t=None)

Drawdown at observation over the whole pumping schedule.

Theis

Bases: Model

Confined aquifer, line-sink well: Theis (1935).

HantushJacob

Bases: Model

Leaky confined aquifer, aquitard without storage: Hantush and Jacob (1955).

Leakage is fitted as the leakage factor B; the aquitard resistance c = B**2 / T and the leakance K'/b' = 1 / c are reported as derived values.

PapadopulosCooper

Bases: Model

Large-diameter well with wellbore storage: Papadopulos and Cooper (1967).

The usual solution for dug wells. The screen and casing radii come from the :class:~pumptest.data.Well. With skin=True a dimensionless skin factor (Agarwal et al., 1970) is fitted as well. With casing_radius=True the effective casing radius rc is fitted instead of taken from the well: a pump column or riser in the casing shrinks the area the water level falls in, which shows as early drawdown faster than the nominal casing allows.

Neuman

Bases: Model

Unconfined aquifer with delayed water-table response: Neuman (1974).

Fits T, the elastic storativity S, specific yield Sy and the anisotropy ratio Kd = Kz / Kr. Needs the saturated thickness on the test.

Fitting

pumptest.fit

Automatic estimation: nonlinear least squares over every observation at once.

Positive parameters are estimated as log10 values, which is what makes one optimiser work for transmissivities of 1e-3 and storativities of 1e-6 in the same problem. Several starting points are tried and the lowest residual wins, because the leaky and unconfined families have shallow valleys where a single start stops early.

DIFF_STEP = 0.0001 module-attribute

Relative finite-difference step for the Jacobian. Stehfest inversion carries round-off near 1e-7 relative; scipy's default step (about 1.5e-8) sits under that noise and the optimiser stops on its first iteration.

MAX_STARTS = 3 module-attribute

Optimisations per fit. With more candidate starts than this, the ones with the lowest initial residual are kept: on Texas Hill the unconfined fit fell from 35 s to 16 s with the same answer.

FitResult dataclass

The estimate and how far to trust it.

standard_errors instance-attribute

Linearised one-sigma errors in the units of each parameter.

rmse instance-attribute

Root mean square of (observed - computed), unweighted, over all points.

correlation instance-attribute

Correlation of the estimated parameters, in the order of estimated.

aic property

Akaike information criterion for least squares, n ln(SSR / n) + 2k.

Lower is better. It only compares fits to the same observations: a model with more parameters has to earn each one by a lower residual.

aicc property

AIC with the small-sample correction, the one to use below about 40 points.

summary()

A plain-text table of the result, for a terminal or a report.

starting_points(test, model)

Initial guesses: Cooper-Jacob for T and S, a spread for the rest.

The delayed-yield model starts from Theis fits to the early and late parts of the record instead (see :func:_theis_segment).

fit(test, model, *, initial=None, fixed=None, observations=None)

Estimate model's parameters from test.

fixed holds parameters at given values. initial replaces the automatic starting points with one guess or a list of them. observations restricts the fit to the named observations.

compare(test, models, *, observations=None)

Fit each model to the same observations and rank them by AICc, best first.

A model that cannot be fitted to test (the unconfined solution without a thickness, say) is left out rather than failing the comparison.

Straight-line methods

pumptest.straightline

Straight-line methods: the hand analyses every hydrogeologist checks first.

  • Cooper and Jacob (1946) time-drawdown, valid once u is small.
  • Theis (1935) recovery, on residual drawdown against t / t'.
  • Step-drawdown well losses, s / Q = B + C Q (Jacob 1947; Hantush-Bierschenk).

They also seed the automatic fit, which starts from the Cooper-Jacob line.

JACOB_U_LIMIT = 0.05 module-attribute

Below this u the Cooper-Jacob line is within about 2 percent of Theis.

LineFit dataclass

A straight line through drawdown against log10 of time.

slope instance-attribute

Drawdown per log cycle.

intercept instance-attribute

Drawdown where log10(t) is zero.

used instance-attribute

How many points the line went through.

zero_drawdown_time property

t0, where the line crosses zero drawdown.

CooperJacobResult dataclass

u_max instance-attribute

The largest u among the points used; above 0.05 the result is suspect.

RecoveryResult dataclass

line instance-attribute

Residual drawdown against log10(t / t').

StepTestResult dataclass

Well losses from a step-drawdown test, s = B Q + C Q**2.

aquifer_loss_coefficient instance-attribute

B: drawdown per unit rate from the formation (laminar).

well_loss_coefficient instance-attribute

C: the nonlinear well loss (Jacob's C).

efficiencies instance-attribute

B Q / (B Q + C Q**2) at each step: the share of drawdown the aquifer causes.

drawdown(rate)

Predicted drawdown at rate, for choosing a production rate.

drawdown_curve(rates)

Predicted drawdown at each of rates.

cooper_jacob(time, drawdown, rate, distance, *, start=None)

Cooper-Jacob analysis of a constant-rate test at one well.

With start the line uses times from start on. Without it the line starts on the later half of the record and walks the start later until every point used has u <= 0.05, or until three points are left.

theis_recovery(time_since_start, residual_drawdown, rate, pumping_duration)

Theis recovery: T from residual drawdown per log cycle of t / t'.

time_since_start is t, measured from the start of pumping; t' is the time since pumping stopped. Only recovery points (t > duration) are used.

step_drawdown(rates, drawdowns)

Fit s = B Q + C Q**2 to the drawdown reached in each step.

Give the drawdown each step reached after the same elapsed time in the step, or the cumulative drawdown at the end of equal-length steps (the Hantush-Bierschenk increments are summed the same way).

The fit minimises the error in drawdown, which is what was measured. The textbook plot of s / Q against Q weights the low-rate steps up by 1 / Q; on Clark's "Well 1" that line misses the drawdowns by 0.119 m where this one misses by 0.096 m.

Diagnostics

pumptest.diagnostics

Diagnostic plots' data: the derivative that tells one aquifer from another.

On a log-log plot the drawdown derivative ds/d(ln t) is flat for radial flow in a confined aquifer, falls to zero for leakage or a recharge boundary, dips for delayed yield and rises for a barrier. Reading it before fitting is what stops a leaky test being forced through Theis.

log_derivative(time, drawdown, *, window=0.2)

Bourdet et al. (1989) derivative ds/d(ln t), smoothed over window.

For each point the neighbours used are the first ones at least window (in natural-log units of time) to each side; field data needs 0.1 to 0.5. Returns the times that have neighbours on both sides and the derivative.

Well functions

pumptest.wellfunctions

Dimensionless well functions: the type curves of the classic solutions.

These are the functions tabulated in the original papers, exposed so a user can draw type curves or check a value against a table. The fitted models use them where a closed form exists and the Laplace kernels in :mod:pumptest.radial and :mod:pumptest.neuman where it does not.

theis_w(u)

Theis well function W(u), the exponential integral E1(u).

hantush_w(u, r_over_b)

Hantush and Jacob (1955) leaky well function W(u, r/B).

Evaluated by quadrature, so it is the reference the Laplace kernel is tested against rather than the path the fitting takes.

Units

pumptest.units

Field units to one consistent set, and water levels to drawdown.

The solvers never convert units (see :mod:pumptest.data). Field sheets do not arrive consistent: pumping in litres per minute, time in minutes, levels in metres below ground. Convert here first, then build the test.

RATE = {'m3/d': VOLUME['m3'] / TIME['d'], 'm3/h': VOLUME['m3'] / TIME['h'], 'm3/s': 1.0, 'l/s': VOLUME['l'], 'lps': VOLUME['l'], 'l/min': VOLUME['l'] / TIME['min'], 'lpm': VOLUME['l'] / TIME['min'], 'l/h': VOLUME['l'] / TIME['h'], 'lph': VOLUME['l'] / TIME['h'], 'gpm': VOLUME['gal'] / TIME['min'], 'igpm': VOLUME['igal'] / TIME['min'], 'ft3/s': VOLUME['ft3'], 'cfs': VOLUME['ft3'], 'ft3/d': VOLUME['ft3'] / TIME['d']} module-attribute

Each rate in cubic metres per second.

convert(value, source, target)

Convert value between two units of the same kind.

float(convert(100, "lpm", "m3/d")) 144.0

drawdown_from_levels(levels, static_level, *, below_datum=True)

Drawdown from water levels.

With below_datum (the usual depth to water, measured down from ground or a measuring point) drawdown is level minus static level. With levels as elevations above a datum it is static level minus level.

Example datasets

pumptest.datasets

Published pumping tests, bundled so examples and checks run anywhere.

>>> from pumptest import datasets, fit, Theis
>>> test = datasets.load("sioux_flats")
>>> round(datasets.published("sioux_flats").values["T"], 1)
4307.7

Every test is in metres, days and cubic metres per day. Provenance and licences are in data/README.md next to this module.

Published dataclass

A published analysis of a bundled test, to compare a fit against.

available()

Names accepted by :func:load.

load(name)

A bundled test, in metres, days and cubic metres per day.

published(name)

The published analysis of a bundled test.

Laplace kernels and inversion

pumptest.radial

Radial flow to a well in a confined or leaky aquifer, in the Laplace domain.

One kernel covers the family AQTESOLV lists as separate solutions, because each is a special case of the same boundary-value problem:

  • Theis (1935): line sink, no leakage.
  • Hantush and Jacob (1955): leakage through an aquitard without storage.
  • Papadopulos and Cooper (1967): finite well radius with wellbore storage.
  • Agarwal et al. (1970): an infinitesimal skin at the well face.

The kernel returns drawdown per unit pumping rate. Consistent units are the caller's responsibility: metres and days in, square metres per day out.

radial_drawdown_laplace(p, r, transmissivity, storativity, *, leakage_factor=math.inf, well_radius=0.0, casing_radius=0.0, skin=0.0, at_pumping_well=False)

Laplace transform of drawdown per unit rate at distance r.

With well_radius zero the well is a line sink and wellbore storage and skin have no meaning; they are ignored. leakage_factor is B, with B squared equal to T times the aquitard resistance.

pumptest.neuman

Neuman (1974) delayed water-table response, in the Laplace domain.

A fully penetrating well pumps an anisotropic unconfined aquifer of saturated thickness b. The water table is a linearised free surface that releases specific yield instantly (Neuman's assumption; Moench's gradual drainage is a later extension). Separating variables in the vertical gives eigenvalues eps_n, the roots of eps tan(eps) = p Sy b / Kz, and

s(r, z, p) = 1 / (2 pi T p) * sum_n a_n K0(q_n r) phi_n(z),

with a_n = 2 sin(eps_n) / (eps_n + sin(eps_n) cos(eps_n)), (q_n r)**2 = r**2 S p / T + beta eps_n**2 and beta = Kd r**2 / b**2. phi_n is cos(eps_n z / b) averaged over the observation screen.

Two limits pin the derivation and are tested: with Sy -> 0 the series collapses to Theis with S, and at late time to Theis with S + Sy.

MAX_TERMS = 400 module-attribute

A pumping well (r tiny against b) needs the most terms. The coefficients fall as c2 / eps4 once eps passes c, so 400 terms already agree with 2000 to six figures at r = 0.1 m in a 20 m aquifer; the tests pin that.

eigenvalues(c, n_terms)

Roots of eps tan(eps) = c for each c, the first n_terms each.

Root n is n pi + d with d in (0, pi/2), where f(d) = (n pi + d) sin(d) - c cos(d) vanishes. Solving for the offset d rather than the root keeps full precision at large n, and f rises monotonically from -c to n pi + pi/2 across the interval, so Newton from a close guess converges in a few steps. The first version iterated on the root itself from a rough guess and fell back to bisection; it spent 95 percent of a Neuman fit here and left residuals of 1e-4.

neuman_drawdown_laplace(p, r, transmissivity, storativity, specific_yield, anisotropy, thickness, *, screen=None)

Laplace transform of drawdown per unit rate in an unconfined aquifer.

anisotropy is Kd = Kz / Kr. screen is the observation interval as heights above the aquifer base, in the units of thickness; None means a fully penetrating observation well and a point is (z, z).

pumptest.laplace

Numerical inversion of Laplace transforms by the Stehfest (1970) algorithm.

Most well-hydraulics solutions beyond Theis have no closed form in time but a simple one in the Laplace domain. Stehfest's method samples the transform at n real points per time and needs no complex arithmetic, which is why WTAQ and AQTESOLV use it too. It is accurate for the smooth, monotone responses of pumping tests; it is not suited to oscillating functions.

DEFAULT_TERMS = 16 module-attribute

Sixteen terms holds Theis to 4e-5 wherever u <= 2, which covers every drawdown large enough to measure. Eighteen starts to add round-off at small u; twelve leaves 0.1 percent error near u = 2.

stehfest_weights(n) cached

The n Stehfest coefficients V_i (n must be even).

invert(transform, t, n=DEFAULT_TERMS)

Invert transform at the positive times t.

transform receives a 2-D array of Laplace variables, one row per time, and must return an array of the same shape. Evaluating every row in one call is what keeps fitting fast: the Bessel functions run vectorised.