ruqu_qflg/
error.rs

1//! Error types for ruqu-qflg
2//!
3//! This module defines the error types used throughout the quantum federated
4//! learning crate, including errors for gradient aggregation, Byzantine detection,
5//! privacy mechanisms, and protocol operations.
6
7use thiserror::Error;
8
9/// Main error type for ruqu-qflg operations
10#[derive(Error, Debug)]
11pub enum QflgError {
12    /// Error during gradient aggregation
13    #[error("Aggregation error: {0}")]
14    Aggregation(#[from] AggregationError),
15
16    /// Error during Byzantine detection
17    #[error("Byzantine detection error: {0}")]
18    Byzantine(#[from] ByzantineError),
19
20    /// Error during privacy operations
21    #[error("Privacy error: {0}")]
22    Privacy(#[from] PrivacyError),
23
24    /// Error during protocol operations
25    #[error("Protocol error: {0}")]
26    Protocol(#[from] ProtocolError),
27
28    /// Error during quantum operations
29    #[error("Quantum error: {0}")]
30    Quantum(#[from] QuantumError),
31
32    /// Serialization/deserialization error
33    #[error("Serialization error: {0}")]
34    Serialization(String),
35
36    /// Invalid configuration
37    #[error("Invalid configuration: {0}")]
38    InvalidConfig(String),
39}
40
41/// Errors during gradient aggregation
42#[derive(Error, Debug, Clone)]
43pub enum AggregationError {
44    /// Dimension mismatch between gradients
45    #[error("Dimension mismatch: expected {expected}, got {actual}")]
46    DimensionMismatch { expected: usize, actual: usize },
47
48    /// Empty gradient set
49    #[error("No gradients provided for aggregation")]
50    EmptyGradients,
51
52    /// Invalid weight
53    #[error("Invalid weight: {0} (must be non-negative)")]
54    InvalidWeight(f64),
55
56    /// Weights do not sum to 1.0
57    #[error("Weights do not sum to 1.0: sum = {0}")]
58    WeightNormalization(f64),
59
60    /// Insufficient gradients for robust aggregation
61    #[error("Insufficient gradients: need at least {required}, got {actual}")]
62    InsufficientGradients { required: usize, actual: usize },
63
64    /// Numeric overflow during aggregation
65    #[error("Numeric overflow during aggregation")]
66    NumericOverflow,
67}
68
69/// Errors during Byzantine detection
70#[derive(Error, Debug, Clone)]
71pub enum ByzantineError {
72    /// Too many Byzantine clients detected
73    #[error("Too many Byzantine clients: {detected} > {threshold}")]
74    TooManyByzantine { detected: usize, threshold: usize },
75
76    /// Invalid Byzantine tolerance parameter
77    #[error("Invalid Byzantine tolerance: {0} (must be in (0, 0.5))")]
78    InvalidTolerance(f64),
79
80    /// Insufficient clients for Byzantine-tolerant aggregation
81    #[error("Insufficient clients for Byzantine tolerance: need {required}, have {actual}")]
82    InsufficientClients { required: usize, actual: usize },
83
84    /// Detection algorithm failure
85    #[error("Detection algorithm failed: {0}")]
86    DetectionFailed(String),
87
88    /// Score computation error
89    #[error("Score computation error: {0}")]
90    ScoreError(String),
91}
92
93/// Errors during privacy operations
94#[derive(Error, Debug, Clone)]
95pub enum PrivacyError {
96    /// Privacy budget exceeded
97    #[error("Privacy budget exceeded: epsilon {current} > {max}")]
98    BudgetExceeded { current: f64, max: f64 },
99
100    /// Invalid epsilon value
101    #[error("Invalid epsilon: {0} (must be positive)")]
102    InvalidEpsilon(f64),
103
104    /// Invalid delta value
105    #[error("Invalid delta: {0} (must be in (0, 1))")]
106    InvalidDelta(f64),
107
108    /// Invalid sensitivity
109    #[error("Invalid sensitivity: {0} (must be positive)")]
110    InvalidSensitivity(f64),
111
112    /// Noise generation failed
113    #[error("Noise generation failed: {0}")]
114    NoiseGenerationFailed(String),
115
116    /// Clipping threshold invalid
117    #[error("Invalid clipping threshold: {0} (must be positive)")]
118    InvalidClippingThreshold(f64),
119}
120
121/// Errors during protocol operations
122#[derive(Error, Debug, Clone)]
123pub enum ProtocolError {
124    /// Client not registered
125    #[error("Client not registered: {0}")]
126    ClientNotRegistered(String),
127
128    /// Duplicate client registration
129    #[error("Client already registered: {0}")]
130    DuplicateClient(String),
131
132    /// Round not started
133    #[error("No active round")]
134    NoActiveRound,
135
136    /// Round already in progress
137    #[error("Round {0} already in progress")]
138    RoundInProgress(u64),
139
140    /// Invalid round state transition
141    #[error("Invalid state transition from {from} to {to}")]
142    InvalidStateTransition { from: String, to: String },
143
144    /// Timeout during round
145    #[error("Round {round} timed out after {duration_ms}ms")]
146    RoundTimeout { round: u64, duration_ms: u64 },
147
148    /// Signature verification failed
149    #[error("Signature verification failed for client {0}")]
150    SignatureVerificationFailed(String),
151
152    /// Key exchange failed
153    #[error("Key exchange failed: {0}")]
154    KeyExchangeFailed(String),
155
156    /// Model synchronization failed
157    #[error("Model synchronization failed: {0}")]
158    SyncFailed(String),
159}
160
161/// Errors during quantum operations
162#[derive(Error, Debug, Clone)]
163pub enum QuantumError {
164    /// Invalid quantum state
165    #[error("Invalid quantum state: {0}")]
166    InvalidState(String),
167
168    /// Entanglement verification failed
169    #[error("Entanglement verification failed: fidelity {fidelity} < threshold {threshold}")]
170    EntanglementFailed { fidelity: f64, threshold: f64 },
171
172    /// Quantum random number generation failed
173    #[error("QRNG failed: {0}")]
174    QrngFailed(String),
175
176    /// Post-quantum signature error
177    #[error("Post-quantum signature error: {0}")]
178    PqSignatureError(String),
179
180    /// Coherence loss
181    #[error("Coherence lost: {0}")]
182    CoherenceLost(String),
183}
184
185/// Result type alias for ruqu-qflg operations
186pub type Result<T> = std::result::Result<T, QflgError>;
187
188impl From<serde_json::Error> for QflgError {
189    fn from(err: serde_json::Error) -> Self {
190        QflgError::Serialization(err.to_string())
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[test]
199    fn test_error_display() {
200        let err = QflgError::Aggregation(AggregationError::DimensionMismatch {
201            expected: 100,
202            actual: 50,
203        });
204        assert!(err.to_string().contains("Dimension mismatch"));
205
206        let err = QflgError::Byzantine(ByzantineError::TooManyByzantine {
207            detected: 5,
208            threshold: 3,
209        });
210        assert!(err.to_string().contains("Too many Byzantine"));
211
212        let err = QflgError::Privacy(PrivacyError::BudgetExceeded {
213            current: 2.0,
214            max: 1.0,
215        });
216        assert!(err.to_string().contains("Privacy budget exceeded"));
217    }
218
219    #[test]
220    fn test_error_from_serde() {
221        let json_err = serde_json::from_str::<i32>("invalid").unwrap_err();
222        let qflg_err: QflgError = json_err.into();
223        assert!(matches!(qflg_err, QflgError::Serialization(_)));
224    }
225
226    #[test]
227    fn test_aggregation_errors() {
228        let err = AggregationError::EmptyGradients;
229        assert_eq!(err.to_string(), "No gradients provided for aggregation");
230
231        let err = AggregationError::InvalidWeight(-0.5);
232        assert!(err.to_string().contains("-0.5"));
233    }
234
235    #[test]
236    fn test_byzantine_errors() {
237        let err = ByzantineError::InvalidTolerance(0.6);
238        assert!(err.to_string().contains("0.6"));
239
240        let err = ByzantineError::InsufficientClients {
241            required: 10,
242            actual: 5,
243        };
244        assert!(err.to_string().contains("need 10"));
245    }
246
247    #[test]
248    fn test_privacy_errors() {
249        let err = PrivacyError::InvalidEpsilon(-1.0);
250        assert!(err.to_string().contains("positive"));
251
252        let err = PrivacyError::InvalidDelta(1.5);
253        assert!(err.to_string().contains("(0, 1)"));
254    }
255
256    #[test]
257    fn test_protocol_errors() {
258        let err = ProtocolError::ClientNotRegistered("client_123".to_string());
259        assert!(err.to_string().contains("client_123"));
260
261        let err = ProtocolError::RoundTimeout {
262            round: 5,
263            duration_ms: 30000,
264        };
265        assert!(err.to_string().contains("Round 5"));
266    }
267
268    #[test]
269    fn test_quantum_errors() {
270        let err = QuantumError::EntanglementFailed {
271            fidelity: 0.85,
272            threshold: 0.95,
273        };
274        assert!(err.to_string().contains("fidelity 0.85"));
275    }
276}