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

Parallel XEB: Add option to specify pairs #6787

Open
wants to merge 4 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
63 changes: 51 additions & 12 deletions cirq-core/cirq/experiments/two_qubit_xeb.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,46 @@ def _manhattan_distance(qubit1: 'cirq.GridQubit', qubit2: 'cirq.GridQubit') -> i
return abs(qubit1.row - qubit2.row) + abs(qubit1.col - qubit2.col)


def _qubits_and_pairs(
sampler: 'cirq.Sampler',
qubits: Optional[Sequence['cirq.GridQubit']] = None,
pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None,
) -> Tuple[Sequence['cirq.GridQubit'], Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]]:
"""Extract qubits and pairs from sampler.


If qubits are not provided, then they are extracted from the pairs (if given) or the sampler.
If pairs are not provided then all pairs of adjacent qubits are used.

Args:
sampler: The quantum engine or simulator to run the circuits.
qubits: Optional list of qubits.
pairs: Optional list of pair to use.

Returns:
- Qubits to use.
- Pairs of qubits to use.

Raises:
ValueError: If qubits are not specified and can't be deduced from other arguments.
"""
if qubits is None:
if pairs is None:
qubits = _grid_qubits_for_sampler(sampler)
if qubits is None:
raise ValueError("Couldn't determine qubits from sampler. Please specify them.")
else:
qubits_set = set(itertools.chain(*pairs))
qubits = list(qubits_set)

if pairs is None:
pairs = [
pair for pair in itertools.combinations(qubits, 2) if _manhattan_distance(*pair) == 1
]

return qubits, pairs


@dataclass(frozen=True)
class TwoQubitXEBResult:
"""Results from an XEB experiment."""
Expand Down Expand Up @@ -358,6 +398,7 @@ def parallel_xeb_workflow(
cycle_depths: Sequence[int] = (5, 25, 50, 100, 200, 300),
random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,
ax: Optional[plt.Axes] = None,
pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None,
**plot_kwargs,
) -> Tuple[pd.DataFrame, Sequence['cirq.Circuit'], pd.DataFrame]:
"""A utility method that runs the full XEB workflow.
Expand All @@ -373,6 +414,7 @@ def parallel_xeb_workflow(
random_state: The random state to use.
ax: the plt.Axes to plot the device layout on. If not given,
no plot is created.
pairs: Pairs to use. If not specified, use all pairs between adjacent qubits.
**plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.

Returns:
Expand All @@ -388,14 +430,8 @@ def parallel_xeb_workflow(
"""
rs = value.parse_random_state(random_state)

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.")

graph = nx.Graph(
pair for pair in itertools.combinations(qubits, 2) if _manhattan_distance(*pair) == 1
)
qubits, pairs = _qubits_and_pairs(sampler, qubits, pairs)
graph = nx.Graph(pairs)

if ax is not None:
nx.draw_networkx(graph, pos={q: (q.row, q.col) for q in qubits}, ax=ax)
Expand Down Expand Up @@ -442,6 +478,7 @@ def parallel_two_qubit_xeb(
cycle_depths: Sequence[int] = (5, 25, 50, 100, 200, 300),
random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,
ax: Optional[plt.Axes] = None,
pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None,
**plot_kwargs,
) -> TwoQubitXEBResult:
"""A convenience method that runs the full XEB workflow.
Expand All @@ -457,6 +494,7 @@ def parallel_two_qubit_xeb(
random_state: The random state to use.
ax: the plt.Axes to plot the device layout on. If not given,
no plot is created.
pairs: Pairs to use. If not specified, use all pairs between adjacent qubits.
**plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.
Returns:
A TwoQubitXEBResult object representing the results of the experiment.
Expand All @@ -466,6 +504,7 @@ def parallel_two_qubit_xeb(
fids, *_ = parallel_xeb_workflow(
sampler=sampler,
qubits=qubits,
pairs=pairs,
entangling_gate=entangling_gate,
n_repetitions=n_repetitions,
n_combinations=n_combinations,
Expand All @@ -490,6 +529,7 @@ def run_rb_and_xeb(
depths_xeb: Sequence[int] = (5, 25, 50, 100, 200, 300),
xeb_combinations: int = 10,
random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,
pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None,
) -> InferredXEBResult:
"""A convenience method that runs both RB and XEB workflows.

Expand All @@ -503,6 +543,7 @@ def run_rb_and_xeb(
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.
pairs: Pairs to use. If not specified, use all pairs between adjacent qubits.

Returns:
An InferredXEBResult object representing the results of the experiment.
Expand All @@ -511,10 +552,7 @@ def run_rb_and_xeb(
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.")
qubits, pairs = _qubits_and_pairs(sampler, qubits, pairs)

rb = parallel_single_qubit_randomized_benchmarking(
sampler=sampler,
Expand All @@ -527,6 +565,7 @@ def run_rb_and_xeb(
xeb = parallel_two_qubit_xeb(
sampler=sampler,
qubits=qubits,
pairs=pairs,
entangling_gate=entangling_gate,
n_repetitions=repetitions,
n_circuits=num_circuits,
Expand Down
21 changes: 19 additions & 2 deletions cirq-core/cirq/experiments/two_qubit_xeb_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,26 +261,43 @@ def test_inferred_plots(ax, target_error, kind):


@pytest.mark.parametrize(
'sampler,qubits',
'sampler,qubits,pairs',
[
(
cirq.DensityMatrixSimulator(
seed=0, noise=cirq.ConstantQubitNoiseModel(cirq.amplitude_damp(0.1))
),
cirq.GridQubit.rect(3, 2, 4, 3),
None,
),
(
cirq.DensityMatrixSimulator(
seed=0, noise=cirq.ConstantQubitNoiseModel(cirq.amplitude_damp(0.1))
),
None,
[
(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)),
(cirq.GridQubit(0, 0), cirq.GridQubit(1, 0)),
],
),
(
DensityMatrixSimulatorWithProcessor(
seed=0, noise=cirq.ConstantQubitNoiseModel(cirq.amplitude_damp(0.1))
),
None,
None,
),
],
)
def test_run_rb_and_xeb(sampler: cirq.Sampler, qubits: Optional[Sequence[cirq.GridQubit]]):
def test_run_rb_and_xeb(
sampler: cirq.Sampler,
qubits: Optional[Sequence[cirq.GridQubit]],
pairs: Optional[Sequence[tuple[cirq.GridQubit, cirq.GridQubit]]],
):
res = cirq.experiments.run_rb_and_xeb(
sampler=sampler,
qubits=qubits,
pairs=pairs,
repetitions=100,
num_clifford_range=tuple(np.arange(3, 10, 1)),
xeb_combinations=1,
Expand Down