1# Copyright 2019 The Cirq Developers
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from typing import (
16    Any,
17    Dict,
18    Iterable,
19    List,
20    Mapping,
21    NamedTuple,
22    Optional,
23    Sequence,
24    Set,
25    TYPE_CHECKING,
26    Tuple,
27    Union,
28)
29import dataclasses
30import numpy as np
31import scipy
32from matplotlib import pyplot as plt
33from cirq import circuits, devices, ops, protocols, sim, value, work
34
35if TYPE_CHECKING:
36    import cirq
37
38CrossEntropyPair = NamedTuple('CrossEntropyPair', [('num_cycle', int), ('xeb_fidelity', float)])
39SpecklePurityPair = NamedTuple('SpecklePurityPair', [('num_cycle', int), ('purity', float)])
40
41
42@dataclasses.dataclass
43class CrossEntropyDepolarizingModel:
44    """A depolarizing noise model for cross entropy benchmarking.
45
46    The depolarizing channel maps a density matrix ρ as
47
48        ρ → p_eff ρ + (1 - p_eff) I / D
49
50    where I / D is the maximally mixed state and p_eff is between 0 and 1.
51    It is used to model the effect of noise in certain quantum processes.
52    This class models the noise that results from the execution of multiple
53    layers, or cycles, of a random quantum circuit. In this model, p_eff for
54    the whole process is separated into a part that is independent of the number
55    of cycles (representing depolarization from state preparation and
56    measurement errors), and a part that exhibits exponential decay with the
57    number of cycles (representing depolarization from circuit execution
58    errors). So p_eff is modeled as
59
60        p_eff = S * p**d
61
62    where d is the number of cycles, or depth, S is the part that is independent
63    of depth, and p describes the exponential decay with depth. This class
64    stores S and p, as well as possibly the covariance in their estimation from
65    experimental data.
66
67    Attributes:
68        spam_depolarization: The depolarization constant for state preparation
69            and measurement, i.e., S in p_eff = S * p**d.
70        cycle_depolarization: The depolarization constant for circuit execution,
71            i.e., p in p_eff = S * p**d.
72        covariance: The estimated covariance in the estimation of
73            `spam_depolarization` and `cycle_depolarization`, in that order.
74    """
75
76    spam_depolarization: float
77    cycle_depolarization: float
78    covariance: Optional[np.ndarray] = None
79
80
81class SpecklePurityDepolarizingModel(CrossEntropyDepolarizingModel):
82    """A depolarizing noise model for speckle purity benchmarking.
83
84    In speckle purity benchmarking, the state ρ in the map
85
86        ρ → p_eff ρ + (1 - p_eff) I / D
87
88    is taken to be an unconstrained pure state. The purity of the resultant
89    state is p_eff**2. The aim of speckle purity benchmarking is to measure the
90    purity of the state resulting from applying a single XEB cycle to a pure
91    state. This value is stored in the `purity` property of this class.
92    """
93
94    @property
95    def purity(self) -> float:
96        """The purity. Equal to p**2, where p is the cycle depolarization."""
97        return self.cycle_depolarization ** 2
98
99
100@protocols.json_serializable_dataclass(frozen=True)
101class CrossEntropyResult:
102    """Results from a cross-entropy benchmarking (XEB) experiment.
103
104    May also include results from speckle purity benchmarking (SPB) performed
105    concomitantly.
106
107    Attributes:
108        data: A sequence of NamedTuples, each of which contains two fields:
109            num_cycle: the circuit depth as the number of cycles, where
110            a cycle consists of a layer of single-qubit gates followed
111            by a layer of two-qubit gates.
112            xeb_fidelity: the XEB fidelity after the given cycle number.
113        repetitions: The number of circuit repetitions used.
114        purity_data: A sequence of NamedTuples, each of which contains two
115            fields:
116            num_cycle: the circuit depth as the number of cycles, where
117            a cycle consists of a layer of single-qubit gates followed
118            by a layer of two-qubit gates.
119            purity: the purity after the given cycle number.
120    """
121
122    data: List[CrossEntropyPair]
123    repetitions: int
124    purity_data: Optional[List[SpecklePurityPair]] = None
125
126    def plot(self, ax: Optional[plt.Axes] = None, **plot_kwargs: Any) -> plt.Axes:
127        """Plots the average XEB fidelity vs the number of cycles.
128
129        Args:
130            ax: the plt.Axes to plot on. If not given, a new figure is created,
131                plotted on, and shown.
132            **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.
133        Returns:
134            The plt.Axes containing the plot.
135        """
136        show_plot = not ax
137        if not ax:
138            fig, ax = plt.subplots(1, 1, figsize=(8, 8))
139        num_cycles = [d.num_cycle for d in self.data]
140        fidelities = [d.xeb_fidelity for d in self.data]
141        ax.set_ylim([0, 1.1])
142        ax.plot(num_cycles, fidelities, 'ro-', **plot_kwargs)
143        ax.set_xlabel('Number of Cycles')
144        ax.set_ylabel('XEB Fidelity')
145        if show_plot:
146            fig.show()
147        return ax
148
149    def depolarizing_model(self) -> CrossEntropyDepolarizingModel:
150        """Fit a depolarizing error model for a cycle.
151
152        Fits an exponential model f = S * p**d, where d is the number of cycles
153        and f is the cross entropy fidelity for that number of cycles,
154        using nonlinear least squares.
155
156        Returns:
157            A CrossEntropyDepolarizingModel object, which has attributes
158            `spam_depolarization` representing the value S,
159            `cycle_depolarization` representing the value p, and `covariance`
160            representing the covariance in the estimation of S and p in that
161            order.
162        """
163        depths, fidelities = zip(*self.data)
164        params, covariance = _fit_exponential_decay(depths, fidelities)
165        return CrossEntropyDepolarizingModel(
166            spam_depolarization=params[0], cycle_depolarization=params[1], covariance=covariance
167        )
168
169    # TODO(#3388) Add documentation for Raises.
170    # pylint: disable=missing-raises-doc
171    def purity_depolarizing_model(self) -> CrossEntropyDepolarizingModel:
172        """Fit a depolarizing error model for a cycle to purity data.
173
174        Fits an exponential model f = S * p**d, where d is the number of cycles
175        and p**2 is the purity for that number of cycles, using nonlinear least
176        squares.
177
178        Returns:
179            A SpecklePurityDepolarizingModel object, which has attributes
180            `spam_depolarization` representing the value S,
181            `cycle_depolarization` representing the value p, and `covariance`
182            representing the covariance in the estimation of S and p in that
183            order. It also has the property `purity` representing the purity
184            p**2.
185        """
186        if self.purity_data is None:
187            raise ValueError(
188                'This CrossEntropyResult does not contain data '
189                'from speckle purity benchmarking, so the '
190                'purity depolarizing model cannot be computed.'
191            )
192        depths, purities = zip(*self.purity_data)
193        params, covariance = _fit_exponential_decay(depths, np.sqrt(purities))
194        return SpecklePurityDepolarizingModel(
195            spam_depolarization=params[0], cycle_depolarization=params[1], covariance=covariance
196        )
197
198    # pylint: enable=missing-raises-doc
199    @classmethod
200    def _from_json_dict_(cls, data, repetitions, **kwargs):
201        purity_data = kwargs.get('purity_data', None)
202        if purity_data is not None:
203            purity_data = [SpecklePurityPair(d, f) for d, f in purity_data]
204        return cls(
205            data=[CrossEntropyPair(d, f) for d, f in data],
206            repetitions=repetitions,
207            purity_data=purity_data,
208        )
209
210    def __repr__(self) -> str:
211        args = f'data={[tuple(p) for p in self.data]!r}, repetitions={self.repetitions!r}'
212        if self.purity_data is not None:
213            args += f', purity_data={[tuple(p) for p in self.purity_data]!r}'
214        return f'cirq.experiments.CrossEntropyResult({args})'
215
216
217def _fit_exponential_decay(x: Sequence[int], y: Sequence[float]) -> Tuple[np.ndarray, np.ndarray]:
218    """Fit an exponential model y = S * p**x using nonlinear least squares.
219
220    Args:
221        x: The x-values.
222        y: The y-values.
223
224    Returns:
225        The result of calling `scipy.optimize.curve_fit`. This is a tuple of
226        two arrays. The first array contains the fitted parameters, and the
227        second array is their estimated covariance.
228    """
229    # Get initial guess by linear least squares with logarithm of model
230    u = [a for a, b in zip(x, y) if b > 0]
231    v = [np.log(b) for b in y if b > 0]
232    fit = np.polynomial.polynomial.Polynomial.fit(u, v, 1).convert()
233    p0 = np.exp(fit.coef)
234
235    # Perform nonlinear least squares
236    def f(a, S, p):
237        return S * p ** a
238
239    return scipy.optimize.curve_fit(f, x, y, p0=p0)
240
241
242@dataclasses.dataclass
243class CrossEntropyResultDict(Mapping[Tuple['cirq.Qid', ...], CrossEntropyResult]):
244    """Per-qubit-tuple results from cross-entropy benchmarking.
245
246    Attributes:
247        results: Dictionary from qubit tuple to cross-entropy benchmarking
248            result for that tuple.
249    """
250
251    results: Dict[Tuple['cirq.Qid', ...], CrossEntropyResult]
252
253    def _json_dict_(self) -> Dict[str, Any]:
254        return {
255            'cirq_type': self.__class__.__name__,
256            'results': list(self.results.items()),
257        }
258
259    @classmethod
260    def _from_json_dict_(
261        cls, results: List[Tuple[List['cirq.Qid'], CrossEntropyResult]], **kwargs
262    ) -> 'CrossEntropyResultDict':
263        return cls(results={tuple(qubits): result for qubits, result in results})
264
265    def __repr__(self) -> str:
266        return f'cirq.experiments.CrossEntropyResultDict(results={self.results!r})'
267
268    def __getitem__(self, key: Tuple['cirq.Qid', ...]) -> CrossEntropyResult:
269        return self.results[key]
270
271    def __iter__(self):
272        return iter(self.results)
273
274    def __len__(self):
275        return len(self.results)
276
277
278def cross_entropy_benchmarking(
279    sampler: work.Sampler,
280    qubits: Sequence[ops.Qid],
281    *,
282    benchmark_ops: Sequence[ops.Moment] = None,
283    num_circuits: int = 20,
284    repetitions: int = 1000,
285    cycles: Union[int, Iterable[int]] = range(2, 103, 10),
286    scrambling_gates_per_cycle: List[List[ops.SingleQubitGate]] = None,
287    simulator: sim.Simulator = None,
288) -> CrossEntropyResult:
289    r"""Cross-entropy benchmarking (XEB) of multiple qubits.
290
291    A total of M random circuits are generated, each of which comprises N
292    layers where N = max('cycles') or 'cycles' if a single value is specified
293    for the 'cycles' parameter. Every layer contains randomly generated
294    single-qubit gates applied to each qubit, followed by a set of
295    user-defined benchmarking operations (e.g. a set of two-qubit gates).
296
297    Each circuit (circuit_m) from the M random circuits is further used to
298    generate a set of circuits {circuit_mn}, where circuit_mn is built from the
299    first n cycles of circuit_m. n spans all the values in 'cycles'.
300
301    For each fixed value n, the experiment performs the following:
302
303    1) Experimentally collect a number of bit-strings for each circuit_mn via
304    projective measurements in the z-basis.
305
306    2) Theoretically compute the expected bit-string probabilities
307    $P^{th, mn}_|...00>$,  $P^{th, mn}_|...01>$, $P^{th, mn}_|...10>$,
308    $P^{th, mn}_|...11>$ ... at the end of circuit_mn for all m and for all
309    possible bit-strings in the Hilbert space.
310
311    3) Compute an experimental XEB function for each circuit_mn:
312
313    $f_{mn}^{meas} = \langle D * P^{th, mn}_q - 1 \rangle$
314
315    where D is the number of states in the Hilbert space, $P^{th, mn}_q$ is the
316    theoretical probability of a bit-string q at the end of circuit_mn, and
317    $\langle \rangle$ corresponds to the ensemble average over all measured
318    bit-strings.
319
320    Then, take the average of $f_{mn}^{meas}$ over all circuit_mn with fixed
321    n to obtain:
322
323    $f_{n} ^ {meas} = (\sum_m f_{mn}^{meas}) / M$
324
325    4) Compute a theoretical XEB function for each circuit_mn:
326
327    $f_{mn}^{th} = D \sum_q (P^{th, mn}_q) ** 2 - 1$
328
329    where the summation goes over all possible bit-strings q in the Hilbert
330    space.
331
332    Similarly, we then average $f_m^{th}$ over all circuit_mn with fixed n to
333    obtain:
334
335    $f_{n} ^ {th} = (\sum_m f_{mn}^{th}) / M$
336
337    5) Calculate the XEB fidelity $\alpha_n$ at fixed n:
338
339    $\alpha_n = f_{n} ^ {meas} / f_{n} ^ {th}$
340
341    Args:
342        sampler: The quantum engine or simulator to run the circuits.
343        qubits: The qubits included in the XEB experiment.
344        benchmark_ops: A sequence of ops.Moment containing gate operations
345            between specific qubits which are to be benchmarked for fidelity.
346            If more than one ops.Moment is specified, the random circuits
347            will rotate between the ops.Moment's. As an example,
348            if benchmark_ops = [Moment([ops.CZ(q0, q1), ops.CZ(q2, q3)]),
349            Moment([ops.CZ(q1, q2)]) where q0, q1, q2 and q3 are instances of
350            Qid (such as GridQubits), each random circuit will apply CZ gate
351            between q0 and q1 plus CZ between q2 and q3 for the first cycle,
352            CZ gate between q1 and q2 for the second cycle, CZ between q0 and
353            q1 and CZ between q2 and q3 for the third cycle and so on. If
354            None, the circuits will consist only of single-qubit gates.
355        num_circuits: The total number of random circuits to be used.
356        repetitions: The number of measurements for each circuit to estimate
357            the bit-string probabilities.
358        cycles: The different numbers of circuit layers in the XEB study.
359            Could be a single or a collection of values.
360        scrambling_gates_per_cycle: If None (by default), the single-qubit
361            gates are chosen from X/2 ($\pi/2$ rotation around the X axis),
362            Y/2 ($\pi/2$ rotation around the Y axis) and (X + Y)/2 ($\pi/2$
363            rotation around an axis $\pi/4$ away from the X on the equator of
364            the Bloch sphere). Otherwise the single-qubit gates for each layer
365            are chosen from a list of possible choices (each choice is a list
366            of one or more single-qubit gates).
367        simulator: A simulator that calculates the bit-string probabilities
368            of the ideal circuit. By default, this is set to sim.Simulator().
369
370    Returns:
371        A CrossEntropyResult object that stores and plots the result.
372    """
373    simulator = sim.Simulator() if simulator is None else simulator
374    num_qubits = len(qubits)
375
376    if isinstance(cycles, int):
377        cycle_range = [cycles]
378    else:
379        cycle_range = list(cycles)
380
381    # These store the measured and simulated bit-string probabilities from
382    # all trials in two dictionaries. The keys of the dictionaries are the
383    # numbers of cycles. The values are 2D arrays with each row being the
384    # probabilities obtained from a single trial.
385    probs_meas = {n: np.zeros((num_circuits, 2 ** num_qubits)) for n in cycle_range}
386    probs_exp = {n: np.zeros((num_circuits, 2 ** num_qubits)) for n in cycle_range}
387
388    for k in range(num_circuits):
389
390        # Generates one random XEB circuit with max(num_cycle_range) cycles.
391        # Then the first n cycles of the circuit are taken to generate
392        # shorter circuits with n cycles (n taken from cycles). All of these
393        # circuits are stored in circuits_k.
394        circuits_k = _build_xeb_circuits(
395            qubits, cycle_range, scrambling_gates_per_cycle, benchmark_ops
396        )
397
398        # Run each circuit with the sampler to obtain a collection of
399        # bit-strings, from which the bit-string probabilities are estimated.
400        probs_meas_k = _measure_prob_distribution(sampler, repetitions, qubits, circuits_k)
401
402        # Simulate each circuit with the Cirq simulator to obtain the
403        # state vector at the end of each circuit, from which the
404        # theoretically expected bit-string probabilities are obtained.
405        probs_exp_k = []  # type: List[np.ndarray]
406        for circ_k in circuits_k:
407            res = simulator.simulate(circ_k, qubit_order=qubits)
408            state_probs = value.state_vector_to_probabilities(np.asarray(res.final_state_vector))
409            probs_exp_k.append(state_probs)
410
411        for i, num_cycle in enumerate(cycle_range):
412            probs_exp[num_cycle][k, :] = probs_exp_k[i]
413            probs_meas[num_cycle][k, :] = probs_meas_k[i]
414
415    fidelity_vals = _xeb_fidelities(probs_exp, probs_meas)
416    xeb_data = [CrossEntropyPair(c, k) for (c, k) in zip(cycle_range, fidelity_vals)]
417    return CrossEntropyResult(data=xeb_data, repetitions=repetitions)  # type: ignore
418
419
420def build_entangling_layers(
421    qubits: Sequence[devices.GridQubit], two_qubit_gate: ops.Gate
422) -> List[ops.Moment]:
423    """Builds a sequence of gates that entangle all pairs of qubits on a grid.
424
425    The qubits are restricted to be physically on a square grid with distinct
426    row and column indices (not every node of the grid needs to have a
427    qubit). To entangle all pairs of qubits, a user-specified two-qubit gate
428    is applied between each and every pair of qubit that are next to each
429    other. In general, a total of four sets of parallel operations are needed to
430    perform all possible two-qubit gates. We proceed as follows:
431
432    The first layer applies two-qubit gates to qubits (i, j) and (i, j + 1)
433    where i is any integer and j is an even integer. The second layer
434    applies two-qubit gates to qubits (i, j) and (i + 1, j) where i is an even
435    integer and j is any integer. The third layer applies two-qubit gates
436    to qubits (i, j) and (i, j + 1) where i is any integer and j is an odd
437    integer. The fourth layer applies two-qubit gates to qubits (i, j) and
438    (i + 1, j) where i is an odd integer and j is any integer.
439
440    After the layers are built as above, any empty layer is ejected.:
441
442                 Cycle 1:                            Cycle 2:
443        q00 ── q01    q02 ── q03            q00    q01    q02    q03
444                                             |      |      |      |
445        q10 ── q11    q12 ── q13            q10    q11    q12    q13
446
447        q20 ── q21    q22 ── q23            q20    q21    q22    q23
448                                             |      |      |      |
449        q30 ── q31    q32 ── q33            q30    q31    q32    q33
450
451                  Cycle 3:                           Cycle 4:
452        q00    q01 ── q02    q03            q00    q01    q02    q03
453
454        q10    q11 ── q12    q13            q10    q11    q12    q13
455                                             |      |      |      |
456        q20    q21 ── q22    q23            q20    q21    q22    q23
457
458        q30    q31 ── q32    q33            q30    q31    q32    q33
459
460    Args:
461        qubits: The grid qubits included in the entangling operations.
462        two_qubit_gate: The two-qubit gate to be applied between all
463            neighboring pairs of qubits.
464
465    Returns:
466        A list of ops.Moment, with a maximum length of 4. Each ops.Moment
467        includes two-qubit gates which can be performed at the same time.
468
469    Raises:
470        ValueError: two-qubit gate is not used.
471    """
472    if protocols.num_qubits(two_qubit_gate) != 2:
473        raise ValueError('Input must be a two-qubit gate')
474    interaction_sequence = _default_interaction_sequence(qubits)
475    return [
476        ops.Moment([two_qubit_gate(q_a, q_b) for (q_a, q_b) in pairs])
477        for pairs in interaction_sequence
478    ]
479
480
481def _build_xeb_circuits(
482    qubits: Sequence[ops.Qid],
483    cycles: Sequence[int],
484    single_qubit_gates: List[List[ops.SingleQubitGate]] = None,
485    benchmark_ops: Sequence[ops.Moment] = None,
486) -> List[circuits.Circuit]:
487    if benchmark_ops is not None:
488        num_d = len(benchmark_ops)
489    else:
490        num_d = 0
491    max_cycles = max(cycles)
492
493    if single_qubit_gates is None:
494        single_rots = _random_half_rotations(qubits, max_cycles)
495    else:
496        single_rots = _random_any_gates(qubits, single_qubit_gates, max_cycles)
497    all_circuits = []  # type: List[circuits.Circuit]
498    for num_cycles in cycles:
499        circuit_exp = circuits.Circuit()
500        for i in range(num_cycles):
501            circuit_exp.append(single_rots[i])
502            if benchmark_ops is not None:
503                for op_set in benchmark_ops[i % num_d]:
504                    circuit_exp.append(op_set)
505        all_circuits.append(circuit_exp)
506    return all_circuits
507
508
509def _measure_prob_distribution(
510    sampler: work.Sampler,
511    repetitions: int,
512    qubits: Sequence[ops.Qid],
513    circuit_list: List[circuits.Circuit],
514) -> List[np.ndarray]:
515    all_probs = []  # type: List[np.ndarray]
516    num_states = 2 ** len(qubits)
517    for circuit in circuit_list:
518        trial_circuit = circuit.copy()
519        trial_circuit.append(ops.measure(*qubits, key='z'))
520        res = sampler.run(trial_circuit, repetitions=repetitions)
521        res_hist = dict(res.histogram(key='z'))
522        probs = np.zeros(num_states, dtype=float)
523        for k, v in res_hist.items():
524            probs[k] = float(v) / float(repetitions)
525        all_probs.append(probs)
526    return all_probs
527
528
529def _xeb_fidelities(
530    ideal_probs: Dict[int, np.ndarray], actual_probs: Dict[int, np.ndarray]
531) -> List[float]:
532    num_cycles = sorted(list(ideal_probs.keys()))
533    return [_compute_fidelity(ideal_probs[n], actual_probs[n]) for n in num_cycles]
534
535
536def _compute_fidelity(probs_exp: np.ndarray, probs_meas: np.ndarray) -> float:
537    _, num_states = probs_exp.shape
538    pp_cross = probs_exp * probs_meas
539    pp_exp = probs_exp ** 2
540    f_meas = np.mean(num_states * np.sum(pp_cross, axis=1) - 1.0)
541    f_exp = np.mean(num_states * np.sum(pp_exp, axis=1) - 1.0)
542    return float(f_meas / f_exp)
543
544
545def _random_half_rotations(qubits: Sequence[ops.Qid], num_layers: int) -> List[List[ops.OP_TREE]]:
546    rot_ops = [ops.X ** 0.5, ops.Y ** 0.5, ops.PhasedXPowGate(phase_exponent=0.25, exponent=0.5)]
547    num_qubits = len(qubits)
548    rand_nums = np.random.choice(3, (num_qubits, num_layers))
549    single_q_layers = []  # type: List[List[ops.OP_TREE]]
550    for i in range(num_layers):
551        single_q_layers.append([rot_ops[rand_nums[j, i]](qubits[j]) for j in range(num_qubits)])
552    return single_q_layers
553
554
555def _random_any_gates(
556    qubits: Sequence[ops.Qid], op_list: List[List[ops.SingleQubitGate]], num_layers: int
557) -> List[List[ops.OP_TREE]]:
558    num_ops = len(op_list)
559    num_qubits = len(qubits)
560    rand_nums = np.random.choice(num_ops, (num_qubits, num_layers))
561    single_q_layers = []  # type: List[List[ops.OP_TREE]]
562    for i in range(num_layers):
563        rots_i = []  # type: List[ops.OP_TREE]
564        for j in range(num_qubits):
565            rots_i.extend([rot(qubits[j]) for rot in op_list[rand_nums[j, i]]])
566        single_q_layers.append(rots_i)
567    return single_q_layers
568
569
570def _default_interaction_sequence(
571    qubits: Sequence[devices.GridQubit],
572) -> List[Set[Tuple[devices.GridQubit, devices.GridQubit]]]:
573    qubit_dict = {(qubit.row, qubit.col): qubit for qubit in qubits}
574    qubit_locs = set(qubit_dict)
575    num_rows = max([q.row for q in qubits]) + 1
576    num_cols = max([q.col for q in qubits]) + 1
577
578    l_s = [set() for _ in range(4)]  # type: List[Set[Tuple[devices.GridQubit, devices.GridQubit]]]
579    for i in range(num_rows):
580        for j in range(num_cols - 1):
581            if (i, j) in qubit_locs and (i, j + 1) in qubit_locs:
582                l_s[j % 2 * 2].add((qubit_dict[(i, j)], qubit_dict[(i, j + 1)]))
583
584    for i in range(num_rows - 1):
585        for j in range(num_cols):
586            if (i, j) in qubit_locs and (i + 1, j) in qubit_locs:
587                l_s[i % 2 * 2 + 1].add((qubit_dict[(i, j)], qubit_dict[(i + 1, j)]))
588
589    l_final = []  # type: List[Set[Tuple[devices.GridQubit, devices.GridQubit]]]
590    for gate_set in l_s:
591        if len(gate_set) != 0:
592            l_final.append(gate_set)
593
594    return l_final
595