use rayon::prelude::*;
use crate::arena::{Lineage, PsycheArena};
use crate::graph::{BondGraph, BOND_PRUNE_THRESHOLD};
#[derive(Debug, Clone)]
pub struct DecayConfig {
pub tick_interval_ms: u64,
pub min_energy_threshold: f32,
pub bond_prune_threshold: f32,
pub parallel: bool,
}
impl Default for DecayConfig {
fn default() -> Self {
Self {
tick_interval_ms: 100,
min_energy_threshold: 0.001,
bond_prune_threshold: BOND_PRUNE_THRESHOLD,
parallel: true,
}
}
}
pub struct DecayLUT {
data: Vec<f32>,
rate_buckets: usize,
time_buckets: usize,
time_boundaries: Vec<f32>,
}
impl DecayLUT {
pub fn new() -> Self {
const RATE_BUCKETS: usize = 256;
const TIME_BUCKETS: usize = 32;
let time_boundaries: Vec<f32> = vec![
0.0, 0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0, 30.0, 60.0, 120.0, 300.0, 600.0, 900.0,
1800.0, 3600.0, 7200.0, 14400.0, 21600.0, 43200.0, 86400.0, 172800.0, 259200.0,
432000.0, 604800.0, 1209600.0, 2592000.0, 5184000.0, 7776000.0, 15552000.0, 31104000.0,
];
let mut data = Vec::with_capacity(RATE_BUCKETS * TIME_BUCKETS);
for r in 0..RATE_BUCKETS {
let rate = if r == 0 {
0.0
} else {
(10.0_f32).powf((r as f32 / 255.0) * 3.0 - 6.0)
};
for &elapsed in time_boundaries.iter().take(TIME_BUCKETS) {
let factor = (-rate * elapsed).exp();
data.push(factor);
}
}
Self {
data,
rate_buckets: RATE_BUCKETS,
time_buckets: TIME_BUCKETS,
time_boundaries,
}
}
#[inline]
pub fn get(&self, decay_rate: f32, elapsed_secs: f32) -> f32 {
let rate_bucket = self.rate_to_bucket(decay_rate);
let time_bucket = self.time_to_bucket(elapsed_secs);
self.data[rate_bucket * self.time_buckets + time_bucket]
}
#[inline]
fn rate_to_bucket(&self, rate: f32) -> usize {
if rate <= 0.0 {
return 0;
}
let log_rate = rate.log10();
let bucket = ((log_rate + 6.0) / 3.0 * 255.0).round() as usize;
bucket.min(self.rate_buckets - 1)
}
#[inline]
fn time_to_bucket(&self, elapsed: f32) -> usize {
for (i, &boundary) in self.time_boundaries.iter().enumerate().rev() {
if elapsed >= boundary {
return i;
}
}
0
}
}
impl Default for DecayLUT {
fn default() -> Self {
Self::new()
}
}
pub struct DecayEngine {
config: DecayConfig,
lut: DecayLUT,
last_tick: u64,
}
impl DecayEngine {
pub fn new(config: DecayConfig) -> Self {
Self {
config,
lut: DecayLUT::new(),
last_tick: now_nanos(),
}
}
#[inline]
pub fn decay_factor(&self, decay_rate: f32, elapsed_secs: f32) -> f32 {
self.lut.get(decay_rate, elapsed_secs)
}
pub fn tick_psyche(&mut self, psyche: &mut PsycheArena) -> DecayTickResult {
let now = now_nanos();
let mut dead_count = 0;
let mut processed = 0;
for (_id, lineage) in psyche.iter_mut() {
processed += 1;
let energy = lineage.current_energy();
if energy < self.config.min_energy_threshold {
dead_count += 1;
}
}
self.last_tick = now;
DecayTickResult {
processed,
dead_count,
elapsed_ms: (now - self.last_tick) / 1_000_000,
}
}
pub fn prune_bonds(&self, bonds: &mut BondGraph) -> usize {
bonds.prune(self.config.bond_prune_threshold)
}
pub fn batch_decay_factors(&self, lineages: &[Lineage]) -> Vec<f32> {
if self.config.parallel {
lineages.par_iter().map(|l| l.current_energy()).collect()
} else {
lineages.iter().map(|l| l.current_energy()).collect()
}
}
pub fn process_gc(
&self,
psyche: &mut PsycheArena,
cortex: &mut crate::setun::Cortex,
) -> GcResult {
use crate::setun::{dimension, Trit};
let mut processed = 0;
let mut retained = 0; let mut pending = 0; let mut disposables = Vec::new();
let preservation_bias =
cortex.personality().get(dimension::PRESERVATION).weight() as f64 * 0.1;
for (id, lineage) in psyche.iter_mut() {
processed += 1;
let energy = lineage.current_energy() as f64;
let viability_score = energy - self.config.min_energy_threshold as f64;
let adjusted_score = viability_score + preservation_bias;
let decision = cortex.decide(adjusted_score);
match decision {
Trit::True => {
cortex.retention_mut().restore(id.index());
retained += 1;
}
Trit::Unknown => {
if cortex.retention_mut().mark_or_tick(id.index()) {
disposables.push(id);
} else {
pending += 1;
}
}
Trit::False => {
if cortex.retention_mut().mark_or_tick(id.index()) {
disposables.push(id);
} else {
pending += 1;
}
}
}
}
let pruned = disposables.len();
for id in disposables {
psyche.free(id);
}
GcResult {
processed,
retained,
pending,
pruned,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct GcResult {
pub processed: usize,
pub retained: usize,
pub pending: usize,
pub pruned: usize,
}
impl Default for DecayEngine {
fn default() -> Self {
Self::new(DecayConfig::default())
}
}
#[derive(Debug, Clone, Copy)]
pub struct DecayTickResult {
pub processed: usize,
pub dead_count: usize,
pub elapsed_ms: u64,
}
#[inline]
fn now_nanos() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decay_lut_creation() {
let lut = DecayLUT::new();
assert!(!lut.data.is_empty());
}
#[test]
fn test_decay_lut_zero_rate() {
let lut = DecayLUT::new();
assert_eq!(lut.get(0.0, 0.0), 1.0);
assert_eq!(lut.get(0.0, 3600.0), 1.0);
}
#[test]
fn test_decay_lut_fast_decay() {
let lut = DecayLUT::new();
let factor_1s = lut.get(0.5, 1.0);
let factor_10s = lut.get(0.5, 10.0);
assert!(factor_10s < factor_1s);
}
#[test]
fn test_decay_engine_creation() {
let engine = DecayEngine::default();
assert!(engine.decay_factor(0.001, 0.0) > 0.99);
}
#[test]
fn test_decay_tick() {
let mut engine = DecayEngine::default();
let mut psyche = PsycheArena::with_capacity(100);
psyche.alloc(Lineage::new(0.5));
psyche.alloc(Lineage::new(0.003));
let result = engine.tick_psyche(&mut psyche);
assert_eq!(result.processed, 2);
}
#[test]
fn test_process_gc_with_retention() {
use crate::setun::{dimension, Cortex, Octet, Trit};
let engine = DecayEngine::default();
let mut psyche = PsycheArena::with_capacity(100);
let mut personality = Octet::neutral();
personality.set(dimension::PRESERVATION, Trit::True);
let mut cortex = Cortex::new(personality);
psyche.alloc(Lineage::new(0.9)); psyche.alloc(Lineage::new(0.05)); psyche.alloc(Lineage::new(0.03));
let result1 = engine.process_gc(&mut psyche, &mut cortex);
assert_eq!(result1.processed, 3);
assert_eq!(result1.retained, 1); assert_eq!(result1.pending, 2); assert_eq!(result1.pruned, 0); assert_eq!(cortex.pending_removal_count(), 2);
let result2 = engine.process_gc(&mut psyche, &mut cortex);
assert_eq!(result2.pruned, 0); assert_eq!(result2.pending, 2);
let result3 = engine.process_gc(&mut psyche, &mut cortex);
assert_eq!(result3.pruned, 0);
let result4 = engine.process_gc(&mut psyche, &mut cortex);
assert_eq!(result4.pruned, 2); assert_eq!(psyche.len(), 1); assert_eq!(cortex.pending_removal_count(), 0);
}
}