1use anyhow::Result;
8use chrono::{DateTime, Utc};
9use nalgebra::{DMatrix, DVector};
10use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, VecDeque};
12use std::sync::{Arc, RwLock};
13use std::time::{Duration, Instant};
14use tokio::sync::mpsc;
15use tracing::{debug, info, warn};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct AdaptiveLearningConfig {
20 pub learning_rate: f64,
22 pub buffer_size: usize,
24 pub min_samples_for_adaptation: usize,
26 pub max_adaptation_frequency: f64,
28 pub quality_threshold: f64,
30 pub enable_meta_learning: bool,
32 pub adaptation_batch_size: usize,
34}
35
36impl Default for AdaptiveLearningConfig {
37 fn default() -> Self {
38 Self {
39 learning_rate: 0.001,
40 buffer_size: 10000,
41 min_samples_for_adaptation: 100,
42 max_adaptation_frequency: 1.0,
43 quality_threshold: 0.8,
44 enable_meta_learning: true,
45 adaptation_batch_size: 32,
46 }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct QualityFeedback {
53 pub query: String,
55 pub embedding: Vec<f64>,
57 pub quality_score: f64,
59 #[serde(with = "chrono::serde::ts_seconds")]
61 pub timestamp: DateTime<Utc>,
62 pub relevance: Option<f64>,
64 pub task_context: Option<String>,
66}
67
68#[derive(Debug, Clone)]
70pub struct ExperienceSample {
71 pub input: String,
73 pub target: Vec<f64>,
75 pub current: Vec<f64>,
77 pub improvement_target: f64,
79 pub context: HashMap<String, String>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub enum AdaptationStrategy {
86 GradientDescent { momentum: f64, weight_decay: f64 },
88 Evolutionary {
90 mutation_rate: f64,
91 population_size: usize,
92 },
93 MetaLearning {
95 inner_steps: usize,
96 outer_learning_rate: f64,
97 },
98 BayesianOptimization {
100 exploration_factor: f64,
101 kernel_bandwidth: f64,
102 },
103}
104
105impl Default for AdaptationStrategy {
106 fn default() -> Self {
107 Self::GradientDescent {
108 momentum: 0.9,
109 weight_decay: 0.0001,
110 }
111 }
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct AdaptationMetrics {
117 pub adaptations_count: usize,
119 pub avg_quality_improvement: f64,
121 pub adaptation_rate: f64,
123 pub buffer_utilization: f64,
125 pub performance_drift: f64,
127 #[serde(with = "chrono::serde::ts_seconds_option")]
129 pub last_adaptation: Option<DateTime<Utc>>,
130}
131
132impl Default for AdaptationMetrics {
133 fn default() -> Self {
134 Self {
135 adaptations_count: 0,
136 avg_quality_improvement: 0.0,
137 adaptation_rate: 0.0,
138 buffer_utilization: 0.0,
139 performance_drift: 0.0,
140 last_adaptation: None,
141 }
142 }
143}
144
145pub struct AdaptiveLearningSystem {
147 config: AdaptiveLearningConfig,
149 experience_buffer: Arc<RwLock<VecDeque<ExperienceSample>>>,
151 feedback_receiver: Arc<RwLock<Option<mpsc::UnboundedReceiver<QualityFeedback>>>>,
153 feedback_sender: mpsc::UnboundedSender<QualityFeedback>,
155 strategy: AdaptationStrategy,
157 metrics: Arc<RwLock<AdaptationMetrics>>,
159 model_parameters: Arc<RwLock<HashMap<String, DMatrix<f64>>>>,
161 learning_state: Arc<RwLock<LearningState>>,
163}
164
165#[derive(Debug, Clone)]
167struct LearningState {
168 momentum: HashMap<String, DMatrix<f64>>,
170 adaptation_history: VecDeque<AdaptationRecord>,
172 current_learning_rate: f64,
174 performance_baseline: f64,
176}
177
178#[derive(Debug, Clone)]
180#[allow(dead_code)]
181struct AdaptationRecord {
182 timestamp: DateTime<Utc>,
184 quality_before: f64,
186 quality_after: f64,
188 samples_used: usize,
190 strategy: AdaptationStrategy,
192}
193
194impl AdaptiveLearningSystem {
195 pub fn new(config: AdaptiveLearningConfig) -> Self {
197 let (sender, receiver) = mpsc::unbounded_channel();
198 let learning_rate = config.learning_rate;
199
200 Self {
201 config,
202 experience_buffer: Arc::new(RwLock::new(VecDeque::new())),
203 feedback_receiver: Arc::new(RwLock::new(Some(receiver))),
204 feedback_sender: sender,
205 strategy: AdaptationStrategy::default(),
206 metrics: Arc::new(RwLock::new(AdaptationMetrics::default())),
207 model_parameters: Arc::new(RwLock::new(HashMap::new())),
208 learning_state: Arc::new(RwLock::new(LearningState {
209 momentum: HashMap::new(),
210 adaptation_history: VecDeque::new(),
211 current_learning_rate: learning_rate,
212 performance_baseline: 0.5,
213 })),
214 }
215 }
216
217 pub fn with_strategy(config: AdaptiveLearningConfig, strategy: AdaptationStrategy) -> Self {
219 let mut system = Self::new(config);
220 system.strategy = strategy;
221 system
222 }
223
224 pub fn submit_feedback(&self, feedback: QualityFeedback) -> Result<()> {
226 self.feedback_sender.send(feedback)?;
227 Ok(())
228 }
229
230 pub async fn start_learning(&self) -> Result<()> {
232 let mut receiver = self
233 .feedback_receiver
234 .write()
235 .expect("lock poisoned")
236 .take()
237 .ok_or_else(|| anyhow::anyhow!("Learning already started"))?;
238
239 info!("Starting adaptive learning system");
240
241 let experience_buffer = Arc::clone(&self.experience_buffer);
243 let metrics = Arc::clone(&self.metrics);
244 let config = self.config.clone();
245
246 tokio::spawn(async move {
247 while let Some(feedback) = receiver.recv().await {
248 if let Err(e) =
249 Self::process_feedback(feedback, &experience_buffer, &metrics, &config).await
250 {
251 warn!("Error processing feedback: {}", e);
252 }
253 }
254 });
255
256 let buffer = Arc::clone(&self.experience_buffer);
258 let metrics = Arc::clone(&self.metrics);
259 let parameters = Arc::clone(&self.model_parameters);
260 let learning_state = Arc::clone(&self.learning_state);
261 let config = self.config.clone();
262 let strategy = self.strategy.clone();
263
264 tokio::spawn(async move {
265 let mut last_adaptation = Instant::now();
266
267 loop {
268 tokio::time::sleep(Duration::from_millis(100)).await;
269
270 let should_adapt = {
272 let buffer_guard = buffer.read().expect("lock poisoned");
273 let _metrics_guard = metrics.read().expect("lock poisoned");
274
275 buffer_guard.len() >= config.min_samples_for_adaptation
276 && last_adaptation.elapsed().as_secs_f64()
277 >= 1.0 / config.max_adaptation_frequency
278 };
279
280 if should_adapt {
281 match Self::perform_adaptation(
282 &buffer,
283 &metrics,
284 ¶meters,
285 &learning_state,
286 &config,
287 &strategy,
288 )
289 .await
290 {
291 Err(e) => {
292 warn!("Error during adaptation: {}", e);
293 }
294 _ => {
295 last_adaptation = Instant::now();
296 }
297 }
298 }
299 }
300 });
301
302 Ok(())
303 }
304
305 async fn process_feedback(
307 feedback: QualityFeedback,
308 buffer: &Arc<RwLock<VecDeque<ExperienceSample>>>,
309 metrics: &Arc<RwLock<AdaptationMetrics>>,
310 config: &AdaptiveLearningConfig,
311 ) -> Result<()> {
312 if feedback.quality_score > config.quality_threshold {
314 let sample = ExperienceSample {
315 input: feedback.query.clone(),
316 target: feedback.embedding.clone(),
317 current: feedback.embedding.clone(), improvement_target: 1.0 - feedback.quality_score,
319 context: feedback
320 .task_context
321 .map(|ctx| [("task".to_string(), ctx)].into())
322 .unwrap_or_default(),
323 };
324
325 {
327 let mut buffer_guard = buffer.write().expect("lock poisoned");
328 buffer_guard.push_back(sample);
329
330 while buffer_guard.len() > config.buffer_size {
332 buffer_guard.pop_front();
333 }
334 }
335
336 {
338 let mut metrics_guard = metrics.write().expect("lock poisoned");
339 let buffer_guard = buffer.read().expect("lock poisoned");
340 metrics_guard.buffer_utilization =
341 buffer_guard.len() as f64 / config.buffer_size as f64;
342 }
343
344 debug!(
345 "Processed feedback with quality score: {}",
346 feedback.quality_score
347 );
348 }
349
350 Ok(())
351 }
352
353 async fn perform_adaptation(
355 buffer: &Arc<RwLock<VecDeque<ExperienceSample>>>,
356 metrics: &Arc<RwLock<AdaptationMetrics>>,
357 parameters: &Arc<RwLock<HashMap<String, DMatrix<f64>>>>,
358 learning_state: &Arc<RwLock<LearningState>>,
359 config: &AdaptiveLearningConfig,
360 strategy: &AdaptationStrategy,
361 ) -> Result<()> {
362 let samples = {
363 let buffer_guard = buffer.read().expect("lock poisoned");
364 buffer_guard
365 .iter()
366 .take(config.adaptation_batch_size)
367 .cloned()
368 .collect::<Vec<_>>()
369 };
370
371 if samples.is_empty() {
372 return Ok(());
373 }
374
375 info!("Performing adaptation with {} samples", samples.len());
376
377 let quality_before = Self::calculate_current_quality(&samples)?;
379
380 match strategy {
382 AdaptationStrategy::GradientDescent {
383 momentum,
384 weight_decay,
385 } => {
386 Self::gradient_descent_adaptation(
387 &samples,
388 parameters,
389 learning_state,
390 *momentum,
391 *weight_decay,
392 config.learning_rate,
393 )?;
394 }
395 AdaptationStrategy::MetaLearning {
396 inner_steps,
397 outer_learning_rate,
398 } => {
399 Self::meta_learning_adaptation(
400 &samples,
401 parameters,
402 learning_state,
403 *inner_steps,
404 *outer_learning_rate,
405 )?;
406 }
407 AdaptationStrategy::Evolutionary {
408 mutation_rate,
409 population_size,
410 } => {
411 Self::evolutionary_adaptation(
412 &samples,
413 parameters,
414 *mutation_rate,
415 *population_size,
416 )?;
417 }
418 AdaptationStrategy::BayesianOptimization {
419 exploration_factor,
420 kernel_bandwidth,
421 } => {
422 Self::bayesian_optimization_adaptation(
423 &samples,
424 parameters,
425 *exploration_factor,
426 *kernel_bandwidth,
427 )?;
428 }
429 }
430
431 let quality_after = Self::calculate_current_quality(&samples)?;
433
434 {
436 let mut metrics_guard = metrics.write().expect("lock poisoned");
437 metrics_guard.adaptations_count += 1;
438 let improvement = quality_after - quality_before;
439 metrics_guard.avg_quality_improvement = (metrics_guard.avg_quality_improvement
440 * (metrics_guard.adaptations_count - 1) as f64
441 + improvement)
442 / metrics_guard.adaptations_count as f64;
443 metrics_guard.last_adaptation = Some(Utc::now());
444 }
445
446 {
448 let mut state_guard = learning_state.write().expect("lock poisoned");
449 state_guard.adaptation_history.push_back(AdaptationRecord {
450 timestamp: Utc::now(),
451 quality_before,
452 quality_after,
453 samples_used: samples.len(),
454 strategy: strategy.clone(),
455 });
456
457 while state_guard.adaptation_history.len() > 1000 {
459 state_guard.adaptation_history.pop_front();
460 }
461
462 if quality_after > quality_before {
464 state_guard.current_learning_rate *= 1.01; } else {
466 state_guard.current_learning_rate *= 0.95; }
468 state_guard.current_learning_rate = state_guard
469 .current_learning_rate
470 .max(config.learning_rate * 0.1)
471 .min(config.learning_rate * 10.0);
472 }
473
474 info!(
475 "Adaptation completed: quality improved by {:.4}",
476 quality_after - quality_before
477 );
478
479 Ok(())
480 }
481
482 fn calculate_current_quality(samples: &[ExperienceSample]) -> Result<f64> {
484 if samples.is_empty() {
485 return Ok(0.0);
486 }
487
488 let total_quality: f64 = samples
489 .iter()
490 .map(|sample| {
491 let current = DVector::from_vec(sample.current.clone());
493 let target = DVector::from_vec(sample.target.clone());
494
495 if current.len() != target.len() {
496 return 0.0;
497 }
498
499 let dot_product = current.dot(&target);
501 let norm_current = current.norm();
502 let norm_target = target.norm();
503
504 if norm_current == 0.0 || norm_target == 0.0 {
505 return 0.0;
506 }
507
508 (dot_product / (norm_current * norm_target)).max(0.0)
509 })
510 .sum();
511
512 Ok(total_quality / samples.len() as f64)
513 }
514
515 fn gradient_descent_adaptation(
517 samples: &[ExperienceSample],
518 parameters: &Arc<RwLock<HashMap<String, DMatrix<f64>>>>,
519 learning_state: &Arc<RwLock<LearningState>>,
520 momentum: f64,
521 weight_decay: f64,
522 learning_rate: f64,
523 ) -> Result<()> {
524 let mut params_guard = parameters.write().expect("lock poisoned");
529 let mut state_guard = learning_state.write().expect("lock poisoned");
530
531 for (param_name, param_matrix) in params_guard.iter_mut() {
532 let gradient = Self::compute_gradient(samples, param_matrix)?;
534
535 let momentum_entry = state_guard
537 .momentum
538 .entry(param_name.clone())
539 .or_insert_with(|| DMatrix::zeros(param_matrix.nrows(), param_matrix.ncols()));
540
541 *momentum_entry = momentum_entry.clone() * momentum + &gradient;
542
543 let decay_term = param_matrix.clone() * weight_decay;
545
546 *param_matrix -= &(momentum_entry.clone() * learning_rate + decay_term * learning_rate);
548 }
549
550 Ok(())
551 }
552
553 fn compute_gradient(
555 _samples: &[ExperienceSample],
556 param_matrix: &DMatrix<f64>,
557 ) -> Result<DMatrix<f64>> {
558 let mut gradient = DMatrix::zeros(param_matrix.nrows(), param_matrix.ncols());
561
562 for i in 0..gradient.nrows() {
564 for j in 0..gradient.ncols() {
565 gradient[(i, j)] = ({
566 use scirs2_core::random::{Random, Rng};
567 let mut random = Random::default();
568 random.random::<f64>()
569 } - 0.5)
570 * 0.001;
571 }
572 }
573
574 Ok(gradient)
575 }
576
577 fn meta_learning_adaptation(
579 samples: &[ExperienceSample],
580 parameters: &Arc<RwLock<HashMap<String, DMatrix<f64>>>>,
581 _learning_state: &Arc<RwLock<LearningState>>,
582 inner_steps: usize,
583 outer_learning_rate: f64,
584 ) -> Result<()> {
585 let mut params_guard = parameters.write().expect("lock poisoned");
586
587 for _ in 0..inner_steps {
589 for (_, param_matrix) in params_guard.iter_mut() {
590 let gradient = Self::compute_gradient(samples, param_matrix)?;
591 *param_matrix -= &(gradient * outer_learning_rate);
592 }
593 }
594
595 Ok(())
596 }
597
598 fn evolutionary_adaptation(
600 samples: &[ExperienceSample],
601 parameters: &Arc<RwLock<HashMap<String, DMatrix<f64>>>>,
602 mutation_rate: f64,
603 population_size: usize,
604 ) -> Result<()> {
605 let mut params_guard = parameters.write().expect("lock poisoned");
606
607 for (_, param_matrix) in params_guard.iter_mut() {
609 let mut best_fitness = Self::evaluate_fitness(samples, param_matrix)?;
610 let mut best_params = param_matrix.clone();
611
612 for _ in 0..population_size {
614 let mut mutated = param_matrix.clone();
615
616 for i in 0..mutated.nrows() {
618 for j in 0..mutated.ncols() {
619 if {
620 use scirs2_core::random::{Random, Rng};
621 let mut random = Random::default();
622 random.random::<f64>()
623 } < mutation_rate
624 {
625 mutated[(i, j)] += ({
626 use scirs2_core::random::{Random, Rng};
627 let mut random = Random::default();
628 random.random::<f64>()
629 } - 0.5)
630 * 0.01;
631 }
632 }
633 }
634
635 let fitness = Self::evaluate_fitness(samples, &mutated)?;
636 if fitness > best_fitness {
637 best_fitness = fitness;
638 best_params = mutated;
639 }
640 }
641
642 *param_matrix = best_params;
643 }
644
645 Ok(())
646 }
647
648 fn evaluate_fitness(
650 _samples: &[ExperienceSample],
651 _param_matrix: &DMatrix<f64>,
652 ) -> Result<f64> {
653 Ok({
656 use scirs2_core::random::{Random, Rng};
657 let mut random = Random::default();
658 random.random::<f64>()
659 })
660 }
661
662 fn bayesian_optimization_adaptation(
664 samples: &[ExperienceSample],
665 parameters: &Arc<RwLock<HashMap<String, DMatrix<f64>>>>,
666 exploration_factor: f64,
667 _kernel_bandwidth: f64,
668 ) -> Result<()> {
669 let mut params_guard = parameters.write().expect("lock poisoned");
670
671 for (_, param_matrix) in params_guard.iter_mut() {
673 let current_fitness = Self::evaluate_fitness(samples, param_matrix)?;
674
675 let mut best_candidate = param_matrix.clone();
677 let mut best_acquisition = 0.0;
678
679 for _ in 0..10 {
680 let mut candidate = param_matrix.clone();
681
682 for i in 0..candidate.nrows() {
684 for j in 0..candidate.ncols() {
685 candidate[(i, j)] += ({
686 use scirs2_core::random::{Random, Rng};
687 let mut random = Random::default();
688 random.random::<f64>()
689 } - 0.5)
690 * exploration_factor;
691 }
692 }
693
694 let fitness = Self::evaluate_fitness(samples, &candidate)?;
695 let acquisition = fitness
696 + exploration_factor * {
697 use scirs2_core::random::{Random, Rng};
698 let mut random = Random::default();
699 random.random::<f64>()
700 };
701
702 if acquisition > best_acquisition {
703 best_acquisition = acquisition;
704 best_candidate = candidate;
705 }
706 }
707
708 if best_acquisition > current_fitness + 0.01 {
710 *param_matrix = best_candidate;
711 }
712 }
713
714 Ok(())
715 }
716
717 pub fn get_metrics(&self) -> AdaptationMetrics {
719 self.metrics.read().expect("lock poisoned").clone()
720 }
721
722 pub fn get_feedback_sender(&self) -> mpsc::UnboundedSender<QualityFeedback> {
724 self.feedback_sender.clone()
725 }
726
727 pub fn set_strategy(&mut self, strategy: AdaptationStrategy) {
729 self.strategy = strategy;
730 }
731
732 pub fn reset_learning_state(&self) {
734 let mut state_guard = self.learning_state.write().expect("lock poisoned");
735 state_guard.momentum.clear();
736 state_guard.adaptation_history.clear();
737 state_guard.current_learning_rate = self.config.learning_rate;
738 state_guard.performance_baseline = 0.5;
739 }
740}
741
742#[cfg(test)]
743mod tests {
744 use super::*;
745 use tokio::time::{sleep, Duration};
746
747 #[tokio::test]
748 async fn test_adaptive_learning_system_creation() {
749 let config = AdaptiveLearningConfig::default();
750 let system = AdaptiveLearningSystem::new(config);
751
752 let metrics = system.get_metrics();
753 assert_eq!(metrics.adaptations_count, 0);
754 assert_eq!(metrics.avg_quality_improvement, 0.0);
755 }
756
757 #[tokio::test]
758 async fn test_feedback_submission() {
759 let config = AdaptiveLearningConfig::default();
760 let system = AdaptiveLearningSystem::new(config);
761
762 let feedback = QualityFeedback {
763 query: "test query".to_string(),
764 embedding: vec![0.1, 0.2, 0.3],
765 quality_score: 0.9,
766 timestamp: Utc::now(),
767 relevance: Some(0.8),
768 task_context: Some("similarity".to_string()),
769 };
770
771 assert!(system.submit_feedback(feedback).is_ok());
772 }
773
774 #[tokio::test]
775 async fn test_adaptive_learning_config_default() {
776 let config = AdaptiveLearningConfig::default();
777
778 assert_eq!(config.learning_rate, 0.001);
779 assert_eq!(config.buffer_size, 10000);
780 assert_eq!(config.min_samples_for_adaptation, 100);
781 assert_eq!(config.quality_threshold, 0.8);
782 assert!(config.enable_meta_learning);
783 }
784
785 #[tokio::test]
786 async fn test_adaptation_strategies() {
787 let config = AdaptiveLearningConfig::default();
788
789 let strategies = vec![
791 AdaptationStrategy::GradientDescent {
792 momentum: 0.9,
793 weight_decay: 0.0001,
794 },
795 AdaptationStrategy::MetaLearning {
796 inner_steps: 3,
797 outer_learning_rate: 0.01,
798 },
799 AdaptationStrategy::Evolutionary {
800 mutation_rate: 0.1,
801 population_size: 20,
802 },
803 AdaptationStrategy::BayesianOptimization {
804 exploration_factor: 0.1,
805 kernel_bandwidth: 1.0,
806 },
807 ];
808
809 for strategy in strategies {
810 let system = AdaptiveLearningSystem::with_strategy(config.clone(), strategy);
811 assert!(system.start_learning().await.is_ok());
812
813 sleep(Duration::from_millis(10)).await;
815 }
816 }
817
818 #[tokio::test]
819 async fn test_quality_calculation() {
820 let samples = vec![
821 ExperienceSample {
822 input: "test1".to_string(),
823 target: vec![1.0, 0.0, 0.0],
824 current: vec![0.9, 0.1, 0.0],
825 improvement_target: 0.1,
826 context: HashMap::new(),
827 },
828 ExperienceSample {
829 input: "test2".to_string(),
830 target: vec![0.0, 1.0, 0.0],
831 current: vec![0.0, 0.8, 0.2],
832 improvement_target: 0.2,
833 context: HashMap::new(),
834 },
835 ];
836
837 let quality = AdaptiveLearningSystem::calculate_current_quality(&samples).unwrap();
838 assert!(quality > 0.0 && quality <= 1.0);
839 }
840}