diff --git a/docs/source/apidoc.md b/docs/source/apidoc.md index e6f1eda93c..0d000a3457 100644 --- a/docs/source/apidoc.md +++ b/docs/source/apidoc.md @@ -47,6 +47,12 @@ :members: ``` +### Mirror Quantum Volume Circuits +```{eval-rst} +.. automodule:: mitiq.benchmarks.mirror_qv_circuits + :members: +``` + ## Circuit types and result types ```{eval-rst} diff --git a/docs/source/refs.bib b/docs/source/refs.bib index 36cdc2945e..07d641e34d 100644 --- a/docs/source/refs.bib +++ b/docs/source/refs.bib @@ -1,6 +1,17 @@ # Style for keys: Lastname_YYYY_PRL, # Letter A + +@misc{Amico_2023_arxiv, + author = {{Amico}, Mirko and {Zhang}, Helena and {Jurcevic}, Petar and {Bishop}, Lev S. and {Nation}, Paul and {Wack}, Andrew and {McKay}, David C.}, + title = {{Defining Standard Strategies for Quantum Benchmarks}}, + year = {2023}, + month = {mar}, + archiveprefix = {arXiv}, + eprint = {2303.02108}, + primaryclass = {quant-ph}, +} + # Letter B @article{Bonet_2018_PRA, diff --git a/mitiq/benchmarks/mirror_qv_circuits.py b/mitiq/benchmarks/mirror_qv_circuits.py new file mode 100644 index 0000000000..a6578520d5 --- /dev/null +++ b/mitiq/benchmarks/mirror_qv_circuits.py @@ -0,0 +1,83 @@ +# Copyright (C) Unitary Fund +# +# This source code is licensed under the GPL license (v3) found in the +# LICENSE file in the root directory of this source tree. + +"""Functions to create a Mirror Quantum Volume Benchmarking circuit +as defined in https://arxiv.org/abs/2303.02108.""" + +from typing import Optional + + +from mitiq import QPROGRAM +from mitiq.interface.conversions import convert_to_mitiq, convert_from_mitiq + + +from mitiq.benchmarks.quantum_volume_circuits import ( + generate_quantum_volume_circuit, +) +import cirq + + +def generate_mirror_qv_circuit( + num_qubits: int, + depth: int, + decompose: bool = False, + seed: Optional[int] = None, + return_type: Optional[str] = None, +) -> QPROGRAM: + """Generate a mirror quantum volume circuit with the given number of qubits + and depth as defined in :cite:`Amico_2023_arxiv`. + + The generated circuit consists of a quantum volume circuit up to `depth/2` + layers followed by an inverse of the quantum volume portion up to `depth/2` + when `depth` is an even number. + + When `depth` is odd, the layers will be changed to `depth+1`. + + + The ideal output bit-string is a string of zeroes. + + Args: + num_qubits: The number of qubits in the generated circuit. + depth: The number of layers in the generated circuit. + decompose: Recursively decomposes the randomly sampled (numerical) + unitary matrix gates into simpler gates. + seed: Seed for generating random circuit. + return_type: String which specifies the type of the returned + circuits. See the keys of ``mitiq.SUPPORTED_PROGRAM_TYPES`` + for options. If ``None``, the returned circuits have type + ``cirq.Circuit``. + + Returns: + A quantum volume circuit acting on ``num_qubits`` qubits. + """ + if depth <= 0: + raise ValueError( + "{} is invalid for the generated circuit depth.", depth + ) + + if depth % 2 == 0: + first_half_depth = int(depth / 2) + else: + first_half_depth = int((depth + 1) / 2) + + qv_generated, _ = generate_quantum_volume_circuit( + num_qubits, first_half_depth, seed=seed, decompose=decompose + ) + qv_half_circ, _ = convert_to_mitiq(qv_generated) + + mirror_half_circ = cirq.Circuit() + qv_half_ops = list(qv_half_circ.all_operations()) + for i in range(len(qv_half_ops))[::-1]: + op_inverse = cirq.inverse(qv_half_ops[i]) + mirror_half_circ.append(op_inverse, strategy=cirq.InsertStrategy.NEW) + + circ = qv_half_circ + mirror_half_circ + + if decompose: + # Decompose random unitary gates into simpler gates. + circ = cirq.Circuit(cirq.decompose(circ)) + + return_type = "cirq" if not return_type else return_type + return convert_from_mitiq(circ, return_type) diff --git a/mitiq/benchmarks/quantum_volume_circuits.py b/mitiq/benchmarks/quantum_volume_circuits.py index 4b4781a742..afe2db6a6e 100644 --- a/mitiq/benchmarks/quantum_volume_circuits.py +++ b/mitiq/benchmarks/quantum_volume_circuits.py @@ -47,7 +47,7 @@ def generate_quantum_volume_circuit( Args: num_qubits: The number of qubits in the generated circuit. - depth: The number of qubits in the generated circuit. + depth: The number of layers in the generated circuit. decompose: Recursively decomposes the randomly sampled (numerical) unitary matrix gates into simpler gates. seed: Seed for generating random circuit. diff --git a/mitiq/benchmarks/tests/test_mirror_qv_circuits.py b/mitiq/benchmarks/tests/test_mirror_qv_circuits.py new file mode 100644 index 0000000000..1e10f19291 --- /dev/null +++ b/mitiq/benchmarks/tests/test_mirror_qv_circuits.py @@ -0,0 +1,71 @@ +# Copyright (C) Unitary Fund +# +# This source code is licensed under the GPL license (v3) found in the +# LICENSE file in the root directory of this source tree. + +"""Tests for mirror quantum volume circuits.""" + + +import cirq +import pytest + +from mitiq.benchmarks.mirror_qv_circuits import generate_mirror_qv_circuit +from mitiq import SUPPORTED_PROGRAM_TYPES + + +@pytest.mark.parametrize( + "depth_num", + [1, 2, 3, 4, 5, 6, 7, 8], +) +def test_generate_mirror_qv_circuit(depth_num): + """Check the circuit output.""" + test_circ = generate_mirror_qv_circuit(4, depth_num) + + # check bitstring is all 0's + bit_test = cirq.Simulator().run( + test_circ + cirq.measure(test_circ.all_qubits()), repetitions=1000 + ) + test_bitstring = list(bit_test.measurements.values())[0][0].tolist() + expected_bitstring = [0] * 4 + assert test_bitstring == expected_bitstring + + +def test_bad_depth_number(): + """Check if an unacceptable depth number rasies an error.""" + for n in (-1, 0): + with pytest.raises( + ValueError, match="{} is invalid for the generated circuit depth." + ): + generate_mirror_qv_circuit(3, n) + + +@pytest.mark.parametrize("return_type", SUPPORTED_PROGRAM_TYPES.keys()) +def test_volume_conversion(return_type): + """Check generated circuit's return type.""" + circuit = generate_mirror_qv_circuit(4, 3, return_type=return_type) + assert return_type in circuit.__module__ + + +def test_generate_model_circuit_with_seed(): + """Test that a model circuit is determined by its seed.""" + circuit_1 = generate_mirror_qv_circuit(4, 3, seed=1) + circuit_2 = generate_mirror_qv_circuit(4, 3, seed=1) + circuit_3 = generate_mirror_qv_circuit(4, 3, seed=2) + + assert circuit_1 == circuit_2 + assert circuit_2 != circuit_3 + + +def test_circuit_decomposition(): + """Test that decomposed circuit consists of gates in default cirq gatest. + As defined in cirq.protocols.decompose_protocol, this default gateset is + ops.XPowGate, + ops.YPowGate, + ops.ZPowGate, + ops.CZPowGate, + ops.MeasurementGate, + ops.GlobalPhaseGate + """ + circuit = generate_mirror_qv_circuit(4, 3, decompose=True) + for op in [operation for moment in circuit for operation in moment]: + assert op in cirq.protocols.decompose_protocol.DECOMPOSE_TARGET_GATESET