mirror of https://github.com/Qiskit/qiskit.git
266 lines
11 KiB
Python
266 lines
11 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 converters."""
|
|
|
|
import math
|
|
import unittest
|
|
|
|
import numpy as np
|
|
|
|
from qiskit.converters import circuit_to_instruction
|
|
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
|
|
from qiskit.circuit import Qubit, Clbit, Instruction
|
|
from qiskit.circuit import Parameter
|
|
from qiskit.circuit.classical import expr, types
|
|
from qiskit.quantum_info import Operator
|
|
from qiskit.exceptions import QiskitError
|
|
from test import QiskitTestCase # pylint: disable=wrong-import-order
|
|
|
|
|
|
class TestCircuitToInstruction(QiskitTestCase):
|
|
"""Test Circuit to Instruction."""
|
|
|
|
def test_flatten_circuit_registers(self):
|
|
"""Check correct flattening"""
|
|
qr1 = QuantumRegister(4, "qr1")
|
|
qr2 = QuantumRegister(3, "qr2")
|
|
qr3 = QuantumRegister(3, "qr3")
|
|
cr1 = ClassicalRegister(4, "cr1")
|
|
cr2 = ClassicalRegister(1, "cr2")
|
|
circ = QuantumCircuit(qr1, qr2, qr3, cr1, cr2)
|
|
circ.cx(qr1[1], qr2[2])
|
|
circ.measure(qr3[0], cr2[0])
|
|
|
|
inst = circuit_to_instruction(circ)
|
|
q = QuantumRegister(10, "q")
|
|
c = ClassicalRegister(5, "c")
|
|
|
|
self.assertEqual(inst.definition[0].qubits, (q[1], q[6]))
|
|
self.assertEqual(inst.definition[1].qubits, (q[7],))
|
|
self.assertEqual(inst.definition[1].clbits, (c[4],))
|
|
|
|
def test_flatten_registers_of_circuit_single_bit_cond(self):
|
|
"""Check correct mapping of registers gates conditioned on single classical bits."""
|
|
qr1 = QuantumRegister(2, "qr1")
|
|
qr2 = QuantumRegister(3, "qr2")
|
|
cr1 = ClassicalRegister(3, "cr1")
|
|
cr2 = ClassicalRegister(3, "cr2")
|
|
circ = QuantumCircuit(qr1, qr2, cr1, cr2)
|
|
with self.assertWarns(DeprecationWarning):
|
|
circ.h(qr1[0]).c_if(cr1[1], True)
|
|
with self.assertWarns(DeprecationWarning):
|
|
circ.h(qr2[1]).c_if(cr2[0], False)
|
|
with self.assertWarns(DeprecationWarning):
|
|
circ.cx(qr1[1], qr2[2]).c_if(cr2[2], True)
|
|
circ.measure(qr2[2], cr2[0])
|
|
|
|
with self.assertWarns(DeprecationWarning):
|
|
inst = circuit_to_instruction(circ)
|
|
q = QuantumRegister(5, "q")
|
|
c = ClassicalRegister(6, "c")
|
|
|
|
self.assertEqual(inst.definition[0].qubits, (q[0],))
|
|
self.assertEqual(inst.definition[1].qubits, (q[3],))
|
|
self.assertEqual(inst.definition[2].qubits, (q[1], q[4]))
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(inst.definition[0].operation.condition, (c[1], True))
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(inst.definition[1].operation.condition, (c[3], False))
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(inst.definition[2].operation.condition, (c[5], True))
|
|
|
|
def test_flatten_circuit_registerless(self):
|
|
"""Test that the conversion works when the given circuit has bits that are not contained in
|
|
any register."""
|
|
qr1 = QuantumRegister(2)
|
|
qubits = [Qubit(), Qubit(), Qubit()]
|
|
qr2 = QuantumRegister(3)
|
|
cr1 = ClassicalRegister(2)
|
|
clbits = [Clbit(), Clbit(), Clbit()]
|
|
cr2 = ClassicalRegister(3)
|
|
circ = QuantumCircuit(qr1, qubits, qr2, cr1, clbits, cr2)
|
|
circ.cx(3, 5)
|
|
circ.measure(4, 4)
|
|
|
|
inst = circuit_to_instruction(circ)
|
|
self.assertEqual(inst.num_qubits, len(qr1) + len(qubits) + len(qr2))
|
|
self.assertEqual(inst.num_clbits, len(cr1) + len(clbits) + len(cr2))
|
|
inst_definition = inst.definition
|
|
cx = inst_definition.data[0]
|
|
measure = inst_definition.data[1]
|
|
self.assertEqual(cx.qubits, (inst_definition.qubits[3], inst_definition.qubits[5]))
|
|
self.assertEqual(cx.clbits, ())
|
|
self.assertEqual(measure.qubits, (inst_definition.qubits[4],))
|
|
self.assertEqual(measure.clbits, (inst_definition.clbits[4],))
|
|
|
|
def test_flatten_circuit_overlapping_registers(self):
|
|
"""Test that the conversion works when the given circuit has bits that are contained in more
|
|
than one register."""
|
|
qubits = [Qubit() for _ in [None] * 10]
|
|
qr1 = QuantumRegister(bits=qubits[:6])
|
|
qr2 = QuantumRegister(bits=qubits[4:])
|
|
clbits = [Clbit() for _ in [None] * 10]
|
|
cr1 = ClassicalRegister(bits=clbits[:6])
|
|
cr2 = ClassicalRegister(bits=clbits[4:])
|
|
circ = QuantumCircuit(qubits, clbits, qr1, qr2, cr1, cr2)
|
|
circ.cx(3, 5)
|
|
circ.measure(4, 4)
|
|
|
|
inst = circuit_to_instruction(circ)
|
|
self.assertEqual(inst.num_qubits, len(qubits))
|
|
self.assertEqual(inst.num_clbits, len(clbits))
|
|
inst_definition = inst.definition
|
|
cx = inst_definition.data[0]
|
|
measure = inst_definition.data[1]
|
|
self.assertEqual(cx.qubits, (inst_definition.qubits[3], inst_definition.qubits[5]))
|
|
self.assertEqual(cx.clbits, ())
|
|
self.assertEqual(measure.qubits, (inst_definition.qubits[4],))
|
|
self.assertEqual(measure.clbits, (inst_definition.clbits[4],))
|
|
|
|
def test_flatten_parameters(self):
|
|
"""Verify parameters from circuit are moved to instruction.params"""
|
|
qr = QuantumRegister(3, "qr")
|
|
qc = QuantumCircuit(qr)
|
|
|
|
theta = Parameter("theta")
|
|
phi = Parameter("phi")
|
|
sum_ = theta + phi
|
|
|
|
qc.rz(theta, qr[0])
|
|
qc.rz(phi, qr[1])
|
|
qc.u(theta, phi, 0, qr[2])
|
|
qc.rz(sum_, qr[0])
|
|
|
|
inst = circuit_to_instruction(qc)
|
|
|
|
self.assertEqual(inst.params, [phi, theta])
|
|
self.assertEqual(inst.definition[0].operation.params, [theta])
|
|
self.assertEqual(inst.definition[1].operation.params, [phi])
|
|
self.assertEqual(inst.definition[2].operation.params, [theta, phi, 0])
|
|
self.assertEqual(str(inst.definition[3].operation.params[0]), "phi + theta")
|
|
|
|
def test_underspecified_parameter_map_raises(self):
|
|
"""Verify we raise if not all circuit parameters are present in parameter_map."""
|
|
qr = QuantumRegister(3, "qr")
|
|
qc = QuantumCircuit(qr)
|
|
|
|
theta = Parameter("theta")
|
|
phi = Parameter("phi")
|
|
sum_ = theta + phi
|
|
|
|
gamma = Parameter("gamma")
|
|
|
|
qc.rz(theta, qr[0])
|
|
qc.rz(phi, qr[1])
|
|
qc.u(theta, phi, 0, qr[2])
|
|
qc.rz(sum_, qr[0])
|
|
|
|
self.assertRaises(QiskitError, circuit_to_instruction, qc, {theta: gamma})
|
|
|
|
# Raise if provided more parameters than present in the circuit
|
|
delta = Parameter("delta")
|
|
self.assertRaises(
|
|
QiskitError, circuit_to_instruction, qc, {theta: gamma, phi: phi, delta: delta}
|
|
)
|
|
|
|
def test_parameter_map(self):
|
|
"""Verify alternate parameter specification"""
|
|
qr = QuantumRegister(3, "qr")
|
|
qc = QuantumCircuit(qr)
|
|
|
|
theta = Parameter("theta")
|
|
phi = Parameter("phi")
|
|
sum_ = theta + phi
|
|
|
|
gamma = Parameter("gamma")
|
|
|
|
qc.rz(theta, qr[0])
|
|
qc.rz(phi, qr[1])
|
|
qc.u(theta, phi, 0, qr[2])
|
|
qc.rz(sum_, qr[0])
|
|
|
|
inst = circuit_to_instruction(qc, {theta: gamma, phi: phi})
|
|
|
|
self.assertEqual(inst.params, [gamma, phi])
|
|
self.assertEqual(inst.definition[0].operation.params, [gamma])
|
|
self.assertEqual(inst.definition[1].operation.params, [phi])
|
|
self.assertEqual(inst.definition[2].operation.params, [gamma, phi, 0])
|
|
self.assertEqual(str(inst.definition[3].operation.params[0]), "gamma + phi")
|
|
|
|
def test_registerless_classical_bits(self):
|
|
"""Test that conditions on registerless classical bits can be handled during the conversion.
|
|
|
|
Regression test of gh-7394."""
|
|
expected = QuantumCircuit([Qubit(), Clbit()])
|
|
with self.assertWarns(DeprecationWarning):
|
|
expected.h(0).c_if(expected.clbits[0], 0)
|
|
with self.assertWarns(DeprecationWarning):
|
|
test = circuit_to_instruction(expected)
|
|
|
|
self.assertIsInstance(test, Instruction)
|
|
self.assertIsInstance(test.definition, QuantumCircuit)
|
|
|
|
self.assertEqual(len(test.definition.data), 1)
|
|
test_instruction = test.definition.data[0]
|
|
expected_instruction = expected.data[0]
|
|
self.assertIs(type(test_instruction.operation), type(expected_instruction.operation))
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(test_instruction.operation.condition, (test.definition.clbits[0], 0))
|
|
|
|
def test_zero_operands(self):
|
|
"""Test that an instruction can be created, even if it has zero operands."""
|
|
base = QuantumCircuit(global_phase=math.pi)
|
|
instruction = base.to_instruction()
|
|
self.assertEqual(instruction.num_qubits, 0)
|
|
self.assertEqual(instruction.num_clbits, 0)
|
|
self.assertEqual(instruction.definition, base)
|
|
compound = QuantumCircuit(1)
|
|
compound.append(instruction, [], [])
|
|
np.testing.assert_allclose(-np.eye(2), Operator(compound), atol=1e-16)
|
|
|
|
def test_forbids_captured_vars(self):
|
|
"""Instructions (here an analogue of functions) cannot close over outer scopes."""
|
|
qc = QuantumCircuit(captures=[expr.Var.new("a", types.Bool())])
|
|
with self.assertRaisesRegex(QiskitError, "Circuits that capture variables cannot"):
|
|
qc.to_instruction()
|
|
|
|
def test_forbids_input_vars(self):
|
|
"""This test can be relaxed when we have proper support for the behavior.
|
|
|
|
This actually has a natural meaning; the input variables could become typed parameters.
|
|
We don't have a formal structure for managing that yet, though, so it's forbidden until the
|
|
library is ready for that."""
|
|
qc = QuantumCircuit(inputs=[expr.Var.new("a", types.Bool())])
|
|
with self.assertRaisesRegex(QiskitError, "Circuits with 'input' variables cannot"):
|
|
qc.to_instruction()
|
|
|
|
def test_forbids_declared_vars(self):
|
|
"""This test can be relaxed when we have proper support for the behavior.
|
|
|
|
This has a very natural representation, which needs basically zero special handling, since
|
|
the variables are necessarily entirely internal to the subroutine. The reason it is
|
|
starting off as forbidden is because we don't have a good way to support variable renaming
|
|
during unrolling in transpilation, and we want the error to indicate an alternative at the
|
|
point the conversion happens."""
|
|
qc = QuantumCircuit()
|
|
qc.add_var("a", False)
|
|
with self.assertRaisesRegex(
|
|
QiskitError,
|
|
"Circuits with internal variables.*You may be able to use `QuantumCircuit.compose`",
|
|
):
|
|
qc.to_instruction()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|