quantrs2_circuit/optimization/
analysis.rs

1//! Circuit analysis tools
2//!
3//! This module provides tools for analyzing quantum circuits and generating optimization reports.
4
5use crate::builder::Circuit;
6use crate::optimization::gate_properties::get_gate_properties;
7use quantrs2_core::error::QuantRS2Result;
8use quantrs2_core::gate::GateOp;
9use quantrs2_core::qubit::QubitId;
10use std::collections::{HashMap, HashSet};
11
12use std::fmt::Write;
13/// Metrics for a quantum circuit
14#[derive(Debug, Clone)]
15pub struct CircuitMetrics {
16    /// Total number of gates
17    pub gate_count: usize,
18    /// Number of each gate type
19    pub gate_types: HashMap<String, usize>,
20    /// Circuit depth
21    pub depth: usize,
22    /// Two-qubit gate count
23    pub two_qubit_gates: usize,
24    /// Number of qubits used
25    pub num_qubits: usize,
26    /// Critical path length
27    pub critical_path: usize,
28    /// Total execution time estimate (ns)
29    pub execution_time: f64,
30    /// Total error estimate
31    pub total_error: f64,
32    /// Gate density (gates per qubit)
33    pub gate_density: f64,
34    /// Parallelism factor
35    pub parallelism: f64,
36}
37
38impl CircuitMetrics {
39    /// Calculate improvement percentage compared to another metric
40    #[must_use]
41    pub fn improvement_from(&self, other: &Self) -> MetricImprovement {
42        MetricImprovement {
43            gate_count: Self::percent_change(other.gate_count as f64, self.gate_count as f64),
44            depth: Self::percent_change(other.depth as f64, self.depth as f64),
45            two_qubit_gates: Self::percent_change(
46                other.two_qubit_gates as f64,
47                self.two_qubit_gates as f64,
48            ),
49            execution_time: Self::percent_change(other.execution_time, self.execution_time),
50            total_error: Self::percent_change(other.total_error, self.total_error),
51        }
52    }
53
54    fn percent_change(old_val: f64, new_val: f64) -> f64 {
55        if old_val == 0.0 {
56            0.0
57        } else {
58            ((old_val - new_val) / old_val) * 100.0
59        }
60    }
61}
62
63/// Improvement metrics
64#[derive(Debug, Clone)]
65pub struct MetricImprovement {
66    pub gate_count: f64,
67    pub depth: f64,
68    pub two_qubit_gates: f64,
69    pub execution_time: f64,
70    pub total_error: f64,
71}
72
73/// Circuit analyzer
74pub struct CircuitAnalyzer {
75    analyze_parallelism: bool,
76    analyze_critical_path: bool,
77}
78
79impl CircuitAnalyzer {
80    /// Create a new circuit analyzer
81    #[must_use]
82    pub const fn new() -> Self {
83        Self {
84            analyze_parallelism: true,
85            analyze_critical_path: true,
86        }
87    }
88
89    /// Analyze a circuit and compute metrics
90    pub fn analyze<const N: usize>(&self, circuit: &Circuit<N>) -> QuantRS2Result<CircuitMetrics> {
91        let stats = circuit.get_stats();
92
93        // Calculate execution time estimate (simplified model)
94        let mut execution_time = 0.0;
95        for gate in circuit.gates() {
96            execution_time += self.estimate_gate_time(gate.as_ref());
97        }
98
99        // Calculate total error estimate
100        let total_error = self.estimate_total_error(circuit);
101
102        // Calculate parallelism (average gates per layer)
103        let parallelism = if stats.depth > 0 {
104            stats.total_gates as f64 / stats.depth as f64
105        } else {
106            0.0
107        };
108
109        Ok(CircuitMetrics {
110            gate_count: stats.total_gates,
111            gate_types: stats.gate_counts,
112            depth: stats.depth,
113            two_qubit_gates: stats.two_qubit_gates,
114            num_qubits: stats.used_qubits,
115            critical_path: stats.depth, // For now, same as depth
116            execution_time,
117            total_error,
118            gate_density: stats.gate_density,
119            parallelism,
120        })
121    }
122
123    /// Estimate execution time for a single gate
124    fn estimate_gate_time(&self, gate: &dyn GateOp) -> f64 {
125        match gate.name() {
126            // Single qubit gates (fast)
127            "H" | "X" | "Y" | "Z" | "S" | "T" | "RX" | "RY" | "RZ" => 50.0, // nanoseconds
128            // Two qubit gates (slower)
129            "CNOT" | "CX" | "CZ" | "CY" | "SWAP" | "CRX" | "CRY" | "CRZ" => 200.0,
130            // Multi qubit gates (slowest)
131            "Toffoli" | "Fredkin" | "CSWAP" => 500.0,
132            // Measurements
133            "measure" => 1000.0,
134            // Unknown gates
135            _ => 100.0,
136        }
137    }
138
139    /// Estimate total error for the circuit
140    fn estimate_total_error<const N: usize>(&self, circuit: &Circuit<N>) -> f64 {
141        let mut total_error = 0.0;
142
143        for gate in circuit.gates() {
144            total_error += self.estimate_gate_error(gate.as_ref());
145        }
146
147        // Add coherence errors based on circuit depth and execution time
148        let stats = circuit.get_stats();
149        let coherence_error = stats.depth as f64 * 0.001; // 0.1% error per depth layer
150
151        total_error + coherence_error
152    }
153
154    /// Estimate error for a single gate
155    fn estimate_gate_error(&self, gate: &dyn GateOp) -> f64 {
156        match gate.name() {
157            // Single qubit gates (low error)
158            "H" | "X" | "Y" | "Z" | "S" | "T" => 0.0001,
159            // Rotation gates (medium error)
160            "RX" | "RY" | "RZ" => 0.0005,
161            // Two qubit gates (higher error)
162            "CNOT" | "CX" | "CZ" | "CY" | "SWAP" => 0.01,
163            // Controlled rotations (higher error)
164            "CRX" | "CRY" | "CRZ" => 0.015,
165            // Multi qubit gates (highest error)
166            "Toffoli" | "Fredkin" | "CSWAP" => 0.05,
167            // Measurements (readout error)
168            "measure" => 0.02,
169            // Unknown gates
170            _ => 0.01,
171        }
172    }
173
174    /// Analyze gate sequence (helper for when we have gate list)
175    #[must_use]
176    pub fn analyze_gates(&self, gates: &[Box<dyn GateOp>], num_qubits: usize) -> CircuitMetrics {
177        let mut gate_types = HashMap::new();
178        let mut two_qubit_gates = 0;
179        let mut execution_time = 0.0;
180        let mut total_error = 0.0;
181
182        // Count gates and accumulate costs
183        for gate in gates {
184            let gate_name = gate.name().to_string();
185            *gate_types.entry(gate_name).or_insert(0) += 1;
186
187            if gate.num_qubits() == 2 {
188                two_qubit_gates += 1;
189            }
190
191            let props = get_gate_properties(gate.as_ref());
192            execution_time += props.cost.duration_ns;
193            total_error += props.error.total_error();
194        }
195
196        let depth = if self.analyze_critical_path {
197            self.calculate_depth(gates)
198        } else {
199            gates.len()
200        };
201
202        let critical_path = if self.analyze_critical_path {
203            self.calculate_critical_path(gates)
204        } else {
205            depth
206        };
207
208        let parallelism = if self.analyze_parallelism && depth > 0 {
209            gates.len() as f64 / depth as f64
210        } else {
211            1.0
212        };
213
214        CircuitMetrics {
215            gate_count: gates.len(),
216            gate_types,
217            depth,
218            two_qubit_gates,
219            num_qubits,
220            critical_path,
221            execution_time,
222            total_error,
223            gate_density: gates.len() as f64 / num_qubits as f64,
224            parallelism,
225        }
226    }
227
228    /// Calculate circuit depth
229    fn calculate_depth(&self, gates: &[Box<dyn GateOp>]) -> usize {
230        let mut qubit_depths: HashMap<u32, usize> = HashMap::new();
231        let mut max_depth = 0;
232
233        for gate in gates {
234            let gate_qubits = gate.qubits();
235
236            // Find the maximum depth among involved qubits
237            let current_depth = gate_qubits
238                .iter()
239                .map(|q| qubit_depths.get(&q.id()).copied().unwrap_or(0))
240                .max()
241                .unwrap_or(0);
242
243            // Update depth for all involved qubits
244            let new_depth = current_depth + 1;
245            for qubit in gate_qubits {
246                qubit_depths.insert(qubit.id(), new_depth);
247            }
248
249            max_depth = max_depth.max(new_depth);
250        }
251
252        max_depth
253    }
254
255    /// Calculate critical path length
256    fn calculate_critical_path(&self, gates: &[Box<dyn GateOp>]) -> usize {
257        // Build dependency graph
258        let mut dependencies: Vec<HashSet<usize>> = vec![HashSet::new(); gates.len()];
259        let mut qubit_last_gate: HashMap<u32, usize> = HashMap::new();
260
261        for (i, gate) in gates.iter().enumerate() {
262            for qubit in gate.qubits() {
263                if let Some(&prev_gate) = qubit_last_gate.get(&qubit.id()) {
264                    dependencies[i].insert(prev_gate);
265                }
266                qubit_last_gate.insert(qubit.id(), i);
267            }
268        }
269
270        // Calculate longest path
271        let mut path_lengths = vec![0; gates.len()];
272        let mut max_path = 0;
273
274        for i in 0..gates.len() {
275            let max_dep_length = dependencies[i]
276                .iter()
277                .map(|&j| path_lengths[j])
278                .max()
279                .unwrap_or(0);
280
281            path_lengths[i] = max_dep_length + 1;
282            max_path = max_path.max(path_lengths[i]);
283        }
284
285        max_path
286    }
287}
288
289impl Default for CircuitAnalyzer {
290    fn default() -> Self {
291        Self::new()
292    }
293}
294
295/// Optimization report
296#[derive(Debug)]
297pub struct OptimizationReport {
298    /// Initial circuit metrics
299    pub initial_metrics: CircuitMetrics,
300    /// Final circuit metrics
301    pub final_metrics: CircuitMetrics,
302    /// List of applied optimization passes
303    pub applied_passes: Vec<String>,
304}
305
306impl OptimizationReport {
307    /// Get improvement metrics
308    #[must_use]
309    pub fn improvement(&self) -> MetricImprovement {
310        self.final_metrics.improvement_from(&self.initial_metrics)
311    }
312
313    /// Print a summary of the optimization
314    pub fn print_summary(&self) {
315        println!("=== Circuit Optimization Report ===");
316        println!();
317        println!("Initial Metrics:");
318        println!("  Gate count: {}", self.initial_metrics.gate_count);
319        println!("  Depth: {}", self.initial_metrics.depth);
320        println!(
321            "  Two-qubit gates: {}",
322            self.initial_metrics.two_qubit_gates
323        );
324        println!(
325            "  Execution time: {:.2} ns",
326            self.initial_metrics.execution_time
327        );
328        println!("  Total error: {:.6}", self.initial_metrics.total_error);
329        println!();
330        println!("Final Metrics:");
331        println!("  Gate count: {}", self.final_metrics.gate_count);
332        println!("  Depth: {}", self.final_metrics.depth);
333        println!("  Two-qubit gates: {}", self.final_metrics.two_qubit_gates);
334        println!(
335            "  Execution time: {:.2} ns",
336            self.final_metrics.execution_time
337        );
338        println!("  Total error: {:.6}", self.final_metrics.total_error);
339        println!();
340        println!("Improvements:");
341        let improvement = self.improvement();
342        println!("  Gate count: {:.1}%", improvement.gate_count);
343        println!("  Depth: {:.1}%", improvement.depth);
344        println!("  Two-qubit gates: {:.1}%", improvement.two_qubit_gates);
345        println!("  Execution time: {:.1}%", improvement.execution_time);
346        println!("  Total error: {:.1}%", improvement.total_error);
347        println!();
348        println!("Applied Passes:");
349        for pass in &self.applied_passes {
350            println!("  - {pass}");
351        }
352    }
353
354    /// Generate a detailed report as string
355    #[must_use]
356    pub fn detailed_report(&self) -> String {
357        let mut report = String::new();
358
359        report.push_str("=== Detailed Circuit Optimization Report ===\n\n");
360
361        // Gate type breakdown
362        report.push_str("Gate Type Breakdown:\n");
363        report.push_str("Initial:\n");
364        for (gate_type, count) in &self.initial_metrics.gate_types {
365            let _ = writeln!(report, "  {gate_type}: {count}");
366        }
367        report.push_str("Final:\n");
368        for (gate_type, count) in &self.final_metrics.gate_types {
369            let _ = writeln!(report, "  {gate_type}: {count}");
370        }
371
372        // Additional metrics
373        report.push_str("\nGate Density:\n");
374        let _ = writeln!(
375            report,
376            "  Initial: {:.2} gates/qubit",
377            self.initial_metrics.gate_density
378        );
379        let _ = writeln!(
380            report,
381            "  Final: {:.2} gates/qubit",
382            self.final_metrics.gate_density
383        );
384
385        report.push_str("\nParallelism Factor:\n");
386        let _ = writeln!(report, "  Initial: {:.2}", self.initial_metrics.parallelism);
387        let _ = writeln!(report, "  Final: {:.2}", self.final_metrics.parallelism);
388
389        report
390    }
391}