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 matplotlib.pyplot as plt
16import numpy as np
17import pytest
18import cirq
19
20from cirq.experiments import (
21    CrossEntropyResult,
22    CrossEntropyResultDict,
23    cross_entropy_benchmarking,
24    build_entangling_layers,
25)
26from cirq.experiments.cross_entropy_benchmarking import CrossEntropyPair, SpecklePurityPair
27
28
29def test_cross_entropy_benchmarking():
30    # Check that the fidelities returned from a four-qubit XEB simulation are
31    # close to 1 (deviations from 1 is expected due to finite number of
32    # measurements).
33    simulator = cirq.Simulator()
34    qubits = cirq.GridQubit.square(2)
35
36    # Sanity check single-qubit-gate causes error
37    with pytest.raises(ValueError):
38        build_entangling_layers(qubits, cirq.Z ** 0.91)
39
40    # Build a sequence of CZ gates.
41    interleaved_ops = build_entangling_layers(qubits, cirq.CZ ** 0.91)
42
43    # Specify a set of single-qubit rotations. Pick prime numbers for the
44    # exponent to avoid evolving the system into a basis state.
45    single_qubit_rots = [
46        [cirq.X ** 0.37],
47        [cirq.Y ** 0.73, cirq.X ** 0.53],
48        [cirq.Z ** 0.61, cirq.X ** 0.43],
49        [cirq.Y ** 0.19],
50    ]
51
52    # Simulate XEB using the default single-qubit gate set without two-qubit
53    # gates, XEB using the specified single-qubit gate set without two-qubit
54    # gates, and XEB using the specified single-qubit gate set with two-qubit
55    # gate. Check that the fidelities are close to 1.0 in all cases. Also,
56    # check that a single XEB fidelity is returned if a single cycle number
57    # is specified.
58    results_0 = cross_entropy_benchmarking(
59        simulator, qubits, num_circuits=3, repetitions=1000, cycles=range(4, 20, 5)
60    )
61    results_1 = cross_entropy_benchmarking(
62        simulator,
63        qubits,
64        num_circuits=3,
65        repetitions=1000,
66        cycles=[4, 8, 12],
67        scrambling_gates_per_cycle=single_qubit_rots,
68    )
69    results_2 = cross_entropy_benchmarking(
70        simulator,
71        qubits,
72        benchmark_ops=interleaved_ops,
73        num_circuits=3,
74        repetitions=1000,
75        cycles=[4, 8, 12],
76        scrambling_gates_per_cycle=single_qubit_rots,
77    )
78    results_3 = cross_entropy_benchmarking(
79        simulator,
80        qubits,
81        benchmark_ops=interleaved_ops,
82        num_circuits=3,
83        repetitions=1000,
84        cycles=15,
85        scrambling_gates_per_cycle=single_qubit_rots,
86    )
87    fidelities_0 = [datum.xeb_fidelity for datum in results_0.data]
88    fidelities_1 = [datum.xeb_fidelity for datum in results_1.data]
89    fidelities_2 = [datum.xeb_fidelity for datum in results_2.data]
90    fidelities_3 = [datum.xeb_fidelity for datum in results_3.data]
91    assert np.isclose(np.mean(fidelities_0), 1.0, atol=0.1)
92    assert np.isclose(np.mean(fidelities_1), 1.0, atol=0.1)
93    assert np.isclose(np.mean(fidelities_2), 1.0, atol=0.1)
94    assert len(fidelities_3) == 1
95
96    # Sanity test that plot runs.
97    ax = plt.subplot()
98    results_1.plot(ax)
99
100
101def test_cross_entropy_result_depolarizing_models():
102    prng = np.random.RandomState(59566)
103    S = 0.8
104    p = 0.99
105    data = [
106        CrossEntropyPair(num_cycle=d, xeb_fidelity=S * p ** d + prng.normal(scale=0.01))
107        for d in range(10, 211, 20)
108    ]
109    purity_data = [
110        SpecklePurityPair(num_cycle=d, purity=S * p ** (2 * d) + prng.normal(scale=0.01))
111        for d in range(10, 211, 20)
112    ]
113    result = CrossEntropyResult(data=data, repetitions=1000, purity_data=purity_data)
114    model = result.depolarizing_model()
115    purity_model = result.purity_depolarizing_model()
116    np.testing.assert_allclose(model.spam_depolarization, S, atol=1e-2)
117    np.testing.assert_allclose(model.cycle_depolarization, p, atol=1e-2)
118    np.testing.assert_allclose(purity_model.purity, p ** 2, atol=1e-2)
119
120
121def test_cross_entropy_result_repr():
122    result1 = CrossEntropyResult(
123        data=[CrossEntropyPair(2, 0.9), CrossEntropyPair(5, 0.5)], repetitions=1000
124    )
125    result2 = CrossEntropyResult(
126        data=[CrossEntropyPair(2, 0.9), CrossEntropyPair(5, 0.5)],
127        repetitions=1000,
128        purity_data=[SpecklePurityPair(2, 0.8), SpecklePurityPair(5, 0.3)],
129    )
130    cirq.testing.assert_equivalent_repr(result1)
131    cirq.testing.assert_equivalent_repr(result2)
132
133
134def test_cross_entropy_result_dict_repr():
135    pair = tuple(cirq.LineQubit.range(2))
136    result = CrossEntropyResult(
137        data=[CrossEntropyPair(2, 0.9), CrossEntropyPair(5, 0.5)], repetitions=1000
138    )
139    result_dict = CrossEntropyResultDict(results={pair: result})
140    cirq.testing.assert_equivalent_repr(result_dict)
141
142
143def test_cross_entropy_result_purity_model_fails_with_no_data():
144    data = [
145        CrossEntropyPair(num_cycle=2, xeb_fidelity=0.9),
146        CrossEntropyPair(num_cycle=4, xeb_fidelity=0.8),
147    ]
148    result = CrossEntropyResult(data=data, repetitions=1000)
149    with pytest.raises(ValueError):
150        _ = result.purity_depolarizing_model()
151