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