Skip to content

Commit e893662

Browse files
Add GHZ fidelity tool
1 parent 61a6993 commit e893662

File tree

2 files changed

+155
-0
lines changed

2 files changed

+155
-0
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
from __future__ import annotations
2+
3+
import numpy as np
4+
5+
import cirq.circuits as circuits
6+
import cirq.contrib.paulistring.pauli_string_measurement_with_readout_mitigation as psmrm
7+
import cirq.ops as ops
8+
import cirq.work as work
9+
10+
11+
def int_to_stabilizer(which_stabilizer: int, qubits: list[ops.Qid]) -> ops.PauliString:
12+
"""A mapping from the integers [0, ..., 2**num_qubits - 1] to GHZ stabilizers.
13+
14+
First, `which_stabilizer` is converted to binary. The binary digits indicate whether
15+
the given basis stabilizer is present. The basis stabilizers, in order, are
16+
Z0*Z1, Z1*Z2, ..., Z(N-2)*Z(N-1), X0*X1*...*X(N-1).
17+
18+
Args:
19+
which_stabilizer: The integer to convert to a stabilizer operator.
20+
qubits: The qubits in the GHZ state
21+
22+
Returns:
23+
The stabilizer operator.
24+
"""
25+
num_qubits = len(qubits)
26+
XXX = ops.X(qubits[0])
27+
for qubit in qubits[1:]:
28+
XXX *= ops.X(qubit)
29+
basis_ops = [ops.Z(qubits[i]) * ops.Z(qubits[i + 1]) for i in range(num_qubits - 1)] + [XXX]
30+
which_to_include = np.binary_repr(which_stabilizer, num_qubits)
31+
32+
op_to_return = ops.I(qubits[0])
33+
for q in range(num_qubits):
34+
if which_to_include[-1 - q] == "1":
35+
op_to_return *= basis_ops[q]
36+
37+
return op_to_return
38+
39+
40+
def measure_ghz_fidelity(
41+
circuit: circuits.Circuit,
42+
num_z_type: int,
43+
num_x_type: int,
44+
rng: np.random.Generator,
45+
sampler: work.Sampler,
46+
pauli_repetitions: int = 10000,
47+
readout_repetitions: int = 10_000,
48+
num_random_bitstrings: int = 30,
49+
) -> GHZFidelityResult:
50+
"""Randomly sample z-type and x-type stabilizers of the GHZ state and measure them with and
51+
without readout error mitigation.
52+
53+
Args:
54+
circuit: The circuit that prepares the GHZ state.
55+
num_z_type: The number of z-type stabilizers (all measured simultaneously)
56+
num_x_type: The number of x-type stabilizers
57+
"""
58+
qubits = list(circuit.all_qubits())
59+
n_qubits = len(qubits)
60+
61+
# pick random stabilizers
62+
z_type_ints = rng.choice(2 ** (n_qubits - 1), replace=False, size=num_z_type)
63+
x_type_ints = rng.choice(2 ** (len(qubits) - 1), replace=False, size=num_x_type) + 2 ** (
64+
len(qubits) - 1
65+
)
66+
67+
z_type_paulis = [int_to_stabilizer(i, qubits) for i in z_type_ints]
68+
x_type_paulis = [int_to_stabilizer(i, qubits) for i in x_type_ints]
69+
70+
paulis_to_measure = [z_type_paulis] + [[x] for x in x_type_paulis]
71+
circuits_to_pauli = {circuit.freeze(): paulis_to_measure}
72+
return GHZFidelityResult(
73+
psmrm.measure_pauli_strings(
74+
circuits_to_pauli,
75+
sampler,
76+
pauli_repetitions=pauli_repetitions,
77+
readout_repetitions=readout_repetitions,
78+
num_random_bitstrings=num_random_bitstrings,
79+
rng_or_seed=rng,
80+
)[0].results,
81+
num_z_type,
82+
num_x_type,
83+
)
84+
85+
86+
class GHZFidelityResult:
87+
"""A class for storing and analyzing the results of a GHZ fidelity benchmarking experiment."""
88+
89+
def __init__(
90+
self, data: list[psmrm.PauliStringMeasurementResult], num_z_type: int, num_x_type: int
91+
):
92+
self.data = data
93+
self.num_z_type = num_z_type
94+
self.num_x_type = num_x_type
95+
96+
def compute_z_type_fidelity(self, mitigated: bool = True) -> tuple[float, float]:
97+
"""Compute the z-type fidelity and statistical uncertainty.
98+
99+
Args:
100+
mitigated: Whether to apply readout error mitigation.
101+
102+
Returns:
103+
Return the average of the z-type stabilizers and the uncertainty of the average.
104+
"""
105+
z_outcomes = [
106+
res.mitigated_expectation if mitigated else res.unmitigated_expectation
107+
for res in self.data[: self.num_z_type]
108+
]
109+
return float(np.mean(z_outcomes)), float(np.std(z_outcomes) / np.sqrt(self.num_z_type))
110+
111+
def compute_x_type_fidelity(self, mitigated: bool = True) -> tuple[float, float]:
112+
"""Compute the x-type fidelity and statistical uncertainty.
113+
114+
Args:
115+
mitigated: Whether to apply readout error mitigation.
116+
117+
Returns:
118+
Return the average of the x-type stabilizers and the uncertainty of the average.
119+
"""
120+
x_outcomes = [
121+
res.mitigated_expectation if mitigated else res.unmitigated_expectation
122+
for res in self.data[self.num_z_type :]
123+
]
124+
assert len(x_outcomes) == self.num_x_type
125+
return float(np.mean(x_outcomes)), float(np.std(x_outcomes) / np.sqrt(self.num_x_type))
126+
127+
def compute_fidelity(self, mitigated: bool = True) -> tuple[float, float]:
128+
"""Compute the fidelity and statistical uncertainty.
129+
130+
Args:
131+
mitigated: Whether to apply readout error mitigation.
132+
133+
Returns:
134+
Return the average of the stabilizers and the uncertainty of the average.
135+
"""
136+
z, dz = self.compute_z_type_fidelity(mitigated)
137+
x, dx = self.compute_x_type_fidelity(mitigated)
138+
return (x + z) / 2, np.sqrt(dx**2 + dz**2) / 2
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import numpy as np
2+
3+
import cirq.devices as devices
4+
import cirq.experiments.ghz.fidelity as ghz_fidelity
5+
import cirq.experiments.ghz.ghz_1d as ghz_1d
6+
import cirq.sim as sim
7+
8+
9+
def test_measure_ghz_fidelity():
10+
qubits = devices.LineQubit.range(4)
11+
sampler = sim.Simulator()
12+
circuit = ghz_1d.generate_1d_ghz_circuit(qubits)
13+
rng = np.random.default_rng()
14+
result = ghz_fidelity.measure_ghz_fidelity(circuit, 20, 20, rng, sampler)
15+
f, df = result.compute_fidelity(mitigated=False)
16+
assert f == 1.0
17+
assert df == 0.0

0 commit comments

Comments
 (0)