1use crate::builder::Circuit;
7use quantrs2_core::error::QuantRS2Error;
8use quantrs2_core::qubit::QubitId;
9use scirs2_core::Complex64;
10use std::collections::{HashMap, HashSet, VecDeque};
11
12#[cfg(feature = "scirs")]
14use scirs2_core::sparse::{CsrMatrix, SparseMatrix};
15#[cfg(feature = "scirs")]
16use scirs2_optimize::graph::{
17 find_critical_path, graph_coloring, minimum_feedback_arc_set, topological_sort_weighted,
18};
19
20fn matrix_multiply_2x2(a: &[Vec<Complex64>], b: &[Vec<Complex64>]) -> Vec<Vec<Complex64>> {
22 vec![
23 vec![
24 a[0][0] * b[0][0] + a[0][1] * b[1][0],
25 a[0][0] * b[0][1] + a[0][1] * b[1][1],
26 ],
27 vec![
28 a[1][0] * b[0][0] + a[1][1] * b[1][0],
29 a[1][0] * b[0][1] + a[1][1] * b[1][1],
30 ],
31 ]
32}
33
34#[derive(Debug, Clone, PartialEq)]
36pub struct GraphGate {
37 pub id: usize,
38 pub gate_type: String,
39 pub qubits: Vec<QubitId>,
40 pub params: Vec<f64>,
41 pub matrix: Option<Vec<Vec<Complex64>>>,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum EdgeType {
47 DataDependency,
49 NonCommuting,
51 Parallelizable,
53}
54
55pub struct CircuitDAG {
57 nodes: Vec<GraphGate>,
58 edges: HashMap<(usize, usize), EdgeType>,
59 qubit_chains: HashMap<u32, Vec<usize>>, }
61
62impl CircuitDAG {
63 #[must_use]
65 pub fn new() -> Self {
66 Self {
67 nodes: Vec::new(),
68 edges: HashMap::new(),
69 qubit_chains: HashMap::new(),
70 }
71 }
72
73 pub fn add_gate(&mut self, gate: GraphGate) -> usize {
75 let gate_id = self.nodes.len();
76
77 for qubit in &gate.qubits {
79 self.qubit_chains
80 .entry(qubit.id())
81 .or_default()
82 .push(gate_id);
83 }
84
85 for qubit in &gate.qubits {
87 if let Some(chain) = self.qubit_chains.get(&qubit.id()) {
88 if chain.len() > 1 {
89 let prev_gate = chain[chain.len() - 2];
90 self.edges
91 .insert((prev_gate, gate_id), EdgeType::DataDependency);
92 }
93 }
94 }
95
96 self.nodes.push(gate);
97 gate_id
98 }
99
100 fn gates_commute(&self, g1: &GraphGate, g2: &GraphGate) -> bool {
102 let qubits1: HashSet<_> = g1.qubits.iter().map(quantrs2_core::QubitId::id).collect();
104 let qubits2: HashSet<_> = g2.qubits.iter().map(quantrs2_core::QubitId::id).collect();
105
106 if qubits1.is_disjoint(&qubits2) {
107 return true;
108 }
109
110 match (g1.gate_type.as_str(), g2.gate_type.as_str()) {
112 ("z" | "rz", "z" | "rz") => true,
114 ("cnot", "cnot") => {
116 if g1.qubits.len() == 2 && g2.qubits.len() == 2 {
117 let same_control = g1.qubits[0] == g2.qubits[0];
118 let same_target = g1.qubits[1] == g2.qubits[1];
119 same_control && same_target } else {
121 false
122 }
123 }
124 _ => false,
125 }
126 }
127
128 pub fn compute_commutation_edges(&mut self) {
130 #[cfg(feature = "scirs")]
131 {
132 if self.scirs_compute_commutation_edges() {
133 return;
134 }
135 }
136
137 self.standard_compute_commutation_edges();
138 }
139
140 #[cfg(feature = "scirs")]
141 fn scirs_compute_commutation_edges(&mut self) -> bool {
143 let n = self.nodes.len();
144 if n == 0 {
145 return true;
146 }
147
148 let mut interference = vec![vec![false; n]; n];
150
151 for i in 0..n {
152 for j in i + 1..n {
153 let g1 = &self.nodes[i];
154 let g2 = &self.nodes[j];
155
156 if !self.gates_commute(g1, g2) && !self.has_path(i, j) && !self.has_path(j, i) {
158 interference[i][j] = true;
159 interference[j][i] = true;
160 }
161 }
162 }
163
164 if let Ok(coloring) = graph_coloring(&interference) {
166 for i in 0..n {
168 for j in i + 1..n {
169 if coloring[i] == coloring[j] && !interference[i][j] {
170 self.edges.insert((i, j), EdgeType::Parallelizable);
172 }
173 }
174 }
175 return true;
176 }
177
178 false
179 }
180
181 fn standard_compute_commutation_edges(&mut self) {
183 let n = self.nodes.len();
184
185 for i in 0..n {
186 for j in i + 1..n {
187 let g1 = &self.nodes[i];
188 let g2 = &self.nodes[j];
189
190 if self.edges.contains_key(&(i, j)) || self.edges.contains_key(&(j, i)) {
192 continue;
193 }
194
195 if !self.gates_commute(g1, g2) {
197 if !self.has_path(i, j) && !self.has_path(j, i) {
199 self.edges.insert((i, j), EdgeType::NonCommuting);
200 }
201 } else if g1.qubits.iter().any(|q| g2.qubits.contains(q)) {
202 self.edges.insert((i, j), EdgeType::Parallelizable);
204 }
205 }
206 }
207 }
208
209 #[cfg(feature = "scirs")]
210 pub fn optimize_with_feedback_arc_set(&self) -> Option<Vec<usize>> {
212 let n = self.nodes.len();
213 if n == 0 {
214 return Some(vec![]);
215 }
216
217 let mut edges = Vec::new();
219 let mut weights = Vec::new();
220
221 for ((u, v), edge_type) in &self.edges {
222 if *edge_type == EdgeType::NonCommuting {
223 edges.push((*u, *v));
224 let weight = self.gate_weight(*u) * self.gate_weight(*v);
226 weights.push(weight);
227 }
228 }
229
230 if let Ok(feedback_arcs) = minimum_feedback_arc_set(&edges, &weights) {
232 let mut filtered_edges = edges.clone();
234 for &arc_idx in &feedback_arcs {
235 filtered_edges[arc_idx] = (n, n); }
237 filtered_edges.retain(|&(u, v)| u < n && v < n);
238
239 let mut new_dag = CircuitDAG::new();
241 new_dag.nodes = self.nodes.clone();
242 for (u, v) in filtered_edges {
243 new_dag.edges.insert((u, v), EdgeType::DataDependency);
244 }
245
246 return Some(new_dag.optimized_topological_sort());
247 }
248
249 None
250 }
251
252 fn has_path(&self, src: usize, dst: usize) -> bool {
254 let mut visited = vec![false; self.nodes.len()];
255 let mut queue = VecDeque::new();
256
257 queue.push_back(src);
258 visited[src] = true;
259
260 while let Some(node) = queue.pop_front() {
261 if node == dst {
262 return true;
263 }
264
265 for ((u, v), edge_type) in &self.edges {
266 if *u == node && !visited[*v] && *edge_type == EdgeType::DataDependency {
267 visited[*v] = true;
268 queue.push_back(*v);
269 }
270 }
271 }
272
273 false
274 }
275
276 #[must_use]
278 pub fn optimized_topological_sort(&self) -> Vec<usize> {
279 #[cfg(feature = "scirs")]
280 {
281 if let Some(order) = self.scirs_topological_sort() {
283 return order;
284 }
285 }
286
287 self.standard_topological_sort()
289 }
290
291 #[cfg(feature = "scirs")]
292 fn scirs_topological_sort(&self) -> Option<Vec<usize>> {
294 let n = self.nodes.len();
295 if n == 0 {
296 return Some(vec![]);
297 }
298
299 let mut row_indices = Vec::new();
301 let mut col_indices = Vec::new();
302 let mut values = Vec::new();
303
304 for ((u, v), edge_type) in &self.edges {
305 if *edge_type == EdgeType::DataDependency {
306 row_indices.push(*u);
307 col_indices.push(*v);
308 let weight = self.gate_weight(*u) + self.gate_weight(*v);
310 values.push(weight);
311 }
312 }
313
314 let matrix = CsrMatrix::from_triplets(n, n, &row_indices, &col_indices, &values);
316
317 if let Ok(order) = topological_sort_weighted(&matrix, |i| self.gate_priority(i)) {
319 if let Ok(critical) = find_critical_path(&matrix, &order) {
321 return Some(self.optimize_order_by_critical_path(order, critical));
323 }
324 return Some(order);
325 }
326
327 None
328 }
329
330 fn gate_weight(&self, gate_id: usize) -> f64 {
332 let gate = &self.nodes[gate_id];
333 match gate.gate_type.as_str() {
334 "cnot" | "cz" | "swap" => 10.0,
336 "rzz" | "rxx" | "ryy" => 15.0,
337 "rx" | "ry" | "rz" => 2.0,
339 "h" | "s" | "t" | "x" | "y" | "z" => 1.0,
341 _ => 5.0,
343 }
344 }
345
346 fn gate_priority(&self, gate_id: usize) -> f64 {
348 let parallelism_score = self.count_parallel_successors(gate_id) as f64;
350 let weight = self.gate_weight(gate_id);
352 parallelism_score / weight
354 }
355
356 fn count_parallel_successors(&self, gate_id: usize) -> usize {
358 let mut count = 0;
359 for ((u, _v), edge_type) in &self.edges {
360 if *u == gate_id && *edge_type == EdgeType::Parallelizable {
361 count += 1;
362 }
363 }
364 count
365 }
366
367 #[cfg(feature = "scirs")]
368 fn optimize_order_by_critical_path(
370 &self,
371 order: Vec<usize>,
372 critical: Vec<usize>,
373 ) -> Vec<usize> {
374 let mut optimized = Vec::new();
375 let mut scheduled = HashSet::new();
376 let critical_set: HashSet<_> = critical.into_iter().collect();
377
378 for &gate_id in &order {
380 if critical_set.contains(&gate_id) && !scheduled.contains(&gate_id) {
381 optimized.push(gate_id);
382 scheduled.insert(gate_id);
383 }
384 }
385
386 for &gate_id in &order {
388 if !scheduled.contains(&gate_id) {
389 optimized.push(gate_id);
390 scheduled.insert(gate_id);
391 }
392 }
393
394 optimized
395 }
396
397 fn standard_topological_sort(&self) -> Vec<usize> {
399 let n = self.nodes.len();
400 let mut in_degree = vec![0; n];
401 let mut adj_list: HashMap<usize, Vec<usize>> = HashMap::new();
402
403 for ((u, v), edge_type) in &self.edges {
405 if *edge_type == EdgeType::DataDependency {
406 adj_list.entry(*u).or_default().push(*v);
407 in_degree[*v] += 1;
408 }
409 }
410
411 let mut ready: Vec<usize> = Vec::new();
413 for (i, °ree) in in_degree.iter().enumerate() {
414 if degree == 0 {
415 ready.push(i);
416 }
417 }
418
419 let mut result = Vec::new();
420 let mut layer_qubits: HashSet<u32> = HashSet::new();
421
422 while !ready.is_empty() {
423 ready.sort_by_key(|&i| self.nodes[i].qubits.len());
425
426 let mut next_layer = Vec::new();
428 let mut used = vec![false; ready.len()];
429
430 for (idx, &gate_id) in ready.iter().enumerate() {
431 if used[idx] {
432 continue;
433 }
434
435 let gate = &self.nodes[gate_id];
436 let gate_qubits: HashSet<_> =
437 gate.qubits.iter().map(quantrs2_core::QubitId::id).collect();
438
439 if gate_qubits.is_disjoint(&layer_qubits) {
441 next_layer.push(gate_id);
442 layer_qubits.extend(&gate_qubits);
443 used[idx] = true;
444 }
445 }
446
447 if next_layer.is_empty() && !ready.is_empty() {
449 next_layer.push(ready[0]);
450 used[0] = true;
451 }
452
453 ready.retain(|&g| !next_layer.contains(&g));
455
456 for &gate_id in &next_layer {
458 result.push(gate_id);
459
460 if let Some(neighbors) = adj_list.get(&gate_id) {
461 for &neighbor in neighbors {
462 in_degree[neighbor] -= 1;
463 if in_degree[neighbor] == 0 {
464 ready.push(neighbor);
465 }
466 }
467 }
468 }
469
470 layer_qubits.clear();
471 }
472
473 result
474 }
475}
476
477impl Default for CircuitDAG {
478 fn default() -> Self {
479 Self::new()
480 }
481}
482
483pub struct GraphOptimizer {
485 merge_threshold: f64,
486 #[allow(dead_code)]
487 max_lookahead: usize,
488}
489
490impl GraphOptimizer {
491 #[must_use]
493 pub const fn new() -> Self {
494 Self {
495 merge_threshold: 1e-6,
496 max_lookahead: 10,
497 }
498 }
499
500 pub fn circuit_to_dag<const N: usize>(
502 &self,
503 circuit: &Circuit<N>,
504 ) -> Result<CircuitDAG, QuantRS2Error> {
505 let mut dag = CircuitDAG::new();
506
507 for (gate_id, gate) in circuit.gates().iter().enumerate() {
509 let graph_gate = GraphGate {
510 id: gate_id,
511 gate_type: gate.name().to_string(),
512 qubits: gate.qubits(),
513 params: if gate.is_parameterized() {
514 match gate.name() {
516 "RX" | "RY" | "RZ" => {
517 if let Some(rx_gate) =
519 gate.as_any()
520 .downcast_ref::<quantrs2_core::gate::single::RotationX>()
521 {
522 vec![rx_gate.theta]
523 } else if let Some(ry_gate) =
524 gate.as_any()
525 .downcast_ref::<quantrs2_core::gate::single::RotationY>()
526 {
527 vec![ry_gate.theta]
528 } else if let Some(rz_gate) =
529 gate.as_any()
530 .downcast_ref::<quantrs2_core::gate::single::RotationZ>()
531 {
532 vec![rz_gate.theta]
533 } else {
534 vec![] }
536 }
537 "CRX" | "CRY" | "CRZ" => {
538 if let Some(crx_gate) = gate
540 .as_any()
541 .downcast_ref::<quantrs2_core::gate::multi::CRX>()
542 {
543 vec![crx_gate.theta]
544 } else if let Some(cry_gate) =
545 gate.as_any()
546 .downcast_ref::<quantrs2_core::gate::multi::CRY>()
547 {
548 vec![cry_gate.theta]
549 } else if let Some(crz_gate) =
550 gate.as_any()
551 .downcast_ref::<quantrs2_core::gate::multi::CRZ>()
552 {
553 vec![crz_gate.theta]
554 } else {
555 vec![]
556 }
557 }
558 _ => vec![], }
560 } else {
561 vec![] },
563 matrix: None, };
565
566 dag.add_gate(graph_gate);
567 }
568
569 Ok(dag)
570 }
571
572 #[must_use]
574 pub fn optimize_gate_sequence(&self, gates: Vec<GraphGate>) -> Vec<GraphGate> {
575 let mut dag = CircuitDAG::new();
576
577 for gate in gates {
579 dag.add_gate(gate);
580 }
581
582 dag.compute_commutation_edges();
584
585 #[cfg(feature = "scirs")]
587 {
588 if dag.nodes.len() > 10 {
590 if let Some(optimized_order) = dag.optimize_with_feedback_arc_set() {
591 return optimized_order
592 .iter()
593 .map(|&i| dag.nodes[i].clone())
594 .collect();
595 }
596 }
597 }
598
599 let order = dag.optimized_topological_sort();
601
602 self.merge_gates_in_sequence(order.iter().map(|&i| dag.nodes[i].clone()).collect())
604 }
605
606 fn merge_gates_in_sequence(&self, gates: Vec<GraphGate>) -> Vec<GraphGate> {
608 if gates.is_empty() {
609 return gates;
610 }
611
612 let mut merged = Vec::new();
613 let mut i = 0;
614
615 while i < gates.len() {
616 if i + 1 < gates.len() {
617 if let Some(merged_gate) = self.try_merge_gates(&gates[i], &gates[i + 1]) {
619 merged.push(merged_gate);
620 i += 2; continue;
622 }
623 }
624 merged.push(gates[i].clone());
625 i += 1;
626 }
627
628 merged
629 }
630
631 fn try_merge_gates(&self, g1: &GraphGate, g2: &GraphGate) -> Option<GraphGate> {
633 if g1.qubits.len() == 1 && g2.qubits.len() == 1 && g1.qubits[0] == g2.qubits[0] {
635 self.merge_single_qubit_gates(g1, g2)
636 } else {
637 None
638 }
639 }
640
641 #[must_use]
643 pub fn merge_single_qubit_gates(&self, g1: &GraphGate, g2: &GraphGate) -> Option<GraphGate> {
644 if g1.qubits.len() != 1 || g2.qubits.len() != 1 || g1.qubits[0] != g2.qubits[0] {
646 return None;
647 }
648
649 let m1 = g1.matrix.as_ref()?;
651 let m2 = g2.matrix.as_ref()?;
652
653 let combined = matrix_multiply_2x2(m2, m1);
655
656 if let Some((gate_type, params)) = self.identify_gate(&combined) {
658 Some(GraphGate {
659 id: g1.id, gate_type,
661 qubits: g1.qubits.clone(),
662 params,
663 matrix: Some(combined),
664 })
665 } else {
666 Some(GraphGate {
668 id: g1.id,
669 gate_type: "u".to_string(),
670 qubits: g1.qubits.clone(),
671 params: vec![],
672 matrix: Some(combined),
673 })
674 }
675 }
676
677 fn identify_gate(&self, matrix: &[Vec<Complex64>]) -> Option<(String, Vec<f64>)> {
679 let tolerance = self.merge_threshold;
680
681 if self.is_pauli_x(matrix, tolerance) {
683 return Some(("x".to_string(), vec![]));
684 }
685 if self.is_pauli_y(matrix, tolerance) {
686 return Some(("y".to_string(), vec![]));
687 }
688 if self.is_pauli_z(matrix, tolerance) {
689 return Some(("z".to_string(), vec![]));
690 }
691
692 if self.is_hadamard(matrix, tolerance) {
694 return Some(("h".to_string(), vec![]));
695 }
696
697 if let Some(angle) = self.is_rz(matrix, tolerance) {
699 return Some(("rz".to_string(), vec![angle]));
700 }
701
702 None
703 }
704
705 fn is_pauli_x(&self, matrix: &[Vec<Complex64>], tol: f64) -> bool {
706 matrix.len() == 2
707 && matrix[0].len() == 2
708 && (matrix[0][0].norm() < tol)
709 && (matrix[0][1] - Complex64::new(1.0, 0.0)).norm() < tol
710 && (matrix[1][0] - Complex64::new(1.0, 0.0)).norm() < tol
711 && (matrix[1][1].norm() < tol)
712 }
713
714 fn is_pauli_y(&self, matrix: &[Vec<Complex64>], tol: f64) -> bool {
715 matrix.len() == 2
716 && matrix[0].len() == 2
717 && (matrix[0][0].norm() < tol)
718 && (matrix[0][1] - Complex64::new(0.0, -1.0)).norm() < tol
719 && (matrix[1][0] - Complex64::new(0.0, 1.0)).norm() < tol
720 && (matrix[1][1].norm() < tol)
721 }
722
723 fn is_pauli_z(&self, matrix: &[Vec<Complex64>], tol: f64) -> bool {
724 matrix.len() == 2
725 && matrix[0].len() == 2
726 && (matrix[0][0] - Complex64::new(1.0, 0.0)).norm() < tol
727 && (matrix[0][1].norm() < tol)
728 && (matrix[1][0].norm() < tol)
729 && (matrix[1][1] - Complex64::new(-1.0, 0.0)).norm() < tol
730 }
731
732 fn is_hadamard(&self, matrix: &[Vec<Complex64>], tol: f64) -> bool {
733 let h_val = 1.0 / 2.0_f64.sqrt();
734 matrix.len() == 2
735 && matrix[0].len() == 2
736 && (matrix[0][0] - Complex64::new(h_val, 0.0)).norm() < tol
737 && (matrix[0][1] - Complex64::new(h_val, 0.0)).norm() < tol
738 && (matrix[1][0] - Complex64::new(h_val, 0.0)).norm() < tol
739 && (matrix[1][1] - Complex64::new(-h_val, 0.0)).norm() < tol
740 }
741
742 fn is_rz(&self, matrix: &[Vec<Complex64>], tol: f64) -> Option<f64> {
743 if matrix.len() != 2
744 || matrix[0].len() != 2
745 || matrix[0][1].norm() > tol
746 || matrix[1][0].norm() > tol
747 {
748 return None;
749 }
750
751 let phase1 = matrix[0][0].arg();
752 let phase2 = matrix[1][1].arg();
753
754 if (matrix[0][0].norm() - 1.0).abs() < tol && (matrix[1][1].norm() - 1.0).abs() < tol {
755 let angle = phase2 - phase1;
756 Some(angle)
757 } else {
758 None
759 }
760 }
761}
762
763impl Default for GraphOptimizer {
764 fn default() -> Self {
765 Self::new()
766 }
767}
768
769#[derive(Debug, Clone)]
771pub struct OptimizationStats {
772 pub original_gate_count: usize,
773 pub optimized_gate_count: usize,
774 pub original_depth: usize,
775 pub optimized_depth: usize,
776 pub gates_removed: usize,
777 pub gates_merged: usize,
778}
779
780impl OptimizationStats {
781 #[must_use]
782 pub fn improvement_percentage(&self) -> f64 {
783 if self.original_gate_count == 0 {
784 0.0
785 } else {
786 100.0 * (self.original_gate_count - self.optimized_gate_count) as f64
787 / self.original_gate_count as f64
788 }
789 }
790}
791
792#[cfg(test)]
793mod tests {
794 use super::*;
795
796 #[test]
797 fn test_dag_construction() {
798 let mut dag = CircuitDAG::new();
799
800 let g1 = GraphGate {
801 id: 0,
802 gate_type: "h".to_string(),
803 qubits: vec![QubitId::new(0)],
804 params: vec![],
805 matrix: None,
806 };
807
808 let g2 = GraphGate {
809 id: 1,
810 gate_type: "cnot".to_string(),
811 qubits: vec![QubitId::new(0), QubitId::new(1)],
812 params: vec![],
813 matrix: None,
814 };
815
816 dag.add_gate(g1);
817 dag.add_gate(g2);
818
819 assert_eq!(dag.nodes.len(), 2);
820 assert!(dag.edges.contains_key(&(0, 1)));
821 }
822
823 #[test]
824 fn test_commutation_detection() {
825 let _optimizer = GraphOptimizer::new();
826
827 let g1 = GraphGate {
828 id: 0,
829 gate_type: "z".to_string(),
830 qubits: vec![QubitId::new(0)],
831 params: vec![],
832 matrix: None,
833 };
834
835 let g2 = GraphGate {
836 id: 1,
837 gate_type: "z".to_string(),
838 qubits: vec![QubitId::new(0)],
839 params: vec![],
840 matrix: None,
841 };
842
843 let dag = CircuitDAG::new();
844 assert!(dag.gates_commute(&g1, &g2));
845 }
846
847 #[test]
848 fn test_gate_identification() {
849 let optimizer = GraphOptimizer::new();
850
851 let x_matrix = vec![
853 vec![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)],
854 vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
855 ];
856
857 if let Some((gate_type, _)) = optimizer.identify_gate(&x_matrix) {
858 assert_eq!(gate_type, "x");
859 } else {
860 panic!("Failed to identify Pauli X gate");
861 }
862 }
863}