1#![forbid(unsafe_code)]
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ControlErrorError {
19 InvalidError,
20 InvalidThreshold,
21}
22
23pub fn error(setpoint: f64, measured: f64) -> f64 {
24 setpoint - measured
25}
26
27pub fn absolute_error(setpoint: f64, measured: f64) -> f64 {
28 error(setpoint, measured).abs()
29}
30
31pub fn relative_error(setpoint: f64, measured: f64) -> Option<f64> {
32 if !setpoint.is_finite() || !measured.is_finite() || setpoint == 0.0 {
33 return None;
34 }
35
36 Some(error(setpoint, measured) / setpoint)
37}
38
39pub fn percent_error(setpoint: f64, measured: f64) -> Option<f64> {
40 relative_error(setpoint, measured).map(|value| value * 100.0)
41}
42
43pub fn deadband(error: f64, threshold: f64) -> Result<f64, ControlErrorError> {
44 if !error.is_finite() {
45 return Err(ControlErrorError::InvalidError);
46 }
47
48 if !threshold.is_finite() || threshold < 0.0 {
49 return Err(ControlErrorError::InvalidThreshold);
50 }
51
52 if error.abs() <= threshold {
53 Ok(0.0)
54 } else {
55 Ok(error)
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::{
62 ControlErrorError, absolute_error, deadband, error, percent_error, relative_error,
63 };
64
65 #[test]
66 fn computes_error_forms() {
67 assert_eq!(error(10.0, 8.0), 2.0);
68 assert_eq!(absolute_error(10.0, 8.0), 2.0);
69 assert_eq!(relative_error(10.0, 8.0), Some(0.2));
70 assert_eq!(percent_error(10.0, 8.0), Some(20.0));
71 }
72
73 #[test]
74 fn applies_deadband() {
75 assert_eq!(deadband(0.05, 0.1).unwrap(), 0.0);
76 assert_eq!(deadband(-0.2, 0.1).unwrap(), -0.2);
77 }
78
79 #[test]
80 fn rejects_invalid_values() {
81 assert_eq!(relative_error(0.0, 1.0), None);
82 assert_eq!(percent_error(f64::NAN, 1.0), None);
83 assert_eq!(
84 deadband(f64::NAN, 0.1),
85 Err(ControlErrorError::InvalidError)
86 );
87 assert_eq!(
88 deadband(0.1, -0.1),
89 Err(ControlErrorError::InvalidThreshold)
90 );
91 }
92}