Skip to content

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

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 8 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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,
)
65 changes: 64 additions & 1 deletion 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 @@ -386,3 +389,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)
48 changes: 48 additions & 0 deletions cirq-core/cirq/experiments/two_qubit_xeb_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,51 @@ 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]]):
np.random.seed(0)
random.seed(0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please remove here and in test_parallel_two_qubit_xeb above.

Both tests pass random_state=0 so the global seeds should not matter.
To the contrary if we see any flakiness w/r to global seeds here that would show something is wrong with random_state handling.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

removed them, I forgot that I already figuredout why the methods produce different results for the same seed. the reason isn't treating random_state in a wrong way but that some of the subroutines are parallized. so the order of parallization changes the results. however the tests are not flaky I wrote them to be resilient to that.


with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()):
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)