The purpose of this web page is to present a Python program (PYSTEP) that replaces the Fortran program used to process step test data in the web page posted in the year 2013 on this website. Whereas the Fortran program produced a text file that had to be imported to a spreadsheet for processing and graphing, the Python program processes the data more efficiently. It produces a graph of the step test that fits lines to the scatter plots for each step. The lines are fitted by the least squares method. The slope and intercept of the lines are printed in a text file. These slopes can be used to calculate transmissivity of the aquifer as described in the aforementioned web page. The vertical separation of the lines yield well loss coefficients. Deviations from straight lines or parallel lines may be interpreted as the effect of aquifer inhomogeneity or test error. The Python program (PYSTEP) processes the step test data more efficiently than the old Fortran method, and the Python program can be modified relatively easily to satisfy user preferences an requirements.
Figure 1 shows the graph produced by PYSTEP for step test data published by Green and others (1991) described on the web page titled Python Unconfined Aquifer Test Analysis. The input file for this step test is Attachment 2.
Figure 1. Step test plot.
#Author: Darrel Dunn
#Copyright (c) 2026 Darrel Eugene Dunn. All rights reserved.
'''
Although this program has been used by the author, no warranty, expressed or implied, is made by the author as to the accuracy and functioning of the program and related program material, nor shall the fact of distribution constitute any such warranty, and no responsibility is assumed by the author in connection therewith. The content is provided"AS IS."
'''
import pandas as pd
import matplotlib.pyplot as plt
import math
import numpy as np
import sys
#Print to txt file
f = open('PYSTEP.txt', 'w')
sys.stdout = f
# Read input data and put into a list of strings, "with" insures file closed.
input_file = "wtaq_step_test_in.txt"
with open(input_file, "r") as f:
lines = [line.strip() for line in f if line.strip()]
# Number of pumping steps
ns = int(lines[0]) #Converts the first string (# of steps) to an integer.
# Step info: Q, T_begin, T_end
steps_info = []
for i in range(1, ns + 1):
parts = list(map(float, lines[i].split()))
steps_info.append({'qn': parts[0], 'tbegin': parts[1], 'tend': parts[2]})
# Observation data: List of [Time, Drawdown]
obs_raw = []
for i in range(ns + 1, len(lines)):
parts = list(map(float, lines[i].split()))
if not parts or parts[0] == 999: break
obs_raw.append(parts)
# Helper dictionaries for logic (1-based indexing)
qn = {n+1: steps_info[n]['qn'] for n in range(ns)}
tbegin = {n+1: steps_info[n]['tbegin'] for n in range(ns)}
tend = {n+1: steps_info[n]['tend'] for n in range(ns)}
# Calculation Logic ---
results = []
for time_val, dd_val in obs_raw: #Assign vaules in obs_raw to dd_val and obs_val
rec = 0
at_val = 0.0
ad_val = 0.0
nstep = 0
# Final recovery algorithm (This algorithm not tested.)
if time_val > tend[ns]:
rec = 1
lterm = (time_val - tbegin[ns]) / (time_val - tend[ns])
nstep = ns
at_val = 1.0
if nstep != 1:
for k in range(1, ns + 1):
at_val *= (((time_val - tbegin[k]) / (time_val - tend[k]))
** (qn[k] / qn[ns]))
at_val *= lterm
else:
# Pumping periods or internal recovery
for n in range(1, ns + 1): #n from 1 to ns (4).
if tbegin[n] < time_val <= tend[n]:
nstep = n
lterm = time_val - tbegin[n]
at_val = 1.0 if n > 1 else lterm
if n > 1:
for k in range(1, n):
at_val *= (((time_val - tbegin[k])/(time_val - tend[k]))
** (qn[k] / qn[n]))
at_val *= lterm
break
# Internal recovery (Not tested.)
if n > 1 and tend[n-1] < time_val < tbegin[n]:
nstep = n - 1
rec = 1
lterm = (time_val - tbegin[n-1]) / (time_val - tend[n-1])
at_val = 1.0
if nstep != 1:
for k in range(1, n):
at_val *= (((time_val - tbegin[k])/(time_val - tend[k]))
** (qn[k] / qn[n]))
at_val *= lterm
break
if nstep > 0:
ad_val = dd_val / qn[nstep]
results.append({
'Step': 999 if rec > 0 else int(nstep),
'Time': time_val,
'Drawdown': dd_val,
'Adjusted_Time': at_val,
'Adjusted_Drawdown': ad_val
})
# Create DataFrame and CSV ---
df = pd.DataFrame(results)
df.to_csv("wtaq_step_data_out.csv", index=False)
print("DataFrame Preview:")
print(df.head())
print('pumping rate',qn)
# Generate Graph ---
plt.figure(figsize=(10, 6))
# Plot sets 1, 2, 3, 4
for s in range(1, ns + 1):
subset = df[df['Step'] == s]
if not subset.empty:
# 1. Plot the original scatter points
plt.scatter(subset['Adjusted_Time'], subset['Adjusted_Drawdown'],
label=f'Step {s}')
# Calculate Least Squares Line
x_data = subset['Adjusted_Time']
y_data = subset['Adjusted_Drawdown']
m, b = np.polyfit(np.log10(x_data), y_data, 1)
print('step, slope, intercept = ',s, m, b)
# Define the extended range for the line
# Start at 1 (10^0) and end at the maximum time in the subset
x_ext = np.linspace(1, x_data.max(), 100)
# Calculate y values for the extended range
# Formula: y = m * log10(x) + b
y_ext = m * np.log10(x_ext) + b
# Plot the extended line
plt.plot(x_ext, y_ext, linestyle='--', linewidth=1.5)
# Formatting
plt.xscale('log')
plt.xlim(left=1) # Ensure the x-axis starts at 1 to see the extension
plt.xlabel('Adjusted Time')
plt.ylabel('Adjusted Drawdown')
plt.minorticks_on()
plt.title('Step Test Plot')
plt.grid(True, which="both", linestyle="--", alpha=0.5)
plt.legend()
plt.tight_layout()
plt.savefig('step_test_plot_extended_x.png')
print("\nGraph saved as 'step_test_plot.png'.")
sys.stdout = sys.__stdout__ # Restoring output back to the terminal
print('Close the show window to complete execution')
plt.show()
print('End PYSTEP')
4
43 0 89
58 90 174
78 175 354
87 355 445
5 3.7
10 4.8
15 5.3
20 5.6
25 5.7
30 5.9
35 6.1
40 6.2
45 6.4
50 6.4
55 6.5
60 6.6
65 6.7
70 6.7
75 6.7
80 6.8
85 6.8
90 8.8
95 9.4
100 9.7
105 9.8
110 10.1
115 10.2
120 10.4
125 10.5
130 10.6
135 10.7
140 10.9
145 11
150 11
155 11
160 11.1
165 11.3
170 11.3
175 13.7
185 15.5
190 15.9
195 16.2
200 16.5
205 16.6
210 16.8
215 16.9
220 17
225 17.1
230 17.2
235 17.3
240 17.4
245 17.5
250 17.6
255 17.7
260 17.7
265 17.7
270 17.8
275 17.9
280 18
286 18
291 18
295 18
300 18
305 18.1
310 18.2
315 18.2
320 18.3
325 18.4
330 18.4
335 18.4
340 18.4
346 18.4
350 18.4
355 20.3
360 20.7
365 21
370 21.2
375 21.5
380 21.6
385 21.6
390 21.7
395 21.9
400 22
405 22.1
410 22.2
415 22.2
420 22.3
425 22.3
430 22.4
435 22.5
440 22.5
445 22.6
999 999
Birsoy, Yuksel K. and W. K. Summers (1980): Determination of Aquifer Parameters from Step Tests and Intermittent Pumping Data; Ground Water, Volume 18, Number 2, pages 137-145. 🔗
Green, E. A. and others (1991): Aquifer Tests and Water-Quality Analyses of the Arikaree Formation Near Pine Ridge, South Dakota; U.S. Geological Survey Water-Resources Investigations Report 91-4005. 🔗