use std::collections::HashMap;
use std::time::Instant;
use scirs2_core::ndarray::Array1;
use optirs_core::optimizers::SGD;
use optirs_core::streaming::{StreamingConfig, StreamingDataPoint, StreamingOptimizer};
struct Xorshift64(u64);
impl Xorshift64 {
fn new(seed: u64) -> Self {
Self(seed | 1)
}
fn next_f64(&mut self) -> f64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
(x >> 11) as f64 / (1u64 << 53) as f64 - 0.5
}
fn next_features(&mut self, dim: usize) -> Vec<f64> {
(0..dim).map(|_| self.next_f64()).collect()
}
}
fn true_target(features: &[f64]) -> f64 {
2.0 * features[0] - features[1] + 0.5 * features[2]
}
fn mse(params: &Array1<f64>, test_set: &[(Vec<f64>, f64)]) -> f64 {
let mut sum = 0.0;
for (features, target) in test_set {
let pred: f64 = features.iter().zip(params.iter()).map(|(f, w)| f * w).sum();
let err = pred - target;
sum += err * err;
}
sum / test_set.len() as f64
}
#[test]
fn streaming_linear_regression_converges() {
let config = StreamingConfig {
buffer_size: 8,
async_updates: false,
gradient_compression: false,
adaptive_learning_rate: false,
multi_stream_coordination: false,
predictive_streaming: false,
stream_fusion: false,
adaptive_resource_allocation: false,
..Default::default()
};
let base_optimizer = SGD::new(0.05_f64);
let mut streaming: StreamingOptimizer<SGD<f64>, f64, scirs2_core::ndarray::Ix1> =
StreamingOptimizer::new(base_optimizer, config)
.expect("failed to construct StreamingOptimizer");
let mut test_rng = Xorshift64::new(0x00FE_EDFA_CEC0_FFEEu64);
let test_set: Vec<(Vec<f64>, f64)> = (0..64)
.map(|_| {
let f = test_rng.next_features(3);
let t = true_target(&f);
(f, t)
})
.collect();
let initial_loss = mse(&Array1::zeros(3), &test_set);
assert!(initial_loss > 0.0, "test set should be non-trivial");
let mut train_rng = Xorshift64::new(0x00C0_FFEE_1234_5678u64);
let mut last_params: Option<Array1<f64>> = None;
for _ in 0..3000 {
let f = train_rng.next_features(3);
let t = true_target(&f);
let point = StreamingDataPoint {
features: Array1::from_vec(f),
target: Some(t),
timestamp: Instant::now(),
weight: 1.0,
metadata: HashMap::new(),
};
if let Some(params) = streaming
.process_sample(point)
.expect("process_sample failed")
{
last_params = Some(params);
}
}
if let Some(params) = streaming.flush().expect("flush failed") {
last_params = Some(params);
}
let final_params = last_params.expect("streaming optimizer never produced an update");
let final_loss = mse(&final_params, &test_set);
assert!(
final_loss < initial_loss * 0.1,
"streaming linear regression failed to converge: \
initial_loss={initial_loss}, final_loss={final_loss}, final_params={final_params:?}"
);
assert!(
streaming.get_metrics().current_loss > 0.0,
"current_loss was never populated with a real batch loss"
);
let observed = streaming
.current_parameters()
.expect("current_parameters() returned None after training");
assert_eq!(observed, &final_params);
assert!(
streaming.step_count >= 300,
"step_count={}",
streaming.step_count
);
}
#[test]
fn streaming_async_updates_change_parameters() {
let config = StreamingConfig {
buffer_size: 4,
async_updates: true,
max_staleness: 2,
adaptive_learning_rate: false,
gradient_compression: false,
multi_stream_coordination: false,
predictive_streaming: false,
stream_fusion: false,
adaptive_resource_allocation: false,
..Default::default()
};
let base_optimizer = SGD::new(0.1_f64);
let mut streaming: StreamingOptimizer<SGD<f64>, f64, scirs2_core::ndarray::Ix1> =
StreamingOptimizer::new(base_optimizer, config)
.expect("failed to construct StreamingOptimizer");
let mut train_rng = Xorshift64::new(0x00AB_CDEF_0123_4567u64);
let mut saw_nonzero_update = false;
for _ in 0..200 {
let f = train_rng.next_features(3);
let t = true_target(&f);
let point = StreamingDataPoint {
features: Array1::from_vec(f),
target: Some(t),
timestamp: Instant::now(),
weight: 1.0,
metadata: HashMap::new(),
};
if let Some(params) = streaming
.process_sample(point)
.expect("process_sample failed")
{
if params.iter().any(|&p| p != 0.0) {
saw_nonzero_update = true;
}
}
}
assert!(
saw_nonzero_update,
"async updates never changed the live parameters away from zero"
);
assert!(
streaming
.current_parameters()
.is_some_and(|p| p.iter().any(|&v| v != 0.0)),
"current_parameters() does not reflect applied async updates"
);
}