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 15import dataclasses 16import itertools 17 18from typing import Any, Iterator, List, Optional, Sequence, Tuple, TYPE_CHECKING 19import numpy as np 20import sympy 21 22from matplotlib import pyplot as plt 23 24# this is for older systems with matplotlib <3.2 otherwise 3d projections fail 25from mpl_toolkits import mplot3d # pylint: disable=unused-import 26from cirq import circuits, ops, protocols, study 27 28if TYPE_CHECKING: 29 import cirq 30 31 32@dataclasses.dataclass 33class Cliffords: 34 """The single-qubit Clifford group, decomposed into elementary gates. 35 36 The decomposition of the Cliffords follows those described in 37 Barends et al., Nature 508, 500 (https://arxiv.org/abs/1402.4848). 38 39 Decompositions of the Clifford group: 40 c1_in_xy: decomposed into XPowGate and YPowGate. 41 c1_in_xz: decomposed into XPowGate and ZPowGate, with at most one 42 XPowGate (one microwave gate) per Clifford. 43 44 Subsets used to generate the 2-qubit Clifford group (see paper table S7): 45 s1 46 s1_x 47 s1_y 48 """ 49 50 c1_in_xy: List[List[ops.Gate]] 51 c1_in_xz: List[List[ops.Gate]] 52 s1: List[List[ops.Gate]] 53 s1_x: List[List[ops.Gate]] 54 s1_y: List[List[ops.Gate]] 55 56 57class RabiResult: 58 """Results from a Rabi oscillation experiment.""" 59 60 def __init__(self, rabi_angles: Sequence[float], excited_state_probabilities: Sequence[float]): 61 """Inits RabiResult. 62 63 Args: 64 rabi_angles: The rotation angles of the qubit around the x-axis 65 of the Bloch sphere. 66 excited_state_probabilities: The corresponding probabilities that 67 the qubit is in the excited state. 68 """ 69 self._rabi_angles = rabi_angles 70 self._excited_state_probs = excited_state_probabilities 71 72 @property 73 def data(self) -> Sequence[Tuple[float, float]]: 74 """Returns a sequence of tuple pairs with the first item being a Rabi 75 angle and the second item being the corresponding excited state 76 probability. 77 """ 78 return [(angle, prob) for angle, prob in zip(self._rabi_angles, self._excited_state_probs)] 79 80 def plot(self, ax: Optional[plt.Axes] = None, **plot_kwargs: Any) -> plt.Axes: 81 """Plots excited state probability vs the Rabi angle (angle of rotation 82 around the x-axis). 83 84 Args: 85 ax: the plt.Axes to plot on. If not given, a new figure is created, 86 plotted on, and shown. 87 **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'. 88 Returns: 89 The plt.Axes containing the plot. 90 """ 91 show_plot = not ax 92 if not ax: 93 fig, ax = plt.subplots(1, 1, figsize=(8, 8)) 94 ax.set_ylim([0, 1]) 95 ax.plot(self._rabi_angles, self._excited_state_probs, 'ro-', **plot_kwargs) 96 ax.set_xlabel(r"Rabi Angle (Radian)") 97 ax.set_ylabel('Excited State Probability') 98 if show_plot: 99 fig.show() 100 return ax 101 102 103class RandomizedBenchMarkResult: 104 """Results from a randomized benchmarking experiment.""" 105 106 def __init__(self, num_cliffords: Sequence[int], ground_state_probabilities: Sequence[float]): 107 """Inits RandomizedBenchMarkResult. 108 109 Args: 110 num_cliffords: The different numbers of Cliffords in the RB 111 study. 112 ground_state_probabilities: The corresponding average ground state 113 probabilities. 114 """ 115 self._num_cfds_seq = num_cliffords 116 self._gnd_state_probs = ground_state_probabilities 117 118 @property 119 def data(self) -> Sequence[Tuple[int, float]]: 120 """Returns a sequence of tuple pairs with the first item being a 121 number of Cliffords and the second item being the corresponding average 122 ground state probability. 123 """ 124 return [(num, prob) for num, prob in zip(self._num_cfds_seq, self._gnd_state_probs)] 125 126 def plot(self, ax: Optional[plt.Axes] = None, **plot_kwargs: Any) -> plt.Axes: 127 """Plots the average ground state probability vs the number of 128 Cliffords in the RB study. 129 130 Args: 131 ax: the plt.Axes to plot on. If not given, a new figure is created, 132 plotted on, and shown. 133 **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'. 134 Returns: 135 The plt.Axes containing the plot. 136 """ 137 show_plot = not ax 138 if not ax: 139 fig, ax = plt.subplots(1, 1, figsize=(8, 8)) 140 ax.set_ylim([0, 1]) 141 ax.plot(self._num_cfds_seq, self._gnd_state_probs, 'ro-', **plot_kwargs) 142 ax.set_xlabel(r"Number of Cliffords") 143 ax.set_ylabel('Ground State Probability') 144 if show_plot: 145 fig.show() 146 return ax 147 148 149class TomographyResult: 150 """Results from a state tomography experiment.""" 151 152 def __init__(self, density_matrix: np.ndarray): 153 """Inits TomographyResult. 154 155 Args: 156 density_matrix: The density matrix obtained from tomography. 157 """ 158 self._density_matrix = density_matrix 159 160 @property 161 def data(self) -> np.ndarray: 162 """Returns an n^2 by n^2 complex matrix representing the density 163 matrix of the n-qubit system. 164 """ 165 return self._density_matrix 166 167 def plot(self, axes: Optional[List[plt.Axes]] = None, **plot_kwargs: Any) -> List[plt.Axes]: 168 """Plots the real and imaginary parts of the density matrix as two 3D bar plots. 169 170 Args: 171 axes: A list of 2 `plt.Axes` instances. Note that they must be in 172 3d projections. If not given, a new figure is created with 2 173 axes and the plotted figure is shown. 174 plot_kwargs: The optional kwargs passed to bar3d. 175 176 Returns: 177 the list of `plt.Axes` being plotted on. 178 179 Raises: 180 ValueError: If axes is a list with length != 2. 181 """ 182 show_plot = axes is None 183 if axes is None: 184 fig, axes = plt.subplots(1, 2, figsize=(12.0, 5.0), subplot_kw={'projection': '3d'}) 185 elif len(axes) != 2: 186 raise ValueError('A TomographyResult needs 2 axes to plot.') 187 mat = self._density_matrix 188 a, _ = mat.shape 189 num_qubits = int(np.log2(a)) 190 state_labels = [[0, 1]] * num_qubits 191 kets = [] 192 for label in itertools.product(*state_labels): 193 kets.append('|' + str(list(label))[1:-1] + '>') 194 mat_re = np.real(mat) 195 mat_im = np.imag(mat) 196 _matrix_bar_plot( 197 mat_re, 198 r'Real($\rho$)', 199 axes[0], 200 kets, 201 'Density Matrix (Real Part)', 202 ylim=(-1, 1), 203 **plot_kwargs, 204 ) 205 _matrix_bar_plot( 206 mat_im, 207 r'Imaginary($\rho$)', 208 axes[1], 209 kets, 210 'Density Matrix (Imaginary Part)', 211 ylim=(-1, 1), 212 **plot_kwargs, 213 ) 214 if show_plot: 215 fig.show() 216 return axes 217 218 219def rabi_oscillations( 220 sampler: 'cirq.Sampler', 221 qubit: 'cirq.Qid', 222 max_angle: float = 2 * np.pi, 223 *, 224 repetitions: int = 1000, 225 num_points: int = 200, 226) -> RabiResult: 227 """Runs a Rabi oscillation experiment. 228 229 Rotates a qubit around the x-axis of the Bloch sphere by a sequence of Rabi 230 angles evenly spaced between 0 and max_angle. For each rotation, repeat 231 the circuit a number of times and measure the average probability of the 232 qubit being in the |1> state. 233 234 Args: 235 sampler: The quantum engine or simulator to run the circuits. 236 qubit: The qubit under test. 237 max_angle: The final Rabi angle in radians. 238 repetitions: The number of repetitions of the circuit for each Rabi 239 angle. 240 num_points: The number of Rabi angles. 241 242 Returns: 243 A RabiResult object that stores and plots the result. 244 """ 245 theta = sympy.Symbol('theta') 246 circuit = circuits.Circuit(ops.X(qubit) ** theta) 247 circuit.append(ops.measure(qubit, key='z')) 248 sweep = study.Linspace(key='theta', start=0.0, stop=max_angle / np.pi, length=num_points) 249 results = sampler.run_sweep(circuit, params=sweep, repetitions=repetitions) 250 angles = np.linspace(0.0, max_angle, num_points) 251 excited_state_probs = np.zeros(num_points) 252 for i in range(num_points): 253 excited_state_probs[i] = np.mean(results[i].measurements['z']) 254 255 return RabiResult(angles, excited_state_probs) 256 257 258def single_qubit_randomized_benchmarking( 259 sampler: 'cirq.Sampler', 260 qubit: 'cirq.Qid', 261 use_xy_basis: bool = True, 262 *, 263 num_clifford_range: Sequence[int] = range(10, 100, 10), 264 num_circuits: int = 20, 265 repetitions: int = 1000, 266) -> RandomizedBenchMarkResult: 267 """Clifford-based randomized benchmarking (RB) of a single qubit. 268 269 A total of num_circuits random circuits are generated, each of which 270 contains a fixed number of single-qubit Clifford gates plus one 271 additional Clifford that inverts the whole sequence and a measurement in 272 the z-basis. Each circuit is repeated a number of times and the average 273 |0> state population is determined from the measurement outcomes of all 274 of the circuits. 275 276 The above process is done for different circuit lengths specified by the 277 integers in num_clifford_range. For example, an integer 10 means the 278 random circuits will contain 10 Clifford gates each plus one inverting 279 Clifford. The user may use the result to extract an average gate fidelity, 280 by analyzing the change in the average |0> state population at different 281 circuit lengths. For actual experiments, one should choose 282 num_clifford_range such that a clear exponential decay is observed in the 283 results. 284 285 See Barends et al., Nature 508, 500 for details. 286 287 Args: 288 sampler: The quantum engine or simulator to run the circuits. 289 qubit: The qubit under test. 290 use_xy_basis: Determines if the Clifford gates are built with x and y 291 rotations (True) or x and z rotations (False). 292 num_clifford_range: The different numbers of Cliffords in the RB study. 293 num_circuits: The number of random circuits generated for each 294 number of Cliffords. 295 repetitions: The number of repetitions of each circuit. 296 297 Returns: 298 A RandomizedBenchMarkResult object that stores and plots the result. 299 """ 300 301 cliffords = _single_qubit_cliffords() 302 c1 = cliffords.c1_in_xy if use_xy_basis else cliffords.c1_in_xz 303 cfd_mats = np.array([_gate_seq_to_mats(gates) for gates in c1]) 304 305 gnd_probs = [] 306 for num_cfds in num_clifford_range: 307 excited_probs_l = [] 308 for _ in range(num_circuits): 309 circuit = _random_single_q_clifford(qubit, num_cfds, c1, cfd_mats) 310 circuit.append(ops.measure(qubit, key='z')) 311 results = sampler.run(circuit, repetitions=repetitions) 312 excited_probs_l.append(np.mean(results.measurements['z'])) 313 gnd_probs.append(1.0 - np.mean(excited_probs_l)) 314 315 return RandomizedBenchMarkResult(num_clifford_range, gnd_probs) 316 317 318def two_qubit_randomized_benchmarking( 319 sampler: 'cirq.Sampler', 320 first_qubit: 'cirq.Qid', 321 second_qubit: 'cirq.Qid', 322 *, 323 num_clifford_range: Sequence[int] = range(5, 50, 5), 324 num_circuits: int = 20, 325 repetitions: int = 1000, 326) -> RandomizedBenchMarkResult: 327 """Clifford-based randomized benchmarking (RB) of two qubits. 328 329 A total of num_circuits random circuits are generated, each of which 330 contains a fixed number of two-qubit Clifford gates plus one additional 331 Clifford that inverts the whole sequence and a measurement in the 332 z-basis. Each circuit is repeated a number of times and the average 333 |00> state population is determined from the measurement outcomes of all 334 of the circuits. 335 336 The above process is done for different circuit lengths specified by the 337 integers in num_clifford_range. For example, an integer 10 means the 338 random circuits will contain 10 Clifford gates each plus one inverting 339 Clifford. The user may use the result to extract an average gate fidelity, 340 by analyzing the change in the average |00> state population at different 341 circuit lengths. For actual experiments, one should choose 342 num_clifford_range such that a clear exponential decay is observed in the 343 results. 344 345 The two-qubit Cliffords here are decomposed into CZ gates plus single-qubit 346 x and y rotations. See Barends et al., Nature 508, 500 for details. 347 348 Args: 349 sampler: The quantum engine or simulator to run the circuits. 350 first_qubit: The first qubit under test. 351 second_qubit: The second qubit under test. 352 num_clifford_range: The different numbers of Cliffords in the RB study. 353 num_circuits: The number of random circuits generated for each 354 number of Cliffords. 355 repetitions: The number of repetitions of each circuit. 356 357 Returns: 358 A RandomizedBenchMarkResult object that stores and plots the result. 359 """ 360 cliffords = _single_qubit_cliffords() 361 cfd_matrices = _two_qubit_clifford_matrices(first_qubit, second_qubit, cliffords) 362 gnd_probs = [] 363 for num_cfds in num_clifford_range: 364 gnd_probs_l = [] 365 for _ in range(num_circuits): 366 circuit = _random_two_q_clifford( 367 first_qubit, second_qubit, num_cfds, cfd_matrices, cliffords 368 ) 369 circuit.append(ops.measure(first_qubit, second_qubit, key='z')) 370 results = sampler.run(circuit, repetitions=repetitions) 371 gnds = [(not r[0] and not r[1]) for r in results.measurements['z']] 372 gnd_probs_l.append(np.mean(gnds)) 373 gnd_probs.append(float(np.mean(gnd_probs_l))) 374 375 return RandomizedBenchMarkResult(num_clifford_range, gnd_probs) 376 377 378def single_qubit_state_tomography( 379 sampler: 'cirq.Sampler', 380 qubit: 'cirq.Qid', 381 circuit: 'cirq.AbstractCircuit', 382 repetitions: int = 1000, 383) -> TomographyResult: 384 """Single-qubit state tomography. 385 386 The density matrix of the output state of a circuit is measured by first 387 doing projective measurements in the z-basis, which determine the 388 diagonal elements of the matrix. A X/2 or Y/2 rotation is then added before 389 the z-basis measurement, which determines the imaginary and real parts of 390 the off-diagonal matrix elements, respectively. 391 392 See Vandersypen and Chuang, Rev. Mod. Phys. 76, 1037 for details. 393 394 Args: 395 sampler: The quantum engine or simulator to run the circuits. 396 qubit: The qubit under test. 397 circuit: The circuit to execute on the qubit before tomography. 398 repetitions: The number of measurements for each basis rotation. 399 400 Returns: 401 A TomographyResult object that stores and plots the density matrix. 402 """ 403 circuit_z = circuit + circuits.Circuit(ops.measure(qubit, key='z')) 404 results = sampler.run(circuit_z, repetitions=repetitions) 405 rho_11 = np.mean(results.measurements['z']) 406 rho_00 = 1.0 - rho_11 407 408 circuit_x = circuits.Circuit(circuit, ops.X(qubit) ** 0.5, ops.measure(qubit, key='z')) 409 results = sampler.run(circuit_x, repetitions=repetitions) 410 rho_01_im = np.mean(results.measurements['z']) - 0.5 411 412 circuit_y = circuits.Circuit(circuit, ops.Y(qubit) ** -0.5, ops.measure(qubit, key='z')) 413 results = sampler.run(circuit_y, repetitions=repetitions) 414 rho_01_re = 0.5 - np.mean(results.measurements['z']) 415 416 rho_01 = rho_01_re + 1j * rho_01_im 417 rho_10 = np.conj(rho_01) 418 419 rho = np.array([[rho_00, rho_01], [rho_10, rho_11]]) 420 421 return TomographyResult(rho) 422 423 424def two_qubit_state_tomography( 425 sampler: 'cirq.Sampler', 426 first_qubit: 'cirq.Qid', 427 second_qubit: 'cirq.Qid', 428 circuit: 'cirq.AbstractCircuit', 429 repetitions: int = 1000, 430) -> TomographyResult: 431 r"""Two-qubit state tomography. 432 433 To measure the density matrix of the output state of a two-qubit circuit, 434 different combinations of I, X/2 and Y/2 operations are applied to the 435 two qubits before measurements in the z-basis to determine the state 436 probabilities $P_{00}, P_{01}, P_{10}.$ 437 438 The density matrix rho is decomposed into an operator-sum representation 439 $\sum_{i, j} c_{ij} * \sigma_i \bigotimes \sigma_j$, where $i, j = 0, 1, 2, 440 3$ and $\sigma_0 = I, \sigma_1 = \sigma_x, \sigma_2 = \sigma_y, \sigma_3 = 441 \sigma_z$ are the single-qubit Identity and Pauli matrices. 442 443 Based on the measured probabilities probs and the transformations of the 444 measurement operator by different basis rotations, one can build an 445 overdetermined set of linear equations. 446 447 As an example, if the identity operation (I) is applied to both qubits, the 448 measurement operators are $(I +/- \sigma_z) \bigotimes (I +/- \sigma_z)$. 449 The state probabilities $P_{00}, P_{01}, P_{10}$ thus obtained contribute 450 to the following linear equations (setting $c_{00} = 1$): 451 452 $$ 453 \begin{align} 454 c_{03} + c_{30} + c_{33} &= 4*P_{00} - 1 \\ 455 -c_{03} + c_{30} - c_{33} &= 4*P_{01} - 1 \\ 456 c_{03} - c_{30} - c_{33} &= 4*P_{10} - 1 457 \end{align} 458 $$ 459 460 And if a Y/2 rotation is applied to the first qubit and a X/2 rotation 461 is applied to the second qubit before measurement, the measurement 462 operators are $(I -/+ \sigma_x) \bigotimes (I +/- \sigma_y)$. The 463 probabilities obtained instead contribute to the following linear equations: 464 465 $$ 466 \begin{align} 467 c_{02} - c_{10} - c_{12} &= 4*P_{00} - 1 \\ 468 -c_{02} - c_{10} + c_{12} &= 4*P_{01} - 1 \\ 469 c_{02} + c_{10} + c_{12} &= 4*P_{10} - 1 470 \end{align} 471 $$ 472 473 Note that this set of equations has the same form as the first set under 474 the transformation $c_{03}$ <-> $c_{02}, c_{30}$ <-> $-c_{10}$ and 475 $c_{33}$ <-> $-c_{12}$. 476 477 Since there are 9 possible combinations of rotations (each producing 3 478 independent probabilities) and a total of 15 unknown coefficients $c_{ij}$, 479 one can cast all the measurement results into a overdetermined set of 480 linear equations numpy.dot(mat, c) = probs. Here c is of length 15 and 481 contains all the $c_{ij}$'s (except $c_{00}$ which is set to 1), and mat 482 is a 27 by 15 matrix having three non-zero elements in each row that are 483 either 1 or -1. 484 485 The least-square solution to the above set of linear equations is then 486 used to construct the density matrix rho. 487 488 See Vandersypen and Chuang, Rev. Mod. Phys. 76, 1037 for details and 489 Steffen et al, Science 313, 1423 for a related experiment. 490 491 Args: 492 sampler: The quantum engine or simulator to run the circuits. 493 first_qubit: The first qubit under test. 494 second_qubit: The second qubit under test. 495 circuit: The circuit to execute on the qubits before tomography. 496 repetitions: The number of measurements for each basis rotation. 497 498 Returns: 499 A TomographyResult object that stores and plots the density matrix. 500 """ 501 # The size of the system of linear equations to be solved. 502 num_rows = 27 503 num_cols = 15 504 505 def _measurement(two_qubit_circuit: circuits.Circuit) -> np.ndarray: 506 two_qubit_circuit.append(ops.measure(first_qubit, second_qubit, key='z')) 507 results = sampler.run(two_qubit_circuit, repetitions=repetitions) 508 results_hist = results.histogram(key='z') 509 prob_list = [results_hist[0], results_hist[1], results_hist[2]] 510 return np.asarray(prob_list) / repetitions 511 512 sigma_0 = np.eye(2) * 0.5 513 sigma_1 = np.array([[0.0, 1.0], [1.0, 0.0]]) * 0.5 514 sigma_2 = np.array([[0.0, -1.0j], [1.0j, 0.0]]) * 0.5 515 sigma_3 = np.array([[1.0, 0.0], [0.0, -1.0]]) * 0.5 516 sigmas = [sigma_0, sigma_1, sigma_2, sigma_3] 517 518 # Stores all 27 measured probabilities (P_00, P_01, P_10 after 9 519 # different basis rotations). 520 probs = np.array([]) 521 522 rots = [ops.X ** 0, ops.X ** 0.5, ops.Y ** 0.5] 523 524 # Represents the coefficients in front of the c_ij's (-1, 0 or 1) in the 525 # system of 27 linear equations. 526 mat = np.zeros((num_rows, num_cols)) 527 528 # Represents the relative signs between the linear equations for P_00, 529 # P_01, and P_10. 530 s = np.array([[1.0, 1.0, 1.0], [-1.0, 1.0, -1.0], [1.0, -1.0, -1.0]]) 531 532 for i, rot_1 in enumerate(rots): 533 for j, rot_2 in enumerate(rots): 534 m_idx, indices, signs = _indices_after_basis_rot(i, j) 535 mat[m_idx : (m_idx + 3), indices] = s * np.tile(signs, (3, 1)) 536 test_circuit = circuit + circuits.Circuit(rot_1(first_qubit)) 537 test_circuit.append(rot_2(second_qubit)) 538 probs = np.concatenate((probs, _measurement(test_circuit))) 539 540 c, _, _, _ = np.linalg.lstsq(mat, 4.0 * probs - 1.0, rcond=-1) 541 c = np.concatenate(([1.0], c)) 542 c = c.reshape(4, 4) 543 544 rho = np.zeros((4, 4)) 545 for i in range(4): 546 for j in range(4): 547 rho = rho + c[i, j] * np.kron(sigmas[i], sigmas[j]) 548 549 return TomographyResult(rho) 550 551 552def _indices_after_basis_rot(i: int, j: int) -> Tuple[int, Sequence[int], Sequence[int]]: 553 mat_idx = 3 * (3 * i + j) 554 q_0_i = 3 - i 555 q_1_j = 3 - j 556 indices = [q_1_j - 1, 4 * q_0_i - 1, 4 * q_0_i + q_1_j - 1] 557 signs = [(-1) ** (j == 2), (-1) ** (i == 2), (-1) ** ((i == 2) + (j == 2))] 558 return mat_idx, indices, signs 559 560 561def _two_qubit_clifford_matrices( 562 q_0: 'cirq.Qid', q_1: 'cirq.Qid', cliffords: Cliffords 563) -> np.ndarray: 564 mats = [] 565 566 # Total number of different gates in the two-qubit Clifford group. 567 clifford_group_size = 11520 568 569 starters = [] 570 for idx_0 in range(24): 571 subset = [] 572 for idx_1 in range(24): 573 circuit = circuits.Circuit( 574 _two_qubit_clifford_starters(q_0, q_1, idx_0, idx_1, cliffords) 575 ) 576 subset.append(protocols.unitary(circuit)) 577 starters.append(subset) 578 mixers = [] 579 # Add the identity for the case where there is no mixer. 580 mixers.append(np.eye(4)) 581 for idx_2 in range(1, 20): 582 circuit = circuits.Circuit(_two_qubit_clifford_mixers(q_0, q_1, idx_2, cliffords)) 583 mixers.append(protocols.unitary(circuit)) 584 585 for i in range(clifford_group_size): 586 idx_0, idx_1, idx_2 = _split_two_q_clifford_idx(i) 587 mats.append(np.matmul(mixers[idx_2], starters[idx_0][idx_1])) 588 589 return np.array(mats) 590 591 592def _random_single_q_clifford( 593 qubit: 'cirq.Qid', 594 num_cfds: int, 595 cfds: Sequence[Sequence['cirq.Gate']], 596 cfd_matrices: np.ndarray, 597) -> 'cirq.Circuit': 598 clifford_group_size = 24 599 gate_ids = list(np.random.choice(clifford_group_size, num_cfds)) 600 gate_sequence = [gate for gate_id in gate_ids for gate in cfds[gate_id]] 601 idx = _find_inv_matrix(_gate_seq_to_mats(gate_sequence), cfd_matrices) 602 gate_sequence.extend(cfds[idx]) 603 circuit = circuits.Circuit(gate(qubit) for gate in gate_sequence) 604 return circuit 605 606 607def _random_two_q_clifford( 608 q_0: 'cirq.Qid', q_1: 'cirq.Qid', num_cfds: int, cfd_matrices: np.ndarray, cliffords: Cliffords 609) -> 'cirq.Circuit': 610 clifford_group_size = 11520 611 idx_list = list(np.random.choice(clifford_group_size, num_cfds)) 612 circuit = circuits.Circuit() 613 for idx in idx_list: 614 circuit.append(_two_qubit_clifford(q_0, q_1, idx, cliffords)) 615 inv_idx = _find_inv_matrix(protocols.unitary(circuit), cfd_matrices) 616 circuit.append(_two_qubit_clifford(q_0, q_1, inv_idx, cliffords)) 617 return circuit 618 619 620def _find_inv_matrix(mat: np.ndarray, mat_sequence: np.ndarray) -> int: 621 mat_prod = np.einsum('ij,...jk->...ik', mat, mat_sequence) 622 diag_sums = list(np.absolute(np.einsum('...ii->...', mat_prod))) 623 idx = diag_sums.index(max(diag_sums)) 624 return idx 625 626 627def _matrix_bar_plot( 628 mat: np.ndarray, 629 z_label: str, 630 ax: plt.Axes, 631 kets: Sequence[str] = None, 632 title: str = None, 633 ylim: Tuple[int, int] = (-1, 1), 634 **bar3d_kwargs: Any, 635) -> None: 636 num_rows, num_cols = mat.shape 637 indices = np.meshgrid(range(num_cols), range(num_rows)) 638 x_indices = np.array(indices[1]).flatten() 639 y_indices = np.array(indices[0]).flatten() 640 z_indices = np.zeros(mat.size) 641 642 dx = np.ones(mat.size) * 0.3 643 dy = np.ones(mat.size) * 0.3 644 dz = mat.flatten() 645 ax.bar3d( 646 x_indices, y_indices, z_indices, dx, dy, dz, color='#ff0080', alpha=1.0, **bar3d_kwargs 647 ) 648 649 ax.set_zlabel(z_label) 650 ax.set_zlim3d(ylim[0], ylim[1]) 651 652 if kets is not None: 653 ax.set_xticks(np.arange(num_cols) + 0.15) 654 ax.set_yticks(np.arange(num_rows) + 0.15) 655 ax.set_xticklabels(kets) 656 ax.set_yticklabels(kets) 657 658 if title is not None: 659 ax.set_title(title) 660 661 662def _gate_seq_to_mats(gate_seq: Sequence['cirq.Gate']) -> np.ndarray: 663 mat_rep = protocols.unitary(gate_seq[0]) 664 for gate in gate_seq[1:]: 665 mat_rep = np.dot(protocols.unitary(gate), mat_rep) 666 return mat_rep 667 668 669def _two_qubit_clifford( 670 q_0: 'cirq.Qid', q_1: 'cirq.Qid', idx: int, cliffords: Cliffords 671) -> Iterator['cirq.OP_TREE']: 672 """Generates a two-qubit Clifford gate. 673 674 An integer (idx) from 0 to 11519 is used to generate a two-qubit Clifford 675 gate which is constructed with single-qubit X and Y rotations and CZ gates. 676 The decomposition of the Cliffords follow those described in the appendix 677 of Barends et al., Nature 508, 500 (https://arxiv.org/abs/1402.4848). 678 679 The integer idx is first decomposed into idx_0 (which ranges from 0 to 680 23), idx_1 (ranging from 0 to 23) and idx_2 (ranging from 0 to 19). idx_0 681 and idx_1 determine the two single-qubit rotations which happen at the 682 beginning of all two-qubit Clifford gates. idx_2 determines the 683 subsequent gates in the following: 684 685 a) If idx_2 = 0, do nothing so the Clifford is just two single-qubit 686 Cliffords (total of 24*24 = 576 possibilities). 687 688 b) If idx_2 = 1, perform a CZ, followed by -Y/2 on q_0 and Y/2 on q_1, 689 followed by another CZ, followed by Y/2 on q_0 and -Y/2 on q_1, followed 690 by one more CZ and finally a Y/2 on q_1. The Clifford is then a member of 691 the SWAP-like class (total of 24*24 = 576 possibilities). 692 693 c) If 2 <= idx_2 <= 10, perform a CZ followed by a member of the S_1 694 group on q_0 and a member of the S_1^(Y/2) group on q_1. The Clifford is 695 a member of the CNOT-like class (a total of 3*3*24*24 = 5184 possibilities). 696 697 d) If 11 <= idx_2 <= 19, perform a CZ, followed by Y/2 on q_0 and -X/2 on 698 q_1, followed by another CZ, and finally a member of the S_1^(Y/2) group on 699 q_0 and a member of the S_1^(X/2) group on q_1. The Clifford is a member 700 of the iSWAP-like class (a total of 3*3*24*24 = 5184 possibilities). 701 702 Through the above process, all 11520 members of the two-qubit Clifford 703 group may be generated. 704 705 Args: 706 q_0: The first qubit under test. 707 q_1: The second qubit under test. 708 idx: An integer from 0 to 11519. 709 cliffords: A NamedTuple that contains single-qubit Cliffords from the 710 C1, S1, S_1^(X/2) and S_1^(Y/2) groups. 711 """ 712 idx_0, idx_1, idx_2 = _split_two_q_clifford_idx(idx) 713 yield _two_qubit_clifford_starters(q_0, q_1, idx_0, idx_1, cliffords) 714 yield _two_qubit_clifford_mixers(q_0, q_1, idx_2, cliffords) 715 716 717def _split_two_q_clifford_idx(idx: int): 718 """Decompose the index for two-qubit Cliffords.""" 719 idx_0 = int(idx / 480) 720 idx_1 = int((idx % 480) * 0.05) 721 idx_2 = idx - idx_0 * 480 - idx_1 * 20 722 return (idx_0, idx_1, idx_2) 723 724 725def _two_qubit_clifford_starters( 726 q_0: 'cirq.Qid', q_1: 'cirq.Qid', idx_0: int, idx_1: int, cliffords: Cliffords 727) -> Iterator['cirq.OP_TREE']: 728 """Fulfills part (a) for two-qubit Cliffords.""" 729 c1 = cliffords.c1_in_xy 730 yield _single_qubit_gates(c1[idx_0], q_0) 731 yield _single_qubit_gates(c1[idx_1], q_1) 732 733 734def _two_qubit_clifford_mixers( 735 q_0: 'cirq.Qid', q_1: 'cirq.Qid', idx_2: int, cliffords: Cliffords 736) -> Iterator['cirq.OP_TREE']: 737 """Fulfills parts (b-d) for two-qubit Cliffords.""" 738 s1 = cliffords.s1 739 s1_x = cliffords.s1_x 740 s1_y = cliffords.s1_y 741 if idx_2 == 1: 742 yield ops.CZ(q_0, q_1) 743 yield ops.Y(q_0) ** -0.5 744 yield ops.Y(q_1) ** 0.5 745 yield ops.CZ(q_0, q_1) 746 yield ops.Y(q_0) ** 0.5 747 yield ops.Y(q_1) ** -0.5 748 yield ops.CZ(q_0, q_1) 749 yield ops.Y(q_1) ** 0.5 750 elif 2 <= idx_2 <= 10: 751 yield ops.CZ(q_0, q_1) 752 idx_3 = int((idx_2 - 2) / 3) 753 idx_4 = (idx_2 - 2) % 3 754 yield _single_qubit_gates(s1[idx_3], q_0) 755 yield _single_qubit_gates(s1_y[idx_4], q_1) 756 elif idx_2 >= 11: 757 yield ops.CZ(q_0, q_1) 758 yield ops.Y(q_0) ** 0.5 759 yield ops.X(q_1) ** -0.5 760 yield ops.CZ(q_0, q_1) 761 idx_3 = int((idx_2 - 11) / 3) 762 idx_4 = (idx_2 - 11) % 3 763 yield _single_qubit_gates(s1_y[idx_3], q_0) 764 yield _single_qubit_gates(s1_x[idx_4], q_1) 765 766 767def _single_qubit_gates( 768 gate_seq: Sequence['cirq.Gate'], qubit: 'cirq.Qid' 769) -> Iterator['cirq.OP_TREE']: 770 for gate in gate_seq: 771 yield gate(qubit) 772 773 774def _single_qubit_cliffords() -> Cliffords: 775 X, Y, Z = ops.X, ops.Y, ops.Z 776 777 c1_in_xy: List[List['cirq.Gate']] = [] 778 c1_in_xz: List[List['cirq.Gate']] = [] 779 780 for phi_0, phi_1 in itertools.product([1.0, 0.5, -0.5], [0.0, 0.5, -0.5]): 781 c1_in_xy.append([X ** phi_0, Y ** phi_1]) 782 c1_in_xy.append([Y ** phi_0, X ** phi_1]) 783 c1_in_xz.append([X ** phi_0, Z ** phi_1]) 784 c1_in_xz.append([Z ** phi_0, X ** phi_1]) 785 786 # identity 787 c1_in_xy.append([X ** 0.0]) 788 c1_in_xz.append([X ** 0.0]) 789 790 c1_in_xy.append([Y, X]) 791 c1_in_xz.append([Z, X]) 792 793 phi_xy = [ 794 [-0.5, 0.5, 0.5], 795 [-0.5, -0.5, 0.5], 796 [0.5, 0.5, 0.5], 797 [-0.5, 0.5, -0.5], 798 ] 799 for y0, x, y1 in phi_xy: 800 c1_in_xy.append([Y ** y0, X ** x, Y ** y1]) 801 802 phi_xz = [ 803 [0.5, 0.5, -0.5], 804 [0.5, -0.5, -0.5], 805 [-0.5, -0.5, -0.5], 806 [-0.5, 0.5, -0.5], 807 ] 808 for z0, x, z1 in phi_xz: 809 c1_in_xz.append([Z ** z0, X ** x, Z ** z1]) 810 811 s1: List[List['cirq.Gate']] = [ 812 [X ** 0.0], 813 [Y ** 0.5, X ** 0.5], 814 [X ** -0.5, Y ** -0.5], 815 ] 816 s1_x: List[List['cirq.Gate']] = [ 817 [X ** 0.5], 818 [X ** 0.5, Y ** 0.5, X ** 0.5], 819 [Y ** -0.5], 820 ] 821 s1_y: List[List['cirq.Gate']] = [ 822 [Y ** 0.5], 823 [X ** -0.5, Y ** -0.5, X ** 0.5], 824 [Y, X ** 0.5], 825 ] 826 827 return Cliffords(c1_in_xy, c1_in_xz, s1, s1_x, s1_y) 828