User Guide

The User Guide covers all aspects of GeoEq, from basic soil property calculations to advanced particle size analysis and professional publication-ready plots. Each section documents every function argument, lists all output dictionary keys, and provides working code examples you can copy directly into your own notebooks or scripts.

GeoEq is designed to be lightweight, traceable, and reliable. Every formula is cross-referenced with standard geotechnical textbooks: Das (2018), Holtz & Kovacs, and ASTM / BS / IS standards.

The guide follows the same chapter structure as Principles of Geotechnical Engineering by Braja Das: Chapter 2 covers Particle Size Analysis, and Chapter 3 covers Soil Properties and Classification.

Particle Size Analysis

GeoEq includes a complete, laboratory-grade particle size analysis suite. It covers two distinct test methods — Sieve Analysis for coarse-grained soils and Hydrometer Analysis for fine-grained soils — and is designed so that results from both can be combined into a single, continuous grain size distribution curve.

1. Sieve Analysis — sp.sieve_ana()

This function accepts two lists — the sieve sizes and the mass retained on each sieve — and automatically converts them to a percent-finer-by-mass distribution using one of three internationally recognised standards.

Function Signature

python
sp.sieve_ana(
    opening,           # list of sieve sizes (designations OR mm floats)
    mass_retained,     # list of masses in grams retained on each sieve
    standard="ASTM",  # standard: "ASTM", "BS", or "IS"
    total_mass=None    # total specimen mass (g). If None, sum of retained masses is used
)

Sieve Designation Formats

You can mix and match designation styles and GeoEq will resolve them correctly. All of the following are valid inputs:

python
# ASTM designations (number sieves and inch fractions)
opening = ["3/4\"", "3/8\"", "#4", "#10", "#40", "#100", "#200"]

# BS sieve designations (sizes in mm)
opening = ["20mm", "10mm", "5mm", "2mm", "600um", "212um", "75um"]

# Raw mm values (any standard)
opening = [19.0, 9.5, 4.75, 2.0, 0.425, 0.075]

Return Value (Dictionary)

The function returns a Python dictionary with the following keys, all as NumPy arrays:

python
res = sp.sieve_ana(["#4", "#10", "#40", "#200"], [10, 25, 40, 15], total_mass=100)

res["diameter"]          # Nominal opening in mm  → [4.75, 2.0, 0.425, 0.075]
res["opening"]           # Alias for diameter (same values)
res["mass_retained"]     # Mass on each sieve (g)
res["percent_retained"]  # % of total mass on each sieve
res["cumulative_retained"]# Cumulative % retained
res["percent_finer"]     # % passing (finer) — this is your Y-axis data
res["total_mass"]        # Total specimen mass used for calculations

2. Hydrometer Analysis — sp.hydro_ana()

Implements the full ASTM D7928 sedimentation analysis. Raw readings from a standard 152H hydrometer are corrected for meniscus, temperature, and dispersing agent, then converted to equivalent particle diameters using Stokes' Law.

Function Signature

python
sp.hydro_ana(
    reading,          # list of raw hydrometer readings (top of meniscus)
    time,             # list of elapsed times in minutes
    T,                # temperature of suspension (°C)
    Gs=2.65,         # specific gravity of soil solids (default: 2.65)
    Ws=50.0,         # dry mass of soil specimen in grams (default: 50g)
    Fm=1.0,          # meniscus correction (default: 1.0 for 152H)
    Fz=0.0,          # zero correction (dispersing agent correction)
)

Return Value (Dictionary)

python
h_res = sp.hydro_ana([52, 45, 38, 28], [1, 15, 60, 1440], T=22.0, Ws=50.0)

h_res["diameter"]       # Equivalent particle diameter D (mm) via Stokes' Law
h_res["percent_finer"]  # Percent finer (%) — corrected P value
h_res["reading"]        # Corrected hydrometer readings (R_c)
h_res["depth"]          # Effective depth L (cm) for each reading
h_res["time"]           # Elapsed time (minutes)

3. Combining Sieve + Hydrometer

Once you have results from both tests, simply pass them as a dictionary to the plotting function. GeoEq automatically merges the datasets and calculates parameters across the full range.

python
# Run both tests
s_res = sp.sieve_ana(["#4", "#10", "#40", "#200"], [10, 25, 40, 15])
h_res = sp.hydro_ana([50, 42, 30, 18], [1, 15, 60, 1440], T=22.0)

# Pass both to plot — they are stitched into one continuous curve
sp.grain_size_plot({"Sieve": s_res, "Hydrometer": h_res}, smooth=True)

Advanced Plotting — sp.grain_size_plot()

This is the most feature-rich function in the package. It generates a fully professional, publication-quality, semi-logarithmic grain size distribution curve. Internally it wraps Matplotlib, so every Matplotlib line keyword argument works — colour, linewidth, linestyle, markersize, alpha, and more.

Full Function Signature

python
sp.grain_size_plot(
    data,              # dict with keys 'diameter' & 'percent_finer'  — OR —
                       # dict of named datasets: {"Sieve": s_res, "Hydro": h_res}
    smooth=False,     # bool — enables high-res PCHIP smooth curve (1000 points)
    annotation=False, # bool — show a legend labelling each data source
    D_para=True,      # bool — draw red dotted projection lines for D10/D30/D60
    Cu_para=True,     # bool — show Uniformity Coefficient Cᵤ in the parameter box
    Cc_para=True,     # bool — show Coefficient of Curvature Cᶜ in the parameter box
    param_pos="top right", # str or (x, y) — position of the parameter info box
    save_as=None,     # str — filename to save: 'plot.png', 'report.pdf', 'figure.svg'
    ax=None,          # matplotlib Axes — embed into an existing figure
    **kwargs           # any Matplotlib line2D keyword (see table below)
)

Matplotlib Pass-Through Arguments (**kwargs)

Because GeoEq uses Matplotlib internally, all standard line styling arguments are accepted. This is the same as calling ax.plot() directly. Common examples:

python
sp.grain_size_plot(
    data,
    smooth=True,
    # --- Line Style ---
    color='#1e4d8f',   # any hex, name, or rgb tuple
    linewidth=1.8,      # line thickness
    linestyle='--',    # '-', '--', ':', '-.'
    alpha=0.85,         # transparency (0.0–1.0)
    # --- Markers ---
    marker='o',        # 'o', 's', '^', '*', 'D', 'v', 'none', ...
    markersize=6,      # size of each marker
    markerfacecolor='white',  # fill color of marker
    markeredgecolor='blue',   # border color of marker
    markeredgewidth=1.2,      # border thickness
    # --- Save ---
    save_as='report.pdf'  # .png, .pdf, .svg, .eps — all formats supported
)

Parameter Box Position (param_pos)

The box that shows D₁₀, D₃₀, D₆₀, Cᵤ, and Cᶜ can be placed anywhere. Use a keyword string or a precise (x, y) position in axes-fraction coordinates (0.0 = left/bottom, 1.0 = right/top):

python
# Keyword positions
param_pos="top right"    # default — upper right corner
param_pos="top left"     # upper left corner
param_pos="bottom right" # lower right corner
param_pos="bottom left"  # lower left corner

# Precise (x, y) control in axes-fraction coordinates
param_pos=(0.5, 0.8)  # centre of the plot, 80% up

Embedding in Your Own Figure

Because grain_size_plot() accepts an ax argument and returns the fig object, you can embed it into multi-panel scientific figures or add custom annotations after the fact:

python
import matplotlib.pyplot as plt
import geoeq as sp

# Create your own figure with multiple panels
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 7))

# Embed GeoEq plot in the left panel
sp.grain_size_plot(s_res, ax=ax1, smooth=True, color='blue')

# Add your own plot in the right panel
ax2.plot(depth, settlement)
ax2.set_title("Settlement Curve")

# Add a custom annotation to the grain size plot after the fact
ax1.axhline(50, color='purple', linestyle='--', label='D₅₀')

plt.savefig('combined_report.pdf', bbox_inches='tight', dpi=300)

Saving to Publication Formats

The save_as argument routes directly to Matplotlib's savefig(), so all formats are supported. GeoEq always saves at 300 DPI with a tight bounding box — ideal for journals and reports.

python
sp.grain_size_plot(data, save_as="figure.png")  # raster, 300 DPI
sp.grain_size_plot(data, save_as="figure.svg")  # vector — scalable
sp.grain_size_plot(data, save_as="figure.pdf")  # vector — best for LaTeX
sp.grain_size_plot(data, save_as="figure.eps")  # vector — legacy journals

Parameter Extraction

You can extract geotechnical parameters from any result dictionary — from sieve data, hydrometer data, or a merged combined dataset. All five functions use log-linear interpolation, which is the industry standard for grain size parameter calculations.

Available Functions

python
# Diameter at which N% of soil is finer (mm)
d10 = sp.grain_d10(res)  # effective size
d30 = sp.grain_d30(res)  # used for Cc calculation
d60 = sp.grain_d60(res)  # controlling size

# Uniformity Coefficient — Cᵤ = D₆₀ / D₁₀
# Well-graded if Cᵤ ≥ 4 (gravels) or Cᵤ ≥ 6 (sands)
cu = sp.grain_Cu(res)

# Coefficient of Curvature — Cᶜ = D₃₀² / (D₁₀ × D₆₀)
# Well-graded if 1 ≤ Cᶜ ≤ 3
cc = sp.grain_Cc(res)

print(f"D10={d10:.3f} mm,  D30={d30:.3f} mm,  D60={d60:.3f} mm")
print(f"Cᵤ = {cu:.2f}  →  {'Well-graded' if cu >= 6 else 'Poorly-graded'}")
print(f"Cᶜ = {cc:.2f}  →  {'Within range' if 1 <= cc <= 3 else 'Outside range'}")

Chapter 3 — Void Ratio & Porosity

Void ratio (e) and porosity (n) describe the relative volume of voids in a soil. GeoEq provides two symmetrical functions — sp.void_ratio() and sp.porosity() — that accept different input combinations and automatically resolve the correct path.

1. Void Ratio — sp.void_ratio()

Computes void ratio from any physically meaningful input combination. All inputs are validated to be within geotechnically possible ranges.

python
sp.void_ratio(
    n=None,        # porosity (0 < n < 1)
    Vv=None,       # volume of voids (m³ or any consistent unit)
    Vs=None,       # volume of solids
    Vt=None,       # total volume (Vt = Vv + Vs)
)

# From porosity: e = n / (1 - n)
e = sp.void_ratio(n=0.42)
print(f"e = {e:.3f}")  # → e = 0.724

# From volumes: e = Vv / Vs
e2 = sp.void_ratio(Vv=0.30, Vs=0.70)
print(f"e = {e2:.3f}")  # → e = 0.429

2. Porosity — sp.porosity()

python
sp.porosity(
    e=None,        # void ratio (e > 0)
    Vv=None,       # volume of voids
    Vt=None,       # total volume
)

# From void ratio: n = e / (1 + e)
n = sp.porosity(e=0.72)
print(f"n = {n:.3f}")  # → n = 0.419

# From volumes: n = Vv / Vt
n2 = sp.porosity(Vv=0.28, Vt=1.0)
print(f"n = {n2:.3f}")  # → n = 0.280

Unit Weight & Density — sp.density()

Computing dry, saturated, bulk, and submerged unit weights is one of the most common tasks in geotechnical practice. GeoEq unifies all of these into a single smart function with a kind argument. It also supports multiple output unit systems — so you can request kN/m³ for European standards or pcf for American practice with no manual conversion.

Function Signature

python
sp.density(
    Gs=None,       # specific gravity of solids (dimensionless, typically 2.60–2.80)
    e=None,        # void ratio
    w=None,        # water content (decimal, e.g. 0.18 for 18%)
    S=None,        # degree of saturation (0.0–1.0)
    mass=None,     # total mass of sample (kg or g)
    volume=None,   # total volume of sample
    kind="bulk",   # "bulk", "dry", "saturated", or "submerged"
    unit="kN/m3", # output unit: "kN/m3", "kPa/m", "kg/m3", "g/cm3", "pcf"
)

All Four Density Types

python
# Dry unit weight: γd = (Gs × γw) / (1 + e)
gamma_d   = sp.density(Gs=2.65, e=0.72, kind="dry",       unit="kN/m3")

# Saturated unit weight: γsat = (Gs + e) × γw / (1 + e)
gamma_sat = sp.density(Gs=2.65, e=0.72, kind="saturated",  unit="kN/m3")

# Submerged / buoyant unit weight: γ' = γsat - γw
gamma_sub = sp.density(Gs=2.65, e=0.72, kind="submerged", unit="kN/m3")

# Bulk unit weight: γ = (Gs + S×e) × γw / (1 + e)
gamma_b   = sp.density(Gs=2.65, e=0.72, S=0.80, kind="bulk",       unit="kN/m3")

print(f"γd={gamma_d:.2f}  γsat={gamma_sat:.2f}  γ'={gamma_sub:.2f}  γ={gamma_b:.2f} kN/m³")

# Direct from mass and volume (any unit system)
gamma_pcf = sp.density(mass=50, volume=0.028, unit="pcf")
print(f"Bulk density = {gamma_pcf:.1f} pcf")

Unit Conversion Quick Reference

The unit argument accepts any of the following strings. GeoEq automatically applies the correct water unit weight (9.81 kN/m³ or 62.4 pcf) in its calculations:

python
# SI units
unit="kN/m3"   # kilonewtons per cubic metre  (most common in geotechnical practice)
unit="kPa/m"   # same magnitude, different notation (useful for stress gradients)
unit="kg/m3"   # kilograms per cubic metre
unit="g/cm3"   # grams per cubic centimetre (same as specific gravity scale)

# Imperial units
unit="pcf"     # pounds per cubic foot (lbf/ft³)

Atterberg Limits — sp.atterberg()

Atterberg limits define the water content boundaries at which fine-grained soil transitions between solid, semi-solid, plastic, and liquid states. GeoEq computes the Plasticity Index (PI), Liquidity Index (LI), and Consistency Index (CI) from a single unified function using the kind argument.

Function Signature

python
sp.atterberg(
    LL,            # Liquid Limit (%)
    PL,            # Plastic Limit (%)
    w=None,        # current water content (%) — required for LI and CI
    kind="PI",    # "PI", "LI", "CI", or "all"
)

Usage — All Return Modes

python
# Plasticity Index: PI = LL - PL
PI = sp.atterberg(LL=48, PL=22, kind="PI")
print(f"PI = {PI}")   # → PI = 26

# Liquidity Index: LI = (w - PL) / (LL - PL)
# LI < 0 → Solid, 0–1 → Plastic, > 1 → Liquid
LI = sp.atterberg(LL=48, PL=22, w=35, kind="LI")
print(f"LI = {LI:.2f}")  # → LI = 0.50

# Consistency Index: CI = (LL - w) / (LL - PL) = 1 - LI
CI = sp.atterberg(LL=48, PL=22, w=35, kind="CI")
print(f"CI = {CI:.2f}")  # → CI = 0.50

# All at once — returns a dictionary
result = sp.atterberg(LL=48, PL=22, w=35, kind="all")
result["PI"]  # → 26
result["LI"]  # → 0.50
result["CI"]  # → 0.50

Relative Density — sp.relative_density()

Relative density (Dr) measures how densely packed a granular soil is relative to its loosest and densest states. GeoEq supports two calculation paths via the kind argument — one based on void ratio limits and one based on dry density limits.

Function Signature

python
sp.relative_density(
    e=None,        # current void ratio (used when kind='void')
    e_max=None,    # void ratio at loosest state (maximum voids)
    e_min=None,    # void ratio at densest state (minimum voids)
    rho=None,      # current dry density (used when kind='density')
    rho_max=None,  # maximum dry density (densest state)
    rho_min=None,  # minimum dry density (loosest state)
    kind="void",  # "void" or "density"
)

Usage

python
# From void ratio: Dr = (e_max - e) / (e_max - e_min)
Dr_e = sp.relative_density(e=0.60, e_max=0.85, e_min=0.45, kind="void")
print(f"Dr = {Dr_e:.1%}")  # → Dr = 62.5%  (Medium dense)

# From dry density: Dr = (ρd,max/ρd,min) × (ρd - ρd,min) / (ρd,max - ρd,min) × 1/ρd
Dr_rho = sp.relative_density(rho=1560, rho_max=1720, rho_min=1380, kind="density")
print(f"Dr = {Dr_rho:.1%}")  # → Dr = 51.1%  (Medium dense)

# Interpretation of Dr values (Das, 2018):
# Dr < 15%   → Very Loose
# 15–50%     → Loose
# 50–70%     → Medium Dense
# 70–85%     → Dense
# Dr > 85%   → Very Dense

Phase Relations — sp.saturation() & sp.water_content()

These functions compute the degree of saturation and water content from various combinations of phase variables. GeoEq uses lazy resolution — you only need to provide the inputs you have measured in the lab, and it finds the correct formula automatically.

1. Degree of Saturation — sp.saturation()

python
sp.saturation(
    w=None,    # water content (decimal)
    Gs=None,   # specific gravity of solids
    e=None,    # void ratio
    Vw=None,   # volume of water
    Vv=None,   # volume of voids
)

# From phase parameters: S = (w × Gs) / e
S = sp.saturation(w=0.18, Gs=2.65, e=0.72)
print(f"S = {S:.1%}")   # → S = 66.3%

# From volumes: S = Vw / Vv
S2 = sp.saturation(Vw=0.25, Vv=0.40)
print(f"S = {S2:.1%}")  # → S = 62.5%

# Typical interpretation:
# S = 0%   → Completely dry
# S = 100% → Fully saturated (all voids filled with water)

2. Water Content — sp.water_content()

python
sp.water_content(
    Mw=None,   # mass of water (g or kg)
    Ms=None,   # mass of dry solids
    S=None,    # degree of saturation (0–1)
    Gs=None,   # specific gravity
    e=None,    # void ratio
)

# From lab measurements: w = Mw / Ms
w = sp.water_content(Mw=18.5, Ms=102.0)
print(f"w = {w:.1%}")   # → w = 18.1%

# From phase parameters: w = (S × e) / Gs
w2 = sp.water_content(S=0.80, e=0.72, Gs=2.65)
print(f"w = {w2:.1%}")  # → w = 21.7%

API Quick Reference

Complete list of all functions available at the top-level import geoeq as sp namespace.

Function Category Description
sp.sieve_ana()Grain SizeSieve analysis. ASTM / BS / IS. Returns percent finer distribution.
sp.hydro_ana()Grain SizeHydrometer analysis per ASTM D7928. Returns particle diameters and percent finer.
sp.grain_size_plot()VisualizationPublication-ready grain size distribution plot. Full Matplotlib pass-through.
sp.grain_d10()ParametersInterpolated D₁₀ (effective size) in mm.
sp.grain_d30()ParametersInterpolated D₃₀ in mm.
sp.grain_d60()ParametersInterpolated D₆₀ (controlling size) in mm.
sp.grain_Cu()ParametersUniformity Coefficient Cᵤ = D₆₀ / D₁₀.
sp.grain_Cc()ParametersCoefficient of Curvature Cᶜ = D₃₀² / (D₁₀ × D₆₀).
sp.density()Soil PropsDry, saturated, bulk, or submerged unit weight. Multiple unit systems.
sp.void_ratio()Soil PropsVoid ratio from porosity, masses, or volumes.
sp.saturation()Soil PropsDegree of saturation (S) from various input combinations.
sp.water_content()Soil PropsWater content (w) from masses or phase parameters.
sp.relative_density()Soil PropsDr from void ratio limits or dry density limits.
sp.atterberg()AtterbergPlasticity Index (PI), Liquidity Index (LI), and Consistency Index (CI).

Behind the Code

GeoEq is fully traceable to published standards and textbooks. Every formula is cross-referenced with Das (2018), Holtz & Kovacs, and ASTM standards.

Grain Size Formulas

Equivalent Particle Diameter (Stokes' Law):
D = √(18 η L / ((Gs − Gw) γw t))

Percent Finer (Hydrometer):
P = (a × Rc) / Ws × 100 , where a = Gs / (Gs − 1)

Uniformity Coefficient:
Cᵤ = D₆₀ / D₁₀

Coefficient of Curvature:
Cᶜ = D₃₀² / (D₁₀ × D₆₀)

Soil Phase Formulas

Dry Unit Weight: γd = (Gs γw) / (1 + e)

Porosity to Void Ratio: e = n / (1 − n)

Degree of Saturation: S = (w × Gs) / e