use std::f64::consts::PI;
pub fn reduced_modulus(e1: f64, nu1: f64, e2: f64, nu2: f64) -> f64 {
let inv = (1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2;
1.0 / inv
}
pub fn combined_radius(r1: f64, r2: f64) -> f64 {
1.0 / (1.0 / r1 + 1.0 / r2)
}
#[derive(Debug, Clone)]
pub struct HertzSphereSphere {
pub r_star: f64,
pub e_star: f64,
}
impl HertzSphereSphere {
pub fn new(r1: f64, nu1: f64, e1: f64, r2: f64, nu2: f64, e2: f64) -> Self {
let r_star = combined_radius(r1, r2);
let e_star = reduced_modulus(e1, nu1, e2, nu2);
Self { r_star, e_star }
}
pub fn contact_radius(&self, force: f64) -> f64 {
(3.0 * force * self.r_star / (4.0 * self.e_star)).cbrt()
}
pub fn indentation(&self, force: f64) -> f64 {
let a = self.contact_radius(force);
a * a / self.r_star
}
pub fn peak_pressure(&self, force: f64) -> f64 {
(6.0 * force * self.e_star * self.e_star / (PI * PI * PI * self.r_star * self.r_star))
.cbrt()
}
pub fn mean_pressure(&self, force: f64) -> f64 {
let a = self.contact_radius(force);
if a < 1e-30 {
return 0.0;
}
force / (PI * a * a)
}
pub fn contact_stiffness(&self, force: f64) -> f64 {
2.0 * self.e_star * self.contact_radius(force)
}
pub fn force_from_indentation(&self, delta: f64) -> f64 {
if delta <= 0.0 {
return 0.0;
}
(4.0 / 3.0) * self.e_star * self.r_star.sqrt() * delta.powf(1.5)
}
pub fn pressure_at_radius(&self, r: f64, force: f64) -> f64 {
let a = self.contact_radius(force);
if r > a {
return 0.0;
}
let p0 = self.peak_pressure(force);
let ratio = r / a;
p0 * (1.0 - ratio * ratio).max(0.0).sqrt()
}
}
#[derive(Debug, Clone)]
pub struct HertzSphereFlat {
pub sphere_radius: f64,
pub e_star: f64,
}
impl HertzSphereFlat {
pub fn new(r_sphere: f64, e_sphere: f64, nu_sphere: f64, e_flat: f64, nu_flat: f64) -> Self {
let e_star = reduced_modulus(e_sphere, nu_sphere, e_flat, nu_flat);
Self {
sphere_radius: r_sphere,
e_star,
}
}
pub fn contact_radius(&self, force: f64) -> f64 {
(3.0 * force * self.sphere_radius / (4.0 * self.e_star)).cbrt()
}
pub fn indentation(&self, force: f64) -> f64 {
let a = self.contact_radius(force);
a * a / self.sphere_radius
}
pub fn peak_pressure(&self, force: f64) -> f64 {
let a = self.contact_radius(force);
if a < 1e-30 {
return 0.0;
}
3.0 * force / (2.0 * PI * a * a)
}
pub fn strain_energy(&self, force: f64) -> f64 {
0.4 * force * self.indentation(force)
}
}
#[derive(Debug, Clone)]
pub struct HertzCylinderFlat {
pub cylinder_radius: f64,
pub e_star: f64,
}
impl HertzCylinderFlat {
pub fn new(r_cyl: f64, e_cyl: f64, nu_cyl: f64, e_flat: f64, nu_flat: f64) -> Self {
let e_star = reduced_modulus(e_cyl, nu_cyl, e_flat, nu_flat);
Self {
cylinder_radius: r_cyl,
e_star,
}
}
pub fn half_width(&self, force_per_length: f64) -> f64 {
(4.0 * force_per_length * self.cylinder_radius / (PI * self.e_star)).sqrt()
}
pub fn peak_pressure(&self, force_per_length: f64) -> f64 {
let b = self.half_width(force_per_length);
if b < 1e-30 {
return 0.0;
}
2.0 * force_per_length / (PI * b)
}
pub fn mean_pressure(&self, force_per_length: f64) -> f64 {
let b = self.half_width(force_per_length);
if b < 1e-30 {
return 0.0;
}
force_per_length / (2.0 * b)
}
pub fn pressure_at_position(&self, x: f64, force_per_length: f64) -> f64 {
let b = self.half_width(force_per_length);
if x.abs() > b {
return 0.0;
}
let p0 = self.peak_pressure(force_per_length);
p0 * (1.0 - (x / b).powi(2)).max(0.0).sqrt()
}
pub fn max_shear_stress(&self, force_per_length: f64) -> f64 {
0.300 * self.peak_pressure(force_per_length)
}
pub fn max_shear_depth(&self, force_per_length: f64) -> f64 {
0.786 * self.half_width(force_per_length)
}
}
pub fn mindlin_reduced_shear_modulus(g1: f64, nu1: f64, g2: f64, nu2: f64) -> f64 {
1.0 / ((2.0 - nu1) / (4.0 * g1) + (2.0 - nu2) / (4.0 * g2))
}
#[derive(Debug, Clone)]
pub struct MindlinCompliance {
pub g_star: f64,
pub friction: f64,
}
impl MindlinCompliance {
pub fn new(e1: f64, nu1: f64, e2: f64, nu2: f64, friction: f64) -> Self {
let g1 = e1 / (2.0 * (1.0 + nu1));
let g2 = e2 / (2.0 * (1.0 + nu2));
let g_star = mindlin_reduced_shear_modulus(g1, nu1, g2, nu2);
Self { g_star, friction }
}
pub fn tangential_stiffness(&self, contact_radius: f64) -> f64 {
8.0 * self.g_star * contact_radius
}
pub fn partial_slip_displacement(
&self,
tangential_force: f64,
normal_force: f64,
contact_radius: f64,
) -> Option<f64> {
let limit = self.friction * normal_force;
if tangential_force >= limit {
return None;
}
let prefactor = 3.0 * limit / (8.0 * self.g_star * contact_radius);
let term = 1.0 - tangential_force / limit;
Some(prefactor * (1.0 - term.powf(2.0 / 3.0)))
}
pub fn force_from_displacement(
&self,
delta_t: f64,
normal_force: f64,
contact_radius: f64,
) -> f64 {
let limit = self.friction * normal_force;
let prefactor = 3.0 * limit / (8.0 * self.g_star * contact_radius);
let ratio = (delta_t / prefactor).clamp(0.0, 1.0);
limit * (1.0 - (1.0 - ratio).powf(1.5))
}
pub fn stick_radius(
&self,
tangential_force: f64,
normal_force: f64,
contact_radius: f64,
) -> Option<f64> {
let limit = self.friction * normal_force;
if tangential_force >= limit || limit < 1e-30 {
return None;
}
Some(contact_radius * (1.0 - tangential_force / limit).powf(1.0 / 3.0))
}
pub fn fretting_energy_per_cycle(
&self,
tangential_amplitude: f64,
normal_force: f64,
contact_radius: f64,
) -> f64 {
let limit = self.friction * normal_force;
if limit < 1e-30 {
return 0.0;
}
let k_t = self.tangential_stiffness(contact_radius);
if k_t < 1e-30 {
return 0.0;
}
let ratio = (tangential_amplitude / limit).min(1.0);
(9.0 * limit * limit) / (10.0 * k_t) * (1.0 - (1.0 - ratio).powf(5.0 / 3.0))
}
}
#[derive(Debug, Clone)]
pub struct JkrAdhesion {
pub r_star: f64,
pub e_star: f64,
pub work_adhesion: f64,
}
impl JkrAdhesion {
pub fn new(r_star: f64, e_star: f64, work_adhesion: f64) -> Self {
Self {
r_star,
e_star,
work_adhesion,
}
}
pub fn contact_radius(&self, force: f64) -> f64 {
let w = self.work_adhesion;
let r = self.r_star;
let e_star = self.e_star;
let term = 3.0 * PI * w * r;
let discriminant = 6.0 * PI * w * r * force + term * term;
let a3 = (r / e_star) * (force + term + discriminant.max(0.0).sqrt());
a3.cbrt()
}
pub fn pull_off_force(&self) -> f64 {
-1.5 * PI * self.work_adhesion * self.r_star
}
pub fn zero_load_contact_radius(&self) -> f64 {
self.contact_radius(0.0)
}
pub fn elastic_energy(&self, force: f64) -> f64 {
let a = self.contact_radius(force);
let delta =
a * a / self.r_star - (8.0 * PI * self.work_adhesion * a / (3.0 * self.e_star)).sqrt();
if delta < 0.0 {
return 0.0;
}
(4.0 / 3.0) * self.e_star * self.r_star.sqrt() * delta.powf(1.5)
}
pub fn surface_energy(&self, force: f64) -> f64 {
let a = self.contact_radius(force);
-self.work_adhesion * PI * a * a
}
}
#[derive(Debug, Clone)]
pub struct DmtAdhesion {
pub r_star: f64,
pub e_star: f64,
pub work_adhesion: f64,
}
impl DmtAdhesion {
pub fn new(r_star: f64, e_star: f64, work_adhesion: f64) -> Self {
Self {
r_star,
e_star,
work_adhesion,
}
}
pub fn pull_off_force(&self) -> f64 {
-2.0 * PI * self.work_adhesion * self.r_star
}
fn effective_force(&self, force: f64) -> f64 {
force + 2.0 * PI * self.work_adhesion * self.r_star
}
pub fn contact_radius(&self, force: f64) -> f64 {
let f_eff = self.effective_force(force);
if f_eff <= 0.0 {
return 0.0;
}
(3.0 * f_eff * self.r_star / (4.0 * self.e_star)).cbrt()
}
pub fn indentation(&self, force: f64) -> f64 {
let a = self.contact_radius(force);
a * a / self.r_star
}
pub fn peak_pressure(&self, force: f64) -> f64 {
let a = self.contact_radius(force);
if a < 1e-30 {
return 0.0;
}
let f_eff = self.effective_force(force);
3.0 * f_eff / (2.0 * PI * a * a)
}
pub fn tabor_parameter(&self, z0: f64) -> f64 {
(self.r_star * self.work_adhesion * self.work_adhesion
/ (self.e_star * self.e_star * z0 * z0 * z0))
.cbrt()
}
}
#[derive(Debug, Clone)]
pub struct ContactAreaHysteresis {
pub r_star: f64,
pub e_star: f64,
pub f_max: f64,
pub a_max: f64,
}
impl ContactAreaHysteresis {
pub fn new(r_star: f64, e_star: f64) -> Self {
Self {
r_star,
e_star,
f_max: 0.0,
a_max: 0.0,
}
}
pub fn load(&mut self, force: f64) -> f64 {
let a = (3.0 * force * self.r_star / (4.0 * self.e_star)).cbrt();
if force > self.f_max {
self.f_max = force;
self.a_max = a;
}
a
}
pub fn unload(&self, force: f64) -> f64 {
if force <= 0.0 {
return 0.0;
}
if self.f_max < 1e-30 {
return 0.0;
}
let a_hertz = (3.0 * force * self.r_star / (4.0 * self.e_star)).cbrt();
let a_residual = self.a_max * (1.0 - force / self.f_max).max(0.0).sqrt() * 0.05;
a_hertz + a_residual
}
pub fn hysteresis_energy(&self, n_steps: usize) -> f64 {
if self.f_max < 1e-30 || n_steps < 2 {
return 0.0;
}
let df = self.f_max / (n_steps as f64 - 1.0);
let mut energy = 0.0;
for i in 1..n_steps {
let f_prev = (i - 1) as f64 * df;
let f_curr = i as f64 * df;
let a_load_prev = (3.0 * f_prev * self.r_star / (4.0 * self.e_star)).cbrt();
let a_load_curr = (3.0 * f_curr * self.r_star / (4.0 * self.e_star)).cbrt();
let a_unload_prev = self.unload(f_prev);
let a_unload_curr = self.unload(f_curr);
let da_load = a_load_curr - a_load_prev;
let da_unload = a_unload_curr - a_unload_prev;
energy += 0.5 * (f_prev + f_curr) * (da_load - da_unload).abs();
}
energy
}
}
#[derive(Debug, Clone)]
pub struct ContactForceModel {
pub normal_stiffness: f64,
pub tangential_stiffness: f64,
pub friction: f64,
pub damping: f64,
}
impl ContactForceModel {
pub fn linear_normal_force(&self, gap: f64) -> f64 {
if gap >= 0.0 {
0.0
} else {
-gap * self.normal_stiffness
}
}
pub fn hertz_normal_force(&self, gap: f64) -> f64 {
if gap >= 0.0 {
0.0
} else {
let delta = -gap;
self.normal_stiffness * delta.powf(1.5)
}
}
pub fn damped_normal_force(&self, gap: f64, normal_velocity: f64, mass: f64) -> f64 {
let fn_ = self.linear_normal_force(gap);
if fn_ <= 0.0 {
return 0.0;
}
let c = self.damping * 2.0 * (mass * self.normal_stiffness).sqrt();
(fn_ - c * normal_velocity).max(0.0)
}
pub fn tangential_force(&self, tangential_disp: f64, normal_force: f64) -> f64 {
let f_t_trial = self.tangential_stiffness * tangential_disp;
let limit = self.friction * normal_force;
if f_t_trial.abs() <= limit {
f_t_trial
} else {
limit * f_t_trial.signum()
}
}
pub fn contact_force_vector(&self, gap: f64, tangential_disp: f64) -> [f64; 2] {
let fn_ = self.linear_normal_force(gap);
let ft = self.tangential_force(tangential_disp, fn_);
[ft, fn_]
}
}
#[derive(Debug, Clone)]
pub struct GreenwoodWilliamson {
pub asperity_density: f64,
pub asperity_radius: f64,
pub height_std: f64,
pub e_star: f64,
}
impl GreenwoodWilliamson {
fn phi(&self, z: f64) -> f64 {
let s = self.height_std;
(-0.5 * (z / s).powi(2)).exp() / (s * (2.0 * PI).sqrt())
}
fn prob_contact(&self, separation: f64) -> f64 {
let t = separation / (self.height_std * 2.0_f64.sqrt());
0.5 * gw_erfc(t)
}
fn mean_overlap(&self, separation: f64) -> f64 {
let s = self.height_std;
let t = separation / (s * 2.0_f64.sqrt());
s / (2.0 * PI).sqrt() * (-t * t).exp() - separation * 0.5 * gw_erfc(t)
}
fn mean_overlap_3_2(&self, separation: f64) -> f64 {
let s = self.height_std;
let n = 200usize;
let hi = separation + 8.0 * s;
let dz = (hi - separation) / n as f64;
let mut sum = 0.0;
for i in 0..n {
let z = separation + (i as f64 + 0.5) * dz;
sum += (z - separation).powf(1.5) * self.phi(z) * dz;
}
sum
}
pub fn real_contact_area_fraction(&self, separation: f64) -> f64 {
PI * self.asperity_density * self.asperity_radius * self.mean_overlap(separation)
}
pub fn contact_asperity_density(&self, separation: f64) -> f64 {
self.asperity_density * self.prob_contact(separation)
}
pub fn contact_pressure(&self, separation: f64) -> f64 {
(4.0 / 3.0)
* self.asperity_density
* self.e_star
* self.asperity_radius.sqrt()
* self.mean_overlap_3_2(separation)
}
pub fn plasticity_index(&self, hardness: f64) -> f64 {
if hardness < 1e-30 || self.asperity_radius < 1e-30 {
return 0.0;
}
(self.e_star / hardness) * (self.height_std / self.asperity_radius).sqrt()
}
pub fn separation_for_pressure(&self, target_pressure: f64, tol: f64) -> Option<f64> {
let mut lo = 0.0_f64;
let mut hi = 10.0 * self.height_std;
if self.contact_pressure(lo) < target_pressure {
return None;
}
for _ in 0..80 {
let mid = 0.5 * (lo + hi);
if self.contact_pressure(mid) > target_pressure {
lo = mid;
} else {
hi = mid;
}
if (hi - lo) < tol {
break;
}
}
Some(0.5 * (lo + hi))
}
}
fn gw_erfc(x: f64) -> f64 {
if x < 0.0 {
return 2.0 - gw_erfc(-x);
}
let t = 1.0 / (1.0 + 0.3275911 * x);
let poly = t
* (0.254829592
+ t * (-0.284496736 + t * (1.421413741 + t * (-1.453152027 + t * 1.061405429))));
poly * (-x * x).exp()
}
#[derive(Debug, Clone)]
pub struct FlashTemperature {
pub k1: f64,
pub k2: f64,
pub kappa1: f64,
pub kappa2: f64,
}
impl FlashTemperature {
pub fn new(k1: f64, kappa1: f64, k2: f64, kappa2: f64) -> Self {
Self {
k1,
k2,
kappa1,
kappa2,
}
}
pub fn peclet_number(sliding_speed: f64, contact_radius: f64, thermal_diffusivity: f64) -> f64 {
sliding_speed * contact_radius / (2.0 * thermal_diffusivity)
}
pub fn heat_partition_factor(&self) -> f64 {
let num = self.k1 * self.kappa2.sqrt();
let den = num + self.k2 * self.kappa1.sqrt();
if den < 1e-30 { 0.5 } else { num / den }
}
pub fn flash_temperature_high_speed(
&self,
friction: f64,
normal_force: f64,
sliding_speed: f64,
contact_radius: f64,
) -> f64 {
if contact_radius < 1e-30 || sliding_speed < 1e-30 {
return 0.0;
}
let pe = Self::peclet_number(sliding_speed, contact_radius, self.kappa1);
if pe < 1e-10 {
return 0.0;
}
let q_dot = friction * normal_force * sliding_speed; 0.308 * q_dot / (contact_radius * (self.k1 + self.k2) * pe.sqrt())
}
pub fn flash_temperature_low_speed(
&self,
friction: f64,
normal_force: f64,
sliding_speed: f64,
contact_radius: f64,
) -> f64 {
if contact_radius < 1e-30 {
return 0.0;
}
let q_dot = friction * normal_force * sliding_speed;
q_dot / (2.0 * PI * contact_radius * (self.k1 + self.k2))
}
pub fn flash_temperature_partition(
&self,
friction: f64,
normal_force: f64,
sliding_speed: f64,
contact_radius: f64,
) -> f64 {
if contact_radius < 1e-30 {
return 0.0;
}
let beta = self.heat_partition_factor();
let q_dot = friction * normal_force * sliding_speed;
beta * q_dot / (PI * contact_radius * (self.k1 + self.k2))
}
pub fn bulk_temperature_rise(
friction: f64,
normal_force: f64,
sliding_distance: f64,
thermal_mass: f64,
) -> f64 {
if thermal_mass < 1e-30 {
return 0.0;
}
friction * normal_force * sliding_distance / thermal_mass
}
}
pub fn archard_wear_volume(
wear_coefficient: f64,
normal_force: f64,
sliding_distance: f64,
hardness: f64,
) -> f64 {
if hardness < 1e-30 {
return 0.0;
}
wear_coefficient * normal_force * sliding_distance / hardness
}
pub fn wear_depth_from_volume(wear_volume: f64, contact_radius: f64) -> f64 {
if contact_radius < 1e-30 {
return 0.0;
}
wear_volume / (PI * contact_radius * contact_radius)
}
#[cfg(test)]
mod tests {
use super::*;
fn steel_steel() -> HertzSphereSphere {
HertzSphereSphere::new(0.01, 0.3, 200e9, 0.01, 0.3, 200e9)
}
fn steel_flat() -> HertzSphereFlat {
HertzSphereFlat::new(0.01, 200e9, 0.3, 200e9, 0.3)
}
fn steel_cyl_flat() -> HertzCylinderFlat {
HertzCylinderFlat::new(0.01, 200e9, 0.3, 200e9, 0.3)
}
#[test]
fn test_reduced_modulus_symmetric() {
let e = 200e9_f64;
let nu = 0.3_f64;
let e_star = reduced_modulus(e, nu, e, nu);
let expected = 0.5 * e / (1.0 - nu * nu);
assert!((e_star - expected).abs() / expected < 1e-10);
}
#[test]
fn test_combined_radius_equal() {
let r = combined_radius(0.01, 0.01);
assert!((r - 0.005).abs() < 1e-12);
}
#[test]
fn test_combined_radius_flat() {
let r = combined_radius(0.01, f64::INFINITY);
assert!((r - 0.01).abs() < 1e-12);
}
#[test]
fn test_hertz_ss_contact_radius_positive() {
let h = steel_steel();
assert!(h.contact_radius(100.0) > 0.0);
}
#[test]
fn test_hertz_ss_indentation_positive() {
let h = steel_steel();
assert!(h.indentation(100.0) > 0.0);
}
#[test]
fn test_hertz_ss_peak_pressure_positive() {
let h = steel_steel();
assert!(h.peak_pressure(100.0) > 0.0);
}
#[test]
fn test_hertz_ss_mean_pressure_less_than_peak() {
let h = steel_steel();
let f = 100.0;
assert!(h.mean_pressure(f) < h.peak_pressure(f));
}
#[test]
fn test_hertz_ss_force_roundtrip() {
let h = steel_steel();
let f_in = 500.0;
let delta = h.indentation(f_in);
let f_out = h.force_from_indentation(delta);
assert!((f_in - f_out).abs() / f_in < 1e-6);
}
#[test]
fn test_hertz_ss_stiffness_positive() {
let h = steel_steel();
assert!(h.contact_stiffness(100.0) > 0.0);
}
#[test]
fn test_hertz_ss_pressure_outside_contact_zero() {
let h = steel_steel();
let a = h.contact_radius(100.0);
assert_eq!(h.pressure_at_radius(a * 2.0, 100.0), 0.0);
}
#[test]
fn test_hertz_ss_pressure_at_center_equals_peak() {
let h = steel_steel();
let f = 200.0;
let p0 = h.peak_pressure(f);
let p_center = h.pressure_at_radius(0.0, f);
assert!((p_center - p0).abs() / p0 < 1e-10);
}
#[test]
fn test_sphere_flat_contact_radius_positive() {
let h = steel_flat();
assert!(h.contact_radius(100.0) > 0.0);
}
#[test]
fn test_sphere_flat_indentation_positive() {
let h = steel_flat();
assert!(h.indentation(200.0) > 0.0);
}
#[test]
fn test_sphere_flat_peak_pressure_positive() {
let h = steel_flat();
assert!(h.peak_pressure(100.0) > 0.0);
}
#[test]
fn test_sphere_flat_strain_energy_positive() {
let h = steel_flat();
assert!(h.strain_energy(100.0) > 0.0);
}
#[test]
fn test_cyl_flat_half_width_positive() {
let h = steel_cyl_flat();
assert!(h.half_width(1e4) > 0.0);
}
#[test]
fn test_cyl_flat_peak_pressure_positive() {
let h = steel_cyl_flat();
assert!(h.peak_pressure(1e4) > 0.0);
}
#[test]
fn test_cyl_flat_mean_less_than_peak() {
let h = steel_cyl_flat();
let fpl = 1e5;
assert!(h.mean_pressure(fpl) < h.peak_pressure(fpl));
}
#[test]
fn test_cyl_flat_pressure_outside_zero() {
let h = steel_cyl_flat();
let fpl = 1e4;
let b = h.half_width(fpl);
assert_eq!(h.pressure_at_position(b * 2.0, fpl), 0.0);
}
#[test]
fn test_cyl_flat_max_shear_stress_positive() {
let h = steel_cyl_flat();
assert!(h.max_shear_stress(1e5) > 0.0);
}
#[test]
fn test_cyl_flat_max_shear_depth_proportional() {
let h = steel_cyl_flat();
let fpl = 1e4;
let b = h.half_width(fpl);
let z_max = h.max_shear_depth(fpl);
assert!((z_max - 0.786 * b).abs() < 1e-15);
}
fn make_mindlin() -> MindlinCompliance {
MindlinCompliance::new(200e9, 0.3, 200e9, 0.3, 0.4)
}
#[test]
fn test_mindlin_tangential_stiffness_positive() {
let m = make_mindlin();
assert!(m.tangential_stiffness(1e-4) > 0.0);
}
#[test]
fn test_mindlin_partial_slip_zero_force() {
let m = make_mindlin();
let disp = m.partial_slip_displacement(0.0, 100.0, 1e-4).unwrap();
assert!(disp.abs() < 1e-20);
}
#[test]
fn test_mindlin_gross_slip_returns_none() {
let m = make_mindlin();
let result = m.partial_slip_displacement(40.0, 100.0, 1e-4);
assert!(result.is_none());
}
#[test]
fn test_mindlin_stick_radius_less_than_contact() {
let m = make_mindlin();
let a = 1e-4;
let c = m.stick_radius(5.0, 100.0, a).unwrap();
assert!(c < a);
}
#[test]
fn test_mindlin_fretting_energy_positive() {
let m = make_mindlin();
let e = m.fretting_energy_per_cycle(5.0, 100.0, 1e-4);
assert!(e > 0.0);
}
#[test]
fn test_mindlin_fretting_energy_zero_amplitude() {
let m = make_mindlin();
let e = m.fretting_energy_per_cycle(0.0, 100.0, 1e-4);
assert!(e.abs() < 1e-30);
}
fn make_jkr() -> JkrAdhesion {
JkrAdhesion::new(1e-6, 1e10, 0.05)
}
#[test]
fn test_jkr_pull_off_negative() {
let j = make_jkr();
assert!(j.pull_off_force() < 0.0);
}
#[test]
fn test_jkr_zero_load_contact_positive() {
let j = make_jkr();
assert!(j.zero_load_contact_radius() > 0.0);
}
#[test]
fn test_jkr_contact_radius_increases_with_force() {
let j = make_jkr();
let a0 = j.contact_radius(0.0);
let a1 = j.contact_radius(1e-3);
assert!(a1 > a0);
}
#[test]
fn test_jkr_surface_energy_negative() {
let j = make_jkr();
assert!(j.surface_energy(1e-3) < 0.0);
}
fn make_dmt() -> DmtAdhesion {
DmtAdhesion::new(1e-6, 1e10, 0.05)
}
#[test]
fn test_dmt_pull_off_negative() {
let d = make_dmt();
assert!(d.pull_off_force() < 0.0);
}
#[test]
fn test_dmt_pull_off_stronger_than_jkr() {
let j = make_jkr();
let d = make_dmt();
assert!(d.pull_off_force().abs() > j.pull_off_force().abs());
}
#[test]
fn test_dmt_contact_radius_zero_at_large_negative_force() {
let d = make_dmt();
let f_below = d.pull_off_force() * 1.1;
let a = d.contact_radius(f_below);
assert_eq!(a, 0.0);
}
#[test]
fn test_dmt_tabor_parameter_positive() {
let d = make_dmt();
let tabor = d.tabor_parameter(3e-10);
assert!(tabor > 0.0);
}
#[test]
fn test_dmt_peak_pressure_positive() {
let d = make_dmt();
assert!(d.peak_pressure(1e-3) > 0.0);
}
#[test]
fn test_hysteresis_loading_increases_area() {
let mut h = ContactAreaHysteresis::new(5e-3, 1e11);
let a0 = h.load(100.0);
let a1 = h.load(500.0);
assert!(a1 > a0);
}
#[test]
fn test_hysteresis_unload_less_or_equal_load() {
let mut h = ContactAreaHysteresis::new(5e-3, 1e11);
h.load(500.0);
let a_load = h.load(200.0);
let a_unload = h.unload(200.0);
assert!(a_unload >= a_load * 0.99);
}
#[test]
fn test_hysteresis_energy_positive() {
let mut h = ContactAreaHysteresis::new(5e-3, 1e11);
h.load(500.0);
let e = h.hysteresis_energy(50);
assert!(e >= 0.0);
}
fn make_cfm() -> ContactForceModel {
ContactForceModel {
normal_stiffness: 1e8,
tangential_stiffness: 5e7,
friction: 0.3,
damping: 0.05,
}
}
#[test]
fn test_cfm_no_force_open_gap() {
let c = make_cfm();
assert_eq!(c.linear_normal_force(0.001), 0.0);
}
#[test]
fn test_cfm_linear_normal_force_overlap() {
let c = make_cfm();
let fn_ = c.linear_normal_force(-1e-4);
assert!((fn_ - 1e4).abs() < 1.0);
}
#[test]
fn test_cfm_hertz_normal_force_positive() {
let c = make_cfm();
assert!(c.hertz_normal_force(-1e-5) > 0.0);
}
#[test]
fn test_cfm_tangential_force_coulomb_limited() {
let c = make_cfm();
let fn_ = 1000.0;
let limit = c.friction * fn_;
let ft = c.tangential_force(1.0, fn_); assert!(ft.abs() <= limit + 1e-10);
}
#[test]
fn test_cfm_damped_normal_force_approach() {
let c = make_cfm();
let fn_ = c.damped_normal_force(-1e-4, -0.001, 0.1);
assert!(fn_ >= 0.0);
}
fn make_gw() -> GreenwoodWilliamson {
GreenwoodWilliamson {
asperity_density: 1e10,
asperity_radius: 1e-6,
height_std: 1e-7,
e_star: 1e11,
}
}
#[test]
fn test_gw_contact_area_fraction_positive() {
let gw = make_gw();
let frac = gw.real_contact_area_fraction(0.0);
assert!(frac > 0.0);
}
#[test]
fn test_gw_contact_area_decreases_with_separation() {
let gw = make_gw();
let a0 = gw.real_contact_area_fraction(0.0);
let a1 = gw.real_contact_area_fraction(5e-7);
assert!(a0 > a1);
}
#[test]
fn test_gw_pressure_positive_at_zero_separation() {
let gw = make_gw();
assert!(gw.contact_pressure(0.0) > 0.0);
}
#[test]
fn test_gw_plasticity_index_positive() {
let gw = make_gw();
let psi = gw.plasticity_index(2e9);
assert!(psi > 0.0);
}
#[test]
fn test_gw_asperity_density_positive() {
let gw = make_gw();
let n = gw.contact_asperity_density(0.0);
assert!(n > 0.0);
}
#[test]
fn test_gw_erfc_at_zero() {
assert!((gw_erfc(0.0) - 1.0).abs() < 1e-6);
}
#[test]
fn test_gw_erfc_large_arg() {
assert!(gw_erfc(5.0) < 1e-6);
}
#[test]
fn test_gw_separation_for_pressure() {
let gw = make_gw();
let p0 = gw.contact_pressure(0.0);
let target = p0 * 0.5;
let sep = gw.separation_for_pressure(target, 1e-12);
assert!(sep.is_some());
let sep = sep.unwrap();
let p_check = gw.contact_pressure(sep);
assert!((p_check - target).abs() / target < 0.01);
}
fn make_flash() -> FlashTemperature {
FlashTemperature::new(50.0, 1.4e-5, 50.0, 1.4e-5)
}
#[test]
fn test_flash_partition_factor_symmetric() {
let ft = make_flash();
assert!((ft.heat_partition_factor() - 0.5).abs() < 1e-10);
}
#[test]
fn test_flash_peclet_positive() {
let pe = FlashTemperature::peclet_number(1.0, 1e-3, 1e-5);
assert!(pe > 0.0);
}
#[test]
fn test_flash_high_speed_positive() {
let ft = make_flash();
let dt = ft.flash_temperature_high_speed(0.3, 100.0, 1.0, 1e-3);
assert!(dt > 0.0);
}
#[test]
fn test_flash_low_speed_positive() {
let ft = make_flash();
let dt = ft.flash_temperature_low_speed(0.3, 100.0, 0.01, 1e-3);
assert!(dt > 0.0);
}
#[test]
fn test_flash_partition_positive() {
let ft = make_flash();
let dt = ft.flash_temperature_partition(0.3, 100.0, 1.0, 1e-3);
assert!(dt > 0.0);
}
#[test]
fn test_flash_bulk_temperature_rise_positive() {
let dt = FlashTemperature::bulk_temperature_rise(0.3, 100.0, 1.0, 500.0);
assert!(dt > 0.0);
}
#[test]
fn test_flash_zero_speed_gives_zero() {
let ft = make_flash();
let dt = ft.flash_temperature_high_speed(0.3, 100.0, 0.0, 1e-3);
assert_eq!(dt, 0.0);
}
#[test]
fn test_archard_wear_volume_positive() {
let v = archard_wear_volume(1e-4, 1000.0, 0.5, 5e9);
assert!(v > 0.0);
}
#[test]
fn test_archard_zero_hardness() {
let v = archard_wear_volume(1e-4, 1000.0, 0.5, 0.0);
assert_eq!(v, 0.0);
}
#[test]
fn test_wear_depth_from_volume() {
let depth = wear_depth_from_volume(1e-12, 1e-3);
assert!(depth > 0.0);
}
#[test]
fn test_wear_depth_zero_radius() {
let depth = wear_depth_from_volume(1e-12, 0.0);
assert_eq!(depth, 0.0);
}
}