#![allow(missing_docs)]
#![allow(dead_code)]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PyElasticMaterial {
pub youngs_modulus: f64,
pub poisson_ratio: f64,
pub density: f64,
}
impl PyElasticMaterial {
pub fn new(youngs_modulus: f64, poisson_ratio: f64, density: f64) -> Self {
Self {
youngs_modulus,
poisson_ratio,
density,
}
}
pub fn stress_from_strain(&self, strain: [f64; 3]) -> [f64; 3] {
let e = self.youngs_modulus;
let nu = self.poisson_ratio;
let c = e / (1.0 - nu * nu);
[
c * (strain[0] + nu * strain[1]),
c * (nu * strain[0] + strain[1]),
c * (1.0 - nu) * 0.5 * strain[2],
]
}
pub fn strain_energy_density(&self, strain: [f64; 3]) -> f64 {
let stress = self.stress_from_strain(strain);
0.5 * (stress[0] * strain[0] + stress[1] * strain[1] + stress[2] * strain[2])
}
pub fn shear_modulus(&self) -> f64 {
self.youngs_modulus / (2.0 * (1.0 + self.poisson_ratio))
}
pub fn bulk_modulus(&self) -> f64 {
self.youngs_modulus / (3.0 * (1.0 - 2.0 * self.poisson_ratio))
}
}
impl Default for PyElasticMaterial {
fn default() -> Self {
Self::new(200e9, 0.3, 7850.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PyHyperelasticMaterial {
pub c1: f64,
pub c2: f64,
pub bulk_modulus: f64,
}
impl PyHyperelasticMaterial {
pub fn new(c1: f64, c2: f64, bulk_modulus: f64) -> Self {
Self {
c1,
c2,
bulk_modulus,
}
}
pub fn strain_energy_neo_hookean(&self, i1_bar: f64, j: f64) -> f64 {
self.c1 * (i1_bar - 3.0) + 0.5 * self.bulk_modulus * (j - 1.0).powi(2)
}
pub fn strain_energy_mooney_rivlin(&self, i1_bar: f64, i2_bar: f64, j: f64) -> f64 {
self.c1 * (i1_bar - 3.0)
+ self.c2 * (i2_bar - 3.0)
+ 0.5 * self.bulk_modulus * (j - 1.0).powi(2)
}
pub fn initial_shear_modulus(&self) -> f64 {
2.0 * (self.c1 + self.c2)
}
}
impl Default for PyHyperelasticMaterial {
fn default() -> Self {
Self::new(0.4e6, 0.1e6, 2.0e9)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PyPlasticMaterial {
pub yield_stress: f64,
pub hardening_modulus: f64,
pub youngs_modulus: f64,
}
impl PyPlasticMaterial {
pub fn new(yield_stress: f64, hardening_modulus: f64, youngs_modulus: f64) -> Self {
Self {
yield_stress,
hardening_modulus,
youngs_modulus,
}
}
pub fn yield_function(&self, equivalent_stress: f64, accumulated_plastic_strain: f64) -> f64 {
let hardened_yield =
self.yield_stress + self.hardening_modulus * accumulated_plastic_strain;
equivalent_stress - hardened_yield
}
pub fn current_yield_stress(&self, accumulated_plastic_strain: f64) -> f64 {
self.yield_stress + self.hardening_modulus * accumulated_plastic_strain
}
}
impl Default for PyPlasticMaterial {
fn default() -> Self {
Self::new(250e6, 10e9, 200e9)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PyFatigueMaterial {
pub basquin_coefficient: f64,
pub basquin_exponent: f64,
pub endurance_limit: f64,
}
impl PyFatigueMaterial {
pub fn new(basquin_coefficient: f64, basquin_exponent: f64, endurance_limit: f64) -> Self {
Self {
basquin_coefficient,
basquin_exponent,
endurance_limit,
}
}
pub fn cycles_to_failure(&self, stress_amplitude: f64) -> f64 {
if stress_amplitude <= self.endurance_limit {
return f64::INFINITY;
}
(self.basquin_coefficient / stress_amplitude).powf(1.0 / self.basquin_exponent)
}
pub fn miner_damage(&self, n_cycles: f64, stress_amplitude: f64) -> f64 {
let nf = self.cycles_to_failure(stress_amplitude);
if nf.is_infinite() { 0.0 } else { n_cycles / nf }
}
}
impl Default for PyFatigueMaterial {
fn default() -> Self {
Self::new(900e6, 0.1, 200e6)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PyThermalMaterial {
pub conductivity: f64,
pub specific_heat: f64,
pub density: f64,
}
impl PyThermalMaterial {
pub fn new(conductivity: f64, specific_heat: f64, density: f64) -> Self {
Self {
conductivity,
specific_heat,
density,
}
}
pub fn thermal_diffusivity(&self) -> f64 {
self.conductivity / (self.density * self.specific_heat)
}
pub fn heat_flux(&self, grad_t_magnitude: f64) -> f64 {
self.conductivity * grad_t_magnitude
}
}
impl Default for PyThermalMaterial {
fn default() -> Self {
Self::new(50.0, 480.0, 7850.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PyViscoelasticMaterial {
pub equilibrium_modulus: f64,
pub prony_moduli: Vec<f64>,
pub prony_times: Vec<f64>,
}
impl PyViscoelasticMaterial {
pub fn new(equilibrium_modulus: f64, prony_moduli: Vec<f64>, prony_times: Vec<f64>) -> Self {
Self {
equilibrium_modulus,
prony_moduli,
prony_times,
}
}
pub fn relaxation_modulus(&self, t: f64) -> f64 {
let sum: f64 = self
.prony_moduli
.iter()
.zip(self.prony_times.iter())
.map(|(&ei, &ti)| ei * (-t / ti).exp())
.sum();
self.equilibrium_modulus + sum
}
pub fn glassy_modulus(&self) -> f64 {
self.equilibrium_modulus + self.prony_moduli.iter().sum::<f64>()
}
}
impl Default for PyViscoelasticMaterial {
fn default() -> Self {
Self::new(1e6, vec![2e6, 1e6], vec![0.1, 1.0])
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PyDamageMaterial {
pub critical_energy_release: f64,
pub softening_slope: f64,
pub undamaged_modulus: f64,
}
impl PyDamageMaterial {
pub fn new(critical_energy_release: f64, softening_slope: f64, undamaged_modulus: f64) -> Self {
Self {
critical_energy_release,
softening_slope,
undamaged_modulus,
}
}
pub fn damage_variable(&self, strain_energy: f64) -> f64 {
if strain_energy <= 0.0 {
return 0.0;
}
let d = (strain_energy - self.critical_energy_release)
/ (self.softening_slope.abs() + strain_energy);
d.clamp(0.0, 1.0)
}
pub fn effective_modulus(&self, strain_energy: f64) -> f64 {
let d = self.damage_variable(strain_energy);
(1.0 - d) * self.undamaged_modulus
}
}
impl Default for PyDamageMaterial {
fn default() -> Self {
Self::new(1000.0, 1e8, 200e9)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PyCreepMaterial {
pub norton_coefficient: f64,
pub norton_exponent: f64,
pub activation_temperature: f64,
}
impl PyCreepMaterial {
pub fn new(norton_coefficient: f64, norton_exponent: f64, activation_temperature: f64) -> Self {
Self {
norton_coefficient,
norton_exponent,
activation_temperature,
}
}
pub fn creep_rate(&self, stress: f64, temp: f64) -> f64 {
let arrhenius = (-self.activation_temperature / temp).exp();
self.norton_coefficient * stress.powf(self.norton_exponent) * arrhenius
}
pub fn reference_creep_rate(&self, stress: f64) -> f64 {
self.creep_rate(stress, 293.0)
}
}
impl Default for PyCreepMaterial {
fn default() -> Self {
Self::new(1e-26, 5.0, 15_000.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PyAcousticMaterial {
pub bulk_modulus: f64,
pub density: f64,
}
impl PyAcousticMaterial {
pub fn new(bulk_modulus: f64, density: f64) -> Self {
Self {
bulk_modulus,
density,
}
}
pub fn speed_of_sound(&self) -> f64 {
(self.bulk_modulus / self.density).sqrt()
}
pub fn impedance(&self) -> f64 {
self.density * self.speed_of_sound()
}
pub fn reflection_coefficient(&self, other: &PyAcousticMaterial) -> f64 {
let z1 = self.impedance();
let z2 = other.impedance();
(z2 - z1) / (z2 + z1)
}
}
impl Default for PyAcousticMaterial {
fn default() -> Self {
Self::new(2.2e9, 998.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PyCompositeMaterial {
pub modulus1: f64,
pub modulus2: f64,
pub volume_fraction1: f64,
}
impl PyCompositeMaterial {
pub fn new(modulus1: f64, modulus2: f64, volume_fraction1: f64) -> Self {
Self {
modulus1,
modulus2,
volume_fraction1,
}
}
pub fn voigt_modulus(&self, vf1: f64, e1: f64, e2: f64) -> f64 {
vf1 * e1 + (1.0 - vf1) * e2
}
pub fn reuss_modulus(&self, vf1: f64, e1: f64, e2: f64) -> f64 {
let vf2 = 1.0 - vf1;
if vf1 / e1 + vf2 / e2 == 0.0 {
0.0
} else {
1.0 / (vf1 / e1 + vf2 / e2)
}
}
pub fn hill_modulus(&self) -> f64 {
let v = self.voigt_modulus(self.volume_fraction1, self.modulus1, self.modulus2);
let r = self.reuss_modulus(self.volume_fraction1, self.modulus1, self.modulus2);
0.5 * (v + r)
}
pub fn hashin_shtrikman_lower(&self) -> f64 {
let vf2 = 1.0 - self.volume_fraction1;
let e1 = self.modulus1.min(self.modulus2);
let e2 = self.modulus1.max(self.modulus2);
e1 + vf2 / (1.0 / (e2 - e1) + self.volume_fraction1 / (3.0 * e1))
}
}
impl Default for PyCompositeMaterial {
fn default() -> Self {
Self::new(70e9, 200e9, 0.6)
}
}
pub fn register_materials_module(_m: &str) {
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_elastic_new() {
let mat = PyElasticMaterial::new(200e9, 0.3, 7850.0);
assert_eq!(mat.youngs_modulus, 200e9);
assert_eq!(mat.poisson_ratio, 0.3);
assert_eq!(mat.density, 7850.0);
}
#[test]
fn test_elastic_default() {
let mat = PyElasticMaterial::default();
assert!(mat.youngs_modulus > 0.0);
}
#[test]
fn test_elastic_shear_modulus() {
let mat = PyElasticMaterial::new(200e9, 0.3, 7850.0);
let g = mat.shear_modulus();
assert!((g - 76.923e9).abs() < 1e8);
}
#[test]
fn test_elastic_bulk_modulus() {
let mat = PyElasticMaterial::new(200e9, 0.3, 7850.0);
let k = mat.bulk_modulus();
assert!((k - 166.667e9).abs() < 1e9);
}
#[test]
fn test_elastic_stress_from_strain_uniaxial() {
let mat = PyElasticMaterial::new(200e9, 0.3, 7850.0);
let strain = [1e-3, 0.0, 0.0];
let stress = mat.stress_from_strain(strain);
assert!(stress[0] > 0.0);
assert!(stress[2].abs() < 1.0);
}
#[test]
fn test_elastic_strain_energy_positive() {
let mat = PyElasticMaterial::new(200e9, 0.3, 7850.0);
let e = mat.strain_energy_density([1e-3, 0.0, 0.0]);
assert!(e > 0.0);
}
#[test]
fn test_elastic_zero_strain_zero_stress() {
let mat = PyElasticMaterial::default();
let s = mat.stress_from_strain([0.0, 0.0, 0.0]);
assert_eq!(s, [0.0, 0.0, 0.0]);
}
#[test]
fn test_hyperelastic_new() {
let mat = PyHyperelasticMaterial::new(0.4e6, 0.1e6, 2.0e9);
assert_eq!(mat.c1, 0.4e6);
assert_eq!(mat.c2, 0.1e6);
assert_eq!(mat.bulk_modulus, 2.0e9);
}
#[test]
fn test_hyperelastic_neo_hookean_zero() {
let mat = PyHyperelasticMaterial::default();
let w = mat.strain_energy_neo_hookean(3.0, 1.0);
assert!(w.abs() < 1e-10);
}
#[test]
fn test_hyperelastic_mooney_rivlin_zero() {
let mat = PyHyperelasticMaterial::default();
let w = mat.strain_energy_mooney_rivlin(3.0, 3.0, 1.0);
assert!(w.abs() < 1e-10);
}
#[test]
fn test_hyperelastic_neo_hookean_positive_stretch() {
let mat = PyHyperelasticMaterial::new(0.4e6, 0.0, 2.0e9);
let w = mat.strain_energy_neo_hookean(4.0, 1.0);
assert!(w > 0.0);
}
#[test]
fn test_hyperelastic_initial_shear_modulus() {
let mat = PyHyperelasticMaterial::new(0.4e6, 0.1e6, 2.0e9);
let mu = mat.initial_shear_modulus();
assert!((mu - 1.0e6).abs() < 1.0);
}
#[test]
fn test_hyperelastic_default() {
let mat = PyHyperelasticMaterial::default();
assert!(mat.c1 > 0.0);
assert!(mat.bulk_modulus > 0.0);
}
#[test]
fn test_plastic_new() {
let mat = PyPlasticMaterial::new(250e6, 10e9, 200e9);
assert_eq!(mat.yield_stress, 250e6);
}
#[test]
fn test_plastic_yield_function_elastic() {
let mat = PyPlasticMaterial::new(250e6, 10e9, 200e9);
let f = mat.yield_function(200e6, 0.0);
assert!(f < 0.0);
}
#[test]
fn test_plastic_yield_function_yielding() {
let mat = PyPlasticMaterial::new(250e6, 10e9, 200e9);
let f = mat.yield_function(300e6, 0.0);
assert!(f > 0.0);
}
#[test]
fn test_plastic_hardening_increases_yield() {
let mat = PyPlasticMaterial::new(250e6, 10e9, 200e9);
let y0 = mat.current_yield_stress(0.0);
let y1 = mat.current_yield_stress(0.01);
assert!(y1 > y0);
}
#[test]
fn test_plastic_default() {
let mat = PyPlasticMaterial::default();
assert!(mat.yield_stress > 0.0);
}
#[test]
fn test_fatigue_new() {
let mat = PyFatigueMaterial::new(900e6, 0.1, 200e6);
assert_eq!(mat.basquin_coefficient, 900e6);
}
#[test]
fn test_fatigue_below_endurance_infinite_life() {
let mat = PyFatigueMaterial::default();
let n = mat.cycles_to_failure(100e6); assert!(n.is_infinite());
}
#[test]
fn test_fatigue_above_endurance_finite_life() {
let mat = PyFatigueMaterial::default();
let n = mat.cycles_to_failure(500e6);
assert!(n.is_finite() && n > 0.0);
}
#[test]
fn test_fatigue_miner_damage_zero_below_endurance() {
let mat = PyFatigueMaterial::default();
let d = mat.miner_damage(1000.0, 100e6);
assert_eq!(d, 0.0);
}
#[test]
fn test_fatigue_miner_damage_positive_above_endurance() {
let mat = PyFatigueMaterial::default();
let d = mat.miner_damage(1000.0, 500e6);
assert!(d > 0.0);
}
#[test]
fn test_fatigue_default() {
let mat = PyFatigueMaterial::default();
assert!(mat.endurance_limit > 0.0);
}
#[test]
fn test_thermal_new() {
let mat = PyThermalMaterial::new(50.0, 480.0, 7850.0);
assert_eq!(mat.conductivity, 50.0);
}
#[test]
fn test_thermal_diffusivity() {
let mat = PyThermalMaterial::new(50.0, 480.0, 7850.0);
let alpha = mat.thermal_diffusivity();
assert!(alpha > 0.0);
assert!((alpha - 1.328e-5).abs() < 1e-7);
}
#[test]
fn test_thermal_heat_flux() {
let mat = PyThermalMaterial::default();
let q = mat.heat_flux(10.0); assert!((q - 500.0).abs() < 1e-6);
}
#[test]
fn test_thermal_default() {
let mat = PyThermalMaterial::default();
assert!(mat.specific_heat > 0.0);
}
#[test]
fn test_viscoelastic_new() {
let mat = PyViscoelasticMaterial::new(1e6, vec![2e6], vec![0.1]);
assert_eq!(mat.equilibrium_modulus, 1e6);
}
#[test]
fn test_viscoelastic_relaxation_t0() {
let mat = PyViscoelasticMaterial::default();
let e0 = mat.relaxation_modulus(0.0);
let eg = mat.glassy_modulus();
assert!((e0 - eg).abs() < 1.0);
}
#[test]
fn test_viscoelastic_relaxation_large_t() {
let mat = PyViscoelasticMaterial::default();
let einf = mat.relaxation_modulus(1e12);
assert!((einf - mat.equilibrium_modulus).abs() < 1.0);
}
#[test]
fn test_viscoelastic_glassy_modulus() {
let mat = PyViscoelasticMaterial::new(1e6, vec![2e6, 1e6], vec![0.1, 1.0]);
assert!((mat.glassy_modulus() - 4e6).abs() < 1.0);
}
#[test]
fn test_viscoelastic_default() {
let mat = PyViscoelasticMaterial::default();
assert!(mat.prony_moduli.len() == mat.prony_times.len());
}
#[test]
fn test_damage_new() {
let mat = PyDamageMaterial::new(1000.0, 1e8, 200e9);
assert_eq!(mat.critical_energy_release, 1000.0);
}
#[test]
fn test_damage_zero_strain_energy() {
let mat = PyDamageMaterial::default();
assert_eq!(mat.damage_variable(0.0), 0.0);
}
#[test]
fn test_damage_below_critical() {
let mat = PyDamageMaterial::new(1000.0, 1e8, 200e9);
assert_eq!(mat.damage_variable(500.0), 0.0);
}
#[test]
fn test_damage_above_critical() {
let mat = PyDamageMaterial::new(100.0, 1e8, 200e9);
let d = mat.damage_variable(1000.0);
assert!(d > 0.0 && d <= 1.0);
}
#[test]
fn test_damage_effective_modulus_undamaged() {
let mat = PyDamageMaterial::new(1000.0, 1e8, 200e9);
let e = mat.effective_modulus(0.0);
assert!((e - 200e9).abs() < 1.0);
}
#[test]
fn test_damage_default() {
let mat = PyDamageMaterial::default();
assert!(mat.undamaged_modulus > 0.0);
}
#[test]
fn test_creep_new() {
let mat = PyCreepMaterial::new(1e-26, 5.0, 15_000.0);
assert_eq!(mat.norton_exponent, 5.0);
}
#[test]
fn test_creep_rate_positive() {
let mat = PyCreepMaterial::default();
let rate = mat.creep_rate(100e6, 800.0);
assert!(rate >= 0.0);
}
#[test]
fn test_creep_rate_increases_with_stress() {
let mat = PyCreepMaterial::default();
let r1 = mat.creep_rate(100e6, 800.0);
let r2 = mat.creep_rate(200e6, 800.0);
assert!(r2 > r1);
}
#[test]
fn test_creep_rate_decreases_with_temperature_drop() {
let mat = PyCreepMaterial::default();
let r_hot = mat.creep_rate(100e6, 1000.0);
let r_cold = mat.creep_rate(100e6, 500.0);
assert!(r_hot > r_cold);
}
#[test]
fn test_creep_reference_rate() {
let mat = PyCreepMaterial::default();
let r = mat.reference_creep_rate(100e6);
assert!(r >= 0.0);
}
#[test]
fn test_acoustic_new() {
let mat = PyAcousticMaterial::new(2.2e9, 998.0);
assert_eq!(mat.density, 998.0);
}
#[test]
fn test_acoustic_speed_of_sound_water() {
let mat = PyAcousticMaterial::default();
let c = mat.speed_of_sound();
assert!((c - 1484.0).abs() < 10.0);
}
#[test]
fn test_acoustic_impedance_positive() {
let mat = PyAcousticMaterial::default();
assert!(mat.impedance() > 0.0);
}
#[test]
fn test_acoustic_reflection_same_material() {
let mat = PyAcousticMaterial::default();
let r = mat.reflection_coefficient(&mat.clone());
assert!(r.abs() < 1e-10);
}
#[test]
fn test_acoustic_default() {
let mat = PyAcousticMaterial::default();
assert!(mat.bulk_modulus > 0.0);
}
#[test]
fn test_composite_new() {
let mat = PyCompositeMaterial::new(70e9, 200e9, 0.6);
assert_eq!(mat.volume_fraction1, 0.6);
}
#[test]
fn test_composite_voigt_bounds() {
let mat = PyCompositeMaterial::default();
let v = mat.voigt_modulus(0.6, 70e9, 200e9);
assert!((70e9..=200e9).contains(&v));
}
#[test]
fn test_composite_reuss_bounds() {
let mat = PyCompositeMaterial::default();
let r = mat.reuss_modulus(0.6, 70e9, 200e9);
assert!((70e9..=200e9).contains(&r));
}
#[test]
fn test_composite_voigt_ge_reuss() {
let mat = PyCompositeMaterial::default();
let v = mat.voigt_modulus(0.6, 70e9, 200e9);
let r = mat.reuss_modulus(0.6, 70e9, 200e9);
assert!(v >= r);
}
#[test]
fn test_composite_hill_between_voigt_reuss() {
let mat = PyCompositeMaterial::default();
let h = mat.hill_modulus();
let v = mat.voigt_modulus(mat.volume_fraction1, mat.modulus1, mat.modulus2);
let r = mat.reuss_modulus(mat.volume_fraction1, mat.modulus1, mat.modulus2);
assert!(h >= r && h <= v);
}
#[test]
fn test_composite_hashin_shtrikman_lower_positive() {
let mat = PyCompositeMaterial::default();
let hs = mat.hashin_shtrikman_lower();
assert!(hs > 0.0);
}
#[test]
fn test_composite_pure_phase1() {
let mat = PyCompositeMaterial::new(70e9, 200e9, 1.0);
let v = mat.voigt_modulus(1.0, 70e9, 200e9);
assert!((v - 70e9).abs() < 1.0);
}
#[test]
fn test_composite_default() {
let mat = PyCompositeMaterial::default();
assert!(mat.modulus1 > 0.0 && mat.modulus2 > 0.0);
}
#[test]
fn test_register_materials_module_no_panic() {
register_materials_module("materials");
}
}