Repository: quantumlib/Cirq

Issue:
examples test is slow
This test is taking almost 30s:
```
27.86s call     examples/examples_test.py::test_example_runs_qubit_characterizations
```

I think it is the single_qubit_randomized_benchmarking and two_qubit_randomized_benchamarking, which has default parameters that are pretty large.



Discussion:
Also: 
```
15.59s call     cirq/experiments/qubit_characterizations_test.py::test_two_qubit_randomized_benchmarking
6.09s call     cirq/experiments/qubit_characterizations_test.py::test_two_qubit_state_tomography
```
for qubit_characterizations_test, I tried a little bit of reducing repetitions or number of circuits or clifford range but still slow.
All the slowness is in _two_qubit_clifford, which generates the Clifford group.

I think maybe one could speed this up using a canonical form https://arxiv.org/pdf/1310.6813.pdf

Resolving patch:
diff --git a/cirq/experiments/qubit_characterizations.py b/cirq/experiments/qubit_characterizations.py
--- a/cirq/experiments/qubit_characterizations.py
+++ b/cirq/experiments/qubit_characterizations.py
@@ -206,13 +206,12 @@ def single_qubit_randomized_benchmarking(
 
     cliffords = _single_qubit_cliffords()
     c1 = cliffords.c1_in_xy if use_xy_basis else cliffords.c1_in_xz
-    cfd_mats = np.array([_gate_seq_to_mats(gates) for gates in c1])
 
     gnd_probs = []
     for num_cfds in num_clifford_range:
         excited_probs_l = []
         for _ in range(num_circuits):
-            circuit = _random_single_q_clifford(qubit, num_cfds, c1, cfd_mats)
+            circuit = _random_single_q_clifford(qubit, num_cfds, c1)
             circuit.append(ops.measure(qubit, key='z'))
             results = sampler.run(circuit, repetitions=repetitions)
             excited_probs_l.append(np.mean(results.measurements['z']))
@@ -263,14 +262,13 @@ def two_qubit_randomized_benchmarking(
         A RandomizedBenchMarkResult object that stores and plots the result.
     """
     cliffords = _single_qubit_cliffords()
-    cfd_matrices = _two_qubit_clifford_matrices(first_qubit, second_qubit,
-                                                cliffords)
+
     gnd_probs = []
     for num_cfds in num_clifford_range:
         gnd_probs_l = []
         for _ in range(num_circuits):
             circuit = _random_two_q_clifford(first_qubit, second_qubit,
-                                             num_cfds, cfd_matrices, cliffords)
+                                             num_cfds, cliffords)
             circuit.append(ops.measure(first_qubit, second_qubit, key='z'))
             results = sampler.run(circuit, repetitions=repetitions)
             gnds = [(not r[0] and not r[1]) for r in results.measurements['z']]
@@ -404,10 +402,10 @@ def _measurement(two_qubit_circuit: circuits.Circuit) -> np.ndarray:
         prob_list = [results_hist[0], results_hist[1], results_hist[2]]
         return np.asarray(prob_list) / repetitions
 
-    sigma_0 = np.eye(2) / 2.0
-    sigma_1 = np.array([[0.0, 1.0], [1.0, 0.0]]) / 2.0
-    sigma_2 = np.array([[0.0, -1.0j], [1.0j, 0.0]]) / 2.0
-    sigma_3 = np.array([[1.0, 0.0], [0.0, -1.0]]) / 2.0
+    sigma_0 = np.eye(2) * 0.5
+    sigma_1 = np.array([[0.0, 1.0], [1.0, 0.0]]) * 0.5
+    sigma_2 = np.array([[0.0, -1.0j], [1.0j, 0.0]]) * 0.5
+    sigma_3 = np.array([[1.0, 0.0], [0.0, -1.0]]) * 0.5
     sigmas = [sigma_0, sigma_1, sigma_2, sigma_3]
 
     # Stores all 27 measured probabilities (P_00, P_01, P_10 after 9
@@ -455,57 +453,31 @@ def _indices_after_basis_rot(i: int, j: int) -> Tuple[int, Sequence[int],
     return mat_idx, indices, signs
 
 
-def _two_qubit_clifford_matrices(q_0: devices.GridQubit,
-                                 q_1: devices.GridQubit,
-                                 cliffords: Cliffords
-                                 ) -> np.ndarray:
-    mats = []
-
-    # Total number of different gates in the two-qubit Clifford group.
-    clifford_group_size = 11520
-
-    for i in range(clifford_group_size):
-        circuit = circuits.Circuit.from_ops(
-            _two_qubit_clifford(q_0, q_1, i, cliffords))
-        mats.append(protocols.unitary(circuit))
-    return np.array(mats)
-
-
 def _random_single_q_clifford(qubit: devices.GridQubit, num_cfds: int,
-                              cfds: Sequence[Sequence[ops.Gate]],
-                              cfd_matrices: np.ndarray) -> circuits.Circuit:
+                              cfds: Sequence[Sequence[ops.Gate]]
+                             ) -> circuits.Circuit:
     clifford_group_size = 24
     gate_ids = list(np.random.choice(clifford_group_size, num_cfds))
     gate_sequence = []  # type: List[ops.Gate]
     for gate_id in gate_ids:
         gate_sequence.extend(cfds[gate_id])
-    idx = _find_inv_matrix(_gate_seq_to_mats(gate_sequence), cfd_matrices)
-    gate_sequence.extend(cfds[idx])
+    gate_sequence.extend(protocols.inverse(gate_sequence))
     circuit = circuits.Circuit.from_ops(gate(qubit) for gate in gate_sequence)
     return circuit
 
 
 def _random_two_q_clifford(q_0: devices.GridQubit, q_1: devices.GridQubit,
-                           num_cfds: int, cfd_matrices: np.ndarray,
-                           cliffords: Cliffords
-                           ) -> circuits.Circuit:
+                           num_cfds: int,
+                           cliffords: Cliffords) -> circuits.Circuit:
     clifford_group_size = 11520
     idx_list = list(np.random.choice(clifford_group_size, num_cfds))
     circuit = circuits.Circuit()
     for idx in idx_list:
         circuit.append(_two_qubit_clifford(q_0, q_1, idx, cliffords))
-    inv_idx = _find_inv_matrix(protocols.unitary(circuit), cfd_matrices)
-    circuit.append(_two_qubit_clifford(q_0, q_1, inv_idx, cliffords))
+    circuit.append(protocols.inverse(circuit))
     return circuit
 
 
-def _find_inv_matrix(mat: np.ndarray, mat_sequence: np.ndarray) -> int:
-    mat_prod = np.einsum('ij,...jk->...ik', mat, mat_sequence)
-    diag_sums = list(np.absolute(np.einsum('...ii->...', mat_prod)))
-    idx = diag_sums.index(max(diag_sums))
-    return idx
-
-
 def _matrix_bar_plot(mat: np.ndarray,
                      z_label: str,
                      fig: plt.Figure,
@@ -566,13 +538,6 @@ def _plot_density_matrix(mat: np.ndarray) -> plt.Figure:
     return fig
 
 
-def _gate_seq_to_mats(gate_seq: Sequence[ops.Gate]) -> np.ndarray:
-    mat_rep = protocols.unitary(gate_seq[0])
-    for gate in gate_seq[1:]:
-        mat_rep = np.dot(protocols.unitary(gate), mat_rep)
-    return mat_rep
-
-
 def _two_qubit_clifford(q_0: devices.GridQubit, q_1: devices.GridQubit,
                         idx: int,
                         cliffords: Cliffords) -> Iterator[ops.OP_TREE]:
@@ -622,7 +587,7 @@ def _two_qubit_clifford(q_0: devices.GridQubit, q_1: devices.GridQubit,
     s1_y = cliffords.s1_y
 
     idx_0 = int(idx / 480)
-    idx_1 = int((idx % 480) / 20)
+    idx_1 = int((idx % 480) * 0.05)
     idx_2 = idx - idx_0 * 480 - idx_1 * 20
     yield _single_qubit_gates(c1[idx_0], q_0)
     yield _single_qubit_gates(c1[idx_1], q_1)
diff --git a/examples/qubit_characterizations_example.py b/examples/qubit_characterizations_example.py
--- a/examples/qubit_characterizations_example.py
+++ b/examples/qubit_characterizations_example.py
@@ -14,14 +14,16 @@ def main():
     rabi_results = cirq.experiments.rabi_oscillations(simulator, q_0, 4 * np.pi)
     rabi_results.plot()
 
+    num_cfds = range(5, 20, 5)
+
     # Clifford-based randomized benchmarking of single-qubit gates on q_0.
     rb_result_1q = cirq.experiments.single_qubit_randomized_benchmarking(
-        simulator, q_0)
+        simulator, q_0, num_clifford_range=num_cfds, repetitions=100)
     rb_result_1q.plot()
 
     # Clifford-based randomized benchmarking of two-qubit gates on q_0 and q_1.
     rb_result_2q = cirq.experiments.two_qubit_randomized_benchmarking(
-        simulator, q_0, q_1)
+        simulator, q_0, q_1, num_clifford_range=num_cfds, repetitions=100)
     rb_result_2q.plot()
 
     # State-tomography of q_0 after application of an X/2 rotation.


Test patch:
diff --git a/cirq/experiments/qubit_characterizations_test.py b/cirq/experiments/qubit_characterizations_test.py
--- a/cirq/experiments/qubit_characterizations_test.py
+++ b/cirq/experiments/qubit_characterizations_test.py
@@ -14,7 +14,7 @@ def test_rabi_oscillations():
     # small statistical error.
     simulator = sim.Simulator()
     qubit = GridQubit(0, 0)
-    results = rabi_oscillations(simulator, qubit, np.pi, repetitions=10000)
+    results = rabi_oscillations(simulator, qubit, np.pi, repetitions=1000)
     data = np.asarray(results.data)
     angles = data[:, 0]
     actual_pops = data[:, 1]
@@ -29,8 +29,10 @@ def test_single_qubit_randomized_benchmarking():
     simulator = sim.Simulator()
     qubit = GridQubit(0, 0)
     num_cfds = range(5, 20, 5)
-    results = single_qubit_randomized_benchmarking(simulator, qubit,
-                                                   num_clifford_range=num_cfds)
+    results = single_qubit_randomized_benchmarking(simulator,
+                                                   qubit,
+                                                   num_clifford_range=num_cfds,
+                                                   repetitions=100)
     g_pops = np.asarray(results.data)[:, 1]
     assert np.isclose(np.mean(g_pops), 1.0)
 
@@ -42,8 +44,11 @@ def test_two_qubit_randomized_benchmarking():
     q_0 = GridQubit(0, 0)
     q_1 = GridQubit(0, 1)
     num_cfds = range(5, 20, 5)
-    results = two_qubit_randomized_benchmarking(simulator, q_0, q_1,
-                                                num_clifford_range=num_cfds)
+    results = two_qubit_randomized_benchmarking(simulator,
+                                                q_0,
+                                                q_1,
+                                                num_clifford_range=num_cfds,
+                                                repetitions=100)
     g_pops = np.asarray(results.data)[:, 1]
     assert np.isclose(np.mean(g_pops), 1.0)
 
@@ -59,11 +64,11 @@ def test_single_qubit_state_tomography():
     circuit_3 = circuits.Circuit.from_ops(ops.H(qubit), ops.Y(qubit))
 
     act_rho_1 = single_qubit_state_tomography(simulator, qubit, circuit_1,
-                                              100000).data
+                                              1000).data
     act_rho_2 = single_qubit_state_tomography(simulator, qubit, circuit_2,
-                                              100000).data
+                                              1000).data
     act_rho_3 = single_qubit_state_tomography(simulator, qubit, circuit_3,
-                                              100000).data
+                                              1000).data
 
     tar_rho_1 = np.array([[0.5, 0.5j], [-0.5j, 0.5]])
     tar_rho_2 = np.array([[0.5, 0.5], [0.5, 0.5]])
@@ -96,24 +101,24 @@ def test_two_qubit_state_tomography():
     circuit_yx = circuits.Circuit.from_ops(ops.Y(q_0)**0.5, ops.X(q_1)**0.5)
 
     act_rho_00 = two_qubit_state_tomography(simulator, q_0, q_1, circuit_00,
-                                            100000).data
+                                            1000).data
     act_rho_01 = two_qubit_state_tomography(simulator, q_0, q_1, circuit_01,
-                                            100000).data
+                                            1000).data
     act_rho_10 = two_qubit_state_tomography(simulator, q_0, q_1, circuit_10,
-                                            100000).data
+                                            1000).data
     act_rho_11 = two_qubit_state_tomography(simulator, q_0, q_1, circuit_11,
-                                            100000).data
+                                            1000).data
     act_rho_hh = two_qubit_state_tomography(simulator, q_0, q_1, circuit_hh,
-                                            100000).data
+                                            1000).data
     act_rho_xy = two_qubit_state_tomography(simulator, q_0, q_1, circuit_xy,
-                                            100000).data
+                                            1000).data
     act_rho_yx = two_qubit_state_tomography(simulator, q_0, q_1, circuit_yx,
-                                            100000).data
+                                            1000).data
 
-    tar_rho_00 = np.outer([1.0, 0, 0, 1.0], [1.0, 0, 0, 1.0]) / 2.0
-    tar_rho_01 = np.outer([0, 1.0, 1.0, 0], [0, 1.0, 1.0, 0]) / 2.0
-    tar_rho_10 = np.outer([1.0, 0, 0, -1.0], [1.0, 0, 0, -1.0]) / 2.0
-    tar_rho_11 = np.outer([0, 1.0, -1.0, 0], [0, 1.0, -1.0, 0]) / 2.0
+    tar_rho_00 = np.outer([1.0, 0, 0, 1.0], [1.0, 0, 0, 1.0]) * 0.5
+    tar_rho_01 = np.outer([0, 1.0, 1.0, 0], [0, 1.0, 1.0, 0]) * 0.5
+    tar_rho_10 = np.outer([1.0, 0, 0, -1.0], [1.0, 0, 0, -1.0]) * 0.5
+    tar_rho_11 = np.outer([0, 1.0, -1.0, 0], [0, 1.0, -1.0, 0]) * 0.5
     tar_rho_hh = np.outer([0.5, 0.5, 0.5, 0.5], [0.5, 0.5, 0.5, 0.5])
     tar_rho_xy = np.outer([0.5, 0.5, -0.5j, -0.5j], [0.5, 0.5, 0.5j, 0.5j])
     tar_rho_yx = np.outer([0.5, -0.5j, 0.5, -0.5j], [0.5, 0.5j, 0.5, 0.5j])