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.
14import itertools
15import math
16from typing import Sequence
17
18import numpy as np
19import pytest
20
21import cirq
22
23
24def sample_noisy_bitstrings(
25    circuit: cirq.Circuit, qubit_order: Sequence[cirq.Qid], depolarization: float, repetitions: int
26) -> np.ndarray:
27    assert 0 <= depolarization <= 1
28    dim = np.prod(circuit.qid_shape(), dtype=np.int64)
29    n_incoherent = int(depolarization * repetitions)
30    n_coherent = repetitions - n_incoherent
31    incoherent_samples = np.random.randint(dim, size=n_incoherent)
32    circuit_with_measurements = cirq.Circuit(circuit, cirq.measure(*qubit_order, key='m'))
33    r = cirq.sample(circuit_with_measurements, repetitions=n_coherent)
34    coherent_samples = r.data['m'].to_numpy()
35    return np.concatenate((coherent_samples, incoherent_samples))
36
37
38def make_random_quantum_circuit(qubits: Sequence[cirq.Qid], depth: int) -> cirq.Circuit:
39    SQ_GATES = [cirq.X ** 0.5, cirq.Y ** 0.5, cirq.T]
40    circuit = cirq.Circuit()
41    cz_start = 0
42    for q in qubits:
43        circuit.append(cirq.H(q))
44    for _ in range(depth):
45        for q in qubits:
46            random_gate = SQ_GATES[np.random.randint(len(SQ_GATES))]
47            circuit.append(random_gate(q))
48        for q0, q1 in zip(
49            itertools.islice(qubits, cz_start, None, 2),
50            itertools.islice(qubits, cz_start + 1, None, 2),
51        ):
52            circuit.append(cirq.CNOT(q0, q1))
53        cz_start = 1 - cz_start
54    for q in qubits:
55        circuit.append(cirq.H(q))
56    return circuit
57
58
59@pytest.mark.parametrize(
60    'depolarization, estimator',
61    itertools.product(
62        (0.0, 0.2, 0.7, 1.0),
63        (
64            cirq.hog_score_xeb_fidelity_from_probabilities,
65            cirq.linear_xeb_fidelity_from_probabilities,
66            cirq.log_xeb_fidelity_from_probabilities,
67        ),
68    ),
69)
70def test_xeb_fidelity(depolarization, estimator):
71    prng_state = np.random.get_state()
72    np.random.seed(0)
73
74    fs = []
75    for _ in range(10):
76        qubits = cirq.LineQubit.range(5)
77        circuit = make_random_quantum_circuit(qubits, depth=12)
78        bitstrings = sample_noisy_bitstrings(circuit, qubits, depolarization, repetitions=5000)
79
80        f = cirq.xeb_fidelity(circuit, bitstrings, qubits, estimator=estimator)
81        amplitudes = cirq.final_state_vector(circuit)
82        f2 = cirq.xeb_fidelity(
83            circuit, bitstrings, qubits, amplitudes=amplitudes, estimator=estimator
84        )
85        assert np.abs(f - f2) < 1e-6
86
87        fs.append(f)
88
89    estimated_fidelity = np.mean(fs)
90    expected_fidelity = 1 - depolarization
91    assert np.isclose(estimated_fidelity, expected_fidelity, atol=0.04)
92
93    np.random.set_state(prng_state)
94
95
96def test_linear_and_log_xeb_fidelity():
97    prng_state = np.random.get_state()
98    np.random.seed(0)
99
100    depolarization = 0.5
101
102    fs_log = []
103    fs_lin = []
104    for _ in range(10):
105        qubits = cirq.LineQubit.range(5)
106        circuit = make_random_quantum_circuit(qubits, depth=12)
107        bitstrings = sample_noisy_bitstrings(
108            circuit, qubits, depolarization=depolarization, repetitions=5000
109        )
110
111        f_log = cirq.log_xeb_fidelity(circuit, bitstrings, qubits)
112        f_lin = cirq.linear_xeb_fidelity(circuit, bitstrings, qubits)
113
114        fs_log.append(f_log)
115        fs_lin.append(f_lin)
116
117    assert np.isclose(np.mean(fs_log), 1 - depolarization, atol=0.01)
118    assert np.isclose(np.mean(fs_lin), 1 - depolarization, atol=0.09)
119
120    np.random.set_state(prng_state)
121
122
123def test_xeb_fidelity_invalid_qubits():
124    q0, q1, q2 = cirq.LineQubit.range(3)
125    circuit = cirq.Circuit(cirq.H(q0), cirq.CNOT(q0, q1))
126    bitstrings = sample_noisy_bitstrings(circuit, (q0, q1, q2), 0.9, 10)
127    with pytest.raises(ValueError):
128        cirq.xeb_fidelity(circuit, bitstrings, (q0, q2))
129
130
131def test_xeb_fidelity_invalid_bitstrings():
132    q0, q1 = cirq.LineQubit.range(2)
133    circuit = cirq.Circuit(cirq.H(q0), cirq.CNOT(q0, q1))
134    bitstrings = [0, 1, 2, 3, 4]
135    with pytest.raises(ValueError):
136        cirq.xeb_fidelity(circuit, bitstrings, (q0, q1))
137
138
139def test_xeb_fidelity_tuple_input():
140    q0, q1 = cirq.LineQubit.range(2)
141    circuit = cirq.Circuit(cirq.H(q0), cirq.CNOT(q0, q1))
142    bitstrings = [0, 1, 2]
143    f1 = cirq.xeb_fidelity(circuit, bitstrings, (q0, q1))
144    f2 = cirq.xeb_fidelity(circuit, tuple(bitstrings), (q0, q1))
145    assert f1 == f2
146
147
148def test_least_squares_xeb_fidelity_from_expectations():
149    prng_state = np.random.get_state()
150    np.random.seed(0)
151
152    depolarization = 0.5
153
154    n_qubits = 5
155    dim = 2 ** n_qubits
156    n_circuits = 10
157    qubits = cirq.LineQubit.range(n_qubits)
158
159    measured_expectations_lin = []
160    exact_expectations_lin = []
161    measured_expectations_log = []
162    exact_expectations_log = []
163    uniform_expectations_log = []
164    for _ in range(n_circuits):
165        circuit = make_random_quantum_circuit(qubits, depth=12)
166        bitstrings = sample_noisy_bitstrings(
167            circuit, qubits, depolarization=depolarization, repetitions=5000
168        )
169        amplitudes = cirq.final_state_vector(circuit)
170        probabilities = cirq.state_vector_to_probabilities(amplitudes)
171
172        measured_expectations_lin.append(dim * np.mean(probabilities[bitstrings]))
173        exact_expectations_lin.append(dim * np.sum(probabilities ** 2))
174
175        measured_expectations_log.append(np.mean(np.log(dim * probabilities[bitstrings])))
176        exact_expectations_log.append(np.sum(probabilities * np.log(dim * probabilities)))
177        uniform_expectations_log.append(np.mean(np.log(dim * probabilities)))
178
179    f_lin, r_lin = cirq.experiments.least_squares_xeb_fidelity_from_expectations(
180        measured_expectations_lin, exact_expectations_lin, [1.0] * n_circuits
181    )
182    f_log, r_log = cirq.experiments.least_squares_xeb_fidelity_from_expectations(
183        measured_expectations_log, exact_expectations_log, uniform_expectations_log
184    )
185
186    assert np.isclose(f_lin, 1 - depolarization, atol=0.01)
187    assert np.isclose(f_log, 1 - depolarization, atol=0.01)
188    np.testing.assert_allclose(np.sum(np.array(r_lin) ** 2), 0.0, atol=1e-2)
189    np.testing.assert_allclose(np.sum(np.array(r_log) ** 2), 0.0, atol=1e-2)
190
191    np.random.set_state(prng_state)
192
193
194def test_least_squares_xeb_fidelity_from_expectations_bad_length():
195    with pytest.raises(ValueError) as exception_info:
196        _ = cirq.experiments.least_squares_xeb_fidelity_from_expectations([1.0], [1.0], [1.0, 2.0])
197    assert '1, 1, and 2' in str(exception_info.value)
198
199
200def test_least_squares_xeb_fidelity_from_probabilities():
201    prng_state = np.random.get_state()
202    np.random.seed(0)
203
204    depolarization = 0.5
205
206    n_qubits = 5
207    dim = 2 ** n_qubits
208    n_circuits = 10
209    qubits = cirq.LineQubit.range(n_qubits)
210
211    all_probabilities = []
212    observed_probabilities = []
213    for _ in range(n_circuits):
214        circuit = make_random_quantum_circuit(qubits, depth=12)
215        bitstrings = sample_noisy_bitstrings(
216            circuit, qubits, depolarization=depolarization, repetitions=5000
217        )
218        amplitudes = cirq.final_state_vector(circuit)
219        probabilities = cirq.state_vector_to_probabilities(amplitudes)
220
221        all_probabilities.append(probabilities)
222        observed_probabilities.append(probabilities[bitstrings])
223
224    f_lin, r_lin = cirq.least_squares_xeb_fidelity_from_probabilities(
225        dim, observed_probabilities, all_probabilities, None, True
226    )
227    f_log_np, r_log_np = cirq.least_squares_xeb_fidelity_from_probabilities(
228        dim, observed_probabilities, all_probabilities, np.log, True
229    )
230    f_log_math, r_log_math = cirq.least_squares_xeb_fidelity_from_probabilities(
231        dim, observed_probabilities, all_probabilities, math.log, False
232    )
233
234    assert np.isclose(f_lin, 1 - depolarization, atol=0.01)
235    assert np.isclose(f_log_np, 1 - depolarization, atol=0.01)
236    assert np.isclose(f_log_math, 1 - depolarization, atol=0.01)
237    np.testing.assert_allclose(np.sum(np.array(r_lin) ** 2), 0.0, atol=1e-2)
238    np.testing.assert_allclose(np.sum(np.array(r_log_np) ** 2), 0.0, atol=1e-2)
239    np.testing.assert_allclose(np.sum(np.array(r_log_math) ** 2), 0.0, atol=1e-2)
240
241    np.random.set_state(prng_state)
242