From 196120734ca1401a6e32a81c2bd7fe190c7231d6 Mon Sep 17 00:00:00 2001 From: eliottrosenberg <61400172+eliottrosenberg@users.noreply.github.com> Date: Mon, 18 Dec 2023 21:30:51 -0800 Subject: [PATCH] Add parallel randomized benchmarking (#6382) --- cirq-core/cirq/experiments/__init__.py | 1 + .../experiments/qubit_characterizations.py | 103 +++++++++++++++--- .../qubit_characterizations_test.py | 15 +++ 3 files changed, 105 insertions(+), 14 deletions(-) diff --git a/cirq-core/cirq/experiments/__init__.py b/cirq-core/cirq/experiments/__init__.py index f7842d8f3eb..7b1f1377f0e 100644 --- a/cirq-core/cirq/experiments/__init__.py +++ b/cirq-core/cirq/experiments/__init__.py @@ -20,6 +20,7 @@ TomographyResult, two_qubit_randomized_benchmarking, two_qubit_state_tomography, + parallel_single_qubit_randomized_benchmarking, ) from cirq.experiments.fidelity_estimation import ( diff --git a/cirq-core/cirq/experiments/qubit_characterizations.py b/cirq-core/cirq/experiments/qubit_characterizations.py index aa39631829f..a5c3e7922a8 100644 --- a/cirq-core/cirq/experiments/qubit_characterizations.py +++ b/cirq-core/cirq/experiments/qubit_characterizations.py @@ -15,7 +15,18 @@ import dataclasses import itertools -from typing import Any, cast, Iterator, List, Optional, Sequence, Tuple, TYPE_CHECKING +from typing import ( + Any, + cast, + Iterator, + List, + Optional, + Sequence, + Tuple, + TYPE_CHECKING, + Mapping, + Dict, +) import numpy as np from scipy.optimize import curve_fit @@ -207,9 +218,9 @@ def single_qubit_randomized_benchmarking( qubit: 'cirq.Qid', use_xy_basis: bool = True, *, - num_clifford_range: Sequence[int] = range(10, 100, 10), - num_circuits: int = 20, - repetitions: int = 1000, + num_clifford_range: Sequence[int] = tuple(np.logspace(np.log10(5), 3, 5, dtype=int)), + num_circuits: int = 10, + repetitions: int = 600, ) -> RandomizedBenchMarkResult: """Clifford-based randomized benchmarking (RB) of a single qubit. @@ -245,21 +256,75 @@ def single_qubit_randomized_benchmarking( A RandomizedBenchMarkResult object that stores and plots the result. """ + qubits = cast(Iterator['cirq.Qid'], (qubit,)) + result = parallel_single_qubit_randomized_benchmarking( + sampler, + qubits, + use_xy_basis, + num_clifford_range=num_clifford_range, + num_circuits=num_circuits, + repetitions=repetitions, + ) + return result[qubit] + + +def parallel_single_qubit_randomized_benchmarking( + sampler: 'cirq.Sampler', + qubits: Iterator['cirq.Qid'], + use_xy_basis: bool = True, + *, + num_clifford_range: Sequence[int] = tuple( + np.logspace(np.log10(5), np.log10(1000), 5, dtype=int) + ), + num_circuits: int = 10, + repetitions: int = 1000, +) -> Mapping['cirq.Qid', 'RandomizedBenchMarkResult']: + """Clifford-based randomized benchmarking (RB) single qubits in parallel. + + This is the same as `single_qubit_randomized_benchmarking` except on all + of the specified qubits in parallel, i.e. with the individual randomized + benchmarking circuits zipped together. + + Args: + sampler: The quantum engine or simulator to run the circuits. + use_xy_basis: Determines if the Clifford gates are built with x and y + rotations (True) or x and z rotations (False). + qubits: The qubits to benchmark. + num_clifford_range: The different numbers of Cliffords in the RB study. + num_circuits: The number of random circuits generated for each + number of Cliffords. + repetitions: The number of repetitions of each circuit. + + Returns: + A dictionary from qubits to RandomizedBenchMarkResult objects. + """ + cliffords = _single_qubit_cliffords() c1 = cliffords.c1_in_xy if use_xy_basis else cliffords.c1_in_xz - cfd_mats = np.array([_gate_seq_to_mats(gates) for gates in c1]) + clifford_mats = np.array([_gate_seq_to_mats(gates) for gates in c1]) - gnd_probs = [] - for num_cfds in num_clifford_range: - excited_probs_l = [] + # create circuits + circuits_all: List['cirq.AbstractCircuit'] = [] + for num_cliffords in num_clifford_range: for _ in range(num_circuits): - circuit = _random_single_q_clifford(qubit, num_cfds, c1, cfd_mats) - circuit.append(ops.measure(qubit, key='z')) - results = sampler.run(circuit, repetitions=repetitions) - excited_probs_l.append(np.mean(results.measurements['z'])) - gnd_probs.append(1.0 - np.mean(excited_probs_l)) + circuits_all.append( + _create_parallel_rb_circuit(qubits, num_cliffords, c1, clifford_mats) + ) - return RandomizedBenchMarkResult(num_clifford_range, gnd_probs) + # run circuits + results = sampler.run_batch(circuits_all, repetitions=repetitions) + gnd_probs: dict = {q: [] for q in qubits} + idx = 0 + for num_cliffords in num_clifford_range: + excited_probs: Dict['cirq.Qid', List[float]] = {q: [] for q in qubits} + for _ in range(num_circuits): + result = results[idx][0] + for qubit in qubits: + excited_probs[qubit].append(np.mean(result.measurements[str(qubit)])) + idx += 1 + for qubit in qubits: + gnd_probs[qubit].append(1.0 - np.mean(excited_probs[qubit])) + return {q: RandomizedBenchMarkResult(num_clifford_range, gnd_probs[q]) for q in qubits} def two_qubit_randomized_benchmarking( @@ -496,6 +561,16 @@ def _measurement(two_qubit_circuit: circuits.Circuit) -> np.ndarray: return TomographyResult(rho) +def _create_parallel_rb_circuit( + qubits: Iterator['cirq.Qid'], num_cliffords: int, c1: list, clifford_mats: np.ndarray +) -> 'cirq.Circuit': + circuits_to_zip = [ + _random_single_q_clifford(qubit, num_cliffords, c1, clifford_mats) for qubit in qubits + ] + circuit = circuits.Circuit.zip(*circuits_to_zip) + return circuits.Circuit.from_moments(*circuit, ops.measure_each(*qubits)) + + def _indices_after_basis_rot(i: int, j: int) -> Tuple[int, Sequence[int], Sequence[int]]: mat_idx = 3 * (3 * i + j) q_0_i = 3 - i diff --git a/cirq-core/cirq/experiments/qubit_characterizations_test.py b/cirq-core/cirq/experiments/qubit_characterizations_test.py index e8f09694e42..ba205ad592d 100644 --- a/cirq-core/cirq/experiments/qubit_characterizations_test.py +++ b/cirq-core/cirq/experiments/qubit_characterizations_test.py @@ -26,6 +26,7 @@ two_qubit_randomized_benchmarking, single_qubit_state_tomography, two_qubit_state_tomography, + parallel_single_qubit_randomized_benchmarking, ) @@ -92,6 +93,20 @@ def test_single_qubit_randomized_benchmarking(): assert np.isclose(results.pauli_error(), 0.0, atol=1e-7) # warning is expected +def test_parallel_single_qubit_randomized_benchmarking(): + # Check that the ground state population at the end of the Clifford + # sequences is always unity. + simulator = sim.Simulator() + qubits = (GridQubit(0, 0), GridQubit(0, 1)) + num_cfds = range(5, 20, 5) + results = parallel_single_qubit_randomized_benchmarking( + simulator, num_clifford_range=num_cfds, repetitions=100, qubits=qubits + ) + for qubit in qubits: + g_pops = np.asarray(results[qubit].data)[:, 1] + assert np.isclose(np.mean(g_pops), 1.0) + + def test_two_qubit_randomized_benchmarking(): # Check that the ground state population at the end of the Clifford # sequences is always unity.