mirror of https://github.com/Qiskit/qiskit.git
336 lines
15 KiB
Python
336 lines
15 KiB
Python
# This code is part of Qiskit.
|
|
#
|
|
# (C) Copyright IBM 2017, 2019.
|
|
#
|
|
# 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.
|
|
|
|
"""
|
|
Tests for the Collect2qBlocks transpiler pass.
|
|
"""
|
|
|
|
import math
|
|
import unittest
|
|
|
|
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
|
|
from qiskit.converters import circuit_to_dag
|
|
from qiskit.transpiler import PassManager
|
|
from qiskit.transpiler.passes import CollectMultiQBlocks
|
|
from test import QiskitTestCase # pylint: disable=wrong-import-order
|
|
|
|
|
|
class TestCollect2qBlocks(QiskitTestCase):
|
|
"""
|
|
Tests to verify that blocks of 2q interactions are found correctly.
|
|
"""
|
|
|
|
def test_blocks_in_topological_order(self):
|
|
"""the pass returns blocks in correct topological order
|
|
______
|
|
q0:--[u1]-------.---- q0:-------------| |--
|
|
| ______ | U2 |
|
|
q1:--[u2]--(+)-(+)--- = q1:---| |--|______|--
|
|
| | U1 |
|
|
q2:---------.-------- q2:---|______|------------
|
|
"""
|
|
qr = QuantumRegister(3, "qr")
|
|
qc = QuantumCircuit(qr)
|
|
qc.p(0.5, qr[0])
|
|
qc.u(math.pi / 2, 0.2, 0.6, qr[1])
|
|
qc.cx(qr[2], qr[1])
|
|
qc.cx(qr[0], qr[1])
|
|
dag = circuit_to_dag(qc)
|
|
|
|
topo_ops = list(dag.topological_op_nodes())
|
|
block_1 = [topo_ops[1], topo_ops[2]]
|
|
block_2 = [topo_ops[0], topo_ops[3]]
|
|
|
|
pass_ = CollectMultiQBlocks()
|
|
pass_.run(dag)
|
|
self.assertTrue(pass_.property_set["block_list"], [block_1, block_2])
|
|
|
|
def test_block_interrupted_by_gate(self):
|
|
"""Test that blocks interrupted by a gate that can't be added
|
|
to the block can be collected correctly
|
|
|
|
This was raised in #2775 where a measure in the middle of a block
|
|
stopped the block collection from working properly. This was because
|
|
the pass didn't expect to have measures in the middle of the circuit.
|
|
|
|
blocks : [['cx', 'id', 'id', 'id'], ['id', 'cx']]
|
|
|
|
┌───┐┌───┐┌─┐ ┌───┐┌───┐
|
|
q_0: |0>┤ X ├┤ I ├┤M├─────┤ I ├┤ X ├
|
|
└─┬─┘├───┤└╥┘┌───┐└───┘└─┬─┘
|
|
q_1: |0>──■──┤ I ├─╫─┤ I ├───────■──
|
|
└───┘ ║ └───┘
|
|
c_0: 0 ═══════════╩════════════════
|
|
|
|
"""
|
|
qc = QuantumCircuit(2, 1)
|
|
qc.cx(1, 0)
|
|
qc.id(0)
|
|
qc.id(1)
|
|
qc.measure(0, 0)
|
|
qc.id(0)
|
|
qc.id(1)
|
|
qc.cx(1, 0)
|
|
|
|
dag = circuit_to_dag(qc)
|
|
pass_ = CollectMultiQBlocks()
|
|
pass_.run(dag)
|
|
|
|
# list from Collect2QBlocks of nodes that it should have put into blocks
|
|
good_names = ["cx", "u1", "u2", "u3", "id"]
|
|
dag_nodes = [node for node in dag.topological_op_nodes() if node.name in good_names]
|
|
|
|
# we have to convert them to sets as the ordering can be different
|
|
# but equivalent between python 3.5 and 3.7
|
|
# there is no implied topology in a block, so this isn't an issue
|
|
dag_nodes = [set(dag_nodes[:4]), set(dag_nodes[4:])]
|
|
|
|
pass_nodes = [set(bl) for bl in pass_.property_set["block_list"]]
|
|
|
|
self.assertEqual(dag_nodes, pass_nodes)
|
|
|
|
def test_block_with_classical_register(self):
|
|
"""Test that only blocks that share quantum wires are added to the block.
|
|
It was the case that gates which shared a classical wire could be added to
|
|
the same block, despite not sharing the same qubits. This was fixed in #2956.
|
|
|
|
┌─────────────────────┐
|
|
q_0: |0>────────────────────┤ U2(0.25*pi,0.25*pi) ├
|
|
┌─────────────┐└──────────┬──────────┘
|
|
q_1: |0>──■──┤ U1(0.25*pi) ├───────────┼───────────
|
|
┌─┴─┐└──────┬──────┘ │
|
|
q_2: |0>┤ X ├───────┼──────────────────┼───────────
|
|
└───┘ ┌──┴──┐ ┌──┴──┐
|
|
c0_0: 0 ═════════╡ = 0 ╞════════════╡ = 0 ╞════════
|
|
└─────┘ └─────┘
|
|
|
|
Previously the blocks collected were : [['cx', 'u1', 'u2']]
|
|
This is now corrected to : [['cx', 'u1']]
|
|
"""
|
|
|
|
qasmstr = """
|
|
OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
qreg q[3];
|
|
creg c0[1];
|
|
|
|
cx q[1],q[2];
|
|
if(c0==0) u1(0.25*pi) q[1];
|
|
if(c0==0) u2(0.25*pi, 0.25*pi) q[0];
|
|
"""
|
|
with self.assertWarns(DeprecationWarning):
|
|
qc = QuantumCircuit.from_qasm_str(qasmstr)
|
|
|
|
pass_manager = PassManager()
|
|
pass_manager.append(CollectMultiQBlocks())
|
|
|
|
pass_manager.run(qc)
|
|
|
|
self.assertEqual(
|
|
[["cx"]], [[n.name for n in block] for block in pass_manager.property_set["block_list"]]
|
|
)
|
|
|
|
def test_do_not_merge_conditioned_gates(self):
|
|
"""Validate that classically conditioned gates are never considered for
|
|
inclusion in a block. Note that there are cases where gates conditioned
|
|
on the same (register, value) pair could be correctly merged, but this is
|
|
not yet implemented.
|
|
|
|
┌─────────┐┌─────────┐┌─────────┐ ┌───┐
|
|
qr_0: |0>┤ U1(0.1) ├┤ U1(0.2) ├┤ U1(0.3) ├──■───┤ X ├────■───
|
|
└─────────┘└────┬────┘└────┬────┘┌─┴─┐ └─┬─┘ ┌─┴─┐
|
|
qr_1: |0>────────────────┼──────────┼─────┤ X ├───■────┤ X ├─
|
|
│ │ └───┘ │ └─┬─┘
|
|
qr_2: |0>────────────────┼──────────┼─────────────┼──────┼───
|
|
┌──┴──┐ ┌──┴──┐ ┌──┴──┐┌──┴──┐
|
|
cr_0: 0 ═════════════╡ ╞════╡ ╞═══════╡ ╞╡ ╞
|
|
│ = 0 │ │ = 0 │ │ = 0 ││ = 1 │
|
|
cr_1: 0 ═════════════╡ ╞════╡ ╞═══════╡ ╞╡ ╞
|
|
└─────┘ └─────┘ └─────┘└─────┘
|
|
|
|
Previously the blocks collected were : [['u1', 'u1', 'u1', 'cx', 'cx', 'cx']]
|
|
This is now corrected to : [['cx']]
|
|
"""
|
|
# ref: https://github.com/Qiskit/qiskit-terra/issues/3215
|
|
|
|
qr = QuantumRegister(3, "qr")
|
|
cr = ClassicalRegister(2, "cr")
|
|
|
|
qc = QuantumCircuit(qr, cr)
|
|
qc.p(0.1, 0)
|
|
with self.assertWarns(DeprecationWarning):
|
|
qc.p(0.2, 0).c_if(cr, 0)
|
|
with self.assertWarns(DeprecationWarning):
|
|
qc.p(0.3, 0).c_if(cr, 0)
|
|
qc.cx(0, 1)
|
|
with self.assertWarns(DeprecationWarning):
|
|
qc.cx(1, 0).c_if(cr, 0)
|
|
with self.assertWarns(DeprecationWarning):
|
|
qc.cx(0, 1).c_if(cr, 1)
|
|
|
|
pass_manager = PassManager()
|
|
pass_manager.append(CollectMultiQBlocks())
|
|
|
|
pass_manager.run(qc)
|
|
for block in pass_manager.property_set["block_list"]:
|
|
self.assertTrue(len(block) <= 1)
|
|
|
|
def test_do_not_go_across_barrier(self):
|
|
"""Validate that blocks are not collected across barriers
|
|
░
|
|
q_0: ──■───░───■──
|
|
┌─┴─┐ ░ ┌─┴─┐
|
|
q_1: ┤ X ├─░─┤ X ├
|
|
└───┘ ░ └───┘
|
|
q_2: ──────░──────
|
|
░
|
|
"""
|
|
qr = QuantumRegister(3, "qr")
|
|
qc = QuantumCircuit(qr)
|
|
qc.cx(0, 1)
|
|
qc.barrier()
|
|
qc.cx(0, 1)
|
|
|
|
pass_manager = PassManager()
|
|
pass_manager.append(CollectMultiQBlocks())
|
|
|
|
pass_manager.run(qc)
|
|
for block in pass_manager.property_set["block_list"]:
|
|
self.assertTrue(len(block) <= 1)
|
|
|
|
def test_optimal_blocking(self):
|
|
"""Test that blocks are created optimally in at least the two quibit case.
|
|
Here, if the topological ordering of nodes is wrong then we might create
|
|
an extra block
|
|
┌───┐
|
|
qr_0: ┤ X ├───────■────■───────
|
|
└───┘┌───┐┌─┴─┐┌─┴─┐┌───┐
|
|
qr_1: ──■──┤ H ├┤ X ├┤ X ├┤ H ├
|
|
┌─┴─┐├───┤└───┘└───┘└───┘
|
|
qr_2: ┤ X ├┤ X ├───────────────
|
|
└───┘└───┘
|
|
"""
|
|
qr = QuantumRegister(3, "qr")
|
|
qc = QuantumCircuit(qr)
|
|
qc.x(0)
|
|
qc.cx(1, 2)
|
|
qc.h(1)
|
|
qc.cx(0, 1)
|
|
qc.cx(0, 1)
|
|
qc.h(1)
|
|
qc.x(2)
|
|
|
|
pass_manager = PassManager()
|
|
pass_manager.append(CollectMultiQBlocks())
|
|
|
|
pass_manager.run(qc)
|
|
self.assertTrue(len(pass_manager.property_set["block_list"]) == 2)
|
|
|
|
def test_ignore_measurement(self):
|
|
"""Test that doing a measurement on one qubit will not prevent
|
|
gates from being added to the block that do not act on the qubit
|
|
that was measured
|
|
┌─┐
|
|
q_0: ──■───────┤M├──────────
|
|
┌─┴─┐ └╥┘ ┌───┐
|
|
q_1: ┤ X ├──■───╫───■──┤ X ├
|
|
└───┘┌─┴─┐ ║ ┌─┴─┐├───┤
|
|
q_2: ─────┤ X ├─╫─┤ X ├┤ H ├
|
|
└───┘ ║ └───┘└───┘
|
|
c_0: ═══════════╩═══════════
|
|
"""
|
|
qc = QuantumCircuit(3, 1)
|
|
qc.cx(0, 1)
|
|
qc.cx(1, 2)
|
|
qc.measure(0, 0)
|
|
qc.cx(1, 2)
|
|
qc.x(1)
|
|
qc.h(2)
|
|
|
|
pass_manager = PassManager()
|
|
pass_manager.append(CollectMultiQBlocks(max_block_size=3))
|
|
|
|
pass_manager.run(qc)
|
|
self.assertTrue(len(pass_manager.property_set["block_list"]) == 1)
|
|
|
|
def test_larger_blocks(self):
|
|
"""Test that a max block size of 4 is still being processed
|
|
reasonably. Currently, this test just makes sure that the circuit can be run.
|
|
This is because the current multiqubit block collector is not optimal for this case
|
|
q_0: ──■──────────────■───────
|
|
┌─┴─┐ │
|
|
q_1: ┤ X ├──■─────────■───────
|
|
└───┘┌─┴─┐ ┌─┴─┐
|
|
q_2: ─────┤ X ├──■──┤ X ├─────
|
|
└───┘┌─┴─┐└───┘
|
|
q_3: ──────────┤ X ├──■────■──
|
|
└───┘┌─┴─┐┌─┴─┐
|
|
q_4: ───────────────┤ X ├┤ X ├
|
|
└───┘└───┘
|
|
"""
|
|
qc = QuantumCircuit(5)
|
|
qc.cx(0, 1)
|
|
qc.cx(1, 2)
|
|
qc.cx(2, 3)
|
|
qc.cx(3, 4)
|
|
qc.ccx(0, 1, 2)
|
|
qc.cx(3, 4)
|
|
|
|
pass_manager = PassManager()
|
|
pass_manager.append(CollectMultiQBlocks(max_block_size=4))
|
|
|
|
pass_manager.run(qc)
|
|
|
|
def test_collect_from_back(self):
|
|
"""Test the option to collect blocks from the outputs towards
|
|
the inputs.
|
|
┌───┐
|
|
q_0: ┤ H ├──■────■────■───────
|
|
└───┘┌─┴─┐ │ │
|
|
q_1: ─────┤ X ├──┼────┼───────
|
|
└───┘┌─┴─┐ │
|
|
q_2: ──────────┤ X ├──┼───────
|
|
└───┘┌─┴─┐┌───┐
|
|
q_3: ───────────────┤ X ├┤ H ├
|
|
└───┘└───┘
|
|
"""
|
|
qc = QuantumCircuit(4)
|
|
qc.h(0)
|
|
qc.cx(0, 1)
|
|
qc.cx(0, 2)
|
|
qc.cx(0, 3)
|
|
qc.h(3)
|
|
|
|
dag = circuit_to_dag(qc)
|
|
# For the circuit above, the topological order is unique
|
|
topo_ops = list(dag.topological_op_nodes())
|
|
|
|
# When collecting blocks of size-3 using the default direction,
|
|
# the first block should contain the H-gate and two CX-gates,
|
|
# and the second block should contain a single CX-gate and an H-gate.
|
|
pass_ = CollectMultiQBlocks(max_block_size=3, collect_from_back=False)
|
|
pass_.run(dag)
|
|
expected_blocks = [[topo_ops[0], topo_ops[1], topo_ops[2]], [topo_ops[3], topo_ops[4]]]
|
|
self.assertEqual(pass_.property_set["block_list"], expected_blocks)
|
|
|
|
# When collecting blocks of size-3 using the opposite direction,
|
|
# the first block should contain the H-gate and a single CX-gate,
|
|
# and the second block should contain two CX-gates and an H-gate.
|
|
pass_ = CollectMultiQBlocks(max_block_size=3, collect_from_back=True)
|
|
pass_.run(dag)
|
|
expected_blocks = [[topo_ops[0], topo_ops[1]], [topo_ops[2], topo_ops[3], topo_ops[4]]]
|
|
self.assertEqual(pass_.property_set["block_list"], expected_blocks)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|