use crate::arithmetic::Fx;
use super::csr::CsrMatrix;
pub fn diffusion_step(
phi: &[Fx],
transition: &CsrMatrix,
dangling: &[bool],
teleport: &[Fx],
alpha: Fx,
) -> Vec<Fx> {
let n = phi.len();
let one_minus_alpha = Fx::ONE - alpha;
let mut dangling_mass = Fx::ZERO;
for i in 0..n {
if dangling[i] {
dangling_mass = dangling_mass + phi[i];
}
}
let mut tphi = vec![Fx::ZERO; n];
transition.spmv(phi, &mut tphi);
(0..n)
.map(|i| alpha * teleport[i] + one_minus_alpha * (dangling_mass * teleport[i] + tphi[i]))
.collect()
}
pub fn springs_step(
phi: &[Fx],
sym_weights: &CsrMatrix,
und_degree: &[Fx],
mu: Fx,
x0: &[Fx],
) -> Vec<Fx> {
let n = phi.len();
let mut wphi = vec![Fx::ZERO; n];
sym_weights.spmv(phi, &mut wphi);
(0..n)
.map(|i| (mu * x0[i] + wphi[i]).div(mu + und_degree[i]))
.collect()
}
const HEAT_DEGREE: usize = 8;
pub fn heat_step(
phi: &[Fx],
sym_weights: &CsrMatrix,
und_degree: &[Fx],
lambda_max: Fx,
tau: Fx,
) -> Vec<Fx> {
if lambda_max.is_zero() {
return phi.to_vec();
}
let s = tau * lambda_max.div(Fx::from_int(2)); let s_safe = Fx::from_int(2);
let m = {
let q = s.div(s_safe);
let fl = q.floor_to_i64();
(if Fx::from_int(fl) < q { fl + 1 } else { fl }).max(1) as usize
};
let s_prime = s.div(Fx::from_int(m as i64));
let c = cheb_coeffs(s_prime, HEAT_DEGREE);
let mut v = phi.to_vec();
for _ in 0..m {
v = cheb_apply(&v, &c, sym_weights, und_degree, lambda_max);
}
v
}
fn cheb_coeffs(s: Fx, degree: usize) -> Vec<Fx> {
let half = s.div(Fx::from_int(2));
let half2 = half * half;
let e_neg = (Fx::ZERO - s).exp();
(0..=degree)
.map(|k| {
let mut term = Fx::ONE;
for _ in 0..k {
term = term * half;
}
let mut kfact = Fx::ONE;
for j in 1..=k {
kfact = kfact * Fx::from_int(j as i64);
}
term = term.div(kfact);
let mut ik = term;
for mm in 0..12usize {
let denom = Fx::from_int(((mm + 1) * (mm + 1 + k)) as i64);
term = term * half2.div(denom);
ik = ik + term;
}
let sign = if k % 2 == 0 {
Fx::ONE
} else {
Fx::ZERO - Fx::ONE
};
let scale = if k == 0 { Fx::ONE } else { Fx::from_int(2) };
scale * sign * e_neg * ik
})
.collect()
}
fn cheb_apply(
phi: &[Fx],
c: &[Fx],
sym_weights: &CsrMatrix,
und_degree: &[Fx],
lambda_max: Fx,
) -> Vec<Fx> {
let n = phi.len();
let two_over_lmax = Fx::from_int(2).div(lambda_max);
let two = Fx::from_int(2);
let ltilde = |v: &[Fx]| -> Vec<Fx> {
let mut av = vec![Fx::ZERO; n];
sym_weights.spmv(v, &mut av);
(0..n)
.map(|i| two_over_lmax * (und_degree[i] * v[i] - av[i]) - v[i])
.collect()
};
let mut t_prev = phi.to_vec(); let mut result: Vec<Fx> = t_prev.iter().map(|&x| c[0] * x).collect();
if c.len() > 1 {
let mut t_cur = ltilde(&t_prev); for i in 0..n {
result[i] = result[i] + c[1] * t_cur[i];
}
for ck in c.iter().skip(2) {
let lt = ltilde(&t_cur);
let t_next: Vec<Fx> = (0..n).map(|i| two * lt[i] - t_prev[i]).collect();
for i in 0..n {
result[i] = result[i] + *ck * t_next[i];
}
t_prev = t_cur;
t_cur = t_next;
}
}
result
}
pub fn normalize_l1(v: &[Fx]) -> Vec<Fx> {
let mut sum = Fx::ZERO;
for &x in v {
sum = sum + x;
}
if sum.is_zero() {
return v.to_vec();
}
v.iter().map(|&x| x.div(sum)).collect()
}