1use std::collections::{HashMap, VecDeque};
4use std::time::{Duration, Instant};
5
6use scirs2_core::rand_prelude::IndexedRandom;
7use scirs2_core::random::thread_rng;
8
9use super::config::{
10 AlgorithmSelectionStrategy, DiversityCriteria, DiversityMethod, PortfolioManagementConfig,
11};
12use super::feature_extraction::{
13 AlgorithmType, OptimizationConfiguration, ProblemDomain, ProblemFeatures, ResourceAllocation,
14 ResourceUsage,
15};
16
17pub struct AlgorithmPortfolio {
19 pub algorithms: HashMap<String, Algorithm>,
21 pub composition: PortfolioComposition,
23 pub selection_strategy: AlgorithmSelectionStrategy,
25 pub performance_history: HashMap<String, VecDeque<PerformanceRecord>>,
27 pub diversity_analyzer: DiversityAnalyzer,
29}
30
31impl AlgorithmPortfolio {
32 #[must_use]
33 pub fn new(config: PortfolioManagementConfig) -> Self {
34 let mut portfolio = Self {
35 algorithms: HashMap::new(),
36 composition: PortfolioComposition::new(),
37 selection_strategy: config.selection_strategy,
38 performance_history: HashMap::new(),
39 diversity_analyzer: DiversityAnalyzer::new(config.diversity_criteria),
40 };
41
42 portfolio.add_default_algorithms();
44 portfolio
45 }
46
47 fn add_default_algorithms(&mut self) {
48 let sa_algorithm = Algorithm {
50 id: "simulated_annealing".to_string(),
51 algorithm_type: AlgorithmType::SimulatedAnnealing,
52 default_config: OptimizationConfiguration {
53 algorithm: AlgorithmType::SimulatedAnnealing,
54 hyperparameters: [
55 ("initial_temperature".to_string(), 10.0),
56 ("final_temperature".to_string(), 0.1),
57 ("cooling_rate".to_string(), 0.95),
58 ]
59 .iter()
60 .cloned()
61 .collect(),
62 architecture: None,
63 resources: ResourceAllocation {
64 cpu: 1.0,
65 memory: 256,
66 gpu: 0.0,
67 time: Duration::from_secs(300),
68 },
69 },
70 performance_stats: AlgorithmPerformanceStats::default(),
71 applicability: ApplicabilityConditions {
72 size_range: (1, 10_000),
73 suitable_domains: vec![
74 ProblemDomain::Combinatorial,
75 ProblemDomain::Graph,
76 ProblemDomain::Scheduling,
77 ],
78 required_resources: ResourceRequirements {
79 memory: 128,
80 compute_time: Duration::from_secs(60),
81 parameters: 1000,
82 flops: 1_000_000,
83 },
84 performance_guarantees: vec![],
85 },
86 };
87
88 self.algorithms
89 .insert("simulated_annealing".to_string(), sa_algorithm);
90
91 let qa_algorithm = Algorithm {
93 id: "quantum_annealing".to_string(),
94 algorithm_type: AlgorithmType::QuantumAnnealing,
95 default_config: OptimizationConfiguration {
96 algorithm: AlgorithmType::QuantumAnnealing,
97 hyperparameters: [
98 ("annealing_time".to_string(), 20.0),
99 ("num_reads".to_string(), 1000.0),
100 ("chain_strength".to_string(), 1.0),
101 ]
102 .iter()
103 .cloned()
104 .collect(),
105 architecture: None,
106 resources: ResourceAllocation {
107 cpu: 0.5,
108 memory: 512,
109 gpu: 0.0,
110 time: Duration::from_secs(60),
111 },
112 },
113 performance_stats: AlgorithmPerformanceStats::default(),
114 applicability: ApplicabilityConditions {
115 size_range: (1, 5000),
116 suitable_domains: vec![
117 ProblemDomain::Combinatorial,
118 ProblemDomain::Physics,
119 ProblemDomain::MachineLearning,
120 ],
121 required_resources: ResourceRequirements {
122 memory: 256,
123 compute_time: Duration::from_secs(30),
124 parameters: 5000,
125 flops: 10_000_000,
126 },
127 performance_guarantees: vec![],
128 },
129 };
130
131 self.algorithms
132 .insert("quantum_annealing".to_string(), qa_algorithm);
133
134 let ts_algorithm = Algorithm {
136 id: "tabu_search".to_string(),
137 algorithm_type: AlgorithmType::TabuSearch,
138 default_config: OptimizationConfiguration {
139 algorithm: AlgorithmType::TabuSearch,
140 hyperparameters: [
141 ("tabu_size".to_string(), 50.0),
142 ("max_iterations".to_string(), 1000.0),
143 ("aspiration_criteria".to_string(), 1.0),
144 ]
145 .iter()
146 .cloned()
147 .collect(),
148 architecture: None,
149 resources: ResourceAllocation {
150 cpu: 1.0,
151 memory: 512,
152 gpu: 0.0,
153 time: Duration::from_secs(600),
154 },
155 },
156 performance_stats: AlgorithmPerformanceStats::default(),
157 applicability: ApplicabilityConditions {
158 size_range: (10, 50_000),
159 suitable_domains: vec![
160 ProblemDomain::Combinatorial,
161 ProblemDomain::Scheduling,
162 ProblemDomain::Graph,
163 ],
164 required_resources: ResourceRequirements {
165 memory: 256,
166 compute_time: Duration::from_secs(120),
167 parameters: 10_000,
168 flops: 50_000_000,
169 },
170 performance_guarantees: vec![],
171 },
172 };
173
174 self.algorithms
175 .insert("tabu_search".to_string(), ts_algorithm);
176 }
177
178 pub fn select_algorithm(
179 &mut self,
180 problem_features: &ProblemFeatures,
181 ) -> Result<String, String> {
182 match &self.selection_strategy {
183 AlgorithmSelectionStrategy::MultiArmedBandit => {
184 self.multi_armed_bandit_selection(problem_features)
185 }
186 AlgorithmSelectionStrategy::UpperConfidenceBound => {
187 self.ucb_selection(problem_features)
188 }
189 AlgorithmSelectionStrategy::ThompsonSampling => {
190 self.thompson_sampling_selection(problem_features)
191 }
192 AlgorithmSelectionStrategy::EpsilonGreedy(epsilon) => {
193 self.epsilon_greedy_selection(problem_features, *epsilon)
194 }
195 AlgorithmSelectionStrategy::CollaborativeFiltering => {
196 self.collaborative_filtering_selection(problem_features)
197 }
198 AlgorithmSelectionStrategy::MetaLearningBased => {
199 self.meta_learning_selection(problem_features)
200 }
201 }
202 }
203
204 fn multi_armed_bandit_selection(
205 &self,
206 problem_features: &ProblemFeatures,
207 ) -> Result<String, String> {
208 let applicable_algorithms = self.get_applicable_algorithms(problem_features);
210
211 if applicable_algorithms.is_empty() {
212 return Err("No applicable algorithms found".to_string());
213 }
214
215 let best_algorithm = applicable_algorithms
216 .iter()
217 .max_by(|a, b| {
218 let perf_a = self
219 .algorithms
220 .get(*a)
221 .map_or(0.0, |alg| alg.performance_stats.mean_performance);
222 let perf_b = self
223 .algorithms
224 .get(*b)
225 .map_or(0.0, |alg| alg.performance_stats.mean_performance);
226 perf_a
227 .partial_cmp(&perf_b)
228 .unwrap_or(std::cmp::Ordering::Equal)
229 })
230 .ok_or("Failed to select algorithm")?;
231
232 Ok(best_algorithm.clone())
233 }
234
235 fn ucb_selection(&self, problem_features: &ProblemFeatures) -> Result<String, String> {
236 let applicable_algorithms = self.get_applicable_algorithms(problem_features);
237
238 if applicable_algorithms.is_empty() {
239 return Err("No applicable algorithms found".to_string());
240 }
241
242 let total_trials: f64 = self
244 .performance_history
245 .values()
246 .map(|history| history.len() as f64)
247 .sum();
248
249 let best_algorithm = applicable_algorithms
250 .iter()
251 .max_by(|a, b| {
252 let history_a = self
253 .performance_history
254 .get(*a)
255 .map_or(0, std::collections::VecDeque::len);
256 let history_b = self
257 .performance_history
258 .get(*b)
259 .map_or(0, std::collections::VecDeque::len);
260
261 let mean_a = self
262 .algorithms
263 .get(*a)
264 .map_or(0.0, |alg| alg.performance_stats.mean_performance);
265 let mean_b = self
266 .algorithms
267 .get(*b)
268 .map_or(0.0, |alg| alg.performance_stats.mean_performance);
269
270 let confidence_a = if history_a > 0 {
271 (2.0 * total_trials.ln() / history_a as f64).sqrt()
272 } else {
273 f64::INFINITY
274 };
275
276 let confidence_b = if history_b > 0 {
277 (2.0 * total_trials.ln() / history_b as f64).sqrt()
278 } else {
279 f64::INFINITY
280 };
281
282 let ucb_a = mean_a + confidence_a;
283 let ucb_b = mean_b + confidence_b;
284
285 ucb_a
286 .partial_cmp(&ucb_b)
287 .unwrap_or(std::cmp::Ordering::Equal)
288 })
289 .ok_or("Failed to select algorithm")?;
290
291 Ok(best_algorithm.clone())
292 }
293
294 fn thompson_sampling_selection(
295 &self,
296 problem_features: &ProblemFeatures,
297 ) -> Result<String, String> {
298 let applicable_algorithms = self.get_applicable_algorithms(problem_features);
300
301 if applicable_algorithms.is_empty() {
302 return Err("No applicable algorithms found".to_string());
303 }
304
305 let best_algorithm = applicable_algorithms
306 .iter()
307 .max_by(|a, b| {
308 let var_a = self
309 .algorithms
310 .get(*a)
311 .map_or(0.0, |alg| alg.performance_stats.performance_variance);
312 let var_b = self
313 .algorithms
314 .get(*b)
315 .map_or(0.0, |alg| alg.performance_stats.performance_variance);
316 var_a
317 .partial_cmp(&var_b)
318 .unwrap_or(std::cmp::Ordering::Equal)
319 })
320 .ok_or("Failed to select algorithm")?;
321
322 Ok(best_algorithm.clone())
323 }
324
325 fn epsilon_greedy_selection(
326 &self,
327 problem_features: &ProblemFeatures,
328 epsilon: f64,
329 ) -> Result<String, String> {
330 use scirs2_core::random::prelude::*;
331 let mut rng = thread_rng();
332
333 let applicable_algorithms = self.get_applicable_algorithms(problem_features);
334
335 if applicable_algorithms.is_empty() {
336 return Err("No applicable algorithms found".to_string());
337 }
338
339 if rng.gen_bool(epsilon) {
340 let algorithm = applicable_algorithms
342 .choose(&mut rng)
343 .ok_or("Failed to randomly select algorithm")?;
344 Ok(algorithm.clone())
345 } else {
346 self.multi_armed_bandit_selection(problem_features)
348 }
349 }
350
351 fn collaborative_filtering_selection(
352 &self,
353 problem_features: &ProblemFeatures,
354 ) -> Result<String, String> {
355 self.multi_armed_bandit_selection(problem_features)
357 }
358
359 fn meta_learning_selection(
360 &self,
361 problem_features: &ProblemFeatures,
362 ) -> Result<String, String> {
363 self.multi_armed_bandit_selection(problem_features)
365 }
366
367 fn get_applicable_algorithms(&self, problem_features: &ProblemFeatures) -> Vec<String> {
368 self.algorithms
369 .iter()
370 .filter(|(_, algorithm)| {
371 problem_features.size >= algorithm.applicability.size_range.0
373 && problem_features.size <= algorithm.applicability.size_range.1
374 })
375 .map(|(id, _)| id.clone())
376 .collect()
377 }
378
379 pub fn record_performance(&mut self, algorithm_id: &str, record: PerformanceRecord) {
380 self.performance_history
381 .entry(algorithm_id.to_string())
382 .or_insert_with(VecDeque::new)
383 .push_back(record);
384
385 let algorithm_id_string = algorithm_id.to_string();
387 if self.algorithms.contains_key(algorithm_id) {
388 let history = self.performance_history.get(&algorithm_id_string).cloned();
390 if let Some(algorithm) = self.algorithms.get_mut(algorithm_id) {
391 if let Some(history) = history {
392 if !history.is_empty() {
393 let total_performance: f64 = history.iter().map(|r| r.performance).sum();
395 algorithm.performance_stats.mean_performance =
396 total_performance / history.len() as f64;
397
398 let variance: f64 = history
400 .iter()
401 .map(|r| {
402 (r.performance - algorithm.performance_stats.mean_performance)
403 .powi(2)
404 })
405 .sum::<f64>()
406 / history.len() as f64;
407 algorithm.performance_stats.performance_variance = variance;
408
409 let successful_runs =
411 history.iter().filter(|r| r.performance > 0.5).count();
412 algorithm.performance_stats.success_rate =
413 successful_runs as f64 / history.len() as f64;
414 }
415 }
416 }
417 }
418
419 if let Some(history) = self.performance_history.get_mut(algorithm_id) {
421 while history.len() > 1000 {
422 history.pop_front();
423 }
424 }
425 }
426
427 fn update_algorithm_stats(&self, algorithm: &mut Algorithm, _record: &PerformanceRecord) {
428 let Some(history) = self.performance_history.get(&algorithm.id) else {
429 return;
430 };
431
432 if !history.is_empty() {
433 let total_performance: f64 = history.iter().map(|r| r.performance).sum();
435 algorithm.performance_stats.mean_performance = total_performance / history.len() as f64;
436
437 let variance: f64 = history
439 .iter()
440 .map(|r| (r.performance - algorithm.performance_stats.mean_performance).powi(2))
441 .sum::<f64>()
442 / history.len() as f64;
443 algorithm.performance_stats.performance_variance = variance;
444
445 let successes = history.iter().filter(|r| r.performance > 0.5).count();
447 algorithm.performance_stats.success_rate = successes as f64 / history.len() as f64;
448 }
449 }
450
451 #[must_use]
452 pub const fn get_portfolio_diversity(&self) -> f64 {
453 self.diversity_analyzer.current_diversity
454 }
455
456 pub fn update_composition(&mut self) {
457 let mut new_weights = HashMap::new();
459 let mut total_performance = 0.0;
460
461 for (algorithm_id, algorithm) in &self.algorithms {
462 let weight = algorithm.performance_stats.mean_performance.max(0.1);
463 new_weights.insert(algorithm_id.clone(), weight);
464 total_performance += weight;
465 }
466
467 if total_performance > 0.0 {
469 for (_, weight) in &mut new_weights {
470 *weight /= total_performance;
471 }
472 }
473
474 self.composition.weights = new_weights;
475 self.composition.last_update = Instant::now();
476
477 self.composition.selection_probabilities = self.composition.weights.clone();
479 }
480}
481
482#[derive(Debug)]
484pub struct Algorithm {
485 pub id: String,
487 pub algorithm_type: AlgorithmType,
489 pub default_config: OptimizationConfiguration,
491 pub performance_stats: AlgorithmPerformanceStats,
493 pub applicability: ApplicabilityConditions,
495}
496
497#[derive(Debug, Clone)]
499pub struct PortfolioComposition {
500 pub weights: HashMap<String, f64>,
502 pub selection_probabilities: HashMap<String, f64>,
504 pub last_update: Instant,
506 pub quality_score: f64,
508}
509
510impl PortfolioComposition {
511 #[must_use]
512 pub fn new() -> Self {
513 Self {
514 weights: HashMap::new(),
515 selection_probabilities: HashMap::new(),
516 last_update: Instant::now(),
517 quality_score: 0.0,
518 }
519 }
520}
521
522#[derive(Debug, Clone)]
524pub struct PerformanceRecord {
525 pub timestamp: Instant,
527 pub problem_features: ProblemFeatures,
529 pub performance: f64,
531 pub resource_usage: ResourceUsage,
533 pub context: HashMap<String, String>,
535}
536
537#[derive(Debug, Clone)]
539pub struct AlgorithmPerformanceStats {
540 pub mean_performance: f64,
542 pub performance_variance: f64,
544 pub success_rate: f64,
546 pub avg_runtime: Duration,
548 pub scalability_factor: f64,
550}
551
552impl Default for AlgorithmPerformanceStats {
553 fn default() -> Self {
554 Self {
555 mean_performance: 0.5,
556 performance_variance: 0.1,
557 success_rate: 0.5,
558 avg_runtime: Duration::from_secs(60),
559 scalability_factor: 1.0,
560 }
561 }
562}
563
564#[derive(Debug, Clone)]
566pub struct ApplicabilityConditions {
567 pub size_range: (usize, usize),
569 pub suitable_domains: Vec<ProblemDomain>,
571 pub required_resources: ResourceRequirements,
573 pub performance_guarantees: Vec<PerformanceGuarantee>,
575}
576
577#[derive(Debug, Clone)]
579pub struct PerformanceGuarantee {
580 pub guarantee_type: GuaranteeType,
582 pub confidence: f64,
584 pub conditions: Vec<String>,
586}
587
588#[derive(Debug, Clone, PartialEq)]
590pub enum GuaranteeType {
591 MinimumPerformance(f64),
593 MaximumRuntime(Duration),
595 ResourceBounds(ResourceRequirements),
597 QualityBounds(f64, f64),
599}
600
601#[derive(Debug)]
603pub struct DiversityAnalyzer {
604 pub metrics: Vec<DiversityMetric>,
606 pub methods: Vec<DiversityMethod>,
608 pub current_diversity: f64,
610 pub target_diversity: f64,
612}
613
614impl DiversityAnalyzer {
615 #[must_use]
616 pub fn new(criteria: DiversityCriteria) -> Self {
617 Self {
618 metrics: vec![
619 DiversityMetric::AlgorithmDiversity,
620 DiversityMetric::PerformanceDiversity,
621 ],
622 methods: vec![criteria.diversity_method],
623 current_diversity: 0.5,
624 target_diversity: criteria.min_algorithmic_diversity,
625 }
626 }
627
628 pub fn analyze_diversity(&mut self, portfolio: &AlgorithmPortfolio) -> f64 {
629 let num_algorithms = portfolio.algorithms.len() as f64;
631 let max_diversity = (num_algorithms * (num_algorithms - 1.0) / 2.0).max(1.0);
632
633 let algorithmic_diversity = if num_algorithms > 1.0 {
635 1.0 / num_algorithms
636 } else {
637 0.0
638 };
639
640 let performances: Vec<f64> = portfolio
642 .algorithms
643 .values()
644 .map(|alg| alg.performance_stats.mean_performance)
645 .collect();
646
647 let performance_diversity = if performances.len() > 1 {
648 let mean_perf = performances.iter().sum::<f64>() / performances.len() as f64;
649 let variance = performances
650 .iter()
651 .map(|p| (p - mean_perf).powi(2))
652 .sum::<f64>()
653 / performances.len() as f64;
654 variance.sqrt()
655 } else {
656 0.0
657 };
658
659 self.current_diversity = f64::midpoint(algorithmic_diversity, performance_diversity);
660 self.current_diversity
661 }
662}
663
664#[derive(Debug, Clone, PartialEq, Eq)]
666pub enum DiversityMetric {
667 AlgorithmDiversity,
669 PerformanceDiversity,
671 FeatureDiversity,
673 ErrorDiversity,
675 PredictionDiversity,
677}
678
679use super::neural_architecture_search::ResourceRequirements;
680
681#[cfg(test)]
682mod tests {
683 use super::*;
684 use crate::meta_learning_optimization::feature_extraction::{
685 GraphFeatures, SpectralFeatures, StatisticalFeatures,
686 };
687
688 #[test]
689 fn test_portfolio_creation() {
690 let config = PortfolioManagementConfig::default();
691 let portfolio = AlgorithmPortfolio::new(config);
692 assert!(!portfolio.algorithms.is_empty());
693 }
694
695 #[test]
696 fn test_algorithm_selection() {
697 let config = PortfolioManagementConfig::default();
698 let mut portfolio = AlgorithmPortfolio::new(config);
699
700 let features = ProblemFeatures {
701 size: 100,
702 density: 0.5,
703 graph_features: GraphFeatures::default(),
704 statistical_features: StatisticalFeatures::default(),
705 spectral_features: SpectralFeatures::default(),
706 domain_features: HashMap::new(),
707 };
708
709 let result = portfolio.select_algorithm(&features);
710 assert!(result.is_ok());
711 }
712
713 #[test]
714 fn test_diversity_analyzer() {
715 let criteria = DiversityCriteria::default();
716 let analyzer = DiversityAnalyzer::new(criteria);
717 assert_eq!(analyzer.target_diversity, 0.2);
718 }
719}