1use crate::{DeviceError, DeviceResult};
12use quantrs2_circuit::prelude::Circuit;
13use quantrs2_core::prelude::*;
14use std::cmp::Reverse;
15use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
16
17#[derive(Debug, Clone)]
19pub struct DirectedGraph<T> {
20 nodes: Vec<T>,
21 edges: HashMap<usize, Vec<usize>>,
22}
23
24#[derive(Debug, Clone)]
26pub struct UndirectedGraph {
27 num_nodes: usize,
28 adjacency: HashMap<usize, Vec<(usize, f64)>>,
30}
31
32impl UndirectedGraph {
33 pub fn new(num_nodes: usize) -> Self {
35 Self {
36 num_nodes,
37 adjacency: HashMap::new(),
38 }
39 }
40
41 pub fn add_edge(&mut self, u: usize, v: usize, weight: f64) {
43 self.adjacency
44 .entry(u)
45 .or_insert_with(Vec::new)
46 .push((v, weight));
47 self.adjacency
48 .entry(v)
49 .or_insert_with(Vec::new)
50 .push((u, weight));
51 }
52
53 pub fn num_nodes(&self) -> usize {
55 self.num_nodes
56 }
57
58 pub fn bfs(&self, start: usize) -> Vec<usize> {
60 let mut visited = vec![false; self.num_nodes];
61 let mut order = Vec::new();
62 let mut queue = VecDeque::new();
63
64 if start >= self.num_nodes {
65 return order;
66 }
67
68 visited[start] = true;
69 queue.push_back(start);
70
71 while let Some(node) = queue.pop_front() {
72 order.push(node);
73 if let Some(neighbors) = self.adjacency.get(&node) {
74 let mut sorted_neighbors = neighbors.clone();
75 sorted_neighbors.sort_by_key(|(n, _)| *n);
76 for (neighbor, _) in sorted_neighbors {
77 if !visited[neighbor] {
78 visited[neighbor] = true;
79 queue.push_back(neighbor);
80 }
81 }
82 }
83 }
84 order
85 }
86
87 pub fn dfs(&self, start: usize) -> Vec<usize> {
89 let mut visited = vec![false; self.num_nodes];
90 let mut order = Vec::new();
91 self.dfs_recursive(start, &mut visited, &mut order);
92 order
93 }
94
95 fn dfs_recursive(&self, node: usize, visited: &mut Vec<bool>, order: &mut Vec<usize>) {
96 if node >= self.num_nodes || visited[node] {
97 return;
98 }
99 visited[node] = true;
100 order.push(node);
101 if let Some(neighbors) = self.adjacency.get(&node) {
102 let mut sorted_neighbors = neighbors.clone();
103 sorted_neighbors.sort_by_key(|(n, _)| *n);
104 for (neighbor, _) in sorted_neighbors {
105 self.dfs_recursive(neighbor, visited, order);
106 }
107 }
108 }
109
110 pub fn dijkstra_distances(&self, source: usize) -> HashMap<usize, f64> {
119 const SCALE: f64 = 1_000_000_000.0;
120 let mut heap: BinaryHeap<(Reverse<u64>, usize)> = BinaryHeap::new();
122 let mut dist: HashMap<usize, u64> = HashMap::new();
123
124 dist.insert(source, 0);
125 heap.push((Reverse(0), source));
126
127 while let Some((Reverse(d), u)) = heap.pop() {
128 if dist.get(&u).map_or(true, |&best| d > best) {
129 continue;
130 }
131 if let Some(neighbors) = self.adjacency.get(&u) {
132 for &(v, w) in neighbors {
133 let w_scaled = (w * SCALE) as u64;
134 let next_d = d.saturating_add(w_scaled);
135 let better = dist.get(&v).map_or(true, |&cur| next_d < cur);
136 if better {
137 dist.insert(v, next_d);
138 heap.push((Reverse(next_d), v));
139 }
140 }
141 }
142 }
143 dist.into_iter()
144 .map(|(k, v)| (k, v as f64 / SCALE))
145 .collect()
146 }
147
148 pub fn dijkstra_path(&self, source: usize, target: usize) -> Option<(f64, Vec<usize>)> {
152 const SCALE: f64 = 1_000_000_000.0;
153 let mut heap: BinaryHeap<(Reverse<u64>, usize)> = BinaryHeap::new();
154 let mut dist: HashMap<usize, u64> = HashMap::new();
155 let mut prev: HashMap<usize, usize> = HashMap::new();
156
157 dist.insert(source, 0);
158 heap.push((Reverse(0), source));
159
160 while let Some((Reverse(d), u)) = heap.pop() {
161 if u == target {
162 break;
163 }
164 if dist.get(&u).map_or(true, |&best| d > best) {
165 continue;
166 }
167 if let Some(neighbors) = self.adjacency.get(&u) {
168 for &(v, w) in neighbors {
169 let w_scaled = (w * SCALE) as u64;
170 let next_d = d.saturating_add(w_scaled);
171 let better = dist.get(&v).map_or(true, |&cur| next_d < cur);
172 if better {
173 dist.insert(v, next_d);
174 prev.insert(v, u);
175 heap.push((Reverse(next_d), v));
176 }
177 }
178 }
179 }
180
181 let total_scaled = *dist.get(&target)?;
182 let total = total_scaled as f64 / SCALE;
183
184 let mut path = Vec::new();
186 let mut cur = target;
187 loop {
188 path.push(cur);
189 if cur == source {
190 break;
191 }
192 match prev.get(&cur) {
193 Some(&p) => cur = p,
194 None => return None, }
196 }
197 path.reverse();
198 Some((total, path))
199 }
200}
201
202#[derive(Debug, Clone)]
207pub struct PatternGraph {
208 labels: Vec<String>,
210 edges: HashSet<(usize, usize)>,
212}
213
214impl PatternGraph {
215 pub fn new() -> Self {
217 Self {
218 labels: Vec::new(),
219 edges: HashSet::new(),
220 }
221 }
222
223 pub fn add_node(&mut self, label: impl Into<String>) -> usize {
225 let idx = self.labels.len();
226 self.labels.push(label.into());
227 idx
228 }
229
230 pub fn add_edge(&mut self, from: usize, to: usize) {
232 self.edges.insert((from, to));
233 }
234
235 pub fn num_nodes(&self) -> usize {
237 self.labels.len()
238 }
239
240 pub fn label(&self, i: usize) -> Option<&str> {
242 self.labels.get(i).map(String::as_str)
243 }
244
245 pub fn has_edge(&self, from: usize, to: usize) -> bool {
247 self.edges.contains(&(from, to))
248 }
249}
250
251#[derive(Debug, Clone)]
255pub struct IsomorphismMapping {
256 pub mapping: Vec<usize>,
258}
259
260pub struct SubgraphIsomorphism<'a> {
265 pattern: &'a PatternGraph,
266 target_labels: &'a [String],
267 target_edges: &'a HashSet<(usize, usize)>,
268}
269
270impl<'a> SubgraphIsomorphism<'a> {
271 pub fn new(
273 pattern: &'a PatternGraph,
274 target_labels: &'a [String],
275 target_edges: &'a HashSet<(usize, usize)>,
276 ) -> Self {
277 Self {
278 pattern,
279 target_labels,
280 target_edges,
281 }
282 }
283
284 pub fn find_all(&self) -> Vec<IsomorphismMapping> {
286 let n_p = self.pattern.num_nodes();
287 let n_t = self.target_labels.len();
288 if n_p == 0 || n_p > n_t {
289 return Vec::new();
290 }
291
292 let mut results = Vec::new();
293 let mut partial: Vec<Option<usize>> = vec![None; n_p];
295 let mut used = vec![false; n_t];
297
298 self.backtrack(0, &mut partial, &mut used, &mut results);
299 results
300 }
301
302 fn backtrack(
304 &self,
305 depth: usize,
306 partial: &mut Vec<Option<usize>>,
307 used: &mut Vec<bool>,
308 results: &mut Vec<IsomorphismMapping>,
309 ) {
310 let n_p = self.pattern.num_nodes();
311 if depth == n_p {
312 let mapping: Vec<usize> = partial.iter().filter_map(|x| *x).collect();
314 results.push(IsomorphismMapping { mapping });
315 return;
316 }
317
318 for t in 0..self.target_labels.len() {
320 if used[t] {
321 continue;
322 }
323 if !self.labels_compatible(depth, t) {
325 continue;
326 }
327 if !self.structurally_consistent(depth, t, partial) {
329 continue;
330 }
331
332 partial[depth] = Some(t);
334 used[t] = true;
335
336 self.backtrack(depth + 1, partial, used, results);
337
338 partial[depth] = None;
340 used[t] = false;
341 }
342 }
343
344 fn labels_compatible(&self, p: usize, t: usize) -> bool {
347 let p_label = match self.pattern.label(p) {
348 Some(l) => l,
349 None => return false,
350 };
351 if p_label.is_empty() {
352 return true; }
354 match self.target_labels.get(t) {
355 Some(t_label) => p_label == t_label.as_str(),
356 None => false,
357 }
358 }
359
360 fn structurally_consistent(&self, depth: usize, t: usize, partial: &[Option<usize>]) -> bool {
363 for q in 0..depth {
364 let mapped_q = match partial[q] {
365 Some(m) => m,
366 None => continue,
367 };
368 if self.pattern.has_edge(q, depth) && !self.target_edges.contains(&(mapped_q, t)) {
370 return false;
371 }
372 if self.pattern.has_edge(depth, q) && !self.target_edges.contains(&(t, mapped_q)) {
374 return false;
375 }
376 }
377 true
378 }
379}
380
381impl<T: Clone + PartialEq> DirectedGraph<T> {
382 pub fn new() -> Self {
383 Self {
384 nodes: Vec::new(),
385 edges: HashMap::new(),
386 }
387 }
388
389 pub fn add_node(&mut self, node: T) -> usize {
390 let idx = self.nodes.len();
391 self.nodes.push(node);
392 idx
393 }
394
395 pub fn add_edge(&mut self, from_idx: usize, to_idx: usize) {
396 self.edges
397 .entry(from_idx)
398 .or_insert_with(Vec::new)
399 .push(to_idx);
400 }
401
402 pub fn nodes(&self) -> &[T] {
403 &self.nodes
404 }
405
406 pub fn has_edge(&self, from: &T, to: &T) -> bool {
407 if let Some(from_idx) = self.nodes.iter().position(|n| n == from) {
408 if let Some(to_idx) = self.nodes.iter().position(|n| n == to) {
409 if let Some(neighbors) = self.edges.get(&from_idx) {
410 return neighbors.contains(&to_idx);
411 }
412 }
413 }
414 false
415 }
416
417 pub fn num_nodes(&self) -> usize {
418 self.nodes.len()
419 }
420
421 pub fn num_edges(&self) -> usize {
422 self.edges.values().map(|v| v.len()).sum()
423 }
424}
425
426#[derive(Debug, Clone, PartialEq, Eq, Hash)]
428pub struct GateNode {
429 pub gate_index: usize,
431 pub gate_type: String,
433 pub qubits: Vec<usize>,
435 pub depth: usize,
437}
438
439#[derive(Debug, Clone)]
441pub struct CircuitTopology {
442 pub qubit_graph: DirectedGraph<usize>,
444 pub gate_graph: DirectedGraph<GateNode>,
446 pub critical_path_length: usize,
448 pub circuit_depth: usize,
450}
451
452#[derive(Debug, Clone)]
454pub struct HardwareTopology {
455 pub qubit_connectivity: HashMap<usize, Vec<usize>>,
457 pub num_physical_qubits: usize,
459 pub error_rates: HashMap<(usize, usize), f64>,
461}
462
463impl Default for HardwareTopology {
464 fn default() -> Self {
465 Self {
466 qubit_connectivity: HashMap::new(),
467 num_physical_qubits: 0,
468 error_rates: HashMap::new(),
469 }
470 }
471}
472
473#[derive(Debug, Clone)]
475pub struct SciRS2TranspilerConfig {
476 pub enable_commutation: bool,
478 pub enable_critical_path_opt: bool,
480 pub enable_routing_opt: bool,
482 pub max_optimization_passes: usize,
484 pub hardware_topology: Option<HardwareTopology>,
486}
487
488impl Default for SciRS2TranspilerConfig {
489 fn default() -> Self {
490 Self {
491 enable_commutation: true,
492 enable_critical_path_opt: true,
493 enable_routing_opt: true,
494 max_optimization_passes: 3,
495 hardware_topology: None,
496 }
497 }
498}
499
500pub struct SciRS2GraphTranspiler {
502 config: SciRS2TranspilerConfig,
503}
504
505impl SciRS2GraphTranspiler {
506 pub fn new(config: SciRS2TranspilerConfig) -> Self {
508 Self { config }
509 }
510
511 pub fn build_dependency_graph<const N: usize>(
513 &self,
514 circuit: &Circuit<N>,
515 ) -> DeviceResult<DirectedGraph<GateNode>> {
516 let mut graph = DirectedGraph::new();
517 let mut qubit_last_gate: HashMap<usize, usize> = HashMap::new();
518
519 for (idx, gate) in circuit.gates().iter().enumerate() {
521 let node = GateNode {
522 gate_index: idx,
523 gate_type: gate.name().to_string(),
524 qubits: gate.qubits().iter().map(|q| q.id() as usize).collect(),
525 depth: 0, };
527 let node_idx = graph.add_node(node);
528
529 for qubit in gate.qubits() {
531 let q_id = qubit.id() as usize;
532
533 if let Some(&prev_idx) = qubit_last_gate.get(&q_id) {
535 graph.add_edge(prev_idx, node_idx);
536 }
537
538 qubit_last_gate.insert(q_id, node_idx);
540 }
541 }
542
543 Ok(graph)
544 }
545
546 pub fn analyze_topology<const N: usize>(
548 &self,
549 circuit: &Circuit<N>,
550 ) -> DeviceResult<CircuitTopology> {
551 let gate_graph = self.build_dependency_graph(circuit)?;
553
554 let mut qubit_graph = DirectedGraph::new();
556 let mut qubit_node_indices: HashMap<usize, usize> = HashMap::new();
557
558 for gate in circuit.gates() {
559 let qubits: Vec<usize> = gate.qubits().iter().map(|q| q.id() as usize).collect();
560
561 for &q in &qubits {
563 qubit_node_indices
564 .entry(q)
565 .or_insert_with(|| qubit_graph.add_node(q));
566 }
567
568 if qubits.len() == 2 {
570 let (q0, q1) = (qubits[0], qubits[1]);
571 if q0 != q1 {
572 if let (Some(&idx0), Some(&idx1)) =
573 (qubit_node_indices.get(&q0), qubit_node_indices.get(&q1))
574 {
575 qubit_graph.add_edge(idx0, idx1);
576 qubit_graph.add_edge(idx1, idx0); }
578 }
579 }
580 }
581
582 let circuit_depth = self.compute_circuit_depth(&gate_graph)?;
584
585 let critical_path_length = self.compute_critical_path(&gate_graph)?;
587
588 Ok(CircuitTopology {
589 qubit_graph,
590 gate_graph,
591 critical_path_length,
592 circuit_depth,
593 })
594 }
595
596 fn compute_circuit_depth(&self, gate_graph: &DirectedGraph<GateNode>) -> DeviceResult<usize> {
598 let mut depths: HashMap<usize, usize> = HashMap::new();
600 let mut max_depth = 0;
601
602 for node in gate_graph.nodes() {
604 let mut gate_depth = 0;
606
607 for potential_pred in gate_graph.nodes() {
609 if gate_graph.has_edge(potential_pred, node) {
610 if let Some(&pred_depth) = depths.get(&potential_pred.gate_index) {
611 gate_depth = gate_depth.max(pred_depth + 1);
612 }
613 }
614 }
615
616 depths.insert(node.gate_index, gate_depth);
617 max_depth = max_depth.max(gate_depth);
618 }
619
620 Ok(max_depth + 1)
621 }
622
623 fn compute_critical_path(&self, gate_graph: &DirectedGraph<GateNode>) -> DeviceResult<usize> {
625 let mut longest_paths: HashMap<usize, usize> = HashMap::new();
629 let mut max_path_length = 0;
630
631 for node in gate_graph.nodes() {
632 let mut path_length = 0;
633
634 for potential_pred in gate_graph.nodes() {
636 if gate_graph.has_edge(potential_pred, node) {
637 if let Some(&pred_path) = longest_paths.get(&potential_pred.gate_index) {
638 path_length = path_length.max(pred_path + 1);
639 }
640 }
641 }
642
643 longest_paths.insert(node.gate_index, path_length);
644 max_path_length = max_path_length.max(path_length);
645 }
646
647 Ok(max_path_length)
648 }
649
650 pub fn optimize_qubit_routing<const N: usize>(
657 &self,
658 circuit: &Circuit<N>,
659 hardware_topology: &HardwareTopology,
660 ) -> DeviceResult<HashMap<usize, usize>> {
661 let n_phys = hardware_topology.num_physical_qubits;
663 let mut hw_graph = UndirectedGraph::new(n_phys);
664 for (&phys_q, neighbors) in &hardware_topology.qubit_connectivity {
665 for &neighbor in neighbors {
666 if phys_q < neighbor {
667 let weight = hardware_topology
669 .error_rates
670 .get(&(phys_q, neighbor))
671 .copied()
672 .unwrap_or(1.0);
673 hw_graph.add_edge(phys_q, neighbor, weight);
674 }
675 }
676 }
677
678 let all_dists: Vec<HashMap<usize, f64>> = (0..n_phys)
680 .map(|src| hw_graph.dijkstra_distances(src))
681 .collect();
682
683 let mut interaction_freq: HashMap<(usize, usize), usize> = HashMap::new();
685 for gate in circuit.gates() {
686 let qubits: Vec<usize> = gate.qubits().iter().map(|q| q.id() as usize).collect();
687 if qubits.len() == 2 {
688 let (a, b) = (qubits[0].min(qubits[1]), qubits[0].max(qubits[1]));
689 *interaction_freq.entry((a, b)).or_insert(0) += 1;
690 }
691 }
692
693 let mut qubit_freq = vec![0usize; N];
695 for (&(a, b), &freq) in &interaction_freq {
696 if a < N {
697 qubit_freq[a] += freq;
698 }
699 if b < N {
700 qubit_freq[b] += freq;
701 }
702 }
703
704 let mut sorted_logical: Vec<usize> = (0..N).collect();
706 sorted_logical.sort_by(|&x, &y| qubit_freq[y].cmp(&qubit_freq[x]));
707
708 let mut mapping: HashMap<usize, usize> = HashMap::new();
711 let mut assigned_phys: HashSet<usize> = HashSet::new();
712
713 for &logical in &sorted_logical {
714 let assigned_neighbors: Vec<usize> = mapping.keys().copied().collect();
716
717 let best_phys = (0..n_phys)
718 .filter(|p| !assigned_phys.contains(p))
719 .min_by(|&p1, &p2| {
720 let score = |p: usize| -> f64 {
721 if assigned_neighbors.is_empty() {
722 return p as f64; }
724 assigned_neighbors
725 .iter()
726 .map(|ln| {
727 let phys_n = *mapping.get(ln).unwrap_or(&0);
728 all_dists[p].get(&phys_n).copied().unwrap_or(f64::MAX)
729 })
730 .sum::<f64>()
731 / assigned_neighbors.len() as f64
732 };
733 score(p1)
734 .partial_cmp(&score(p2))
735 .unwrap_or(std::cmp::Ordering::Equal)
736 })
737 .unwrap_or_else(|| logical % n_phys.max(1));
738
739 mapping.insert(logical, best_phys);
740 assigned_phys.insert(best_phys);
741 }
742
743 Ok(mapping)
744 }
745
746 pub fn find_circuit_pattern_matches<const N: usize>(
752 &self,
753 circuit: &Circuit<N>,
754 pattern: &PatternGraph,
755 ) -> DeviceResult<Vec<IsomorphismMapping>> {
756 let dep_graph = self.build_dependency_graph(circuit)?;
758
759 let target_labels: Vec<String> = dep_graph
760 .nodes()
761 .iter()
762 .map(|n| n.gate_type.clone())
763 .collect();
764
765 let mut target_edges: HashSet<(usize, usize)> = HashSet::new();
766 for (from_idx, to_list) in &dep_graph.edges {
767 for &to_idx in to_list {
768 target_edges.insert((*from_idx, to_idx));
769 }
770 }
771
772 let iso = SubgraphIsomorphism::new(pattern, &target_labels, &target_edges);
773 Ok(iso.find_all())
774 }
775
776 pub fn find_commuting_gates<const N: usize>(
778 &self,
779 circuit: &Circuit<N>,
780 ) -> DeviceResult<Vec<(usize, usize)>> {
781 let mut commuting_pairs = Vec::new();
782 let gates = circuit.gates();
783
784 for i in 0..gates.len() {
785 for j in (i + 1)..gates.len() {
786 let qubits_i: HashSet<u32> = gates[i].qubits().iter().map(|q| q.id()).collect();
788 let qubits_j: HashSet<u32> = gates[j].qubits().iter().map(|q| q.id()).collect();
789
790 if qubits_i.is_disjoint(&qubits_j) {
791 commuting_pairs.push((i, j));
792 }
793 }
794 }
795
796 Ok(commuting_pairs)
797 }
798
799 pub fn optimize_circuit<const N: usize>(
801 &self,
802 circuit: &Circuit<N>,
803 ) -> DeviceResult<Circuit<N>> {
804 let _topology = self.analyze_topology(circuit)?;
806
807 Ok(circuit.clone())
814 }
815
816 pub fn generate_optimization_report<const N: usize>(
818 &self,
819 circuit: &Circuit<N>,
820 ) -> DeviceResult<String> {
821 let topology = self.analyze_topology(circuit)?;
822
823 let mut report = String::from("=== SciRS2 Graph Transpiler Analysis ===\n\n");
824 report.push_str(&format!("Circuit Depth: {}\n", topology.circuit_depth));
825 report.push_str(&format!(
826 "Critical Path Length: {}\n",
827 topology.critical_path_length
828 ));
829 report.push_str(&format!("Number of Gates: {}\n", circuit.gates().len()));
830 report.push_str(&format!("Number of Qubits: {}\n", N));
831
832 let num_qubit_edges = topology.qubit_graph.num_edges();
834 report.push_str(&format!("Qubit Connections: {}\n", num_qubit_edges));
835
836 let num_dependencies = topology.gate_graph.num_edges();
838 report.push_str(&format!("Gate Dependencies: {}\n", num_dependencies));
839
840 if self.config.enable_commutation {
842 let commuting = self.find_commuting_gates(circuit)?;
843 report.push_str(&format!("Commuting Gate Pairs: {}\n", commuting.len()));
844 }
845
846 Ok(report)
847 }
848}
849
850#[cfg(test)]
851#[allow(clippy::pedantic, clippy::field_reassign_with_default)]
852mod tests {
853 use super::*;
854 use quantrs2_circuit::prelude::*;
855
856 #[test]
857 fn test_transpiler_creation() {
858 let config = SciRS2TranspilerConfig::default();
859 let transpiler = SciRS2GraphTranspiler::new(config);
860 assert!(transpiler.config.enable_commutation);
861 }
862
863 #[test]
864 fn test_dependency_graph_building() {
865 let mut circuit = Circuit::<2>::new();
866 let _ = circuit.h(0);
867 let _ = circuit.cnot(0, 1);
868 let _ = circuit.h(1);
869
870 let config = SciRS2TranspilerConfig::default();
871 let transpiler = SciRS2GraphTranspiler::new(config);
872
873 let graph = transpiler
874 .build_dependency_graph(&circuit)
875 .expect("Failed to build dependency graph");
876
877 assert_eq!(graph.num_nodes(), 3); }
879
880 #[test]
881 fn test_topology_analysis() {
882 let mut circuit = Circuit::<3>::new();
883 let _ = circuit.h(0);
884 let _ = circuit.h(1);
885 let _ = circuit.cnot(0, 1);
886 let _ = circuit.cnot(1, 2);
887
888 let config = SciRS2TranspilerConfig::default();
889 let transpiler = SciRS2GraphTranspiler::new(config);
890
891 let topology = transpiler
892 .analyze_topology(&circuit)
893 .expect("Failed to analyze topology");
894
895 assert!(topology.circuit_depth > 0);
896 assert!(topology.critical_path_length > 0);
897 }
898
899 #[test]
900 fn test_commuting_gates_detection() {
901 let mut circuit = Circuit::<4>::new();
902 let _ = circuit.h(0);
903 let _ = circuit.h(1); let _ = circuit.x(2); let _ = circuit.cnot(0, 1); let config = SciRS2TranspilerConfig::default();
908 let transpiler = SciRS2GraphTranspiler::new(config);
909
910 let commuting = transpiler
911 .find_commuting_gates(&circuit)
912 .expect("Failed to find commuting gates");
913
914 assert!(!commuting.is_empty());
915 }
916
917 #[test]
918 fn test_optimization_report() {
919 let mut circuit = Circuit::<2>::new();
920 let _ = circuit.h(0);
921 let _ = circuit.cnot(0, 1);
922 let _ = circuit.measure_all();
923
924 let config = SciRS2TranspilerConfig::default();
925 let transpiler = SciRS2GraphTranspiler::new(config);
926
927 let report = transpiler
928 .generate_optimization_report(&circuit)
929 .expect("Failed to generate report");
930
931 assert!(report.contains("Circuit Depth"));
932 assert!(report.contains("Critical Path"));
933 }
934
935 #[test]
936 fn test_hardware_topology_creation() {
937 let mut topology = HardwareTopology {
938 num_physical_qubits: 5,
939 ..Default::default()
940 };
941
942 topology.qubit_connectivity.insert(0, vec![1]);
944 topology.qubit_connectivity.insert(1, vec![0, 2]);
945 topology.qubit_connectivity.insert(2, vec![1, 3]);
946 topology.qubit_connectivity.insert(3, vec![2, 4]);
947 topology.qubit_connectivity.insert(4, vec![3]);
948
949 assert_eq!(topology.num_physical_qubits, 5);
950 assert_eq!(topology.qubit_connectivity.len(), 5);
951 }
952
953 #[test]
954 fn test_qubit_routing_optimization() {
955 let mut circuit = Circuit::<3>::new();
956 let _ = circuit.cnot(0, 1);
957 let _ = circuit.cnot(1, 2);
958
959 let mut hardware = HardwareTopology::default();
960 hardware.num_physical_qubits = 5;
961 hardware.qubit_connectivity.insert(0, vec![1]);
962 hardware.qubit_connectivity.insert(1, vec![0, 2]);
963 hardware.qubit_connectivity.insert(2, vec![1]);
964
965 let config = SciRS2TranspilerConfig {
966 enable_routing_opt: true,
967 ..Default::default()
968 };
969 let transpiler = SciRS2GraphTranspiler::new(config);
970
971 let mapping = transpiler
972 .optimize_qubit_routing(&circuit, &hardware)
973 .expect("Failed to optimize routing");
974
975 assert_eq!(mapping.len(), 3);
976 }
977
978 #[test]
981 fn test_undirected_graph_bfs() {
982 let mut g = UndirectedGraph::new(4);
984 g.add_edge(0, 1, 1.0);
985 g.add_edge(1, 2, 1.0);
986 g.add_edge(2, 3, 1.0);
987
988 let order = g.bfs(0);
989 assert_eq!(order, vec![0, 1, 2, 3]);
990 }
991
992 #[test]
993 fn test_undirected_graph_dfs() {
994 let mut g = UndirectedGraph::new(4);
996 g.add_edge(0, 1, 1.0);
997 g.add_edge(1, 2, 1.0);
998 g.add_edge(2, 3, 1.0);
999
1000 let order = g.dfs(0);
1001 assert_eq!(order, vec![0, 1, 2, 3]);
1002 }
1003
1004 #[test]
1005 fn test_dijkstra_linear_graph() {
1006 let mut g = UndirectedGraph::new(4);
1008 g.add_edge(0, 1, 1.0);
1009 g.add_edge(1, 2, 2.0);
1010 g.add_edge(2, 3, 1.0);
1011
1012 let dists = g.dijkstra_distances(0);
1013 assert!((dists[&0] - 0.0).abs() < 1e-6);
1014 assert!((dists[&1] - 1.0).abs() < 1e-6);
1015 assert!((dists[&2] - 3.0).abs() < 1e-6);
1016 assert!((dists[&3] - 4.0).abs() < 1e-6);
1017 }
1018
1019 #[test]
1020 fn test_dijkstra_path() {
1021 let mut g = UndirectedGraph::new(5);
1022 g.add_edge(0, 1, 1.0);
1023 g.add_edge(1, 2, 1.0);
1024 g.add_edge(0, 3, 5.0);
1025 g.add_edge(3, 2, 1.0); let result = g.dijkstra_path(0, 2);
1028 assert!(result.is_some());
1029 let (dist, path) = result.expect("path should exist");
1030 assert!(
1031 (dist - 2.0).abs() < 1e-6,
1032 "expected distance 2.0, got {}",
1033 dist
1034 );
1035 assert_eq!(path, vec![0, 1, 2]);
1036 }
1037
1038 #[test]
1039 fn test_pattern_graph_construction() {
1040 let mut pattern = PatternGraph::new();
1041 let h = pattern.add_node("H");
1042 let cx = pattern.add_node("CNOT");
1043 pattern.add_edge(h, cx);
1044
1045 assert_eq!(pattern.num_nodes(), 2);
1046 assert!(pattern.has_edge(0, 1));
1047 assert!(!pattern.has_edge(1, 0));
1048 }
1049
1050 #[test]
1051 fn test_subgraph_isomorphism_simple() {
1052 let mut pattern = PatternGraph::new();
1054 let ph = pattern.add_node("H");
1055 let pcx = pattern.add_node("CNOT");
1056 pattern.add_edge(ph, pcx);
1057
1058 let target_labels = vec!["H".to_string(), "CNOT".to_string(), "H".to_string()];
1060 let mut target_edges: HashSet<(usize, usize)> = HashSet::new();
1061 target_edges.insert((0, 1)); target_edges.insert((1, 2)); let iso = SubgraphIsomorphism::new(&pattern, &target_labels, &target_edges);
1065 let mappings = iso.find_all();
1066 assert!(!mappings.is_empty(), "Should find at least one isomorphism");
1068 }
1069
1070 #[test]
1071 fn test_find_circuit_pattern_matches() {
1072 let mut circuit = Circuit::<2>::new();
1073 let _ = circuit.h(0);
1074 let _ = circuit.cnot(0, 1);
1075 let _ = circuit.h(1);
1076
1077 let mut pattern = PatternGraph::new();
1079 let _p0 = pattern.add_node(""); let p1 = pattern.add_node("CNOT");
1081 pattern.add_edge(0, 1);
1082
1083 let config = SciRS2TranspilerConfig::default();
1084 let transpiler = SciRS2GraphTranspiler::new(config);
1085
1086 let matches = transpiler
1087 .find_circuit_pattern_matches(&circuit, &pattern)
1088 .expect("Pattern match should succeed");
1089
1090 assert!(
1091 !matches.is_empty(),
1092 "Should find at least one pattern occurrence"
1093 );
1094 }
1095}