Coverage Report

Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/trueno/src/blis/jidoka.rs
Line
Count
Source
1
//! Jidoka (Autonomation) - Stop on Defect
2
//!
3
//! Runtime guards that detect and halt computation on numerical errors.
4
//! Part of Toyota Production System integration in BLIS.
5
//!
6
//! # Philosophy
7
//!
8
//! Jidoka (自働化) is a Toyota Production System principle meaning "automation
9
//! with a human touch." When a defect is detected, the process stops immediately
10
//! rather than propagating bad data downstream.
11
//!
12
//! # Usage
13
//!
14
//! ```
15
//! use trueno::blis::jidoka::{JidokaGuard, JidokaError};
16
//!
17
//! let guard = JidokaGuard::strict();
18
//! guard.check_input(1.0, "matrix_a")?;
19
//! let computed = 1.0f32;
20
//! let expected = 1.0f32;
21
//! guard.validate(computed, expected)?;
22
//! # Ok::<(), JidokaError>(())
23
//! ```
24
25
/// Jidoka error types for runtime validation
26
#[derive(Debug, Clone, PartialEq)]
27
pub enum JidokaError {
28
    /// Numerical deviation beyond acceptable threshold
29
    NumericalDeviation {
30
        computed: f32,
31
        expected: f32,
32
        relative_error: f32,
33
    },
34
    /// NaN detected in computation
35
    NaNDetected { location: &'static str },
36
    /// Infinity detected in computation
37
    InfDetected { location: &'static str },
38
    /// Dimension mismatch
39
    DimensionMismatch {
40
        expected: (usize, usize, usize),
41
        actual: (usize, usize, usize),
42
    },
43
}
44
45
impl std::fmt::Display for JidokaError {
46
0
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47
0
        match self {
48
            Self::NumericalDeviation {
49
0
                computed,
50
0
                expected,
51
0
                relative_error,
52
            } => {
53
0
                write!(
54
0
                    f,
55
0
                    "Jidoka: numerical deviation - computed={}, expected={}, error={}",
56
                    computed, expected, relative_error
57
                )
58
            }
59
0
            Self::NaNDetected { location } => {
60
0
                write!(f, "Jidoka: NaN detected at {}", location)
61
            }
62
0
            Self::InfDetected { location } => {
63
0
                write!(f, "Jidoka: Inf detected at {}", location)
64
            }
65
0
            Self::DimensionMismatch { expected, actual } => {
66
0
                write!(
67
0
                    f,
68
0
                    "Jidoka: dimension mismatch - expected {:?}, got {:?}",
69
                    expected, actual
70
                )
71
            }
72
        }
73
0
    }
74
}
75
76
impl std::error::Error for JidokaError {}
77
78
/// Jidoka guard for runtime validation
79
#[derive(Debug, Clone)]
80
pub struct JidokaGuard {
81
    /// Maximum allowed relative error
82
    pub epsilon: f32,
83
    /// Whether to check for NaN/Inf
84
    pub check_special: bool,
85
    /// Sample rate (check every N outputs)
86
    pub sample_rate: usize,
87
}
88
89
impl Default for JidokaGuard {
90
0
    fn default() -> Self {
91
0
        Self {
92
0
            epsilon: 1e-5,
93
0
            check_special: true,
94
0
            sample_rate: 1000, // Check every 1000th output in release
95
0
        }
96
0
    }
97
}
98
99
impl JidokaGuard {
100
    /// Create a strict guard for testing (checks every output)
101
0
    pub fn strict() -> Self {
102
0
        Self {
103
0
            epsilon: 1e-6,
104
0
            check_special: true,
105
0
            sample_rate: 1,
106
0
        }
107
0
    }
108
109
    /// Validate a computed value against expected
110
    #[inline]
111
0
    pub fn validate(&self, computed: f32, expected: f32) -> Result<(), JidokaError> {
112
0
        if self.check_special {
113
0
            if computed.is_nan() {
114
0
                return Err(JidokaError::NaNDetected {
115
0
                    location: "output",
116
0
                });
117
0
            }
118
0
            if computed.is_infinite() {
119
0
                return Err(JidokaError::InfDetected {
120
0
                    location: "output",
121
0
                });
122
0
            }
123
0
        }
124
125
0
        let abs_diff = (computed - expected).abs();
126
0
        let max_abs = computed.abs().max(expected.abs()).max(1e-10);
127
0
        let relative_error = abs_diff / max_abs;
128
129
0
        if relative_error > self.epsilon {
130
0
            return Err(JidokaError::NumericalDeviation {
131
0
                computed,
132
0
                expected,
133
0
                relative_error,
134
0
            });
135
0
        }
136
137
0
        Ok(())
138
0
    }
139
140
    /// Check input for NaN/Inf
141
    #[inline]
142
0
    pub fn check_input(&self, value: f32, location: &'static str) -> Result<(), JidokaError> {
143
0
        if !self.check_special {
144
0
            return Ok(());
145
0
        }
146
0
        if value.is_nan() {
147
0
            return Err(JidokaError::NaNDetected { location });
148
0
        }
149
0
        if value.is_infinite() {
150
0
            return Err(JidokaError::InfDetected { location });
151
0
        }
152
0
        Ok(())
153
0
    }
154
}
155
156
#[cfg(test)]
157
mod tests {
158
    use super::*;
159
160
    #[test]
161
    fn test_jidoka_default() {
162
        let guard = JidokaGuard::default();
163
        assert!((guard.epsilon - 1e-5).abs() < 1e-10);
164
        assert!(guard.check_special);
165
        assert_eq!(guard.sample_rate, 1000);
166
    }
167
168
    #[test]
169
    fn test_jidoka_strict() {
170
        let guard = JidokaGuard::strict();
171
        assert!((guard.epsilon - 1e-6).abs() < 1e-10);
172
        assert!(guard.check_special);
173
        assert_eq!(guard.sample_rate, 1);
174
    }
175
176
    #[test]
177
    fn test_validate_pass() {
178
        let guard = JidokaGuard::default();
179
        assert!(guard.validate(1.0, 1.0).is_ok());
180
        assert!(guard.validate(1.0, 1.000001).is_ok());
181
    }
182
183
    #[test]
184
    fn test_validate_nan() {
185
        let guard = JidokaGuard::default();
186
        let result = guard.validate(f32::NAN, 1.0);
187
        assert!(matches!(result, Err(JidokaError::NaNDetected { .. })));
188
    }
189
190
    #[test]
191
    fn test_validate_inf() {
192
        let guard = JidokaGuard::default();
193
        let result = guard.validate(f32::INFINITY, 1.0);
194
        assert!(matches!(result, Err(JidokaError::InfDetected { .. })));
195
    }
196
197
    #[test]
198
    fn test_validate_deviation() {
199
        let guard = JidokaGuard::strict();
200
        let result = guard.validate(1.0, 2.0);
201
        assert!(matches!(
202
            result,
203
            Err(JidokaError::NumericalDeviation { .. })
204
        ));
205
    }
206
207
    #[test]
208
    fn test_check_input_nan() {
209
        let guard = JidokaGuard::default();
210
        let result = guard.check_input(f32::NAN, "test");
211
        assert!(matches!(result, Err(JidokaError::NaNDetected { .. })));
212
    }
213
214
    #[test]
215
    fn test_check_input_inf() {
216
        let guard = JidokaGuard::default();
217
        let result = guard.check_input(f32::INFINITY, "test");
218
        assert!(matches!(result, Err(JidokaError::InfDetected { .. })));
219
    }
220
221
    #[test]
222
    fn test_error_display() {
223
        let err = JidokaError::NaNDetected { location: "test" };
224
        assert!(format!("{}", err).contains("NaN"));
225
226
        let err = JidokaError::InfDetected { location: "test" };
227
        assert!(format!("{}", err).contains("Inf"));
228
229
        let err = JidokaError::NumericalDeviation {
230
            computed: 1.0,
231
            expected: 2.0,
232
            relative_error: 0.5,
233
        };
234
        assert!(format!("{}", err).contains("deviation"));
235
236
        let err = JidokaError::DimensionMismatch {
237
            expected: (1, 2, 3),
238
            actual: (4, 5, 6),
239
        };
240
        assert!(format!("{}", err).contains("mismatch"));
241
    }
242
}