Skip to main content

amari_gpu/
lib.rs

1//! GPU acceleration for geometric algebra operations using WebGPU/wgpu
2//!
3//! This crate provides GPU-accelerated implementations of core Amari operations
4//! using WebGPU/wgpu for cross-platform compatibility (native + WASM).
5//!
6//! # Overview
7//!
8//! The crate currently offers GPU acceleration for:
9//!
10//! - **Clifford Algebra**: Batch geometric products with Cayley table upload
11//! - **GF(2) Algebra**: Batch binary Clifford products, matrix-vector multiply, Hamming distance (with `gf2` feature)
12//! - **Information Geometry**: Batch Amari-Chentsov tensor computation
13//! - **Holographic Memory**: Batch bind, unbind, bundle, similarity (with `holographic` feature)
14//! - **Measure Theory**: GPU-accelerated Monte Carlo integration (with `measure` feature)
15//! - **Relativistic Physics**: Batch Lorentz transformations
16//! - **Topology**: Distance matrices, Morse critical points, Rips filtrations (with `topology` feature)
17//!
18//! Some source modules are still undergoing redesign before full public exposure.
19//! In particular, `tropical` exposes a narrow crate-root v1 surface, while `fusion`
20//! has only a reduced first public surface intended for restoration so far.
21//!
22//! # Quick Start
23//!
24//! ```ignore
25//! use amari_gpu::{GpuCliffordAlgebra, AdaptiveCompute};
26//!
27//! // Create GPU context for Cl(3,0,0)
28//! let gpu = GpuCliffordAlgebra::new::<3, 0, 0>().await?;
29//!
30//! // Batch geometric product
31//! let results = gpu.batch_geometric_product(&a_batch, &b_batch).await?;
32//!
33//! // Or use adaptive dispatch (auto CPU/GPU)
34//! let adaptive = AdaptiveCompute::new::<3, 0, 0>().await;
35//! let results = adaptive.batch_geometric_product(&a_batch, &b_batch).await?;
36//! ```
37//!
38//! # Holographic Memory (with `holographic` feature)
39//!
40//! GPU-accelerated batch operations for vector symbolic architectures:
41//!
42//! ```ignore
43//! use amari_gpu::GpuHolographic;
44//!
45//! // Create GPU holographic processor
46//! let gpu = GpuHolographic::new(256).await?;  // 256-dimensional vectors
47//!
48//! // Batch bind thousands of key-value pairs
49//! let bound_flat = gpu.batch_bind(&keys_flat, &values_flat).await?;
50//!
51//! // Batch similarity computation
52//! let similarities = gpu.batch_similarity(&a_flat, &b_flat).await?;
53//!
54//! // Batch bundle operation
55//! let bundled_flat = gpu.batch_bundle(&a_flat, &b_flat).await?;
56//! ```
57//!
58//! # Information Geometry
59//!
60//! ```ignore
61//! use amari_gpu::GpuInfoGeometry;
62//!
63//! let gpu = GpuInfoGeometry::new().await?;
64//!
65//! // Batch Amari-Chentsov tensor computation
66//! let tensors = gpu.amari_chentsov_tensor_batch(&x_batch, &y_batch, &z_batch).await?;
67//!
68//! // Fisher information matrix
69//! let fisher = gpu.fisher_information_matrix(&params).await?;
70//! ```
71//!
72//! # Adaptive CPU/GPU Dispatch
73//!
74//! All GPU operations automatically fall back to CPU when:
75//! - GPU is unavailable
76//! - Batch size is too small to benefit from GPU parallelism
77//! - Operating in CI/test environments
78//!
79//! The threshold is typically ~100 operations for GPU to be beneficial.
80//!
81//! # Feature Flags
82//!
83//! | Feature | Description |
84//! |---------|-------------|
85//! | `std` | Standard library support |
86//! | `holographic` | GPU-accelerated holographic memory |
87//! | `measure` | GPU-accelerated Monte Carlo integration |
88//! | `calculus` | GPU-accelerated differential geometry |
89//! | `dual` | Narrow GPU-backed dual-number v1 surface; broader gradient/training scaffolding private |
90//! | `probabilistic` | GPU-accelerated probability sampling |
91//! | `automata` | GPU-backed automata rule/energy kernels with documented CPU neighborhood fallback |
92//! | `enumerative` | Broad GPU-backed enumerative kernels; high-use surface with representative baseline tests |
93//! | `functional` | Mixed GPU-backed functional analysis and documented CPU spectral/fallback paths |
94//! | `topology` | Mixed GPU-backed topology and documented CPU Rips/Betti fallback paths |
95//! | `gf2` | GPU-accelerated GF(2) algebra (binary Clifford products, matrix ops, Hamming distance) |
96//! | `fusion` | Reduced first public surface restored; broader fusion GPU API still redesign-pending |
97//! | `tropical` | Narrow crate-root public v1 surface for tropical matrix multiply/adaptive dispatch |
98//! | `webgpu` | Enable WebGPU backend |
99//! | `high-precision` | Enable 128-bit float support |
100//!
101//! # Tropical GPU v1 (with `tropical` feature)
102//!
103//! A narrow public tropical GPU surface is available at the crate root:
104//!
105//! ```ignore
106//! use amari_gpu::{TropicalExecutionPath, TropicalGpuOps};
107//! use amari_tropical::{TropicalMatrix, TropicalNumber};
108//!
109//! let mut gpu = TropicalGpuOps::new().await?;
110//!
111//! let mut a = TropicalMatrix::new(64, 64);
112//! let mut b = TropicalMatrix::new(64, 64);
113//!
114//! for i in 0..64 {
115//!     for j in 0..64 {
116//!         a.data[i][j] = TropicalNumber::new((i as f32 - j as f32) * 0.25);
117//!         b.data[i][j] = TropicalNumber::new((i as f32 + j as f32) * 0.125);
118//!     }
119//! }
120//!
121//! match gpu.matrix_multiply_execution_path(a.rows, a.cols, b.cols) {
122//!     TropicalExecutionPath::Cpu => println!("CPU path"),
123//!     TropicalExecutionPath::Gpu => println!("GPU path"),
124//! }
125//!
126//! let gpu_result = gpu.matrix_multiply(&a, &b).await?;
127//! let adaptive_result = gpu.matrix_multiply_adaptive(&a, &b).await?;
128//! let attention_scores = gpu.attention_scores(&a).await?;
129//! # let _ = (gpu_result, adaptive_result, attention_scores);
130//! ```
131//!
132//! This is intentionally narrower than a full `amari_gpu::tropical` module. It currently
133//! includes dense tropical matrix multiply and winner-takes-all attention scores. Broader
134//! tropical GPU APIs remain redesign-pending.
135//!
136//! # Performance
137//!
138//! GPU acceleration provides significant speedups for batch operations:
139//!
140//! | Operation | Batch Size | Speedup |
141//! |-----------|------------|---------|
142//! | Geometric Product | 1000 | ~10-50x |
143//! | Holographic Bind | 10000 | ~20-100x |
144//! | Similarity Batch | 10000 | ~50-200x |
145//! | Monte Carlo | 100000 | ~100-500x |
146//! | Distance Matrix | 1000 pts | ~50x |
147//! | Morse Critical Points | 100x100 | ~100x |
148//!
149//! Actual speedups depend on GPU hardware and driver support.
150
151pub 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// NOTE: `tropical.rs` is still redesigning toward a fuller public module, but a
182// narrow v1 surface is now re-exported at the crate root under the `tropical` feature.
183#[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
307/// GPU-accelerated Clifford algebra operations.
308///
309/// Public v1 exposes batch geometric products for flat multivector coefficient
310/// arrays. Inputs must contain complete multivectors of length `2^(P+Q+R)` and
311/// are computed on the GPU in `f32`, then converted back to `f64` for API
312/// compatibility.
313///
314/// `AdaptiveCompute::batch_geometric_product` is a separate Cl(3,0,0) helper;
315/// use `GpuCliffordAlgebra::new::<P,Q,R>()` directly for other signatures.
316pub 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    /// Initialize GPU context and compile shaders
328    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        // Generate and upload Cayley table
356        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        // Create compute shader with a signature-specific basis count.
364        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    /// Generate Cayley table as flat array for GPU
443    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    /// Perform batch geometric product on GPU.
464    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        // Convert to f32 for GPU
475        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        // Create GPU buffers
479        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        // Create bind group
510        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        // Dispatch compute shader
535        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        // Read back results
563        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    /// Heuristic to determine if GPU should be used
586    pub fn should_use_gpu(operation_count: usize) -> bool {
587        // GPU is beneficial for batch operations with many multivectors
588        operation_count >= 100
589    }
590
591    /// Number of basis blades for this signature.
592    pub fn basis_count(&self) -> usize {
593        self.basis_count
594    }
595
596    /// Algebra dimension `P + Q + R` for this GPU context.
597    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/// Cayley table entry for GPU
630#[repr(C)]
631#[derive(Copy, Clone, Pod, Zeroable)]
632struct CayleyEntry {
633    sign: f32,
634    index: u32,
635}
636
637/// WGSL compute shader for geometric product
638const 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
689/// Adaptive GPU/CPU dispatcher for the legacy Cl(3,0,0) flat-batch helper.
690///
691/// Single multivector products always use the CPU `amari-core` baseline.
692/// `batch_geometric_product` accepts flat Cl(3,0,0) batches with 8 coefficients
693/// per multivector and uses GPU only when a context is available and the batch
694/// crosses `GpuCliffordAlgebra::should_use_gpu`.
695pub struct AdaptiveCompute {
696    gpu: Option<GpuCliffordAlgebra>,
697}
698
699impl AdaptiveCompute {
700    /// Create with optional GPU acceleration
701    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    /// Perform geometric product, automatically choosing CPU or GPU
707    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        // For single operations, always use CPU
713        a.geometric_product(b)
714    }
715
716    /// Batch geometric product with adaptive dispatch for flat Cl(3,0,0) inputs.
717    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        // Fallback to CPU
734        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
815/// Information-geometry operations with GPU-ready infrastructure.
816///
817/// The public 0.20 surface is a correctness-first CPU baseline after WebGPU
818/// context creation. Amari-Chentsov batches, Fisher metrics, and Bregman/KL-style
819/// divergences are validated and computed with `amari-info-geom` semantics while
820/// shader-backed tensor/fisher/divergence pipelines remain private restoration
821/// candidates.
822///
823/// This preserves public API stability without claiming unvalidated GPU kernels.
824pub 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    /// Initialize GPU context for information geometry operations
836    pub async fn new() -> Result<Self, GpuError> {
837        let instance = wgpu::Instance::default();
838
839        // Try different adapter options, starting with high performance, then fallback
840        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        // Create compute pipelines for different operations
886        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    /// Create with specific device preference for edge computing
900    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    /// Compute single Amari-Chentsov tensor (CPU fallback for small operations)
951    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        // For single computations, use CPU
958        Ok(amari_chentsov_tensor(x, y, z))
959    }
960
961    /// Batch compute Amari-Chentsov tensors with CPU-baseline semantics.
962    ///
963    /// All three batches must have equal length. The current public path uses
964    /// `amari-info-geom::amari_chentsov_tensor` for correctness; the shader path
965    /// remains private until hardware parity is validated.
966    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    /// Compute tensor batch from TypedArray-style flat data.
994    ///
995    /// Each item is `[x0,x1,x2, y0,y1,y2, z0,z1,z2]` for Cl(3,0,0) vector
996    /// components. All values must be finite.
997    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        // Convert flat data to multivector batches
1022        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            // Extract vector components
1033            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    /// Get device information for edge computing
1055    pub async fn device_info(&self) -> Result<GpuDeviceInfo, GpuError> {
1056        Ok(GpuDeviceInfo::new(true, "WebGPU Device"))
1057    }
1058
1059    /// Get current memory usage if the backend exposes it.
1060    ///
1061    /// Portable WebGPU does not expose allocator usage through `wgpu`, so this
1062    /// returns `0` rather than a fabricated placeholder value.
1063    pub async fn memory_usage(&self) -> Result<u64, GpuError> {
1064        Ok(0)
1065    }
1066
1067    /// Compute a Fisher Information Matrix CPU baseline.
1068    ///
1069    /// Interprets `parameters` as coordinates of a probability-simplex style
1070    /// point and delegates to `amari-info-geom::DuallyFlatManifold`'s Fisher
1071    /// metric implementation. Values must be finite and non-negative.
1072    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    /// Batch compute KL-style Bregman divergences on the CPU correctness path.
1105    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                // Simple KL divergence implementation
1126                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    // Private implementation methods
1142
1143    /// GPU tensor batch computation implementation
1144    ///
1145    /// This method contains the full WebGPU implementation for GPU-accelerated
1146    /// tensor computation using WGSL compute shaders. Currently not used in the
1147    /// public API due to GPU access restrictions in test environments.
1148    ///
1149    /// In production environments with proper GPU access, this method would be
1150    /// called from `amari_chentsov_tensor_batch()` for large batch sizes.
1151    #[allow(dead_code)] // Currently unused due to CPU fallback
1152    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        // Create input buffers
1161        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        // Create GPU buffers
1195        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, // f32 results
1222            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        // Create bind group
1234        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        // Dispatch compute shader
1259        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); // 64 threads per workgroup
1273            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        // Read back results
1287        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        // Placeholder - would implement Fisher matrix computation shader
1329        Self::create_tensor_pipeline(device)
1330    }
1331
1332    fn create_divergence_pipeline(
1333        device: &wgpu::Device,
1334    ) -> Result<wgpu::ComputePipeline, GpuError> {
1335        // Placeholder - would implement Bregman divergence computation shader
1336        Self::create_tensor_pipeline(device)
1337    }
1338}
1339
1340/// GPU device information for edge computing
1341#[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    /// Human-readable device/backend description.
1368    pub fn description(&self) -> &str {
1369        &self.description
1370    }
1371}
1372
1373/// Fisher Information Matrix returned by `GpuInfoGeometry`.
1374#[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    /// Borrow the matrix rows.
1385    pub fn matrix(&self) -> &[Vec<f64>] {
1386        &self.matrix
1387    }
1388
1389    /// Matrix dimension, assuming the validated square matrices produced by this crate.
1390    pub fn dimension(&self) -> usize {
1391        self.matrix.len()
1392    }
1393
1394    pub async fn eigenvalues(&self) -> Result<Vec<f64>, GpuError> {
1395        // Simplified eigenvalue computation
1396        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
1406/// WGSL compute shader for batch Amari-Chentsov tensor computation
1407const 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        // Test boundary conditions around the threshold
1452        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        // Test all error variants can be created and display correctly
1489        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            // Ensure Display trait works
1497            let _ = err.to_string();
1498            // Ensure Debug trait works
1499            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        // Test AdaptiveCompute which has CPU fallback
1507        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        // Single product - always uses CPU, returns Multivector directly
1513        let result = adaptive.geometric_product(&e1, &e2).await;
1514        // e1 * e2 = e12 (bivector)
1515        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        // Test AdaptiveCompute with small batch (should use CPU)
1522        // Skip if GPU initialization fails (expected in CI)
1523        let adaptive = AdaptiveCompute::new::<3, 0, 0>().await;
1524
1525        // Create flat coefficient arrays (8 elements per Cl(3,0,0) multivector)
1526        let e1_coeffs = vec![0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]; // e1
1527        let e2_coeffs = vec![0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]; // e2
1528
1529        // 2 multivectors in batch
1530        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        // 2 multivectors * 8 coefficients = 16
1540        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        // supports_webgpu returns is_gpu
1549        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        // Skip GPU tests in CI environments where GPU is not available
1565        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        // This test will fail if no GPU is available, which is expected in CI
1574        match GpuInfoGeometry::new().await {
1575            Ok(_) => {
1576                // GPU available - test basic functionality
1577                println!("GPU initialization successful");
1578            }
1579            Err(GpuError::InitializationError(_)) => {
1580                // No GPU available - this is fine
1581                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        // Test that CayleyEntry can be created and is Pod-compatible
1619        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        // Test bytemuck cast works (verifies Pod + Zeroable traits)
1627        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); // 2 entries * 8 bytes each (f32 + u32)
1636    }
1637
1638    #[test]
1639    fn test_cayley_entry_zero() {
1640        // Test that CayleyEntry implements Zeroable
1641        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        // Verify shader constant is properly defined
1649        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        // Verify tensor shader constant is properly defined
1658        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        // Test error messages contain the original message
1668        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        // Test with large batch sizes
1682        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        // Test values near the threshold
1690        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        // is_initialized always returns true
1701        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        // Test that small batches use CPU (doesn't require GPU)
1710        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        // e1 * e2 = e12 (bivector) - verify a non-zero coefficient exists
1722        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        // Test CPU fallback with multiple multivectors
1729        let adaptive = AdaptiveCompute { gpu: None };
1730
1731        let mut a_batch = Vec::new();
1732        let mut b_batch = Vec::new();
1733
1734        // 5 multivectors
1735        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); // 5 * 8
1743    }
1744
1745    #[tokio::test]
1746    async fn test_adaptive_compute_geometric_product_cpu() {
1747        // Test single geometric product (always uses CPU)
1748        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        // Test basis vector products
1760        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        // e1 * e1 = 1 in Cl(3,0,0)
1766        let result = adaptive.geometric_product(&e1, &e1_clone).await;
1767        assert!((result.scalar_part() - 1.0).abs() < 1e-10);
1768    }
1769}