#![allow(clippy::many_single_char_names)]
#![allow(clippy::cast_precision_loss)] #![allow(clippy::cast_lossless)] #![allow(clippy::cast_possible_truncation)] #![allow(clippy::needless_range_loop)] #![allow(clippy::too_many_lines)]
use std::sync::Arc;
use semiflow::{
graph::{Graph, Laplacian},
graph_heat::GraphHeatChernoff,
graph_signal::GraphSignal,
ChernoffFunction, ChernoffSemigroup, ScratchPool,
};
const N_VALUES: [usize; 5] = [25, 50, 100, 200, 400];
struct EigDecomp {
eigenvalues: Vec<f64>,
eigenvectors_col_major: Vec<f64>,
}
fn jacobi_eig(lap: &Laplacian<f64>) -> EigDecomp {
let n = lap.n_nodes();
let mut a = vec![0.0_f64; n * n];
for i in 0..n {
for k in lap.row_ptr()[i]..lap.row_ptr()[i + 1] {
a[i * n + lap.col_idx()[k] as usize] = lap.vals()[k];
}
}
let mut q = vec![0.0_f64; n * n];
for k in 0..n {
q[k + k * n] = 1.0;
}
let tol = 1e-24_f64;
for _ in 0..(200 * n) {
let mut off2 = 0.0_f64;
for p in 0..n {
for qi in (p + 1)..n {
let v = a[p * n + qi];
off2 += v * v;
}
}
off2 *= 2.0;
if off2 <= 0.0 {
break;
}
let diag2: f64 = (0..n)
.map(|k| {
let v = a[k * n + k];
v * v
})
.sum();
if off2 < tol * diag2 {
break;
}
let (mut p, mut qi) = (0usize, 1usize);
let mut mx = 0.0_f64;
for pi in 0..n {
for qj in (pi + 1)..n {
let av = a[pi * n + qj].abs();
if av > mx {
mx = av;
p = pi;
qi = qj;
}
}
}
if a[p * n + qi] == 0.0 {
break;
}
let (c, s) = jacobi_cs(a[p * n + p], a[p * n + qi], a[qi * n + qi]);
for r in 0..n {
let rp = a[r * n + p];
let rq = a[r * n + qi];
a[r * n + p] = c * rp - s * rq;
a[r * n + qi] = s * rp + c * rq;
}
for r in 0..n {
let pr = a[p * n + r];
let qr = a[qi * n + r];
a[p * n + r] = c * pr - s * qr;
a[qi * n + r] = s * pr + c * qr;
}
for r in 0..n {
let rp = q[r + p * n];
let rq = q[r + qi * n];
q[r + p * n] = c * rp - s * rq;
q[r + qi * n] = s * rp + c * rq;
}
}
let mut pairs: Vec<(f64, usize)> = (0..n).map(|k| (a[k * n + k], k)).collect();
pairs.sort_unstable_by(|x, y| x.0.partial_cmp(&y.0).unwrap());
let eigenvalues: Vec<f64> = pairs.iter().map(|&(v, _)| v).collect();
let mut evecs = vec![0.0_f64; n * n];
for (new_k, &(_, old_k)) in pairs.iter().enumerate() {
for j in 0..n {
evecs[j + new_k * n] = q[j + old_k * n];
}
}
for k in 0..n {
if evecs[k * n] < 0.0 {
for j in 0..n {
evecs[j + k * n] = -evecs[j + k * n];
}
}
}
EigDecomp {
eigenvalues,
eigenvectors_col_major: evecs,
}
}
fn jacobi_cs(app: f64, apq: f64, aqq: f64) -> (f64, f64) {
let tau = (aqq - app) / (2.0 * apq);
let t = if tau >= 0.0 {
1.0 / (tau + (1.0 + tau * tau).sqrt())
} else {
-1.0 / (-tau + (1.0 + tau * tau).sqrt())
};
let c = 1.0 / (1.0 + t * t).sqrt();
(c, t * c)
}
fn heat_oracle(
decomp: &EigDecomp,
f0_vals: &[f64],
g: Arc<Graph<f64>>,
t: f64,
) -> GraphSignal<f64> {
let n = f0_vals.len();
let mut alpha = vec![0.0_f64; n];
for k in 0..n {
let mut dot = 0.0_f64;
for j in 0..n {
dot += decomp.eigenvectors_col_major[j + k * n] * f0_vals[j];
}
alpha[k] = dot;
}
let mut u = vec![0.0_f64; n];
for k in 0..n {
let coeff = alpha[k] * (-t * decomp.eigenvalues[k]).exp();
for j in 0..n {
u[j] += coeff * decomp.eigenvectors_col_major[j + k * n];
}
}
GraphSignal::from_fn(g, |i| u[i as usize])
}
fn log_log_slope(xs: &[f64], ys: &[f64]) -> f64 {
let m = xs.len() as f64;
let lxs: Vec<f64> = xs.iter().map(|&x| x.ln()).collect();
let lys: Vec<f64> = ys.iter().map(|&y| y.ln()).collect();
let sx: f64 = lxs.iter().sum();
let sy: f64 = lys.iter().sum();
let sxx: f64 = lxs.iter().map(|&x| x * x).sum();
let sxy: f64 = lxs.iter().zip(lys.iter()).map(|(&x, &y)| x * y).sum();
(m * sxy - sx * sy) / (m * sxx - sx * sx)
}
#[test]
fn g7_graph_heat_convergence_slope_f64() {
let n_nodes = 64_usize;
let t = 0.5_f64;
let g = Arc::new(Graph::<f64>::erdos_renyi(n_nodes, 0.15, 0xDEAD_BEEF));
let lap = Arc::new(Laplacian::assemble_combinatorial(&g));
let decomp = jacobi_eig(&lap);
let f0 = GraphSignal::from_fn(Arc::clone(&g), |i| {
let x = i as f64 - (n_nodes as f64) / 2.0;
(-x * x / 16.0).exp()
});
let f0_vals: Vec<f64> = f0.values().to_vec();
let u_oracle = heat_oracle(&decomp, &f0_vals, Arc::clone(&g), t);
let errs: Vec<f64> = N_VALUES
.iter()
.map(|&n_steps| {
let chernoff = GraphHeatChernoff::new(Arc::clone(&lap));
let semi = ChernoffSemigroup::new(chernoff, n_steps).expect("n >= 1");
let u = semi.evolve(t, &f0).expect("evolve must succeed");
u.values()
.iter()
.zip(u_oracle.values().iter())
.map(|(&a, &b)| (a - b).abs())
.fold(0.0_f64, f64::max)
})
.collect();
for (&n_steps, &err) in N_VALUES.iter().zip(errs.iter()) {
println!("n_steps={n_steps:4}, err={err:.4e}");
}
let n_vals_f64: Vec<f64> = N_VALUES.iter().map(|&n| n as f64).collect();
let slope = log_log_slope(&n_vals_f64, &errs);
println!("G7 f64 slope = {slope:.4}");
assert!(
slope <= -0.95,
"G7 f64 slope {slope:.4} > -0.95 (order-1 gate)"
);
}
#[test]
fn g7_graph_heat_convergence_slope_f32() {
let n_nodes = 64_usize;
let t = 0.5_f32;
let g = Arc::new(Graph::<f32>::erdos_renyi(n_nodes, 0.15, 0xDEAD_BEEF));
let lap = Arc::new(Laplacian::assemble_combinatorial(&g));
let g64 = Arc::new(Graph::<f64>::erdos_renyi(n_nodes, 0.15, 0xDEAD_BEEF));
let lap64 = Arc::new(Laplacian::assemble_combinatorial(&g64));
let decomp = jacobi_eig(&lap64);
let f0_f32 = GraphSignal::from_fn(Arc::clone(&g), |i| {
let x = i as f32 - (n_nodes as f32) / 2.0;
(-x * x / 16.0).exp()
});
let f0_vals64: Vec<f64> = f0_f32.values().iter().map(|&v| v as f64).collect();
let u_oracle64 = heat_oracle(&decomp, &f0_vals64, Arc::clone(&g64), t as f64);
let chernoff = GraphHeatChernoff::new(Arc::clone(&lap));
let errs: Vec<f64> = N_VALUES
.iter()
.map(|&n_steps| {
let tau = t / n_steps as f32;
let mut pool = ScratchPool::<f32>::new();
let mut current = f0_f32.clone();
let mut next = f0_f32.clone();
for _ in 0..n_steps {
chernoff
.apply_into(tau, ¤t, &mut next, &mut pool)
.expect("apply_into f32 must succeed");
core::mem::swap(&mut current, &mut next);
}
current
.values()
.iter()
.zip(u_oracle64.values().iter())
.map(|(&a, &b)| (a as f64 - b).abs())
.fold(0.0_f64, f64::max)
})
.collect();
for (&n_steps, &err) in N_VALUES.iter().zip(errs.iter()) {
println!("n_steps={n_steps:4}, err={err:.4e}");
}
let n_vals_f64: Vec<f64> = N_VALUES.iter().map(|&n| n as f64).collect();
let slope = log_log_slope(&n_vals_f64, &errs);
println!("G7 f32 slope = {slope:.4}");
assert!(
slope <= -0.90,
"G7 f32 slope {slope:.4} > -0.90 (order-1 gate)"
);
}