196 lines
5.8 KiB
Rust
196 lines
5.8 KiB
Rust
//! 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);
|
||
}
|
||
}
|