Skip to main content

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}