Skip to main content

quantrs2_circuit/
ml_optimization.rs

1//! Machine Learning-based circuit optimization
2//!
3//! This module provides ML-driven optimization techniques for quantum circuits,
4//! including reinforcement learning for gate scheduling, neural networks for
5//! pattern recognition, and automated hyperparameter tuning.
6
7use crate::builder::Circuit;
8use crate::dag::{circuit_to_dag, CircuitDag};
9use crate::scirs2_integration::{AnalysisResult, SciRS2CircuitAnalyzer};
10use quantrs2_core::{
11    error::{QuantRS2Error, QuantRS2Result},
12    gate::GateOp,
13    qubit::QubitId,
14};
15use serde::{Deserialize, Serialize};
16use std::collections::{HashMap, VecDeque};
17use std::sync::{Arc, Mutex};
18use std::time::{Duration, Instant};
19
20// Pseudo-random number generation without rand crate.
21// Uses a simple xorshift64 PRNG seeded from the system time.
22fn xorshift64(state: &mut u64) -> u64 {
23    let mut x = *state;
24    x ^= x << 13;
25    x ^= x >> 7;
26    x ^= x << 17;
27    *state = x;
28    x
29}
30
31fn prng_f64(state: &mut u64) -> f64 {
32    let bits = xorshift64(state);
33    // Map to [0, 1)
34    (bits >> 11) as f64 / (1u64 << 53) as f64
35}
36
37fn prng_usize(state: &mut u64, upper: usize) -> usize {
38    if upper == 0 {
39        return 0;
40    }
41    (xorshift64(state) as usize) % upper
42}
43
44fn make_prng_seed() -> u64 {
45    use std::time::{SystemTime, UNIX_EPOCH};
46    SystemTime::now()
47        .duration_since(UNIX_EPOCH)
48        .map(|d| {
49            d.as_nanos() as u64 ^ (d.subsec_nanos() as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15)
50        })
51        .unwrap_or(0xdeadbeef_cafebabe)
52}
53
54/// ML optimization strategy
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56pub enum MLStrategy {
57    /// Reinforcement learning for gate scheduling
58    ReinforcementLearning {
59        /// Q-learning parameters
60        learning_rate: f64,
61        discount_factor: f64,
62        exploration_rate: f64,
63        /// Number of training episodes
64        episodes: usize,
65    },
66    /// Neural network for pattern recognition
67    NeuralNetwork {
68        /// Network architecture (layer sizes)
69        architecture: Vec<usize>,
70        /// Learning rate
71        learning_rate: f64,
72        /// Number of training epochs
73        epochs: usize,
74        /// Batch size
75        batch_size: usize,
76    },
77    /// Genetic algorithm for optimization
78    GeneticAlgorithm {
79        /// Population size
80        population_size: usize,
81        /// Number of generations
82        generations: usize,
83        /// Mutation rate
84        mutation_rate: f64,
85        /// Selection pressure
86        selection_pressure: f64,
87    },
88    /// Bayesian optimization for hyperparameter tuning
89    BayesianOptimization {
90        /// Number of initial random samples
91        initial_samples: usize,
92        /// Number of optimization iterations
93        iterations: usize,
94        /// Acquisition function
95        acquisition: AcquisitionFunction,
96    },
97}
98
99/// Acquisition functions for Bayesian optimization
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
101pub enum AcquisitionFunction {
102    ExpectedImprovement,
103    UpperConfidenceBound { beta: f64 },
104    ProbabilityOfImprovement,
105}
106
107/// Circuit representation for ML algorithms
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct MLCircuitRepresentation {
110    /// Feature vector representing the circuit
111    pub features: Vec<f64>,
112    /// Gate sequence encoding
113    pub gate_sequence: Vec<usize>,
114    /// Adjacency matrix
115    pub adjacency_matrix: Vec<Vec<f64>>,
116    /// Qubit connectivity
117    pub qubit_connectivity: Vec<Vec<bool>>,
118    /// Circuit metrics
119    pub metrics: CircuitMetrics,
120}
121
122/// Circuit metrics for ML training
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct CircuitMetrics {
125    /// Circuit depth
126    pub depth: usize,
127    /// Gate count
128    pub gate_count: usize,
129    /// Two-qubit gate count
130    pub two_qubit_gate_count: usize,
131    /// Entanglement measure
132    pub entanglement_measure: f64,
133    /// Critical path length
134    pub critical_path_length: usize,
135    /// Parallelization potential
136    pub parallelization_potential: f64,
137}
138
139/// ML-based optimizer
140pub struct MLCircuitOptimizer {
141    /// Optimization strategy
142    strategy: MLStrategy,
143    /// Feature extractor
144    feature_extractor: Arc<Mutex<FeatureExtractor>>,
145    /// Model storage
146    models: Arc<Mutex<HashMap<String, MLModel>>>,
147    /// Training data
148    training_data: Arc<Mutex<Vec<TrainingExample>>>,
149    /// Configuration
150    config: MLOptimizerConfig,
151}
152
153/// ML optimizer configuration
154#[derive(Debug, Clone)]
155pub struct MLOptimizerConfig {
156    /// Enable feature caching
157    pub cache_features: bool,
158    /// Maximum training examples to keep
159    pub max_training_examples: usize,
160    /// Model update frequency
161    pub model_update_frequency: usize,
162    /// Enable parallel training
163    pub parallel_training: bool,
164    /// Feature selection threshold
165    pub feature_selection_threshold: f64,
166}
167
168impl Default for MLOptimizerConfig {
169    fn default() -> Self {
170        Self {
171            cache_features: true,
172            max_training_examples: 10000,
173            model_update_frequency: 100,
174            parallel_training: true,
175            feature_selection_threshold: 0.01,
176        }
177    }
178}
179
180/// Training example for supervised learning
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct TrainingExample {
183    /// Input circuit representation
184    pub input: MLCircuitRepresentation,
185    /// Target optimization result
186    pub target: OptimizationTarget,
187    /// Quality score
188    pub score: f64,
189    /// Metadata
190    pub metadata: HashMap<String, String>,
191}
192
193/// Optimization target
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub enum OptimizationTarget {
196    /// Minimize circuit depth
197    MinimizeDepth { target_depth: usize },
198    /// Minimize gate count
199    MinimizeGates { target_count: usize },
200    /// Maximize parallelization
201    MaximizeParallelization { target_parallel_fraction: f64 },
202    /// Custom objective
203    Custom {
204        objective: String,
205        target_value: f64,
206    },
207}
208
209/// Feature extractor for circuits
210pub struct FeatureExtractor {
211    /// `SciRS2` analyzer for graph features
212    analyzer: SciRS2CircuitAnalyzer,
213    /// Feature cache
214    cache: HashMap<String, Vec<f64>>,
215    /// Feature importance weights
216    feature_weights: Vec<f64>,
217}
218
219impl Default for FeatureExtractor {
220    fn default() -> Self {
221        Self::new()
222    }
223}
224
225impl FeatureExtractor {
226    /// Create a new feature extractor
227    #[must_use]
228    pub fn new() -> Self {
229        Self {
230            analyzer: SciRS2CircuitAnalyzer::new(),
231            cache: HashMap::new(),
232            feature_weights: Vec::new(),
233        }
234    }
235
236    /// Extract features from a circuit
237    pub fn extract_features<const N: usize>(
238        &mut self,
239        circuit: &Circuit<N>,
240    ) -> QuantRS2Result<Vec<f64>> {
241        // Generate cache key
242        let cache_key = self.generate_cache_key(circuit);
243
244        // Check cache
245        if let Some(features) = self.cache.get(&cache_key) {
246            return Ok(features.clone());
247        }
248
249        // Extract features
250        let mut features = Vec::new();
251
252        // Basic circuit features
253        features.extend(self.extract_basic_features(circuit));
254
255        // Graph-based features using SciRS2
256        features.extend(self.extract_graph_features(circuit)?);
257
258        // Gate pattern features
259        features.extend(self.extract_pattern_features(circuit));
260
261        // Entanglement features
262        features.extend(self.extract_entanglement_features(circuit));
263
264        // Cache the result
265        self.cache.insert(cache_key, features.clone());
266
267        Ok(features)
268    }
269
270    /// Extract basic circuit features
271    fn extract_basic_features<const N: usize>(&self, circuit: &Circuit<N>) -> Vec<f64> {
272        let gates = circuit.gates();
273        let depth = circuit.gates().len() as f64;
274        let gate_count = gates.len() as f64;
275
276        // Gate type distribution
277        let mut gate_types = HashMap::new();
278        for gate in gates {
279            *gate_types.entry(gate.name().to_string()).or_insert(0.0) += 1.0;
280        }
281
282        let h_count = gate_types.get("H").copied().unwrap_or(0.0);
283        let cnot_count = gate_types.get("CNOT").copied().unwrap_or(0.0);
284        let single_qubit_count = gate_count - cnot_count;
285
286        vec![
287            depth,
288            gate_count,
289            single_qubit_count,
290            cnot_count,
291            h_count,
292            depth / gate_count.max(1.0),      // Density
293            cnot_count / gate_count.max(1.0), // Two-qubit fraction
294            N as f64,                         // Number of qubits
295        ]
296    }
297
298    /// Extract graph-based features using `SciRS2`
299    fn extract_graph_features<const N: usize>(
300        &mut self,
301        circuit: &Circuit<N>,
302    ) -> QuantRS2Result<Vec<f64>> {
303        let analysis = self.analyzer.analyze_circuit(circuit)?;
304
305        let metrics = &analysis.metrics;
306
307        Ok(vec![
308            metrics.num_nodes as f64,
309            metrics.num_edges as f64,
310            metrics.density,
311            metrics.clustering_coefficient,
312            metrics.connected_components as f64,
313            metrics.diameter.unwrap_or(0) as f64,
314            metrics.average_path_length.unwrap_or(0.0),
315            analysis.communities.len() as f64,
316            analysis.critical_paths.len() as f64,
317        ])
318    }
319
320    /// Extract gate pattern features
321    fn extract_pattern_features<const N: usize>(&self, circuit: &Circuit<N>) -> Vec<f64> {
322        let gates = circuit.gates();
323        let mut features = Vec::new();
324
325        // Sequential patterns
326        let mut h_cnot_patterns = 0.0;
327        let mut cnot_chains = 0.0;
328
329        for window in gates.windows(2) {
330            if window.len() == 2 {
331                let gate1 = &window[0];
332                let gate2 = &window[1];
333
334                if gate1.name() == "H" && gate2.name() == "CNOT" {
335                    h_cnot_patterns += 1.0;
336                }
337
338                if gate1.name() == "CNOT" && gate2.name() == "CNOT" {
339                    cnot_chains += 1.0;
340                }
341            }
342        }
343
344        features.push(h_cnot_patterns);
345        features.push(cnot_chains);
346
347        // Qubit usage patterns
348        let mut qubit_usage = vec![0.0; N];
349        for gate in gates {
350            for qubit in gate.qubits() {
351                qubit_usage[qubit.id() as usize] += 1.0;
352            }
353        }
354
355        let max_usage: f64 = qubit_usage.iter().fold(0.0_f64, |a, &b| a.max(b));
356        let min_usage = qubit_usage.iter().fold(f64::INFINITY, |a, &b| a.min(b));
357        let avg_usage = qubit_usage.iter().sum::<f64>() / N as f64;
358
359        features.push(max_usage);
360        features.push(min_usage);
361        features.push(avg_usage);
362        features.push(max_usage - min_usage); // Usage variance
363
364        features
365    }
366
367    /// Extract entanglement-related features
368    fn extract_entanglement_features<const N: usize>(&self, circuit: &Circuit<N>) -> Vec<f64> {
369        let gates = circuit.gates();
370        let mut features = Vec::new();
371
372        // Entangling gate distribution
373        let cnot_gates: Vec<_> = gates.iter().filter(|gate| gate.name() == "CNOT").collect();
374
375        // Connectivity graph
376        let mut connectivity = vec![vec![false; N]; N];
377        for gate in &cnot_gates {
378            let qubits = gate.qubits();
379            if qubits.len() == 2 {
380                let q1 = qubits[0].id() as usize;
381                let q2 = qubits[1].id() as usize;
382                connectivity[q1][q2] = true;
383                connectivity[q2][q1] = true;
384            }
385        }
386
387        // Calculate connectivity features
388        let total_connections: f64 = connectivity
389            .iter()
390            .flat_map(|row| row.iter())
391            .map(|&connected| if connected { 1.0 } else { 0.0 })
392            .sum();
393
394        let max_connections = N * (N - 1);
395        let connectivity_ratio = total_connections / max_connections as f64;
396
397        features.push(cnot_gates.len() as f64);
398        features.push(connectivity_ratio);
399
400        // Star connectivity (one qubit connected to many)
401        let max_degree = connectivity
402            .iter()
403            .map(|row| row.iter().filter(|&&c| c).count())
404            .max()
405            .unwrap_or(0) as f64;
406
407        features.push(max_degree);
408
409        features
410    }
411
412    /// Generate cache key for a circuit
413    fn generate_cache_key<const N: usize>(&self, circuit: &Circuit<N>) -> String {
414        use std::collections::hash_map::DefaultHasher;
415        use std::hash::{Hash, Hasher};
416
417        let mut hasher = DefaultHasher::new();
418
419        N.hash(&mut hasher);
420        circuit.gates().len().hash(&mut hasher);
421
422        for gate in circuit.gates() {
423            gate.name().hash(&mut hasher);
424            for qubit in gate.qubits() {
425                qubit.id().hash(&mut hasher);
426            }
427        }
428
429        format!("{:x}", hasher.finish())
430    }
431}
432
433/// ML model abstraction
434#[derive(Debug, Clone)]
435pub enum MLModel {
436    /// Linear regression model
437    LinearRegression { weights: Vec<f64>, bias: f64 },
438    /// Neural network model
439    NeuralNetwork {
440        layers: Vec<Layer>,
441        learning_rate: f64,
442    },
443    /// Q-learning model
444    QLearning {
445        q_table: HashMap<String, HashMap<String, f64>>,
446        learning_rate: f64,
447        discount_factor: f64,
448    },
449    /// Random forest model
450    RandomForest {
451        trees: Vec<DecisionTree>,
452        num_trees: usize,
453    },
454}
455
456/// Neural network layer
457#[derive(Debug, Clone)]
458pub struct Layer {
459    pub weights: Vec<Vec<f64>>,
460    pub biases: Vec<f64>,
461    pub activation: ActivationFunction,
462}
463
464/// Activation functions
465#[derive(Debug, Clone)]
466pub enum ActivationFunction {
467    ReLU,
468    Sigmoid,
469    Tanh,
470    Linear,
471}
472
473/// Decision tree for random forest
474#[derive(Debug, Clone)]
475pub struct DecisionTree {
476    pub nodes: Vec<TreeNode>,
477    pub root: usize,
478}
479
480/// Tree node
481#[derive(Debug, Clone)]
482pub struct TreeNode {
483    pub feature_index: Option<usize>,
484    pub threshold: Option<f64>,
485    pub value: Option<f64>,
486    pub left_child: Option<usize>,
487    pub right_child: Option<usize>,
488}
489
490impl MLCircuitOptimizer {
491    /// Create a new ML optimizer
492    #[must_use]
493    pub fn new(strategy: MLStrategy) -> Self {
494        Self {
495            strategy,
496            feature_extractor: Arc::new(Mutex::new(FeatureExtractor::new())),
497            models: Arc::new(Mutex::new(HashMap::new())),
498            training_data: Arc::new(Mutex::new(Vec::new())),
499            config: MLOptimizerConfig::default(),
500        }
501    }
502
503    /// Create optimizer with custom configuration
504    #[must_use]
505    pub fn with_config(strategy: MLStrategy, config: MLOptimizerConfig) -> Self {
506        Self {
507            strategy,
508            feature_extractor: Arc::new(Mutex::new(FeatureExtractor::new())),
509            models: Arc::new(Mutex::new(HashMap::new())),
510            training_data: Arc::new(Mutex::new(Vec::new())),
511            config,
512        }
513    }
514
515    /// Optimize a circuit using ML
516    pub fn optimize<const N: usize>(
517        &mut self,
518        circuit: &Circuit<N>,
519    ) -> QuantRS2Result<MLOptimizationResult<N>> {
520        let start_time = Instant::now();
521
522        // Extract features
523        let features = {
524            let mut extractor = self.feature_extractor.lock().map_err(|e| {
525                QuantRS2Error::RuntimeError(format!("Failed to lock feature extractor: {e}"))
526            })?;
527            extractor.extract_features(circuit)?
528        };
529
530        // Apply optimization based on strategy
531        let optimized_circuit = match &self.strategy {
532            MLStrategy::ReinforcementLearning { .. } => {
533                self.optimize_with_rl(circuit, &features)?
534            }
535            MLStrategy::NeuralNetwork { .. } => self.optimize_with_nn(circuit, &features)?,
536            MLStrategy::GeneticAlgorithm { .. } => self.optimize_with_ga(circuit, &features)?,
537            MLStrategy::BayesianOptimization { .. } => {
538                self.optimize_with_bayesian(circuit, &features)?
539            }
540        };
541
542        let optimization_time = start_time.elapsed();
543
544        Ok(MLOptimizationResult {
545            original_circuit: circuit.clone(),
546            optimized_circuit: optimized_circuit.clone(),
547            features,
548            optimization_time,
549            improvement_metrics: self.calculate_improvement_metrics(circuit, &optimized_circuit),
550            strategy_used: self.strategy.clone(),
551        })
552    }
553
554    /// Optimize using reinforcement learning (Q-learning).
555    ///
556    /// State:   circuit feature vector quantized to integers (resolution 10).
557    /// Actions: NoOp | RemoveGate(idx) | SwapAdjacent(i, i+1)
558    /// Reward:  (old_depth - new_depth) + (old_gates - new_gates) * 0.1
559    /// Update:  Q(s,a) += lr * (reward + γ * max_Q(s') - Q(s,a))
560    fn optimize_with_rl<const N: usize>(
561        &self,
562        circuit: &Circuit<N>,
563        features: &[f64],
564    ) -> QuantRS2Result<Circuit<N>> {
565        let (episodes, learning_rate, discount_factor, exploration_rate) = match &self.strategy {
566            MLStrategy::ReinforcementLearning {
567                episodes,
568                learning_rate,
569                discount_factor,
570                exploration_rate,
571            } => (
572                *episodes,
573                *learning_rate,
574                *discount_factor,
575                *exploration_rate,
576            ),
577            _ => (100, 0.1, 0.9, 0.2),
578        };
579
580        // Q-table: state (quantized feature vec) -> action values
581        let mut q_table: HashMap<Vec<i32>, Vec<f64>> = HashMap::new();
582
583        let gate_count = circuit.gates().len();
584        // Action space:
585        //   0         → NoOp
586        //   1..=G     → RemoveGate(i-1)
587        //   G+1..=2G  → SwapAdjacent(i-G-1, i-G)
588        let n_actions = 1 + gate_count + gate_count.saturating_sub(1);
589
590        let mut rng = make_prng_seed();
591
592        // Helper: quantize feature vec to i32 state key
593        let quantize =
594            |feats: &[f64]| -> Vec<i32> { feats.iter().map(|&v| (v * 10.0) as i32).collect() };
595
596        // Helper: build gates-as-boxes from circuit for mutation
597        let gates_to_boxes = |c: &Circuit<N>| -> Vec<Box<dyn GateOp>> { c.gates_as_boxes() };
598
599        let mut best_circuit = circuit.clone();
600        let mut best_score = gate_count as f64;
601
602        for _ep in 0..episodes {
603            let mut current_circuit = circuit.clone();
604
605            // Extract current state features lazily (reuse provided features for initial)
606            let mut state_feats = features.to_vec();
607
608            for _step in 0..10 {
609                let state_key = quantize(&state_feats);
610                let n_acts = {
611                    let g = current_circuit.gates().len();
612                    1 + g + g.saturating_sub(1)
613                };
614
615                // Ensure Q-table row exists
616                let q_row = q_table
617                    .entry(state_key.clone())
618                    .or_insert_with(|| vec![0.0; n_acts.max(n_actions)]);
619
620                // ε-greedy action selection
621                let action = if prng_f64(&mut rng) < exploration_rate {
622                    prng_usize(&mut rng, n_acts.max(1))
623                } else {
624                    q_row
625                        .iter()
626                        .take(n_acts.max(1))
627                        .enumerate()
628                        .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
629                        .map(|(i, _)| i)
630                        .unwrap_or(0)
631                };
632
633                let old_depth = current_circuit.gates().len();
634                let old_gates = old_depth;
635
636                // Apply action to a clone, then accept if reward ≥ 0
637                let gate_vec = gates_to_boxes(&current_circuit);
638                let g_count = gate_vec.len();
639
640                let new_circuit_opt: Option<Circuit<N>> = if action == 0 || g_count == 0 {
641                    // NoOp
642                    None
643                } else if action <= g_count {
644                    // RemoveGate(action - 1)
645                    let idx = action - 1;
646                    let mut new_gates: Vec<Box<dyn GateOp>> = gate_vec
647                        .into_iter()
648                        .enumerate()
649                        .filter(|(i, _)| *i != idx)
650                        .map(|(_, g)| g)
651                        .collect();
652                    Circuit::<N>::from_gates(new_gates).ok()
653                } else {
654                    // SwapAdjacent(i, i+1)
655                    let i = action - g_count - 1;
656                    if i + 1 < g_count {
657                        let mut new_gates = gate_vec;
658                        new_gates.swap(i, i + 1);
659                        Circuit::<N>::from_gates(new_gates).ok()
660                    } else {
661                        None
662                    }
663                };
664
665                let (next_circuit, reward) = match new_circuit_opt {
666                    Some(nc) => {
667                        let new_depth = nc.gates().len();
668                        let new_gates_count = new_depth;
669                        let r = (old_depth as f64 - new_depth as f64)
670                            + (old_gates as f64 - new_gates_count as f64) * 0.1;
671                        (nc, r)
672                    }
673                    None => (current_circuit.clone(), 0.0),
674                };
675
676                // Build next-state features (basic: gate count, depth proxy)
677                let next_gate_count = next_circuit.gates().len();
678                let next_feats: Vec<f64> = {
679                    let mut f = state_feats.clone();
680                    if !f.is_empty() {
681                        f[0] = next_gate_count as f64;
682                    }
683                    if f.len() > 1 {
684                        f[1] = next_gate_count as f64;
685                    }
686                    f
687                };
688                let next_key = quantize(&next_feats);
689
690                // max Q(s')
691                let max_q_next = q_table
692                    .get(&next_key)
693                    .and_then(|row| row.iter().cloned().reduce(f64::max))
694                    .unwrap_or(0.0);
695
696                // Q-table update
697                let q_row = q_table
698                    .entry(state_key)
699                    .or_insert_with(|| vec![0.0; n_actions.max(1)]);
700                let act_idx = action.min(q_row.len().saturating_sub(1));
701                let old_q = q_row[act_idx];
702                q_row[act_idx] =
703                    old_q + learning_rate * (reward + discount_factor * max_q_next - old_q);
704
705                // Track best
706                let score = next_circuit.gates().len() as f64;
707                if score < best_score {
708                    best_score = score;
709                    best_circuit = next_circuit.clone();
710                }
711
712                current_circuit = next_circuit;
713                state_feats = next_feats;
714            }
715        }
716
717        Ok(best_circuit)
718    }
719
720    /// Optimize using neural network
721    fn optimize_with_nn<const N: usize>(
722        &self,
723        circuit: &Circuit<N>,
724        features: &[f64],
725    ) -> QuantRS2Result<Circuit<N>> {
726        // Simplified NN optimization
727        // In a full implementation, this would:
728        // 1. Use trained NN to predict optimal gate sequences
729        // 2. Apply predicted transformations
730        // 3. Validate and refine results
731
732        Ok(circuit.clone())
733    }
734
735    /// Optimize using genetic algorithm.
736    ///
737    /// Individual: permutation of gate indices (a reordering of the original gate list).
738    /// Fitness:    gate_count_reduction + depth_reduction (both relative to original).
739    /// Selection:  tournament selection (tournament size 3).
740    /// Crossover:  one-point cut on gate index list.
741    /// Mutation:   swap two random positions with probability `mutation_rate`.
742    fn optimize_with_ga<const N: usize>(
743        &self,
744        circuit: &Circuit<N>,
745        _features: &[f64],
746    ) -> QuantRS2Result<Circuit<N>> {
747        let (population_size, generations, mutation_rate, _selection_pressure) =
748            match &self.strategy {
749                MLStrategy::GeneticAlgorithm {
750                    population_size,
751                    generations,
752                    mutation_rate,
753                    selection_pressure,
754                } => (
755                    *population_size,
756                    *generations,
757                    *mutation_rate,
758                    *selection_pressure,
759                ),
760                _ => (20, 50, 0.1, 2.0),
761            };
762
763        let original_gates = circuit.gates_as_boxes();
764        let gate_count = original_gates.len();
765
766        if gate_count == 0 {
767            return Ok(circuit.clone());
768        }
769
770        let original_gate_count = gate_count as f64;
771        let original_depth = gate_count as f64; // proxy: sequential depth
772
773        let mut rng = make_prng_seed();
774
775        // Fitness evaluation: build circuit from ordering, count gates, return fitness score.
776        // Higher fitness = more reduction.  Gate count reduction is weighted 0.7 and depth proxy 0.3.
777        let evaluate_fitness = |ordering: &[usize]| -> f64 {
778            // Prune adjacent inverses that cancel (H-H, X-X, Z-Z on same qubit)
779            let mut kept: Vec<usize> = Vec::with_capacity(ordering.len());
780            for &idx in ordering {
781                if let Some(&last) = kept.last() {
782                    let g1 = original_gates[last].name();
783                    let g2 = original_gates[idx].name();
784                    let q1 = original_gates[last].qubits();
785                    let q2 = original_gates[idx].qubits();
786                    let same_target = q1.len() == 1 && q2.len() == 1 && q1[0] == q2[0];
787                    let self_inverse = matches!(g1, "H" | "X" | "Y" | "Z" | "CNOT" | "CX");
788                    if same_target && g1 == g2 && self_inverse {
789                        kept.pop();
790                        continue;
791                    }
792                }
793                kept.push(idx);
794            }
795            let new_gate_count = kept.len() as f64;
796            let gate_reduction =
797                (original_gate_count - new_gate_count) / original_gate_count.max(1.0);
798            let depth_reduction = (original_depth - new_gate_count) / original_depth.max(1.0);
799            gate_reduction * 0.7 + depth_reduction * 0.3
800        };
801
802        // Initialize population: identity ordering plus mutations
803        let identity: Vec<usize> = (0..gate_count).collect();
804        let mut population: Vec<(Vec<usize>, f64)> = Vec::with_capacity(population_size);
805
806        // Add identity first
807        let id_fit = evaluate_fitness(&identity);
808        population.push((identity.clone(), id_fit));
809
810        for _ in 1..population_size {
811            let mut individual = identity.clone();
812            // Random shuffle via Fisher-Yates
813            for i in (1..gate_count).rev() {
814                let j = prng_usize(&mut rng, i + 1);
815                individual.swap(i, j);
816            }
817            let fit = evaluate_fitness(&individual);
818            population.push((individual, fit));
819        }
820
821        // Tournament selection: pick best of 3 random individuals
822        let tournament_select = |pop: &[(Vec<usize>, f64)], rng: &mut u64| -> usize {
823            let a = prng_usize(rng, pop.len());
824            let b = prng_usize(rng, pop.len());
825            let c = prng_usize(rng, pop.len());
826            let (ia, fa) = (a, pop[a].1);
827            let (ib, fb) = (b, pop[b].1);
828            let (ic, fc) = (c, pop[c].1);
829            if fa >= fb && fa >= fc {
830                ia
831            } else if fb >= fc {
832                ib
833            } else {
834                ic
835            }
836        };
837
838        for _gen in 0..generations {
839            let mut new_population: Vec<(Vec<usize>, f64)> = Vec::with_capacity(population_size);
840
841            // Elitism: keep the best individual
842            let best_idx = population
843                .iter()
844                .enumerate()
845                .max_by(|a, b| {
846                    a.1 .1
847                        .partial_cmp(&b.1 .1)
848                        .unwrap_or(std::cmp::Ordering::Equal)
849                })
850                .map(|(i, _)| i)
851                .unwrap_or(0);
852            new_population.push(population[best_idx].clone());
853
854            while new_population.len() < population_size {
855                // Selection
856                let p1_idx = tournament_select(&population, &mut rng);
857                let p2_idx = tournament_select(&population, &mut rng);
858                let p1 = &population[p1_idx].0;
859                let p2 = &population[p2_idx].0;
860
861                // One-point crossover
862                let cut = prng_usize(&mut rng, gate_count.max(1));
863                let mut child: Vec<usize> = p1[..cut].to_vec();
864                // Append elements from p2 that are not yet in child
865                for &g in p2 {
866                    if !child.contains(&g) {
867                        child.push(g);
868                    }
869                }
870                // Append any still-missing elements from p1 (fill gaps)
871                for &g in p1 {
872                    if !child.contains(&g) {
873                        child.push(g);
874                    }
875                }
876
877                // Mutation: swap two positions
878                if prng_f64(&mut rng) < mutation_rate && gate_count >= 2 {
879                    let i = prng_usize(&mut rng, gate_count);
880                    let j = prng_usize(&mut rng, gate_count);
881                    child.swap(i, j);
882                }
883
884                let fit = evaluate_fitness(&child);
885                new_population.push((child, fit));
886            }
887
888            population = new_population;
889        }
890
891        // Extract best individual
892        let best = population
893            .iter()
894            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
895
896        let best_ordering = best.map(|(o, _)| o.as_slice()).unwrap_or(&[]);
897
898        // Reconstruct pruned gate list
899        let mut kept_indices: Vec<usize> = Vec::with_capacity(gate_count);
900        for &idx in best_ordering {
901            if let Some(&last) = kept_indices.last() {
902                let g1 = original_gates[last].name();
903                let g2 = original_gates[idx].name();
904                let q1 = original_gates[last].qubits();
905                let q2 = original_gates[idx].qubits();
906                let same_target = q1.len() == 1 && q2.len() == 1 && q1[0] == q2[0];
907                let self_inverse = matches!(g1, "H" | "X" | "Y" | "Z" | "CNOT" | "CX");
908                if same_target && g1 == g2 && self_inverse {
909                    kept_indices.pop();
910                    continue;
911                }
912            }
913            kept_indices.push(idx);
914        }
915
916        let new_gates: Vec<Box<dyn GateOp>> = kept_indices
917            .iter()
918            .map(|&i| original_gates[i].clone_gate())
919            .collect();
920
921        Circuit::<N>::from_gates(new_gates)
922    }
923
924    /// Optimize using Bayesian optimization
925    fn optimize_with_bayesian<const N: usize>(
926        &self,
927        circuit: &Circuit<N>,
928        features: &[f64],
929    ) -> QuantRS2Result<Circuit<N>> {
930        // Simplified Bayesian optimization
931        // In a full implementation, this would:
932        // 1. Build Gaussian process model of optimization landscape
933        // 2. Use acquisition function to select next optimization point
934        // 3. Iteratively improve based on observed results
935
936        Ok(circuit.clone())
937    }
938
939    /// Calculate improvement metrics
940    fn calculate_improvement_metrics<const N: usize>(
941        &self,
942        original: &Circuit<N>,
943        optimized: &Circuit<N>,
944    ) -> ImprovementMetrics {
945        let original_depth = original.gates().len();
946        let optimized_depth = optimized.gates().len();
947        let original_gates = original.gates().len();
948        let optimized_gates = optimized.gates().len();
949
950        ImprovementMetrics {
951            depth_reduction: (original_depth as f64 - optimized_depth as f64)
952                / original_depth as f64,
953            gate_reduction: (original_gates as f64 - optimized_gates as f64)
954                / original_gates as f64,
955            compilation_speedup: 1.0,  // Placeholder
956            fidelity_improvement: 0.0, // Placeholder
957        }
958    }
959
960    /// Add training example
961    pub fn add_training_example(&mut self, example: TrainingExample) {
962        let mut data = self
963            .training_data
964            .lock()
965            .expect("Training data mutex poisoned");
966        data.push(example);
967
968        // Maintain maximum size
969        if data.len() > self.config.max_training_examples {
970            data.remove(0);
971        }
972    }
973
974    /// Train models with current data
975    pub fn train_models(&mut self) -> QuantRS2Result<()> {
976        let data = {
977            let training_data = self.training_data.lock().map_err(|e| {
978                QuantRS2Error::RuntimeError(format!("Failed to lock training data: {e}"))
979            })?;
980            training_data.clone()
981        };
982
983        if data.is_empty() {
984            return Err(QuantRS2Error::InvalidInput(
985                "No training data available".to_string(),
986            ));
987        }
988
989        // Train based on strategy
990        let strategy = self.strategy.clone();
991        match strategy {
992            MLStrategy::NeuralNetwork {
993                architecture,
994                learning_rate,
995                epochs,
996                batch_size,
997            } => {
998                self.train_neural_network(&data, &architecture, learning_rate, epochs, batch_size)?;
999            }
1000            MLStrategy::ReinforcementLearning {
1001                learning_rate,
1002                discount_factor,
1003                ..
1004            } => {
1005                self.train_rl_model(&data, learning_rate, discount_factor)?;
1006            }
1007            _ => {
1008                // Other strategies would be implemented here
1009            }
1010        }
1011
1012        Ok(())
1013    }
1014
1015    /// Train neural network model
1016    fn train_neural_network(
1017        &self,
1018        data: &[TrainingExample],
1019        architecture: &[usize],
1020        learning_rate: f64,
1021        epochs: usize,
1022        batch_size: usize,
1023    ) -> QuantRS2Result<()> {
1024        // Simplified NN training
1025        // In a full implementation, this would implement backpropagation
1026
1027        let input_size = data.first().map_or(0, |ex| ex.input.features.len());
1028
1029        // Create network layers
1030        let mut layers = Vec::new();
1031        let mut prev_size = input_size;
1032
1033        for &layer_size in architecture {
1034            let weights = vec![vec![0.1; prev_size]; layer_size]; // Random initialization
1035            let biases = vec![0.0; layer_size];
1036
1037            layers.push(Layer {
1038                weights,
1039                biases,
1040                activation: ActivationFunction::ReLU,
1041            });
1042
1043            prev_size = layer_size;
1044        }
1045
1046        let model = MLModel::NeuralNetwork {
1047            layers,
1048            learning_rate,
1049        };
1050
1051        let mut models = self
1052            .models
1053            .lock()
1054            .map_err(|e| QuantRS2Error::RuntimeError(format!("Failed to lock models: {e}")))?;
1055        models.insert("neural_network".to_string(), model);
1056
1057        Ok(())
1058    }
1059
1060    /// Train reinforcement learning model
1061    fn train_rl_model(
1062        &self,
1063        data: &[TrainingExample],
1064        learning_rate: f64,
1065        discount_factor: f64,
1066    ) -> QuantRS2Result<()> {
1067        // Simplified Q-learning training
1068        let model = MLModel::QLearning {
1069            q_table: HashMap::new(),
1070            learning_rate,
1071            discount_factor,
1072        };
1073
1074        let mut models = self
1075            .models
1076            .lock()
1077            .map_err(|e| QuantRS2Error::RuntimeError(format!("Failed to lock models: {e}")))?;
1078        models.insert("q_learning".to_string(), model);
1079
1080        Ok(())
1081    }
1082}
1083
1084/// ML optimization result
1085#[derive(Debug, Clone)]
1086pub struct MLOptimizationResult<const N: usize> {
1087    /// Original circuit
1088    pub original_circuit: Circuit<N>,
1089    /// Optimized circuit
1090    pub optimized_circuit: Circuit<N>,
1091    /// Extracted features
1092    pub features: Vec<f64>,
1093    /// Time taken for optimization
1094    pub optimization_time: Duration,
1095    /// Improvement metrics
1096    pub improvement_metrics: ImprovementMetrics,
1097    /// Strategy used
1098    pub strategy_used: MLStrategy,
1099}
1100
1101/// Improvement metrics
1102#[derive(Debug, Clone)]
1103pub struct ImprovementMetrics {
1104    /// Relative depth reduction
1105    pub depth_reduction: f64,
1106    /// Relative gate count reduction
1107    pub gate_reduction: f64,
1108    /// Compilation speedup factor
1109    pub compilation_speedup: f64,
1110    /// Fidelity improvement
1111    pub fidelity_improvement: f64,
1112}
1113
1114#[cfg(test)]
1115mod tests {
1116    use super::*;
1117    use quantrs2_core::gate::multi::CNOT;
1118    use quantrs2_core::gate::single::Hadamard;
1119
1120    #[test]
1121    fn test_feature_extraction() {
1122        let mut extractor = FeatureExtractor::new();
1123
1124        let mut circuit = Circuit::<2>::new();
1125        circuit
1126            .add_gate(Hadamard { target: QubitId(0) })
1127            .expect("Failed to add Hadamard gate");
1128        circuit
1129            .add_gate(CNOT {
1130                control: QubitId(0),
1131                target: QubitId(1),
1132            })
1133            .expect("Failed to add CNOT gate");
1134
1135        let features = extractor
1136            .extract_features(&circuit)
1137            .expect("Failed to extract features");
1138        assert!(!features.is_empty());
1139        assert!(features.len() > 10); // Should have multiple feature categories
1140    }
1141
1142    #[test]
1143    fn test_ml_optimizer_creation() {
1144        let strategy = MLStrategy::NeuralNetwork {
1145            architecture: vec![64, 32, 16],
1146            learning_rate: 0.001,
1147            epochs: 100,
1148            batch_size: 32,
1149        };
1150
1151        let optimizer = MLCircuitOptimizer::new(strategy);
1152        assert!(matches!(
1153            optimizer.strategy,
1154            MLStrategy::NeuralNetwork { .. }
1155        ));
1156    }
1157
1158    #[test]
1159    fn test_training_example() {
1160        let features = vec![1.0, 2.0, 3.0, 4.0];
1161        let target = OptimizationTarget::MinimizeDepth { target_depth: 5 };
1162
1163        let representation = MLCircuitRepresentation {
1164            features,
1165            gate_sequence: vec![0, 1, 2],
1166            adjacency_matrix: vec![vec![0.0, 1.0], vec![1.0, 0.0]],
1167            qubit_connectivity: vec![vec![false, true], vec![true, false]],
1168            metrics: CircuitMetrics {
1169                depth: 3,
1170                gate_count: 3,
1171                two_qubit_gate_count: 1,
1172                entanglement_measure: 0.5,
1173                critical_path_length: 3,
1174                parallelization_potential: 0.3,
1175            },
1176        };
1177
1178        let example = TrainingExample {
1179            input: representation,
1180            target,
1181            score: 0.8,
1182            metadata: HashMap::new(),
1183        };
1184
1185        assert!(example.score > 0.0);
1186    }
1187
1188    #[test]
1189    fn test_ml_strategies() {
1190        let rl_strategy = MLStrategy::ReinforcementLearning {
1191            learning_rate: 0.1,
1192            discount_factor: 0.9,
1193            exploration_rate: 0.1,
1194            episodes: 1000,
1195        };
1196
1197        let nn_strategy = MLStrategy::NeuralNetwork {
1198            architecture: vec![32, 16, 8],
1199            learning_rate: 0.001,
1200            epochs: 50,
1201            batch_size: 16,
1202        };
1203
1204        assert!(matches!(
1205            rl_strategy,
1206            MLStrategy::ReinforcementLearning { .. }
1207        ));
1208        assert!(matches!(nn_strategy, MLStrategy::NeuralNetwork { .. }));
1209    }
1210
1211    #[test]
1212    fn test_optimize_with_rl_reduces_or_preserves() {
1213        let strategy = MLStrategy::ReinforcementLearning {
1214            learning_rate: 0.1,
1215            discount_factor: 0.9,
1216            exploration_rate: 0.2,
1217            episodes: 5,
1218        };
1219        let mut optimizer = MLCircuitOptimizer::new(strategy);
1220
1221        // Build a circuit with adjacent H-H cancellations
1222        let mut circuit = Circuit::<2>::new();
1223        circuit
1224            .add_gate(Hadamard { target: QubitId(0) })
1225            .expect("H gate");
1226        circuit
1227            .add_gate(Hadamard { target: QubitId(0) })
1228            .expect("H gate");
1229        circuit
1230            .add_gate(CNOT {
1231                control: QubitId(0),
1232                target: QubitId(1),
1233            })
1234            .expect("CNOT gate");
1235
1236        let result = optimizer.optimize(&circuit).expect("RL optimize");
1237        // After RL: optimized circuit should have ≤ original gate count
1238        assert!(result.optimized_circuit.gates().len() <= circuit.gates().len());
1239    }
1240
1241    #[test]
1242    fn test_optimize_with_ga_cancels_adjacent_inverses() {
1243        let strategy = MLStrategy::GeneticAlgorithm {
1244            population_size: 10,
1245            generations: 5,
1246            mutation_rate: 0.1,
1247            selection_pressure: 2.0,
1248        };
1249        let mut optimizer = MLCircuitOptimizer::new(strategy);
1250
1251        // H H on same qubit: GA should detect and cancel
1252        let mut circuit = Circuit::<2>::new();
1253        circuit
1254            .add_gate(Hadamard { target: QubitId(0) })
1255            .expect("H gate 1");
1256        circuit
1257            .add_gate(Hadamard { target: QubitId(0) })
1258            .expect("H gate 2");
1259        circuit
1260            .add_gate(CNOT {
1261                control: QubitId(0),
1262                target: QubitId(1),
1263            })
1264            .expect("CNOT gate");
1265
1266        let result = optimizer.optimize(&circuit).expect("GA optimize");
1267        assert!(result.optimized_circuit.gates().len() <= circuit.gates().len());
1268    }
1269
1270    #[test]
1271    fn test_circuit_representation() {
1272        let metrics = CircuitMetrics {
1273            depth: 5,
1274            gate_count: 10,
1275            two_qubit_gate_count: 3,
1276            entanglement_measure: 0.7,
1277            critical_path_length: 5,
1278            parallelization_potential: 0.4,
1279        };
1280
1281        let representation = MLCircuitRepresentation {
1282            features: vec![1.0, 2.0, 3.0],
1283            gate_sequence: vec![0, 1, 2, 1, 0],
1284            adjacency_matrix: vec![vec![0.0; 3]; 3],
1285            qubit_connectivity: vec![vec![false; 3]; 3],
1286            metrics,
1287        };
1288
1289        assert_eq!(representation.metrics.depth, 5);
1290        assert_eq!(representation.gate_sequence.len(), 5);
1291    }
1292}