use super::super::state::Id;
use super::weights::Axon;
pub const PHI_BITS: u32 = 60;
pub const LN_BITS: u32 = 40;
pub const GRAD_BITS: u32 = PHI_BITS + LN_BITS;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FocusGraph {
pub particles: Vec<Id>,
pub src: Vec<u32>,
pub dst: Vec<u32>,
pub weight: Vec<u128>,
}
impl FocusGraph {
pub fn build(axons: &[Axon<u128>]) -> FocusGraph {
let mut particles: Vec<Id> = Vec::with_capacity(axons.len() * 2);
for (p, q, _) in axons {
particles.push(*p);
particles.push(*q);
}
particles.sort_unstable();
particles.dedup();
let index = |x: &Id| particles.binary_search(x).expect("endpoint indexed") as u32;
let mut g = FocusGraph {
src: Vec::with_capacity(axons.len()),
dst: Vec::with_capacity(axons.len()),
weight: Vec::with_capacity(axons.len()),
particles: Vec::new(),
};
for (p, q, w) in axons {
debug_assert!(*w > 0);
g.src.push(index(p));
g.dst.push(index(q));
g.weight.push(*w);
}
g.particles = particles;
g
}
pub fn n(&self) -> usize {
self.particles.len()
}
pub fn edges(&self) -> usize {
self.src.len()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Focus {
pub phi: Vec<u64>,
pub syntropy: i128,
}
pub trait FocusEngine: Send + Sync {
fn id(&self) -> &'static str;
fn focus(&self, g: &FocusGraph) -> Focus;
fn gradient(&self, g: &FocusGraph) -> Vec<i128>;
}
pub struct Tru {
pub params: tru::focusing::epoch::EpochParams,
}
impl Tru {
pub fn new(threads: usize) -> Tru {
let mut params = tru::focusing::epoch::EpochParams::default();
params.steps = super::super::params::PHI_STEPS;
params.threads = threads.max(1);
Tru { params }
}
pub fn for_node() -> Tru {
let cores = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
Tru::new(cores.saturating_sub(1).clamp(1, 8))
}
fn axons(g: &FocusGraph) -> Vec<tru::focusing::epoch::Axon> {
(0..g.edges())
.map(|e| tru::focusing::epoch::Axon { from: g.particles[g.src[e] as usize], to: g.particles[g.dst[e] as usize], weight: g.weight[e] })
.collect()
}
}
impl Default for Tru {
fn default() -> Self {
Tru::new(1)
}
}
impl FocusEngine for Tru {
fn id(&self) -> &'static str {
"tru-epoch-e660d1e"
}
fn focus(&self, g: &FocusGraph) -> Focus {
let f = tru::focusing::epoch::phi(&Self::axons(g), &self.params);
debug_assert_eq!(f.particles, g.particles, "both index particles by ascending id");
Focus { phi: f.phi, syntropy: f.syntropy }
}
fn gradient(&self, g: &FocusGraph) -> Vec<i128> {
tru::focusing::epoch::gradient(&Self::axons(g), &self.params).by_axon
}
}
const LN2_Q40: i128 = 762_123_384_786;
pub fn log2_q40(v: u128, frac_bits: u32) -> i128 {
debug_assert!(v > 0);
let k = 127 - v.leading_zeros(); let mut y: u128 = if k >= 62 { v >> (k - 62) } else { v << (62 - k) };
let mut frac: i128 = 0;
for i in 1..=LN_BITS {
y = (y * y) >> 62;
if y >= 1u128 << 63 {
y >>= 1;
frac |= 1 << (LN_BITS - i);
}
}
((k as i128 - frac_bits as i128) << LN_BITS) + frac
}
pub fn ln_q40(v: u128, frac_bits: u32) -> i128 {
(log2_q40(v, frac_bits) * LN2_Q40) >> LN_BITS
}
pub struct Reference {
pub steps: usize,
}
impl Default for Reference {
fn default() -> Self {
Reference { steps: super::super::params::PHI_STEPS }
}
}
const ALPHA_NUM: u128 = 15;
const ALPHA_DEN: u128 = 100;
const KEEP_NUM: u128 = ALPHA_DEN - ALPHA_NUM;
const T_BITS: u32 = 62;
struct Walk {
out: Vec<u128>,
t: Vec<u64>,
w2: u128,
prior: Vec<u128>,
}
impl Walk {
fn new(g: &FocusGraph) -> Walk {
let mut out = vec![0u128; g.n()];
let mut deg = vec![0u128; g.n()];
for e in 0..g.edges() {
let w = g.weight[e];
out[g.src[e] as usize] += w;
deg[g.src[e] as usize] += w;
deg[g.dst[e] as usize] += w;
}
let t = g
.src
.iter()
.zip(&g.weight)
.map(|(s, w)| {
let d = out[*s as usize];
let k = (128 - d.leading_zeros()).saturating_sub(64);
(((*w >> k) << T_BITS) / (d >> k)) as u64
})
.collect();
let w2: u128 = deg.iter().sum();
let prior = deg.iter().map(|d| (d << PHI_BITS) / w2).collect();
Walk { out, t, w2, prior }
}
fn prior_dot(&self, v: &[i128]) -> i128 {
self.prior.iter().zip(v).map(|(p, x)| (*p as i128 * x) >> PHI_BITS).sum()
}
}
impl Reference {
fn forward(&self, g: &FocusGraph, w: &Walk) -> Vec<u64> {
let n = g.n() as u128;
let one = 1u128 << PHI_BITS;
let mut phi = vec![(one / n) as u64; g.n()];
let mut inflow = vec![0u128; g.n()];
for _ in 0..self.steps {
inflow.iter_mut().for_each(|x| *x = 0);
let dangling: u128 = w.out.iter().zip(&phi).filter(|(d, _)| **d == 0).map(|(_, f)| *f as u128).sum();
for e in 0..g.edges() {
inflow[g.dst[e] as usize] += (phi[g.src[e] as usize] as u128 * w.t[e] as u128) >> T_BITS;
}
for (j, f) in phi.iter_mut().enumerate() {
let pi = w.prior[j];
*f = ((ALPHA_NUM * pi + KEEP_NUM * (inflow[j] + ((dangling * pi) >> PHI_BITS))) / ALPHA_DEN) as u64;
}
}
phi
}
fn syntropy(phi: &[u64]) -> i128 {
let n = phi.len() as u128;
phi.iter().filter(|f| **f > 0).map(|f| (*f as i128 * ln_q40(n * *f as u128, PHI_BITS)) >> LN_BITS).sum()
}
fn expect(g: &FocusGraph, w: &Walk, lambda: &[i128]) -> Vec<i128> {
let jump = w.prior_dot(lambda);
let mut out: Vec<i128> = w.out.iter().map(|d| if *d == 0 { jump } else { 0 }).collect();
for e in 0..g.edges() {
out[g.src[e] as usize] += (lambda[g.dst[e] as usize] * w.t[e] as i128) >> T_BITS;
}
out
}
}
impl FocusEngine for Reference {
fn id(&self) -> &'static str {
"soft3-reference-prior-walk-v1"
}
fn focus(&self, g: &FocusGraph) -> Focus {
if g.n() == 0 {
return Focus::default();
}
let w = Walk::new(g);
let phi = self.forward(g, &w);
let syntropy = Self::syntropy(&phi);
Focus { phi, syntropy }
}
fn gradient(&self, g: &FocusGraph) -> Vec<i128> {
if g.n() == 0 {
return Vec::new();
}
let w = Walk::new(g);
let phi = self.forward(g, &w);
let n = g.n() as u128;
let grad_j: Vec<i128> = phi.iter().map(|f| ln_q40(n * (*f).max(1) as u128, PHI_BITS) + (1 << LN_BITS)).collect();
let mut lambda = vec![0i128; g.n()];
for _ in 0..self.steps {
let pl = Self::expect(g, &w, &lambda);
for (i, l) in lambda.iter_mut().enumerate() {
*l = grad_j[i] + (KEEP_NUM as i128 * pl[i]).div_euclid(ALPHA_DEN as i128);
}
}
let pl = Self::expect(g, &w, &lambda);
let one = 1i128 << PHI_BITS;
let dangling: i128 = w.out.iter().zip(&phi).filter(|(d, _)| **d == 0).map(|(_, f)| *f as i128).sum();
let c = (ALPHA_NUM as i128 * one + KEEP_NUM as i128 * dangling).div_euclid(ALPHA_DEN as i128); let mean = w.prior_dot(&lambda);
(0..g.edges())
.map(|e| {
let (s, d) = (g.src[e] as usize, g.dst[e] as usize);
let walk = (KEEP_NUM as i128 * phi[s] as i128 * (lambda[d] - pl[s])).div_euclid(ALPHA_DEN as i128 * w.out[s] as i128);
let prior = (c * (lambda[s] + lambda[d] - 2 * mean)).div_euclid(w.w2 as i128);
walk + prior
})
.collect()
}
}