1use crate::builder::Circuit;
7use quantrs2_core::qubit::QubitId;
8use std::collections::{HashMap, HashSet};
9
10#[derive(Debug, Clone, PartialEq)]
12pub enum OptGate {
13 Single(QubitId, String, Vec<f64>),
14 Double(QubitId, QubitId, String, Vec<f64>),
15 Multi(Vec<QubitId>, String, Vec<f64>),
16}
17
18impl OptGate {
19 fn from_gate_op(gate: &dyn quantrs2_core::gate::GateOp) -> Self {
21 let qubits = gate.qubits();
22 let name = gate.name().to_string();
23 let params = Vec::new(); match qubits.len() {
26 1 => Self::Single(qubits[0], name, params),
27 2 => Self::Double(qubits[0], qubits[1], name, params),
28 _ => Self::Multi(qubits, name, params),
29 }
30 }
31}
32
33pub struct OptimizationContext<const N: usize> {
35 pub circuit: Circuit<N>,
36 pub gate_count: usize,
37 pub depth: usize,
38}
39
40pub struct PassResult<const N: usize> {
42 pub circuit: Circuit<N>,
43 pub improved: bool,
44 pub improvement: f64,
45}
46
47pub struct SingleQubitGateFusion;
49
50impl SingleQubitGateFusion {
51 #[must_use]
52 pub fn apply<const N: usize>(&self, ctx: &OptimizationContext<N>) -> PassResult<N> {
53 let gates = ctx.circuit.gates();
54 if gates.len() < 2 {
55 return PassResult {
56 circuit: ctx.circuit.clone(),
57 improved: false,
58 improvement: 0.0,
59 };
60 }
61
62 let opt_gates: Vec<OptGate> = gates
64 .iter()
65 .map(|g| OptGate::from_gate_op(g.as_ref()))
66 .collect();
67
68 let mut fusion_groups: Vec<Vec<usize>> = Vec::new();
70 let mut current_group: Vec<usize> = vec![];
71 let mut current_qubit: Option<QubitId> = None;
72
73 for (idx, opt_gate) in opt_gates.iter().enumerate() {
74 if let OptGate::Single(qubit, _, _) = opt_gate {
75 if current_qubit == Some(*qubit) {
76 current_group.push(idx);
78 } else {
79 if current_group.len() >= 2 {
81 fusion_groups.push(current_group.clone());
82 }
83 current_group = vec![idx];
84 current_qubit = Some(*qubit);
85 }
86 } else {
87 if current_group.len() >= 2 {
89 fusion_groups.push(current_group.clone());
90 }
91 current_group.clear();
92 current_qubit = None;
93 }
94 }
95
96 if current_group.len() >= 2 {
98 fusion_groups.push(current_group);
99 }
100
101 if fusion_groups.is_empty() {
102 return PassResult {
103 circuit: ctx.circuit.clone(),
104 improved: false,
105 improvement: 0.0,
106 };
107 }
108
109 let mut gates_that_could_be_fused = 0;
115 for group in &fusion_groups {
116 gates_that_could_be_fused += group.len() - 1; }
118
119 PassResult {
122 circuit: ctx.circuit.clone(),
123 improved: false,
124 improvement: 0.0, }
126 }
127
128 #[must_use]
129 pub const fn name(&self) -> &'static str {
130 "Single-Qubit Gate Fusion"
131 }
132}
133
134pub struct RedundantGateElimination;
136
137impl RedundantGateElimination {
138 #[allow(dead_code)]
140 fn gates_cancel(gate1: &OptGate, gate2: &OptGate) -> bool {
141 match (gate1, gate2) {
142 (OptGate::Single(q1, name1, _), OptGate::Single(q2, name2, _)) => {
143 if q1 != q2 {
144 return false;
145 }
146
147 matches!(
149 (name1.as_str(), name2.as_str()),
150 ("X", "X") | ("Y", "Y") | ("Z", "Z") | ("H", "H") | ("CNOT", "CNOT")
151 )
152 }
153 _ => false,
154 }
155 }
156
157 #[must_use]
158 pub fn apply<const N: usize>(&self, ctx: &OptimizationContext<N>) -> PassResult<N> {
159 let gates = ctx.circuit.gates();
160 if gates.len() < 2 {
161 return PassResult {
162 circuit: ctx.circuit.clone(),
163 improved: false,
164 improvement: 0.0,
165 };
166 }
167
168 let mut to_remove = HashSet::new();
170
171 let opt_gates: Vec<OptGate> = gates
173 .iter()
174 .map(|g| OptGate::from_gate_op(g.as_ref()))
175 .collect();
176
177 let mut i = 0;
179 while i < opt_gates.len() - 1 {
180 if !to_remove.contains(&i)
181 && !to_remove.contains(&(i + 1))
182 && Self::gates_cancel(&opt_gates[i], &opt_gates[i + 1])
183 {
184 to_remove.insert(i);
185 to_remove.insert(i + 1);
186 i += 2; continue;
188 }
189 i += 1;
190 }
191
192 if to_remove.is_empty() {
193 return PassResult {
194 circuit: ctx.circuit.clone(),
195 improved: false,
196 improvement: 0.0,
197 };
198 }
199
200 let mut new_circuit = Circuit::<N>::with_capacity(gates.len() - to_remove.len());
202 for (idx, gate) in gates.iter().enumerate() {
203 if !to_remove.contains(&idx) {
204 let _ = new_circuit.add_gate_arc(gate.clone());
205 }
206 }
207
208 let gates_removed = to_remove.len();
209 let improvement = gates_removed as f64; PassResult {
212 circuit: new_circuit,
213 improved: gates_removed > 0,
214 improvement,
215 }
216 }
217
218 #[must_use]
219 pub const fn name(&self) -> &'static str {
220 "Redundant Gate Elimination"
221 }
222}
223
224pub struct CommutationOptimizer;
226
227impl CommutationOptimizer {
228 #[allow(dead_code)]
230 fn gates_commute(gate1: &OptGate, gate2: &OptGate) -> bool {
231 match (gate1, gate2) {
232 (OptGate::Single(q1, name1, _), OptGate::Single(q2, name2, _)) => {
235 if q1 == q2 {
236 name1 == "Z" && name2 == "Z"
238 } else {
239 true
240 }
241 }
242
243 (OptGate::Double(c1, t1, name1, _), OptGate::Double(c2, t2, name2, _)) => {
245 name1 == "CNOT" && name2 == "CNOT" && c1 != c2 && c1 != t2 && t1 != c2 && t1 != t2
246 }
247
248 _ => false,
249 }
250 }
251
252 #[must_use]
253 pub fn apply<const N: usize>(&self, ctx: &OptimizationContext<N>) -> PassResult<N> {
254 let gates = ctx.circuit.gates();
255 if gates.len() < 2 {
256 return PassResult {
257 circuit: ctx.circuit.clone(),
258 improved: false,
259 improvement: 0.0,
260 };
261 }
262
263 let opt_gates: Vec<OptGate> = gates
265 .iter()
266 .map(|g| OptGate::from_gate_op(g.as_ref()))
267 .collect();
268
269 let mut reordered_indices: Vec<usize> = (0..gates.len()).collect();
272 let mut made_changes = false;
273
274 for _ in 0..3 {
276 let mut i = 0;
277 while i + 1 < reordered_indices.len() {
278 let idx1 = reordered_indices[i];
279 let idx2 = reordered_indices[i + 1];
280
281 if Self::gates_commute(&opt_gates[idx1], &opt_gates[idx2]) {
282 let should_swap = matches!(opt_gates[idx2], OptGate::Single(_, _, _))
285 && !matches!(opt_gates[idx1], OptGate::Single(_, _, _));
286
287 if should_swap {
288 reordered_indices.swap(i, i + 1);
289 made_changes = true;
290 }
291 }
292 i += 1;
293 }
294 }
295
296 if !made_changes {
297 return PassResult {
298 circuit: ctx.circuit.clone(),
299 improved: false,
300 improvement: 0.0,
301 };
302 }
303
304 let mut new_circuit = Circuit::<N>::with_capacity(gates.len());
306 for idx in reordered_indices {
307 let _ = new_circuit.add_gate_arc(gates[idx].clone());
308 }
309
310 let old_depth = ctx.circuit.calculate_depth() as f64;
312 let new_depth = new_circuit.calculate_depth() as f64;
313 let improvement = (old_depth - new_depth).max(0.0);
314
315 PassResult {
316 circuit: new_circuit,
317 improved: improvement > 0.0,
318 improvement,
319 }
320 }
321
322 #[must_use]
323 pub const fn name(&self) -> &'static str {
324 "Commutation-Based Optimization"
325 }
326}
327
328pub struct PeepholeOptimizer {
330 #[allow(dead_code)]
331 patterns: Vec<PatternRule>,
332}
333
334#[derive(Clone)]
335#[allow(dead_code)]
336struct PatternRule {
337 pattern: Vec<OptGate>,
338 replacement: Vec<OptGate>,
339 name: String,
340}
341
342impl Default for PeepholeOptimizer {
343 fn default() -> Self {
344 let patterns = vec![
345 PatternRule {
347 pattern: vec![
348 OptGate::Single(QubitId::new(0), "H".to_string(), vec![]),
349 OptGate::Single(QubitId::new(0), "X".to_string(), vec![]),
350 OptGate::Single(QubitId::new(0), "H".to_string(), vec![]),
351 ],
352 replacement: vec![OptGate::Single(QubitId::new(0), "Z".to_string(), vec![])],
353 name: "H-X-H to Z".to_string(),
354 },
355 PatternRule {
357 pattern: vec![
358 OptGate::Single(QubitId::new(0), "H".to_string(), vec![]),
359 OptGate::Single(QubitId::new(0), "Z".to_string(), vec![]),
360 OptGate::Single(QubitId::new(0), "H".to_string(), vec![]),
361 ],
362 replacement: vec![OptGate::Single(QubitId::new(0), "X".to_string(), vec![])],
363 name: "H-Z-H to X".to_string(),
364 },
365 ];
366
367 Self { patterns }
368 }
369}
370
371impl PeepholeOptimizer {
372 fn matches_pattern(gates: &[OptGate], pattern: &[OptGate]) -> bool {
374 if gates.len() != pattern.len() {
375 return false;
376 }
377
378 for (gate, pat) in gates.iter().zip(pattern.iter()) {
379 match (gate, pat) {
380 (OptGate::Single(q1, n1, _), OptGate::Single(q2, n2, _)) => {
381 if q1 != q2 || n1 != n2 {
382 return false;
383 }
384 }
385 _ => return false, }
387 }
388 true
389 }
390
391 #[must_use]
392 pub fn apply<const N: usize>(&self, ctx: &OptimizationContext<N>) -> PassResult<N> {
393 let gates = ctx.circuit.gates();
394 if gates.len() < 3 {
395 return PassResult {
396 circuit: ctx.circuit.clone(),
397 improved: false,
398 improvement: 0.0,
399 };
400 }
401
402 let opt_gates: Vec<OptGate> = gates
404 .iter()
405 .map(|g| OptGate::from_gate_op(g.as_ref()))
406 .collect();
407
408 let mut replacements: Vec<(usize, usize, String, QubitId)> = Vec::new();
410
411 let mut i = 0;
413 while i + 2 < opt_gates.len() {
414 if let (
416 OptGate::Single(q1, n1, _),
417 OptGate::Single(q2, n2, _),
418 OptGate::Single(q3, n3, _),
419 ) = (&opt_gates[i], &opt_gates[i + 1], &opt_gates[i + 2])
420 {
421 if q1 == q2 && q2 == q3 {
422 if n1 == "H" && n2 == "X" && n3 == "H" {
423 replacements.push((i, i + 2, "Z".to_string(), *q1));
424 i += 3;
425 continue;
426 } else if n1 == "H" && n2 == "Z" && n3 == "H" {
427 replacements.push((i, i + 2, "X".to_string(), *q1));
428 i += 3;
429 continue;
430 }
431 }
432 }
433 i += 1;
434 }
435
436 if replacements.is_empty() {
437 return PassResult {
438 circuit: ctx.circuit.clone(),
439 improved: false,
440 improvement: 0.0,
441 };
442 }
443
444 let mut new_circuit = Circuit::<N>::new();
446 let mut idx = 0;
447 let mut replacement_iter = replacements.iter().peekable();
448
449 while idx < gates.len() {
450 if let Some((start, end, replacement_name, qubit)) = replacement_iter.peek() {
451 if idx == *start {
452 match replacement_name.as_str() {
454 "X" => {
455 let _ = new_circuit
456 .add_gate(quantrs2_core::gate::single::PauliX { target: *qubit });
457 }
458 "Z" => {
459 let _ = new_circuit
460 .add_gate(quantrs2_core::gate::single::PauliZ { target: *qubit });
461 }
462 _ => {}
463 }
464 idx = *end + 1;
465 replacement_iter.next();
466 continue;
467 }
468 }
469 let _ = new_circuit.add_gate_arc(gates[idx].clone());
471 idx += 1;
472 }
473
474 let gates_saved = replacements.len() * 2; let improvement = gates_saved as f64;
476
477 PassResult {
478 circuit: new_circuit,
479 improved: !replacements.is_empty(),
480 improvement,
481 }
482 }
483
484 #[must_use]
485 pub const fn name(&self) -> &'static str {
486 "Peephole Optimization"
487 }
488}
489
490pub struct TemplateOptimizer {
492 #[allow(dead_code)]
493 templates: Vec<Template>,
494}
495
496#[allow(dead_code)]
497struct Template {
498 name: String,
499 pattern: Vec<OptGate>,
500 cost_reduction: f64,
501}
502
503impl Default for TemplateOptimizer {
504 fn default() -> Self {
505 let templates = vec![Template {
506 name: "Toffoli Decomposition".to_string(),
507 pattern: vec![], cost_reduction: 0.3,
509 }];
510
511 Self { templates }
512 }
513}
514
515impl TemplateOptimizer {
516 #[must_use]
517 pub fn apply<const N: usize>(&self, ctx: &OptimizationContext<N>) -> PassResult<N> {
518 PassResult {
520 circuit: ctx.circuit.clone(),
521 improved: false,
522 improvement: 0.0,
523 }
524 }
525
526 #[must_use]
527 pub const fn name(&self) -> &'static str {
528 "Template Matching Optimization"
529 }
530}
531
532pub enum OptimizationPassType {
534 SingleQubitFusion(SingleQubitGateFusion),
535 RedundantElimination(RedundantGateElimination),
536 Commutation(CommutationOptimizer),
537 Peephole(PeepholeOptimizer),
538 Template(TemplateOptimizer),
539 Hardware(HardwareOptimizer),
540}
541
542impl OptimizationPassType {
543 #[must_use]
544 pub fn apply<const N: usize>(&self, ctx: &OptimizationContext<N>) -> PassResult<N> {
545 match self {
546 Self::SingleQubitFusion(p) => p.apply(ctx),
547 Self::RedundantElimination(p) => p.apply(ctx),
548 Self::Commutation(p) => p.apply(ctx),
549 Self::Peephole(p) => p.apply(ctx),
550 Self::Template(p) => p.apply(ctx),
551 Self::Hardware(p) => p.apply(ctx),
552 }
553 }
554
555 #[must_use]
556 pub const fn name(&self) -> &str {
557 match self {
558 Self::SingleQubitFusion(p) => p.name(),
559 Self::RedundantElimination(p) => p.name(),
560 Self::Commutation(p) => p.name(),
561 Self::Peephole(p) => p.name(),
562 Self::Template(p) => p.name(),
563 Self::Hardware(p) => p.name(),
564 }
565 }
566}
567
568pub struct CircuitOptimizer<const N: usize> {
570 passes: Vec<OptimizationPassType>,
571 max_iterations: usize,
572}
573
574impl<const N: usize> Default for CircuitOptimizer<N> {
575 fn default() -> Self {
576 Self::new()
577 }
578}
579
580impl<const N: usize> CircuitOptimizer<N> {
581 #[must_use]
583 pub fn new() -> Self {
584 let passes = vec![
585 OptimizationPassType::RedundantElimination(RedundantGateElimination),
586 OptimizationPassType::SingleQubitFusion(SingleQubitGateFusion),
587 OptimizationPassType::Commutation(CommutationOptimizer),
588 OptimizationPassType::Peephole(PeepholeOptimizer::default()),
589 OptimizationPassType::Template(TemplateOptimizer::default()),
590 ];
591
592 Self {
593 passes,
594 max_iterations: 10,
595 }
596 }
597
598 #[must_use]
600 pub const fn with_passes(passes: Vec<OptimizationPassType>) -> Self {
601 Self {
602 passes,
603 max_iterations: 10,
604 }
605 }
606
607 #[must_use]
609 pub const fn with_max_iterations(mut self, max_iterations: usize) -> Self {
610 self.max_iterations = max_iterations;
611 self
612 }
613
614 #[must_use]
616 pub fn add_pass(mut self, pass: OptimizationPassType) -> Self {
617 self.passes.push(pass);
618 self
619 }
620
621 #[must_use]
623 pub fn optimize(&self, circuit: &Circuit<N>) -> OptimizationResult<N> {
624 let mut current_circuit = circuit.clone();
625 let mut total_iterations = 0;
626 let mut pass_statistics = HashMap::new();
627
628 let initial_cost = self.estimate_cost(¤t_circuit);
630 let mut current_cost = initial_cost;
631
632 for iteration in 0..self.max_iterations {
634 let iteration_start_cost = current_cost;
635
636 for pass in &self.passes {
637 let pass_name = pass.name().to_string();
638 let before_cost = current_cost;
639
640 let ctx = OptimizationContext {
641 circuit: current_circuit.clone(),
642 gate_count: 10, depth: 5, };
645
646 let result = pass.apply(&ctx);
647 current_circuit = result.circuit;
648
649 if result.improved {
650 current_cost -= result.improvement;
651 }
652
653 let improvement = before_cost - current_cost;
654 pass_statistics
655 .entry(pass_name)
656 .and_modify(|stats: &mut PassStats| {
657 stats.applications += 1;
658 stats.total_improvement += improvement;
659 })
660 .or_insert(PassStats {
661 applications: 1,
662 total_improvement: improvement,
663 });
664 }
665
666 total_iterations = iteration + 1;
667
668 if (iteration_start_cost - current_cost).abs() < 1e-10 {
670 break;
671 }
672 }
673
674 OptimizationResult {
675 optimized_circuit: current_circuit,
676 initial_cost,
677 final_cost: current_cost,
678 iterations: total_iterations,
679 pass_statistics,
680 }
681 }
682
683 fn estimate_cost(&self, circuit: &Circuit<N>) -> f64 {
685 let stats = circuit.get_stats();
686
687 let single_qubit_cost = 1.0;
689 let two_qubit_cost = 10.0; let multi_qubit_cost = 50.0; let single_qubit_gates =
694 stats.total_gates - stats.two_qubit_gates - stats.multi_qubit_gates;
695 let gate_cost = single_qubit_gates as f64 * single_qubit_cost
696 + stats.two_qubit_gates as f64 * two_qubit_cost
697 + stats.multi_qubit_gates as f64 * multi_qubit_cost;
698
699 let depth_cost = stats.depth as f64 * 2.0;
701
702 gate_cost + depth_cost
703 }
704}
705
706#[derive(Debug, Clone)]
708pub struct PassStats {
709 pub applications: usize,
710 pub total_improvement: f64,
711}
712
713#[derive(Debug, Clone)]
715pub struct OptimizationResult<const N: usize> {
716 pub optimized_circuit: Circuit<N>,
717 pub initial_cost: f64,
718 pub final_cost: f64,
719 pub iterations: usize,
720 pub pass_statistics: HashMap<String, PassStats>,
721}
722
723impl<const N: usize> OptimizationResult<N> {
724 #[must_use]
726 pub fn improvement_ratio(&self) -> f64 {
727 if self.initial_cost > 0.0 {
728 (self.initial_cost - self.final_cost) / self.initial_cost
729 } else {
730 0.0
731 }
732 }
733
734 pub fn print_summary(&self) {
736 println!("Circuit Optimization Summary");
737 println!("===========================");
738 println!("Initial cost: {:.2}", self.initial_cost);
739 println!("Final cost: {:.2}", self.final_cost);
740 println!("Improvement: {:.1}%", self.improvement_ratio() * 100.0);
741 println!("Iterations: {}", self.iterations);
742 println!("\nPass Statistics:");
743
744 for (pass_name, stats) in &self.pass_statistics {
745 if stats.total_improvement > 0.0 {
746 println!(
747 " {}: {} applications, {:.2} total improvement",
748 pass_name, stats.applications, stats.total_improvement
749 );
750 }
751 }
752 }
753}
754
755pub struct HardwareOptimizer {
757 #[allow(dead_code)]
758 connectivity: Vec<(usize, usize)>,
759 #[allow(dead_code)]
760 native_gates: HashSet<String>,
761}
762
763impl HardwareOptimizer {
764 #[must_use]
765 pub const fn new(connectivity: Vec<(usize, usize)>, native_gates: HashSet<String>) -> Self {
766 Self {
767 connectivity,
768 native_gates,
769 }
770 }
771
772 #[must_use]
773 pub fn apply<const N: usize>(&self, ctx: &OptimizationContext<N>) -> PassResult<N> {
774 PassResult {
777 circuit: ctx.circuit.clone(),
778 improved: false,
779 improvement: 0.0,
780 }
781 }
782
783 #[must_use]
784 pub const fn name(&self) -> &'static str {
785 "Hardware-Aware Optimization"
786 }
787}
788
789#[cfg(test)]
790mod tests {
791 use super::*;
792
793 #[test]
794 fn test_circuit_optimizer_creation() {
795 let optimizer = CircuitOptimizer::<4>::new();
796 assert_eq!(optimizer.passes.len(), 5);
797 assert_eq!(optimizer.max_iterations, 10);
798 }
799
800 #[test]
801 fn test_optimization_result() {
802 let circuit = Circuit::<4>::new();
803 let optimizer = CircuitOptimizer::new();
804 let result = optimizer.optimize(&circuit);
805
806 assert!(result.improvement_ratio() >= 0.0);
807 assert!(result.iterations > 0);
808 }
809}