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>(
809 &self,
810 circuit: &Circuit<N>,
811 ) -> DeviceResult<Circuit<N>> {
812 let topology = self.analyze_topology(circuit)?;
813 let gate_graph = &topology.gate_graph;
814 let original_gates = circuit.gates();
815 let num_gates = original_gates.len();
816
817 if num_gates == 0 {
818 return Ok(Circuit::<N>::new());
819 }
820
821 let mut levels: Vec<usize> = vec![0; num_gates];
824 let nodes = gate_graph.nodes();
825
826 for node in nodes {
830 let idx = node.gate_index;
831 let mut max_pred_level = 0usize;
832
833 for pred in nodes {
834 if gate_graph.has_edge(pred, node) {
835 let pred_level = levels.get(pred.gate_index).copied().unwrap_or(0);
836 if pred_level + 1 > max_pred_level {
837 max_pred_level = pred_level + 1;
838 }
839 }
840 }
841
842 if idx < levels.len() {
843 levels[idx] = max_pred_level;
844 }
845 }
846
847 let max_level = levels.iter().copied().max().unwrap_or(0);
850 let mut buckets: Vec<Vec<usize>> = vec![Vec::new(); max_level + 1];
851 for (gate_idx, &level) in levels.iter().enumerate() {
852 buckets[level].push(gate_idx);
853 }
854
855 for bucket in &mut buckets {
857 bucket.sort_by_key(|&gate_idx| {
858 original_gates
859 .get(gate_idx)
860 .map(|g| g.qubits().iter().map(|q| q.id()).min().unwrap_or(u32::MAX))
861 .unwrap_or(u32::MAX)
862 });
863 }
864
865 let mut optimized = Circuit::<N>::new();
867 for bucket in &buckets {
868 for &gate_idx in bucket {
869 if let Some(gate_arc) = original_gates.get(gate_idx) {
870 optimized.add_gate_arc(gate_arc.clone())?;
871 }
872 }
873 }
874
875 Ok(optimized)
876 }
877
878 pub fn generate_optimization_report<const N: usize>(
880 &self,
881 circuit: &Circuit<N>,
882 ) -> DeviceResult<String> {
883 let topology = self.analyze_topology(circuit)?;
884
885 let mut report = String::from("=== SciRS2 Graph Transpiler Analysis ===\n\n");
886 report.push_str(&format!("Circuit Depth: {}\n", topology.circuit_depth));
887 report.push_str(&format!(
888 "Critical Path Length: {}\n",
889 topology.critical_path_length
890 ));
891 report.push_str(&format!("Number of Gates: {}\n", circuit.gates().len()));
892 report.push_str(&format!("Number of Qubits: {}\n", N));
893
894 let num_qubit_edges = topology.qubit_graph.num_edges();
896 report.push_str(&format!("Qubit Connections: {}\n", num_qubit_edges));
897
898 let num_dependencies = topology.gate_graph.num_edges();
900 report.push_str(&format!("Gate Dependencies: {}\n", num_dependencies));
901
902 if self.config.enable_commutation {
904 let commuting = self.find_commuting_gates(circuit)?;
905 report.push_str(&format!("Commuting Gate Pairs: {}\n", commuting.len()));
906 }
907
908 Ok(report)
909 }
910}
911
912#[cfg(test)]
913#[allow(clippy::pedantic, clippy::field_reassign_with_default)]
914mod tests {
915 use super::*;
916 use quantrs2_circuit::prelude::*;
917
918 #[test]
919 fn test_transpiler_creation() {
920 let config = SciRS2TranspilerConfig::default();
921 let transpiler = SciRS2GraphTranspiler::new(config);
922 assert!(transpiler.config.enable_commutation);
923 }
924
925 #[test]
926 fn test_dependency_graph_building() {
927 let mut circuit = Circuit::<2>::new();
928 let _ = circuit.h(0);
929 let _ = circuit.cnot(0, 1);
930 let _ = circuit.h(1);
931
932 let config = SciRS2TranspilerConfig::default();
933 let transpiler = SciRS2GraphTranspiler::new(config);
934
935 let graph = transpiler
936 .build_dependency_graph(&circuit)
937 .expect("Failed to build dependency graph");
938
939 assert_eq!(graph.num_nodes(), 3); }
941
942 #[test]
943 fn test_topology_analysis() {
944 let mut circuit = Circuit::<3>::new();
945 let _ = circuit.h(0);
946 let _ = circuit.h(1);
947 let _ = circuit.cnot(0, 1);
948 let _ = circuit.cnot(1, 2);
949
950 let config = SciRS2TranspilerConfig::default();
951 let transpiler = SciRS2GraphTranspiler::new(config);
952
953 let topology = transpiler
954 .analyze_topology(&circuit)
955 .expect("Failed to analyze topology");
956
957 assert!(topology.circuit_depth > 0);
958 assert!(topology.critical_path_length > 0);
959 }
960
961 #[test]
962 fn test_commuting_gates_detection() {
963 let mut circuit = Circuit::<4>::new();
964 let _ = circuit.h(0);
965 let _ = circuit.h(1); let _ = circuit.x(2); let _ = circuit.cnot(0, 1); let config = SciRS2TranspilerConfig::default();
970 let transpiler = SciRS2GraphTranspiler::new(config);
971
972 let commuting = transpiler
973 .find_commuting_gates(&circuit)
974 .expect("Failed to find commuting gates");
975
976 assert!(!commuting.is_empty());
977 }
978
979 #[test]
980 fn test_optimization_report() {
981 let mut circuit = Circuit::<2>::new();
982 let _ = circuit.h(0);
983 let _ = circuit.cnot(0, 1);
984 let _ = circuit.measure_all();
985
986 let config = SciRS2TranspilerConfig::default();
987 let transpiler = SciRS2GraphTranspiler::new(config);
988
989 let report = transpiler
990 .generate_optimization_report(&circuit)
991 .expect("Failed to generate report");
992
993 assert!(report.contains("Circuit Depth"));
994 assert!(report.contains("Critical Path"));
995 }
996
997 #[test]
998 fn test_hardware_topology_creation() {
999 let mut topology = HardwareTopology {
1000 num_physical_qubits: 5,
1001 ..Default::default()
1002 };
1003
1004 topology.qubit_connectivity.insert(0, vec![1]);
1006 topology.qubit_connectivity.insert(1, vec![0, 2]);
1007 topology.qubit_connectivity.insert(2, vec![1, 3]);
1008 topology.qubit_connectivity.insert(3, vec![2, 4]);
1009 topology.qubit_connectivity.insert(4, vec![3]);
1010
1011 assert_eq!(topology.num_physical_qubits, 5);
1012 assert_eq!(topology.qubit_connectivity.len(), 5);
1013 }
1014
1015 #[test]
1016 fn test_qubit_routing_optimization() {
1017 let mut circuit = Circuit::<3>::new();
1018 let _ = circuit.cnot(0, 1);
1019 let _ = circuit.cnot(1, 2);
1020
1021 let mut hardware = HardwareTopology::default();
1022 hardware.num_physical_qubits = 5;
1023 hardware.qubit_connectivity.insert(0, vec![1]);
1024 hardware.qubit_connectivity.insert(1, vec![0, 2]);
1025 hardware.qubit_connectivity.insert(2, vec![1]);
1026
1027 let config = SciRS2TranspilerConfig {
1028 enable_routing_opt: true,
1029 ..Default::default()
1030 };
1031 let transpiler = SciRS2GraphTranspiler::new(config);
1032
1033 let mapping = transpiler
1034 .optimize_qubit_routing(&circuit, &hardware)
1035 .expect("Failed to optimize routing");
1036
1037 assert_eq!(mapping.len(), 3);
1038 }
1039
1040 #[test]
1043 fn test_undirected_graph_bfs() {
1044 let mut g = UndirectedGraph::new(4);
1046 g.add_edge(0, 1, 1.0);
1047 g.add_edge(1, 2, 1.0);
1048 g.add_edge(2, 3, 1.0);
1049
1050 let order = g.bfs(0);
1051 assert_eq!(order, vec![0, 1, 2, 3]);
1052 }
1053
1054 #[test]
1055 fn test_undirected_graph_dfs() {
1056 let mut g = UndirectedGraph::new(4);
1058 g.add_edge(0, 1, 1.0);
1059 g.add_edge(1, 2, 1.0);
1060 g.add_edge(2, 3, 1.0);
1061
1062 let order = g.dfs(0);
1063 assert_eq!(order, vec![0, 1, 2, 3]);
1064 }
1065
1066 #[test]
1067 fn test_dijkstra_linear_graph() {
1068 let mut g = UndirectedGraph::new(4);
1070 g.add_edge(0, 1, 1.0);
1071 g.add_edge(1, 2, 2.0);
1072 g.add_edge(2, 3, 1.0);
1073
1074 let dists = g.dijkstra_distances(0);
1075 assert!((dists[&0] - 0.0).abs() < 1e-6);
1076 assert!((dists[&1] - 1.0).abs() < 1e-6);
1077 assert!((dists[&2] - 3.0).abs() < 1e-6);
1078 assert!((dists[&3] - 4.0).abs() < 1e-6);
1079 }
1080
1081 #[test]
1082 fn test_dijkstra_path() {
1083 let mut g = UndirectedGraph::new(5);
1084 g.add_edge(0, 1, 1.0);
1085 g.add_edge(1, 2, 1.0);
1086 g.add_edge(0, 3, 5.0);
1087 g.add_edge(3, 2, 1.0); let result = g.dijkstra_path(0, 2);
1090 assert!(result.is_some());
1091 let (dist, path) = result.expect("path should exist");
1092 assert!(
1093 (dist - 2.0).abs() < 1e-6,
1094 "expected distance 2.0, got {}",
1095 dist
1096 );
1097 assert_eq!(path, vec![0, 1, 2]);
1098 }
1099
1100 #[test]
1101 fn test_pattern_graph_construction() {
1102 let mut pattern = PatternGraph::new();
1103 let h = pattern.add_node("H");
1104 let cx = pattern.add_node("CNOT");
1105 pattern.add_edge(h, cx);
1106
1107 assert_eq!(pattern.num_nodes(), 2);
1108 assert!(pattern.has_edge(0, 1));
1109 assert!(!pattern.has_edge(1, 0));
1110 }
1111
1112 #[test]
1113 fn test_subgraph_isomorphism_simple() {
1114 let mut pattern = PatternGraph::new();
1116 let ph = pattern.add_node("H");
1117 let pcx = pattern.add_node("CNOT");
1118 pattern.add_edge(ph, pcx);
1119
1120 let target_labels = vec!["H".to_string(), "CNOT".to_string(), "H".to_string()];
1122 let mut target_edges: HashSet<(usize, usize)> = HashSet::new();
1123 target_edges.insert((0, 1)); target_edges.insert((1, 2)); let iso = SubgraphIsomorphism::new(&pattern, &target_labels, &target_edges);
1127 let mappings = iso.find_all();
1128 assert!(!mappings.is_empty(), "Should find at least one isomorphism");
1130 }
1131
1132 #[test]
1133 fn test_find_circuit_pattern_matches() {
1134 let mut circuit = Circuit::<2>::new();
1135 let _ = circuit.h(0);
1136 let _ = circuit.cnot(0, 1);
1137 let _ = circuit.h(1);
1138
1139 let mut pattern = PatternGraph::new();
1141 let _p0 = pattern.add_node(""); let p1 = pattern.add_node("CNOT");
1143 pattern.add_edge(0, 1);
1144
1145 let config = SciRS2TranspilerConfig::default();
1146 let transpiler = SciRS2GraphTranspiler::new(config);
1147
1148 let matches = transpiler
1149 .find_circuit_pattern_matches(&circuit, &pattern)
1150 .expect("Pattern match should succeed");
1151
1152 assert!(
1153 !matches.is_empty(),
1154 "Should find at least one pattern occurrence"
1155 );
1156 }
1157}