1use super::functions::erfc;
6#[allow(unused_imports)]
7use super::functions::*;
8use std::f64::consts::PI;
9
10#[derive(Debug, Clone, Copy)]
12pub struct ArchardWear {
13 pub k: f64,
15 pub hardness: f64,
17}
18impl ArchardWear {
19 pub fn new(k: f64, hardness: f64) -> Self {
21 Self { k, hardness }
22 }
23 pub fn wear_rate(&self, pressure: f64) -> f64 {
26 self.k * pressure / self.hardness
27 }
28 pub fn volume_loss(&self, w: f64, s: f64) -> f64 {
30 self.k * w * s / self.hardness
31 }
32 pub fn depth_loss(&self, w: f64, s: f64, area: f64) -> f64 {
34 if area < 1e-30 {
35 return 0.0;
36 }
37 self.volume_loss(w, s) / area
38 }
39}
40#[derive(Debug, Clone, Copy)]
42pub struct ScuffingModel {
43 pub mu: f64,
45 pub k1: f64,
47 pub k2: f64,
49 pub rho_cp1: f64,
51 pub rho_cp2: f64,
53 pub t_critical: f64,
55 pub t_bulk: f64,
57}
58impl ScuffingModel {
59 pub fn steel_steel() -> Self {
61 ScuffingModel {
62 mu: 0.1,
63 k1: 50.0,
64 k2: 50.0,
65 rho_cp1: 3.75e6,
66 rho_cp2: 3.75e6,
67 t_critical: 700.0 + 273.15,
68 t_bulk: 80.0 + 273.15,
69 }
70 }
71 pub fn thermal_diffusivity_1(&self) -> f64 {
73 self.k1 / self.rho_cp1
74 }
75 pub fn thermal_diffusivity_2(&self) -> f64 {
77 self.k2 / self.rho_cp2
78 }
79 pub fn flash_temperature_rise(&self, q: f64, a: f64, v: f64) -> f64 {
82 if a < 1e-30 || v < 1e-10 {
83 return 0.0;
84 }
85 let beta1 = (self.rho_cp1 * self.k1).sqrt();
86 let beta2 = (self.rho_cp2 * self.k2).sqrt();
87 1.11 * q * (a / v).sqrt() / (beta1 + beta2)
88 }
89 pub fn contact_temperature(&self, q: f64, a: f64, v: f64) -> f64 {
91 self.t_bulk + self.flash_temperature_rise(q, a, v)
92 }
93 pub fn scuffing_risk(&self, q: f64, a: f64, v: f64) -> bool {
95 self.contact_temperature(q, a, v) > self.t_critical
96 }
97 pub fn safety_factor(&self, q: f64, a: f64, v: f64) -> f64 {
99 let tc = self.contact_temperature(q, a, v);
100 if tc < 1.0 {
101 return f64::INFINITY;
102 }
103 self.t_critical / tc
104 }
105}
106#[derive(Debug, Clone, Copy)]
108pub struct BearingLife {
109 pub c: f64,
111 pub p: f64,
113 pub life_exponent: f64,
115 pub a_iso: f64,
117 pub a_skf: f64,
119}
120impl BearingLife {
121 pub fn ball_bearing(c: f64, p: f64) -> Self {
123 BearingLife {
124 c,
125 p,
126 life_exponent: 3.0,
127 a_iso: 1.0,
128 a_skf: 1.0,
129 }
130 }
131 pub fn roller_bearing(c: f64, p: f64) -> Self {
133 BearingLife {
134 c,
135 p,
136 life_exponent: 10.0 / 3.0,
137 a_iso: 1.0,
138 a_skf: 1.0,
139 }
140 }
141 pub fn l10_mrv(&self) -> f64 {
143 if self.p < 1e-10 {
144 return f64::INFINITY;
145 }
146 (self.c / self.p).powf(self.life_exponent)
147 }
148 pub fn l10_hours(&self, n_rpm: f64) -> f64 {
150 if n_rpm < 1e-10 {
151 return f64::INFINITY;
152 }
153 self.l10_mrv() * 1e6 / (60.0 * n_rpm)
154 }
155 pub fn l10m_hours(&self, n_rpm: f64, a1: f64) -> f64 {
158 a1 * self.a_iso * self.a_skf * self.l10_hours(n_rpm)
159 }
160 pub fn reliability_at(&self, t_hours: f64, n_rpm: f64) -> f64 {
163 let l10 = self.l10_hours(n_rpm);
164 if l10 < 1e-10 {
165 return 0.0;
166 }
167 let l50 = 5.0 * l10;
168 let weibull_slope = 1.5_f64;
169 (-(t_hours / l50).powf(weibull_slope)).exp()
170 }
171 pub fn equivalent_load(fr: f64, fa: f64, x: f64, y: f64) -> f64 {
174 x * fr + y * fa
175 }
176 pub fn speed_factor(&self, n_rpm: f64) -> f64 {
178 if n_rpm < 1e-10 {
179 return 0.0;
180 }
181 (33.3 / n_rpm).powf(1.0 / 3.0)
182 }
183}
184#[derive(Debug, Clone, Copy)]
186pub struct HertzResult {
187 pub contact_radius: f64,
189 pub max_pressure: f64,
191 pub depth: f64,
193 pub force: f64,
195}
196#[derive(Debug, Clone, Copy)]
198pub struct ThermalWear {
199 pub friction_coeff: f64,
201 pub k1: f64,
203 pub k2: f64,
205 pub t_ambient: f64,
207 pub activation_energy: f64,
209 pub wear_factor_a0: f64,
211}
212impl ThermalWear {
213 pub fn new(
215 friction_coeff: f64,
216 k1: f64,
217 k2: f64,
218 t_ambient: f64,
219 activation_energy: f64,
220 wear_factor_a0: f64,
221 ) -> Self {
222 Self {
223 friction_coeff,
224 k1,
225 k2,
226 t_ambient,
227 activation_energy,
228 wear_factor_a0,
229 }
230 }
231 pub fn flash_temperature(&self, f: f64, v: f64, a: f64) -> f64 {
234 if a < 1e-30 {
235 return self.t_ambient;
236 }
237 let heat_rate = self.friction_coeff * f * v;
238 let delta_t = heat_rate / (PI * a * (self.k1 + self.k2));
239 self.t_ambient + delta_t
240 }
241 pub fn oxidative_wear_rate(&self, temperature: f64) -> f64 {
244 const R_GAS: f64 = 8.314;
245 self.wear_factor_a0 * (-(self.activation_energy / (R_GAS * temperature))).exp()
246 }
247 pub fn total_wear_volume(&self, f: f64, v: f64, a: f64, t: f64) -> f64 {
249 let temp = self.flash_temperature(f, v, a);
250 let rate = self.oxidative_wear_rate(temp);
251 rate * a * a * t
252 }
253}
254#[derive(Debug, Clone, Copy)]
256pub struct AbrasiveWear {
257 pub ka: f64,
259 pub hardness: f64,
261 pub particle_size: f64,
263 pub efficiency: f64,
265}
266impl AbrasiveWear {
267 pub fn new(ka: f64, hardness: f64, particle_size: f64, efficiency: f64) -> Self {
269 AbrasiveWear {
270 ka,
271 hardness,
272 particle_size,
273 efficiency,
274 }
275 }
276 pub fn two_body_volume(&self, w: f64, s: f64) -> f64 {
278 self.ka * w * s / self.hardness
279 }
280 pub fn three_body_volume(&self, w: f64, s: f64) -> f64 {
282 self.two_body_volume(w, s) / 3.0
283 }
284 pub fn scratch_depth(&self, w_per_asperity: f64) -> f64 {
286 (2.0 * w_per_asperity / (PI * self.hardness)).sqrt()
287 }
288 pub fn volume_per_scratch(&self, w_per_asperity: f64, scratch_length: f64) -> f64 {
290 let depth = self.scratch_depth(w_per_asperity);
291 0.5 * PI * depth.powi(2) * scratch_length
292 }
293 pub fn specific_wear_rate(&self) -> f64 {
295 self.ka / self.hardness
296 }
297}
298#[derive(Debug, Clone, Copy)]
301pub struct HertzElliptical {
302 pub effective_modulus: f64,
304 pub sum_curvature: f64,
306 pub curvature_difference: f64,
308}
309impl HertzElliptical {
310 #[allow(clippy::too_many_arguments)]
313 pub fn new(
314 e1: f64,
315 nu1: f64,
316 e2: f64,
317 nu2: f64,
318 r1a: f64,
319 r1b: f64,
320 r2a: f64,
321 r2b: f64,
322 ) -> Self {
323 let e_star = 1.0 / ((1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2);
324 let sum_k = 1.0 / r1a + 1.0 / r1b + 1.0 / r2a + 1.0 / r2b;
325 let diff_k = (1.0 / r1a - 1.0 / r1b + 1.0 / r2a - 1.0 / r2b)
326 .powi(2)
327 .sqrt();
328 HertzElliptical {
329 effective_modulus: e_star,
330 sum_curvature: sum_k,
331 curvature_difference: diff_k,
332 }
333 }
334 pub fn ellipticity_ratio(&self) -> f64 {
337 let cap_b = (self.sum_curvature + self.curvature_difference) / 2.0;
338 let cap_a = (self.sum_curvature - self.curvature_difference) / 2.0;
339 if cap_a < 1e-30 {
340 return 1.0;
341 }
342 (cap_b / cap_a).powf(2.0 / 3.0).min(1.0)
343 }
344 pub fn semi_axis_a(&self, force: f64) -> f64 {
346 let r_eff = 2.0 / self.sum_curvature;
347 let h = HertzContact {
348 effective_modulus: self.effective_modulus,
349 effective_radius: r_eff,
350 };
351 h.compute(force).contact_radius
352 }
353 pub fn semi_axis_b(&self, force: f64) -> f64 {
355 self.ellipticity_ratio() * self.semi_axis_a(force)
356 }
357 pub fn max_pressure(&self, force: f64) -> f64 {
359 let a = self.semi_axis_a(force);
360 let b = self.semi_axis_b(force);
361 if a < 1e-30 || b < 1e-30 {
362 return 0.0;
363 }
364 3.0 * force / (2.0 * PI * a * b)
365 }
366 pub fn contact_area(&self, force: f64) -> f64 {
368 PI * self.semi_axis_a(force) * self.semi_axis_b(force)
369 }
370}
371#[derive(Debug, Clone)]
373pub struct GearTooth {
374 pub module: f64,
376 pub z1: u32,
378 pub z2: u32,
380 pub pressure_angle: f64,
382 pub face_width: f64,
384 pub wt: f64,
386 pub ze: f64,
388 pub n1: f64,
390}
391impl GearTooth {
392 pub fn spur_pair(
394 module: f64,
395 z1: u32,
396 z2: u32,
397 pressure_angle: f64,
398 face_width: f64,
399 wt: f64,
400 n1: f64,
401 ) -> Self {
402 GearTooth {
403 module,
404 z1,
405 z2,
406 pressure_angle,
407 face_width,
408 wt,
409 ze: 191.0,
410 n1,
411 }
412 }
413 pub fn d1(&self) -> f64 {
415 self.module * self.z1 as f64
416 }
417 pub fn d2(&self) -> f64 {
419 self.module * self.z2 as f64
420 }
421 pub fn pitch_line_velocity(&self) -> f64 {
423 PI * self.d1() * self.n1 / (60.0 * 1000.0)
424 }
425 pub fn gear_ratio(&self) -> f64 {
427 self.z2 as f64 / self.z1 as f64
428 }
429 pub fn contact_ratio(&self) -> f64 {
431 let phi = self.pressure_angle.to_radians();
432 let r1 = self.d1() / 2.0;
433 let r2 = self.d2() / 2.0;
434 let ra1 = r1 + self.module;
435 let ra2 = r2 + self.module;
436 let cp = (self.d1() + self.d2()) / 2.0 * phi.sin();
437 let term1 = (ra1.powi(2) - (r1 * phi.cos()).powi(2)).sqrt();
438 let term2 = (ra2.powi(2) - (r2 * phi.cos()).powi(2)).sqrt();
439 (term1 + term2 - cp) / (PI * self.module * phi.cos())
440 }
441 pub fn hertz_contact_stress(&self) -> f64 {
443 let phi = self.pressure_angle.to_radians();
444 let d1 = self.d1();
445 let i = self.gear_ratio();
446 let term = self.wt / (d1 * self.face_width) * (i + 1.0) / i / phi.cos();
447 if term < 0.0 {
448 return 0.0;
449 }
450 self.ze * term.sqrt()
451 }
452 pub fn sliding_velocity_at(&self, s_mm: f64) -> f64 {
454 let _vp = self.pitch_line_velocity() * 1000.0;
455 let omega1 = 2.0 * PI * self.n1 / 60.0;
456 let i = self.gear_ratio();
457 omega1 * s_mm * (1.0 + 1.0 / i) / 1000.0
458 }
459 pub fn sliding_ratio(&self) -> f64 {
461 let phi = self.pressure_angle.to_radians();
462 2.0 * PI * (1.0 / self.z1 as f64 + 1.0 / self.z2 as f64) / (PI * phi.cos())
463 * self.contact_ratio()
464 }
465 pub fn friction_power_loss(&self, mu: f64) -> f64 {
467 let vs = self.sliding_velocity_at(self.module);
468 mu * self.wt * vs.abs()
469 }
470}
471#[derive(Debug, Clone)]
473pub struct BrakePad {
474 pub fn_force: f64,
476 pub mu: f64,
478 pub rotor_radius: f64,
480 pub pad_area: f64,
482 pub k_pad: f64,
484 pub beta_pad: f64,
486 pub kw: f64,
488 pub tp: f64,
490 pub cp_pad: f64,
492 pub density_pad: f64,
494}
495impl BrakePad {
496 pub fn semi_metallic() -> Self {
498 BrakePad {
499 fn_force: 3000.0,
500 mu: 0.40,
501 rotor_radius: 0.12,
502 pad_area: 50e-4,
503 k_pad: 2.0,
504 beta_pad: 0.05,
505 kw: 1e-14,
506 tp: 0.012,
507 cp_pad: 900.0,
508 density_pad: 2500.0,
509 }
510 }
511 pub fn braking_torque(&self) -> f64 {
513 self.mu * self.fn_force * self.rotor_radius
514 }
515 pub fn frictional_power(&self, v: f64) -> f64 {
517 self.mu * self.fn_force * v
518 }
519 pub fn temperature_rise_steady(&self, v: f64) -> f64 {
521 let q = self.frictional_power(v) * self.beta_pad;
522 if self.k_pad < 1e-10 || self.pad_area < 1e-10 {
523 return 0.0;
524 }
525 q * self.tp / (self.k_pad * self.pad_area)
526 }
527 pub fn temperature_rise_transient(&self, v: f64, t: f64) -> f64 {
529 let q = self.frictional_power(v) * self.beta_pad;
530 let mass = self.density_pad * self.pad_area * self.tp;
531 if mass < 1e-10 {
532 return 0.0;
533 }
534 q * t / (mass * self.cp_pad)
535 }
536 pub fn wear_depth(&self, s: f64) -> f64 {
538 self.kw * self.fn_force * s / self.pad_area
539 }
540 pub fn remaining_life(&self, current_thickness: f64, min_thickness: f64) -> f64 {
542 let wear_allowed = current_thickness - min_thickness;
543 if wear_allowed <= 0.0 {
544 return 0.0;
545 }
546 wear_allowed * self.pad_area / (self.kw * self.fn_force)
547 }
548 pub fn contact_pressure(&self) -> f64 {
550 self.fn_force / self.pad_area
551 }
552}
553#[derive(Debug, Clone, Copy, PartialEq)]
555pub enum LubricationRegimeKind {
556 Boundary,
558 Mixed,
560 Elastohydrodynamic,
562 FullFilm,
564}
565#[derive(Debug, Clone)]
567pub struct SurfaceRoughness {
568 pub ra: f64,
570 pub rq: f64,
572 pub rz: f64,
574 pub rsk: f64,
576 pub rku: f64,
578 pub rsm: f64,
580}
581impl SurfaceRoughness {
582 pub fn new(ra: f64, rq: f64, rz: f64, rsk: f64, rku: f64, rsm: f64) -> Self {
584 SurfaceRoughness {
585 ra,
586 rq,
587 rz,
588 rsk,
589 rku,
590 rsm,
591 }
592 }
593 pub fn ground_steel() -> Self {
595 SurfaceRoughness {
596 ra: 0.8e-6,
597 rq: 1.0e-6,
598 rz: 4.0e-6,
599 rsk: 0.0,
600 rku: 3.0,
601 rsm: 80.0e-6,
602 }
603 }
604 pub fn lapped() -> Self {
606 SurfaceRoughness {
607 ra: 0.1e-6,
608 rq: 0.12e-6,
609 rz: 0.6e-6,
610 rsk: -0.2,
611 rku: 3.5,
612 rsm: 20.0e-6,
613 }
614 }
615 pub fn combined_rq(&self, other: &SurfaceRoughness) -> f64 {
617 (self.rq.powi(2) + other.rq.powi(2)).sqrt()
618 }
619 pub fn rq_ra_ratio(&self) -> f64 {
621 if self.ra < 1e-30 {
622 return 0.0;
623 }
624 self.rq / self.ra
625 }
626 pub fn surface_texture_index(&self) -> f64 {
628 if self.ra < 1e-30 {
629 return 0.0;
630 }
631 self.rz / self.ra
632 }
633 pub fn bearing_area(&self, z_norm: f64) -> f64 {
636 let threshold = self.rz * (1.0 - z_norm) - self.ra;
637 let z = threshold / (self.rq * 2.0_f64.sqrt());
638 0.5 * erfc(z)
639 }
640 pub fn valley_depth(&self) -> f64 {
642 self.rz * if self.rsk < 0.0 { 0.6 } else { 0.4 }
643 }
644}
645#[derive(Debug, Clone, Copy)]
647pub struct FrictionEvolution {
648 pub mu_initial: f64,
650 pub mu_steady: f64,
652 pub time_constant: f64,
654 pub time: f64,
656}
657impl FrictionEvolution {
658 pub fn new(mu_initial: f64, mu_steady: f64, time_constant: f64) -> Self {
660 Self {
661 mu_initial,
662 mu_steady,
663 time_constant,
664 time: 0.0,
665 }
666 }
667 pub fn friction_at(&self, t: f64) -> f64 {
669 self.mu_steady + (self.mu_initial - self.mu_steady) * (-(t / self.time_constant)).exp()
670 }
671 pub fn step(&mut self, dt: f64) {
673 self.time += dt;
674 }
675 pub fn current_friction(&self) -> f64 {
677 self.friction_at(self.time)
678 }
679 pub fn convergence_time(&self, epsilon: f64) -> f64 {
681 let range = (self.mu_initial - self.mu_steady).abs();
682 if range < 1e-12 {
683 return 0.0;
684 }
685 -self.time_constant * (epsilon / range).ln()
686 }
687}
688#[derive(Debug, Clone, Copy)]
690pub struct LubricantViscosity {
691 pub eta0: f64,
693 pub t0: f64,
695 pub beta: f64,
697 pub walther_a: f64,
699 pub walther_b: f64,
701 pub density: f64,
703}
704impl LubricantViscosity {
705 pub fn sae10w40() -> Self {
707 LubricantViscosity {
708 eta0: 0.1,
709 t0: 313.15,
710 beta: 0.028,
711 walther_a: 10.5,
712 walther_b: 3.5,
713 density: 870.0,
714 }
715 }
716 pub fn iso_vg46() -> Self {
718 LubricantViscosity {
719 eta0: 0.046,
720 t0: 313.15,
721 beta: 0.025,
722 walther_a: 9.8,
723 walther_b: 3.3,
724 density: 875.0,
725 }
726 }
727 pub fn eta_reynolds(&self, t: f64) -> f64 {
729 self.eta0 * (-(self.beta * (t - self.t0))).exp()
730 }
731 pub fn nu_kinematic(&self, t: f64) -> f64 {
733 self.eta_reynolds(t) / self.density
734 }
735 pub fn eta_barus(&self, t: f64, alpha_p: f64, pressure: f64) -> f64 {
737 self.eta_reynolds(t) * (alpha_p * pressure).exp()
738 }
739 pub fn viscosity_index(&self) -> f64 {
742 let eta40 = self.eta_reynolds(313.15);
743 let eta100 = self.eta_reynolds(373.15);
744 if eta100 < 1e-10 {
745 return 0.0;
746 }
747 100.0 * (eta40 - eta100) / eta100
748 }
749 pub fn pour_point_estimate(&self) -> f64 {
751 let target_eta = 3.0;
752 if self.eta0 <= target_eta {
753 return self.t0;
754 }
755 self.t0 - (target_eta / self.eta0).ln() / self.beta
756 }
757}
758#[derive(Debug, Clone)]
760pub struct FrettingFatigue {
761 pub normal_load: f64,
763 pub mu: f64,
765 pub amplitude: f64,
767 pub contact_stiffness: f64,
769 pub fatigue_strength: f64,
771 pub paris_m: f64,
773 pub paris_c: f64,
775 pub a0: f64,
777}
778impl FrettingFatigue {
779 pub fn new(
781 normal_load: f64,
782 mu: f64,
783 amplitude: f64,
784 contact_stiffness: f64,
785 fatigue_strength: f64,
786 ) -> Self {
787 FrettingFatigue {
788 normal_load,
789 mu,
790 amplitude,
791 contact_stiffness,
792 fatigue_strength,
793 paris_m: 3.0,
794 paris_c: 1e-11,
795 a0: 10e-6,
796 }
797 }
798 pub fn max_tangential_force(&self) -> f64 {
800 self.mu * self.normal_load
801 }
802 pub fn stick_slip_threshold(&self) -> f64 {
804 self.max_tangential_force() / self.contact_stiffness
805 }
806 pub fn is_gross_slip(&self) -> bool {
808 self.amplitude > self.stick_slip_threshold()
809 }
810 pub fn energy_per_cycle(&self) -> f64 {
812 if self.is_gross_slip() {
813 let delta_s = self.stick_slip_threshold();
814 4.0 * self.max_tangential_force() * (self.amplitude - delta_s)
815 } else {
816 let ratio = self.amplitude / self.stick_slip_threshold();
817 let w_stick = 4.0 * self.contact_stiffness * self.amplitude.powi(2);
818 w_stick * (1.0 - (1.0 - ratio).powi(3)) / 3.0
819 }
820 }
821 pub fn stress_intensity_range(&self, stress_range: f64, a: f64) -> f64 {
823 stress_range * (PI * a).sqrt()
824 }
825 pub fn cycles_to_crack(&self, stress_range: f64, a_critical: f64) -> f64 {
827 let m = self.paris_m;
828 let c = self.paris_c;
829 let f_sig = stress_range * PI.sqrt();
830 let exponent = 1.0 - m / 2.0;
831 if exponent.abs() < 1e-10 {
832 return (a_critical / self.a0).ln() / (c * f_sig.powi(2));
833 }
834 let denom = c * f_sig.powf(m) * exponent;
835 if denom.abs() < 1e-30 {
836 return f64::INFINITY;
837 }
838 (a_critical.powf(exponent) - self.a0.powf(exponent)) / denom
839 }
840 pub fn fretting_factor(&self) -> f64 {
842 let q_max = self.max_tangential_force();
843 1.0 + q_max / self.fatigue_strength.max(1.0)
844 }
845}
846#[derive(Debug, Clone, Copy)]
848pub struct HertzContact {
849 pub effective_modulus: f64,
851 pub effective_radius: f64,
853}
854impl HertzContact {
855 pub fn sphere_sphere(e1: f64, nu1: f64, r1: f64, e2: f64, nu2: f64, r2: f64) -> Self {
858 let e_star = 1.0 / ((1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2);
859 let r_star = r1 * r2 / (r1 + r2);
860 Self {
861 effective_modulus: e_star,
862 effective_radius: r_star,
863 }
864 }
865 pub fn sphere_flat(e1: f64, nu1: f64, r: f64, e2: f64, nu2: f64) -> Self {
867 let e_star = 1.0 / ((1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2);
868 Self {
869 effective_modulus: e_star,
870 effective_radius: r,
871 }
872 }
873 pub fn cylinder_cylinder(e1: f64, nu1: f64, r1: f64, e2: f64, nu2: f64, r2: f64) -> Self {
876 let e_star = 1.0 / ((1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2);
877 let r_star = r1 * r2 / (r1 + r2);
878 Self {
879 effective_modulus: e_star,
880 effective_radius: r_star,
881 }
882 }
883 pub fn compute(&self, force: f64) -> HertzResult {
885 let a = ((3.0 * force * self.effective_radius) / (4.0 * self.effective_modulus))
886 .powf(1.0 / 3.0);
887 let p0 = if a > 1e-30 {
888 3.0 * force / (2.0 * PI * a * a)
889 } else {
890 0.0
891 };
892 let depth = a * a / self.effective_radius;
893 HertzResult {
894 contact_radius: a,
895 max_pressure: p0,
896 depth,
897 force,
898 }
899 }
900 pub fn force_for_radius(&self, a: f64) -> f64 {
902 4.0 * self.effective_modulus * a * a * a / (3.0 * self.effective_radius)
903 }
904}
905#[derive(Debug, Clone)]
907pub struct AsperityModel {
908 pub asperity_density: f64,
910 pub rms_roughness: f64,
912 pub tip_radius: f64,
914 pub effective_modulus: f64,
916}
917impl AsperityModel {
918 pub fn new(
920 asperity_density: f64,
921 rms_roughness: f64,
922 tip_radius: f64,
923 effective_modulus: f64,
924 ) -> Self {
925 Self {
926 asperity_density,
927 rms_roughness,
928 tip_radius,
929 effective_modulus,
930 }
931 }
932 pub fn plasticity_index(&self, hardness: f64) -> f64 {
934 (self.effective_modulus / hardness) * (self.rms_roughness / self.tip_radius).sqrt()
935 }
936 pub fn n_contacts(&self, area: f64, separation: f64) -> f64 {
939 let z = separation / (2.0_f64.sqrt() * self.rms_roughness);
940 let p = 0.5 * erfc(z);
941 self.asperity_density * area * p
942 }
943 pub fn bearing_area(&self, separation: f64) -> f64 {
945 let z = separation / (2.0_f64.sqrt() * self.rms_roughness);
946 0.5 * erfc(z)
947 }
948 pub fn normal_load(&self, area: f64, separation: f64) -> f64 {
950 let n = self.n_contacts(area, separation);
951 let avg_deform = self.rms_roughness;
952 let f_per =
953 (4.0 / 3.0) * self.effective_modulus * self.tip_radius.sqrt() * avg_deform.powf(1.5);
954 n * f_per
955 }
956}
957#[derive(Debug, Clone)]
959pub struct StribeckCurve {
960 pub mu_boundary: f64,
962 pub mu_fullfilm: f64,
964 pub sc: f64,
966 pub exponent: f64,
968 pub mu_min: f64,
970 pub sn_min: f64,
972}
973impl StribeckCurve {
974 pub fn new(mu_boundary: f64, mu_fullfilm: f64, sc: f64) -> Self {
976 StribeckCurve {
977 mu_boundary,
978 mu_fullfilm,
979 sc,
980 exponent: 1.0,
981 mu_min: (mu_boundary + mu_fullfilm) / 2.0,
982 sn_min: sc,
983 }
984 }
985 pub fn hersey_number(&self, eta: f64, n: f64, p: f64) -> f64 {
987 if p < 1e-10 {
988 return f64::INFINITY;
989 }
990 eta * n / p
991 }
992 pub fn mu_at_hersey(&self, hs: f64) -> f64 {
995 let transition_hs = self.sc / 10.0;
996 if hs <= transition_hs {
997 self.mu_boundary
998 } else {
999 let x = (hs / self.sc).ln();
1000 self.mu_min
1001 + (self.mu_boundary - self.mu_min) * (-x.powi(2) / 2.0).exp()
1002 + (self.mu_fullfilm - self.mu_min) * (1.0 - (-hs / self.sc).exp())
1003 }
1004 }
1005 pub fn classify(&self, lambda: f64) -> LubricationRegimeKind {
1007 if lambda < 1.0 {
1008 LubricationRegimeKind::Boundary
1009 } else if lambda < 3.0 {
1010 LubricationRegimeKind::Mixed
1011 } else if lambda < 10.0 {
1012 LubricationRegimeKind::Elastohydrodynamic
1013 } else {
1014 LubricationRegimeKind::FullFilm
1015 }
1016 }
1017 pub fn viscous_shear_stress(&self, eta: f64, v: f64, h: f64) -> f64 {
1019 if h < 1e-30 {
1020 return 0.0;
1021 }
1022 eta * v / h
1023 }
1024}
1025#[derive(Debug, Clone)]
1027pub struct CoatingMaterial {
1028 pub substrate_modulus: f64,
1030 pub coating_modulus: f64,
1032 pub coating_thickness: f64,
1034 pub vickers_hardness: f64,
1036 pub adhesion_strength: f64,
1038 pub density: f64,
1040}
1041impl CoatingMaterial {
1042 pub fn new(
1044 substrate_modulus: f64,
1045 coating_modulus: f64,
1046 coating_thickness: f64,
1047 vickers_hardness: f64,
1048 adhesion_strength: f64,
1049 density: f64,
1050 ) -> Self {
1051 Self {
1052 substrate_modulus,
1053 coating_modulus,
1054 coating_thickness,
1055 vickers_hardness,
1056 adhesion_strength,
1057 density,
1058 }
1059 }
1060 pub fn effective_hardness(&self, indent_depth: f64) -> f64 {
1063 let ratio = indent_depth / self.coating_thickness;
1064 if ratio < 0.1 {
1065 self.vickers_hardness
1066 } else {
1067 let h_sub = 0.3 * self.vickers_hardness;
1068 let t = ((ratio - 0.1) / 0.9).min(1.0);
1069 self.vickers_hardness * (1.0 - t) + h_sub * t
1070 }
1071 }
1072 pub fn will_delaminate(&self, scratch_load: f64) -> bool {
1074 scratch_load > self.adhesion_strength
1075 }
1076 pub fn modulus_ratio(&self) -> f64 {
1078 self.coating_modulus / self.substrate_modulus
1079 }
1080}
1081#[derive(Debug, Clone, Copy)]
1083pub struct BowdenTaborFriction {
1084 pub shear_strength: f64,
1086 pub hardness: f64,
1088 pub ploughing: f64,
1090}
1091impl BowdenTaborFriction {
1092 pub fn new(shear_strength: f64, hardness: f64, ploughing: f64) -> Self {
1094 BowdenTaborFriction {
1095 shear_strength,
1096 hardness,
1097 ploughing,
1098 }
1099 }
1100 pub fn mu_adhesion(&self) -> f64 {
1102 self.shear_strength / self.hardness
1103 }
1104 pub fn mu_total(&self) -> f64 {
1106 self.mu_adhesion() + self.ploughing
1107 }
1108 pub fn real_contact_fraction(&self, pressure: f64) -> f64 {
1110 pressure / self.hardness
1111 }
1112 pub fn friction_force(&self, normal_load: f64) -> f64 {
1114 self.mu_total() * normal_load
1115 }
1116}
1117#[derive(Debug, Clone, Copy)]
1119pub struct HamrockDowson {
1120 pub eta0: f64,
1122 pub alpha_visc: f64,
1124 pub e_prime: f64,
1126 pub rx: f64,
1128 pub ry: f64,
1130}
1131impl HamrockDowson {
1132 pub fn new(eta0: f64, alpha_visc: f64, e_prime: f64, rx: f64, ry: f64) -> Self {
1134 HamrockDowson {
1135 eta0,
1136 alpha_visc,
1137 e_prime,
1138 rx,
1139 ry,
1140 }
1141 }
1142 pub fn re(&self) -> f64 {
1144 (self.rx * self.ry).sqrt()
1145 }
1146 pub fn ellipticity(&self) -> f64 {
1148 (self.rx / self.ry).max(self.ry / self.rx)
1149 }
1150 pub fn speed_param(&self, u: f64) -> f64 {
1152 self.eta0 * u / (self.e_prime * self.re())
1153 }
1154 pub fn load_param(&self, force: f64) -> f64 {
1156 force / (self.e_prime * self.re().powi(2))
1157 }
1158 pub fn material_param(&self) -> f64 {
1160 self.alpha_visc * self.e_prime
1161 }
1162 pub fn central_film_thickness(&self, u: f64, force: f64) -> f64 {
1165 let uu = self.speed_param(u);
1166 let ww = self.load_param(force);
1167 let gg = self.material_param();
1168 let k = self.ellipticity();
1169 if ww < 1e-30 {
1170 return 0.0;
1171 }
1172 let hc_re = 2.69
1173 * uu.powf(0.67)
1174 * gg.powf(0.53)
1175 * ww.powf(-0.067)
1176 * (1.0 - 0.61 * (-0.73 * k).exp());
1177 hc_re * self.re()
1178 }
1179 pub fn minimum_film_thickness(&self, u: f64, force: f64) -> f64 {
1182 let uu = self.speed_param(u);
1183 let ww = self.load_param(force);
1184 let gg = self.material_param();
1185 let k = self.ellipticity();
1186 if ww < 1e-30 {
1187 return 0.0;
1188 }
1189 let hmin_re =
1190 3.63 * uu.powf(0.68) * gg.powf(0.49) * ww.powf(-0.073) * (1.0 - (-0.68 * k).exp());
1191 hmin_re * self.re()
1192 }
1193 pub fn lambda_ratio(&self, u: f64, force: f64, roughness: f64) -> f64 {
1195 let hmin = self.minimum_film_thickness(u, force);
1196 if roughness < 1e-30 {
1197 return f64::INFINITY;
1198 }
1199 hmin / roughness
1200 }
1201}
1202#[derive(Debug, Clone, Copy)]
1207pub struct ElastohydrodynamicFilm {
1208 pub eta0: f64,
1210 pub alpha: f64,
1212 pub u: f64,
1214 pub r: f64,
1216 pub e_star: f64,
1218}
1219impl ElastohydrodynamicFilm {
1220 pub fn new(eta0: f64, alpha: f64, u: f64, r: f64, e_star: f64) -> Self {
1222 Self {
1223 eta0,
1224 alpha,
1225 u,
1226 r,
1227 e_star,
1228 }
1229 }
1230 fn speed_param(&self) -> f64 {
1232 self.eta0 * self.u / (self.e_star * self.r)
1233 }
1234 fn material_param(&self) -> f64 {
1236 self.alpha * self.e_star
1237 }
1238 fn load_param(&self, force: f64) -> f64 {
1240 force / (self.e_star * self.r * self.r)
1241 }
1242 pub fn central_film_thickness(&self, force: f64) -> f64 {
1246 let u_dim = self.speed_param();
1247 let g_dim = self.material_param();
1248 let w_dim = self.load_param(force).max(1e-30);
1249 2.69 * u_dim.powf(0.67) * g_dim.powf(0.53) * w_dim.powf(-0.067) * self.r
1250 }
1251 pub fn minimum_film_thickness(&self, force: f64) -> f64 {
1255 0.75 * self.central_film_thickness(force)
1256 }
1257 pub fn lambda_ratio(&self, composite_roughness: f64, force: f64) -> f64 {
1261 let sigma = composite_roughness.max(1e-30);
1262 self.minimum_film_thickness(force) / sigma
1263 }
1264}
1265#[derive(Debug, Clone, Copy)]
1267pub struct DerjaguinMullerToporov {
1268 pub effective_modulus: f64,
1270 pub effective_radius: f64,
1272 pub work_of_adhesion: f64,
1274}
1275impl DerjaguinMullerToporov {
1276 pub fn new(effective_modulus: f64, effective_radius: f64, work_of_adhesion: f64) -> Self {
1278 Self {
1279 effective_modulus,
1280 effective_radius,
1281 work_of_adhesion,
1282 }
1283 }
1284 pub fn pull_off_force(&self) -> f64 {
1286 -2.0 * PI * self.work_of_adhesion * self.effective_radius
1287 }
1288 pub fn contact_radius(&self, force: f64) -> f64 {
1290 let f_pull = self.pull_off_force().abs();
1291 let f_eff = force + f_pull;
1292 if f_eff <= 0.0 {
1293 return 0.0;
1294 }
1295 let h = HertzContact {
1296 effective_modulus: self.effective_modulus,
1297 effective_radius: self.effective_radius,
1298 };
1299 h.compute(f_eff).contact_radius
1300 }
1301 pub fn tabor_parameter(&self, z0: f64) -> f64 {
1304 let num = self.effective_radius * self.work_of_adhesion * self.work_of_adhesion;
1305 let den = self.effective_modulus * self.effective_modulus * z0 * z0 * z0;
1306 (num / den).powf(1.0 / 3.0)
1307 }
1308}
1309#[derive(Debug, Clone, Copy)]
1314pub struct WearModel {
1315 pub k_archard: f64,
1317 pub hardness: f64,
1319}
1320impl WearModel {
1321 pub fn new(k_archard: f64, hardness: f64) -> Self {
1323 Self {
1324 k_archard,
1325 hardness,
1326 }
1327 }
1328 pub fn wear_volume(&self, normal_force: f64, sliding_distance: f64) -> f64 {
1330 self.k_archard * normal_force * sliding_distance / self.hardness
1331 }
1332 pub fn wear_rate(&self, normal_force: f64, sliding_speed: f64) -> f64 {
1334 self.k_archard * normal_force * sliding_speed / self.hardness
1335 }
1336}
1337#[derive(Debug, Clone, Copy)]
1340pub struct HolmWear {
1341 pub z: f64,
1343}
1344impl HolmWear {
1345 pub fn new(z: f64) -> Self {
1347 HolmWear { z }
1348 }
1349 pub fn volume_rate(&self, normal_load: f64) -> f64 {
1351 self.z * normal_load
1352 }
1353 pub fn volume_loss(&self, normal_load: f64, s: f64) -> f64 {
1355 self.z * normal_load * s
1356 }
1357 pub fn wear_depth(&self, normal_load: f64, s: f64, area: f64) -> f64 {
1359 if area < 1e-30 {
1360 return 0.0;
1361 }
1362 self.volume_loss(normal_load, s) / area
1363 }
1364}
1365#[derive(Debug, Clone, Copy)]
1367pub struct CylinderContact {
1368 pub effective_modulus: f64,
1370 pub effective_radius: f64,
1372 pub contact_half_width: f64,
1374}
1375impl CylinderContact {
1376 pub fn new(e1: f64, nu1: f64, r1: f64, e2: f64, nu2: f64, r2: f64) -> Self {
1378 let e_star = 1.0 / ((1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2);
1379 let r_star = r1 * r2 / (r1 + r2);
1380 CylinderContact {
1381 effective_modulus: e_star,
1382 effective_radius: r_star,
1383 contact_half_width: 0.0,
1384 }
1385 }
1386 pub fn half_width(&self, w: f64) -> f64 {
1388 (4.0 * w * self.effective_radius / (PI * self.effective_modulus)).sqrt()
1389 }
1390 pub fn max_pressure(&self, w: f64) -> f64 {
1392 let a = self.half_width(w);
1393 if a < 1e-30 {
1394 return 0.0;
1395 }
1396 2.0 * w / (PI * a)
1397 }
1398 pub fn max_subsurface_shear(&self, w: f64) -> f64 {
1400 0.300 * self.max_pressure(w)
1401 }
1402 pub fn pressure_at(&self, w: f64, x: f64) -> f64 {
1404 let a = self.half_width(w);
1405 let p0 = self.max_pressure(w);
1406 if x.abs() >= a {
1407 return 0.0;
1408 }
1409 p0 * (1.0 - (x / a).powi(2)).sqrt()
1410 }
1411 pub fn depth_max_shear(&self, w: f64) -> f64 {
1413 0.786 * self.half_width(w)
1414 }
1415}
1416#[derive(Debug, Clone)]
1418pub struct TribologicalState {
1419 pub velocity: f64,
1421 pub load: f64,
1423 pub temperature: f64,
1425 pub film_thickness: f64,
1427 pub lambda: f64,
1429 pub mu: f64,
1431 pub wear_volume: f64,
1433 pub regime: LubricationRegimeKind,
1435}
1436impl TribologicalState {
1437 pub fn new(velocity: f64, load: f64) -> Self {
1439 TribologicalState {
1440 velocity,
1441 load,
1442 temperature: 293.15,
1443 film_thickness: 0.0,
1444 lambda: 0.0,
1445 mu: 0.15,
1446 wear_volume: 0.0,
1447 regime: LubricationRegimeKind::Boundary,
1448 }
1449 }
1450 pub fn update_film(&mut self, h: f64, roughness: f64) {
1452 self.film_thickness = h;
1453 self.lambda = if roughness > 1e-30 {
1454 h / roughness
1455 } else {
1456 f64::INFINITY
1457 };
1458 self.regime = if self.lambda < 1.0 {
1459 LubricationRegimeKind::Boundary
1460 } else if self.lambda < 3.0 {
1461 LubricationRegimeKind::Mixed
1462 } else if self.lambda < 10.0 {
1463 LubricationRegimeKind::Elastohydrodynamic
1464 } else {
1465 LubricationRegimeKind::FullFilm
1466 };
1467 }
1468 pub fn accumulate_wear(&mut self, k: f64, hardness: f64, dt: f64) {
1470 let rate = k * self.load * self.velocity / hardness;
1471 self.wear_volume += rate * dt;
1472 }
1473 pub fn update_friction(&mut self, mu_boundary: f64, mu_full: f64) {
1475 let t = ((self.lambda - 1.0) / 9.0).clamp(0.0, 1.0);
1476 self.mu = mu_boundary * (1.0 - t) + mu_full * t;
1477 }
1478 pub fn power_dissipation(&self) -> f64 {
1480 self.mu * self.load * self.velocity
1481 }
1482}
1483#[derive(Debug, Clone)]
1490pub struct FrictionOscillator {
1491 pub mass: f64,
1493 pub stiffness: f64,
1495 pub mu_s: f64,
1497 pub mu_k: f64,
1499}
1500impl FrictionOscillator {
1501 pub fn new(mass: f64, stiffness: f64, mu_s: f64, mu_k: f64) -> Self {
1503 Self {
1504 mass,
1505 stiffness,
1506 mu_s,
1507 mu_k,
1508 }
1509 }
1510 pub fn is_sticking(&self, v: f64, v_drive: f64) -> bool {
1514 (v - v_drive).abs() < 1e-9
1515 }
1516 pub fn step(&mut self, x: f64, v: f64, v_drive: f64, dt: f64) -> (f64, f64) {
1523 let g = 9.81;
1524 let f_normal = self.mass * g;
1525 let f_spring = -self.stiffness * x;
1526 let relative_v = v - v_drive;
1527 let f_friction = if relative_v.abs() < 1e-9 {
1528 let f_static_max = self.mu_s * f_normal;
1529 (-f_spring).clamp(-f_static_max, f_static_max)
1530 } else {
1531 -relative_v.signum() * self.mu_k * f_normal
1532 };
1533 let a = (f_spring + f_friction) / self.mass;
1534 let new_v = v + a * dt;
1535 let new_x = x + new_v * dt;
1536 (new_x, new_v)
1537 }
1538}
1539#[derive(Debug, Clone, Copy)]
1541pub struct JohnsonKendallRoberts {
1542 pub effective_modulus: f64,
1544 pub effective_radius: f64,
1546 pub work_of_adhesion: f64,
1548}
1549impl JohnsonKendallRoberts {
1550 pub fn new(effective_modulus: f64, effective_radius: f64, work_of_adhesion: f64) -> Self {
1552 Self {
1553 effective_modulus,
1554 effective_radius,
1555 work_of_adhesion,
1556 }
1557 }
1558 pub fn pull_off_force(&self) -> f64 {
1560 -1.5 * PI * self.work_of_adhesion * self.effective_radius
1561 }
1562 pub fn contact_radius(&self, force: f64) -> f64 {
1564 let w = self.work_of_adhesion;
1565 let r = self.effective_radius;
1566 let e = self.effective_modulus;
1567 let term1 = 3.0 * PI * w * r;
1568 let discriminant = 6.0 * PI * w * r * force + term1 * term1;
1569 if discriminant < 0.0 {
1570 return 0.0;
1571 }
1572 let inner = force + term1 + discriminant.sqrt();
1573 let a3 = r / e * inner;
1574 a3.powf(1.0 / 3.0)
1575 }
1576 pub fn load_for_radius(&self, a: f64) -> f64 {
1578 let e = self.effective_modulus;
1579 let r = self.effective_radius;
1580 let w = self.work_of_adhesion;
1581 let hertz_term = 4.0 * e * a * a * a / (3.0 * r);
1582 let adhesion_term = (8.0 * PI * e * w * a * a * a).sqrt();
1583 hertz_term - adhesion_term
1584 }
1585}
1586#[derive(Debug, Clone, Copy)]
1588pub struct EhlFilm {
1589 pub viscosity: f64,
1591 pub pressure_viscosity_coeff: f64,
1593 pub effective_modulus: f64,
1595 pub effective_radius: f64,
1597}
1598impl EhlFilm {
1599 pub fn new(
1601 viscosity: f64,
1602 pressure_viscosity_coeff: f64,
1603 effective_modulus: f64,
1604 effective_radius: f64,
1605 ) -> Self {
1606 Self {
1607 viscosity,
1608 pressure_viscosity_coeff,
1609 effective_modulus,
1610 effective_radius,
1611 }
1612 }
1613 pub fn central_film_thickness_line(&self, u: f64, w: f64) -> f64 {
1616 let ue = (self.viscosity * u) / (self.effective_modulus * self.effective_radius);
1617 let we = w / (self.effective_modulus * self.effective_radius);
1618 let ge = self.pressure_viscosity_coeff * self.effective_modulus;
1619 if we < 1e-30 {
1620 return 0.0;
1621 }
1622 let h_c_r = 2.65 * ge.powf(0.54) * ue.powf(0.7) / we.powf(0.13);
1623 h_c_r * self.effective_radius
1624 }
1625 pub fn minimum_film_thickness_point(&self, u: f64, w: f64) -> f64 {
1628 let ue = (self.viscosity * u) / (self.effective_modulus * self.effective_radius);
1629 let we = w / (self.effective_modulus * self.effective_radius * self.effective_radius);
1630 let ge = self.pressure_viscosity_coeff * self.effective_modulus;
1631 if we < 1e-30 {
1632 return 0.0;
1633 }
1634 let h_min_r = 3.63 * ue.powf(0.68) * ge.powf(0.49) * we.powf(-0.073);
1635 h_min_r * self.effective_radius
1636 }
1637 pub fn lambda_ratio(&self, u: f64, w: f64, roughness: f64) -> f64 {
1639 let h_min = self.minimum_film_thickness_point(u, w);
1640 if roughness < 1e-30 {
1641 f64::INFINITY
1642 } else {
1643 h_min / roughness
1644 }
1645 }
1646}
1647#[derive(Debug, Clone, Copy)]
1652pub struct HertzContactParams {
1653 pub r1: f64,
1655 pub r2: f64,
1657 pub e1: f64,
1659 pub nu1: f64,
1661 pub e2: f64,
1663 pub nu2: f64,
1665}
1666impl HertzContactParams {
1667 pub fn new(r1: f64, r2: f64, e1: f64, nu1: f64, e2: f64, nu2: f64) -> Self {
1669 Self {
1670 r1,
1671 r2,
1672 e1,
1673 nu1,
1674 e2,
1675 nu2,
1676 }
1677 }
1678 pub fn reduced_modulus(&self) -> f64 {
1680 1.0 / ((1.0 - self.nu1 * self.nu1) / self.e1 + (1.0 - self.nu2 * self.nu2) / self.e2)
1681 }
1682 pub fn reduced_radius(&self) -> f64 {
1686 if self.r1.is_infinite() {
1687 return self.r2;
1688 }
1689 if self.r2.is_infinite() {
1690 return self.r1;
1691 }
1692 self.r1 * self.r2 / (self.r1 + self.r2)
1693 }
1694 pub fn contact_radius(&self, force: f64) -> f64 {
1696 let e_star = self.reduced_modulus();
1697 let r_star = self.reduced_radius();
1698 ((3.0 * force * r_star) / (4.0 * e_star)).powf(1.0 / 3.0)
1699 }
1700 pub fn max_pressure(&self, force: f64) -> f64 {
1702 let a = self.contact_radius(force);
1703 if a < 1e-30 {
1704 return 0.0;
1705 }
1706 3.0 * force / (2.0 * PI * a * a)
1707 }
1708 pub fn contact_stiffness(&self, force: f64) -> f64 {
1710 let e_star = self.reduced_modulus();
1711 let a = self.contact_radius(force);
1712 2.0 * e_star * a
1713 }
1714 pub fn max_shear_stress(&self, force: f64) -> f64 {
1716 0.31 * self.max_pressure(force)
1717 }
1718}
1719#[derive(Debug, Clone)]
1721pub struct LubricationRegime {
1722 pub mu_boundary: f64,
1724 pub mu_fullfilm: f64,
1726 pub stribeck_constant: f64,
1728}
1729impl LubricationRegime {
1730 pub fn new(mu_boundary: f64, mu_fullfilm: f64, stribeck_constant: f64) -> Self {
1732 Self {
1733 mu_boundary,
1734 mu_fullfilm,
1735 stribeck_constant,
1736 }
1737 }
1738 pub fn classify(&self, lambda: f64) -> LubricationRegimeKind {
1740 if lambda < 1.0 {
1741 LubricationRegimeKind::Boundary
1742 } else if lambda < 3.0 {
1743 LubricationRegimeKind::Mixed
1744 } else if lambda < 10.0 {
1745 LubricationRegimeKind::Elastohydrodynamic
1746 } else {
1747 LubricationRegimeKind::FullFilm
1748 }
1749 }
1750 pub fn friction_coefficient(&self, eta: f64, velocity: f64, pressure: f64) -> f64 {
1753 if pressure < 1e-10 {
1754 return self.mu_boundary;
1755 }
1756 let stribeck_num = eta * velocity / pressure;
1757 self.mu_fullfilm
1758 + (self.mu_boundary - self.mu_fullfilm)
1759 * (-(stribeck_num / self.stribeck_constant)).exp()
1760 }
1761}