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
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    /// Compute score for an algorithm option
813    fn compute_option_score(
814        &self,
815        option: &AlgorithmOption,
816        _chars: &ProblemCharacteristics,
817        _constraints: &ResourceConstraints,
818    ) -> f64 {
819        // Multi-objective scoring
820        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        // Weighted combination
826        accuracy_score * 0.4 + speed_score * 0.2 + cost_score * 0.2 + confidence_score * 0.2
827    }
828
829    /// Generate comprehensive recommendation
830    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        // Compute quantum advantage metrics
861        let quantum_advantage = self.compute_quantum_advantage(&best_option, chars)?;
862
863        // Generate hyperparameters
864        let hyperparameters = self.generate_hyperparameters(&best_option, chars)?;
865
866        // Estimate performance
867        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        // Cost analysis
879        let cost_analysis = self.generate_cost_analysis(&best_option, chars)?;
880
881        // Calibration recommendation
882        let calibration_method = self.recommend_calibration(&best_option, chars)?;
883
884        // Production configuration
885        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    /// Compute quantum advantage metrics
901    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    /// Generate recommended hyperparameters
918    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    /// Generate cost analysis
948    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    /// Recommend calibration method
969    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    /// Generate production configuration
982    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    /// Initialize performance prediction models
1035    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    /// Initialize cost prediction models
1058    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/// Algorithm option being evaluated
1086#[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/// Type of algorithm
1100#[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    /// Extract problem characteristics from dataset
1120    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        // Compute sparsity
1128        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        // Estimate condition number (simplified)
1133        let condition_number = 100.0; // Placeholder
1134
1135        // Compute class imbalance
1136        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}