use super::fixed::{GRAD_BITS, LN_BITS, PHI_BITS, Q_BITS, mul_shift_div, shr_i};
use super::graph::EpochGraph;
use super::kernel::{Coeffs, row_value, sym_adj, sym_fwd, teleported};
pub(crate) fn edge_gradient(
g: &EpochGraph,
c: &Coeffs,
b: &[usize],
phi: &[u64],
lam: &[i64],
) -> Vec<i128> {
let n = g.n();
let kk = c.tau_k.len();
let mut v: Vec<Vec<u64>> = Vec::with_capacity(kk);
v.push(phi.to_vec());
for i in 1..kk {
let mut nx = vec![0u64; n];
sym_fwd(g, b, &v[i - 1], &mut nx);
v.push(nx);
}
let mut u: Vec<Vec<i64>> = Vec::with_capacity(kk + 1);
u.push(lam.to_vec());
for j in 1..=kk {
let mut nx = vec![0i64; n];
sym_adj(g, b, &u[j - 1], &mut nx);
u.push(nx);
}
let mass: u128 = phi.iter().map(|&x| x as u128).sum();
let dm = teleported(g, phi);
let ct: i128 = shr_i(
g.tele
.iter()
.zip(lam)
.map(|(&t, &l)| t as i128 * l as i128)
.sum(),
PHI_BITS,
);
let dm_q = (dm << Q_BITS).checked_div(mass).unwrap_or(0) as i128;
let c_dq = c.alpha as i128 + shr_i(c.oma as i128 * dm_q, Q_BITS);
let q = |a: i128, b: i128| shr_i(a * b, Q_BITS);
let k_d = q(c.lam_d as i128, c.oma as i128);
let (gd, gi) = (g.ground.degree as i128, g.ground.inbound as i128);
let k_gp = q(k_d, gd);
let k_gq = q(k_d, gd + gi);
let mut f = vec![0i64; n];
super::kernel::par_fill(b, &mut f, |i, slot| {
*slot = row_value(g, i, lam, ct);
0i128
});
let k_t = q(c.lam_d as i128, c_dq) + q(c.lam_s as i128, c.mu1 as i128);
let k_s = q(c.lam_s as i128, c.mu2 as i128);
let k_h = c.lam_he as i128;
let total = g.total.min(u64::MAX as u128) as u64;
let s_bits = GRAD_BITS - PHI_BITS;
let heat = |a: usize, bb: usize| -> i128 {
let mut y = 0i128;
for k in 1..=kk {
let mut acc = 0i128;
for j in 0..k {
acc += v[k - 1 - j][a] as i128 * (u[j][bb] as i128 - u[j + 1][a] as i128);
}
y += shr_i(c.c_k[k - 1] as i128 * shr_i(acc, LN_BITS), Q_BITS);
}
y
};
let div = |num: i128, den: u64| {
if den == 0 {
0
} else {
mul_shift_div(num, s_bits, den)
}
};
let row = |s: usize, out: &mut [i128]| {
let pd = f[s] as i128;
let n_gs = shr_i(phi[s] as i128 * (ct - pd), LN_BITS);
let (fs, ls, u1s) = (phi[s] as i128, lam[s] as i128, u[1][s] as i128);
for (k, e) in (g.out_ptr[s] as usize..g.out_ptr[s + 1] as usize).enumerate() {
let d = g.out_dst[e] as usize;
let (fd, ld, u1d) = (phi[d] as i128, lam[d] as i128, u[1][d] as i128);
let n_d = shr_i(fs * (ld - pd), LN_BITS);
let n_t = (ls + ld - 2 * ct) << (PHI_BITS - LN_BITS);
let n_ss = shr_i(fs * (ld - u1s), LN_BITS);
let n_sd = shr_i(fd * (ls - u1d), LN_BITS);
let n_gd = shr_i(fd * (ct - f[d] as i128), LN_BITS);
let gsum = div(q(k_d, n_d) + q(k_gp, n_gs), g.norm_w[s])
.saturating_add(div(q(k_gq, n_gd), g.norm_w[d]))
.saturating_add(div(q(k_t, n_t), total))
.saturating_add(div(q(k_s, n_ss) + q(k_h, heat(s, d)), g.deg[s]))
.saturating_add(div(q(k_s, n_sd) + q(k_h, heat(d, s)), g.deg[d]));
out[k] = if g.shift == 0 {
gsum
} else {
shr_i(gsum, g.shift)
};
}
};
let m = g.directed_nnz();
let mut grad = vec![0i128; m];
if b.len() <= 2 {
for s in 0..n {
let (lo, hi) = (g.out_ptr[s] as usize, g.out_ptr[s + 1] as usize);
row(s, &mut grad[lo..hi]);
}
return grad;
}
let mut chunks: Vec<(usize, usize, &mut [i128])> = Vec::new();
let mut rest: &mut [i128] = &mut grad;
for w in b.windows(2) {
let len = (g.out_ptr[w[1]] - g.out_ptr[w[0]]) as usize;
let (head, tail) = rest.split_at_mut(len);
chunks.push((w[0], w[1], head));
rest = tail;
}
let row = &row;
std::thread::scope(|sc| {
for (lo, hi, out) in chunks {
sc.spawn(move || {
let base = g.out_ptr[lo] as usize;
for s in lo..hi {
let (a, z) = (
g.out_ptr[s] as usize - base,
g.out_ptr[s + 1] as usize - base,
);
row(s, &mut out[a..z]);
}
});
}
});
grad
}