mirror of
https://github.com/ruvnet/RuView
synced 2026-08-04 19:31:42 +00:00
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/.
This commit is contained in:
@@ -0,0 +1,453 @@
|
||||
//! Fast sampling techniques for sublinear algorithms
|
||||
//!
|
||||
//! Implements advanced sampling methods needed for true sublinear complexity,
|
||||
//! including importance sampling, reservoir sampling, and sketching techniques.
|
||||
|
||||
use crate::types::Precision;
|
||||
use crate::error::{SolverError, Result};
|
||||
use alloc::{vec::Vec, string::String};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand::rngs::StdRng;
|
||||
|
||||
/// Configuration for sampling algorithms
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SamplingConfig {
|
||||
/// Sampling probability
|
||||
pub sampling_prob: Precision,
|
||||
/// Reservoir size for reservoir sampling
|
||||
pub reservoir_size: usize,
|
||||
/// Sketch dimension for matrix sketching
|
||||
pub sketch_dimension: usize,
|
||||
/// Random seed
|
||||
pub seed: Option<u64>,
|
||||
}
|
||||
|
||||
impl Default for SamplingConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
sampling_prob: 0.01,
|
||||
reservoir_size: 1000,
|
||||
sketch_dimension: 64,
|
||||
seed: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Importance sampling engine
|
||||
#[derive(Debug)]
|
||||
pub struct ImportanceSampler {
|
||||
config: SamplingConfig,
|
||||
rng: StdRng,
|
||||
}
|
||||
|
||||
impl ImportanceSampler {
|
||||
/// Create new importance sampler
|
||||
pub fn new(config: SamplingConfig) -> Self {
|
||||
let rng = match config.seed {
|
||||
Some(seed) => StdRng::seed_from_u64(seed),
|
||||
None => StdRng::from_entropy(),
|
||||
};
|
||||
|
||||
Self { config, rng }
|
||||
}
|
||||
|
||||
/// Sample matrix entries with importance weighting
|
||||
pub fn sample_matrix_entries(
|
||||
&mut self,
|
||||
entries: &[(usize, usize, Precision)],
|
||||
) -> Result<Vec<(usize, usize, Precision)>> {
|
||||
if entries.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
// Compute importance weights (based on magnitude)
|
||||
let mut weights = Vec::with_capacity(entries.len());
|
||||
let mut total_weight = 0.0;
|
||||
|
||||
for &(_, _, value) in entries {
|
||||
let weight = value.abs();
|
||||
weights.push(weight);
|
||||
total_weight += weight;
|
||||
}
|
||||
|
||||
if total_weight == 0.0 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
// Normalize weights to probabilities
|
||||
for weight in &mut weights {
|
||||
*weight /= total_weight;
|
||||
}
|
||||
|
||||
// Sample entries based on importance
|
||||
let target_samples = (entries.len() as f64 * self.config.sampling_prob).ceil() as usize;
|
||||
let mut sampled_entries = Vec::new();
|
||||
|
||||
for _ in 0..target_samples {
|
||||
let sample_index = self.weighted_sample(&weights)?;
|
||||
let (i, j, value) = entries[sample_index];
|
||||
|
||||
// Reweight to maintain expectation
|
||||
let reweighted_value = value / weights[sample_index];
|
||||
sampled_entries.push((i, j, reweighted_value));
|
||||
}
|
||||
|
||||
Ok(sampled_entries)
|
||||
}
|
||||
|
||||
/// Sample a single index based on weights
|
||||
fn weighted_sample(&mut self, weights: &[Precision]) -> Result<usize> {
|
||||
let random_val = self.rng.gen::<f64>();
|
||||
let mut cumulative = 0.0;
|
||||
|
||||
for (i, &weight) in weights.iter().enumerate() {
|
||||
cumulative += weight;
|
||||
if random_val <= cumulative {
|
||||
return Ok(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to last index
|
||||
Ok(weights.len() - 1)
|
||||
}
|
||||
|
||||
/// Sample vector entries with importance weights
|
||||
pub fn sample_vector_entries(
|
||||
&mut self,
|
||||
vector: &[Precision],
|
||||
) -> Result<Vec<(usize, Precision)>> {
|
||||
if vector.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
// Compute importance weights
|
||||
let total_magnitude: Precision = vector.iter().map(|x| x.abs()).sum();
|
||||
if total_magnitude == 0.0 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let target_samples = (vector.len() as f64 * self.config.sampling_prob).ceil() as usize;
|
||||
let mut sampled_entries = Vec::new();
|
||||
|
||||
for i in 0..target_samples.min(vector.len()) {
|
||||
let importance_weight = vector[i].abs() / total_magnitude;
|
||||
|
||||
if self.rng.gen::<f64>() < importance_weight / self.config.sampling_prob {
|
||||
let reweighted_value = vector[i] / importance_weight;
|
||||
sampled_entries.push((i, reweighted_value));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(sampled_entries)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reservoir sampling for streaming data
|
||||
#[derive(Debug)]
|
||||
pub struct ReservoirSampler {
|
||||
reservoir: Vec<(usize, usize, Precision)>,
|
||||
reservoir_size: usize,
|
||||
items_seen: usize,
|
||||
rng: StdRng,
|
||||
}
|
||||
|
||||
impl ReservoirSampler {
|
||||
/// Create new reservoir sampler
|
||||
pub fn new(reservoir_size: usize, seed: Option<u64>) -> Self {
|
||||
let rng = match seed {
|
||||
Some(s) => StdRng::seed_from_u64(s),
|
||||
None => StdRng::from_entropy(),
|
||||
};
|
||||
|
||||
Self {
|
||||
reservoir: Vec::with_capacity(reservoir_size),
|
||||
reservoir_size,
|
||||
items_seen: 0,
|
||||
rng,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add new item to reservoir (maintains uniform sample)
|
||||
pub fn add_item(&mut self, i: usize, j: usize, value: Precision) {
|
||||
self.items_seen += 1;
|
||||
|
||||
if self.reservoir.len() < self.reservoir_size {
|
||||
// Fill reservoir first
|
||||
self.reservoir.push((i, j, value));
|
||||
} else {
|
||||
// Randomly replace existing item
|
||||
let replace_index = self.rng.gen_range(0..self.items_seen);
|
||||
if replace_index < self.reservoir_size {
|
||||
self.reservoir[replace_index] = (i, j, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current reservoir contents
|
||||
pub fn get_sample(&self) -> Vec<(usize, usize, Precision)> {
|
||||
self.reservoir.clone()
|
||||
}
|
||||
|
||||
/// Get number of items processed
|
||||
pub fn items_seen(&self) -> usize {
|
||||
self.items_seen
|
||||
}
|
||||
|
||||
/// Clear reservoir and reset counters
|
||||
pub fn reset(&mut self) {
|
||||
self.reservoir.clear();
|
||||
self.items_seen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Matrix sketching for dimension reduction
|
||||
#[derive(Debug)]
|
||||
pub struct MatrixSketcher {
|
||||
sketch_dimension: usize,
|
||||
sketch_matrix: Vec<Vec<Precision>>,
|
||||
original_dimension: usize,
|
||||
rng: StdRng,
|
||||
}
|
||||
|
||||
impl MatrixSketcher {
|
||||
/// Create new matrix sketcher
|
||||
pub fn new(
|
||||
original_dimension: usize,
|
||||
sketch_dimension: usize,
|
||||
seed: Option<u64>,
|
||||
) -> Result<Self> {
|
||||
if sketch_dimension > original_dimension {
|
||||
return Err(SolverError::InvalidInput {
|
||||
message: "Sketch dimension must be <= original dimension".to_string(),
|
||||
parameter: Some("sketch_dimension".to_string()),
|
||||
});
|
||||
}
|
||||
|
||||
let mut rng = match seed {
|
||||
Some(s) => StdRng::seed_from_u64(s),
|
||||
None => StdRng::from_entropy(),
|
||||
};
|
||||
|
||||
// Generate random sketch matrix
|
||||
let mut sketch_matrix = vec![vec![0.0; original_dimension]; sketch_dimension];
|
||||
let scale = (1.0 / sketch_dimension as f64).sqrt();
|
||||
|
||||
for i in 0..sketch_dimension {
|
||||
for j in 0..original_dimension {
|
||||
// Random sign matrix (Rademacher distribution)
|
||||
sketch_matrix[i][j] = if rng.gen::<bool>() { scale } else { -scale };
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
sketch_dimension,
|
||||
sketch_matrix,
|
||||
original_dimension,
|
||||
rng,
|
||||
})
|
||||
}
|
||||
|
||||
/// Sketch a vector (reduce dimension)
|
||||
pub fn sketch_vector(&self, vector: &[Precision]) -> Result<Vec<Precision>> {
|
||||
if vector.len() != self.original_dimension {
|
||||
return Err(SolverError::DimensionMismatch {
|
||||
expected: self.original_dimension,
|
||||
actual: vector.len(),
|
||||
operation: "sketch_vector".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut sketched = vec![0.0; self.sketch_dimension];
|
||||
|
||||
for i in 0..self.sketch_dimension {
|
||||
for j in 0..self.original_dimension {
|
||||
sketched[i] += self.sketch_matrix[i][j] * vector[j];
|
||||
}
|
||||
}
|
||||
|
||||
Ok(sketched)
|
||||
}
|
||||
|
||||
/// Sketch a matrix (reduce both dimensions)
|
||||
pub fn sketch_matrix(
|
||||
&self,
|
||||
matrix_rows: &[Vec<Precision>],
|
||||
) -> Result<Vec<Vec<Precision>>> {
|
||||
if matrix_rows.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut sketched_rows = Vec::new();
|
||||
|
||||
for row in matrix_rows {
|
||||
sketched_rows.push(self.sketch_vector(row)?);
|
||||
}
|
||||
|
||||
Ok(sketched_rows)
|
||||
}
|
||||
|
||||
/// Get compression ratio
|
||||
pub fn compression_ratio(&self) -> Precision {
|
||||
self.sketch_dimension as Precision / self.original_dimension as Precision
|
||||
}
|
||||
|
||||
/// Reconstruct approximate vector (simplified)
|
||||
pub fn reconstruct_vector(&self, sketched: &[Precision]) -> Result<Vec<Precision>> {
|
||||
if sketched.len() != self.sketch_dimension {
|
||||
return Err(SolverError::DimensionMismatch {
|
||||
expected: self.sketch_dimension,
|
||||
actual: sketched.len(),
|
||||
operation: "reconstruct_vector".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Simple reconstruction using transpose
|
||||
let mut reconstructed = vec![0.0; self.original_dimension];
|
||||
|
||||
for j in 0..self.original_dimension {
|
||||
for i in 0..self.sketch_dimension {
|
||||
reconstructed[j] += self.sketch_matrix[i][j] * sketched[i];
|
||||
}
|
||||
}
|
||||
|
||||
Ok(reconstructed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Adaptive sampling that adjusts parameters based on observed error
|
||||
#[derive(Debug)]
|
||||
pub struct AdaptiveSampler {
|
||||
importance_sampler: ImportanceSampler,
|
||||
reservoir_sampler: ReservoirSampler,
|
||||
matrix_sketcher: Option<MatrixSketcher>,
|
||||
adaptive_threshold: Precision,
|
||||
current_error: Precision,
|
||||
}
|
||||
|
||||
impl AdaptiveSampler {
|
||||
/// Create new adaptive sampler
|
||||
pub fn new(
|
||||
config: SamplingConfig,
|
||||
original_dimension: Option<usize>,
|
||||
) -> Result<Self> {
|
||||
let importance_sampler = ImportanceSampler::new(config.clone());
|
||||
let reservoir_sampler = ReservoirSampler::new(config.reservoir_size, config.seed);
|
||||
|
||||
let matrix_sketcher = if let Some(dim) = original_dimension {
|
||||
Some(MatrixSketcher::new(dim, config.sketch_dimension, config.seed)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
importance_sampler,
|
||||
reservoir_sampler,
|
||||
matrix_sketcher,
|
||||
adaptive_threshold: 0.1,
|
||||
current_error: 0.0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Adapt sampling parameters based on error
|
||||
pub fn adapt_parameters(&mut self, observed_error: Precision) {
|
||||
self.current_error = observed_error;
|
||||
|
||||
if observed_error > self.adaptive_threshold * 2.0 {
|
||||
// Increase sampling probability
|
||||
self.importance_sampler.config.sampling_prob =
|
||||
(self.importance_sampler.config.sampling_prob * 1.5).min(1.0);
|
||||
} else if observed_error < self.adaptive_threshold * 0.5 {
|
||||
// Decrease sampling probability
|
||||
self.importance_sampler.config.sampling_prob =
|
||||
(self.importance_sampler.config.sampling_prob * 0.8).max(0.001);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current sampling statistics
|
||||
pub fn get_statistics(&self) -> SamplingStatistics {
|
||||
SamplingStatistics {
|
||||
current_sampling_prob: self.importance_sampler.config.sampling_prob,
|
||||
reservoir_items_seen: self.reservoir_sampler.items_seen(),
|
||||
current_error: self.current_error,
|
||||
compression_ratio: self.matrix_sketcher
|
||||
.as_ref()
|
||||
.map(|s| s.compression_ratio())
|
||||
.unwrap_or(1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sampling performance statistics
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SamplingStatistics {
|
||||
pub current_sampling_prob: Precision,
|
||||
pub reservoir_items_seen: usize,
|
||||
pub current_error: Precision,
|
||||
pub compression_ratio: Precision,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_importance_sampler() {
|
||||
let config = SamplingConfig {
|
||||
sampling_prob: 0.5,
|
||||
..Default::default()
|
||||
};
|
||||
let mut sampler = ImportanceSampler::new(config);
|
||||
|
||||
let entries = vec![
|
||||
(0, 0, 1.0),
|
||||
(0, 1, 10.0), // High importance
|
||||
(1, 0, 0.1),
|
||||
(1, 1, 2.0),
|
||||
];
|
||||
|
||||
let sampled = sampler.sample_matrix_entries(&entries).unwrap();
|
||||
assert!(!sampled.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reservoir_sampler() {
|
||||
let mut sampler = ReservoirSampler::new(3, Some(42));
|
||||
|
||||
// Add more items than reservoir size
|
||||
for i in 0..10 {
|
||||
sampler.add_item(i, i, i as f64);
|
||||
}
|
||||
|
||||
let sample = sampler.get_sample();
|
||||
assert_eq!(sample.len(), 3);
|
||||
assert_eq!(sampler.items_seen(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matrix_sketcher() {
|
||||
let sketcher = MatrixSketcher::new(10, 5, Some(123)).unwrap();
|
||||
let vector = vec![1.0; 10];
|
||||
|
||||
let sketched = sketcher.sketch_vector(&vector).unwrap();
|
||||
assert_eq!(sketched.len(), 5);
|
||||
|
||||
let reconstructed = sketcher.reconstruct_vector(&sketched).unwrap();
|
||||
assert_eq!(reconstructed.len(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_adaptive_sampler() {
|
||||
let config = SamplingConfig::default();
|
||||
let mut adaptive = AdaptiveSampler::new(config, Some(20)).unwrap();
|
||||
|
||||
let initial_prob = adaptive.importance_sampler.config.sampling_prob;
|
||||
|
||||
// High error should increase sampling
|
||||
adaptive.adapt_parameters(1.0);
|
||||
assert!(adaptive.importance_sampler.config.sampling_prob >= initial_prob);
|
||||
|
||||
// Low error should decrease sampling
|
||||
adaptive.adapt_parameters(0.001);
|
||||
assert!(adaptive.importance_sampler.config.sampling_prob <= initial_prob);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user