#[cfg(feature = "physics")]
use ruvector_verified::{
ProofEnvironment, ProofAttestation,
prove_dim_eq,
proof_store::create_attestation,
gated::{route_proof, ProofKind, ProofTier},
};
#[cfg(feature = "physics")]
use crate::config::PhysicsConfig;
#[cfg(feature = "physics")]
use crate::error::{GraphTransformerError, Result};
#[cfg(feature = "physics")]
pub struct HamiltonianGraphNet {
config: PhysicsConfig,
dim: usize,
env: ProofEnvironment,
}
#[cfg(feature = "physics")]
#[derive(Debug, Clone)]
pub struct HamiltonianState {
pub q: Vec<Vec<f32>>,
pub p: Vec<Vec<f32>>,
pub energy: f32,
}
#[cfg(feature = "physics")]
#[derive(Debug)]
pub struct HamiltonianOutput {
pub state: HamiltonianState,
pub initial_energy: f32,
pub final_energy: f32,
pub drift_ratio: f32,
pub attestation: Option<ProofAttestation>,
}
#[cfg(feature = "physics")]
#[derive(Debug)]
pub struct HamiltonianStepResult {
pub state: HamiltonianState,
pub energy_before: f32,
pub energy_after: f32,
pub energy_conserved: bool,
pub attestation: Option<ProofAttestation>,
}
#[cfg(feature = "physics")]
impl HamiltonianGraphNet {
pub fn new(dim: usize, config: PhysicsConfig) -> Self {
Self {
config,
dim,
env: ProofEnvironment::new(),
}
}
pub fn init_state(&self, node_features: &[Vec<f32>]) -> Result<HamiltonianState> {
for (i, feat) in node_features.iter().enumerate() {
if feat.len() != self.dim {
return Err(GraphTransformerError::DimensionMismatch {
expected: self.dim,
actual: feat.len(),
});
}
for &v in feat {
if !v.is_finite() {
return Err(GraphTransformerError::NumericalError(
format!("non-finite value in node_features[{}]", i),
));
}
}
}
let n = node_features.len();
let q = node_features.to_vec();
let p = vec![vec![0.0f32; self.dim]; n];
let energy = self.compute_energy(&q, &p);
Ok(HamiltonianState { q, p, energy })
}
pub fn step(
&mut self,
state: &HamiltonianState,
adjacency: &[(usize, usize, f32)],
) -> Result<HamiltonianStepResult> {
let output = self.forward(state, adjacency)?;
let energy_conserved = output.attestation.is_some();
Ok(HamiltonianStepResult {
energy_before: output.initial_energy,
energy_after: output.final_energy,
energy_conserved,
attestation: output.attestation,
state: output.state,
})
}
pub fn forward(
&mut self,
state: &HamiltonianState,
adjacency: &[(usize, usize, f32)],
) -> Result<HamiltonianOutput> {
let n = state.q.len();
let dt = self.config.dt;
let initial_energy = state.energy;
let mut q = state.q.clone();
let mut p = state.p.clone();
for _ in 0..self.config.leapfrog_steps {
let grad_q = self.compute_grad_q(&q, adjacency);
for i in 0..n {
for d in 0..self.dim {
p[i][d] -= 0.5 * dt * grad_q[i][d];
}
}
let grad_p = self.compute_grad_p(&p);
for i in 0..n {
for d in 0..self.dim {
q[i][d] += dt * grad_p[i][d];
}
}
let grad_q = self.compute_grad_q(&q, adjacency);
for i in 0..n {
for d in 0..self.dim {
p[i][d] -= 0.5 * dt * grad_q[i][d];
}
}
}
let final_energy = self.compute_energy(&q, &p);
let energy_diff = (final_energy - initial_energy).abs();
let denominator = initial_energy.abs().max(1e-12);
let drift_ratio = energy_diff / denominator;
let decision = route_proof(
ProofKind::DimensionEquality {
expected: self.dim as u32,
actual: self.dim as u32,
},
&self.env,
);
debug_assert_eq!(decision.tier, ProofTier::Reflex);
let attestation = if drift_ratio < self.config.energy_tolerance {
let dim_u32 = self.dim as u32;
let proof_id = prove_dim_eq(&mut self.env, dim_u32, dim_u32)?;
Some(create_attestation(&self.env, proof_id))
} else {
None
};
let new_state = HamiltonianState {
q,
p,
energy: final_energy,
};
Ok(HamiltonianOutput {
state: new_state,
initial_energy,
final_energy,
drift_ratio,
attestation,
})
}
fn compute_energy(&self, q: &[Vec<f32>], p: &[Vec<f32>]) -> f32 {
let kinetic: f32 = p
.iter()
.map(|pi| pi.iter().map(|&x| x * x).sum::<f32>() * 0.5)
.sum();
let potential: f32 = q
.iter()
.map(|qi| qi.iter().map(|&x| x * x).sum::<f32>() * 0.5)
.sum();
kinetic + potential
}
fn compute_grad_q(
&self,
q: &[Vec<f32>],
adjacency: &[(usize, usize, f32)],
) -> Vec<Vec<f32>> {
let n = q.len();
let mut grad = vec![vec![0.0f32; self.dim]; n];
for i in 0..n {
for d in 0..self.dim {
grad[i][d] = q[i][d];
}
}
for &(u, v, w) in adjacency {
if u < n && v < n {
for d in 0..self.dim {
let diff = q[u][d] - q[v][d];
grad[u][d] += w * diff;
grad[v][d] -= w * diff;
}
}
}
grad
}
fn compute_grad_p(&self, p: &[Vec<f32>]) -> Vec<Vec<f32>> {
p.to_vec()
}
pub fn dim(&self) -> usize {
self.dim
}
}
#[cfg(feature = "physics")]
pub struct GaugeEquivariantMP {
pub gauge_dim: usize,
pub ym_lambda: f32,
env: ProofEnvironment,
}
#[cfg(feature = "physics")]
#[derive(Debug, Clone)]
pub struct GaugeOutput {
pub features: Vec<Vec<f32>>,
pub ym_energy: f32,
pub attestation: Option<ProofAttestation>,
}
#[cfg(feature = "physics")]
impl GaugeEquivariantMP {
pub fn new(gauge_dim: usize, ym_lambda: f32) -> Self {
Self {
gauge_dim,
ym_lambda,
env: ProofEnvironment::new(),
}
}
pub fn forward(
&mut self,
node_features: &[Vec<f32>],
edges: &[(usize, usize, Vec<f32>)],
) -> Result<GaugeOutput> {
let n = node_features.len();
let d = self.gauge_dim;
for feat in node_features {
if feat.len() != d {
return Err(GraphTransformerError::DimensionMismatch {
expected: d,
actual: feat.len(),
});
}
}
for (idx, (src, dst, conn)) in edges.iter().enumerate() {
if *src >= n || *dst >= n {
return Err(GraphTransformerError::InvariantViolation(
format!("edge {} references out-of-bounds node ({}, {})", idx, src, dst),
));
}
if conn.len() != d * d {
return Err(GraphTransformerError::DimensionMismatch {
expected: d * d,
actual: conn.len(),
});
}
}
let mut dest_edges: Vec<Vec<(usize, &Vec<f32>)>> = vec![Vec::new(); n];
for (src, dst, conn) in edges {
dest_edges[*dst].push((*src, conn));
}
let mut output = vec![vec![0.0f32; d]; n];
for dst_node in 0..n {
if dest_edges[dst_node].is_empty() {
output[dst_node] = node_features[dst_node].clone();
continue;
}
let query = &node_features[dst_node];
let mut scores: Vec<f32> = Vec::with_capacity(dest_edges[dst_node].len());
for &(src, conn) in &dest_edges[dst_node] {
let key = mat_vec_mul(conn, &node_features[src], d);
let score: f32 = query.iter().zip(key.iter()).map(|(a, b)| a * b).sum();
scores.push(score);
}
let max_score = scores.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let exp_scores: Vec<f32> = scores.iter().map(|&s| (s - max_score).exp()).collect();
let sum_exp: f32 = exp_scores.iter().sum();
let weights: Vec<f32> = exp_scores.iter().map(|&e| e / sum_exp.max(1e-12)).collect();
for (j, &(src, _)) in dest_edges[dst_node].iter().enumerate() {
let w = weights[j];
for dd in 0..d {
output[dst_node][dd] += w * node_features[src][dd];
}
}
}
let mut ym_energy = 0.0f32;
for (_src, _dst, conn) in edges {
let mut norm_sq = 0.0f32;
for row in 0..d {
for col in 0..d {
let g = conn[row * d + col];
let target = if row == col { 1.0 } else { 0.0 };
let diff = g - target;
norm_sq += diff * diff;
}
}
ym_energy += norm_sq;
}
ym_energy *= self.ym_lambda;
let dim_u32 = d as u32;
let proof_id = prove_dim_eq(&mut self.env, dim_u32, dim_u32)?;
let attestation = Some(create_attestation(&self.env, proof_id));
Ok(GaugeOutput {
features: output,
ym_energy,
attestation,
})
}
}
#[cfg(feature = "physics")]
fn mat_vec_mul(mat: &[f32], v: &[f32], d: usize) -> Vec<f32> {
let mut out = vec![0.0f32; d];
for row in 0..d {
let mut s = 0.0f32;
for col in 0..d {
s += mat[row * d + col] * v[col];
}
out[row] = s;
}
out
}
#[cfg(feature = "physics")]
pub struct LagrangianAttention {
pub beta: f32,
pub dt: f32,
pub action_bound: f32,
env: ProofEnvironment,
}
#[cfg(feature = "physics")]
#[derive(Debug, Clone)]
pub struct LagrangianOutput {
pub features: Vec<Vec<f32>>,
pub actions: Vec<Vec<f32>>,
pub attestation: Option<ProofAttestation>,
}
#[cfg(feature = "physics")]
impl LagrangianAttention {
pub fn new(beta: f32, dt: f32, action_bound: f32) -> Self {
Self {
beta,
dt,
action_bound,
env: ProofEnvironment::new(),
}
}
pub fn forward(
&mut self,
node_features: &[Vec<f32>],
edges: &[(usize, usize, f32)],
) -> Result<LagrangianOutput> {
let n = node_features.len();
if n == 0 {
return Ok(LagrangianOutput {
features: vec![],
actions: vec![],
attestation: None,
});
}
let d = node_features[0].len();
let mut dest_edges: Vec<Vec<(usize, f32)>> = vec![Vec::new(); n];
for &(src, dst, w) in edges {
if src < n && dst < n {
dest_edges[dst].push((src, w));
}
}
let mut output = vec![vec![0.0f32; d]; n];
let mut all_actions: Vec<Vec<f32>> = vec![Vec::new(); n];
let mut action_in_bound = true;
for dst in 0..n {
if dest_edges[dst].is_empty() {
output[dst] = node_features[dst].clone();
continue;
}
let q_dst = &node_features[dst];
let mut actions: Vec<f32> = Vec::with_capacity(dest_edges[dst].len());
for &(src, edge_w) in &dest_edges[dst] {
let q_src = &node_features[src];
let dist_sq: f32 = q_dst
.iter()
.zip(q_src.iter())
.map(|(a, b)| (a - b) * (a - b))
.sum();
let kinetic = dist_sq / (2.0 * self.dt);
let v_mean: f32 = edge_w
* q_dst
.iter()
.zip(q_src.iter())
.map(|(a, b)| (a * a + b * b) * 0.25)
.sum::<f32>();
let potential = self.dt * v_mean;
let action = kinetic - potential;
if action.abs() > self.action_bound {
action_in_bound = false;
}
actions.push(action);
}
let min_beta_s = actions
.iter()
.cloned()
.map(|s| self.beta * s)
.fold(f32::INFINITY, f32::min);
let exp_weights: Vec<f32> = actions
.iter()
.map(|&s| (-(self.beta * s - min_beta_s)).exp())
.collect();
let z: f32 = exp_weights.iter().sum::<f32>().max(1e-12);
let weights: Vec<f32> = exp_weights.iter().map(|&e| e / z).collect();
for (j, &(src, _)) in dest_edges[dst].iter().enumerate() {
let w = weights[j];
for dd in 0..d {
output[dst][dd] += w * node_features[src][dd];
}
}
all_actions[dst] = actions;
}
let attestation = if action_in_bound {
let dim_u32 = d as u32;
let _decision = route_proof(
ProofKind::DimensionEquality {
expected: dim_u32,
actual: dim_u32,
},
&self.env,
);
let proof_id = prove_dim_eq(&mut self.env, dim_u32, dim_u32)?;
Some(create_attestation(&self.env, proof_id))
} else {
None
};
Ok(LagrangianOutput {
features: output,
actions: all_actions,
attestation,
})
}
}
#[cfg(feature = "physics")]
pub struct ConservativePdeAttention {
pub diffusion_coeff: f32,
pub dt: f32,
pub mass_tolerance: f32,
env: ProofEnvironment,
}
#[cfg(feature = "physics")]
#[derive(Debug, Clone)]
pub struct PdeOutput {
pub features: Vec<Vec<f32>>,
pub mass_before: f32,
pub mass_after: f32,
pub mass_conserved: bool,
pub attestation: Option<ProofAttestation>,
}
#[cfg(feature = "physics")]
impl ConservativePdeAttention {
pub fn new(diffusion_coeff: f32, dt: f32, mass_tolerance: f32) -> Self {
Self {
diffusion_coeff,
dt,
mass_tolerance,
env: ProofEnvironment::new(),
}
}
pub fn forward(
&mut self,
node_features: &[Vec<f32>],
edges: &[(usize, usize, f32)],
) -> Result<PdeOutput> {
let n = node_features.len();
if n == 0 {
return Ok(PdeOutput {
features: vec![],
mass_before: 0.0,
mass_after: 0.0,
mass_conserved: true,
attestation: None,
});
}
let d = node_features[0].len();
let mass_before: f32 = node_features
.iter()
.flat_map(|f| f.iter())
.sum();
let mut output: Vec<Vec<f32>> = node_features.to_vec();
let alpha_dt = self.diffusion_coeff * self.dt;
for &(u, v, w) in edges {
if u < n && v < n {
for dd in 0..d {
let flux = alpha_dt * w * (node_features[v][dd] - node_features[u][dd]);
output[u][dd] += flux;
output[v][dd] -= flux;
}
}
}
let mass_after: f32 = output
.iter()
.flat_map(|f| f.iter())
.sum();
let mass_diff = (mass_after - mass_before).abs();
let mass_conserved = mass_diff < self.mass_tolerance;
let attestation = if mass_conserved {
let dim_u32 = d as u32;
let _decision = route_proof(
ProofKind::DimensionEquality {
expected: dim_u32,
actual: dim_u32,
},
&self.env,
);
let proof_id = prove_dim_eq(&mut self.env, dim_u32, dim_u32)?;
Some(create_attestation(&self.env, proof_id))
} else {
None
};
Ok(PdeOutput {
features: output,
mass_before,
mass_after,
mass_conserved,
attestation,
})
}
}
#[cfg(test)]
#[cfg(feature = "physics")]
mod tests {
use super::*;
#[test]
fn test_hamiltonian_init() {
let config = PhysicsConfig {
dt: 0.01,
leapfrog_steps: 5,
energy_tolerance: 1e-2,
};
let hgn = HamiltonianGraphNet::new(4, config);
let features = vec![
vec![1.0, 0.0, 0.0, 0.0],
vec![0.0, 1.0, 0.0, 0.0],
];
let state = hgn.init_state(&features).unwrap();
assert_eq!(state.q.len(), 2);
assert_eq!(state.p.len(), 2);
assert!(state.energy > 0.0);
}
#[test]
fn test_hamiltonian_4nodes_energy_conservation() {
let config = PhysicsConfig {
dt: 0.001,
leapfrog_steps: 10,
energy_tolerance: 0.05,
};
let mut hgn = HamiltonianGraphNet::new(3, config);
let features = vec![
vec![1.0, 0.0, 0.0],
vec![0.0, 1.0, 0.0],
vec![0.0, 0.0, 1.0],
vec![0.5, 0.5, 0.0],
];
let state = hgn.init_state(&features).unwrap();
let edges = vec![
(0, 1, 0.5),
(1, 2, 0.5),
(2, 3, 0.5),
(3, 0, 0.5),
];
let output = hgn.forward(&state, &edges).unwrap();
let drift = output.drift_ratio;
assert!(
drift < 0.05,
"energy drift ratio too large: {} (initial={}, final={})",
drift, output.initial_energy, output.final_energy,
);
assert!(
output.attestation.is_some(),
"attestation should be present when energy is conserved"
);
}
#[test]
fn test_hamiltonian_step_backward_compat() {
let config = PhysicsConfig {
dt: 0.001,
leapfrog_steps: 1,
energy_tolerance: 0.1,
};
let mut hgn = HamiltonianGraphNet::new(2, config);
let features = vec![vec![0.5, 0.3], vec![0.2, 0.4]];
let state = hgn.init_state(&features).unwrap();
let edges = vec![(0, 1, 0.1)];
let result = hgn.step(&state, &edges).unwrap();
let energy_diff = (result.energy_after - result.energy_before).abs();
assert!(energy_diff < 0.1, "energy diff too large: {}", energy_diff);
assert!(result.energy_conserved);
assert!(result.attestation.is_some());
}
#[test]
fn test_hamiltonian_dimension_mismatch() {
let config = PhysicsConfig::default();
let hgn = HamiltonianGraphNet::new(4, config);
let features = vec![vec![1.0, 2.0]]; let result = hgn.init_state(&features);
assert!(result.is_err());
}
#[test]
fn test_hamiltonian_rejects_nan() {
let config = PhysicsConfig::default();
let hgn = HamiltonianGraphNet::new(2, config);
let features = vec![vec![f32::NAN, 1.0]];
let result = hgn.init_state(&features);
assert!(result.is_err());
}
#[test]
fn test_hamiltonian_output_fields() {
let config = PhysicsConfig {
dt: 0.01,
leapfrog_steps: 1,
energy_tolerance: 1.0,
};
let mut hgn = HamiltonianGraphNet::new(2, config);
let state = hgn.init_state(&[vec![1.0, 0.0]]).unwrap();
let output = hgn.forward(&state, &[]).unwrap();
assert!(output.initial_energy > 0.0);
assert!(output.final_energy > 0.0);
assert!(output.drift_ratio >= 0.0);
}
#[test]
fn test_pde_mass_conservation() {
let mut pde = ConservativePdeAttention::new(0.1, 0.01, 1e-4);
let features = vec![
vec![1.0, 2.0, 3.0],
vec![4.0, 5.0, 6.0],
vec![7.0, 8.0, 9.0],
];
let edges = vec![
(0, 1, 1.0),
(1, 2, 1.0),
(0, 2, 1.0),
];
let output = pde.forward(&features, &edges).unwrap();
assert!(
output.mass_conserved,
"mass not conserved: before={}, after={}, diff={}",
output.mass_before,
output.mass_after,
(output.mass_after - output.mass_before).abs(),
);
assert!(output.attestation.is_some());
let features_changed = output
.features
.iter()
.zip(features.iter())
.any(|(new_f, old_f)| {
new_f.iter().zip(old_f.iter()).any(|(a, b)| (a - b).abs() > 1e-8)
});
assert!(features_changed, "diffusion should modify features");
}
#[test]
fn test_pde_empty_graph() {
let mut pde = ConservativePdeAttention::new(0.1, 0.01, 1e-6);
let output = pde.forward(&[], &[]).unwrap();
assert_eq!(output.mass_before, 0.0);
assert_eq!(output.mass_after, 0.0);
assert!(output.mass_conserved);
}
#[test]
fn test_pde_no_edges() {
let mut pde = ConservativePdeAttention::new(0.1, 0.01, 1e-6);
let features = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
let output = pde.forward(&features, &[]).unwrap();
assert_eq!(output.features, features);
assert!(output.mass_conserved);
}
#[test]
fn test_pde_mass_values() {
let mut pde = ConservativePdeAttention::new(0.5, 0.1, 1e-3);
let features = vec![
vec![10.0, 0.0],
vec![0.0, 10.0],
];
let edges = vec![(0, 1, 1.0)];
let output = pde.forward(&features, &edges).unwrap();
assert!((output.mass_before - 20.0).abs() < 1e-6);
assert!(
(output.mass_after - output.mass_before).abs() < 1e-3,
"mass drift: {}",
(output.mass_after - output.mass_before).abs(),
);
}
#[test]
fn test_gauge_basic_forward() {
let gauge_dim = 3;
let mut gauge = GaugeEquivariantMP::new(gauge_dim, 0.01);
let features = vec![
vec![1.0, 0.0, 0.0],
vec![0.0, 1.0, 0.0],
vec![0.0, 0.0, 1.0],
];
let identity: Vec<f32> = vec![
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
];
let edges = vec![
(0, 1, identity.clone()),
(1, 2, identity.clone()),
(2, 0, identity.clone()),
];
let output = gauge.forward(&features, &edges).unwrap();
assert_eq!(output.features.len(), 3);
assert_eq!(output.features[0].len(), gauge_dim);
assert!(output.attestation.is_some());
assert!(
output.ym_energy.abs() < 1e-6,
"ym_energy should be ~0 for identity connections, got {}",
output.ym_energy,
);
}
#[test]
fn test_gauge_ym_energy_nonidentity() {
let gauge_dim = 2;
let mut gauge = GaugeEquivariantMP::new(gauge_dim, 1.0);
let features = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
let rotation: Vec<f32> = vec![0.0, -1.0, 1.0, 0.0];
let edges = vec![(0, 1, rotation)];
let output = gauge.forward(&features, &edges).unwrap();
assert!(
output.ym_energy > 0.0,
"ym_energy should be > 0 for non-identity connection",
);
}
#[test]
fn test_gauge_dimension_mismatch() {
let mut gauge = GaugeEquivariantMP::new(3, 0.01);
let features = vec![vec![1.0, 0.0]]; let edges = vec![];
let result = gauge.forward(&features, &edges);
assert!(result.is_err());
}
#[test]
fn test_gauge_connection_dimension_mismatch() {
let mut gauge = GaugeEquivariantMP::new(2, 0.01);
let features = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
let edges = vec![(0, 1, vec![1.0, 0.0, 0.0])];
let result = gauge.forward(&features, &edges);
assert!(result.is_err());
}
#[test]
fn test_lagrangian_basic() {
let mut lagr = LagrangianAttention::new(1.0, 0.1, 100.0);
let features = vec![
vec![1.0, 0.0],
vec![0.0, 1.0],
vec![1.0, 1.0],
];
let edges = vec![(0, 1, 1.0), (1, 2, 1.0), (0, 2, 1.0)];
let output = lagr.forward(&features, &edges).unwrap();
assert_eq!(output.features.len(), 3);
assert!(output.attestation.is_some());
}
#[test]
fn test_lagrangian_empty() {
let mut lagr = LagrangianAttention::new(1.0, 0.1, 100.0);
let output = lagr.forward(&[], &[]).unwrap();
assert!(output.features.is_empty());
}
}