use crate::{DeviceError, DeviceResult};
use quantrs2_circuit::prelude::Circuit;
use quantrs2_core::prelude::*;
use std::cmp::Reverse;
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
#[derive(Debug, Clone)]
pub struct DirectedGraph<T> {
nodes: Vec<T>,
edges: HashMap<usize, Vec<usize>>,
}
#[derive(Debug, Clone)]
pub struct UndirectedGraph {
num_nodes: usize,
adjacency: HashMap<usize, Vec<(usize, f64)>>,
}
impl UndirectedGraph {
pub fn new(num_nodes: usize) -> Self {
Self {
num_nodes,
adjacency: HashMap::new(),
}
}
pub fn add_edge(&mut self, u: usize, v: usize, weight: f64) {
self.adjacency
.entry(u)
.or_insert_with(Vec::new)
.push((v, weight));
self.adjacency
.entry(v)
.or_insert_with(Vec::new)
.push((u, weight));
}
pub fn num_nodes(&self) -> usize {
self.num_nodes
}
pub fn bfs(&self, start: usize) -> Vec<usize> {
let mut visited = vec![false; self.num_nodes];
let mut order = Vec::new();
let mut queue = VecDeque::new();
if start >= self.num_nodes {
return order;
}
visited[start] = true;
queue.push_back(start);
while let Some(node) = queue.pop_front() {
order.push(node);
if let Some(neighbors) = self.adjacency.get(&node) {
let mut sorted_neighbors = neighbors.clone();
sorted_neighbors.sort_by_key(|(n, _)| *n);
for (neighbor, _) in sorted_neighbors {
if !visited[neighbor] {
visited[neighbor] = true;
queue.push_back(neighbor);
}
}
}
}
order
}
pub fn dfs(&self, start: usize) -> Vec<usize> {
let mut visited = vec![false; self.num_nodes];
let mut order = Vec::new();
self.dfs_recursive(start, &mut visited, &mut order);
order
}
fn dfs_recursive(&self, node: usize, visited: &mut Vec<bool>, order: &mut Vec<usize>) {
if node >= self.num_nodes || visited[node] {
return;
}
visited[node] = true;
order.push(node);
if let Some(neighbors) = self.adjacency.get(&node) {
let mut sorted_neighbors = neighbors.clone();
sorted_neighbors.sort_by_key(|(n, _)| *n);
for (neighbor, _) in sorted_neighbors {
self.dfs_recursive(neighbor, visited, order);
}
}
}
pub fn dijkstra_distances(&self, source: usize) -> HashMap<usize, f64> {
const SCALE: f64 = 1_000_000_000.0;
let mut heap: BinaryHeap<(Reverse<u64>, usize)> = BinaryHeap::new();
let mut dist: HashMap<usize, u64> = HashMap::new();
dist.insert(source, 0);
heap.push((Reverse(0), source));
while let Some((Reverse(d), u)) = heap.pop() {
if dist.get(&u).map_or(true, |&best| d > best) {
continue;
}
if let Some(neighbors) = self.adjacency.get(&u) {
for &(v, w) in neighbors {
let w_scaled = (w * SCALE) as u64;
let next_d = d.saturating_add(w_scaled);
let better = dist.get(&v).map_or(true, |&cur| next_d < cur);
if better {
dist.insert(v, next_d);
heap.push((Reverse(next_d), v));
}
}
}
}
dist.into_iter()
.map(|(k, v)| (k, v as f64 / SCALE))
.collect()
}
pub fn dijkstra_path(&self, source: usize, target: usize) -> Option<(f64, Vec<usize>)> {
const SCALE: f64 = 1_000_000_000.0;
let mut heap: BinaryHeap<(Reverse<u64>, usize)> = BinaryHeap::new();
let mut dist: HashMap<usize, u64> = HashMap::new();
let mut prev: HashMap<usize, usize> = HashMap::new();
dist.insert(source, 0);
heap.push((Reverse(0), source));
while let Some((Reverse(d), u)) = heap.pop() {
if u == target {
break;
}
if dist.get(&u).map_or(true, |&best| d > best) {
continue;
}
if let Some(neighbors) = self.adjacency.get(&u) {
for &(v, w) in neighbors {
let w_scaled = (w * SCALE) as u64;
let next_d = d.saturating_add(w_scaled);
let better = dist.get(&v).map_or(true, |&cur| next_d < cur);
if better {
dist.insert(v, next_d);
prev.insert(v, u);
heap.push((Reverse(next_d), v));
}
}
}
}
let total_scaled = *dist.get(&target)?;
let total = total_scaled as f64 / SCALE;
let mut path = Vec::new();
let mut cur = target;
loop {
path.push(cur);
if cur == source {
break;
}
match prev.get(&cur) {
Some(&p) => cur = p,
None => return None, }
}
path.reverse();
Some((total, path))
}
}
#[derive(Debug, Clone)]
pub struct PatternGraph {
labels: Vec<String>,
edges: HashSet<(usize, usize)>,
}
impl PatternGraph {
pub fn new() -> Self {
Self {
labels: Vec::new(),
edges: HashSet::new(),
}
}
pub fn add_node(&mut self, label: impl Into<String>) -> usize {
let idx = self.labels.len();
self.labels.push(label.into());
idx
}
pub fn add_edge(&mut self, from: usize, to: usize) {
self.edges.insert((from, to));
}
pub fn num_nodes(&self) -> usize {
self.labels.len()
}
pub fn label(&self, i: usize) -> Option<&str> {
self.labels.get(i).map(String::as_str)
}
pub fn has_edge(&self, from: usize, to: usize) -> bool {
self.edges.contains(&(from, to))
}
}
#[derive(Debug, Clone)]
pub struct IsomorphismMapping {
pub mapping: Vec<usize>,
}
pub struct SubgraphIsomorphism<'a> {
pattern: &'a PatternGraph,
target_labels: &'a [String],
target_edges: &'a HashSet<(usize, usize)>,
}
impl<'a> SubgraphIsomorphism<'a> {
pub fn new(
pattern: &'a PatternGraph,
target_labels: &'a [String],
target_edges: &'a HashSet<(usize, usize)>,
) -> Self {
Self {
pattern,
target_labels,
target_edges,
}
}
pub fn find_all(&self) -> Vec<IsomorphismMapping> {
let n_p = self.pattern.num_nodes();
let n_t = self.target_labels.len();
if n_p == 0 || n_p > n_t {
return Vec::new();
}
let mut results = Vec::new();
let mut partial: Vec<Option<usize>> = vec![None; n_p];
let mut used = vec![false; n_t];
self.backtrack(0, &mut partial, &mut used, &mut results);
results
}
fn backtrack(
&self,
depth: usize,
partial: &mut Vec<Option<usize>>,
used: &mut Vec<bool>,
results: &mut Vec<IsomorphismMapping>,
) {
let n_p = self.pattern.num_nodes();
if depth == n_p {
let mapping: Vec<usize> = partial.iter().filter_map(|x| *x).collect();
results.push(IsomorphismMapping { mapping });
return;
}
for t in 0..self.target_labels.len() {
if used[t] {
continue;
}
if !self.labels_compatible(depth, t) {
continue;
}
if !self.structurally_consistent(depth, t, partial) {
continue;
}
partial[depth] = Some(t);
used[t] = true;
self.backtrack(depth + 1, partial, used, results);
partial[depth] = None;
used[t] = false;
}
}
fn labels_compatible(&self, p: usize, t: usize) -> bool {
let p_label = match self.pattern.label(p) {
Some(l) => l,
None => return false,
};
if p_label.is_empty() {
return true; }
match self.target_labels.get(t) {
Some(t_label) => p_label == t_label.as_str(),
None => false,
}
}
fn structurally_consistent(&self, depth: usize, t: usize, partial: &[Option<usize>]) -> bool {
for q in 0..depth {
let mapped_q = match partial[q] {
Some(m) => m,
None => continue,
};
if self.pattern.has_edge(q, depth) && !self.target_edges.contains(&(mapped_q, t)) {
return false;
}
if self.pattern.has_edge(depth, q) && !self.target_edges.contains(&(t, mapped_q)) {
return false;
}
}
true
}
}
impl<T: Clone + PartialEq> DirectedGraph<T> {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
edges: HashMap::new(),
}
}
pub fn add_node(&mut self, node: T) -> usize {
let idx = self.nodes.len();
self.nodes.push(node);
idx
}
pub fn add_edge(&mut self, from_idx: usize, to_idx: usize) {
self.edges
.entry(from_idx)
.or_insert_with(Vec::new)
.push(to_idx);
}
pub fn nodes(&self) -> &[T] {
&self.nodes
}
pub fn has_edge(&self, from: &T, to: &T) -> bool {
if let Some(from_idx) = self.nodes.iter().position(|n| n == from) {
if let Some(to_idx) = self.nodes.iter().position(|n| n == to) {
if let Some(neighbors) = self.edges.get(&from_idx) {
return neighbors.contains(&to_idx);
}
}
}
false
}
pub fn num_nodes(&self) -> usize {
self.nodes.len()
}
pub fn num_edges(&self) -> usize {
self.edges.values().map(|v| v.len()).sum()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GateNode {
pub gate_index: usize,
pub gate_type: String,
pub qubits: Vec<usize>,
pub depth: usize,
}
#[derive(Debug, Clone)]
pub struct CircuitTopology {
pub qubit_graph: DirectedGraph<usize>,
pub gate_graph: DirectedGraph<GateNode>,
pub critical_path_length: usize,
pub circuit_depth: usize,
}
#[derive(Debug, Clone)]
pub struct HardwareTopology {
pub qubit_connectivity: HashMap<usize, Vec<usize>>,
pub num_physical_qubits: usize,
pub error_rates: HashMap<(usize, usize), f64>,
}
impl Default for HardwareTopology {
fn default() -> Self {
Self {
qubit_connectivity: HashMap::new(),
num_physical_qubits: 0,
error_rates: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct SciRS2TranspilerConfig {
pub enable_commutation: bool,
pub enable_critical_path_opt: bool,
pub enable_routing_opt: bool,
pub max_optimization_passes: usize,
pub hardware_topology: Option<HardwareTopology>,
}
impl Default for SciRS2TranspilerConfig {
fn default() -> Self {
Self {
enable_commutation: true,
enable_critical_path_opt: true,
enable_routing_opt: true,
max_optimization_passes: 3,
hardware_topology: None,
}
}
}
pub struct SciRS2GraphTranspiler {
config: SciRS2TranspilerConfig,
}
impl SciRS2GraphTranspiler {
pub fn new(config: SciRS2TranspilerConfig) -> Self {
Self { config }
}
pub fn build_dependency_graph<const N: usize>(
&self,
circuit: &Circuit<N>,
) -> DeviceResult<DirectedGraph<GateNode>> {
let mut graph = DirectedGraph::new();
let mut qubit_last_gate: HashMap<usize, usize> = HashMap::new();
for (idx, gate) in circuit.gates().iter().enumerate() {
let node = GateNode {
gate_index: idx,
gate_type: gate.name().to_string(),
qubits: gate.qubits().iter().map(|q| q.id() as usize).collect(),
depth: 0, };
let node_idx = graph.add_node(node);
for qubit in gate.qubits() {
let q_id = qubit.id() as usize;
if let Some(&prev_idx) = qubit_last_gate.get(&q_id) {
graph.add_edge(prev_idx, node_idx);
}
qubit_last_gate.insert(q_id, node_idx);
}
}
Ok(graph)
}
pub fn analyze_topology<const N: usize>(
&self,
circuit: &Circuit<N>,
) -> DeviceResult<CircuitTopology> {
let gate_graph = self.build_dependency_graph(circuit)?;
let mut qubit_graph = DirectedGraph::new();
let mut qubit_node_indices: HashMap<usize, usize> = HashMap::new();
for gate in circuit.gates() {
let qubits: Vec<usize> = gate.qubits().iter().map(|q| q.id() as usize).collect();
for &q in &qubits {
qubit_node_indices
.entry(q)
.or_insert_with(|| qubit_graph.add_node(q));
}
if qubits.len() == 2 {
let (q0, q1) = (qubits[0], qubits[1]);
if q0 != q1 {
if let (Some(&idx0), Some(&idx1)) =
(qubit_node_indices.get(&q0), qubit_node_indices.get(&q1))
{
qubit_graph.add_edge(idx0, idx1);
qubit_graph.add_edge(idx1, idx0); }
}
}
}
let circuit_depth = self.compute_circuit_depth(&gate_graph)?;
let critical_path_length = self.compute_critical_path(&gate_graph)?;
Ok(CircuitTopology {
qubit_graph,
gate_graph,
critical_path_length,
circuit_depth,
})
}
fn compute_circuit_depth(&self, gate_graph: &DirectedGraph<GateNode>) -> DeviceResult<usize> {
let mut depths: HashMap<usize, usize> = HashMap::new();
let mut max_depth = 0;
for node in gate_graph.nodes() {
let mut gate_depth = 0;
for potential_pred in gate_graph.nodes() {
if gate_graph.has_edge(potential_pred, node) {
if let Some(&pred_depth) = depths.get(&potential_pred.gate_index) {
gate_depth = gate_depth.max(pred_depth + 1);
}
}
}
depths.insert(node.gate_index, gate_depth);
max_depth = max_depth.max(gate_depth);
}
Ok(max_depth + 1)
}
fn compute_critical_path(&self, gate_graph: &DirectedGraph<GateNode>) -> DeviceResult<usize> {
let mut longest_paths: HashMap<usize, usize> = HashMap::new();
let mut max_path_length = 0;
for node in gate_graph.nodes() {
let mut path_length = 0;
for potential_pred in gate_graph.nodes() {
if gate_graph.has_edge(potential_pred, node) {
if let Some(&pred_path) = longest_paths.get(&potential_pred.gate_index) {
path_length = path_length.max(pred_path + 1);
}
}
}
longest_paths.insert(node.gate_index, path_length);
max_path_length = max_path_length.max(path_length);
}
Ok(max_path_length)
}
pub fn optimize_qubit_routing<const N: usize>(
&self,
circuit: &Circuit<N>,
hardware_topology: &HardwareTopology,
) -> DeviceResult<HashMap<usize, usize>> {
let n_phys = hardware_topology.num_physical_qubits;
let mut hw_graph = UndirectedGraph::new(n_phys);
for (&phys_q, neighbors) in &hardware_topology.qubit_connectivity {
for &neighbor in neighbors {
if phys_q < neighbor {
let weight = hardware_topology
.error_rates
.get(&(phys_q, neighbor))
.copied()
.unwrap_or(1.0);
hw_graph.add_edge(phys_q, neighbor, weight);
}
}
}
let all_dists: Vec<HashMap<usize, f64>> = (0..n_phys)
.map(|src| hw_graph.dijkstra_distances(src))
.collect();
let mut interaction_freq: HashMap<(usize, usize), usize> = HashMap::new();
for gate in circuit.gates() {
let qubits: Vec<usize> = gate.qubits().iter().map(|q| q.id() as usize).collect();
if qubits.len() == 2 {
let (a, b) = (qubits[0].min(qubits[1]), qubits[0].max(qubits[1]));
*interaction_freq.entry((a, b)).or_insert(0) += 1;
}
}
let mut qubit_freq = vec![0usize; N];
for (&(a, b), &freq) in &interaction_freq {
if a < N {
qubit_freq[a] += freq;
}
if b < N {
qubit_freq[b] += freq;
}
}
let mut sorted_logical: Vec<usize> = (0..N).collect();
sorted_logical.sort_by(|&x, &y| qubit_freq[y].cmp(&qubit_freq[x]));
let mut mapping: HashMap<usize, usize> = HashMap::new();
let mut assigned_phys: HashSet<usize> = HashSet::new();
for &logical in &sorted_logical {
let assigned_neighbors: Vec<usize> = mapping.keys().copied().collect();
let best_phys = (0..n_phys)
.filter(|p| !assigned_phys.contains(p))
.min_by(|&p1, &p2| {
let score = |p: usize| -> f64 {
if assigned_neighbors.is_empty() {
return p as f64; }
assigned_neighbors
.iter()
.map(|ln| {
let phys_n = *mapping.get(ln).unwrap_or(&0);
all_dists[p].get(&phys_n).copied().unwrap_or(f64::MAX)
})
.sum::<f64>()
/ assigned_neighbors.len() as f64
};
score(p1)
.partial_cmp(&score(p2))
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap_or_else(|| logical % n_phys.max(1));
mapping.insert(logical, best_phys);
assigned_phys.insert(best_phys);
}
Ok(mapping)
}
pub fn find_circuit_pattern_matches<const N: usize>(
&self,
circuit: &Circuit<N>,
pattern: &PatternGraph,
) -> DeviceResult<Vec<IsomorphismMapping>> {
let dep_graph = self.build_dependency_graph(circuit)?;
let target_labels: Vec<String> = dep_graph
.nodes()
.iter()
.map(|n| n.gate_type.clone())
.collect();
let mut target_edges: HashSet<(usize, usize)> = HashSet::new();
for (from_idx, to_list) in &dep_graph.edges {
for &to_idx in to_list {
target_edges.insert((*from_idx, to_idx));
}
}
let iso = SubgraphIsomorphism::new(pattern, &target_labels, &target_edges);
Ok(iso.find_all())
}
pub fn find_commuting_gates<const N: usize>(
&self,
circuit: &Circuit<N>,
) -> DeviceResult<Vec<(usize, usize)>> {
let mut commuting_pairs = Vec::new();
let gates = circuit.gates();
for i in 0..gates.len() {
for j in (i + 1)..gates.len() {
let qubits_i: HashSet<u32> = gates[i].qubits().iter().map(|q| q.id()).collect();
let qubits_j: HashSet<u32> = gates[j].qubits().iter().map(|q| q.id()).collect();
if qubits_i.is_disjoint(&qubits_j) {
commuting_pairs.push((i, j));
}
}
}
Ok(commuting_pairs)
}
pub fn optimize_circuit<const N: usize>(
&self,
circuit: &Circuit<N>,
) -> DeviceResult<Circuit<N>> {
let _topology = self.analyze_topology(circuit)?;
Ok(circuit.clone())
}
pub fn generate_optimization_report<const N: usize>(
&self,
circuit: &Circuit<N>,
) -> DeviceResult<String> {
let topology = self.analyze_topology(circuit)?;
let mut report = String::from("=== SciRS2 Graph Transpiler Analysis ===\n\n");
report.push_str(&format!("Circuit Depth: {}\n", topology.circuit_depth));
report.push_str(&format!(
"Critical Path Length: {}\n",
topology.critical_path_length
));
report.push_str(&format!("Number of Gates: {}\n", circuit.gates().len()));
report.push_str(&format!("Number of Qubits: {}\n", N));
let num_qubit_edges = topology.qubit_graph.num_edges();
report.push_str(&format!("Qubit Connections: {}\n", num_qubit_edges));
let num_dependencies = topology.gate_graph.num_edges();
report.push_str(&format!("Gate Dependencies: {}\n", num_dependencies));
if self.config.enable_commutation {
let commuting = self.find_commuting_gates(circuit)?;
report.push_str(&format!("Commuting Gate Pairs: {}\n", commuting.len()));
}
Ok(report)
}
}
#[cfg(test)]
#[allow(clippy::pedantic, clippy::field_reassign_with_default)]
mod tests {
use super::*;
use quantrs2_circuit::prelude::*;
#[test]
fn test_transpiler_creation() {
let config = SciRS2TranspilerConfig::default();
let transpiler = SciRS2GraphTranspiler::new(config);
assert!(transpiler.config.enable_commutation);
}
#[test]
fn test_dependency_graph_building() {
let mut circuit = Circuit::<2>::new();
let _ = circuit.h(0);
let _ = circuit.cnot(0, 1);
let _ = circuit.h(1);
let config = SciRS2TranspilerConfig::default();
let transpiler = SciRS2GraphTranspiler::new(config);
let graph = transpiler
.build_dependency_graph(&circuit)
.expect("Failed to build dependency graph");
assert_eq!(graph.num_nodes(), 3); }
#[test]
fn test_topology_analysis() {
let mut circuit = Circuit::<3>::new();
let _ = circuit.h(0);
let _ = circuit.h(1);
let _ = circuit.cnot(0, 1);
let _ = circuit.cnot(1, 2);
let config = SciRS2TranspilerConfig::default();
let transpiler = SciRS2GraphTranspiler::new(config);
let topology = transpiler
.analyze_topology(&circuit)
.expect("Failed to analyze topology");
assert!(topology.circuit_depth > 0);
assert!(topology.critical_path_length > 0);
}
#[test]
fn test_commuting_gates_detection() {
let mut circuit = Circuit::<4>::new();
let _ = circuit.h(0);
let _ = circuit.h(1); let _ = circuit.x(2); let _ = circuit.cnot(0, 1);
let config = SciRS2TranspilerConfig::default();
let transpiler = SciRS2GraphTranspiler::new(config);
let commuting = transpiler
.find_commuting_gates(&circuit)
.expect("Failed to find commuting gates");
assert!(!commuting.is_empty());
}
#[test]
fn test_optimization_report() {
let mut circuit = Circuit::<2>::new();
let _ = circuit.h(0);
let _ = circuit.cnot(0, 1);
let _ = circuit.measure_all();
let config = SciRS2TranspilerConfig::default();
let transpiler = SciRS2GraphTranspiler::new(config);
let report = transpiler
.generate_optimization_report(&circuit)
.expect("Failed to generate report");
assert!(report.contains("Circuit Depth"));
assert!(report.contains("Critical Path"));
}
#[test]
fn test_hardware_topology_creation() {
let mut topology = HardwareTopology {
num_physical_qubits: 5,
..Default::default()
};
topology.qubit_connectivity.insert(0, vec![1]);
topology.qubit_connectivity.insert(1, vec![0, 2]);
topology.qubit_connectivity.insert(2, vec![1, 3]);
topology.qubit_connectivity.insert(3, vec![2, 4]);
topology.qubit_connectivity.insert(4, vec![3]);
assert_eq!(topology.num_physical_qubits, 5);
assert_eq!(topology.qubit_connectivity.len(), 5);
}
#[test]
fn test_qubit_routing_optimization() {
let mut circuit = Circuit::<3>::new();
let _ = circuit.cnot(0, 1);
let _ = circuit.cnot(1, 2);
let mut hardware = HardwareTopology::default();
hardware.num_physical_qubits = 5;
hardware.qubit_connectivity.insert(0, vec![1]);
hardware.qubit_connectivity.insert(1, vec![0, 2]);
hardware.qubit_connectivity.insert(2, vec![1]);
let config = SciRS2TranspilerConfig {
enable_routing_opt: true,
..Default::default()
};
let transpiler = SciRS2GraphTranspiler::new(config);
let mapping = transpiler
.optimize_qubit_routing(&circuit, &hardware)
.expect("Failed to optimize routing");
assert_eq!(mapping.len(), 3);
}
#[test]
fn test_undirected_graph_bfs() {
let mut g = UndirectedGraph::new(4);
g.add_edge(0, 1, 1.0);
g.add_edge(1, 2, 1.0);
g.add_edge(2, 3, 1.0);
let order = g.bfs(0);
assert_eq!(order, vec![0, 1, 2, 3]);
}
#[test]
fn test_undirected_graph_dfs() {
let mut g = UndirectedGraph::new(4);
g.add_edge(0, 1, 1.0);
g.add_edge(1, 2, 1.0);
g.add_edge(2, 3, 1.0);
let order = g.dfs(0);
assert_eq!(order, vec![0, 1, 2, 3]);
}
#[test]
fn test_dijkstra_linear_graph() {
let mut g = UndirectedGraph::new(4);
g.add_edge(0, 1, 1.0);
g.add_edge(1, 2, 2.0);
g.add_edge(2, 3, 1.0);
let dists = g.dijkstra_distances(0);
assert!((dists[&0] - 0.0).abs() < 1e-6);
assert!((dists[&1] - 1.0).abs() < 1e-6);
assert!((dists[&2] - 3.0).abs() < 1e-6);
assert!((dists[&3] - 4.0).abs() < 1e-6);
}
#[test]
fn test_dijkstra_path() {
let mut g = UndirectedGraph::new(5);
g.add_edge(0, 1, 1.0);
g.add_edge(1, 2, 1.0);
g.add_edge(0, 3, 5.0);
g.add_edge(3, 2, 1.0);
let result = g.dijkstra_path(0, 2);
assert!(result.is_some());
let (dist, path) = result.expect("path should exist");
assert!(
(dist - 2.0).abs() < 1e-6,
"expected distance 2.0, got {}",
dist
);
assert_eq!(path, vec![0, 1, 2]);
}
#[test]
fn test_pattern_graph_construction() {
let mut pattern = PatternGraph::new();
let h = pattern.add_node("H");
let cx = pattern.add_node("CNOT");
pattern.add_edge(h, cx);
assert_eq!(pattern.num_nodes(), 2);
assert!(pattern.has_edge(0, 1));
assert!(!pattern.has_edge(1, 0));
}
#[test]
fn test_subgraph_isomorphism_simple() {
let mut pattern = PatternGraph::new();
let ph = pattern.add_node("H");
let pcx = pattern.add_node("CNOT");
pattern.add_edge(ph, pcx);
let target_labels = vec!["H".to_string(), "CNOT".to_string(), "H".to_string()];
let mut target_edges: HashSet<(usize, usize)> = HashSet::new();
target_edges.insert((0, 1)); target_edges.insert((1, 2));
let iso = SubgraphIsomorphism::new(&pattern, &target_labels, &target_edges);
let mappings = iso.find_all();
assert!(!mappings.is_empty(), "Should find at least one isomorphism");
}
#[test]
fn test_find_circuit_pattern_matches() {
let mut circuit = Circuit::<2>::new();
let _ = circuit.h(0);
let _ = circuit.cnot(0, 1);
let _ = circuit.h(1);
let mut pattern = PatternGraph::new();
let _p0 = pattern.add_node(""); let p1 = pattern.add_node("CNOT");
pattern.add_edge(0, 1);
let config = SciRS2TranspilerConfig::default();
let transpiler = SciRS2GraphTranspiler::new(config);
let matches = transpiler
.find_circuit_pattern_matches(&circuit, &pattern)
.expect("Pattern match should succeed");
assert!(
!matches.is_empty(),
"Should find at least one pattern occurrence"
);
}
}