mirror of https://github.com/Qiskit/qiskit.git
1497 lines
64 KiB
Python
1497 lines
64 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.
|
|
|
|
"""Test library of n-local circuits."""
|
|
|
|
import unittest
|
|
from test import combine
|
|
|
|
import numpy as np
|
|
|
|
from ddt import ddt, data, unpack
|
|
|
|
from qiskit import transpile
|
|
from qiskit.circuit import QuantumCircuit, Parameter, ParameterVector, ParameterExpression, Gate
|
|
from qiskit.circuit.library import (
|
|
n_local,
|
|
efficient_su2,
|
|
real_amplitudes,
|
|
excitation_preserving,
|
|
pauli_two_design,
|
|
NLocal,
|
|
TwoLocal,
|
|
RealAmplitudes,
|
|
ExcitationPreserving,
|
|
HGate,
|
|
XGate,
|
|
CRXGate,
|
|
CCXGate,
|
|
SwapGate,
|
|
RXGate,
|
|
RYGate,
|
|
EfficientSU2,
|
|
RZGate,
|
|
RXXGate,
|
|
RYYGate,
|
|
CXGate,
|
|
SXGate,
|
|
)
|
|
from qiskit.circuit.random.utils import random_circuit
|
|
from qiskit.converters.circuit_to_dag import circuit_to_dag
|
|
from qiskit.quantum_info import Operator
|
|
from qiskit.exceptions import QiskitError
|
|
|
|
from qiskit._accelerate.circuit_library import get_entangler_map as fast_entangler_map
|
|
from qiskit._accelerate.circuit_library import Block
|
|
|
|
from test import QiskitTestCase # pylint: disable=wrong-import-order
|
|
|
|
|
|
class Gato(Gate):
|
|
"""A custom gate."""
|
|
|
|
def __init__(self, x, y):
|
|
super().__init__("meow", 1, [x, y])
|
|
|
|
def _define(self):
|
|
x, y = self.params
|
|
self.definition = QuantumCircuit(1)
|
|
self.definition.p(x + y, 0)
|
|
|
|
|
|
@ddt
|
|
class TestNLocal(QiskitTestCase):
|
|
"""Test the n-local circuit class."""
|
|
|
|
def test_if_reps_is_negative(self):
|
|
"""Test to check if error is raised for negative value of reps"""
|
|
with self.assertRaises(ValueError):
|
|
_ = NLocal(reps=-1)
|
|
|
|
def test_if_reps_is_str(self):
|
|
"""Test to check if proper error is raised for str value of reps"""
|
|
with self.assertRaises(TypeError):
|
|
_ = NLocal(reps="3")
|
|
|
|
def test_if_reps_is_float(self):
|
|
"""Test to check if proper error is raised for float value of reps"""
|
|
with self.assertRaises(TypeError):
|
|
_ = NLocal(reps=5.6)
|
|
|
|
def test_if_reps_is_npint32(self):
|
|
"""Equality test for reps with int value and np.int32 value"""
|
|
self.assertEqual(NLocal(reps=3), NLocal(reps=np.int32(3)))
|
|
|
|
def test_if_reps_is_npint64(self):
|
|
"""Equality test for reps with int value and np.int64 value"""
|
|
self.assertEqual(NLocal(reps=3), NLocal(reps=np.int64(3)))
|
|
|
|
def test_reps_setter_when_negative(self):
|
|
"""Test to check if setter raises error for reps < 0"""
|
|
nlocal = NLocal(reps=1)
|
|
with self.assertRaises(ValueError):
|
|
nlocal.reps = -1
|
|
|
|
def assertCircuitEqual(self, qc1, qc2, visual=False, transpiled=True):
|
|
"""An equality test specialized to circuits."""
|
|
if transpiled:
|
|
basis_gates = ["id", "u1", "u3", "cx"]
|
|
qc1_transpiled = transpile(qc1, basis_gates=basis_gates, optimization_level=0)
|
|
qc2_transpiled = transpile(qc2, basis_gates=basis_gates, optimization_level=0)
|
|
qc1, qc2 = qc1_transpiled, qc2_transpiled
|
|
|
|
if visual:
|
|
self.assertEqual(qc1.draw(), qc2.draw())
|
|
else:
|
|
self.assertEqual(qc1, qc2)
|
|
|
|
def test_empty_nlocal(self):
|
|
"""Test the creation of an empty NLocal."""
|
|
nlocal = NLocal()
|
|
self.assertEqual(nlocal.num_qubits, 0)
|
|
self.assertEqual(nlocal.num_parameters_settable, 0)
|
|
self.assertEqual(nlocal.reps, 1)
|
|
|
|
self.assertEqual(nlocal, QuantumCircuit())
|
|
|
|
for attribute in [nlocal.rotation_blocks, nlocal.entanglement_blocks]:
|
|
self.assertEqual(len(attribute), 0)
|
|
|
|
@data(
|
|
(XGate(), [[0], [2], [1]]),
|
|
(XGate(), [[0]]),
|
|
(CRXGate(-0.2), [[2, 0], [1, 3]]),
|
|
)
|
|
@unpack
|
|
def test_add_layer_to_empty_nlocal(self, block, entangler_map):
|
|
"""Test appending gates to an empty nlocal."""
|
|
nlocal = NLocal()
|
|
nlocal.add_layer(block, entangler_map)
|
|
|
|
max_num_qubits = max(max(indices) for indices in entangler_map)
|
|
reference = QuantumCircuit(max_num_qubits + 1)
|
|
for indices in entangler_map:
|
|
reference.append(block, indices)
|
|
|
|
self.assertCircuitEqual(nlocal, reference)
|
|
|
|
@data([5, 3], [1, 5], [1, 1], [1, 2, 3, 10])
|
|
def test_append_circuit(self, num_qubits):
|
|
"""Test appending circuits to an nlocal works normally."""
|
|
# fixed depth of 3 gates per circuit
|
|
depth = 3
|
|
|
|
# keep track of a reference circuit
|
|
reference = QuantumCircuit(max(num_qubits))
|
|
|
|
# construct the NLocal from the first circuit
|
|
first_circuit = random_circuit(num_qubits[0], depth, seed=4200)
|
|
# TODO Terra bug: if this is to_gate it fails, since the QC adds an instruction not gate
|
|
nlocal = NLocal(max(num_qubits), entanglement_blocks=first_circuit.to_instruction(), reps=1)
|
|
reference.append(first_circuit, list(range(num_qubits[0])))
|
|
|
|
# append the rest
|
|
for num in num_qubits[1:]:
|
|
circuit = random_circuit(num, depth, seed=4200)
|
|
nlocal.append(circuit, list(range(num)))
|
|
reference.append(circuit, list(range(num)))
|
|
|
|
self.assertCircuitEqual(nlocal, reference)
|
|
|
|
@data([5, 3], [1, 5], [1, 1], [1, 2, 3, 10])
|
|
def test_add_nlocal(self, num_qubits):
|
|
"""Test adding an nlocal to an nlocal (using add_layer)."""
|
|
# fixed depth of 3 gates per circuit
|
|
depth = 3
|
|
|
|
# keep track of a reference circuit
|
|
reference = QuantumCircuit(max(num_qubits))
|
|
|
|
# construct the NLocal from the first circuit
|
|
first_circuit = random_circuit(num_qubits[0], depth, seed=4220)
|
|
# TODO Terra bug: if this is to_gate it fails, since the QC adds an instruction not gate
|
|
nlocal = NLocal(max(num_qubits), entanglement_blocks=first_circuit.to_instruction(), reps=1)
|
|
nlocal2 = nlocal.copy()
|
|
_ = nlocal2.data
|
|
reference.append(first_circuit, list(range(num_qubits[0])))
|
|
|
|
# append the rest
|
|
for num in num_qubits[1:]:
|
|
circuit = random_circuit(num, depth, seed=4220)
|
|
layer = NLocal(num, entanglement_blocks=circuit, reps=1)
|
|
nlocal.add_layer(layer)
|
|
nlocal2.add_layer(layer)
|
|
reference.append(circuit, list(range(num)))
|
|
|
|
self.assertCircuitEqual(nlocal, reference)
|
|
self.assertCircuitEqual(nlocal2, reference)
|
|
|
|
@unittest.skip("Feature missing")
|
|
def test_iadd_overload(self):
|
|
"""Test the overloaded + operator."""
|
|
num_qubits, depth = 2, 2
|
|
|
|
# construct two circuits for adding
|
|
first_circuit = random_circuit(num_qubits, depth, seed=4242)
|
|
circuit = random_circuit(num_qubits, depth, seed=4242)
|
|
|
|
# get a reference
|
|
reference = first_circuit + circuit
|
|
|
|
# convert the object to be appended to different types
|
|
others = [circuit, circuit.to_instruction(), circuit.to_gate(), NLocal(circuit)]
|
|
|
|
# try adding each type
|
|
for other in others:
|
|
nlocal = NLocal(num_qubits, entanglement_blocks=first_circuit, reps=1)
|
|
nlocal += other
|
|
with self.subTest(msg=f"type: {type(other)}"):
|
|
self.assertCircuitEqual(nlocal, reference)
|
|
|
|
def test_parameter_getter_from_automatic_repetition(self):
|
|
"""Test getting and setting of the nlocal parameters."""
|
|
circuit = QuantumCircuit(2)
|
|
circuit.ry(Parameter("a"), 0)
|
|
circuit.crx(Parameter("b"), 0, 1)
|
|
|
|
# repeat circuit and check that parameters are duplicated
|
|
reps = 3
|
|
nlocal = NLocal(2, entanglement_blocks=circuit, reps=reps)
|
|
self.assertTrue(nlocal.num_parameters, 6)
|
|
self.assertTrue(len(nlocal.parameters), 6)
|
|
|
|
@data(list(range(6)), ParameterVector("θ", length=6), [0, 1, Parameter("theta"), 3, 4, 5])
|
|
def test_parameter_setter_from_automatic_repetition(self, params):
|
|
"""Test getting and setting of the nlocal parameters."""
|
|
circuit = QuantumCircuit(2)
|
|
circuit.ry(Parameter("a"), 0)
|
|
circuit.crx(Parameter("b"), 0, 1)
|
|
|
|
# repeat circuit and check that parameters are duplicated
|
|
reps = 3
|
|
nlocal = NLocal(2, entanglement_blocks=circuit, reps=reps)
|
|
nlocal.assign_parameters(params, inplace=True)
|
|
|
|
param_set = {p for p in params if isinstance(p, ParameterExpression)}
|
|
with self.subTest(msg="Test the parameters of the non-transpiled circuit"):
|
|
# check the parameters of the final circuit
|
|
self.assertEqual(nlocal.parameters, param_set)
|
|
|
|
with self.subTest(msg="Test the parameters of the transpiled circuit"):
|
|
basis_gates = ["id", "u1", "u2", "u3", "cx"]
|
|
transpiled_circuit = transpile(nlocal, basis_gates=basis_gates)
|
|
self.assertEqual(transpiled_circuit.parameters, param_set)
|
|
|
|
@data(list(range(6)), ParameterVector("θ", length=6), [0, 1, Parameter("theta"), 3, 4, 5])
|
|
def test_parameters_setter(self, params):
|
|
"""Test setting the parameters via list."""
|
|
# construct circuit with some parameters
|
|
initial_params = ParameterVector("p", length=6)
|
|
circuit = QuantumCircuit(1)
|
|
for i, initial_param in enumerate(initial_params):
|
|
circuit.ry(i * initial_param, 0)
|
|
|
|
# create an NLocal from the circuit and set the new parameters
|
|
nlocal = NLocal(1, entanglement_blocks=circuit, reps=1)
|
|
nlocal.assign_parameters(params, inplace=True)
|
|
|
|
param_set = {p for p in params if isinstance(p, ParameterExpression)}
|
|
with self.subTest(msg="Test the parameters of the non-transpiled circuit"):
|
|
# check the parameters of the final circuit
|
|
self.assertEqual(nlocal.parameters, param_set)
|
|
|
|
with self.subTest(msg="Test the parameters of the transpiled circuit"):
|
|
basis_gates = ["id", "u1", "u2", "u3", "cx"]
|
|
transpiled_circuit = transpile(nlocal, basis_gates=basis_gates)
|
|
self.assertEqual(transpiled_circuit.parameters, param_set)
|
|
|
|
def test_repetetive_parameter_setting(self):
|
|
"""Test alternate setting of parameters and circuit construction."""
|
|
x = Parameter("x")
|
|
circuit = QuantumCircuit(1)
|
|
circuit.rx(x, 0)
|
|
|
|
nlocal = NLocal(1, entanglement_blocks=circuit, reps=3, insert_barriers=True)
|
|
with self.subTest(msg="immediately after initialization"):
|
|
self.assertEqual(len(nlocal.parameters), 3)
|
|
|
|
with self.subTest(msg="after circuit construction"):
|
|
self.assertEqual(len(nlocal.parameters), 3)
|
|
|
|
q = Parameter("q")
|
|
nlocal.assign_parameters([x, q, q], inplace=True)
|
|
with self.subTest(msg="setting parameter to Parameter objects"):
|
|
self.assertEqual(nlocal.parameters, set({x, q}))
|
|
|
|
nlocal.assign_parameters([0, -1], inplace=True)
|
|
with self.subTest(msg="setting parameter to numbers"):
|
|
self.assertEqual(nlocal.parameters, set())
|
|
|
|
def test_skip_unentangled_qubits(self):
|
|
"""Test skipping the unentangled qubits."""
|
|
num_qubits = 6
|
|
entanglement_1 = [[0, 1, 3], [1, 3, 5], [0, 1, 5]]
|
|
skipped_1 = [2, 4]
|
|
|
|
entanglement_2 = [entanglement_1, [[0, 1, 2], [2, 3, 5]]]
|
|
skipped_2 = [4]
|
|
|
|
for entanglement, skipped in zip([entanglement_1, entanglement_2], [skipped_1, skipped_2]):
|
|
with self.subTest(entanglement=entanglement, skipped=skipped):
|
|
nlocal = NLocal(
|
|
num_qubits,
|
|
rotation_blocks=XGate(),
|
|
entanglement_blocks=CCXGate(),
|
|
entanglement=entanglement,
|
|
reps=3,
|
|
skip_unentangled_qubits=True,
|
|
)
|
|
decomposed = nlocal.decompose()
|
|
|
|
skipped_set = {decomposed.qubits[i] for i in skipped}
|
|
dag = circuit_to_dag(decomposed)
|
|
idle = set(dag.idle_wires())
|
|
self.assertEqual(skipped_set, idle)
|
|
|
|
@data(
|
|
"linear",
|
|
"full",
|
|
"circular",
|
|
"sca",
|
|
"reverse_linear",
|
|
["linear", "full"],
|
|
["reverse_linear", "full"],
|
|
["circular", "linear", "sca"],
|
|
)
|
|
def test_entanglement_by_str(self, entanglement):
|
|
"""Test setting the entanglement of the layers by str."""
|
|
reps = 3
|
|
nlocal = NLocal(
|
|
5,
|
|
rotation_blocks=XGate(),
|
|
entanglement_blocks=CCXGate(),
|
|
entanglement=entanglement,
|
|
reps=reps,
|
|
)
|
|
|
|
def get_expected_entangler_map(rep_num, mode):
|
|
if mode == "linear":
|
|
return [(0, 1, 2), (1, 2, 3), (2, 3, 4)]
|
|
elif mode == "reverse_linear":
|
|
return [(2, 3, 4), (1, 2, 3), (0, 1, 2)]
|
|
elif mode == "full":
|
|
return [
|
|
(0, 1, 2),
|
|
(0, 1, 3),
|
|
(0, 1, 4),
|
|
(0, 2, 3),
|
|
(0, 2, 4),
|
|
(0, 3, 4),
|
|
(1, 2, 3),
|
|
(1, 2, 4),
|
|
(1, 3, 4),
|
|
(2, 3, 4),
|
|
]
|
|
else:
|
|
circular = [(3, 4, 0), (4, 0, 1), (0, 1, 2), (1, 2, 3), (2, 3, 4)]
|
|
if mode == "circular":
|
|
return circular
|
|
sca = circular[-rep_num:] + circular[:-rep_num]
|
|
if rep_num % 2 == 1:
|
|
sca = [tuple(reversed(indices)) for indices in sca]
|
|
return sca
|
|
|
|
for rep_num in range(reps):
|
|
entangler_map = nlocal.get_entangler_map(rep_num, 0, 3)
|
|
if isinstance(entanglement, list):
|
|
mode = entanglement[rep_num % len(entanglement)]
|
|
else:
|
|
mode = entanglement
|
|
expected = get_expected_entangler_map(rep_num, mode)
|
|
|
|
with self.subTest(rep_num=rep_num):
|
|
# using a set here since the order does not matter
|
|
self.assertEqual(entangler_map, expected)
|
|
|
|
def test_pairwise_entanglement(self):
|
|
"""Test pairwise entanglement."""
|
|
nlocal = NLocal(
|
|
5,
|
|
rotation_blocks=XGate(),
|
|
entanglement_blocks=CXGate(),
|
|
entanglement="pairwise",
|
|
reps=1,
|
|
)
|
|
entangler_map = nlocal.get_entangler_map(0, 0, 2)
|
|
pairwise = [(0, 1), (2, 3), (1, 2), (3, 4)]
|
|
|
|
self.assertEqual(pairwise, entangler_map)
|
|
|
|
def test_pairwise_entanglement_raises(self):
|
|
"""Test choosing pairwise entanglement raises an error for too large blocks."""
|
|
nlocal = NLocal(3, XGate(), CCXGate(), entanglement="pairwise", reps=1)
|
|
|
|
# pairwise entanglement is only defined if the entangling gate has 2 qubits
|
|
with self.assertRaises(ValueError):
|
|
_ = str(nlocal.draw())
|
|
|
|
def test_entanglement_by_list(self):
|
|
"""Test setting the entanglement by list.
|
|
|
|
This is the circuit we test (times 2, with final X layer)
|
|
┌───┐ ┌───┐┌───┐ ┌───┐
|
|
q_0: |0>┤ X ├──■────■───X────┤ X ├┤ X ├──■───X─────── .. ┤ X ├
|
|
├───┤ │ │ │ ├───┤└─┬─┘ │ │ ├───┤
|
|
q_1: |0>┤ X ├──■────┼───┼──X─┤ X ├──■────┼───X──X──── .. ┤ X ├
|
|
├───┤┌─┴─┐ │ │ │ ├───┤ │ │ │ x2 ├───┤
|
|
q_2: |0>┤ X ├┤ X ├──■───┼──X─┤ X ├──■────■──────X──X─ .. ┤ X ├
|
|
├───┤└───┘┌─┴─┐ │ ├───┤ ┌─┴─┐ │ ├───┤
|
|
q_3: |0>┤ X ├─────┤ X ├─X────┤ X ├─────┤ X ├───────X─ .. ┤ X ├
|
|
└───┘ └───┘ └───┘ └───┘ └───┘
|
|
"""
|
|
circuit = QuantumCircuit(4)
|
|
for _ in range(2):
|
|
circuit.x([0, 1, 2, 3])
|
|
circuit.barrier()
|
|
circuit.ccx(0, 1, 2)
|
|
circuit.ccx(0, 2, 3)
|
|
circuit.swap(0, 3)
|
|
circuit.swap(1, 2)
|
|
circuit.barrier()
|
|
circuit.x([0, 1, 2, 3])
|
|
circuit.barrier()
|
|
circuit.ccx(2, 1, 0)
|
|
circuit.ccx(0, 2, 3)
|
|
circuit.swap(0, 1)
|
|
circuit.swap(1, 2)
|
|
circuit.swap(2, 3)
|
|
circuit.barrier()
|
|
circuit.x([0, 1, 2, 3])
|
|
|
|
layer_1_ccx = [(0, 1, 2), (0, 2, 3)]
|
|
layer_1_swap = [(0, 3), (1, 2)]
|
|
layer_1 = [layer_1_ccx, layer_1_swap]
|
|
|
|
layer_2_ccx = [(2, 1, 0), (0, 2, 3)]
|
|
layer_2_swap = [(0, 1), (1, 2), (2, 3)]
|
|
layer_2 = [layer_2_ccx, layer_2_swap]
|
|
|
|
entanglement = [layer_1, layer_2]
|
|
|
|
nlocal = NLocal(
|
|
4,
|
|
rotation_blocks=XGate(),
|
|
entanglement_blocks=[CCXGate(), SwapGate()],
|
|
reps=4,
|
|
entanglement=entanglement,
|
|
insert_barriers=True,
|
|
)
|
|
|
|
self.assertCircuitEqual(nlocal, circuit)
|
|
|
|
def test_initial_state_as_circuit_object(self):
|
|
"""Test setting `initial_state` to `QuantumCircuit` object"""
|
|
# ┌───┐ ┌───┐
|
|
# q_0: ──■──┤ X ├───────■──┤ X ├
|
|
# ┌─┴─┐├───┤┌───┐┌─┴─┐├───┤
|
|
# q_1: ┤ X ├┤ H ├┤ X ├┤ X ├┤ X ├
|
|
# └───┘└───┘└───┘└───┘└───┘
|
|
ref = QuantumCircuit(2)
|
|
ref.cx(0, 1)
|
|
ref.x(0)
|
|
ref.h(1)
|
|
ref.x(1)
|
|
ref.cx(0, 1)
|
|
ref.x(0)
|
|
ref.x(1)
|
|
|
|
qc = QuantumCircuit(2)
|
|
qc.cx(0, 1)
|
|
qc.h(1)
|
|
|
|
expected = NLocal(
|
|
num_qubits=2,
|
|
rotation_blocks=XGate(),
|
|
entanglement_blocks=CXGate(),
|
|
initial_state=qc,
|
|
reps=1,
|
|
)
|
|
|
|
self.assertCircuitEqual(ref, expected)
|
|
|
|
def test_inplace_assignment_with_cache(self):
|
|
"""Test parameters are correctly re-bound in the cached gates.
|
|
|
|
This test requires building with the Rust feature "cache_pygates" enabled, otherwise
|
|
it does not test what it is supposed to.
|
|
|
|
Regression test of #13478.
|
|
"""
|
|
qc = EfficientSU2(2, flatten=True)
|
|
binds = [1.25] * qc.num_parameters
|
|
|
|
qc.assign_parameters(binds, inplace=True)
|
|
bound_op = qc.data[0].operation
|
|
self.assertAlmostEqual(bound_op.params[0], binds[0])
|
|
|
|
|
|
@ddt
|
|
class TestNLocalFunction(QiskitTestCase):
|
|
"""Test the n_local circuit library function."""
|
|
|
|
def test_empty_blocks(self):
|
|
"""Test passing no rotation and entanglement blocks."""
|
|
circuit = n_local(2, rotation_blocks=[], entanglement_blocks=[])
|
|
expected = QuantumCircuit(2)
|
|
|
|
self.assertEqual(expected, circuit)
|
|
|
|
def test_invalid_custom_block(self):
|
|
"""Test constructing a block from callable but not with a callable."""
|
|
my_block = QuantumCircuit(2)
|
|
with self.assertRaises(QiskitError):
|
|
_ = Block.from_callable(2, 0, my_block)
|
|
|
|
def test_str_blocks(self):
|
|
"""Test passing blocks as strings."""
|
|
circuit = n_local(2, "h", "ecr", reps=2)
|
|
expected = QuantumCircuit(2)
|
|
for _ in range(2):
|
|
expected.h([0, 1])
|
|
expected.ecr(0, 1)
|
|
expected.h([0, 1])
|
|
|
|
self.assertEqual(expected, circuit)
|
|
|
|
def test_stdgate_blocks(self):
|
|
"""Test passing blocks as standard gates."""
|
|
circuit = n_local(2, HGate(), CRXGate(Parameter("x")), reps=2)
|
|
|
|
param_iter = iter(circuit.parameters)
|
|
expected = QuantumCircuit(2)
|
|
for _ in range(2):
|
|
expected.h([0, 1])
|
|
expected.crx(next(param_iter), 0, 1)
|
|
expected.h([0, 1])
|
|
|
|
self.assertEqual(expected, circuit)
|
|
|
|
def test_invalid_str_blocks(self):
|
|
"""Test passing blocks as invalid string raises."""
|
|
with self.assertRaises(ValueError):
|
|
_ = n_local(2, "h", "iamnotanexisting2qgateeventhoughiwanttobe")
|
|
|
|
def test_gate_blocks(self):
|
|
"""Test passing blocks as gates."""
|
|
x = ParameterVector("x", 2)
|
|
my_gate = Gato(*x)
|
|
|
|
circuit = n_local(4, my_gate, "cx", "linear", reps=3)
|
|
|
|
expected_cats = 4 * (3 + 1) # num_qubits * (reps + 1)
|
|
expected_cx = 3 * 3 # gates per block * reps
|
|
expected_num_params = expected_cats * 2
|
|
|
|
self.assertEqual(expected_cats, circuit.count_ops().get("meow", 0))
|
|
self.assertEqual(expected_cx, circuit.count_ops().get("cx", 0))
|
|
self.assertEqual(expected_num_params, circuit.num_parameters)
|
|
|
|
def test_gate_lists(self):
|
|
"""Test passing a list of strings and gates."""
|
|
reps = 2
|
|
circuit = n_local(4, [XGate(), "ry", SXGate()], ["ryy", CCXGate()], "full", reps)
|
|
expected_1q = 4 * (reps + 1) # num_qubits * (reps + 1)
|
|
expected_2q = 4 * 3 / 2 * reps # 4 choose 2 * reps
|
|
expected_3q = 4 * reps # 4 choose 3 * reps
|
|
|
|
ops = circuit.count_ops()
|
|
for gate in ["x", "ry", "sx"]:
|
|
with self.subTest(gate=gate):
|
|
self.assertEqual(expected_1q, ops.get(gate, 0))
|
|
|
|
with self.subTest(gate="ryy"):
|
|
self.assertEqual(expected_2q, ops.get("ryy", 0))
|
|
|
|
with self.subTest(gate="ccx"):
|
|
self.assertEqual(expected_3q, ops.get("ccx", 0))
|
|
|
|
def test_reps(self):
|
|
"""Test setting the repetitions."""
|
|
all_reps = [0, 1, 2, 10]
|
|
for reps in all_reps:
|
|
circuit = n_local(2, rotation_blocks="rx", entanglement_blocks="cz", reps=reps)
|
|
expected_rx = (reps + 1) * 2
|
|
expected_cz = reps
|
|
|
|
with self.subTest(reps=reps):
|
|
self.assertEqual(expected_rx, circuit.count_ops().get("rx", 0))
|
|
self.assertEqual(expected_cz, circuit.count_ops().get("cz", 0))
|
|
|
|
def test_negative_reps(self):
|
|
"""Test negative reps raises."""
|
|
with self.assertRaises(ValueError):
|
|
_ = n_local(1, [], [], reps=-1)
|
|
|
|
def test_barrier(self):
|
|
"""Test setting barriers."""
|
|
circuit = n_local(2, "ry", "cx", reps=2, insert_barriers=True)
|
|
values = np.ones(circuit.num_parameters)
|
|
|
|
expected = QuantumCircuit(2)
|
|
expected.ry(1, [0, 1])
|
|
expected.barrier()
|
|
expected.cx(0, 1)
|
|
expected.barrier()
|
|
expected.ry(1, [0, 1])
|
|
expected.barrier()
|
|
expected.cx(0, 1)
|
|
expected.barrier()
|
|
expected.ry(1, [0, 1])
|
|
|
|
self.assertEqual(expected, circuit.assign_parameters(values))
|
|
|
|
def test_parameter_prefix(self):
|
|
"""Test setting the parameter prefix."""
|
|
circuit = n_local(2, "h", "crx", parameter_prefix="x")
|
|
prefixes = [p.name[0] for p in circuit.parameters]
|
|
self.assertTrue(all(prefix == "x" for prefix in prefixes))
|
|
|
|
@data(True, False)
|
|
def test_overwrite_block_parameters(self, overwrite):
|
|
"""Test overwriting the block parameters."""
|
|
x = Parameter("x")
|
|
block = QuantumCircuit(2)
|
|
block.rxx(x, 0, 1)
|
|
|
|
reps = 3
|
|
circuit = n_local(
|
|
4, [], [block.to_gate()], "linear", reps, overwrite_block_parameters=overwrite
|
|
)
|
|
|
|
expected_num_params = reps * 3 if overwrite else 1
|
|
self.assertEqual(expected_num_params, circuit.num_parameters)
|
|
|
|
@data(True, False)
|
|
def test_skip_final_rotation_layer(self, skip):
|
|
"""Test skipping the final rotation layer."""
|
|
reps = 5
|
|
num_qubits = 2
|
|
circuit = n_local(num_qubits, "rx", "ch", reps=reps, skip_final_rotation_layer=skip)
|
|
expected_rx = num_qubits * (reps + (0 if skip else 1))
|
|
|
|
self.assertEqual(expected_rx, circuit.count_ops().get("rx", 0))
|
|
|
|
def test_skip_unentangled_qubits(self):
|
|
"""Test skipping the unentangled qubits."""
|
|
num_qubits = 6
|
|
entanglement_1 = [[0, 1, 3], [1, 3, 5], [0, 1, 5]]
|
|
skipped_1 = [2, 4]
|
|
|
|
def entanglement_2(layer):
|
|
return entanglement_1 if layer % 2 == 0 else [[0, 1, 2], [2, 3, 5]]
|
|
|
|
skipped_2 = [4]
|
|
|
|
for entanglement, skipped in zip([entanglement_1, entanglement_2], [skipped_1, skipped_2]):
|
|
with self.subTest(entanglement=entanglement, skipped=skipped):
|
|
nlocal = n_local(
|
|
num_qubits,
|
|
rotation_blocks=XGate(),
|
|
entanglement_blocks=CCXGate(),
|
|
entanglement=entanglement,
|
|
reps=3,
|
|
skip_unentangled_qubits=True,
|
|
)
|
|
|
|
skipped_set = {nlocal.qubits[i] for i in skipped}
|
|
dag = circuit_to_dag(nlocal)
|
|
idle = set(dag.idle_wires())
|
|
self.assertEqual(skipped_set, idle)
|
|
|
|
def test_empty_entanglement(self):
|
|
"""Test passing an empty list as entanglement."""
|
|
circuit = n_local(3, "h", "cx", entanglement=[], reps=1)
|
|
self.assertEqual(6, circuit.count_ops().get("h", 0))
|
|
self.assertEqual(0, circuit.count_ops().get("cx", 0))
|
|
|
|
def test_entanglement_list_of_str(self):
|
|
"""Test different entanglement strings per entanglement block."""
|
|
circuit = n_local(3, [], ["cx", "cz"], entanglement=["reverse_linear", "full"], reps=1)
|
|
self.assertEqual(2, circuit.count_ops().get("cx", 0))
|
|
self.assertEqual(3, circuit.count_ops().get("cz", 0))
|
|
|
|
def test_invalid_entanglement_list(self):
|
|
"""Test passing an invalid list."""
|
|
with self.assertRaises(TypeError):
|
|
_ = n_local(3, "h", "cx", entanglement=[0, 1]) # should be [(0, 1)]
|
|
|
|
def test_mismatching_entanglement_blocks_str(self):
|
|
"""Test an error is raised if the number of entanglements does not match the blocks."""
|
|
entanglement = ["full", "linear", "pairwise"]
|
|
blocks = ["ryy", "iswap"]
|
|
|
|
with self.assertRaises(QiskitError):
|
|
_ = n_local(3, [], blocks, entanglement=entanglement)
|
|
|
|
def test_mismatching_entanglement_blocks_indices(self):
|
|
"""Test an error is raised if the number of entanglements does not match the blocks."""
|
|
ent1 = [(0, 1), (1, 2)]
|
|
ent2 = [(0, 2)]
|
|
blocks = ["ryy", "iswap"]
|
|
|
|
with self.assertRaises(QiskitError):
|
|
_ = n_local(3, [], blocks, entanglement=[ent1, ent1, ent2])
|
|
|
|
def test_mismatching_entanglement_indices(self):
|
|
"""Test an error is raised if the entanglement does not match the blocksize."""
|
|
entanglement = [[0, 1], [2]]
|
|
|
|
with self.assertRaises(QiskitError):
|
|
_ = n_local(3, "ry", "cx", entanglement)
|
|
|
|
def test_entanglement_by_callable(self):
|
|
"""Test setting the entanglement by callable.
|
|
|
|
This is the circuit we test (times 2, with final X layer)
|
|
┌───┐ ┌───┐┌───┐ ┌───┐
|
|
q_0: |0>┤ X ├──■────■───┤ X ├┤ X ├──■─── .. ┤ X ├
|
|
├───┤ │ │ ├───┤└─┬─┘ │ ├───┤
|
|
q_1: |0>┤ X ├──■────┼───┤ X ├──■────┼─── .. ┤ X ├
|
|
├───┤┌─┴─┐ │ ├───┤ │ │ x2 ├───┤
|
|
q_2: |0>┤ X ├┤ X ├──■───┤ X ├──■────■─── .. ┤ X ├
|
|
├───┤└───┘┌─┴─┐ ├───┤ ┌─┴─┐ ├───┤
|
|
q_3: |0>┤ X ├─────┤ X ├─┤ X ├─────┤ X ├─ .. ┤ X ├
|
|
└───┘ └───┘ └───┘ └───┘ └───┘
|
|
"""
|
|
circuit = QuantumCircuit(4)
|
|
for _ in range(2):
|
|
circuit.x([0, 1, 2, 3])
|
|
circuit.barrier()
|
|
circuit.ccx(0, 1, 2)
|
|
circuit.ccx(0, 2, 3)
|
|
circuit.barrier()
|
|
circuit.x([0, 1, 2, 3])
|
|
circuit.barrier()
|
|
circuit.ccx(2, 1, 0)
|
|
circuit.ccx(0, 2, 3)
|
|
circuit.barrier()
|
|
circuit.x([0, 1, 2, 3])
|
|
|
|
layer_1 = [(0, 1, 2), (0, 2, 3)]
|
|
layer_2 = [(2, 1, 0), (0, 2, 3)]
|
|
|
|
entanglement = lambda offset: layer_1 if offset % 2 == 0 else layer_2
|
|
|
|
nlocal = QuantumCircuit(4)
|
|
nlocal.compose(
|
|
n_local(
|
|
4,
|
|
rotation_blocks=XGate(),
|
|
entanglement_blocks=CCXGate(),
|
|
reps=4,
|
|
entanglement=entanglement,
|
|
insert_barriers=True,
|
|
),
|
|
inplace=True,
|
|
)
|
|
|
|
self.assertEqual(nlocal, circuit)
|
|
|
|
def test_nice_error_if_circuit_passed(self):
|
|
"""Check the transition-helper error."""
|
|
block = QuantumCircuit(1)
|
|
|
|
with self.assertRaisesRegex(ValueError, "but you passed a QuantumCircuit"):
|
|
_ = n_local(3, block, "cz")
|
|
|
|
|
|
@ddt
|
|
class TestNLocalFamily(QiskitTestCase):
|
|
"""Test the derived circuit functions."""
|
|
|
|
def test_real_amplitudes(self):
|
|
"""Test the real amplitudes circuit."""
|
|
circuit = real_amplitudes(4)
|
|
expected = n_local(4, "ry", "cx", "reverse_linear", reps=3)
|
|
self.assertEqual(expected.assign_parameters(circuit.parameters), circuit)
|
|
|
|
def test_real_amplitudes_numqubits_equal1(self):
|
|
"""Test the real amplitudes circuit for a single qubit."""
|
|
circuit = real_amplitudes(1)
|
|
expected = n_local(1, "ry", [])
|
|
self.assertEqual(expected.assign_parameters(circuit.parameters), circuit)
|
|
|
|
def test_efficient_su2(self):
|
|
"""Test the efficient SU(2) circuit."""
|
|
circuit = efficient_su2(4)
|
|
expected = n_local(4, ["ry", "rz"], "cx", "reverse_linear", reps=3)
|
|
self.assertEqual(expected.assign_parameters(circuit.parameters), circuit)
|
|
|
|
def test_efficient_su2_numqubits_equal1(self):
|
|
"""Test the efficient SU(2) circuit for a single qubit."""
|
|
circuit = efficient_su2(1)
|
|
expected = n_local(1, ["ry", "rz"], [])
|
|
self.assertEqual(expected.assign_parameters(circuit.parameters), circuit)
|
|
|
|
@data("fsim", "iswap")
|
|
def test_excitation_preserving(self, mode):
|
|
"""Test the excitation preserving circuit."""
|
|
circuit = excitation_preserving(4, mode=mode)
|
|
|
|
x = Parameter("x")
|
|
block = QuantumCircuit(2)
|
|
block.rxx(x, 0, 1)
|
|
block.ryy(x, 0, 1)
|
|
if mode == "fsim":
|
|
y = Parameter("y")
|
|
block.cp(y, 0, 1)
|
|
|
|
expected = n_local(4, "rz", block.to_gate(), "full", reps=3)
|
|
self.assertEqual(
|
|
expected.assign_parameters(circuit.parameters).decompose(), circuit.decompose()
|
|
)
|
|
|
|
@data("fsim", "iswap")
|
|
def test_excitation_preserving_numqubits_equal1(self, mode):
|
|
"""Test the excitation preserving circuit for a single qubit."""
|
|
circuit = excitation_preserving(1, mode=mode)
|
|
expected = n_local(1, "rz", [])
|
|
self.assertEqual(
|
|
expected.assign_parameters(circuit.parameters).decompose(), circuit.decompose()
|
|
)
|
|
|
|
def test_excitation_preserving_invalid_mode(self):
|
|
"""Test an error is raised for an invalid mode."""
|
|
with self.assertRaises(ValueError):
|
|
_ = excitation_preserving(2, mode="Fsim")
|
|
|
|
with self.assertRaises(ValueError):
|
|
_ = excitation_preserving(2, mode="swaip")
|
|
|
|
def test_two_design(self):
|
|
"""Test the Pauli 2-design circuit."""
|
|
circuit = pauli_two_design(3)
|
|
expected_ops = {"rx", "ry", "rz", "cz"}
|
|
circuit_ops = set(circuit.count_ops().keys())
|
|
|
|
self.assertTrue(circuit_ops.issubset(expected_ops))
|
|
|
|
def test_two_design_numqubits_equal1(self):
|
|
"""Test the Pauli 2-design circuit for a single qubit."""
|
|
circuit = pauli_two_design(1)
|
|
expected_ops = {"rx", "ry", "rz", "id"}
|
|
circuit_ops = set(circuit.count_ops().keys())
|
|
|
|
self.assertTrue(circuit_ops.issubset(expected_ops))
|
|
|
|
def test_two_design_seed(self):
|
|
"""Test the seed"""
|
|
seed1 = 123
|
|
seed2 = 321
|
|
|
|
with self.subTest(msg="same circuit with same seed"):
|
|
first = pauli_two_design(3, seed=seed1)
|
|
second = pauli_two_design(3, seed=seed1)
|
|
|
|
self.assertEqual(first.assign_parameters(second.parameters), second)
|
|
|
|
with self.subTest(msg="different circuit with different seed"):
|
|
first = pauli_two_design(3, seed=seed1)
|
|
second = pauli_two_design(3, seed=seed2)
|
|
|
|
self.assertNotEqual(first.assign_parameters(second.parameters), second)
|
|
|
|
|
|
@ddt
|
|
class TestTwoLocal(QiskitTestCase):
|
|
"""Tests for the TwoLocal circuit."""
|
|
|
|
def assertCircuitEqual(self, qc1, qc2, visual=False, transpiled=True):
|
|
"""An equality test specialized to circuits."""
|
|
if transpiled:
|
|
basis_gates = ["id", "u1", "u3", "cx"]
|
|
qc1_transpiled = transpile(qc1, basis_gates=basis_gates, optimization_level=0)
|
|
qc2_transpiled = transpile(qc2, basis_gates=basis_gates, optimization_level=0)
|
|
qc1, qc2 = qc1_transpiled, qc2_transpiled
|
|
|
|
if visual:
|
|
self.assertEqual(qc1.draw(), qc2.draw())
|
|
else:
|
|
self.assertEqual(qc1, qc2)
|
|
|
|
def test_skip_final_rotation_layer(self):
|
|
"""Test skipping the final rotation layer works."""
|
|
two = TwoLocal(3, ["ry", "h"], ["cz", "cx"], reps=2, skip_final_rotation_layer=True)
|
|
self.assertEqual(two.num_parameters, 6) # would be 9 with a final rotation layer
|
|
|
|
@data(
|
|
(5, "rx", "cx", "full", 2, 15),
|
|
(3, "x", "z", "linear", 1, 0),
|
|
(3, "rx", "cz", "linear", 0, 3),
|
|
(3, ["rx", "ry"], ["cry", "cx"], "circular", 2, 24),
|
|
)
|
|
@unpack
|
|
def test_num_parameters(self, num_qubits, rot, ent, ent_mode, reps, expected):
|
|
"""Test the number of parameters."""
|
|
two = TwoLocal(
|
|
num_qubits,
|
|
rotation_blocks=rot,
|
|
entanglement_blocks=ent,
|
|
entanglement=ent_mode,
|
|
reps=reps,
|
|
)
|
|
|
|
with self.subTest(msg="num_parameters_settable"):
|
|
self.assertEqual(two.num_parameters_settable, expected)
|
|
|
|
with self.subTest(msg="num_parameters"):
|
|
self.assertEqual(two.num_parameters, expected)
|
|
|
|
def test_empty_two_local(self):
|
|
"""Test the setup of an empty two-local circuit."""
|
|
two = TwoLocal()
|
|
|
|
with self.subTest(msg="0 qubits"):
|
|
self.assertEqual(two.num_qubits, 0)
|
|
|
|
with self.subTest(msg="no blocks are set"):
|
|
self.assertListEqual(two.rotation_blocks, [])
|
|
self.assertListEqual(two.entanglement_blocks, [])
|
|
|
|
with self.subTest(msg="equal to empty circuit"):
|
|
self.assertEqual(two, QuantumCircuit())
|
|
|
|
@data("rx", RXGate(Parameter("p")), RXGate, "circuit")
|
|
def test_various_block_types(self, rot):
|
|
"""Test setting the rotation blocks to various type and assert the output type is RX."""
|
|
if rot == "circuit":
|
|
rot = QuantumCircuit(1)
|
|
rot.rx(Parameter("angle"), 0)
|
|
|
|
two = TwoLocal(3, rot, reps=0)
|
|
self.assertEqual(len(two.rotation_blocks), 1)
|
|
rotation = two.rotation_blocks[0]
|
|
|
|
# decompose
|
|
self.assertIsInstance(rotation.data[0].operation, RXGate)
|
|
|
|
def test_parameter_setters(self):
|
|
"""Test different possibilities to set parameters."""
|
|
two = TwoLocal(3, rotation_blocks="rx", entanglement="cz", reps=2)
|
|
params = [0, 1, 2, Parameter("x"), Parameter("y"), Parameter("z"), 6, 7, 0]
|
|
params_set = {param for param in params if isinstance(param, Parameter)}
|
|
|
|
with self.subTest(msg="dict assign and copy"):
|
|
ordered = two.ordered_parameters
|
|
bound = two.assign_parameters(dict(zip(ordered, params)), inplace=False)
|
|
self.assertEqual(bound.parameters, params_set)
|
|
self.assertEqual(two.num_parameters, 9)
|
|
|
|
with self.subTest(msg="list assign and copy"):
|
|
ordered = two.ordered_parameters
|
|
bound = two.assign_parameters(params, inplace=False)
|
|
self.assertEqual(bound.parameters, params_set)
|
|
self.assertEqual(two.num_parameters, 9)
|
|
|
|
with self.subTest(msg="list assign inplace"):
|
|
ordered = two.ordered_parameters
|
|
two.assign_parameters(params, inplace=True)
|
|
self.assertEqual(two.parameters, params_set)
|
|
self.assertEqual(two.num_parameters, 3)
|
|
self.assertEqual(two.num_parameters_settable, 9)
|
|
|
|
def test_parameters_settable_is_constant(self):
|
|
"""Test the attribute num_parameters_settable does not change on parameter change."""
|
|
two = TwoLocal(3, rotation_blocks="rx", entanglement="cz", reps=2)
|
|
ordered_params = two.ordered_parameters
|
|
|
|
x = Parameter("x")
|
|
two.assign_parameters(dict(zip(ordered_params, [x] * two.num_parameters)), inplace=True)
|
|
|
|
with self.subTest(msg="num_parameters collapsed to 1"):
|
|
self.assertEqual(two.num_parameters, 1)
|
|
|
|
with self.subTest(msg="num_parameters_settable remained constant"):
|
|
self.assertEqual(two.num_parameters_settable, len(ordered_params))
|
|
|
|
def test_compose_inplace_to_circuit(self):
|
|
"""Test adding a two-local to an existing circuit."""
|
|
two = TwoLocal(3, ["ry", "rz"], "cz", "full", reps=1, insert_barriers=True)
|
|
circuit = QuantumCircuit(3)
|
|
circuit.compose(two, inplace=True)
|
|
|
|
# ┌──────────┐┌──────────┐ ░ ░ ┌──────────┐ ┌──────────┐
|
|
# q_0: ┤ Ry(θ[0]) ├┤ Rz(θ[3]) ├─░──■──■─────░─┤ Ry(θ[6]) ├─┤ Rz(θ[9]) ├
|
|
# ├──────────┤├──────────┤ ░ │ │ ░ ├──────────┤┌┴──────────┤
|
|
# q_1: ┤ Ry(θ[1]) ├┤ Rz(θ[4]) ├─░──■──┼──■──░─┤ Ry(θ[7]) ├┤ Rz(θ[10]) ├
|
|
# ├──────────┤├──────────┤ ░ │ │ ░ ├──────────┤├───────────┤
|
|
# q_2: ┤ Ry(θ[2]) ├┤ Rz(θ[5]) ├─░─────■──■──░─┤ Ry(θ[8]) ├┤ Rz(θ[11]) ├
|
|
# └──────────┘└──────────┘ ░ ░ └──────────┘└───────────┘
|
|
reference = QuantumCircuit(3)
|
|
param_iter = iter(two.ordered_parameters)
|
|
for i in range(3):
|
|
reference.ry(next(param_iter), i)
|
|
for i in range(3):
|
|
reference.rz(next(param_iter), i)
|
|
reference.barrier()
|
|
reference.cz(0, 1)
|
|
reference.cz(0, 2)
|
|
reference.cz(1, 2)
|
|
reference.barrier()
|
|
for i in range(3):
|
|
reference.ry(next(param_iter), i)
|
|
for i in range(3):
|
|
reference.rz(next(param_iter), i)
|
|
|
|
self.assertCircuitEqual(circuit.decompose(), reference)
|
|
|
|
def test_composing_two(self):
|
|
"""Test adding two two-local circuits."""
|
|
entangler_map = [[0, 3], [0, 2]]
|
|
two = TwoLocal(4, [], "cry", entangler_map, reps=1)
|
|
circuit = two.compose(two)
|
|
|
|
reference = QuantumCircuit(4)
|
|
params = two.ordered_parameters
|
|
for _ in range(2):
|
|
reference.cry(params[0], 0, 3)
|
|
reference.cry(params[1], 0, 2)
|
|
|
|
self.assertCircuitEqual(reference, circuit)
|
|
|
|
def test_ry_blocks(self):
|
|
"""Test that the RealAmplitudes circuit is instantiated correctly."""
|
|
two = RealAmplitudes(4)
|
|
with self.subTest(msg="test rotation gate"):
|
|
self.assertEqual(len(two.rotation_blocks), 1)
|
|
self.assertIsInstance(two.rotation_blocks[0].data[0].operation, RYGate)
|
|
|
|
with self.subTest(msg="test parameter bounds"):
|
|
expected = [(-np.pi, np.pi)] * two.num_parameters
|
|
np.testing.assert_almost_equal(two.parameter_bounds, expected)
|
|
|
|
def test_ry_circuit_reverse_linear(self):
|
|
"""Test a RealAmplitudes circuit with entanglement = "reverse_linear"."""
|
|
num_qubits = 3
|
|
reps = 2
|
|
entanglement = "reverse_linear"
|
|
parameters = ParameterVector("theta", num_qubits * (reps + 1))
|
|
param_iter = iter(parameters)
|
|
|
|
expected = QuantumCircuit(3)
|
|
for _ in range(reps):
|
|
for i in range(num_qubits):
|
|
expected.ry(next(param_iter), i)
|
|
expected.cx(1, 2)
|
|
expected.cx(0, 1)
|
|
for i in range(num_qubits):
|
|
expected.ry(next(param_iter), i)
|
|
|
|
library = RealAmplitudes(
|
|
num_qubits, reps=reps, entanglement=entanglement
|
|
).assign_parameters(parameters)
|
|
self.assertCircuitEqual(library, expected)
|
|
|
|
def test_ry_circuit_full(self):
|
|
"""Test a RealAmplitudes circuit with entanglement = "full"."""
|
|
num_qubits = 3
|
|
reps = 2
|
|
entanglement = "full"
|
|
parameters = ParameterVector("theta", num_qubits * (reps + 1))
|
|
param_iter = iter(parameters)
|
|
|
|
# ┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
# q_0: ┤ Ry(θ[0]) ├──■────■──┤ Ry(θ[3]) ├──────────────■────■──┤ Ry(θ[6]) ├────────────
|
|
# ├──────────┤┌─┴─┐ │ └──────────┘┌──────────┐┌─┴─┐ │ └──────────┘┌──────────┐
|
|
# q_1: ┤ Ry(θ[1]) ├┤ X ├──┼─────────■────┤ Ry(θ[4]) ├┤ X ├──┼─────────■────┤ Ry(θ[7]) ├
|
|
# ├──────────┤└───┘┌─┴─┐ ┌─┴─┐ ├──────────┤└───┘┌─┴─┐ ┌─┴─┐ ├──────────┤
|
|
# q_2: ┤ Ry(θ[2]) ├─────┤ X ├─────┤ X ├──┤ Ry(θ[5]) ├─────┤ X ├─────┤ X ├──┤ Ry(θ[8]) ├
|
|
# └──────────┘ └───┘ └───┘ └──────────┘ └───┘ └───┘ └──────────┘
|
|
expected = QuantumCircuit(3)
|
|
for _ in range(reps):
|
|
for i in range(num_qubits):
|
|
expected.ry(next(param_iter), i)
|
|
expected.cx(0, 1)
|
|
expected.cx(0, 2)
|
|
expected.cx(1, 2)
|
|
for i in range(num_qubits):
|
|
expected.ry(next(param_iter), i)
|
|
|
|
library = RealAmplitudes(
|
|
num_qubits, reps=reps, entanglement=entanglement
|
|
).assign_parameters(parameters)
|
|
self.assertCircuitEqual(library, expected)
|
|
|
|
def test_ryrz_blocks(self):
|
|
"""Test that the EfficientSU2 circuit is instantiated correctly."""
|
|
two = EfficientSU2(3)
|
|
with self.subTest(msg="test rotation gate"):
|
|
self.assertEqual(len(two.rotation_blocks), 2)
|
|
self.assertIsInstance(two.rotation_blocks[0].data[0].operation, RYGate)
|
|
self.assertIsInstance(two.rotation_blocks[1].data[0].operation, RZGate)
|
|
|
|
with self.subTest(msg="test parameter bounds"):
|
|
expected = [(-np.pi, np.pi)] * two.num_parameters
|
|
np.testing.assert_almost_equal(two.parameter_bounds, expected)
|
|
|
|
def test_rzsx_blocks(self):
|
|
"""Test that the EfficientSU2 circuit is instantiated correctly."""
|
|
two = EfficientSU2(3, ["rz", "sx", "rz", "sx", "rz"])
|
|
expected = [[RZGate], [SXGate], [RZGate], [SXGate], [RZGate]]
|
|
actual = [
|
|
[instruction.operation.base_class for instruction in block]
|
|
for block in two.rotation_blocks
|
|
]
|
|
self.assertEqual(actual, expected)
|
|
|
|
def test_ryrz_circuit(self):
|
|
"""Test an EfficientSU2 circuit."""
|
|
num_qubits = 3
|
|
reps = 2
|
|
entanglement = "circular"
|
|
parameters = ParameterVector("theta", 2 * num_qubits * (reps + 1))
|
|
param_iter = iter(parameters)
|
|
|
|
# ┌──────────┐┌──────────┐┌───┐ ┌──────────┐┌──────────┐ »
|
|
# q_0: ┤ Ry(θ[0]) ├┤ Rz(θ[3]) ├┤ X ├──■──┤ Ry(θ[6]) ├┤ Rz(θ[9]) ├─────────────»
|
|
# ├──────────┤├──────────┤└─┬─┘┌─┴─┐└──────────┘├──────────┤┌───────────┐»
|
|
# q_1: ┤ Ry(θ[1]) ├┤ Rz(θ[4]) ├──┼──┤ X ├─────■──────┤ Ry(θ[7]) ├┤ Rz(θ[10]) ├»
|
|
# ├──────────┤├──────────┤ │ └───┘ ┌─┴─┐ ├──────────┤├───────────┤»
|
|
# q_2: ┤ Ry(θ[2]) ├┤ Rz(θ[5]) ├──■──────────┤ X ├────┤ Ry(θ[8]) ├┤ Rz(θ[11]) ├»
|
|
# └──────────┘└──────────┘ └───┘ └──────────┘└───────────┘»
|
|
# « ┌───┐ ┌───────────┐┌───────────┐
|
|
# «q_0: ┤ X ├──■──┤ Ry(θ[12]) ├┤ Rz(θ[15]) ├─────────────
|
|
# « └─┬─┘┌─┴─┐└───────────┘├───────────┤┌───────────┐
|
|
# «q_1: ──┼──┤ X ├──────■──────┤ Ry(θ[13]) ├┤ Rz(θ[16]) ├
|
|
# « │ └───┘ ┌─┴─┐ ├───────────┤├───────────┤
|
|
# «q_2: ──■───────────┤ X ├────┤ Ry(θ[14]) ├┤ Rz(θ[17]) ├
|
|
# « └───┘ └───────────┘└───────────┘
|
|
expected = QuantumCircuit(3)
|
|
for _ in range(reps):
|
|
for i in range(num_qubits):
|
|
expected.ry(next(param_iter), i)
|
|
for i in range(num_qubits):
|
|
expected.rz(next(param_iter), i)
|
|
expected.cx(2, 0)
|
|
expected.cx(0, 1)
|
|
expected.cx(1, 2)
|
|
for i in range(num_qubits):
|
|
expected.ry(next(param_iter), i)
|
|
for i in range(num_qubits):
|
|
expected.rz(next(param_iter), i)
|
|
|
|
library = EfficientSU2(num_qubits, reps=reps, entanglement=entanglement).assign_parameters(
|
|
parameters
|
|
)
|
|
|
|
self.assertCircuitEqual(library, expected)
|
|
|
|
def test_swaprz_blocks(self):
|
|
"""Test that the ExcitationPreserving circuit is instantiated correctly."""
|
|
two = ExcitationPreserving(5)
|
|
with self.subTest(msg="test rotation gate"):
|
|
self.assertEqual(len(two.rotation_blocks), 1)
|
|
self.assertIsInstance(two.rotation_blocks[0].data[0].operation, RZGate)
|
|
|
|
with self.subTest(msg="test entanglement gate"):
|
|
self.assertEqual(len(two.entanglement_blocks), 1)
|
|
block = two.entanglement_blocks[0]
|
|
self.assertEqual(len(block.data), 2)
|
|
self.assertIsInstance(block.data[0].operation, RXXGate)
|
|
self.assertIsInstance(block.data[1].operation, RYYGate)
|
|
|
|
with self.subTest(msg="test parameter bounds"):
|
|
expected = [(-np.pi, np.pi)] * two.num_parameters
|
|
np.testing.assert_almost_equal(two.parameter_bounds, expected)
|
|
|
|
def test_swaprz_circuit(self):
|
|
"""Test a ExcitationPreserving circuit in iswap mode."""
|
|
num_qubits = 3
|
|
reps = 2
|
|
entanglement = "linear"
|
|
parameters = ParameterVector("theta", num_qubits * (reps + 1) + reps * (num_qubits - 1))
|
|
param_iter = iter(parameters)
|
|
|
|
# ┌──────────┐┌────────────┐┌────────────┐ ┌──────────┐ »
|
|
# q_0: ┤ Rz(θ[0]) ├┤0 ├┤0 ├─┤ Rz(θ[5]) ├───────────────»
|
|
# ├──────────┤│ Rxx(θ[3]) ││ Ryy(θ[3]) │┌┴──────────┴┐┌────────────┐»
|
|
# q_1: ┤ Rz(θ[1]) ├┤1 ├┤1 ├┤0 ├┤0 ├»
|
|
# ├──────────┤└────────────┘└────────────┘│ Rxx(θ[4]) ││ Ryy(θ[4]) │»
|
|
# q_2: ┤ Rz(θ[2]) ├────────────────────────────┤1 ├┤1 ├»
|
|
# └──────────┘ └────────────┘└────────────┘»
|
|
# « ┌────────────┐┌────────────┐┌───────────┐ »
|
|
# «q_0: ────────────┤0 ├┤0 ├┤ Rz(θ[10]) ├───────────────»
|
|
# « ┌──────────┐│ Rxx(θ[8]) ││ Ryy(θ[8]) │├───────────┴┐┌────────────┐»
|
|
# «q_1: ┤ Rz(θ[6]) ├┤1 ├┤1 ├┤0 ├┤0 ├»
|
|
# « ├──────────┤└────────────┘└────────────┘│ Rxx(θ[9]) ││ Ryy(θ[9]) │»
|
|
# «q_2: ┤ Rz(θ[7]) ├────────────────────────────┤1 ├┤1 ├»
|
|
# « └──────────┘ └────────────┘└────────────┘»
|
|
# «
|
|
# «q_0: ─────────────
|
|
# « ┌───────────┐
|
|
# «q_1: ┤ Rz(θ[11]) ├
|
|
# « ├───────────┤
|
|
# «q_2: ┤ Rz(θ[12]) ├
|
|
# « └───────────┘
|
|
expected = QuantumCircuit(3)
|
|
for _ in range(reps):
|
|
for i in range(num_qubits):
|
|
expected.rz(next(param_iter), i)
|
|
shared_param = next(param_iter)
|
|
expected.rxx(shared_param, 0, 1)
|
|
expected.ryy(shared_param, 0, 1)
|
|
shared_param = next(param_iter)
|
|
expected.rxx(shared_param, 1, 2)
|
|
expected.ryy(shared_param, 1, 2)
|
|
for i in range(num_qubits):
|
|
expected.rz(next(param_iter), i)
|
|
|
|
library = ExcitationPreserving(
|
|
num_qubits, reps=reps, entanglement=entanglement
|
|
).assign_parameters(parameters)
|
|
|
|
self.assertCircuitEqual(library, expected)
|
|
|
|
def test_fsim_circuit(self):
|
|
"""Test a ExcitationPreserving circuit in fsim mode."""
|
|
num_qubits = 3
|
|
reps = 2
|
|
entanglement = "linear"
|
|
# need the parameters in the entanglement blocks to be the same because the order
|
|
# can get mixed up in ExcitationPreserving (since parameters are not ordered in circuits)
|
|
parameters = [1] * (num_qubits * (reps + 1) + reps * (1 + num_qubits))
|
|
param_iter = iter(parameters)
|
|
|
|
# ┌───────┐┌─────────┐┌─────────┐ ┌───────┐ »
|
|
# q_0: ┤ Rz(1) ├┤0 ├┤0 ├─■──────┤ Rz(1) ├───────────────────»
|
|
# ├───────┤│ Rxx(1) ││ Ryy(1) │ │P(1) ┌┴───────┴┐┌─────────┐ »
|
|
# q_1: ┤ Rz(1) ├┤1 ├┤1 ├─■─────┤0 ├┤0 ├─■─────»
|
|
# ├───────┤└─────────┘└─────────┘ │ Rxx(1) ││ Ryy(1) │ │P(1) »
|
|
# q_2: ┤ Rz(1) ├─────────────────────────────┤1 ├┤1 ├─■─────»
|
|
# └───────┘ └─────────┘└─────────┘ »
|
|
# « ┌─────────┐┌─────────┐ ┌───────┐ »
|
|
# «q_0: ─────────┤0 ├┤0 ├─■──────┤ Rz(1) ├───────────────────»
|
|
# « ┌───────┐│ Rxx(1) ││ Ryy(1) │ │P(1) ┌┴───────┴┐┌─────────┐ »
|
|
# «q_1: ┤ Rz(1) ├┤1 ├┤1 ├─■─────┤0 ├┤0 ├─■─────»
|
|
# « ├───────┤└─────────┘└─────────┘ │ Rxx(1) ││ Ryy(1) │ │P(1) »
|
|
# «q_2: ┤ Rz(1) ├─────────────────────────────┤1 ├┤1 ├─■─────»
|
|
# « └───────┘ └─────────┘└─────────┘ »
|
|
# «
|
|
# «q_0: ─────────
|
|
# « ┌───────┐
|
|
# «q_1: ┤ Rz(1) ├
|
|
# « ├───────┤
|
|
# «q_2: ┤ Rz(1) ├
|
|
# « └───────┘
|
|
expected = QuantumCircuit(3)
|
|
for _ in range(reps):
|
|
for i in range(num_qubits):
|
|
expected.rz(next(param_iter), i)
|
|
shared_param = next(param_iter)
|
|
expected.rxx(shared_param, 0, 1)
|
|
expected.ryy(shared_param, 0, 1)
|
|
expected.cp(next(param_iter), 0, 1)
|
|
shared_param = next(param_iter)
|
|
expected.rxx(shared_param, 1, 2)
|
|
expected.ryy(shared_param, 1, 2)
|
|
expected.cp(next(param_iter), 1, 2)
|
|
for i in range(num_qubits):
|
|
expected.rz(next(param_iter), i)
|
|
|
|
library = ExcitationPreserving(
|
|
num_qubits, reps=reps, mode="fsim", entanglement=entanglement
|
|
).assign_parameters(parameters)
|
|
|
|
self.assertCircuitEqual(library, expected)
|
|
|
|
def test_excitation_preserving_invalid_mode(self):
|
|
"""Test an error is raised for an invalid mode."""
|
|
with self.assertRaises(ValueError):
|
|
_ = ExcitationPreserving(2, mode="Fsim")
|
|
|
|
with self.assertRaises(ValueError):
|
|
_ = ExcitationPreserving(2, mode="swaip")
|
|
|
|
def test_circular_on_same_block_and_circuit_size(self):
|
|
"""Test circular entanglement works correctly if the circuit and block sizes match."""
|
|
|
|
two = TwoLocal(2, "ry", "cx", entanglement="circular", reps=1)
|
|
parameters = np.arange(two.num_parameters)
|
|
|
|
# ┌───────┐ ┌───────┐
|
|
# q_0: ┤ Ry(0) ├──■──┤ Ry(2) ├
|
|
# ├───────┤┌─┴─┐├───────┤
|
|
# q_1: ┤ Ry(1) ├┤ X ├┤ Ry(3) ├
|
|
# └───────┘└───┘└───────┘
|
|
ref = QuantumCircuit(2)
|
|
ref.ry(parameters[0], 0)
|
|
ref.ry(parameters[1], 1)
|
|
ref.cx(0, 1)
|
|
ref.ry(parameters[2], 0)
|
|
ref.ry(parameters[3], 1)
|
|
|
|
self.assertCircuitEqual(two.assign_parameters(parameters), ref)
|
|
|
|
def test_circuit_with_numpy_integers(self):
|
|
"""Test if TwoLocal can be made from numpy integers"""
|
|
num_qubits = 6
|
|
reps = 3
|
|
expected_np32 = [
|
|
(i, j)
|
|
for i in np.arange(num_qubits, dtype=np.int32)
|
|
for j in np.arange(num_qubits, dtype=np.int32)
|
|
if i < j
|
|
]
|
|
expected_np64 = [
|
|
(i, j)
|
|
for i in np.arange(num_qubits, dtype=np.int64)
|
|
for j in np.arange(num_qubits, dtype=np.int64)
|
|
if i < j
|
|
]
|
|
|
|
two_np32 = TwoLocal(num_qubits, "ry", "cx", entanglement=expected_np32, reps=reps)
|
|
two_np64 = TwoLocal(num_qubits, "ry", "cx", entanglement=expected_np64, reps=reps)
|
|
|
|
expected_cx = reps * num_qubits * (num_qubits - 1) / 2
|
|
|
|
self.assertEqual(two_np32.decompose().count_ops()["cx"], expected_cx)
|
|
self.assertEqual(two_np64.decompose().count_ops()["cx"], expected_cx)
|
|
|
|
@combine(num_qubits=[4, 5])
|
|
def test_full_vs_reverse_linear(self, num_qubits):
|
|
"""Test that 'full' and 'reverse_linear' provide the same unitary element."""
|
|
reps = 2
|
|
full = RealAmplitudes(num_qubits=num_qubits, entanglement="full", reps=reps)
|
|
num_params = (reps + 1) * num_qubits
|
|
np.random.seed(num_qubits)
|
|
params = np.random.rand(num_params)
|
|
reverse = RealAmplitudes(num_qubits=num_qubits, entanglement="reverse_linear", reps=reps)
|
|
full.assign_parameters(params, inplace=True)
|
|
reverse.assign_parameters(params, inplace=True)
|
|
|
|
self.assertEqual(Operator(full), Operator(reverse))
|
|
|
|
|
|
@ddt
|
|
class TestEntanglement(QiskitTestCase):
|
|
"""Test getting the entanglement structure."""
|
|
|
|
@data(
|
|
("linear", [(0, 1), (1, 2), (2, 3)]),
|
|
("reverse_linear", [(2, 3), (1, 2), (0, 1)]),
|
|
("pairwise", [(0, 1), (2, 3), (1, 2)]),
|
|
("circular", [(3, 0), (0, 1), (1, 2), (2, 3)]),
|
|
("full", [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]),
|
|
)
|
|
@unpack
|
|
def test_2q_str(self, strategy, expected):
|
|
"""Test getting by string."""
|
|
entanglement = fast_entangler_map(
|
|
num_qubits=4, block_size=2, entanglement=strategy, offset=0
|
|
)
|
|
self.assertEqual(expected, entanglement)
|
|
|
|
@data(
|
|
("linear", [(0, 1, 2), (1, 2, 3)]),
|
|
("reverse_linear", [(1, 2, 3), (0, 1, 2)]),
|
|
("circular", [(2, 3, 0), (3, 0, 1), (0, 1, 2), (1, 2, 3)]),
|
|
("full", [(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)]),
|
|
)
|
|
@unpack
|
|
def test_3q_str(self, strategy, expected):
|
|
"""Test getting by string."""
|
|
entanglement = fast_entangler_map(
|
|
num_qubits=4, block_size=3, entanglement=strategy, offset=0
|
|
)
|
|
self.assertEqual(expected, entanglement)
|
|
|
|
def test_2q_sca(self):
|
|
"""Test shift, circular, alternating on 2-qubit blocks."""
|
|
expected = { # offset: result
|
|
0: [(3, 0), (0, 1), (1, 2), (2, 3)],
|
|
1: [(3, 2), (0, 3), (1, 0), (2, 1)],
|
|
2: [(1, 2), (2, 3), (3, 0), (0, 1)],
|
|
3: [(1, 0), (2, 1), (3, 2), (0, 3)],
|
|
}
|
|
for offset in range(8):
|
|
with self.subTest(offset=offset):
|
|
entanglement = fast_entangler_map(
|
|
num_qubits=4, block_size=2, entanglement="sca", offset=offset
|
|
)
|
|
self.assertEqual(expected[offset % 4], entanglement)
|
|
|
|
def test_3q_sca(self):
|
|
"""Test shift, circular, alternating on 3-qubit blocks."""
|
|
circular = [(2, 3, 0), (3, 0, 1), (0, 1, 2), (1, 2, 3)]
|
|
for offset in range(8):
|
|
expected = circular[-(offset % 4) :] + circular[: -(offset % 4)]
|
|
if offset % 2 == 1:
|
|
expected = [tuple(reversed(indices)) for indices in expected]
|
|
with self.subTest(offset=offset):
|
|
entanglement = fast_entangler_map(
|
|
num_qubits=4, block_size=3, entanglement="sca", offset=offset
|
|
)
|
|
self.assertEqual(expected, entanglement)
|
|
|
|
@data("full", "reverse_linear", "linear", "circular", "sca", "pairwise")
|
|
def test_0q(self, entanglement):
|
|
"""Test the corner case of a single qubit block."""
|
|
entanglement = fast_entangler_map(
|
|
num_qubits=3, block_size=0, entanglement=entanglement, offset=0
|
|
)
|
|
expect = []
|
|
self.assertEqual(entanglement, expect)
|
|
|
|
@data("full", "reverse_linear", "linear", "circular", "sca", "pairwise")
|
|
def test_1q(self, entanglement):
|
|
"""Test the corner case of a single qubit block."""
|
|
entanglement = fast_entangler_map(
|
|
num_qubits=3, block_size=1, entanglement=entanglement, offset=0
|
|
)
|
|
expect = [(i,) for i in range(3)]
|
|
|
|
self.assertEqual(set(entanglement), set(expect)) # order does not matter for 1 qubit
|
|
|
|
@data("full", "reverse_linear", "linear", "circular", "sca")
|
|
def test_full_block(self, entanglement):
|
|
"""Test the corner case of the block size equal the number of qubits."""
|
|
entanglement = fast_entangler_map(
|
|
num_qubits=5, block_size=5, entanglement=entanglement, offset=0
|
|
)
|
|
expect = [tuple(range(5))]
|
|
|
|
self.assertEqual(entanglement, expect)
|
|
|
|
def test_pairwise_limit(self):
|
|
"""Test pairwise raises an error above 2 qubits."""
|
|
_ = fast_entangler_map(num_qubits=4, block_size=1, entanglement="pairwise", offset=0)
|
|
_ = fast_entangler_map(num_qubits=4, block_size=2, entanglement="pairwise", offset=0)
|
|
with self.assertRaises(QiskitError):
|
|
_ = fast_entangler_map(num_qubits=4, block_size=3, entanglement="pairwise", offset=0)
|
|
|
|
def test_invalid_blocksize(self):
|
|
"""Test the block size being too large."""
|
|
with self.assertRaises(QiskitError):
|
|
_ = fast_entangler_map(num_qubits=2, block_size=3, entanglement="linear", offset=0)
|
|
|
|
def test_invalid_entanglement_str(self):
|
|
"""Test invalid entanglement string."""
|
|
with self.assertRaises(QiskitError):
|
|
_ = fast_entangler_map(num_qubits=4, block_size=2, entanglement="lniaer", offset=0)
|
|
|
|
def test_as_list(self):
|
|
"""Test passing a list just returns the list."""
|
|
expected = [(0, 1), (1, 10), (2, 10)]
|
|
out = fast_entangler_map(num_qubits=20, block_size=2, entanglement=expected, offset=0)
|
|
self.assertEqual(expected, out)
|
|
|
|
def test_invalid_list(self):
|
|
"""Test passing a list that does not match the block size."""
|
|
|
|
# TODO this test fails, somehow the error is not propagated correctly!
|
|
expected = [(0, 1), (1, 2, 10)]
|
|
with self.assertRaises(QiskitError):
|
|
_ = fast_entangler_map(num_qubits=20, block_size=2, entanglement=expected, offset=0)
|
|
|
|
def test_callable_list(self):
|
|
"""Test using a callable."""
|
|
|
|
def my_entanglement(offset):
|
|
return [(0, 1)] if offset % 2 == 0 else [(1, 2)]
|
|
|
|
for offset in range(3):
|
|
with self.subTest(offset=offset):
|
|
expect = my_entanglement(offset)
|
|
result = fast_entangler_map(
|
|
num_qubits=3, block_size=2, entanglement=my_entanglement, offset=offset
|
|
)
|
|
self.assertEqual(expect, result)
|
|
|
|
def test_callable_str(self):
|
|
"""Test using a callable."""
|
|
|
|
def my_entanglement(offset):
|
|
return "linear" if offset % 2 == 0 else "pairwise"
|
|
|
|
expected = {"linear": [(0, 1), (1, 2), (2, 3)], "pairwise": [(0, 1), (2, 3), (1, 2)]}
|
|
|
|
for offset in range(3):
|
|
with self.subTest(offset=offset):
|
|
result = fast_entangler_map(
|
|
num_qubits=4, block_size=2, entanglement=my_entanglement, offset=offset
|
|
)
|
|
self.assertEqual(expected["linear" if offset % 2 == 0 else "pairwise"], result)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|