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