Skip to main content

feagi_npu_plasticity/
executor.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Plasticity Executor Abstraction Layer
5//!
6//! Provides backend-agnostic execution models for plasticity computation:
7//! - AsyncPlasticityExecutor: For std environments (CPU, CUDA, WebGPU)
8//! - SyncPlasticityExecutor: For no_std environments (RTOS, embedded) [Future]
9//!
10//! This abstraction allows FEAGI to run plasticity optimally on different backends
11//! without code duplication or runtime overhead.
12
13use crate::memory_neuron_array::MemoryNeuronDetail;
14use crate::memory_stats_cache::MemoryStatsCache;
15use crate::service::{
16    MemoryCorticalAreaRuntimeInfo, PlasticityCommand, PlasticityConfig, PlasticityService,
17};
18use std::sync::{Arc, Mutex};
19use tracing::{debug, warn};
20
21/// Trait for executing plasticity computation
22///
23/// Implementations of this trait define how plasticity is executed on different backends.
24/// - Async implementations run plasticity in a background thread (std environments)
25/// - Sync implementations run plasticity inline in burst loop (no_std environments)
26pub trait PlasticityExecutor: Send + Sync {
27    /// Notify the executor of a completed burst
28    ///
29    /// # Arguments
30    /// * `timestep` - The timestep/burst count that just completed
31    fn notify_burst(&self, timestep: u64);
32
33    /// Drain pending commands from the executor
34    ///
35    /// Commands are generated by plasticity computation and need to be
36    /// applied to the NPU (e.g., inject memory neurons to FCL).
37    ///
38    /// # Returns
39    /// Vector of plasticity commands to execute
40    fn drain_commands(&self) -> Vec<PlasticityCommand>;
41
42    /// Register a memory area with the plasticity system
43    ///
44    /// # Arguments
45    /// * `area_idx` - Cortical area index
46    /// * `area_name` - Human-readable name for the area
47    /// * `temporal_depth` - Number of historical timesteps to consider
48    /// * `upstream_areas` - Cortical indices of areas that project to this memory area
49    /// * `lifecycle_config` - Optional lifecycle configuration for memory neurons
50    /// * `mp_learning_enabled` - Whether to capture membrane potentials in replay frames
51    fn register_memory_area(
52        &self,
53        area_idx: u32,
54        area_name: String,
55        temporal_depth: u32,
56        upstream_areas: Vec<u32>,
57        lifecycle_config: Option<crate::MemoryNeuronLifecycleConfig>,
58        mp_learning_enabled: bool,
59    );
60
61    /// Start the executor (for async implementations)
62    ///
63    /// Default implementation is no-op (for sync implementations).
64    fn start(&mut self) {}
65
66    /// Stop the executor (for async implementations)
67    ///
68    /// Default implementation is no-op (for sync implementations).
69    fn stop(&mut self) {}
70
71    /// Check if the executor is running
72    fn is_running(&self) -> bool {
73        false // Default for sync implementations
74    }
75}
76
77/// Async plasticity executor for std environments
78///
79/// Runs plasticity computation in a background thread, allowing the burst loop
80/// to continue processing while patterns are detected and memory neurons created.
81///
82/// This is optimal for CPU/GPU backends where:
83/// - FireLedger is CPU-resident
84/// - Plasticity computation can overlap with GPU burst processing
85/// - Thread overhead is acceptable
86pub struct AsyncPlasticityExecutor {
87    /// The plasticity service instance
88    service: Arc<Mutex<Option<PlasticityService>>>,
89
90    /// Memory stats cache (shared with health check)
91    memory_stats_cache: MemoryStatsCache,
92
93    /// Configuration for the plasticity service
94    _config: PlasticityConfig,
95
96    /// Running state
97    running: bool,
98}
99
100impl AsyncPlasticityExecutor {
101    /// Create a new async plasticity executor
102    pub fn new(
103        config: PlasticityConfig,
104        memory_stats_cache: MemoryStatsCache,
105        npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
106    ) -> Self {
107        let service = PlasticityService::new(config.clone(), memory_stats_cache.clone(), npu);
108
109        Self {
110            service: Arc::new(Mutex::new(Some(service))),
111            memory_stats_cache,
112            _config: config,
113            running: false,
114        }
115    }
116
117    /// Get the memory stats cache
118    pub fn get_memory_stats_cache(&self) -> MemoryStatsCache {
119        self.memory_stats_cache.clone()
120    }
121
122    /// Get a reference to the underlying PlasticityService (for RuntimeService wiring)
123    pub fn get_service(&self) -> Option<PlasticityService> {
124        self.service.lock().ok()?.as_ref().cloned()
125    }
126
127    pub fn enqueue_commands_for_test(&self, commands: Vec<crate::PlasticityCommand>) {
128        if let Some(service) = self.service.lock().unwrap().as_ref() {
129            service.enqueue_commands_for_test(commands);
130        }
131    }
132
133    /// ST/LTM counts and upstream pattern cache size for a memory cortical index.
134    pub fn memory_cortical_area_runtime_info(
135        &self,
136        cortical_idx: u32,
137    ) -> Option<MemoryCorticalAreaRuntimeInfo> {
138        self.service
139            .lock()
140            .ok()?
141            .as_ref()
142            .map(|s| s.memory_cortical_area_runtime_info(cortical_idx))
143    }
144
145    /// Plasticity snapshot for a memory neuron by global id.
146    pub fn memory_neuron_detail(&self, neuron_id: u32) -> Option<MemoryNeuronDetail> {
147        self.service
148            .lock()
149            .ok()?
150            .as_ref()
151            .and_then(|s| s.memory_neuron_detail(neuron_id))
152    }
153
154    /// Active memory neuron ids for a cortical area (sorted), paginated: `(page, total)`.
155    pub fn paginated_memory_neuron_ids_in_area(
156        &self,
157        cortical_idx: u32,
158        offset: usize,
159        limit: usize,
160    ) -> Option<(Vec<u32>, usize)> {
161        let guard = self.service.lock().ok()?;
162        let service = guard.as_ref()?;
163        let array_arc = service.get_memory_neuron_array();
164        let array = array_arc.lock().ok()?;
165        Some(array.paginated_neuron_ids_in_area(cortical_idx, offset, limit))
166    }
167}
168
169impl PlasticityExecutor for AsyncPlasticityExecutor {
170    fn notify_burst(&self, timestep: u64) {
171        // trace!("[PLASTICITY-EXEC] 🔔 notify_burst({}) called", timestep);
172        if let Some(service) = self.service.lock().unwrap().as_ref() {
173            service.notify_burst(timestep);
174        } else {
175            warn!("[PLASTICITY-EXEC] ⚠️ Service is None, cannot notify");
176        }
177    }
178
179    fn drain_commands(&self) -> Vec<PlasticityCommand> {
180        if let Some(service) = self.service.lock().unwrap().as_ref() {
181            let drained = service.drain_commands();
182            if !drained.is_empty() {
183                debug!(
184                    target: "plasticity",
185                    "[PLASTICITY-EXEC] Drained {} command(s) from executor",
186                    drained.len()
187                );
188                for command in &drained {
189                    match command {
190                        PlasticityCommand::RegisterMemoryNeuron {
191                            neuron_id,
192                            area_idx,
193                            ..
194                        } => {
195                            debug!(
196                                target: "plasticity",
197                                "[PLASTICITY-EXEC] RegisterMemoryNeuron area={} neuron_id={}",
198                                area_idx,
199                                neuron_id
200                            );
201                        }
202                        PlasticityCommand::MemoryNeuronConvertedToLtm {
203                            neuron_id,
204                            area_idx,
205                            ..
206                        } => {
207                            debug!(
208                                target: "plasticity",
209                                "[PLASTICITY-EXEC] MemoryNeuronConvertedToLtm area={} neuron_id={}",
210                                area_idx,
211                                neuron_id
212                            );
213                        }
214                        PlasticityCommand::InjectMemoryNeuronToFCL {
215                            neuron_id,
216                            area_idx,
217                            is_reactivation,
218                            replay_frames,
219                            ..
220                        } => {
221                            debug!(
222                                target: "plasticity",
223                                "[PLASTICITY-EXEC] InjectMemoryNeuronToFCL area={} neuron_id={} reactivation={} replay_frames={}",
224                                area_idx,
225                                neuron_id,
226                                is_reactivation,
227                                replay_frames.len()
228                            );
229                        }
230                        PlasticityCommand::UpdateWeightsDelta { .. } => {}
231                        PlasticityCommand::UpdateStateCounters { .. } => {}
232                        PlasticityCommand::ResetMemoryNeuronsInArea { cortical_idx } => {
233                            debug!(
234                                target: "plasticity",
235                                "[PLASTICITY-EXEC] ResetMemoryNeuronsInArea cortical_idx={}",
236                                cortical_idx
237                            );
238                        }
239                    }
240                }
241            }
242            drained
243        } else {
244            Vec::new()
245        }
246    }
247
248    fn register_memory_area(
249        &self,
250        area_idx: u32,
251        area_name: String,
252        temporal_depth: u32,
253        upstream_areas: Vec<u32>,
254        lifecycle_config: Option<crate::MemoryNeuronLifecycleConfig>,
255        mp_learning_enabled: bool,
256    ) {
257        if let Some(service) = self.service.lock().unwrap().as_mut() {
258            service.register_memory_area(
259                area_idx,
260                area_name,
261                temporal_depth,
262                upstream_areas,
263                lifecycle_config,
264                mp_learning_enabled,
265            );
266        }
267    }
268
269    fn start(&mut self) {
270        if self.running {
271            tracing::warn!(target: "plasticity", "⚠️  PlasticityExecutor already running");
272            return;
273        }
274
275        if let Some(service) = self.service.lock().unwrap().as_ref() {
276            tracing::info!(target: "plasticity", "🚀 Initializing AsyncPlasticityExecutor...");
277            service.start();
278            self.running = true;
279            tracing::info!(target: "plasticity",
280                "✅ AsyncPlasticityExecutor started successfully - ready to monitor memory areas and process STDP"
281            );
282        } else {
283            tracing::error!(target: "plasticity", "❌ Failed to start PlasticityExecutor - service not initialized");
284        }
285    }
286
287    fn stop(&mut self) {
288        if !self.running {
289            return;
290        }
291
292        if let Some(service) = self.service.lock().unwrap().as_ref() {
293            service.stop();
294            self.running = false;
295            tracing::info!("Stopped async plasticity executor");
296        }
297    }
298
299    fn is_running(&self) -> bool {
300        self.running
301    }
302}
303
304// ============================================================================
305// FUTURE: Sync Executor for no_std Environments
306// ============================================================================
307
308/// Sync plasticity executor for no_std environments
309///
310/// **NOT YET IMPLEMENTED** - Placeholder for future embedded/RTOS support.
311///
312/// This would run plasticity computation synchronously in the burst loop,
313/// blocking until pattern detection and memory neuron creation completes.
314///
315/// Optimal for embedded environments where:
316/// - No std::thread available
317/// - Memory is constrained (no separate thread stack)
318/// - Deterministic timing is critical
319///
320/// To implement:
321/// 1. Create no_std-compatible PlasticityService variant
322/// 2. Use spin::Mutex or critical_section::Mutex
323/// 3. Call compute_plasticity() directly in execute()
324#[cfg(not(feature = "std"))]
325pub struct SyncPlasticityExecutor {
326    // TODO: Implement for embedded support
327    _marker: core::marker::PhantomData<()>,
328}
329
330#[cfg(not(feature = "std"))]
331impl PlasticityExecutor for SyncPlasticityExecutor {
332    fn notify_burst(&self, _timestep: u64) {
333        unimplemented!("SyncPlasticityExecutor not yet implemented");
334    }
335
336    fn drain_commands(&self) -> Vec<PlasticityCommand> {
337        unimplemented!("SyncPlasticityExecutor not yet implemented");
338    }
339
340    fn register_memory_area(
341        &self,
342        _area_idx: u32,
343        _area_name: String,
344        _temporal_depth: u32,
345        _upstream_areas: Vec<u32>,
346        _lifecycle_config: Option<crate::MemoryNeuronLifecycleConfig>,
347        _mp_learning_enabled: bool,
348    ) {
349        unimplemented!("SyncPlasticityExecutor not yet implemented");
350    }
351}