Skip to main content

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}