diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 3f0fa48be8dc..0736f2b58901 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -985,21 +985,19 @@ def __eq__(self, other): self_bit_indices = {bit: idx for idx, bit in enumerate(self.qubits + self.clbits)} other_bit_indices = {bit: idx for idx, bit in enumerate(other.qubits + other.clbits)} - self_qreg_indices = [ - (regname, [self_bit_indices[bit] for bit in reg]) for regname, reg in self.qregs.items() - ] - self_creg_indices = [ - (regname, [self_bit_indices[bit] for bit in reg]) for regname, reg in self.cregs.items() - ] - - other_qreg_indices = [ - (regname, [other_bit_indices[bit] for bit in reg]) - for regname, reg in other.qregs.items() - ] - other_creg_indices = [ - (regname, [other_bit_indices[bit] for bit in reg]) - for regname, reg in other.cregs.items() - ] + self_qreg_indices = { + regname: [self_bit_indices[bit] for bit in reg] for regname, reg in self.qregs.items() + } + self_creg_indices = { + regname: [self_bit_indices[bit] for bit in reg] for regname, reg in self.cregs.items() + } + + other_qreg_indices = { + regname: [other_bit_indices[bit] for bit in reg] for regname, reg in other.qregs.items() + } + other_creg_indices = { + regname: [other_bit_indices[bit] for bit in reg] for regname, reg in other.cregs.items() + } if self_qreg_indices != other_qreg_indices or self_creg_indices != other_creg_indices: return False diff --git a/qiskit/test/_canonical.py b/qiskit/test/_canonical.py new file mode 100644 index 000000000000..ea8e7cfce3b4 --- /dev/null +++ b/qiskit/test/_canonical.py @@ -0,0 +1,125 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 20022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Utility methods for canonicalising various Qiskit objects, to help with testing.""" + +import threading + +from qiskit.circuit import ( + BreakLoopOp, + CircuitInstruction, + ContinueLoopOp, + ControlFlowOp, + ForLoopOp, + Parameter, + QuantumCircuit, +) + + +class _CanonicalParametersIterator: + """An object that, when iterated through, will produce the same sequence of parameters as every + other instance of this iterator.""" + + __parameters = [] + __mutex = threading.Lock() + + def __init__(self): + self._counter = 0 + + def __iter__(self): + return self + + def __next__(self): + with self.__mutex: + if len(self.__parameters) >= self._counter: + param = Parameter(f"_canonicalization_loop_{self._counter}") + self.__parameters.append(param) + out = self.__parameters[self._counter] + self._counter += 1 + return out + + +def canonicalize_control_flow(circuit: QuantumCircuit) -> QuantumCircuit: + """Canonicalize all control-flow operations in a circuit. + + This is not an efficient operation, and does not affect any properties of the circuit. Its + intent is to normalise parts of circuits that have a non-deterministic construction. These are + the ordering of bit arguments in control-flow blocks output by the builder interface, and + automatically generated ``for``-loop variables. + + The canonical form sorts the bits in the arguments of these operations so that they always + appear in the order they were originally added to the outer-most circuit. For-loop variables + are re-bound into new, cached auto-generated ones.""" + params = iter(_CanonicalParametersIterator()) + base_bit_order = {bit: i for i, bit in enumerate(circuit.qubits)} + base_bit_order.update((bit, i) for i, bit in enumerate(circuit.clbits)) + + def worker(circuit, bit_map=None): + if bit_map is None: + bit_map = {bit: bit for bits in (circuit.qubits, circuit.clbits) for bit in bits} + + def bit_key(bit): + return base_bit_order[bit_map[bit]] + + # This isn't quite QuantumCircuit.copy_empty_like because of the bit reordering. + out = QuantumCircuit( + sorted(circuit.qubits, key=bit_key), + sorted(circuit.clbits, key=bit_key), + *circuit.qregs, + *circuit.cregs, + name=circuit.name, + global_phase=circuit.global_phase, + metadata=circuit.metadata, + ) + for instruction in circuit.data: + new_instruction = instruction + # Control-flow operations associated bits in the instruction arguments with bits in the + # circuit blocks just by sequencing. All blocks must have the same width. + if isinstance(new_instruction.operation, ControlFlowOp): + op = new_instruction.operation + first_block = op.blocks[0] + inner_bit_map = dict( + zip(first_block.qubits, (bit_map[bit] for bit in new_instruction.qubits)) + ) + inner_bit_map.update( + zip(first_block.clbits, (bit_map[bit] for bit in new_instruction.clbits)) + ) + new_instruction = CircuitInstruction( + operation=op.replace_blocks( + [worker(block, inner_bit_map) for block in op.blocks] + ), + qubits=sorted(new_instruction.qubits, key=bit_key), + clbits=sorted(new_instruction.clbits, key=bit_key), + ) + elif isinstance(new_instruction.operation, (BreakLoopOp, ContinueLoopOp)): + new_instruction = new_instruction.replace( + qubits=sorted(new_instruction.qubits, key=bit_key), + clbits=sorted(new_instruction.clbits, key=bit_key), + ) + # For for loops specifically, the control-flow builders generate a loop parameter if one + # is needed but not explicitly supplied. We want the parameters to compare equal, so we + # replace them with those from a shared list. + if isinstance(new_instruction.operation, ForLoopOp): + old_op = new_instruction.operation + indexset, loop_param, body = old_op.params + if loop_param is not None: + new_loop_param = next(params) + new_op = ForLoopOp( + indexset, + new_loop_param, + body.assign_parameters({loop_param: new_loop_param}), + ) + new_instruction = new_instruction.replace(operation=new_op) + out._append(new_instruction) + return out + + return worker(circuit) diff --git a/releasenotes/notes/fix-nondeterministic-dagcircuit-eq-7caa9041093c6e4c.yaml b/releasenotes/notes/fix-nondeterministic-dagcircuit-eq-7caa9041093c6e4c.yaml new file mode 100644 index 000000000000..551fca109857 --- /dev/null +++ b/releasenotes/notes/fix-nondeterministic-dagcircuit-eq-7caa9041093c6e4c.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Comparing :class:`.QuantumCircuit` and :class:`.DAGCircuit`\ s for equality + was previously non-deterministic if the circuits contained more than one + register of the same type (*e.g.* two or more :class:`.QuantumRegister`\ s), + sometimes returning ``False`` even if the registers were identical. It will + now correctly compare circuits with multiple registers. diff --git a/test/python/circuit/test_control_flow_builders.py b/test/python/circuit/test_control_flow_builders.py index 0452ef8a701e..58da7c84ee2b 100644 --- a/test/python/circuit/test_control_flow_builders.py +++ b/test/python/circuit/test_control_flow_builders.py @@ -26,11 +26,12 @@ QuantumRegister, Qubit, ) -from qiskit.circuit.controlflow import ForLoopOp, IfElseOp, WhileLoopOp, BreakLoopOp, ContinueLoopOp +from qiskit.circuit.controlflow import ForLoopOp, IfElseOp, WhileLoopOp from qiskit.circuit.controlflow.builder import ControlFlowBuilderBlock from qiskit.circuit.controlflow.if_else import IfElsePlaceholder from qiskit.circuit.exceptions import CircuitError from qiskit.test import QiskitTestCase +from qiskit.test._canonical import canonicalize_control_flow class SentinelException(Exception): @@ -41,77 +42,6 @@ class SentinelException(Exception): class TestControlFlowBuilders(QiskitTestCase): """Test that the control-flow builder interfaces work, and manage resources correctly.""" - def assertCircuitsEquivalent(self, a, b): - """Assert that two circuits (``a`` and ``b``) contain all the same qubits and clbits, and - then have the same instructions in order, recursing into nested control-flow constructs. - - Relying on ``QuantumCircuit.__eq__`` doesn't work reliably in all cases here, because we - don't care about the order that the builder interface chooses for resources in the inner - blocks. This order is non-deterministic, because internally it uses sets for efficiency, - and the order of iteration through a set is dependent on the hash seed. Instead, we just - need to be a bit more explicit about what we care about. This isn't a full method for - comparing if two circuits are equivalent, but for the restricted cases used in these tests, - where we deliberately construct the expected result to be equal in the good case, it should - test all that is needed. - """ - - self.assertIsInstance(a, QuantumCircuit) - self.assertIsInstance(b, QuantumCircuit) - - # For our purposes here, we don't care about the order bits were added. - self.assertEqual(set(a.qubits), set(b.qubits)) - self.assertEqual(set(a.clbits), set(b.clbits)) - self.assertEqual(set(a.qregs), set(b.qregs)) - self.assertEqual(set(a.cregs), set(b.cregs)) - self.assertEqual(len(a.data), len(b.data)) - - for a_i, b_i in zip(a.data, b.data): - # Make sure that the operations are the same. - self.assertEqual(type(a_i.operation), type(b_i.operation)) - self.assertEqual( - hasattr(a_i.operation, "condition"), hasattr(b_i.operation, "condition") - ) - if hasattr(a_i.operation, "condition") and not isinstance( - a_i.operation, (IfElseOp, WhileLoopOp) - ): - self.assertEqual(a_i.operation.condition, b_i.operation.condition) - self.assertEqual(hasattr(a_i.operation, "label"), hasattr(b_i.operation, "label")) - if hasattr(a_i.operation, "condition"): - self.assertEqual(a_i.operation.label, b_i.operation.label) - # If it's a block op, we don't care what order the resources are specified in. - if isinstance(a_i.operation, WhileLoopOp): - self.assertEqual(set(a_i.qubits), set(b_i.qubits)) - self.assertEqual(set(a_i.clbits), set(b_i.clbits)) - self.assertEqual(a_i.operation.condition, b_i.operation.condition) - self.assertCircuitsEquivalent(a_i.operation.blocks[0], b_i.operation.blocks[0]) - elif isinstance(a_i.operation, ForLoopOp): - self.assertEqual(set(a_i.qubits), set(b_i.qubits)) - self.assertEqual(set(a_i.clbits), set(b_i.clbits)) - a_indexset, a_loop_parameter, a_body = a_i.operation.params - b_indexset, b_loop_parameter, b_body = b_i.operation.params - self.assertEqual(a_loop_parameter is None, b_loop_parameter is None) - self.assertEqual(a_indexset, b_indexset) - if a_loop_parameter is not None: - a_body = a_body.assign_parameters({a_loop_parameter: b_loop_parameter}) - self.assertCircuitsEquivalent(a_body, b_body) - elif isinstance(a_i.operation, IfElseOp): - self.assertEqual(set(a_i.qubits), set(b_i.qubits)) - self.assertEqual(set(a_i.clbits), set(b_i.clbits)) - self.assertEqual(a_i.operation.condition, b_i.operation.condition) - self.assertEqual(len(a_i.operation.blocks), len(b_i.operation.blocks)) - self.assertCircuitsEquivalent(a_i.operation.blocks[0], b_i.operation.blocks[0]) - if len(a_i.operation.blocks) > 1: - self.assertCircuitsEquivalent(a_i.operation.blocks[1], b_i.operation.blocks[1]) - elif isinstance(a_i.operation, (BreakLoopOp, ContinueLoopOp)): - self.assertEqual(set(a_i.qubits), set(b_i.qubits)) - self.assertEqual(set(a_i.clbits), set(b_i.clbits)) - else: - # For any other op, we care that the resources are the same, and in the same order, - # but we don't mind what sort of iterable they're contained in. - self.assertEqual(tuple(a_i.qubits), tuple(b_i.qubits)) - self.assertEqual(tuple(a_i.clbits), tuple(b_i.clbits)) - self.assertEqual(a_i.operation, b_i.operation) - def test_if_simple(self): """Test a simple if statement builds correctly, in the midst of other instructions.""" qubits = [Qubit(), Qubit()] @@ -143,7 +73,7 @@ def test_if_simple(self): expected.measure(qubits[0], clbits[1]) expected.if_test((clbits[1], 0), if_true1, [qubits[0], qubits[1]], [clbits[1]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_if_register(self): """Test a simple if statement builds correctly, when using a register as the condition. @@ -163,7 +93,7 @@ def test_if_register(self): expected.measure(qr, cr) expected.if_test((cr, 0), if_true0, [qr[0]], [cr[:]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_register_condition_in_nested_block(self): """Test that nested blocks can use registers of the outermost circuits as conditions, and @@ -202,7 +132,7 @@ def test_register_condition_in_nested_block(self): expected = QuantumCircuit(qr, clbits, cr1, cr2, cr3, cr4) expected.for_loop(range(3), None, for_body, [qr[0]], clbits + list(cr1)) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("for/while"): test = QuantumCircuit(qr, clbits, cr1, cr2, cr3, cr4) @@ -229,7 +159,7 @@ def test_register_condition_in_nested_block(self): expected = QuantumCircuit(qr, clbits, cr1, cr2, cr3, cr4) expected.for_loop(range(3), None, for_body, [qr[0]], clbits + list(cr1)) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("if/c_if"): test = QuantumCircuit(qr, clbits, cr1, cr2, cr3, cr4) @@ -237,14 +167,14 @@ def test_register_condition_in_nested_block(self): test.x(0).c_if(cr2, 0) test.z(0).c_if(cr3, 0) - true_body = QuantumCircuit([qr[0]], cr1, cr2, cr3) + true_body = QuantumCircuit([qr[0]], clbits, cr1, cr2, cr3) true_body.x(0).c_if(cr2, 0) true_body.z(0).c_if(cr3, 0) expected = QuantumCircuit(qr, clbits, cr1, cr2, cr3, cr4) expected.if_test((cr1, 0), true_body, [qr[0]], clbits + list(cr1)) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("while/else/c_if"): test = QuantumCircuit(qr, clbits, cr1, cr2, cr3, cr4) @@ -259,13 +189,13 @@ def test_register_condition_in_nested_block(self): false_body = QuantumCircuit([qr[0]], cr2, cr3, cr4) false_body.z(0).c_if(cr4, 0) - while_body = QuantumCircuit([qr[0]], cr1, cr2, cr3, cr4) + while_body = QuantumCircuit([qr[0]], clbits, cr1, cr2, cr3, cr4) while_body.if_else((cr2, 0), true_body, false_body, [qr[0]], clbits) expected = QuantumCircuit(qr, clbits, cr1, cr2, cr3, cr4) expected.while_loop((cr1, 0), while_body, [qr[0]], clbits + list(cr1)) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_if_else_simple(self): """Test a simple if/else statement builds correctly, in the midst of other instructions. @@ -310,7 +240,7 @@ def test_if_else_simple(self): expected.measure(qubits[0], clbits[1]) expected.if_else((clbits[1], 0), if_true1, if_false1, [qubits[0], qubits[1]], [clbits[1]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_if_else_resources_expand_true_superset_false(self): """Test that the resources of the if and else bodies come out correctly if the true body @@ -340,7 +270,7 @@ def test_if_else_resources_expand_true_superset_false(self): expected.measure(qubits[0], clbits[0]) expected.if_else((clbits[0], 0), if_true0, if_false0, qubits, clbits) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_if_else_resources_expand_false_superset_true(self): """Test that the resources of the if and else bodies come out correctly if the false body @@ -371,7 +301,7 @@ def test_if_else_resources_expand_false_superset_true(self): expected.measure(qubits[0], clbits[0]) expected.if_else((clbits[0], 0), if_true0, if_false0, qubits, clbits) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_if_else_resources_expand_true_false_symmetric_difference(self): """Test that the resources of the if and else bodies come out correctly if the sets of @@ -400,7 +330,7 @@ def test_if_else_resources_expand_true_false_symmetric_difference(self): expected.measure(qubits[0], clbits[0]) expected.if_else((clbits[0], 0), if_true0, if_false0, qubits, [clbits[0]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_if_else_empty_branches(self): """Test that the context managers can cope with a body being empty.""" @@ -439,7 +369,7 @@ def test_if_else_empty_branches(self): expected.if_else(cond, empty_with_qubit, only_x, [qubits[0]], [clbits[0]]) expected.if_else(cond, empty, empty, [], [clbits[0]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_if_else_tracks_registers(self): """Test that classical registers used in both branches of if statements are tracked @@ -466,7 +396,7 @@ def test_if_else_tracks_registers(self): (cr[0], 0), true_body, false_body, [qr[0]], list(cr[0]) + list(cr[1]) + list(cr[2]) ) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_if_else_nested(self): """Test that the if and else context managers can be nested, and don't interfere with each @@ -497,7 +427,7 @@ def test_if_else_nested(self): expected.if_else( outer_cond, outer_true, outer_false, [qubits[0], qubits[1]], [clbits[0], clbits[2]] ) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("if (if else) else"): test = QuantumCircuit(qubits, clbits) @@ -523,7 +453,7 @@ def test_if_else_nested(self): expected = QuantumCircuit(qubits, clbits) expected.if_else(outer_cond, outer_true, outer_false, qubits, [clbits[0], clbits[2]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_break_continue_expand_to_match_arguments_simple(self): """Test that ``break`` and ``continue`` statements expand to include all resources in the @@ -548,7 +478,7 @@ def test_break_continue_expand_to_match_arguments_simple(self): expected = QuantumCircuit(qubits, clbits) expected.for_loop(range(2), None, body, qubits, [clbits[0]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("while"): cond = (clbits[0], 0) @@ -568,7 +498,7 @@ def test_break_continue_expand_to_match_arguments_simple(self): expected = QuantumCircuit(qubits, clbits) expected.while_loop(cond, body, qubits, [clbits[0]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) @ddt.data(QuantumCircuit.break_loop, QuantumCircuit.continue_loop) def test_break_continue_accept_c_if(self, loop_operation): @@ -590,7 +520,7 @@ def test_break_continue_accept_c_if(self, loop_operation): expected = QuantumCircuit(qubits, clbits) expected.for_loop(range(2), None, body, [qubits[0]], [clbits[1]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("while"): cond = (clbits[0], 0) @@ -606,7 +536,7 @@ def test_break_continue_accept_c_if(self, loop_operation): expected = QuantumCircuit(qubits, clbits) expected.while_loop(cond, body, [qubits[0]], clbits) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) @ddt.data(QuantumCircuit.break_loop, QuantumCircuit.continue_loop) def test_break_continue_only_expand_to_nearest_loop(self, loop_operation): @@ -638,7 +568,7 @@ def test_break_continue_only_expand_to_nearest_loop(self, loop_operation): expected = QuantumCircuit(qubits, clbits) expected.for_loop(range(2), None, outer_body, [qubits[0], qubits[1]], [clbits[1]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("for while"): test = QuantumCircuit(qubits, clbits) @@ -663,7 +593,7 @@ def test_break_continue_only_expand_to_nearest_loop(self, loop_operation): range(2), None, outer_body, [qubits[0], qubits[1]], [clbits[0], clbits[1]] ) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("while for"): test = QuantumCircuit(qubits, clbits) @@ -686,7 +616,7 @@ def test_break_continue_only_expand_to_nearest_loop(self, loop_operation): expected = QuantumCircuit(qubits, clbits) expected.while_loop(cond_outer, outer_body, [qubits[0], qubits[1]], [clbits[1]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("while while"): test = QuantumCircuit(qubits, clbits) @@ -711,7 +641,7 @@ def test_break_continue_only_expand_to_nearest_loop(self, loop_operation): cond_outer, outer_body, [qubits[0], qubits[1]], [clbits[0], clbits[1]] ) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("for (for, for)"): # This test is specifically to check that multiple inner loops with different numbers of @@ -744,7 +674,7 @@ def test_break_continue_only_expand_to_nearest_loop(self, loop_operation): expected = QuantumCircuit(qubits, clbits) expected.for_loop(range(2), None, outer_body, qubits, [clbits[1]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) @ddt.data(QuantumCircuit.break_loop, QuantumCircuit.continue_loop) def test_break_continue_nested_in_if(self, loop_operation): @@ -790,7 +720,7 @@ def test_break_continue_nested_in_if(self, loop_operation): expected = QuantumCircuit(qubits, clbits) expected.for_loop(range(2), None, loop_body, [qubits[0]], [clbits[0], clbits[2]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("for/else"): test = QuantumCircuit(qubits, clbits) @@ -825,7 +755,7 @@ def test_break_continue_nested_in_if(self, loop_operation): range(2), None, loop_body, [qubits[0], qubits[1]], [clbits[0], clbits[2]] ) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("while/if"): test = QuantumCircuit(qubits, clbits) @@ -853,7 +783,7 @@ def test_break_continue_nested_in_if(self, loop_operation): cond_outer, loop_body, [qubits[0]], [clbits[0], clbits[1], clbits[2]] ) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("while/else"): test = QuantumCircuit(qubits, clbits) @@ -892,7 +822,7 @@ def test_break_continue_nested_in_if(self, loop_operation): cond_outer, loop_body, [qubits[0], qubits[1]], [clbits[0], clbits[1], clbits[2]] ) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) @ddt.data(QuantumCircuit.break_loop, QuantumCircuit.continue_loop) def test_break_continue_deeply_nested_in_if(self, loop_operation): @@ -961,7 +891,7 @@ def test_break_continue_deeply_nested_in_if(self, loop_operation): expected = QuantumCircuit(qubits, clbits) expected.for_loop(range(2), None, loop_body, qubits[:4], clbits[:2] + clbits[3:7]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("for/if/else"): test = QuantumCircuit(qubits, clbits) @@ -1028,7 +958,7 @@ def test_break_continue_deeply_nested_in_if(self, loop_operation): expected = QuantumCircuit(qubits, clbits) expected.for_loop(range(2), None, loop_body, qubits[:7], clbits[:2] + clbits[3:11]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("for/else/else"): # Look on my works, ye Mighty, and despair! @@ -1183,7 +1113,7 @@ def test_break_continue_deeply_nested_in_if(self, loop_operation): expected = QuantumCircuit(qubits, clbits) expected.for_loop(range(2), None, loop_body, loop_qubits, loop_clbits) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) # And now we repeat everything for "while" loops... Trying to parameterize the test over # 'for/while' mostly just ends up in it being a bit illegible, because so many parameters @@ -1232,7 +1162,7 @@ def test_break_continue_deeply_nested_in_if(self, loop_operation): expected = QuantumCircuit(qubits, clbits) expected.while_loop(cond_loop, loop_body, qubits[:4], clbits[:7]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("while/if/else"): test = QuantumCircuit(qubits, clbits) @@ -1297,7 +1227,7 @@ def test_break_continue_deeply_nested_in_if(self, loop_operation): expected = QuantumCircuit(qubits, clbits) expected.while_loop(cond_loop, loop_body, qubits[:7], clbits[:11]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("while/else/else"): test = QuantumCircuit(qubits, clbits) @@ -1448,7 +1378,7 @@ def test_break_continue_deeply_nested_in_if(self, loop_operation): expected = QuantumCircuit(qubits, clbits) expected.while_loop(cond_loop, loop_body, loop_qubits, loop_clbits) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_for_handles_iterables_correctly(self): """Test that the ``indexset`` in ``for`` loops is handled the way we expect. In general, @@ -1614,7 +1544,7 @@ def test_access_of_resources_from_direct_append(self): expected = QuantumCircuit(qubits, clbits) expected.if_test(cond, true_body, [qubits[1]], clbits) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("else"): test = QuantumCircuit(qubits, clbits) @@ -1629,7 +1559,7 @@ def test_access_of_resources_from_direct_append(self): expected = QuantumCircuit(qubits, clbits) expected.if_else(cond, true_body, false_body, [qubits[1]], clbits) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("for"): test = QuantumCircuit(qubits, clbits) @@ -1641,7 +1571,7 @@ def test_access_of_resources_from_direct_append(self): expected = QuantumCircuit(qubits, clbits) expected.for_loop(range(2), None, body, [qubits[1]], [clbits[1]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("while"): test = QuantumCircuit(qubits, clbits) @@ -1653,7 +1583,7 @@ def test_access_of_resources_from_direct_append(self): expected = QuantumCircuit(qubits, clbits) expected.while_loop(cond, body, [qubits[1]], clbits) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_access_of_clbit_from_c_if(self): """Test that resources added from a call to :meth:`.InstructionSet.c_if` propagate through @@ -1803,7 +1733,7 @@ def test_accept_broadcast_gates(self): expected = QuantumCircuit(qubits, clbits) expected.if_test(cond, body, [qubits[0], qubits[1]], [clbits[0], clbits[1]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("else"): test = QuantumCircuit(qubits, clbits) @@ -1822,7 +1752,7 @@ def test_accept_broadcast_gates(self): cond, true_body, false_body, [qubits[0], qubits[1]], [clbits[0], clbits[1]] ) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("for"): test = QuantumCircuit(qubits, clbits) @@ -1836,7 +1766,7 @@ def test_accept_broadcast_gates(self): expected = QuantumCircuit(qubits, clbits) expected.for_loop(range(2), None, body, [qubits[0], qubits[1]], [clbits[0], clbits[1]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("while"): test = QuantumCircuit(qubits, clbits) @@ -1850,7 +1780,7 @@ def test_accept_broadcast_gates(self): expected = QuantumCircuit(qubits, clbits) expected.while_loop(cond, body, [qubits[0], qubits[1]], [clbits[0], clbits[1]]) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) with self.subTest("if inside for"): test = QuantumCircuit(qubits, clbits) @@ -1870,7 +1800,7 @@ def test_accept_broadcast_gates(self): range(2), None, for_body, [qubits[0], qubits[1]], [clbits[0], clbits[1]] ) - self.assertCircuitsEquivalent(test, expected) + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) def test_labels_propagated_to_instruction(self): """Test that labels given to the circuit-builder interface are passed through.""" @@ -2081,7 +2011,7 @@ def test_inplace_compose_within_builder(self): with expected.if_test((expected.clbits[0], 1)): expected.x(0) - self.assertCircuitsEquivalent(outer, expected) + self.assertEqual(canonicalize_control_flow(outer), canonicalize_control_flow(expected)) with self.subTest("else"): outer = base.copy() @@ -2096,7 +2026,7 @@ def test_inplace_compose_within_builder(self): with else_: expected.x(0) - self.assertCircuitsEquivalent(outer, expected) + self.assertEqual(canonicalize_control_flow(outer), canonicalize_control_flow(expected)) with self.subTest("for"): outer = base.copy() @@ -2107,7 +2037,7 @@ def test_inplace_compose_within_builder(self): with expected.for_loop(range(3)): expected.x(0) - self.assertCircuitsEquivalent(outer, expected) + self.assertEqual(canonicalize_control_flow(outer), canonicalize_control_flow(expected)) with self.subTest("while"): outer = base.copy() @@ -2118,7 +2048,7 @@ def test_inplace_compose_within_builder(self): with expected.while_loop((outer.clbits[0], 0)): expected.x(0) - self.assertCircuitsEquivalent(outer, expected) + self.assertEqual(canonicalize_control_flow(outer), canonicalize_control_flow(expected)) @ddt.ddt