use super::functions::erfc;
#[allow(unused_imports)]
use super::functions::*;
use std::f64::consts::PI;
#[derive(Debug, Clone, Copy)]
pub struct ArchardWear {
pub k: f64,
pub hardness: f64,
}
impl ArchardWear {
pub fn new(k: f64, hardness: f64) -> Self {
Self { k, hardness }
}
pub fn wear_rate(&self, pressure: f64) -> f64 {
self.k * pressure / self.hardness
}
pub fn volume_loss(&self, w: f64, s: f64) -> f64 {
self.k * w * s / self.hardness
}
pub fn depth_loss(&self, w: f64, s: f64, area: f64) -> f64 {
if area < 1e-30 {
return 0.0;
}
self.volume_loss(w, s) / area
}
}
#[derive(Debug, Clone, Copy)]
pub struct ScuffingModel {
pub mu: f64,
pub k1: f64,
pub k2: f64,
pub rho_cp1: f64,
pub rho_cp2: f64,
pub t_critical: f64,
pub t_bulk: f64,
}
impl ScuffingModel {
pub fn steel_steel() -> Self {
ScuffingModel {
mu: 0.1,
k1: 50.0,
k2: 50.0,
rho_cp1: 3.75e6,
rho_cp2: 3.75e6,
t_critical: 700.0 + 273.15,
t_bulk: 80.0 + 273.15,
}
}
pub fn thermal_diffusivity_1(&self) -> f64 {
self.k1 / self.rho_cp1
}
pub fn thermal_diffusivity_2(&self) -> f64 {
self.k2 / self.rho_cp2
}
pub fn flash_temperature_rise(&self, q: f64, a: f64, v: f64) -> f64 {
if a < 1e-30 || v < 1e-10 {
return 0.0;
}
let beta1 = (self.rho_cp1 * self.k1).sqrt();
let beta2 = (self.rho_cp2 * self.k2).sqrt();
1.11 * q * (a / v).sqrt() / (beta1 + beta2)
}
pub fn contact_temperature(&self, q: f64, a: f64, v: f64) -> f64 {
self.t_bulk + self.flash_temperature_rise(q, a, v)
}
pub fn scuffing_risk(&self, q: f64, a: f64, v: f64) -> bool {
self.contact_temperature(q, a, v) > self.t_critical
}
pub fn safety_factor(&self, q: f64, a: f64, v: f64) -> f64 {
let tc = self.contact_temperature(q, a, v);
if tc < 1.0 {
return f64::INFINITY;
}
self.t_critical / tc
}
}
#[derive(Debug, Clone, Copy)]
pub struct BearingLife {
pub c: f64,
pub p: f64,
pub life_exponent: f64,
pub a_iso: f64,
pub a_skf: f64,
}
impl BearingLife {
pub fn ball_bearing(c: f64, p: f64) -> Self {
BearingLife {
c,
p,
life_exponent: 3.0,
a_iso: 1.0,
a_skf: 1.0,
}
}
pub fn roller_bearing(c: f64, p: f64) -> Self {
BearingLife {
c,
p,
life_exponent: 10.0 / 3.0,
a_iso: 1.0,
a_skf: 1.0,
}
}
pub fn l10_mrv(&self) -> f64 {
if self.p < 1e-10 {
return f64::INFINITY;
}
(self.c / self.p).powf(self.life_exponent)
}
pub fn l10_hours(&self, n_rpm: f64) -> f64 {
if n_rpm < 1e-10 {
return f64::INFINITY;
}
self.l10_mrv() * 1e6 / (60.0 * n_rpm)
}
pub fn l10m_hours(&self, n_rpm: f64, a1: f64) -> f64 {
a1 * self.a_iso * self.a_skf * self.l10_hours(n_rpm)
}
pub fn reliability_at(&self, t_hours: f64, n_rpm: f64) -> f64 {
let l10 = self.l10_hours(n_rpm);
if l10 < 1e-10 {
return 0.0;
}
let l50 = 5.0 * l10;
let weibull_slope = 1.5_f64;
(-(t_hours / l50).powf(weibull_slope)).exp()
}
pub fn equivalent_load(fr: f64, fa: f64, x: f64, y: f64) -> f64 {
x * fr + y * fa
}
pub fn speed_factor(&self, n_rpm: f64) -> f64 {
if n_rpm < 1e-10 {
return 0.0;
}
(33.3 / n_rpm).powf(1.0 / 3.0)
}
}
#[derive(Debug, Clone, Copy)]
pub struct HertzResult {
pub contact_radius: f64,
pub max_pressure: f64,
pub depth: f64,
pub force: f64,
}
#[derive(Debug, Clone, Copy)]
pub struct ThermalWear {
pub friction_coeff: f64,
pub k1: f64,
pub k2: f64,
pub t_ambient: f64,
pub activation_energy: f64,
pub wear_factor_a0: f64,
}
impl ThermalWear {
pub fn new(
friction_coeff: f64,
k1: f64,
k2: f64,
t_ambient: f64,
activation_energy: f64,
wear_factor_a0: f64,
) -> Self {
Self {
friction_coeff,
k1,
k2,
t_ambient,
activation_energy,
wear_factor_a0,
}
}
pub fn flash_temperature(&self, f: f64, v: f64, a: f64) -> f64 {
if a < 1e-30 {
return self.t_ambient;
}
let heat_rate = self.friction_coeff * f * v;
let delta_t = heat_rate / (PI * a * (self.k1 + self.k2));
self.t_ambient + delta_t
}
pub fn oxidative_wear_rate(&self, temperature: f64) -> f64 {
const R_GAS: f64 = 8.314;
self.wear_factor_a0 * (-(self.activation_energy / (R_GAS * temperature))).exp()
}
pub fn total_wear_volume(&self, f: f64, v: f64, a: f64, t: f64) -> f64 {
let temp = self.flash_temperature(f, v, a);
let rate = self.oxidative_wear_rate(temp);
rate * a * a * t
}
}
#[derive(Debug, Clone, Copy)]
pub struct AbrasiveWear {
pub ka: f64,
pub hardness: f64,
pub particle_size: f64,
pub efficiency: f64,
}
impl AbrasiveWear {
pub fn new(ka: f64, hardness: f64, particle_size: f64, efficiency: f64) -> Self {
AbrasiveWear {
ka,
hardness,
particle_size,
efficiency,
}
}
pub fn two_body_volume(&self, w: f64, s: f64) -> f64 {
self.ka * w * s / self.hardness
}
pub fn three_body_volume(&self, w: f64, s: f64) -> f64 {
self.two_body_volume(w, s) / 3.0
}
pub fn scratch_depth(&self, w_per_asperity: f64) -> f64 {
(2.0 * w_per_asperity / (PI * self.hardness)).sqrt()
}
pub fn volume_per_scratch(&self, w_per_asperity: f64, scratch_length: f64) -> f64 {
let depth = self.scratch_depth(w_per_asperity);
0.5 * PI * depth.powi(2) * scratch_length
}
pub fn specific_wear_rate(&self) -> f64 {
self.ka / self.hardness
}
}
#[derive(Debug, Clone, Copy)]
pub struct HertzElliptical {
pub effective_modulus: f64,
pub sum_curvature: f64,
pub curvature_difference: f64,
}
impl HertzElliptical {
#[allow(clippy::too_many_arguments)]
pub fn new(
e1: f64,
nu1: f64,
e2: f64,
nu2: f64,
r1a: f64,
r1b: f64,
r2a: f64,
r2b: f64,
) -> Self {
let e_star = 1.0 / ((1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2);
let sum_k = 1.0 / r1a + 1.0 / r1b + 1.0 / r2a + 1.0 / r2b;
let diff_k = (1.0 / r1a - 1.0 / r1b + 1.0 / r2a - 1.0 / r2b)
.powi(2)
.sqrt();
HertzElliptical {
effective_modulus: e_star,
sum_curvature: sum_k,
curvature_difference: diff_k,
}
}
pub fn ellipticity_ratio(&self) -> f64 {
let cap_b = (self.sum_curvature + self.curvature_difference) / 2.0;
let cap_a = (self.sum_curvature - self.curvature_difference) / 2.0;
if cap_a < 1e-30 {
return 1.0;
}
(cap_b / cap_a).powf(2.0 / 3.0).min(1.0)
}
pub fn semi_axis_a(&self, force: f64) -> f64 {
let r_eff = 2.0 / self.sum_curvature;
let h = HertzContact {
effective_modulus: self.effective_modulus,
effective_radius: r_eff,
};
h.compute(force).contact_radius
}
pub fn semi_axis_b(&self, force: f64) -> f64 {
self.ellipticity_ratio() * self.semi_axis_a(force)
}
pub fn max_pressure(&self, force: f64) -> f64 {
let a = self.semi_axis_a(force);
let b = self.semi_axis_b(force);
if a < 1e-30 || b < 1e-30 {
return 0.0;
}
3.0 * force / (2.0 * PI * a * b)
}
pub fn contact_area(&self, force: f64) -> f64 {
PI * self.semi_axis_a(force) * self.semi_axis_b(force)
}
}
#[derive(Debug, Clone)]
pub struct GearTooth {
pub module: f64,
pub z1: u32,
pub z2: u32,
pub pressure_angle: f64,
pub face_width: f64,
pub wt: f64,
pub ze: f64,
pub n1: f64,
}
impl GearTooth {
pub fn spur_pair(
module: f64,
z1: u32,
z2: u32,
pressure_angle: f64,
face_width: f64,
wt: f64,
n1: f64,
) -> Self {
GearTooth {
module,
z1,
z2,
pressure_angle,
face_width,
wt,
ze: 191.0,
n1,
}
}
pub fn d1(&self) -> f64 {
self.module * self.z1 as f64
}
pub fn d2(&self) -> f64 {
self.module * self.z2 as f64
}
pub fn pitch_line_velocity(&self) -> f64 {
PI * self.d1() * self.n1 / (60.0 * 1000.0)
}
pub fn gear_ratio(&self) -> f64 {
self.z2 as f64 / self.z1 as f64
}
pub fn contact_ratio(&self) -> f64 {
let phi = self.pressure_angle.to_radians();
let r1 = self.d1() / 2.0;
let r2 = self.d2() / 2.0;
let ra1 = r1 + self.module;
let ra2 = r2 + self.module;
let cp = (self.d1() + self.d2()) / 2.0 * phi.sin();
let term1 = (ra1.powi(2) - (r1 * phi.cos()).powi(2)).sqrt();
let term2 = (ra2.powi(2) - (r2 * phi.cos()).powi(2)).sqrt();
(term1 + term2 - cp) / (PI * self.module * phi.cos())
}
pub fn hertz_contact_stress(&self) -> f64 {
let phi = self.pressure_angle.to_radians();
let d1 = self.d1();
let i = self.gear_ratio();
let term = self.wt / (d1 * self.face_width) * (i + 1.0) / i / phi.cos();
if term < 0.0 {
return 0.0;
}
self.ze * term.sqrt()
}
pub fn sliding_velocity_at(&self, s_mm: f64) -> f64 {
let _vp = self.pitch_line_velocity() * 1000.0;
let omega1 = 2.0 * PI * self.n1 / 60.0;
let i = self.gear_ratio();
omega1 * s_mm * (1.0 + 1.0 / i) / 1000.0
}
pub fn sliding_ratio(&self) -> f64 {
let phi = self.pressure_angle.to_radians();
2.0 * PI * (1.0 / self.z1 as f64 + 1.0 / self.z2 as f64) / (PI * phi.cos())
* self.contact_ratio()
}
pub fn friction_power_loss(&self, mu: f64) -> f64 {
let vs = self.sliding_velocity_at(self.module);
mu * self.wt * vs.abs()
}
}
#[derive(Debug, Clone)]
pub struct BrakePad {
pub fn_force: f64,
pub mu: f64,
pub rotor_radius: f64,
pub pad_area: f64,
pub k_pad: f64,
pub beta_pad: f64,
pub kw: f64,
pub tp: f64,
pub cp_pad: f64,
pub density_pad: f64,
}
impl BrakePad {
pub fn semi_metallic() -> Self {
BrakePad {
fn_force: 3000.0,
mu: 0.40,
rotor_radius: 0.12,
pad_area: 50e-4,
k_pad: 2.0,
beta_pad: 0.05,
kw: 1e-14,
tp: 0.012,
cp_pad: 900.0,
density_pad: 2500.0,
}
}
pub fn braking_torque(&self) -> f64 {
self.mu * self.fn_force * self.rotor_radius
}
pub fn frictional_power(&self, v: f64) -> f64 {
self.mu * self.fn_force * v
}
pub fn temperature_rise_steady(&self, v: f64) -> f64 {
let q = self.frictional_power(v) * self.beta_pad;
if self.k_pad < 1e-10 || self.pad_area < 1e-10 {
return 0.0;
}
q * self.tp / (self.k_pad * self.pad_area)
}
pub fn temperature_rise_transient(&self, v: f64, t: f64) -> f64 {
let q = self.frictional_power(v) * self.beta_pad;
let mass = self.density_pad * self.pad_area * self.tp;
if mass < 1e-10 {
return 0.0;
}
q * t / (mass * self.cp_pad)
}
pub fn wear_depth(&self, s: f64) -> f64 {
self.kw * self.fn_force * s / self.pad_area
}
pub fn remaining_life(&self, current_thickness: f64, min_thickness: f64) -> f64 {
let wear_allowed = current_thickness - min_thickness;
if wear_allowed <= 0.0 {
return 0.0;
}
wear_allowed * self.pad_area / (self.kw * self.fn_force)
}
pub fn contact_pressure(&self) -> f64 {
self.fn_force / self.pad_area
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LubricationRegimeKind {
Boundary,
Mixed,
Elastohydrodynamic,
FullFilm,
}
#[derive(Debug, Clone)]
pub struct SurfaceRoughness {
pub ra: f64,
pub rq: f64,
pub rz: f64,
pub rsk: f64,
pub rku: f64,
pub rsm: f64,
}
impl SurfaceRoughness {
pub fn new(ra: f64, rq: f64, rz: f64, rsk: f64, rku: f64, rsm: f64) -> Self {
SurfaceRoughness {
ra,
rq,
rz,
rsk,
rku,
rsm,
}
}
pub fn ground_steel() -> Self {
SurfaceRoughness {
ra: 0.8e-6,
rq: 1.0e-6,
rz: 4.0e-6,
rsk: 0.0,
rku: 3.0,
rsm: 80.0e-6,
}
}
pub fn lapped() -> Self {
SurfaceRoughness {
ra: 0.1e-6,
rq: 0.12e-6,
rz: 0.6e-6,
rsk: -0.2,
rku: 3.5,
rsm: 20.0e-6,
}
}
pub fn combined_rq(&self, other: &SurfaceRoughness) -> f64 {
(self.rq.powi(2) + other.rq.powi(2)).sqrt()
}
pub fn rq_ra_ratio(&self) -> f64 {
if self.ra < 1e-30 {
return 0.0;
}
self.rq / self.ra
}
pub fn surface_texture_index(&self) -> f64 {
if self.ra < 1e-30 {
return 0.0;
}
self.rz / self.ra
}
pub fn bearing_area(&self, z_norm: f64) -> f64 {
let threshold = self.rz * (1.0 - z_norm) - self.ra;
let z = threshold / (self.rq * 2.0_f64.sqrt());
0.5 * erfc(z)
}
pub fn valley_depth(&self) -> f64 {
self.rz * if self.rsk < 0.0 { 0.6 } else { 0.4 }
}
}
#[derive(Debug, Clone, Copy)]
pub struct FrictionEvolution {
pub mu_initial: f64,
pub mu_steady: f64,
pub time_constant: f64,
pub time: f64,
}
impl FrictionEvolution {
pub fn new(mu_initial: f64, mu_steady: f64, time_constant: f64) -> Self {
Self {
mu_initial,
mu_steady,
time_constant,
time: 0.0,
}
}
pub fn friction_at(&self, t: f64) -> f64 {
self.mu_steady + (self.mu_initial - self.mu_steady) * (-(t / self.time_constant)).exp()
}
pub fn step(&mut self, dt: f64) {
self.time += dt;
}
pub fn current_friction(&self) -> f64 {
self.friction_at(self.time)
}
pub fn convergence_time(&self, epsilon: f64) -> f64 {
let range = (self.mu_initial - self.mu_steady).abs();
if range < 1e-12 {
return 0.0;
}
-self.time_constant * (epsilon / range).ln()
}
}
#[derive(Debug, Clone, Copy)]
pub struct LubricantViscosity {
pub eta0: f64,
pub t0: f64,
pub beta: f64,
pub walther_a: f64,
pub walther_b: f64,
pub density: f64,
}
impl LubricantViscosity {
pub fn sae10w40() -> Self {
LubricantViscosity {
eta0: 0.1,
t0: 313.15,
beta: 0.028,
walther_a: 10.5,
walther_b: 3.5,
density: 870.0,
}
}
pub fn iso_vg46() -> Self {
LubricantViscosity {
eta0: 0.046,
t0: 313.15,
beta: 0.025,
walther_a: 9.8,
walther_b: 3.3,
density: 875.0,
}
}
pub fn eta_reynolds(&self, t: f64) -> f64 {
self.eta0 * (-(self.beta * (t - self.t0))).exp()
}
pub fn nu_kinematic(&self, t: f64) -> f64 {
self.eta_reynolds(t) / self.density
}
pub fn eta_barus(&self, t: f64, alpha_p: f64, pressure: f64) -> f64 {
self.eta_reynolds(t) * (alpha_p * pressure).exp()
}
pub fn viscosity_index(&self) -> f64 {
let eta40 = self.eta_reynolds(313.15);
let eta100 = self.eta_reynolds(373.15);
if eta100 < 1e-10 {
return 0.0;
}
100.0 * (eta40 - eta100) / eta100
}
pub fn pour_point_estimate(&self) -> f64 {
let target_eta = 3.0;
if self.eta0 <= target_eta {
return self.t0;
}
self.t0 - (target_eta / self.eta0).ln() / self.beta
}
}
#[derive(Debug, Clone)]
pub struct FrettingFatigue {
pub normal_load: f64,
pub mu: f64,
pub amplitude: f64,
pub contact_stiffness: f64,
pub fatigue_strength: f64,
pub paris_m: f64,
pub paris_c: f64,
pub a0: f64,
}
impl FrettingFatigue {
pub fn new(
normal_load: f64,
mu: f64,
amplitude: f64,
contact_stiffness: f64,
fatigue_strength: f64,
) -> Self {
FrettingFatigue {
normal_load,
mu,
amplitude,
contact_stiffness,
fatigue_strength,
paris_m: 3.0,
paris_c: 1e-11,
a0: 10e-6,
}
}
pub fn max_tangential_force(&self) -> f64 {
self.mu * self.normal_load
}
pub fn stick_slip_threshold(&self) -> f64 {
self.max_tangential_force() / self.contact_stiffness
}
pub fn is_gross_slip(&self) -> bool {
self.amplitude > self.stick_slip_threshold()
}
pub fn energy_per_cycle(&self) -> f64 {
if self.is_gross_slip() {
let delta_s = self.stick_slip_threshold();
4.0 * self.max_tangential_force() * (self.amplitude - delta_s)
} else {
let ratio = self.amplitude / self.stick_slip_threshold();
let w_stick = 4.0 * self.contact_stiffness * self.amplitude.powi(2);
w_stick * (1.0 - (1.0 - ratio).powi(3)) / 3.0
}
}
pub fn stress_intensity_range(&self, stress_range: f64, a: f64) -> f64 {
stress_range * (PI * a).sqrt()
}
pub fn cycles_to_crack(&self, stress_range: f64, a_critical: f64) -> f64 {
let m = self.paris_m;
let c = self.paris_c;
let f_sig = stress_range * PI.sqrt();
let exponent = 1.0 - m / 2.0;
if exponent.abs() < 1e-10 {
return (a_critical / self.a0).ln() / (c * f_sig.powi(2));
}
let denom = c * f_sig.powf(m) * exponent;
if denom.abs() < 1e-30 {
return f64::INFINITY;
}
(a_critical.powf(exponent) - self.a0.powf(exponent)) / denom
}
pub fn fretting_factor(&self) -> f64 {
let q_max = self.max_tangential_force();
1.0 + q_max / self.fatigue_strength.max(1.0)
}
}
#[derive(Debug, Clone, Copy)]
pub struct HertzContact {
pub effective_modulus: f64,
pub effective_radius: f64,
}
impl HertzContact {
pub fn sphere_sphere(e1: f64, nu1: f64, r1: f64, e2: f64, nu2: f64, r2: f64) -> Self {
let e_star = 1.0 / ((1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2);
let r_star = r1 * r2 / (r1 + r2);
Self {
effective_modulus: e_star,
effective_radius: r_star,
}
}
pub fn sphere_flat(e1: f64, nu1: f64, r: f64, e2: f64, nu2: f64) -> Self {
let e_star = 1.0 / ((1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2);
Self {
effective_modulus: e_star,
effective_radius: r,
}
}
pub fn cylinder_cylinder(e1: f64, nu1: f64, r1: f64, e2: f64, nu2: f64, r2: f64) -> Self {
let e_star = 1.0 / ((1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2);
let r_star = r1 * r2 / (r1 + r2);
Self {
effective_modulus: e_star,
effective_radius: r_star,
}
}
pub fn compute(&self, force: f64) -> HertzResult {
let a = ((3.0 * force * self.effective_radius) / (4.0 * self.effective_modulus))
.powf(1.0 / 3.0);
let p0 = if a > 1e-30 {
3.0 * force / (2.0 * PI * a * a)
} else {
0.0
};
let depth = a * a / self.effective_radius;
HertzResult {
contact_radius: a,
max_pressure: p0,
depth,
force,
}
}
pub fn force_for_radius(&self, a: f64) -> f64 {
4.0 * self.effective_modulus * a * a * a / (3.0 * self.effective_radius)
}
}
#[derive(Debug, Clone)]
pub struct AsperityModel {
pub asperity_density: f64,
pub rms_roughness: f64,
pub tip_radius: f64,
pub effective_modulus: f64,
}
impl AsperityModel {
pub fn new(
asperity_density: f64,
rms_roughness: f64,
tip_radius: f64,
effective_modulus: f64,
) -> Self {
Self {
asperity_density,
rms_roughness,
tip_radius,
effective_modulus,
}
}
pub fn plasticity_index(&self, hardness: f64) -> f64 {
(self.effective_modulus / hardness) * (self.rms_roughness / self.tip_radius).sqrt()
}
pub fn n_contacts(&self, area: f64, separation: f64) -> f64 {
let z = separation / (2.0_f64.sqrt() * self.rms_roughness);
let p = 0.5 * erfc(z);
self.asperity_density * area * p
}
pub fn bearing_area(&self, separation: f64) -> f64 {
let z = separation / (2.0_f64.sqrt() * self.rms_roughness);
0.5 * erfc(z)
}
pub fn normal_load(&self, area: f64, separation: f64) -> f64 {
let n = self.n_contacts(area, separation);
let avg_deform = self.rms_roughness;
let f_per =
(4.0 / 3.0) * self.effective_modulus * self.tip_radius.sqrt() * avg_deform.powf(1.5);
n * f_per
}
}
#[derive(Debug, Clone)]
pub struct StribeckCurve {
pub mu_boundary: f64,
pub mu_fullfilm: f64,
pub sc: f64,
pub exponent: f64,
pub mu_min: f64,
pub sn_min: f64,
}
impl StribeckCurve {
pub fn new(mu_boundary: f64, mu_fullfilm: f64, sc: f64) -> Self {
StribeckCurve {
mu_boundary,
mu_fullfilm,
sc,
exponent: 1.0,
mu_min: (mu_boundary + mu_fullfilm) / 2.0,
sn_min: sc,
}
}
pub fn hersey_number(&self, eta: f64, n: f64, p: f64) -> f64 {
if p < 1e-10 {
return f64::INFINITY;
}
eta * n / p
}
pub fn mu_at_hersey(&self, hs: f64) -> f64 {
let transition_hs = self.sc / 10.0;
if hs <= transition_hs {
self.mu_boundary
} else {
let x = (hs / self.sc).ln();
self.mu_min
+ (self.mu_boundary - self.mu_min) * (-x.powi(2) / 2.0).exp()
+ (self.mu_fullfilm - self.mu_min) * (1.0 - (-hs / self.sc).exp())
}
}
pub fn classify(&self, lambda: f64) -> LubricationRegimeKind {
if lambda < 1.0 {
LubricationRegimeKind::Boundary
} else if lambda < 3.0 {
LubricationRegimeKind::Mixed
} else if lambda < 10.0 {
LubricationRegimeKind::Elastohydrodynamic
} else {
LubricationRegimeKind::FullFilm
}
}
pub fn viscous_shear_stress(&self, eta: f64, v: f64, h: f64) -> f64 {
if h < 1e-30 {
return 0.0;
}
eta * v / h
}
}
#[derive(Debug, Clone)]
pub struct CoatingMaterial {
pub substrate_modulus: f64,
pub coating_modulus: f64,
pub coating_thickness: f64,
pub vickers_hardness: f64,
pub adhesion_strength: f64,
pub density: f64,
}
impl CoatingMaterial {
pub fn new(
substrate_modulus: f64,
coating_modulus: f64,
coating_thickness: f64,
vickers_hardness: f64,
adhesion_strength: f64,
density: f64,
) -> Self {
Self {
substrate_modulus,
coating_modulus,
coating_thickness,
vickers_hardness,
adhesion_strength,
density,
}
}
pub fn effective_hardness(&self, indent_depth: f64) -> f64 {
let ratio = indent_depth / self.coating_thickness;
if ratio < 0.1 {
self.vickers_hardness
} else {
let h_sub = 0.3 * self.vickers_hardness;
let t = ((ratio - 0.1) / 0.9).min(1.0);
self.vickers_hardness * (1.0 - t) + h_sub * t
}
}
pub fn will_delaminate(&self, scratch_load: f64) -> bool {
scratch_load > self.adhesion_strength
}
pub fn modulus_ratio(&self) -> f64 {
self.coating_modulus / self.substrate_modulus
}
}
#[derive(Debug, Clone, Copy)]
pub struct BowdenTaborFriction {
pub shear_strength: f64,
pub hardness: f64,
pub ploughing: f64,
}
impl BowdenTaborFriction {
pub fn new(shear_strength: f64, hardness: f64, ploughing: f64) -> Self {
BowdenTaborFriction {
shear_strength,
hardness,
ploughing,
}
}
pub fn mu_adhesion(&self) -> f64 {
self.shear_strength / self.hardness
}
pub fn mu_total(&self) -> f64 {
self.mu_adhesion() + self.ploughing
}
pub fn real_contact_fraction(&self, pressure: f64) -> f64 {
pressure / self.hardness
}
pub fn friction_force(&self, normal_load: f64) -> f64 {
self.mu_total() * normal_load
}
}
#[derive(Debug, Clone, Copy)]
pub struct HamrockDowson {
pub eta0: f64,
pub alpha_visc: f64,
pub e_prime: f64,
pub rx: f64,
pub ry: f64,
}
impl HamrockDowson {
pub fn new(eta0: f64, alpha_visc: f64, e_prime: f64, rx: f64, ry: f64) -> Self {
HamrockDowson {
eta0,
alpha_visc,
e_prime,
rx,
ry,
}
}
pub fn re(&self) -> f64 {
(self.rx * self.ry).sqrt()
}
pub fn ellipticity(&self) -> f64 {
(self.rx / self.ry).max(self.ry / self.rx)
}
pub fn speed_param(&self, u: f64) -> f64 {
self.eta0 * u / (self.e_prime * self.re())
}
pub fn load_param(&self, force: f64) -> f64 {
force / (self.e_prime * self.re().powi(2))
}
pub fn material_param(&self) -> f64 {
self.alpha_visc * self.e_prime
}
pub fn central_film_thickness(&self, u: f64, force: f64) -> f64 {
let uu = self.speed_param(u);
let ww = self.load_param(force);
let gg = self.material_param();
let k = self.ellipticity();
if ww < 1e-30 {
return 0.0;
}
let hc_re = 2.69
* uu.powf(0.67)
* gg.powf(0.53)
* ww.powf(-0.067)
* (1.0 - 0.61 * (-0.73 * k).exp());
hc_re * self.re()
}
pub fn minimum_film_thickness(&self, u: f64, force: f64) -> f64 {
let uu = self.speed_param(u);
let ww = self.load_param(force);
let gg = self.material_param();
let k = self.ellipticity();
if ww < 1e-30 {
return 0.0;
}
let hmin_re =
3.63 * uu.powf(0.68) * gg.powf(0.49) * ww.powf(-0.073) * (1.0 - (-0.68 * k).exp());
hmin_re * self.re()
}
pub fn lambda_ratio(&self, u: f64, force: f64, roughness: f64) -> f64 {
let hmin = self.minimum_film_thickness(u, force);
if roughness < 1e-30 {
return f64::INFINITY;
}
hmin / roughness
}
}
#[derive(Debug, Clone, Copy)]
pub struct ElastohydrodynamicFilm {
pub eta0: f64,
pub alpha: f64,
pub u: f64,
pub r: f64,
pub e_star: f64,
}
impl ElastohydrodynamicFilm {
pub fn new(eta0: f64, alpha: f64, u: f64, r: f64, e_star: f64) -> Self {
Self {
eta0,
alpha,
u,
r,
e_star,
}
}
fn speed_param(&self) -> f64 {
self.eta0 * self.u / (self.e_star * self.r)
}
fn material_param(&self) -> f64 {
self.alpha * self.e_star
}
fn load_param(&self, force: f64) -> f64 {
force / (self.e_star * self.r * self.r)
}
pub fn central_film_thickness(&self, force: f64) -> f64 {
let u_dim = self.speed_param();
let g_dim = self.material_param();
let w_dim = self.load_param(force).max(1e-30);
2.69 * u_dim.powf(0.67) * g_dim.powf(0.53) * w_dim.powf(-0.067) * self.r
}
pub fn minimum_film_thickness(&self, force: f64) -> f64 {
0.75 * self.central_film_thickness(force)
}
pub fn lambda_ratio(&self, composite_roughness: f64, force: f64) -> f64 {
let sigma = composite_roughness.max(1e-30);
self.minimum_film_thickness(force) / sigma
}
}
#[derive(Debug, Clone, Copy)]
pub struct DerjaguinMullerToporov {
pub effective_modulus: f64,
pub effective_radius: f64,
pub work_of_adhesion: f64,
}
impl DerjaguinMullerToporov {
pub fn new(effective_modulus: f64, effective_radius: f64, work_of_adhesion: f64) -> Self {
Self {
effective_modulus,
effective_radius,
work_of_adhesion,
}
}
pub fn pull_off_force(&self) -> f64 {
-2.0 * PI * self.work_of_adhesion * self.effective_radius
}
pub fn contact_radius(&self, force: f64) -> f64 {
let f_pull = self.pull_off_force().abs();
let f_eff = force + f_pull;
if f_eff <= 0.0 {
return 0.0;
}
let h = HertzContact {
effective_modulus: self.effective_modulus,
effective_radius: self.effective_radius,
};
h.compute(f_eff).contact_radius
}
pub fn tabor_parameter(&self, z0: f64) -> f64 {
let num = self.effective_radius * self.work_of_adhesion * self.work_of_adhesion;
let den = self.effective_modulus * self.effective_modulus * z0 * z0 * z0;
(num / den).powf(1.0 / 3.0)
}
}
#[derive(Debug, Clone, Copy)]
pub struct WearModel {
pub k_archard: f64,
pub hardness: f64,
}
impl WearModel {
pub fn new(k_archard: f64, hardness: f64) -> Self {
Self {
k_archard,
hardness,
}
}
pub fn wear_volume(&self, normal_force: f64, sliding_distance: f64) -> f64 {
self.k_archard * normal_force * sliding_distance / self.hardness
}
pub fn wear_rate(&self, normal_force: f64, sliding_speed: f64) -> f64 {
self.k_archard * normal_force * sliding_speed / self.hardness
}
}
#[derive(Debug, Clone, Copy)]
pub struct HolmWear {
pub z: f64,
}
impl HolmWear {
pub fn new(z: f64) -> Self {
HolmWear { z }
}
pub fn volume_rate(&self, normal_load: f64) -> f64 {
self.z * normal_load
}
pub fn volume_loss(&self, normal_load: f64, s: f64) -> f64 {
self.z * normal_load * s
}
pub fn wear_depth(&self, normal_load: f64, s: f64, area: f64) -> f64 {
if area < 1e-30 {
return 0.0;
}
self.volume_loss(normal_load, s) / area
}
}
#[derive(Debug, Clone, Copy)]
pub struct CylinderContact {
pub effective_modulus: f64,
pub effective_radius: f64,
pub contact_half_width: f64,
}
impl CylinderContact {
pub fn new(e1: f64, nu1: f64, r1: f64, e2: f64, nu2: f64, r2: f64) -> Self {
let e_star = 1.0 / ((1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2);
let r_star = r1 * r2 / (r1 + r2);
CylinderContact {
effective_modulus: e_star,
effective_radius: r_star,
contact_half_width: 0.0,
}
}
pub fn half_width(&self, w: f64) -> f64 {
(4.0 * w * self.effective_radius / (PI * self.effective_modulus)).sqrt()
}
pub fn max_pressure(&self, w: f64) -> f64 {
let a = self.half_width(w);
if a < 1e-30 {
return 0.0;
}
2.0 * w / (PI * a)
}
pub fn max_subsurface_shear(&self, w: f64) -> f64 {
0.300 * self.max_pressure(w)
}
pub fn pressure_at(&self, w: f64, x: f64) -> f64 {
let a = self.half_width(w);
let p0 = self.max_pressure(w);
if x.abs() >= a {
return 0.0;
}
p0 * (1.0 - (x / a).powi(2)).sqrt()
}
pub fn depth_max_shear(&self, w: f64) -> f64 {
0.786 * self.half_width(w)
}
}
#[derive(Debug, Clone)]
pub struct TribologicalState {
pub velocity: f64,
pub load: f64,
pub temperature: f64,
pub film_thickness: f64,
pub lambda: f64,
pub mu: f64,
pub wear_volume: f64,
pub regime: LubricationRegimeKind,
}
impl TribologicalState {
pub fn new(velocity: f64, load: f64) -> Self {
TribologicalState {
velocity,
load,
temperature: 293.15,
film_thickness: 0.0,
lambda: 0.0,
mu: 0.15,
wear_volume: 0.0,
regime: LubricationRegimeKind::Boundary,
}
}
pub fn update_film(&mut self, h: f64, roughness: f64) {
self.film_thickness = h;
self.lambda = if roughness > 1e-30 {
h / roughness
} else {
f64::INFINITY
};
self.regime = if self.lambda < 1.0 {
LubricationRegimeKind::Boundary
} else if self.lambda < 3.0 {
LubricationRegimeKind::Mixed
} else if self.lambda < 10.0 {
LubricationRegimeKind::Elastohydrodynamic
} else {
LubricationRegimeKind::FullFilm
};
}
pub fn accumulate_wear(&mut self, k: f64, hardness: f64, dt: f64) {
let rate = k * self.load * self.velocity / hardness;
self.wear_volume += rate * dt;
}
pub fn update_friction(&mut self, mu_boundary: f64, mu_full: f64) {
let t = ((self.lambda - 1.0) / 9.0).clamp(0.0, 1.0);
self.mu = mu_boundary * (1.0 - t) + mu_full * t;
}
pub fn power_dissipation(&self) -> f64 {
self.mu * self.load * self.velocity
}
}
#[derive(Debug, Clone)]
pub struct FrictionOscillator {
pub mass: f64,
pub stiffness: f64,
pub mu_s: f64,
pub mu_k: f64,
}
impl FrictionOscillator {
pub fn new(mass: f64, stiffness: f64, mu_s: f64, mu_k: f64) -> Self {
Self {
mass,
stiffness,
mu_s,
mu_k,
}
}
pub fn is_sticking(&self, v: f64, v_drive: f64) -> bool {
(v - v_drive).abs() < 1e-9
}
pub fn step(&mut self, x: f64, v: f64, v_drive: f64, dt: f64) -> (f64, f64) {
let g = 9.81;
let f_normal = self.mass * g;
let f_spring = -self.stiffness * x;
let relative_v = v - v_drive;
let f_friction = if relative_v.abs() < 1e-9 {
let f_static_max = self.mu_s * f_normal;
(-f_spring).clamp(-f_static_max, f_static_max)
} else {
-relative_v.signum() * self.mu_k * f_normal
};
let a = (f_spring + f_friction) / self.mass;
let new_v = v + a * dt;
let new_x = x + new_v * dt;
(new_x, new_v)
}
}
#[derive(Debug, Clone, Copy)]
pub struct JohnsonKendallRoberts {
pub effective_modulus: f64,
pub effective_radius: f64,
pub work_of_adhesion: f64,
}
impl JohnsonKendallRoberts {
pub fn new(effective_modulus: f64, effective_radius: f64, work_of_adhesion: f64) -> Self {
Self {
effective_modulus,
effective_radius,
work_of_adhesion,
}
}
pub fn pull_off_force(&self) -> f64 {
-1.5 * PI * self.work_of_adhesion * self.effective_radius
}
pub fn contact_radius(&self, force: f64) -> f64 {
let w = self.work_of_adhesion;
let r = self.effective_radius;
let e = self.effective_modulus;
let term1 = 3.0 * PI * w * r;
let discriminant = 6.0 * PI * w * r * force + term1 * term1;
if discriminant < 0.0 {
return 0.0;
}
let inner = force + term1 + discriminant.sqrt();
let a3 = r / e * inner;
a3.powf(1.0 / 3.0)
}
pub fn load_for_radius(&self, a: f64) -> f64 {
let e = self.effective_modulus;
let r = self.effective_radius;
let w = self.work_of_adhesion;
let hertz_term = 4.0 * e * a * a * a / (3.0 * r);
let adhesion_term = (8.0 * PI * e * w * a * a * a).sqrt();
hertz_term - adhesion_term
}
}
#[derive(Debug, Clone, Copy)]
pub struct EhlFilm {
pub viscosity: f64,
pub pressure_viscosity_coeff: f64,
pub effective_modulus: f64,
pub effective_radius: f64,
}
impl EhlFilm {
pub fn new(
viscosity: f64,
pressure_viscosity_coeff: f64,
effective_modulus: f64,
effective_radius: f64,
) -> Self {
Self {
viscosity,
pressure_viscosity_coeff,
effective_modulus,
effective_radius,
}
}
pub fn central_film_thickness_line(&self, u: f64, w: f64) -> f64 {
let ue = (self.viscosity * u) / (self.effective_modulus * self.effective_radius);
let we = w / (self.effective_modulus * self.effective_radius);
let ge = self.pressure_viscosity_coeff * self.effective_modulus;
if we < 1e-30 {
return 0.0;
}
let h_c_r = 2.65 * ge.powf(0.54) * ue.powf(0.7) / we.powf(0.13);
h_c_r * self.effective_radius
}
pub fn minimum_film_thickness_point(&self, u: f64, w: f64) -> f64 {
let ue = (self.viscosity * u) / (self.effective_modulus * self.effective_radius);
let we = w / (self.effective_modulus * self.effective_radius * self.effective_radius);
let ge = self.pressure_viscosity_coeff * self.effective_modulus;
if we < 1e-30 {
return 0.0;
}
let h_min_r = 3.63 * ue.powf(0.68) * ge.powf(0.49) * we.powf(-0.073);
h_min_r * self.effective_radius
}
pub fn lambda_ratio(&self, u: f64, w: f64, roughness: f64) -> f64 {
let h_min = self.minimum_film_thickness_point(u, w);
if roughness < 1e-30 {
f64::INFINITY
} else {
h_min / roughness
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct HertzContactParams {
pub r1: f64,
pub r2: f64,
pub e1: f64,
pub nu1: f64,
pub e2: f64,
pub nu2: f64,
}
impl HertzContactParams {
pub fn new(r1: f64, r2: f64, e1: f64, nu1: f64, e2: f64, nu2: f64) -> Self {
Self {
r1,
r2,
e1,
nu1,
e2,
nu2,
}
}
pub fn reduced_modulus(&self) -> f64 {
1.0 / ((1.0 - self.nu1 * self.nu1) / self.e1 + (1.0 - self.nu2 * self.nu2) / self.e2)
}
pub fn reduced_radius(&self) -> f64 {
if self.r1.is_infinite() {
return self.r2;
}
if self.r2.is_infinite() {
return self.r1;
}
self.r1 * self.r2 / (self.r1 + self.r2)
}
pub fn contact_radius(&self, force: f64) -> f64 {
let e_star = self.reduced_modulus();
let r_star = self.reduced_radius();
((3.0 * force * r_star) / (4.0 * e_star)).powf(1.0 / 3.0)
}
pub fn max_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 contact_stiffness(&self, force: f64) -> f64 {
let e_star = self.reduced_modulus();
let a = self.contact_radius(force);
2.0 * e_star * a
}
pub fn max_shear_stress(&self, force: f64) -> f64 {
0.31 * self.max_pressure(force)
}
}
#[derive(Debug, Clone)]
pub struct LubricationRegime {
pub mu_boundary: f64,
pub mu_fullfilm: f64,
pub stribeck_constant: f64,
}
impl LubricationRegime {
pub fn new(mu_boundary: f64, mu_fullfilm: f64, stribeck_constant: f64) -> Self {
Self {
mu_boundary,
mu_fullfilm,
stribeck_constant,
}
}
pub fn classify(&self, lambda: f64) -> LubricationRegimeKind {
if lambda < 1.0 {
LubricationRegimeKind::Boundary
} else if lambda < 3.0 {
LubricationRegimeKind::Mixed
} else if lambda < 10.0 {
LubricationRegimeKind::Elastohydrodynamic
} else {
LubricationRegimeKind::FullFilm
}
}
pub fn friction_coefficient(&self, eta: f64, velocity: f64, pressure: f64) -> f64 {
if pressure < 1e-10 {
return self.mu_boundary;
}
let stribeck_num = eta * velocity / pressure;
self.mu_fullfilm
+ (self.mu_boundary - self.mu_fullfilm)
* (-(stribeck_num / self.stribeck_constant)).exp()
}
}