enki 0.0.1

Asemantic computation and Church-style isomorphism using combinatory logic.
Documentation
use crate::molecule::Molecule;
use crate::tree::FenwickTree;
use rand::Rng;
use std::collections::HashMap;

/// Dynamic Reactor
pub struct Reactor {
    molecules: Vec<Molecule>,       // representative molecules
    population: Vec<usize>,         // population
    population_tree: FenwickTree,   // Binary tree of population for sampling
    index_map: HashMap<u64, usize>, // hash -> index in molecules
}

impl Reactor {
    pub fn new() -> Self {
        Self {
            molecules: Vec::new(),
            population: Vec::new(),
            population_tree: FenwickTree::new(),
            index_map: HashMap::new(),
        }
    }

    /// Add a molecule or increment existing
    pub fn add_or_increment(&mut self, mol: Molecule, hash: u64, delta: usize) {
        if let Some(&idx) = self.index_map.get(&hash) {
            self.population[idx] += delta;
            self.population_tree.update(idx, delta as isize);
        } else {
            let idx = self.molecules.len();
            self.molecules.push(mol);
            self.population.push(delta);
            self.index_map.insert(hash, idx);
            self.population_tree.push(delta);
        }
    }

    /// Decrement count of molecule at index i
    pub fn decrement(&mut self, idx: usize, delta: usize) {
        assert!(self.population[idx] >= delta);
        self.population[idx] -= delta;
        self.population_tree.update(idx, -(delta as isize));
        // optional: remove if count==0 and cleanup
    }

    /// Weighted sampling
    pub fn sample_weighted<'a>(&'a self, rng: &mut impl Rng) -> Option<&'a Molecule> {
        let total = self.population_tree.total_sum();
        if total == 0 {
            return None;
        }
        let r = rng.random_range(1..=total);
        let idx = self.population_tree.lower_bound(r)?;
        Some(&self.molecules[idx])
    }

    /// Optional: cleanup zero-count species to save memory
    pub fn prune_zero_population(&mut self) {
        let mut new_molecules = Vec::new();
        let mut new_population = Vec::new();
        let mut new_index_map = HashMap::new();
        self.population_tree = FenwickTree::new();

        for (i, mol) in self.molecules.iter().enumerate() {
            if self.population[i] > 0 {
                let idx = new_molecules.len();
                new_molecules.push(mol.clone());
                new_population.push(self.population[i]);
                let hash = mol.wl_hash();
                new_index_map.insert(hash, idx);
                self.population_tree.push(self.population[i]);
            }
        }

        self.molecules = new_molecules;
        self.population = new_population;
        self.index_map = new_index_map;
    }
}