amari_gpu/
verification.rs

1//! GPU Verification Framework for Phase 4B
2//!
3//! This module implements boundary verification strategies for GPU-accelerated
4//! geometric algebra operations, addressing the challenge that phantom types
5//! cannot cross GPU memory boundaries while maintaining mathematical correctness.
6//! Extended for Phase 4C with relativistic physics verification.
7
8use crate::relativistic::{GpuRelativisticParticle, GpuSpacetimeVector};
9use crate::{GpuCliffordAlgebra, GpuError};
10use amari_core::Multivector;
11use amari_info_geom::Parameter;
12use amari_relativistic::constants::C;
13use std::collections::HashMap;
14use std::marker::PhantomData;
15use std::time::{Duration, Instant};
16use thiserror::Error;
17
18#[derive(Error, Debug)]
19pub enum GpuVerificationError {
20    #[error("Verification failed: {0}")]
21    VerificationFailed(String),
22
23    #[error("Signature mismatch: expected {expected:?}, got {actual:?}")]
24    SignatureMismatch {
25        expected: (usize, usize, usize),
26        actual: (usize, usize, usize),
27    },
28
29    #[error("Statistical verification failed: {failed}/{total} samples failed")]
30    StatisticalMismatch { failed: usize, total: usize },
31
32    #[error("Mathematical invariant violated: {invariant}")]
33    InvariantViolation { invariant: String },
34
35    #[error("GPU operation failed: {0}")]
36    GpuOperation(#[from] GpuError),
37
38    #[error("Performance budget exceeded: {actual:?} > {budget:?}")]
39    PerformanceBudgetExceeded { actual: Duration, budget: Duration },
40}
41
42/// Verification strategy for GPU operations
43#[derive(Debug, Clone)]
44pub enum VerificationStrategy {
45    /// Full verification of all elements (expensive)
46    Strict,
47    /// Statistical sampling verification (balanced)
48    Statistical { sample_rate: f64 },
49    /// Boundary verification only (fast)
50    Boundary,
51    /// Minimal verification (fastest)
52    Minimal,
53}
54
55/// Platform-aware verification configuration
56#[derive(Debug, Clone)]
57pub struct VerificationConfig {
58    pub strategy: VerificationStrategy,
59    pub performance_budget: Duration,
60    pub tolerance: f64,
61    pub enable_invariant_checking: bool,
62}
63
64impl Default for VerificationConfig {
65    fn default() -> Self {
66        Self {
67            strategy: VerificationStrategy::Statistical { sample_rate: 0.1 },
68            performance_budget: Duration::from_millis(10),
69            tolerance: 1e-12,
70            enable_invariant_checking: true,
71        }
72    }
73}
74
75/// Verified multivector with signature information preserved
76#[derive(Debug, Clone)]
77pub struct VerifiedMultivector<const P: usize, const Q: usize, const R: usize> {
78    pub inner: Multivector<P, Q, R>,
79    verification_hash: u64,
80    _phantom: PhantomData<(SignatureP<P>, SignatureQ<Q>, SignatureR<R>)>,
81}
82
83impl<const P: usize, const Q: usize, const R: usize> VerifiedMultivector<P, Q, R> {
84    /// Create verified multivector with compile-time signature checking
85    pub fn new(inner: Multivector<P, Q, R>) -> Self {
86        let verification_hash = Self::compute_verification_hash(&inner);
87        Self {
88            inner,
89            verification_hash,
90            _phantom: PhantomData,
91        }
92    }
93
94    /// Extract inner multivector for GPU operations (loses verification)
95    pub fn into_inner(self) -> Multivector<P, Q, R> {
96        self.inner
97    }
98
99    /// Get reference to inner multivector
100    pub fn inner(&self) -> &Multivector<P, Q, R> {
101        &self.inner
102    }
103
104    /// Verify mathematical invariants
105    pub fn verify_invariants(&self) -> Result<(), GpuVerificationError> {
106        // Check magnitude invariant
107        let magnitude = self.inner.magnitude();
108        if !magnitude.is_finite() {
109            return Err(GpuVerificationError::InvariantViolation {
110                invariant: "Magnitude must be finite".to_string(),
111            });
112        }
113
114        // Verify signature consistency
115        if !self.verify_signature_constraints() {
116            return Err(GpuVerificationError::InvariantViolation {
117                invariant: "Signature constraints violated".to_string(),
118            });
119        }
120
121        Ok(())
122    }
123
124    /// Compute verification hash for integrity checking
125    fn compute_verification_hash(mv: &Multivector<P, Q, R>) -> u64 {
126        use std::collections::hash_map::DefaultHasher;
127        use std::hash::{Hash, Hasher};
128
129        let mut hasher = DefaultHasher::new();
130
131        // Hash signature
132        (P, Q, R).hash(&mut hasher);
133
134        // Hash coefficients (with tolerance for floating point)
135        for i in 0..mv.dimension() {
136            let coeff = mv.get(i);
137            let normalized = (coeff * 1e12).round() as i64; // 12 decimal places
138            normalized.hash(&mut hasher);
139        }
140
141        hasher.finish()
142    }
143
144    /// Verify signature constraints are satisfied
145    fn verify_signature_constraints(&self) -> bool {
146        // Check that P + Q + R matches dimension
147        let expected_dim = 1 << (P + Q + R);
148        self.inner.dimension() == expected_dim
149    }
150
151    /// Get signature tuple
152    pub fn signature() -> (usize, usize, usize) {
153        (P, Q, R)
154    }
155}
156
157/// Phantom types for compile-time signature verification
158struct SignatureP<const P: usize>;
159struct SignatureQ<const Q: usize>;
160struct SignatureR<const R: usize>;
161
162/// GPU boundary verification system
163pub struct GpuBoundaryVerifier {
164    config: VerificationConfig,
165    performance_stats: PerformanceStats,
166}
167
168impl GpuBoundaryVerifier {
169    /// Create new boundary verifier with configuration
170    pub fn new(config: VerificationConfig) -> Self {
171        Self {
172            config,
173            performance_stats: PerformanceStats::new(),
174        }
175    }
176
177    /// Verify batch geometric product with boundary checking
178    pub async fn verified_batch_geometric_product<
179        const P: usize,
180        const Q: usize,
181        const R: usize,
182    >(
183        &mut self,
184        gpu: &GpuCliffordAlgebra,
185        a_batch: &[VerifiedMultivector<P, Q, R>],
186        b_batch: &[VerifiedMultivector<P, Q, R>],
187    ) -> Result<Vec<VerifiedMultivector<P, Q, R>>, GpuVerificationError> {
188        let start_time = Instant::now();
189
190        // 1. Pre-GPU verification phase
191        self.verify_input_batch_invariants(a_batch, b_batch)?;
192
193        // 2. Extract raw data for GPU (loses phantom types temporarily)
194        let raw_a = self.extract_raw_coefficients(a_batch);
195        let raw_b = self.extract_raw_coefficients(b_batch);
196
197        // 3. GPU computation (unverified internally)
198        let raw_result = gpu
199            .batch_geometric_product(&raw_a, &raw_b)
200            .await
201            .map_err(GpuVerificationError::GpuOperation)?;
202
203        // 4. Post-GPU verification and phantom type restoration
204        let verified_result =
205            self.verify_and_restore_types::<P, Q, R>(&raw_result, a_batch, b_batch)?;
206
207        // 5. Performance tracking
208        let elapsed = start_time.elapsed();
209        self.performance_stats
210            .record_operation(elapsed, a_batch.len());
211
212        if elapsed > self.config.performance_budget {
213            return Err(GpuVerificationError::PerformanceBudgetExceeded {
214                actual: elapsed,
215                budget: self.config.performance_budget,
216            });
217        }
218
219        Ok(verified_result)
220    }
221
222    /// Verify input batch mathematical invariants
223    fn verify_input_batch_invariants<const P: usize, const Q: usize, const R: usize>(
224        &self,
225        a_batch: &[VerifiedMultivector<P, Q, R>],
226        b_batch: &[VerifiedMultivector<P, Q, R>],
227    ) -> Result<(), GpuVerificationError> {
228        if a_batch.len() != b_batch.len() {
229            return Err(GpuVerificationError::VerificationFailed(
230                "Batch sizes must match".to_string(),
231            ));
232        }
233
234        if !self.config.enable_invariant_checking {
235            return Ok(());
236        }
237
238        // Verify invariants based on strategy
239        match &self.config.strategy {
240            VerificationStrategy::Strict => {
241                // Verify all elements
242                for (i, (a, b)) in a_batch.iter().zip(b_batch.iter()).enumerate() {
243                    a.verify_invariants().map_err(|e| {
244                        GpuVerificationError::VerificationFailed(format!("Input A[{}]: {}", i, e))
245                    })?;
246                    b.verify_invariants().map_err(|e| {
247                        GpuVerificationError::VerificationFailed(format!("Input B[{}]: {}", i, e))
248                    })?;
249                }
250            }
251            VerificationStrategy::Statistical { sample_rate } => {
252                // Verify random sample
253                let sample_size = ((a_batch.len() as f64) * sample_rate).ceil() as usize;
254                let indices = self.select_random_indices(a_batch.len(), sample_size);
255
256                for &idx in &indices {
257                    a_batch[idx].verify_invariants()?;
258                    b_batch[idx].verify_invariants()?;
259                }
260            }
261            VerificationStrategy::Boundary | VerificationStrategy::Minimal => {
262                // Only verify first and last elements
263                if !a_batch.is_empty() {
264                    a_batch[0].verify_invariants()?;
265                    b_batch[0].verify_invariants()?;
266
267                    if a_batch.len() > 1 {
268                        let last = a_batch.len() - 1;
269                        a_batch[last].verify_invariants()?;
270                        b_batch[last].verify_invariants()?;
271                    }
272                }
273            }
274        }
275
276        Ok(())
277    }
278
279    /// Extract raw coefficients for GPU computation
280    fn extract_raw_coefficients<const P: usize, const Q: usize, const R: usize>(
281        &self,
282        batch: &[VerifiedMultivector<P, Q, R>],
283    ) -> Vec<f64> {
284        let basis_count = 1 << (P + Q + R);
285        let mut raw_data = Vec::with_capacity(batch.len() * basis_count);
286
287        for mv in batch {
288            for i in 0..basis_count {
289                raw_data.push(mv.inner.get(i));
290            }
291        }
292
293        raw_data
294    }
295
296    /// Verify GPU results and restore phantom types
297    fn verify_and_restore_types<const P: usize, const Q: usize, const R: usize>(
298        &self,
299        raw_result: &[f64],
300        a_batch: &[VerifiedMultivector<P, Q, R>],
301        b_batch: &[VerifiedMultivector<P, Q, R>],
302    ) -> Result<Vec<VerifiedMultivector<P, Q, R>>, GpuVerificationError> {
303        let basis_count = 1 << (P + Q + R);
304        let batch_size = raw_result.len() / basis_count;
305
306        if batch_size != a_batch.len() {
307            return Err(GpuVerificationError::VerificationFailed(
308                "Result batch size mismatch".to_string(),
309            ));
310        }
311
312        let mut verified_results = Vec::with_capacity(batch_size);
313
314        for i in 0..batch_size {
315            let start_idx = i * basis_count;
316            let end_idx = start_idx + basis_count;
317
318            let coefficients = raw_result[start_idx..end_idx].to_vec();
319            let result_mv = Multivector::<P, Q, R>::from_coefficients(coefficients);
320
321            // Verify result based on strategy
322            match &self.config.strategy {
323                VerificationStrategy::Strict => {
324                    // Full verification: check against CPU computation
325                    let expected = a_batch[i].inner.geometric_product(&b_batch[i].inner);
326                    self.verify_approximately_equal(&result_mv, &expected, i)?;
327                }
328                VerificationStrategy::Statistical { sample_rate } => {
329                    // Statistical verification: check random samples
330                    if self.should_verify_sample(i, *sample_rate) {
331                        let expected = a_batch[i].inner.geometric_product(&b_batch[i].inner);
332                        self.verify_approximately_equal(&result_mv, &expected, i)?;
333                    }
334                }
335                VerificationStrategy::Boundary => {
336                    // Boundary verification: check first and last
337                    if i == 0 || i == batch_size - 1 {
338                        let expected = a_batch[i].inner.geometric_product(&b_batch[i].inner);
339                        self.verify_approximately_equal(&result_mv, &expected, i)?;
340                    }
341                }
342                VerificationStrategy::Minimal => {
343                    // Minimal verification: basic sanity checks only
344                    if !result_mv.magnitude().is_finite() {
345                        return Err(GpuVerificationError::InvariantViolation {
346                            invariant: format!("Result[{}] magnitude is not finite", i),
347                        });
348                    }
349                }
350            }
351
352            verified_results.push(VerifiedMultivector::new(result_mv));
353        }
354
355        Ok(verified_results)
356    }
357
358    /// Verify two multivectors are approximately equal
359    fn verify_approximately_equal<const P: usize, const Q: usize, const R: usize>(
360        &self,
361        actual: &Multivector<P, Q, R>,
362        expected: &Multivector<P, Q, R>,
363        index: usize,
364    ) -> Result<(), GpuVerificationError> {
365        let basis_count = 1 << (P + Q + R);
366
367        for i in 0..basis_count {
368            let diff = (actual.get(i) - expected.get(i)).abs();
369            let rel_error = if expected.get(i).abs() > self.config.tolerance {
370                diff / expected.get(i).abs()
371            } else {
372                diff
373            };
374
375            if rel_error > self.config.tolerance {
376                return Err(GpuVerificationError::VerificationFailed(
377                    format!(
378                        "Verification failed at result[{}], component[{}]: expected {}, got {}, error {}",
379                        index, i, expected.get(i), actual.get(i), rel_error
380                    )
381                ));
382            }
383        }
384
385        Ok(())
386    }
387
388    /// Select random indices for statistical sampling
389    fn select_random_indices(&self, total: usize, sample_size: usize) -> Vec<usize> {
390        use std::collections::HashSet;
391
392        let mut indices = HashSet::new();
393        let sample_size = sample_size.min(total);
394
395        // Simple deterministic "random" selection for reproducibility
396        let step = total / sample_size.max(1);
397        for i in 0..sample_size {
398            indices.insert((i * step) % total);
399        }
400
401        // Ensure we always include first and last
402        if total > 0 {
403            indices.insert(0);
404            if total > 1 {
405                indices.insert(total - 1);
406            }
407        }
408
409        indices.into_iter().collect()
410    }
411
412    /// Determine if a sample should be verified
413    fn should_verify_sample(&self, index: usize, sample_rate: f64) -> bool {
414        // Simple deterministic sampling based on index
415        let hash = index.wrapping_mul(2654435761); // Large prime
416        let normalized = (hash as f64) / (u32::MAX as f64);
417        normalized < sample_rate
418    }
419
420    /// Get performance statistics
421    pub fn performance_stats(&self) -> &PerformanceStats {
422        &self.performance_stats
423    }
424}
425
426/// Performance tracking for verification operations
427#[derive(Debug, Clone)]
428pub struct PerformanceStats {
429    operation_count: usize,
430    total_duration: Duration,
431    total_elements: usize,
432    max_duration: Duration,
433}
434
435impl PerformanceStats {
436    fn new() -> Self {
437        Self {
438            operation_count: 0,
439            total_duration: Duration::ZERO,
440            total_elements: 0,
441            max_duration: Duration::ZERO,
442        }
443    }
444
445    fn record_operation(&mut self, duration: Duration, element_count: usize) {
446        self.operation_count += 1;
447        self.total_duration += duration;
448        self.total_elements += element_count;
449        if duration > self.max_duration {
450            self.max_duration = duration;
451        }
452    }
453
454    /// Get average operation duration
455    pub fn average_duration(&self) -> Duration {
456        if self.operation_count > 0 {
457            self.total_duration / (self.operation_count as u32)
458        } else {
459            Duration::ZERO
460        }
461    }
462
463    /// Get average throughput (elements per second)
464    pub fn average_throughput(&self) -> f64 {
465        if self.total_duration.as_secs_f64() > 0.0 {
466            self.total_elements as f64 / self.total_duration.as_secs_f64()
467        } else {
468            0.0
469        }
470    }
471
472    /// Get verification overhead as percentage
473    pub fn verification_overhead_percent(&self, baseline_duration: Duration) -> f64 {
474        if baseline_duration.as_secs_f64() > 0.0 {
475            let overhead = self.average_duration().as_secs_f64() / baseline_duration.as_secs_f64();
476            (overhead - 1.0) * 100.0
477        } else {
478            0.0
479        }
480    }
481
482    /// Get operation count
483    pub fn operation_count(&self) -> usize {
484        self.operation_count
485    }
486
487    /// Get total elements processed
488    pub fn total_elements(&self) -> usize {
489        self.total_elements
490    }
491
492    /// Get maximum operation duration
493    pub fn max_duration(&self) -> Duration {
494        self.max_duration
495    }
496}
497
498/// Statistical verification for large GPU batches
499pub struct StatisticalGpuVerifier<const P: usize, const Q: usize, const R: usize> {
500    sample_rate: f64,
501    tolerance: f64,
502    verification_cache: HashMap<u64, bool>,
503}
504
505impl<const P: usize, const Q: usize, const R: usize> StatisticalGpuVerifier<P, Q, R> {
506    /// Create new statistical verifier
507    pub fn new(sample_rate: f64, tolerance: f64) -> Self {
508        Self {
509            sample_rate,
510            tolerance,
511            verification_cache: HashMap::new(),
512        }
513    }
514
515    /// Verify batch result through statistical sampling
516    pub async fn verify_batch_statistical(
517        &mut self,
518        _gpu: &GpuCliffordAlgebra,
519        inputs: &[(VerifiedMultivector<P, Q, R>, VerifiedMultivector<P, Q, R>)],
520        gpu_results: &[Multivector<P, Q, R>],
521    ) -> Result<Vec<VerifiedMultivector<P, Q, R>>, GpuVerificationError> {
522        if inputs.len() != gpu_results.len() {
523            return Err(GpuVerificationError::VerificationFailed(
524                "Input and result batch sizes must match".to_string(),
525            ));
526        }
527
528        let sample_size = (inputs.len() as f64 * self.sample_rate).ceil() as usize;
529        let indices = self.select_random_indices(inputs.len(), sample_size);
530
531        let mut failed_samples = 0;
532
533        for &idx in &indices {
534            let (a, b) = &inputs[idx];
535            let expected = a.inner.geometric_product(&b.inner);
536            let actual = &gpu_results[idx];
537
538            if !self.approximately_equal(&expected, actual) {
539                failed_samples += 1;
540
541                // Cache failed verification
542                let hash = self.compute_input_hash(a, b);
543                self.verification_cache.insert(hash, false);
544            }
545        }
546
547        // Allow small number of failures for statistical verification
548        let failure_rate = failed_samples as f64 / indices.len() as f64;
549        let max_failure_rate = 0.01; // 1% maximum failure rate
550
551        if failure_rate > max_failure_rate {
552            return Err(GpuVerificationError::StatisticalMismatch {
553                failed: failed_samples,
554                total: indices.len(),
555            });
556        }
557
558        // If samples pass, assume entire batch is correct with verification restoration
559        let verified_results = gpu_results
560            .iter()
561            .map(|mv| VerifiedMultivector::new(mv.clone()))
562            .collect();
563
564        Ok(verified_results)
565    }
566
567    /// Check if two multivectors are approximately equal
568    fn approximately_equal(&self, a: &Multivector<P, Q, R>, b: &Multivector<P, Q, R>) -> bool {
569        let basis_count = 1 << (P + Q + R);
570
571        for i in 0..basis_count {
572            let diff = (a.get(i) - b.get(i)).abs();
573            let rel_error = if b.get(i).abs() > self.tolerance {
574                diff / b.get(i).abs()
575            } else {
576                diff
577            };
578
579            if rel_error > self.tolerance {
580                return false;
581            }
582        }
583
584        true
585    }
586
587    /// Select random indices for sampling
588    fn select_random_indices(&self, total: usize, sample_size: usize) -> Vec<usize> {
589        let mut indices = Vec::new();
590        let sample_size = sample_size.min(total);
591
592        if total == 0 {
593            return indices;
594        }
595
596        // Always include first and last
597        indices.push(0);
598        if total > 1 {
599            indices.push(total - 1);
600        }
601
602        // Add random intermediate indices
603        let step = if sample_size > 2 {
604            total / (sample_size - 2).max(1)
605        } else {
606            total
607        };
608
609        for i in 1..sample_size.saturating_sub(1) {
610            let idx = (i * step) % total;
611            if !indices.contains(&idx) {
612                indices.push(idx);
613            }
614        }
615
616        indices.sort_unstable();
617        indices
618    }
619
620    /// Compute hash for input pair caching
621    fn compute_input_hash(
622        &self,
623        a: &VerifiedMultivector<P, Q, R>,
624        b: &VerifiedMultivector<P, Q, R>,
625    ) -> u64 {
626        use std::collections::hash_map::DefaultHasher;
627        use std::hash::{Hash, Hasher};
628
629        let mut hasher = DefaultHasher::new();
630        a.verification_hash.hash(&mut hasher);
631        b.verification_hash.hash(&mut hasher);
632        hasher.finish()
633    }
634}
635
636#[cfg(test)]
637mod tests {
638    use super::*;
639
640    #[test]
641    fn test_verified_multivector_creation() {
642        let mv = Multivector::<3, 0, 0>::zero();
643        let verified = VerifiedMultivector::new(mv);
644
645        assert_eq!(VerifiedMultivector::<3, 0, 0>::signature(), (3, 0, 0));
646        assert!(verified.verify_invariants().is_ok());
647    }
648
649    #[test]
650    fn test_verification_config_default() {
651        let config = VerificationConfig::default();
652
653        match config.strategy {
654            VerificationStrategy::Statistical { sample_rate } => {
655                assert!((sample_rate - 0.1).abs() < 1e-10);
656            }
657            _ => panic!("Expected statistical strategy"),
658        }
659
660        assert_eq!(config.performance_budget, Duration::from_millis(10));
661        assert!((config.tolerance - 1e-12).abs() < 1e-15);
662        assert!(config.enable_invariant_checking);
663    }
664
665    #[test]
666    fn test_performance_stats() {
667        let mut stats = PerformanceStats::new();
668
669        stats.record_operation(Duration::from_millis(5), 100);
670        stats.record_operation(Duration::from_millis(10), 200);
671
672        assert_eq!(stats.operation_count, 2);
673        assert_eq!(stats.total_elements, 300);
674        assert_eq!(
675            stats.average_duration(),
676            Duration::from_millis(7) + Duration::from_micros(500)
677        );
678        assert_eq!(stats.max_duration, Duration::from_millis(10));
679
680        let throughput = stats.average_throughput();
681        assert!(throughput > 0.0);
682    }
683
684    #[test]
685    fn test_statistical_verifier_sampling() {
686        let verifier = StatisticalGpuVerifier::<3, 0, 0>::new(0.1, 1e-12);
687
688        let indices = verifier.select_random_indices(100, 10);
689        assert!(indices.len() <= 10);
690        assert!(indices.contains(&0)); // First element
691        assert!(indices.contains(&99)); // Last element
692
693        // Test empty case
694        let empty_indices = verifier.select_random_indices(0, 5);
695        assert!(empty_indices.is_empty());
696    }
697
698    #[tokio::test]
699    async fn test_boundary_verifier_creation() {
700        let config = VerificationConfig::default();
701        let verifier = GpuBoundaryVerifier::new(config);
702
703        assert_eq!(verifier.performance_stats().operation_count, 0);
704        assert_eq!(
705            verifier.performance_stats().average_duration(),
706            Duration::ZERO
707        );
708    }
709}
710
711/// Relativistic physics verification functions for GPU operations
712pub struct RelativisticVerifier {
713    tolerance: f64,
714    #[allow(dead_code)]
715    config: VerificationConfig,
716}
717
718impl RelativisticVerifier {
719    /// Create new relativistic verifier
720    pub fn new(tolerance: f64) -> Self {
721        Self {
722            tolerance,
723            config: VerificationConfig::default(),
724        }
725    }
726
727    /// Verify four-velocity normalization: u·u = c²
728    pub fn verify_four_velocity_normalization(
729        &self,
730        velocities: &[GpuSpacetimeVector],
731    ) -> Result<(), GpuVerificationError> {
732        let c_squared = (C * C) as f32;
733
734        for (i, velocity) in velocities.iter().enumerate() {
735            let norm_squared = self.minkowski_norm_squared(velocity);
736            let deviation = (norm_squared - c_squared).abs();
737            let relative_error = deviation / c_squared;
738
739            if relative_error > self.tolerance as f32 {
740                return Err(GpuVerificationError::InvariantViolation {
741                    invariant: format!(
742                        "Four-velocity normalization violation at index {}: |u|² = {:.6e}, expected c² = {:.6e}, deviation = {:.6e}",
743                        i, norm_squared, c_squared, deviation
744                    ),
745                });
746            }
747        }
748
749        Ok(())
750    }
751
752    /// Verify energy-momentum relation: E² = (pc)² + (mc²)²
753    pub fn verify_energy_momentum_relation(
754        &self,
755        particles: &[GpuRelativisticParticle],
756    ) -> Result<(), GpuVerificationError> {
757        for (i, particle) in particles.iter().enumerate() {
758            let gamma = self.lorentz_factor(&particle.velocity);
759            let c = C as f32;
760
761            // Total energy: E = γmc²
762            let energy = gamma * particle.mass * c * c;
763
764            // Spatial momentum magnitude: |p| = γm|v|
765            let spatial_vel_mag = (particle.velocity.x * particle.velocity.x
766                + particle.velocity.y * particle.velocity.y
767                + particle.velocity.z * particle.velocity.z)
768                .sqrt();
769            let momentum_mag = gamma * particle.mass * spatial_vel_mag;
770
771            // Energy-momentum relation: E² = (pc)² + (mc²)²
772            let lhs = energy * energy;
773            let rhs = (momentum_mag * c) * (momentum_mag * c)
774                + (particle.mass * c * c) * (particle.mass * c * c);
775
776            let deviation = (lhs - rhs).abs();
777            let relative_error = deviation / lhs.max(rhs);
778
779            if relative_error > self.tolerance as f32 {
780                return Err(GpuVerificationError::InvariantViolation {
781                    invariant: format!(
782                        "Energy-momentum relation violation at particle {}: E² = {:.6e}, (pc)² + (mc²)² = {:.6e}, relative error = {:.6e}",
783                        i, lhs, rhs, relative_error
784                    ),
785                });
786            }
787        }
788
789        Ok(())
790    }
791
792    /// Verify Minkowski signature preservation
793    pub fn verify_minkowski_signature(
794        &self,
795        vectors: &[GpuSpacetimeVector],
796        expected_signs: &[i8], // +1 for timelike, -1 for spacelike, 0 for null
797    ) -> Result<(), GpuVerificationError> {
798        if vectors.len() != expected_signs.len() {
799            return Err(GpuVerificationError::VerificationFailed(
800                "Vector and expected sign arrays must have same length".to_string(),
801            ));
802        }
803
804        for (i, (vector, &expected_sign)) in vectors.iter().zip(expected_signs.iter()).enumerate() {
805            let norm_squared = self.minkowski_norm_squared(vector);
806
807            let actual_sign = if norm_squared > self.tolerance as f32 {
808                1 // Timelike
809            } else if norm_squared < -(self.tolerance as f32) {
810                -1 // Spacelike
811            } else {
812                0 // Null
813            };
814
815            if actual_sign != expected_sign {
816                return Err(GpuVerificationError::InvariantViolation {
817                    invariant: format!(
818                        "Minkowski signature violation at index {}: expected {}, got {} (norm² = {:.6e})",
819                        i, expected_sign, actual_sign, norm_squared
820                    ),
821                });
822            }
823        }
824
825        Ok(())
826    }
827
828    /// Verify causality constraints
829    pub fn verify_causality_constraints(
830        &self,
831        velocities: &[GpuSpacetimeVector],
832    ) -> Result<(), GpuVerificationError> {
833        let c = C as f32;
834
835        for (i, velocity) in velocities.iter().enumerate() {
836            let spatial_speed_squared =
837                velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z;
838            let spatial_speed = spatial_speed_squared.sqrt();
839
840            // Check that spatial velocity magnitude is less than c
841            if spatial_speed >= c * (1.0 - self.tolerance as f32) {
842                return Err(GpuVerificationError::InvariantViolation {
843                    invariant: format!(
844                        "Causality violation at index {}: spatial speed {:.6e} >= c ({:.6e})",
845                        i, spatial_speed, c
846                    ),
847                });
848            }
849        }
850
851        Ok(())
852    }
853
854    /// Compute Minkowski norm squared: t² - x² - y² - z²
855    fn minkowski_norm_squared(&self, vector: &GpuSpacetimeVector) -> f32 {
856        vector.t * vector.t - vector.x * vector.x - vector.y * vector.y - vector.z * vector.z
857    }
858
859    /// Compute Lorentz factor γ = 1/√(1 - v²/c²)
860    fn lorentz_factor(&self, four_velocity: &GpuSpacetimeVector) -> f32 {
861        let c = C as f32;
862        four_velocity.t / c
863    }
864
865    /// Verify a batch of relativistic particles with comprehensive checks
866    pub fn verify_particle_batch(
867        &self,
868        particles: &[GpuRelativisticParticle],
869    ) -> Result<(), GpuVerificationError> {
870        // Extract velocities for verification
871        let velocities: Vec<GpuSpacetimeVector> = particles.iter().map(|p| p.velocity).collect();
872
873        // Verify four-velocity normalization
874        self.verify_four_velocity_normalization(&velocities)?;
875
876        // Verify energy-momentum relation
877        self.verify_energy_momentum_relation(particles)?;
878
879        // Verify causality constraints
880        self.verify_causality_constraints(&velocities)?;
881
882        Ok(())
883    }
884
885    /// Statistical verification of relativistic invariants
886    pub fn statistical_verify_particles(
887        &self,
888        particles: &[GpuRelativisticParticle],
889        sample_rate: f64,
890    ) -> Result<(), GpuVerificationError> {
891        if particles.is_empty() {
892            return Ok(());
893        }
894
895        let sample_count = ((particles.len() as f64) * sample_rate).ceil() as usize;
896        let sample_count = sample_count.min(particles.len()).max(1);
897
898        let mut sampled_particles = Vec::with_capacity(sample_count);
899        let step = particles.len() / sample_count;
900
901        for i in 0..sample_count {
902            let index = i * step;
903            if index < particles.len() {
904                sampled_particles.push(particles[index]);
905            }
906        }
907
908        // Always include first and last if not already included
909        if !sampled_particles.is_empty() {
910            sampled_particles[0] = particles[0];
911            if sampled_particles.len() > 1 && sample_count < particles.len() {
912                let last_index = sampled_particles.len() - 1;
913                sampled_particles[last_index] = particles[particles.len() - 1];
914            }
915        }
916
917        self.verify_particle_batch(&sampled_particles)
918    }
919}
920
921#[cfg(test)]
922mod relativistic_tests {
923    use super::*;
924
925    #[test]
926    fn test_four_velocity_normalization_verification() {
927        // Use relative tolerance appropriate for c² scale values
928        let verifier = RelativisticVerifier::new(1e-5);
929        let c = C as f32;
930
931        // Test with properly normalized four-velocity directly
932
933        // Actually create properly normalized four-velocity
934        let beta = 0.6_f32;
935        let gamma = 1.0_f32 / (1.0_f32 - beta * beta).sqrt();
936        let normalized_velocity = GpuSpacetimeVector::new(gamma * c, gamma * beta * c, 0.0, 0.0);
937
938        // Debug: Check the actual norm
939        let actual_norm_sq = normalized_velocity.t * normalized_velocity.t
940            - normalized_velocity.x * normalized_velocity.x
941            - normalized_velocity.y * normalized_velocity.y
942            - normalized_velocity.z * normalized_velocity.z;
943        let expected_c_sq = c * c;
944        let deviation = (actual_norm_sq - expected_c_sq).abs();
945
946        // Check relative error instead of absolute error for large numbers like c²
947        let relative_error = deviation / expected_c_sq;
948        if relative_error > 1e-6 {
949            panic!("Four-velocity not properly normalized: actual = {:.8e}, expected = {:.8e}, relative error = {:.8e}",
950                actual_norm_sq, expected_c_sq, relative_error);
951        }
952
953        let velocities = vec![normalized_velocity];
954        assert!(verifier
955            .verify_four_velocity_normalization(&velocities)
956            .is_ok());
957
958        // Invalid four-velocity (not normalized)
959        let invalid_velocity = GpuSpacetimeVector::new(1.0, 2.0, 3.0, 4.0);
960        let invalid_velocities = vec![invalid_velocity];
961        assert!(verifier
962            .verify_four_velocity_normalization(&invalid_velocities)
963            .is_err());
964    }
965
966    #[test]
967    fn test_causality_verification() {
968        let verifier = RelativisticVerifier::new(1e-6);
969        let c = C as f32;
970
971        // Valid subluminal velocity
972        let valid_velocity = GpuSpacetimeVector::new(c, 0.5 * c, 0.0, 0.0);
973        let valid_velocities = vec![valid_velocity];
974        assert!(verifier
975            .verify_causality_constraints(&valid_velocities)
976            .is_ok());
977
978        // Invalid superluminal velocity
979        let invalid_velocity = GpuSpacetimeVector::new(c, 1.1 * c, 0.0, 0.0);
980        let invalid_velocities = vec![invalid_velocity];
981        assert!(verifier
982            .verify_causality_constraints(&invalid_velocities)
983            .is_err());
984    }
985
986    #[test]
987    fn test_minkowski_signature_verification() {
988        let verifier = RelativisticVerifier::new(1e-6);
989
990        // Timelike vector (t² > x² + y² + z²)
991        let timelike = GpuSpacetimeVector::new(2.0, 1.0, 0.0, 0.0);
992        // Spacelike vector (t² < x² + y² + z²)
993        let spacelike = GpuSpacetimeVector::new(1.0, 2.0, 0.0, 0.0);
994        // Null vector (t² = x² + y² + z²)
995        let null = GpuSpacetimeVector::new(1.0, 1.0, 0.0, 0.0);
996
997        let vectors = vec![timelike, spacelike, null];
998        let expected_signs = vec![1, -1, 0];
999
1000        assert!(verifier
1001            .verify_minkowski_signature(&vectors, &expected_signs)
1002            .is_ok());
1003
1004        // Wrong expectations should fail
1005        let wrong_signs = vec![-1, 1, 0];
1006        assert!(verifier
1007            .verify_minkowski_signature(&vectors, &wrong_signs)
1008            .is_err());
1009    }
1010}