|
| 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 |
0 commit comments