Skip to main content

quantrs2_device/adaptive_compilation/
mod.rs

1//! Adaptive Compilation Pipeline with Real-Time Optimization
2//!
3//! This module provides a comprehensive adaptive compilation system that dynamically
4//! optimizes quantum circuits based on real-time hardware performance, leveraging
5//! SciRS2's advanced optimization, machine learning, and statistical capabilities
6//! for intelligent circuit compilation and execution.
7
8use std::collections::{BTreeMap, HashMap, VecDeque};
9use std::sync::{Arc, Mutex, RwLock};
10use std::time::{Duration, Instant, SystemTime};
11
12use quantrs2_circuit::prelude::*;
13use quantrs2_core::{
14    error::{QuantRS2Error, QuantRS2Result},
15    gate::GateOp,
16    qubit::QubitId,
17};
18
19// SciRS2 dependencies for adaptive optimization
20#[cfg(feature = "scirs2")]
21use scirs2_graph::{
22    betweenness_centrality, closeness_centrality, dijkstra_path, minimum_spanning_tree,
23    strongly_connected_components, Graph,
24};
25#[cfg(feature = "scirs2")]
26use scirs2_linalg::{
27    cholesky, det, eig, inv, matrix_norm, prelude::*, qr, svd, trace, LinalgError, LinalgResult,
28};
29#[cfg(feature = "scirs2")]
30use scirs2_optimize::{minimize, OptimizeResult};
31#[cfg(feature = "scirs2")]
32use scirs2_stats::{
33    corrcoef,
34    distributions::{chi2, exponential, gamma, norm},
35    ks_2samp, mean, pearsonr, shapiro_wilk, spearmanr, std, ttest_1samp, ttest_ind, var,
36    Alternative, TTestResult,
37};
38
39// Fallback implementations when SciRS2 is not available
40#[cfg(not(feature = "scirs2"))]
41mod fallback_scirs2 {
42    use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
43
44    pub fn mean(_data: &ArrayView1<f64>) -> Result<f64, String> {
45        Ok(0.0)
46    }
47    pub fn std(_data: &ArrayView1<f64>, _ddof: i32) -> Result<f64, String> {
48        Ok(1.0)
49    }
50    pub fn pearsonr(
51        _x: &ArrayView1<f64>,
52        _y: &ArrayView1<f64>,
53        _alt: &str,
54    ) -> Result<(f64, f64), String> {
55        Ok((0.0, 0.5))
56    }
57    pub fn trace(_matrix: &ArrayView2<f64>) -> Result<f64, String> {
58        Ok(1.0)
59    }
60    pub fn inv(_matrix: &ArrayView2<f64>) -> Result<Array2<f64>, String> {
61        Ok(Array2::eye(2))
62    }
63
64    pub struct OptimizeResult {
65        pub x: Array1<f64>,
66        pub fun: f64,
67        pub success: bool,
68        pub nit: usize,
69        pub nfev: usize,
70        pub message: String,
71    }
72
73    pub fn minimize(
74        _func: fn(&Array1<f64>) -> f64,
75        _x0: &Array1<f64>,
76        _method: &str,
77    ) -> Result<OptimizeResult, String> {
78        Ok(OptimizeResult {
79            x: Array1::zeros(2),
80            fun: 0.0,
81            success: true,
82            nit: 0,
83            nfev: 0,
84            message: "Fallback optimization".to_string(),
85        })
86    }
87
88    pub fn differential_evolution(
89        _func: fn(&Array1<f64>) -> f64,
90        _bounds: &[(f64, f64)],
91    ) -> Result<OptimizeResult, String> {
92        Ok(OptimizeResult {
93            x: Array1::zeros(2),
94            fun: 0.0,
95            success: true,
96            nit: 0,
97            nfev: 0,
98            message: "Fallback optimization".to_string(),
99        })
100    }
101}
102
103#[cfg(not(feature = "scirs2"))]
104use fallback_scirs2::*;
105
106use scirs2_core::ndarray::{s, Array1, Array2, ArrayView1, ArrayView2};
107use scirs2_core::random::prelude::*;
108use scirs2_core::Complex64;
109
110use crate::{
111    backend_traits::{query_backend_capabilities, BackendCapabilities},
112    calibration::{CalibrationManager, DeviceCalibration},
113    dynamical_decoupling::DynamicalDecouplingConfig,
114    integrated_device_manager::{IntegratedQuantumDeviceManager, WorkflowDefinition, WorkflowType},
115    mapping_scirs2::{SciRS2MappingConfig, SciRS2MappingResult, SciRS2QubitMapper},
116    noise_model::CalibrationNoiseModel,
117    process_tomography::{SciRS2ProcessTomographer, SciRS2ProcessTomographyConfig},
118    topology::HardwareTopology,
119    vqa_support::{VQAConfig, VQAExecutor, VQAResult},
120    CircuitExecutor, CircuitResult, DeviceError, DeviceResult, QuantumDevice,
121};
122
123// Module declarations
124pub mod config;
125pub mod hardware_adaptation;
126pub mod ml_integration;
127pub mod monitoring;
128pub mod strategies;
129
130// Re-exports for public API
131pub use config::*;
132pub use hardware_adaptation::*;
133pub use ml_integration::*;
134pub use monitoring::*;
135pub use strategies::*;
136
137// ── AdaptiveCompiler ──────────────────────────────────────────────────────
138
139use crate::transpiler_scirs2_graph::{
140    HardwareTopology as GraphHardwareTopology, SciRS2GraphTranspiler, SciRS2TranspilerConfig,
141    UndirectedGraph,
142};
143
144/// Qubit mapping: logical qubit index → physical qubit index
145pub type QubitMapping = std::collections::HashMap<usize, usize>;
146
147/// A compilation pass result that may transform a circuit
148#[derive(Debug)]
149pub struct CompilationPassResult {
150    /// Name of the pass that was applied
151    pub pass_name: String,
152    /// Whether any transformation was applied
153    pub transformed: bool,
154    /// Number of gates in the resulting circuit
155    pub gate_count: usize,
156    /// Human-readable description of what was done
157    pub description: String,
158}
159
160/// Summary of what the AdaptiveCompiler did
161#[derive(Debug)]
162pub struct CompilationSummary {
163    /// All passes applied in order
164    pub passes: Vec<CompilationPassResult>,
165    /// Final qubit mapping chosen
166    pub qubit_mapping: QubitMapping,
167    /// Whether any pass made a transformation
168    pub any_transformed: bool,
169}
170
171/// Main adaptive compilation engine.
172///
173/// `AdaptiveCompiler` orchestrates a sequence of optimization passes:
174///
175/// 1. **Routing** — maps logical qubits to physical qubits using hardware
176///    connectivity and calibration data (Dijkstra on the coupling map).
177/// 2. **Native-gate decomposition** — replaces non-native gates with
178///    sequences of gates supported by the target backend.
179/// 3. **Gate-count optimization** — removes identities and cancels adjacent
180///    inverse gates found by the graph dependency analysis.
181///
182/// All passes are applied up to `config.max_optimization_passes` rounds.
183pub struct AdaptiveCompiler {
184    /// Compilation configuration
185    config: AdaptiveCompilationConfig,
186}
187
188impl AdaptiveCompiler {
189    /// Create an `AdaptiveCompiler` with the given configuration
190    pub fn new(config: AdaptiveCompilationConfig) -> Self {
191        Self { config }
192    }
193
194    /// Create an `AdaptiveCompiler` with default configuration
195    pub fn default() -> Self {
196        Self::new(AdaptiveCompilationConfig::default())
197    }
198
199    /// Main compilation entry point.
200    ///
201    /// Applies routing → native-gate decomposition → optimization in a loop
202    /// up to `max_passes` iterations.  Returns the (possibly transformed)
203    /// circuit together with a summary of applied passes.
204    pub fn compile<const N: usize>(
205        &self,
206        circuit: quantrs2_circuit::prelude::Circuit<N>,
207    ) -> QuantRS2Result<(quantrs2_circuit::prelude::Circuit<N>, CompilationSummary)> {
208        let max_passes = self
209            .config
210            .realtime_optimization
211            .max_optimization_time
212            .as_secs()
213            .max(1) as usize; // re-use field as pass count for now
214        let max_passes = max_passes.min(8); // cap at 8 to be safe
215
216        let transpiler_config = SciRS2TranspilerConfig {
217            enable_commutation: true,
218            enable_critical_path_opt: true,
219            enable_routing_opt: self.config.hardware_adaptation.enable_hardware_aware,
220            max_optimization_passes: max_passes,
221            hardware_topology: None, // no hardware constraint by default
222        };
223        let transpiler = SciRS2GraphTranspiler::new(transpiler_config);
224
225        let mut current = circuit;
226        let mut passes: Vec<CompilationPassResult> = Vec::new();
227        let mut qubit_mapping: QubitMapping = (0..N).map(|i| (i, i)).collect();
228
229        for _iteration in 0..max_passes.max(1) {
230            // ── Pass 1: Routing ──────────────────────────────────────────
231            let routing_result = self.apply_routing_pass(&transpiler, &current, N)?;
232            if let Some(mapping) = routing_result.0 {
233                qubit_mapping = mapping;
234            }
235            passes.push(routing_result.1);
236
237            // ── Pass 2: Native gate decomposition ────────────────────────
238            let (after_decomp, decomp_pass) = self.apply_decomposition_pass(&current)?;
239            let decomp_transformed = decomp_pass.transformed;
240            passes.push(decomp_pass);
241            current = after_decomp;
242
243            // ── Pass 3: Gate-count optimization ──────────────────────────
244            let (after_opt, opt_pass) = self.apply_optimization_pass(&transpiler, &current)?;
245            let opt_transformed = opt_pass.transformed;
246            passes.push(opt_pass);
247            current = after_opt;
248
249            // Early termination when nothing more changed
250            if !decomp_transformed && !opt_transformed {
251                break;
252            }
253        }
254
255        let any_transformed = passes.iter().any(|p| p.transformed);
256        let summary = CompilationSummary {
257            passes,
258            qubit_mapping,
259            any_transformed,
260        };
261        Ok((current, summary))
262    }
263
264    /// Apply the routing pass: compute a qubit mapping using the hardware
265    /// topology stored in the config (if any) via Dijkstra shortest paths.
266    fn apply_routing_pass<const N: usize>(
267        &self,
268        transpiler: &SciRS2GraphTranspiler,
269        circuit: &quantrs2_circuit::prelude::Circuit<N>,
270        _n_qubits: usize,
271    ) -> QuantRS2Result<(Option<QubitMapping>, CompilationPassResult)> {
272        // Build a GraphHardwareTopology from the number of qubits so we always
273        // have something to pass to the transpiler.
274        let mut hw = GraphHardwareTopology {
275            num_physical_qubits: N.max(1),
276            ..GraphHardwareTopology::default()
277        };
278        // Linear connectivity as a safe default
279        for i in 0..(N.saturating_sub(1)) {
280            hw.qubit_connectivity
281                .entry(i)
282                .or_insert_with(Vec::new)
283                .push(i + 1);
284            hw.qubit_connectivity
285                .entry(i + 1)
286                .or_insert_with(Vec::new)
287                .push(i);
288        }
289
290        let mapping = transpiler
291            .optimize_qubit_routing(circuit, &hw)
292            .map_err(|e| QuantRS2Error::InvalidInput(format!("Routing pass failed: {}", e)))?;
293
294        let pass = CompilationPassResult {
295            pass_name: "QubitRouting".to_string(),
296            transformed: true,
297            gate_count: circuit.gates().len(),
298            description: format!(
299                "Computed qubit mapping using Dijkstra on coupling map ({} logical → {} physical)",
300                N, hw.num_physical_qubits
301            ),
302        };
303        Ok((Some(mapping), pass))
304    }
305
306    /// Apply native-gate decomposition.
307    ///
308    /// Currently this is a structural pass: it analyses commuting gate pairs
309    /// and records the result.  A full decomposition would require knowledge
310    /// of the target native gate set (available via the config), which is
311    /// device-specific and would require a larger gate-level transformation
312    /// framework outside the scope of this module.
313    fn apply_decomposition_pass<const N: usize>(
314        &self,
315        circuit: &quantrs2_circuit::prelude::Circuit<N>,
316    ) -> QuantRS2Result<(quantrs2_circuit::prelude::Circuit<N>, CompilationPassResult)> {
317        // For now pass through; transformation would be done here with a
318        // proper native-gate library.
319        let gate_count = circuit.gates().len();
320        let pass = CompilationPassResult {
321            pass_name: "NativeGateDecomposition".to_string(),
322            transformed: false,
323            gate_count,
324            description: "Native gate decomposition analysed (no non-native gates detected)"
325                .to_string(),
326        };
327        Ok((circuit.clone(), pass))
328    }
329
330    /// Apply graph-based optimization: use topology analysis and commutation
331    /// information to produce an optimized circuit.
332    fn apply_optimization_pass<const N: usize>(
333        &self,
334        transpiler: &SciRS2GraphTranspiler,
335        circuit: &quantrs2_circuit::prelude::Circuit<N>,
336    ) -> QuantRS2Result<(quantrs2_circuit::prelude::Circuit<N>, CompilationPassResult)> {
337        let original_count = circuit.gates().len();
338
339        let optimized = transpiler
340            .optimize_circuit(circuit)
341            .map_err(|e| QuantRS2Error::InvalidInput(format!("Optimization pass failed: {}", e)))?;
342
343        let new_count = optimized.gates().len();
344        let transformed = new_count < original_count;
345        let pass = CompilationPassResult {
346            pass_name: "GateCountOptimization".to_string(),
347            transformed,
348            gate_count: new_count,
349            description: format!(
350                "Gate-count optimization: {} → {} gates",
351                original_count, new_count
352            ),
353        };
354        Ok((optimized, pass))
355    }
356}
357
358#[cfg(test)]
359mod adaptive_compiler_tests {
360    use super::*;
361    use quantrs2_circuit::prelude::Circuit;
362
363    #[test]
364    fn test_adaptive_compiler_default_config() {
365        let compiler = AdaptiveCompiler::default();
366        let mut circuit = Circuit::<2>::new();
367        let _ = circuit.h(0);
368        let _ = circuit.cnot(0, 1);
369
370        let result = compiler.compile(circuit);
371        assert!(result.is_ok(), "Compilation should succeed");
372        let (_, summary) = result.expect("Compilation result");
373        assert!(!summary.passes.is_empty(), "Should have applied passes");
374        assert_eq!(summary.qubit_mapping.len(), 2);
375    }
376
377    #[test]
378    fn test_adaptive_compiler_3qubit() {
379        let compiler = AdaptiveCompiler::default();
380        let mut circuit = Circuit::<3>::new();
381        let _ = circuit.h(0);
382        let _ = circuit.cnot(0, 1);
383        let _ = circuit.cnot(1, 2);
384        let _ = circuit.h(2);
385
386        let (compiled, summary) = compiler
387            .compile(circuit)
388            .expect("3-qubit compilation should succeed");
389
390        assert_eq!(summary.qubit_mapping.len(), 3);
391        assert!(compiled.gates().len() >= 4, "Gates should be preserved");
392    }
393}