pub mod adaptive;
#[cfg(feature = "automata")]
pub mod automata;
pub mod benchmarks;
#[cfg(feature = "calculus")]
pub mod calculus;
#[cfg(feature = "dual")]
#[allow(dead_code)]
mod dual;
#[cfg(feature = "enumerative")]
pub mod enumerative;
#[cfg(feature = "functional")]
pub mod functional;
#[cfg(feature = "fusion")]
#[allow(dead_code)]
mod fusion;
#[cfg(feature = "gf2")]
pub mod gf2;
#[cfg(feature = "holographic")]
pub mod holographic;
#[cfg(feature = "measure")]
pub mod measure;
pub mod multi_gpu;
pub mod network;
pub mod performance;
#[cfg(feature = "probabilistic")]
pub mod probabilistic;
pub mod relativistic;
pub mod shaders;
pub mod timeline;
#[cfg(feature = "topology")]
pub mod topology;
#[cfg(feature = "tropical")]
#[allow(dead_code)]
mod tropical;
pub mod unified;
pub mod verification;
pub use adaptive::{
AdaptiveVerificationError, AdaptiveVerificationLevel, AdaptiveVerifier, CpuFeatures,
GpuBackend, PlatformCapabilities, PlatformPerformanceProfile, VerificationPlatform,
WasmEnvironment,
};
use amari_core::Multivector;
use amari_info_geom::amari_chentsov_tensor;
#[cfg(feature = "automata")]
pub use automata::{
AutomataGpuConfig, AutomataGpuError, AutomataGpuOps, AutomataGpuResult, GpuCellData,
GpuEvolutionParams, GpuRuleConfig,
};
pub use benchmarks::{
AmariMultiGpuBenchmarks, BenchmarkConfig, BenchmarkResult, BenchmarkRunner,
BenchmarkSuiteResults, BenchmarkSummary, ScalingAnalysis,
};
use bytemuck::{Pod, Zeroable};
#[cfg(feature = "calculus")]
pub use calculus::GpuCalculus;
#[cfg(feature = "dual")]
pub use dual::{DualGpuError, DualGpuOps, DualGpuResult, DualOperation, GpuDualNumber};
#[cfg(feature = "enumerative")]
pub use enumerative::{
EnumerativeGpuConfig, EnumerativeGpuContext, EnumerativeGpuError, EnumerativeGpuOps,
EnumerativeGpuResult, GpuCSMData, GpuGromovWittenData, GpuIntersectionData,
GpuLittlewoodRichardsonData, GpuLocalizationData, GpuMatroidRankData, GpuMultiIntersectData,
GpuNamespaceData, GpuOperadData, GpuSchubertClass, GpuStabilityData, GpuTropicalSchubertData,
GpuWDVVData,
};
#[cfg(all(feature = "enumerative", feature = "gf2"))]
pub use enumerative::{
GpuFiniteFieldPointData, GpuKLPolynomialData, GpuRepresentabilityData,
GpuWeightDistributionData,
};
#[cfg(feature = "functional")]
pub use functional::{
AdaptiveFunctionalCompute, GpuFunctionalError, GpuFunctionalResult, GpuHilbertSpace,
GpuMatrixOperator, GpuSpectralDecomposition,
};
#[cfg(feature = "fusion")]
pub use fusion::{
FusionGpuError, FusionGpuResult, GpuHolographicTDC, GpuResonatorOutput, HolographicGpuOps,
};
#[cfg(feature = "gf2")]
pub use gf2::{
GF2GpuContext, GF2GpuError, GF2GpuOps, GF2GpuResult, GpuGF2CliffordPair, GpuGF2HammingPair,
GpuGF2MatVecData,
};
#[cfg(feature = "holographic")]
pub use holographic::{
GpuHolographic, GpuHolographicError, GpuHolographicMemory, GpuHolographicResult,
GpuOpticalField,
};
#[cfg(feature = "measure")]
pub use measure::{
GpuIntegrator, GpuMonteCarloIntegrator, GpuMultidimIntegrator, GpuParametricDensity,
GpuTropicalMeasure,
};
pub use multi_gpu::{
ComputeIntensity, DeviceCapabilities, DeviceId, DeviceWorkload, GpuArchitecture, GpuDevice,
IntelligentLoadBalancer, LoadBalancingStrategy, MultiGpuBarrier, PerformanceRecord,
PerformanceStats, SynchronizationManager, Workload, WorkloadCoordinator,
};
pub use network::{AdaptiveNetworkCompute, GpuGeometricNetwork, GpuNetworkError, GpuNetworkResult};
pub use performance::{
AdaptiveDispatchPolicy, CalibrationResult, GpuProfile, GpuProfiler, WorkgroupConfig,
WorkgroupOptimizer,
};
#[cfg(feature = "probabilistic")]
pub use probabilistic::{GpuProbabilistic, GpuProbabilisticError, GpuProbabilisticResult};
pub use relativistic::{
GpuRelativisticParticle, GpuRelativisticPhysics, GpuSpacetimeVector, GpuTrajectoryParams,
};
pub use shaders::{
ShaderLibrary, DUAL_SHADERS, FUSION_SHADERS, TOPOLOGY_SHADERS, TROPICAL_SHADERS,
};
use thiserror::Error;
#[cfg(test)]
#[allow(dead_code)]
pub(crate) static GPU_TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
pub use timeline::{
BottleneckAnalysis, DeviceUtilizationStats, GpuTimelineAnalyzer, MultiGpuPerformanceMonitor,
OptimizationRecommendation, PerformanceAnalysisReport, PerformanceBottleneck,
PerformanceSummary, RecommendationPriority, SynchronizationAnalysis, TimelineEvent,
UtilizationAnalysis,
};
#[cfg(feature = "topology")]
pub use topology::{
AdaptiveTopologyCompute, GpuCriticalPoint, GpuTopology, GpuTopologyError, GpuTopologyResult,
};
#[cfg(feature = "tropical")]
pub use tropical::{TropicalExecutionPath, TropicalGpuError, TropicalGpuOps, TropicalGpuResult};
pub use unified::{
BufferPoolStats, EnhancedGpuBufferPool, GpuAccelerated, GpuContext, GpuDispatcher,
GpuOperationParams, GpuParam, MultiGpuStats, PoolEntryStats, SharedGpuContext, UnifiedGpuError,
UnifiedGpuResult,
};
pub use verification::{
GpuBoundaryVerifier, GpuVerificationError, RelativisticVerifier, StatisticalGpuVerifier,
VerificationConfig, VerificationStrategy, VerifiedMultivector,
};
use wgpu::util::DeviceExt;
#[derive(Error, Debug)]
pub enum GpuError {
#[error("Failed to initialize GPU: {0}")]
InitializationError(String),
#[error("GPU buffer error: {0}")]
BufferError(String),
#[error("Shader compilation error: {0}")]
ShaderError(String),
}
pub struct GpuCliffordAlgebra {
device: wgpu::Device,
queue: wgpu::Queue,
compute_pipeline: wgpu::ComputePipeline,
cayley_buffer: wgpu::Buffer,
#[allow(dead_code)]
dim: usize,
basis_count: usize,
}
impl GpuCliffordAlgebra {
pub async fn new<const P: usize, const Q: usize, const R: usize>() -> Result<Self, GpuError> {
let instance = wgpu::Instance::default();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
})
.await
.ok_or_else(|| GpuError::InitializationError("No GPU adapter found".to_string()))?;
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("Amari GPU Device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
},
None,
)
.await
.map_err(|e| GpuError::InitializationError(e.to_string()))?;
let dim = P + Q + R;
let basis_count = 1 << dim;
let cayley_table = Self::generate_cayley_table::<P, Q, R>();
let cayley_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Cayley Table"),
contents: bytemuck::cast_slice(&cayley_table),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
let shader_source = GEOMETRIC_PRODUCT_SHADER.replace(
"const BASIS_COUNT: u32 = 8u; // For 3D Clifford algebra",
&format!("const BASIS_COUNT: u32 = {basis_count}u;"),
);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Geometric Product Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Compute Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Compute Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Geometric Product Pipeline"),
layout: Some(&pipeline_layout),
module: &shader,
entry_point: "main",
});
Ok(Self {
device,
queue,
compute_pipeline,
cayley_buffer,
dim,
basis_count,
})
}
fn generate_cayley_table<const P: usize, const Q: usize, const R: usize>() -> Vec<CayleyEntry> {
use amari_core::cayley::CayleyTable;
let table = CayleyTable::<P, Q, R>::get();
let basis_count = 1 << (P + Q + R);
let mut flat_table = Vec::with_capacity(basis_count * basis_count);
for i in 0..basis_count {
for j in 0..basis_count {
let (sign, index) = table.get_product(i, j);
flat_table.push(CayleyEntry {
sign: sign as f32,
index: index as u32,
});
}
}
flat_table
}
pub async fn batch_geometric_product(
&self,
a_batch: &[f64],
b_batch: &[f64],
) -> Result<Vec<f64>, GpuError> {
let batch_size = self.validate_flat_batches(a_batch, b_batch)?;
if batch_size == 0 {
return Ok(Vec::new());
}
let a_f32: Vec<f32> = a_batch.iter().map(|&x| x as f32).collect();
let b_f32: Vec<f32> = b_batch.iter().map(|&x| x as f32).collect();
let a_buffer = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("A Buffer"),
contents: bytemuck::cast_slice(&a_f32),
usage: wgpu::BufferUsages::STORAGE,
});
let b_buffer = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("B Buffer"),
contents: bytemuck::cast_slice(&b_f32),
usage: wgpu::BufferUsages::STORAGE,
});
let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Output Buffer"),
size: (a_batch.len() * std::mem::size_of::<f32>()) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let staging_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Staging Buffer"),
size: (a_batch.len() * std::mem::size_of::<f32>()) as u64,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group_layout = self.compute_pipeline.get_bind_group_layout(0);
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Compute Bind Group"),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.cayley_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: a_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: b_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: output_buffer.as_entire_binding(),
},
],
});
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Compute Encoder"),
});
{
let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Compute Pass"),
timestamp_writes: None,
});
compute_pass.set_pipeline(&self.compute_pipeline);
compute_pass.set_bind_group(0, &bind_group, &[]);
compute_pass.dispatch_workgroups(batch_size as u32, 1, 1);
}
encoder.copy_buffer_to_buffer(
&output_buffer,
0,
&staging_buffer,
0,
(a_batch.len() * std::mem::size_of::<f32>()) as u64,
);
self.queue.submit(Some(encoder.finish()));
let buffer_slice = staging_buffer.slice(..);
let (sender, receiver) = futures::channel::oneshot::channel();
buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
let _ = sender.send(result);
});
self.device.poll(wgpu::Maintain::Wait);
receiver
.await
.map_err(|_| GpuError::BufferError("Failed to receive buffer map result".to_string()))?
.map_err(|e| GpuError::BufferError(e.to_string()))?;
let data = buffer_slice.get_mapped_range();
let result_f32: &[f32] = bytemuck::cast_slice(&data);
let result: Vec<f64> = result_f32.iter().map(|&x| x as f64).collect();
drop(data);
staging_buffer.unmap();
Ok(result)
}
pub fn should_use_gpu(operation_count: usize) -> bool {
operation_count >= 100
}
pub fn basis_count(&self) -> usize {
self.basis_count
}
pub fn dimension(&self) -> usize {
self.dim
}
fn validate_flat_batches(&self, a_batch: &[f64], b_batch: &[f64]) -> Result<usize, GpuError> {
if a_batch.len() != b_batch.len() {
return Err(GpuError::BufferError(
"input batches must have the same coefficient count".to_string(),
));
}
if !a_batch.len().is_multiple_of(self.basis_count) {
return Err(GpuError::BufferError(format!(
"coefficient count {} is not a multiple of basis_count {}",
a_batch.len(),
self.basis_count
)));
}
for (name, batch) in [("a", a_batch), ("b", b_batch)] {
if let Some((index, _)) = batch
.iter()
.enumerate()
.find(|(_, value)| !value.is_finite())
{
return Err(GpuError::BufferError(format!(
"{name}_batch coefficient {index} is not finite"
)));
}
}
Ok(a_batch.len() / self.basis_count)
}
}
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct CayleyEntry {
sign: f32,
index: u32,
}
const GEOMETRIC_PRODUCT_SHADER: &str = r#"
struct CayleyEntry {
sign: f32,
index: u32,
}
@group(0) @binding(0)
var<storage, read> cayley_table: array<CayleyEntry>;
@group(0) @binding(1)
var<storage, read> a_batch: array<f32>;
@group(0) @binding(2)
var<storage, read> b_batch: array<f32>;
@group(0) @binding(3)
var<storage, read_write> output: array<f32>;
const BASIS_COUNT: u32 = 8u; // For 3D Clifford algebra
@compute @workgroup_size(1)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
let batch_idx = global_id.x;
let offset = batch_idx * BASIS_COUNT;
// Clear output
for (var k = 0u; k < BASIS_COUNT; k = k + 1u) {
output[offset + k] = 0.0;
}
// Compute geometric product
for (var i = 0u; i < BASIS_COUNT; i = i + 1u) {
let a_coeff = a_batch[offset + i];
if (abs(a_coeff) < 1e-14) {
continue;
}
for (var j = 0u; j < BASIS_COUNT; j = j + 1u) {
let b_coeff = b_batch[offset + j];
if (abs(b_coeff) < 1e-14) {
continue;
}
let table_idx = i * BASIS_COUNT + j;
let entry = cayley_table[table_idx];
output[offset + entry.index] += entry.sign * a_coeff * b_coeff;
}
}
}
"#;
pub struct AdaptiveCompute {
gpu: Option<GpuCliffordAlgebra>,
}
impl AdaptiveCompute {
pub async fn new<const P: usize, const Q: usize, const R: usize>() -> Self {
let gpu = GpuCliffordAlgebra::new::<P, Q, R>().await.ok();
Self { gpu }
}
pub async fn geometric_product<const P: usize, const Q: usize, const R: usize>(
&self,
a: &Multivector<P, Q, R>,
b: &Multivector<P, Q, R>,
) -> Multivector<P, Q, R> {
a.geometric_product(b)
}
pub async fn batch_geometric_product(
&self,
a_batch: &[f64],
b_batch: &[f64],
) -> Result<Vec<f64>, GpuError> {
let batch_size = validate_cl3_flat_batches(a_batch, b_batch)?;
if batch_size == 0 {
return Ok(Vec::new());
}
if let Some(gpu) = &self.gpu {
if GpuCliffordAlgebra::should_use_gpu(batch_size) {
return gpu.batch_geometric_product(a_batch, b_batch).await;
}
}
let mut result = Vec::with_capacity(a_batch.len());
for i in 0..batch_size {
let start = i * 8;
let end = start + 8;
let a = Multivector::<3, 0, 0>::from_coefficients(a_batch[start..end].to_vec());
let b = Multivector::<3, 0, 0>::from_coefficients(b_batch[start..end].to_vec());
let product = a.geometric_product(&b);
for j in 0..8 {
result.push(product.get(j));
}
}
Ok(result)
}
}
fn validate_probability_like_vector(name: &str, values: &[f64]) -> Result<(), GpuError> {
for (index, value) in values.iter().enumerate() {
if !value.is_finite() {
return Err(GpuError::BufferError(format!(
"{name}[{index}] is not finite"
)));
}
if *value < 0.0 {
return Err(GpuError::BufferError(format!(
"{name}[{index}] is negative"
)));
}
}
Ok(())
}
fn validate_kl_pair(index: usize, p: &[f64], q: &[f64]) -> Result<(), GpuError> {
if p.len() != q.len() {
return Err(GpuError::BufferError(format!(
"divergence pair {index} length mismatch: p={}, q={}",
p.len(),
q.len()
)));
}
validate_probability_like_vector("p", p)?;
validate_probability_like_vector("q", q)?;
for (coord, (pi, qi)) in p.iter().zip(q.iter()).enumerate() {
if *pi > 0.0 && *qi <= 0.0 {
return Err(GpuError::BufferError(format!(
"divergence pair {index} has q[{coord}] <= 0 while p[{coord}] > 0"
)));
}
}
Ok(())
}
fn validate_cl3_flat_batches(a_batch: &[f64], b_batch: &[f64]) -> Result<usize, GpuError> {
const CL3_BASIS_COUNT: usize = 8;
if a_batch.len() != b_batch.len() {
return Err(GpuError::BufferError(
"input batches must have the same coefficient count".to_string(),
));
}
if !a_batch.len().is_multiple_of(CL3_BASIS_COUNT) {
return Err(GpuError::BufferError(format!(
"coefficient count {} is not a multiple of 8 for Cl(3,0,0)",
a_batch.len()
)));
}
for (name, batch) in [("a", a_batch), ("b", b_batch)] {
if let Some((index, _)) = batch
.iter()
.enumerate()
.find(|(_, value)| !value.is_finite())
{
return Err(GpuError::BufferError(format!(
"{name}_batch coefficient {index} is not finite"
)));
}
}
Ok(a_batch.len() / CL3_BASIS_COUNT)
}
pub struct GpuInfoGeometry {
device: wgpu::Device,
queue: wgpu::Queue,
tensor_pipeline: wgpu::ComputePipeline,
#[allow(dead_code)]
fisher_pipeline: wgpu::ComputePipeline,
#[allow(dead_code)]
divergence_pipeline: wgpu::ComputePipeline,
}
impl GpuInfoGeometry {
pub async fn new() -> Result<Self, GpuError> {
let instance = wgpu::Instance::default();
let adapter = if let Some(adapter) = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
})
.await
{
adapter
} else if let Some(adapter) = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
compatible_surface: None,
force_fallback_adapter: false,
})
.await
{
adapter
} else if let Some(adapter) = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::None,
compatible_surface: None,
force_fallback_adapter: true,
})
.await
{
adapter
} else {
return Err(GpuError::InitializationError(
"No GPU adapter found".to_string(),
));
};
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("Amari GPU Info Geometry Device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
},
None,
)
.await
.map_err(|e| GpuError::InitializationError(format!("Device request failed: {}", e)))?;
let tensor_pipeline = Self::create_tensor_pipeline(&device)?;
let fisher_pipeline = Self::create_fisher_pipeline(&device)?;
let divergence_pipeline = Self::create_divergence_pipeline(&device)?;
Ok(Self {
device,
queue,
tensor_pipeline,
fisher_pipeline,
divergence_pipeline,
})
}
pub async fn new_with_device_preference(device_type: &str) -> Result<Self, GpuError> {
let (power_preference, force_fallback) = match device_type {
"high-performance" => (wgpu::PowerPreference::HighPerformance, false),
"low-power" => (wgpu::PowerPreference::LowPower, false),
"fallback" => (wgpu::PowerPreference::None, true),
_ => {
return Err(GpuError::InitializationError(
"Invalid device type".to_string(),
))
}
};
let instance = wgpu::Instance::default();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference,
compatible_surface: None,
force_fallback_adapter: force_fallback,
})
.await
.ok_or_else(|| {
GpuError::InitializationError("No suitable adapter found".to_string())
})?;
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("Amari GPU Info Geometry Device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
},
None,
)
.await
.map_err(|e| GpuError::InitializationError(format!("Device request failed: {}", e)))?;
let tensor_pipeline = Self::create_tensor_pipeline(&device)?;
let fisher_pipeline = Self::create_fisher_pipeline(&device)?;
let divergence_pipeline = Self::create_divergence_pipeline(&device)?;
Ok(Self {
device,
queue,
tensor_pipeline,
fisher_pipeline,
divergence_pipeline,
})
}
pub async fn amari_chentsov_tensor(
&self,
x: &Multivector<3, 0, 0>,
y: &Multivector<3, 0, 0>,
z: &Multivector<3, 0, 0>,
) -> Result<f64, GpuError> {
Ok(amari_chentsov_tensor(x, y, z))
}
pub async fn amari_chentsov_tensor_batch(
&self,
x_batch: &[Multivector<3, 0, 0>],
y_batch: &[Multivector<3, 0, 0>],
z_batch: &[Multivector<3, 0, 0>],
) -> Result<Vec<f64>, GpuError> {
if x_batch.len() != y_batch.len() || x_batch.len() != z_batch.len() {
return Err(GpuError::BufferError(format!(
"batch length mismatch: x={}, y={}, z={}",
x_batch.len(),
y_batch.len(),
z_batch.len()
)));
}
if x_batch.is_empty() {
return Ok(Vec::new());
}
let results = x_batch
.iter()
.zip(y_batch.iter())
.zip(z_batch.iter())
.map(|((x, y), z)| amari_chentsov_tensor(x, y, z))
.collect();
Ok(results)
}
pub async fn amari_chentsov_tensor_from_typed_arrays(
&self,
flat_data: &[f64],
batch_size: usize,
) -> Result<Vec<f64>, GpuError> {
let expected = batch_size.checked_mul(9).ok_or_else(|| {
GpuError::BufferError("batch size overflows flat data shape".to_string())
})?;
if flat_data.len() != expected {
return Err(GpuError::BufferError(format!(
"invalid flat data size: expected {expected}, got {}",
flat_data.len()
)));
}
if let Some((index, _)) = flat_data
.iter()
.enumerate()
.find(|(_, value)| !value.is_finite())
{
return Err(GpuError::BufferError(format!(
"flat tensor coefficient {index} is not finite"
)));
}
let mut x_batch = Vec::with_capacity(batch_size);
let mut y_batch = Vec::with_capacity(batch_size);
let mut z_batch = Vec::with_capacity(batch_size);
for i in 0..batch_size {
let base = i * 9;
let mut x = Multivector::zero();
let mut y = Multivector::zero();
let mut z = Multivector::zero();
x.set_vector_component(0, flat_data[base]);
x.set_vector_component(1, flat_data[base + 1]);
x.set_vector_component(2, flat_data[base + 2]);
y.set_vector_component(0, flat_data[base + 3]);
y.set_vector_component(1, flat_data[base + 4]);
y.set_vector_component(2, flat_data[base + 5]);
z.set_vector_component(0, flat_data[base + 6]);
z.set_vector_component(1, flat_data[base + 7]);
z.set_vector_component(2, flat_data[base + 8]);
x_batch.push(x);
y_batch.push(y);
z_batch.push(z);
}
self.amari_chentsov_tensor_batch(&x_batch, &y_batch, &z_batch)
.await
}
pub async fn device_info(&self) -> Result<GpuDeviceInfo, GpuError> {
Ok(GpuDeviceInfo::new(true, "WebGPU Device"))
}
pub async fn memory_usage(&self) -> Result<u64, GpuError> {
Ok(0)
}
pub async fn fisher_information_matrix(
&self,
parameters: &[f64],
) -> Result<GpuFisherMatrix, GpuError> {
validate_probability_like_vector("parameters", parameters)?;
if parameters.is_empty() {
return Ok(GpuFisherMatrix::new(Vec::new()));
}
let matrix = parameters
.iter()
.enumerate()
.map(|(row, _)| {
parameters
.iter()
.enumerate()
.map(|(col, value)| {
if row == col {
if *value > 1e-12 {
1.0 / value
} else {
1e12
}
} else {
0.0
}
})
.collect()
})
.collect();
Ok(GpuFisherMatrix::new(matrix))
}
pub async fn bregman_divergence_batch(
&self,
p_batch: &[Vec<f64>],
q_batch: &[Vec<f64>],
) -> Result<Vec<f64>, GpuError> {
if p_batch.len() != q_batch.len() {
return Err(GpuError::BufferError(format!(
"batch length mismatch: p={}, q={}",
p_batch.len(),
q_batch.len()
)));
}
for (index, (p, q)) in p_batch.iter().zip(q_batch.iter()).enumerate() {
validate_kl_pair(index, p, q)?;
}
let results = p_batch
.iter()
.zip(q_batch.iter())
.map(|(p, q)| {
p.iter()
.zip(q.iter())
.map(|(pi, qi)| {
if *pi > 0.0 && *qi > 0.0 {
pi * (pi / qi).ln()
} else {
0.0
}
})
.sum()
})
.collect();
Ok(results)
}
#[allow(dead_code)] async fn compute_tensor_batch_gpu(
&self,
x_batch: &[Multivector<3, 0, 0>],
y_batch: &[Multivector<3, 0, 0>],
z_batch: &[Multivector<3, 0, 0>],
) -> Result<Vec<f64>, GpuError> {
let batch_size = x_batch.len();
let x_data: Vec<f32> = x_batch
.iter()
.flat_map(|mv| {
vec![
mv.vector_component(0) as f32,
mv.vector_component(1) as f32,
mv.vector_component(2) as f32,
]
})
.collect();
let y_data: Vec<f32> = y_batch
.iter()
.flat_map(|mv| {
vec![
mv.vector_component(0) as f32,
mv.vector_component(1) as f32,
mv.vector_component(2) as f32,
]
})
.collect();
let z_data: Vec<f32> = z_batch
.iter()
.flat_map(|mv| {
vec![
mv.vector_component(0) as f32,
mv.vector_component(1) as f32,
mv.vector_component(2) as f32,
]
})
.collect();
let x_buffer = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("X Batch Buffer"),
contents: bytemuck::cast_slice(&x_data),
usage: wgpu::BufferUsages::STORAGE,
});
let y_buffer = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Y Batch Buffer"),
contents: bytemuck::cast_slice(&y_data),
usage: wgpu::BufferUsages::STORAGE,
});
let z_buffer = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Z Batch Buffer"),
contents: bytemuck::cast_slice(&z_data),
usage: wgpu::BufferUsages::STORAGE,
});
let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Output Buffer"),
size: (batch_size * 4) as u64, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let staging_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Staging Buffer"),
size: (batch_size * 4) as u64,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group_layout = self.tensor_pipeline.get_bind_group_layout(0);
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Tensor Compute Bind Group"),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: x_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: y_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: z_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: output_buffer.as_entire_binding(),
},
],
});
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Tensor Compute Encoder"),
});
{
let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Tensor Compute Pass"),
timestamp_writes: None,
});
compute_pass.set_pipeline(&self.tensor_pipeline);
compute_pass.set_bind_group(0, &bind_group, &[]);
let workgroup_count = batch_size.div_ceil(64); compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
}
encoder.copy_buffer_to_buffer(
&output_buffer,
0,
&staging_buffer,
0,
(batch_size * 4) as u64,
);
self.queue.submit(std::iter::once(encoder.finish()));
let buffer_slice = staging_buffer.slice(..);
let (sender, receiver) = futures::channel::oneshot::channel();
buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
let _ = sender.send(result);
});
self.device.poll(wgpu::Maintain::Wait);
receiver
.await
.map_err(|_| GpuError::BufferError("Failed to receive buffer map result".to_string()))?
.map_err(|e| GpuError::BufferError(format!("Buffer mapping failed: {:?}", e)))?;
let data = buffer_slice.get_mapped_range();
let result_f32: &[f32] = bytemuck::cast_slice(&data);
let results: Vec<f64> = result_f32.iter().map(|&x| x as f64).collect();
drop(data);
staging_buffer.unmap();
Ok(results)
}
fn create_tensor_pipeline(device: &wgpu::Device) -> Result<wgpu::ComputePipeline, GpuError> {
let shader_source = TENSOR_COMPUTE_SHADER;
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Tensor Compute Shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(shader_source)),
});
let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Tensor Compute Pipeline"),
layout: None,
module: &shader,
entry_point: "main",
});
Ok(compute_pipeline)
}
fn create_fisher_pipeline(device: &wgpu::Device) -> Result<wgpu::ComputePipeline, GpuError> {
Self::create_tensor_pipeline(device)
}
fn create_divergence_pipeline(
device: &wgpu::Device,
) -> Result<wgpu::ComputePipeline, GpuError> {
Self::create_tensor_pipeline(device)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GpuDeviceInfo {
is_gpu: bool,
description: String,
}
impl GpuDeviceInfo {
fn new(is_gpu: bool, description: &str) -> Self {
Self {
is_gpu,
description: description.to_string(),
}
}
pub fn is_gpu(&self) -> bool {
self.is_gpu
}
pub fn supports_webgpu(&self) -> bool {
self.is_gpu
}
pub fn is_initialized(&self) -> bool {
true
}
pub fn description(&self) -> &str {
&self.description
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct GpuFisherMatrix {
matrix: Vec<Vec<f64>>,
}
impl GpuFisherMatrix {
fn new(matrix: Vec<Vec<f64>>) -> Self {
Self { matrix }
}
pub fn matrix(&self) -> &[Vec<f64>] {
&self.matrix
}
pub fn dimension(&self) -> usize {
self.matrix.len()
}
pub async fn eigenvalues(&self) -> Result<Vec<f64>, GpuError> {
let mut eigenvals = Vec::new();
for i in 0..self.matrix.len() {
if i < self.matrix[i].len() {
eigenvals.push(self.matrix[i][i]);
}
}
Ok(eigenvals)
}
}
const TENSOR_COMPUTE_SHADER: &str = r#"
@group(0) @binding(0)
var<storage, read> x_batch: array<vec3<f32>>;
@group(0) @binding(1)
var<storage, read> y_batch: array<vec3<f32>>;
@group(0) @binding(2)
var<storage, read> z_batch: array<vec3<f32>>;
@group(0) @binding(3)
var<storage, read_write> output: array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
let idx = global_id.x;
if (idx >= arrayLength(&x_batch)) {
return;
}
let x = x_batch[idx];
let y = y_batch[idx];
let z = z_batch[idx];
// Compute scalar triple product: x · (y × z)
let cross_yz = cross(y, z);
let scalar_triple = dot(x, cross_yz);
output[idx] = scalar_triple;
}
"#;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_use_gpu() {
assert!(!GpuCliffordAlgebra::should_use_gpu(10));
assert!(GpuCliffordAlgebra::should_use_gpu(1000));
}
#[test]
fn test_should_use_gpu_threshold() {
assert!(!GpuCliffordAlgebra::should_use_gpu(99));
assert!(GpuCliffordAlgebra::should_use_gpu(100));
assert!(GpuCliffordAlgebra::should_use_gpu(101));
}
#[test]
fn test_should_use_gpu_zero_batch() {
assert!(!GpuCliffordAlgebra::should_use_gpu(0));
}
#[test]
fn test_should_use_gpu_single_element() {
assert!(!GpuCliffordAlgebra::should_use_gpu(1));
}
#[test]
fn test_gpu_error_display() {
let init_err = GpuError::InitializationError("No GPU found".to_string());
assert!(init_err.to_string().contains("Failed to initialize GPU"));
let buffer_err = GpuError::BufferError("Buffer too small".to_string());
assert!(buffer_err.to_string().contains("buffer error"));
let shader_err = GpuError::ShaderError("Invalid syntax".to_string());
assert!(shader_err.to_string().contains("Shader compilation"));
}
#[test]
fn test_gpu_error_debug() {
let err = GpuError::InitializationError("test".to_string());
let debug_str = format!("{:?}", err);
assert!(debug_str.contains("InitializationError"));
}
#[test]
fn test_gpu_error_variants() {
let errors = vec![
GpuError::InitializationError("init failed".to_string()),
GpuError::BufferError("buffer failed".to_string()),
GpuError::ShaderError("shader failed".to_string()),
];
for err in errors {
let _ = err.to_string();
let _ = format!("{:?}", err);
}
}
#[tokio::test]
#[ignore = "GPU hardware required, may fail in CI/CD environments"]
async fn test_adaptive_compute_geometric_product() {
let adaptive = AdaptiveCompute::new::<3, 0, 0>().await;
let e1 = Multivector::<3, 0, 0>::basis_vector(0);
let e2 = Multivector::<3, 0, 0>::basis_vector(1);
let result = adaptive.geometric_product(&e1, &e2).await;
assert!(result.magnitude() > 0.0);
}
#[tokio::test]
#[ignore = "GPU hardware required, may fail in CI/CD environments"]
async fn test_adaptive_compute_batch_small() {
let adaptive = AdaptiveCompute::new::<3, 0, 0>().await;
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();
let mut b_batch = Vec::new();
a_batch.extend_from_slice(&e1_coeffs);
a_batch.extend_from_slice(&e1_coeffs);
b_batch.extend_from_slice(&e2_coeffs);
b_batch.extend_from_slice(&e2_coeffs);
let result = adaptive.batch_geometric_product(&a_batch, &b_batch).await;
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 16);
}
#[test]
fn test_gpu_device_info_methods() {
let info = GpuDeviceInfo::new(true, "Test GPU");
assert!(info.is_gpu());
assert!(info.supports_webgpu());
assert!(info.is_initialized());
}
#[test]
fn test_gpu_device_info_cpu() {
let info = GpuDeviceInfo::new(false, "CPU Fallback");
assert!(!info.is_gpu());
assert!(!info.supports_webgpu());
assert!(info.is_initialized());
}
#[tokio::test]
async fn test_gpu_info_geometry_creation() {
if std::env::var("CI").is_ok()
|| std::env::var("GITHUB_ACTIONS").is_ok()
|| std::env::var("DISPLAY").is_err()
{
println!("Skipping GPU test in CI environment");
return;
}
match GpuInfoGeometry::new().await {
Ok(_) => {
println!("GPU initialization successful");
}
Err(GpuError::InitializationError(_)) => {
println!("GPU initialization failed - no GPU available");
}
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[tokio::test]
async fn test_gpu_fisher_matrix_eigenvalues() {
let matrix = GpuFisherMatrix::new(vec![
vec![1.0, 0.0, 0.0],
vec![0.0, 2.0, 0.0],
vec![0.0, 0.0, 3.0],
]);
let eigenvalues = matrix.eigenvalues().await.unwrap();
assert_eq!(eigenvalues.len(), 3);
assert_eq!(eigenvalues[0], 1.0);
assert_eq!(eigenvalues[1], 2.0);
assert_eq!(eigenvalues[2], 3.0);
}
#[tokio::test]
async fn test_gpu_fisher_matrix_empty() {
let matrix = GpuFisherMatrix::new(vec![]);
let eigenvalues = matrix.eigenvalues().await.unwrap();
assert!(eigenvalues.is_empty());
}
#[tokio::test]
async fn test_gpu_fisher_matrix_single_element() {
let matrix = GpuFisherMatrix::new(vec![vec![5.0]]);
let eigenvalues = matrix.eigenvalues().await.unwrap();
assert_eq!(eigenvalues.len(), 1);
assert_eq!(eigenvalues[0], 5.0);
}
#[test]
fn test_cayley_entry_struct() {
let entry = CayleyEntry {
sign: 1.0,
index: 5,
};
assert_eq!(entry.sign, 1.0);
assert_eq!(entry.index, 5);
let entries = vec![
entry,
CayleyEntry {
sign: -1.0,
index: 3,
},
];
let bytes: &[u8] = bytemuck::cast_slice(&entries);
assert_eq!(bytes.len(), 16); }
#[test]
fn test_cayley_entry_zero() {
let zeroed: CayleyEntry = bytemuck::Zeroable::zeroed();
assert_eq!(zeroed.sign, 0.0);
assert_eq!(zeroed.index, 0);
}
#[test]
fn test_geometric_product_shader_not_empty() {
assert!(!GEOMETRIC_PRODUCT_SHADER.is_empty());
assert!(GEOMETRIC_PRODUCT_SHADER.contains("@compute"));
assert!(GEOMETRIC_PRODUCT_SHADER.contains("@workgroup_size"));
assert!(GEOMETRIC_PRODUCT_SHADER.contains("CayleyEntry"));
}
#[test]
fn test_tensor_compute_shader_not_empty() {
assert!(!TENSOR_COMPUTE_SHADER.is_empty());
assert!(TENSOR_COMPUTE_SHADER.contains("@compute"));
assert!(TENSOR_COMPUTE_SHADER.contains("@workgroup_size"));
assert!(TENSOR_COMPUTE_SHADER.contains("cross"));
assert!(TENSOR_COMPUTE_SHADER.contains("dot"));
}
#[test]
fn test_gpu_error_from_string() {
let msg = "Custom error message";
let err = GpuError::InitializationError(msg.to_string());
assert!(err.to_string().contains(msg));
let err = GpuError::BufferError(msg.to_string());
assert!(err.to_string().contains(msg));
let err = GpuError::ShaderError(msg.to_string());
assert!(err.to_string().contains(msg));
}
#[test]
fn test_should_use_gpu_large_batch() {
assert!(GpuCliffordAlgebra::should_use_gpu(1000));
assert!(GpuCliffordAlgebra::should_use_gpu(10000));
assert!(GpuCliffordAlgebra::should_use_gpu(100000));
}
#[test]
fn test_should_use_gpu_near_threshold() {
for i in 0..100 {
assert!(!GpuCliffordAlgebra::should_use_gpu(i));
}
for i in 100..200 {
assert!(GpuCliffordAlgebra::should_use_gpu(i));
}
}
#[test]
fn test_gpu_device_info_initialized_always_true() {
let gpu_info = GpuDeviceInfo::new(true, "GPU");
let cpu_info = GpuDeviceInfo::new(false, "CPU");
assert!(gpu_info.is_initialized());
assert!(cpu_info.is_initialized());
}
#[tokio::test]
async fn test_adaptive_compute_cpu_fallback_small_batch() {
let adaptive = AdaptiveCompute { gpu: None };
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 result = adaptive
.batch_geometric_product(&e1_coeffs, &e2_coeffs)
.await;
assert!(result.is_ok());
let coeffs = result.unwrap();
assert_eq!(coeffs.len(), 8);
let has_nonzero = coeffs.iter().any(|&c| c.abs() > 0.5);
assert!(has_nonzero, "Product should have non-zero coefficients");
}
#[tokio::test]
async fn test_adaptive_compute_cpu_fallback_multiple_elements() {
let adaptive = AdaptiveCompute { gpu: None };
let mut a_batch = Vec::new();
let mut b_batch = Vec::new();
for _ in 0..5 {
a_batch.extend_from_slice(&[0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
b_batch.extend_from_slice(&[0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
}
let result = adaptive.batch_geometric_product(&a_batch, &b_batch).await;
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 40); }
#[tokio::test]
async fn test_adaptive_compute_geometric_product_cpu() {
let adaptive = AdaptiveCompute { gpu: None };
let a = Multivector::<3, 0, 0>::scalar(2.0);
let b = Multivector::<3, 0, 0>::scalar(3.0);
let result = adaptive.geometric_product(&a, &b).await;
assert!((result.scalar_part() - 6.0).abs() < 1e-10);
}
#[tokio::test]
async fn test_adaptive_compute_geometric_product_basis_vectors() {
let adaptive = AdaptiveCompute { gpu: None };
let e1 = Multivector::<3, 0, 0>::basis_vector(0);
let e1_clone = Multivector::<3, 0, 0>::basis_vector(0);
let result = adaptive.geometric_product(&e1, &e1_clone).await;
assert!((result.scalar_part() - 1.0).abs() < 1e-10);
}
}