Skip to content

Add parallel randomized benchmarking #6382

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

Merged
merged 21 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cirq-core/cirq/experiments/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
TomographyResult,
two_qubit_randomized_benchmarking,
two_qubit_state_tomography,
parallel_single_qubit_randomized_benchmarking,
)

from cirq.experiments.fidelity_estimation import (
Expand Down
103 changes: 89 additions & 14 deletions cirq-core/cirq/experiments/qubit_characterizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOC, would it be possible to put this in the existing single_qubit_randomized_benchmarking module? Seems like it's basically the same thing and there could be opportunities for code reuse. The fact that one is running in parallel on multiple qubits is a minor details (the existing single-qubit code could just become a special case of the parallel code).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I changed it so that single_qubit_randomized_benchmarking is now a wrapper for parallel_single_qubit_randomized_benchmarking. What do you think?

sampler: 'cirq.Sampler',
qubits: Iterator['cirq.Qid'],
use_xy_basis: bool = True,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this is before the kwargs cut-off?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the same format as in single_qubit_randomized_benchmarking() in the same file, but I can change it.

*,
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(
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions cirq-core/cirq/experiments/qubit_characterizations_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
two_qubit_randomized_benchmarking,
single_qubit_state_tomography,
two_qubit_state_tomography,
parallel_single_qubit_randomized_benchmarking,
)


Expand Down Expand Up @@ -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.
Expand Down