1#![allow(dead_code)]
11#![allow(clippy::too_many_arguments)]
12
13use std::f64::consts::PI;
14
15#[derive(Debug, Clone, Copy)]
21pub struct ContactInterface {
22 pub roughness_rms: f64,
24 pub young_modulus: f64,
26 pub poisson_ratio: f64,
28 pub hardness: f64,
30}
31
32impl ContactInterface {
33 pub fn new(roughness_rms: f64, young_modulus: f64, poisson_ratio: f64, hardness: f64) -> Self {
35 Self {
36 roughness_rms,
37 young_modulus,
38 poisson_ratio,
39 hardness,
40 }
41 }
42}
43
44pub fn reduced_modulus(e1: f64, nu1: f64, e2: f64, nu2: f64) -> f64 {
58 1.0 / ((1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2)
59}
60
61pub fn hertz_contact_radius(r1: f64, r2: f64, e_star: f64, load: f64) -> f64 {
77 let r_eff = r1 * r2 / (r1 + r2);
78 (3.0 * load * r_eff / (4.0 * e_star)).cbrt()
79}
80
81pub fn hertz_max_pressure(load: f64, contact_radius: f64) -> f64 {
93 3.0 * load / (2.0 * PI * contact_radius * contact_radius)
94}
95
96pub fn stribeck_curve(speed: f64, viscosity: f64, pressure: f64, r: f64) -> f64 {
113 viscosity * speed / (pressure * r)
114}
115
116pub fn hydrodynamic_lift(viscosity: f64, speed: f64, gap: f64, length: f64, width: f64) -> f64 {
131 viscosity * speed * length * width / (gap * gap)
132}
133
134#[derive(Debug, Clone, Copy)]
144pub struct ArchardWear {
145 pub k: f64,
147 pub hardness: f64,
149}
150
151impl ArchardWear {
152 pub fn new(k: f64, hardness: f64) -> Self {
154 Self { k, hardness }
155 }
156
157 pub fn wear_volume(&self, load: f64, sliding_dist: f64) -> f64 {
165 self.k * load * sliding_dist / self.hardness
166 }
167
168 pub fn wear_depth(&self, load: f64, sliding_dist: f64, area: f64) -> f64 {
178 self.wear_volume(load, sliding_dist) / area
179 }
180}
181
182pub fn sommerfeld_number(viscosity: f64, speed: f64, load: f64, r: f64, l: f64, c: f64) -> f64 {
201 let c_over_r = c / r;
202 viscosity * speed * r * l / (load * c_over_r * c_over_r)
203}
204
205pub fn elasto_hydrodynamic_film_thickness(
225 r_eff: f64,
226 e_star: f64,
227 u: f64,
228 eta: f64,
229 load: f64,
230) -> f64 {
231 let e_prime = 2.0 * e_star;
233 let w_star = load / (e_prime * r_eff * r_eff);
235 let u_star = eta * u / (e_prime * r_eff);
237 r_eff * 2.69 * u_star.powf(0.67) * w_star.powf(-0.067)
239}
240
241pub fn friction_power_dissipation(friction_force: f64, sliding_velocity: f64) -> f64 {
253 friction_force * sliding_velocity
254}
255
256pub fn flash_temperature(q_friction: f64, r_contact: f64, kappa: f64, rho_cp: f64, v: f64) -> f64 {
274 let kappa_diff = kappa / rho_cp;
276 let peclet_term = (PI * r_contact / (v * kappa_diff)).sqrt();
278 q_friction * peclet_term / (2.0 * kappa)
279}
280
281#[cfg(test)]
286mod tests {
287 use super::*;
288
289 const EPS: f64 = 1e-9;
290
291 #[test]
293 fn test_reduced_modulus_positive() {
294 let e_star = reduced_modulus(200e9, 0.3, 200e9, 0.3);
295 assert!(
296 e_star > 0.0,
297 "Reduced modulus must be positive, got {e_star}"
298 );
299 }
300
301 #[test]
303 fn test_reduced_modulus_equal_materials() {
304 let e = 200e9_f64;
305 let nu = 0.3_f64;
306 let e_star = reduced_modulus(e, nu, e, nu);
307 let expected = e / (2.0 * (1.0 - nu * nu));
308 assert!(
309 (e_star - expected).abs() / expected < 1e-9,
310 "Equal materials: E* = {e_star} vs expected {expected}"
311 );
312 }
313
314 #[test]
316 fn test_reduced_modulus_increases_with_e() {
317 let e1 = reduced_modulus(100e9, 0.3, 200e9, 0.3);
318 let e2 = reduced_modulus(200e9, 0.3, 200e9, 0.3);
319 assert!(e2 > e1, "Larger E should give larger E*");
320 }
321
322 #[test]
324 fn test_hertz_contact_radius_positive() {
325 let a = hertz_contact_radius(0.01, 0.01, 100e9, 100.0);
326 assert!(a > 0.0, "Contact radius must be positive, got {a}");
327 }
328
329 #[test]
331 fn test_hertz_contact_radius_increases_with_load() {
332 let a1 = hertz_contact_radius(0.01, 0.01, 100e9, 100.0);
333 let a2 = hertz_contact_radius(0.01, 0.01, 100e9, 1000.0);
334 assert!(
335 a2 > a1,
336 "Contact radius should increase with load: a1={a1}, a2={a2}"
337 );
338 }
339
340 #[test]
342 fn test_hertz_contact_radius_scaling() {
343 let r1 = 0.01_f64;
344 let e_star = 100e9_f64;
345 let a1 = hertz_contact_radius(r1, r1, e_star, 100.0);
346 let a8 = hertz_contact_radius(r1, r1, e_star, 800.0);
347 let ratio = a8 / a1;
349 assert!(
350 (ratio - 2.0).abs() < 1e-6,
351 "Contact radius scales as F^1/3, got ratio={ratio}"
352 );
353 }
354
355 #[test]
357 fn test_hertz_max_pressure_formula() {
358 let load = 100.0_f64;
359 let a = 1e-3_f64;
360 let p0 = hertz_max_pressure(load, a);
361 let expected = 3.0 * load / (2.0 * PI * a * a);
362 assert!(
363 (p0 - expected).abs() < EPS,
364 "Max pressure formula mismatch: {p0} vs {expected}"
365 );
366 }
367
368 #[test]
370 fn test_hertz_max_pressure_positive() {
371 let p0 = hertz_max_pressure(50.0, 5e-4);
372 assert!(p0 > 0.0, "Max pressure should be positive, got {p0}");
373 }
374
375 #[test]
377 fn test_hertz_max_pressure_increases_with_load() {
378 let a = 1e-3_f64;
379 let p1 = hertz_max_pressure(100.0, a);
380 let p2 = hertz_max_pressure(200.0, a);
381 assert!(p2 > p1, "Higher load → higher max pressure");
382 }
383
384 #[test]
386 fn test_stribeck_positive() {
387 let s = stribeck_curve(1.0, 0.001, 1e6, 1e-3);
388 assert!(s > 0.0, "Stribeck number must be positive, got {s}");
389 }
390
391 #[test]
393 fn test_stribeck_increases_with_speed() {
394 let s1 = stribeck_curve(1.0, 0.001, 1e6, 1e-3);
395 let s2 = stribeck_curve(10.0, 0.001, 1e6, 1e-3);
396 assert!(s2 > s1, "Stribeck number should increase with speed");
397 }
398
399 #[test]
401 fn test_stribeck_scales_with_viscosity() {
402 let s1 = stribeck_curve(1.0, 0.001, 1e6, 1e-3);
403 let s2 = stribeck_curve(1.0, 0.002, 1e6, 1e-3);
404 assert!(
405 (s2 / s1 - 2.0).abs() < EPS,
406 "Stribeck scales with viscosity: {s2} vs 2*{s1}"
407 );
408 }
409
410 #[test]
412 fn test_hydro_lift_positive() {
413 let f = hydrodynamic_lift(0.001, 5.0, 1e-5, 0.1, 0.05);
414 assert!(f > 0.0, "Hydrodynamic lift must be positive, got {f}");
415 }
416
417 #[test]
419 fn test_hydro_lift_increases_with_speed() {
420 let f1 = hydrodynamic_lift(0.001, 1.0, 1e-5, 0.1, 0.05);
421 let f2 = hydrodynamic_lift(0.001, 2.0, 1e-5, 0.1, 0.05);
422 assert!(f2 > f1, "Lift should increase with speed");
423 }
424
425 #[test]
427 fn test_hydro_lift_gap_scaling() {
428 let f1 = hydrodynamic_lift(0.001, 1.0, 1e-5, 0.1, 0.05);
429 let f2 = hydrodynamic_lift(0.001, 1.0, 2e-5, 0.1, 0.05);
430 let ratio = f1 / f2;
432 assert!(
433 (ratio - 4.0).abs() < 1e-6,
434 "Lift scales as 1/h^2: got ratio={ratio}"
435 );
436 }
437
438 #[test]
440 fn test_archard_wear_scales_with_load() {
441 let wear = ArchardWear::new(1e-4, 1e9);
442 let v1 = wear.wear_volume(100.0, 1.0);
443 let v2 = wear.wear_volume(200.0, 1.0);
444 assert!(
445 (v2 / v1 - 2.0).abs() < EPS,
446 "Wear volume should scale with load: v1={v1}, v2={v2}"
447 );
448 }
449
450 #[test]
452 fn test_archard_wear_scales_with_distance() {
453 let wear = ArchardWear::new(1e-4, 1e9);
454 let v1 = wear.wear_volume(100.0, 1.0);
455 let v2 = wear.wear_volume(100.0, 5.0);
456 assert!(
457 (v2 / v1 - 5.0).abs() < EPS,
458 "Wear volume should scale with sliding distance: {v2} vs 5*{v1}"
459 );
460 }
461
462 #[test]
464 fn test_archard_wear_positive() {
465 let wear = ArchardWear::new(1e-5, 5e9);
466 let v = wear.wear_volume(500.0, 10.0);
467 assert!(v > 0.0, "Wear volume must be positive, got {v}");
468 }
469
470 #[test]
472 fn test_archard_wear_depth_consistent() {
473 let wear = ArchardWear::new(1e-4, 1e9);
474 let area = 1e-4_f64; let vol = wear.wear_volume(100.0, 0.5);
476 let depth = wear.wear_depth(100.0, 0.5, area);
477 assert!(
478 (depth - vol / area).abs() < EPS,
479 "Wear depth must be vol/area: {depth} vs {}",
480 vol / area
481 );
482 }
483
484 #[test]
486 fn test_archard_harder_less_wear() {
487 let soft = ArchardWear::new(1e-4, 1e9);
488 let hard = ArchardWear::new(1e-4, 5e9);
489 let v_soft = soft.wear_volume(100.0, 1.0);
490 let v_hard = hard.wear_volume(100.0, 1.0);
491 assert!(v_hard < v_soft, "Harder material should wear less");
492 }
493
494 #[test]
496 fn test_sommerfeld_positive() {
497 let s = sommerfeld_number(0.01, 100.0, 1000.0, 0.05, 0.05, 0.0001);
498 assert!(s > 0.0, "Sommerfeld number must be positive, got {s}");
499 }
500
501 #[test]
503 fn test_sommerfeld_increases_with_viscosity() {
504 let s1 = sommerfeld_number(0.01, 100.0, 1000.0, 0.05, 0.05, 0.0001);
505 let s2 = sommerfeld_number(0.02, 100.0, 1000.0, 0.05, 0.05, 0.0001);
506 assert!(s2 > s1, "Sommerfeld increases with viscosity");
507 }
508
509 #[test]
511 fn test_sommerfeld_scales_with_speed() {
512 let s1 = sommerfeld_number(0.01, 100.0, 1000.0, 0.05, 0.05, 0.0001);
513 let s2 = sommerfeld_number(0.01, 200.0, 1000.0, 0.05, 0.05, 0.0001);
514 assert!(
515 (s2 / s1 - 2.0).abs() < EPS,
516 "Sommerfeld scales with speed: got ratio {}",
517 s2 / s1
518 );
519 }
520
521 #[test]
523 fn test_ehl_film_positive() {
524 let h = elasto_hydrodynamic_film_thickness(0.01, 100e9, 1.0, 0.01, 1000.0);
525 assert!(h > 0.0, "EHL film thickness must be positive, got {h}");
526 }
527
528 #[test]
530 fn test_ehl_film_increases_with_speed() {
531 let h1 = elasto_hydrodynamic_film_thickness(0.01, 100e9, 0.5, 0.01, 1000.0);
532 let h2 = elasto_hydrodynamic_film_thickness(0.01, 100e9, 1.0, 0.01, 1000.0);
533 assert!(h2 > h1, "EHL film increases with speed: h1={h1}, h2={h2}");
534 }
535
536 #[test]
538 fn test_friction_power_positive() {
539 let p = friction_power_dissipation(50.0, 2.0);
540 assert!(p > 0.0, "Friction power must be positive, got {p}");
541 }
542
543 #[test]
545 fn test_friction_power_formula() {
546 let f = 75.0_f64;
547 let v = 3.5_f64;
548 let p = friction_power_dissipation(f, v);
549 assert!(
550 (p - f * v).abs() < EPS,
551 "P = F*v: got {p}, expected {}",
552 f * v
553 );
554 }
555
556 #[test]
558 fn test_friction_power_scales_with_force() {
559 let p1 = friction_power_dissipation(10.0, 2.0);
560 let p2 = friction_power_dissipation(20.0, 2.0);
561 assert!(
562 (p2 / p1 - 2.0).abs() < EPS,
563 "Friction power scales with force"
564 );
565 }
566
567 #[test]
569 fn test_flash_temperature_positive() {
570 let dt = flash_temperature(1e6, 1e-4, 50.0, 3e6, 1.0);
571 assert!(dt > 0.0, "Flash temperature must be positive, got {dt}");
572 }
573
574 #[test]
576 fn test_flash_temperature_increases_with_flux() {
577 let dt1 = flash_temperature(1e6, 1e-4, 50.0, 3e6, 1.0);
578 let dt2 = flash_temperature(2e6, 1e-4, 50.0, 3e6, 1.0);
579 assert!(dt2 > dt1, "Higher heat flux → higher flash temperature");
580 }
581
582 #[test]
584 fn test_flash_temperature_decreases_with_conductivity() {
585 let dt1 = flash_temperature(1e6, 1e-4, 50.0, 3e6, 1.0);
586 let dt2 = flash_temperature(1e6, 1e-4, 100.0, 3e6, 1.0);
587 assert!(dt2 < dt1, "Higher conductivity → lower flash temperature");
588 }
589
590 #[test]
592 fn test_contact_interface_fields() {
593 let ci = ContactInterface::new(1e-6, 200e9, 0.3, 2e9);
594 assert!((ci.roughness_rms - 1e-6).abs() < EPS);
595 assert!((ci.young_modulus - 200e9).abs() < 1.0);
596 assert!((ci.poisson_ratio - 0.3).abs() < EPS);
597 assert!((ci.hardness - 2e9).abs() < 1.0);
598 }
599
600 #[test]
602 fn test_hertz_contact_radius_larger_sphere() {
603 let a1 = hertz_contact_radius(0.01, 0.01, 100e9, 100.0);
604 let a2 = hertz_contact_radius(0.02, 0.02, 100e9, 100.0);
605 assert!(a2 > a1, "Larger spheres → larger contact radius");
606 }
607
608 #[test]
610 fn test_archard_new_fields() {
611 let w = ArchardWear::new(2e-4, 3e9);
612 assert!((w.k - 2e-4).abs() < EPS);
613 assert!((w.hardness - 3e9).abs() < 1.0);
614 }
615
616 #[test]
618 fn test_stribeck_zero_speed() {
619 let s = stribeck_curve(0.0, 0.001, 1e6, 1e-3);
620 assert_eq!(s, 0.0, "Stribeck = 0 at zero speed");
621 }
622
623 #[test]
625 fn test_hertz_pressure_decreases_with_radius() {
626 let p1 = hertz_max_pressure(100.0, 1e-3);
627 let p2 = hertz_max_pressure(100.0, 2e-3);
628 assert!(
629 p2 < p1,
630 "Larger contact radius → lower pressure for same load"
631 );
632 }
633
634 #[test]
636 fn test_sommerfeld_decreases_with_load() {
637 let s1 = sommerfeld_number(0.01, 100.0, 500.0, 0.05, 0.05, 0.0001);
638 let s2 = sommerfeld_number(0.01, 100.0, 1000.0, 0.05, 0.05, 0.0001);
639 assert!(s2 < s1, "Higher load → lower Sommerfeld number");
640 }
641
642 #[test]
644 fn test_ehl_film_increases_with_viscosity() {
645 let h1 = elasto_hydrodynamic_film_thickness(0.01, 100e9, 1.0, 0.01, 1000.0);
646 let h2 = elasto_hydrodynamic_film_thickness(0.01, 100e9, 1.0, 0.02, 1000.0);
647 assert!(h2 > h1, "Higher viscosity → thicker EHL film");
648 }
649}