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
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:
# 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:
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
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)
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.
# 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
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:
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):
# 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:
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.
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
# 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.
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()
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
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
# 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:
# 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
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
# 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
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
# 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()
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()
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 Size | Sieve analysis. ASTM / BS / IS. Returns percent finer distribution. |
sp.hydro_ana() | Grain Size | Hydrometer analysis per ASTM D7928. Returns particle diameters and percent finer. |
sp.grain_size_plot() | Visualization | Publication-ready grain size distribution plot. Full Matplotlib pass-through. |
sp.grain_d10() | Parameters | Interpolated D₁₀ (effective size) in mm. |
sp.grain_d30() | Parameters | Interpolated D₃₀ in mm. |
sp.grain_d60() | Parameters | Interpolated D₆₀ (controlling size) in mm. |
sp.grain_Cu() | Parameters | Uniformity Coefficient Cᵤ = D₆₀ / D₁₀. |
sp.grain_Cc() | Parameters | Coefficient of Curvature Cᶜ = D₃₀² / (D₁₀ × D₆₀). |
sp.density() | Soil Props | Dry, saturated, bulk, or submerged unit weight. Multiple unit systems. |
sp.void_ratio() | Soil Props | Void ratio from porosity, masses, or volumes. |
sp.saturation() | Soil Props | Degree of saturation (S) from various input combinations. |
sp.water_content() | Soil Props | Water content (w) from masses or phase parameters. |
sp.relative_density() | Soil Props | Dr from void ratio limits or dry density limits. |
sp.atterberg() | Atterberg | Plasticity 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