Files
wifi-densepose/crates/ruvector-graph-transformer-wasm/src/transformer.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

1441 lines
43 KiB
Rust

//! Self-contained graph transformer implementation for the WASM bindings.
//!
//! Provides proof-gated operations, sublinear attention, physics-informed
//! layers, biological learning, verified training, manifold distance,
//! temporal causal attention, and economic game-theoretic attention --
//! all without external crate dependencies beyond serde.
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
// ---------------------------------------------------------------------------
// Error
// ---------------------------------------------------------------------------
#[derive(Debug)]
pub enum GraphTransformerError {
DimensionMismatch { expected: u32, actual: u32 },
ProofFailed(String),
}
impl std::fmt::Display for GraphTransformerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DimensionMismatch { expected, actual } => {
write!(f, "dimension mismatch: expected {expected}, got {actual}")
}
Self::ProofFailed(msg) => write!(f, "proof verification failed: {msg}"),
}
}
}
impl std::error::Error for GraphTransformerError {}
pub type Result<T> = std::result::Result<T, GraphTransformerError>;
// ---------------------------------------------------------------------------
// Proof-gated types
// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProofGate {
pub id: u32,
pub dimension: u32,
pub verified: bool,
pub proof_term_id: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DimProofResult {
pub proof_id: u32,
pub expected: u32,
pub actual: u32,
pub verified: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attestation {
pub proof_id: u32,
pub proof_term_hash: [u8; 32],
pub environment_hash: [u8; 32],
pub timestamp_ns: u64,
pub verifier_version: u32,
pub reduction_steps: u32,
pub cache_hit_rate_bps: u16,
}
pub const ATTESTATION_SIZE: usize = 82;
impl Attestation {
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(ATTESTATION_SIZE);
buf.extend_from_slice(&self.proof_term_hash);
buf.extend_from_slice(&self.environment_hash);
buf.extend_from_slice(&self.timestamp_ns.to_le_bytes());
buf.extend_from_slice(&self.verifier_version.to_le_bytes());
buf.extend_from_slice(&self.reduction_steps.to_le_bytes());
buf.extend_from_slice(&self.cache_hit_rate_bps.to_le_bytes());
buf
}
pub fn from_bytes(data: &[u8]) -> std::result::Result<Self, &'static str> {
if data.len() < ATTESTATION_SIZE {
return Err("attestation data too short");
}
let mut proof_term_hash = [0u8; 32];
proof_term_hash.copy_from_slice(&data[0..32]);
let mut environment_hash = [0u8; 32];
environment_hash.copy_from_slice(&data[32..64]);
let timestamp_ns =
u64::from_le_bytes(data[64..72].try_into().map_err(|_| "bad timestamp")?);
let verifier_version =
u32::from_le_bytes(data[72..76].try_into().map_err(|_| "bad version")?);
let reduction_steps = u32::from_le_bytes(data[76..80].try_into().map_err(|_| "bad steps")?);
let cache_hit_rate_bps =
u16::from_le_bytes(data[80..82].try_into().map_err(|_| "bad rate")?);
Ok(Self {
proof_id: 0,
proof_term_hash,
environment_hash,
timestamp_ns,
verifier_version,
reduction_steps,
cache_hit_rate_bps,
})
}
fn verify(&self) -> bool {
self.verifier_version != 0 && self.proof_term_hash != [0u8; 32]
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PipelineStage {
pub name: String,
pub input_type_id: u32,
pub output_type_id: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComposedProof {
pub proof_id: u32,
pub input_type_id: u32,
pub output_type_id: u32,
pub stages_verified: u32,
pub chain_name: String,
}
// ---------------------------------------------------------------------------
// Serializable input/result types for new APIs
// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Edge {
pub src: u32,
pub tgt: u32,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Spike {
pub neuron: u32,
pub time: f64,
pub strength: f64,
}
// Result types for existing and new APIs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttentionResult {
pub scores: Vec<f64>,
pub top_k_indices: Vec<u32>,
pub sparsity_ratio: f64,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HamiltonianState {
pub positions: Vec<f64>,
pub momenta: Vec<f64>,
pub energy: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HamiltonianOutput {
pub positions: Vec<f64>,
pub momenta: Vec<f64>,
pub energy: f64,
pub energy_conserved: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnergyConservation {
pub conserved: bool,
pub delta: f64,
pub relative_error: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerifiedStepResult {
pub weights: Vec<f64>,
pub proof_id: u32,
pub loss_before: f64,
pub loss_after: f64,
pub gradient_norm: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpikingStepResult {
pub features: Vec<Vec<f64>>,
pub spikes: Vec<bool>,
pub weights: Vec<Vec<f64>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainingStepResult {
pub weights: Vec<f64>,
pub certificate_id: u32,
pub loss: f64,
pub loss_monotonic: bool,
pub lipschitz_satisfied: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManifoldOutput {
pub output: Vec<f64>,
pub curvatures: Vec<f64>,
pub distances: Vec<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrangerDag {
pub edges: Vec<GrangerEdge>,
pub num_nodes: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrangerEdge {
pub source: u32,
pub target: u32,
pub f_statistic: f64,
pub is_causal: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EquilibriumOutput {
pub allocations: Vec<f64>,
pub utilities: Vec<f64>,
pub nash_gap: f64,
pub converged: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TransformerStats {
pub proofs_constructed: u64,
pub proofs_verified: u64,
pub cache_hits: u64,
pub cache_misses: u64,
pub attention_ops: u64,
pub physics_ops: u64,
pub bio_ops: u64,
pub training_steps: u64,
}
// ---------------------------------------------------------------------------
// Core implementation
// ---------------------------------------------------------------------------
pub struct CoreGraphTransformer {
term_counter: u32,
proof_cache: HashMap<u64, u32>,
gates: HashMap<u32, ProofGate>,
stats: TransformerStats,
prev_loss: Option<f64>,
}
impl Default for CoreGraphTransformer {
fn default() -> Self {
Self::new()
}
}
impl CoreGraphTransformer {
pub fn new() -> Self {
Self {
term_counter: 0,
proof_cache: HashMap::with_capacity(256),
gates: HashMap::new(),
stats: TransformerStats::default(),
prev_loss: None,
}
}
fn alloc_term(&mut self) -> u32 {
let id = self.term_counter;
self.term_counter = self.term_counter.wrapping_add(1);
self.stats.proofs_constructed += 1;
id
}
fn cache_key(a: u64, b: u64) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
h ^= a;
h = h.wrapping_mul(0x0100_0000_01b3);
h ^= b;
h = h.wrapping_mul(0x0100_0000_01b3);
h
}
pub fn version(&self) -> String {
env!("CARGO_PKG_VERSION").to_string()
}
// -- Proof-gated --
pub fn create_proof_gate(&mut self, dim: u32) -> ProofGate {
let id = self.alloc_term();
let gate = ProofGate {
id,
dimension: dim,
verified: false,
proof_term_id: None,
};
self.gates.insert(id, gate.clone());
gate
}
pub fn prove_dimension(&mut self, expected: u32, actual: u32) -> Result<DimProofResult> {
if expected != actual {
return Err(GraphTransformerError::DimensionMismatch { expected, actual });
}
let key = Self::cache_key(u64::from(expected), u64::from(actual));
let proof_id = if let Some(&cached) = self.proof_cache.get(&key) {
self.stats.cache_hits += 1;
cached
} else {
self.stats.cache_misses += 1;
let id = self.alloc_term();
self.proof_cache.insert(key, id);
id
};
self.stats.proofs_verified += 1;
Ok(DimProofResult {
proof_id,
expected,
actual,
verified: true,
})
}
pub fn create_attestation(&self, proof_id: u32) -> Attestation {
let mut proof_hash = [0u8; 32];
proof_hash[0..4].copy_from_slice(&proof_id.to_le_bytes());
proof_hash[4..8].copy_from_slice(&self.term_counter.to_le_bytes());
let mut env_hash = [0u8; 32];
env_hash[0..4].copy_from_slice(&(self.gates.len() as u32).to_le_bytes());
let total = self.stats.cache_hits + self.stats.cache_misses;
let rate = if total > 0 {
((self.stats.cache_hits * 10000) / total) as u16
} else {
0
};
Attestation {
proof_id,
proof_term_hash: proof_hash,
environment_hash: env_hash,
timestamp_ns: 0, // No system time in WASM
verifier_version: 0x0002_0004,
reduction_steps: self.stats.proofs_verified as u32,
cache_hit_rate_bps: rate,
}
}
pub fn verify_attestation(&self, bytes: &[u8]) -> bool {
Attestation::from_bytes(bytes)
.map(|a| a.verify())
.unwrap_or(false)
}
pub fn compose_proofs(&mut self, stages: &[PipelineStage]) -> Result<ComposedProof> {
if stages.is_empty() {
return Err(GraphTransformerError::ProofFailed(
"empty pipeline chain".into(),
));
}
let mut current_output = stages[0].output_type_id;
let mut chain_name = stages[0].name.clone();
for stage in stages.iter().skip(1) {
if current_output != stage.input_type_id {
return Err(GraphTransformerError::ProofFailed(format!(
"pipeline type mismatch: type#{} != type#{}",
current_output, stage.input_type_id,
)));
}
chain_name = format!("{} >> {}", chain_name, stage.name);
current_output = stage.output_type_id;
self.alloc_term();
}
let proof_id = self.alloc_term();
self.stats.proofs_verified += stages.len() as u64;
Ok(ComposedProof {
proof_id,
input_type_id: stages[0].input_type_id,
output_type_id: current_output,
stages_verified: stages.len() as u32,
chain_name,
})
}
// -- Sublinear attention --
pub fn sublinear_attention(
&mut self,
query: &[f64],
edges: &[Vec<u32>],
dim: u32,
k: u32,
) -> Result<AttentionResult> {
if query.len() != dim as usize {
return Err(GraphTransformerError::DimensionMismatch {
expected: dim,
actual: query.len() as u32,
});
}
let n = edges.len();
let k = (k as usize).min(n);
let ppr = self.compute_ppr(0, edges, 0.15);
let mut indexed: Vec<(usize, f64)> = ppr.iter().copied().enumerate().collect();
indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let top_k: Vec<(usize, f64)> = indexed.into_iter().take(k).collect();
let q_norm = query.iter().map(|x| x * x).sum::<f64>().sqrt().max(1e-12);
let scores: Vec<f64> = top_k.iter().map(|(_, s)| s / q_norm).collect();
let max_s = scores.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let exps: Vec<f64> = scores.iter().map(|s| (s - max_s).exp()).collect();
let sum_exp: f64 = exps.iter().sum();
let normalized: Vec<f64> = exps.iter().map(|e| e / sum_exp).collect();
let indices: Vec<u32> = top_k.iter().map(|(i, _)| *i as u32).collect();
let sparsity = if n > 0 {
1.0 - (k as f64 / n as f64)
} else {
0.0
};
self.stats.attention_ops += 1;
Ok(AttentionResult {
scores: normalized,
top_k_indices: indices,
sparsity_ratio: sparsity,
})
}
pub fn ppr_scores(&mut self, source: u32, adjacency: &[Vec<u32>], alpha: f64) -> Vec<f64> {
self.compute_ppr(source as usize, adjacency, alpha)
}
fn compute_ppr(&self, source: usize, adjacency: &[Vec<u32>], alpha: f64) -> Vec<f64> {
let n = adjacency.len();
if n == 0 {
return vec![];
}
let src = source.min(n - 1);
let mut scores = vec![0.0f64; n];
scores[src] = 1.0;
for _ in 0..20 {
let mut next = vec![0.0f64; n];
for (node, neighbors) in adjacency.iter().enumerate() {
if neighbors.is_empty() {
next[node] += scores[node];
} else {
let share = scores[node] / neighbors.len() as f64;
for &nb in neighbors {
if (nb as usize) < n {
next[nb as usize] += share;
}
}
}
}
for i in 0..n {
scores[i] = alpha * (if i == src { 1.0 } else { 0.0 }) + (1.0 - alpha) * next[i];
}
}
scores
}
// -- Physics --
#[allow(dead_code)]
pub fn hamiltonian_step(
&mut self,
positions: &[f64],
momenta: &[f64],
dt: f64,
) -> Result<HamiltonianState> {
if positions.len() != momenta.len() {
return Err(GraphTransformerError::DimensionMismatch {
expected: positions.len() as u32,
actual: momenta.len() as u32,
});
}
let n = positions.len();
let mut new_p = vec![0.0; n];
let mut new_m = vec![0.0; n];
for i in 0..n {
new_m[i] = momenta[i] - 0.5 * dt * positions[i];
}
for i in 0..n {
new_p[i] = positions[i] + dt * new_m[i];
}
for i in 0..n {
new_m[i] -= 0.5 * dt * new_p[i];
}
let kinetic: f64 = new_m.iter().map(|p| 0.5 * p * p).sum();
let potential: f64 = new_p.iter().map(|q| 0.5 * q * q).sum();
self.stats.physics_ops += 1;
Ok(HamiltonianState {
positions: new_p,
momenta: new_m,
energy: kinetic + potential,
})
}
/// Graph-aware Hamiltonian step with edge interactions.
///
/// Uses leapfrog integration with a potential that includes both
/// harmonic self-potential and pairwise edge interactions.
pub fn hamiltonian_step_graph(
&mut self,
positions: &[f64],
momenta: &[f64],
edges: &[Edge],
dt: f64,
) -> Result<HamiltonianOutput> {
if positions.len() != momenta.len() {
return Err(GraphTransformerError::DimensionMismatch {
expected: positions.len() as u32,
actual: momenta.len() as u32,
});
}
let n = positions.len();
let energy_before = compute_energy(positions, momenta);
// Leapfrog integration with edge interaction forces
let mut q = positions.to_vec();
let mut p = momenta.to_vec();
let grad = compute_grad_with_edges(&q, edges, n);
for i in 0..n {
p[i] -= 0.5 * dt * grad[i];
}
for i in 0..n {
q[i] += dt * p[i];
}
let grad = compute_grad_with_edges(&q, edges, n);
for i in 0..n {
p[i] -= 0.5 * dt * grad[i];
}
let energy_after = compute_energy(&q, &p);
let delta = (energy_after - energy_before).abs();
let energy_conserved = delta < 0.01 * energy_before.abs().max(1e-8);
self.stats.physics_ops += 1;
Ok(HamiltonianOutput {
positions: q,
momenta: p,
energy: energy_after,
energy_conserved,
})
}
pub fn verify_energy_conservation(
&self,
before: f64,
after: f64,
tolerance: f64,
) -> EnergyConservation {
let delta = (after - before).abs();
let relative_error = if before.abs() > 1e-12 {
delta / before.abs()
} else {
delta
};
EnergyConservation {
conserved: relative_error < tolerance,
delta,
relative_error,
}
}
// -- Biological --
#[allow(dead_code)]
pub fn spiking_attention(
&mut self,
spikes: &[f64],
edges: &[Vec<u32>],
threshold: f64,
) -> Vec<f64> {
let n = spikes.len();
let mut output = vec![0.0f64; n];
for (i, &spike) in spikes.iter().enumerate() {
if spike > threshold {
if i < edges.len() {
let weight = spike - threshold;
for &nb in &edges[i] {
if (nb as usize) < n {
output[nb as usize] += weight;
}
}
}
output[i] += spike;
}
}
self.stats.bio_ops += 1;
output
}
/// Spiking step over 2D node features + adjacency matrix.
///
/// `features`: n x dim matrix, `adjacency`: flat n x n row-major.
/// Returns updated features, spike flags, and updated weights.
pub fn spiking_step(
&mut self,
features: &[Vec<f64>],
adjacency: &[f64],
threshold: f64,
) -> SpikingStepResult {
let n = features.len();
let dim = if n > 0 { features[0].len() } else { 0 };
// Compute membrane potential as mean of features
let potentials: Vec<f64> = features
.iter()
.map(|f| f.iter().sum::<f64>() / dim.max(1) as f64)
.collect();
// Determine spikes
let spikes: Vec<bool> = potentials.iter().map(|&v| v >= threshold).collect();
// Compute output features via spiking attention
let mut out_features = vec![vec![0.0; dim]; n];
for i in 0..n {
if spikes[i] {
for d in 0..dim {
out_features[i][d] = features[i][d] * threshold;
}
} else {
let attenuation = (potentials[i] / threshold).abs().min(1.0);
for d in 0..dim {
out_features[i][d] = features[i][d] * attenuation;
}
}
}
// Extract weights from adjacency and apply STDP-like update
let mut weights = vec![vec![0.0; n]; n];
for i in 0..n {
for j in 0..n {
let idx = i * n + j;
let w = if idx < adjacency.len() {
adjacency[idx]
} else {
0.0
};
let dw = if spikes[i] && spikes[j] {
0.01 // co-activation potentiation
} else if spikes[i] && !spikes[j] {
-0.005 // depression
} else {
0.0
};
weights[i][j] = (w + dw).clamp(-5.0, 5.0);
}
}
self.stats.bio_ops += 1;
SpikingStepResult {
features: out_features,
spikes,
weights,
}
}
pub fn hebbian_update(
&mut self,
pre: &[f64],
post: &[f64],
weights: &[f64],
lr: f64,
) -> Vec<f64> {
let n_pre = pre.len();
let n_post = post.len();
let expected_len = n_pre * n_post;
let mut result = if weights.len() == expected_len {
weights.to_vec()
} else {
vec![0.0; expected_len]
};
for i in 0..n_pre {
for j in 0..n_post {
result[i * n_post + j] += lr * pre[i] * post[j];
}
}
self.stats.bio_ops += 1;
result
}
// -- Verified training --
pub fn verified_step(
&mut self,
weights: &[f64],
gradients: &[f64],
lr: f64,
) -> Result<VerifiedStepResult> {
if weights.len() != gradients.len() {
return Err(GraphTransformerError::DimensionMismatch {
expected: weights.len() as u32,
actual: gradients.len() as u32,
});
}
let grad_norm: f64 = gradients.iter().map(|g| g * g).sum::<f64>().sqrt();
let loss_before: f64 = weights.iter().map(|w| w * w).sum::<f64>() * 0.5;
let new_weights: Vec<f64> = weights
.iter()
.zip(gradients.iter())
.map(|(w, g)| w - lr * g)
.collect();
let loss_after: f64 = new_weights.iter().map(|w| w * w).sum::<f64>() * 0.5;
let proof_id = self.alloc_term();
self.stats.proofs_verified += 1;
self.stats.training_steps += 1;
Ok(VerifiedStepResult {
weights: new_weights,
proof_id,
loss_before,
loss_after,
gradient_norm: grad_norm,
})
}
/// Verified training step with features, targets, and weight update.
///
/// Computes MSE loss, applies SGD, and produces a training certificate.
pub fn verified_training_step(
&mut self,
features: &[f64],
targets: &[f64],
weights: &[f64],
lr: f64,
) -> Result<TrainingStepResult> {
let dim = features.len().min(targets.len());
if dim == 0 {
return Err(GraphTransformerError::ProofFailed(
"empty features or targets".into(),
));
}
// Forward: simple linear transform
let mut outputs = vec![0.0; dim];
for i in 0..dim {
let w = if i < weights.len() { weights[i] } else { 0.0 };
outputs[i] = features[i] * w;
}
// MSE loss
let loss: f64 = outputs
.iter()
.zip(targets.iter())
.map(|(o, t)| (o - t).powi(2))
.sum::<f64>()
/ dim as f64;
// Gradients: d(MSE)/dw = 2/n * sum(output - target) * feature
let new_weights: Vec<f64> = (0..weights.len())
.map(|i| {
let grad = if i < dim {
2.0 * (outputs[i] - targets[i]) * features[i] / dim as f64
} else {
0.0
};
weights[i] - lr * grad
})
.collect();
let loss_monotonic = match self.prev_loss {
Some(prev) => loss <= prev + 1e-6,
None => true,
};
self.prev_loss = Some(loss);
let max_update: f64 = weights
.iter()
.zip(new_weights.iter())
.map(|(w, nw)| (nw - w).abs())
.fold(0.0, f64::max);
let lipschitz_satisfied = max_update <= 10.0;
let certificate_id = self.alloc_term();
self.stats.proofs_verified += 1;
self.stats.training_steps += 1;
Ok(TrainingStepResult {
weights: new_weights,
certificate_id,
loss,
loss_monotonic,
lipschitz_satisfied,
})
}
// -- Manifold --
pub fn product_manifold_distance(&self, a: &[f64], b: &[f64], curvatures: &[f64]) -> f64 {
if a.len() != b.len() || curvatures.is_empty() {
return 0.0;
}
let n = a.len();
let n_spaces = curvatures.len();
let chunk_size = (n + n_spaces - 1) / n_spaces;
let mut total_dist_sq = 0.0;
for (space_idx, &k) in curvatures.iter().enumerate() {
let start = space_idx * chunk_size;
let end = (start + chunk_size).min(n);
if start >= n {
break;
}
let mut dist_sq = 0.0;
for i in start..end {
let diff = a[i] - b[i];
dist_sq += diff * diff;
}
if k.abs() < 1e-12 {
total_dist_sq += dist_sq;
} else if k > 0.0 {
let d = dist_sq.sqrt();
total_dist_sq += (d * k.sqrt()).min(std::f64::consts::PI).powi(2) / k;
} else {
total_dist_sq += dist_sq / k.abs();
}
}
total_dist_sq.sqrt()
}
/// Product manifold attention with mixed curvatures.
///
/// Computes attention in a product of spherical, hyperbolic, and
/// Euclidean subspaces, then combines the results.
pub fn product_manifold_attention(
&mut self,
features: &[f64],
edges: &[Edge],
curvatures: &[f64],
) -> ManifoldOutput {
let dim = features.len();
let n_spaces = curvatures.len().max(1);
let chunk_size = (dim + n_spaces - 1) / n_spaces;
// Compute manifold distances from each edge
let mut distances = Vec::new();
for edge in edges {
let s = edge.src as usize;
let t = edge.tgt as usize;
// Approximate: use distance in the feature space
if s < dim && t < dim {
distances.push((features[s] - features[t]).abs());
} else {
distances.push(0.0);
}
}
// Attention: compute output as curvature-weighted feature transform
let mut output = vec![0.0; dim];
for (space_idx, &k) in curvatures.iter().enumerate() {
let start = space_idx * chunk_size;
let end = (start + chunk_size).min(dim);
for i in start..end {
let scale = if k.abs() < 1e-12 {
1.0 // Euclidean
} else if k > 0.0 {
(features[i] * k.sqrt()).sin() / (features[i] * k.sqrt()).max(1e-12)
} else {
(features[i] * k.abs().sqrt()).sinh()
/ (features[i] * k.abs().sqrt()).max(1e-12)
};
output[i] = features[i] * scale;
}
}
self.stats.attention_ops += 1;
ManifoldOutput {
output,
curvatures: curvatures.to_vec(),
distances,
}
}
// -- Temporal --
#[allow(dead_code)]
pub fn causal_attention(
&mut self,
query: &[f64],
keys: &[Vec<f64>],
timestamps: &[f64],
) -> Vec<f64> {
let dim = query.len();
if keys.is_empty() || timestamps.len() != keys.len() {
return vec![];
}
let q_time = timestamps.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let scores: Vec<f64> = keys
.iter()
.zip(timestamps.iter())
.map(|(key, &t)| {
if t > q_time {
f64::NEG_INFINITY
} else {
let dot: f64 = query.iter().zip(key.iter()).map(|(q, k)| q * k).sum();
dot / (dim as f64).sqrt()
}
})
.collect();
let max_s = scores.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
if max_s.is_infinite() && max_s < 0.0 {
return vec![0.0; keys.len()];
}
let exps: Vec<f64> = scores.iter().map(|s| (s - max_s).exp()).collect();
let sum_exp: f64 = exps.iter().sum();
if sum_exp < 1e-12 {
return vec![0.0; keys.len()];
}
self.stats.attention_ops += 1;
exps.iter().map(|e| e / sum_exp).collect()
}
/// Causal attention over features, timestamps, and edges.
///
/// Returns attention-weighted output features.
pub fn causal_attention_graph(
&mut self,
features: &[f64],
timestamps: &[f64],
edges: &[Edge],
) -> Vec<f64> {
let n = features.len();
if n == 0 || timestamps.len() != n {
return vec![];
}
let mut output = vec![0.0; n];
// For each node, attend to causally-valid neighbors
for i in 0..n {
let t_i = timestamps[i];
let mut weighted_sum = 0.0;
let mut weight_sum = 0.0;
for edge in edges {
let j = edge.src as usize;
let k = edge.tgt as usize;
let neighbor = if k == i && j < n {
j
} else if j == i && k < n {
k
} else {
continue;
};
if timestamps[neighbor] <= t_i {
let dt = t_i - timestamps[neighbor];
let decay = (-0.1 * dt).exp();
let w = decay * features[neighbor].abs().max(1e-12);
weighted_sum += w * features[neighbor];
weight_sum += w;
}
}
output[i] = if weight_sum > 1e-12 {
weighted_sum / weight_sum
} else {
features[i]
};
}
self.stats.attention_ops += 1;
output
}
/// Extract Granger causality DAG from attention history.
///
/// `attention_history` is a T x N matrix (flattened row-major).
/// Returns edges where Granger causality F-statistic exceeds threshold.
pub fn granger_extract(
&mut self,
attention_history: &[f64],
num_nodes: u32,
num_steps: u32,
) -> GrangerDag {
let n = num_nodes as usize;
let t = num_steps as usize;
if n == 0 || t < 3 || attention_history.len() < n * t {
return GrangerDag {
edges: vec![],
num_nodes,
};
}
// Extract time series for each node
let mut series: Vec<Vec<f64>> = vec![Vec::with_capacity(t); n];
for step in 0..t {
for node in 0..n {
series[node].push(attention_history[step * n + node]);
}
}
let lags = 2.min(t - 1);
let mut edges = Vec::new();
for source in 0..n {
for target in 0..n {
if source == target {
continue;
}
// Restricted: predict target from its own lags
let rss_r = var_rss(&series[target], &[&series[target]], lags);
// Unrestricted: predict target from its own lags + source lags
let rss_u = var_rss(&series[target], &[&series[target], &series[source]], lags);
let n_obs = (t - lags) as f64;
let df_diff = lags as f64;
let df_denom = n_obs - 2.0 * lags as f64;
let f_stat = if rss_u > 1e-10 && df_denom > 0.0 && df_diff > 0.0 {
let raw = ((rss_r - rss_u) / df_diff) / (rss_u / df_denom);
if raw.is_finite() {
raw.max(0.0)
} else {
0.0
}
} else {
0.0
};
let is_causal = f_stat > 3.84;
if is_causal {
edges.push(GrangerEdge {
source: source as u32,
target: target as u32,
f_statistic: f_stat,
is_causal,
});
}
}
}
self.stats.attention_ops += 1;
GrangerDag { edges, num_nodes }
}
// -- Economic / Game-Theoretic --
/// Game-theoretic attention: computes Nash equilibrium allocations.
///
/// Each node is a player; edges define interactions. Attention weights
/// are set by a best-response iteration that converges to Nash equilibrium.
pub fn game_theoretic_attention(
&mut self,
features: &[f64],
edges: &[Edge],
) -> EquilibriumOutput {
let n = features.len();
if n == 0 {
return EquilibriumOutput {
allocations: vec![],
utilities: vec![],
nash_gap: 0.0,
converged: true,
};
}
// Build adjacency for fast neighbor lookup
let mut neighbors: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n];
for edge in edges {
let s = edge.src as usize;
let t = edge.tgt as usize;
if s < n && t < n {
neighbors[s].push((t, features[t]));
neighbors[t].push((s, features[s]));
}
}
// Initialize allocations proportional to features
let feat_sum: f64 = features.iter().map(|x| x.abs()).sum::<f64>().max(1e-12);
let mut allocations: Vec<f64> = features.iter().map(|x| x.abs() / feat_sum).collect();
// Best-response iteration (fictitious play)
let max_iters = 50;
let mut nash_gap = f64::MAX;
for _ in 0..max_iters {
let mut new_alloc = vec![0.0; n];
for i in 0..n {
// Each player maximizes utility = feature * allocation
// subject to neighbor interactions
let mut best_response = features[i].abs() / feat_sum;
for &(j, _fj) in &neighbors[i] {
// Strategic complementarity: benefit from neighbor allocations
best_response += 0.1 * allocations[j];
}
new_alloc[i] = best_response;
}
// Normalize allocations to sum to 1
let alloc_sum: f64 = new_alloc.iter().sum::<f64>().max(1e-12);
for v in &mut new_alloc {
*v /= alloc_sum;
}
// Compute Nash gap (max deviation from best response)
nash_gap = allocations
.iter()
.zip(new_alloc.iter())
.map(|(a, b)| (a - b).abs())
.fold(0.0, f64::max);
allocations = new_alloc;
if nash_gap < 1e-6 {
break;
}
}
// Compute utilities
let utilities: Vec<f64> = (0..n)
.map(|i| {
let self_util = features[i] * allocations[i];
let neighbor_util: f64 = neighbors[i]
.iter()
.map(|&(j, _)| 0.1 * allocations[j] * features[i])
.sum();
self_util + neighbor_util
})
.collect();
self.stats.attention_ops += 1;
EquilibriumOutput {
allocations,
utilities,
nash_gap,
converged: nash_gap < 1e-6,
}
}
// -- Stats --
pub fn stats(&self) -> &TransformerStats {
&self.stats
}
pub fn reset(&mut self) {
self.term_counter = 0;
self.proof_cache.clear();
self.gates.clear();
self.stats = TransformerStats::default();
self.prev_loss = None;
}
}
// ---------------------------------------------------------------------------
// Helper functions
// ---------------------------------------------------------------------------
fn compute_energy(positions: &[f64], momenta: &[f64]) -> f64 {
let kinetic: f64 = momenta.iter().map(|p| 0.5 * p * p).sum();
let potential: f64 = positions.iter().map(|q| 0.5 * q * q).sum();
kinetic + potential
}
fn compute_grad_with_edges(q: &[f64], edges: &[Edge], n: usize) -> Vec<f64> {
let mut grad = q.to_vec(); // Harmonic potential gradient: dV/dq = q
for edge in edges {
let u = edge.src as usize;
let v = edge.tgt as usize;
if u < n && v < n {
let diff = q[u] - q[v];
grad[u] += diff;
grad[v] -= diff;
}
}
grad
}
fn var_rss(target: &[f64], predictors: &[&[f64]], lags: usize) -> f64 {
let t = target.len();
if t <= lags {
return 0.0;
}
let mut rss = 0.0;
for i in lags..t {
let actual = target[i];
let mut predicted = 0.0;
let mut count = 0;
for pred in predictors {
for lag in 1..=lags {
if i >= lag && pred.len() > i - lag {
predicted += pred[i - lag];
count += 1;
}
}
}
if count > 0 {
predicted /= count as f64;
}
let residual = actual - predicted;
rss += residual * residual;
}
rss
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_proof_gate() {
let mut gt = CoreGraphTransformer::new();
let gate = gt.create_proof_gate(128);
assert_eq!(gate.dimension, 128);
}
#[test]
fn test_prove_dim_ok() {
let mut gt = CoreGraphTransformer::new();
assert!(gt.prove_dimension(64, 64).unwrap().verified);
}
#[test]
fn test_prove_dim_err() {
let mut gt = CoreGraphTransformer::new();
assert!(gt.prove_dimension(64, 128).is_err());
}
#[test]
fn test_attestation_roundtrip() {
let mut gt = CoreGraphTransformer::new();
let _ = gt.prove_dimension(32, 32).unwrap();
let att = gt.create_attestation(0);
let bytes = att.to_bytes();
assert_eq!(bytes.len(), ATTESTATION_SIZE);
assert!(gt.verify_attestation(&bytes));
}
#[test]
fn test_compose() {
let mut gt = CoreGraphTransformer::new();
let stages = vec![
PipelineStage {
name: "a".into(),
input_type_id: 1,
output_type_id: 2,
},
PipelineStage {
name: "b".into(),
input_type_id: 2,
output_type_id: 3,
},
];
let r = gt.compose_proofs(&stages).unwrap();
assert_eq!(r.stages_verified, 2);
}
#[test]
fn test_sublinear() {
let mut gt = CoreGraphTransformer::new();
let r = gt
.sublinear_attention(&[1.0, 0.5], &[vec![1], vec![0]], 2, 1)
.unwrap();
assert_eq!(r.scores.len(), 1);
}
#[test]
fn test_hamiltonian() {
let mut gt = CoreGraphTransformer::new();
let r = gt.hamiltonian_step(&[1.0], &[0.0], 0.001).unwrap();
assert!(r.energy > 0.0);
}
#[test]
fn test_hamiltonian_graph() {
let mut gt = CoreGraphTransformer::new();
let edges = vec![Edge { src: 0, tgt: 1 }];
let r = gt
.hamiltonian_step_graph(&[1.0, 0.0], &[0.0, 1.0], &edges, 0.001)
.unwrap();
assert!(r.energy > 0.0);
}
#[test]
fn test_spiking() {
let mut gt = CoreGraphTransformer::new();
let o = gt.spiking_attention(&[0.5, 2.0], &[vec![1], vec![0]], 1.0);
assert_eq!(o.len(), 2);
assert!(o[0] > 0.0);
}
#[test]
fn test_spiking_step() {
let mut gt = CoreGraphTransformer::new();
let features = vec![vec![0.8, 0.6], vec![0.1, 0.2]];
let adjacency = vec![0.0, 0.5, 0.3, 0.0];
let result = gt.spiking_step(&features, &adjacency, 0.5);
assert_eq!(result.features.len(), 2);
assert_eq!(result.spikes.len(), 2);
}
#[test]
fn test_hebbian() {
let mut gt = CoreGraphTransformer::new();
let r = gt.hebbian_update(&[1.0], &[1.0], &[0.0], 0.5);
assert!((r[0] - 0.5).abs() < 1e-9);
}
#[test]
fn test_verified_step() {
let mut gt = CoreGraphTransformer::new();
let r = gt.verified_step(&[1.0, 2.0], &[0.1, 0.2], 0.01).unwrap();
assert!(r.loss_after < r.loss_before);
}
#[test]
fn test_verified_training_step() {
let mut gt = CoreGraphTransformer::new();
let r = gt
.verified_training_step(&[1.0, 2.0], &[0.5, 1.0], &[0.5, 0.5], 0.01)
.unwrap();
assert!(r.loss >= 0.0);
assert!(r.loss_monotonic);
}
#[test]
fn test_manifold_euclidean() {
let gt = CoreGraphTransformer::new();
let d = gt.product_manifold_distance(&[0.0, 0.0], &[3.0, 4.0], &[0.0]);
assert!((d - 5.0).abs() < 1e-6);
}
#[test]
fn test_product_manifold_attention() {
let mut gt = CoreGraphTransformer::new();
let features = vec![1.0, 0.5, -0.3, 0.8];
let edges = vec![Edge { src: 0, tgt: 1 }];
let curvatures = vec![0.0, -1.0];
let result = gt.product_manifold_attention(&features, &edges, &curvatures);
assert_eq!(result.output.len(), 4);
assert_eq!(result.curvatures.len(), 2);
}
#[test]
fn test_causal_attention() {
let mut gt = CoreGraphTransformer::new();
let s = gt.causal_attention(&[1.0], &[vec![1.0], vec![0.5]], &[1.0, 2.0]);
assert_eq!(s.len(), 2);
let sum: f64 = s.iter().sum();
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_causal_attention_graph() {
let mut gt = CoreGraphTransformer::new();
let features = vec![1.0, 0.5, 0.8];
let timestamps = vec![1.0, 2.0, 3.0];
let edges = vec![Edge { src: 0, tgt: 1 }, Edge { src: 1, tgt: 2 }];
let out = gt.causal_attention_graph(&features, &timestamps, &edges);
assert_eq!(out.len(), 3);
}
#[test]
fn test_granger_extract() {
let mut gt = CoreGraphTransformer::new();
// 2 nodes, 10 steps: node 0 causes node 1 with lag
let mut history = Vec::new();
for t in 0..10 {
let x = (t as f64 * 0.5).sin();
let y = if t > 0 {
((t - 1) as f64 * 0.5).sin() * 0.8
} else {
0.0
};
history.push(x);
history.push(y);
}
let dag = gt.granger_extract(&history, 2, 10);
assert_eq!(dag.num_nodes, 2);
}
#[test]
fn test_game_theoretic_attention() {
let mut gt = CoreGraphTransformer::new();
let features = vec![1.0, 0.5, 0.8];
let edges = vec![Edge { src: 0, tgt: 1 }, Edge { src: 1, tgt: 2 }];
let result = gt.game_theoretic_attention(&features, &edges);
assert_eq!(result.allocations.len(), 3);
assert_eq!(result.utilities.len(), 3);
let alloc_sum: f64 = result.allocations.iter().sum();
assert!((alloc_sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_stats_reset() {
let mut gt = CoreGraphTransformer::new();
gt.create_proof_gate(64);
assert!(gt.stats().proofs_constructed > 0);
gt.reset();
assert_eq!(gt.stats().proofs_constructed, 0);
}
}