Soil mechanics

The three-phase soil system — phase relationships, decoded

Every geotechnical calculation begins with one idea: a soil element is a mix of solids, water, and air. Get this right, and everything that follows — bearing capacity, settlement, liquefaction — clicks into place.

Ripon Chandra Malo · · 9 min read

Take a fistful of soil. Hold it up to the light. What you have, by volume, is three phases mixed together:

The non-solid space is the void volume Vv = Vw + Va. The total volume is V = Vs + Vv. From these few quantities, every other phase parameter — void ratio, porosity, saturation, water content, unit weight — is just a ratio.

Try it yourself

Drag the sliders. The 3-D column updates live, and so do the derived parameters. Notice how raising the void ratio (more void space) lowers the unit weight, and how raising saturation (more water in those voids) raises it again.

n = 0.412 w = 15.8% γ = 19.0 kN/m³
Drag the cube to orbit · drag sliders to change the soil state

The five phase parameters, defined

Every textbook starts here. The same five ratios — written exactly the same way in Das, Bowles, Holtz & Kovacs, Budhu, Craig. We are not inventing anything; we are giving you the simplest API to compute them.

Void ratio (e)

e = Vv / Vs

Dimensionless. Typical values: 0.3 for dense gravel, 0.65 for medium sand, 0.9–1.5 for soft to sensitive clays, up to 3.0 for organic peats. Void ratio can exceed 1 — there is genuinely more void space than solid.

import geoeq as ge

e = ge.void_ratio(n=0.42)        # 0.7241  — from porosity
e = ge.void_ratio(Vv=72, Vs=100)  # 0.72    — from volumes

Porosity (n)

n = Vv / V = e / (1 + e)

The fraction of the total volume that is void. Always between 0 and 1. A dense sand might have n ≈ 0.30; a soft clay n ≈ 0.55.

n = ge.porosity(e=0.72)         # 0.4186

Degree of saturation (S)

S = Vw / Vv

The fraction of the void space filled with water. S = 0 is a perfectly dry soil; S = 1 is fully saturated (which is what we assume below the water table for most engineering calculations).

S = ge.saturation(w=0.18, Gs=2.65, e=0.72)
# 0.6625  — 66% saturated

Water content (w)

w = Mw / Ms

A mass ratio — water mass divided by dry-solids mass. Get it by weighing a moist sample, oven-drying it overnight at 105 °C, weighing again. Typical values: 5–10 % for compacted gravels, 15–35 % for in-situ clays, >50 % for sensitive soft clays.

w = ge.water_content(S=1.0, Gs=2.65, e=0.72)
# 0.2717  — w at full saturation

Specific gravity of solids (Gs)

Gs = ρs / ρw

The density of the solid mineral grains, normalised by the density of water. Quartz sand: 2.65. Most clays: 2.70–2.75. Iron ores: 3.0+. Measured with a pycnometer.

Gs = ge.specific_gravity(Ms=265.0, Vs=100.0)  # 2.65

The one identity that ties them all together

w · Gs = S · e

This is the master equation. Given any three of (w, Gs, S, e) you can solve for the fourth without ever touching a volume or mass directly. Memorise it.

GeoEq's ge.saturation() and ge.water_content() functions are literally just rearranged copies of this identity:

# If we know w, Gs, e -- find S
ge.saturation(w=0.20, Gs=2.70, e=0.85)
# = w*Gs/e = 0.20 * 2.70 / 0.85 = 0.635

# If we know S, Gs, e -- find w
ge.water_content(S=0.80, Gs=2.70, e=0.85)
# = S*e/Gs = 0.80 * 0.85 / 2.70 = 0.252

Unit weights — the four-in-one function

Once we have e, S, and Gs, we can compute any of four unit weights. Textbooks teach them as four separate formulas; GeoEq's ge.density() picks the right one based on a single kind= argument.

γd = Gs γw / (1 + e)     (dry)
γsat = (Gs + e) γw / (1 + e)     (saturated, S=1)
γ = (Gs + Se) γw / (1 + e)     (bulk, general S)
γ' = γsat − γw     (submerged / buoyant)
γ_d   = ge.density(Gs=2.65, e=0.72, kind="dry",        unit="kN/m3")
γ_s   = ge.density(Gs=2.65, e=0.72, kind="saturated",  unit="kN/m3")
γ_b   = ge.density(Gs=2.65, e=0.72, S=0.8, kind="bulk", unit="pcf")
γ_sub = ge.density(Gs=2.65, e=0.72, kind="submerged",  unit="kN/m3")

# 15.10 kN/m³,  19.23 kN/m³,  117.3 pcf,  9.42 kN/m³

A worked example — undisturbed clay sample

A clay specimen weighs 1010 g in the wet state and 800 g after oven drying. Its bulk volume is 600 cm³, and the specific gravity of solids is 2.72. Determine w, e, n, and S.
import geoeq as ge

# Step 1 — water content (direct from masses)
w = (1010 - 800) / 800          # 0.2625

# Step 2 — volume of solids (mass / density)
Vs = 800 / 2.72                  # 294.12 cm³
Vv = 600 - Vs                    # 305.88 cm³

# Step 3 — phase ratios
e = ge.void_ratio(Vv=Vv, Vs=Vs)      # 1.040
n = ge.porosity(e=e)               # 0.510
S = ge.saturation(w=w, Gs=2.72, e=e) # 0.687

print(f"w={w:.3f}, e={e:.3f}, n={n:.3f}, S={S:.3f}")
# w=0.262, e=1.040, n=0.510, S=0.687

That's the whole loop. Three lines of unique work, plus a couple of unit-conversions. In a spreadsheet, this would be six cells with hand-typed formulas; here it's traceable, validated code that you can re-run a year from now and get the same answer.

Why this matters for everything else

Every later calculation in soil mechanics consumes one or more of these five parameters. A bearing-capacity check needs γ. A settlement prediction needs e. A seepage analysis needs n. A liquefaction triggering analysis needs σ'v, which is γ-integrated downward.

Get the phase relationships wrong, and every downstream number is wrong. Get them right — once, in code — and they propagate cleanly through the rest of the workflow. That is the entire reason for ge.SoilProfile: define each layer's phase parameters once, and stress, settlement, liquefaction-triggering all consume them automatically.

What to read next

Share this article