Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Cryoris committed Oct 17, 2024
1 parent 2eea43e commit dbfe5f0
Show file tree
Hide file tree
Showing 2 changed files with 235 additions and 3 deletions.
18 changes: 16 additions & 2 deletions qiskit/circuit/library/n_local/n_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ def entanglement(layer_index: int) -> list[list[int]]:
Returns:
An n-local circuit.
"""
if reps < 0:
# this is an important check, since we cast this to an unsigned integer Rust-side
raise ValueError(f"reps must be non-negative, but is {reps}")

supported_gates = get_standard_gate_name_mapping()
rotation_blocks = _normalize_blocks(
rotation_blocks, supported_gates, overwrite_block_parameters
Expand Down Expand Up @@ -1295,7 +1299,8 @@ def _normalize_entanglement(
) -> list[str | list[tuple[int]]] | Callable[[int], list[str | list[tuple[int]]]]:
"""If the entanglement is Iterable[Iterable], normalize to list[tuple]."""
if isinstance(entanglement, str):
return [entanglement]
return [entanglement] * num_entanglement_blocks

elif isinstance(entanglement, Iterable):
# handle edge cases when entanglement is set to an empty list
if len(entanglement) == 0:
Expand Down Expand Up @@ -1335,11 +1340,20 @@ def _normalize_blocks(
supported_gates: dict[str, Gate],
overwrite_block_parameters: bool,
) -> list[Block]:
if not isinstance(blocks, Iterable):
if not isinstance(blocks, Iterable) or isinstance(blocks, str):
blocks = [blocks]

normalized = []
for block in blocks:
# since the NLocal circuit accepted circuits as inputs, we raise a warning here
# to simplify the transition (even though, strictly speaking, quantum circuits are
# not a supported input type)
if isinstance(block, QuantumCircuit):
raise ValueError(
"The blocks should be of type Gate or str, but you passed a QuantumCircuit. "
"You can call .to_gate() on the circuit to turn it into a Gate object."
)

is_standard = False
if isinstance(block, str):
if block not in supported_gates:
Expand Down
220 changes: 219 additions & 1 deletion test/python/circuit/library/test_nlocal.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
from ddt import ddt, data, unpack

from qiskit import transpile
from qiskit.circuit import QuantumCircuit, Parameter, ParameterVector, ParameterExpression
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,
Expand Down Expand Up @@ -50,6 +54,18 @@
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."""
Expand Down Expand Up @@ -471,9 +487,129 @@ def test_initial_state_as_circuit_object(self):
self.assertCircuitEqual(ref, expected)


@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_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_gate_blocks(self):
"""Test passing blocks as gates."""
x, y = Parameter("x"), Parameter("y")
my_gate = Gato(x, y)

circuit = n_local(4, my_gate, "cx", "linear", reps=3)
print(circuit.draw())

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
Expand Down Expand Up @@ -501,6 +637,21 @@ def entanglement_2(layer):
idle = set(dag.idle_wires())
self.assertEqual(skipped_set, idle)

def test_mismatching_entanglement_blocks(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_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.
Expand Down Expand Up @@ -549,6 +700,73 @@ def test_entanglement_by_callable(self):

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_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)

@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()
)

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_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):
Expand Down

0 comments on commit dbfe5f0

Please sign in to comment.