Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mirror Quantum Volume Circuits #1838

Merged
merged 14 commits into from
Jun 8, 2023
6 changes: 6 additions & 0 deletions docs/source/apidoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
11 changes: 11 additions & 0 deletions docs/source/refs.bib
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
83 changes: 83 additions & 0 deletions mitiq/benchmarks/mirror_qv_circuits.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion mitiq/benchmarks/quantum_volume_circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
71 changes: 71 additions & 0 deletions mitiq/benchmarks/tests/test_mirror_qv_circuits.py
Original file line number Diff line number Diff line change
@@ -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