use std::collections::HashMap;
use std::sync::{atomic::{AtomicU64, Ordering}, Mutex};
pub type BufferId = u64;
pub type ShaderId = u64;
pub type PipelineId = u64;
pub type TextureId = u64;
pub enum BufferUsage { Compute, Vertex, Index, Uniform, Readback }
pub enum ShaderStage { Compute, Vertex, Fragment }
pub enum NumericDomain { Fp32, Fp64, FixedQ32, Goldilocks }
pub struct Binding { pub buffer: BufferId, pub offset: usize, pub index: u32 }
pub struct PipelineShaders { pub vertex: Option<ShaderId>, pub fragment: Option<ShaderId>, pub compute: Option<ShaderId> }
pub struct PipelineLayout { pub bind_groups: u32 }
pub trait RenderBackend: Send + Sync {
fn buffer_create(&self, size: usize, usage: BufferUsage) -> BufferId;
fn buffer_write(&self, id: BufferId, data: &[u8], offset: usize);
fn buffer_read(&self, id: BufferId, offset: usize, size: usize) -> Vec<u8>;
fn shader_compile(&self, source: &str, stage: ShaderStage) -> ShaderId;
fn compute_dispatch(&self, shader: ShaderId, bindings: &[Binding], workgroups: [u32; 3]);
fn pipeline_create(&self, shaders: &PipelineShaders, layout: &PipelineLayout) -> PipelineId;
fn draw_indirect(&self, pipeline: PipelineId, bindings: &[Binding], indirect_buffer: BufferId, count: u32);
fn surface_acquire(&self) -> TextureId;
fn surface_present(&self, texture: TextureId);
fn roll(&self, tensor: BufferId, shift: i32, axis: u32) -> BufferId;
fn shifted_inner(&self, h: BufferId, c: BufferId, shifts: &[i32]) -> BufferId;
fn shifted_wedge(&self, h: BufferId, c: BufferId, shifts: &[i32]) -> BufferId;
fn clifford_block(&self, h: BufferId, c: BufferId, shifts: &[i32], gate: BufferId) -> BufferId;
fn deterministic_mode(&self) -> bool;
fn numeric_domain(&self) -> NumericDomain;
}
pub struct CpuReferenceBackend {
next_id: AtomicU64,
buffers: Mutex<HashMap<BufferId, Vec<u8>>>,
}
impl CpuReferenceBackend {
pub fn new() -> Self {
Self {
next_id: AtomicU64::new(1),
buffers: Mutex::new(HashMap::new()),
}
}
fn alloc_id(&self) -> u64 { self.next_id.fetch_add(1, Ordering::Relaxed) }
pub fn render_epoch(&self, csr: &crate::graph::Csr) -> crate::epoch::EpochState {
crate::epoch::cpu_reference_epoch(csr)
}
pub fn verify_topology(&self, reference: &crate::epoch::EpochState, other: &crate::epoch::EpochState) -> bool {
reference.cluster_ids == other.cluster_ids
}
pub fn verify_positions(&self, reference: &crate::epoch::EpochState, other: &crate::epoch::EpochState, r_scene: f32) -> (bool, f32) {
let epsilon = 1e-3 * r_scene;
let n = reference.positions.len() / 3;
if other.positions.len() / 3 != n { return (false, f32::INFINITY); }
let max_dev = (0..n).map(|i| {
let b = i * 3;
let dx = reference.positions[b] - other.positions[b];
let dy = reference.positions[b+1] - other.positions[b+1];
let dz = reference.positions[b+2] - other.positions[b+2];
(dx*dx + dy*dy + dz*dz).sqrt()
}).fold(0.0f32, f32::max);
(max_dev <= epsilon, max_dev)
}
}
impl Default for CpuReferenceBackend {
fn default() -> Self { Self::new() }
}
impl RenderBackend for CpuReferenceBackend {
fn buffer_create(&self, size: usize, _usage: BufferUsage) -> BufferId {
let id = self.alloc_id();
self.buffers.lock().unwrap().insert(id, vec![0u8; size]);
id
}
fn buffer_write(&self, id: BufferId, data: &[u8], offset: usize) {
if let Some(buf) = self.buffers.lock().unwrap().get_mut(&id) {
let end = (offset + data.len()).min(buf.len());
if offset < buf.len() {
buf[offset..end].copy_from_slice(&data[..end - offset]);
}
}
}
fn buffer_read(&self, id: BufferId, offset: usize, size: usize) -> Vec<u8> {
self.buffers.lock().unwrap().get(&id).map(|buf| {
let start = offset.min(buf.len());
let end = (offset + size).min(buf.len());
buf[start..end].to_vec()
}).unwrap_or_default()
}
fn shader_compile(&self, _source: &str, _stage: ShaderStage) -> ShaderId { self.alloc_id() }
fn compute_dispatch(&self, _shader: ShaderId, _bindings: &[Binding], _workgroups: [u32; 3]) {
}
fn pipeline_create(&self, _shaders: &PipelineShaders, _layout: &PipelineLayout) -> PipelineId { self.alloc_id() }
fn draw_indirect(&self, _pipeline: PipelineId, _bindings: &[Binding], _indirect_buffer: BufferId, _count: u32) {}
fn surface_acquire(&self) -> TextureId { self.alloc_id() }
fn surface_present(&self, _texture: TextureId) {}
fn roll(&self, tensor: BufferId, shift: i32, axis: u32) -> BufferId {
let data = self.buffer_read(tensor, 0, usize::MAX);
let floats: Vec<f32> = data.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
let n = floats.len();
if n == 0 { return self.alloc_id(); }
let _ = axis; let s = shift.rem_euclid(n as i32) as usize;
let mut rolled = Vec::with_capacity(n);
rolled.extend_from_slice(&floats[n - s..]);
rolled.extend_from_slice(&floats[..n - s]);
let bytes: Vec<u8> = rolled.iter().flat_map(|f| f.to_le_bytes()).collect();
let id = self.buffer_create(bytes.len(), BufferUsage::Compute);
self.buffer_write(id, &bytes, 0);
id
}
fn shifted_inner(&self, _h: BufferId, _c: BufferId, _shifts: &[i32]) -> BufferId {
self.alloc_id() }
fn shifted_wedge(&self, _h: BufferId, _c: BufferId, _shifts: &[i32]) -> BufferId {
self.alloc_id() }
fn clifford_block(&self, _h: BufferId, _c: BufferId, _shifts: &[i32], _gate: BufferId) -> BufferId {
self.alloc_id() }
fn deterministic_mode(&self) -> bool { true }
fn numeric_domain(&self) -> NumericDomain { NumericDomain::Fp64 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cpu_backend_buffer_roundtrip() {
let b = CpuReferenceBackend::new();
let id = b.buffer_create(16, BufferUsage::Compute);
let data = vec![1u8, 2, 3, 4, 5, 6, 7, 8];
b.buffer_write(id, &data, 4);
let out = b.buffer_read(id, 4, 8);
assert_eq!(out, data);
}
#[test]
fn cpu_backend_roll_cyclic() {
let b = CpuReferenceBackend::new();
let floats = [1.0f32, 2.0, 3.0, 4.0];
let bytes: Vec<u8> = floats.iter().flat_map(|f| f.to_le_bytes()).collect();
let id = b.buffer_create(bytes.len(), BufferUsage::Compute);
b.buffer_write(id, &bytes, 0);
let out_id = b.roll(id, 1, 0);
let out_bytes = b.buffer_read(out_id, 0, 16);
let out_f: Vec<f32> = out_bytes.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
assert!((out_f[0] - 4.0).abs() < 1e-6, "roll[0]={}", out_f[0]);
assert!((out_f[1] - 1.0).abs() < 1e-6, "roll[1]={}", out_f[1]);
}
#[test]
fn cpu_backend_deterministic() {
let b = CpuReferenceBackend::new();
assert!(b.deterministic_mode());
assert!(matches!(b.numeric_domain(), NumericDomain::Fp64));
}
#[test]
fn verify_positions_self_is_zero_deviation() {
let b = CpuReferenceBackend::new();
let csr = crate::graph::Csr::empty();
let epoch = b.render_epoch(&csr);
let (ok, dev) = b.verify_positions(&epoch, &epoch, 1000.0);
assert!(ok, "self-comparison should pass");
assert_eq!(dev, 0.0);
}
#[test]
fn verify_topology_self_matches() {
let b = CpuReferenceBackend::new();
let csr = crate::graph::Csr::empty();
let epoch = b.render_epoch(&csr);
assert!(b.verify_topology(&epoch, &epoch));
}
}