mirror of https://github.com/Qiskit/qiskit.git
1120 lines
51 KiB
Python
1120 lines
51 KiB
Python
# This code is part of Qiskit.
|
|
#
|
|
# (C) Copyright IBM 2017, 2020.
|
|
#
|
|
# 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.
|
|
|
|
# pylint: disable=invalid-name
|
|
|
|
"""Test QuantumCircuit.compose()."""
|
|
|
|
import unittest
|
|
|
|
import numpy as np
|
|
|
|
from qiskit import transpile
|
|
from qiskit.pulse import Schedule
|
|
from qiskit.circuit import (
|
|
QuantumRegister,
|
|
ClassicalRegister,
|
|
Clbit,
|
|
QuantumCircuit,
|
|
Qubit,
|
|
Parameter,
|
|
Gate,
|
|
Instruction,
|
|
CASE_DEFAULT,
|
|
SwitchCaseOp,
|
|
CircuitError,
|
|
)
|
|
from qiskit.circuit.library import HGate, RZGate, CXGate, CCXGate, TwoLocal
|
|
from qiskit.circuit.classical import expr, types
|
|
from test import QiskitTestCase # pylint: disable=wrong-import-order
|
|
|
|
|
|
class TestCircuitCompose(QiskitTestCase):
|
|
"""Test composition of two circuits."""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
qreg1 = QuantumRegister(3, "lqr_1")
|
|
qreg2 = QuantumRegister(2, "lqr_2")
|
|
creg = ClassicalRegister(2, "lcr")
|
|
|
|
self.circuit_left = QuantumCircuit(qreg1, qreg2, creg)
|
|
self.circuit_left.h(qreg1[0])
|
|
self.circuit_left.x(qreg1[1])
|
|
self.circuit_left.p(0.1, qreg1[2])
|
|
self.circuit_left.cx(qreg2[0], qreg2[1])
|
|
|
|
self.left_qubit0 = qreg1[0]
|
|
self.left_qubit1 = qreg1[1]
|
|
self.left_qubit2 = qreg1[2]
|
|
self.left_qubit3 = qreg2[0]
|
|
self.left_qubit4 = qreg2[1]
|
|
self.left_clbit0 = creg[0]
|
|
self.left_clbit1 = creg[1]
|
|
self.condition = (creg, 3)
|
|
|
|
def test_compose_inorder(self):
|
|
"""Composing two circuits of the same width, default order.
|
|
|
|
┌───┐
|
|
lqr_1_0: |0>──┤ H ├─── rqr_0: |0>──■───────
|
|
├───┤ │ ┌───┐
|
|
lqr_1_1: |0>──┤ X ├─── rqr_1: |0>──┼──┤ X ├
|
|
┌─┴───┴──┐ │ ├───┤
|
|
lqr_1_2: |0>┤ P(0.1) ├ + rqr_2: |0>──┼──┤ Y ├ =
|
|
└────────┘ ┌─┴─┐└───┘
|
|
lqr_2_0: |0>────■───── rqr_3: |0>┤ X ├─────
|
|
┌─┴─┐ └───┘┌───┐
|
|
lqr_2_1: |0>──┤ X ├─── rqr_4: |0>─────┤ Z ├
|
|
└───┘ └───┘
|
|
lcr_0: 0 ═══════════
|
|
|
|
lcr_1: 0 ═══════════
|
|
|
|
|
|
┌───┐
|
|
lqr_1_0: |0>──┤ H ├─────■───────
|
|
├───┤ │ ┌───┐
|
|
lqr_1_1: |0>──┤ X ├─────┼──┤ X ├
|
|
┌─┴───┴──┐ │ ├───┤
|
|
lqr_1_2: |0>┤ P(0.1) ├──┼──┤ Y ├
|
|
└────────┘┌─┴─┐└───┘
|
|
lqr_2_0: |0>────■─────┤ X ├─────
|
|
┌─┴─┐ └───┘┌───┐
|
|
lqr_2_1: |0>──┤ X ├────────┤ Z ├
|
|
└───┘ └───┘
|
|
lcr_0: 0 ═══════════════════════
|
|
|
|
lcr_1: 0 ═══════════════════════
|
|
|
|
"""
|
|
qreg = QuantumRegister(5, "rqr")
|
|
circuit_right = QuantumCircuit(qreg)
|
|
circuit_right.cx(qreg[0], qreg[3])
|
|
circuit_right.x(qreg[1])
|
|
circuit_right.y(qreg[2])
|
|
circuit_right.z(qreg[4])
|
|
|
|
circuit_expected = self.circuit_left.copy()
|
|
circuit_expected.cx(self.left_qubit0, self.left_qubit3)
|
|
circuit_expected.x(self.left_qubit1)
|
|
circuit_expected.y(self.left_qubit2)
|
|
circuit_expected.z(self.left_qubit4)
|
|
|
|
circuit_composed = self.circuit_left.compose(circuit_right, inplace=False)
|
|
self.assertEqual(circuit_composed, circuit_expected)
|
|
|
|
def test_compose_inorder_unusual_types(self):
|
|
"""Test that composition works in order, using Numpy integer types as well as regular
|
|
integer types. In general, it should be permissible to use any of the same `QubitSpecifier`
|
|
types (or similar for `Clbit`) that `QuantumCircuit.append` uses."""
|
|
qreg = QuantumRegister(5, "rqr")
|
|
creg = ClassicalRegister(2, "rcr")
|
|
circuit_right = QuantumCircuit(qreg, creg)
|
|
circuit_right.cx(qreg[0], qreg[3])
|
|
circuit_right.x(qreg[1])
|
|
circuit_right.y(qreg[2])
|
|
circuit_right.z(qreg[4])
|
|
circuit_right.measure([0, 1], [0, 1])
|
|
|
|
circuit_expected = self.circuit_left.copy()
|
|
circuit_expected.cx(self.left_qubit0, self.left_qubit3)
|
|
circuit_expected.x(self.left_qubit1)
|
|
circuit_expected.y(self.left_qubit2)
|
|
circuit_expected.z(self.left_qubit4)
|
|
circuit_expected.measure(self.left_qubit0, self.left_clbit0)
|
|
circuit_expected.measure(self.left_qubit1, self.left_clbit1)
|
|
|
|
circuit_composed = self.circuit_left.compose(circuit_right, np.arange(5), slice(0, 2))
|
|
self.assertEqual(circuit_composed, circuit_expected)
|
|
|
|
def test_compose_inorder_inplace(self):
|
|
"""Composing two circuits of the same width, default order, inplace.
|
|
|
|
┌───┐
|
|
lqr_1_0: |0>───┤ H ├─── rqr_0: |0>──■───────
|
|
├───┤ │ ┌───┐
|
|
lqr_1_1: |0>───┤ X ├─── rqr_1: |0>──┼──┤ X ├
|
|
┌──┴───┴──┐ │ ├───┤
|
|
lqr_1_2: |0>┤ U1(0.1) ├ + rqr_2: |0>──┼──┤ Y ├ =
|
|
└─────────┘ ┌─┴─┐└───┘
|
|
lqr_2_0: |0>─────■───── rqr_3: |0>┤ X ├─────
|
|
┌─┴─┐ └───┘┌───┐
|
|
lqr_2_1: |0>───┤ X ├─── rqr_4: |0>─────┤ Z ├
|
|
└───┘ └───┘
|
|
lcr_0: 0 ═══════════
|
|
|
|
lcr_1: 0 ═══════════
|
|
|
|
|
|
┌───┐
|
|
lqr_1_0: |0>───┤ H ├─────■───────
|
|
├───┤ │ ┌───┐
|
|
lqr_1_1: |0>───┤ X ├─────┼──┤ X ├
|
|
┌──┴───┴──┐ │ ├───┤
|
|
lqr_1_2: |0>┤ U1(0.1) ├──┼──┤ Y ├
|
|
└─────────┘┌─┴─┐└───┘
|
|
lqr_2_0: |0>─────■─────┤ X ├─────
|
|
┌─┴─┐ └───┘┌───┐
|
|
lqr_2_1: |0>───┤ X ├────────┤ Z ├
|
|
└───┘ └───┘
|
|
lcr_0: 0 ════════════════════════
|
|
|
|
lcr_1: 0 ════════════════════════
|
|
|
|
"""
|
|
qreg = QuantumRegister(5, "rqr")
|
|
circuit_right = QuantumCircuit(qreg)
|
|
circuit_right.cx(qreg[0], qreg[3])
|
|
circuit_right.x(qreg[1])
|
|
circuit_right.y(qreg[2])
|
|
circuit_right.z(qreg[4])
|
|
|
|
circuit_expected = self.circuit_left.copy()
|
|
circuit_expected.cx(self.left_qubit0, self.left_qubit3)
|
|
circuit_expected.x(self.left_qubit1)
|
|
circuit_expected.y(self.left_qubit2)
|
|
circuit_expected.z(self.left_qubit4)
|
|
|
|
# inplace
|
|
circuit_left = self.circuit_left.copy()
|
|
circuit_left.compose(circuit_right, inplace=True)
|
|
self.assertEqual(circuit_left, circuit_expected)
|
|
|
|
def test_compose_inorder_smaller(self):
|
|
"""Composing with a smaller RHS dag, default order.
|
|
|
|
┌───┐ ┌─────┐
|
|
lqr_1_0: |0>───┤ H ├─── rqr_0: |0>──■──┤ Tdg ├
|
|
├───┤ ┌─┴─┐└─────┘
|
|
lqr_1_1: |0>───┤ X ├─── rqr_1: |0>┤ X ├───────
|
|
┌──┴───┴──┐ └───┘
|
|
lqr_1_2: |0>┤ U1(0.1) ├ + =
|
|
└─────────┘
|
|
lqr_2_0: |0>─────■─────
|
|
┌─┴─┐
|
|
lqr_2_1: |0>───┤ X ├───
|
|
└───┘
|
|
lcr_0: 0 ══════════════
|
|
|
|
lcr_1: 0 ══════════════
|
|
|
|
┌───┐ ┌─────┐
|
|
lqr_1_0: |0>───┤ H ├─────■──┤ Tdg ├
|
|
├───┤ ┌─┴─┐└─────┘
|
|
lqr_1_1: |0>───┤ X ├───┤ X ├───────
|
|
┌──┴───┴──┐└───┘
|
|
lqr_1_2: |0>┤ U1(0.1) ├────────────
|
|
└─────────┘
|
|
lqr_2_0: |0>─────■─────────────────
|
|
┌─┴─┐
|
|
lqr_2_1: |0>───┤ X ├───────────────
|
|
└───┘
|
|
lcr_0: 0 ══════════════════════════
|
|
|
|
lcr_1: 0 ══════════════════════════
|
|
|
|
"""
|
|
qreg = QuantumRegister(2, "rqr")
|
|
|
|
circuit_right = QuantumCircuit(qreg)
|
|
circuit_right.cx(qreg[0], qreg[1])
|
|
circuit_right.tdg(qreg[0])
|
|
|
|
circuit_expected = self.circuit_left.copy()
|
|
circuit_expected.cx(self.left_qubit0, self.left_qubit1)
|
|
circuit_expected.tdg(self.left_qubit0)
|
|
|
|
circuit_composed = self.circuit_left.compose(circuit_right)
|
|
self.assertEqual(circuit_composed, circuit_expected)
|
|
|
|
def test_compose_permuted(self):
|
|
"""Composing two dags of the same width, permuted wires.
|
|
┌───┐
|
|
lqr_1_0: |0>──┤ H ├─── rqr_0: |0>──■───────
|
|
├───┤ │ ┌───┐
|
|
lqr_1_1: |0>──┤ X ├─── rqr_1: |0>──┼──┤ X ├
|
|
┌─┴───┴──┐ │ ├───┤
|
|
lqr_1_2: |0>┤ P(0.1) ├ rqr_2: |0>──┼──┤ Y ├
|
|
└────────┘ ┌─┴─┐└───┘
|
|
lqr_2_0: |0>────■───── + rqr_3: |0>┤ X ├───── =
|
|
┌─┴─┐ └───┘┌───┐
|
|
lqr_2_1: |0>──┤ X ├─── rqr_4: |0>─────┤ Z ├
|
|
└───┘ └───┘
|
|
lcr_0: 0 ══════════════
|
|
|
|
lcr_1: 0 ══════════════
|
|
|
|
┌───┐ ┌───┐
|
|
lqr_1_0: |0>──┤ H ├───┤ Z ├
|
|
├───┤ ├───┤
|
|
lqr_1_1: |0>──┤ X ├───┤ X ├
|
|
┌─┴───┴──┐├───┤
|
|
lqr_1_2: |0>┤ P(0.1) ├┤ Y ├
|
|
└────────┘└───┘
|
|
lqr_2_0: |0>────■───────■──
|
|
┌─┴─┐ ┌─┴─┐
|
|
lqr_2_1: |0>──┤ X ├───┤ X ├
|
|
└───┘ └───┘
|
|
lcr_0: 0 ══════════════════
|
|
|
|
lcr_1: 0 ══════════════════
|
|
"""
|
|
qreg = QuantumRegister(5, "rqr")
|
|
|
|
circuit_right = QuantumCircuit(qreg)
|
|
circuit_right.cx(qreg[0], qreg[3])
|
|
circuit_right.x(qreg[1])
|
|
circuit_right.y(qreg[2])
|
|
circuit_right.z(qreg[4])
|
|
|
|
circuit_expected = self.circuit_left.copy()
|
|
circuit_expected.z(self.left_qubit0)
|
|
circuit_expected.x(self.left_qubit1)
|
|
circuit_expected.y(self.left_qubit2)
|
|
circuit_expected.cx(self.left_qubit3, self.left_qubit4)
|
|
|
|
# permuted wiring
|
|
circuit_composed = self.circuit_left.compose(
|
|
circuit_right,
|
|
qubits=[
|
|
self.left_qubit3,
|
|
self.left_qubit1,
|
|
self.left_qubit2,
|
|
self.left_qubit4,
|
|
self.left_qubit0,
|
|
],
|
|
inplace=False,
|
|
)
|
|
self.assertEqual(circuit_composed, circuit_expected)
|
|
|
|
def test_compose_permuted_smaller(self):
|
|
"""Composing with a smaller RHS dag, and permuted wires.
|
|
Compose using indices.
|
|
|
|
┌───┐ ┌─────┐
|
|
lqr_1_0: |0>──┤ H ├─── rqr_0: |0>──■──┤ Tdg ├
|
|
├───┤ ┌─┴─┐└─────┘
|
|
lqr_1_1: |0>──┤ X ├─── rqr_1: |0>┤ X ├───────
|
|
┌─┴───┴──┐ └───┘
|
|
lqr_1_2: |0>┤ P(0.1) ├ + =
|
|
└────────┘
|
|
lqr_2_0: |0>────■─────
|
|
┌─┴─┐
|
|
lqr_2_1: |0>──┤ X ├───
|
|
└───┘
|
|
lcr_0: 0 ═════════════
|
|
|
|
lcr_1: 0 ═════════════
|
|
|
|
┌───┐
|
|
lqr_1_0: |0>──┤ H ├───────────────
|
|
├───┤
|
|
lqr_1_1: |0>──┤ X ├───────────────
|
|
┌─┴───┴──┐┌───┐
|
|
lqr_1_2: |0>┤ P(0.1) ├┤ X ├───────
|
|
└────────┘└─┬─┘┌─────┐
|
|
lqr_2_0: |0>────■───────■──┤ Tdg ├
|
|
┌─┴─┐ └─────┘
|
|
lqr_2_1: |0>──┤ X ├───────────────
|
|
└───┘
|
|
lcr_0: 0 ═════════════════════════
|
|
|
|
lcr_1: 0 ═════════════════════════
|
|
"""
|
|
qreg = QuantumRegister(2, "rqr")
|
|
circuit_right = QuantumCircuit(qreg)
|
|
circuit_right.cx(qreg[0], qreg[1])
|
|
circuit_right.tdg(qreg[0])
|
|
|
|
# permuted wiring of subset
|
|
circuit_composed = self.circuit_left.compose(circuit_right, qubits=[3, 2])
|
|
|
|
circuit_expected = self.circuit_left.copy()
|
|
circuit_expected.cx(self.left_qubit3, self.left_qubit2)
|
|
circuit_expected.tdg(self.left_qubit3)
|
|
|
|
self.assertEqual(circuit_composed, circuit_expected)
|
|
|
|
def test_compose_copy(self):
|
|
"""Test that `compose` copies instructions where appropriate."""
|
|
base = QuantumCircuit(2, 2)
|
|
|
|
# If given a parametric instruction, the instruction should be copied in the output unless
|
|
# specifically set to take ownership.
|
|
parametric = QuantumCircuit(1)
|
|
parametric.rz(Parameter("x"), 0)
|
|
should_copy = base.compose(parametric, qubits=[0])
|
|
self.assertIsNot(should_copy.data[-1].operation, parametric.data[-1].operation)
|
|
self.assertEqual(should_copy.data[-1].operation, parametric.data[-1].operation)
|
|
forbid_copy = base.compose(parametric, qubits=[0], copy=False)
|
|
# For standard gates a fresh copy is returned from the data list each time
|
|
self.assertEqual(forbid_copy.data[-1].operation, parametric.data[-1].operation)
|
|
|
|
class Custom(Gate):
|
|
"""Custom gate that cannot be decomposed into Rust space."""
|
|
|
|
def __init__(self):
|
|
super().__init__("mygate", 1, [])
|
|
|
|
conditional = QuantumCircuit(1, 1)
|
|
with self.assertWarns(DeprecationWarning):
|
|
conditional.append(Custom(), [0], []).c_if(conditional.clbits[0], True)
|
|
test = base.compose(conditional, qubits=[0], clbits=[0], copy=False)
|
|
self.assertIs(test.data[-1].operation, conditional.data[-1].operation)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(test.data[-1].operation.condition, (test.clbits[0], True))
|
|
|
|
def test_compose_classical(self):
|
|
"""Composing on classical bits.
|
|
|
|
┌───┐ ┌─────┐┌─┐
|
|
lqr_1_0: |0>──┤ H ├─── rqr_0: |0>──■──┤ Tdg ├┤M├
|
|
├───┤ ┌─┴─┐└─┬─┬─┘└╥┘
|
|
lqr_1_1: |0>──┤ X ├─── rqr_1: |0>┤ X ├──┤M├───╫─
|
|
┌─┴───┴──┐ └───┘ └╥┘ ║
|
|
lqr_1_2: |0>┤ P(0.1) ├ + rcr_0: 0 ════════╬════╩═ =
|
|
└────────┘ ║
|
|
lqr_2_0: |0>────■───── rcr_1: 0 ════════╩══════
|
|
┌─┴─┐
|
|
lqr_2_1: |0>──┤ X ├───
|
|
└───┘
|
|
lcr_0: 0 ══════════════
|
|
|
|
lcr_1: 0 ══════════════
|
|
|
|
┌───┐
|
|
lqr_1_0: |0>──┤ H ├──────────────────
|
|
├───┤ ┌─────┐┌─┐
|
|
lqr_1_1: |0>──┤ X ├─────■──┤ Tdg ├┤M├
|
|
┌─┴───┴──┐ │ └─────┘└╥┘
|
|
lqr_1_2: |0>┤ P(0.1) ├──┼──────────╫─
|
|
└────────┘ │ ║
|
|
lqr_2_0: |0>────■───────┼──────────╫─
|
|
┌─┴─┐ ┌─┴─┐ ┌─┐ ║
|
|
lqr_2_1: |0>──┤ X ├───┤ X ├──┤M├───╫─
|
|
└───┘ └───┘ └╥┘ ║
|
|
lcr_0: 0 ══════════════════╩════╬═
|
|
║
|
|
lcr_1: 0 ═══════════════════════╩═
|
|
"""
|
|
qreg = QuantumRegister(2, "rqr")
|
|
creg = ClassicalRegister(2, "rcr")
|
|
|
|
circuit_right = QuantumCircuit(qreg, creg)
|
|
circuit_right.cx(qreg[0], qreg[1])
|
|
circuit_right.tdg(qreg[0])
|
|
circuit_right.measure(qreg, creg)
|
|
|
|
# permuted subset of qubits and clbits
|
|
circuit_composed = self.circuit_left.compose(circuit_right, qubits=[1, 4], clbits=[1, 0])
|
|
|
|
circuit_expected = self.circuit_left.copy()
|
|
circuit_expected.cx(self.left_qubit1, self.left_qubit4)
|
|
circuit_expected.tdg(self.left_qubit1)
|
|
circuit_expected.measure(self.left_qubit4, self.left_clbit0)
|
|
circuit_expected.measure(self.left_qubit1, self.left_clbit1)
|
|
|
|
self.assertEqual(circuit_composed, circuit_expected)
|
|
|
|
def test_compose_conditional(self):
|
|
"""Composing on classical bits.
|
|
|
|
┌───┐ ┌───┐ ┌─┐
|
|
lqr_1_0: |0>──┤ H ├─── rqr_0: ────────┤ H ├─┤M├───
|
|
├───┤ ┌───┐ └─┬─┘ └╥┘┌─┐
|
|
lqr_1_1: |0>──┤ X ├─── rqr_1: ─┤ X ├────┼────╫─┤M├
|
|
┌─┴───┴──┐ └─┬─┘ │ ║ └╥┘
|
|
lqr_1_2: |0>┤ P(0.1) ├ + ┌──┴──┐┌──┴──┐ ║ ║
|
|
└────────┘ rcr_0: ╡ ╞╡ ╞═╩══╬═
|
|
lqr_2_0: |0>────■───── │ = 3 ││ = 3 │ ║
|
|
┌─┴─┐ rcr_1: ╡ ╞╡ ╞════╩═
|
|
lqr_2_1: |0>──┤ X ├─── └─────┘└─────┘
|
|
└───┘
|
|
lcr_0: 0 ══════════════
|
|
|
|
lcr_1: 0 ══════════════
|
|
|
|
┌───┐
|
|
lqr_1_0: ──┤ H ├───────────────────────
|
|
├───┤ ┌───┐ ┌─┐
|
|
lqr_1_1: ──┤ X ├───────────┤ H ├────┤M├
|
|
┌─┴───┴──┐ └─┬─┘ └╥┘
|
|
lqr_1_2: ┤ P(0.1) ├──────────┼───────╫─
|
|
└────────┘ │ ║
|
|
lqr_2_0: ────■───────────────┼───────╫─
|
|
┌─┴─┐ ┌───┐ │ ┌─┐ ║
|
|
lqr_2_1: ──┤ X ├────┤ X ├────┼───┤M├─╫─
|
|
└───┘ └─┬─┘ │ └╥┘ ║
|
|
┌──┴──┐┌──┴──┐ ║ ║
|
|
lcr_0: ════════════╡ ╞╡ ╞═╬══╩═
|
|
│ = 3 ││ = 3 │ ║
|
|
lcr_1: ════════════╡ ╞╡ ╞═╩════
|
|
└─────┘└─────┘
|
|
"""
|
|
qreg = QuantumRegister(2, "rqr")
|
|
creg = ClassicalRegister(2, "rcr")
|
|
|
|
circuit_right = QuantumCircuit(qreg, creg)
|
|
with self.assertWarns(DeprecationWarning):
|
|
circuit_right.x(qreg[1]).c_if(creg, 3)
|
|
with self.assertWarns(DeprecationWarning):
|
|
circuit_right.h(qreg[0]).c_if(creg, 3)
|
|
circuit_right.measure(qreg, creg)
|
|
|
|
# permuted subset of qubits and clbits
|
|
circuit_composed = self.circuit_left.compose(circuit_right, qubits=[1, 4], clbits=[0, 1])
|
|
|
|
circuit_expected = self.circuit_left.copy()
|
|
with self.assertWarns(DeprecationWarning):
|
|
circuit_expected.x(self.left_qubit4).c_if(*self.condition)
|
|
with self.assertWarns(DeprecationWarning):
|
|
circuit_expected.h(self.left_qubit1).c_if(*self.condition)
|
|
circuit_expected.measure(self.left_qubit1, self.left_clbit0)
|
|
circuit_expected.measure(self.left_qubit4, self.left_clbit1)
|
|
|
|
self.assertEqual(circuit_composed, circuit_expected)
|
|
|
|
def test_compose_conditional_no_match(self):
|
|
"""Test that compose correctly maps registers in conditions to the new circuit, even when
|
|
there are no matching registers in the destination circuit.
|
|
|
|
Regression test of gh-6583 and gh-6584."""
|
|
right = QuantumCircuit(QuantumRegister(3), ClassicalRegister(1), ClassicalRegister(1))
|
|
right.h(1)
|
|
right.cx(1, 2)
|
|
right.cx(0, 1)
|
|
right.h(0)
|
|
right.measure([0, 1], [0, 1])
|
|
with self.assertWarns(DeprecationWarning):
|
|
right.z(2).c_if(right.cregs[0], 1)
|
|
with self.assertWarns(DeprecationWarning):
|
|
right.x(2).c_if(right.cregs[1], 1)
|
|
test = QuantumCircuit(3, 3).compose(right, range(3), range(2))
|
|
z = next(ins.operation for ins in test.data[::-1] if ins.operation.name == "z")
|
|
x = next(ins.operation for ins in test.data[::-1] if ins.operation.name == "x")
|
|
# The registers should have been mapped, including the bits inside them. Unlike the
|
|
# previous test, there are no matching registers in the destination circuit, so the
|
|
# composition needs to add new registers (bit groupings) over the existing mapped bits.
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertIsNot(z.condition, None)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertIsInstance(z.condition[0], ClassicalRegister)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(len(z.condition[0]), len(right.cregs[0]))
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertIs(z.condition[0][0], test.clbits[0])
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(z.condition[1], 1)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertIsNot(x.condition, None)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertIsInstance(x.condition[0], ClassicalRegister)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(len(x.condition[0]), len(right.cregs[1]))
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(z.condition[1], 1)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertIs(x.condition[0][0], test.clbits[1])
|
|
|
|
def test_compose_switch_match(self):
|
|
"""Test that composition containing a `switch` with a register that matches proceeds
|
|
correctly."""
|
|
case_0 = QuantumCircuit(1, 2)
|
|
case_0.x(0)
|
|
case_1 = QuantumCircuit(1, 2)
|
|
case_1.z(0)
|
|
case_default = QuantumCircuit(1, 2)
|
|
cr = ClassicalRegister(2, "target")
|
|
right = QuantumCircuit(QuantumRegister(1), cr)
|
|
right.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [0], [0, 1])
|
|
|
|
test = QuantumCircuit(QuantumRegister(3), cr, ClassicalRegister(2)).compose(
|
|
right, [1], [0, 1]
|
|
)
|
|
|
|
expected = test.copy_empty_like()
|
|
expected.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [1], [0, 1])
|
|
self.assertEqual(test, expected)
|
|
|
|
def test_compose_switch_no_match(self):
|
|
"""Test that composition containing a `switch` with a register that matches proceeds
|
|
correctly."""
|
|
case_0 = QuantumCircuit(1, 2)
|
|
case_0.x(0)
|
|
case_1 = QuantumCircuit(1, 2)
|
|
case_1.z(0)
|
|
case_default = QuantumCircuit(1, 2)
|
|
cr = ClassicalRegister(2, "target")
|
|
right = QuantumCircuit(QuantumRegister(1), cr)
|
|
right.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [0], [0, 1])
|
|
test = QuantumCircuit(3, 3).compose(right, [1], [0, 1])
|
|
|
|
self.assertEqual(len(test.data), 1)
|
|
self.assertIsInstance(test.data[0].operation, SwitchCaseOp)
|
|
target = test.data[0].operation.target
|
|
self.assertIn(target, test.cregs)
|
|
self.assertEqual(list(target), test.clbits[0:2])
|
|
|
|
def test_compose_gate(self):
|
|
"""Composing with a gate.
|
|
|
|
┌───┐ ┌───┐ ┌───┐
|
|
lqr_1_0: ──┤ H ├─── lqr_1_0: ──┤ H ├────┤ X ├
|
|
├───┤ ├───┤ └─┬─┘
|
|
lqr_1_1: ──┤ X ├─── lqr_1_1: ──┤ X ├──────┼───
|
|
┌─┴───┴──┐ ───■─── ┌─┴───┴──┐ │
|
|
lqr_1_2: ┤ P(0.1) ├ + ┌─┴─┐ = lqr_1_2: ┤ P(0.1) ├───┼───
|
|
└────────┘ ─┤ X ├─ └────────┘ │
|
|
lqr_2_0: ────■───── └───┘ lqr_2_0: ────■────────┼──
|
|
┌─┴─┐ ┌─┴─┐ │
|
|
lqr_2_1: ──┤ X ├─── lqr_2_1: ──┤ X ├──────■───
|
|
└───┘ └───┘
|
|
lcr_0: 0 ══════════ lcr_0: 0 ═════════════════
|
|
|
|
lcr_1: 0 ══════════ lcr_1: 0 ═════════════════
|
|
|
|
"""
|
|
circuit_composed = self.circuit_left.compose(CXGate(), qubits=[4, 0])
|
|
|
|
circuit_expected = self.circuit_left.copy()
|
|
circuit_expected.cx(self.left_qubit4, self.left_qubit0)
|
|
|
|
self.assertEqual(circuit_composed, circuit_expected)
|
|
|
|
def test_compose_calibrations(self):
|
|
"""Test that composing two circuits updates calibrations."""
|
|
circ_left = QuantumCircuit(1)
|
|
circ_right = QuantumCircuit(1)
|
|
with self.assertWarns(DeprecationWarning):
|
|
circ_left.add_calibration("h", [0], None)
|
|
circ_right.add_calibration("rx", [0], None)
|
|
circ = circ_left.compose(circ_right)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(len(circ.calibrations), 2)
|
|
self.assertEqual(len(circ_left.calibrations), 1)
|
|
|
|
circ_left = QuantumCircuit(1)
|
|
circ_right = QuantumCircuit(1)
|
|
with self.assertWarns(DeprecationWarning):
|
|
circ_left.add_calibration("h", [0], None)
|
|
circ_right.add_calibration("h", [1], None)
|
|
circ = circ_left.compose(circ_right)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(len(circ.calibrations), 1)
|
|
self.assertEqual(len(circ.calibrations["h"]), 2)
|
|
self.assertEqual(len(circ_left.calibrations), 1)
|
|
|
|
# Ensure that transpiled _calibration is defaultdict
|
|
qc = QuantumCircuit(2, 2)
|
|
qc.h(0)
|
|
qc.cx(0, 1)
|
|
qc.measure(0, 0)
|
|
qc = transpile(qc, None, basis_gates=["h", "cx"], coupling_map=[[0, 1], [1, 0]])
|
|
with self.assertWarns(DeprecationWarning):
|
|
qc.add_calibration("cx", [0, 1], Schedule())
|
|
|
|
def test_compose_one_liner(self):
|
|
"""Test building a circuit in one line, for fun."""
|
|
circ = QuantumCircuit(3)
|
|
h = HGate()
|
|
rz = RZGate(0.1)
|
|
cx = CXGate()
|
|
ccx = CCXGate()
|
|
circ = circ.compose(h, [0]).compose(cx, [0, 2]).compose(ccx, [2, 1, 0]).compose(rz, [1])
|
|
|
|
expected = QuantumCircuit(3)
|
|
expected.h(0)
|
|
expected.cx(0, 2)
|
|
expected.ccx(2, 1, 0)
|
|
expected.rz(0.1, 1)
|
|
|
|
self.assertEqual(circ, expected)
|
|
|
|
def test_compose_global_phase(self):
|
|
"""Composing with global phase."""
|
|
circ1 = QuantumCircuit(1, global_phase=1)
|
|
circ1.rz(0.5, 0)
|
|
circ2 = QuantumCircuit(1, global_phase=2)
|
|
circ3 = QuantumCircuit(1, global_phase=3)
|
|
circ4 = circ1.compose(circ2).compose(circ3)
|
|
self.assertEqual(
|
|
circ4.global_phase, circ1.global_phase + circ2.global_phase + circ3.global_phase
|
|
)
|
|
|
|
def test_compose_front_circuit(self):
|
|
"""Test composing a circuit at the front of a circuit."""
|
|
|
|
qc = QuantumCircuit(2)
|
|
qc.h(0)
|
|
qc.cx(0, 1)
|
|
|
|
other = QuantumCircuit(2)
|
|
other.cz(1, 0)
|
|
other.z(1)
|
|
|
|
output = qc.compose(other, front=True)
|
|
|
|
expected = QuantumCircuit(2)
|
|
expected.cz(1, 0)
|
|
expected.z(1)
|
|
expected.h(0)
|
|
expected.cx(0, 1)
|
|
|
|
self.assertEqual(output, expected)
|
|
|
|
def test_compose_front_gate(self):
|
|
"""Test composing a gate at the front of a circuit."""
|
|
|
|
qc = QuantumCircuit(2)
|
|
qc.h(0)
|
|
qc.cx(0, 1)
|
|
|
|
output = qc.compose(CXGate(), [1, 0], front=True)
|
|
|
|
expected = QuantumCircuit(2)
|
|
expected.cx(1, 0)
|
|
expected.h(0)
|
|
expected.cx(0, 1)
|
|
|
|
self.assertEqual(output, expected)
|
|
|
|
def test_compose_adds_parameters(self):
|
|
"""Test the composed circuit contains all parameters."""
|
|
a, b = Parameter("a"), Parameter("b")
|
|
|
|
qc_a = QuantumCircuit(1)
|
|
qc_a.rx(a, 0)
|
|
|
|
qc_b = QuantumCircuit(1)
|
|
qc_b.rx(b, 0)
|
|
|
|
with self.subTest("compose with other circuit out-of-place"):
|
|
qc_1 = qc_a.compose(qc_b)
|
|
self.assertEqual(qc_1.parameters, {a, b})
|
|
|
|
with self.subTest("compose with other instruction out-of-place"):
|
|
instr_b = qc_b.to_instruction()
|
|
qc_2 = qc_a.compose(instr_b, [0])
|
|
self.assertEqual(qc_2.parameters, {a, b})
|
|
|
|
with self.subTest("compose with other circuit in-place"):
|
|
qc_a.compose(qc_b, inplace=True)
|
|
self.assertEqual(qc_a.parameters, {a, b})
|
|
|
|
def test_wrapped_compose(self):
|
|
"""Test wrapping the circuit upon composition works."""
|
|
qc_a = QuantumCircuit(1)
|
|
qc_a.x(0)
|
|
|
|
qc_b = QuantumCircuit(1, name="B")
|
|
qc_b.h(0)
|
|
|
|
qc_a.compose(qc_b, wrap=True, inplace=True)
|
|
|
|
self.assertDictEqual(qc_a.count_ops(), {"B": 1, "x": 1})
|
|
self.assertDictEqual(qc_a.decompose().count_ops(), {"h": 1, "u3": 1})
|
|
|
|
def test_wrapping_unitary_circuit(self):
|
|
"""Test a unitary circuit will be wrapped as Gate, else as Instruction."""
|
|
qc_init = QuantumCircuit(1)
|
|
qc_init.x(0)
|
|
|
|
qc_unitary = QuantumCircuit(1, name="a")
|
|
qc_unitary.ry(0.23, 0)
|
|
|
|
qc_nonunitary = QuantumCircuit(1)
|
|
qc_nonunitary.reset(0)
|
|
|
|
with self.subTest("wrapping a unitary circuit"):
|
|
qc = qc_init.compose(qc_unitary, wrap=True)
|
|
self.assertIsInstance(qc.data[1].operation, Gate)
|
|
|
|
with self.subTest("wrapping a non-unitary circuit"):
|
|
qc = qc_init.compose(qc_nonunitary, wrap=True)
|
|
self.assertIsInstance(qc.data[1].operation, Instruction)
|
|
|
|
def test_single_bit_condition(self):
|
|
"""Test that compose can correctly handle circuits that contain conditions on single
|
|
bits. This is a regression test of the bug that broke qiskit-experiments in gh-7653."""
|
|
base = QuantumCircuit(1, 1)
|
|
with self.assertWarns(DeprecationWarning):
|
|
base.x(0).c_if(0, True)
|
|
test = QuantumCircuit(1, 1).compose(base)
|
|
self.assertIsNot(base.clbits[0], test.clbits[0])
|
|
self.assertEqual(base, test)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertIs(test.data[0].operation.condition[0], test.clbits[0])
|
|
|
|
def test_condition_mapping_ifelseop(self):
|
|
"""Test that the condition in an `IfElseOp` is correctly mapped to a new set of bits and
|
|
registers."""
|
|
base_loose = Clbit()
|
|
base_creg = ClassicalRegister(2)
|
|
base_qreg = QuantumRegister(1)
|
|
base = QuantumCircuit(base_qreg, [base_loose], base_creg)
|
|
with base.if_test((base_loose, True)):
|
|
base.x(0)
|
|
with base.if_test((base_creg, 3)):
|
|
base.x(0)
|
|
|
|
test_loose = Clbit()
|
|
test_creg = ClassicalRegister(2)
|
|
test_qreg = QuantumRegister(1)
|
|
test = QuantumCircuit(test_qreg, [test_loose], test_creg).compose(base)
|
|
|
|
bit_instruction = test.data[0].operation
|
|
reg_instruction = test.data[1].operation
|
|
self.assertIs(bit_instruction.condition[0], test_loose)
|
|
self.assertEqual(bit_instruction.condition, (test_loose, True))
|
|
self.assertIs(reg_instruction.condition[0], test_creg)
|
|
self.assertEqual(reg_instruction.condition, (test_creg, 3))
|
|
|
|
def test_condition_mapping_whileloopop(self):
|
|
"""Test that the condition in a `WhileLoopOp` is correctly mapped to a new set of bits and
|
|
registers."""
|
|
base_loose = Clbit()
|
|
base_creg = ClassicalRegister(2)
|
|
base_qreg = QuantumRegister(1)
|
|
base = QuantumCircuit(base_qreg, [base_loose], base_creg)
|
|
with base.while_loop((base_loose, True)):
|
|
base.x(0)
|
|
with base.while_loop((base_creg, 3)):
|
|
base.x(0)
|
|
|
|
test_loose = Clbit()
|
|
test_creg = ClassicalRegister(2)
|
|
test_qreg = QuantumRegister(1)
|
|
test = QuantumCircuit(test_qreg, [test_loose], test_creg).compose(base)
|
|
|
|
bit_instruction = test.data[0].operation
|
|
reg_instruction = test.data[1].operation
|
|
self.assertIs(bit_instruction.condition[0], test_loose)
|
|
self.assertEqual(bit_instruction.condition, (test_loose, True))
|
|
self.assertIs(reg_instruction.condition[0], test_creg)
|
|
self.assertEqual(reg_instruction.condition, (test_creg, 3))
|
|
|
|
def test_compose_no_clbits_in_one(self):
|
|
"""Test combining a circuit with cregs to one without"""
|
|
ansatz = TwoLocal(2, rotation_blocks="ry", entanglement_blocks="cx")
|
|
|
|
qc = QuantumCircuit(2)
|
|
qc.measure_all()
|
|
out = ansatz.compose(qc)
|
|
self.assertEqual(out.clbits, qc.clbits)
|
|
|
|
def test_compose_no_clbits_in_one_inplace(self):
|
|
"""Test combining a circuit with cregs to one without inplace"""
|
|
ansatz = TwoLocal(2, rotation_blocks="ry", entanglement_blocks="cx")
|
|
|
|
qc = QuantumCircuit(2)
|
|
qc.measure_all()
|
|
ansatz.compose(qc, inplace=True)
|
|
self.assertEqual(ansatz.clbits, qc.clbits)
|
|
|
|
def test_compose_no_clbits_in_one_multireg(self):
|
|
"""Test combining a circuit with cregs to one without, multi cregs"""
|
|
ansatz = TwoLocal(2, rotation_blocks="ry", entanglement_blocks="cx")
|
|
|
|
qa = QuantumRegister(2, "q")
|
|
ca = ClassicalRegister(2, "a")
|
|
cb = ClassicalRegister(2, "b")
|
|
qc = QuantumCircuit(qa, ca, cb)
|
|
qc.measure(0, cb[1])
|
|
out = ansatz.compose(qc)
|
|
self.assertEqual(out.clbits, qc.clbits)
|
|
self.assertEqual(out.cregs, qc.cregs)
|
|
|
|
def test_compose_noclbits_registerless(self):
|
|
"""Combining a circuit with cregs to one without, registerless case"""
|
|
inner = QuantumCircuit([Qubit(), Qubit()], [Clbit(), Clbit()])
|
|
inner.measure([0, 1], [0, 1])
|
|
outer = QuantumCircuit(2)
|
|
outer.compose(inner, inplace=True)
|
|
self.assertEqual(outer.clbits, inner.clbits)
|
|
self.assertEqual(outer.cregs, [])
|
|
|
|
def test_expr_condition_is_mapped(self):
|
|
"""Test that an expression in a condition involving several registers is mapped correctly to
|
|
the destination circuit."""
|
|
inner = QuantumCircuit(1)
|
|
inner.x(0)
|
|
a_src = ClassicalRegister(2, "a_src")
|
|
b_src = ClassicalRegister(2, "b_src")
|
|
c_src = ClassicalRegister(name="c_src", bits=list(a_src) + list(b_src))
|
|
source = QuantumCircuit(QuantumRegister(1), a_src, b_src, c_src)
|
|
target_var = source.add_input("target_var", types.Uint(2))
|
|
|
|
test_1 = lambda: expr.lift(a_src[0])
|
|
test_2 = lambda: expr.logic_not(b_src[1])
|
|
test_3 = lambda: expr.logic_and(expr.bit_and(b_src, 2), expr.less(c_src, 7))
|
|
test_4 = lambda: expr.bit_xor(expr.index(target_var, 0), expr.index(target_var, 1))
|
|
source.if_test(test_1(), inner.copy(), [0], [])
|
|
source.if_else(test_2(), inner.copy(), inner.copy(), [0], [])
|
|
source.while_loop(test_3(), inner.copy(), [0], [])
|
|
source.if_test(test_4(), inner.copy(), [0], [])
|
|
|
|
a_dest = ClassicalRegister(2, "a_dest")
|
|
b_dest = ClassicalRegister(2, "b_dest")
|
|
dest = QuantumCircuit(QuantumRegister(1), a_dest, b_dest).compose(source)
|
|
|
|
# Check that the input conditions weren't mutated.
|
|
for in_condition, instruction in zip((test_1, test_2, test_3), source.data):
|
|
self.assertEqual(in_condition(), instruction.operation.condition)
|
|
|
|
# Should be `a_dest`, `b_dest` and an added one to account for `c_src`.
|
|
self.assertEqual(len(dest.cregs), 3)
|
|
mapped_reg = dest.cregs[-1]
|
|
|
|
expected = QuantumCircuit(dest.qregs[0], a_dest, b_dest, mapped_reg, inputs=[target_var])
|
|
expected.if_test(expr.lift(a_dest[0]), inner.copy(), [0], [])
|
|
expected.if_else(expr.logic_not(b_dest[1]), inner.copy(), inner.copy(), [0], [])
|
|
expected.while_loop(
|
|
expr.logic_and(expr.bit_and(b_dest, 2), expr.less(mapped_reg, 7)), inner.copy(), [0], []
|
|
)
|
|
# `Var` nodes aren't remapped, but this should be passed through fine.
|
|
expected.if_test(
|
|
expr.bit_xor(expr.index(target_var, 0), expr.index(target_var, 1)),
|
|
inner.copy(),
|
|
[0],
|
|
[],
|
|
)
|
|
self.assertEqual(dest, expected)
|
|
|
|
def test_expr_target_is_mapped(self):
|
|
"""Test that an expression in a switch statement's target is mapping correctly to the
|
|
destination circuit."""
|
|
inner1 = QuantumCircuit(1)
|
|
inner1.x(0)
|
|
inner2 = QuantumCircuit(1)
|
|
inner2.z(0)
|
|
|
|
a_src = ClassicalRegister(2, "a_src")
|
|
b_src = ClassicalRegister(2, "b_src")
|
|
c_src = ClassicalRegister(name="c_src", bits=list(a_src) + list(b_src))
|
|
source = QuantumCircuit(QuantumRegister(1), a_src, b_src, c_src)
|
|
|
|
test_1 = lambda: expr.lift(a_src[0])
|
|
test_2 = lambda: expr.logic_not(b_src[1])
|
|
test_3 = lambda: expr.lift(b_src)
|
|
test_4 = lambda: expr.bit_and(c_src, 7)
|
|
source.switch(test_1(), [(False, inner1.copy()), (True, inner2.copy())], [0], [])
|
|
source.switch(test_2(), [(False, inner1.copy()), (True, inner2.copy())], [0], [])
|
|
source.switch(test_3(), [(0, inner1.copy()), (CASE_DEFAULT, inner2.copy())], [0], [])
|
|
source.switch(test_4(), [(0, inner1.copy()), (CASE_DEFAULT, inner2.copy())], [0], [])
|
|
|
|
a_dest = ClassicalRegister(2, "a_dest")
|
|
b_dest = ClassicalRegister(2, "b_dest")
|
|
dest = QuantumCircuit(QuantumRegister(1), a_dest, b_dest).compose(source)
|
|
|
|
# Check that the input expressions weren't mutated.
|
|
for in_target, instruction in zip((test_1, test_2, test_3, test_4), source.data):
|
|
self.assertEqual(in_target(), instruction.operation.target)
|
|
|
|
# Should be `a_dest`, `b_dest` and an added one to account for `c_src`.
|
|
self.assertEqual(len(dest.cregs), 3)
|
|
mapped_reg = dest.cregs[-1]
|
|
|
|
expected = QuantumCircuit(dest.qregs[0], a_dest, b_dest, mapped_reg)
|
|
expected.switch(
|
|
expr.lift(a_dest[0]), [(False, inner1.copy()), (True, inner2.copy())], [0], []
|
|
)
|
|
expected.switch(
|
|
expr.logic_not(b_dest[1]), [(False, inner1.copy()), (True, inner2.copy())], [0], []
|
|
)
|
|
expected.switch(
|
|
expr.lift(b_dest), [(0, inner1.copy()), (CASE_DEFAULT, inner2.copy())], [0], []
|
|
)
|
|
expected.switch(
|
|
expr.bit_and(mapped_reg, 7),
|
|
[(0, inner1.copy()), (CASE_DEFAULT, inner2.copy())],
|
|
[0],
|
|
[],
|
|
)
|
|
|
|
self.assertEqual(dest, expected)
|
|
|
|
def test_join_unrelated_vars(self):
|
|
"""Composing disjoint sets of vars should produce an additive output."""
|
|
a = expr.Var.new("a", types.Bool())
|
|
b = expr.Var.new("b", types.Uint(8))
|
|
|
|
base = QuantumCircuit(inputs=[a])
|
|
other = QuantumCircuit(inputs=[b])
|
|
out = base.compose(other)
|
|
self.assertEqual({a, b}, set(out.iter_vars()))
|
|
self.assertEqual({a, b}, set(out.iter_input_vars()))
|
|
# Assert that base was unaltered.
|
|
self.assertEqual({a}, set(base.iter_vars()))
|
|
|
|
base = QuantumCircuit(captures=[a])
|
|
other = QuantumCircuit(captures=[b])
|
|
out = base.compose(other)
|
|
self.assertEqual({a, b}, set(out.iter_vars()))
|
|
self.assertEqual({a, b}, set(out.iter_captured_vars()))
|
|
self.assertEqual({a}, set(base.iter_vars()))
|
|
|
|
base = QuantumCircuit(inputs=[a])
|
|
other = QuantumCircuit(declarations=[(b, 255)])
|
|
out = base.compose(other)
|
|
self.assertEqual({a, b}, set(out.iter_vars()))
|
|
self.assertEqual({a}, set(out.iter_input_vars()))
|
|
self.assertEqual({b}, set(out.iter_declared_vars()))
|
|
|
|
def test_var_remap_to_avoid_collisions(self):
|
|
"""We can use `var_remap` to avoid a variable collision."""
|
|
a1 = expr.Var.new("a", types.Bool())
|
|
a2 = expr.Var.new("a", types.Bool())
|
|
b = expr.Var.new("b", types.Bool())
|
|
|
|
base = QuantumCircuit(inputs=[a1])
|
|
other = QuantumCircuit(inputs=[a2])
|
|
|
|
out = base.compose(other, var_remap={a2: b})
|
|
self.assertEqual([a1, b], list(out.iter_input_vars()))
|
|
self.assertEqual([a1, b], list(out.iter_vars()))
|
|
|
|
out = base.compose(other, var_remap={"a": b})
|
|
self.assertEqual([a1, b], list(out.iter_input_vars()))
|
|
self.assertEqual([a1, b], list(out.iter_vars()))
|
|
|
|
out = base.compose(other, var_remap={"a": "c"})
|
|
self.assertTrue(out.has_var("c"))
|
|
c = out.get_var("c")
|
|
self.assertEqual(c.name, "c")
|
|
self.assertEqual([a1, c], list(out.iter_input_vars()))
|
|
self.assertEqual([a1, c], list(out.iter_vars()))
|
|
|
|
def test_simple_inline_captures(self):
|
|
"""We should be able to inline captures onto other variables."""
|
|
a = expr.Var.new("a", types.Bool())
|
|
b = expr.Var.new("b", types.Bool())
|
|
c = expr.Var.new("c", types.Uint(8))
|
|
|
|
base = QuantumCircuit(inputs=[a, b])
|
|
base.add_var(c, 255)
|
|
base.store(a, expr.logic_or(a, b))
|
|
other = QuantumCircuit(captures=[a, b, c])
|
|
other.store(c, 254)
|
|
other.store(b, expr.logic_or(a, b))
|
|
new = base.compose(other, inline_captures=True)
|
|
|
|
expected = QuantumCircuit(inputs=[a, b])
|
|
expected.add_var(c, 255)
|
|
expected.store(a, expr.logic_or(a, b))
|
|
expected.store(c, 254)
|
|
expected.store(b, expr.logic_or(a, b))
|
|
self.assertEqual(new, expected)
|
|
|
|
def test_can_inline_a_capture_after_remapping(self):
|
|
"""We can use `var_remap` to redefine a capture variable _and then_ inline it in deeply
|
|
nested scopes. This is a stress test of capture inlining."""
|
|
a = expr.Var.new("a", types.Bool())
|
|
b = expr.Var.new("b", types.Bool())
|
|
c = expr.Var.new("c", types.Uint(8))
|
|
|
|
# We shouldn't be able to inline `qc`'s variable use as-is because it closes over the wrong
|
|
# variable, but it should work after variable remapping. (This isn't expected to be super
|
|
# useful, it's just a consequence of how the order between `var_remap` and `inline_captures`
|
|
# is defined).
|
|
base = QuantumCircuit(inputs=[a])
|
|
qc = QuantumCircuit(declarations=[(c, 255)], captures=[b])
|
|
qc.store(b, expr.logic_and(b, b))
|
|
with qc.if_test(expr.logic_not(b)):
|
|
with qc.while_loop(b):
|
|
qc.store(b, expr.logic_not(b))
|
|
# Note that 'c' is captured in this scope, so this is also a test that 'inline_captures'
|
|
# doesn't do something silly in nested scopes.
|
|
with qc.switch(c) as case:
|
|
with case(0):
|
|
qc.store(c, expr.bit_and(c, 255))
|
|
with case(case.DEFAULT):
|
|
qc.store(b, expr.equal(c, 255))
|
|
base.compose(qc, inplace=True, inline_captures=True, var_remap={b: a})
|
|
|
|
expected = QuantumCircuit(inputs=[a], declarations=[(c, 255)])
|
|
expected.store(a, expr.logic_and(a, a))
|
|
with expected.if_test(expr.logic_not(a)):
|
|
with expected.while_loop(a):
|
|
expected.store(a, expr.logic_not(a))
|
|
# Note that 'c' is not remapped.
|
|
with expected.switch(c) as case:
|
|
with case(0):
|
|
expected.store(c, expr.bit_and(c, 255))
|
|
with case(case.DEFAULT):
|
|
expected.store(a, expr.equal(c, 255))
|
|
|
|
self.assertEqual(base, expected)
|
|
|
|
def test_rejects_duplicate_bits(self):
|
|
"""Test that compose rejects duplicates in either qubits or clbits."""
|
|
base = QuantumCircuit(5, 5)
|
|
|
|
attempt = QuantumCircuit(2, 2)
|
|
with self.assertRaisesRegex(CircuitError, "Duplicate qubits"):
|
|
base.compose(attempt, [1, 1], [0, 1])
|
|
with self.assertRaisesRegex(CircuitError, "Duplicate clbits"):
|
|
base.compose(attempt, [0, 1], [1, 1])
|
|
|
|
def test_cannot_mix_inputs_and_captures(self):
|
|
"""The rules about mixing `input` and `capture` vars should still apply."""
|
|
a = expr.Var.new("a", types.Bool())
|
|
b = expr.Var.new("b", types.Uint(8))
|
|
with self.assertRaisesRegex(CircuitError, "circuits with input variables cannot be"):
|
|
QuantumCircuit(inputs=[a]).compose(QuantumCircuit(captures=[b]))
|
|
with self.assertRaisesRegex(CircuitError, "circuits to be enclosed with captures cannot"):
|
|
QuantumCircuit(captures=[a]).compose(QuantumCircuit(inputs=[b]))
|
|
|
|
def test_reject_var_naming_collision(self):
|
|
"""We can't have multiple vars with the same name."""
|
|
a1 = expr.Var.new("a", types.Bool())
|
|
a2 = expr.Var.new("a", types.Bool())
|
|
b = expr.Var.new("b", types.Bool())
|
|
self.assertNotEqual(a1, a2)
|
|
|
|
with self.assertRaisesRegex(CircuitError, "cannot add.*shadows"):
|
|
QuantumCircuit(inputs=[a1]).compose(QuantumCircuit(inputs=[a2]))
|
|
with self.assertRaisesRegex(CircuitError, "cannot add.*shadows"):
|
|
QuantumCircuit(captures=[a1]).compose(QuantumCircuit(declarations=[(a2, False)]))
|
|
with self.assertRaisesRegex(CircuitError, "cannot add.*shadows"):
|
|
QuantumCircuit(declarations=[(a1, True)]).compose(
|
|
QuantumCircuit(inputs=[b]), var_remap={b: a2}
|
|
)
|
|
|
|
def test_reject_remap_var_to_bad_type(self):
|
|
"""Can't map a var to a different type."""
|
|
a = expr.Var.new("a", types.Bool())
|
|
b = expr.Var.new("b", types.Uint(8))
|
|
qc = QuantumCircuit(inputs=[a])
|
|
with self.assertRaisesRegex(CircuitError, "mismatched types"):
|
|
QuantumCircuit().compose(qc, var_remap={a: b})
|
|
qc = QuantumCircuit(captures=[b])
|
|
with self.assertRaisesRegex(CircuitError, "mismatched types"):
|
|
QuantumCircuit().compose(qc, var_remap={b: a})
|
|
|
|
def test_reject_inlining_missing_var(self):
|
|
"""Can't inline a var that doesn't exist."""
|
|
a = expr.Var.new("a", types.Bool())
|
|
b = expr.Var.new("b", types.Bool())
|
|
qc = QuantumCircuit(captures=[a])
|
|
with self.assertRaisesRegex(CircuitError, "Variable '.*' to be inlined is not in the base"):
|
|
QuantumCircuit().compose(qc, inline_captures=True)
|
|
|
|
# 'a' _would_ be present, except we also say to remap it before attempting the inline.
|
|
qc = QuantumCircuit(captures=[a])
|
|
with self.assertRaisesRegex(CircuitError, "Replacement '.*' for variable '.*' is not in"):
|
|
QuantumCircuit(inputs=[a]).compose(qc, var_remap={a: b}, inline_captures=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|