use std::collections::VecDeque;
use crate::circuit::QuantumCircuit;
use crate::gate::Gate;
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, PI};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BasisGateSet {
IbmEagle,
IonQAria,
RigettiAspen,
Universal,
}
#[derive(Debug, Clone)]
pub struct TranspilerConfig {
pub basis: BasisGateSet,
pub coupling_map: Option<Vec<(u32, u32)>>,
pub optimization_level: u8,
}
pub fn transpile(circuit: &QuantumCircuit, config: &TranspilerConfig) -> QuantumCircuit {
let decomposed = decompose(circuit, config.basis);
let routed = match &config.coupling_map {
Some(map) => route_circuit(&decomposed, map),
None => decomposed,
};
optimize_gates(&routed, config.optimization_level)
}
fn decompose(circuit: &QuantumCircuit, basis: BasisGateSet) -> QuantumCircuit {
if basis == BasisGateSet::Universal {
return circuit.clone();
}
let mut result = QuantumCircuit::new(circuit.num_qubits());
for gate in circuit.gates() {
let decomposed = match basis {
BasisGateSet::IbmEagle => decompose_to_ibm(gate),
BasisGateSet::IonQAria => decompose_to_ionq(gate),
BasisGateSet::RigettiAspen => decompose_to_rigetti(gate),
BasisGateSet::Universal => unreachable!(),
};
for g in decomposed {
result.add_gate(g);
}
}
result
}
pub fn decompose_to_ibm(gate: &Gate) -> Vec<Gate> {
match gate {
Gate::CNOT(c, t) => vec![Gate::CNOT(*c, *t)],
Gate::X(q) => vec![Gate::X(*q)],
Gate::Rz(q, theta) => vec![Gate::Rz(*q, *theta)],
Gate::H(q) => vec![
Gate::Rz(*q, PI),
Gate::Rx(*q, FRAC_PI_2), Gate::Rz(*q, PI),
],
Gate::S(q) => vec![Gate::Rz(*q, FRAC_PI_2)],
Gate::Sdg(q) => vec![Gate::Rz(*q, -FRAC_PI_2)],
Gate::T(q) => vec![Gate::Rz(*q, FRAC_PI_4)],
Gate::Tdg(q) => vec![Gate::Rz(*q, -FRAC_PI_4)],
Gate::Y(q) => vec![Gate::X(*q), Gate::Rz(*q, PI)],
Gate::Z(q) => vec![Gate::Rz(*q, PI)],
Gate::Phase(q, theta) => vec![Gate::Rz(*q, *theta)],
Gate::Rx(q, theta) => {
if (*theta - FRAC_PI_2).abs() < 1e-12 {
vec![Gate::Rx(*q, FRAC_PI_2)]
} else {
vec![
Gate::Rz(*q, -FRAC_PI_2),
Gate::Rx(*q, FRAC_PI_2),
Gate::Rz(*q, PI - theta),
Gate::Rx(*q, FRAC_PI_2),
Gate::Rz(*q, -FRAC_PI_2),
]
}
}
Gate::Ry(q, theta) => vec![
Gate::Rz(*q, -FRAC_PI_2),
Gate::Rx(*q, FRAC_PI_2),
Gate::Rz(*q, theta + PI),
Gate::Rx(*q, FRAC_PI_2),
Gate::Rz(*q, FRAC_PI_2),
],
Gate::CZ(q1, q2) => {
let mut gates = Vec::new();
gates.extend(decompose_to_ibm(&Gate::H(*q2)));
gates.push(Gate::CNOT(*q1, *q2));
gates.extend(decompose_to_ibm(&Gate::H(*q2)));
gates
}
Gate::SWAP(a, b) => vec![
Gate::CNOT(*a, *b),
Gate::CNOT(*b, *a),
Gate::CNOT(*a, *b),
],
Gate::Rzz(a, b, theta) => vec![
Gate::CNOT(*a, *b),
Gate::Rz(*b, *theta),
Gate::CNOT(*a, *b),
],
Gate::Measure(q) => vec![Gate::Measure(*q)],
Gate::Reset(q) => vec![Gate::Reset(*q)],
Gate::Barrier => vec![Gate::Barrier],
Gate::Unitary1Q(q, m) => vec![Gate::Unitary1Q(*q, *m)],
}
}
pub fn decompose_to_rigetti(gate: &Gate) -> Vec<Gate> {
match gate {
Gate::CZ(q1, q2) => vec![Gate::CZ(*q1, *q2)],
Gate::Rx(q, theta) => vec![Gate::Rx(*q, *theta)],
Gate::Rz(q, theta) => vec![Gate::Rz(*q, *theta)],
Gate::H(q) => vec![Gate::Rz(*q, PI), Gate::Rx(*q, FRAC_PI_2)],
Gate::X(q) => vec![Gate::Rx(*q, PI)],
Gate::Y(q) => vec![Gate::Rx(*q, PI), Gate::Rz(*q, PI)],
Gate::Z(q) => vec![Gate::Rz(*q, PI)],
Gate::S(q) => vec![Gate::Rz(*q, FRAC_PI_2)],
Gate::Sdg(q) => vec![Gate::Rz(*q, -FRAC_PI_2)],
Gate::T(q) => vec![Gate::Rz(*q, FRAC_PI_4)],
Gate::Tdg(q) => vec![Gate::Rz(*q, -FRAC_PI_4)],
Gate::Phase(q, theta) => vec![Gate::Rz(*q, *theta)],
Gate::Ry(q, theta) => vec![
Gate::Rz(*q, -FRAC_PI_2),
Gate::Rx(*q, *theta),
Gate::Rz(*q, FRAC_PI_2),
],
Gate::CNOT(c, t) => {
let mut gates = Vec::new();
gates.extend(decompose_to_rigetti(&Gate::H(*t)));
gates.push(Gate::CZ(*c, *t));
gates.extend(decompose_to_rigetti(&Gate::H(*t)));
gates
}
Gate::SWAP(a, b) => {
let mut gates = Vec::new();
gates.extend(decompose_to_rigetti(&Gate::CNOT(*a, *b)));
gates.extend(decompose_to_rigetti(&Gate::CNOT(*b, *a)));
gates.extend(decompose_to_rigetti(&Gate::CNOT(*a, *b)));
gates
}
Gate::Rzz(a, b, theta) => {
let mut gates = Vec::new();
gates.extend(decompose_to_rigetti(&Gate::CNOT(*a, *b)));
gates.push(Gate::Rz(*b, *theta));
gates.extend(decompose_to_rigetti(&Gate::CNOT(*a, *b)));
gates
}
Gate::Measure(q) => vec![Gate::Measure(*q)],
Gate::Reset(q) => vec![Gate::Reset(*q)],
Gate::Barrier => vec![Gate::Barrier],
Gate::Unitary1Q(q, m) => vec![Gate::Unitary1Q(*q, *m)],
}
}
pub fn decompose_to_ionq(gate: &Gate) -> Vec<Gate> {
match gate {
Gate::Rx(q, theta) => vec![Gate::Rx(*q, *theta)],
Gate::Ry(q, theta) => vec![Gate::Ry(*q, *theta)],
Gate::Rzz(a, b, theta) => vec![Gate::Rzz(*a, *b, *theta)],
Gate::H(q) => vec![Gate::Ry(*q, FRAC_PI_2), Gate::Rx(*q, PI)],
Gate::X(q) => vec![Gate::Rx(*q, PI)],
Gate::Y(q) => vec![Gate::Ry(*q, PI)],
Gate::Z(q) => vec![Gate::Rx(*q, PI), Gate::Ry(*q, PI)],
Gate::S(q) => vec![
Gate::Rx(*q, -FRAC_PI_2),
Gate::Ry(*q, FRAC_PI_2),
Gate::Rx(*q, FRAC_PI_2),
],
Gate::Sdg(q) => vec![
Gate::Rx(*q, -FRAC_PI_2),
Gate::Ry(*q, -FRAC_PI_2),
Gate::Rx(*q, FRAC_PI_2),
],
Gate::T(q) => vec![
Gate::Rx(*q, -FRAC_PI_2),
Gate::Ry(*q, FRAC_PI_4),
Gate::Rx(*q, FRAC_PI_2),
],
Gate::Tdg(q) => vec![
Gate::Rx(*q, -FRAC_PI_2),
Gate::Ry(*q, -FRAC_PI_4),
Gate::Rx(*q, FRAC_PI_2),
],
Gate::Rz(q, theta) => vec![
Gate::Rx(*q, -FRAC_PI_2),
Gate::Ry(*q, *theta),
Gate::Rx(*q, FRAC_PI_2),
],
Gate::Phase(q, theta) => decompose_to_ionq(&Gate::Rz(*q, *theta)),
Gate::CNOT(c, t) => vec![
Gate::Ry(*t, -FRAC_PI_2),
Gate::Rzz(*c, *t, FRAC_PI_2),
Gate::Rx(*c, -FRAC_PI_2),
Gate::Rx(*t, -FRAC_PI_2),
Gate::Ry(*t, FRAC_PI_2),
],
Gate::CZ(q1, q2) => {
let mut gates = Vec::new();
gates.extend(decompose_to_ionq(&Gate::H(*q2)));
gates.extend(decompose_to_ionq(&Gate::CNOT(*q1, *q2)));
gates.extend(decompose_to_ionq(&Gate::H(*q2)));
gates
}
Gate::SWAP(a, b) => {
let mut gates = Vec::new();
gates.extend(decompose_to_ionq(&Gate::CNOT(*a, *b)));
gates.extend(decompose_to_ionq(&Gate::CNOT(*b, *a)));
gates.extend(decompose_to_ionq(&Gate::CNOT(*a, *b)));
gates
}
Gate::Measure(q) => vec![Gate::Measure(*q)],
Gate::Reset(q) => vec![Gate::Reset(*q)],
Gate::Barrier => vec![Gate::Barrier],
Gate::Unitary1Q(q, m) => vec![Gate::Unitary1Q(*q, *m)],
}
}
pub fn route_circuit(circuit: &QuantumCircuit, coupling_map: &[(u32, u32)]) -> QuantumCircuit {
let n = circuit.num_qubits() as usize;
let adj = build_adjacency_list(coupling_map, n);
let mut log2phys: Vec<u32> = (0..n as u32).collect();
let mut phys2log: Vec<u32> = (0..n as u32).collect();
let mut result = QuantumCircuit::new(circuit.num_qubits());
for gate in circuit.gates() {
let qubits = gate.qubits();
if qubits.len() == 2 {
let logical_a = qubits[0];
let logical_b = qubits[1];
let mut phys_a = log2phys[logical_a as usize];
let mut phys_b = log2phys[logical_b as usize];
if !are_adjacent(&adj, phys_a, phys_b) {
let path = bfs_shortest_path(&adj, phys_a, phys_b, n);
for i in 0..path.len() - 2 {
let p1 = path[i];
let p2 = path[i + 1];
result.add_gate(Gate::SWAP(p1, p2));
let log1 = phys2log[p1 as usize];
let log2 = phys2log[p2 as usize];
log2phys[log1 as usize] = p2;
log2phys[log2 as usize] = p1;
phys2log[p1 as usize] = log2;
phys2log[p2 as usize] = log1;
}
phys_a = log2phys[logical_a as usize];
phys_b = log2phys[logical_b as usize];
}
result.add_gate(remap_gate(gate, &log2phys));
debug_assert!(
are_adjacent(&adj, phys_a, phys_b),
"routing failed: qubits {} and {} are not adjacent after SWAP insertion",
phys_a,
phys_b
);
} else if qubits.len() == 1 {
result.add_gate(remap_gate(gate, &log2phys));
} else {
result.add_gate(gate.clone());
}
}
result
}
fn build_adjacency_list(coupling_map: &[(u32, u32)], n: usize) -> Vec<Vec<u32>> {
let mut adj: Vec<Vec<u32>> = vec![Vec::new(); n];
for &(a, b) in coupling_map {
if (a as usize) < n && (b as usize) < n {
if !adj[a as usize].contains(&b) {
adj[a as usize].push(b);
}
if !adj[b as usize].contains(&a) {
adj[b as usize].push(a);
}
}
}
adj
}
fn are_adjacent(adj: &[Vec<u32>], a: u32, b: u32) -> bool {
adj.get(a as usize)
.map(|neighbors| neighbors.contains(&b))
.unwrap_or(false)
}
fn bfs_shortest_path(adj: &[Vec<u32>], start: u32, end: u32, n: usize) -> Vec<u32> {
if start == end {
return vec![start];
}
let mut visited = vec![false; n];
let mut parent: Vec<Option<u32>> = vec![None; n];
let mut queue = VecDeque::new();
visited[start as usize] = true;
queue.push_back(start);
while let Some(current) = queue.pop_front() {
if current == end {
break;
}
for &neighbor in &adj[current as usize] {
if !visited[neighbor as usize] {
visited[neighbor as usize] = true;
parent[neighbor as usize] = Some(current);
queue.push_back(neighbor);
}
}
}
let mut path = Vec::new();
let mut current = end;
path.push(current);
while let Some(p) = parent[current as usize] {
path.push(p);
current = p;
if current == start {
break;
}
}
path.reverse();
path
}
fn remap_gate(gate: &Gate, log2phys: &[u32]) -> Gate {
match gate {
Gate::H(q) => Gate::H(log2phys[*q as usize]),
Gate::X(q) => Gate::X(log2phys[*q as usize]),
Gate::Y(q) => Gate::Y(log2phys[*q as usize]),
Gate::Z(q) => Gate::Z(log2phys[*q as usize]),
Gate::S(q) => Gate::S(log2phys[*q as usize]),
Gate::Sdg(q) => Gate::Sdg(log2phys[*q as usize]),
Gate::T(q) => Gate::T(log2phys[*q as usize]),
Gate::Tdg(q) => Gate::Tdg(log2phys[*q as usize]),
Gate::Rx(q, theta) => Gate::Rx(log2phys[*q as usize], *theta),
Gate::Ry(q, theta) => Gate::Ry(log2phys[*q as usize], *theta),
Gate::Rz(q, theta) => Gate::Rz(log2phys[*q as usize], *theta),
Gate::Phase(q, theta) => Gate::Phase(log2phys[*q as usize], *theta),
Gate::CNOT(c, t) => Gate::CNOT(log2phys[*c as usize], log2phys[*t as usize]),
Gate::CZ(a, b) => Gate::CZ(log2phys[*a as usize], log2phys[*b as usize]),
Gate::SWAP(a, b) => Gate::SWAP(log2phys[*a as usize], log2phys[*b as usize]),
Gate::Rzz(a, b, theta) => {
Gate::Rzz(log2phys[*a as usize], log2phys[*b as usize], *theta)
}
Gate::Measure(q) => Gate::Measure(log2phys[*q as usize]),
Gate::Reset(q) => Gate::Reset(log2phys[*q as usize]),
Gate::Barrier => Gate::Barrier,
Gate::Unitary1Q(q, m) => Gate::Unitary1Q(log2phys[*q as usize], *m),
}
}
pub fn optimize_gates(circuit: &QuantumCircuit, level: u8) -> QuantumCircuit {
if level == 0 {
return circuit.clone();
}
let mut gates: Vec<Gate> = circuit.gates().to_vec();
let mut changed = true;
while changed {
changed = false;
let (new_gates, did_cancel) = cancel_inverse_pairs(&gates);
if did_cancel {
gates = new_gates;
changed = true;
}
if level >= 2 {
let (new_gates, did_merge) = merge_adjacent_rz(&gates);
if did_merge {
gates = new_gates;
changed = true;
}
}
}
let mut result = QuantumCircuit::new(circuit.num_qubits());
for g in gates {
result.add_gate(g);
}
result
}
fn cancel_inverse_pairs(gates: &[Gate]) -> (Vec<Gate>, bool) {
let mut result: Vec<Gate> = Vec::with_capacity(gates.len());
let mut changed = false;
let mut i = 0;
while i < gates.len() {
if i + 1 < gates.len() && is_inverse_pair(&gates[i], &gates[i + 1]) {
changed = true;
i += 2;
} else {
result.push(gates[i].clone());
i += 1;
}
}
(result, changed)
}
fn is_inverse_pair(a: &Gate, b: &Gate) -> bool {
match (a, b) {
(Gate::H(q1), Gate::H(q2)) if q1 == q2 => true,
(Gate::X(q1), Gate::X(q2)) if q1 == q2 => true,
(Gate::Y(q1), Gate::Y(q2)) if q1 == q2 => true,
(Gate::Z(q1), Gate::Z(q2)) if q1 == q2 => true,
(Gate::S(q1), Gate::Sdg(q2)) if q1 == q2 => true,
(Gate::Sdg(q1), Gate::S(q2)) if q1 == q2 => true,
(Gate::T(q1), Gate::Tdg(q2)) if q1 == q2 => true,
(Gate::Tdg(q1), Gate::T(q2)) if q1 == q2 => true,
(Gate::CNOT(c1, t1), Gate::CNOT(c2, t2)) if c1 == c2 && t1 == t2 => true,
(Gate::CZ(a1, b1), Gate::CZ(a2, b2))
if (a1 == a2 && b1 == b2) || (a1 == b2 && b1 == a2) =>
{
true
}
(Gate::SWAP(a1, b1), Gate::SWAP(a2, b2))
if (a1 == a2 && b1 == b2) || (a1 == b2 && b1 == a2) =>
{
true
}
_ => false,
}
}
fn merge_adjacent_rz(gates: &[Gate]) -> (Vec<Gate>, bool) {
let mut result: Vec<Gate> = Vec::with_capacity(gates.len());
let mut changed = false;
let mut i = 0;
let epsilon = 1e-12;
while i < gates.len() {
if let Gate::Rz(q1, a) = &gates[i] {
let mut total_angle = *a;
let qubit = *q1;
let mut count = 1;
while i + count < gates.len() {
if let Gate::Rz(q2, b) = &gates[i + count] {
if *q2 == qubit {
total_angle += b;
count += 1;
continue;
}
}
break;
}
if count > 1 {
changed = true;
if total_angle.abs() > epsilon {
result.push(Gate::Rz(qubit, total_angle));
}
} else {
result.push(gates[i].clone());
}
i += count;
} else {
result.push(gates[i].clone());
i += 1;
}
}
(result, changed)
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, PI};
#[test]
fn test_decompose_h_to_ibm() {
let gates = decompose_to_ibm(&Gate::H(0));
assert_eq!(gates.len(), 3);
assert!(matches!(gates[0], Gate::Rz(0, _)));
assert!(matches!(gates[1], Gate::Rx(0, _)));
assert!(matches!(gates[2], Gate::Rz(0, _)));
if let Gate::Rx(_, theta) = &gates[1] {
assert!((theta - FRAC_PI_2).abs() < 1e-12);
} else {
panic!("expected Rx");
}
}
#[test]
fn test_decompose_s_to_ibm() {
let gates = decompose_to_ibm(&Gate::S(0));
assert_eq!(gates.len(), 1);
if let Gate::Rz(0, theta) = &gates[0] {
assert!((theta - FRAC_PI_2).abs() < 1e-12);
} else {
panic!("expected Rz(pi/2)");
}
}
#[test]
fn test_decompose_t_to_ibm() {
let gates = decompose_to_ibm(&Gate::T(0));
assert_eq!(gates.len(), 1);
if let Gate::Rz(0, theta) = &gates[0] {
assert!((theta - FRAC_PI_4).abs() < 1e-12);
} else {
panic!("expected Rz(pi/4)");
}
}
#[test]
fn test_decompose_swap_to_ibm() {
let gates = decompose_to_ibm(&Gate::SWAP(0, 1));
assert_eq!(gates.len(), 3);
assert!(gates.iter().all(|g| matches!(g, Gate::CNOT(_, _))));
}
#[test]
fn test_decompose_cz_to_ibm() {
let gates = decompose_to_ibm(&Gate::CZ(0, 1));
assert_eq!(gates.len(), 7);
assert!(matches!(gates[3], Gate::CNOT(0, 1)));
}
#[test]
fn test_decompose_cnot_to_rigetti_produces_cz() {
let gates = decompose_to_rigetti(&Gate::CNOT(0, 1));
assert_eq!(gates.len(), 5);
let cz_count = gates.iter().filter(|g| matches!(g, Gate::CZ(_, _))).count();
assert_eq!(cz_count, 1);
assert!(matches!(gates[2], Gate::CZ(0, 1)));
}
#[test]
fn test_decompose_h_to_rigetti() {
let gates = decompose_to_rigetti(&Gate::H(0));
assert_eq!(gates.len(), 2);
assert!(matches!(gates[0], Gate::Rz(0, _)));
assert!(matches!(gates[1], Gate::Rx(0, _)));
}
#[test]
fn test_decompose_cnot_to_ionq() {
let gates = decompose_to_ionq(&Gate::CNOT(0, 1));
let rzz_count = gates
.iter()
.filter(|g| matches!(g, Gate::Rzz(_, _, _)))
.count();
assert_eq!(rzz_count, 1);
assert_eq!(gates.len(), 5);
}
#[test]
fn test_decompose_preserves_non_unitary() {
let measure_ibm = decompose_to_ibm(&Gate::Measure(0));
assert_eq!(measure_ibm.len(), 1);
assert!(matches!(measure_ibm[0], Gate::Measure(0)));
let barrier_rigetti = decompose_to_rigetti(&Gate::Barrier);
assert_eq!(barrier_rigetti.len(), 1);
assert!(matches!(barrier_rigetti[0], Gate::Barrier));
let reset_ionq = decompose_to_ionq(&Gate::Reset(2));
assert_eq!(reset_ionq.len(), 1);
assert!(matches!(reset_ionq[0], Gate::Reset(2)));
}
#[test]
fn test_route_adjacent_cnot_no_swaps() {
let coupling = vec![(0, 1), (1, 2)];
let mut circuit = QuantumCircuit::new(3);
circuit.cnot(0, 1);
let routed = route_circuit(&circuit, &coupling);
let swap_count = routed
.gates()
.iter()
.filter(|g| matches!(g, Gate::SWAP(_, _)))
.count();
assert_eq!(swap_count, 0);
assert_eq!(routed.gates().len(), 1);
}
#[test]
fn test_route_non_adjacent_cnot_inserts_swaps() {
let coupling = vec![(0, 1), (1, 2)];
let mut circuit = QuantumCircuit::new(3);
circuit.cnot(0, 2);
let routed = route_circuit(&circuit, &coupling);
let swap_count = routed
.gates()
.iter()
.filter(|g| matches!(g, Gate::SWAP(_, _)))
.count();
assert!(swap_count >= 1, "expected at least 1 SWAP, got {}", swap_count);
}
#[test]
fn test_route_single_qubit_gate_remapped() {
let coupling = vec![(0, 1), (1, 2)];
let mut circuit = QuantumCircuit::new(3);
circuit.h(0);
let routed = route_circuit(&circuit, &coupling);
assert_eq!(routed.gates().len(), 1);
assert!(matches!(routed.gates()[0], Gate::H(0)));
}
#[test]
fn test_bfs_shortest_path_linear() {
let coupling = vec![(0, 1), (1, 2), (2, 3)];
let adj = build_adjacency_list(&coupling, 4);
let path = bfs_shortest_path(&adj, 0, 3, 4);
assert_eq!(path, vec![0, 1, 2, 3]);
}
#[test]
fn test_bfs_shortest_path_branching() {
let coupling = vec![(0, 1), (0, 2), (0, 3)];
let adj = build_adjacency_list(&coupling, 4);
let path = bfs_shortest_path(&adj, 1, 3, 4);
assert_eq!(path.len(), 3);
assert_eq!(path[0], 1);
assert_eq!(*path.last().unwrap(), 3);
}
#[test]
fn test_bfs_same_node() {
let coupling = vec![(0, 1)];
let adj = build_adjacency_list(&coupling, 2);
let path = bfs_shortest_path(&adj, 0, 0, 2);
assert_eq!(path, vec![0]);
}
#[test]
fn test_cancel_hh_produces_empty() {
let mut circuit = QuantumCircuit::new(1);
circuit.h(0);
circuit.h(0);
let optimized = optimize_gates(&circuit, 1);
assert_eq!(optimized.gate_count(), 0);
}
#[test]
fn test_cancel_xx() {
let mut circuit = QuantumCircuit::new(1);
circuit.x(0);
circuit.x(0);
let optimized = optimize_gates(&circuit, 1);
assert_eq!(optimized.gate_count(), 0);
}
#[test]
fn test_cancel_zz() {
let mut circuit = QuantumCircuit::new(1);
circuit.z(0);
circuit.z(0);
let optimized = optimize_gates(&circuit, 1);
assert_eq!(optimized.gate_count(), 0);
}
#[test]
fn test_cancel_s_sdg() {
let mut circuit = QuantumCircuit::new(1);
circuit.s(0);
circuit.add_gate(Gate::Sdg(0));
let optimized = optimize_gates(&circuit, 1);
assert_eq!(optimized.gate_count(), 0);
}
#[test]
fn test_cancel_t_tdg() {
let mut circuit = QuantumCircuit::new(1);
circuit.t(0);
circuit.add_gate(Gate::Tdg(0));
let optimized = optimize_gates(&circuit, 1);
assert_eq!(optimized.gate_count(), 0);
}
#[test]
fn test_cancel_cnot_cnot() {
let mut circuit = QuantumCircuit::new(2);
circuit.cnot(0, 1);
circuit.cnot(0, 1);
let optimized = optimize_gates(&circuit, 1);
assert_eq!(optimized.gate_count(), 0);
}
#[test]
fn test_no_cancel_different_qubits() {
let mut circuit = QuantumCircuit::new(2);
circuit.h(0);
circuit.h(1);
let optimized = optimize_gates(&circuit, 1);
assert_eq!(optimized.gate_count(), 2);
}
#[test]
fn test_merge_rz_level2() {
let mut circuit = QuantumCircuit::new(1);
circuit.rz(0, FRAC_PI_4);
circuit.rz(0, FRAC_PI_4);
let optimized = optimize_gates(&circuit, 2);
assert_eq!(optimized.gate_count(), 1);
if let Gate::Rz(0, theta) = &optimized.gates()[0] {
assert!((theta - FRAC_PI_2).abs() < 1e-12);
} else {
panic!("expected merged Rz(pi/2)");
}
}
#[test]
fn test_merge_rz_to_zero_eliminates() {
let mut circuit = QuantumCircuit::new(1);
circuit.rz(0, PI);
circuit.rz(0, -PI);
let optimized = optimize_gates(&circuit, 2);
assert_eq!(optimized.gate_count(), 0);
}
#[test]
fn test_merge_three_rz() {
let mut circuit = QuantumCircuit::new(1);
circuit.rz(0, FRAC_PI_4);
circuit.rz(0, FRAC_PI_4);
circuit.rz(0, FRAC_PI_4);
let optimized = optimize_gates(&circuit, 2);
assert_eq!(optimized.gate_count(), 1);
if let Gate::Rz(0, theta) = &optimized.gates()[0] {
assert!((theta - 3.0 * FRAC_PI_4).abs() < 1e-12);
} else {
panic!("expected merged Rz(3*pi/4)");
}
}
#[test]
fn test_level0_no_optimization() {
let mut circuit = QuantumCircuit::new(1);
circuit.h(0);
circuit.h(0);
let optimized = optimize_gates(&circuit, 0);
assert_eq!(optimized.gate_count(), 2);
}
#[test]
fn test_level1_does_not_merge_rz() {
let mut circuit = QuantumCircuit::new(1);
circuit.rz(0, FRAC_PI_4);
circuit.rz(0, FRAC_PI_4);
let optimized = optimize_gates(&circuit, 1);
assert_eq!(optimized.gate_count(), 2);
}
#[test]
fn test_transpile_universal_passthrough() {
let mut circuit = QuantumCircuit::new(2);
circuit.h(0);
circuit.cnot(0, 1);
let config = TranspilerConfig {
basis: BasisGateSet::Universal,
coupling_map: None,
optimization_level: 0,
};
let result = transpile(&circuit, &config);
assert_eq!(result.gate_count(), 2);
}
#[test]
fn test_transpile_ibm_decomposes_then_optimizes() {
let mut circuit = QuantumCircuit::new(1);
circuit.h(0);
circuit.h(0);
let config = TranspilerConfig {
basis: BasisGateSet::IbmEagle,
coupling_map: None,
optimization_level: 2,
};
let result = transpile(&circuit, &config);
assert!(result.gate_count() < 6, "expected some optimization");
}
#[test]
fn test_transpile_with_routing() {
let mut circuit = QuantumCircuit::new(3);
circuit.cnot(0, 2);
let config = TranspilerConfig {
basis: BasisGateSet::Universal,
coupling_map: Some(vec![(0, 1), (1, 2)]),
optimization_level: 0,
};
let result = transpile(&circuit, &config);
let swap_count = result
.gates()
.iter()
.filter(|g| matches!(g, Gate::SWAP(_, _)))
.count();
assert!(swap_count >= 1);
}
#[test]
fn test_transpile_rigetti_bell_state() {
let mut circuit = QuantumCircuit::new(2);
circuit.h(0);
circuit.cnot(0, 1);
let config = TranspilerConfig {
basis: BasisGateSet::RigettiAspen,
coupling_map: None,
optimization_level: 0,
};
let result = transpile(&circuit, &config);
for gate in result.gates() {
match gate {
Gate::CZ(_, _) | Gate::Rx(_, _) | Gate::Rz(_, _) => {}
Gate::Measure(_) | Gate::Reset(_) | Gate::Barrier => {}
other => panic!("gate {:?} not in Rigetti basis", other),
}
}
}
#[test]
fn test_transpile_ionq_single_qubit() {
let mut circuit = QuantumCircuit::new(1);
circuit.h(0);
let config = TranspilerConfig {
basis: BasisGateSet::IonQAria,
coupling_map: None,
optimization_level: 0,
};
let result = transpile(&circuit, &config);
for gate in result.gates() {
match gate {
Gate::Rx(_, _) | Gate::Ry(_, _) | Gate::Rzz(_, _, _) => {}
Gate::Measure(_) | Gate::Reset(_) | Gate::Barrier => {}
other => panic!("gate {:?} not in IonQ basis", other),
}
}
}
#[test]
fn test_iterative_cancellation() {
let mut circuit = QuantumCircuit::new(1);
circuit.x(0);
circuit.h(0);
circuit.h(0);
circuit.x(0);
let optimized = optimize_gates(&circuit, 1);
assert_eq!(optimized.gate_count(), 0);
}
#[test]
fn test_routing_updates_mapping_correctly() {
let coupling = vec![(0, 1), (1, 2), (2, 3)];
let mut circuit = QuantumCircuit::new(4);
circuit.cnot(0, 3);
circuit.h(0);
let routed = route_circuit(&circuit, &coupling);
let swap_count = routed
.gates()
.iter()
.filter(|g| matches!(g, Gate::SWAP(_, _)))
.count();
assert!(swap_count >= 1);
let h_count = routed
.gates()
.iter()
.filter(|g| matches!(g, Gate::H(_)))
.count();
assert_eq!(h_count, 1);
}
#[test]
fn test_decompose_rzz_to_ibm() {
let gates = decompose_to_ibm(&Gate::Rzz(0, 1, FRAC_PI_4));
assert_eq!(gates.len(), 3);
assert!(matches!(gates[0], Gate::CNOT(0, 1)));
assert!(matches!(gates[1], Gate::Rz(1, _)));
assert!(matches!(gates[2], Gate::CNOT(0, 1)));
}
#[test]
fn test_basis_gate_set_variants() {
let variants = [
BasisGateSet::IbmEagle,
BasisGateSet::IonQAria,
BasisGateSet::RigettiAspen,
BasisGateSet::Universal,
];
for (i, a) in variants.iter().enumerate() {
for (j, b) in variants.iter().enumerate() {
if i == j {
assert_eq!(a, b);
} else {
assert_ne!(a, b);
}
}
}
}
}