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.partial_cmp(&score_b).unwrap()
804 })
805 .unwrap();
806
807 Ok(best)
808 }
809
810 fn compute_option_score(
812 &self,
813 option: &AlgorithmOption,
814 _chars: &ProblemCharacteristics,
815 _constraints: &ResourceConstraints,
816 ) -> f64 {
817 let accuracy_score = option.expected_accuracy;
819 let speed_score = 1.0 / (1.0 + option.expected_training_time_s / 100.0);
820 let cost_score = 1.0 / (1.0 + option.expected_cost);
821 let confidence_score = option.confidence;
822
823 accuracy_score * 0.4 + speed_score * 0.2 + cost_score * 0.2 + confidence_score * 0.2
825 }
826
827 fn generate_recommendation(
829 &self,
830 best_option: AlgorithmOption,
831 chars: &ProblemCharacteristics,
832 constraints: &ResourceConstraints,
833 ) -> Result<AlgorithmRecommendation> {
834 let algorithm_choice = match best_option.algorithm_type {
835 AlgorithmType::Quantum => AlgorithmChoice::QuantumOnly {
836 algorithm: best_option.name.clone(),
837 device: constraints
838 .quantum_devices
839 .get(0)
840 .map(|d| d.name.clone())
841 .unwrap_or_else(|| "simulator".to_string()),
842 },
843 AlgorithmType::Classical => AlgorithmChoice::ClassicalOnly {
844 algorithm: best_option.name.clone(),
845 backend: if constraints.classical_compute.has_gpu {
846 "GPU".to_string()
847 } else {
848 "CPU".to_string()
849 },
850 },
851 AlgorithmType::Hybrid => AlgorithmChoice::Hybrid {
852 quantum_component: "QNN".to_string(),
853 classical_component: "Neural Network".to_string(),
854 splitting_strategy: "Feature Engineering".to_string(),
855 },
856 };
857
858 let quantum_advantage = self.compute_quantum_advantage(&best_option, chars)?;
860
861 let hyperparameters = self.generate_hyperparameters(&best_option, chars)?;
863
864 let expected_performance = PerformanceEstimate {
866 accuracy: best_option.expected_accuracy,
867 accuracy_ci: (
868 best_option.expected_accuracy - 0.05,
869 best_option.expected_accuracy + 0.05,
870 ),
871 training_time_s: best_option.expected_training_time_s,
872 inference_latency_ms: best_option.expected_inference_latency_ms,
873 memory_mb: chars.n_samples as f64 * chars.n_features as f64 * 8.0 / 1024.0 / 1024.0,
874 };
875
876 let cost_analysis = self.generate_cost_analysis(&best_option, chars)?;
878
879 let calibration_method = self.recommend_calibration(&best_option, chars)?;
881
882 let production_config =
884 self.generate_production_config(&best_option, chars, constraints)?;
885
886 Ok(AlgorithmRecommendation {
887 algorithm_choice,
888 quantum_advantage,
889 hyperparameters,
890 expected_performance,
891 cost_analysis,
892 confidence: best_option.confidence,
893 calibration_method,
894 production_config,
895 })
896 }
897
898 fn compute_quantum_advantage(
900 &self,
901 option: &AlgorithmOption,
902 _chars: &ProblemCharacteristics,
903 ) -> Result<QuantumAdvantageMetrics> {
904 let is_quantum = option.algorithm_type == AlgorithmType::Quantum;
905
906 Ok(QuantumAdvantageMetrics {
907 speedup: if is_quantum { 2.5 } else { 1.0 },
908 accuracy_improvement: if is_quantum { 0.05 } else { 0.0 },
909 sample_efficiency: if is_quantum { 1.8 } else { 1.0 },
910 generalization_improvement: if is_quantum { 0.03 } else { 0.0 },
911 statistical_significance: if is_quantum { 0.01 } else { 1.0 },
912 })
913 }
914
915 fn generate_hyperparameters(
917 &self,
918 option: &AlgorithmOption,
919 chars: &ProblemCharacteristics,
920 ) -> Result<HashMap<String, f64>> {
921 let mut params = HashMap::new();
922
923 match option.name.as_str() {
924 "QSVM" => {
925 params.insert("n_shots".to_string(), 1000.0);
926 params.insert("kernel_depth".to_string(), 3.0);
927 }
928 "QNN" => {
929 params.insert("n_layers".to_string(), 5.0);
930 params.insert("learning_rate".to_string(), 0.01);
931 params.insert("batch_size".to_string(), 32.0);
932 }
933 "Neural Network" => {
934 params.insert("hidden_layers".to_string(), 3.0);
935 params.insert("neurons_per_layer".to_string(), 128.0);
936 params.insert("learning_rate".to_string(), 0.001);
937 params.insert("dropout".to_string(), 0.2);
938 }
939 _ => {}
940 }
941
942 Ok(params)
943 }
944
945 fn generate_cost_analysis(
947 &self,
948 option: &AlgorithmOption,
949 chars: &ProblemCharacteristics,
950 ) -> Result<CostAnalysis> {
951 let training_cost = option.expected_cost;
952 let inference_cost_per_sample = option.expected_cost / chars.n_samples as f64;
953
954 let mut breakdown = HashMap::new();
955 breakdown.insert("training".to_string(), training_cost);
956 breakdown.insert("inference".to_string(), inference_cost_per_sample * 1000.0);
957
958 Ok(CostAnalysis {
959 training_cost,
960 inference_cost_per_sample,
961 total_cost: training_cost + inference_cost_per_sample * 10000.0,
962 breakdown,
963 })
964 }
965
966 fn recommend_calibration(
968 &self,
969 option: &AlgorithmOption,
970 _chars: &ProblemCharacteristics,
971 ) -> Result<Option<String>> {
972 if option.algorithm_type == AlgorithmType::Quantum {
973 Ok(Some("Bayesian Binning into Quantiles (BBQ)".to_string()))
974 } else {
975 Ok(Some("Platt Scaling".to_string()))
976 }
977 }
978
979 fn generate_production_config(
981 &self,
982 option: &AlgorithmOption,
983 chars: &ProblemCharacteristics,
984 constraints: &ResourceConstraints,
985 ) -> Result<ProductionConfig> {
986 let batch_size = if option.algorithm_type == AlgorithmType::Quantum {
987 16
988 } else if constraints.classical_compute.has_gpu {
989 128
990 } else {
991 32
992 };
993
994 let n_workers = if option.algorithm_type == AlgorithmType::Quantum {
995 1
996 } else {
997 constraints.classical_compute.n_cpu_cores.min(8)
998 };
999
1000 let mut alert_thresholds = HashMap::new();
1001 alert_thresholds.insert(
1002 "latency_ms".to_string(),
1003 option.expected_inference_latency_ms * 2.0,
1004 );
1005 alert_thresholds.insert("accuracy".to_string(), option.expected_accuracy - 0.05);
1006 alert_thresholds.insert("error_rate".to_string(), 0.01);
1007
1008 Ok(ProductionConfig {
1009 batch_size,
1010 n_workers,
1011 enable_caching: true,
1012 monitoring: MonitoringConfig {
1013 log_interval: 100,
1014 alert_thresholds,
1015 tracked_metrics: vec![
1016 "accuracy".to_string(),
1017 "latency".to_string(),
1018 "throughput".to_string(),
1019 "error_rate".to_string(),
1020 ],
1021 },
1022 scaling: ScalingConfig {
1023 auto_scaling: true,
1024 min_instances: 1,
1025 max_instances: 10,
1026 scale_up_threshold: 70.0,
1027 scale_down_threshold: 30.0,
1028 },
1029 })
1030 }
1031
1032 fn initialize_performance_models() -> HashMap<String, PerformanceModel> {
1034 let mut models = HashMap::new();
1035
1036 models.insert(
1037 "QSVM".to_string(),
1038 PerformanceModel {
1039 model_type: "linear".to_string(),
1040 coefficients: vec![0.9, -0.05, 0.03],
1041 },
1042 );
1043
1044 models.insert(
1045 "QNN".to_string(),
1046 PerformanceModel {
1047 model_type: "linear".to_string(),
1048 coefficients: vec![0.87, -0.04, 0.05],
1049 },
1050 );
1051
1052 models
1053 }
1054
1055 fn initialize_cost_models() -> HashMap<String, CostModel> {
1057 let mut models = HashMap::new();
1058
1059 models.insert(
1060 "QSVM".to_string(),
1061 CostModel {
1062 base_cost: 10.0,
1063 cost_per_sample: 0.01,
1064 cost_per_feature: 0.001,
1065 cost_per_qubit: 1.0,
1066 },
1067 );
1068
1069 models.insert(
1070 "QNN".to_string(),
1071 CostModel {
1072 base_cost: 20.0,
1073 cost_per_sample: 0.02,
1074 cost_per_feature: 0.002,
1075 cost_per_qubit: 2.0,
1076 },
1077 );
1078
1079 models
1080 }
1081}
1082
1083#[derive(Debug, Clone)]
1085struct AlgorithmOption {
1086 name: String,
1087 algorithm_type: AlgorithmType,
1088 is_feasible: bool,
1089 expected_accuracy: f64,
1090 expected_training_time_s: f64,
1091 expected_inference_latency_ms: f64,
1092 expected_cost: f64,
1093 required_qubits: Option<usize>,
1094 confidence: f64,
1095}
1096
1097#[derive(Debug, Clone, Copy, PartialEq)]
1099enum AlgorithmType {
1100 Quantum,
1101 Classical,
1102 Hybrid,
1103}
1104
1105impl Default for DecisionThresholds {
1106 fn default() -> Self {
1107 Self {
1108 min_quantum_speedup: 1.5,
1109 min_accuracy_improvement: 0.02,
1110 max_cost_ratio: 10.0,
1111 min_confidence: 0.70,
1112 }
1113 }
1114}
1115
1116impl ProblemCharacteristics {
1117 pub fn from_dataset(x: &Array2<f64>, y: &Array1<usize>) -> Self {
1119 let n_samples = x.shape()[0];
1120 let n_features = x.shape()[1];
1121 let n_classes = y.iter().max().map(|&m| m + 1).unwrap_or(2);
1122
1123 let dimensionality_ratio = n_features as f64 / n_samples as f64;
1124
1125 let total_elements = n_samples * n_features;
1127 let zero_elements = x.iter().filter(|&&val| val.abs() < 1e-10).count();
1128 let sparsity = zero_elements as f64 / total_elements as f64;
1129
1130 let condition_number = 100.0; let mut class_counts = vec![0; n_classes];
1135 for &label in y.iter() {
1136 if label < n_classes {
1137 class_counts[label] += 1;
1138 }
1139 }
1140 let max_count = class_counts.iter().max().copied().unwrap_or(1);
1141 let min_count = class_counts
1142 .iter()
1143 .filter(|&&c| c > 0)
1144 .min()
1145 .copied()
1146 .unwrap_or(1);
1147 let class_imbalance = max_count as f64 / min_count as f64;
1148
1149 Self {
1150 n_samples,
1151 n_features,
1152 n_classes,
1153 dimensionality_ratio,
1154 sparsity,
1155 condition_number,
1156 class_imbalance,
1157 task_type: if n_classes == 2 {
1158 TaskType::BinaryClassification
1159 } else {
1160 TaskType::MultiClassClassification
1161 },
1162 domain: ProblemDomain::General,
1163 }
1164 }
1165}
1166
1167#[cfg(test)]
1168mod tests {
1169 use super::*;
1170
1171 #[test]
1172 fn test_engine_creation() {
1173 let engine = HybridAutoMLEngine::new();
1174 assert!(!engine.performance_models.is_empty());
1175 }
1176
1177 #[test]
1178 fn test_problem_characteristics_extraction() {
1179 let x = Array2::from_shape_fn((100, 10), |(i, j)| (i + j) as f64);
1180 let y = Array1::from_shape_fn(100, |i| i % 2);
1181
1182 let chars = ProblemCharacteristics::from_dataset(&x, &y);
1183
1184 assert_eq!(chars.n_samples, 100);
1185 assert_eq!(chars.n_features, 10);
1186 assert_eq!(chars.n_classes, 2);
1187 assert_eq!(chars.task_type, TaskType::BinaryClassification);
1188 }
1189
1190 #[test]
1191 fn test_algorithm_recommendation() {
1192 let engine = HybridAutoMLEngine::new();
1193
1194 let chars = ProblemCharacteristics {
1195 n_samples: 1000,
1196 n_features: 20,
1197 n_classes: 2,
1198 dimensionality_ratio: 0.02,
1199 sparsity: 0.0,
1200 condition_number: 10.0,
1201 class_imbalance: 1.2,
1202 task_type: TaskType::BinaryClassification,
1203 domain: ProblemDomain::General,
1204 };
1205
1206 let constraints = ResourceConstraints {
1207 quantum_devices: vec![QuantumDevice {
1208 name: "ibm_quantum".to_string(),
1209 n_qubits: 20,
1210 gate_error_rate: 0.001,
1211 measurement_error_rate: 0.01,
1212 decoherence_time_us: 100.0,
1213 cost_per_shot: 0.0001,
1214 availability: DeviceAvailability::Available,
1215 }],
1216 classical_compute: ClassicalCompute {
1217 n_cpu_cores: 8,
1218 has_gpu: true,
1219 gpu_memory_gb: 16.0,
1220 ram_gb: 64.0,
1221 },
1222 max_latency_ms: Some(100.0),
1223 max_cost_per_inference: Some(1.0),
1224 max_training_time: Some(1000.0),
1225 max_power_consumption: None,
1226 };
1227
1228 let recommendation = engine.analyze_and_recommend(&chars, &constraints).unwrap();
1229
1230 assert!(recommendation.confidence > 0.0);
1231 assert!(recommendation.expected_performance.accuracy > 0.0);
1232 }
1233}