1use 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
20fn 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 (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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56pub enum MLStrategy {
57 ReinforcementLearning {
59 learning_rate: f64,
61 discount_factor: f64,
62 exploration_rate: f64,
63 episodes: usize,
65 },
66 NeuralNetwork {
68 architecture: Vec<usize>,
70 learning_rate: f64,
72 epochs: usize,
74 batch_size: usize,
76 },
77 GeneticAlgorithm {
79 population_size: usize,
81 generations: usize,
83 mutation_rate: f64,
85 selection_pressure: f64,
87 },
88 BayesianOptimization {
90 initial_samples: usize,
92 iterations: usize,
94 acquisition: AcquisitionFunction,
96 },
97}
98
99#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
101pub enum AcquisitionFunction {
102 ExpectedImprovement,
103 UpperConfidenceBound { beta: f64 },
104 ProbabilityOfImprovement,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct MLCircuitRepresentation {
110 pub features: Vec<f64>,
112 pub gate_sequence: Vec<usize>,
114 pub adjacency_matrix: Vec<Vec<f64>>,
116 pub qubit_connectivity: Vec<Vec<bool>>,
118 pub metrics: CircuitMetrics,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct CircuitMetrics {
125 pub depth: usize,
127 pub gate_count: usize,
129 pub two_qubit_gate_count: usize,
131 pub entanglement_measure: f64,
133 pub critical_path_length: usize,
135 pub parallelization_potential: f64,
137}
138
139pub struct MLCircuitOptimizer {
141 strategy: MLStrategy,
143 feature_extractor: Arc<Mutex<FeatureExtractor>>,
145 models: Arc<Mutex<HashMap<String, MLModel>>>,
147 training_data: Arc<Mutex<Vec<TrainingExample>>>,
149 config: MLOptimizerConfig,
151}
152
153#[derive(Debug, Clone)]
155pub struct MLOptimizerConfig {
156 pub cache_features: bool,
158 pub max_training_examples: usize,
160 pub model_update_frequency: usize,
162 pub parallel_training: bool,
164 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#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct TrainingExample {
183 pub input: MLCircuitRepresentation,
185 pub target: OptimizationTarget,
187 pub score: f64,
189 pub metadata: HashMap<String, String>,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub enum OptimizationTarget {
196 MinimizeDepth { target_depth: usize },
198 MinimizeGates { target_count: usize },
200 MaximizeParallelization { target_parallel_fraction: f64 },
202 Custom {
204 objective: String,
205 target_value: f64,
206 },
207}
208
209pub struct FeatureExtractor {
211 analyzer: SciRS2CircuitAnalyzer,
213 cache: HashMap<String, Vec<f64>>,
215 feature_weights: Vec<f64>,
217}
218
219impl Default for FeatureExtractor {
220 fn default() -> Self {
221 Self::new()
222 }
223}
224
225impl FeatureExtractor {
226 #[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 pub fn extract_features<const N: usize>(
238 &mut self,
239 circuit: &Circuit<N>,
240 ) -> QuantRS2Result<Vec<f64>> {
241 let cache_key = self.generate_cache_key(circuit);
243
244 if let Some(features) = self.cache.get(&cache_key) {
246 return Ok(features.clone());
247 }
248
249 let mut features = Vec::new();
251
252 features.extend(self.extract_basic_features(circuit));
254
255 features.extend(self.extract_graph_features(circuit)?);
257
258 features.extend(self.extract_pattern_features(circuit));
260
261 features.extend(self.extract_entanglement_features(circuit));
263
264 self.cache.insert(cache_key, features.clone());
266
267 Ok(features)
268 }
269
270 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 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), cnot_count / gate_count.max(1.0), N as f64, ]
296 }
297
298 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 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 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 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); features
365 }
366
367 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 let cnot_gates: Vec<_> = gates.iter().filter(|gate| gate.name() == "CNOT").collect();
374
375 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 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 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 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#[derive(Debug, Clone)]
435pub enum MLModel {
436 LinearRegression { weights: Vec<f64>, bias: f64 },
438 NeuralNetwork {
440 layers: Vec<Layer>,
441 learning_rate: f64,
442 },
443 QLearning {
445 q_table: HashMap<String, HashMap<String, f64>>,
446 learning_rate: f64,
447 discount_factor: f64,
448 },
449 RandomForest {
451 trees: Vec<DecisionTree>,
452 num_trees: usize,
453 },
454}
455
456#[derive(Debug, Clone)]
458pub struct Layer {
459 pub weights: Vec<Vec<f64>>,
460 pub biases: Vec<f64>,
461 pub activation: ActivationFunction,
462}
463
464#[derive(Debug, Clone)]
466pub enum ActivationFunction {
467 ReLU,
468 Sigmoid,
469 Tanh,
470 Linear,
471}
472
473#[derive(Debug, Clone)]
475pub struct DecisionTree {
476 pub nodes: Vec<TreeNode>,
477 pub root: usize,
478}
479
480#[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 #[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 #[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 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 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 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 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 let mut q_table: HashMap<Vec<i32>, Vec<f64>> = HashMap::new();
582
583 let gate_count = circuit.gates().len();
584 let n_actions = 1 + gate_count + gate_count.saturating_sub(1);
589
590 let mut rng = make_prng_seed();
591
592 let quantize =
594 |feats: &[f64]| -> Vec<i32> { feats.iter().map(|&v| (v * 10.0) as i32).collect() };
595
596 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 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 let q_row = q_table
617 .entry(state_key.clone())
618 .or_insert_with(|| vec![0.0; n_acts.max(n_actions)]);
619
620 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 let gate_vec = gates_to_boxes(¤t_circuit);
638 let g_count = gate_vec.len();
639
640 let new_circuit_opt: Option<Circuit<N>> = if action == 0 || g_count == 0 {
641 None
643 } else if action <= g_count {
644 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 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 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 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 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 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 fn optimize_with_nn<const N: usize>(
722 &self,
723 circuit: &Circuit<N>,
724 features: &[f64],
725 ) -> QuantRS2Result<Circuit<N>> {
726 Ok(circuit.clone())
733 }
734
735 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; let mut rng = make_prng_seed();
774
775 let evaluate_fitness = |ordering: &[usize]| -> f64 {
778 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 let identity: Vec<usize> = (0..gate_count).collect();
804 let mut population: Vec<(Vec<usize>, f64)> = Vec::with_capacity(population_size);
805
806 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 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 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 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 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 let cut = prng_usize(&mut rng, gate_count.max(1));
863 let mut child: Vec<usize> = p1[..cut].to_vec();
864 for &g in p2 {
866 if !child.contains(&g) {
867 child.push(g);
868 }
869 }
870 for &g in p1 {
872 if !child.contains(&g) {
873 child.push(g);
874 }
875 }
876
877 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 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 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 fn optimize_with_bayesian<const N: usize>(
926 &self,
927 circuit: &Circuit<N>,
928 features: &[f64],
929 ) -> QuantRS2Result<Circuit<N>> {
930 Ok(circuit.clone())
937 }
938
939 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, fidelity_improvement: 0.0, }
958 }
959
960 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 if data.len() > self.config.max_training_examples {
970 data.remove(0);
971 }
972 }
973
974 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 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 }
1010 }
1011
1012 Ok(())
1013 }
1014
1015 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 let input_size = data.first().map_or(0, |ex| ex.input.features.len());
1028
1029 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]; 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 fn train_rl_model(
1062 &self,
1063 data: &[TrainingExample],
1064 learning_rate: f64,
1065 discount_factor: f64,
1066 ) -> QuantRS2Result<()> {
1067 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#[derive(Debug, Clone)]
1086pub struct MLOptimizationResult<const N: usize> {
1087 pub original_circuit: Circuit<N>,
1089 pub optimized_circuit: Circuit<N>,
1091 pub features: Vec<f64>,
1093 pub optimization_time: Duration,
1095 pub improvement_metrics: ImprovementMetrics,
1097 pub strategy_used: MLStrategy,
1099}
1100
1101#[derive(Debug, Clone)]
1103pub struct ImprovementMetrics {
1104 pub depth_reduction: f64,
1106 pub gate_reduction: f64,
1108 pub compilation_speedup: f64,
1110 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); }
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 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 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 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}