Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix single-bit-condition equality in QuantumCircuit and DAGCircuit #8930

Merged
merged 3 commits into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
lhs, rhs = cond
jakelishman marked this conversation as resolved.
Show resolved Hide resolved
indices = [bit_indices[lhs]] if isinstance(lhs, Clbit) else [bit_indices[x] for x in lhs]
return indices, rhs


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