1use super::anomaly_detection::{AnomalyDetector, AnomalyDiagnostics};
8use super::buffering::{AdaptiveBuffer, BufferDiagnostics};
9use super::config::*;
10use super::drift_detection::{DriftDiagnostics, EnhancedDriftDetector};
11use super::meta_learning::{MetaAction, MetaLearner, MetaLearningDiagnostics, MetaState};
12use super::performance::{
13 DataStatistics, PerformanceDiagnostics, PerformanceSnapshot, PerformanceTracker,
14};
15use super::resource_management::{ResourceDiagnostics, ResourceManager, ResourceUsage};
16
17use crate::optimizers::Optimizer;
18use crate::utils::try_scalar_str;
19use scirs2_core::ndarray::{Array, Array1, Dimension};
20use scirs2_core::numeric::Float;
21use scirs2_core::ScientificNumber;
22use serde::Serialize;
23use std::collections::{HashMap, VecDeque};
24use std::marker::PhantomData;
25use std::time::{Duration, Instant};
26
27pub const RECENT_ADAPTATION_WINDOW: Duration = Duration::from_secs(300);
30
31#[derive(Debug, Clone)]
51pub struct AdaptiveLearningRateController<A: Float> {
52 current_lr: A,
54 initial_lr: A,
56 min_lr: A,
58 max_lr: A,
60 squared_gradient_norm_sum: A,
62 trend_step: A,
64 last_change: Option<A>,
66 updates: usize,
69 cyclical: Option<CyclicalSchedule<A>>,
72}
73
74#[derive(Debug, Clone)]
83struct CyclicalSchedule<A: Float> {
84 base_rate: A,
86 max_rate: A,
88 step_size: A,
90 cycle_mode: CycleMode,
92 scale_function: ScaleFunction,
94}
95
96impl<A: Float> CyclicalSchedule<A> {
97 fn rate_at(&self, iteration: usize) -> A {
103 let Some(step) = A::from(iteration) else {
104 return self.base_rate;
105 };
106 let two = match A::from(2.0) {
107 Some(two) => two,
108 None => return self.base_rate,
109 };
110
111 let cycle = (A::one() + step / (two * self.step_size)).floor();
113 let x = (step / self.step_size - two * cycle + A::one()).abs();
114 let position = (A::one() - x).max(A::zero()).min(A::one());
115
116 let ramp = match &self.scale_function {
117 ScaleFunction::Linear => position,
118 ScaleFunction::Polynomial { power } => match A::from(*power) {
119 Some(power) => position.powf(power),
120 None => position,
121 },
122 ScaleFunction::Exponential { factor } => match A::from(*factor) {
124 Some(factor) if factor > A::zero() => factor.powf(A::one() - position),
125 _ => position,
126 },
127 ScaleFunction::Custom(_) => position,
129 };
130
131 let amplitude = match &self.cycle_mode {
132 CycleMode::Triangular => A::one(),
133 CycleMode::Triangular2 => {
135 let exponent = (cycle - A::one()).max(A::zero());
136 A::one() / two.powf(exponent)
137 }
138 CycleMode::ExponentialRange => match &self.scale_function {
141 ScaleFunction::Exponential { factor } => match A::from(*factor) {
142 Some(gamma) if gamma > A::zero() => gamma.powf(step),
143 _ => A::one(),
144 },
145 _ => A::one(),
146 },
147 CycleMode::Custom(_) => A::one(),
149 };
150
151 let span = self.max_rate - self.base_rate;
152 (self.base_rate + span * ramp * amplitude)
153 .max(self.base_rate.min(self.max_rate))
154 .min(self.base_rate.max(self.max_rate))
155 }
156}
157
158impl<A: Float> AdaptiveLearningRateController<A> {
159 pub fn new(config: &StreamingConfig) -> Result<Self, crate::error::OptimError> {
161 let lr_config = &config.learning_rate_config;
162 let convert = |value: f64, name: &str| -> Result<A, crate::error::OptimError> {
163 A::from(value).ok_or_else(|| {
164 crate::error::OptimError::InvalidConfig(format!(
165 "learning rate {name} ({value}) is not representable in the element type"
166 ))
167 })
168 };
169
170 let initial_lr = convert(lr_config.initial_rate, "initial_rate")?;
171 let min_lr = convert(lr_config.min_rate, "min_rate")?;
172 let max_lr = convert(lr_config.max_rate, "max_rate")?;
173 if min_lr > max_lr {
174 return Err(crate::error::OptimError::InvalidConfig(format!(
175 "learning rate min_rate ({}) exceeds max_rate ({})",
176 lr_config.min_rate, lr_config.max_rate
177 )));
178 }
179 let trend_step = convert(
180 lr_config.performance_sensitivity.clamp(1e-6, 0.5),
181 "performance_sensitivity",
182 )?;
183
184 let cyclical = if lr_config.enable_cyclical_rates {
185 let cycle = &lr_config.cycle_config;
186 if let ScaleFunction::Custom(name) = &cycle.scale_function {
187 return Err(crate::error::OptimError::InvalidConfig(format!(
188 "cyclical learning-rate scale_function Custom(\"{name}\") has no \
189 registered implementation; use Linear, Exponential or Polynomial"
190 )));
191 }
192 if let CycleMode::Custom(name) = &cycle.cycle_mode {
193 return Err(crate::error::OptimError::InvalidConfig(format!(
194 "cyclical learning-rate cycle_mode Custom(\"{name}\") has no \
195 registered implementation; use Triangular, Triangular2 or \
196 ExponentialRange"
197 )));
198 }
199 if matches!(cycle.cycle_mode, CycleMode::ExponentialRange)
200 && !matches!(cycle.scale_function, ScaleFunction::Exponential { .. })
201 {
202 return Err(crate::error::OptimError::InvalidConfig(
203 "cyclical cycle_mode ExponentialRange needs its decay factor from \
204 scale_function = Exponential { factor }"
205 .to_string(),
206 ));
207 }
208 if cycle.cycle_length == 0 {
209 return Err(crate::error::OptimError::InvalidConfig(
210 "cyclical learning-rate cycle_length must be greater than zero".to_string(),
211 ));
212 }
213 let base_rate = convert(cycle.base_rate, "cycle_config.base_rate")?;
214 let cycle_max_rate = convert(cycle.max_rate, "cycle_config.max_rate")?;
215 if base_rate > cycle_max_rate {
216 return Err(crate::error::OptimError::InvalidConfig(format!(
217 "cyclical base_rate ({}) exceeds cycle max_rate ({})",
218 cycle.base_rate, cycle.max_rate
219 )));
220 }
221 let step_size = convert(
222 ((cycle.cycle_length as f64) / 2.0).max(1.0),
223 "cycle_config.cycle_length",
224 )?;
225 Some(CyclicalSchedule {
226 base_rate,
227 max_rate: cycle_max_rate,
228 step_size,
229 cycle_mode: cycle.cycle_mode.clone(),
230 scale_function: cycle.scale_function.clone(),
231 })
232 } else {
233 None
234 };
235
236 let initial_current = match cyclical.as_ref() {
237 Some(schedule) => schedule.rate_at(0).max(min_lr).min(max_lr),
240 None => initial_lr.max(min_lr).min(max_lr),
241 };
242
243 Ok(Self {
244 current_lr: initial_current,
245 initial_lr,
246 min_lr,
247 max_lr,
248 squared_gradient_norm_sum: A::zero(),
249 trend_step,
250 last_change: None,
251 updates: 0,
252 cyclical,
253 })
254 }
255
256 pub fn update_learning_rate(&mut self, gradient: &Array1<A>) -> A {
262 let squared_norm = gradient.iter().fold(A::zero(), |acc, &g| acc + g * g);
263 if squared_norm.is_finite() {
264 self.squared_gradient_norm_sum = self.squared_gradient_norm_sum + squared_norm;
265 }
266
267 let proposed = match self.cyclical.as_ref() {
272 Some(schedule) => schedule.rate_at(self.updates),
273 None => {
274 self.initial_lr * (A::one() / (A::one() + self.squared_gradient_norm_sum.sqrt()))
275 }
276 };
277 let proposed = proposed.max(self.min_lr).min(self.max_lr);
278 self.set_rate(proposed);
279 self.current_lr
280 }
281
282 #[cfg(test)]
284 pub(crate) fn rate_at_for_test(&self, iteration: usize) -> A {
285 match self.cyclical.as_ref() {
286 Some(schedule) => schedule.rate_at(iteration),
287 None => self.current_lr,
288 }
289 }
290
291 pub fn is_cyclical(&self) -> bool {
293 self.cyclical.is_some()
294 }
295
296 pub fn current_rate(&self) -> A {
298 self.current_lr
299 }
300
301 pub fn accumulated_squared_gradient_norm(&self) -> A {
303 self.squared_gradient_norm_sum
304 }
305
306 pub fn update_count(&self) -> usize {
308 self.updates
309 }
310
311 pub fn compute_adaptation(&self, performance_metrics: &[A]) -> A {
321 if let Some(schedule) = self.cyclical.as_ref() {
324 return schedule
325 .rate_at(self.updates.saturating_add(1))
326 .max(self.min_lr)
327 .min(self.max_lr);
328 }
329 if performance_metrics.len() < 2 {
330 return self.current_lr;
331 }
332
333 let n = match A::from(performance_metrics.len()) {
334 Some(value) => value,
335 None => return self.current_lr,
336 };
337 let (Some(one_half), Some(six), Some(two)) = (A::from(0.5), A::from(6.0), A::from(2.0))
338 else {
339 return self.current_lr;
340 };
341
342 let sum_x = n * (n + A::one()) * one_half;
344 let sum_x_squared = n * (n + A::one()) * (two * n + A::one()) / six;
345 let mut sum_y = A::zero();
346 let mut sum_xy = A::zero();
347 for (index, &value) in performance_metrics.iter().enumerate() {
348 let x = match A::from(index + 1) {
349 Some(x) => x,
350 None => return self.current_lr,
351 };
352 sum_y = sum_y + value;
353 sum_xy = sum_xy + x * value;
354 }
355
356 let denominator = n * sum_x_squared - sum_x * sum_x;
357 if denominator == A::zero() {
358 return self.current_lr;
359 }
360 let slope = (n * sum_xy - sum_x * sum_y) / denominator;
361
362 let factor = if slope < A::zero() {
363 A::one() + self.trend_step
364 } else if slope > A::zero() {
365 A::one() - self.trend_step
366 } else {
367 A::one()
368 };
369
370 (self.current_lr * factor).max(self.min_lr).min(self.max_lr)
371 }
372
373 pub fn apply_adaptation(&mut self, adaptation: A) {
375 if !adaptation.is_finite() || adaptation <= A::zero() {
376 return;
379 }
380 self.set_rate(adaptation.max(self.min_lr).min(self.max_lr));
381 }
382
383 fn set_rate(&mut self, new_rate: A) {
384 let delta = new_rate - self.current_lr;
385 if delta != A::zero() {
386 self.last_change = Some(delta);
387 self.updates += 1;
388 }
389 self.current_lr = new_rate;
390 }
391
392 pub fn last_change(&self) -> Option<A> {
395 self.last_change
396 }
397
398 pub fn reset(&mut self) {
400 self.current_lr = self.initial_lr.max(self.min_lr).min(self.max_lr);
401 self.squared_gradient_norm_sum = A::zero();
402 self.last_change = None;
403 self.updates = 0;
404 }
405}
406
407#[derive(Debug, Clone)]
409pub struct StreamingDataPoint<A: Float + Send + Sync> {
410 pub features: Array1<A>,
412 pub target: Option<Array1<A>>,
414 pub timestamp: Instant,
416 pub source_id: Option<String>,
418 pub quality_score: A,
420 pub metadata: HashMap<String, String>,
422}
423
424#[derive(Debug, Clone)]
426pub struct Adaptation<A: Float + Send + Sync> {
427 pub adaptation_type: AdaptationType,
429 pub magnitude: A,
431 pub target_component: String,
433 pub parameters: HashMap<String, A>,
435 pub priority: AdaptationPriority,
437 pub timestamp: Instant,
439}
440
441#[derive(Debug, Clone, PartialEq, Eq)]
443pub enum AdaptationType {
444 LearningRate,
446 BufferSize,
448 DriftSensitivity,
450 ResourceAllocation,
452 PerformanceThreshold,
454 AnomalyDetection,
456 MetaLearning,
458 Custom(String),
460}
461
462#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
464pub enum AdaptationPriority {
465 Low = 0,
467 Normal = 1,
469 High = 2,
471 Critical = 3,
473}
474
475#[derive(Debug, Clone, Serialize)]
477pub struct AdaptiveStreamingStats {
478 pub total_data_points: usize,
480 pub optimization_steps: usize,
482 pub drift_events: usize,
484 pub anomalies_detected: usize,
486 pub adaptations_applied: usize,
488 pub recent_adaptations: usize,
492 pub current_buffer_size: usize,
494 pub current_learning_rate: f64,
496 pub avg_processing_time_ms: f64,
498 pub resource_utilization: ResourceUsage,
500 pub performance_trend: f64,
502 pub meta_learning_score: f64,
504}
505
506pub struct AdaptiveStreamingOptimizer<O, A, D>
508where
509 A: Float + Default + Clone + Send + Sync + std::iter::Sum,
510 D: Dimension,
511{
512 base_optimizer: O,
514 config: StreamingConfig,
516 buffer: AdaptiveBuffer<A>,
518 drift_detector: EnhancedDriftDetector<A>,
520 performance_tracker: PerformanceTracker<A>,
522 resource_manager: ResourceManager,
524 meta_learner: MetaLearner<A>,
526 anomaly_detector: AnomalyDetector<A>,
528 learning_rate_controller: AdaptiveLearningRateController<A>,
530 parameters: Option<Array<A, D>>,
532 stats: AdaptiveStreamingStats,
534 last_adaptation: Instant,
536 adaptation_history: VecDeque<Adaptation<A>>,
538 performance_baseline: Option<A>,
540 recent_feature_window: VecDeque<Vec<A>>,
543 last_gradient_norm: Option<A>,
545 last_update_magnitude: Option<A>,
547 last_step_duration: Duration,
549 _phantom: PhantomData<D>,
551}
552
553const FEATURE_WINDOW_CAPACITY: usize = 256;
555
556const LR_TREND_WINDOW: usize = 10;
559
560const ACCURACY_RELATIVE_TOLERANCE: f64 = 0.1;
563
564impl<O, A, D> AdaptiveStreamingOptimizer<O, A, D>
565where
566 A: Float
567 + Default
568 + Clone
569 + Send
570 + Sync
571 + std::iter::Sum
572 + std::fmt::Debug
573 + std::ops::DivAssign
574 + scirs2_core::ndarray::ScalarOperand
575 + 'static,
576 D: Dimension,
577 O: Optimizer<A, D> + Clone,
578{
579 pub fn new(base_optimizer: O, config: StreamingConfig) -> Result<Self, String> {
581 config.validate()?;
583
584 let buffer = AdaptiveBuffer::new(&config)?;
585 let drift_detector = EnhancedDriftDetector::new(&config)?;
586 let performance_tracker = PerformanceTracker::new(&config)?;
587 let resource_manager = ResourceManager::new(&config)?;
588 let meta_learner = MetaLearner::new(&config)?;
589 let anomaly_detector = AnomalyDetector::new(&config)?;
590 let learning_rate_controller =
591 AdaptiveLearningRateController::new(&config).map_err(|e| e.to_string())?;
592
593 let stats = AdaptiveStreamingStats {
594 total_data_points: 0,
595 optimization_steps: 0,
596 drift_events: 0,
597 anomalies_detected: 0,
598 adaptations_applied: 0,
599 recent_adaptations: 0,
600 current_buffer_size: config.buffer_config.initial_size,
601 current_learning_rate: config.learning_rate_config.initial_rate,
602 avg_processing_time_ms: 0.0,
603 resource_utilization: ResourceUsage::default(),
604 performance_trend: 0.0,
605 meta_learning_score: 0.0,
606 };
607
608 Ok(Self {
609 base_optimizer,
610 config,
611 buffer,
612 drift_detector,
613 performance_tracker,
614 resource_manager,
615 meta_learner,
616 anomaly_detector,
617 learning_rate_controller,
618 parameters: None,
619 stats,
620 last_adaptation: Instant::now(),
621 adaptation_history: VecDeque::with_capacity(1000),
622 performance_baseline: None,
623 recent_feature_window: VecDeque::with_capacity(FEATURE_WINDOW_CAPACITY),
624 last_gradient_norm: None,
625 last_update_magnitude: None,
626 last_step_duration: Duration::ZERO,
627 _phantom: PhantomData,
628 })
629 }
630
631 pub fn adaptive_step(
633 &mut self,
634 data_batch: Vec<StreamingDataPoint<A>>,
635 ) -> Result<Array<A, D>, String> {
636 let start_time = Instant::now();
637
638 self.resource_manager.update_utilization()?;
640
641 let filtered_batch = self.filter_anomalies(data_batch)?;
643 self.buffer.add_batch(filtered_batch)?;
644
645 if !self.should_process_buffer()? {
647 return self
648 .parameters
649 .clone()
650 .ok_or("No parameters available".to_string());
651 }
652
653 let processing_batch = self.buffer.get_batch_for_processing()?;
655 self.stats.total_data_points += processing_batch.len();
656
657 let drift_detected = self.drift_detector.detect_drift(&processing_batch)?;
659 if drift_detected {
660 self.stats.drift_events += 1;
661 }
662
663 let adaptations = self.compute_adaptations(&processing_batch, drift_detected)?;
665
666 self.apply_adaptations(&adaptations)?;
676 self.stats.adaptations_applied += adaptations.len();
677
678 let updated_parameters = self.perform_optimization_step(&processing_batch)?;
680
681 let performance = self.evaluate_performance(&processing_batch, &updated_parameters)?;
683
684 self.buffer
688 .record_processing_duration(self.last_step_duration);
689
690 self.performance_tracker
692 .add_performance(performance.clone())?;
693
694 self.update_meta_learner(&adaptations, &performance)?;
696
697 self.stats.optimization_steps += 1;
699 self.stats.current_buffer_size = self.buffer.current_size();
700 self.stats.current_learning_rate = self
701 .learning_rate_controller
702 .current_rate()
703 .to_f64()
704 .unwrap_or(0.0);
705 self.stats.performance_trend = self.compute_performance_trend();
706 self.stats.meta_learning_score = self
707 .meta_learner
708 .get_effectiveness_score()
709 .to_f64()
710 .unwrap_or(0.0);
711
712 let processing_time = start_time.elapsed().as_millis() as f64;
713 self.stats.avg_processing_time_ms = (self.stats.avg_processing_time_ms
714 * (self.stats.optimization_steps - 1) as f64
715 + processing_time)
716 / self.stats.optimization_steps as f64;
717
718 self.parameters = Some(updated_parameters.clone());
720
721 Ok(updated_parameters)
722 }
723
724 fn filter_anomalies(
726 &mut self,
727 data_batch: Vec<StreamingDataPoint<A>>,
728 ) -> Result<Vec<StreamingDataPoint<A>>, String> {
729 if !self.config.anomaly_config.enable_detection {
730 return Ok(data_batch);
731 }
732
733 self.publish_anomaly_context_signals()?;
737
738 let mut filtered_batch = Vec::new();
739
740 for data_point in data_batch {
741 self.remember_features(&data_point.features);
745 let is_anomaly = self.anomaly_detector.detect_anomaly(&data_point)?;
746
747 if is_anomaly {
748 self.stats.anomalies_detected += 1;
749
750 match &self.config.anomaly_config.response_strategy {
752 AnomalyResponseStrategy::Ignore => {
753 filtered_batch.push(data_point);
755 }
756 AnomalyResponseStrategy::Filter => {
757 continue;
759 }
760 AnomalyResponseStrategy::Adaptive => {
761 let adapted_point = self.adapt_for_anomaly(data_point)?;
763 filtered_batch.push(adapted_point);
764 }
765 AnomalyResponseStrategy::Reset => {
766 filtered_batch.push(data_point);
768 }
769 AnomalyResponseStrategy::Custom(_) => {
770 filtered_batch.push(data_point);
772 }
773 }
774 } else {
775 filtered_batch.push(data_point);
776 }
777 }
778
779 Ok(filtered_batch)
780 }
781
782 fn publish_anomaly_context_signals(&mut self) -> Result<(), String> {
785 let performance_metrics: Vec<A> =
786 match self.performance_tracker.get_recent_performance(1).first() {
787 Some(snapshot) => vec![
788 snapshot.loss,
789 snapshot.accuracy.unwrap_or_else(A::zero),
790 snapshot.convergence_rate.unwrap_or_else(A::zero),
791 ],
792 None => Vec::new(),
793 };
794
795 let usage = self.resource_manager.current_usage()?;
796 let mut resource_usage = Vec::new();
797 if let Some(memory_mb) = A::from(usage.memory_usage_mb as f64) {
798 resource_usage.push(memory_mb);
799 }
800 if let Some(cpu) = A::from(usage.cpu_usage_percent) {
801 resource_usage.push(cpu);
802 }
803
804 let diagnostics = self.drift_detector.get_diagnostics();
807 let mut drift_indicators = Vec::new();
808 if let Some(state) = A::from(match diagnostics.current_state {
809 crate::streaming::adaptive_streaming::drift_detection::DriftState::Stable => 0.0,
810 crate::streaming::adaptive_streaming::drift_detection::DriftState::Warning => 1.0,
811 crate::streaming::adaptive_streaming::drift_detection::DriftState::Drift => 2.0,
812 crate::streaming::adaptive_streaming::drift_detection::DriftState::Recovery => 3.0,
813 }) {
814 drift_indicators.push(state);
815 }
816 if let Some(fp_rate) = A::from(diagnostics.false_positive_rate) {
817 drift_indicators.push(fp_rate);
818 }
819
820 self.meta_learner
822 .update_context_signals(resource_usage.clone(), drift_indicators.clone());
823
824 self.anomaly_detector.update_context_signals(
825 performance_metrics,
826 resource_usage,
827 drift_indicators,
828 );
829 Ok(())
830 }
831
832 fn adapt_for_anomaly(
834 &self,
835 mut data_point: StreamingDataPoint<A>,
836 ) -> Result<StreamingDataPoint<A>, String> {
837 let median = self.compute_feature_median(&data_point.features)?;
839
840 for (i, value) in data_point.features.iter_mut().enumerate() {
841 let diff = (*value - median[i]).abs();
842 let threshold =
843 median[i] * try_scalar_str::<A, _>(self.config.anomaly_config.threshold)?;
844
845 if diff > threshold {
846 let sign = if *value > median[i] {
848 A::one()
849 } else {
850 -A::one()
851 };
852 *value = median[i] + sign * threshold;
853 }
854 }
855
856 data_point.quality_score = data_point.quality_score * try_scalar_str::<A, _>(0.5)?;
858
859 Ok(data_point)
860 }
861
862 fn compute_feature_median(&self, features: &Array1<A>) -> Result<Array1<A>, String> {
877 let window = &self.recent_feature_window;
878 if window.is_empty() {
879 return Ok(features.clone());
881 }
882
883 let mut medians = Array1::zeros(features.len());
884 for index in 0..features.len() {
885 let mut column: Vec<A> = window
886 .iter()
887 .filter_map(|point| point.get(index).copied())
888 .filter(|value| !value.is_nan())
889 .collect();
890 medians[index] = match super::statistics::median_in_place(&mut column) {
891 Some(median) => median,
892 None => features[index],
893 };
894 }
895 Ok(medians)
896 }
897
898 fn remember_features(&mut self, features: &Array1<A>) {
901 if self.recent_feature_window.len() >= FEATURE_WINDOW_CAPACITY {
902 self.recent_feature_window.pop_front();
903 }
904 self.recent_feature_window.push_back(features.to_vec());
905 }
906
907 pub fn feature_window_len(&self) -> usize {
909 self.recent_feature_window.len()
910 }
911
912 fn should_process_buffer(&self) -> Result<bool, String> {
914 let buffer_quality = self.buffer.get_quality_metrics();
915 let buffer_size = self.buffer.current_size();
916
917 let size_threshold = self.config.buffer_config.initial_size;
919 let size_ready = buffer_size >= size_threshold;
920
921 let quality_ready = buffer_quality.average_quality
923 >= try_scalar_str::<A, _>(self.config.buffer_config.quality_threshold)?;
924
925 let timeout_ready = self.buffer.time_since_last_processing()
927 >= self.config.buffer_config.processing_timeout;
928
929 let resources_available = self
931 .resource_manager
932 .has_sufficient_resources_for_processing()?;
933
934 Ok((size_ready && quality_ready) || timeout_ready && resources_available)
935 }
936
937 fn compute_adaptations(
939 &mut self,
940 batch: &[StreamingDataPoint<A>],
941 drift_detected: bool,
942 ) -> Result<Vec<Adaptation<A>>, String> {
943 let mut adaptations = Vec::new();
944
945 let mut recent_losses: Vec<A> = self
950 .performance_tracker
951 .get_recent_performance(LR_TREND_WINDOW)
952 .iter()
953 .map(|snapshot| snapshot.loss)
954 .collect();
955 recent_losses.reverse();
956 let lr_value = self
957 .learning_rate_controller
958 .compute_adaptation(&recent_losses);
959 let lr_adaptation = Adaptation {
960 adaptation_type: AdaptationType::LearningRate,
961 magnitude: lr_value,
962 target_component: String::from("learning_rate"),
963 parameters: HashMap::new(),
964 priority: AdaptationPriority::Normal,
965 timestamp: Instant::now(),
966 };
967 adaptations.push(lr_adaptation);
968
969 if drift_detected {
971 if let Some(drift_adaptation) = self.drift_detector.compute_sensitivity_adaptation()? {
972 adaptations.push(drift_adaptation);
973 }
974 }
975
976 if let Some(buffer_adaptation) = self
978 .buffer
979 .compute_size_adaptation(&self.performance_tracker)?
980 {
981 adaptations.push(buffer_adaptation);
982 }
983
984 if let Some(resource_adaptation) = self.resource_manager.compute_allocation_adaptation()? {
993 let magnitude = A::from(resource_adaptation.magnitude).ok_or_else(|| {
994 format!(
995 "resource adaptation magnitude {} is not representable in the element type",
996 resource_adaptation.magnitude
997 )
998 })?;
999 let mut parameters = HashMap::new();
1000 for (key, value) in &resource_adaptation.parameters {
1001 let converted = A::from(*value).ok_or_else(|| {
1002 format!("resource adaptation parameter '{key}' ({value}) is not representable")
1003 })?;
1004 parameters.insert(key.clone(), converted);
1005 }
1006 adaptations.push(Adaptation {
1007 adaptation_type: resource_adaptation.adaptation_type.clone(),
1008 magnitude,
1009 target_component: resource_adaptation.target_component.clone(),
1010 parameters,
1011 priority: resource_adaptation.priority.clone(),
1012 timestamp: resource_adaptation.timestamp,
1013 });
1014 }
1015
1016 let meta_adaptations = self
1018 .meta_learner
1019 .recommend_adaptations(batch, &self.performance_tracker)?;
1020 adaptations.extend(meta_adaptations);
1021
1022 adaptations.sort_by(|a, b| b.priority.cmp(&a.priority));
1024
1025 Ok(adaptations)
1026 }
1027
1028 fn apply_adaptations(&mut self, adaptations: &[Adaptation<A>]) -> Result<(), String> {
1030 for adaptation in adaptations {
1031 match &adaptation.adaptation_type {
1032 AdaptationType::LearningRate => {
1033 self.learning_rate_controller
1034 .apply_adaptation(adaptation.magnitude);
1035 }
1036 AdaptationType::BufferSize => {
1037 self.buffer.apply_size_adaptation(adaptation)?;
1038 }
1039 AdaptationType::DriftSensitivity => {
1040 self.drift_detector
1041 .apply_sensitivity_adaptation(adaptation)?;
1042 }
1043 AdaptationType::ResourceAllocation => {
1044 let magnitude = adaptation.magnitude.to_f32().ok_or_else(|| {
1047 "resource adaptation magnitude is not representable as f32".to_string()
1048 })?;
1049 let mut parameters = HashMap::new();
1050 for (key, value) in &adaptation.parameters {
1051 let converted = value.to_f32().ok_or_else(|| {
1052 format!(
1053 "resource adaptation parameter '{key}' is not representable as f32"
1054 )
1055 })?;
1056 parameters.insert(key.clone(), converted);
1057 }
1058 let converted = Adaptation::<f32> {
1059 adaptation_type: adaptation.adaptation_type.clone(),
1060 magnitude,
1061 target_component: adaptation.target_component.clone(),
1062 parameters,
1063 priority: adaptation.priority.clone(),
1064 timestamp: adaptation.timestamp,
1065 };
1066 self.resource_manager
1067 .apply_allocation_adaptation(&converted)?;
1068 }
1069 AdaptationType::PerformanceThreshold => {
1070 self.performance_tracker
1071 .apply_threshold_adaptation(adaptation)?;
1072 }
1073 AdaptationType::AnomalyDetection => {
1074 self.anomaly_detector.apply_adaptation(adaptation)?;
1075 }
1076 AdaptationType::MetaLearning => {
1077 self.meta_learner.apply_adaptation(adaptation)?;
1078 }
1079 AdaptationType::Custom(name) => {
1080 return Err(format!(
1085 "no handler is registered for custom adaptation '{name}'"
1086 ));
1087 }
1088 }
1089
1090 if self.adaptation_history.len() >= 1000 {
1092 self.adaptation_history.pop_front();
1093 }
1094 self.adaptation_history.push_back(adaptation.clone());
1095 }
1096
1097 self.last_adaptation = Instant::now();
1098 Ok(())
1099 }
1100
1101 fn perform_optimization_step(
1103 &mut self,
1104 batch: &[StreamingDataPoint<A>],
1105 ) -> Result<Array<A, D>, String> {
1106 let started = Instant::now();
1107
1108 let gradients = self.compute_batch_gradients(batch)?;
1110
1111 let learning_rate = self
1115 .learning_rate_controller
1116 .update_learning_rate(&gradients);
1117
1118 let parameters = if let Some(params) = self.parameters.clone() {
1119 params
1120 } else {
1121 return Err("Parameters not initialized".to_string());
1123 };
1124
1125 if parameters.len() != gradients.len() {
1135 return Err(format!(
1136 "parameter/gradient dimensionality mismatch: {} parameters but {} gradients",
1137 parameters.len(),
1138 gradients.len()
1139 ));
1140 }
1141 let gradients_d = gradients
1142 .clone()
1143 .into_dimensionality::<D>()
1144 .map_err(|e| format!("gradient does not fit the parameter dimensionality: {e}"))?;
1145 self.base_optimizer.set_learning_rate(learning_rate);
1146 let updated_parameters = self
1147 .base_optimizer
1148 .step(¶meters, &gradients_d)
1149 .map_err(|e| format!("base optimizer step failed: {e}"))?;
1150
1151 let squared_update = parameters.iter().zip(updated_parameters.iter()).fold(
1152 A::zero(),
1153 |acc, (&before, &after)| {
1154 let delta = after - before;
1155 acc + delta * delta
1156 },
1157 );
1158
1159 let squared_gradient = gradients.iter().fold(A::zero(), |acc, &g| acc + g * g);
1162 self.last_gradient_norm = Some(squared_gradient.sqrt());
1163 self.last_update_magnitude = Some(squared_update.sqrt());
1164 self.last_step_duration = started.elapsed();
1165
1166 Ok(updated_parameters)
1167 }
1168
1169 fn compute_batch_gradients(
1171 &self,
1172 batch: &[StreamingDataPoint<A>],
1173 ) -> Result<Array1<A>, String> {
1174 if batch.is_empty() {
1175 return Err("Cannot compute gradients from empty batch".to_string());
1176 }
1177
1178 let feature_dim = batch[0].features.len();
1179 let mut gradients = Array1::zeros(feature_dim);
1180
1181 for data_point in batch {
1183 for (i, &feature) in data_point.features.iter().enumerate() {
1184 gradients[i] = gradients[i] + feature * data_point.quality_score;
1185 }
1186 }
1187
1188 let batch_size = try_scalar_str::<A, _>(batch.len())?;
1190 gradients /= batch_size;
1191
1192 Ok(gradients)
1193 }
1194
1195 fn evaluate_performance(
1197 &self,
1198 batch: &[StreamingDataPoint<A>],
1199 parameters: &Array<A, D>,
1200 ) -> Result<PerformanceSnapshot<A>, String> {
1201 let loss = self.compute_loss(batch, parameters)?;
1203 let accuracy = self.compute_accuracy(batch, parameters)?;
1204 let convergence_rate = self.compute_convergence_rate(parameters)?;
1205
1206 let data_stats = self.compute_data_statistics(batch)?;
1208
1209 let resource_usage = self.resource_manager.current_usage()?;
1211
1212 let performance = PerformanceSnapshot {
1213 timestamp: Instant::now(),
1214 processing_duration: self.last_step_duration,
1218 loss,
1219 accuracy: Some(accuracy),
1220 convergence_rate: Some(convergence_rate),
1221 gradient_norm: self.last_gradient_norm,
1224 parameter_update_magnitude: self.last_update_magnitude,
1225 data_statistics: data_stats,
1226 resource_usage,
1227 custom_metrics: HashMap::new(),
1228 };
1229
1230 Ok(performance)
1231 }
1232
1233 fn linear_prediction(&self, features: &Array1<A>, parameters: &Array<A, D>) -> A {
1242 parameters
1243 .iter()
1244 .zip(features.iter())
1245 .fold(A::zero(), |acc, (&weight, &feature)| acc + weight * feature)
1246 }
1247
1248 fn compute_loss(
1250 &self,
1251 batch: &[StreamingDataPoint<A>],
1252 parameters: &Array<A, D>,
1253 ) -> Result<A, String> {
1254 let mut total_loss = A::zero();
1259 let mut count = 0usize;
1260
1261 for data_point in batch {
1262 let Some(target) = data_point.target.as_ref() else {
1263 continue;
1264 };
1265 let Some(&target_value) = target.iter().next() else {
1266 continue;
1267 };
1268 let prediction = self.linear_prediction(&data_point.features, parameters);
1269 let residual = prediction - target_value;
1270 total_loss = total_loss + residual * residual;
1271 count += 1;
1272 }
1273
1274 if count == 0 {
1275 return Ok(A::zero());
1278 }
1279 let divisor =
1280 A::from(count).ok_or_else(|| format!("batch size {count} is not representable"))?;
1281 Ok(total_loss / divisor)
1282 }
1283
1284 fn compute_accuracy(
1293 &self,
1294 batch: &[StreamingDataPoint<A>],
1295 parameters: &Array<A, D>,
1296 ) -> Result<A, String> {
1297 let relative_tolerance = A::from(ACCURACY_RELATIVE_TOLERANCE).ok_or_else(|| {
1298 format!("accuracy tolerance {ACCURACY_RELATIVE_TOLERANCE} is not representable")
1299 })?;
1300 let epsilon = A::from(1e-8).ok_or_else(|| "1e-8 is not representable".to_string())?;
1301
1302 let mut correct = 0usize;
1303 let mut total = 0usize;
1304
1305 for data_point in batch {
1306 let Some(target) = data_point.target.as_ref() else {
1307 continue;
1308 };
1309 let Some(&target_value) = target.iter().next() else {
1310 continue;
1311 };
1312 let prediction = self.linear_prediction(&data_point.features, parameters);
1313 let tolerance = relative_tolerance * target_value.abs().max(epsilon);
1314 if (prediction - target_value).abs() <= tolerance {
1315 correct += 1;
1316 }
1317 total += 1;
1318 }
1319
1320 if total == 0 {
1321 return Ok(A::zero());
1324 }
1325 let numerator =
1326 A::from(correct).ok_or_else(|| format!("{correct} is not representable"))?;
1327 let denominator = A::from(total).ok_or_else(|| format!("{total} is not representable"))?;
1328 Ok(numerator / denominator)
1329 }
1330
1331 fn compute_convergence_rate(&self, _parameters: &Array<A, D>) -> Result<A, String> {
1333 let recent_losses = self.performance_tracker.get_recent_losses(10);
1341 if recent_losses.len() >= 2 {
1342 let newest = recent_losses[0];
1343 let oldest = recent_losses[recent_losses.len() - 1];
1344 let improvement = oldest - newest;
1345 if oldest != A::zero() {
1346 Ok(improvement / oldest)
1347 } else {
1348 Ok(A::zero())
1349 }
1350 } else {
1351 Ok(A::zero())
1352 }
1353 }
1354
1355 fn compute_data_statistics(
1357 &self,
1358 batch: &[StreamingDataPoint<A>],
1359 ) -> Result<DataStatistics<A>, String> {
1360 if batch.is_empty() {
1361 return Ok(DataStatistics::default());
1362 }
1363
1364 let feature_dim = batch[0].features.len();
1365 let mut feature_means = Array1::zeros(feature_dim);
1366 let mut feature_stds = Array1::zeros(feature_dim);
1367 let mut quality_scores = Vec::new();
1368
1369 for data_point in batch {
1371 feature_means = feature_means + &data_point.features;
1372 quality_scores.push(data_point.quality_score);
1373 }
1374 feature_means /= try_scalar_str::<A, _>(batch.len())?;
1375
1376 for data_point in batch {
1378 let diff = &data_point.features - &feature_means;
1379 feature_stds = feature_stds + &diff.mapv(|x| x * x);
1380 }
1381 feature_stds /= try_scalar_str::<A, _>(batch.len())?;
1382 feature_stds = feature_stds.mapv(|x| x.sqrt());
1383
1384 let avg_quality = quality_scores.iter().copied().sum::<A>()
1385 / try_scalar_str::<A, _>(quality_scores.len())?;
1386
1387 Ok(DataStatistics {
1388 sample_count: batch.len(),
1389 feature_means,
1390 feature_stds,
1391 average_quality: avg_quality,
1392 timestamp: Instant::now(),
1393 })
1394 }
1395
1396 fn update_meta_learner(
1403 &mut self,
1404 adaptations: &[Adaptation<A>],
1405 performance: &PerformanceSnapshot<A>,
1406 ) -> Result<(), String> {
1407 if !self.config.meta_learning_config.enable_meta_learning {
1408 return Ok(());
1409 }
1410
1411 let meta_state = self.extract_meta_state(performance)?;
1413
1414 let meta_action = self.extract_meta_action(adaptations)?;
1416
1417 let reward = self.compute_meta_reward(performance)?;
1419
1420 self.meta_learner
1422 .update_experience(meta_state, meta_action, reward)?;
1423
1424 Ok(())
1425 }
1426
1427 fn extract_meta_state(
1429 &self,
1430 performance: &PerformanceSnapshot<A>,
1431 ) -> Result<MetaState<A>, String> {
1432 let state = MetaState {
1433 performance_metrics: vec![
1434 performance.loss,
1435 performance.accuracy.unwrap_or(A::zero()),
1436 performance.convergence_rate.unwrap_or(A::zero()),
1437 ],
1438 resource_state: vec![
1439 try_scalar_str::<A, _>(performance.resource_usage.memory_usage_mb as f64)?,
1440 try_scalar_str::<A, _>(performance.resource_usage.cpu_usage_percent)?,
1441 ],
1442 drift_indicators: vec![try_scalar_str::<A, _>(
1443 if self.drift_detector.is_drift_detected() {
1444 1.0
1445 } else {
1446 0.0
1447 },
1448 )?],
1449 adaptation_history: self.adaptation_history.len(),
1450 timestamp: Instant::now(),
1451 };
1452
1453 Ok(state)
1454 }
1455
1456 fn extract_meta_action(&self, adaptations: &[Adaptation<A>]) -> Result<MetaAction<A>, String> {
1458 let mut adaptation_vector = Vec::new();
1459 let mut adaptation_types = Vec::new();
1460
1461 for adaptation in adaptations {
1462 adaptation_vector.push(adaptation.magnitude);
1463 adaptation_types.push(adaptation.adaptation_type.clone());
1464 }
1465
1466 let action = MetaAction {
1467 adaptation_magnitudes: adaptation_vector,
1468 adaptation_types,
1469 learning_rate_change: self
1470 .learning_rate_controller
1471 .last_change()
1472 .unwrap_or(A::zero()),
1473 buffer_size_change: A::from(self.buffer.last_size_change()).unwrap_or(A::zero()),
1474 timestamp: Instant::now(),
1475 };
1476
1477 Ok(action)
1478 }
1479
1480 fn compute_meta_reward(&self, performance: &PerformanceSnapshot<A>) -> Result<A, String> {
1482 let reward = if let Some(baseline) = self.performance_baseline {
1484 performance.loss - baseline } else {
1486 A::zero()
1487 };
1488
1489 Ok(reward)
1490 }
1491
1492 pub fn get_adaptive_stats(&self) -> AdaptiveStreamingStats {
1494 let mut stats = self.stats.clone();
1495 stats.resource_utilization = self.resource_manager.current_usage().unwrap_or_default();
1496 stats.recent_adaptations = self.count_adaptations_applied(RECENT_ADAPTATION_WINDOW);
1500 stats
1501 }
1502
1503 fn count_adaptations_applied(&self, window: Duration) -> usize {
1509 let now = Instant::now();
1510 self.adaptation_history
1511 .iter()
1512 .filter(|adaptation| now.duration_since(adaptation.timestamp) <= window)
1513 .count()
1514 }
1515
1516 fn compute_performance_trend(&self) -> f64 {
1518 let recent_performance = self.performance_tracker.get_recent_performance(20);
1519 if recent_performance.len() >= 2 {
1520 let recent_avg = recent_performance
1521 .iter()
1522 .rev()
1523 .take(5)
1524 .map(|p| p.loss.to_f64().unwrap_or(0.0))
1525 .sum::<f64>()
1526 / 5.0;
1527
1528 let older_avg = recent_performance
1529 .iter()
1530 .take(5)
1531 .map(|p| p.loss.to_f64().unwrap_or(0.0))
1532 .sum::<f64>()
1533 / 5.0;
1534
1535 (recent_avg - older_avg) / older_avg
1537 } else {
1538 0.0
1539 }
1540 }
1541
1542 pub fn force_adaptation(&mut self) -> Result<(), String> {
1544 let empty_batch = Vec::new();
1545 let adaptations = self.compute_adaptations(&empty_batch, false)?;
1546 self.apply_adaptations(&adaptations)?;
1547 Ok(())
1548 }
1549
1550 pub fn soft_reset(&mut self) -> Result<(), String> {
1552 self.buffer.reset()?;
1554 self.drift_detector.reset()?;
1555 self.performance_tracker.reset()?;
1556
1557 self.stats = AdaptiveStreamingStats {
1561 total_data_points: 0,
1562 optimization_steps: 0,
1563 drift_events: 0,
1564 anomalies_detected: 0,
1565 adaptations_applied: 0,
1566 recent_adaptations: 0,
1567 current_buffer_size: self.config.buffer_config.initial_size,
1568 current_learning_rate: self.config.learning_rate_config.initial_rate,
1569 avg_processing_time_ms: 0.0,
1570 resource_utilization: ResourceUsage::default(),
1571 performance_trend: 0.0,
1572 meta_learning_score: self.meta_learner.get_effectiveness_score() as f64,
1573 };
1574
1575 self.adaptation_history.clear();
1576 self.performance_baseline = None;
1577
1578 Ok(())
1579 }
1580
1581 pub fn get_diagnostics(&self) -> StreamingDiagnostics {
1583 StreamingDiagnostics {
1584 buffer_diagnostics: self.buffer.get_diagnostics(),
1585 drift_diagnostics: self.drift_detector.get_diagnostics(),
1586 performance_diagnostics: self.performance_tracker.get_diagnostics(),
1587 resource_diagnostics: self.resource_manager.get_diagnostics(),
1588 meta_learning_diagnostics: self.meta_learner.get_diagnostics(),
1589 anomaly_diagnostics: self.anomaly_detector.get_diagnostics(),
1590 }
1591 }
1592}
1593
1594#[derive(Debug, Clone)]
1596pub struct StreamingDiagnostics {
1597 pub buffer_diagnostics: BufferDiagnostics,
1598 pub drift_diagnostics: DriftDiagnostics,
1599 pub performance_diagnostics: PerformanceDiagnostics,
1600 pub resource_diagnostics: ResourceDiagnostics,
1601 pub meta_learning_diagnostics: MetaLearningDiagnostics,
1602 pub anomaly_diagnostics: AnomalyDiagnostics,
1603}
1604
1605#[cfg(test)]
1606#[path = "optimizer_convergence_tests.rs"]
1607mod o5_convergence_rate_tests;
1608
1609#[cfg(test)]
1610#[path = "optimizer_regression_tests.rs"]
1611mod regression_tests;