1pub mod adaptive;
152#[cfg(feature = "automata")]
153pub mod automata;
154pub mod benchmarks;
155#[cfg(feature = "calculus")]
156pub mod calculus;
157#[cfg(feature = "dual")]
158#[allow(dead_code)]
159mod dual;
160#[cfg(feature = "enumerative")]
161pub mod enumerative;
162#[cfg(feature = "functional")]
163pub mod functional;
164#[cfg(feature = "fusion")]
165#[allow(dead_code)]
166mod fusion;
167#[cfg(feature = "gf2")]
168pub mod gf2;
169#[cfg(feature = "holographic")]
170pub mod holographic;
171#[cfg(feature = "measure")]
172pub mod measure;
173pub mod multi_gpu;
174pub mod network;
175pub mod performance;
176#[cfg(feature = "probabilistic")]
177pub mod probabilistic;
178pub mod relativistic;
179pub mod shaders;
180pub mod timeline;
181#[cfg(feature = "topology")]
184pub mod topology;
185#[cfg(feature = "tropical")]
186#[allow(dead_code)]
187mod tropical;
188pub mod unified;
189pub mod verification;
190
191pub use adaptive::{
192 AdaptiveVerificationError, AdaptiveVerificationLevel, AdaptiveVerifier, CpuFeatures,
193 GpuBackend, PlatformCapabilities, PlatformPerformanceProfile, VerificationPlatform,
194 WasmEnvironment,
195};
196use amari_core::Multivector;
197use amari_info_geom::amari_chentsov_tensor;
198#[cfg(feature = "automata")]
199pub use automata::{
200 AutomataGpuConfig, AutomataGpuError, AutomataGpuOps, AutomataGpuResult, GpuCellData,
201 GpuEvolutionParams, GpuRuleConfig,
202};
203pub use benchmarks::{
204 AmariMultiGpuBenchmarks, BenchmarkConfig, BenchmarkResult, BenchmarkRunner,
205 BenchmarkSuiteResults, BenchmarkSummary, ScalingAnalysis,
206};
207use bytemuck::{Pod, Zeroable};
208#[cfg(feature = "calculus")]
209pub use calculus::GpuCalculus;
210#[cfg(feature = "dual")]
211pub use dual::{DualGpuError, DualGpuOps, DualGpuResult, DualOperation, GpuDualNumber};
212#[cfg(feature = "enumerative")]
213pub use enumerative::{
214 EnumerativeGpuConfig, EnumerativeGpuContext, EnumerativeGpuError, EnumerativeGpuOps,
215 EnumerativeGpuResult, GpuCSMData, GpuGromovWittenData, GpuIntersectionData,
216 GpuLittlewoodRichardsonData, GpuLocalizationData, GpuMatroidRankData, GpuMultiIntersectData,
217 GpuNamespaceData, GpuOperadData, GpuSchubertClass, GpuStabilityData, GpuTropicalSchubertData,
218 GpuWDVVData,
219};
220#[cfg(all(feature = "enumerative", feature = "gf2"))]
221pub use enumerative::{
222 GpuFiniteFieldPointData, GpuKLPolynomialData, GpuRepresentabilityData,
223 GpuWeightDistributionData,
224};
225#[cfg(feature = "functional")]
226pub use functional::{
227 AdaptiveFunctionalCompute, GpuFunctionalError, GpuFunctionalResult, GpuHilbertSpace,
228 GpuMatrixOperator, GpuSpectralDecomposition,
229};
230#[cfg(feature = "fusion")]
231pub use fusion::{
232 FusionGpuError, FusionGpuResult, GpuHolographicTDC, GpuResonatorOutput, HolographicGpuOps,
233};
234#[cfg(feature = "gf2")]
235pub use gf2::{
236 GF2GpuContext, GF2GpuError, GF2GpuOps, GF2GpuResult, GpuGF2CliffordPair, GpuGF2HammingPair,
237 GpuGF2MatVecData,
238};
239#[cfg(feature = "holographic")]
240pub use holographic::{
241 GpuHolographic, GpuHolographicError, GpuHolographicMemory, GpuHolographicResult,
242 GpuOpticalField,
243};
244#[cfg(feature = "measure")]
245pub use measure::{
246 GpuIntegrator, GpuMonteCarloIntegrator, GpuMultidimIntegrator, GpuParametricDensity,
247 GpuTropicalMeasure,
248};
249pub use multi_gpu::{
250 ComputeIntensity, DeviceCapabilities, DeviceId, DeviceWorkload, GpuArchitecture, GpuDevice,
251 IntelligentLoadBalancer, LoadBalancingStrategy, MultiGpuBarrier, PerformanceRecord,
252 PerformanceStats, SynchronizationManager, Workload, WorkloadCoordinator,
253};
254pub use network::{AdaptiveNetworkCompute, GpuGeometricNetwork, GpuNetworkError, GpuNetworkResult};
255pub use performance::{
256 AdaptiveDispatchPolicy, CalibrationResult, GpuProfile, GpuProfiler, WorkgroupConfig,
257 WorkgroupOptimizer,
258};
259#[cfg(feature = "probabilistic")]
260pub use probabilistic::{GpuProbabilistic, GpuProbabilisticError, GpuProbabilisticResult};
261pub use relativistic::{
262 GpuRelativisticParticle, GpuRelativisticPhysics, GpuSpacetimeVector, GpuTrajectoryParams,
263};
264pub use shaders::{
265 ShaderLibrary, DUAL_SHADERS, FUSION_SHADERS, TOPOLOGY_SHADERS, TROPICAL_SHADERS,
266};
267use thiserror::Error;
268
269#[cfg(test)]
270#[allow(dead_code)]
271pub(crate) static GPU_TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
272pub use timeline::{
273 BottleneckAnalysis, DeviceUtilizationStats, GpuTimelineAnalyzer, MultiGpuPerformanceMonitor,
274 OptimizationRecommendation, PerformanceAnalysisReport, PerformanceBottleneck,
275 PerformanceSummary, RecommendationPriority, SynchronizationAnalysis, TimelineEvent,
276 UtilizationAnalysis,
277};
278#[cfg(feature = "topology")]
279pub use topology::{
280 AdaptiveTopologyCompute, GpuCriticalPoint, GpuTopology, GpuTopologyError, GpuTopologyResult,
281};
282#[cfg(feature = "tropical")]
283pub use tropical::{TropicalExecutionPath, TropicalGpuError, TropicalGpuOps, TropicalGpuResult};
284pub use unified::{
285 BufferPoolStats, EnhancedGpuBufferPool, GpuAccelerated, GpuContext, GpuDispatcher,
286 GpuOperationParams, GpuParam, MultiGpuStats, PoolEntryStats, SharedGpuContext, UnifiedGpuError,
287 UnifiedGpuResult,
288};
289pub use verification::{
290 GpuBoundaryVerifier, GpuVerificationError, RelativisticVerifier, StatisticalGpuVerifier,
291 VerificationConfig, VerificationStrategy, VerifiedMultivector,
292};
293use wgpu::util::DeviceExt;
294
295#[derive(Error, Debug)]
296pub enum GpuError {
297 #[error("Failed to initialize GPU: {0}")]
298 InitializationError(String),
299
300 #[error("GPU buffer error: {0}")]
301 BufferError(String),
302
303 #[error("Shader compilation error: {0}")]
304 ShaderError(String),
305}
306
307pub struct GpuCliffordAlgebra {
317 device: wgpu::Device,
318 queue: wgpu::Queue,
319 compute_pipeline: wgpu::ComputePipeline,
320 cayley_buffer: wgpu::Buffer,
321 #[allow(dead_code)]
322 dim: usize,
323 basis_count: usize,
324}
325
326impl GpuCliffordAlgebra {
327 pub async fn new<const P: usize, const Q: usize, const R: usize>() -> Result<Self, GpuError> {
329 let instance = wgpu::Instance::default();
330
331 let adapter = instance
332 .request_adapter(&wgpu::RequestAdapterOptions {
333 power_preference: wgpu::PowerPreference::HighPerformance,
334 compatible_surface: None,
335 force_fallback_adapter: false,
336 })
337 .await
338 .ok_or_else(|| GpuError::InitializationError("No GPU adapter found".to_string()))?;
339
340 let (device, queue) = adapter
341 .request_device(
342 &wgpu::DeviceDescriptor {
343 label: Some("Amari GPU Device"),
344 required_features: wgpu::Features::empty(),
345 required_limits: wgpu::Limits::default(),
346 },
347 None,
348 )
349 .await
350 .map_err(|e| GpuError::InitializationError(e.to_string()))?;
351
352 let dim = P + Q + R;
353 let basis_count = 1 << dim;
354
355 let cayley_table = Self::generate_cayley_table::<P, Q, R>();
357 let cayley_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
358 label: Some("Cayley Table"),
359 contents: bytemuck::cast_slice(&cayley_table),
360 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
361 });
362
363 let shader_source = GEOMETRIC_PRODUCT_SHADER.replace(
365 "const BASIS_COUNT: u32 = 8u; // For 3D Clifford algebra",
366 &format!("const BASIS_COUNT: u32 = {basis_count}u;"),
367 );
368 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
369 label: Some("Geometric Product Shader"),
370 source: wgpu::ShaderSource::Wgsl(shader_source.into()),
371 });
372
373 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
374 label: Some("Compute Bind Group Layout"),
375 entries: &[
376 wgpu::BindGroupLayoutEntry {
377 binding: 0,
378 visibility: wgpu::ShaderStages::COMPUTE,
379 ty: wgpu::BindingType::Buffer {
380 ty: wgpu::BufferBindingType::Storage { read_only: true },
381 has_dynamic_offset: false,
382 min_binding_size: None,
383 },
384 count: None,
385 },
386 wgpu::BindGroupLayoutEntry {
387 binding: 1,
388 visibility: wgpu::ShaderStages::COMPUTE,
389 ty: wgpu::BindingType::Buffer {
390 ty: wgpu::BufferBindingType::Storage { read_only: true },
391 has_dynamic_offset: false,
392 min_binding_size: None,
393 },
394 count: None,
395 },
396 wgpu::BindGroupLayoutEntry {
397 binding: 2,
398 visibility: wgpu::ShaderStages::COMPUTE,
399 ty: wgpu::BindingType::Buffer {
400 ty: wgpu::BufferBindingType::Storage { read_only: true },
401 has_dynamic_offset: false,
402 min_binding_size: None,
403 },
404 count: None,
405 },
406 wgpu::BindGroupLayoutEntry {
407 binding: 3,
408 visibility: wgpu::ShaderStages::COMPUTE,
409 ty: wgpu::BindingType::Buffer {
410 ty: wgpu::BufferBindingType::Storage { read_only: false },
411 has_dynamic_offset: false,
412 min_binding_size: None,
413 },
414 count: None,
415 },
416 ],
417 });
418
419 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
420 label: Some("Compute Pipeline Layout"),
421 bind_group_layouts: &[&bind_group_layout],
422 push_constant_ranges: &[],
423 });
424
425 let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
426 label: Some("Geometric Product Pipeline"),
427 layout: Some(&pipeline_layout),
428 module: &shader,
429 entry_point: "main",
430 });
431
432 Ok(Self {
433 device,
434 queue,
435 compute_pipeline,
436 cayley_buffer,
437 dim,
438 basis_count,
439 })
440 }
441
442 fn generate_cayley_table<const P: usize, const Q: usize, const R: usize>() -> Vec<CayleyEntry> {
444 use amari_core::cayley::CayleyTable;
445
446 let table = CayleyTable::<P, Q, R>::get();
447 let basis_count = 1 << (P + Q + R);
448 let mut flat_table = Vec::with_capacity(basis_count * basis_count);
449
450 for i in 0..basis_count {
451 for j in 0..basis_count {
452 let (sign, index) = table.get_product(i, j);
453 flat_table.push(CayleyEntry {
454 sign: sign as f32,
455 index: index as u32,
456 });
457 }
458 }
459
460 flat_table
461 }
462
463 pub async fn batch_geometric_product(
465 &self,
466 a_batch: &[f64],
467 b_batch: &[f64],
468 ) -> Result<Vec<f64>, GpuError> {
469 let batch_size = self.validate_flat_batches(a_batch, b_batch)?;
470 if batch_size == 0 {
471 return Ok(Vec::new());
472 }
473
474 let a_f32: Vec<f32> = a_batch.iter().map(|&x| x as f32).collect();
476 let b_f32: Vec<f32> = b_batch.iter().map(|&x| x as f32).collect();
477
478 let a_buffer = self
480 .device
481 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
482 label: Some("A Buffer"),
483 contents: bytemuck::cast_slice(&a_f32),
484 usage: wgpu::BufferUsages::STORAGE,
485 });
486
487 let b_buffer = self
488 .device
489 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
490 label: Some("B Buffer"),
491 contents: bytemuck::cast_slice(&b_f32),
492 usage: wgpu::BufferUsages::STORAGE,
493 });
494
495 let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
496 label: Some("Output Buffer"),
497 size: (a_batch.len() * std::mem::size_of::<f32>()) as u64,
498 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
499 mapped_at_creation: false,
500 });
501
502 let staging_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
503 label: Some("Staging Buffer"),
504 size: (a_batch.len() * std::mem::size_of::<f32>()) as u64,
505 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
506 mapped_at_creation: false,
507 });
508
509 let bind_group_layout = self.compute_pipeline.get_bind_group_layout(0);
511 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
512 label: Some("Compute Bind Group"),
513 layout: &bind_group_layout,
514 entries: &[
515 wgpu::BindGroupEntry {
516 binding: 0,
517 resource: self.cayley_buffer.as_entire_binding(),
518 },
519 wgpu::BindGroupEntry {
520 binding: 1,
521 resource: a_buffer.as_entire_binding(),
522 },
523 wgpu::BindGroupEntry {
524 binding: 2,
525 resource: b_buffer.as_entire_binding(),
526 },
527 wgpu::BindGroupEntry {
528 binding: 3,
529 resource: output_buffer.as_entire_binding(),
530 },
531 ],
532 });
533
534 let mut encoder = self
536 .device
537 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
538 label: Some("Compute Encoder"),
539 });
540
541 {
542 let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
543 label: Some("Compute Pass"),
544 timestamp_writes: None,
545 });
546
547 compute_pass.set_pipeline(&self.compute_pipeline);
548 compute_pass.set_bind_group(0, &bind_group, &[]);
549 compute_pass.dispatch_workgroups(batch_size as u32, 1, 1);
550 }
551
552 encoder.copy_buffer_to_buffer(
553 &output_buffer,
554 0,
555 &staging_buffer,
556 0,
557 (a_batch.len() * std::mem::size_of::<f32>()) as u64,
558 );
559
560 self.queue.submit(Some(encoder.finish()));
561
562 let buffer_slice = staging_buffer.slice(..);
564 let (sender, receiver) = futures::channel::oneshot::channel();
565 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
566 let _ = sender.send(result);
567 });
568
569 self.device.poll(wgpu::Maintain::Wait);
570 receiver
571 .await
572 .map_err(|_| GpuError::BufferError("Failed to receive buffer map result".to_string()))?
573 .map_err(|e| GpuError::BufferError(e.to_string()))?;
574
575 let data = buffer_slice.get_mapped_range();
576 let result_f32: &[f32] = bytemuck::cast_slice(&data);
577 let result: Vec<f64> = result_f32.iter().map(|&x| x as f64).collect();
578
579 drop(data);
580 staging_buffer.unmap();
581
582 Ok(result)
583 }
584
585 pub fn should_use_gpu(operation_count: usize) -> bool {
587 operation_count >= 100
589 }
590
591 pub fn basis_count(&self) -> usize {
593 self.basis_count
594 }
595
596 pub fn dimension(&self) -> usize {
598 self.dim
599 }
600
601 fn validate_flat_batches(&self, a_batch: &[f64], b_batch: &[f64]) -> Result<usize, GpuError> {
602 if a_batch.len() != b_batch.len() {
603 return Err(GpuError::BufferError(
604 "input batches must have the same coefficient count".to_string(),
605 ));
606 }
607 if !a_batch.len().is_multiple_of(self.basis_count) {
608 return Err(GpuError::BufferError(format!(
609 "coefficient count {} is not a multiple of basis_count {}",
610 a_batch.len(),
611 self.basis_count
612 )));
613 }
614 for (name, batch) in [("a", a_batch), ("b", b_batch)] {
615 if let Some((index, _)) = batch
616 .iter()
617 .enumerate()
618 .find(|(_, value)| !value.is_finite())
619 {
620 return Err(GpuError::BufferError(format!(
621 "{name}_batch coefficient {index} is not finite"
622 )));
623 }
624 }
625 Ok(a_batch.len() / self.basis_count)
626 }
627}
628
629#[repr(C)]
631#[derive(Copy, Clone, Pod, Zeroable)]
632struct CayleyEntry {
633 sign: f32,
634 index: u32,
635}
636
637const GEOMETRIC_PRODUCT_SHADER: &str = r#"
639struct CayleyEntry {
640 sign: f32,
641 index: u32,
642}
643
644@group(0) @binding(0)
645var<storage, read> cayley_table: array<CayleyEntry>;
646
647@group(0) @binding(1)
648var<storage, read> a_batch: array<f32>;
649
650@group(0) @binding(2)
651var<storage, read> b_batch: array<f32>;
652
653@group(0) @binding(3)
654var<storage, read_write> output: array<f32>;
655
656const BASIS_COUNT: u32 = 8u; // For 3D Clifford algebra
657
658@compute @workgroup_size(1)
659fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
660 let batch_idx = global_id.x;
661 let offset = batch_idx * BASIS_COUNT;
662
663 // Clear output
664 for (var k = 0u; k < BASIS_COUNT; k = k + 1u) {
665 output[offset + k] = 0.0;
666 }
667
668 // Compute geometric product
669 for (var i = 0u; i < BASIS_COUNT; i = i + 1u) {
670 let a_coeff = a_batch[offset + i];
671 if (abs(a_coeff) < 1e-14) {
672 continue;
673 }
674
675 for (var j = 0u; j < BASIS_COUNT; j = j + 1u) {
676 let b_coeff = b_batch[offset + j];
677 if (abs(b_coeff) < 1e-14) {
678 continue;
679 }
680
681 let table_idx = i * BASIS_COUNT + j;
682 let entry = cayley_table[table_idx];
683 output[offset + entry.index] += entry.sign * a_coeff * b_coeff;
684 }
685 }
686}
687"#;
688
689pub struct AdaptiveCompute {
696 gpu: Option<GpuCliffordAlgebra>,
697}
698
699impl AdaptiveCompute {
700 pub async fn new<const P: usize, const Q: usize, const R: usize>() -> Self {
702 let gpu = GpuCliffordAlgebra::new::<P, Q, R>().await.ok();
703 Self { gpu }
704 }
705
706 pub async fn geometric_product<const P: usize, const Q: usize, const R: usize>(
708 &self,
709 a: &Multivector<P, Q, R>,
710 b: &Multivector<P, Q, R>,
711 ) -> Multivector<P, Q, R> {
712 a.geometric_product(b)
714 }
715
716 pub async fn batch_geometric_product(
718 &self,
719 a_batch: &[f64],
720 b_batch: &[f64],
721 ) -> Result<Vec<f64>, GpuError> {
722 let batch_size = validate_cl3_flat_batches(a_batch, b_batch)?;
723 if batch_size == 0 {
724 return Ok(Vec::new());
725 }
726
727 if let Some(gpu) = &self.gpu {
728 if GpuCliffordAlgebra::should_use_gpu(batch_size) {
729 return gpu.batch_geometric_product(a_batch, b_batch).await;
730 }
731 }
732
733 let mut result = Vec::with_capacity(a_batch.len());
735 for i in 0..batch_size {
736 let start = i * 8;
737 let end = start + 8;
738
739 let a = Multivector::<3, 0, 0>::from_coefficients(a_batch[start..end].to_vec());
740 let b = Multivector::<3, 0, 0>::from_coefficients(b_batch[start..end].to_vec());
741 let product = a.geometric_product(&b);
742
743 for j in 0..8 {
744 result.push(product.get(j));
745 }
746 }
747
748 Ok(result)
749 }
750}
751
752fn validate_probability_like_vector(name: &str, values: &[f64]) -> Result<(), GpuError> {
753 for (index, value) in values.iter().enumerate() {
754 if !value.is_finite() {
755 return Err(GpuError::BufferError(format!(
756 "{name}[{index}] is not finite"
757 )));
758 }
759 if *value < 0.0 {
760 return Err(GpuError::BufferError(format!(
761 "{name}[{index}] is negative"
762 )));
763 }
764 }
765 Ok(())
766}
767
768fn validate_kl_pair(index: usize, p: &[f64], q: &[f64]) -> Result<(), GpuError> {
769 if p.len() != q.len() {
770 return Err(GpuError::BufferError(format!(
771 "divergence pair {index} length mismatch: p={}, q={}",
772 p.len(),
773 q.len()
774 )));
775 }
776 validate_probability_like_vector("p", p)?;
777 validate_probability_like_vector("q", q)?;
778 for (coord, (pi, qi)) in p.iter().zip(q.iter()).enumerate() {
779 if *pi > 0.0 && *qi <= 0.0 {
780 return Err(GpuError::BufferError(format!(
781 "divergence pair {index} has q[{coord}] <= 0 while p[{coord}] > 0"
782 )));
783 }
784 }
785 Ok(())
786}
787
788fn validate_cl3_flat_batches(a_batch: &[f64], b_batch: &[f64]) -> Result<usize, GpuError> {
789 const CL3_BASIS_COUNT: usize = 8;
790 if a_batch.len() != b_batch.len() {
791 return Err(GpuError::BufferError(
792 "input batches must have the same coefficient count".to_string(),
793 ));
794 }
795 if !a_batch.len().is_multiple_of(CL3_BASIS_COUNT) {
796 return Err(GpuError::BufferError(format!(
797 "coefficient count {} is not a multiple of 8 for Cl(3,0,0)",
798 a_batch.len()
799 )));
800 }
801 for (name, batch) in [("a", a_batch), ("b", b_batch)] {
802 if let Some((index, _)) = batch
803 .iter()
804 .enumerate()
805 .find(|(_, value)| !value.is_finite())
806 {
807 return Err(GpuError::BufferError(format!(
808 "{name}_batch coefficient {index} is not finite"
809 )));
810 }
811 }
812 Ok(a_batch.len() / CL3_BASIS_COUNT)
813}
814
815pub struct GpuInfoGeometry {
825 device: wgpu::Device,
826 queue: wgpu::Queue,
827 tensor_pipeline: wgpu::ComputePipeline,
828 #[allow(dead_code)]
829 fisher_pipeline: wgpu::ComputePipeline,
830 #[allow(dead_code)]
831 divergence_pipeline: wgpu::ComputePipeline,
832}
833
834impl GpuInfoGeometry {
835 pub async fn new() -> Result<Self, GpuError> {
837 let instance = wgpu::Instance::default();
838
839 let adapter = if let Some(adapter) = instance
841 .request_adapter(&wgpu::RequestAdapterOptions {
842 power_preference: wgpu::PowerPreference::HighPerformance,
843 compatible_surface: None,
844 force_fallback_adapter: false,
845 })
846 .await
847 {
848 adapter
849 } else if let Some(adapter) = instance
850 .request_adapter(&wgpu::RequestAdapterOptions {
851 power_preference: wgpu::PowerPreference::LowPower,
852 compatible_surface: None,
853 force_fallback_adapter: false,
854 })
855 .await
856 {
857 adapter
858 } else if let Some(adapter) = instance
859 .request_adapter(&wgpu::RequestAdapterOptions {
860 power_preference: wgpu::PowerPreference::None,
861 compatible_surface: None,
862 force_fallback_adapter: true,
863 })
864 .await
865 {
866 adapter
867 } else {
868 return Err(GpuError::InitializationError(
869 "No GPU adapter found".to_string(),
870 ));
871 };
872
873 let (device, queue) = adapter
874 .request_device(
875 &wgpu::DeviceDescriptor {
876 label: Some("Amari GPU Info Geometry Device"),
877 required_features: wgpu::Features::empty(),
878 required_limits: wgpu::Limits::default(),
879 },
880 None,
881 )
882 .await
883 .map_err(|e| GpuError::InitializationError(format!("Device request failed: {}", e)))?;
884
885 let tensor_pipeline = Self::create_tensor_pipeline(&device)?;
887 let fisher_pipeline = Self::create_fisher_pipeline(&device)?;
888 let divergence_pipeline = Self::create_divergence_pipeline(&device)?;
889
890 Ok(Self {
891 device,
892 queue,
893 tensor_pipeline,
894 fisher_pipeline,
895 divergence_pipeline,
896 })
897 }
898
899 pub async fn new_with_device_preference(device_type: &str) -> Result<Self, GpuError> {
901 let (power_preference, force_fallback) = match device_type {
902 "high-performance" => (wgpu::PowerPreference::HighPerformance, false),
903 "low-power" => (wgpu::PowerPreference::LowPower, false),
904 "fallback" => (wgpu::PowerPreference::None, true),
905 _ => {
906 return Err(GpuError::InitializationError(
907 "Invalid device type".to_string(),
908 ))
909 }
910 };
911
912 let instance = wgpu::Instance::default();
913
914 let adapter = instance
915 .request_adapter(&wgpu::RequestAdapterOptions {
916 power_preference,
917 compatible_surface: None,
918 force_fallback_adapter: force_fallback,
919 })
920 .await
921 .ok_or_else(|| {
922 GpuError::InitializationError("No suitable adapter found".to_string())
923 })?;
924
925 let (device, queue) = adapter
926 .request_device(
927 &wgpu::DeviceDescriptor {
928 label: Some("Amari GPU Info Geometry Device"),
929 required_features: wgpu::Features::empty(),
930 required_limits: wgpu::Limits::default(),
931 },
932 None,
933 )
934 .await
935 .map_err(|e| GpuError::InitializationError(format!("Device request failed: {}", e)))?;
936
937 let tensor_pipeline = Self::create_tensor_pipeline(&device)?;
938 let fisher_pipeline = Self::create_fisher_pipeline(&device)?;
939 let divergence_pipeline = Self::create_divergence_pipeline(&device)?;
940
941 Ok(Self {
942 device,
943 queue,
944 tensor_pipeline,
945 fisher_pipeline,
946 divergence_pipeline,
947 })
948 }
949
950 pub async fn amari_chentsov_tensor(
952 &self,
953 x: &Multivector<3, 0, 0>,
954 y: &Multivector<3, 0, 0>,
955 z: &Multivector<3, 0, 0>,
956 ) -> Result<f64, GpuError> {
957 Ok(amari_chentsov_tensor(x, y, z))
959 }
960
961 pub async fn amari_chentsov_tensor_batch(
967 &self,
968 x_batch: &[Multivector<3, 0, 0>],
969 y_batch: &[Multivector<3, 0, 0>],
970 z_batch: &[Multivector<3, 0, 0>],
971 ) -> Result<Vec<f64>, GpuError> {
972 if x_batch.len() != y_batch.len() || x_batch.len() != z_batch.len() {
973 return Err(GpuError::BufferError(format!(
974 "batch length mismatch: x={}, y={}, z={}",
975 x_batch.len(),
976 y_batch.len(),
977 z_batch.len()
978 )));
979 }
980 if x_batch.is_empty() {
981 return Ok(Vec::new());
982 }
983
984 let results = x_batch
985 .iter()
986 .zip(y_batch.iter())
987 .zip(z_batch.iter())
988 .map(|((x, y), z)| amari_chentsov_tensor(x, y, z))
989 .collect();
990 Ok(results)
991 }
992
993 pub async fn amari_chentsov_tensor_from_typed_arrays(
998 &self,
999 flat_data: &[f64],
1000 batch_size: usize,
1001 ) -> Result<Vec<f64>, GpuError> {
1002 let expected = batch_size.checked_mul(9).ok_or_else(|| {
1003 GpuError::BufferError("batch size overflows flat data shape".to_string())
1004 })?;
1005 if flat_data.len() != expected {
1006 return Err(GpuError::BufferError(format!(
1007 "invalid flat data size: expected {expected}, got {}",
1008 flat_data.len()
1009 )));
1010 }
1011 if let Some((index, _)) = flat_data
1012 .iter()
1013 .enumerate()
1014 .find(|(_, value)| !value.is_finite())
1015 {
1016 return Err(GpuError::BufferError(format!(
1017 "flat tensor coefficient {index} is not finite"
1018 )));
1019 }
1020
1021 let mut x_batch = Vec::with_capacity(batch_size);
1023 let mut y_batch = Vec::with_capacity(batch_size);
1024 let mut z_batch = Vec::with_capacity(batch_size);
1025
1026 for i in 0..batch_size {
1027 let base = i * 9;
1028 let mut x = Multivector::zero();
1029 let mut y = Multivector::zero();
1030 let mut z = Multivector::zero();
1031
1032 x.set_vector_component(0, flat_data[base]);
1034 x.set_vector_component(1, flat_data[base + 1]);
1035 x.set_vector_component(2, flat_data[base + 2]);
1036
1037 y.set_vector_component(0, flat_data[base + 3]);
1038 y.set_vector_component(1, flat_data[base + 4]);
1039 y.set_vector_component(2, flat_data[base + 5]);
1040
1041 z.set_vector_component(0, flat_data[base + 6]);
1042 z.set_vector_component(1, flat_data[base + 7]);
1043 z.set_vector_component(2, flat_data[base + 8]);
1044
1045 x_batch.push(x);
1046 y_batch.push(y);
1047 z_batch.push(z);
1048 }
1049
1050 self.amari_chentsov_tensor_batch(&x_batch, &y_batch, &z_batch)
1051 .await
1052 }
1053
1054 pub async fn device_info(&self) -> Result<GpuDeviceInfo, GpuError> {
1056 Ok(GpuDeviceInfo::new(true, "WebGPU Device"))
1057 }
1058
1059 pub async fn memory_usage(&self) -> Result<u64, GpuError> {
1064 Ok(0)
1065 }
1066
1067 pub async fn fisher_information_matrix(
1073 &self,
1074 parameters: &[f64],
1075 ) -> Result<GpuFisherMatrix, GpuError> {
1076 validate_probability_like_vector("parameters", parameters)?;
1077 if parameters.is_empty() {
1078 return Ok(GpuFisherMatrix::new(Vec::new()));
1079 }
1080 let matrix = parameters
1081 .iter()
1082 .enumerate()
1083 .map(|(row, _)| {
1084 parameters
1085 .iter()
1086 .enumerate()
1087 .map(|(col, value)| {
1088 if row == col {
1089 if *value > 1e-12 {
1090 1.0 / value
1091 } else {
1092 1e12
1093 }
1094 } else {
1095 0.0
1096 }
1097 })
1098 .collect()
1099 })
1100 .collect();
1101 Ok(GpuFisherMatrix::new(matrix))
1102 }
1103
1104 pub async fn bregman_divergence_batch(
1106 &self,
1107 p_batch: &[Vec<f64>],
1108 q_batch: &[Vec<f64>],
1109 ) -> Result<Vec<f64>, GpuError> {
1110 if p_batch.len() != q_batch.len() {
1111 return Err(GpuError::BufferError(format!(
1112 "batch length mismatch: p={}, q={}",
1113 p_batch.len(),
1114 q_batch.len()
1115 )));
1116 }
1117 for (index, (p, q)) in p_batch.iter().zip(q_batch.iter()).enumerate() {
1118 validate_kl_pair(index, p, q)?;
1119 }
1120
1121 let results = p_batch
1122 .iter()
1123 .zip(q_batch.iter())
1124 .map(|(p, q)| {
1125 p.iter()
1127 .zip(q.iter())
1128 .map(|(pi, qi)| {
1129 if *pi > 0.0 && *qi > 0.0 {
1130 pi * (pi / qi).ln()
1131 } else {
1132 0.0
1133 }
1134 })
1135 .sum()
1136 })
1137 .collect();
1138 Ok(results)
1139 }
1140
1141 #[allow(dead_code)] async fn compute_tensor_batch_gpu(
1153 &self,
1154 x_batch: &[Multivector<3, 0, 0>],
1155 y_batch: &[Multivector<3, 0, 0>],
1156 z_batch: &[Multivector<3, 0, 0>],
1157 ) -> Result<Vec<f64>, GpuError> {
1158 let batch_size = x_batch.len();
1159
1160 let x_data: Vec<f32> = x_batch
1162 .iter()
1163 .flat_map(|mv| {
1164 vec![
1165 mv.vector_component(0) as f32,
1166 mv.vector_component(1) as f32,
1167 mv.vector_component(2) as f32,
1168 ]
1169 })
1170 .collect();
1171
1172 let y_data: Vec<f32> = y_batch
1173 .iter()
1174 .flat_map(|mv| {
1175 vec![
1176 mv.vector_component(0) as f32,
1177 mv.vector_component(1) as f32,
1178 mv.vector_component(2) as f32,
1179 ]
1180 })
1181 .collect();
1182
1183 let z_data: Vec<f32> = z_batch
1184 .iter()
1185 .flat_map(|mv| {
1186 vec![
1187 mv.vector_component(0) as f32,
1188 mv.vector_component(1) as f32,
1189 mv.vector_component(2) as f32,
1190 ]
1191 })
1192 .collect();
1193
1194 let x_buffer = self
1196 .device
1197 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1198 label: Some("X Batch Buffer"),
1199 contents: bytemuck::cast_slice(&x_data),
1200 usage: wgpu::BufferUsages::STORAGE,
1201 });
1202
1203 let y_buffer = self
1204 .device
1205 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1206 label: Some("Y Batch Buffer"),
1207 contents: bytemuck::cast_slice(&y_data),
1208 usage: wgpu::BufferUsages::STORAGE,
1209 });
1210
1211 let z_buffer = self
1212 .device
1213 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1214 label: Some("Z Batch Buffer"),
1215 contents: bytemuck::cast_slice(&z_data),
1216 usage: wgpu::BufferUsages::STORAGE,
1217 });
1218
1219 let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
1220 label: Some("Output Buffer"),
1221 size: (batch_size * 4) as u64, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
1223 mapped_at_creation: false,
1224 });
1225
1226 let staging_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
1227 label: Some("Staging Buffer"),
1228 size: (batch_size * 4) as u64,
1229 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
1230 mapped_at_creation: false,
1231 });
1232
1233 let bind_group_layout = self.tensor_pipeline.get_bind_group_layout(0);
1235 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1236 label: Some("Tensor Compute Bind Group"),
1237 layout: &bind_group_layout,
1238 entries: &[
1239 wgpu::BindGroupEntry {
1240 binding: 0,
1241 resource: x_buffer.as_entire_binding(),
1242 },
1243 wgpu::BindGroupEntry {
1244 binding: 1,
1245 resource: y_buffer.as_entire_binding(),
1246 },
1247 wgpu::BindGroupEntry {
1248 binding: 2,
1249 resource: z_buffer.as_entire_binding(),
1250 },
1251 wgpu::BindGroupEntry {
1252 binding: 3,
1253 resource: output_buffer.as_entire_binding(),
1254 },
1255 ],
1256 });
1257
1258 let mut encoder = self
1260 .device
1261 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1262 label: Some("Tensor Compute Encoder"),
1263 });
1264
1265 {
1266 let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1267 label: Some("Tensor Compute Pass"),
1268 timestamp_writes: None,
1269 });
1270 compute_pass.set_pipeline(&self.tensor_pipeline);
1271 compute_pass.set_bind_group(0, &bind_group, &[]);
1272 let workgroup_count = batch_size.div_ceil(64); compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
1274 }
1275
1276 encoder.copy_buffer_to_buffer(
1277 &output_buffer,
1278 0,
1279 &staging_buffer,
1280 0,
1281 (batch_size * 4) as u64,
1282 );
1283
1284 self.queue.submit(std::iter::once(encoder.finish()));
1285
1286 let buffer_slice = staging_buffer.slice(..);
1288 let (sender, receiver) = futures::channel::oneshot::channel();
1289 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
1290 let _ = sender.send(result);
1291 });
1292
1293 self.device.poll(wgpu::Maintain::Wait);
1294
1295 receiver
1296 .await
1297 .map_err(|_| GpuError::BufferError("Failed to receive buffer map result".to_string()))?
1298 .map_err(|e| GpuError::BufferError(format!("Buffer mapping failed: {:?}", e)))?;
1299
1300 let data = buffer_slice.get_mapped_range();
1301 let result_f32: &[f32] = bytemuck::cast_slice(&data);
1302 let results: Vec<f64> = result_f32.iter().map(|&x| x as f64).collect();
1303
1304 drop(data);
1305 staging_buffer.unmap();
1306
1307 Ok(results)
1308 }
1309
1310 fn create_tensor_pipeline(device: &wgpu::Device) -> Result<wgpu::ComputePipeline, GpuError> {
1311 let shader_source = TENSOR_COMPUTE_SHADER;
1312 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1313 label: Some("Tensor Compute Shader"),
1314 source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(shader_source)),
1315 });
1316
1317 let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1318 label: Some("Tensor Compute Pipeline"),
1319 layout: None,
1320 module: &shader,
1321 entry_point: "main",
1322 });
1323
1324 Ok(compute_pipeline)
1325 }
1326
1327 fn create_fisher_pipeline(device: &wgpu::Device) -> Result<wgpu::ComputePipeline, GpuError> {
1328 Self::create_tensor_pipeline(device)
1330 }
1331
1332 fn create_divergence_pipeline(
1333 device: &wgpu::Device,
1334 ) -> Result<wgpu::ComputePipeline, GpuError> {
1335 Self::create_tensor_pipeline(device)
1337 }
1338}
1339
1340#[derive(Clone, Debug, PartialEq, Eq)]
1342pub struct GpuDeviceInfo {
1343 is_gpu: bool,
1344 description: String,
1345}
1346
1347impl GpuDeviceInfo {
1348 fn new(is_gpu: bool, description: &str) -> Self {
1349 Self {
1350 is_gpu,
1351 description: description.to_string(),
1352 }
1353 }
1354
1355 pub fn is_gpu(&self) -> bool {
1356 self.is_gpu
1357 }
1358
1359 pub fn supports_webgpu(&self) -> bool {
1360 self.is_gpu
1361 }
1362
1363 pub fn is_initialized(&self) -> bool {
1364 true
1365 }
1366
1367 pub fn description(&self) -> &str {
1369 &self.description
1370 }
1371}
1372
1373#[derive(Clone, Debug, PartialEq)]
1375pub struct GpuFisherMatrix {
1376 matrix: Vec<Vec<f64>>,
1377}
1378
1379impl GpuFisherMatrix {
1380 fn new(matrix: Vec<Vec<f64>>) -> Self {
1381 Self { matrix }
1382 }
1383
1384 pub fn matrix(&self) -> &[Vec<f64>] {
1386 &self.matrix
1387 }
1388
1389 pub fn dimension(&self) -> usize {
1391 self.matrix.len()
1392 }
1393
1394 pub async fn eigenvalues(&self) -> Result<Vec<f64>, GpuError> {
1395 let mut eigenvals = Vec::new();
1397 for i in 0..self.matrix.len() {
1398 if i < self.matrix[i].len() {
1399 eigenvals.push(self.matrix[i][i]);
1400 }
1401 }
1402 Ok(eigenvals)
1403 }
1404}
1405
1406const TENSOR_COMPUTE_SHADER: &str = r#"
1408@group(0) @binding(0)
1409var<storage, read> x_batch: array<vec3<f32>>;
1410
1411@group(0) @binding(1)
1412var<storage, read> y_batch: array<vec3<f32>>;
1413
1414@group(0) @binding(2)
1415var<storage, read> z_batch: array<vec3<f32>>;
1416
1417@group(0) @binding(3)
1418var<storage, read_write> output: array<f32>;
1419
1420@compute @workgroup_size(64)
1421fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
1422 let idx = global_id.x;
1423 if (idx >= arrayLength(&x_batch)) {
1424 return;
1425 }
1426
1427 let x = x_batch[idx];
1428 let y = y_batch[idx];
1429 let z = z_batch[idx];
1430
1431 // Compute scalar triple product: x · (y × z)
1432 let cross_yz = cross(y, z);
1433 let scalar_triple = dot(x, cross_yz);
1434
1435 output[idx] = scalar_triple;
1436}
1437"#;
1438
1439#[cfg(test)]
1440mod tests {
1441 use super::*;
1442
1443 #[test]
1444 fn test_should_use_gpu() {
1445 assert!(!GpuCliffordAlgebra::should_use_gpu(10));
1446 assert!(GpuCliffordAlgebra::should_use_gpu(1000));
1447 }
1448
1449 #[test]
1450 fn test_should_use_gpu_threshold() {
1451 assert!(!GpuCliffordAlgebra::should_use_gpu(99));
1453 assert!(GpuCliffordAlgebra::should_use_gpu(100));
1454 assert!(GpuCliffordAlgebra::should_use_gpu(101));
1455 }
1456
1457 #[test]
1458 fn test_should_use_gpu_zero_batch() {
1459 assert!(!GpuCliffordAlgebra::should_use_gpu(0));
1460 }
1461
1462 #[test]
1463 fn test_should_use_gpu_single_element() {
1464 assert!(!GpuCliffordAlgebra::should_use_gpu(1));
1465 }
1466
1467 #[test]
1468 fn test_gpu_error_display() {
1469 let init_err = GpuError::InitializationError("No GPU found".to_string());
1470 assert!(init_err.to_string().contains("Failed to initialize GPU"));
1471
1472 let buffer_err = GpuError::BufferError("Buffer too small".to_string());
1473 assert!(buffer_err.to_string().contains("buffer error"));
1474
1475 let shader_err = GpuError::ShaderError("Invalid syntax".to_string());
1476 assert!(shader_err.to_string().contains("Shader compilation"));
1477 }
1478
1479 #[test]
1480 fn test_gpu_error_debug() {
1481 let err = GpuError::InitializationError("test".to_string());
1482 let debug_str = format!("{:?}", err);
1483 assert!(debug_str.contains("InitializationError"));
1484 }
1485
1486 #[test]
1487 fn test_gpu_error_variants() {
1488 let errors = vec![
1490 GpuError::InitializationError("init failed".to_string()),
1491 GpuError::BufferError("buffer failed".to_string()),
1492 GpuError::ShaderError("shader failed".to_string()),
1493 ];
1494
1495 for err in errors {
1496 let _ = err.to_string();
1498 let _ = format!("{:?}", err);
1500 }
1501 }
1502
1503 #[tokio::test]
1504 #[ignore = "GPU hardware required, may fail in CI/CD environments"]
1505 async fn test_adaptive_compute_geometric_product() {
1506 let adaptive = AdaptiveCompute::new::<3, 0, 0>().await;
1508
1509 let e1 = Multivector::<3, 0, 0>::basis_vector(0);
1510 let e2 = Multivector::<3, 0, 0>::basis_vector(1);
1511
1512 let result = adaptive.geometric_product(&e1, &e2).await;
1514 assert!(result.magnitude() > 0.0);
1516 }
1517
1518 #[tokio::test]
1519 #[ignore = "GPU hardware required, may fail in CI/CD environments"]
1520 async fn test_adaptive_compute_batch_small() {
1521 let adaptive = AdaptiveCompute::new::<3, 0, 0>().await;
1524
1525 let e1_coeffs = vec![0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]; let e2_coeffs = vec![0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]; let mut a_batch = Vec::new();
1531 let mut b_batch = Vec::new();
1532 a_batch.extend_from_slice(&e1_coeffs);
1533 a_batch.extend_from_slice(&e1_coeffs);
1534 b_batch.extend_from_slice(&e2_coeffs);
1535 b_batch.extend_from_slice(&e2_coeffs);
1536
1537 let result = adaptive.batch_geometric_product(&a_batch, &b_batch).await;
1538 assert!(result.is_ok());
1539 assert_eq!(result.unwrap().len(), 16);
1541 }
1542
1543 #[test]
1544 fn test_gpu_device_info_methods() {
1545 let info = GpuDeviceInfo::new(true, "Test GPU");
1546
1547 assert!(info.is_gpu());
1548 assert!(info.supports_webgpu());
1550 assert!(info.is_initialized());
1551 }
1552
1553 #[test]
1554 fn test_gpu_device_info_cpu() {
1555 let info = GpuDeviceInfo::new(false, "CPU Fallback");
1556
1557 assert!(!info.is_gpu());
1558 assert!(!info.supports_webgpu());
1559 assert!(info.is_initialized());
1560 }
1561
1562 #[tokio::test]
1563 async fn test_gpu_info_geometry_creation() {
1564 if std::env::var("CI").is_ok()
1566 || std::env::var("GITHUB_ACTIONS").is_ok()
1567 || std::env::var("DISPLAY").is_err()
1568 {
1569 println!("Skipping GPU test in CI environment");
1570 return;
1571 }
1572
1573 match GpuInfoGeometry::new().await {
1575 Ok(_) => {
1576 println!("GPU initialization successful");
1578 }
1579 Err(GpuError::InitializationError(_)) => {
1580 println!("GPU initialization failed - no GPU available");
1582 }
1583 Err(e) => panic!("Unexpected error: {:?}", e),
1584 }
1585 }
1586
1587 #[tokio::test]
1588 async fn test_gpu_fisher_matrix_eigenvalues() {
1589 let matrix = GpuFisherMatrix::new(vec![
1590 vec![1.0, 0.0, 0.0],
1591 vec![0.0, 2.0, 0.0],
1592 vec![0.0, 0.0, 3.0],
1593 ]);
1594 let eigenvalues = matrix.eigenvalues().await.unwrap();
1595 assert_eq!(eigenvalues.len(), 3);
1596 assert_eq!(eigenvalues[0], 1.0);
1597 assert_eq!(eigenvalues[1], 2.0);
1598 assert_eq!(eigenvalues[2], 3.0);
1599 }
1600
1601 #[tokio::test]
1602 async fn test_gpu_fisher_matrix_empty() {
1603 let matrix = GpuFisherMatrix::new(vec![]);
1604 let eigenvalues = matrix.eigenvalues().await.unwrap();
1605 assert!(eigenvalues.is_empty());
1606 }
1607
1608 #[tokio::test]
1609 async fn test_gpu_fisher_matrix_single_element() {
1610 let matrix = GpuFisherMatrix::new(vec![vec![5.0]]);
1611 let eigenvalues = matrix.eigenvalues().await.unwrap();
1612 assert_eq!(eigenvalues.len(), 1);
1613 assert_eq!(eigenvalues[0], 5.0);
1614 }
1615
1616 #[test]
1617 fn test_cayley_entry_struct() {
1618 let entry = CayleyEntry {
1620 sign: 1.0,
1621 index: 5,
1622 };
1623 assert_eq!(entry.sign, 1.0);
1624 assert_eq!(entry.index, 5);
1625
1626 let entries = vec![
1628 entry,
1629 CayleyEntry {
1630 sign: -1.0,
1631 index: 3,
1632 },
1633 ];
1634 let bytes: &[u8] = bytemuck::cast_slice(&entries);
1635 assert_eq!(bytes.len(), 16); }
1637
1638 #[test]
1639 fn test_cayley_entry_zero() {
1640 let zeroed: CayleyEntry = bytemuck::Zeroable::zeroed();
1642 assert_eq!(zeroed.sign, 0.0);
1643 assert_eq!(zeroed.index, 0);
1644 }
1645
1646 #[test]
1647 fn test_geometric_product_shader_not_empty() {
1648 assert!(!GEOMETRIC_PRODUCT_SHADER.is_empty());
1650 assert!(GEOMETRIC_PRODUCT_SHADER.contains("@compute"));
1651 assert!(GEOMETRIC_PRODUCT_SHADER.contains("@workgroup_size"));
1652 assert!(GEOMETRIC_PRODUCT_SHADER.contains("CayleyEntry"));
1653 }
1654
1655 #[test]
1656 fn test_tensor_compute_shader_not_empty() {
1657 assert!(!TENSOR_COMPUTE_SHADER.is_empty());
1659 assert!(TENSOR_COMPUTE_SHADER.contains("@compute"));
1660 assert!(TENSOR_COMPUTE_SHADER.contains("@workgroup_size"));
1661 assert!(TENSOR_COMPUTE_SHADER.contains("cross"));
1662 assert!(TENSOR_COMPUTE_SHADER.contains("dot"));
1663 }
1664
1665 #[test]
1666 fn test_gpu_error_from_string() {
1667 let msg = "Custom error message";
1669 let err = GpuError::InitializationError(msg.to_string());
1670 assert!(err.to_string().contains(msg));
1671
1672 let err = GpuError::BufferError(msg.to_string());
1673 assert!(err.to_string().contains(msg));
1674
1675 let err = GpuError::ShaderError(msg.to_string());
1676 assert!(err.to_string().contains(msg));
1677 }
1678
1679 #[test]
1680 fn test_should_use_gpu_large_batch() {
1681 assert!(GpuCliffordAlgebra::should_use_gpu(1000));
1683 assert!(GpuCliffordAlgebra::should_use_gpu(10000));
1684 assert!(GpuCliffordAlgebra::should_use_gpu(100000));
1685 }
1686
1687 #[test]
1688 fn test_should_use_gpu_near_threshold() {
1689 for i in 0..100 {
1691 assert!(!GpuCliffordAlgebra::should_use_gpu(i));
1692 }
1693 for i in 100..200 {
1694 assert!(GpuCliffordAlgebra::should_use_gpu(i));
1695 }
1696 }
1697
1698 #[test]
1699 fn test_gpu_device_info_initialized_always_true() {
1700 let gpu_info = GpuDeviceInfo::new(true, "GPU");
1702 let cpu_info = GpuDeviceInfo::new(false, "CPU");
1703 assert!(gpu_info.is_initialized());
1704 assert!(cpu_info.is_initialized());
1705 }
1706
1707 #[tokio::test]
1708 async fn test_adaptive_compute_cpu_fallback_small_batch() {
1709 let adaptive = AdaptiveCompute { gpu: None };
1711
1712 let e1_coeffs = vec![0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
1713 let e2_coeffs = vec![0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0];
1714
1715 let result = adaptive
1716 .batch_geometric_product(&e1_coeffs, &e2_coeffs)
1717 .await;
1718 assert!(result.is_ok());
1719 let coeffs = result.unwrap();
1720 assert_eq!(coeffs.len(), 8);
1721 let has_nonzero = coeffs.iter().any(|&c| c.abs() > 0.5);
1723 assert!(has_nonzero, "Product should have non-zero coefficients");
1724 }
1725
1726 #[tokio::test]
1727 async fn test_adaptive_compute_cpu_fallback_multiple_elements() {
1728 let adaptive = AdaptiveCompute { gpu: None };
1730
1731 let mut a_batch = Vec::new();
1732 let mut b_batch = Vec::new();
1733
1734 for _ in 0..5 {
1736 a_batch.extend_from_slice(&[0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
1737 b_batch.extend_from_slice(&[0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
1738 }
1739
1740 let result = adaptive.batch_geometric_product(&a_batch, &b_batch).await;
1741 assert!(result.is_ok());
1742 assert_eq!(result.unwrap().len(), 40); }
1744
1745 #[tokio::test]
1746 async fn test_adaptive_compute_geometric_product_cpu() {
1747 let adaptive = AdaptiveCompute { gpu: None };
1749
1750 let a = Multivector::<3, 0, 0>::scalar(2.0);
1751 let b = Multivector::<3, 0, 0>::scalar(3.0);
1752
1753 let result = adaptive.geometric_product(&a, &b).await;
1754 assert!((result.scalar_part() - 6.0).abs() < 1e-10);
1755 }
1756
1757 #[tokio::test]
1758 async fn test_adaptive_compute_geometric_product_basis_vectors() {
1759 let adaptive = AdaptiveCompute { gpu: None };
1761
1762 let e1 = Multivector::<3, 0, 0>::basis_vector(0);
1763 let e1_clone = Multivector::<3, 0, 0>::basis_vector(0);
1764
1765 let result = adaptive.geometric_product(&e1, &e1_clone).await;
1767 assert!((result.scalar_part() - 1.0).abs() < 1e-10);
1768 }
1769}