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