Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions cirq-core/cirq/experiments/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@

from cirq.experiments.qubit_characterizations import (
RandomizedBenchMarkResult as RandomizedBenchMarkResult,
RBParameters as RBParameters,
single_qubit_randomized_benchmarking as single_qubit_randomized_benchmarking,
single_qubit_rb as single_qubit_rb,
single_qubit_state_tomography as single_qubit_state_tomography,
TomographyResult as TomographyResult,
two_qubit_randomized_benchmarking as two_qubit_randomized_benchmarking,
two_qubit_state_tomography as two_qubit_state_tomography,
parallel_single_qubit_randomized_benchmarking as parallel_single_qubit_randomized_benchmarking,
parallel_single_qubit_rb as parallel_single_qubit_rb,
)

from cirq.experiments.fidelity_estimation import (
Expand Down
156 changes: 134 additions & 22 deletions cirq-core/cirq/experiments/qubit_characterizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import itertools
from typing import Any, cast, Iterator, Mapping, Sequence, TYPE_CHECKING

import attrs
import numpy as np
from matplotlib import pyplot as plt

Expand All @@ -28,6 +29,7 @@
import cirq.vis.heatmap as cirq_heatmap
import cirq.vis.histogram as cirq_histogram
from cirq import circuits, ops, protocols
from cirq._compat import deprecated
from cirq.devices import grid_qubit

if TYPE_CHECKING:
Expand All @@ -36,6 +38,12 @@
import cirq


def _canonize_clifford_sequences(
sequences: list[list[ops.SingleQubitCliffordGate]],
) -> list[list[ops.SingleQubitCliffordGate]]:
return [[_reduce_gate_seq(seq)] for seq in sequences]


@dataclasses.dataclass
class Cliffords:
"""The single-qubit Clifford group, decomposed into elementary gates.
Expand Down Expand Up @@ -134,7 +142,7 @@ def _fit_exponential(self) -> tuple[np.ndarray, np.ndarray]:
xdata=self._num_cfds_seq,
ydata=self._gnd_state_probs,
p0=[0.5, 0.5, 1.0 - 1e-3],
bounds=([0, 0.25, 0], [0.5, 0.75, 1]),
bounds=([0, -1, 0], [1, 1, 1]),
)


Expand Down Expand Up @@ -333,6 +341,44 @@ def plot(
return axes


@attrs.frozen
class RBParameters:
r"""Parameters for running randomized benchmarking.

Arguments:
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.
use_xy_basis: Determines if the Clifford gates are built with x and y
rotations (True) or x and z rotations (False).
strict_basis: whether to use only cliffords that can be represented by at
most 2 gates of the choses basis. For example,
if True and use_xy_basis is True, this excludes $I, Z, \sqrt(Z), \-sqrt(Z)^\dagger$.
if True and use_xy_basis is False, this excludes $I, Y, \sqrt(Y), -\sqrt(Y)^\dagger$.
"""

num_clifford_range: Sequence[int] = tuple(np.logspace(np.log10(5), 3, 5, dtype=int))
num_circuits: int = 10
repetitions: int = 600
use_xy_basis: bool = False
strict_basis: bool = True

def gateset(self) -> list[list[ops.SingleQubitCliffordGate]]:
clifford_group = _single_qubit_cliffords()
sequences = clifford_group.c1_in_xy if self.use_xy_basis else clifford_group.c1_in_xz
sequences = _canonize_clifford_sequences(sequences)
if self.strict_basis:
if self.use_xy_basis:
excluded_gates = ops.Gateset(ops.I, ops.Z, ops.Z**0.5, ops.Z**-0.5)
else:
excluded_gates = ops.Gateset(ops.I, ops.Y, ops.Y**0.5, ops.Y**-0.5)

sequences = [[g] for (g,) in sequences if g not in excluded_gates]
return sequences


@deprecated(deadline='v2.0', fix='please use single_qubit_rb instead')
def single_qubit_randomized_benchmarking(
sampler: cirq.Sampler,
qubit: cirq.Qid,
Expand Down Expand Up @@ -376,17 +422,20 @@ def single_qubit_randomized_benchmarking(
A RandomizedBenchMarkResult object that stores and plots the result.
"""

result = parallel_single_qubit_randomized_benchmarking(
return single_qubit_rb(
sampler,
(qubit,),
use_xy_basis,
num_clifford_range=num_clifford_range,
num_circuits=num_circuits,
repetitions=repetitions,
qubit,
RBParameters(
num_clifford_range=num_clifford_range,
num_circuits=num_circuits,
repetitions=repetitions,
use_xy_basis=use_xy_basis,
strict_basis=False,
),
)
return result.results_dictionary[qubit]


@deprecated(deadline='v2.0', fix='please use parallel_single_qubit_rb instead')
def parallel_single_qubit_randomized_benchmarking(
sampler: cirq.Sampler,
qubits: Sequence[cirq.Qid],
Expand All @@ -413,35 +462,90 @@ def parallel_single_qubit_randomized_benchmarking(
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.
"""
return parallel_single_qubit_rb(
sampler,
qubits,
RBParameters(
num_clifford_range=num_clifford_range,
num_circuits=num_circuits,
repetitions=repetitions,
use_xy_basis=use_xy_basis,
strict_basis=False,
),
)


def single_qubit_rb(
sampler: cirq.Sampler,
qubit: cirq.Qid,
parameters: RBParameters = RBParameters(),
rng_or_seed: np.random.Generator | int | None = None,
) -> RandomizedBenchMarkResult:
"""Clifford-based randomized benchmarking (RB) on a single qubit.

Args:
sampler: The quantum engine or simulator to run the circuits.
qubit: The qubit(s) to benchmark.
parameters: The parameters of the experiment.
rng_or_seed: A np.random.Generator object or seed.
Returns:
A dictionary from qubits to RandomizedBenchMarkResult objects.
"""
return parallel_single_qubit_rb(sampler, [qubit], parameters, rng_or_seed).results_dictionary[
qubit
]


def parallel_single_qubit_rb(
sampler: cirq.Sampler,
qubits: Sequence[cirq.Qid],
parameters: RBParameters = RBParameters(),
rng_or_seed: np.random.Generator | int | None = None,
) -> ParallelRandomizedBenchmarkingResult:
"""Clifford-based randomized benchmarking (RB) single qubits in parallel.

Args:
sampler: The quantum engine or simulator to run the circuits.
qubits: The qubit(s) to benchmark.
parameters: The parameters of the experiment.
rng_or_seed: A np.random.Generator object or seed.
Returns:
A dictionary from qubits to RandomizedBenchMarkResult objects.
"""

clifford_group = _single_qubit_cliffords()
c1 = clifford_group.c1_in_xy if use_xy_basis else clifford_group.c1_in_xz
rng_or_seed = (
rng_or_seed
if isinstance(rng_or_seed, np.random.Generator)
else np.random.default_rng(rng_or_seed)
)

c1 = parameters.gateset()

# create circuits
circuits_all: list[cirq.AbstractCircuit] = []
for num_cliffords in num_clifford_range:
for _ in range(num_circuits):
circuits_all.append(_create_parallel_rb_circuit(qubits, num_cliffords, c1))
for num_cliffords in parameters.num_clifford_range:
for _ in range(parameters.num_circuits):
circuits_all.append(_create_parallel_rb_circuit(qubits, num_cliffords, c1, rng_or_seed))

# run circuits
results = sampler.run_batch(circuits_all, repetitions=repetitions)
results = sampler.run_batch(circuits_all, repetitions=parameters.repetitions)
gnd_probs: dict = {q: [] for q in qubits}
idx = 0
for num_cliffords in num_clifford_range:
for num_cliffords in parameters.num_clifford_range:
excited_probs: dict[cirq.Qid, list[float]] = {q: [] for q in qubits}
for _ in range(num_circuits):
for _ in range(parameters.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 ParallelRandomizedBenchmarkingResult(
{q: RandomizedBenchMarkResult(num_clifford_range, gnd_probs[q]) for q in qubits}
{q: RandomizedBenchMarkResult(parameters.num_clifford_range, gnd_probs[q]) for q in qubits}
)


Expand Down Expand Up @@ -677,9 +781,14 @@ def _measurement(two_qubit_circuit: circuits.Circuit) -> np.ndarray:


def _create_parallel_rb_circuit(
qubits: Sequence[cirq.Qid], num_cliffords: int, c1: list
qubits: Sequence[cirq.Qid],
num_cliffords: int,
c1: list[list[ops.SingleQubitCliffordGate]],
rng: np.random.Generator | None = None,
) -> cirq.Circuit:
sequences_to_zip = [_random_single_q_clifford(qubit, num_cliffords, c1) for qubit in qubits]
sequences_to_zip = [
_random_single_q_clifford(qubit, num_cliffords, c1, rng) for qubit in qubits
]
# Ensure each sequence has the same number of moments.
num_moments = max(len(sequence) for sequence in sequences_to_zip)
for q, sequence in zip(qubits, sequences_to_zip):
Expand Down Expand Up @@ -730,11 +839,14 @@ def _two_qubit_clifford_matrices(q_0: cirq.Qid, q_1: cirq.Qid, cliffords: Cliffo


def _random_single_q_clifford(
qubit: cirq.Qid, num_cfds: int, cfds: Sequence[Sequence[cirq.ops.SingleQubitCliffordGate]]
qubit: cirq.Qid,
num_cfds: int,
cfds: Sequence[Sequence[cirq.ops.SingleQubitCliffordGate]],
rng: np.random.Generator | None = None,
) -> list[cirq.Operation]:
clifford_group_size = 24
operations = [[gate.to_phased_xz_gate()(qubit) for gate in gates] for gates in cfds]
gate_ids = np.random.choice(clifford_group_size, num_cfds).tolist()
choice_fn = rng.choice if rng else np.random.choice
gate_ids = choice_fn(len(cfds), num_cfds).tolist()
adjoint = _reduce_gate_seq([gate for gate_id in gate_ids for gate in cfds[gate_id]]) ** -1
return [op for gate_id in gate_ids for op in operations[gate_id]] + [
adjoint.to_phased_xz_gate()(qubit)
Expand Down
24 changes: 20 additions & 4 deletions cirq-core/cirq/experiments/qubit_characterizations_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

from __future__ import annotations

import os
from unittest import mock

import matplotlib.pyplot as plt
import numpy as np
import pytest
Expand Down Expand Up @@ -104,6 +107,7 @@ def check_distinct(unitaries):
assert num_x <= 1


@mock.patch.dict(os.environ, clear='CIRQ_TESTING')
def test_single_qubit_randomized_benchmarking():
# Check that the ground state population at the end of the Clifford
# sequences is always unity.
Expand All @@ -116,7 +120,8 @@ 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():
@mock.patch.dict(os.environ, clear='CIRQ_TESTING')
def test_parallel_single_qubit_parallel_single_qubit_randomized_benchmarking():
# Check that the ground state population at the end of the Clifford
# sequences is always unity.
simulator = sim.Simulator()
Expand Down Expand Up @@ -229,13 +234,24 @@ def test_tomography_plot_raises_for_incorrect_number_of_axes():
result.plot(axes)


def test_single_qubit_cliffords_gateset():
@pytest.mark.parametrize('num_cliffords', range(5, 10))
@pytest.mark.parametrize('use_xy_basis', [False, True])
@pytest.mark.parametrize('strict_basis', [False, True])
def test_single_qubit_cliffords_gateset(num_cliffords, use_xy_basis, strict_basis):
qubits = [GridQubit(0, i) for i in range(4)]
clifford_group = cirq.experiments.qubit_characterizations._single_qubit_cliffords()
c1_in_xy = cirq.experiments.qubit_characterizations.RBParameters(
use_xy_basis=use_xy_basis, strict_basis=strict_basis
).gateset()
if strict_basis:
assert len(c1_in_xy) == 20
else:
assert len(c1_in_xy) == 24
c = cirq.experiments.qubit_characterizations._create_parallel_rb_circuit(
qubits, 5, clifford_group.c1_in_xy
qubits, num_cliffords, c1_in_xy
)
device = cirq.testing.ValidatingTestDevice(
qubits=qubits, allowed_gates=(cirq.ops.PhasedXZGate, cirq.MeasurementGate)
)
device.validate_circuit(c)

assert len(c) == num_cliffords + 2
13 changes: 8 additions & 5 deletions cirq-core/cirq/experiments/two_qubit_xeb.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@
from cirq._compat import cached_method
from cirq.experiments import random_quantum_circuit_generation as rqcg
from cirq.experiments.qubit_characterizations import (
parallel_single_qubit_randomized_benchmarking,
parallel_single_qubit_rb,
ParallelRandomizedBenchmarkingResult,
RBParameters,
)
from cirq.experiments.xeb_fitting import (
benchmark_2q_xeb_fidelities,
Expand Down Expand Up @@ -586,12 +587,14 @@ def run_rb_and_xeb(

qubits, pairs = qubits_and_pairs(sampler, qubits, pairs)

rb = parallel_single_qubit_randomized_benchmarking(
rb = parallel_single_qubit_rb(
sampler=sampler,
qubits=qubits,
repetitions=repetitions,
num_circuits=num_circuits,
num_clifford_range=num_clifford_range,
parameters=RBParameters(
num_circuits=num_circuits,
repetitions=repetitions,
num_clifford_range=num_clifford_range,
),
)

xeb = parallel_two_qubit_xeb(
Expand Down
6 changes: 4 additions & 2 deletions examples/qubit_characterizations_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ def main(minimum_cliffords=5, maximum_cliffords=20, cliffords_step=5):
clifford_range = range(minimum_cliffords, maximum_cliffords, cliffords_step)

# Clifford-based randomized benchmarking of single-qubit gates on q_0.
rb_result_1q = cirq.experiments.single_qubit_randomized_benchmarking(
simulator, q_0, num_clifford_range=clifford_range, repetitions=100
rb_result_1q = cirq.experiments.single_qubit_rb(
simulator,
q_0,
cirq.experiments.RBParameters(num_clifford_range=clifford_range, repetitions=100),
)
rb_result_1q.plot()

Expand Down