Files
rustytorch/crates/specialized/rtx-materials/src/elastic.rs
T
2026-03-04 00:08:42 +00:00

196 lines
5.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Linear elastic material models.
//!
//! Provides linear elastic material properties commonly used in finite element analysis.
use serde::{Deserialize, Serialize};
/// Linear elastic material properties.
///
/// This is a simple isotropic elastic material defined by Young's modulus
/// and Poisson's ratio.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct LinearElastic {
/// Young's modulus (E) in Pa
pub youngs_modulus: f64,
/// Poisson's ratio (ν), typically 0.0 to 0.5
pub poissons_ratio: f64,
/// Density in kg/m³
pub density: f64,
}
impl LinearElastic {
/// Create a new linear elastic material.
///
/// # Arguments
/// * `youngs_modulus` - Young's modulus (E) in Pa
/// * `poissons_ratio` - Poisson's ratio (ν)
/// * `density` - Density in kg/m³
///
/// # Panics
/// Panics if Poisson's ratio is outside [0, 0.5] or if E or density are negative.
pub fn new(youngs_modulus: f64, poissons_ratio: f64, density: f64) -> Self {
assert!(
youngs_modulus >= 0.0,
"Young's modulus must be non-negative"
);
assert!(
(0.0..=0.5).contains(&poissons_ratio),
"Poisson's ratio must be between 0 and 0.5"
);
assert!(density >= 0.0, "Density must be non-negative");
Self {
youngs_modulus,
poissons_ratio,
density,
}
}
/// Create a material from shear modulus (G) and bulk modulus (K).
pub fn from_shear_bulk(shear_modulus: f64, bulk_modulus: f64, density: f64) -> Self {
let youngs_modulus =
9.0 * bulk_modulus * shear_modulus / (3.0 * bulk_modulus + shear_modulus);
let poissons_ratio =
(3.0 * bulk_modulus - 2.0 * shear_modulus) / (6.0 * bulk_modulus + 2.0 * shear_modulus);
Self::new(youngs_modulus, poissons_ratio, density)
}
/// Create a material from Lamé parameters (λ, μ).
pub fn from_lame(lambda: f64, mu: f64, density: f64) -> Self {
let youngs_modulus = mu * (3.0 * lambda + 2.0 * mu) / (lambda + mu);
let poissons_ratio = lambda / (2.0 * (lambda + mu));
Self::new(youngs_modulus, poissons_ratio, density)
}
/// Calculate shear modulus (G or μ).
pub fn shear_modulus(&self) -> f64 {
self.youngs_modulus / (2.0 * (1.0 + self.poissons_ratio))
}
/// Calculate bulk modulus (K).
pub fn bulk_modulus(&self) -> f64 {
self.youngs_modulus / (3.0 * (1.0 - 2.0 * self.poissons_ratio))
}
/// Calculate first Lamé parameter (λ).
pub fn lame_lambda(&self) -> f64 {
let e = self.youngs_modulus;
let nu = self.poissons_ratio;
e * nu / ((1.0 + nu) * (1.0 - 2.0 * nu))
}
/// Calculate second Lamé parameter (μ), same as shear modulus.
pub fn lame_mu(&self) -> f64 {
self.shear_modulus()
}
/// Calculate P-wave modulus (M = K + 4G/3).
pub fn p_wave_modulus(&self) -> f64 {
self.bulk_modulus() + 4.0 * self.shear_modulus() / 3.0
}
/// Calculate P-wave velocity.
pub fn p_wave_velocity(&self) -> f64 {
(self.p_wave_modulus() / self.density).sqrt()
}
/// Calculate S-wave velocity.
pub fn s_wave_velocity(&self) -> f64 {
(self.shear_modulus() / self.density).sqrt()
}
}
impl Default for LinearElastic {
fn default() -> Self {
// Typical soft tissue properties
Self {
youngs_modulus: 3000.0, // 3 kPa
poissons_ratio: 0.49, // Nearly incompressible
density: 1000.0, // ~water density
}
}
}
/// Common material presets
impl LinearElastic {
/// Brain tissue (approximate)
pub fn brain_tissue() -> Self {
Self::new(3000.0, 0.49, 1040.0)
}
/// Cortical bone
pub fn cortical_bone() -> Self {
Self::new(17e9, 0.3, 1900.0)
}
/// Cancellous bone
pub fn cancellous_bone() -> Self {
Self::new(1e9, 0.3, 600.0)
}
/// Soft tissue (general)
pub fn soft_tissue() -> Self {
Self::new(5000.0, 0.49, 1000.0)
}
/// Steel
pub fn steel() -> Self {
Self::new(200e9, 0.3, 7850.0)
}
/// Aluminum
pub fn aluminum() -> Self {
Self::new(70e9, 0.33, 2700.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_elastic_creation() {
let mat = LinearElastic::new(200e9, 0.3, 7850.0);
assert_eq!(mat.youngs_modulus, 200e9);
assert_eq!(mat.poissons_ratio, 0.3);
assert_eq!(mat.density, 7850.0);
}
#[test]
fn test_shear_modulus() {
let mat = LinearElastic::new(200e9, 0.3, 7850.0);
let g = mat.shear_modulus();
// G = E / (2 * (1 + ν)) = 200e9 / (2 * 1.3) ≈ 76.92e9
assert!((g - 76.92307692307693e9).abs() < 1e6);
}
#[test]
fn test_bulk_modulus() {
let mat = LinearElastic::new(200e9, 0.3, 7850.0);
let k = mat.bulk_modulus();
// K = E / (3 * (1 - 2ν)) = 200e9 / (3 * 0.4) ≈ 166.67e9
assert!((k - 166.66666666666666e9).abs() < 1e6);
}
#[test]
fn test_from_shear_bulk() {
let g = 76.92307692307693e9;
let k = 166.66666666666666e9;
let mat = LinearElastic::from_shear_bulk(g, k, 7850.0);
assert!((mat.youngs_modulus - 200e9).abs() < 1e6);
assert!((mat.poissons_ratio - 0.3).abs() < 0.001);
}
#[test]
fn test_lame_parameters() {
let mat = LinearElastic::new(200e9, 0.3, 7850.0);
let lambda = mat.lame_lambda();
let mu = mat.lame_mu();
// Verify round-trip
let mat2 = LinearElastic::from_lame(lambda, mu, 7850.0);
assert!((mat2.youngs_modulus - mat.youngs_modulus).abs() < 1e3);
assert!((mat2.poissons_ratio - mat.poissons_ratio).abs() < 0.0001);
}
}