pamoja_kit/shape.rs
1//! Shaping a reading: ignoring small wiggle around a value.
2
3/// Holds a reading at a center value while it stays within a band, ignoring small wiggle.
4///
5/// A reading that hovers near a setpoint jitters a little in both directions. Acting on that
6/// jitter makes an actuator chatter - a valve or heater switching on and off, "hunting"
7/// around the target. A deadband ignores it: while `value` is within `width` of `center` the
8/// center is returned unchanged, so nothing downstream reacts; once `value` moves beyond the
9/// band it passes through as-is.
10///
11/// # Arguments
12///
13/// * `value` - the reading to shape.
14/// * `center` - the value the band is centered on.
15/// * `width` - the half-width of the band; its magnitude is used, so the band runs from
16/// `center - width` to `center + width`.
17///
18/// # Returns
19///
20/// `center` when `value` is within `width` of it, otherwise `value` unchanged.
21///
22/// # Examples
23///
24/// ```
25/// use pamoja_kit::deadband;
26///
27/// // A setpoint of 20 with a 0.5 deadband ignores small wiggle around it.
28/// assert_eq!(deadband(20.2, 20.0, 0.5), 20.0); // within the band: held at center
29/// assert_eq!(deadband(21.0, 20.0, 0.5), 21.0); // beyond the band: passes through
30/// ```
31pub fn deadband(value: f32, center: f32, width: f32) -> f32 {
32 let half = if width < 0.0 { -width } else { width };
33 let deviation = value - center;
34 let distance = if deviation < 0.0 {
35 -deviation
36 } else {
37 deviation
38 };
39 if distance <= half {
40 center
41 } else {
42 value
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49
50 #[test]
51 fn within_the_band_returns_the_center() {
52 assert_eq!(deadband(20.2, 20.0, 0.5), 20.0);
53 assert_eq!(deadband(19.8, 20.0, 0.5), 20.0);
54 }
55
56 #[test]
57 fn beyond_the_band_passes_through() {
58 assert_eq!(deadband(21.0, 20.0, 0.5), 21.0);
59 assert_eq!(deadband(18.0, 20.0, 0.5), 18.0);
60 }
61
62 #[test]
63 fn the_band_edge_counts_as_inside() {
64 assert_eq!(deadband(20.5, 20.0, 0.5), 20.0);
65 assert_eq!(deadband(19.5, 20.0, 0.5), 20.0);
66 }
67
68 #[test]
69 fn a_negative_width_is_treated_as_its_magnitude() {
70 assert_eq!(deadband(20.2, 20.0, -0.5), 20.0);
71 }
72
73 #[test]
74 fn a_zero_width_holds_only_the_exact_center() {
75 assert_eq!(deadband(20.0, 20.0, 0.0), 20.0);
76 assert_eq!(deadband(20.1, 20.0, 0.0), 20.1);
77 }
78}