Files
ruvnet--RuView/vendor/sublinear-time-solver/crates/neural-network-implementation/src/config.rs
T
rUv 407b46b206 feat: vendor midstream and sublinear-time-solver libraries (#109)
Add ruvnet/midstream (AIMDS real-time inference) and
ruvnet/sublinear-time-solver (sublinear optimization algorithms)
as vendored dependencies under vendor/.
2026-03-02 23:34:05 -05:00

520 lines
16 KiB
Rust

//! Configuration management for temporal neural network systems
use crate::error::{Result, TemporalNeuralError};
use serde::{Deserialize, Serialize};
use std::path::Path;
/// Main configuration structure containing all system settings
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
/// Common settings shared between systems
pub common: CommonConfig,
/// Model architecture configuration
pub model: ModelConfig,
/// Training configuration
pub training: TrainingConfig,
/// Inference configuration
pub inference: InferenceConfig,
/// System-specific configuration
pub system: SystemConfig,
}
/// Common configuration shared between System A and B
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommonConfig {
/// Prediction horizon in milliseconds
pub horizon_ms: u32,
/// Input window size in milliseconds
pub window_ms: u32,
/// Sample rate in Hz
pub sample_rate_hz: u32,
/// Feature names and order
pub features: Vec<String>,
/// Whether to use INT8 quantization for inference
pub quantize: bool,
/// Random seed for reproducibility
pub random_seed: Option<u64>,
/// Enable detailed logging
pub verbose: bool,
}
/// Model architecture configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelConfig {
/// Model type: "micro_gru" or "micro_tcn"
pub model_type: String,
/// Hidden layer size
pub hidden_size: u32,
/// Number of layers
pub num_layers: u32,
/// Dropout rate during training
pub dropout: f64,
/// Whether to use residual connections
pub residual: bool,
/// Activation function: "relu", "tanh", "gelu"
pub activation: String,
/// Layer normalization
pub layer_norm: bool,
}
/// Training configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainingConfig {
/// Optimizer type: "adam", "sgd", "rmsprop"
pub optimizer: String,
/// Learning rate
pub learning_rate: f64,
/// Batch size
pub batch_size: u32,
/// Number of training epochs
pub epochs: u32,
/// Early stopping patience
pub patience: u32,
/// Validation frequency (epochs)
pub val_frequency: u32,
/// Gradient clipping threshold
pub grad_clip: Option<f64>,
/// Weight decay / L2 regularization
pub weight_decay: f64,
/// Smoothness penalty weight for velocity
pub smoothness_weight: f64,
/// Checkpoint saving frequency
pub checkpoint_frequency: u32,
}
/// Inference configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InferenceConfig {
/// Target P99.9 latency in milliseconds
pub target_latency_ms: f64,
/// Enable SIMD optimizations
pub enable_simd: bool,
/// Number of inference threads
pub num_threads: u32,
/// Memory pinning for performance
pub pin_memory: bool,
/// CPU affinity settings
pub cpu_affinity: Option<Vec<u32>>,
/// Batch size for inference (usually 1 for real-time)
pub batch_size: u32,
}
/// System-specific configuration (A or B)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum SystemConfig {
/// Traditional micro-net (System A)
Traditional(TraditionalConfig),
/// Temporal solver net (System B)
TemporalSolver(TemporalSolverConfig),
}
/// Configuration for traditional system (System A)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraditionalConfig {
/// Whether traditional system is enabled
pub enabled: bool,
}
/// Configuration for temporal solver system (System B)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalSolverConfig {
/// Kalman filter prior configuration
pub prior: KalmanConfig,
/// Sublinear solver gate configuration
pub solver_gate: SolverGateConfig,
/// Active sample selection configuration
pub active_selection: ActiveSelectionConfig,
}
/// Kalman filter configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KalmanConfig {
/// Process noise covariance
pub process_noise: f64,
/// Measurement noise covariance
pub measurement_noise: f64,
/// Initial state uncertainty
pub initial_uncertainty: f64,
/// State transition model: "constant_velocity", "constant_acceleration"
pub transition_model: String,
/// Update frequency in Hz
pub update_frequency: f64,
}
/// Sublinear solver gate configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverGateConfig {
/// Solver algorithm: "neumann", "random_walk", "forward_push"
pub algorithm: String,
/// Convergence tolerance
pub epsilon: f64,
/// Computational budget
pub budget: u64,
/// Maximum certificate error to allow passage
pub max_cert_error: f64,
/// Fallback strategy: "hold_last", "kalman_only", "disable_gate"
pub fallback_strategy: String,
}
/// Active sample selection configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActiveSelectionConfig {
/// k-NN graph construction parameter
pub k: u32,
/// PageRank tolerance for convergence
pub pagerank_eps: f64,
/// Number of active samples per epoch
pub samples_per_epoch: u32,
/// Error weight for PageRank scoring
pub error_weight: f64,
/// Diversity weight to avoid clustering
pub diversity_weight: f64,
}
impl Config {
/// Load configuration from YAML file
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let content = std::fs::read_to_string(path.as_ref()).map_err(|e| {
TemporalNeuralError::IoError {
message: format!("Failed to read config file: {}", e),
path: Some(path.as_ref().to_string_lossy().to_string()),
source: Some(e),
}
})?;
let config: Self = serde_yaml::from_str(&content).map_err(|e| {
TemporalNeuralError::ConfigurationError {
message: format!("Failed to parse config: {}", e),
field: None,
}
})?;
config.validate()?;
Ok(config)
}
/// Save configuration to YAML file
pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let content = serde_yaml::to_string(self).map_err(|e| {
TemporalNeuralError::SerializationError {
message: format!("Failed to serialize config: {}", e),
format: Some("yaml".to_string()),
}
})?;
std::fs::write(path.as_ref(), content).map_err(|e| {
TemporalNeuralError::IoError {
message: format!("Failed to write config file: {}", e),
path: Some(path.as_ref().to_string_lossy().to_string()),
source: Some(e),
}
})?;
Ok(())
}
/// Validate configuration parameters
pub fn validate(&self) -> Result<()> {
// Validate common config
if self.common.horizon_ms == 0 {
return Err(TemporalNeuralError::config_field_error(
"horizon_ms", "Must be greater than 0"
));
}
if self.common.window_ms == 0 {
return Err(TemporalNeuralError::config_field_error(
"window_ms", "Must be greater than 0"
));
}
if self.common.sample_rate_hz == 0 {
return Err(TemporalNeuralError::config_field_error(
"sample_rate_hz", "Must be greater than 0"
));
}
if self.common.features.is_empty() {
return Err(TemporalNeuralError::config_field_error(
"features", "Must specify at least one feature"
));
}
// Validate model config
if self.model.hidden_size == 0 {
return Err(TemporalNeuralError::config_field_error(
"hidden_size", "Must be greater than 0"
));
}
if self.model.num_layers == 0 {
return Err(TemporalNeuralError::config_field_error(
"num_layers", "Must be greater than 0"
));
}
if !(0.0..=1.0).contains(&self.model.dropout) {
return Err(TemporalNeuralError::config_field_error(
"dropout", "Must be between 0.0 and 1.0"
));
}
// Validate training config
if self.training.learning_rate <= 0.0 {
return Err(TemporalNeuralError::config_field_error(
"learning_rate", "Must be positive"
));
}
if self.training.batch_size == 0 {
return Err(TemporalNeuralError::config_field_error(
"batch_size", "Must be greater than 0"
));
}
if self.training.epochs == 0 {
return Err(TemporalNeuralError::config_field_error(
"epochs", "Must be greater than 0"
));
}
// Validate inference config
if self.inference.target_latency_ms <= 0.0 {
return Err(TemporalNeuralError::config_field_error(
"target_latency_ms", "Must be positive"
));
}
if self.inference.num_threads == 0 {
return Err(TemporalNeuralError::config_field_error(
"num_threads", "Must be greater than 0"
));
}
// Validate system-specific config
match &self.system {
SystemConfig::Traditional(_) => {
// No additional validation needed
}
SystemConfig::TemporalSolver(config) => {
self.validate_temporal_solver_config(config)?;
}
}
Ok(())
}
fn validate_temporal_solver_config(&self, config: &TemporalSolverConfig) -> Result<()> {
// Validate Kalman config
if config.prior.process_noise <= 0.0 {
return Err(TemporalNeuralError::config_field_error(
"process_noise", "Must be positive"
));
}
if config.prior.measurement_noise <= 0.0 {
return Err(TemporalNeuralError::config_field_error(
"measurement_noise", "Must be positive"
));
}
if config.prior.update_frequency <= 0.0 {
return Err(TemporalNeuralError::config_field_error(
"update_frequency", "Must be positive"
));
}
// Validate solver gate config
if config.solver_gate.epsilon <= 0.0 {
return Err(TemporalNeuralError::config_field_error(
"epsilon", "Must be positive"
));
}
if config.solver_gate.budget == 0 {
return Err(TemporalNeuralError::config_field_error(
"budget", "Must be greater than 0"
));
}
if config.solver_gate.max_cert_error < 0.0 {
return Err(TemporalNeuralError::config_field_error(
"max_cert_error", "Must be non-negative"
));
}
// Validate active selection config
if config.active_selection.k == 0 {
return Err(TemporalNeuralError::config_field_error(
"k", "Must be greater than 0"
));
}
if config.active_selection.pagerank_eps <= 0.0 {
return Err(TemporalNeuralError::config_field_error(
"pagerank_eps", "Must be positive"
));
}
Ok(())
}
/// Get the expected input window size in samples
pub fn window_samples(&self) -> usize {
((self.common.window_ms as f64 / 1000.0) * self.common.sample_rate_hz as f64) as usize
}
/// Get the prediction horizon in samples
pub fn horizon_samples(&self) -> usize {
((self.common.horizon_ms as f64 / 1000.0) * self.common.sample_rate_hz as f64) as usize
}
/// Get the feature count
pub fn feature_count(&self) -> usize {
self.common.features.len()
}
/// Get input shape for the neural network
pub fn input_shape(&self) -> (usize, usize) {
(self.window_samples(), self.feature_count())
}
/// Check if this is a temporal solver system
pub fn is_temporal_solver(&self) -> bool {
matches!(self.system, SystemConfig::TemporalSolver(_))
}
}
impl Default for Config {
fn default() -> Self {
Self {
common: CommonConfig {
horizon_ms: 500,
window_ms: 128,
sample_rate_hz: 2000,
features: vec!["x".to_string(), "y".to_string(), "vx".to_string(), "vy".to_string()],
quantize: true,
random_seed: Some(42),
verbose: false,
},
model: ModelConfig {
model_type: "micro_gru".to_string(),
hidden_size: 32,
num_layers: 1,
dropout: 0.1,
residual: true,
activation: "tanh".to_string(),
layer_norm: false,
},
training: TrainingConfig {
optimizer: "adam".to_string(),
learning_rate: 1e-3,
batch_size: 256,
epochs: 15,
patience: 5,
val_frequency: 1,
grad_clip: Some(1.0),
weight_decay: 1e-4,
smoothness_weight: 0.1,
checkpoint_frequency: 5,
},
inference: InferenceConfig {
target_latency_ms: 0.9,
enable_simd: true,
num_threads: 1,
pin_memory: true,
cpu_affinity: None,
batch_size: 1,
},
system: SystemConfig::Traditional(TraditionalConfig { enabled: true }),
}
}
}
impl Default for TemporalSolverConfig {
fn default() -> Self {
Self {
prior: KalmanConfig {
process_noise: 0.01,
measurement_noise: 0.1,
initial_uncertainty: 1.0,
transition_model: "constant_velocity".to_string(),
update_frequency: 2000.0,
},
solver_gate: SolverGateConfig {
algorithm: "neumann".to_string(),
epsilon: 0.02,
budget: 200000,
max_cert_error: 0.02,
fallback_strategy: "kalman_only".to_string(),
},
active_selection: ActiveSelectionConfig {
k: 15,
pagerank_eps: 0.03,
samples_per_epoch: 1000,
error_weight: 0.8,
diversity_weight: 0.2,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::NamedTempFile;
#[test]
fn test_default_config_validation() {
let config = Config::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_config_serialization() {
let config = Config::default();
let yaml = serde_yaml::to_string(&config).unwrap();
let deserialized: Config = serde_yaml::from_str(&yaml).unwrap();
assert_eq!(config.common.horizon_ms, deserialized.common.horizon_ms);
}
#[test]
fn test_file_operations() {
let config = Config::default();
let temp_file = NamedTempFile::new().unwrap();
// Test save
config.to_file(temp_file.path()).unwrap();
// Test load
let loaded_config = Config::from_file(temp_file.path()).unwrap();
assert_eq!(config.common.horizon_ms, loaded_config.common.horizon_ms);
}
#[test]
fn test_validation_errors() {
let mut config = Config::default();
// Test invalid horizon
config.common.horizon_ms = 0;
assert!(config.validate().is_err());
config.common.horizon_ms = 500; // Reset
// Test invalid learning rate
config.training.learning_rate = -1.0;
assert!(config.validate().is_err());
}
#[test]
fn test_helper_methods() {
let config = Config::default();
assert_eq!(config.window_samples(), 256); // 128ms at 2kHz
assert_eq!(config.horizon_samples(), 1000); // 500ms at 2kHz
assert_eq!(config.feature_count(), 4);
assert_eq!(config.input_shape(), (256, 4));
assert!(!config.is_temporal_solver());
}
}