quantrs2_ml/
hybrid_automl_engine.rs

1//! # Quantum-Classical Hybrid AutoML Decision Engine
2//!
3//! An advanced system that intelligently determines when quantum algorithms provide
4//! advantages over classical approaches and automatically configures optimal solutions.
5//!
6//! ## Key Features
7//!
8//! - **Problem Analysis**: Extracts characteristics from datasets and problem definitions
9//! - **Algorithm Selection**: Chooses optimal quantum or classical algorithms
10//! - **Performance Prediction**: Estimates accuracy, latency, and resource usage
11//! - **Cost Optimization**: Balances quantum hardware costs with performance gains
12//! - **Calibration Integration**: Automatically applies probability calibration
13//! - **Production Configuration**: Generates deployment-ready configurations
14//!
15//! ## Example
16//!
17//! ```rust,ignore
18//! use quantrs2_ml::hybrid_automl_engine::{
19//!     HybridAutoMLEngine, ProblemCharacteristics, ResourceConstraints
20//! };
21//! use scirs2_core::ndarray::{Array1, Array2};
22//!
23//! // Example dataset
24//! let X = Array2::<f64>::zeros((100, 10)); // 100 samples, 10 features
25//! let y = Array1::<usize>::zeros(100);      // 100 labels (class indices)
26//!
27//! let engine = HybridAutoMLEngine::new();
28//! let problem = ProblemCharacteristics::from_dataset(&X, &y);
29//! let constraints = ResourceConstraints::default(); // Define resource constraints
30//! let recommendation = engine.analyze_and_recommend(&problem, &constraints)?;
31//!
32//! println!("Recommended: {:?}", recommendation.algorithm_choice);
33//! println!("Expected speedup: {:.2}x", recommendation.quantum_advantage.speedup);
34//! # Ok::<(), quantrs2_ml::error::MLError>(())
35//! ```
36
37use crate::error::{MLError, Result};
38use scirs2_core::ndarray::{Array1, Array2};
39use scirs2_core::random::{thread_rng, Rng};
40use std::collections::HashMap;
41
42/// Problem characteristics extracted from dataset and task definition
43#[derive(Debug, Clone)]
44pub struct ProblemCharacteristics {
45    /// Number of samples in the dataset
46    pub n_samples: usize,
47
48    /// Number of features per sample
49    pub n_features: usize,
50
51    /// Number of classes (for classification) or 1 (for regression)
52    pub n_classes: usize,
53
54    /// Dimensionality ratio (features/samples)
55    pub dimensionality_ratio: f64,
56
57    /// Data sparsity (fraction of zero elements)
58    pub sparsity: f64,
59
60    /// Feature correlation matrix condition number
61    pub condition_number: f64,
62
63    /// Class imbalance ratio (max_class_count / min_class_count)
64    pub class_imbalance: f64,
65
66    /// Task type
67    pub task_type: TaskType,
68
69    /// Problem domain
70    pub domain: ProblemDomain,
71}
72
73/// Type of machine learning task
74#[derive(Debug, Clone, Copy, PartialEq)]
75pub enum TaskType {
76    BinaryClassification,
77    MultiClassClassification,
78    Regression,
79    Clustering,
80    DimensionalityReduction,
81}
82
83/// Problem domain for specialized optimizations
84#[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/// Available computational resources
97#[derive(Debug, Clone)]
98pub struct ResourceConstraints {
99    /// Available quantum devices
100    pub quantum_devices: Vec<QuantumDevice>,
101
102    /// Available classical compute
103    pub classical_compute: ClassicalCompute,
104
105    /// Maximum latency requirement (milliseconds)
106    pub max_latency_ms: Option<f64>,
107
108    /// Maximum cost per inference (USD)
109    pub max_cost_per_inference: Option<f64>,
110
111    /// Maximum training time (seconds)
112    pub max_training_time: Option<f64>,
113
114    /// Power consumption limit (watts)
115    pub max_power_consumption: Option<f64>,
116}
117
118/// Quantum device specification
119#[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/// Device availability status
131#[derive(Debug, Clone, Copy, PartialEq)]
132pub enum DeviceAvailability {
133    Available,
134    Queued,
135    Unavailable,
136}
137
138/// Classical compute resources
139#[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/// Algorithm recommendation
148#[derive(Debug, Clone)]
149pub struct AlgorithmRecommendation {
150    /// Recommended algorithm choice
151    pub algorithm_choice: AlgorithmChoice,
152
153    /// Predicted quantum advantage
154    pub quantum_advantage: QuantumAdvantageMetrics,
155
156    /// Recommended hyperparameters
157    pub hyperparameters: HashMap<String, f64>,
158
159    /// Expected performance metrics
160    pub expected_performance: PerformanceEstimate,
161
162    /// Cost analysis
163    pub cost_analysis: CostAnalysis,
164
165    /// Confidence in recommendation (0-1)
166    pub confidence: f64,
167
168    /// Calibration recommendation
169    pub calibration_method: Option<String>,
170
171    /// Production configuration
172    pub production_config: ProductionConfig,
173}
174
175/// Algorithm choice (quantum, classical, or hybrid)
176#[derive(Debug, Clone, PartialEq)]
177pub enum AlgorithmChoice {
178    /// Use quantum algorithm exclusively
179    QuantumOnly { algorithm: String, device: String },
180
181    /// Use classical algorithm exclusively
182    ClassicalOnly { algorithm: String, backend: String },
183
184    /// Use hybrid quantum-classical approach
185    Hybrid {
186        quantum_component: String,
187        classical_component: String,
188        splitting_strategy: String,
189    },
190}
191
192/// Quantum advantage metrics
193#[derive(Debug, Clone)]
194pub struct QuantumAdvantageMetrics {
195    /// Expected speedup factor
196    pub speedup: f64,
197
198    /// Accuracy improvement (percentage points)
199    pub accuracy_improvement: f64,
200
201    /// Sample efficiency improvement
202    pub sample_efficiency: f64,
203
204    /// Generalization improvement
205    pub generalization_improvement: f64,
206
207    /// Statistical significance (p-value)
208    pub statistical_significance: f64,
209}
210
211/// Performance estimate
212#[derive(Debug, Clone)]
213pub struct PerformanceEstimate {
214    /// Expected accuracy (0-1)
215    pub accuracy: f64,
216
217    /// Accuracy confidence interval (95%)
218    pub accuracy_ci: (f64, f64),
219
220    /// Expected training time (seconds)
221    pub training_time_s: f64,
222
223    /// Expected inference latency (milliseconds)
224    pub inference_latency_ms: f64,
225
226    /// Memory footprint (MB)
227    pub memory_mb: f64,
228}
229
230/// Cost analysis
231#[derive(Debug, Clone)]
232pub struct CostAnalysis {
233    /// Training cost (USD)
234    pub training_cost: f64,
235
236    /// Inference cost per sample (USD)
237    pub inference_cost_per_sample: f64,
238
239    /// Total cost for expected workload (USD)
240    pub total_cost: f64,
241
242    /// Cost breakdown
243    pub breakdown: HashMap<String, f64>,
244}
245
246/// Production deployment configuration
247#[derive(Debug, Clone)]
248pub struct ProductionConfig {
249    /// Recommended batch size
250    pub batch_size: usize,
251
252    /// Number of parallel workers
253    pub n_workers: usize,
254
255    /// Enable caching
256    pub enable_caching: bool,
257
258    /// Monitoring configuration
259    pub monitoring: MonitoringConfig,
260
261    /// Scaling configuration
262    pub scaling: ScalingConfig,
263}
264
265/// Monitoring configuration
266#[derive(Debug, Clone)]
267pub struct MonitoringConfig {
268    /// Log every N inferences
269    pub log_interval: usize,
270
271    /// Alert thresholds
272    pub alert_thresholds: HashMap<String, f64>,
273
274    /// Metrics to track
275    pub tracked_metrics: Vec<String>,
276}
277
278/// Scaling configuration
279#[derive(Debug, Clone)]
280pub struct ScalingConfig {
281    /// Auto-scaling enabled
282    pub auto_scaling: bool,
283
284    /// Minimum instances
285    pub min_instances: usize,
286
287    /// Maximum instances
288    pub max_instances: usize,
289
290    /// Scale up threshold (CPU %)
291    pub scale_up_threshold: f64,
292
293    /// Scale down threshold (CPU %)
294    pub scale_down_threshold: f64,
295}
296
297/// Quantum-Classical Hybrid AutoML Decision Engine
298pub struct HybridAutoMLEngine {
299    /// Performance prediction models
300    performance_models: HashMap<String, PerformanceModel>,
301
302    /// Cost models
303    cost_models: HashMap<String, CostModel>,
304
305    /// Decision thresholds
306    decision_thresholds: DecisionThresholds,
307}
308
309/// Performance prediction model
310struct PerformanceModel {
311    /// Model type
312    model_type: String,
313
314    /// Coefficients for prediction
315    coefficients: Vec<f64>,
316}
317
318/// Cost prediction model
319struct CostModel {
320    /// Base cost
321    base_cost: f64,
322
323    /// Cost per sample
324    cost_per_sample: f64,
325
326    /// Cost per feature
327    cost_per_feature: f64,
328
329    /// Cost per qubit
330    cost_per_qubit: f64,
331}
332
333/// Decision thresholds for algorithm selection
334struct DecisionThresholds {
335    /// Minimum speedup to justify quantum (default: 1.5x)
336    min_quantum_speedup: f64,
337
338    /// Minimum accuracy improvement (percentage points)
339    min_accuracy_improvement: f64,
340
341    /// Maximum acceptable cost ratio
342    max_cost_ratio: f64,
343
344    /// Minimum confidence for quantum recommendation
345    min_confidence: f64,
346}
347
348impl HybridAutoMLEngine {
349    /// Create a new Hybrid AutoML Engine with default models
350    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    /// Analyze problem and recommend optimal approach
359    pub fn analyze_and_recommend(
360        &self,
361        characteristics: &ProblemCharacteristics,
362        constraints: &ResourceConstraints,
363    ) -> Result<AlgorithmRecommendation> {
364        // Extract features for decision making
365        let features = self.extract_decision_features(characteristics);
366
367        // Evaluate quantum algorithms
368        let quantum_options = self.evaluate_quantum_algorithms(characteristics, constraints)?;
369
370        // Evaluate classical algorithms
371        let classical_options = self.evaluate_classical_algorithms(characteristics, constraints)?;
372
373        // Compare and select best option
374        let best_option = self.select_best_option(
375            &quantum_options,
376            &classical_options,
377            characteristics,
378            constraints,
379        )?;
380
381        // Generate comprehensive recommendation
382        let recommendation =
383            self.generate_recommendation(best_option, characteristics, constraints)?;
384
385        Ok(recommendation)
386    }
387
388    /// Extract features for decision making
389    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(), // Log features for quantum circuit depth
399        ]
400    }
401
402    /// Evaluate quantum algorithm options
403    fn evaluate_quantum_algorithms(
404        &self,
405        chars: &ProblemCharacteristics,
406        constraints: &ResourceConstraints,
407    ) -> Result<Vec<AlgorithmOption>> {
408        let mut options = Vec::new();
409
410        // Check if quantum devices are available
411        if constraints.quantum_devices.is_empty() {
412            return Ok(options);
413        }
414
415        // Quantum SVM (QSVM)
416        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        // Quantum Neural Network (QNN)
427        let qnn_option = self.evaluate_qnn(chars, constraints)?;
428        if qnn_option.is_feasible {
429            options.push(qnn_option);
430        }
431
432        // Variational Quantum Eigensolver (VQE) for specific problems
433        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        // Quantum Approximate Optimization Algorithm (QAOA)
441        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    /// Evaluate classical algorithm options
452    fn evaluate_classical_algorithms(
453        &self,
454        chars: &ProblemCharacteristics,
455        constraints: &ResourceConstraints,
456    ) -> Result<Vec<AlgorithmOption>> {
457        let mut options = Vec::new();
458
459        // Classical SVM
460        if matches!(
461            chars.task_type,
462            TaskType::BinaryClassification | TaskType::MultiClassClassification
463        ) {
464            options.push(self.evaluate_classical_svm(chars, constraints)?);
465        }
466
467        // Neural Network
468        options.push(self.evaluate_classical_nn(chars, constraints)?);
469
470        // Random Forest
471        options.push(self.evaluate_random_forest(chars, constraints)?);
472
473        // Gradient Boosting
474        options.push(self.evaluate_gradient_boosting(chars, constraints)?);
475
476        Ok(options)
477    }
478
479    /// Evaluate QSVM option
480    fn evaluate_qsvm(
481        &self,
482        chars: &ProblemCharacteristics,
483        constraints: &ResourceConstraints,
484    ) -> Result<AlgorithmOption> {
485        let device = &constraints.quantum_devices[0];
486
487        // Check if problem fits on device
488        let required_qubits = (chars.n_features as f64).log2().ceil() as usize;
489        let is_feasible = required_qubits <= device.n_qubits;
490
491        // Estimate performance
492        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, // Kernel evaluation time
503            expected_cost: cost,
504            required_qubits: Some(required_qubits),
505            confidence: 0.85,
506        })
507    }
508
509    /// Evaluate QNN option
510    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); // Limit to 20 qubits
518        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    /// Evaluate VQE option
538    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    /// Evaluate QAOA option
562    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    /// Evaluate classical SVM
586    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    /// Evaluate classical neural network
608    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    /// Evaluate random forest
636    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    /// Evaluate gradient boosting
658    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    /// Estimate QSVM accuracy
680    fn estimate_qsvm_accuracy(
681        &self,
682        chars: &ProblemCharacteristics,
683        device: &QuantumDevice,
684    ) -> Result<f64> {
685        // Base accuracy for QSVM
686        let base_accuracy = 0.90;
687
688        // Adjust for noise
689        let noise_penalty = device.gate_error_rate * 50.0;
690
691        // Adjust for dimensionality
692        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    /// Estimate QSVM training time
700    fn estimate_qsvm_training_time(
701        &self,
702        chars: &ProblemCharacteristics,
703        _device: &QuantumDevice,
704    ) -> Result<f64> {
705        // Kernel matrix computation is O(n^2)
706        let time = (chars.n_samples * chars.n_samples) as f64 / 100.0;
707        Ok(time)
708    }
709
710    /// Estimate QSVM cost
711    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    /// Estimate QNN accuracy
723    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    /// Estimate QNN training time
738    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    /// Estimate QNN cost
749    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    /// Select best option from quantum and classical algorithms
761    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        // Filter by constraints
772        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        // Score each option
798        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    /// Compute score for an algorithm option
811    fn compute_option_score(
812        &self,
813        option: &AlgorithmOption,
814        _chars: &ProblemCharacteristics,
815        _constraints: &ResourceConstraints,
816    ) -> f64 {
817        // Multi-objective scoring
818        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        // Weighted combination
824        accuracy_score * 0.4 + speed_score * 0.2 + cost_score * 0.2 + confidence_score * 0.2
825    }
826
827    /// Generate comprehensive recommendation
828    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        // Compute quantum advantage metrics
859        let quantum_advantage = self.compute_quantum_advantage(&best_option, chars)?;
860
861        // Generate hyperparameters
862        let hyperparameters = self.generate_hyperparameters(&best_option, chars)?;
863
864        // Estimate performance
865        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        // Cost analysis
877        let cost_analysis = self.generate_cost_analysis(&best_option, chars)?;
878
879        // Calibration recommendation
880        let calibration_method = self.recommend_calibration(&best_option, chars)?;
881
882        // Production configuration
883        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    /// Compute quantum advantage metrics
899    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    /// Generate recommended hyperparameters
916    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    /// Generate cost analysis
946    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    /// Recommend calibration method
967    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    /// Generate production configuration
980    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    /// Initialize performance prediction models
1033    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    /// Initialize cost prediction models
1056    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/// Algorithm option being evaluated
1084#[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/// Type of algorithm
1098#[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    /// Extract problem characteristics from dataset
1118    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        // Compute sparsity
1126        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        // Estimate condition number (simplified)
1131        let condition_number = 100.0; // Placeholder
1132
1133        // Compute class imbalance
1134        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}