Skip to content

Commit 598c84f

Browse files
Add a convenience method that runs both RB and XEB (#6471)
1 parent e76702f commit 598c84f

File tree

3 files changed

+157
-23
lines changed

3 files changed

+157
-23
lines changed

cirq-core/cirq/experiments/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,5 @@
6969
InferredXEBResult,
7070
TwoQubitXEBResult,
7171
parallel_two_qubit_xeb,
72+
run_rb_and_xeb,
7273
)

cirq-core/cirq/experiments/two_qubit_xeb.py

+101-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
15+
"""Provides functions for running and analyzing two-qubit XEB experiments."""
1416
from typing import Sequence, TYPE_CHECKING, Optional, Tuple, Dict, cast, Mapping
1517

1618
from dataclasses import dataclass
@@ -28,7 +30,10 @@
2830
from cirq.experiments.xeb_fitting import benchmark_2q_xeb_fidelities
2931
from cirq.experiments.xeb_fitting import fit_exponential_decays, exponential_decay
3032
from cirq.experiments import random_quantum_circuit_generation as rqcg
31-
from cirq.experiments.qubit_characterizations import ParallelRandomizedBenchmarkingResult
33+
from cirq.experiments.qubit_characterizations import (
34+
ParallelRandomizedBenchmarkingResult,
35+
parallel_single_qubit_randomized_benchmarking,
36+
)
3237
from cirq.qis import noise_utils
3338
from cirq._compat import cached_method
3439

@@ -71,6 +76,9 @@ def plot_heatmap(self, ax: Optional[plt.Axes] = None, **plot_kwargs) -> plt.Axes
7176
ax: the plt.Axes to plot on. If not given, a new figure is created,
7277
plotted on, and shown.
7378
**plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.
79+
80+
Returns:
81+
The plt.Axes that was plotted on.
7482
"""
7583
show_plot = not ax
7684
if not isinstance(ax, plt.Axes):
@@ -101,6 +109,9 @@ def plot_fitted_exponential(
101109
ax: the plt.Axes to plot on. If not given, a new figure is created,
102110
plotted on, and shown.
103111
**plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.
112+
113+
Returns:
114+
The plt.Axes that was plotted on.
104115
"""
105116
show_plot = not ax
106117
if not isinstance(ax, plt.Axes):
@@ -143,12 +154,15 @@ def all_errors(self) -> Dict[Tuple['cirq.GridQubit', 'cirq.GridQubit'], float]:
143154
return {(q0, q1): self.xeb_error(q0, q1) for q0, q1 in self.all_qubit_pairs}
144155

145156
def plot_histogram(self, ax: Optional[plt.Axes] = None, **plot_kwargs) -> plt.Axes:
146-
"""plot a histogram of all xeb errors
157+
"""plot a histogram of all xeb errors.
147158
148159
Args:
149160
ax: the plt.Axes to plot on. If not given, a new figure is created,
150161
plotted on, and shown.
151162
**plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.
163+
164+
Returns:
165+
The plt.Axes that was plotted on.
152166
"""
153167
fig = None
154168
if ax is None:
@@ -172,7 +186,12 @@ def pauli_error(self) -> Dict[Tuple['cirq.GridQubit', 'cirq.GridQubit'], float]:
172186

173187
@dataclass(frozen=True)
174188
class InferredXEBResult:
175-
"""Uses the results from XEB and RB to compute inferred two-qubit Pauli errors."""
189+
"""Uses the results from XEB and RB to compute inferred two-qubit Pauli errors.
190+
191+
The result of running just XEB combines both two-qubit and single-qubit error rates,
192+
this class computes inferred errors which are the result of removing the single qubit errors
193+
from the two-qubit errors.
194+
"""
176195

177196
rb_result: ParallelRandomizedBenchmarkingResult
178197
xeb_result: TwoQubitXEBResult
@@ -226,6 +245,19 @@ def inferred_xeb_error(self) -> Mapping[Tuple['cirq.GridQubit', 'cirq.GridQubit'
226245
def _target_errors(
227246
self, target_error: str
228247
) -> Mapping[Tuple['cirq.GridQubit', 'cirq.GridQubit'], float]:
248+
"""Returns requested error.
249+
250+
The requested error must be one of 'pauli', 'decay_constant', or 'xeb'.
251+
252+
Args:
253+
target_error: The error to draw.
254+
255+
Returns:
256+
A mapping of qubit pairs to the requested error.
257+
258+
Raises:
259+
ValueError: If the requested error is not one of 'pauli', 'decay_constant', or 'xeb'.
260+
"""
229261
error_funcs = {
230262
'pauli': self.inferred_pauli_error,
231263
'decay_constant': self.inferred_decay_constant,
@@ -270,12 +302,15 @@ def plot_histogram(
270302
271303
Args:
272304
target_error: The error to draw. Must be one of 'xeb', 'pauli', or 'decay_constant'
273-
kind: Whether to plot the single-qubit RB errors ('single_qubit') or the
274-
two-qubit inferred errors ('two_qubit') or both ('both').
275305
ax: the plt.Axes to plot on. If not given, a new figure is created,
276306
plotted on, and shown.
307+
kind: Whether to plot the single-qubit RB errors ('single_qubit') or the
308+
two-qubit inferred errors ('two_qubit') or both ('both').
277309
**plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.
278310
311+
Returns:
312+
The plt.Axes that was plotted on.
313+
279314
Raises:
280315
ValueError: If
281316
- `kind` is not one of 'single_qubit', 'two_qubit', or 'both'.
@@ -320,7 +355,7 @@ def parallel_two_qubit_xeb(
320355
n_combinations: int = 10,
321356
n_circuits: int = 20,
322357
cycle_depths: Sequence[int] = tuple(np.arange(3, 100, 20)),
323-
random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = 42,
358+
random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,
324359
ax: Optional[plt.Axes] = None,
325360
**plot_kwargs,
326361
) -> TwoQubitXEBResult:
@@ -386,3 +421,63 @@ def parallel_two_qubit_xeb(
386421
)
387422

388423
return TwoQubitXEBResult(fit_exponential_decays(fids))
424+
425+
426+
def run_rb_and_xeb(
427+
sampler: 'cirq.Sampler',
428+
qubits: Optional[Sequence['cirq.GridQubit']] = None,
429+
repetitions: int = 10**3,
430+
num_circuits: int = 20,
431+
num_clifford_range: Sequence[int] = tuple(
432+
np.logspace(np.log10(5), np.log10(1000), 5, dtype=int)
433+
),
434+
entangling_gate: 'cirq.Gate' = ops.CZ,
435+
depths_xeb: Sequence[int] = tuple(np.arange(3, 100, 20)),
436+
xeb_combinations: int = 10,
437+
random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,
438+
) -> InferredXEBResult:
439+
"""A convenience method that runs both RB and XEB workflows.
440+
441+
Args:
442+
sampler: The quantum engine or simulator to run the circuits.
443+
qubits: Qubits under test. If none, uses all qubits on the sampler's device.
444+
repetitions: The number of repetitions to use for RB and XEB.
445+
num_circuits: The number of circuits to generate for RB and XEB.
446+
num_clifford_range: The different numbers of Cliffords in the RB study.
447+
entangling_gate: The entangling gate to use.
448+
depths_xeb: The cycle depths to use for XEB.
449+
xeb_combinations: The number of combinations to generate for XEB.
450+
random_state: The random state to use.
451+
452+
Returns:
453+
An InferredXEBResult object representing the results of the experiment.
454+
455+
Raises:
456+
ValueError: If qubits are not specified and the sampler has no device.
457+
"""
458+
459+
if qubits is None:
460+
qubits = _grid_qubits_for_sampler(sampler)
461+
if qubits is None:
462+
raise ValueError("Couldn't determine qubits from sampler. Please specify them.")
463+
464+
rb = parallel_single_qubit_randomized_benchmarking(
465+
sampler=sampler,
466+
qubits=qubits,
467+
repetitions=repetitions,
468+
num_circuits=num_circuits,
469+
num_clifford_range=num_clifford_range,
470+
)
471+
472+
xeb = parallel_two_qubit_xeb(
473+
sampler=sampler,
474+
qubits=qubits,
475+
entangling_gate=entangling_gate,
476+
n_repetitions=repetitions,
477+
n_circuits=num_circuits,
478+
cycle_depths=depths_xeb,
479+
n_combinations=xeb_combinations,
480+
random_state=random_state,
481+
)
482+
483+
return InferredXEBResult(rb, xeb)

cirq-core/cirq/experiments/two_qubit_xeb_test.py

+55-17
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@
1313
# limitations under the License.
1414
"""Wraps Parallel Two Qubit XEB into a few convenience methods."""
1515
from typing import Optional, Sequence, Dict
16-
from contextlib import redirect_stdout, redirect_stderr
1716
import itertools
1817
import io
19-
import random
2018

2119
import matplotlib.pyplot as plt
2220

@@ -90,22 +88,18 @@ def test_parallel_two_qubit_xeb_simulator_without_processor_fails():
9088
],
9189
)
9290
def test_parallel_two_qubit_xeb(sampler: cirq.Sampler, qubits: Optional[Sequence[cirq.GridQubit]]):
93-
np.random.seed(0)
94-
random.seed(0)
95-
96-
with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()):
97-
res = cirq.experiments.parallel_two_qubit_xeb(
98-
sampler=sampler,
99-
qubits=qubits,
100-
n_repetitions=100,
101-
n_combinations=1,
102-
n_circuits=1,
103-
cycle_depths=[3, 4, 5],
104-
random_state=0,
105-
)
91+
res = cirq.experiments.parallel_two_qubit_xeb(
92+
sampler=sampler,
93+
qubits=qubits,
94+
n_repetitions=100,
95+
n_combinations=1,
96+
n_circuits=1,
97+
cycle_depths=[3, 4, 5],
98+
random_state=0,
99+
)
106100

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

110104

111105
@pytest.mark.usefixtures('closefigures')
@@ -264,3 +258,47 @@ def test_inferred_plots(ax, target_error, kind):
264258
combined_results.plot_histogram(target_error=target_error, kind=kind, ax=ax)
265259
else:
266260
combined_results.plot_histogram(target_error=target_error, kind=kind, ax=ax)
261+
262+
263+
@pytest.mark.parametrize(
264+
'sampler,qubits',
265+
[
266+
(
267+
cirq.DensityMatrixSimulator(
268+
seed=0, noise=cirq.ConstantQubitNoiseModel(cirq.amplitude_damp(0.1))
269+
),
270+
cirq.GridQubit.rect(3, 2, 4, 3),
271+
),
272+
(
273+
DensityMatrixSimulatorWithProcessor(
274+
seed=0, noise=cirq.ConstantQubitNoiseModel(cirq.amplitude_damp(0.1))
275+
),
276+
None,
277+
),
278+
],
279+
)
280+
def test_run_rb_and_xeb(sampler: cirq.Sampler, qubits: Optional[Sequence[cirq.GridQubit]]):
281+
res = cirq.experiments.run_rb_and_xeb(
282+
sampler=sampler,
283+
qubits=qubits,
284+
repetitions=100,
285+
num_clifford_range=tuple(np.arange(3, 10, 1)),
286+
xeb_combinations=1,
287+
num_circuits=1,
288+
depths_xeb=(3, 4, 5),
289+
random_state=0,
290+
)
291+
np.testing.assert_allclose(
292+
[res.xeb_result.xeb_error(*pair) for pair in res.all_qubit_pairs], 0.1, atol=1e-1
293+
)
294+
295+
296+
def test_run_rb_and_xeb_without_processor_fails():
297+
sampler = (
298+
cirq.DensityMatrixSimulator(
299+
seed=0, noise=cirq.ConstantQubitNoiseModel(cirq.amplitude_damp(0.1))
300+
),
301+
)
302+
303+
with pytest.raises(ValueError):
304+
_ = cirq.experiments.run_rb_and_xeb(sampler=sampler)

0 commit comments

Comments
 (0)