use crate::error::{MLError, Result};
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::random::{thread_rng, Rng};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ProblemCharacteristics {
pub n_samples: usize,
pub n_features: usize,
pub n_classes: usize,
pub dimensionality_ratio: f64,
pub sparsity: f64,
pub condition_number: f64,
pub class_imbalance: f64,
pub task_type: TaskType,
pub domain: ProblemDomain,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TaskType {
BinaryClassification,
MultiClassClassification,
Regression,
Clustering,
DimensionalityReduction,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ProblemDomain {
General,
DrugDiscovery,
Finance,
ComputerVision,
NaturalLanguage,
TimeSeriesForecasting,
AnomalyDetection,
RecommenderSystem,
}
#[derive(Debug, Clone)]
pub struct ResourceConstraints {
pub quantum_devices: Vec<QuantumDevice>,
pub classical_compute: ClassicalCompute,
pub max_latency_ms: Option<f64>,
pub max_cost_per_inference: Option<f64>,
pub max_training_time: Option<f64>,
pub max_power_consumption: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct QuantumDevice {
pub name: String,
pub n_qubits: usize,
pub gate_error_rate: f64,
pub measurement_error_rate: f64,
pub decoherence_time_us: f64,
pub cost_per_shot: f64,
pub availability: DeviceAvailability,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DeviceAvailability {
Available,
Queued,
Unavailable,
}
#[derive(Debug, Clone)]
pub struct ClassicalCompute {
pub n_cpu_cores: usize,
pub has_gpu: bool,
pub gpu_memory_gb: f64,
pub ram_gb: f64,
}
#[derive(Debug, Clone)]
pub struct AlgorithmRecommendation {
pub algorithm_choice: AlgorithmChoice,
pub quantum_advantage: QuantumAdvantageMetrics,
pub hyperparameters: HashMap<String, f64>,
pub expected_performance: PerformanceEstimate,
pub cost_analysis: CostAnalysis,
pub confidence: f64,
pub calibration_method: Option<String>,
pub production_config: ProductionConfig,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AlgorithmChoice {
QuantumOnly { algorithm: String, device: String },
ClassicalOnly { algorithm: String, backend: String },
Hybrid {
quantum_component: String,
classical_component: String,
splitting_strategy: String,
},
}
#[derive(Debug, Clone)]
pub struct QuantumAdvantageMetrics {
pub speedup: f64,
pub accuracy_improvement: f64,
pub sample_efficiency: f64,
pub generalization_improvement: f64,
pub statistical_significance: f64,
}
#[derive(Debug, Clone)]
pub struct PerformanceEstimate {
pub accuracy: f64,
pub accuracy_ci: (f64, f64),
pub training_time_s: f64,
pub inference_latency_ms: f64,
pub memory_mb: f64,
}
#[derive(Debug, Clone)]
pub struct CostAnalysis {
pub training_cost: f64,
pub inference_cost_per_sample: f64,
pub total_cost: f64,
pub breakdown: HashMap<String, f64>,
}
#[derive(Debug, Clone)]
pub struct ProductionConfig {
pub batch_size: usize,
pub n_workers: usize,
pub enable_caching: bool,
pub monitoring: MonitoringConfig,
pub scaling: ScalingConfig,
}
#[derive(Debug, Clone)]
pub struct MonitoringConfig {
pub log_interval: usize,
pub alert_thresholds: HashMap<String, f64>,
pub tracked_metrics: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ScalingConfig {
pub auto_scaling: bool,
pub min_instances: usize,
pub max_instances: usize,
pub scale_up_threshold: f64,
pub scale_down_threshold: f64,
}
pub struct HybridAutoMLEngine {
performance_models: HashMap<String, PerformanceModel>,
cost_models: HashMap<String, CostModel>,
decision_thresholds: DecisionThresholds,
}
struct PerformanceModel {
model_type: String,
coefficients: Vec<f64>,
}
struct CostModel {
base_cost: f64,
cost_per_sample: f64,
cost_per_feature: f64,
cost_per_qubit: f64,
}
struct DecisionThresholds {
min_quantum_speedup: f64,
min_accuracy_improvement: f64,
max_cost_ratio: f64,
min_confidence: f64,
}
impl HybridAutoMLEngine {
pub fn new() -> Self {
Self {
performance_models: Self::initialize_performance_models(),
cost_models: Self::initialize_cost_models(),
decision_thresholds: DecisionThresholds::default(),
}
}
pub fn analyze_and_recommend(
&self,
characteristics: &ProblemCharacteristics,
constraints: &ResourceConstraints,
) -> Result<AlgorithmRecommendation> {
let features = self.extract_decision_features(characteristics);
let quantum_options = self.evaluate_quantum_algorithms(characteristics, constraints)?;
let classical_options = self.evaluate_classical_algorithms(characteristics, constraints)?;
let best_option = self.select_best_option(
&quantum_options,
&classical_options,
characteristics,
constraints,
)?;
let recommendation =
self.generate_recommendation(best_option, characteristics, constraints)?;
Ok(recommendation)
}
fn extract_decision_features(&self, chars: &ProblemCharacteristics) -> Vec<f64> {
vec![
chars.n_samples as f64,
chars.n_features as f64,
chars.n_classes as f64,
chars.dimensionality_ratio,
chars.sparsity,
chars.condition_number,
chars.class_imbalance,
(chars.n_features as f64).log2(), ]
}
fn evaluate_quantum_algorithms(
&self,
chars: &ProblemCharacteristics,
constraints: &ResourceConstraints,
) -> Result<Vec<AlgorithmOption>> {
let mut options = Vec::new();
if constraints.quantum_devices.is_empty() {
return Ok(options);
}
if matches!(
chars.task_type,
TaskType::BinaryClassification | TaskType::MultiClassClassification
) {
let qsvm_option = self.evaluate_qsvm(chars, constraints)?;
if qsvm_option.is_feasible {
options.push(qsvm_option);
}
}
let qnn_option = self.evaluate_qnn(chars, constraints)?;
if qnn_option.is_feasible {
options.push(qnn_option);
}
if chars.domain == ProblemDomain::DrugDiscovery {
let vqe_option = self.evaluate_vqe(chars, constraints)?;
if vqe_option.is_feasible {
options.push(vqe_option);
}
}
if matches!(chars.task_type, TaskType::Clustering) {
let qaoa_option = self.evaluate_qaoa(chars, constraints)?;
if qaoa_option.is_feasible {
options.push(qaoa_option);
}
}
Ok(options)
}
fn evaluate_classical_algorithms(
&self,
chars: &ProblemCharacteristics,
constraints: &ResourceConstraints,
) -> Result<Vec<AlgorithmOption>> {
let mut options = Vec::new();
if matches!(
chars.task_type,
TaskType::BinaryClassification | TaskType::MultiClassClassification
) {
options.push(self.evaluate_classical_svm(chars, constraints)?);
}
options.push(self.evaluate_classical_nn(chars, constraints)?);
options.push(self.evaluate_random_forest(chars, constraints)?);
options.push(self.evaluate_gradient_boosting(chars, constraints)?);
Ok(options)
}
fn evaluate_qsvm(
&self,
chars: &ProblemCharacteristics,
constraints: &ResourceConstraints,
) -> Result<AlgorithmOption> {
let device = &constraints.quantum_devices[0];
let required_qubits = (chars.n_features as f64).log2().ceil() as usize;
let is_feasible = required_qubits <= device.n_qubits;
let accuracy = self.estimate_qsvm_accuracy(chars, device)?;
let training_time = self.estimate_qsvm_training_time(chars, device)?;
let cost = self.estimate_qsvm_cost(chars, device)?;
Ok(AlgorithmOption {
name: "QSVM".to_string(),
algorithm_type: AlgorithmType::Quantum,
is_feasible,
expected_accuracy: accuracy,
expected_training_time_s: training_time,
expected_inference_latency_ms: 10.0, expected_cost: cost,
required_qubits: Some(required_qubits),
confidence: 0.85,
})
}
fn evaluate_qnn(
&self,
chars: &ProblemCharacteristics,
constraints: &ResourceConstraints,
) -> Result<AlgorithmOption> {
let device = &constraints.quantum_devices[0];
let required_qubits = chars.n_features.min(20); let is_feasible = required_qubits <= device.n_qubits;
let accuracy = self.estimate_qnn_accuracy(chars, device)?;
let training_time = self.estimate_qnn_training_time(chars, device)?;
let cost = self.estimate_qnn_cost(chars, device)?;
Ok(AlgorithmOption {
name: "QNN".to_string(),
algorithm_type: AlgorithmType::Quantum,
is_feasible,
expected_accuracy: accuracy,
expected_training_time_s: training_time,
expected_inference_latency_ms: 5.0,
expected_cost: cost,
required_qubits: Some(required_qubits),
confidence: 0.80,
})
}
fn evaluate_vqe(
&self,
chars: &ProblemCharacteristics,
constraints: &ResourceConstraints,
) -> Result<AlgorithmOption> {
let device = &constraints.quantum_devices[0];
let required_qubits = 10.min(device.n_qubits);
let is_feasible = true;
Ok(AlgorithmOption {
name: "VQE".to_string(),
algorithm_type: AlgorithmType::Quantum,
is_feasible,
expected_accuracy: 0.92,
expected_training_time_s: 300.0,
expected_inference_latency_ms: 50.0,
expected_cost: 100.0,
required_qubits: Some(required_qubits),
confidence: 0.75,
})
}
fn evaluate_qaoa(
&self,
chars: &ProblemCharacteristics,
constraints: &ResourceConstraints,
) -> Result<AlgorithmOption> {
let device = &constraints.quantum_devices[0];
let required_qubits = chars.n_samples.min(20);
let is_feasible = required_qubits <= device.n_qubits;
Ok(AlgorithmOption {
name: "QAOA".to_string(),
algorithm_type: AlgorithmType::Quantum,
is_feasible,
expected_accuracy: 0.88,
expected_training_time_s: 200.0,
expected_inference_latency_ms: 30.0,
expected_cost: 80.0,
required_qubits: Some(required_qubits),
confidence: 0.78,
})
}
fn evaluate_classical_svm(
&self,
chars: &ProblemCharacteristics,
_constraints: &ResourceConstraints,
) -> Result<AlgorithmOption> {
let accuracy = 0.85 - (chars.dimensionality_ratio * 0.05).min(0.15);
let training_time = chars.n_samples as f64 * chars.n_features as f64 / 1000.0;
Ok(AlgorithmOption {
name: "Classical SVM".to_string(),
algorithm_type: AlgorithmType::Classical,
is_feasible: true,
expected_accuracy: accuracy,
expected_training_time_s: training_time,
expected_inference_latency_ms: 0.1,
expected_cost: 0.0001,
required_qubits: None,
confidence: 0.95,
})
}
fn evaluate_classical_nn(
&self,
chars: &ProblemCharacteristics,
constraints: &ResourceConstraints,
) -> Result<AlgorithmOption> {
let base_accuracy = 0.88;
let accuracy = base_accuracy - (chars.class_imbalance.log2() * 0.02).min(0.1);
let training_time = if constraints.classical_compute.has_gpu {
chars.n_samples as f64 / 100.0
} else {
chars.n_samples as f64 / 10.0
};
Ok(AlgorithmOption {
name: "Neural Network".to_string(),
algorithm_type: AlgorithmType::Classical,
is_feasible: true,
expected_accuracy: accuracy,
expected_training_time_s: training_time,
expected_inference_latency_ms: 0.5,
expected_cost: 0.0001,
required_qubits: None,
confidence: 0.90,
})
}
fn evaluate_random_forest(
&self,
chars: &ProblemCharacteristics,
_constraints: &ResourceConstraints,
) -> Result<AlgorithmOption> {
let accuracy = 0.86;
let training_time = chars.n_samples as f64 * chars.n_features as f64 / 500.0;
Ok(AlgorithmOption {
name: "Random Forest".to_string(),
algorithm_type: AlgorithmType::Classical,
is_feasible: true,
expected_accuracy: accuracy,
expected_training_time_s: training_time,
expected_inference_latency_ms: 0.2,
expected_cost: 0.00005,
required_qubits: None,
confidence: 0.92,
})
}
fn evaluate_gradient_boosting(
&self,
chars: &ProblemCharacteristics,
_constraints: &ResourceConstraints,
) -> Result<AlgorithmOption> {
let accuracy = 0.89;
let training_time = chars.n_samples as f64 * chars.n_features as f64 / 300.0;
Ok(AlgorithmOption {
name: "Gradient Boosting".to_string(),
algorithm_type: AlgorithmType::Classical,
is_feasible: true,
expected_accuracy: accuracy,
expected_training_time_s: training_time,
expected_inference_latency_ms: 0.3,
expected_cost: 0.0001,
required_qubits: None,
confidence: 0.93,
})
}
fn estimate_qsvm_accuracy(
&self,
chars: &ProblemCharacteristics,
device: &QuantumDevice,
) -> Result<f64> {
let base_accuracy = 0.90;
let noise_penalty = device.gate_error_rate * 50.0;
let dim_bonus = (1.0 / (1.0 + chars.dimensionality_ratio)) * 0.05;
Ok((base_accuracy - noise_penalty + dim_bonus)
.max(0.5)
.min(0.99))
}
fn estimate_qsvm_training_time(
&self,
chars: &ProblemCharacteristics,
_device: &QuantumDevice,
) -> Result<f64> {
let time = (chars.n_samples * chars.n_samples) as f64 / 100.0;
Ok(time)
}
fn estimate_qsvm_cost(
&self,
chars: &ProblemCharacteristics,
device: &QuantumDevice,
) -> Result<f64> {
let n_shots = 1000;
let n_kernel_evaluations = chars.n_samples * chars.n_samples;
let cost = n_kernel_evaluations as f64 * n_shots as f64 * device.cost_per_shot;
Ok(cost)
}
fn estimate_qnn_accuracy(
&self,
chars: &ProblemCharacteristics,
device: &QuantumDevice,
) -> Result<f64> {
let base_accuracy = 0.87;
let noise_penalty = device.gate_error_rate * 40.0;
let complexity_bonus = (chars.n_features as f64 / 100.0).min(0.08);
Ok((base_accuracy - noise_penalty + complexity_bonus)
.max(0.5)
.min(0.99))
}
fn estimate_qnn_training_time(
&self,
chars: &ProblemCharacteristics,
_device: &QuantumDevice,
) -> Result<f64> {
let n_epochs = 100;
let time_per_epoch = chars.n_samples as f64 / 10.0;
Ok(n_epochs as f64 * time_per_epoch)
}
fn estimate_qnn_cost(
&self,
chars: &ProblemCharacteristics,
device: &QuantumDevice,
) -> Result<f64> {
let n_shots = 1000;
let n_epochs = 100;
let cost_per_epoch = chars.n_samples as f64 * n_shots as f64 * device.cost_per_shot;
Ok(n_epochs as f64 * cost_per_epoch)
}
fn select_best_option(
&self,
quantum_options: &[AlgorithmOption],
classical_options: &[AlgorithmOption],
chars: &ProblemCharacteristics,
constraints: &ResourceConstraints,
) -> Result<AlgorithmOption> {
let mut all_options = quantum_options.to_vec();
all_options.extend(classical_options.iter().cloned());
let filtered: Vec<_> = all_options
.into_iter()
.filter(|opt| {
if let Some(max_time) = constraints.max_training_time {
if opt.expected_training_time_s > max_time {
return false;
}
}
if let Some(max_cost) = constraints.max_cost_per_inference {
if opt.expected_cost > max_cost {
return false;
}
}
true
})
.collect();
if filtered.is_empty() {
return Err(MLError::InvalidInput(
"No algorithms satisfy the given constraints".to_string(),
));
}
let best = filtered
.into_iter()
.max_by(|a, b| {
let score_a = self.compute_option_score(a, chars, constraints);
let score_b = self.compute_option_score(b, chars, constraints);
score_a
.partial_cmp(&score_b)
.unwrap_or(std::cmp::Ordering::Equal)
})
.expect("filtered verified non-empty above");
Ok(best)
}
fn compute_option_score(
&self,
option: &AlgorithmOption,
_chars: &ProblemCharacteristics,
_constraints: &ResourceConstraints,
) -> f64 {
let accuracy_score = option.expected_accuracy;
let speed_score = 1.0 / (1.0 + option.expected_training_time_s / 100.0);
let cost_score = 1.0 / (1.0 + option.expected_cost);
let confidence_score = option.confidence;
accuracy_score * 0.4 + speed_score * 0.2 + cost_score * 0.2 + confidence_score * 0.2
}
fn generate_recommendation(
&self,
best_option: AlgorithmOption,
chars: &ProblemCharacteristics,
constraints: &ResourceConstraints,
) -> Result<AlgorithmRecommendation> {
let algorithm_choice = match best_option.algorithm_type {
AlgorithmType::Quantum => AlgorithmChoice::QuantumOnly {
algorithm: best_option.name.clone(),
device: constraints
.quantum_devices
.get(0)
.map(|d| d.name.clone())
.unwrap_or_else(|| "simulator".to_string()),
},
AlgorithmType::Classical => AlgorithmChoice::ClassicalOnly {
algorithm: best_option.name.clone(),
backend: if constraints.classical_compute.has_gpu {
"GPU".to_string()
} else {
"CPU".to_string()
},
},
AlgorithmType::Hybrid => AlgorithmChoice::Hybrid {
quantum_component: "QNN".to_string(),
classical_component: "Neural Network".to_string(),
splitting_strategy: "Feature Engineering".to_string(),
},
};
let quantum_advantage = self.compute_quantum_advantage(&best_option, chars)?;
let hyperparameters = self.generate_hyperparameters(&best_option, chars)?;
let expected_performance = PerformanceEstimate {
accuracy: best_option.expected_accuracy,
accuracy_ci: (
best_option.expected_accuracy - 0.05,
best_option.expected_accuracy + 0.05,
),
training_time_s: best_option.expected_training_time_s,
inference_latency_ms: best_option.expected_inference_latency_ms,
memory_mb: chars.n_samples as f64 * chars.n_features as f64 * 8.0 / 1024.0 / 1024.0,
};
let cost_analysis = self.generate_cost_analysis(&best_option, chars)?;
let calibration_method = self.recommend_calibration(&best_option, chars)?;
let production_config =
self.generate_production_config(&best_option, chars, constraints)?;
Ok(AlgorithmRecommendation {
algorithm_choice,
quantum_advantage,
hyperparameters,
expected_performance,
cost_analysis,
confidence: best_option.confidence,
calibration_method,
production_config,
})
}
fn compute_quantum_advantage(
&self,
option: &AlgorithmOption,
_chars: &ProblemCharacteristics,
) -> Result<QuantumAdvantageMetrics> {
let is_quantum = option.algorithm_type == AlgorithmType::Quantum;
Ok(QuantumAdvantageMetrics {
speedup: if is_quantum { 2.5 } else { 1.0 },
accuracy_improvement: if is_quantum { 0.05 } else { 0.0 },
sample_efficiency: if is_quantum { 1.8 } else { 1.0 },
generalization_improvement: if is_quantum { 0.03 } else { 0.0 },
statistical_significance: if is_quantum { 0.01 } else { 1.0 },
})
}
fn generate_hyperparameters(
&self,
option: &AlgorithmOption,
chars: &ProblemCharacteristics,
) -> Result<HashMap<String, f64>> {
let mut params = HashMap::new();
match option.name.as_str() {
"QSVM" => {
params.insert("n_shots".to_string(), 1000.0);
params.insert("kernel_depth".to_string(), 3.0);
}
"QNN" => {
params.insert("n_layers".to_string(), 5.0);
params.insert("learning_rate".to_string(), 0.01);
params.insert("batch_size".to_string(), 32.0);
}
"Neural Network" => {
params.insert("hidden_layers".to_string(), 3.0);
params.insert("neurons_per_layer".to_string(), 128.0);
params.insert("learning_rate".to_string(), 0.001);
params.insert("dropout".to_string(), 0.2);
}
_ => {}
}
Ok(params)
}
fn generate_cost_analysis(
&self,
option: &AlgorithmOption,
chars: &ProblemCharacteristics,
) -> Result<CostAnalysis> {
let training_cost = option.expected_cost;
let inference_cost_per_sample = option.expected_cost / chars.n_samples as f64;
let mut breakdown = HashMap::new();
breakdown.insert("training".to_string(), training_cost);
breakdown.insert("inference".to_string(), inference_cost_per_sample * 1000.0);
Ok(CostAnalysis {
training_cost,
inference_cost_per_sample,
total_cost: training_cost + inference_cost_per_sample * 10000.0,
breakdown,
})
}
fn recommend_calibration(
&self,
option: &AlgorithmOption,
_chars: &ProblemCharacteristics,
) -> Result<Option<String>> {
if option.algorithm_type == AlgorithmType::Quantum {
Ok(Some("Bayesian Binning into Quantiles (BBQ)".to_string()))
} else {
Ok(Some("Platt Scaling".to_string()))
}
}
fn generate_production_config(
&self,
option: &AlgorithmOption,
chars: &ProblemCharacteristics,
constraints: &ResourceConstraints,
) -> Result<ProductionConfig> {
let batch_size = if option.algorithm_type == AlgorithmType::Quantum {
16
} else if constraints.classical_compute.has_gpu {
128
} else {
32
};
let n_workers = if option.algorithm_type == AlgorithmType::Quantum {
1
} else {
constraints.classical_compute.n_cpu_cores.min(8)
};
let mut alert_thresholds = HashMap::new();
alert_thresholds.insert(
"latency_ms".to_string(),
option.expected_inference_latency_ms * 2.0,
);
alert_thresholds.insert("accuracy".to_string(), option.expected_accuracy - 0.05);
alert_thresholds.insert("error_rate".to_string(), 0.01);
Ok(ProductionConfig {
batch_size,
n_workers,
enable_caching: true,
monitoring: MonitoringConfig {
log_interval: 100,
alert_thresholds,
tracked_metrics: vec![
"accuracy".to_string(),
"latency".to_string(),
"throughput".to_string(),
"error_rate".to_string(),
],
},
scaling: ScalingConfig {
auto_scaling: true,
min_instances: 1,
max_instances: 10,
scale_up_threshold: 70.0,
scale_down_threshold: 30.0,
},
})
}
fn initialize_performance_models() -> HashMap<String, PerformanceModel> {
let mut models = HashMap::new();
models.insert(
"QSVM".to_string(),
PerformanceModel {
model_type: "linear".to_string(),
coefficients: vec![0.9, -0.05, 0.03],
},
);
models.insert(
"QNN".to_string(),
PerformanceModel {
model_type: "linear".to_string(),
coefficients: vec![0.87, -0.04, 0.05],
},
);
models
}
fn initialize_cost_models() -> HashMap<String, CostModel> {
let mut models = HashMap::new();
models.insert(
"QSVM".to_string(),
CostModel {
base_cost: 10.0,
cost_per_sample: 0.01,
cost_per_feature: 0.001,
cost_per_qubit: 1.0,
},
);
models.insert(
"QNN".to_string(),
CostModel {
base_cost: 20.0,
cost_per_sample: 0.02,
cost_per_feature: 0.002,
cost_per_qubit: 2.0,
},
);
models
}
}
#[derive(Debug, Clone)]
struct AlgorithmOption {
name: String,
algorithm_type: AlgorithmType,
is_feasible: bool,
expected_accuracy: f64,
expected_training_time_s: f64,
expected_inference_latency_ms: f64,
expected_cost: f64,
required_qubits: Option<usize>,
confidence: f64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum AlgorithmType {
Quantum,
Classical,
Hybrid,
}
impl Default for DecisionThresholds {
fn default() -> Self {
Self {
min_quantum_speedup: 1.5,
min_accuracy_improvement: 0.02,
max_cost_ratio: 10.0,
min_confidence: 0.70,
}
}
}
impl ProblemCharacteristics {
pub fn from_dataset(x: &Array2<f64>, y: &Array1<usize>) -> Self {
let n_samples = x.shape()[0];
let n_features = x.shape()[1];
let n_classes = y.iter().max().map(|&m| m + 1).unwrap_or(2);
let dimensionality_ratio = n_features as f64 / n_samples as f64;
let total_elements = n_samples * n_features;
let zero_elements = x.iter().filter(|&&val| val.abs() < 1e-10).count();
let sparsity = zero_elements as f64 / total_elements as f64;
let condition_number = 100.0;
let mut class_counts = vec![0; n_classes];
for &label in y.iter() {
if label < n_classes {
class_counts[label] += 1;
}
}
let max_count = class_counts.iter().max().copied().unwrap_or(1);
let min_count = class_counts
.iter()
.filter(|&&c| c > 0)
.min()
.copied()
.unwrap_or(1);
let class_imbalance = max_count as f64 / min_count as f64;
Self {
n_samples,
n_features,
n_classes,
dimensionality_ratio,
sparsity,
condition_number,
class_imbalance,
task_type: if n_classes == 2 {
TaskType::BinaryClassification
} else {
TaskType::MultiClassClassification
},
domain: ProblemDomain::General,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_engine_creation() {
let engine = HybridAutoMLEngine::new();
assert!(!engine.performance_models.is_empty());
}
#[test]
fn test_problem_characteristics_extraction() {
let x = Array2::from_shape_fn((100, 10), |(i, j)| (i + j) as f64);
let y = Array1::from_shape_fn(100, |i| i % 2);
let chars = ProblemCharacteristics::from_dataset(&x, &y);
assert_eq!(chars.n_samples, 100);
assert_eq!(chars.n_features, 10);
assert_eq!(chars.n_classes, 2);
assert_eq!(chars.task_type, TaskType::BinaryClassification);
}
#[test]
fn test_algorithm_recommendation() {
let engine = HybridAutoMLEngine::new();
let chars = ProblemCharacteristics {
n_samples: 1000,
n_features: 20,
n_classes: 2,
dimensionality_ratio: 0.02,
sparsity: 0.0,
condition_number: 10.0,
class_imbalance: 1.2,
task_type: TaskType::BinaryClassification,
domain: ProblemDomain::General,
};
let constraints = ResourceConstraints {
quantum_devices: vec![QuantumDevice {
name: "ibm_quantum".to_string(),
n_qubits: 20,
gate_error_rate: 0.001,
measurement_error_rate: 0.01,
decoherence_time_us: 100.0,
cost_per_shot: 0.0001,
availability: DeviceAvailability::Available,
}],
classical_compute: ClassicalCompute {
n_cpu_cores: 8,
has_gpu: true,
gpu_memory_gb: 16.0,
ram_gb: 64.0,
},
max_latency_ms: Some(100.0),
max_cost_per_inference: Some(1.0),
max_training_time: Some(1000.0),
max_power_consumption: None,
};
let recommendation = engine
.analyze_and_recommend(&chars, &constraints)
.expect("Failed to analyze and recommend");
assert!(recommendation.confidence > 0.0);
assert!(recommendation.expected_performance.accuracy > 0.0);
}
}