diff --git a/crates/transpiler/src/passes/inverse_cancellation.rs b/crates/transpiler/src/passes/inverse_cancellation.rs index dd5be9068983..42594b7e8b46 100644 --- a/crates/transpiler/src/passes/inverse_cancellation.rs +++ b/crates/transpiler/src/passes/inverse_cancellation.rs @@ -248,6 +248,7 @@ fn std_inverse_pairs(dag: &mut DAGCircuit) { { continue; } + let pair_symmetric_2q = matches!([gate_0, gate_1], [StandardGate::CS, StandardGate::CSdg]); let filter = |inst: &PackedInstruction| -> bool { match inst.op.view() { OperationRef::StandardGate(gate) => gate == gate_0 || gate == gate_1, @@ -264,12 +265,17 @@ fn std_inverse_pairs(dag: &mut DAGCircuit) { let NodeType::Operation(next_inst) = &dag[nodes[i + 1]] else { unreachable!("Not an op node"); }; - if inst.qubits == next_inst.qubits - && (inst.op.try_standard_gate() == Some(gate_0) - && next_inst.op.try_standard_gate() == Some(gate_1)) - || (inst.op.try_standard_gate() == Some(gate_1) - && next_inst.op.try_standard_gate() == Some(gate_0)) - { + let inst_gate = inst.op.try_standard_gate(); + let next_gate = next_inst.op.try_standard_gate(); + let pair_match = (inst_gate == Some(gate_0) && next_gate == Some(gate_1)) + || (inst_gate == Some(gate_1) && next_gate == Some(gate_0)); + let qargs_match = inst.qubits == next_inst.qubits + || (pair_symmetric_2q && { + let a = dag.get_qargs(inst.qubits); + let b = dag.get_qargs(next_inst.qubits); + a.len() == 2 && b.len() == 2 && a[0] == b[1] && a[1] == b[0] + }); + if pair_match && qargs_match { dag.remove_op_node(nodes[i]); dag.remove_op_node(nodes[i + 1]); i += 2; diff --git a/releasenotes/notes/fix-inverse-cancellation-qubits-15855.yaml b/releasenotes/notes/fix-inverse-cancellation-qubits-15855.yaml new file mode 100644 index 000000000000..2f722b4b06de --- /dev/null +++ b/releasenotes/notes/fix-inverse-cancellation-qubits-15855.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed :class:`.InverseCancellation` so the default :class:`.CSGate` and + :class:`.CSdgGate` inverse pair is cancelled consistently when the two + gates act on the same qubits in opposite order. diff --git a/test/python/transpiler/test_inverse_cancellation.py b/test/python/transpiler/test_inverse_cancellation.py index 5a33ea29ca4b..c3cf06563f27 100644 --- a/test/python/transpiler/test_inverse_cancellation.py +++ b/test/python/transpiler/test_inverse_cancellation.py @@ -379,6 +379,16 @@ def test_some_inverse_pairs(self, gates_to_cancel): self.assertNotIn("t", new_circ.count_ops()) self.assertNotIn("tdg", new_circ.count_ops()) + def test_cs_csdg_cancel_with_reversed_qargs_in_both_orders(self): + """CS/CSdg are symmetric in qubit order, so both inverse orders cancel.""" + for first, second in (("csdg", "cs"), ("cs", "csdg")): + with self.subTest(first=first, second=second): + qc = QuantumCircuit(2) + getattr(qc, first)(0, 1) + getattr(qc, second)(1, 0) + new_circ = InverseCancellation()(qc) + self.assertEqual(new_circ, QuantumCircuit(2)) + @ddt.data([HGate(), CXGate(), CZGate(), (TGate(), TdgGate())], None) def test_some_inverse_and_cancelled(self, gates_to_cancel): """Test when there are some but not all pairs to cancel."""