quantrs2_circuit/optimization/
analysis.rs1use 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#[derive(Debug, Clone)]
15pub struct CircuitMetrics {
16 pub gate_count: usize,
18 pub gate_types: HashMap<String, usize>,
20 pub depth: usize,
22 pub two_qubit_gates: usize,
24 pub num_qubits: usize,
26 pub critical_path: usize,
28 pub execution_time: f64,
30 pub total_error: f64,
32 pub gate_density: f64,
34 pub parallelism: f64,
36}
37
38impl CircuitMetrics {
39 #[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#[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
73pub struct CircuitAnalyzer {
75 analyze_parallelism: bool,
76 analyze_critical_path: bool,
77}
78
79impl CircuitAnalyzer {
80 #[must_use]
82 pub const fn new() -> Self {
83 Self {
84 analyze_parallelism: true,
85 analyze_critical_path: true,
86 }
87 }
88
89 pub fn analyze<const N: usize>(&self, circuit: &Circuit<N>) -> QuantRS2Result<CircuitMetrics> {
91 let stats = circuit.get_stats();
92
93 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 let total_error = self.estimate_total_error(circuit);
101
102 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, execution_time,
117 total_error,
118 gate_density: stats.gate_density,
119 parallelism,
120 })
121 }
122
123 fn estimate_gate_time(&self, gate: &dyn GateOp) -> f64 {
125 match gate.name() {
126 "H" | "X" | "Y" | "Z" | "S" | "T" | "RX" | "RY" | "RZ" => 50.0, "CNOT" | "CX" | "CZ" | "CY" | "SWAP" | "CRX" | "CRY" | "CRZ" => 200.0,
130 "Toffoli" | "Fredkin" | "CSWAP" => 500.0,
132 "measure" => 1000.0,
134 _ => 100.0,
136 }
137 }
138
139 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 let stats = circuit.get_stats();
149 let coherence_error = stats.depth as f64 * 0.001; total_error + coherence_error
152 }
153
154 fn estimate_gate_error(&self, gate: &dyn GateOp) -> f64 {
156 match gate.name() {
157 "H" | "X" | "Y" | "Z" | "S" | "T" => 0.0001,
159 "RX" | "RY" | "RZ" => 0.0005,
161 "CNOT" | "CX" | "CZ" | "CY" | "SWAP" => 0.01,
163 "CRX" | "CRY" | "CRZ" => 0.015,
165 "Toffoli" | "Fredkin" | "CSWAP" => 0.05,
167 "measure" => 0.02,
169 _ => 0.01,
171 }
172 }
173
174 #[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 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 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 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 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 fn calculate_critical_path(&self, gates: &[Box<dyn GateOp>]) -> usize {
257 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 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#[derive(Debug)]
297pub struct OptimizationReport {
298 pub initial_metrics: CircuitMetrics,
300 pub final_metrics: CircuitMetrics,
302 pub applied_passes: Vec<String>,
304}
305
306impl OptimizationReport {
307 #[must_use]
309 pub fn improvement(&self) -> MetricImprovement {
310 self.final_metrics.improvement_from(&self.initial_metrics)
311 }
312
313 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 #[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 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 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}