Skip to content
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

Add a convenience method that runs both RB and XEB #6471

Merged
merged 8 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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 @@ -69,4 +69,5 @@
InferredXEBResult,
TwoQubitXEBResult,
parallel_two_qubit_xeb,
run_rb_and_xeb,
)
72 changes: 70 additions & 2 deletions cirq-core/cirq/experiments/two_qubit_xeb.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
from cirq.experiments.xeb_fitting import benchmark_2q_xeb_fidelities
from cirq.experiments.xeb_fitting import fit_exponential_decays, exponential_decay
from cirq.experiments import random_quantum_circuit_generation as rqcg
from cirq.experiments.qubit_characterizations import ParallelRandomizedBenchmarkingResult
from cirq.experiments.qubit_characterizations import (
ParallelRandomizedBenchmarkingResult,
parallel_single_qubit_randomized_benchmarking,
)
from cirq.qis import noise_utils
from cirq._compat import cached_method

Expand Down Expand Up @@ -172,7 +175,12 @@ def pauli_error(self) -> Dict[Tuple['cirq.GridQubit', 'cirq.GridQubit'], float]:

@dataclass(frozen=True)
class InferredXEBResult:
"""Uses the results from XEB and RB to compute inferred two-qubit Pauli errors."""
"""Uses the results from XEB and RB to compute inferred two-qubit Pauli errors.

The result of running just XEB combines both two-qubit and single-qubit error rates,
this class computes inferred errors which are the result of removing the single qubit errors
from the two-qubit errors.
"""

rb_result: ParallelRandomizedBenchmarkingResult
xeb_result: TwoQubitXEBResult
Expand Down Expand Up @@ -386,3 +394,63 @@ def parallel_two_qubit_xeb(
)

return TwoQubitXEBResult(fit_exponential_decays(fids))


def run_rb_and_xeb(
sampler: 'cirq.Sampler',
qubits: Optional[Sequence['cirq.GridQubit']] = None,
repetitions: int = 10**3,
num_circuits: int = 20,
num_clifford_range: Sequence[int] = tuple(
np.logspace(np.log10(5), np.log10(1000), 5, dtype=int)
),
entangling_gate: 'cirq.Gate' = ops.CZ,
depths_xeb: Sequence[int] = tuple(np.arange(3, 100, 20)),
xeb_combinations: int = 10,
random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = 42,
) -> InferredXEBResult:
"""A convenience method that runs both RB and XEB workflows.

Args:
sampler: The quantum engine or simulator to run the circuits.
qubits: Qubits under test. If none, uses all qubits on the sampler's device.
repetitions: The number of repetitions to use for RB and XEB.
num_circuits: The number of circuits to generate for RB and XEB.
num_clifford_range: The different numbers of Cliffords in the RB study.
entangling_gate: The entangling gate to use.
depths_xeb: The cycle depths to use for XEB.
xeb_combinations: The number of combinations to generate for XEB.
random_state: The random state to use.

Returns:
An InferredXEBResult object representing the results of the experiment.

Raises:
ValueError: If qubits are not specified and the sampler has no device.
"""

if qubits is None:
qubits = _grid_qubits_for_sampler(sampler)
if qubits is None:
raise ValueError("Couldn't determine qubits from sampler. Please specify them.")

rb = parallel_single_qubit_randomized_benchmarking(
sampler=sampler,
qubits=qubits,
repetitions=repetitions,
num_circuits=num_circuits,
num_clifford_range=num_clifford_range,
)

xeb = parallel_two_qubit_xeb(
sampler=sampler,
qubits=qubits,
entangling_gate=entangling_gate,
n_repetitions=repetitions,
n_circuits=num_circuits,
cycle_depths=depths_xeb,
n_combinations=xeb_combinations,
random_state=random_state,
)

return InferredXEBResult(rb, xeb)
72 changes: 55 additions & 17 deletions cirq-core/cirq/experiments/two_qubit_xeb_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
# limitations under the License.
"""Wraps Parallel Two Qubit XEB into a few convenience methods."""
from typing import Optional, Sequence, Dict
from contextlib import redirect_stdout, redirect_stderr
import itertools
import io
import random

import matplotlib.pyplot as plt

Expand Down Expand Up @@ -90,22 +88,18 @@ def test_parallel_two_qubit_xeb_simulator_without_processor_fails():
],
)
def test_parallel_two_qubit_xeb(sampler: cirq.Sampler, qubits: Optional[Sequence[cirq.GridQubit]]):
np.random.seed(0)
random.seed(0)

with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()):
res = cirq.experiments.parallel_two_qubit_xeb(
sampler=sampler,
qubits=qubits,
n_repetitions=100,
n_combinations=1,
n_circuits=1,
cycle_depths=[3, 4, 5],
random_state=0,
)
res = cirq.experiments.parallel_two_qubit_xeb(
sampler=sampler,
qubits=qubits,
n_repetitions=100,
n_combinations=1,
n_circuits=1,
cycle_depths=[3, 4, 5],
random_state=0,
)

got = [res.xeb_error(*reversed(pair)) for pair in res.all_qubit_pairs]
np.testing.assert_allclose(got, 0.1, atol=1e-1)
got = [res.xeb_error(*reversed(pair)) for pair in res.all_qubit_pairs]
np.testing.assert_allclose(got, 0.1, atol=1e-1)


@pytest.mark.usefixtures('closefigures')
Expand Down Expand Up @@ -264,3 +258,47 @@ def test_inferred_plots(ax, target_error, kind):
combined_results.plot_histogram(target_error=target_error, kind=kind, ax=ax)
else:
combined_results.plot_histogram(target_error=target_error, kind=kind, ax=ax)


@pytest.mark.parametrize(
'sampler,qubits',
[
(
cirq.DensityMatrixSimulator(
seed=0, noise=cirq.ConstantQubitNoiseModel(cirq.amplitude_damp(0.1))
),
cirq.GridQubit.rect(3, 2, 4, 3),
),
(
DensityMatrixSimulatorWithProcessor(
seed=0, noise=cirq.ConstantQubitNoiseModel(cirq.amplitude_damp(0.1))
),
None,
),
],
)
def test_run_rb_and_xeb(sampler: cirq.Sampler, qubits: Optional[Sequence[cirq.GridQubit]]):
res = cirq.experiments.run_rb_and_xeb(
sampler=sampler,
qubits=qubits,
repetitions=100,
num_clifford_range=tuple(np.arange(3, 10, 1)),
xeb_combinations=1,
num_circuits=1,
depths_xeb=(3, 4, 5),
random_state=0,
)
np.testing.assert_allclose(
[res.xeb_result.xeb_error(*pair) for pair in res.all_qubit_pairs], 0.1, atol=1e-1
)


def test_run_rb_and_xeb_without_processor_fails():
sampler = (
cirq.DensityMatrixSimulator(
seed=0, noise=cirq.ConstantQubitNoiseModel(cirq.amplitude_damp(0.1))
),
)

with pytest.raises(ValueError):
_ = cirq.experiments.run_rb_and_xeb(sampler=sampler)