#![allow(dead_code)]
#![allow(clippy::too_many_arguments)]
use std::f64::consts::PI;
const G: f64 = 9.81;
const GAMMA_W: f64 = 9_810.0;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SoilType {
Clay,
Sand,
Gravel,
Silt,
Peat,
}
#[derive(Debug, Clone)]
pub struct RockMaterial {
pub cohesion: f64,
pub friction_angle: f64,
pub tensile_strength: f64,
pub ucs: f64,
pub youngs_modulus: f64,
pub poisson_ratio: f64,
}
impl RockMaterial {
pub fn new(
cohesion: f64,
friction_angle_deg: f64,
tensile_strength: f64,
ucs: f64,
youngs_modulus: f64,
poisson_ratio: f64,
) -> Self {
Self {
cohesion,
friction_angle: friction_angle_deg.to_radians(),
tensile_strength,
ucs,
youngs_modulus,
poisson_ratio,
}
}
pub fn mohr_coulomb_strength(&self, sigma3: f64) -> f64 {
let phi = self.friction_angle;
let n_phi = ((PI / 4.0 + phi / 2.0).tan()).powi(2);
n_phi * sigma3 + 2.0 * self.cohesion * n_phi.sqrt()
}
pub fn hoek_brown_strength(&self, sigma3: f64, mi: f64, gsi: f64) -> f64 {
let sigma_ci = self.ucs;
let s = ((gsi - 100.0) / 9.0).exp();
let arg = mi * sigma3 / sigma_ci + s;
sigma3 + sigma_ci * arg.max(0.0).sqrt()
}
pub fn drucker_prager_yield(&self, p: f64, q: f64) -> f64 {
let phi = self.friction_angle;
let sin_phi = phi.sin();
let cos_phi = phi.cos();
let alpha = 2.0 * sin_phi / (3.0f64.sqrt() * (3.0 - sin_phi));
let k = 6.0 * self.cohesion * cos_phi / (3.0f64.sqrt() * (3.0 - sin_phi));
q - alpha * p - k
}
pub fn triaxial_deformation(&self, sigma1: f64, sigma3: f64) -> (f64, f64) {
let e = self.youngs_modulus;
let nu = self.poisson_ratio;
let eps1 = (sigma1 - 2.0 * nu * sigma3) / e;
let eps_v = (sigma1 - 2.0 * sigma3) * (1.0 - 2.0 * nu) / e;
(eps1, eps_v)
}
}
#[derive(Debug, Clone)]
pub struct PoroelasticMaterial {
pub biot_coeff: f64,
pub bulk_modulus: f64,
pub porosity: f64,
pub permeability: f64,
pub fluid_bulk_modulus: f64,
pub fluid_viscosity: f64,
}
impl PoroelasticMaterial {
pub fn new(
biot_coeff: f64,
bulk_modulus: f64,
porosity: f64,
permeability: f64,
fluid_bulk_modulus: f64,
fluid_viscosity: f64,
) -> Self {
Self {
biot_coeff,
bulk_modulus,
porosity,
permeability,
fluid_bulk_modulus,
fluid_viscosity,
}
}
pub fn biot_modulus(&self) -> f64 {
self.fluid_bulk_modulus / self.porosity.max(1e-10)
}
pub fn undrained_bulk_modulus(&self) -> f64 {
let m = self.biot_modulus();
self.bulk_modulus + self.biot_coeff * self.biot_coeff * m
}
pub fn skempton_b(&self) -> f64 {
let m = self.biot_modulus();
let ku = self.undrained_bulk_modulus();
self.biot_coeff * m / ku.max(1e-30)
}
pub fn consolidation_coefficient(&self) -> f64 {
let m = self.biot_modulus();
let denom = self.fluid_viscosity
* (self.biot_coeff * self.biot_coeff / self.bulk_modulus.max(1e-30) + 1.0 / m);
self.permeability / denom.max(1e-30)
}
pub fn terzaghi_settlement(&self, sigma: f64, depth: f64, time: f64) -> f64 {
let cv = self.consolidation_coefficient();
let tv = cv * time / (depth * depth).max(1e-30);
let u = degree_of_consolidation(tv);
let mv = self.biot_coeff / self.bulk_modulus.max(1e-30);
u * mv * sigma * depth
}
}
#[derive(Debug, Clone)]
pub struct Soil {
pub classification: SoilType,
pub void_ratio: f64,
pub liquid_limit: f64,
pub plastic_limit: f64,
pub water_content: f64,
pub cohesion: f64,
pub friction_angle: f64,
}
impl Soil {
pub fn new(
classification: SoilType,
void_ratio: f64,
liquid_limit: f64,
plastic_limit: f64,
water_content: f64,
cohesion: f64,
friction_angle_deg: f64,
) -> Self {
Self {
classification,
void_ratio,
liquid_limit,
plastic_limit,
water_content,
cohesion,
friction_angle: friction_angle_deg.to_radians(),
}
}
pub fn plasticity_index(&self) -> f64 {
self.liquid_limit - self.plastic_limit
}
pub fn compression_index(&self) -> f64 {
0.009 * (self.liquid_limit - 10.0)
}
pub fn preconsolidation_pressure(&self) -> f64 {
let li = (self.water_content - self.plastic_limit) / self.plasticity_index().max(1e-10);
let ocr_est = (2.0 - li.clamp(0.0, 2.0)).max(0.5);
let sigma_ref = 101_325.0; ocr_est * sigma_ref
}
pub fn permeability_hazen(&self, d10: f64) -> f64 {
let c = 0.01; c * d10 * d10
}
pub fn shear_strength_cu(&self) -> f64 {
let sigma_v = 100_000.0; let ip = self.plasticity_index();
(0.11 + 0.0037 * ip) * sigma_v
}
}
#[derive(Debug, Clone)]
pub struct Rockfill {
pub d50: f64,
pub cu: f64,
}
impl Rockfill {
pub fn new(d50: f64, cu: f64) -> Self {
Self { d50, cu }
}
pub fn friction_angle(&self) -> f64 {
let phi_deg = 38.0 + 2.0 * self.cu.max(1.0).log10();
phi_deg.to_radians()
}
pub fn stiffness(&self, sigma3: f64) -> f64 {
let pa = 101_325.0; let k_const = 300.0;
let n = 0.45;
k_const * pa * (sigma3 / pa).max(1e-6).powf(n)
}
}
pub fn cam_clay_yield(p: f64, q: f64, m: f64, p0: f64) -> f64 {
q * q / (m * m) - p * (p0 - p)
}
pub fn critical_state_line(p: f64, m: f64) -> f64 {
m * p
}
pub fn degree_of_consolidation(tv: f64) -> f64 {
if tv <= 0.0 {
return 0.0;
}
let mut u = 1.0f64;
for m in 0..15usize {
let n = (2 * m + 1) as f64;
u -= (8.0 / (n * n * PI * PI)) * (-(n * n * PI * PI * tv) / 4.0).exp();
}
u.clamp(0.0, 1.0)
}
#[cfg(test)]
mod tests {
use super::*;
fn granite() -> RockMaterial {
RockMaterial::new(
15e6, 45.0, 5e6, 200e6, 70e9, 0.25, )
}
#[test]
fn test_mohr_coulomb_zero_confinement() {
let rock = granite();
let sigma1 = rock.mohr_coulomb_strength(0.0);
let expected = 2.0 * 15e6 * (PI / 4.0 + PI / 8.0).tan();
assert!((sigma1 - expected).abs() / expected < 1e-10);
}
#[test]
fn test_mohr_coulomb_increases_with_confinement() {
let rock = granite();
let s1 = rock.mohr_coulomb_strength(10e6);
let s2 = rock.mohr_coulomb_strength(20e6);
assert!(s2 > s1);
}
#[test]
fn test_hoek_brown_gsi100_equals_intact() {
let rock = granite();
let sigma3 = 10e6;
let mi = 32.0; let sigma1 = rock.hoek_brown_strength(sigma3, mi, 100.0);
assert!(sigma1 > sigma3, "σ₁ should exceed σ₃");
}
#[test]
fn test_hoek_brown_lower_gsi_lower_strength() {
let rock = granite();
let sigma3 = 5e6;
let mi = 32.0;
let s100 = rock.hoek_brown_strength(sigma3, mi, 100.0);
let s50 = rock.hoek_brown_strength(sigma3, mi, 50.0);
assert!(s100 > s50);
}
#[test]
fn test_drucker_prager_yield_negative_elastic() {
let rock = granite();
let f = rock.drucker_prager_yield(100e6, 10e6);
assert!(f < 0.0);
}
#[test]
fn test_drucker_prager_yield_large_q_positive() {
let rock = granite();
let f = rock.drucker_prager_yield(1e6, 500e6);
assert!(f > 0.0);
}
#[test]
fn test_triaxial_deformation_elastic() {
let rock = granite();
let (eps1, _) = rock.triaxial_deformation(200e6, 10e6);
let expected = (200e6 - 2.0 * 0.25 * 10e6) / 70e9;
assert!((eps1 - expected).abs() / expected.abs() < 1e-10);
}
#[test]
fn test_triaxial_volumetric_strain() {
let rock = granite();
let (_, ev) = rock.triaxial_deformation(200e6, 10e6);
assert!(ev.is_finite());
}
fn sandstone() -> PoroelasticMaterial {
PoroelasticMaterial::new(
0.7, 10e9, 0.20, 1e-14, 2.2e9, 1e-3, )
}
#[test]
fn test_biot_modulus_positive() {
let m = sandstone().biot_modulus();
assert!(m > 0.0);
}
#[test]
fn test_undrained_bulk_gt_drained() {
let s = sandstone();
assert!(s.undrained_bulk_modulus() > s.bulk_modulus);
}
#[test]
fn test_skempton_b_between_0_and_1() {
let b = sandstone().skempton_b();
assert!((0.0..=1.0).contains(&b), "B = {b}");
}
#[test]
fn test_consolidation_coefficient_positive() {
let cv = sandstone().consolidation_coefficient();
assert!(cv > 0.0);
}
#[test]
fn test_terzaghi_settlement_zero_time() {
let s = sandstone().terzaghi_settlement(100e3, 10.0, 0.0);
assert_eq!(s, 0.0);
}
#[test]
fn test_terzaghi_settlement_increases_with_time() {
let mat = sandstone();
let s1 = mat.terzaghi_settlement(100e3, 10.0, 1e6);
let s2 = mat.terzaghi_settlement(100e3, 10.0, 1e10);
assert!(s2 >= s1);
}
fn london_clay() -> Soil {
Soil::new(SoilType::Clay, 0.9, 70.0, 28.0, 45.0, 20e3, 25.0)
}
#[test]
fn test_plasticity_index() {
let s = london_clay();
assert!((s.plasticity_index() - 42.0).abs() < 1e-10);
}
#[test]
fn test_compression_index() {
let s = london_clay();
assert!((s.compression_index() - 0.54).abs() < 1e-10);
}
#[test]
fn test_preconsolidation_positive() {
let p = london_clay().preconsolidation_pressure();
assert!(p > 0.0);
}
#[test]
fn test_hazen_permeability() {
let s = london_clay();
let k = s.permeability_hazen(0.0001); assert!(k > 0.0 && k < 1.0);
}
#[test]
fn test_shear_strength_cu_positive() {
let cu = london_clay().shear_strength_cu();
assert!(cu > 0.0);
}
#[test]
fn test_soil_type_variants() {
let types = [
SoilType::Clay,
SoilType::Sand,
SoilType::Gravel,
SoilType::Silt,
SoilType::Peat,
];
assert_eq!(types.len(), 5);
}
fn dam_fill() -> Rockfill {
Rockfill::new(0.2, 10.0) }
#[test]
fn test_rockfill_friction_angle_in_range() {
let phi = dam_fill().friction_angle();
assert!(phi > 0.5 && phi < 1.2);
}
#[test]
fn test_rockfill_stiffness_positive() {
let e = dam_fill().stiffness(200e3);
assert!(e > 0.0);
}
#[test]
fn test_rockfill_stiffness_increases_with_confinement() {
let fill = dam_fill();
let e1 = fill.stiffness(100e3);
let e2 = fill.stiffness(400e3);
assert!(e2 > e1);
}
#[test]
fn test_cam_clay_yield_at_csl() {
let p = 100e3;
let m = 1.2;
let p0 = 2.0 * p;
let q = m * p; let f = cam_clay_yield(p, q, m, p0);
assert!(f.abs() < 1e-6);
}
#[test]
fn test_cam_clay_yield_inside_negative() {
let p = 50e3;
let m = 1.2;
let p0 = 200e3;
let q = 10e3; let f = cam_clay_yield(p, q, m, p0);
assert!(f < 0.0);
}
#[test]
fn test_cam_clay_yield_outside_positive() {
let p = 50e3;
let m = 1.2;
let p0 = 200e3;
let q = 200e3; let f = cam_clay_yield(p, q, m, p0);
assert!(f > 0.0);
}
#[test]
fn test_csl_proportional() {
let p = 200e3;
let m = 1.2;
assert!((critical_state_line(p, m) - m * p).abs() < 1e-10);
}
#[test]
fn test_csl_zero_p() {
assert_eq!(critical_state_line(0.0, 1.2), 0.0);
}
#[test]
fn test_consolidation_zero_tv() {
let u = degree_of_consolidation(0.0);
assert!(u < 0.01, "U(Tv=0) should be ~0");
}
#[test]
fn test_consolidation_large_tv() {
let u = degree_of_consolidation(10.0);
assert!((u - 1.0).abs() < 1e-6);
}
#[test]
fn test_consolidation_monotone() {
let u1 = degree_of_consolidation(0.1);
let u2 = degree_of_consolidation(0.5);
let u3 = degree_of_consolidation(1.0);
assert!(u1 < u2 && u2 < u3);
}
#[test]
fn test_consolidation_half_tv_approx() {
let u = degree_of_consolidation(0.197);
assert!(u > 0.45 && u < 0.55, "U(0.197) ≈ 0.5, got {u}");
}
}