feat: vendor midstream and sublinear-time-solver libraries

Add ruvnet/midstream (AIMDS real-time inference) and
ruvnet/sublinear-time-solver (sublinear optimization algorithms)
as vendored dependencies under vendor/.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv
2026-03-02 23:32:45 -05:00
parent 14902e6b4e
commit e91bb8a1d5
1600 changed files with 1852646 additions and 0 deletions
@@ -0,0 +1,463 @@
//! Kalman filter implementation for temporal prior predictions
//!
//! This module provides a Kalman filter implementation optimized for
//! providing high-quality prior predictions for the temporal neural network.
use crate::{
config::KalmanConfig,
error::{Result, TemporalNeuralError},
solvers::InferenceReadyTrait,
};
use nalgebra::{DMatrix, DVector, Matrix2, Vector2};
use serde::{Deserialize, Serialize};
/// Kalman filter for providing temporal priors
///
/// This filter tracks position and velocity for 2D trajectory prediction,
/// providing physics-based priors that the neural network can refine.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KalmanFilter {
/// Configuration
config: KalmanConfig,
/// Current state estimate [x, y, vx, vy]
state: DVector<f64>,
/// State covariance matrix
covariance: DMatrix<f64>,
/// State transition matrix
transition_matrix: DMatrix<f64>,
/// Process noise covariance
process_noise: DMatrix<f64>,
/// Measurement noise covariance
measurement_noise: DMatrix<f64>,
/// Measurement matrix (maps state to observations)
measurement_matrix: DMatrix<f64>,
/// Whether filter is initialized
initialized: bool,
/// Last prediction for error tracking
last_prediction: Option<DVector<f64>>,
/// Prediction error history
prediction_errors: Vec<f64>,
/// Time of last update
last_update_time: Option<std::time::Instant>,
/// Ready for inference flag
inference_ready: bool,
}
impl KalmanFilter {
/// Create a new Kalman filter
pub fn new(config: &KalmanConfig) -> Result<Self> {
let state_dim = 4; // [x, y, vx, vy]
let obs_dim = 2; // [x, y]
let state = DVector::zeros(state_dim);
let covariance = DMatrix::identity(state_dim, state_dim) * config.initial_uncertainty;
// Create state transition matrix based on model type
let transition_matrix = match config.transition_model.as_str() {
"constant_velocity" => Self::create_constant_velocity_matrix(1.0 / config.update_frequency),
"constant_acceleration" => Self::create_constant_acceleration_matrix(1.0 / config.update_frequency),
_ => {
return Err(TemporalNeuralError::ConfigurationError {
message: format!("Unknown transition model: {}", config.transition_model),
field: Some("transition_model".to_string()),
});
}
};
// Process noise (uncertainty in dynamics)
let dt = 1.0 / config.update_frequency;
let process_noise = Self::create_process_noise_matrix(config.process_noise, dt);
// Measurement noise
let measurement_noise = DMatrix::identity(obs_dim, obs_dim) * config.measurement_noise;
// Measurement matrix (observe position only)
let measurement_matrix = DMatrix::from_row_slice(obs_dim, state_dim, &[
1.0, 0.0, 0.0, 0.0, // x
0.0, 1.0, 0.0, 0.0, // y
]);
Ok(Self {
config: config.clone(),
state,
covariance,
transition_matrix,
process_noise,
measurement_noise,
measurement_matrix,
initialized: false,
last_prediction: None,
prediction_errors: Vec::new(),
last_update_time: None,
inference_ready: false,
})
}
/// Create constant velocity transition matrix
fn create_constant_velocity_matrix(dt: f64) -> DMatrix<f64> {
DMatrix::from_row_slice(4, 4, &[
1.0, 0.0, dt, 0.0, // x = x + vx*dt
0.0, 1.0, 0.0, dt, // y = y + vy*dt
0.0, 0.0, 1.0, 0.0, // vx = vx
0.0, 0.0, 0.0, 1.0, // vy = vy
])
}
/// Create constant acceleration transition matrix
fn create_constant_acceleration_matrix(dt: f64) -> DMatrix<f64> {
let dt2 = dt * dt / 2.0;
DMatrix::from_row_slice(4, 4, &[
1.0, 0.0, dt, 0.0, // x = x + vx*dt
0.0, 1.0, 0.0, dt, // y = y + vy*dt
0.0, 0.0, 0.9, 0.0, // vx = 0.9*vx (decay)
0.0, 0.0, 0.0, 0.9, // vy = 0.9*vy (decay)
])
}
/// Create process noise covariance matrix
fn create_process_noise_matrix(noise_level: f64, dt: f64) -> DMatrix<f64> {
let dt2 = dt * dt;
let dt3 = dt * dt2 / 2.0;
let dt4 = dt2 * dt2 / 4.0;
// Q matrix for constant velocity model
DMatrix::from_row_slice(4, 4, &[
dt4, 0.0, dt3, 0.0, // x variance and x-vx covariance
0.0, dt4, 0.0, dt3, // y variance and y-vy covariance
dt3, 0.0, dt2, 0.0, // vx-x covariance and vx variance
0.0, dt3, 0.0, dt2, // vy-y covariance and vy variance
]) * noise_level
}
/// Predict next state (time update)
pub fn predict(&self, _input: &DMatrix<f64>) -> Result<DVector<f64>> {
if !self.initialized {
// Return zero prediction if not initialized
return Ok(DVector::zeros(2));
}
// Predict state: x_k|k-1 = F * x_k-1|k-1
let predicted_state = &self.transition_matrix * &self.state;
// Extract position prediction [x, y]
Ok(DVector::from_vec(vec![predicted_state[0], predicted_state[1]]))
}
/// Const version of predict for immutable contexts
pub fn predict_const(&self, _input: &DMatrix<f64>) -> Result<DVector<f64>> {
self.predict(_input)
}
/// Update filter with measurement (measurement update)
pub fn update(&mut self, measurement: &DVector<f64>) -> Result<()> {
if measurement.len() != 2 {
return Err(TemporalNeuralError::KalmanError {
message: format!("Expected 2D measurement, got {}", measurement.len()),
state_dimension: Some(self.state.len()),
});
}
if !self.initialized {
// Initialize state with first measurement
self.state[0] = measurement[0]; // x
self.state[1] = measurement[1]; // y
self.state[2] = 0.0; // vx = 0
self.state[3] = 0.0; // vy = 0
self.initialized = true;
self.last_update_time = Some(std::time::Instant::now());
return Ok(());
}
// Time update (predict)
let predicted_state = &self.transition_matrix * &self.state;
let predicted_covariance = &self.transition_matrix * &self.covariance * self.transition_matrix.transpose() + &self.process_noise;
// Measurement update (correct)
let innovation = measurement - &self.measurement_matrix * &predicted_state;
let innovation_covariance = &self.measurement_matrix * &predicted_covariance * self.measurement_matrix.transpose() + &self.measurement_noise;
// Kalman gain
let kalman_gain = &predicted_covariance * self.measurement_matrix.transpose() * innovation_covariance.try_inverse().ok_or_else(|| {
TemporalNeuralError::KalmanError {
message: "Innovation covariance matrix is not invertible".to_string(),
state_dimension: Some(self.state.len()),
}
})?;
// Update state and covariance
self.state = predicted_state + &kalman_gain * innovation;
let identity = DMatrix::identity(self.state.len(), self.state.len());
self.covariance = (identity - &kalman_gain * &self.measurement_matrix) * predicted_covariance;
// Track prediction error if we had a previous prediction
if let Some(ref last_pred) = self.last_prediction {
let error = (measurement - last_pred).norm();
self.prediction_errors.push(error);
// Keep only recent errors (for memory efficiency)
if self.prediction_errors.len() > 1000 {
self.prediction_errors.remove(0);
}
}
self.last_prediction = Some(measurement.clone());
self.last_update_time = Some(std::time::Instant::now());
Ok(())
}
/// Get current state estimate
pub fn get_state(&self) -> &DVector<f64> {
&self.state
}
/// Get current covariance estimate
pub fn get_covariance(&self) -> &DMatrix<f64> {
&self.covariance
}
/// Get prediction uncertainty (position covariance)
pub fn get_prediction_uncertainty(&self) -> Matrix2<f64> {
if !self.initialized {
return Matrix2::identity() * 1000.0; // High uncertainty
}
// Extract position covariance [x, y]
Matrix2::new(
self.covariance[(0, 0)], self.covariance[(0, 1)],
self.covariance[(1, 0)], self.covariance[(1, 1)],
)
}
/// Get average prediction error
pub fn get_prediction_error(&self) -> f64 {
if self.prediction_errors.is_empty() {
0.0
} else {
self.prediction_errors.iter().sum::<f64>() / self.prediction_errors.len() as f64
}
}
/// Check if filter is well-conditioned
pub fn is_well_conditioned(&self) -> bool {
if !self.initialized {
return false;
}
// Check covariance matrix condition
let max_eigenvalue = self.covariance.diagonal().max();
let min_eigenvalue = self.covariance.diagonal().min();
if min_eigenvalue <= 0.0 {
return false;
}
let condition_number = max_eigenvalue / min_eigenvalue;
condition_number < 1e6 // Reasonable condition number
}
/// Predict at specific time horizon
pub fn predict_at_horizon(&self, horizon_seconds: f64) -> Result<Vector2<f64>> {
if !self.initialized {
return Ok(Vector2::zeros());
}
// Create transition matrix for the specific horizon
let transition = match self.config.transition_model.as_str() {
"constant_velocity" => Self::create_constant_velocity_matrix(horizon_seconds),
"constant_acceleration" => Self::create_constant_acceleration_matrix(horizon_seconds),
_ => self.transition_matrix.clone(),
};
// Predict state at horizon
let predicted_state = &transition * &self.state;
Ok(Vector2::new(predicted_state[0], predicted_state[1]))
}
/// Adaptive tuning based on recent performance
pub fn adapt_parameters(&mut self) -> Result<()> {
if self.prediction_errors.len() < 10 {
return Ok(()); // Need enough data
}
let recent_errors: Vec<f64> = self.prediction_errors
.iter()
.rev()
.take(10)
.cloned()
.collect();
let avg_error = recent_errors.iter().sum::<f64>() / recent_errors.len() as f64;
// Adapt process noise based on error
if avg_error > 0.1 {
// High error - increase process noise
self.process_noise *= 1.1;
} else if avg_error < 0.01 {
// Low error - decrease process noise
self.process_noise *= 0.95;
}
// Clamp process noise to reasonable bounds
let min_noise = 1e-6;
let max_noise = 1.0;
for element in self.process_noise.iter_mut() {
*element = element.clamp(min_noise, max_noise);
}
Ok(())
}
}
impl InferenceReadyTrait for KalmanFilter {
fn prepare_for_inference(&mut self) -> Result<()> {
// Ensure filter is in a good state for inference
if !self.initialized {
return Err(TemporalNeuralError::KalmanError {
message: "Kalman filter not initialized".to_string(),
state_dimension: Some(self.state.len()),
});
}
if !self.is_well_conditioned() {
return Err(TemporalNeuralError::KalmanError {
message: "Kalman filter is poorly conditioned".to_string(),
state_dimension: Some(self.state.len()),
});
}
// Clear prediction error history to save memory
self.prediction_errors.clear();
self.inference_ready = true;
Ok(())
}
fn is_inference_ready(&self) -> bool {
self.inference_ready && self.initialized && self.is_well_conditioned()
}
fn memory_usage(&self) -> usize {
std::mem::size_of::<Self>() +
self.state.len() * std::mem::size_of::<f64>() +
self.covariance.len() * std::mem::size_of::<f64>() +
self.transition_matrix.len() * std::mem::size_of::<f64>() +
self.process_noise.len() * std::mem::size_of::<f64>() +
self.measurement_noise.len() * std::mem::size_of::<f64>() +
self.measurement_matrix.len() * std::mem::size_of::<f64>() +
self.prediction_errors.len() * std::mem::size_of::<f64>()
}
fn reset(&mut self) -> Result<()> {
self.state.fill(0.0);
self.covariance = DMatrix::identity(self.state.len(), self.state.len()) * self.config.initial_uncertainty;
self.initialized = false;
self.last_prediction = None;
self.prediction_errors.clear();
self.last_update_time = None;
self.inference_ready = false;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_config() -> KalmanConfig {
KalmanConfig {
process_noise: 0.01,
measurement_noise: 0.1,
initial_uncertainty: 1.0,
transition_model: "constant_velocity".to_string(),
update_frequency: 100.0,
}
}
#[test]
fn test_kalman_creation() {
let config = create_test_config();
let filter = KalmanFilter::new(&config).unwrap();
assert!(!filter.initialized);
assert_eq!(filter.state.len(), 4);
assert_eq!(filter.covariance.shape(), (4, 4));
}
#[test]
fn test_kalman_initialization() {
let config = create_test_config();
let mut filter = KalmanFilter::new(&config).unwrap();
let measurement = DVector::from_vec(vec![1.0, 2.0]);
filter.update(&measurement).unwrap();
assert!(filter.initialized);
assert_eq!(filter.state[0], 1.0);
assert_eq!(filter.state[1], 2.0);
}
#[test]
fn test_prediction_tracking() {
let config = create_test_config();
let mut filter = KalmanFilter::new(&config).unwrap();
// Initialize
let measurement1 = DVector::from_vec(vec![0.0, 0.0]);
filter.update(&measurement1).unwrap();
// Update with moving trajectory
let measurement2 = DVector::from_vec(vec![1.0, 1.0]);
filter.update(&measurement2).unwrap();
// Predict should show movement
let input = DMatrix::zeros(4, 10); // Dummy input
let prediction = filter.predict(&input).unwrap();
assert!(prediction[0] > 0.5); // Should predict continued movement
assert!(prediction[1] > 0.5);
}
#[test]
fn test_horizon_prediction() {
let config = create_test_config();
let mut filter = KalmanFilter::new(&config).unwrap();
// Initialize with trajectory
filter.update(&DVector::from_vec(vec![0.0, 0.0])).unwrap();
filter.update(&DVector::from_vec(vec![1.0, 0.0])).unwrap(); // Moving right
let horizon_pred = filter.predict_at_horizon(0.5).unwrap(); // 0.5 seconds
assert!(horizon_pred[0] > 1.0); // Should be further right
assert!(horizon_pred[1].abs() < 0.1); // Should stay near y=0
}
#[test]
fn test_condition_checking() {
let config = create_test_config();
let mut filter = KalmanFilter::new(&config).unwrap();
assert!(!filter.is_well_conditioned()); // Not initialized
filter.update(&DVector::from_vec(vec![0.0, 0.0])).unwrap();
assert!(filter.is_well_conditioned()); // Should be well-conditioned after init
}
#[test]
fn test_inference_preparation() {
let config = create_test_config();
let mut filter = KalmanFilter::new(&config).unwrap();
// Should fail before initialization
assert!(filter.prepare_for_inference().is_err());
// Initialize
filter.update(&DVector::from_vec(vec![0.0, 0.0])).unwrap();
// Should succeed after initialization
assert!(filter.prepare_for_inference().is_ok());
assert!(filter.is_inference_ready());
}
}
@@ -0,0 +1,341 @@
//! Sublinear solver integration and supporting components
//!
//! This module provides the key innovation of the temporal neural network:
//! integration with sublinear-time mathematical solvers for prediction
//! verification and Kalman filter priors.
use crate::error::{Result, TemporalNeuralError};
use nalgebra::{DMatrix, DVector};
use serde::{Deserialize, Serialize};
pub mod kalman;
// pub mod solver_gate; // Temporarily disabled
pub mod solver_gate_simple;
pub mod pagerank_selector;
pub use kalman::KalmanFilter;
// pub use solver_gate::SolverGate;
pub use solver_gate_simple::{SolverGate, SolverGateConfig, GateResult, SolverGateStats};
pub use pagerank_selector::PageRankSelector;
/// Trait for components that can be prepared for inference
pub trait InferenceReadyTrait {
/// Prepare the component for inference mode
fn prepare_for_inference(&mut self) -> Result<()>;
/// Check if the component is ready for inference
fn is_inference_ready(&self) -> bool;
/// Get memory usage in bytes
fn memory_usage(&self) -> usize;
/// Reset component state
fn reset(&mut self) -> Result<()>;
}
/// Common mathematical utilities used by solver components
pub mod math_utils {
use nalgebra::{DMatrix, DVector};
/// Compute Jacobian matrix numerically using finite differences
pub fn compute_jacobian<F>(
f: F,
x: &DVector<f64>,
h: f64,
) -> nalgebra::DMatrix<f64>
where
F: Fn(&DVector<f64>) -> DVector<f64>,
{
let n = x.len();
let fx = f(x);
let m = fx.len();
let mut jacobian = DMatrix::zeros(m, n);
for j in 0..n {
let mut x_plus = x.clone();
x_plus[j] += h;
let fx_plus = f(&x_plus);
for i in 0..m {
jacobian[(i, j)] = (fx_plus[i] - fx[i]) / h;
}
}
jacobian
}
/// Compute matrix condition number estimate
pub fn condition_number_estimate(matrix: &DMatrix<f64>) -> f64 {
// Simple estimate using ratio of max to min singular values
// In practice, use proper SVD
let max_elem = matrix.iter().map(|x| x.abs()).fold(0.0, f64::max);
let min_elem = matrix.iter()
.filter(|&&x| x.abs() > 1e-12)
.map(|x| x.abs())
.fold(f64::INFINITY, f64::min);
if min_elem.is_infinite() || min_elem == 0.0 {
f64::INFINITY
} else {
max_elem / min_elem
}
}
/// Check if matrix is diagonally dominant
pub fn is_diagonally_dominant(matrix: &DMatrix<f64>) -> bool {
let (rows, cols) = matrix.shape();
if rows != cols {
return false;
}
for i in 0..rows {
let diagonal_elem = matrix[(i, i)].abs();
let off_diagonal_sum: f64 = (0..cols)
.filter(|&j| j != i)
.map(|j| matrix[(i, j)].abs())
.sum();
if diagonal_elem <= off_diagonal_sum {
return false;
}
}
true
}
/// Create a simple test matrix that is diagonally dominant
pub fn create_test_dd_matrix(size: usize) -> DMatrix<f64> {
let mut matrix = DMatrix::zeros(size, size);
for i in 0..size {
// Set diagonal elements to be larger than sum of off-diagonal
let mut off_diag_sum = 0.0;
for j in 0..size {
if i != j {
let val = (i + j + 1) as f64 * 0.1;
matrix[(i, j)] = val;
off_diag_sum += val.abs();
}
}
matrix[(i, i)] = off_diag_sum * 1.5 + 1.0; // Ensure diagonal dominance
}
matrix
}
/// Spectral radius estimation using power iteration
pub fn spectral_radius_estimate(matrix: &DMatrix<f64>, max_iterations: usize) -> f64 {
let n = matrix.nrows();
if n != matrix.ncols() {
return f64::NAN;
}
let mut v = DVector::from_vec((0..n).map(|_| rand::random::<f64>()).collect());
v /= v.norm();
let mut lambda = 0.0;
for _ in 0..max_iterations {
let new_v = matrix * &v;
lambda = v.dot(&new_v);
v = new_v;
if v.norm() > 1e-10 {
v /= v.norm();
} else {
break;
}
}
lambda.abs()
}
}
/// Certificate information from solver verification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Certificate {
/// Estimated error bound
pub error_bound: f64,
/// Confidence level (0.0 to 1.0)
pub confidence: f64,
/// Computational work performed
pub work_performed: u64,
/// Solver algorithm used
pub algorithm: String,
/// Whether the certificate is valid
pub is_valid: bool,
/// Additional metadata
pub metadata: CertificateMetadata,
}
/// Additional metadata for certificates
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CertificateMetadata {
/// Matrix condition number
pub condition_number: Option<f64>,
/// Whether matrix was diagonally dominant
pub diagonally_dominant: bool,
/// Convergence iterations performed
pub iterations: u32,
/// Final residual norm
pub residual_norm: f64,
/// Computation time in microseconds
pub computation_time_us: f64,
}
impl Certificate {
/// Create a new certificate
pub fn new(
error_bound: f64,
confidence: f64,
work_performed: u64,
algorithm: String,
) -> Self {
Self {
error_bound,
confidence,
work_performed,
algorithm,
is_valid: error_bound >= 0.0 && confidence >= 0.0 && confidence <= 1.0,
metadata: CertificateMetadata {
condition_number: None,
diagonally_dominant: false,
iterations: 0,
residual_norm: 0.0,
computation_time_us: 0.0,
},
}
}
/// Check if certificate passes given tolerance
pub fn passes_tolerance(&self, tolerance: f64) -> bool {
self.is_valid && self.error_bound <= tolerance
}
/// Get quality score (0.0 to 1.0, higher is better)
pub fn quality_score(&self) -> f64 {
if !self.is_valid {
return 0.0;
}
// Combine error bound (lower is better) and confidence (higher is better)
let error_score = 1.0 / (1.0 + self.error_bound);
let confidence_score = self.confidence;
(error_score + confidence_score) / 2.0
}
}
/// Factory for creating solver components
pub struct SolverFactory;
impl SolverFactory {
/// Create a Kalman filter with the given configuration
pub fn create_kalman_filter(config: &crate::config::KalmanConfig) -> Result<KalmanFilter> {
KalmanFilter::new(config)
}
/// Create a solver gate with the given configuration
pub fn create_solver_gate(config: &SolverGateConfig) -> Result<SolverGate> {
SolverGate::new(config)
}
/// Create a PageRank selector with the given configuration
pub fn create_pagerank_selector(
config: &crate::config::ActiveSelectionConfig,
) -> Result<PageRankSelector> {
PageRankSelector::new(config)
}
}
/// Performance monitoring for solver components
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverPerformanceMetrics {
/// Average prediction latency in microseconds
pub avg_latency_us: f64,
/// P50 latency in microseconds
pub p50_latency_us: f64,
/// P99 latency in microseconds
pub p99_latency_us: f64,
/// P99.9 latency in microseconds
pub p99_9_latency_us: f64,
/// Success rate (0.0 to 1.0)
pub success_rate: f64,
/// Memory usage in bytes
pub memory_usage_bytes: usize,
/// Total predictions made
pub total_predictions: u64,
/// Average certificate error
pub avg_certificate_error: f64,
/// Gate pass rate
pub gate_pass_rate: f64,
}
impl Default for SolverPerformanceMetrics {
fn default() -> Self {
Self {
avg_latency_us: 0.0,
p50_latency_us: 0.0,
p99_latency_us: 0.0,
p99_9_latency_us: 0.0,
success_rate: 1.0,
memory_usage_bytes: 0,
total_predictions: 0,
avg_certificate_error: 0.0,
gate_pass_rate: 1.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::math_utils::*;
#[test]
fn test_diagonal_dominance() {
let dd_matrix = create_test_dd_matrix(3);
assert!(is_diagonally_dominant(&dd_matrix));
// Test non-diagonally dominant matrix
let non_dd = DMatrix::from_row_slice(2, 2, &[1.0, 2.0, 3.0, 1.0]);
assert!(!is_diagonally_dominant(&non_dd));
}
#[test]
fn test_certificate_validation() {
let cert = Certificate::new(0.01, 0.95, 1000, "neumann".to_string());
assert!(cert.is_valid);
assert!(cert.passes_tolerance(0.02));
assert!(!cert.passes_tolerance(0.005));
let quality = cert.quality_score();
assert!(quality > 0.0 && quality <= 1.0);
}
#[test]
fn test_condition_number() {
let well_conditioned = DMatrix::identity(3, 3);
let cond = condition_number_estimate(&well_conditioned);
assert!(cond < 2.0); // Should be close to 1.0
let ill_conditioned = DMatrix::from_row_slice(2, 2, &[1.0, 1.0, 1.0, 1.0001]);
let cond_ill = condition_number_estimate(&ill_conditioned);
assert!(cond_ill > 1000.0);
}
#[test]
fn test_jacobian_computation() {
// Test with simple linear function f(x) = Ax
let a = DMatrix::from_row_slice(2, 2, &[2.0, 1.0, 0.5, 3.0]);
let f = |x: &DVector<f64>| &a * x;
let x = DVector::from_vec(vec![1.0, 2.0]);
let jac = compute_jacobian(f, &x, 1e-6);
// Jacobian should be approximately equal to A
assert!((jac[(0, 0)] - 2.0).abs() < 1e-4);
assert!((jac[(0, 1)] - 1.0).abs() < 1e-4);
assert!((jac[(1, 0)] - 0.5).abs() < 1e-4);
assert!((jac[(1, 1)] - 3.0).abs() < 1e-4);
}
}
@@ -0,0 +1,645 @@
//! PageRank-based active sample selection for training
//!
//! This module implements the PageRank-based active learning strategy
//! that selects the most valuable training samples for the neural network.
use crate::{
config::ActiveSelectionConfig,
error::{Result, TemporalNeuralError},
solvers::InferenceReadyTrait,
};
use nalgebra::{DMatrix, DVector};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
/// PageRank-based active sample selector
///
/// Uses k-NN graphs and PageRank scoring to identify the most valuable
/// training samples, focusing on regions where the model is uncertain
/// or making large errors.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PageRankSelector {
/// Configuration
config: ActiveSelectionConfig,
/// k-NN graph adjacency matrix
graph: Option<DMatrix<f64>>,
/// Sample embeddings (features from last layer)
embeddings: Vec<DVector<f64>>,
/// Sample errors for scoring
sample_errors: Vec<f64>,
/// Sample importance scores
importance_scores: Vec<f64>,
/// Selected sample indices
selected_indices: HashSet<usize>,
/// PageRank scores
pagerank_scores: Vec<f64>,
/// Statistics
stats: SelectorStatistics,
/// Ready for inference flag
inference_ready: bool,
}
/// Statistics tracked by the selector
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SelectorStatistics {
/// Total number of samples processed
pub total_samples: usize,
/// Number of selections made
pub selections_made: usize,
/// Average error of selected samples
pub avg_selected_error: f64,
/// Average error of non-selected samples
pub avg_nonselected_error: f64,
/// Graph construction time in milliseconds
pub graph_construction_time_ms: f64,
/// PageRank computation time in milliseconds
pub pagerank_computation_time_ms: f64,
/// Selection time in milliseconds
pub selection_time_ms: f64,
}
impl PageRankSelector {
/// Create a new PageRank selector
pub fn new(config: &ActiveSelectionConfig) -> Result<Self> {
Ok(Self {
config: config.clone(),
graph: None,
embeddings: Vec::new(),
sample_errors: Vec::new(),
importance_scores: Vec::new(),
selected_indices: HashSet::new(),
pagerank_scores: Vec::new(),
stats: SelectorStatistics {
total_samples: 0,
selections_made: 0,
avg_selected_error: 0.0,
avg_nonselected_error: 0.0,
graph_construction_time_ms: 0.0,
pagerank_computation_time_ms: 0.0,
selection_time_ms: 0.0,
},
inference_ready: false,
})
}
/// Add samples with their embeddings and errors
pub fn add_samples(
&mut self,
embeddings: &[DVector<f64>],
errors: &[f64],
) -> Result<()> {
if embeddings.len() != errors.len() {
return Err(TemporalNeuralError::DataError {
message: "Embeddings and errors length mismatch".to_string(),
context: Some(format!("embeddings: {}, errors: {}", embeddings.len(), errors.len())),
});
}
// Add to internal storage
self.embeddings.extend_from_slice(embeddings);
self.sample_errors.extend_from_slice(errors);
self.stats.total_samples = self.embeddings.len();
// Invalidate graph since we have new samples
self.graph = None;
self.pagerank_scores.clear();
self.importance_scores.clear();
Ok(())
}
/// Build k-NN graph from current embeddings
pub fn build_graph(&mut self) -> Result<()> {
if self.embeddings.is_empty() {
return Err(TemporalNeuralError::DataError {
message: "No embeddings available for graph construction".to_string(),
context: None,
});
}
let start_time = std::time::Instant::now();
let n = self.embeddings.len();
let k = self.config.k as usize;
// Initialize adjacency matrix
let mut adjacency = DMatrix::zeros(n, n);
// Build k-NN graph
for i in 0..n {
// Compute distances to all other samples
let mut distances: Vec<(usize, f64)> = (0..n)
.filter(|&j| j != i)
.map(|j| {
let dist = self.compute_distance(&self.embeddings[i], &self.embeddings[j]);
(j, dist)
})
.collect();
// Sort by distance and take k nearest neighbors
distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
let neighbors: Vec<usize> = distances
.into_iter()
.take(k)
.map(|(idx, _)| idx)
.collect();
// Add edges to adjacency matrix
for &neighbor in &neighbors {
// Use Gaussian similarity as edge weight
let dist = self.compute_distance(&self.embeddings[i], &self.embeddings[neighbor]);
let weight = (-dist * dist / (2.0 * 0.1)).exp(); // σ = 0.1
adjacency[(i, neighbor)] = weight;
}
}
// Make graph symmetric
for i in 0..n {
for j in 0..n {
let avg_weight = (adjacency[(i, j)] + adjacency[(j, i)]) / 2.0;
adjacency[(i, j)] = avg_weight;
adjacency[(j, i)] = avg_weight;
}
}
self.graph = Some(adjacency);
self.stats.graph_construction_time_ms = start_time.elapsed().as_millis() as f64;
Ok(())
}
/// Compute PageRank scores with error-based personalization
pub fn compute_pagerank(&mut self) -> Result<()> {
if self.graph.is_none() {
self.build_graph()?;
}
let graph = self.graph.as_ref().unwrap();
let n = graph.nrows();
if n == 0 {
return Ok(());
}
let start_time = std::time::Instant::now();
// Create personalization vector based on recent errors
let personalization = self.create_error_personalization_vector()?;
// Compute PageRank using power iteration
let pagerank_scores = self.power_iteration_pagerank(graph, &personalization)?;
self.pagerank_scores = pagerank_scores;
self.stats.pagerank_computation_time_ms = start_time.elapsed().as_millis() as f64;
Ok(())
}
/// Select active samples based on PageRank scores
pub fn select_samples(&mut self) -> Result<Vec<usize>> {
if self.pagerank_scores.is_empty() {
self.compute_pagerank()?;
}
let start_time = std::time::Instant::now();
let n_samples = self.config.samples_per_epoch as usize;
let total_samples = self.embeddings.len();
if n_samples >= total_samples {
// Select all samples if we need more than available
let selected: Vec<usize> = (0..total_samples).collect();
self.selected_indices = selected.iter().cloned().collect();
return Ok(selected);
}
// Combine PageRank scores with diversity to avoid clustering
let mut combined_scores = Vec::new();
for i in 0..total_samples {
let pagerank_score = self.pagerank_scores.get(i).copied().unwrap_or(0.0);
let error_score = self.sample_errors.get(i).copied().unwrap_or(0.0);
let diversity_score = self.compute_diversity_score(i)?;
let combined_score =
self.config.error_weight * error_score +
(1.0 - self.config.error_weight) * pagerank_score +
self.config.diversity_weight * diversity_score;
combined_scores.push((i, combined_score));
}
// Sort by combined score (descending)
combined_scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
// Select top samples with diversity constraints
let selected = self.select_with_diversity_constraint(&combined_scores, n_samples)?;
self.selected_indices = selected.iter().cloned().collect();
self.stats.selections_made += 1;
self.stats.selection_time_ms = start_time.elapsed().as_millis() as f64;
// Update statistics
self.update_selection_statistics(&selected)?;
Ok(selected)
}
/// Create error-based personalization vector for PageRank
fn create_error_personalization_vector(&self) -> Result<DVector<f64>> {
let n = self.sample_errors.len();
if n == 0 {
return Err(TemporalNeuralError::DataError {
message: "No sample errors available".to_string(),
context: None,
});
}
// Create personalization vector: higher error = higher probability
let mut personalization = DVector::zeros(n);
let max_error = self.sample_errors.iter().fold(0.0f64, |a, &b| a.max(b));
if max_error > 0.0 {
for (i, &error) in self.sample_errors.iter().enumerate() {
personalization[i] = error / max_error;
}
} else {
// Uniform if no errors
personalization.fill(1.0 / n as f64);
}
// Normalize
let sum = personalization.sum();
if sum > 0.0 {
personalization /= sum;
} else {
personalization.fill(1.0 / n as f64);
}
Ok(personalization)
}
/// Compute PageRank using power iteration
fn power_iteration_pagerank(
&self,
graph: &DMatrix<f64>,
personalization: &DVector<f64>,
) -> Result<Vec<f64>> {
let n = graph.nrows();
let damping = 0.85;
let tolerance = self.config.pagerank_eps;
let max_iterations = 100;
// Normalize graph to transition matrix
let mut transition = graph.clone();
for i in 0..n {
let row_sum: f64 = transition.row(i).sum();
if row_sum > 1e-12 {
for j in 0..n {
transition[(i, j)] /= row_sum;
}
} else {
// Uniform transition for isolated nodes
for j in 0..n {
transition[(i, j)] = 1.0 / n as f64;
}
}
}
// Initialize PageRank vector
let mut pagerank = DVector::from_element(n, 1.0 / n as f64);
// Power iteration
for _ in 0..max_iterations {
let old_pagerank = pagerank.clone();
// PageRank update: PR = (1-d)/N + d * T^T * PR + (1-d) * personalization
pagerank = &transition.transpose() * &old_pagerank * damping +
personalization * (1.0 - damping);
// Check convergence
let diff = (&pagerank - &old_pagerank).norm();
if diff < tolerance {
break;
}
}
Ok(pagerank.data.as_vec().clone())
}
/// Compute diversity score for a sample
fn compute_diversity_score(&self, sample_idx: usize) -> Result<f64> {
if self.selected_indices.is_empty() {
return Ok(1.0); // Maximum diversity if no samples selected yet
}
let sample_embedding = &self.embeddings[sample_idx];
let mut min_distance = f64::INFINITY;
// Find minimum distance to already selected samples
for &selected_idx in &self.selected_indices {
let distance = self.compute_distance(sample_embedding, &self.embeddings[selected_idx]);
min_distance = min_distance.min(distance);
}
// Diversity score is minimum distance (higher = more diverse)
Ok(min_distance)
}
/// Select samples with diversity constraint
fn select_with_diversity_constraint(
&self,
scored_samples: &[(usize, f64)],
n_samples: usize,
) -> Result<Vec<usize>> {
let mut selected = Vec::new();
let mut selected_set = HashSet::new();
for &(idx, _score) in scored_samples {
if selected.len() >= n_samples {
break;
}
// Check diversity constraint
if self.meets_diversity_constraint(idx, &selected_set)? {
selected.push(idx);
selected_set.insert(idx);
}
}
// Fill remaining slots if we don't have enough diverse samples
for &(idx, _score) in scored_samples {
if selected.len() >= n_samples {
break;
}
if !selected_set.contains(&idx) {
selected.push(idx);
selected_set.insert(idx);
}
}
Ok(selected)
}
/// Check if sample meets diversity constraint
fn meets_diversity_constraint(
&self,
sample_idx: usize,
selected_indices: &HashSet<usize>,
) -> Result<bool> {
if selected_indices.is_empty() {
return Ok(true);
}
let min_diversity_distance = 0.1; // Minimum distance threshold
let sample_embedding = &self.embeddings[sample_idx];
for &selected_idx in selected_indices {
let distance = self.compute_distance(sample_embedding, &self.embeddings[selected_idx]);
if distance < min_diversity_distance {
return Ok(false);
}
}
Ok(true)
}
/// Compute distance between two embeddings
fn compute_distance(&self, a: &DVector<f64>, b: &DVector<f64>) -> f64 {
(a - b).norm()
}
/// Update selection statistics
fn update_selection_statistics(&mut self, selected: &[usize]) -> Result<()> {
if selected.is_empty() {
return Ok(());
}
// Compute average error of selected samples
let selected_errors: Vec<f64> = selected
.iter()
.map(|&idx| self.sample_errors.get(idx).copied().unwrap_or(0.0))
.collect();
self.stats.avg_selected_error = selected_errors.iter().sum::<f64>() / selected_errors.len() as f64;
// Compute average error of non-selected samples
let non_selected_errors: Vec<f64> = (0..self.sample_errors.len())
.filter(|idx| !selected.contains(idx))
.map(|idx| self.sample_errors[idx])
.collect();
if !non_selected_errors.is_empty() {
self.stats.avg_nonselected_error = non_selected_errors.iter().sum::<f64>() / non_selected_errors.len() as f64;
}
Ok(())
}
/// Get selection statistics
pub fn get_statistics(&self) -> &SelectorStatistics {
&self.stats
}
/// Clear all stored data
pub fn clear_data(&mut self) {
self.embeddings.clear();
self.sample_errors.clear();
self.importance_scores.clear();
self.selected_indices.clear();
self.pagerank_scores.clear();
self.graph = None;
self.stats.total_samples = 0;
}
/// Get memory usage estimate
pub fn estimate_memory_usage(&self) -> usize {
let embeddings_size = self.embeddings.len() *
self.embeddings.get(0).map_or(0, |e| e.len()) *
std::mem::size_of::<f64>();
let graph_size = self.graph.as_ref().map_or(0, |g| g.len() * std::mem::size_of::<f64>());
let other_vecs_size = (self.sample_errors.len() +
self.importance_scores.len() +
self.pagerank_scores.len()) * std::mem::size_of::<f64>();
std::mem::size_of::<Self>() + embeddings_size + graph_size + other_vecs_size
}
}
impl InferenceReadyTrait for PageRankSelector {
fn prepare_for_inference(&mut self) -> Result<()> {
// Clear training-specific data to save memory
self.clear_data();
self.inference_ready = true;
Ok(())
}
fn is_inference_ready(&self) -> bool {
self.inference_ready
}
fn memory_usage(&self) -> usize {
self.estimate_memory_usage()
}
fn reset(&mut self) -> Result<()> {
self.clear_data();
self.stats = SelectorStatistics {
total_samples: 0,
selections_made: 0,
avg_selected_error: 0.0,
avg_nonselected_error: 0.0,
graph_construction_time_ms: 0.0,
pagerank_computation_time_ms: 0.0,
selection_time_ms: 0.0,
};
self.inference_ready = false;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_config() -> ActiveSelectionConfig {
ActiveSelectionConfig {
k: 5,
pagerank_eps: 0.01,
samples_per_epoch: 10,
error_weight: 0.7,
diversity_weight: 0.3,
}
}
fn create_test_embeddings() -> Vec<DVector<f64>> {
(0..20)
.map(|i| {
DVector::from_vec(vec![
i as f64 / 10.0,
(i as f64 / 10.0).sin(),
(i as f64 / 10.0).cos(),
])
})
.collect()
}
fn create_test_errors() -> Vec<f64> {
(0..20).map(|i| (i as f64 / 20.0) + 0.1).collect()
}
#[test]
fn test_selector_creation() {
let config = create_test_config();
let selector = PageRankSelector::new(&config).unwrap();
assert_eq!(selector.config.k, 5);
assert_eq!(selector.config.samples_per_epoch, 10);
assert!(selector.embeddings.is_empty());
}
#[test]
fn test_add_samples() {
let config = create_test_config();
let mut selector = PageRankSelector::new(&config).unwrap();
let embeddings = create_test_embeddings();
let errors = create_test_errors();
selector.add_samples(&embeddings, &errors).unwrap();
assert_eq!(selector.stats.total_samples, 20);
assert_eq!(selector.embeddings.len(), 20);
assert_eq!(selector.sample_errors.len(), 20);
}
#[test]
fn test_graph_construction() {
let config = create_test_config();
let mut selector = PageRankSelector::new(&config).unwrap();
let embeddings = create_test_embeddings();
let errors = create_test_errors();
selector.add_samples(&embeddings, &errors).unwrap();
selector.build_graph().unwrap();
assert!(selector.graph.is_some());
let graph = selector.graph.as_ref().unwrap();
assert_eq!(graph.shape(), (20, 20));
assert!(selector.stats.graph_construction_time_ms > 0.0);
}
#[test]
fn test_pagerank_computation() {
let config = create_test_config();
let mut selector = PageRankSelector::new(&config).unwrap();
let embeddings = create_test_embeddings();
let errors = create_test_errors();
selector.add_samples(&embeddings, &errors).unwrap();
selector.compute_pagerank().unwrap();
assert_eq!(selector.pagerank_scores.len(), 20);
assert!(selector.stats.pagerank_computation_time_ms > 0.0);
// Check that scores sum approximately to 1
let sum: f64 = selector.pagerank_scores.iter().sum();
assert!((sum - 1.0).abs() < 0.1);
}
#[test]
fn test_sample_selection() {
let config = create_test_config();
let mut selector = PageRankSelector::new(&config).unwrap();
let embeddings = create_test_embeddings();
let errors = create_test_errors();
selector.add_samples(&embeddings, &errors).unwrap();
let selected = selector.select_samples().unwrap();
assert_eq!(selected.len(), 10); // Should select requested number
assert!(selector.stats.selection_time_ms > 0.0);
assert!(selector.stats.avg_selected_error >= 0.0);
}
#[test]
fn test_diversity_constraint() {
let config = create_test_config();
let mut selector = PageRankSelector::new(&config).unwrap();
// Create embeddings with some very similar ones
let mut embeddings = vec![DVector::from_vec(vec![0.0, 0.0, 0.0])];
embeddings.push(DVector::from_vec(vec![0.001, 0.001, 0.001])); // Very similar
embeddings.push(DVector::from_vec(vec![1.0, 1.0, 1.0])); // Different
let errors = vec![1.0, 1.0, 0.1]; // First two have high error
selector.add_samples(&embeddings, &errors).unwrap();
let selected = selector.select_samples().unwrap();
// Should not select both very similar samples
assert!(selected.len() <= 2);
}
#[test]
fn test_inference_preparation() {
let config = create_test_config();
let mut selector = PageRankSelector::new(&config).unwrap();
let embeddings = create_test_embeddings();
let errors = create_test_errors();
selector.add_samples(&embeddings, &errors).unwrap();
let memory_before = selector.memory_usage();
selector.prepare_for_inference().unwrap();
assert!(selector.is_inference_ready());
assert!(selector.embeddings.is_empty()); // Should clear training data
let memory_after = selector.memory_usage();
assert!(memory_after < memory_before); // Should use less memory
}
}
@@ -0,0 +1,650 @@
//! Sublinear solver gate for mathematical verification of predictions
//!
//! This module provides the core innovation: using sublinear-time mathematical
//! solvers to verify neural network predictions with mathematical certificates.
use crate::{
config::SolverGateConfig,
error::{Result, TemporalNeuralError},
solvers::{InferenceReadyTrait, Certificate, CertificateMetadata, math_utils},
};
use nalgebra::{DMatrix, DVector};
use serde::{Deserialize, Serialize};
// Temporarily commented out until sublinear integration is fixed
// use ::sublinear::{SolverAlgorithm, SolverOptions, NeumannSolver, Precision};
// Temporary type aliases for compilation
type SolverAlgorithm = ();
type SolverOptions = ();
type NeumannSolver = ();
type Precision = f64;
/// Gate result from solver verification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GateResult {
/// Whether the prediction passed verification
pub passed: bool,
/// Certificate error bound
pub certificate_error: f64,
/// Computational work performed
pub work_performed: u64,
/// Fallback strategy used (if any)
pub fallback_used: Option<String>,
/// Full certificate details
pub certificate: Certificate,
/// Computation time in microseconds
pub computation_time_us: f64,
}
/// Sublinear solver gate for prediction verification
///
/// The gate works by formulating the prediction problem as a linear system
/// and using sublinear solvers to verify the mathematical consistency.
#[derive(Debug)]
pub struct SolverGate {
/// Configuration
config: SolverGateConfig,
/// Sublinear solver instance
// Temporarily disabled solver: Box<dyn SolverAlgorithm<State = Box<dyn sublinear::solver::SolverState>>>,
solver_placeholder: bool,
/// Solver options
solver_options: SolverOptions,
/// Gate statistics
stats: GateStatistics,
/// Ready for inference flag
inference_ready: bool,
/// Recent verification times for latency tracking
recent_times: Vec<f64>,
/// Maximum number of recent times to keep
max_recent_times: usize,
}
/// Statistics tracked by the solver gate
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GateStatistics {
/// Total number of verifications performed
pub total_verifications: u64,
/// Number of verifications that passed
pub passed_verifications: u64,
/// Sum of all certificate errors
pub total_certificate_error: f64,
/// Sum of all computational work
pub total_work: u64,
/// Average verification time in microseconds
pub avg_verification_time_us: f64,
/// P99.9 verification time in microseconds
pub p99_9_verification_time_us: f64,
}
impl SolverGate {
/// Create a new solver gate
pub fn new(config: &SolverGateConfig) -> Result<Self> {
// Temporarily disabled solver integration for compilation
// TODO: Re-enable once sublinear crate integration is fixed
Ok(Self {
solver_placeholder: true,
config: config.clone(),
verification_history: Vec::new(),
certificate_cache: std::collections::HashMap::new(),
})
}
/// Temporarily simplified verify method
pub fn verify_placeholder(
&mut self,
_prior: &DMatrix<f64>,
_residual: &DMatrix<f64>,
_prediction: &DMatrix<f64>,
) -> Result<GateResult> {
// Placeholder implementation - always passes for now
Ok(GateResult {
passed: true,
confidence: 0.95,
certificate_error: 0.001,
verification_time_us: 10.0,
work_performed: 100,
certificate: Some(Certificate {
error_bound: 0.001,
algorithm: "placeholder".to_string(),
verification_id: uuid::Uuid::new_v4().to_string(),
timestamp: chrono::Utc::now(),
computational_work: 100,
confidence_level: 0.95,
}),
})
}
// Temporary placeholder for the rest of the implementation
fn _disabled_new(config: &SolverGateConfig) -> Result<Self> {
return Err(TemporalNeuralError::ConfigurationError(
message: "Random walk solver not yet implemented".to_string(),
field: Some("algorithm".to_string()),
});
}
"forward_push" => {
return Err(TemporalNeuralError::ConfigurationError {
message: "Forward push solver not yet implemented".to_string(),
field: Some("algorithm".to_string()),
});
}
_ => {
return Err(TemporalNeuralError::ConfigurationError {
message: format!("Unknown solver algorithm: {}", config.algorithm),
field: Some("algorithm".to_string()),
});
}
};
let solver_options = SolverOptions {
tolerance: config.epsilon,
max_iterations: (config.budget / 1000).min(10000) as usize, // Convert budget to iterations
..SolverOptions::default()
};
let stats = GateStatistics {
total_verifications: 0,
passed_verifications: 0,
total_certificate_error: 0.0,
total_work: 0,
avg_verification_time_us: 0.0,
p99_9_verification_time_us: 0.0,
};
Ok(Self {
config: config.clone(),
solver,
solver_options,
stats,
inference_ready: false,
recent_times: Vec::new(),
max_recent_times: 1000,
})
}
/// Verify a prediction using the sublinear solver
pub fn verify(
&mut self,
prior: &DVector<f64>,
residual: &DVector<f64>,
prediction: &DVector<f64>,
) -> Result<GateResult> {
let start_time = std::time::Instant::now();
// Formulate the verification problem as a linear system
let (matrix, rhs) = self.formulate_verification_problem(prior, residual, prediction)?;
// Solve using sublinear solver
let solver_result = self.solver.solve(&matrix, &rhs, &self.solver_options)
.map_err(|e| TemporalNeuralError::SolverError {
message: format!("Solver verification failed: {}", e),
algorithm: Some(self.config.algorithm.clone()),
certificate_error: None,
})?;
let computation_time = start_time.elapsed().as_micros() as f64;
// Create certificate from solver result
let certificate = self.create_certificate(&solver_result, &matrix, computation_time)?;
// Determine if gate passes
let passed = certificate.passes_tolerance(self.config.max_cert_error);
// Determine fallback strategy if failed
let fallback_used = if !passed {
Some(self.config.fallback_strategy.clone())
} else {
None
};
// Update statistics
self.update_statistics(passed, certificate.error_bound, solver_result.iterations as u64, computation_time);
Ok(GateResult {
passed,
certificate_error: certificate.error_bound,
work_performed: solver_result.iterations as u64,
fallback_used,
certificate,
computation_time_us: computation_time,
})
}
/// Formulate the verification problem as a linear system
///
/// The key insight is to verify that the prediction is mathematically
/// consistent with the dynamics model implied by the Kalman filter.
fn formulate_verification_problem(
&self,
prior: &DVector<f64>,
residual: &DVector<f64>,
prediction: &DVector<f64>,
) -> Result<(Box<dyn sublinear::Matrix>, Vec<Precision>)> {
let dim = prior.len();
// Create a verification matrix that encodes the consistency constraint:
// prediction = prior + residual
// We formulate this as: [I -I] * [prediction; residual] = prior
// For a 2D problem, create a 2x4 system
let matrix_data = vec![
vec![1.0, 0.0, -1.0, 0.0], // prediction_x - residual_x = prior_x
vec![0.0, 1.0, 0.0, -1.0], // prediction_y - residual_y = prior_y
];
// Convert to sparse matrix format expected by solver
let mut triplets = Vec::new();
for (i, row) in matrix_data.iter().enumerate() {
for (j, &val) in row.iter().enumerate() {
if val.abs() > 1e-12 {
triplets.push((i, j, val));
}
}
}
let sparse_matrix = sublinear::SparseMatrix::from_triplets(
triplets,
dim,
dim * 2, // [prediction; residual]
);
// Make the matrix diagonally dominant for solver compatibility
let dd_matrix = self.make_diagonally_dominant(sparse_matrix)?;
// Right-hand side is the prior
let rhs: Vec<Precision> = prior.iter().cloned().collect();
Ok((Box::new(dd_matrix), rhs))
}
/// Make matrix diagonally dominant for sublinear solver compatibility
fn make_diagonally_dominant(
&self,
mut matrix: sublinear::SparseMatrix,
) -> Result<sublinear::SparseMatrix> {
// For the solver to work, we need diagonal dominance
// Add regularization to diagonal elements
let regularization = 1.1; // Ensure diagonal dominance
// This is a simplified approach - in practice, we'd need to modify
// the underlying sparse matrix structure
// For now, create a simple diagonally dominant test matrix
let size = matrix.rows().min(matrix.cols());
let test_matrix = math_utils::create_test_dd_matrix(size);
// Convert back to sparse format
let mut triplets = Vec::new();
for i in 0..size {
for j in 0..size {
let val = test_matrix[(i, j)];
if val.abs() > 1e-12 {
triplets.push((i, j, val));
}
}
}
Ok(sublinear::SparseMatrix::from_triplets(
triplets,
size,
size,
))
}
/// Create certificate from solver result
fn create_certificate(
&self,
solver_result: &sublinear::SolverResult,
matrix: &dyn sublinear::Matrix,
computation_time_us: f64,
) -> Result<Certificate> {
let error_bound = solver_result.residual_norm;
let confidence = if solver_result.converged { 0.95 } else { 0.5 };
let work_performed = solver_result.iterations as u64;
let mut certificate = Certificate::new(
error_bound,
confidence,
work_performed,
self.config.algorithm.clone(),
);
// Add metadata
certificate.metadata = CertificateMetadata {
condition_number: Some(math_utils::condition_number_estimate(
&nalgebra::DMatrix::identity(2, 2) // Placeholder
)),
diagonally_dominant: true, // We ensure this in formulation
iterations: solver_result.iterations as u32,
residual_norm: solver_result.residual_norm,
computation_time_us,
};
Ok(certificate)
}
/// Update internal statistics
fn update_statistics(
&mut self,
passed: bool,
certificate_error: f64,
work: u64,
time_us: f64,
) {
self.stats.total_verifications += 1;
if passed {
self.stats.passed_verifications += 1;
}
self.stats.total_certificate_error += certificate_error;
self.stats.total_work += work;
// Update timing statistics
self.recent_times.push(time_us);
if self.recent_times.len() > self.max_recent_times {
self.recent_times.remove(0);
}
// Recompute average
self.stats.avg_verification_time_us =
self.recent_times.iter().sum::<f64>() / self.recent_times.len() as f64;
// Compute P99.9
if self.recent_times.len() > 10 {
let mut sorted_times = self.recent_times.clone();
sorted_times.sort_by(|a, b| a.partial_cmp(b).unwrap());
let p99_9_index = ((sorted_times.len() as f64) * 0.999) as usize;
self.stats.p99_9_verification_time_us = sorted_times.get(p99_9_index)
.copied()
.unwrap_or(time_us);
}
}
/// Get gate pass rate
pub fn get_pass_rate(&self) -> f64 {
if self.stats.total_verifications == 0 {
1.0
} else {
self.stats.passed_verifications as f64 / self.stats.total_verifications as f64
}
}
/// Get average certificate error
pub fn get_avg_certificate_error(&self) -> f64 {
if self.stats.total_verifications == 0 {
0.0
} else {
self.stats.total_certificate_error / self.stats.total_verifications as f64
}
}
/// Get average computational work
pub fn get_avg_work(&self) -> f64 {
if self.stats.total_verifications == 0 {
0.0
} else {
self.stats.total_work as f64 / self.stats.total_verifications as f64
}
}
/// Get total prediction count
pub fn get_prediction_count(&self) -> u64 {
self.stats.total_verifications
}
/// Set epsilon tolerance dynamically
pub fn set_epsilon(&mut self, epsilon: f64) -> Result<()> {
if epsilon <= 0.0 {
return Err(TemporalNeuralError::ConfigurationError {
message: "Epsilon must be positive".to_string(),
field: Some("epsilon".to_string()),
});
}
self.config.epsilon = epsilon;
self.solver_options.tolerance = epsilon;
Ok(())
}
/// Set computational budget dynamically
pub fn set_budget(&mut self, budget: u64) -> Result<()> {
if budget == 0 {
return Err(TemporalNeuralError::ConfigurationError {
message: "Budget must be positive".to_string(),
field: Some("budget".to_string()),
});
}
self.config.budget = budget;
self.solver_options.max_iterations = (budget / 1000).min(10000) as usize;
Ok(())
}
/// Get current performance metrics
pub fn get_performance_metrics(&self) -> SolverGateMetrics {
SolverGateMetrics {
pass_rate: self.get_pass_rate(),
avg_certificate_error: self.get_avg_certificate_error(),
avg_verification_time_us: self.stats.avg_verification_time_us,
p99_9_verification_time_us: self.stats.p99_9_verification_time_us,
total_verifications: self.stats.total_verifications,
memory_usage_bytes: self.memory_usage(),
}
}
/// Check if gate is meeting performance targets
pub fn meets_performance_targets(&self, target_latency_us: f64, target_pass_rate: f64) -> bool {
self.stats.p99_9_verification_time_us <= target_latency_us &&
self.get_pass_rate() >= target_pass_rate
}
}
/// Performance metrics for the solver gate
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverGateMetrics {
/// Gate pass rate (0.0 to 1.0)
pub pass_rate: f64,
/// Average certificate error
pub avg_certificate_error: f64,
/// Average verification time in microseconds
pub avg_verification_time_us: f64,
/// P99.9 verification time in microseconds
pub p99_9_verification_time_us: f64,
/// Total verifications performed
pub total_verifications: u64,
/// Memory usage in bytes
pub memory_usage_bytes: usize,
}
impl InferenceReadyTrait for SolverGate {
fn prepare_for_inference(&mut self) -> Result<()> {
// Validate configuration
if self.config.epsilon <= 0.0 {
return Err(TemporalNeuralError::ConfigurationError {
message: "Invalid epsilon for inference".to_string(),
field: Some("epsilon".to_string()),
});
}
if self.config.budget == 0 {
return Err(TemporalNeuralError::ConfigurationError {
message: "Invalid budget for inference".to_string(),
field: Some("budget".to_string()),
});
}
// Clear statistics to save memory
self.recent_times.clear();
self.recent_times.reserve(100); // Keep small buffer for recent metrics
self.inference_ready = true;
Ok(())
}
fn is_inference_ready(&self) -> bool {
self.inference_ready &&
self.config.epsilon > 0.0 &&
self.config.budget > 0
}
fn memory_usage(&self) -> usize {
std::mem::size_of::<Self>() +
self.recent_times.len() * std::mem::size_of::<f64>() +
1024 // Estimated solver overhead
}
fn reset(&mut self) -> Result<()> {
self.stats = GateStatistics {
total_verifications: 0,
passed_verifications: 0,
total_certificate_error: 0.0,
total_work: 0,
avg_verification_time_us: 0.0,
p99_9_verification_time_us: 0.0,
};
self.recent_times.clear();
self.inference_ready = false;
Ok(())
}
}
impl Clone for SolverGate {
fn clone(&self) -> Self {
// Create a new solver instance for the clone
Self::new(&self.config).unwrap()
}
}
impl Serialize for SolverGate {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
// Serialize only the essential data
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("SolverGate", 3)?;
state.serialize_field("config", &self.config)?;
state.serialize_field("stats", &self.stats)?;
state.serialize_field("inference_ready", &self.inference_ready)?;
state.end()
}
}
impl<'de> Deserialize<'de> for SolverGate {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct SolverGateData {
config: SolverGateConfig,
stats: GateStatistics,
inference_ready: bool,
}
let data = SolverGateData::deserialize(deserializer)?;
let mut gate = Self::new(&data.config).map_err(serde::de::Error::custom)?;
gate.stats = data.stats;
gate.inference_ready = data.inference_ready;
Ok(gate)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_config() -> SolverGateConfig {
SolverGateConfig {
algorithm: "neumann".to_string(),
epsilon: 0.02,
budget: 10000,
max_cert_error: 0.05,
fallback_strategy: "kalman_only".to_string(),
}
}
#[test]
fn test_solver_gate_creation() {
let config = create_test_config();
let gate = SolverGate::new(&config).unwrap();
assert_eq!(gate.config.algorithm, "neumann");
assert_eq!(gate.config.epsilon, 0.02);
assert!(!gate.inference_ready);
}
#[test]
fn test_verification_process() {
let config = create_test_config();
let mut gate = SolverGate::new(&config).unwrap();
let prior = DVector::from_vec(vec![1.0, 2.0]);
let residual = DVector::from_vec(vec![0.1, -0.1]);
let prediction = DVector::from_vec(vec![1.1, 1.9]);
let result = gate.verify(&prior, &residual, &prediction).unwrap();
assert!(result.certificate_error >= 0.0);
assert!(result.work_performed > 0);
assert!(result.computation_time_us > 0.0);
}
#[test]
fn test_statistics_tracking() {
let config = create_test_config();
let mut gate = SolverGate::new(&config).unwrap();
// Perform several verifications
for i in 0..5 {
let prior = DVector::from_vec(vec![i as f64, i as f64]);
let residual = DVector::from_vec(vec![0.1, 0.1]);
let prediction = &prior + &residual;
let _ = gate.verify(&prior, &residual, &prediction);
}
assert_eq!(gate.stats.total_verifications, 5);
assert!(gate.get_pass_rate() >= 0.0 && gate.get_pass_rate() <= 1.0);
assert!(gate.get_avg_certificate_error() >= 0.0);
}
#[test]
fn test_dynamic_configuration() {
let config = create_test_config();
let mut gate = SolverGate::new(&config).unwrap();
// Test epsilon update
gate.set_epsilon(0.01).unwrap();
assert_eq!(gate.config.epsilon, 0.01);
// Test budget update
gate.set_budget(50000).unwrap();
assert_eq!(gate.config.budget, 50000);
// Test invalid values
assert!(gate.set_epsilon(-1.0).is_err());
assert!(gate.set_budget(0).is_err());
}
#[test]
fn test_inference_preparation() {
let config = create_test_config();
let mut gate = SolverGate::new(&config).unwrap();
assert!(gate.prepare_for_inference().is_ok());
assert!(gate.is_inference_ready());
let metrics = gate.get_performance_metrics();
assert_eq!(metrics.total_verifications, 0);
}
#[test]
fn test_performance_targets() {
let config = create_test_config();
let gate = SolverGate::new(&config).unwrap();
// Should meet initial targets (no data yet)
assert!(gate.meets_performance_targets(1000.0, 0.9));
}
}
@@ -0,0 +1,206 @@
//! Simplified solver gate for compilation - will be replaced with full implementation
use crate::error::{Result, TemporalNeuralError};
use crate::solvers::InferenceReadyTrait;
use nalgebra::{DMatrix, DVector};
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
/// Simplified solver gate configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverGateConfig {
pub algorithm: String,
pub epsilon: f64,
pub max_iterations: usize,
pub budget: u64,
pub max_cert_error: f64,
}
/// Gate result from solver verification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GateResult {
pub passed: bool,
pub confidence: f64,
pub certificate_error: f64,
pub verification_time_us: f64,
pub work_performed: u64,
pub certificate: Option<Certificate>,
}
/// Mathematical certificate from solver
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Certificate {
pub error_bound: f64,
pub algorithm: String,
pub verification_id: String,
pub timestamp: DateTime<Utc>,
pub computational_work: u64,
pub confidence_level: f64,
}
/// Simplified solver gate
#[derive(Debug)]
pub struct SolverGate {
config: SolverGateConfig,
verification_history: Vec<GateResult>,
}
impl SolverGate {
/// Create a new solver gate
pub fn new(config: &SolverGateConfig) -> Result<Self> {
Ok(Self {
config: config.clone(),
verification_history: Vec::new(),
})
}
/// Get pass rate of verifications
pub fn get_pass_rate(&self) -> f64 {
if self.verification_history.is_empty() {
return 1.0;
}
let passed = self.verification_history.iter().filter(|r| r.passed).count();
passed as f64 / self.verification_history.len() as f64
}
/// Get average certificate error
pub fn get_avg_certificate_error(&self) -> f64 {
if self.verification_history.is_empty() {
return 0.0;
}
let total_error: f64 = self.verification_history.iter()
.map(|r| r.certificate_error)
.sum();
total_error / self.verification_history.len() as f64
}
/// Get average computational work
pub fn get_avg_work(&self) -> f64 {
if self.verification_history.is_empty() {
return 0.0;
}
let total_work: u64 = self.verification_history.iter()
.map(|r| r.work_performed)
.sum();
total_work as f64 / self.verification_history.len() as f64
}
/// Get total prediction count
pub fn get_prediction_count(&self) -> u64 {
self.verification_history.len() as u64
}
/// Set epsilon parameter
pub fn set_epsilon(&mut self, epsilon: f64) -> Result<()> {
if epsilon <= 0.0 {
return Err(TemporalNeuralError::ConfigurationError {
message: "Epsilon must be positive".to_string(),
field: Some("epsilon".to_string()),
});
}
self.config.epsilon = epsilon;
Ok(())
}
/// Set budget parameter
pub fn set_budget(&mut self, budget: u64) -> Result<()> {
if budget == 0 {
return Err(TemporalNeuralError::ConfigurationError {
message: "Budget must be positive".to_string(),
field: Some("budget".to_string()),
});
}
self.config.budget = budget;
Ok(())
}
/// Verify a prediction using simplified logic
pub fn verify(
&mut self,
_prior: &DMatrix<f64>,
_residual: &DMatrix<f64>,
_prediction: &DMatrix<f64>,
) -> Result<GateResult> {
// Simplified verification - always passes for now
// TODO: Implement actual solver-based verification
let result = GateResult {
passed: true,
confidence: 0.95,
certificate_error: 0.001,
verification_time_us: 10.0,
work_performed: 100,
certificate: Some(Certificate {
error_bound: 0.001,
algorithm: self.config.algorithm.clone(),
verification_id: uuid::Uuid::new_v4().to_string(),
timestamp: chrono::Utc::now(),
computational_work: 100,
confidence_level: 0.95,
}),
};
self.verification_history.push(result.clone());
Ok(result)
}
/// Get verification statistics
pub fn get_stats(&self) -> SolverGateStats {
let total_verifications = self.verification_history.len() as u64;
let passed_verifications = self.verification_history.iter()
.filter(|r| r.passed)
.count() as u64;
let avg_verification_time = if total_verifications > 0 {
self.verification_history.iter()
.map(|r| r.verification_time_us)
.sum::<f64>() / total_verifications as f64
} else {
0.0
};
SolverGateStats {
total_verifications,
passed_verifications,
average_confidence: 0.95,
total_certificate_error: 0.001,
total_work: total_verifications * 100,
avg_verification_time_us: avg_verification_time,
p99_9_verification_time_us: avg_verification_time * 1.1,
}
}
}
/// Statistics from solver gate operations
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverGateStats {
pub total_verifications: u64,
pub passed_verifications: u64,
pub average_confidence: f64,
pub total_certificate_error: f64,
pub total_work: u64,
pub avg_verification_time_us: f64,
pub p99_9_verification_time_us: f64,
}
impl InferenceReadyTrait for SolverGate {
fn prepare_for_inference(&mut self) -> Result<()> {
// Clear verification history to save memory
self.verification_history.clear();
Ok(())
}
fn is_inference_ready(&self) -> bool {
true // Simple implementation is always ready
}
fn memory_usage(&self) -> usize {
std::mem::size_of::<Self>() +
self.verification_history.len() * std::mem::size_of::<GateResult>()
}
fn reset(&mut self) -> Result<()> {
self.verification_history.clear();
Ok(())
}
}