use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum BoundaryType {
Divergent,
Convergent,
Transform,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct EulerPole {
pub latitude_deg: f64,
pub longitude_deg: f64,
pub omega_deg_per_myr: f64,
}
impl EulerPole {
#[must_use]
pub fn velocity_mm_yr(&self, angular_distance_deg: f64) -> f64 {
let earth_radius_km = 6371.0;
let omega_rad_per_yr = self.omega_deg_per_myr.to_radians() / 1e6;
let delta_rad = angular_distance_deg.to_radians();
omega_rad_per_yr * earth_radius_km * delta_rad.sin() * 1e6
}
#[must_use]
pub fn max_velocity_mm_yr(&self) -> f64 {
self.velocity_mm_yr(90.0)
}
}
#[must_use]
pub fn full_spreading_rate(half_rate_mm_yr: f64) -> f64 {
2.0 * half_rate_mm_yr
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum RidgeType {
UltraSlow,
Slow,
Intermediate,
Fast,
UltraFast,
}
#[must_use]
pub fn classify_ridge(full_rate_mm_yr: f64) -> RidgeType {
if full_rate_mm_yr < 20.0 {
RidgeType::UltraSlow
} else if full_rate_mm_yr < 55.0 {
RidgeType::Slow
} else if full_rate_mm_yr < 75.0 {
RidgeType::Intermediate
} else if full_rate_mm_yr < 150.0 {
RidgeType::Fast
} else {
RidgeType::UltraFast
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct SubductionZone {
pub dip_deg: f64,
pub convergence_rate_mm_yr: f64,
pub plate_age_ma: f64,
}
impl SubductionZone {
#[must_use]
pub fn slab_depth_km(&self, distance_from_trench_km: f64) -> f64 {
distance_from_trench_km * self.dip_deg.to_radians().tan()
}
#[must_use]
pub fn estimated_dip_from_age(plate_age_ma: f64) -> f64 {
(30.0 + 0.3 * plate_age_ma).min(80.0)
}
}
#[must_use]
pub fn ocean_floor_age(distance_km: f64, half_rate_mm_yr: f64) -> f64 {
if half_rate_mm_yr <= 0.0 {
return 0.0;
}
distance_km * 1e6 / half_rate_mm_yr / 1e6
}
#[must_use]
pub fn lithosphere_thickness(age_ma: f64) -> f64 {
if age_ma <= 0.0 {
return 0.0;
}
10.0 * age_ma.sqrt()
}
#[must_use]
pub fn ocean_depth_m(age_ma: f64) -> f64 {
if age_ma <= 0.0 {
return 2500.0; }
2500.0 + 350.0 * age_ma.sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn euler_pole_velocity_at_equator() {
let pole = EulerPole {
latitude_deg: 0.0,
longitude_deg: 0.0,
omega_deg_per_myr: 1.0,
};
let v = pole.velocity_mm_yr(90.0);
assert!(v > 100.0 && v < 120.0, "Expected ~111 mm/yr, got {v}");
}
#[test]
fn euler_pole_zero_at_pole() {
let pole = EulerPole {
latitude_deg: 90.0,
longitude_deg: 0.0,
omega_deg_per_myr: 1.0,
};
let v = pole.velocity_mm_yr(0.0);
assert!(v.abs() < 0.01);
}
#[test]
fn spreading_rate_classification() {
assert_eq!(classify_ridge(10.0), RidgeType::UltraSlow);
assert_eq!(classify_ridge(30.0), RidgeType::Slow);
assert_eq!(classify_ridge(60.0), RidgeType::Intermediate);
assert_eq!(classify_ridge(100.0), RidgeType::Fast);
assert_eq!(classify_ridge(160.0), RidgeType::UltraFast);
}
#[test]
fn mid_atlantic_ridge_is_slow() {
let full = full_spreading_rate(12.0);
assert_eq!(classify_ridge(full), RidgeType::Slow);
}
#[test]
fn slab_depth_increases_with_distance() {
let sz = SubductionZone {
dip_deg: 45.0,
convergence_rate_mm_yr: 80.0,
plate_age_ma: 100.0,
};
let shallow = sz.slab_depth_km(50.0);
let deep = sz.slab_depth_km(200.0);
assert!(deep > shallow);
}
#[test]
fn slab_depth_45_degrees() {
let sz = SubductionZone {
dip_deg: 45.0,
convergence_rate_mm_yr: 80.0,
plate_age_ma: 100.0,
};
let depth = sz.slab_depth_km(100.0);
assert!(
(depth - 100.0).abs() < 0.1,
"45° dip at 100km → ~100km depth, got {depth}"
);
}
#[test]
fn older_plates_dip_steeper() {
let young = SubductionZone::estimated_dip_from_age(10.0);
let old = SubductionZone::estimated_dip_from_age(100.0);
assert!(old > young);
}
#[test]
fn dip_capped_at_80() {
let very_old = SubductionZone::estimated_dip_from_age(500.0);
assert!((very_old - 80.0).abs() < 0.01);
}
#[test]
fn ocean_floor_age_basic() {
let age = ocean_floor_age(500.0, 25.0);
assert!((age - 20.0).abs() < 0.1);
}
#[test]
fn lithosphere_thickens_with_age() {
let young = lithosphere_thickness(10.0);
let old = lithosphere_thickness(100.0);
assert!(old > young);
assert!((old - 100.0).abs() < 1.0);
}
#[test]
fn ocean_deepens_with_age() {
let ridge = ocean_depth_m(0.0);
let old = ocean_depth_m(100.0);
assert!(old > ridge);
assert!((ridge - 2500.0).abs() < 1.0);
}
}