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.
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.
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.
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.
CooperJacobResult
dataclass
¶
u_max
instance-attribute
¶
The largest u among the points used; above 0.05 the result is suspect.
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.
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.
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.