From 9b4765d8a9e33d017b5e002d658f8548fb2a72fe Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Thu, 7 Nov 2024 18:00:20 +0000 Subject: [PATCH 1/6] update test --- .../cirq/experiments/two_qubit_xeb_test.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/experiments/two_qubit_xeb_test.py b/cirq-core/cirq/experiments/two_qubit_xeb_test.py index f7ef2a26d7b..35ae17b166d 100644 --- a/cirq-core/cirq/experiments/two_qubit_xeb_test.py +++ b/cirq-core/cirq/experiments/two_qubit_xeb_test.py @@ -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, From 9a48e3cd763c9633dce3cd1032390f7b58d3b658 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Thu, 7 Nov 2024 18:14:48 +0000 Subject: [PATCH 2/6] add option to specify pairs for parallel two-qubit xeb --- cirq-core/cirq/experiments/two_qubit_xeb.py | 36 ++++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/cirq-core/cirq/experiments/two_qubit_xeb.py b/cirq-core/cirq/experiments/two_qubit_xeb.py index 5cb78c5b859..d5cd4fee7f7 100644 --- a/cirq-core/cirq/experiments/two_qubit_xeb.py +++ b/cirq-core/cirq/experiments/two_qubit_xeb.py @@ -351,6 +351,7 @@ def plot_histogram( def parallel_xeb_workflow( sampler: 'cirq.Sampler', qubits: Optional[Sequence['cirq.GridQubit']] = None, + pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None, entangling_gate: 'cirq.Gate' = ops.CZ, n_repetitions: int = 10**4, n_combinations: int = 10, @@ -358,6 +359,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, + pool: Optional['multiprocessing.pool.Pool'] = None, **plot_kwargs, ) -> Tuple[pd.DataFrame, Sequence['cirq.Circuit'], pd.DataFrame]: """A utility method that runs the full XEB workflow. @@ -365,6 +367,7 @@ def parallel_xeb_workflow( 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. + pairs: Pairs to use. If not specified, use all pairs between adjacent qubits. entangling_gate: The entangling gate to use. n_repetitions: The number of repetitions to use. n_combinations: The number of combinations to generate. @@ -373,6 +376,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. + pool: An optional multiprocessing pool. **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'. Returns: @@ -389,13 +393,23 @@ 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 - ) + 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() + for pair in pairs: + qubits_set.add(pair[0]) + qubits_set.add(pair[1]) + qubits = list(qubits_set) + + if pairs is None: + pairs = [ + pair for pair in itertools.combinations(qubits, 2) if _manhattan_distance(*pair) == 1 + ] + + 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) @@ -426,7 +440,7 @@ def parallel_xeb_workflow( ) fids = benchmark_2q_xeb_fidelities( - sampled_df=sampled_df, circuits=circuit_library, cycle_depths=cycle_depths + sampled_df=sampled_df, circuits=circuit_library, cycle_depths=cycle_depths, pool=pool ) return fids, circuit_library, sampled_df @@ -435,6 +449,7 @@ def parallel_xeb_workflow( def parallel_two_qubit_xeb( sampler: 'cirq.Sampler', qubits: Optional[Sequence['cirq.GridQubit']] = None, + pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None, entangling_gate: 'cirq.Gate' = ops.CZ, n_repetitions: int = 10**4, n_combinations: int = 10, @@ -449,6 +464,7 @@ def parallel_two_qubit_xeb( 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. + pairs: Pairs to use. If not specified, use all pairs between adjacent qubits. entangling_gate: The entangling gate to use. n_repetitions: The number of repetitions to use. n_combinations: The number of combinations to generate. @@ -466,6 +482,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, @@ -481,6 +498,7 @@ def parallel_two_qubit_xeb( def run_rb_and_xeb( sampler: 'cirq.Sampler', qubits: Optional[Sequence['cirq.GridQubit']] = None, + pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None, repetitions: int = 10**3, num_circuits: int = 20, num_clifford_range: Sequence[int] = tuple( @@ -496,6 +514,7 @@ def run_rb_and_xeb( 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. + pairs: Pairs to use. If not specified, use all pairs between adjacent qubits. 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. @@ -527,6 +546,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, From d526de4d03ae7404354f9be901ef0f3a3ce670a6 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Thu, 7 Nov 2024 18:30:47 +0000 Subject: [PATCH 3/6] remove unrelated changes --- cirq-core/cirq/experiments/two_qubit_xeb.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cirq-core/cirq/experiments/two_qubit_xeb.py b/cirq-core/cirq/experiments/two_qubit_xeb.py index d5cd4fee7f7..1dc0a11595d 100644 --- a/cirq-core/cirq/experiments/two_qubit_xeb.py +++ b/cirq-core/cirq/experiments/two_qubit_xeb.py @@ -359,7 +359,6 @@ 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, - pool: Optional['multiprocessing.pool.Pool'] = None, **plot_kwargs, ) -> Tuple[pd.DataFrame, Sequence['cirq.Circuit'], pd.DataFrame]: """A utility method that runs the full XEB workflow. @@ -376,7 +375,6 @@ 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. - pool: An optional multiprocessing pool. **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'. Returns: @@ -440,7 +438,7 @@ def parallel_xeb_workflow( ) fids = benchmark_2q_xeb_fidelities( - sampled_df=sampled_df, circuits=circuit_library, cycle_depths=cycle_depths, pool=pool + sampled_df=sampled_df, circuits=circuit_library, cycle_depths=cycle_depths ) return fids, circuit_library, sampled_df From 4aff27ffb317b1d2993944c96f7aad0f3277d09c Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Fri, 8 Nov 2024 01:04:22 +0000 Subject: [PATCH 4/6] fix test, nits --- cirq-core/cirq/experiments/two_qubit_xeb.py | 75 +++++++++++++-------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/cirq-core/cirq/experiments/two_qubit_xeb.py b/cirq-core/cirq/experiments/two_qubit_xeb.py index 1dc0a11595d..3ae837db060 100644 --- a/cirq-core/cirq/experiments/two_qubit_xeb.py +++ b/cirq-core/cirq/experiments/two_qubit_xeb.py @@ -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.""" @@ -351,7 +391,6 @@ def plot_histogram( def parallel_xeb_workflow( sampler: 'cirq.Sampler', qubits: Optional[Sequence['cirq.GridQubit']] = None, - pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None, entangling_gate: 'cirq.Gate' = ops.CZ, n_repetitions: int = 10**4, n_combinations: int = 10, @@ -359,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. @@ -366,7 +406,6 @@ def parallel_xeb_workflow( 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. - pairs: Pairs to use. If not specified, use all pairs between adjacent qubits. entangling_gate: The entangling gate to use. n_repetitions: The number of repetitions to use. n_combinations: The number of combinations to generate. @@ -375,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: @@ -390,23 +430,7 @@ def parallel_xeb_workflow( """ rs = value.parse_random_state(random_state) - 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() - for pair in pairs: - qubits_set.add(pair[0]) - qubits_set.add(pair[1]) - qubits = list(qubits_set) - - if pairs is None: - pairs = [ - 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: @@ -447,7 +471,6 @@ def parallel_xeb_workflow( def parallel_two_qubit_xeb( sampler: 'cirq.Sampler', qubits: Optional[Sequence['cirq.GridQubit']] = None, - pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None, entangling_gate: 'cirq.Gate' = ops.CZ, n_repetitions: int = 10**4, n_combinations: int = 10, @@ -455,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. @@ -462,7 +486,6 @@ def parallel_two_qubit_xeb( 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. - pairs: Pairs to use. If not specified, use all pairs between adjacent qubits. entangling_gate: The entangling gate to use. n_repetitions: The number of repetitions to use. n_combinations: The number of combinations to generate. @@ -471,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. @@ -496,7 +520,6 @@ def parallel_two_qubit_xeb( def run_rb_and_xeb( sampler: 'cirq.Sampler', qubits: Optional[Sequence['cirq.GridQubit']] = None, - pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None, repetitions: int = 10**3, num_circuits: int = 20, num_clifford_range: Sequence[int] = tuple( @@ -506,13 +529,13 @@ 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. 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. - pairs: Pairs to use. If not specified, use all pairs between adjacent qubits. 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. @@ -520,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. @@ -528,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, From 525971572adcd0e44e26d6f9b98f07fb9410f0d6 Mon Sep 17 00:00:00 2001 From: Noureldin Date: Fri, 8 Nov 2024 10:08:25 -0800 Subject: [PATCH 5/6] Update cirq-core/cirq/experiments/two_qubit_xeb.py --- cirq-core/cirq/experiments/two_qubit_xeb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/experiments/two_qubit_xeb.py b/cirq-core/cirq/experiments/two_qubit_xeb.py index 99a9f6591fe..b9e33d99dbd 100644 --- a/cirq-core/cirq/experiments/two_qubit_xeb.py +++ b/cirq-core/cirq/experiments/two_qubit_xeb.py @@ -53,7 +53,7 @@ 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( +def qubits_and_pairs( sampler: 'cirq.Sampler', qubits: Optional[Sequence['cirq.GridQubit']] = None, pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None, From 00917c021988ca1995e3eb1e8366c46c45268c4e Mon Sep 17 00:00:00 2001 From: Noureldin Date: Fri, 8 Nov 2024 10:09:32 -0800 Subject: [PATCH 6/6] Make `qubits_and_pairs` public --- cirq-core/cirq/experiments/two_qubit_xeb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/experiments/two_qubit_xeb.py b/cirq-core/cirq/experiments/two_qubit_xeb.py index b9e33d99dbd..78c7055334e 100644 --- a/cirq-core/cirq/experiments/two_qubit_xeb.py +++ b/cirq-core/cirq/experiments/two_qubit_xeb.py @@ -433,7 +433,7 @@ def parallel_xeb_workflow( """ rs = value.parse_random_state(random_state) - qubits, pairs = _qubits_and_pairs(sampler, qubits, pairs) + qubits, pairs = qubits_and_pairs(sampler, qubits, pairs) graph = nx.Graph(pairs) if ax is not None: @@ -555,7 +555,7 @@ def run_rb_and_xeb( ValueError: If qubits are not specified and the sampler has no device. """ - qubits, pairs = _qubits_and_pairs(sampler, qubits, pairs) + qubits, pairs = qubits_and_pairs(sampler, qubits, pairs) rb = parallel_single_qubit_randomized_benchmarking( sampler=sampler,