1use crate::applications::{ApplicationError, ApplicationResult};
11use crate::ising::IsingModel;
12use scirs2_core::ndarray::{Array1, Array2};
13use scirs2_core::random::prelude::*;
14use scirs2_core::ChaCha8Rng;
15use scirs2_core::Complex64;
16use scirs2_core::RngExt;
17use serde::{Deserialize, Serialize};
18use std::collections::{HashMap, VecDeque};
19use std::time::{Duration, Instant};
20
21pub use crate::ising::Coupling;
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct ProblemFeatures {
27 pub num_variables: usize,
29 pub density: f64,
31 pub coupling_mean: f64,
33 pub coupling_std: f64,
34 pub coupling_max: f64,
35 pub bias_mean: f64,
37 pub bias_std: f64,
38 pub average_degree: f64,
40 pub max_degree: usize,
41 pub clustering_coefficient: f64,
42 pub estimated_barriers: f64,
44 pub frustration_index: f64,
45 pub symmetry_score: f64,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct OptimizationRecord {
52 pub features: ProblemFeatures,
54 pub strategy: OptimizationStrategy,
56 pub parameters: HashMap<String, f64>,
58 pub best_energy: f64,
60 pub convergence_time: Duration,
61 pub iterations_to_converge: usize,
62 pub success_rate: f64,
63 pub cpu_time: Duration,
65 pub memory_peak_mb: f64,
66 #[serde(skip, default = "Instant::now")]
68 pub timestamp: Instant,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
73pub enum OptimizationStrategy {
74 ClassicalAnnealing,
75 QuantumAnnealing,
76 PopulationAnnealing,
77 CoherentIsingMachine,
78 QuantumWalk,
79 HybridQCML,
80 AdaptiveSchedule,
81 ReversedAnnealing,
82}
83
84#[derive(Debug, Clone)]
86pub struct PerformancePredictor {
87 feature_weights: Array1<f64>,
89 strategy_adjustments: HashMap<OptimizationStrategy, f64>,
91 confidence: f64,
93}
94
95impl PerformancePredictor {
96 #[must_use]
98 pub fn new(num_features: usize) -> Self {
99 Self {
100 feature_weights: Array1::zeros(num_features),
101 strategy_adjustments: HashMap::new(),
102 confidence: 0.0,
103 }
104 }
105
106 #[must_use]
108 pub fn predict(
109 &self,
110 features: &ProblemFeatures,
111 strategy: OptimizationStrategy,
112 ) -> PredictedPerformance {
113 let feature_vec = self.features_to_vector(features);
114 let base_score = feature_vec.dot(&self.feature_weights);
115
116 let strategy_adj = self
117 .strategy_adjustments
118 .get(&strategy)
119 .copied()
120 .unwrap_or(0.0);
121
122 let predicted_quality = (base_score + strategy_adj).tanh();
123 let predicted_time = self.estimate_time(features, strategy);
124
125 PredictedPerformance {
126 strategy,
127 quality_score: predicted_quality,
128 estimated_time: predicted_time,
129 confidence: self.confidence,
130 }
131 }
132
133 fn features_to_vector(&self, features: &ProblemFeatures) -> Array1<f64> {
135 Array1::from_vec(vec![
136 features.num_variables as f64,
137 features.density,
138 features.coupling_mean,
139 features.coupling_std,
140 features.average_degree,
141 features.clustering_coefficient,
142 features.frustration_index,
143 features.symmetry_score,
144 ])
145 }
146
147 fn estimate_time(
149 &self,
150 features: &ProblemFeatures,
151 strategy: OptimizationStrategy,
152 ) -> Duration {
153 let base_complexity = match strategy {
154 OptimizationStrategy::ClassicalAnnealing => features.num_variables as f64,
155 OptimizationStrategy::QuantumAnnealing => (features.num_variables as f64).powf(1.5),
156 OptimizationStrategy::PopulationAnnealing => {
157 features.num_variables as f64 * features.density
158 }
159 OptimizationStrategy::CoherentIsingMachine => (features.num_variables as f64).powi(2),
160 OptimizationStrategy::QuantumWalk => {
161 features.num_variables as f64 * features.average_degree
162 }
163 OptimizationStrategy::HybridQCML => features.num_variables as f64 * 1.5,
164 OptimizationStrategy::AdaptiveSchedule => features.num_variables as f64 * 0.8,
165 OptimizationStrategy::ReversedAnnealing => features.num_variables as f64 * 0.6,
166 };
167
168 let difficulty_factor = 1.0 + features.frustration_index + (1.0 - features.symmetry_score);
170 let estimated_ms = base_complexity * difficulty_factor * 10.0;
171
172 Duration::from_millis(estimated_ms as u64)
173 }
174
175 pub fn update(&mut self, record: &OptimizationRecord, learning_rate: f64) {
177 let feature_vec = self.features_to_vector(&record.features);
178
179 let predicted = self.predict(&record.features, record.strategy);
181 let error = record.success_rate - predicted.quality_score;
182
183 for i in 0..self.feature_weights.len() {
184 self.feature_weights[i] += learning_rate * error * feature_vec[i];
185 }
186
187 let current_adj = self
189 .strategy_adjustments
190 .entry(record.strategy)
191 .or_insert(0.0);
192 *current_adj += learning_rate * error * 0.1;
193
194 self.confidence = 0.9f64.mul_add(self.confidence, 0.1 * (1.0 - error.abs()));
196 }
197}
198
199#[derive(Debug, Clone)]
201pub struct PredictedPerformance {
202 pub strategy: OptimizationStrategy,
203 pub quality_score: f64,
204 pub estimated_time: Duration,
205 pub confidence: f64,
206}
207
208#[derive(Debug, Clone)]
210pub struct TransferLearningEngine {
211 source_records: Vec<OptimizationRecord>,
213 similarity_cache: HashMap<(usize, usize), f64>,
215 transfer_weights: Vec<f64>,
217}
218
219impl TransferLearningEngine {
220 #[must_use]
222 pub fn new() -> Self {
223 Self {
224 source_records: Vec::new(),
225 similarity_cache: HashMap::new(),
226 transfer_weights: Vec::new(),
227 }
228 }
229
230 pub fn add_source_knowledge(&mut self, records: Vec<OptimizationRecord>) {
232 self.source_records.extend(records);
233 self.similarity_cache.clear(); }
235
236 #[must_use]
238 pub fn compute_similarity(
239 &self,
240 features1: &ProblemFeatures,
241 features2: &ProblemFeatures,
242 ) -> f64 {
243 let diff_size = ((features1.num_variables as f64 - features2.num_variables as f64)
245 / features1.num_variables.max(features2.num_variables) as f64)
246 .powi(2);
247 let diff_density = (features1.density - features2.density).powi(2);
248 let diff_frustration = (features1.frustration_index - features2.frustration_index).powi(2);
249 let diff_symmetry = (features1.symmetry_score - features2.symmetry_score).powi(2);
250
251 let distance = (diff_size + diff_density + diff_frustration + diff_symmetry).sqrt();
252
253 let bandwidth: f64 = 0.5;
255 (-distance.powi(2) / (2.0 * bandwidth.powi(2))).exp()
256 }
257
258 pub fn transfer_knowledge(
260 &self,
261 target_features: &ProblemFeatures,
262 ) -> Vec<(OptimizationStrategy, f64)> {
263 let mut strategy_scores: HashMap<OptimizationStrategy, Vec<f64>> = HashMap::new();
264
265 for record in &self.source_records {
266 let similarity = self.compute_similarity(target_features, &record.features);
267
268 if similarity > 0.3 {
270 let weighted_score = record.success_rate * similarity;
271 strategy_scores
272 .entry(record.strategy)
273 .or_insert_with(Vec::new)
274 .push(weighted_score);
275 }
276 }
277
278 let mut recommendations: Vec<(OptimizationStrategy, f64)> = strategy_scores
280 .into_iter()
281 .map(|(strategy, scores)| {
282 let avg_score = scores.iter().sum::<f64>() / scores.len() as f64;
283 (strategy, avg_score)
284 })
285 .collect();
286
287 recommendations.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
289
290 recommendations
291 }
292}
293
294impl Default for TransferLearningEngine {
295 fn default() -> Self {
296 Self::new()
297 }
298}
299
300#[derive(Debug)]
302pub struct AdaptiveStrategySelector {
303 predictor: PerformancePredictor,
305 transfer_engine: TransferLearningEngine,
307 exploration_rate: f64,
309 rng: ChaCha8Rng,
311}
312
313impl AdaptiveStrategySelector {
314 #[must_use]
316 pub fn new(seed: u64) -> Self {
317 Self {
318 predictor: PerformancePredictor::new(8),
319 transfer_engine: TransferLearningEngine::new(),
320 exploration_rate: 0.1,
321 rng: ChaCha8Rng::seed_from_u64(seed),
322 }
323 }
324
325 pub fn select_strategy(
327 &mut self,
328 features: &ProblemFeatures,
329 available_strategies: &[OptimizationStrategy],
330 ) -> OptimizationStrategy {
331 if self.rng.random::<f64>() < self.exploration_rate {
333 return *available_strategies
334 .get(self.rng.random_range(0..available_strategies.len()))
335 .expect("random index should be within bounds");
336 }
337
338 let mut best_strategy = available_strategies[0];
340 let mut best_score = f64::NEG_INFINITY;
341
342 for &strategy in available_strategies {
343 let prediction = self.predictor.predict(features, strategy);
344 let transfer_bonus = self.get_transfer_bonus(features, strategy);
345 let total_score = 0.3f64.mul_add(transfer_bonus, prediction.quality_score);
346
347 if total_score > best_score {
348 best_score = total_score;
349 best_strategy = strategy;
350 }
351 }
352
353 best_strategy
354 }
355
356 fn get_transfer_bonus(
358 &self,
359 features: &ProblemFeatures,
360 strategy: OptimizationStrategy,
361 ) -> f64 {
362 let recommendations = self.transfer_engine.transfer_knowledge(features);
363
364 recommendations
365 .iter()
366 .find(|(s, _)| *s == strategy)
367 .map_or(0.0, |(_, score)| *score)
368 }
369
370 pub fn update(&mut self, record: OptimizationRecord) {
372 self.predictor.update(&record, 0.01);
373 self.transfer_engine.add_source_knowledge(vec![record]);
374
375 self.exploration_rate *= 0.999;
377 }
378
379 #[must_use]
381 pub fn predict_performance(
382 &self,
383 features: &ProblemFeatures,
384 strategy: OptimizationStrategy,
385 ) -> PredictedPerformance {
386 self.predictor.predict(features, strategy)
387 }
388}
389
390#[derive(Debug)]
392pub struct MetaLearningOptimizer {
393 history: VecDeque<OptimizationRecord>,
395 max_history: usize,
397 selector: AdaptiveStrategySelector,
399 pub total_optimizations: usize,
401 pub average_success_rate: f64,
402}
403
404impl MetaLearningOptimizer {
405 #[must_use]
407 pub fn new(max_history: usize, seed: u64) -> Self {
408 Self {
409 history: VecDeque::new(),
410 max_history,
411 selector: AdaptiveStrategySelector::new(seed),
412 total_optimizations: 0,
413 average_success_rate: 0.0,
414 }
415 }
416
417 pub fn extract_features(&self, model: &IsingModel) -> ProblemFeatures {
419 let num_variables = model.num_qubits;
420
421 let couplings = model.couplings();
423 let mut coupling_values = Vec::new();
424 let mut degrees = vec![0; num_variables];
425
426 for coupling in &couplings {
427 coupling_values.push(coupling.strength.abs());
428 degrees[coupling.i] += 1;
429 degrees[coupling.j] += 1;
430 }
431
432 let density = couplings.len() as f64 / (num_variables * (num_variables - 1) / 2) as f64;
433
434 let coupling_mean = if coupling_values.is_empty() {
435 0.0
436 } else {
437 coupling_values.iter().sum::<f64>() / coupling_values.len() as f64
438 };
439
440 let coupling_std = if coupling_values.len() > 1 {
441 let variance = coupling_values
442 .iter()
443 .map(|v| (v - coupling_mean).powi(2))
444 .sum::<f64>()
445 / coupling_values.len() as f64;
446 variance.sqrt()
447 } else {
448 0.0
449 };
450
451 let coupling_max = coupling_values.iter().copied().fold(0.0, f64::max);
452
453 let biases = model.biases();
455 let bias_values: Vec<f64> = biases.iter().map(|(_, b)| *b).collect();
456 let bias_mean = if bias_values.is_empty() {
457 0.0
458 } else {
459 bias_values.iter().sum::<f64>() / bias_values.len() as f64
460 };
461
462 let bias_std = if bias_values.len() > 1 {
463 let variance = bias_values
464 .iter()
465 .map(|v| (v - bias_mean).powi(2))
466 .sum::<f64>()
467 / bias_values.len() as f64;
468 variance.sqrt()
469 } else {
470 0.0
471 };
472
473 let average_degree = if degrees.is_empty() {
475 0.0
476 } else {
477 degrees.iter().sum::<usize>() as f64 / degrees.len() as f64
478 };
479
480 let max_degree = degrees.iter().copied().max().unwrap_or(0);
481
482 let clustering_coefficient = self.estimate_clustering(model);
484
485 let estimated_barriers = coupling_std / (1.0 + density);
487 let frustration_index = self.estimate_frustration(model);
488
489 let symmetry_score = 1.0 - (bias_std / (1.0 + bias_mean.abs()));
491
492 ProblemFeatures {
493 num_variables,
494 density,
495 coupling_mean,
496 coupling_std,
497 coupling_max,
498 bias_mean,
499 bias_std,
500 average_degree,
501 max_degree,
502 clustering_coefficient,
503 estimated_barriers,
504 frustration_index,
505 symmetry_score: symmetry_score.clamp(0.0, 1.0),
506 }
507 }
508
509 fn estimate_clustering(&self, model: &IsingModel) -> f64 {
511 let couplings = model.couplings();
513
514 let mut adj: HashMap<usize, Vec<usize>> = HashMap::new();
516 for coupling in &couplings {
517 adj.entry(coupling.i)
518 .or_insert_with(Vec::new)
519 .push(coupling.j);
520 adj.entry(coupling.j)
521 .or_insert_with(Vec::new)
522 .push(coupling.i);
523 }
524
525 let mut triangles = 0;
526 let mut triples = 0;
527
528 for i in 0..model.num_qubits {
529 if let Some(neighbors) = adj.get(&i) {
530 for k in 0..neighbors.len() {
531 for l in (k + 1)..neighbors.len() {
532 triples += 1;
533 let j1 = neighbors[k];
534 let j2 = neighbors[l];
535
536 if let Some(j1_neighbors) = adj.get(&j1) {
538 if j1_neighbors.contains(&j2) {
539 triangles += 1;
540 }
541 }
542 }
543 }
544 }
545 }
546
547 if triples > 0 {
548 f64::from(triangles) / f64::from(triples)
549 } else {
550 0.0
551 }
552 }
553
554 fn estimate_frustration(&self, model: &IsingModel) -> f64 {
556 let couplings = model.couplings();
558 let mut frustrated = 0;
559 let total = couplings.len();
560
561 for coupling in &couplings {
562 if coupling.strength > 0.0 {
563 frustrated += 1;
565 }
566 }
567
568 if total > 0 {
569 f64::from(frustrated) / total as f64
570 } else {
571 0.0
572 }
573 }
574
575 pub fn select_strategy(&mut self, model: &IsingModel) -> OptimizationStrategy {
577 let features = self.extract_features(model);
578 let available = vec![
579 OptimizationStrategy::ClassicalAnnealing,
580 OptimizationStrategy::QuantumAnnealing,
581 OptimizationStrategy::PopulationAnnealing,
582 OptimizationStrategy::AdaptiveSchedule,
583 ];
584
585 self.selector.select_strategy(&features, &available)
586 }
587
588 pub fn record_optimization(&mut self, record: OptimizationRecord) {
590 self.total_optimizations += 1;
591
592 self.average_success_rate = self
594 .average_success_rate
595 .mul_add((self.total_optimizations - 1) as f64, record.success_rate)
596 / self.total_optimizations as f64;
597
598 self.selector.update(record.clone());
600
601 self.history.push_back(record);
603
604 if self.history.len() > self.max_history {
606 self.history.pop_front();
607 }
608 }
609
610 pub fn recommend_strategies(
612 &mut self,
613 model: &IsingModel,
614 top_k: usize,
615 ) -> Vec<(OptimizationStrategy, PredictedPerformance)> {
616 let features = self.extract_features(model);
617 let strategies = vec![
618 OptimizationStrategy::ClassicalAnnealing,
619 OptimizationStrategy::QuantumAnnealing,
620 OptimizationStrategy::PopulationAnnealing,
621 OptimizationStrategy::CoherentIsingMachine,
622 OptimizationStrategy::QuantumWalk,
623 OptimizationStrategy::HybridQCML,
624 OptimizationStrategy::AdaptiveSchedule,
625 OptimizationStrategy::ReversedAnnealing,
626 ];
627
628 let mut recommendations: Vec<_> = strategies
629 .iter()
630 .map(|&strategy| {
631 let prediction = self.selector.predict_performance(&features, strategy);
632 (strategy, prediction)
633 })
634 .collect();
635
636 recommendations.sort_by(|a, b| {
638 b.1.quality_score
639 .partial_cmp(&a.1.quality_score)
640 .unwrap_or(std::cmp::Ordering::Equal)
641 });
642
643 recommendations.into_iter().take(top_k).collect()
644 }
645
646 #[must_use]
648 pub fn get_statistics(&self) -> MetaLearningStatistics {
649 MetaLearningStatistics {
650 total_optimizations: self.total_optimizations,
651 average_success_rate: self.average_success_rate,
652 history_size: self.history.len(),
653 exploration_rate: self.selector.exploration_rate,
654 }
655 }
656}
657
658#[derive(Debug, Clone, Serialize, Deserialize)]
660pub struct MetaLearningStatistics {
661 pub total_optimizations: usize,
662 pub average_success_rate: f64,
663 pub history_size: usize,
664 pub exploration_rate: f64,
665}
666
667#[cfg(test)]
668mod tests {
669 use super::*;
670
671 #[test]
672 fn test_meta_learning_optimizer_creation() {
673 let optimizer = MetaLearningOptimizer::new(100, 42);
674 assert_eq!(optimizer.total_optimizations, 0);
675 assert_eq!(optimizer.average_success_rate, 0.0);
676 }
677
678 #[test]
679 fn test_feature_extraction() {
680 let optimizer = MetaLearningOptimizer::new(100, 42);
681 let mut model = IsingModel::new(5);
682 model.set_coupling(0, 1, -1.0).expect("should set coupling");
683 model.set_coupling(1, 2, -1.0).expect("should set coupling");
684 model.set_bias(0, 0.5).expect("should set bias");
685
686 let features = optimizer.extract_features(&model);
687 assert_eq!(features.num_variables, 5);
688 assert!(features.density > 0.0);
689 }
690
691 #[test]
692 fn test_strategy_selection() {
693 let mut optimizer = MetaLearningOptimizer::new(100, 42);
694 let mut model = IsingModel::new(10);
695 model.set_coupling(0, 1, -1.0).expect("should set coupling");
696
697 let strategy = optimizer.select_strategy(&model);
698 assert!(matches!(
699 strategy,
700 OptimizationStrategy::ClassicalAnnealing
701 | OptimizationStrategy::QuantumAnnealing
702 | OptimizationStrategy::PopulationAnnealing
703 | OptimizationStrategy::AdaptiveSchedule
704 ));
705 }
706
707 #[test]
708 fn test_performance_predictor() {
709 let predictor = PerformancePredictor::new(8);
710 let features = ProblemFeatures {
711 num_variables: 20,
712 density: 0.3,
713 coupling_mean: 1.0,
714 coupling_std: 0.5,
715 coupling_max: 2.0,
716 bias_mean: 0.0,
717 bias_std: 0.2,
718 average_degree: 6.0,
719 max_degree: 10,
720 clustering_coefficient: 0.3,
721 estimated_barriers: 0.5,
722 frustration_index: 0.4,
723 symmetry_score: 0.7,
724 };
725
726 let prediction = predictor.predict(&features, OptimizationStrategy::ClassicalAnnealing);
727 assert!(prediction.quality_score.abs() <= 1.0);
728 assert!(prediction.estimated_time.as_millis() > 0);
729 }
730
731 #[test]
732 fn test_transfer_learning() {
733 let mut engine = TransferLearningEngine::new();
734
735 let features1 = ProblemFeatures {
736 num_variables: 20,
737 density: 0.3,
738 coupling_mean: 1.0,
739 coupling_std: 0.5,
740 coupling_max: 2.0,
741 bias_mean: 0.0,
742 bias_std: 0.2,
743 average_degree: 6.0,
744 max_degree: 10,
745 clustering_coefficient: 0.3,
746 estimated_barriers: 0.5,
747 frustration_index: 0.4,
748 symmetry_score: 0.7,
749 };
750
751 let features2 = features1.clone();
752
753 let similarity = engine.compute_similarity(&features1, &features2);
754 assert!((similarity - 1.0).abs() < 0.01); }
756
757 #[test]
758 fn test_record_optimization() {
759 let mut optimizer = MetaLearningOptimizer::new(100, 42);
760
761 let features = ProblemFeatures {
762 num_variables: 10,
763 density: 0.2,
764 coupling_mean: 1.0,
765 coupling_std: 0.3,
766 coupling_max: 1.5,
767 bias_mean: 0.0,
768 bias_std: 0.1,
769 average_degree: 4.0,
770 max_degree: 8,
771 clustering_coefficient: 0.2,
772 estimated_barriers: 0.3,
773 frustration_index: 0.3,
774 symmetry_score: 0.8,
775 };
776
777 let record = OptimizationRecord {
778 features,
779 strategy: OptimizationStrategy::ClassicalAnnealing,
780 parameters: HashMap::new(),
781 best_energy: -10.0,
782 convergence_time: Duration::from_secs(1),
783 iterations_to_converge: 100,
784 success_rate: 0.95,
785 cpu_time: Duration::from_secs(1),
786 memory_peak_mb: 50.0,
787 timestamp: Instant::now(),
788 };
789
790 optimizer.record_optimization(record);
791 assert_eq!(optimizer.total_optimizations, 1);
792 assert_eq!(optimizer.average_success_rate, 0.95);
793 }
794
795 #[test]
796 fn test_recommend_strategies() {
797 let mut optimizer = MetaLearningOptimizer::new(100, 42);
798 let mut model = IsingModel::new(15);
799 model.set_coupling(0, 1, -1.0).expect("should set coupling");
800 model.set_coupling(1, 2, 1.0).expect("should set coupling");
801
802 let recommendations = optimizer.recommend_strategies(&model, 3);
803 assert_eq!(recommendations.len(), 3);
804
805 if recommendations.len() >= 2 {
807 assert!(recommendations[0].1.quality_score >= recommendations[1].1.quality_score);
808 }
809 }
810}