Skip to content

Commit

Permalink
Fix single-bit-condition equality in QuantumCircuit and DAGCircuit (#…
Browse files Browse the repository at this point in the history
…8930) (#8943)

* Fix single-bit-condition equality in QuantumCircuit and DAGCircuit

Previously, the `condition` of a given operation would not be converted
into bit indices in order do the semantic-equality checking that the
rest of the qargs and cargs get in a circuit.  This caused loose-bit
comparisons to produce false negatives.

* Improve temporary variable names

Co-authored-by: Matthew Treinish <[email protected]>

Co-authored-by: Matthew Treinish <[email protected]>
(cherry picked from commit 3bebd0f)

Co-authored-by: Jake Lishman <[email protected]>
  • Loading branch information
mergify[bot] and jakelishman authored Oct 18, 2022
1 parent 6c4a3b6 commit e3849ad
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 8 deletions.
30 changes: 22 additions & 8 deletions qiskit/dagcircuit/dagnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@

import warnings

from qiskit.circuit import Clbit


def _condition_as_indices(operation, bit_indices):
cond = getattr(operation, "condition", None)
if cond is None:
return None
bits, value = cond
indices = [bit_indices[bits]] if isinstance(bits, Clbit) else [bit_indices[x] for x in bits]
return indices, value


class DAGNode:
"""Parent class for DAGOpNode, DAGInNode, and DAGOutNode."""
Expand Down Expand Up @@ -77,16 +88,19 @@ def semantic_eq(node1, node2, bit_indices1=None, bit_indices2=None):
if node1.op.name == node2.op.name and node1.name in {"barrier", "swap"}:
return set(node1_qargs) == set(node2_qargs)

if node1_qargs == node2_qargs:
if node1_cargs == node2_cargs:
if getattr(node1.op, "condition", None) == getattr(node2.op, "condition", None):
if node1.op == node2.op:
return True
elif (isinstance(node1, DAGInNode) and isinstance(node2, DAGInNode)) or (
return (
node1_qargs == node2_qargs
and node1_cargs == node2_cargs
and (
_condition_as_indices(node1.op, bit_indices1)
== _condition_as_indices(node2.op, bit_indices2)
)
and node1.op == node2.op
)
if (isinstance(node1, DAGInNode) and isinstance(node2, DAGInNode)) or (
isinstance(node1, DAGOutNode) and isinstance(node2, DAGOutNode)
):
if bit_indices1.get(node1.wire, None) == bit_indices2.get(node2.wire, None):
return True
return bit_indices1.get(node1.wire, None) == bit_indices2.get(node2.wire, None)

return False

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
fixes:
- |
The equality checkers for :class:`.QuantumCircuit` and :class:`.DAGCircuit`
(with objects of the same type) will now correctly handle conditions on single
bits. Previously, these would produce false negatives for equality, as the
bits would use "exact" equality checks instead of the "semantic" checks the rest
of the properties of circuit instructions get.
30 changes: 30 additions & 0 deletions test/python/circuit/test_circuit_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,36 @@ def test_compare_two_different_circuits(self):

self.assertFalse(qc1 == qc2)

def test_compare_circuits_with_single_bit_conditions(self):
"""Test that circuits with single-bit conditions can be compared correctly."""
qreg = QuantumRegister(1, name="q")
creg = ClassicalRegister(1, name="c")
qc1 = QuantumCircuit(qreg, creg, [Clbit()])
qc1.x(0).c_if(qc1.cregs[0], 1)
qc1.x(0).c_if(qc1.clbits[-1], True)
qc2 = QuantumCircuit(qreg, creg, [Clbit()])
qc2.x(0).c_if(qc2.cregs[0], 1)
qc2.x(0).c_if(qc2.clbits[-1], True)
self.assertEqual(qc1, qc2)

# Order of operations transposed.
qc1 = QuantumCircuit(qreg, creg, [Clbit()])
qc1.x(0).c_if(qc1.cregs[0], 1)
qc1.x(0).c_if(qc1.clbits[-1], True)
qc2 = QuantumCircuit(qreg, creg, [Clbit()])
qc2.x(0).c_if(qc2.clbits[-1], True)
qc2.x(0).c_if(qc2.cregs[0], 1)
self.assertNotEqual(qc1, qc2)

# Single-bit condition values not the same.
qc1 = QuantumCircuit(qreg, creg, [Clbit()])
qc1.x(0).c_if(qc1.cregs[0], 1)
qc1.x(0).c_if(qc1.clbits[-1], True)
qc2 = QuantumCircuit(qreg, creg, [Clbit()])
qc2.x(0).c_if(qc2.cregs[0], 1)
qc2.x(0).c_if(qc2.clbits[-1], False)
self.assertNotEqual(qc1, qc2)

def test_compare_a_circuit_with_none(self):
"""Test to compare that a circuit is different to None."""
qc1 = QuantumCircuit(2, 2)
Expand Down
30 changes: 30 additions & 0 deletions test/python/dagcircuit/test_dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1462,6 +1462,36 @@ def test_node_params_equal_unequal(self):
self.assertEqual(dag1, dag2)
self.assertNotEqual(dag2, dag3)

def test_semantic_conditions(self):
"""Test that the semantic equality is applied to the bits in conditions as well."""
qreg = QuantumRegister(1, name="q")
creg = ClassicalRegister(1, name="c")
qc1 = QuantumCircuit(qreg, creg, [Clbit()])
qc1.x(0).c_if(qc1.cregs[0], 1)
qc1.x(0).c_if(qc1.clbits[-1], True)
qc2 = QuantumCircuit(qreg, creg, [Clbit()])
qc2.x(0).c_if(qc2.cregs[0], 1)
qc2.x(0).c_if(qc2.clbits[-1], True)
self.assertEqual(circuit_to_dag(qc1), circuit_to_dag(qc2))

# Order of operations transposed.
qc1 = QuantumCircuit(qreg, creg, [Clbit()])
qc1.x(0).c_if(qc1.cregs[0], 1)
qc1.x(0).c_if(qc1.clbits[-1], True)
qc2 = QuantumCircuit(qreg, creg, [Clbit()])
qc2.x(0).c_if(qc2.clbits[-1], True)
qc2.x(0).c_if(qc2.cregs[0], 1)
self.assertNotEqual(circuit_to_dag(qc1), circuit_to_dag(qc2))

# Single-bit condition values not the same.
qc1 = QuantumCircuit(qreg, creg, [Clbit()])
qc1.x(0).c_if(qc1.cregs[0], 1)
qc1.x(0).c_if(qc1.clbits[-1], True)
qc2 = QuantumCircuit(qreg, creg, [Clbit()])
qc2.x(0).c_if(qc2.cregs[0], 1)
qc2.x(0).c_if(qc2.clbits[-1], False)
self.assertNotEqual(circuit_to_dag(qc1), circuit_to_dag(qc2))


class TestDagSubstitute(QiskitTestCase):
"""Test substituting a dag node with a sub-dag"""
Expand Down

0 comments on commit e3849ad

Please sign in to comment.