feagi_npu_burst_engine/lib.rs
1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4// Manual `n % divisor == 0` used instead of `n.is_multiple_of(divisor)` for stable Rust
5// compatibility (is_multiple_of is unstable on older stable toolchains).
6#![allow(clippy::manual_is_multiple_of)]
7/*
8 * Copyright 2025 Neuraville Inc.
9 *
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
21 */
22
23//! # FEAGI Burst Engine
24//!
25//! High-performance neural burst processing engine.
26//!
27//! ## Performance Targets
28//! - **30Hz burst frequency** with 1.2M neuron firings
29//! - **50-100x faster** than Python implementation
30//! - **SIMD-optimized** for modern CPUs
31//! - **Cache-friendly** data structures
32//!
33//! ## Architecture
34//! - Pure Rust, no Python overhead
35//! - Rayon for multi-threading
36//! - Zero-copy data access where possible
37//! - Minimal allocations in hot paths
38
39/// Crate version from Cargo.toml
40pub const VERSION: &str = env!("CARGO_PKG_VERSION");
41
42// ---------------------------------------------------------------------------------------------
43// Simulation timestep (burst interval) telemetry support
44//
45// NOTE:
46// - FEAGI is used in both real-time and batch workloads; we must not hardcode 15Hz/30Hz timing
47// assumptions into warning thresholds.
48// - The burst loop owns the authoritative runtime frequency (Hz). We snapshot the derived
49// timestep (ns) into a single atomic so hot-path code (e.g., injection timing) can compare
50// durations against the current simulation timestep without plumbing frequency everywhere.
51// ---------------------------------------------------------------------------------------------
52use core::sync::atomic::{AtomicU64, Ordering};
53use core::time::Duration;
54
55/// Current simulation timestep (burst interval) in nanoseconds.
56///
57/// Updated by the burst loop thread once per iteration (or when frequency changes).
58pub(crate) static SIM_TIMESTEP_NS: AtomicU64 = AtomicU64::new(0);
59
60/// Update the global simulation timestep snapshot from the configured runtime frequency (Hz).
61///
62/// # Panics
63/// Panics if `frequency_hz` is not positive. The burst loop uses this value for scheduling and
64/// would also fail if invalid.
65pub(crate) fn update_sim_timestep_from_hz(frequency_hz: f64) {
66 assert!(
67 frequency_hz.is_finite() && frequency_hz > 0.0,
68 "frequency_hz must be finite and > 0"
69 );
70 let timestep_ns = (1_000_000_000.0 / frequency_hz) as u64;
71 SIM_TIMESTEP_NS.store(timestep_ns, Ordering::Relaxed);
72}
73
74/// Get the current simulation timestep snapshot.
75///
76/// This is intended for logging thresholds (e.g., warn when injection exceeds timestep).
77pub(crate) fn sim_timestep() -> Duration {
78 let timestep_ns = SIM_TIMESTEP_NS.load(Ordering::Relaxed);
79 // If this is called before the burst loop initializes the value, return 0ns (no threshold).
80 // In normal operation, burst loop initializes this before the first burst.
81 Duration::from_nanos(timestep_ns)
82}
83
84#[cfg(any(feature = "async-tokio", feature = "wasm"))]
85pub mod async_burst_loop; // Pure Rust burst loop
86pub mod backend;
87#[cfg(feature = "std")]
88pub mod burst_loop_runner;
89pub use burst_loop_runner::SensoryIntake;
90pub mod fire_ledger;
91pub mod fire_structures;
92pub mod fq_sampler;
93pub mod motor_shm_writer;
94pub mod neural_dynamics;
95#[cfg(feature = "std")]
96pub mod tracing_mutex;
97// Neuron models moved to feagi-neural::models (Phase 2b)
98pub mod dynamic_npu;
99pub mod npu;
100pub mod parameter_update_queue;
101pub mod sensory; // Rust sensory injection system
102 // Disabled - uses DynamicNPU
103 // pub mod sleep; // Sleep manager for energy efficiency and memory optimization
104pub mod synaptic_propagation;
105pub mod viz_shm_writer; // Rust visualization SHM writer // Rust motor SHM writer
106
107pub use backend::*;
108#[cfg(feature = "std")]
109pub use burst_loop_runner::*;
110#[cfg(feature = "std")]
111pub use dynamic_npu::DynamicNPU;
112/// Conditional NPU mutex: TracingMutex if feature enabled, else wrapper around std::sync::Mutex
113/// This allows zero-overhead when lock tracing is disabled
114#[cfg(feature = "std")]
115pub use tracing_mutex::TracingMutex;
116
117pub use dynamic_npu::DynamicNPUGeneric;
118pub use fire_ledger::*;
119pub use fire_structures::*;
120pub use fq_sampler::*;
121pub use neural_dynamics::*;
122// Neuron models now in feagi-neural::models
123pub use npu::*;
124pub use parameter_update_queue::{ParameterUpdate, ParameterUpdateQueue};
125pub use sensory::*;
126// pub use sleep::*;
127pub use synaptic_propagation::*;
128pub use viz_shm_writer::*;
129
130/// Burst engine performance statistics
131#[derive(Debug, Clone, Default)]
132pub struct BurstEngineStats {
133 pub total_bursts: u64,
134 pub total_neurons_fired: u64,
135 pub total_synapses_processed: u64,
136 pub total_processing_time_us: u64,
137}
138
139impl BurstEngineStats {
140 /// Get average neurons per burst
141 pub fn avg_neurons_per_burst(&self) -> f64 {
142 if self.total_bursts == 0 {
143 0.0
144 } else {
145 self.total_neurons_fired as f64 / self.total_bursts as f64
146 }
147 }
148
149 /// Get average processing time per burst (microseconds)
150 pub fn avg_processing_time_us(&self) -> f64 {
151 if self.total_bursts == 0 {
152 0.0
153 } else {
154 self.total_processing_time_us as f64 / self.total_bursts as f64
155 }
156 }
157
158 /// Get average synapses per neuron
159 pub fn avg_synapses_per_neuron(&self) -> f64 {
160 if self.total_neurons_fired == 0 {
161 0.0
162 } else {
163 self.total_synapses_processed as f64 / self.total_neurons_fired as f64
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_burst_stats() {
174 let stats = BurstEngineStats {
175 total_bursts: 100,
176 total_neurons_fired: 10000,
177 total_synapses_processed: 50000,
178 total_processing_time_us: 1000000,
179 };
180
181 assert_eq!(stats.avg_neurons_per_burst(), 100.0);
182 assert_eq!(stats.avg_processing_time_us(), 10000.0);
183 assert_eq!(stats.avg_synapses_per_neuron(), 5.0);
184 }
185}