1use crate::error::{MLError, Result};
38use scirs2_core::ndarray::{Array1, Array2};
39use scirs2_core::random::{thread_rng, Rng};
40use std::collections::HashMap;
41
42#[derive(Debug, Clone)]
44pub struct ProblemCharacteristics {
45 pub n_samples: usize,
47
48 pub n_features: usize,
50
51 pub n_classes: usize,
53
54 pub dimensionality_ratio: f64,
56
57 pub sparsity: f64,
59
60 pub condition_number: f64,
62
63 pub class_imbalance: f64,
65
66 pub task_type: TaskType,
68
69 pub domain: ProblemDomain,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq)]
75pub enum TaskType {
76 BinaryClassification,
77 MultiClassClassification,
78 Regression,
79 Clustering,
80 DimensionalityReduction,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq)]
85pub enum ProblemDomain {
86 General,
87 DrugDiscovery,
88 Finance,
89 ComputerVision,
90 NaturalLanguage,
91 TimeSeriesForecasting,
92 AnomalyDetection,
93 RecommenderSystem,
94}
95
96#[derive(Debug, Clone)]
98pub struct ResourceConstraints {
99 pub quantum_devices: Vec<QuantumDevice>,
101
102 pub classical_compute: ClassicalCompute,
104
105 pub max_latency_ms: Option<f64>,
107
108 pub max_cost_per_inference: Option<f64>,
110
111 pub max_training_time: Option<f64>,
113
114 pub max_power_consumption: Option<f64>,
116}
117
118#[derive(Debug, Clone)]
120pub struct QuantumDevice {
121 pub name: String,
122 pub n_qubits: usize,
123 pub gate_error_rate: f64,
124 pub measurement_error_rate: f64,
125 pub decoherence_time_us: f64,
126 pub cost_per_shot: f64,
127 pub availability: DeviceAvailability,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq)]
132pub enum DeviceAvailability {
133 Available,
134 Queued,
135 Unavailable,
136}
137
138#[derive(Debug, Clone)]
140pub struct ClassicalCompute {
141 pub n_cpu_cores: usize,
142 pub has_gpu: bool,
143 pub gpu_memory_gb: f64,
144 pub ram_gb: f64,
145}
146
147#[derive(Debug, Clone)]
149pub struct AlgorithmRecommendation {
150 pub algorithm_choice: AlgorithmChoice,
152
153 pub quantum_advantage: QuantumAdvantageMetrics,
155
156 pub hyperparameters: HashMap<String, f64>,
158
159 pub expected_performance: PerformanceEstimate,
161
162 pub cost_analysis: CostAnalysis,
164
165 pub confidence: f64,
167
168 pub calibration_method: Option<String>,
170
171 pub production_config: ProductionConfig,
173}
174
175#[derive(Debug, Clone, PartialEq)]
177pub enum AlgorithmChoice {
178 QuantumOnly { algorithm: String, device: String },
180
181 ClassicalOnly { algorithm: String, backend: String },
183
184 Hybrid {
186 quantum_component: String,
187 classical_component: String,
188 splitting_strategy: String,
189 },
190}
191
192#[derive(Debug, Clone)]
194pub struct QuantumAdvantageMetrics {
195 pub speedup: f64,
197
198 pub accuracy_improvement: f64,
200
201 pub sample_efficiency: f64,
203
204 pub generalization_improvement: f64,
206
207 pub statistical_significance: f64,
209}
210
211#[derive(Debug, Clone)]
213pub struct PerformanceEstimate {
214 pub accuracy: f64,
216
217 pub accuracy_ci: (f64, f64),
219
220 pub training_time_s: f64,
222
223 pub inference_latency_ms: f64,
225
226 pub memory_mb: f64,
228}
229
230#[derive(Debug, Clone)]
232pub struct CostAnalysis {
233 pub training_cost: f64,
235
236 pub inference_cost_per_sample: f64,
238
239 pub total_cost: f64,
241
242 pub breakdown: HashMap<String, f64>,
244}
245
246#[derive(Debug, Clone)]
248pub struct ProductionConfig {
249 pub batch_size: usize,
251
252 pub n_workers: usize,
254
255 pub enable_caching: bool,
257
258 pub monitoring: MonitoringConfig,
260
261 pub scaling: ScalingConfig,
263}
264
265#[derive(Debug, Clone)]
267pub struct MonitoringConfig {
268 pub log_interval: usize,
270
271 pub alert_thresholds: HashMap<String, f64>,
273
274 pub tracked_metrics: Vec<String>,
276}
277
278#[derive(Debug, Clone)]
280pub struct ScalingConfig {
281 pub auto_scaling: bool,
283
284 pub min_instances: usize,
286
287 pub max_instances: usize,
289
290 pub scale_up_threshold: f64,
292
293 pub scale_down_threshold: f64,
295}
296
297pub struct HybridAutoMLEngine {
299 performance_models: HashMap<String, PerformanceModel>,
301
302 cost_models: HashMap<String, CostModel>,
304
305 decision_thresholds: DecisionThresholds,
307}
308
309struct PerformanceModel {
311 model_type: String,
313
314 coefficients: Vec<f64>,
316}
317
318struct CostModel {
320 base_cost: f64,
322
323 cost_per_sample: f64,
325
326 cost_per_feature: f64,
328
329 cost_per_qubit: f64,
331}
332
333struct DecisionThresholds {
335 min_quantum_speedup: f64,
337
338 min_accuracy_improvement: f64,
340
341 max_cost_ratio: f64,
343
344 min_confidence: f64,
346}
347
348impl HybridAutoMLEngine {
349 pub fn new() -> Self {
351 Self {
352 performance_models: Self::initialize_performance_models(),
353 cost_models: Self::initialize_cost_models(),
354 decision_thresholds: DecisionThresholds::default(),
355 }
356 }
357
358 pub fn analyze_and_recommend(
360 &self,
361 characteristics: &ProblemCharacteristics,
362 constraints: &ResourceConstraints,
363 ) -> Result<AlgorithmRecommendation> {
364 let features = self.extract_decision_features(characteristics);
366
367 let quantum_options = self.evaluate_quantum_algorithms(characteristics, constraints)?;
369
370 let classical_options = self.evaluate_classical_algorithms(characteristics, constraints)?;
372
373 let best_option = self.select_best_option(
375 &quantum_options,
376 &classical_options,
377 characteristics,
378 constraints,
379 )?;
380
381 let recommendation =
383 self.generate_recommendation(best_option, characteristics, constraints)?;
384
385 Ok(recommendation)
386 }
387
388 fn extract_decision_features(&self, chars: &ProblemCharacteristics) -> Vec<f64> {
390 vec![
391 chars.n_samples as f64,
392 chars.n_features as f64,
393 chars.n_classes as f64,
394 chars.dimensionality_ratio,
395 chars.sparsity,
396 chars.condition_number,
397 chars.class_imbalance,
398 (chars.n_features as f64).log2(), ]
400 }
401
402 fn evaluate_quantum_algorithms(
404 &self,
405 chars: &ProblemCharacteristics,
406 constraints: &ResourceConstraints,
407 ) -> Result<Vec<AlgorithmOption>> {
408 let mut options = Vec::new();
409
410 if constraints.quantum_devices.is_empty() {
412 return Ok(options);
413 }
414
415 if matches!(
417 chars.task_type,
418 TaskType::BinaryClassification | TaskType::MultiClassClassification
419 ) {
420 let qsvm_option = self.evaluate_qsvm(chars, constraints)?;
421 if qsvm_option.is_feasible {
422 options.push(qsvm_option);
423 }
424 }
425
426 let qnn_option = self.evaluate_qnn(chars, constraints)?;
428 if qnn_option.is_feasible {
429 options.push(qnn_option);
430 }
431
432 if chars.domain == ProblemDomain::DrugDiscovery {
434 let vqe_option = self.evaluate_vqe(chars, constraints)?;
435 if vqe_option.is_feasible {
436 options.push(vqe_option);
437 }
438 }
439
440 if matches!(chars.task_type, TaskType::Clustering) {
442 let qaoa_option = self.evaluate_qaoa(chars, constraints)?;
443 if qaoa_option.is_feasible {
444 options.push(qaoa_option);
445 }
446 }
447
448 Ok(options)
449 }
450
451 fn evaluate_classical_algorithms(
453 &self,
454 chars: &ProblemCharacteristics,
455 constraints: &ResourceConstraints,
456 ) -> Result<Vec<AlgorithmOption>> {
457 let mut options = Vec::new();
458
459 if matches!(
461 chars.task_type,
462 TaskType::BinaryClassification | TaskType::MultiClassClassification
463 ) {
464 options.push(self.evaluate_classical_svm(chars, constraints)?);
465 }
466
467 options.push(self.evaluate_classical_nn(chars, constraints)?);
469
470 options.push(self.evaluate_random_forest(chars, constraints)?);
472
473 options.push(self.evaluate_gradient_boosting(chars, constraints)?);
475
476 Ok(options)
477 }
478
479 fn evaluate_qsvm(
481 &self,
482 chars: &ProblemCharacteristics,
483 constraints: &ResourceConstraints,
484 ) -> Result<AlgorithmOption> {
485 let device = &constraints.quantum_devices[0];
486
487 let required_qubits = (chars.n_features as f64).log2().ceil() as usize;
489 let is_feasible = required_qubits <= device.n_qubits;
490
491 let accuracy = self.estimate_qsvm_accuracy(chars, device)?;
493 let training_time = self.estimate_qsvm_training_time(chars, device)?;
494 let cost = self.estimate_qsvm_cost(chars, device)?;
495
496 Ok(AlgorithmOption {
497 name: "QSVM".to_string(),
498 algorithm_type: AlgorithmType::Quantum,
499 is_feasible,
500 expected_accuracy: accuracy,
501 expected_training_time_s: training_time,
502 expected_inference_latency_ms: 10.0, expected_cost: cost,
504 required_qubits: Some(required_qubits),
505 confidence: 0.85,
506 })
507 }
508
509 fn evaluate_qnn(
511 &self,
512 chars: &ProblemCharacteristics,
513 constraints: &ResourceConstraints,
514 ) -> Result<AlgorithmOption> {
515 let device = &constraints.quantum_devices[0];
516
517 let required_qubits = chars.n_features.min(20); let is_feasible = required_qubits <= device.n_qubits;
519
520 let accuracy = self.estimate_qnn_accuracy(chars, device)?;
521 let training_time = self.estimate_qnn_training_time(chars, device)?;
522 let cost = self.estimate_qnn_cost(chars, device)?;
523
524 Ok(AlgorithmOption {
525 name: "QNN".to_string(),
526 algorithm_type: AlgorithmType::Quantum,
527 is_feasible,
528 expected_accuracy: accuracy,
529 expected_training_time_s: training_time,
530 expected_inference_latency_ms: 5.0,
531 expected_cost: cost,
532 required_qubits: Some(required_qubits),
533 confidence: 0.80,
534 })
535 }
536
537 fn evaluate_vqe(
539 &self,
540 chars: &ProblemCharacteristics,
541 constraints: &ResourceConstraints,
542 ) -> Result<AlgorithmOption> {
543 let device = &constraints.quantum_devices[0];
544
545 let required_qubits = 10.min(device.n_qubits);
546 let is_feasible = true;
547
548 Ok(AlgorithmOption {
549 name: "VQE".to_string(),
550 algorithm_type: AlgorithmType::Quantum,
551 is_feasible,
552 expected_accuracy: 0.92,
553 expected_training_time_s: 300.0,
554 expected_inference_latency_ms: 50.0,
555 expected_cost: 100.0,
556 required_qubits: Some(required_qubits),
557 confidence: 0.75,
558 })
559 }
560
561 fn evaluate_qaoa(
563 &self,
564 chars: &ProblemCharacteristics,
565 constraints: &ResourceConstraints,
566 ) -> Result<AlgorithmOption> {
567 let device = &constraints.quantum_devices[0];
568
569 let required_qubits = chars.n_samples.min(20);
570 let is_feasible = required_qubits <= device.n_qubits;
571
572 Ok(AlgorithmOption {
573 name: "QAOA".to_string(),
574 algorithm_type: AlgorithmType::Quantum,
575 is_feasible,
576 expected_accuracy: 0.88,
577 expected_training_time_s: 200.0,
578 expected_inference_latency_ms: 30.0,
579 expected_cost: 80.0,
580 required_qubits: Some(required_qubits),
581 confidence: 0.78,
582 })
583 }
584
585 fn evaluate_classical_svm(
587 &self,
588 chars: &ProblemCharacteristics,
589 _constraints: &ResourceConstraints,
590 ) -> Result<AlgorithmOption> {
591 let accuracy = 0.85 - (chars.dimensionality_ratio * 0.05).min(0.15);
592 let training_time = chars.n_samples as f64 * chars.n_features as f64 / 1000.0;
593
594 Ok(AlgorithmOption {
595 name: "Classical SVM".to_string(),
596 algorithm_type: AlgorithmType::Classical,
597 is_feasible: true,
598 expected_accuracy: accuracy,
599 expected_training_time_s: training_time,
600 expected_inference_latency_ms: 0.1,
601 expected_cost: 0.0001,
602 required_qubits: None,
603 confidence: 0.95,
604 })
605 }
606
607 fn evaluate_classical_nn(
609 &self,
610 chars: &ProblemCharacteristics,
611 constraints: &ResourceConstraints,
612 ) -> Result<AlgorithmOption> {
613 let base_accuracy = 0.88;
614 let accuracy = base_accuracy - (chars.class_imbalance.log2() * 0.02).min(0.1);
615
616 let training_time = if constraints.classical_compute.has_gpu {
617 chars.n_samples as f64 / 100.0
618 } else {
619 chars.n_samples as f64 / 10.0
620 };
621
622 Ok(AlgorithmOption {
623 name: "Neural Network".to_string(),
624 algorithm_type: AlgorithmType::Classical,
625 is_feasible: true,
626 expected_accuracy: accuracy,
627 expected_training_time_s: training_time,
628 expected_inference_latency_ms: 0.5,
629 expected_cost: 0.0001,
630 required_qubits: None,
631 confidence: 0.90,
632 })
633 }
634
635 fn evaluate_random_forest(
637 &self,
638 chars: &ProblemCharacteristics,
639 _constraints: &ResourceConstraints,
640 ) -> Result<AlgorithmOption> {
641 let accuracy = 0.86;
642 let training_time = chars.n_samples as f64 * chars.n_features as f64 / 500.0;
643
644 Ok(AlgorithmOption {
645 name: "Random Forest".to_string(),
646 algorithm_type: AlgorithmType::Classical,
647 is_feasible: true,
648 expected_accuracy: accuracy,
649 expected_training_time_s: training_time,
650 expected_inference_latency_ms: 0.2,
651 expected_cost: 0.00005,
652 required_qubits: None,
653 confidence: 0.92,
654 })
655 }
656
657 fn evaluate_gradient_boosting(
659 &self,
660 chars: &ProblemCharacteristics,
661 _constraints: &ResourceConstraints,
662 ) -> Result<AlgorithmOption> {
663 let accuracy = 0.89;
664 let training_time = chars.n_samples as f64 * chars.n_features as f64 / 300.0;
665
666 Ok(AlgorithmOption {
667 name: "Gradient Boosting".to_string(),
668 algorithm_type: AlgorithmType::Classical,
669 is_feasible: true,
670 expected_accuracy: accuracy,
671 expected_training_time_s: training_time,
672 expected_inference_latency_ms: 0.3,
673 expected_cost: 0.0001,
674 required_qubits: None,
675 confidence: 0.93,
676 })
677 }
678
679 fn estimate_qsvm_accuracy(
681 &self,
682 chars: &ProblemCharacteristics,
683 device: &QuantumDevice,
684 ) -> Result<f64> {
685 let base_accuracy = 0.90;
687
688 let noise_penalty = device.gate_error_rate * 50.0;
690
691 let dim_bonus = (1.0 / (1.0 + chars.dimensionality_ratio)) * 0.05;
693
694 Ok((base_accuracy - noise_penalty + dim_bonus)
695 .max(0.5)
696 .min(0.99))
697 }
698
699 fn estimate_qsvm_training_time(
701 &self,
702 chars: &ProblemCharacteristics,
703 _device: &QuantumDevice,
704 ) -> Result<f64> {
705 let time = (chars.n_samples * chars.n_samples) as f64 / 100.0;
707 Ok(time)
708 }
709
710 fn estimate_qsvm_cost(
712 &self,
713 chars: &ProblemCharacteristics,
714 device: &QuantumDevice,
715 ) -> Result<f64> {
716 let n_shots = 1000;
717 let n_kernel_evaluations = chars.n_samples * chars.n_samples;
718 let cost = n_kernel_evaluations as f64 * n_shots as f64 * device.cost_per_shot;
719 Ok(cost)
720 }
721
722 fn estimate_qnn_accuracy(
724 &self,
725 chars: &ProblemCharacteristics,
726 device: &QuantumDevice,
727 ) -> Result<f64> {
728 let base_accuracy = 0.87;
729 let noise_penalty = device.gate_error_rate * 40.0;
730 let complexity_bonus = (chars.n_features as f64 / 100.0).min(0.08);
731
732 Ok((base_accuracy - noise_penalty + complexity_bonus)
733 .max(0.5)
734 .min(0.99))
735 }
736
737 fn estimate_qnn_training_time(
739 &self,
740 chars: &ProblemCharacteristics,
741 _device: &QuantumDevice,
742 ) -> Result<f64> {
743 let n_epochs = 100;
744 let time_per_epoch = chars.n_samples as f64 / 10.0;
745 Ok(n_epochs as f64 * time_per_epoch)
746 }
747
748 fn estimate_qnn_cost(
750 &self,
751 chars: &ProblemCharacteristics,
752 device: &QuantumDevice,
753 ) -> Result<f64> {
754 let n_shots = 1000;
755 let n_epochs = 100;
756 let cost_per_epoch = chars.n_samples as f64 * n_shots as f64 * device.cost_per_shot;
757 Ok(n_epochs as f64 * cost_per_epoch)
758 }
759
760 fn select_best_option(
762 &self,
763 quantum_options: &[AlgorithmOption],
764 classical_options: &[AlgorithmOption],
765 chars: &ProblemCharacteristics,
766 constraints: &ResourceConstraints,
767 ) -> Result<AlgorithmOption> {
768 let mut all_options = quantum_options.to_vec();
769 all_options.extend(classical_options.iter().cloned());
770
771 let filtered: Vec<_> = all_options
773 .into_iter()
774 .filter(|opt| {
775 if let Some(max_time) = constraints.max_training_time {
776 if opt.expected_training_time_s > max_time {
777 return false;
778 }
779 }
780
781 if let Some(max_cost) = constraints.max_cost_per_inference {
782 if opt.expected_cost > max_cost {
783 return false;
784 }
785 }
786
787 true
788 })
789 .collect();
790
791 if filtered.is_empty() {
792 return Err(MLError::InvalidInput(
793 "No algorithms satisfy the given constraints".to_string(),
794 ));
795 }
796
797 let best = filtered
799 .into_iter()
800 .max_by(|a, b| {
801 let score_a = self.compute_option_score(a, chars, constraints);
802 let score_b = self.compute_option_score(b, chars, constraints);
803 score_a
804 .partial_cmp(&score_b)
805 .unwrap_or(std::cmp::Ordering::Equal)
806 })
807 .expect("filtered verified non-empty above");
808
809 Ok(best)
810 }
811
812 fn compute_option_score(
814 &self,
815 option: &AlgorithmOption,
816 _chars: &ProblemCharacteristics,
817 _constraints: &ResourceConstraints,
818 ) -> f64 {
819 let accuracy_score = option.expected_accuracy;
821 let speed_score = 1.0 / (1.0 + option.expected_training_time_s / 100.0);
822 let cost_score = 1.0 / (1.0 + option.expected_cost);
823 let confidence_score = option.confidence;
824
825 accuracy_score * 0.4 + speed_score * 0.2 + cost_score * 0.2 + confidence_score * 0.2
827 }
828
829 fn generate_recommendation(
831 &self,
832 best_option: AlgorithmOption,
833 chars: &ProblemCharacteristics,
834 constraints: &ResourceConstraints,
835 ) -> Result<AlgorithmRecommendation> {
836 let algorithm_choice = match best_option.algorithm_type {
837 AlgorithmType::Quantum => AlgorithmChoice::QuantumOnly {
838 algorithm: best_option.name.clone(),
839 device: constraints
840 .quantum_devices
841 .get(0)
842 .map(|d| d.name.clone())
843 .unwrap_or_else(|| "simulator".to_string()),
844 },
845 AlgorithmType::Classical => AlgorithmChoice::ClassicalOnly {
846 algorithm: best_option.name.clone(),
847 backend: if constraints.classical_compute.has_gpu {
848 "GPU".to_string()
849 } else {
850 "CPU".to_string()
851 },
852 },
853 AlgorithmType::Hybrid => AlgorithmChoice::Hybrid {
854 quantum_component: "QNN".to_string(),
855 classical_component: "Neural Network".to_string(),
856 splitting_strategy: "Feature Engineering".to_string(),
857 },
858 };
859
860 let quantum_advantage = self.compute_quantum_advantage(&best_option, chars)?;
862
863 let hyperparameters = self.generate_hyperparameters(&best_option, chars)?;
865
866 let expected_performance = PerformanceEstimate {
868 accuracy: best_option.expected_accuracy,
869 accuracy_ci: (
870 best_option.expected_accuracy - 0.05,
871 best_option.expected_accuracy + 0.05,
872 ),
873 training_time_s: best_option.expected_training_time_s,
874 inference_latency_ms: best_option.expected_inference_latency_ms,
875 memory_mb: chars.n_samples as f64 * chars.n_features as f64 * 8.0 / 1024.0 / 1024.0,
876 };
877
878 let cost_analysis = self.generate_cost_analysis(&best_option, chars)?;
880
881 let calibration_method = self.recommend_calibration(&best_option, chars)?;
883
884 let production_config =
886 self.generate_production_config(&best_option, chars, constraints)?;
887
888 Ok(AlgorithmRecommendation {
889 algorithm_choice,
890 quantum_advantage,
891 hyperparameters,
892 expected_performance,
893 cost_analysis,
894 confidence: best_option.confidence,
895 calibration_method,
896 production_config,
897 })
898 }
899
900 fn compute_quantum_advantage(
902 &self,
903 option: &AlgorithmOption,
904 _chars: &ProblemCharacteristics,
905 ) -> Result<QuantumAdvantageMetrics> {
906 let is_quantum = option.algorithm_type == AlgorithmType::Quantum;
907
908 Ok(QuantumAdvantageMetrics {
909 speedup: if is_quantum { 2.5 } else { 1.0 },
910 accuracy_improvement: if is_quantum { 0.05 } else { 0.0 },
911 sample_efficiency: if is_quantum { 1.8 } else { 1.0 },
912 generalization_improvement: if is_quantum { 0.03 } else { 0.0 },
913 statistical_significance: if is_quantum { 0.01 } else { 1.0 },
914 })
915 }
916
917 fn generate_hyperparameters(
919 &self,
920 option: &AlgorithmOption,
921 chars: &ProblemCharacteristics,
922 ) -> Result<HashMap<String, f64>> {
923 let mut params = HashMap::new();
924
925 match option.name.as_str() {
926 "QSVM" => {
927 params.insert("n_shots".to_string(), 1000.0);
928 params.insert("kernel_depth".to_string(), 3.0);
929 }
930 "QNN" => {
931 params.insert("n_layers".to_string(), 5.0);
932 params.insert("learning_rate".to_string(), 0.01);
933 params.insert("batch_size".to_string(), 32.0);
934 }
935 "Neural Network" => {
936 params.insert("hidden_layers".to_string(), 3.0);
937 params.insert("neurons_per_layer".to_string(), 128.0);
938 params.insert("learning_rate".to_string(), 0.001);
939 params.insert("dropout".to_string(), 0.2);
940 }
941 _ => {}
942 }
943
944 Ok(params)
945 }
946
947 fn generate_cost_analysis(
949 &self,
950 option: &AlgorithmOption,
951 chars: &ProblemCharacteristics,
952 ) -> Result<CostAnalysis> {
953 let training_cost = option.expected_cost;
954 let inference_cost_per_sample = option.expected_cost / chars.n_samples as f64;
955
956 let mut breakdown = HashMap::new();
957 breakdown.insert("training".to_string(), training_cost);
958 breakdown.insert("inference".to_string(), inference_cost_per_sample * 1000.0);
959
960 Ok(CostAnalysis {
961 training_cost,
962 inference_cost_per_sample,
963 total_cost: training_cost + inference_cost_per_sample * 10000.0,
964 breakdown,
965 })
966 }
967
968 fn recommend_calibration(
970 &self,
971 option: &AlgorithmOption,
972 _chars: &ProblemCharacteristics,
973 ) -> Result<Option<String>> {
974 if option.algorithm_type == AlgorithmType::Quantum {
975 Ok(Some("Bayesian Binning into Quantiles (BBQ)".to_string()))
976 } else {
977 Ok(Some("Platt Scaling".to_string()))
978 }
979 }
980
981 fn generate_production_config(
983 &self,
984 option: &AlgorithmOption,
985 chars: &ProblemCharacteristics,
986 constraints: &ResourceConstraints,
987 ) -> Result<ProductionConfig> {
988 let batch_size = if option.algorithm_type == AlgorithmType::Quantum {
989 16
990 } else if constraints.classical_compute.has_gpu {
991 128
992 } else {
993 32
994 };
995
996 let n_workers = if option.algorithm_type == AlgorithmType::Quantum {
997 1
998 } else {
999 constraints.classical_compute.n_cpu_cores.min(8)
1000 };
1001
1002 let mut alert_thresholds = HashMap::new();
1003 alert_thresholds.insert(
1004 "latency_ms".to_string(),
1005 option.expected_inference_latency_ms * 2.0,
1006 );
1007 alert_thresholds.insert("accuracy".to_string(), option.expected_accuracy - 0.05);
1008 alert_thresholds.insert("error_rate".to_string(), 0.01);
1009
1010 Ok(ProductionConfig {
1011 batch_size,
1012 n_workers,
1013 enable_caching: true,
1014 monitoring: MonitoringConfig {
1015 log_interval: 100,
1016 alert_thresholds,
1017 tracked_metrics: vec![
1018 "accuracy".to_string(),
1019 "latency".to_string(),
1020 "throughput".to_string(),
1021 "error_rate".to_string(),
1022 ],
1023 },
1024 scaling: ScalingConfig {
1025 auto_scaling: true,
1026 min_instances: 1,
1027 max_instances: 10,
1028 scale_up_threshold: 70.0,
1029 scale_down_threshold: 30.0,
1030 },
1031 })
1032 }
1033
1034 fn initialize_performance_models() -> HashMap<String, PerformanceModel> {
1036 let mut models = HashMap::new();
1037
1038 models.insert(
1039 "QSVM".to_string(),
1040 PerformanceModel {
1041 model_type: "linear".to_string(),
1042 coefficients: vec![0.9, -0.05, 0.03],
1043 },
1044 );
1045
1046 models.insert(
1047 "QNN".to_string(),
1048 PerformanceModel {
1049 model_type: "linear".to_string(),
1050 coefficients: vec![0.87, -0.04, 0.05],
1051 },
1052 );
1053
1054 models
1055 }
1056
1057 fn initialize_cost_models() -> HashMap<String, CostModel> {
1059 let mut models = HashMap::new();
1060
1061 models.insert(
1062 "QSVM".to_string(),
1063 CostModel {
1064 base_cost: 10.0,
1065 cost_per_sample: 0.01,
1066 cost_per_feature: 0.001,
1067 cost_per_qubit: 1.0,
1068 },
1069 );
1070
1071 models.insert(
1072 "QNN".to_string(),
1073 CostModel {
1074 base_cost: 20.0,
1075 cost_per_sample: 0.02,
1076 cost_per_feature: 0.002,
1077 cost_per_qubit: 2.0,
1078 },
1079 );
1080
1081 models
1082 }
1083}
1084
1085#[derive(Debug, Clone)]
1087struct AlgorithmOption {
1088 name: String,
1089 algorithm_type: AlgorithmType,
1090 is_feasible: bool,
1091 expected_accuracy: f64,
1092 expected_training_time_s: f64,
1093 expected_inference_latency_ms: f64,
1094 expected_cost: f64,
1095 required_qubits: Option<usize>,
1096 confidence: f64,
1097}
1098
1099#[derive(Debug, Clone, Copy, PartialEq)]
1101enum AlgorithmType {
1102 Quantum,
1103 Classical,
1104 Hybrid,
1105}
1106
1107impl Default for DecisionThresholds {
1108 fn default() -> Self {
1109 Self {
1110 min_quantum_speedup: 1.5,
1111 min_accuracy_improvement: 0.02,
1112 max_cost_ratio: 10.0,
1113 min_confidence: 0.70,
1114 }
1115 }
1116}
1117
1118impl ProblemCharacteristics {
1119 pub fn from_dataset(x: &Array2<f64>, y: &Array1<usize>) -> Self {
1121 let n_samples = x.shape()[0];
1122 let n_features = x.shape()[1];
1123 let n_classes = y.iter().max().map(|&m| m + 1).unwrap_or(2);
1124
1125 let dimensionality_ratio = n_features as f64 / n_samples as f64;
1126
1127 let total_elements = n_samples * n_features;
1129 let zero_elements = x.iter().filter(|&&val| val.abs() < 1e-10).count();
1130 let sparsity = zero_elements as f64 / total_elements as f64;
1131
1132 let condition_number = 100.0; let mut class_counts = vec![0; n_classes];
1137 for &label in y.iter() {
1138 if label < n_classes {
1139 class_counts[label] += 1;
1140 }
1141 }
1142 let max_count = class_counts.iter().max().copied().unwrap_or(1);
1143 let min_count = class_counts
1144 .iter()
1145 .filter(|&&c| c > 0)
1146 .min()
1147 .copied()
1148 .unwrap_or(1);
1149 let class_imbalance = max_count as f64 / min_count as f64;
1150
1151 Self {
1152 n_samples,
1153 n_features,
1154 n_classes,
1155 dimensionality_ratio,
1156 sparsity,
1157 condition_number,
1158 class_imbalance,
1159 task_type: if n_classes == 2 {
1160 TaskType::BinaryClassification
1161 } else {
1162 TaskType::MultiClassClassification
1163 },
1164 domain: ProblemDomain::General,
1165 }
1166 }
1167}
1168
1169#[cfg(test)]
1170mod tests {
1171 use super::*;
1172
1173 #[test]
1174 fn test_engine_creation() {
1175 let engine = HybridAutoMLEngine::new();
1176 assert!(!engine.performance_models.is_empty());
1177 }
1178
1179 #[test]
1180 fn test_problem_characteristics_extraction() {
1181 let x = Array2::from_shape_fn((100, 10), |(i, j)| (i + j) as f64);
1182 let y = Array1::from_shape_fn(100, |i| i % 2);
1183
1184 let chars = ProblemCharacteristics::from_dataset(&x, &y);
1185
1186 assert_eq!(chars.n_samples, 100);
1187 assert_eq!(chars.n_features, 10);
1188 assert_eq!(chars.n_classes, 2);
1189 assert_eq!(chars.task_type, TaskType::BinaryClassification);
1190 }
1191
1192 #[test]
1193 fn test_algorithm_recommendation() {
1194 let engine = HybridAutoMLEngine::new();
1195
1196 let chars = ProblemCharacteristics {
1197 n_samples: 1000,
1198 n_features: 20,
1199 n_classes: 2,
1200 dimensionality_ratio: 0.02,
1201 sparsity: 0.0,
1202 condition_number: 10.0,
1203 class_imbalance: 1.2,
1204 task_type: TaskType::BinaryClassification,
1205 domain: ProblemDomain::General,
1206 };
1207
1208 let constraints = ResourceConstraints {
1209 quantum_devices: vec![QuantumDevice {
1210 name: "ibm_quantum".to_string(),
1211 n_qubits: 20,
1212 gate_error_rate: 0.001,
1213 measurement_error_rate: 0.01,
1214 decoherence_time_us: 100.0,
1215 cost_per_shot: 0.0001,
1216 availability: DeviceAvailability::Available,
1217 }],
1218 classical_compute: ClassicalCompute {
1219 n_cpu_cores: 8,
1220 has_gpu: true,
1221 gpu_memory_gb: 16.0,
1222 ram_gb: 64.0,
1223 },
1224 max_latency_ms: Some(100.0),
1225 max_cost_per_inference: Some(1.0),
1226 max_training_time: Some(1000.0),
1227 max_power_consumption: None,
1228 };
1229
1230 let recommendation = engine
1231 .analyze_and_recommend(&chars, &constraints)
1232 .expect("Failed to analyze and recommend");
1233
1234 assert!(recommendation.confidence > 0.0);
1235 assert!(recommendation.expected_performance.accuracy > 0.0);
1236 }
1237}