use std::collections::VecDeque;
use crate::error::{M1ndError, M1ndResult};
use crate::graph::Graph;
use crate::types::*;
pub const F_HOT: f32 = 1.0;
pub const F_COLD: f32 = 3.7;
pub const SPECTRAL_BANDWIDTH: f32 = 0.8;
pub const IMMUNITY_HOPS: u8 = 2;
pub const SIGMOID_STEEPNESS: f32 = 6.0;
pub const SPECTRAL_BUCKETS: usize = 20;
pub const DENSITY_FLOOR: f32 = 0.3;
pub const DENSITY_CAP: f32 = 2.0;
pub const INHIBITORY_COLD_ATTENUATION: f32 = 0.5;
#[derive(Clone, Copy, Debug)]
pub struct SpectralPulse {
pub node: NodeId,
pub amplitude: FiniteF32,
pub phase: FiniteF32,
pub frequency: PosF32,
pub hops: u8,
pub prev_node: NodeId,
pub recent_path: [NodeId; 3],
}
#[derive(Clone, Debug, Default)]
pub struct SpectralWaveBuffer {
pub hot_amplitudes: Vec<FiniteF32>,
pub hot_frequencies: Vec<FiniteF32>,
pub cold_amplitudes: Vec<FiniteF32>,
pub cold_frequencies: Vec<FiniteF32>,
}
pub struct XlrParams {
pub num_anti_seeds: usize,
pub immunity_hops: u8,
pub min_degree_ratio: FiniteF32,
pub max_jaccard_similarity: FiniteF32,
pub density_clamp_min: FiniteF32,
pub density_clamp_max: FiniteF32,
pub pulse_budget: u64,
}
impl Default for XlrParams {
fn default() -> Self {
Self {
num_anti_seeds: 3,
immunity_hops: IMMUNITY_HOPS,
min_degree_ratio: FiniteF32::new(0.3),
max_jaccard_similarity: FiniteF32::new(0.2),
density_clamp_min: FiniteF32::new(0.3),
density_clamp_max: FiniteF32::new(2.0),
pulse_budget: 50_000,
}
}
}
#[derive(Clone, Debug)]
pub struct XlrResult {
pub activations: Vec<(NodeId, FiniteF32)>,
pub anti_seeds: Vec<NodeId>,
pub fallback_to_hot_only: bool,
pub pulses_processed: u64,
}
pub struct AdaptiveXlrEngine {
params: XlrParams,
}
impl AdaptiveXlrEngine {
pub fn new(params: XlrParams) -> Self {
Self { params }
}
pub fn with_defaults() -> Self {
Self::new(XlrParams::default())
}
pub fn query(
&self,
graph: &Graph,
seeds: &[(NodeId, FiniteF32)],
config: &PropagationConfig,
) -> M1ndResult<XlrResult> {
let n = graph.num_nodes() as usize;
if n == 0 || seeds.is_empty() {
return Ok(XlrResult {
activations: Vec::new(),
anti_seeds: Vec::new(),
fallback_to_hot_only: false,
pulses_processed: 0,
});
}
let seed_nodes: Vec<NodeId> = seeds.iter().map(|s| s.0).collect();
let anti_seeds = self.pick_anti_seeds(graph, &seed_nodes)?;
let immunity = self.compute_immunity(graph, &seed_nodes)?;
let hot_freq = PosF32::new(F_HOT).unwrap();
let half_budget = self.params.pulse_budget / 2;
let hot_pulses = self.propagate_spectral(graph, seeds, hot_freq, config, half_budget)?;
let cold_freq = PosF32::new(F_COLD).unwrap();
let anti_seed_pairs: Vec<(NodeId, FiniteF32)> =
anti_seeds.iter().map(|&n| (n, FiniteF32::ONE)).collect();
let cold_pulses =
self.propagate_spectral(graph, &anti_seed_pairs, cold_freq, config, half_budget)?;
let total_pulses = hot_pulses.len() as u64 + cold_pulses.len() as u64;
let mut hot_amp = vec![0.0f32; n];
let mut cold_amp = vec![0.0f32; n];
for p in &hot_pulses {
let idx = p.node.as_usize();
if idx < n {
hot_amp[idx] += p.amplitude.get().abs();
}
}
for p in &cold_pulses {
let idx = p.node.as_usize();
if idx < n {
cold_amp[idx] += p.amplitude.get().abs();
}
}
let mut activations = Vec::new();
let mut all_zero = true;
let avg_deg = graph.avg_degree();
for (i, &hot) in hot_amp.iter().enumerate().take(n) {
if hot <= 0.0 {
continue;
}
let immune = if i < immunity.len() {
immunity[i]
} else {
false
};
let effective_cold = if immune { 0.0 } else { cold_amp[i] };
let raw = hot - effective_cold;
let out_deg = {
let lo = graph.csr.offsets[i] as usize;
let hi = if i + 1 < graph.csr.offsets.len() {
graph.csr.offsets[i + 1] as usize
} else {
lo
};
(hi - lo) as f32
};
let density = if avg_deg > 0.0 {
(out_deg / avg_deg).clamp(DENSITY_FLOOR, DENSITY_CAP)
} else {
1.0
};
let gated = Self::sigmoid_gate(FiniteF32::new(raw * density));
let val = gated.get();
if val > 0.01 {
activations.push((NodeId::new(i as u32), gated));
all_zero = false;
}
}
let fallback = all_zero && !hot_pulses.is_empty();
if fallback {
activations.clear();
for (i, &) in hot_amp.iter().enumerate().take(n) {
if amp > 0.01 {
activations.push((NodeId::new(i as u32), FiniteF32::new(amp)));
}
}
}
activations.sort_by(|a, b| b.1.cmp(&a.1));
Ok(XlrResult {
activations,
anti_seeds,
fallback_to_hot_only: fallback,
pulses_processed: total_pulses,
})
}
pub fn pick_anti_seeds(&self, graph: &Graph, seeds: &[NodeId]) -> M1ndResult<Vec<NodeId>> {
let n = graph.num_nodes() as usize;
if n == 0 || seeds.is_empty() {
return Ok(Vec::new());
}
let mut seed_set = vec![false; n];
let mut seed_neighbors = vec![false; n];
for &s in seeds {
let idx = s.as_usize();
if idx < n {
seed_set[idx] = true;
seed_neighbors[idx] = true;
let range = graph.csr.out_range(s);
for j in range {
let tgt = graph.csr.targets[j].as_usize();
if tgt < n {
seed_neighbors[tgt] = true;
}
}
}
}
let avg_seed_degree: f32 = if seeds.is_empty() {
0.0
} else {
let sum: usize = seeds
.iter()
.map(|s| {
let r = graph.csr.out_range(*s);
r.end - r.start
})
.sum();
sum as f32 / seeds.len() as f32
};
let mut candidates: Vec<(NodeId, f32)> = Vec::new();
for i in 0..n {
if seed_set[i] {
continue; }
let range = graph.csr.out_range(NodeId::new(i as u32));
let degree = (range.end - range.start) as f32;
if avg_seed_degree > 0.0 {
let ratio = degree / avg_seed_degree;
if ratio < self.params.min_degree_ratio.get() {
continue;
}
}
let mut intersection = 0usize;
let mut union_size = 0usize;
for j in range.clone() {
let tgt = graph.csr.targets[j].as_usize();
if tgt < n {
union_size += 1;
if seed_neighbors[tgt] {
intersection += 1;
}
}
}
let jaccard = if union_size > 0 {
intersection as f32 / union_size as f32
} else {
0.0
};
if jaccard > self.params.max_jaccard_similarity.get() {
continue; }
let distance_score = if seed_neighbors[i] { 0.0 } else { 1.0 };
let score = distance_score + (1.0 - jaccard);
candidates.push((NodeId::new(i as u32), score));
}
candidates.sort_by(|a, b| b.1.total_cmp(&a.1));
let result: Vec<NodeId> = candidates
.iter()
.take(self.params.num_anti_seeds)
.map(|c| c.0)
.collect();
Ok(result)
}
pub fn compute_immunity(&self, graph: &Graph, seeds: &[NodeId]) -> M1ndResult<Vec<bool>> {
let n = graph.num_nodes() as usize;
let mut immune = vec![false; n];
let mut queue = VecDeque::new();
let mut dist = vec![u8::MAX; n];
for &s in seeds {
let idx = s.as_usize();
if idx < n {
queue.push_back((s, 0u8));
dist[idx] = 0;
immune[idx] = true;
}
}
while let Some((node, d)) = queue.pop_front() {
if d >= self.params.immunity_hops {
continue;
}
let range = graph.csr.out_range(node);
for j in range {
let tgt = graph.csr.targets[j];
let tgt_idx = tgt.as_usize();
if tgt_idx < n && d + 1 < dist[tgt_idx] {
dist[tgt_idx] = d + 1;
immune[tgt_idx] = true;
queue.push_back((tgt, d + 1));
}
}
}
Ok(immune)
}
pub fn propagate_spectral(
&self,
graph: &Graph,
origins: &[(NodeId, FiniteF32)],
frequency: PosF32,
config: &PropagationConfig,
budget: u64,
) -> M1ndResult<Vec<SpectralPulse>> {
let n = graph.num_nodes() as usize;
let decay = config.decay.get();
let threshold = config.threshold.get();
let mut pulses_out = Vec::new();
let mut pulse_count = 0u64;
let mut queue: VecDeque<SpectralPulse> = VecDeque::new();
for &(node, amp) in origins {
if node.as_usize() >= n {
continue;
}
let pulse = SpectralPulse {
node,
amplitude: amp,
phase: FiniteF32::ZERO,
frequency,
hops: 0,
prev_node: node,
recent_path: [node; 3],
};
queue.push_back(pulse);
pulses_out.push(pulse);
pulse_count += 1;
}
let max_depth = config.max_depth.min(20);
while let Some(pulse) = queue.pop_front() {
if pulse_count >= budget {
break; }
if pulse.hops >= max_depth {
continue;
}
if pulse.amplitude.get().abs() < threshold {
continue;
}
let range = graph.csr.out_range(pulse.node);
for j in range {
let tgt = graph.csr.targets[j];
if tgt == pulse.prev_node {
continue; }
let w = graph.csr.read_weight(EdgeIdx::new(j as u32)).get();
let is_inhib = graph.csr.inhibitory[j];
let mut new_amp = pulse.amplitude.get() * w * decay;
if is_inhib {
new_amp *= INHIBITORY_COLD_ATTENUATION;
}
if new_amp.abs() < threshold {
continue;
}
let phase_advance = 2.0 * std::f32::consts::PI * frequency.get();
let new_phase = (pulse.phase.get() + phase_advance) % (2.0 * std::f32::consts::PI);
let mut rp = pulse.recent_path;
rp[2] = rp[1];
rp[1] = rp[0];
rp[0] = pulse.node;
let new_pulse = SpectralPulse {
node: tgt,
amplitude: FiniteF32::new(new_amp),
phase: FiniteF32::new(new_phase),
frequency,
hops: pulse.hops + 1,
prev_node: pulse.node,
recent_path: rp,
};
pulses_out.push(new_pulse);
pulse_count += 1;
if pulse_count < budget {
queue.push_back(new_pulse);
}
}
}
Ok(pulses_out)
}
pub fn spectral_overlap(hot_freqs: &[FiniteF32], cold_freqs: &[FiniteF32]) -> FiniteF32 {
if hot_freqs.is_empty() || cold_freqs.is_empty() {
return FiniteF32::ZERO;
}
let mut hot_buckets = [0.0f32; SPECTRAL_BUCKETS];
let mut cold_buckets = [0.0f32; SPECTRAL_BUCKETS];
let max_freq = 10.0f32; let bucket_width = max_freq / SPECTRAL_BUCKETS as f32;
for f in hot_freqs {
let b = ((f.get() / bucket_width) as usize).min(SPECTRAL_BUCKETS - 1);
hot_buckets[b] += 1.0;
}
for f in cold_freqs {
let b = ((f.get() / bucket_width) as usize).min(SPECTRAL_BUCKETS - 1);
cold_buckets[b] += 1.0;
}
let mut overlap = 0.0f32;
let mut hot_total = 0.0f32;
for b in 0..SPECTRAL_BUCKETS {
overlap += hot_buckets[b].min(cold_buckets[b]);
hot_total += hot_buckets[b];
}
if hot_total > 0.0 {
FiniteF32::new(overlap / hot_total)
} else {
FiniteF32::ZERO
}
}
pub fn sigmoid_gate(net_signal: FiniteF32) -> FiniteF32 {
let x = net_signal.get() * SIGMOID_STEEPNESS;
let clamped = x.clamp(-20.0, 20.0);
let result = 1.0 / (1.0 + (-clamped).exp());
FiniteF32::new(result)
}
}
static_assertions::assert_impl_all!(AdaptiveXlrEngine: Send, Sync);