use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TunerConfig {
pub hidden_dim: usize,
pub embedding_dim: usize,
pub initial_thresholds: ThresholdConfig,
pub instant_loop: LearningLoopConfig,
pub background_loop: LearningLoopConfig,
pub ewc_lambda: f32,
pub pattern_similarity_threshold: f32,
pub max_patterns: usize,
pub auto_consolidate_after: usize,
}
impl Default for TunerConfig {
fn default() -> Self {
Self {
hidden_dim: 256,
embedding_dim: 256,
initial_thresholds: ThresholdConfig::default(),
instant_loop: LearningLoopConfig::instant(),
background_loop: LearningLoopConfig::background(),
ewc_lambda: 0.4,
pattern_similarity_threshold: 0.85,
max_patterns: 10000,
auto_consolidate_after: 100,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct ThresholdConfig {
pub reflex: f32,
pub retrieval: f32,
pub heavy: f32,
pub persistence_window_secs: u64,
}
impl Default for ThresholdConfig {
fn default() -> Self {
Self {
reflex: 0.1, retrieval: 0.3, heavy: 0.7, persistence_window_secs: 5,
}
}
}
impl ThresholdConfig {
#[must_use]
pub fn conservative() -> Self {
Self {
reflex: 0.05,
retrieval: 0.15,
heavy: 0.5,
persistence_window_secs: 10,
}
}
#[must_use]
pub fn aggressive() -> Self {
Self {
reflex: 0.2,
retrieval: 0.5,
heavy: 0.9,
persistence_window_secs: 2,
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.reflex >= 0.0
&& self.retrieval > self.reflex
&& self.heavy > self.retrieval
&& self.heavy <= 1.0
}
#[must_use]
pub fn lane_for_energy(&self, energy: f32) -> ComputeLane {
if energy < self.reflex {
ComputeLane::Reflex
} else if energy < self.retrieval {
ComputeLane::Retrieval
} else if energy < self.heavy {
ComputeLane::Heavy
} else {
ComputeLane::Human
}
}
#[must_use]
pub fn lerp(&self, other: &Self, t: f32) -> Self {
let t = t.clamp(0.0, 1.0);
Self {
reflex: self.reflex + (other.reflex - self.reflex) * t,
retrieval: self.retrieval + (other.retrieval - self.retrieval) * t,
heavy: self.heavy + (other.heavy - self.heavy) * t,
persistence_window_secs: if t < 0.5 {
self.persistence_window_secs
} else {
other.persistence_window_secs
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ComputeLane {
Reflex = 0,
Retrieval = 1,
Heavy = 2,
Human = 3,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LearningLoopConfig {
pub learning_rate: f32,
pub lora_rank: usize,
pub batch_size: usize,
pub max_latency_us: u64,
pub gradient_clipping: bool,
pub gradient_clip_value: f32,
}
impl LearningLoopConfig {
#[must_use]
pub fn instant() -> Self {
Self {
learning_rate: 0.01,
lora_rank: 1, batch_size: 1,
max_latency_us: 50, gradient_clipping: true,
gradient_clip_value: 1.0,
}
}
#[must_use]
pub fn background() -> Self {
Self {
learning_rate: 0.001,
lora_rank: 8, batch_size: 32,
max_latency_us: 10_000, gradient_clipping: true,
gradient_clip_value: 1.0,
}
}
}
impl Default for LearningLoopConfig {
fn default() -> Self {
Self::instant()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_threshold_config_validity() {
assert!(ThresholdConfig::default().is_valid());
assert!(ThresholdConfig::conservative().is_valid());
assert!(ThresholdConfig::aggressive().is_valid());
let invalid = ThresholdConfig {
reflex: 0.5,
retrieval: 0.3, heavy: 0.7,
persistence_window_secs: 5,
};
assert!(!invalid.is_valid());
}
#[test]
fn test_lane_for_energy() {
let config = ThresholdConfig::default();
assert_eq!(config.lane_for_energy(0.0), ComputeLane::Reflex);
assert_eq!(config.lane_for_energy(0.05), ComputeLane::Reflex);
assert_eq!(config.lane_for_energy(0.15), ComputeLane::Retrieval);
assert_eq!(config.lane_for_energy(0.5), ComputeLane::Heavy);
assert_eq!(config.lane_for_energy(1.0), ComputeLane::Human);
}
#[test]
fn test_threshold_lerp() {
let conservative = ThresholdConfig::conservative();
let aggressive = ThresholdConfig::aggressive();
let mid = conservative.lerp(&aggressive, 0.5);
assert!(mid.reflex > conservative.reflex);
assert!(mid.reflex < aggressive.reflex);
}
}