From 3da2973a504c11fe76ed6252eb0c02ddacdd178a Mon Sep 17 00:00:00 2001 From: Misty-W <82074193+Misty-W@users.noreply.github.com> Date: Tue, 26 Sep 2023 16:35:31 -0700 Subject: [PATCH] Add functions and tests; remove `.md` file --- docs/source/apidoc.md | 7 ++ docs/source/examples/benchmarking_qem.md | 113 ------------------ mitiq/benchmarks/__init__.py | 1 + .../rotated_randomized_benchmarking.py | 83 +++++++++++++ .../test_rotated_randomized_benchmarking.py | 44 +++++++ 5 files changed, 135 insertions(+), 113 deletions(-) delete mode 100644 docs/source/examples/benchmarking_qem.md create mode 100644 mitiq/benchmarks/rotated_randomized_benchmarking.py create mode 100644 mitiq/benchmarks/tests/test_rotated_randomized_benchmarking.py diff --git a/docs/source/apidoc.md b/docs/source/apidoc.md index aa64dec344..42436d20d0 100644 --- a/docs/source/apidoc.md +++ b/docs/source/apidoc.md @@ -23,6 +23,13 @@ :members: ``` +### Rotated Randomized Benchmarking Circuits + +```{eval-rst} +.. automodule:: mitiq.benchmarks.rotated_randomized_benchmarking + :members: +``` + ### GHZ Circuits ```{eval-rst} .. automodule:: mitiq.benchmarks.ghz_circuits diff --git a/docs/source/examples/benchmarking_qem.md b/docs/source/examples/benchmarking_qem.md deleted file mode 100644 index 78b3447772..0000000000 --- a/docs/source/examples/benchmarking_qem.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -jupytext: - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.14.1 -kernelspec: - display_name: Python 3 - name: python3 ---- - -# Benchmarking ZNE on random circuits with expecation values other than 0 or 1 - -## Issue Description - -Most examples and most benchmarks in Mitiq are based on benchmark circuits whose ideal expectation value is 1. -For example: - https://github.com/unitaryfund/mitiq/blob/9a13b3ec476cd2cd978e572d12081af4d73536cb/mitiq/benchmarks/randomized_benchmarking.py#L23 - -Typically we test how good a QEM technique is, by checking how close a mitigated expectation value is to 1 (ideal result) compared to the noisy unmitigated result. - - -This is very convenient and intuitive. However, it is also useful to have more general benchmarks in which the expectation value can be any number in a continuous interval e.g. - -$$ E_{\rm ideal} \in [-1, 1] $$ - -This benchmarking method enables testing QEM techniques in more general scenarios, closer to real-world applications in which expectation values can take arbitrary values. - - -+++ - -## Setup - -+++ - -```{code-cell} ipython3 -import numpy as np - -import cirq - -import mitiq -``` - -### Circuit - - -Here we generate "rotated" RB circuits in which we insert a $R_z(\theta)$ rotation in the middle of an RB circuit, such that - -$$ C(\theta) = G_n \dots G_{n/2 +1} R_z(\theta)G_{n/2} \dots G_2 G_1 $$ - -where $G_j$ are Clifford elements or Clifford gates. - -This should generate expectation values which are sinusoidal functions of $\theta$, so something varying in with a continuous range of ideal expectation values. - -At the same time since, up to factors of 2, we have $R_z(\theta) =cos(\theta) I + i \ sin(\theta) Z$, the rotated Clifford circuit $C(\theta)$ can be written as a linear combination of just two Clifford circuits and, therefore, it is still easy to classically simulate. - - - -```{code-cell} ipython3 -n_qubits = 2 -trials = 10 -n_cliffords = 10 -circuits = mitiq.benchmarks.generate_rb_circuits( - n_qubits, n_cliffords, trials -) -composed_circs = [] - -rng = np.random.default_rng() - -for c in range(5): - circ = circuits[c] - q = circ.all_qubits() - rotated_circ = circ.append(cirq.Rz(rads=rng.random() * np.pi)(q[0])) - rotated_circ = circ.append(cirq.Rz(rads=rng.random() * np.pi)(q[1])) - composed_circs.append(rotated_circ.append(circuits[c + 5])) - -``` - - -```{code-cell} ipython3 -def execute(circuit, noise_level=0.01): - noisy_circuit = cirq.Circuit() - for op in circuit.all_operations(): - noisy_circuit.append(op) - if len(op.qubits) == 2: - noisy_circuit.append( - cirq.depolarize(p=noise_level, n_qubits=2)(*op.qubits) - ) - - rho = ( - cirq.DensityMatrixSimulator() - .simulate(noisy_circuit) - .final_density_matrix - ) - return rho[0, 0].real -``` - - - -```{code-cell} ipython3 -ideal_values = [] -unmitigated_values = [] -mitigated_values = [] - -for circuit in composed_circs: - ideal_values.append(execute(circuit, 0)) - unmitigated_values.append(execute(circuit)) - mitigated_values.append(mitiq.zne.execute_with_zne(circuit, execute)) - -``` - -### TODO: Plot per trial diff --git a/mitiq/benchmarks/__init__.py b/mitiq/benchmarks/__init__.py index 3bd67a35ab..e2eb2abecb 100644 --- a/mitiq/benchmarks/__init__.py +++ b/mitiq/benchmarks/__init__.py @@ -4,6 +4,7 @@ # LICENSE file in the root directory of this source tree. from mitiq.benchmarks.randomized_benchmarking import generate_rb_circuits +from mitiq.benchmarks.rotated_randomized_benchmarking import generate_rotated_rb_circuits from mitiq.benchmarks.mirror_circuits import generate_mirror_circuit from mitiq.benchmarks.ghz_circuits import generate_ghz_circuit from mitiq.benchmarks.quantum_volume_circuits import ( diff --git a/mitiq/benchmarks/rotated_randomized_benchmarking.py b/mitiq/benchmarks/rotated_randomized_benchmarking.py new file mode 100644 index 0000000000..dae8f66021 --- /dev/null +++ b/mitiq/benchmarks/rotated_randomized_benchmarking.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 for generating randomized benchmarking circuits.""" +from typing import List, Optional + +import numpy as np + +import cirq + +from mitiq import QPROGRAM +from mitiq.interface import convert_to_mitiq, convert_from_mitiq +from mitiq.benchmarks import generate_rb_circuits + + +def generate_rotated_rb_circuits( + n_qubits: int, + num_cliffords: int, + trials: int = 1, + return_type: Optional[str] = None, + seed: Optional[int] = None, +) -> List[QPROGRAM]: + + r""" + Generates a list of "rotated" randomized benchmarking circuits. + This benchmarking method enables testing QEM techniques in more general + scenarios, closer to real-world applications in which expectation values + can take arbitrary values. + + Rotated randomized bencmarking circuits are randomized benchmarking + circuits in which an $R_z(\theta)$ rotation is inserted in the middle, such + that: + + $$ C(\theta) = G_n \dots G_{n/2 +1} R_z(\theta)G_{n/2} \dots G_2 G_1 $$ + + where $G_j$ are Clifford elements or Clifford gates. + + The circuits generate expectation values which are sinusoidal functions of + $\theta$, which in the ideal (noiseless) case vary in a continuous interval + of $ E_{\rm ideal} \in [-1, 1] $. + + Since (up to factors of 2) we have + $R_z(\theta) =cos(\theta) I + i \ sin(\theta) Z$, the rotated Clifford + circuit $C(\theta)$ can be written as a linear combination of just two + Clifford circuits and, therefore, it is still easy to classically simulate. + + Args: + n_qubits: The number of qubits. Can be either 1 or 2. + num_cliffords: The number of Clifford group elements in the + random circuits. This is proportional to the depth per circuit. + trials: The number of random circuits at each num_cfd. + 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``. + seed: An optional seed for reproducibility of $\theta$ in + $R_z(\theta)$. + + Returns: + A list of rotated randomized benchmarking circuits. + """ + + circuits = generate_rb_circuits(n_qubits, num_cliffords, 2 * trials) + rotated_circuits = [] + + rng = np.random.default_rng(seed=seed) + + for circ in circuits: + rotated_circ, _ = convert_to_mitiq(circ) + qubits = rotated_circ.all_qubits() + rads = rng.random() * np.pi + rotated_circ.insert( + len(circ) // 2, cirq.Rz(rads=rads).on_each(*qubits) + ) + rotated_circuits.append(rotated_circ) + + return_type = "cirq" if not return_type else return_type + return [ + convert_from_mitiq(circuit, return_type) + for circuit in rotated_circuits + ] diff --git a/mitiq/benchmarks/tests/test_rotated_randomized_benchmarking.py b/mitiq/benchmarks/tests/test_rotated_randomized_benchmarking.py new file mode 100644 index 0000000000..39c2e0102b --- /dev/null +++ b/mitiq/benchmarks/tests/test_rotated_randomized_benchmarking.py @@ -0,0 +1,44 @@ +# 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 rotated randomized benchmarking circuits.""" + +import pytest + +from mitiq import SUPPORTED_PROGRAM_TYPES +from mitiq.benchmarks.rotated_randomized_benchmarking import ( + generate_rotated_rb_circuits, +) + + +@pytest.mark.parametrize("n_qubits", (1, 2)) +def test_rotated_rb_circuits(n_qubits): + depth = 10 + results = [] + for trials in [5, 10]: + circuits = generate_rotated_rb_circuits( + n_qubits=n_qubits, num_cliffords=depth, trials=trials + ) + for qc in circuits: + # we check the ground state population to ignore any global phase + wvf = qc.final_state_vector() + zero_prob = abs(wvf[0] ** 2) + assert -1.0001 <= zero_prob <= 1.0001 + results.append(zero_prob) + + +@pytest.mark.parametrize("n_qubits", (1, 2)) +@pytest.mark.parametrize("return_type", SUPPORTED_PROGRAM_TYPES.keys()) +def test_rotated_rb_conversion(n_qubits, return_type): + depth = 10 + for trials in [2, 3]: + circuits = generate_rotated_rb_circuits( + n_qubits=n_qubits, + num_cliffords=depth, + trials=trials, + return_type=return_type, + ) + for qc in circuits: + assert return_type in qc.__module__