//! HTMRegion: compose SpatialPooler + TemporalMemory into a single step(). use crate::sp::{SpatialPooler, SpatialPoolerConfig}; use crate::tm::{TemporalMemory, TemporalMemoryConfig}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub struct HTMRegionCore { pub sp: SpatialPooler, pub tm: TemporalMemory, } impl HTMRegionCore { pub fn new( input_bits: usize, n_columns: usize, cells_per_column: usize, seed: u64, ) -> Self { let defaults = SpatialPoolerConfig::default(); let sp_cfg = SpatialPoolerConfig { input_bits, n_columns, // Scale potential_radius to at most the input size. potential_radius: defaults.potential_radius.min(input_bits), ..defaults }; let tm_cfg = TemporalMemoryConfig { n_columns, cells_per_column, ..TemporalMemoryConfig::default() }; Self { sp: SpatialPooler::new(sp_cfg, seed), tm: TemporalMemory::new(tm_cfg, seed.wrapping_add(0x9E3779B97F4A7C15)), } } /// Process one timestep. Returns (active_columns_mask, /// active_cells_mask, predicted_cells_mask, anomaly). pub fn step( &mut self, input_sdr: &[bool], learn: bool, ) -> (Vec, Vec, Vec, f32) { let active_cols = self.sp.compute(input_sdr, learn); let mut active_cols_mask = vec![false; self.sp.cfg.n_columns]; for &c in &active_cols { active_cols_mask[c as usize] = true; } let anomaly = self.tm.compute(&active_cols, learn); // active_cells and predictive_cells are stored as Vec already. let active_cells_mask = self.tm.active_cells.clone(); let predicted_cells_mask = self.tm.predictive_cells.clone(); (active_cols_mask, active_cells_mask, predicted_cells_mask, anomaly) } pub fn reset(&mut self) { self.tm.reset(); } /// Process T timesteps in one call. Returns flat `(T*n_columns)` active-column /// mask (u8 0/1) and `(T,)` anomaly scores. /// /// Amortises the per-step Python round-trip for training: one GIL release, /// one copy-out. Used by `HTMLayer.step_many`. pub fn step_many( &mut self, inputs_flat: &[bool], input_bits: usize, t: usize, learn: bool, ) -> (Vec, Vec) { let n_cols = self.sp.cfg.n_columns; debug_assert_eq!(inputs_flat.len(), t * input_bits); let mut cols = vec![0u8; t * n_cols]; let mut anom = vec![0f32; t]; for ti in 0..t { let off = ti * input_bits; let input = &inputs_flat[off..off + input_bits]; let active_cols = self.sp.compute(input, learn); let co = ti * n_cols; for &c in &active_cols { cols[co + c as usize] = 1; } anom[ti] = self.tm.compute(&active_cols, learn); } (cols, anom) } }