Skip to main content

use_stress/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive stress and pressure-like helpers.
3//!
4//! Initial calculations assume SI units unless otherwise documented.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use use_stress::{Stress, force_from_stress, normal_stress, shear_stress};
10//!
11//! let stress = Stress::new(250_000_000.0).unwrap();
12//!
13//! assert_eq!(stress.megapascals(), 250.0);
14//! assert_eq!(normal_stress(1_000.0, 0.01).unwrap(), 100_000.0);
15//! assert_eq!(shear_stress(1_000.0, 0.01).unwrap(), 100_000.0);
16//! assert_eq!(force_from_stress(100_000.0, 0.01).unwrap(), 1_000.0);
17//! ```
18
19#[derive(Debug, Clone, Copy, PartialEq)]
20pub struct Stress {
21    pascals: f64,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum StressError {
26    InvalidStress,
27    InvalidForce,
28    InvalidArea,
29}
30
31fn validate_finite(value: f64, error: StressError) -> Result<f64, StressError> {
32    if !value.is_finite() {
33        Err(error)
34    } else {
35        Ok(value)
36    }
37}
38
39fn validate_area(area_m2: f64) -> Result<f64, StressError> {
40    if !area_m2.is_finite() || area_m2 <= 0.0 {
41        Err(StressError::InvalidArea)
42    } else {
43        Ok(area_m2)
44    }
45}
46
47impl Stress {
48    pub fn new(pascals: f64) -> Result<Self, StressError> {
49        Ok(Self {
50            pascals: validate_finite(pascals, StressError::InvalidStress)?,
51        })
52    }
53
54    #[must_use]
55    pub fn pascals(&self) -> f64 {
56        self.pascals
57    }
58
59    #[must_use]
60    pub fn megapascals(&self) -> f64 {
61        self.pascals / 1_000_000.0
62    }
63
64    #[must_use]
65    pub fn gigapascals(&self) -> f64 {
66        self.pascals / 1_000_000_000.0
67    }
68}
69
70pub fn normal_stress(force_newtons: f64, area_m2: f64) -> Result<f64, StressError> {
71    Ok(validate_finite(force_newtons, StressError::InvalidForce)? / validate_area(area_m2)?)
72}
73
74pub fn shear_stress(force_newtons: f64, area_m2: f64) -> Result<f64, StressError> {
75    normal_stress(force_newtons, area_m2)
76}
77
78pub fn force_from_stress(stress_pa: f64, area_m2: f64) -> Result<f64, StressError> {
79    Ok(validate_finite(stress_pa, StressError::InvalidStress)? * validate_area(area_m2)?)
80}
81
82#[cfg(test)]
83mod tests {
84    use super::{Stress, StressError, force_from_stress, normal_stress, shear_stress};
85
86    #[test]
87    fn computes_stress_and_force_values() {
88        let stress = Stress::new(250_000_000.0).unwrap();
89
90        assert_eq!(stress.pascals(), 250_000_000.0);
91        assert_eq!(stress.megapascals(), 250.0);
92        assert_eq!(stress.gigapascals(), 0.25);
93        assert_eq!(normal_stress(1_000.0, 0.01).unwrap(), 100_000.0);
94        assert_eq!(shear_stress(1_000.0, 0.01).unwrap(), 100_000.0);
95        assert_eq!(force_from_stress(100_000.0, 0.01).unwrap(), 1_000.0);
96    }
97
98    #[test]
99    fn allows_signed_stress_values() {
100        let stress = Stress::new(-50_000.0).unwrap();
101
102        assert_eq!(stress.megapascals(), -0.05);
103        assert_eq!(normal_stress(-100.0, 0.01).unwrap(), -10_000.0);
104    }
105
106    #[test]
107    fn rejects_invalid_force_stress_and_area_inputs() {
108        assert_eq!(Stress::new(f64::NAN), Err(StressError::InvalidStress));
109        assert_eq!(
110            normal_stress(f64::INFINITY, 1.0),
111            Err(StressError::InvalidForce)
112        );
113        assert_eq!(shear_stress(1.0, 0.0), Err(StressError::InvalidArea));
114        assert_eq!(force_from_stress(1.0, -1.0), Err(StressError::InvalidArea));
115    }
116}