ยฉ 2023 Daniel F. Ruiz, Exneyder A. Montoya-Araque y Universidad EAFIT.
This notebook can be interactively run in Google - Colab.
This notebook was developed following the procedure by Gonzalez (1999) for estimating the shear strength parameters from the Standard Penetration Test data (paper in Spanish).
Required modules and global setup for plotsยถ
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
from scipy.optimize import curve_fit, minimize
from ipywidgets import widgets as wgt
from IPython import get_ipython
from IPython.display import display
if 'google.colab' in str(get_ipython()):
print('Running on CoLab. Installing the required modules...')
from subprocess import run
# run('pip install ipympl', shell=True);
from google.colab import output, files
output.enable_custom_widget_manager()
else:
import tkinter as tk
from tkinter.filedialog import askopenfilename
# Figures setup
# %matplotlib widget
mpl.rcParams.update({
"font.family": "serif",
"font.serif": ["Computer Modern Roman", "cmr", "cmr10", "DejaVu Serif"], # or
"mathtext.fontset": "cm", # Use Computer Modern fonts for math
"axes.formatter.use_mathtext": True, # Use mathtext for axis labels
"axes.unicode_minus": False, # Use standard minus sign instead of a unicode character
})
Funcionesยถ
def x_from_y(x_coord, y_coord, target_y):
for i in range(len(y_coord)):
if y_coord[i] == target_y: # if target_y coincides with a node
if i < len(y_coord) and y_coord[i+1] == y_coord[i]: # if on a horizontal segment:
return 0.5 * (x_coord[i] + x_coord[i+1]) # return midpoint in x
else:
return x_coord[i]
elif i > 0 and y_coord[i-1] < target_y < y_coord[i]:
# Interpolate x-value when the target y-value lies between two points
x1, x2 = x_coord[i-1], x_coord[i]
y1, y2 = y_coord[i-1], y_coord[i]
return x1 + (x2 - x1) * (target_y - y1) / (y2 - y1)
return None # Return None if y-value not found within the range
def compute_stresses(df, mat_depths, ๐พ_moist, ๐พ_sat, wt_depth=None):
๐พ_w = 9.81 # unit weight of water [kN/mยณ]
# Copying arrays
mat_depths = mat_depths.copy()
๐พ_moist = ๐พ_moist.copy()
๐พ_sat = ๐พ_sat.copy()
# Find an appropiate index for locating the watertable
if wt_depth in mat_depths:
idx_wt = mat_depths.index(wt_depth)
elif wt_depth >= mat_depths[-1] or wt_depth is None:
wt_depth = mat_depths[-1]
idx_wt = len(mat_depths) - 1
else:
for i in range(len(mat_depths)):
if wt_depth <= mat_depths[i]:
idx_wt = i
break
# Insert the new value at the appropriate index
mat_depths.insert(idx_wt, wt_depth)
๐พ_moist.insert(idx_wt, ๐พ_moist[idx_wt])
๐พ_sat.insert(idx_wt, ๐พ_sat[idx_wt])
# Create an unified unit weight vector
๐พ_s = ๐พ_moist[: idx_wt + 1] + ๐พ_sat[idx_wt + 1 :]
# Create vector of thicknesses
mat_depths.insert(0, 0) # insert zero for the first layer
thickness = np.diff(mat_depths) # thickness of each soil layer [m]
# Create a vector for unit weigth of water (zero above wt, and ๐พ_w below)
๐พ_w = np.full_like(๐พ_s, ๐พ_w)
๐พ_w[: idx_wt + 1] = 0
# Compute vertical stresses and water pressure at boundaries
๐_v = np.insert(np.cumsum(thickness * ๐พ_s), 0, 0)
p_w = np.insert(np.cumsum(thickness * ๐พ_w), 0, 0)
๐_v_eff = ๐_v - p_w
df['Sigma_eff_(kPa)'] = [
x_from_y(x_coord=๐_v_eff, y_coord=mat_depths, target_y=z)
for z in df['Profundidad_(m)'].to_numpy()
]
return
def compute_corrected_N(df, perfo_diam, field_test_energy):
# Stress level correction
n, ๐_ref = 0.5, 95.76 # reference stress level = 1 tsf(short) to kPa
# df['Cn'] = (๐_ref / df['Sigma_eff_(kPa)']) ** n # Liao y Whitman (1986)
df['Cn'] = 2 / (1 + df['Sigma_eff_(kPa)']/๐_ref) # Skempton (1986)
# Correction factor to reach an standard energy level of 60%
df['n1_E60'] = field_test_energy / 60
# Correction factor due to the length of the drill rod
n2 = np.empty_like(df['Profundidad_(m)'], dtype=float)
depth = df['Profundidad_(m)'].to_numpy()
n2[depth >= 10] = 1
n2[depth < 10] = 0.95
n2[depth < 6] = 0.85
n2[depth < 4] = 0.75
df['n2'] = n2
# Correction factor due to the cassing
mat_type = np.array([int(d[0]) for d in df['Descripciรณn'].to_list()])
n3 = np.ones_like(df['Profundidad_(m)'], dtype=float)
n3[mat_type == 1] = 0.8
n3[mat_type == 2] = 0.8
n3[mat_type == 3] = 0.9
n3[mat_type == 4] = 0.8
n3[mat_type == 5] = 0.85
n3[mat_type == 6] = 0.9
n3[df['Revestimiento_(m)'] < df['Profundidad_(m)']] = 1
df['n3'] = n3
# Correction factor due to the hole diameter
if perfo_diam <= 120:
df['n4'] = 1
elif perfo_diam <= 150:
df['n4'] = 1.05
elif perfo_diam > 150:
df['n4'] = 1.15
# N corrected
df['N45'] = np.int64(df['N_campo'] * df['Cn'] * df['n2'] * df['n3'] * df['n4'] * field_test_energy / 45)
df['N55'] = np.int64(df['N_campo'] * df['Cn'] * df['n2'] * df['n3'] * df['n4'] * field_test_energy / 55)
df['N60'] = np.int64(df['N_campo'] * df['Cn'] * df['n2'] * df['n3'] * df['n4'] * field_test_energy / 60)
def compute_correlations(df, corr_๐='f'):
'''
Correlations for the friction angle based on the letters accompanying the
formulas (7) and (8) in Gonzalez (1999)
'''
# Equivalent friction angle
if corr_๐ == 'a': # Peck
df['๐_eq'] = 28.5 + 0.25 * df['N45']
elif corr_๐ == 'b': # Peck, Hanson & Thornburn
df['๐_eq'] = 26.25 * (2 - np.exp(-1 * df['N45']/62))
elif corr_๐ == 'c': # Kishida
df['๐_eq'] = 15 + (12.5 * df['N45']) ** 0.5
elif corr_๐ == 'd': # Schmertmann
df['๐_eq'] = np.arctan((df['N45'] / 43.3)**0.34)
elif corr_๐ == 'e': # Japan National Railway (JNR)
df['๐_eq'] = 27 + 0.1875 * df['N45']
elif corr_๐ == 'f': # Japan Road Bureau (JRB)
df['๐_eq'] = 15 + (9.375 * df['N45']) ** 0.5
# Equivalent shear strength
df['๐_eq'] = df['Sigma_eff_(kPa)'] * np.tan(np.deg2rad(df['๐_eq']))
# Undrained shear strength based on Schmertmann (1975)
df['Su_(kPa)'] = df['N60'] / 15 * 95.76
# Put nan to 'Su_(kPa)' where fines are < 50% or isnan
df.loc[df['Finos_(%)'] < 50, 'Su_(kPa)'] = np.nan
df.loc[df['Finos_(%)'].isna(), 'Su_(kPa)'] = np.nan
# Elasticity modulus
mat_type = np.array([int(d[0]) for d in df['Descripciรณn'].to_list()])
elastic_mod = np.ones_like(df['Profundidad_(m)'])
elastic_mod[mat_type == 1] = (250 * (df['N55'] + 15))[mat_type == 1]
elastic_mod[mat_type == 2] = (500 * (df['N55'] + 15))[mat_type == 2]
elastic_mod[mat_type == 3] = (40000 + (df['N55'] * 1050))[mat_type == 3]
mask_4a = np.logical_and(mat_type == 4, df['N55'] <= 15)
elastic_mod[mask_4a] = (600 * (df['N55'] + 6))[mask_4a]
mask_4b = np.logical_and(mat_type == 4, df['N55'] > 15)
elastic_mod[mask_4b] = (2000 + (600 * (df['N55'] + 6)))[mask_4b]
elastic_mod[mat_type == 5] = (320 * (df['N55'] + 15))[mat_type == 5]
elastic_mod[mat_type == 6] = (300 * (df['N55'] + 6))[mat_type == 6]
df['Es_(kPa)'] = elastic_mod
def complete_table(df, mat_depths, ๐พ_moist, ๐พ_sat, wt_depth=None, perfo_diam=75,
field_test_energy=60, corr_๐='f'):
compute_stresses(df, mat_depths, ๐พ_moist, ๐พ_sat, wt_depth)
compute_corrected_N(df, perfo_diam, field_test_energy)
compute_correlations(df, corr_๐='f')
def f_mc(x, m, b): # Linear function for Mohr-Coulomb envelope with non-zero intercept
return m * x + b
def f_mc_b0(x, m): # Linear function for Mohr-Coulomb envelope with zero intercept forced if negative
return m * x
palette = mpl.colors.ListedColormap(['#4477AA', '#EE6677', '#228833', '#CCBB44', '#66CCEE', '#AA3377', '#BBBBBB'])
palette = mpl.colors.ListedColormap(['#004488', '#DDAA33', '#BB5566', '#6699CC', '#EECC66', '#EE99AA'])
def plot_processing(df, mat_depths, wt_depth, plot_su=True, figsize=None):
if figsize is None:
figsize = [9, 6]
fig, axs = plt.subplot_mosaic([['A', 'B', 'B'], ['A', '.', '.']],
layout='constrained', figsize=figsize)
# Set Dark2 as the default color cycle
# plt.style.use('seaborn-darkgrid')
plt.rcParams.update({'axes.prop_cycle': plt.cycler(color=palette.colors)})
# Create a twin axis for Su in axs['A'] if plot_su is True
if plot_su:
ax_twin = axs['B'].twinx()
# Plotting the fine fraction content
if 'Finos_(%)' in df.columns:
if not df['Finos_(%)'].isna().all(): # check if it's not full of nan
axs['A'].plot(df['Finos_(%)'], df['Profundidad_(m)'], ls="",
marker="|", mfc="w", mec="k", ms=10, mew=2.5, label="Fine fraction [%]")
for i, mat in enumerate(df['Material'].unique()):
df_mat = df[df['Material'] == mat]
# Plotting N values vs depth
axs['A'].plot(df_mat['N_campo'], df_mat['Profundidad_(m)'], ls="", marker="o", ms=6,
mfc=mpl.colors.to_rgba(f"C{i}", 0.8), mec=mpl.colors.to_rgba('k', 1), mew=.75)#, label=mat)
# Plotting shear strength vs sigma_v
axs['B'].plot(df_mat['Sigma_eff_(kPa)'], df_mat['๐_eq'], ls="", marker="o", ms=6,
mfc=mpl.colors.to_rgba(f"C{i}", 0.8), mec=mpl.colors.to_rgba('k', 1), mew=.75, label=mat)
if len(df_mat) == 1:
phi = df_mat['๐_eq'].values[0]
m, b = np.tan(np.deg2rad(phi)), 0.0
axs['B'].plot(df_mat['Sigma_eff_(kPa)'], m * df_mat['Sigma_eff_(kPa)'] + b,
ls=':', c=f"C{i}", label=f"$\\phi'={phi:.1f}^\\circ$")
else:
m, b = curve_fit(f_mc, df_mat['Sigma_eff_(kPa)'], df_mat['๐_eq'])[0]
if b < 0:
m, b = curve_fit(f_mc_b0, df_mat['Sigma_eff_(kPa)'], df_mat['๐_eq'])[0][0], 0.0
# phi = np.rad2deg(m)
phi = np.rad2deg(np.arctan(m)) # Correction by O. Perafรกn (2025/04/19)
axs['B'].plot(df_mat['Sigma_eff_(kPa)'], m * df_mat['Sigma_eff_(kPa)'] + b,
ls=':', c=f"C{i}", label=f"$\\phi'={phi:.1f}^\\circ$, $c'={b:.1f}$ kPa")
# Plotting Su values vs depth in a twin axis if plot_su is True
if plot_su and not np.isnan(df_mat['Su_(kPa)'].mean()):
label = f"Average $S_\\mathrm{{u}} = {df_mat['Su_(kPa)'].mean():.1f}$ kPa"
ax_twin.plot(df_mat['Sigma_eff_(kPa)'], df_mat['Su_(kPa)'], ls="", marker="_", ms=7,
mec=mpl.colors.to_rgba(f"C{i}", 1), mew=2.5)#, label=label)
# Fake plot in the original axis to show the legend
axs['B'].plot([], [], ls="", marker="_", ms=7, mec=mpl.colors.to_rgba(f"C{i}", 1),
mew=2.5, label=label)
for d in mat_depths:
axs['A'].axhline(d, color="k", ls="--", lw=1.25)
axs['A'].axhline(0, color="g", ls="-", lw=3.0, label="Terrain surface")
axs['A'].axhline(np.nan, color="k", ls="--", lw=1.25, label="Material boundary")
axs['A'].axhline(y=wt_depth, ls="-", color="XKCD:electric blue", lw=1.25, label="Watertable")
# Plot setup
axs['A'].invert_yaxis()
# invert y-axis for depth for the twin axis
if plot_su:
ax_twin.set_ylabel("$S_\\mathrm{u}$ [kPa]")
ax_twin.spines["right"].set_linewidth(1.5)
axs['A'].set(xlabel="N$_\\mathrm{SPT}$ & Fine fraction [%]", ylabel="Depth [m]", xlim=[-5, 105])
axs['B'].set(xlabel="$\\sigma'$ [kPa]", ylabel="$\\tau'$ [kPa]")
for ax in axs.values():
ax.xaxis.set_label_position("top")
ax.xaxis.tick_top()
ax.spines["top"].set_linewidth(1.5)
ax.spines["left"].set_linewidth(1.5)
ax.grid(True, ls="--", color="silver")
# axs['A'].legend(loc="upper right")
fig.legend(loc='upper center', bbox_to_anchor=(0.7, 0.45), ncol=1, handlelength=2.5)
# Manual legend for plot A
handles = [mpl.lines.Line2D([0], [0], marker="o", mec="k", mfc="0.5", ms=6, lw=0, label="N$_\\mathrm{SPT}$"),
mpl.lines.Line2D([0], [0], marker="|", mec="k", ms=10, mew=2.5, lw=0, label="Fine\nfraction")]
axs['A'].legend(handles=handles, loc="upper right", fontsize="small")
# Manual legend for plot B
handles = [mpl.lines.Line2D([0], [0], marker="o", mec="k", mfc="0.5", ms=6, ls=':', label="$\\phi'-c'$ correlation"),
mpl.lines.Line2D([0], [0], marker="_", mec="k", ms=7, mew=2.5, lw=0, label=f"$S_\\mathrm{{u}}$ correlation")]
axs['B'].legend(handles=handles, loc="upper left", fontsize="small", handlelength=2.5)
fig.canvas.header_visible = False
fig.canvas.toolbar_position = 'bottom'
plt.show()
returnExampleยถ
Reading the input dataยถ
testing_data = True # Set to False to use the GUI to load the data from an external file# Non-tabulated data
mat_depths = [7, 12.5, 18] # bottom depth of each material [m]
๐พ_moist = [18.50, 16.5, 16.00] # Total/moist/bulk unit weight of each soil layer [kN/mยณ]
๐พ_sat = [19.50, 17.0, 17.50] # Saturated unit weight of each soil layer [kN/mยณ]
wt_depth = 10 # watertable depth [m]
perfo_diam = 75 # [mm]
field_test_energy = 45 # [%]
corr_๐ = 'f' # JRB
# Tabulated data
if testing_data:
url = "https://raw.githubusercontent.com/eamontoyaa/data4testing/main/spt/"
df = pd.read_excel(f"{url}spt_processing_input_2.xlsx")
elif testing_data is False and 'google.colab' in str(get_ipython()):
file = files.upload()
df = pd.read_excel(list(file.values())[0])
else: # GUI for file selection from local machine if not in CoLab
tk.Tk().withdraw() # part of the import if you are not using other tkinter functions
file = askopenfilename()
df = pd.read_excel(file)
# # df.loc[4, 'Finos_(%)'] = 55
df/home/eamontoyaa/.pyenv/versions/3.11.11/envs/EAFIT-3p11-env/lib/python3.11/site-packages/openpyxl/worksheet/_reader.py:329: UserWarning: Data Validation extension is not supported and will be removed
warn(msg)
Loading...
Processing the data and completing the tableยถ
complete_table(df, mat_depths, ๐พ_moist, ๐พ_sat, wt_depth, perfo_diam, field_test_energy, corr_๐)
dfLoading...
Plotting the processed dataยถ
plot_processing(df, mat_depths, wt_depth)
- Gonzalez, A. (1999). Estimativos de parametros de resistencia con el SPT. X Jornadas Geotรฉcnicas De La Ingenierรญa Colombiana. https://www.scg.org.co/divulgacion/publicaciones/descargas/