1use crate::grammar::{GrammarState, ReasonCode};
13use crate::heuristics::RoboticsMotif;
14use crate::sign::SignTuple;
15
16#[must_use]
34pub fn classify(state: GrammarState, sign: &SignTuple) -> RoboticsMotif {
35 debug_assert!(sign.norm.is_finite() || sign.norm.is_nan(), "sign norm must be finite or NaN");
36 debug_assert!(sign.drift.is_finite() || sign.drift.is_nan(), "sign drift must be finite or NaN");
37 debug_assert!(sign.slew.is_finite() || sign.slew.is_nan(), "sign slew must be finite or NaN");
38 match state {
39 GrammarState::Admissible => RoboticsMotif::Unknown,
40 GrammarState::Violation => RoboticsMotif::Unknown,
41 GrammarState::Boundary(reason) => match reason {
42 ReasonCode::RecurrentBoundaryGrazing => {
43 if crate::math::abs_f64(sign.drift) < 1e-6 {
45 RoboticsMotif::BacklashRing
46 } else {
47 RoboticsMotif::Unknown
48 }
49 }
50 ReasonCode::SustainedOutwardDrift => {
51 if sign.slew > 0.0 {
53 RoboticsMotif::BpfiGrowth
54 } else {
55 RoboticsMotif::Unknown
56 }
57 }
58 ReasonCode::AbruptSlewViolation | ReasonCode::EnvelopeViolation => {
59 RoboticsMotif::Unknown
60 }
61 },
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn admissible_is_unknown() {
71 let s = SignTuple::zero();
72 assert_eq!(classify(GrammarState::Admissible, &s), RoboticsMotif::Unknown);
73 }
74
75 #[test]
76 fn grazing_with_zero_drift_is_backlash_ring() {
77 let s = SignTuple::new(0.05, 0.0, 0.001);
78 let m = classify(GrammarState::Boundary(ReasonCode::RecurrentBoundaryGrazing), &s);
79 assert_eq!(m, RoboticsMotif::BacklashRing);
80 }
81
82 #[test]
83 fn outward_drift_with_positive_slew_is_bpfi_growth() {
84 let s = SignTuple::new(0.05, 0.01, 0.002);
85 let m = classify(GrammarState::Boundary(ReasonCode::SustainedOutwardDrift), &s);
86 assert_eq!(m, RoboticsMotif::BpfiGrowth);
87 }
88
89 #[test]
90 fn outward_drift_with_flat_slew_is_unknown() {
91 let s = SignTuple::new(0.05, 0.01, 0.0);
92 let m = classify(GrammarState::Boundary(ReasonCode::SustainedOutwardDrift), &s);
93 assert_eq!(m, RoboticsMotif::Unknown);
94 }
95
96 #[test]
97 fn violation_state_is_unknown_motif() {
98 let s = SignTuple::new(0.2, 0.05, 0.01);
99 assert_eq!(classify(GrammarState::Violation, &s), RoboticsMotif::Unknown);
100 }
101}