Skip to main content

stateset_nsr/nsr/
error.rs

1//! Custom Error Types for NSR Engine
2//!
3//! Provides strongly-typed errors with context for all NSR operations.
4//! Uses thiserror for ergonomic error definitions and anyhow for error chains.
5
6use thiserror::Error;
7
8/// Main error type for NSR operations
9#[derive(Error, Debug)]
10pub enum NSRError {
11    /// Perception module errors
12    #[error("Perception error: {0}")]
13    Perception(#[from] PerceptionError),
14
15    /// Parser errors
16    #[error("Parser error: {0}")]
17    Parser(#[from] ParserError),
18
19    /// Program evaluation errors
20    #[error("Program error: {0}")]
21    Program(#[from] ProgramError),
22
23    /// Learning algorithm errors
24    #[error("Learning error: {0}")]
25    Learning(#[from] LearningError),
26
27    /// GSS structure errors
28    #[error("GSS error: {0}")]
29    GSS(#[from] GSSError),
30
31    /// MCTS search errors
32    #[error("MCTS error: {0}")]
33    MCTS(#[from] MCTSError),
34
35    /// LLM synthesis errors
36    #[error("LLM synthesis error: {0}")]
37    LLMSynthesis(#[from] LLMSynthesisError),
38
39    /// VSA errors
40    #[error("VSA error: {0}")]
41    VSA(#[from] VSAError),
42
43    /// Configuration errors
44    #[error("Configuration error: {message}")]
45    Config { message: String },
46
47    /// IO errors
48    #[error("IO error: {0}")]
49    IO(#[from] std::io::Error),
50
51    /// Generic internal error
52    #[error("Internal error: {message}")]
53    Internal { message: String },
54}
55
56/// Perception module errors
57#[derive(Error, Debug)]
58pub enum PerceptionError {
59    #[error("Unknown symbol: {symbol_id}")]
60    UnknownSymbol { symbol_id: usize },
61
62    #[error("Embedding dimension mismatch: expected {expected}, got {actual}")]
63    DimensionMismatch { expected: usize, actual: usize },
64
65    #[error("Empty input sequence")]
66    EmptyInput,
67
68    #[error("Invalid input type for modality: {message}")]
69    InvalidInputType { message: String },
70
71    #[error("Training failed: {message}")]
72    TrainingFailed { message: String },
73
74    #[error("Vocabulary not initialized")]
75    VocabularyNotInitialized,
76}
77
78/// Parser errors
79#[derive(Error, Debug)]
80pub enum ParserError {
81    #[error("Invalid transition: {transition} in state {state}")]
82    InvalidTransition { transition: String, state: String },
83
84    #[error("Parse failed: could not find valid parse for input")]
85    ParseFailed,
86
87    #[error("Empty stack during parsing")]
88    EmptyStack,
89
90    #[error("Buffer underflow during parsing")]
91    BufferUnderflow,
92
93    #[error("Cycle detected in dependency structure")]
94    CycleDetected,
95
96    #[error("Non-projective structure not supported")]
97    NonProjective,
98}
99
100/// Program evaluation errors
101#[derive(Error, Debug)]
102pub enum ProgramError {
103    #[error("Type error: expected {expected}, got {actual}")]
104    TypeError { expected: String, actual: String },
105
106    #[error("Division by zero")]
107    DivisionByZero,
108
109    #[error("Undefined variable: {name}")]
110    UndefinedVariable { name: String },
111
112    #[error("Invalid primitive: {name}")]
113    InvalidPrimitive { name: String },
114
115    #[error("Stack overflow: maximum recursion depth {depth} exceeded")]
116    StackOverflow { depth: usize },
117
118    #[error("Arity mismatch: {name} expected {expected} arguments, got {actual}")]
119    ArityMismatch {
120        name: String,
121        expected: usize,
122        actual: usize,
123    },
124
125    #[error("Synthesis failed: {message}")]
126    SynthesisFailed { message: String },
127
128    #[error("Program too deep: depth {depth} exceeds maximum {max_depth}")]
129    ProgramTooDeep { depth: usize, max_depth: usize },
130}
131
132/// Learning algorithm errors
133#[derive(Error, Debug)]
134pub enum LearningError {
135    #[error("Abduction failed: could not find satisfying GSS after {attempts} attempts")]
136    AbductionFailed { attempts: usize },
137
138    #[error("Training diverged: loss {loss} exceeds threshold")]
139    TrainingDiverged { loss: f64 },
140
141    #[error("No training examples provided")]
142    NoExamples,
143
144    #[error("Convergence failed: {message}")]
145    ConvergenceFailed { message: String },
146
147    #[error("Invalid learning rate: {rate}")]
148    InvalidLearningRate { rate: f64 },
149
150    #[error("Curriculum violation: example difficulty {difficulty} exceeds current level {level}")]
151    CurriculumViolation { difficulty: f64, level: f64 },
152}
153
154/// GSS structure errors
155#[derive(Error, Debug)]
156pub enum GSSError {
157    #[error("Invalid node ID: {node_id}")]
158    InvalidNodeId { node_id: usize },
159
160    #[error("Invalid edge: {from} -> {to}")]
161    InvalidEdge { from: usize, to: usize },
162
163    #[error("Cycle detected in GSS")]
164    CycleDetected,
165
166    #[error("Missing root node")]
167    MissingRoot,
168
169    #[error("Orphaned node: {node_id}")]
170    OrphanedNode { node_id: usize },
171
172    #[error("Semantic computation failed at node {node_id}: {message}")]
173    SemanticComputationFailed { node_id: usize, message: String },
174}
175
176/// MCTS search errors
177#[derive(Error, Debug)]
178pub enum MCTSError {
179    #[error("Search budget exhausted: {simulations} simulations with no improvement")]
180    BudgetExhausted { simulations: usize },
181
182    #[error("No valid actions from state")]
183    NoValidActions,
184
185    #[error("Value network failed: {message}")]
186    ValueNetworkFailed { message: String },
187
188    #[error("Policy network failed: {message}")]
189    PolicyNetworkFailed { message: String },
190
191    #[error("Tree size exceeded maximum: {size} > {max_size}")]
192    TreeSizeExceeded { size: usize, max_size: usize },
193}
194
195/// VSA (Vector Symbolic Architecture) errors
196#[derive(Error, Debug)]
197pub enum VSAError {
198    #[error("Empty vector list: cannot bundle empty vector list")]
199    EmptyVectorList,
200
201    #[error("Invalid dimension: {dimension} (must be greater than zero)")]
202    InvalidDimension { dimension: usize },
203
204    #[error("Dimension mismatch: expected {expected}, got {actual}")]
205    DimensionMismatch { expected: usize, actual: usize },
206
207    #[error("Symbol not found in codebook: {symbol}")]
208    SymbolNotFound { symbol: String },
209
210    #[error("Invalid similarity threshold: {threshold} (must be in range [0, 1])")]
211    InvalidThreshold { threshold: f64 },
212
213    #[error("Invalid sparsity: {sparsity} (must be in range [0.0, 1.0))")]
214    InvalidSparsity { sparsity: f64 },
215
216    #[error("Memory capacity exceeded: {size} items exceeds capacity of {capacity}")]
217    CapacityExceeded { size: usize, capacity: usize },
218}
219
220/// LLM synthesis errors
221#[derive(Error, Debug)]
222pub enum LLMSynthesisError {
223    #[error("LLM API error: {message}")]
224    APIError { message: String },
225
226    #[error("Rate limited: retry after {retry_after_secs} seconds")]
227    RateLimited { retry_after_secs: u64 },
228
229    #[error("Invalid response format: {message}")]
230    InvalidResponse { message: String },
231
232    #[error("Verification failed: program does not satisfy specification")]
233    VerificationFailed,
234
235    #[error("Parse error: could not parse generated program: {message}")]
236    ParseError { message: String },
237
238    #[error("Timeout: LLM did not respond within {timeout_secs} seconds")]
239    Timeout { timeout_secs: u64 },
240
241    #[error("Context length exceeded: {tokens} tokens > {max_tokens} maximum")]
242    ContextLengthExceeded { tokens: usize, max_tokens: usize },
243}
244
245/// Result type alias for NSR operations
246pub type NSRResult<T> = Result<T, NSRError>;
247
248/// Extension trait for adding context to errors
249pub trait ErrorContext<T> {
250    /// Add context to an error
251    fn context(self, message: impl Into<String>) -> NSRResult<T>;
252
253    /// Add context with a closure (lazy evaluation)
254    fn with_context<F: FnOnce() -> String>(self, f: F) -> NSRResult<T>;
255}
256
257impl<T, E: Into<NSRError>> ErrorContext<T> for Result<T, E> {
258    fn context(self, message: impl Into<String>) -> NSRResult<T> {
259        self.map_err(|e| {
260            let inner = e.into();
261            NSRError::Internal {
262                message: format!("{}: {}", message.into(), inner),
263            }
264        })
265    }
266
267    fn with_context<F: FnOnce() -> String>(self, f: F) -> NSRResult<T> {
268        self.map_err(|e| {
269            let inner = e.into();
270            NSRError::Internal {
271                message: format!("{}: {}", f(), inner),
272            }
273        })
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn test_error_display() {
283        let err = PerceptionError::UnknownSymbol { symbol_id: 42 };
284        assert_eq!(err.to_string(), "Unknown symbol: 42");
285
286        let err = ProgramError::TypeError {
287            expected: "Integer".to_string(),
288            actual: "String".to_string(),
289        };
290        assert_eq!(err.to_string(), "Type error: expected Integer, got String");
291    }
292
293    #[test]
294    fn test_error_conversion() {
295        let perception_err = PerceptionError::EmptyInput;
296        let nsr_err: NSRError = perception_err.into();
297        assert!(matches!(nsr_err, NSRError::Perception(_)));
298    }
299
300    #[test]
301    fn test_error_context() {
302        let result: Result<(), PerceptionError> = Err(PerceptionError::EmptyInput);
303        let contextualized = result.context("During symbol grounding");
304        assert!(contextualized.is_err());
305    }
306}