Surface processes & geomorphology
Landform evolution and fluvial morphometrics, soil/rock mechanics for construction, soil formation and classification science, and sediment transport/grain-size statistics, across four independent sca
Four small, independent modules for the physics and empirical formulas of the Earth's surface: how a river basin's shape and flow carve and diffuse a landscape (geomorphology), how soil and rock support or fail under a foundation or a slope (geotechnical), how a soil profile develops its horizons and gets classified (pedology), and how sediment grains move, settle, and sort themselves once eroded (sedimentology). None of the four share a namespace prefix or import Python — each is a flat, single-file module of Float64-in/Float64-out functions with no structs and no shared state, called directly by its bare module name.
from algenta import MojoRuntime
runtime = MojoRuntime(mode="local")
runtime.execute("geotechnical", "bearing_capacity_terzaghi",
[25.0, 18.0, 1.5, 2.0, 37.2, 22.5, 19.7])["result"]
# 1892.1 — Terzaghi ultimate bearing capacity (kPa) for a 2 m strip footing at 1.5 m depth
runtime.execute("sedimentology", "grain_size_phi", [0.0625])["result"]
# 4.0 — 0.0625 mm is exactly the sand/silt boundary on the Wentworth scale, phi 4The modules
geomorphology
Landform evolution and river-basin morphometrics: stream power, bed shear stress, the Shields parameter and Stokes settling velocity for sediment entrainment, stream-power-law erosion, hillslope diffusion, and basin-shape statistics (drainage density, relief ratio, sinuosity, Hack's law length, ruggedness number, hypsometric integral).
geotechnical
Soil and rock mechanics for construction: Terzaghi ultimate bearing capacity, effective stress, Rankine active/passive earth-pressure coefficients and Jaky's at-rest coefficient, simplified elastic settlement, slope-stability factor of safety, and the phase relationships (void ratio, porosity, degree of saturation) that connect them.
pedology
Soil formation and classification science: composite development indices (soil development index, profile development index), clay illuviation, Munsell-colour rubification, organic-horizon depth from a litter/decomposition balance, base saturation, Jenny's (1941) climate-age solum-thickness model, biological weathering, element translocation, and an inverse age-from-pedogenesis estimate.
sedimentology
Sediment transport, deposition, and grain-size statistics: Stokes settling velocity, the Shields parameter and its critical threshold, Meyer-Peter–Müller bedload transport, the Rouse suspended-sediment concentration profile, phi-scale grain-size conversion, and Folk-and-Ward sorting and skewness.
All four modules match their name and the working assumption behind this page — there is no distillation-style surprise here: geomorphology.mojo, geotechnical.mojo, pedology.mojo, and sedimentology.mojo each open with a docstring describing exactly the earth-science subject their name implies, and the function bodies bear that out.
Everything here is a scalar in, a scalar out
Every function across all four modules takes only Float64 parameters and returns a single Float64 — the contract lists zero structs for any of them. There is no TxBuffer-style shared carrier the way tensorx has, and no KMeansResult-style struct the way the ML modules return; a caller building a multi-step calculation (say, computing a Shields parameter and feeding it into bedload transport) threads every intermediate value through Python itself, one ["result"] at a time.
Two independent Shields parameters, two independent settling velocities
geomorphology and sedimentology both implement the Shields parameter and the Stokes settling velocity — the two modules never call into each other, and the two implementations are not byte-for-byte the same function under two names:
Shields parameter.
geomorphology.shields_parameter(shear_stress, density_s, density_f, g, grain_size)takes gravitational acceleration as an explicit parameter and only guards the exact casedenom == 0.0.sedimentology.shields_parameter(bed_shear_stress, grain_diameter_m, grain_density, fluid_density)hardcodesg = 9.81internally (dropping it as a parameter entirely) and guards the broaderdenom <= 0.0— so a caller who accidentally passesfluid_density > grain_densitygets0.0fromsedimentologybut a real (negative) number fromgeomorphology.Stokes settling velocity.
geomorphology.settling_velocity_stokes(density_s, density_f, g, diameter, viscosity)takes dynamic viscosity μ (Pa·s) directly:ws = (ρs−ρf)·g·d² / (18·μ).sedimentology.settling_velocity_stokes(grain_diameter_m, grain_density, fluid_density, kinematic_viscosity)takes kinematic viscosity ν (m²/s) and fluid density separately, hardcodesg = 9.81, and divides by18·ν·ρf— algebraically the same formula, sinceν·ρf = μ, but the parameter list, order, and units are entirely different. The two do agree numerically once fed physically equivalent inputs — see the worked examples below.
Neither pairing is a bug; they are separate authors' solutions to the same physics, reachable under two different module names, and a caller who assumes one signature works for the other will get a TypeError-style argument-count mismatch, not a wrong answer.
Bearing capacity doesn't compute its own N-factors
geotechnical.bearing_capacity_terzaghi(c, gamma, d, b, nc, nq, ngamma) implements only the combination step of Terzaghi's formula — q_ult = c·Nc + γ·D·Nq + 0.5·γ·B·Nγ — and takes the three bearing-capacity factors Nc, Nq, Nγ as plain Float64 arguments. It does not look them up from a friction-angle table and does not know what φ the caller had in mind. A caller has to already have the right factors for their footing type and friction angle (Terzaghi published separate general-shear and local-shear tables, and later authors such as Meyerhof and Vesic revised them) — the worked example below uses figures commonly tabulated for φ ≈ 30° strip footings under general shear failure (Nc ≈ 37.2, Nq ≈ 22.5, Nγ ≈ 19.7), but that table lookup happens entirely outside this kernel.
A river basin, worked by hand
Every number below was computed independently in Python against the formulas in mojo/engine/geomorphology.mojo, not read from the module's own test file — see Verification. One small basin, consistent SI units throughout:
The same basin under a flood discharge of 50 m³/s, a 0.002 channel slope, and 2 m of flow depth:
That last number reappears, computed a different way, in the sedimentology example below — sedimentology.settling_velocity_stokes at kinematic viscosity 1e-6 m²/s and the same 1000 kg/m³ water is the identical physical input (ν·ρf = μ = 0.001), and it produces the same 0.0089925 m/s.
geomorphology.mojo's division-by-zero guards are inconsistent across its own file: only shields_parameter (exact denom == 0.0) and hypsometric_integral (max_elev == min_elev) return the 0.0 sentinel documented as necessary because "inf/nan cannot cross the result wire." drainage_density, relief_ratio, and sinuosity have no such guard — a zero basin_area, basin_length, or valley_length reaches an unguarded division and produces inf rather than a defined value. hack_law_length and erosion_rate_spl both call pow with fractional exponents (h ≈ 0.6, m/n) against a caller-supplied area/slope with no non-negativity check; a negative area or slope — never physically valid, but not rejected — produces NaN from pow rather than a caught error.
A shallow foundation, worked by hand
mojo/engine/geotechnical.mojo, traced independently in Python. A strip footing 2 m wide at 1.5 m depth, cohesion 25 kPa, unit weight 18 kN/m³, and φ = 30° bearing-capacity factors:
Settlement and slope stability, and the phase relationships that connect void ratio and porosity:
void_ratio is the only function in this module with an explicit guard: porosity >= 1.0 (which would divide by zero) or a self-comparison failure (porosity != porosity, i.e. NaN) both return 0.0. porosity_from_void_ratio, degree_of_saturation, effective_stress, overburden_pressure, factor_of_safety_slope, settlement_elastic, rankine_ka, and rankine_kp have no equivalent checks — a void ratio of -1, a saturation calculation with e = 0, a zero driving_force, a zero modulus, or a phi of exactly ±90° (which zeroes rankine_kp's or rankine_ka's denominator) all reach an unguarded division and produce inf.
A soil profile, worked by hand
mojo/engine/pedology.mojo, traced independently in Python. A single developing profile, so the numbers below chain together rather than standing alone:
soil_age_from_pedogenesis and profile_development_index are algebraic inverses of each other given a fixed rate (age = PDI / rate, PDI = clay_increase * structure_grade * thickness), which is why feeding one module's output into the other above returns exactly the age (100 ky) the profile example started from — not a coincidence, a property of the two formulas.
Eight of this module's ten functions guard their division or their pow-with-non-positive-base case explicitly, returning 0.0: soil_development_index (product <= 0.0), clay_illuviation_ratio (eluvial_clay_pct <= 0.0), rubification_index (value <= 0.0), organic_horizon_depth (decomposition_rate <= 0.0), base_saturation_percent (cec_cmol_kg <= 0.0), solum_thickness_estimate (temperature_c <= 0.0), translocation_coefficient (element_topsoil <= 0.0), and soil_age_from_pedogenesis (pdi_rate <= 0.0). The remaining two, profile_development_index and biological_weathering_index, are pure multiplication with no division to guard.
A sand bed under flow, worked by hand
mojo/engine/sedimentology.mojo, traced independently in Python. Grain-size conversion first:
1 mm quartz sand (density 2650 kg/m³) in water (1000 kg/m³):
Grain-size distribution statistics (phi values 84th=1.5, 16th=0.2, 50th=0.8, 95th=2.0, 5th=-0.3):
Six of this module's ten functions guard against a non-positive denominator or base and return 0.0: settling_velocity_stokes and shields_parameter (denom <= 0.0, the broader guard described above), bedload_transport (excess <= 0.0), suspended_load_rouse (height outside (0, depth) or a non-positive ratio), grain_size_phi (diameter_mm <= 0.0), and skewness_folk_ward (either phi-range denominator equal to 0.0). critical_shear_stress, phi_to_mm, sorting_coefficient, and sediment_budget have no division by a variable quantity (sorting_coefficient only ever divides by the fixed constants 4.0 and 6.6), so there is nothing to guard.
Verification
mojo/tests/libs/test_geomorphology.mojo, test_geotechnical.mojo, test_pedology.mojo, and test_sedimentology.mojo are auto-generated coverage smoke tests, the same pattern documented on Classical ML algorithms and Mixture-of-experts and routing kernels: every function is called once against a shared, arbitrary 0.5-valued fixture (or, for bearing_capacity_terzaghi, seven copies of 0.5), and the only assertion is that the result is finite (not isnan(x) and not isinf(x)) — none of the four files pins a specific numeric output. Every number in the worked examples above was instead computed independently, by re-implementing each function's formula in plain Python from the real source in mojo/engine/geomorphology.mojo, geotechnical.mojo, pedology.mojo, and sedimentology.mojo, and can be reproduced by re-running the same arithmetic.
Next: see Geospatial indexing & joins for the other Earth-science kernel family in this runtime, or Runtime library catalog for every module and function.
Last updated
Was this helpful?