Skip to main content

quantrs2_device/ml_optimization/
mod.rs

1//! ML-Driven Circuit Optimization and Hardware Prediction with SciRS2
2//!
3//! This module provides comprehensive machine learning-driven circuit optimization
4//! and hardware performance prediction using SciRS2's advanced ML capabilities,
5//! statistical analysis, and optimization algorithms for intelligent quantum computing.
6
7use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
8use std::f64::consts::PI;
9use std::sync::{Arc, Mutex, RwLock};
10use std::time::{Duration, Instant};
11
12use quantrs2_circuit::prelude::*;
13use quantrs2_core::{
14    error::{QuantRS2Error, QuantRS2Result},
15    gate::GateOp,
16    qubit::QubitId,
17};
18
19// SciRS2 dependencies are conditionally re-exported through fallback_scirs2
20
21// Fallback implementations when SciRS2 is not available
22pub mod fallback_scirs2;
23pub use fallback_scirs2::*;
24
25use scirs2_core::ndarray::{s, Array1, Array2, Array3, Array4, ArrayView1, ArrayView2, Axis};
26use scirs2_core::random::prelude::*;
27use scirs2_core::Complex64;
28use tokio::sync::{broadcast, mpsc};
29
30use crate::{
31    adaptive_compilation::AdaptiveCompilationConfig,
32    backend_traits::{query_backend_capabilities, BackendCapabilities},
33    calibration::{CalibrationManager, DeviceCalibration},
34    integrated_device_manager::IntegratedQuantumDeviceManager,
35    noise_model::CalibrationNoiseModel,
36    topology::HardwareTopology,
37    CircuitResult, DeviceError, DeviceResult,
38};
39
40// Module declarations
41pub mod config;
42pub mod ensemble;
43pub mod features;
44pub mod hardware;
45pub mod monitoring;
46pub mod online_learning;
47pub mod optimization;
48pub mod training;
49pub mod transfer_learning;
50pub mod validation;
51
52#[cfg(not(feature = "scirs2"))]
53pub mod fallback;
54
55// Re-exports for public API
56pub use config::*;
57pub use ensemble::*;
58pub use features::*;
59pub use hardware::*;
60pub use monitoring::*;
61pub use online_learning::*;
62pub use optimization::*;
63pub use training::*;
64pub use transfer_learning::*;
65pub use validation::*;
66
67#[cfg(not(feature = "scirs2"))]
68pub use fallback::*;
69
70// ── MLCircuitOptimizer ────────────────────────────────────────────────────
71
72/// Strategy used by `MLCircuitOptimizer` when choosing between candidate
73/// circuit transformations.
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum MLOptimizationStrategy {
76    /// Prefer circuits that minimise total gate count.
77    MinimizeGateCount,
78    /// Prefer circuits with the smallest critical-path depth.
79    MinimizeDepth,
80    /// Balance gate count and depth equally.
81    BalancedOptimization,
82}
83
84impl Default for MLOptimizationStrategy {
85    fn default() -> Self {
86        MLOptimizationStrategy::BalancedOptimization
87    }
88}
89
90/// Score computed for a candidate circuit, used to rank transformations.
91#[derive(Debug, Clone)]
92pub struct CircuitScore {
93    /// Raw two-qubit gate count (dominant cost on real hardware)
94    pub two_qubit_gate_count: usize,
95    /// Total gate count
96    pub total_gate_count: usize,
97    /// Estimated circuit depth (number of layers)
98    pub circuit_depth: usize,
99    /// Composite score (lower is better)
100    pub composite: f64,
101}
102
103impl CircuitScore {
104    /// Compute a score for `circuit` under the given strategy.
105    pub fn compute<const N: usize>(
106        circuit: &quantrs2_circuit::prelude::Circuit<N>,
107        strategy: &MLOptimizationStrategy,
108    ) -> Self {
109        let gates = circuit.gates();
110        let total = gates.len();
111        let two_q = gates.iter().filter(|g| g.qubits().len() == 2).count();
112
113        // Simple depth estimate: max over qubits of the number of gates touching it
114        let mut qubit_depth = vec![0usize; N];
115        for gate in gates {
116            let max_d = gate
117                .qubits()
118                .iter()
119                .map(|q| qubit_depth.get(q.id() as usize).copied().unwrap_or(0))
120                .max()
121                .unwrap_or(0);
122            for q in gate.qubits() {
123                let idx = q.id() as usize;
124                if idx < N {
125                    qubit_depth[idx] = max_d + 1;
126                }
127            }
128        }
129        let depth = qubit_depth.iter().copied().max().unwrap_or(0);
130
131        let composite = match strategy {
132            MLOptimizationStrategy::MinimizeGateCount => total as f64,
133            MLOptimizationStrategy::MinimizeDepth => depth as f64,
134            MLOptimizationStrategy::BalancedOptimization => {
135                // Weighted combination: two-qubit gates are ~10× more expensive
136                (two_q as f64) * 10.0 + (total as f64) + (depth as f64)
137            }
138        };
139
140        Self {
141            two_qubit_gate_count: two_q,
142            total_gate_count: total,
143            circuit_depth: depth,
144            composite,
145        }
146    }
147}
148
149/// ML-driven circuit optimizer.
150///
151/// Uses a simple learned heuristic — prefer circuits with lower two-qubit gate
152/// count weighted by estimated depth — to select among candidate transformed
153/// circuits.  The optimisation proceeds in passes; each pass tries commuting
154/// independent gates and removing adjacent inverse pairs.
155///
156/// # Strategy
157/// | `MLOptimizationStrategy` | Scoring function |
158/// |---|---|
159/// | `MinimizeGateCount` | total gate count |
160/// | `MinimizeDepth` | circuit depth (critical path) |
161/// | `BalancedOptimization` | `10 * two_q + total + depth` |
162pub struct MLCircuitOptimizer {
163    /// Optimisation strategy
164    pub strategy: MLOptimizationStrategy,
165    /// Maximum number of optimisation passes
166    pub max_passes: usize,
167    /// Minimum score improvement to continue iterating (as a fraction)
168    pub convergence_threshold: f64,
169}
170
171impl MLCircuitOptimizer {
172    /// Create a new optimizer with the given strategy and default pass limit.
173    pub fn new(strategy: MLOptimizationStrategy) -> Self {
174        Self {
175            strategy,
176            max_passes: 5,
177            convergence_threshold: 0.01,
178        }
179    }
180
181    /// Create a balanced optimizer with a custom pass limit.
182    pub fn with_passes(mut self, passes: usize) -> Self {
183        self.max_passes = passes;
184        self
185    }
186
187    /// Optimise `circuit` and return the best circuit found together with its
188    /// score.
189    ///
190    /// The method uses graph-based topology analysis (`SciRS2GraphTranspiler`)
191    /// to identify commuting gate pairs, then greedily reorders them to
192    /// minimise the composite score under the chosen strategy.
193    pub fn optimize<const N: usize>(
194        &self,
195        circuit: &quantrs2_circuit::prelude::Circuit<N>,
196    ) -> QuantRS2Result<quantrs2_circuit::prelude::Circuit<N>> {
197        use crate::transpiler_scirs2_graph::{SciRS2GraphTranspiler, SciRS2TranspilerConfig};
198
199        let transpiler = SciRS2GraphTranspiler::new(SciRS2TranspilerConfig {
200            enable_commutation: true,
201            enable_critical_path_opt: true,
202            enable_routing_opt: false,
203            max_optimization_passes: self.max_passes,
204            hardware_topology: None,
205        });
206
207        let mut best = circuit.clone();
208        let mut best_score = CircuitScore::compute(&best, &self.strategy);
209
210        for _pass in 0..self.max_passes {
211            // Apply graph-based optimization
212            let candidate = transpiler.optimize_circuit(&best).map_err(|e| {
213                QuantRS2Error::InvalidInput(format!("ML optimizer pass failed: {}", e))
214            })?;
215
216            let candidate_score = CircuitScore::compute(&candidate, &self.strategy);
217
218            // Accept if strictly better
219            let improvement =
220                (best_score.composite - candidate_score.composite) / best_score.composite.max(1.0);
221
222            if candidate_score.composite < best_score.composite {
223                best = candidate;
224                best_score = candidate_score;
225                if improvement < self.convergence_threshold {
226                    break; // Converged
227                }
228            } else {
229                break; // No improvement
230            }
231        }
232
233        Ok(best)
234    }
235
236    /// Return the current score for a circuit without modifying it.
237    pub fn score<const N: usize>(
238        &self,
239        circuit: &quantrs2_circuit::prelude::Circuit<N>,
240    ) -> CircuitScore {
241        CircuitScore::compute(circuit, &self.strategy)
242    }
243}
244
245#[cfg(test)]
246mod ml_optimizer_tests {
247    use super::*;
248    use quantrs2_circuit::prelude::Circuit;
249
250    #[test]
251    fn test_ml_optimizer_creation() {
252        let opt = MLCircuitOptimizer::new(MLOptimizationStrategy::MinimizeGateCount);
253        assert_eq!(opt.strategy, MLOptimizationStrategy::MinimizeGateCount);
254        assert_eq!(opt.max_passes, 5);
255    }
256
257    #[test]
258    fn test_circuit_score_basic() {
259        let mut circuit = Circuit::<2>::new();
260        let _ = circuit.h(0);
261        let _ = circuit.cnot(0, 1);
262
263        let score = CircuitScore::compute(&circuit, &MLOptimizationStrategy::BalancedOptimization);
264        assert_eq!(score.total_gate_count, 2);
265        assert_eq!(score.two_qubit_gate_count, 1);
266        assert!(score.composite > 0.0);
267    }
268
269    #[test]
270    fn test_ml_optimizer_optimize() {
271        let opt = MLCircuitOptimizer::new(MLOptimizationStrategy::BalancedOptimization);
272
273        let mut circuit = Circuit::<3>::new();
274        let _ = circuit.h(0);
275        let _ = circuit.h(1);
276        let _ = circuit.cnot(0, 1);
277        let _ = circuit.cnot(1, 2);
278
279        let result = opt.optimize(&circuit);
280        assert!(result.is_ok(), "Optimization should succeed");
281        let optimized = result.expect("Optimized circuit");
282        assert!(
283            !optimized.gates().is_empty(),
284            "Optimized circuit should have gates"
285        );
286    }
287
288    #[test]
289    fn test_minimize_depth_strategy() {
290        let opt = MLCircuitOptimizer::new(MLOptimizationStrategy::MinimizeDepth);
291        let mut circuit = Circuit::<2>::new();
292        let _ = circuit.h(0);
293        let _ = circuit.h(1);
294
295        let score = opt.score(&circuit);
296        // Two parallel H gates: depth should be 1
297        assert_eq!(score.circuit_depth, 1);
298    }
299}