Skip to content

update the RB fitting method to compensate for the standard deviation induced from the random number generator #7562

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
36 changes: 32 additions & 4 deletions cirq-core/cirq/experiments/qubit_characterizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import functools
import itertools
import uuid
from typing import Any, cast, Iterator, Mapping, Sequence, TYPE_CHECKING
from typing import Any, cast, Iterator, Mapping, Optional, Sequence, TYPE_CHECKING

import attrs
import numpy as np
Expand Down Expand Up @@ -73,17 +73,33 @@ class Cliffords:
class RandomizedBenchMarkResult:
"""Results from a randomized benchmarking experiment."""

def __init__(self, num_cliffords: Sequence[int], ground_state_probabilities: Sequence[float]):
def __init__(
self,
num_cliffords: Sequence[int],
ground_state_probabilities: Sequence[float],
ground_state_probabilities_std: Optional[Sequence[float]] | None = None,
):
"""Inits RandomizedBenchMarkResult.

Args:
num_cliffords: The different numbers of Cliffords in the RB
study.
ground_state_probabilities: The corresponding average ground state
probabilities.
ground_state_probabilities_std: The standard deviation of the probabilities.
"""
self._num_cfds_seq = num_cliffords
self._gnd_state_probs = ground_state_probabilities
if ground_state_probabilities_std is None or np.all(
np.isclose(ground_state_probabilities_std, 0)
):
self._gnd_state_probs_std = None
else:
self._gnd_state_probs_std = np.array(ground_state_probabilities_std)
zeros = np.isclose(self._gnd_state_probs_std, 0)
self._gnd_state_probs_std[zeros] = self._gnd_state_probs_std[
np.logical_not(zeros)
].min()

@property
def data(self) -> Sequence[tuple[int, float]]:
Expand Down Expand Up @@ -142,6 +158,7 @@ def _fit_exponential(self) -> tuple[np.ndarray, np.ndarray]:
f=exp_fit,
xdata=self._num_cfds_seq,
ydata=self._gnd_state_probs,
sigma=self._gnd_state_probs_std,
p0=[0.5, 0.5, 1.0 - 1e-3],
bounds=([0, -1, 0], [1, 1, 1]),
)
Expand Down Expand Up @@ -534,6 +551,7 @@ def parallel_single_qubit_rb(
# run circuits
results = sampler.run_batch(circuits_all, repetitions=parameters.repetitions)
gnd_probs: dict = {q: [] for q in qubits}
gnd_probs_std: dict = {q: [] for q in qubits}
idx = 0
for num_cliffords in parameters.num_clifford_range:
excited_probs: dict[cirq.Qid, list[float]] = {q: [] for q in qubits}
Expand All @@ -544,9 +562,17 @@ def parallel_single_qubit_rb(
idx += 1
for qubit in qubits:
gnd_probs[qubit].append(1.0 - np.mean(excited_probs[qubit]))
gnd_probs_std[qubit].append(
np.std(excited_probs[qubit]) / np.sqrt(parameters.repetitions)
)

return ParallelRandomizedBenchmarkingResult(
{q: RandomizedBenchMarkResult(parameters.num_clifford_range, gnd_probs[q]) for q in qubits}
{
q: RandomizedBenchMarkResult(
parameters.num_clifford_range, gnd_probs[q], gnd_probs_std[q]
)
for q in qubits
}
)


Expand Down Expand Up @@ -595,6 +621,7 @@ def two_qubit_randomized_benchmarking(
cliffords = _single_qubit_cliffords()
cfd_matrices = _two_qubit_clifford_matrices(first_qubit, second_qubit, cliffords)
gnd_probs = []
gnd_probs_std = []
for num_cfds in num_clifford_range:
gnd_probs_l = []
for _ in range(num_circuits):
Expand All @@ -606,8 +633,9 @@ def two_qubit_randomized_benchmarking(
gnds = [(not r[0] and not r[1]) for r in results.measurements['z']]
gnd_probs_l.append(np.mean(gnds))
gnd_probs.append(float(np.mean(gnd_probs_l)))
gnd_probs_std.append(float(np.std(gnd_probs_l) / np.sqrt(repetitions)))

return RandomizedBenchMarkResult(num_clifford_range, gnd_probs)
return RandomizedBenchMarkResult(num_clifford_range, gnd_probs, gnd_probs_std)


def single_qubit_state_tomography(
Expand Down
16 changes: 16 additions & 0 deletions cirq-core/cirq/experiments/qubit_characterizations_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,22 @@ def test_parallel_single_qubit_parallel_single_qubit_randomized_benchmarking():
_ = results.plot_integrated_histogram()


@mock.patch.dict(os.environ, clear='CIRQ_TESTING')
def test_parallel_single_qubit_parallel_single_qubit_randomized_benchmarking_with_noise():
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this function name have double "parallel_single_qubit"?

simulator = sim.Simulator(noise=cirq.depolarize(1e-3), seed=0)
qubits = (GridQubit(0, 0), GridQubit(0, 1))
num_cfds = range(5, 7, 1)
results = parallel_single_qubit_randomized_benchmarking(
simulator, num_clifford_range=num_cfds, repetitions=10, qubits=qubits
)
for qubit in qubits:
g_pops = np.asarray(results.results_dictionary[qubit].data)[:, 1]
assert np.isclose(np.mean(g_pops), 0.99, atol=1e-2)
_ = results.plot_single_qubit(qubit)
pauli_errors = results.pauli_error()
assert len(pauli_errors) == len(qubits)


def test_two_qubit_randomized_benchmarking():
# Check that the ground state population at the end of the Clifford
# sequences is always unity.
Expand Down