1use 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#[derive(Debug, Clone)]
44pub enum VerificationStrategy {
45 Strict,
47 Statistical { sample_rate: f64 },
49 Boundary,
51 Minimal,
53}
54
55#[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#[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 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 pub fn into_inner(self) -> Multivector<P, Q, R> {
96 self.inner
97 }
98
99 pub fn inner(&self) -> &Multivector<P, Q, R> {
101 &self.inner
102 }
103
104 pub fn verify_invariants(&self) -> Result<(), GpuVerificationError> {
106 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 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 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 (P, Q, R).hash(&mut hasher);
133
134 for i in 0..mv.dimension() {
136 let coeff = mv.get(i);
137 let normalized = (coeff * 1e12).round() as i64; normalized.hash(&mut hasher);
139 }
140
141 hasher.finish()
142 }
143
144 fn verify_signature_constraints(&self) -> bool {
146 let expected_dim = 1 << (P + Q + R);
148 self.inner.dimension() == expected_dim
149 }
150
151 pub fn signature() -> (usize, usize, usize) {
153 (P, Q, R)
154 }
155}
156
157struct SignatureP<const P: usize>;
159struct SignatureQ<const Q: usize>;
160struct SignatureR<const R: usize>;
161
162pub struct GpuBoundaryVerifier {
164 config: VerificationConfig,
165 performance_stats: PerformanceStats,
166}
167
168impl GpuBoundaryVerifier {
169 pub fn new(config: VerificationConfig) -> Self {
171 Self {
172 config,
173 performance_stats: PerformanceStats::new(),
174 }
175 }
176
177 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 self.verify_input_batch_invariants(a_batch, b_batch)?;
192
193 let raw_a = self.extract_raw_coefficients(a_batch);
195 let raw_b = self.extract_raw_coefficients(b_batch);
196
197 let raw_result = gpu
199 .batch_geometric_product(&raw_a, &raw_b)
200 .await
201 .map_err(GpuVerificationError::GpuOperation)?;
202
203 let verified_result =
205 self.verify_and_restore_types::<P, Q, R>(&raw_result, a_batch, b_batch)?;
206
207 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 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 match &self.config.strategy {
240 VerificationStrategy::Strict => {
241 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 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 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 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 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 match &self.config.strategy {
323 VerificationStrategy::Strict => {
324 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 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 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 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 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 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 let step = total / sample_size.max(1);
397 for i in 0..sample_size {
398 indices.insert((i * step) % total);
399 }
400
401 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 fn should_verify_sample(&self, index: usize, sample_rate: f64) -> bool {
414 let hash = index.wrapping_mul(2654435761); let normalized = (hash as f64) / (u32::MAX as f64);
417 normalized < sample_rate
418 }
419
420 pub fn performance_stats(&self) -> &PerformanceStats {
422 &self.performance_stats
423 }
424}
425
426#[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 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 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 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 pub fn operation_count(&self) -> usize {
484 self.operation_count
485 }
486
487 pub fn total_elements(&self) -> usize {
489 self.total_elements
490 }
491
492 pub fn max_duration(&self) -> Duration {
494 self.max_duration
495 }
496}
497
498pub 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 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 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 let hash = self.compute_input_hash(a, b);
543 self.verification_cache.insert(hash, false);
544 }
545 }
546
547 let failure_rate = failed_samples as f64 / indices.len() as f64;
549 let max_failure_rate = 0.01; if failure_rate > max_failure_rate {
552 return Err(GpuVerificationError::StatisticalMismatch {
553 failed: failed_samples,
554 total: indices.len(),
555 });
556 }
557
558 let verified_results = gpu_results
560 .iter()
561 .map(|mv| VerifiedMultivector::new(mv.clone()))
562 .collect();
563
564 Ok(verified_results)
565 }
566
567 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 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 indices.push(0);
598 if total > 1 {
599 indices.push(total - 1);
600 }
601
602 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 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)); assert!(indices.contains(&99)); 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
711pub struct RelativisticVerifier {
713 tolerance: f64,
714 #[allow(dead_code)]
715 config: VerificationConfig,
716}
717
718impl RelativisticVerifier {
719 pub fn new(tolerance: f64) -> Self {
721 Self {
722 tolerance,
723 config: VerificationConfig::default(),
724 }
725 }
726
727 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 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 let energy = gamma * particle.mass * c * c;
763
764 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 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 pub fn verify_minkowski_signature(
794 &self,
795 vectors: &[GpuSpacetimeVector],
796 expected_signs: &[i8], ) -> 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 } else if norm_squared < -(self.tolerance as f32) {
810 -1 } else {
812 0 };
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 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 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 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 fn lorentz_factor(&self, four_velocity: &GpuSpacetimeVector) -> f32 {
861 let c = C as f32;
862 four_velocity.t / c
863 }
864
865 pub fn verify_particle_batch(
867 &self,
868 particles: &[GpuRelativisticParticle],
869 ) -> Result<(), GpuVerificationError> {
870 let velocities: Vec<GpuSpacetimeVector> = particles.iter().map(|p| p.velocity).collect();
872
873 self.verify_four_velocity_normalization(&velocities)?;
875
876 self.verify_energy_momentum_relation(particles)?;
878
879 self.verify_causality_constraints(&velocities)?;
881
882 Ok(())
883 }
884
885 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 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 let verifier = RelativisticVerifier::new(1e-5);
929 let c = C as f32;
930
931 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 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 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 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 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 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 let timelike = GpuSpacetimeVector::new(2.0, 1.0, 0.0, 0.0);
992 let spacelike = GpuSpacetimeVector::new(1.0, 2.0, 0.0, 0.0);
994 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 let wrong_signs = vec![-1, 1, 0];
1006 assert!(verifier
1007 .verify_minkowski_signature(&vectors, &wrong_signs)
1008 .is_err());
1009 }
1010}