From e89366260425c0d6bf9e1878756befa4b8ab7f65 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Mon, 9 Mar 2026 22:59:40 +0000 Subject: [PATCH 01/16] Add GHZ fidelity tool --- cirq-core/cirq/experiments/ghz/fidelity.py | 138 ++++++++++++++++++ .../cirq/experiments/ghz/fidelity_test.py | 17 +++ 2 files changed, 155 insertions(+) create mode 100644 cirq-core/cirq/experiments/ghz/fidelity.py create mode 100644 cirq-core/cirq/experiments/ghz/fidelity_test.py diff --git a/cirq-core/cirq/experiments/ghz/fidelity.py b/cirq-core/cirq/experiments/ghz/fidelity.py new file mode 100644 index 00000000000..7ba16212147 --- /dev/null +++ b/cirq-core/cirq/experiments/ghz/fidelity.py @@ -0,0 +1,138 @@ +from __future__ import annotations + +import numpy as np + +import cirq.circuits as circuits +import cirq.contrib.paulistring.pauli_string_measurement_with_readout_mitigation as psmrm +import cirq.ops as ops +import cirq.work as work + + +def int_to_stabilizer(which_stabilizer: int, qubits: list[ops.Qid]) -> ops.PauliString: + """A mapping from the integers [0, ..., 2**num_qubits - 1] to GHZ stabilizers. + + First, `which_stabilizer` is converted to binary. The binary digits indicate whether + the given basis stabilizer is present. The basis stabilizers, in order, are + Z0*Z1, Z1*Z2, ..., Z(N-2)*Z(N-1), X0*X1*...*X(N-1). + + Args: + which_stabilizer: The integer to convert to a stabilizer operator. + qubits: The qubits in the GHZ state + + Returns: + The stabilizer operator. + """ + num_qubits = len(qubits) + XXX = ops.X(qubits[0]) + for qubit in qubits[1:]: + XXX *= ops.X(qubit) + basis_ops = [ops.Z(qubits[i]) * ops.Z(qubits[i + 1]) for i in range(num_qubits - 1)] + [XXX] + which_to_include = np.binary_repr(which_stabilizer, num_qubits) + + op_to_return = ops.I(qubits[0]) + for q in range(num_qubits): + if which_to_include[-1 - q] == "1": + op_to_return *= basis_ops[q] + + return op_to_return + + +def measure_ghz_fidelity( + circuit: circuits.Circuit, + num_z_type: int, + num_x_type: int, + rng: np.random.Generator, + sampler: work.Sampler, + pauli_repetitions: int = 10000, + readout_repetitions: int = 10_000, + num_random_bitstrings: int = 30, +) -> GHZFidelityResult: + """Randomly sample z-type and x-type stabilizers of the GHZ state and measure them with and + without readout error mitigation. + + Args: + circuit: The circuit that prepares the GHZ state. + num_z_type: The number of z-type stabilizers (all measured simultaneously) + num_x_type: The number of x-type stabilizers + """ + qubits = list(circuit.all_qubits()) + n_qubits = len(qubits) + + # pick random stabilizers + z_type_ints = rng.choice(2 ** (n_qubits - 1), replace=False, size=num_z_type) + x_type_ints = rng.choice(2 ** (len(qubits) - 1), replace=False, size=num_x_type) + 2 ** ( + len(qubits) - 1 + ) + + z_type_paulis = [int_to_stabilizer(i, qubits) for i in z_type_ints] + x_type_paulis = [int_to_stabilizer(i, qubits) for i in x_type_ints] + + paulis_to_measure = [z_type_paulis] + [[x] for x in x_type_paulis] + circuits_to_pauli = {circuit.freeze(): paulis_to_measure} + return GHZFidelityResult( + psmrm.measure_pauli_strings( + circuits_to_pauli, + sampler, + pauli_repetitions=pauli_repetitions, + readout_repetitions=readout_repetitions, + num_random_bitstrings=num_random_bitstrings, + rng_or_seed=rng, + )[0].results, + num_z_type, + num_x_type, + ) + + +class GHZFidelityResult: + """A class for storing and analyzing the results of a GHZ fidelity benchmarking experiment.""" + + def __init__( + self, data: list[psmrm.PauliStringMeasurementResult], num_z_type: int, num_x_type: int + ): + self.data = data + self.num_z_type = num_z_type + self.num_x_type = num_x_type + + def compute_z_type_fidelity(self, mitigated: bool = True) -> tuple[float, float]: + """Compute the z-type fidelity and statistical uncertainty. + + Args: + mitigated: Whether to apply readout error mitigation. + + Returns: + Return the average of the z-type stabilizers and the uncertainty of the average. + """ + z_outcomes = [ + res.mitigated_expectation if mitigated else res.unmitigated_expectation + for res in self.data[: self.num_z_type] + ] + return float(np.mean(z_outcomes)), float(np.std(z_outcomes) / np.sqrt(self.num_z_type)) + + def compute_x_type_fidelity(self, mitigated: bool = True) -> tuple[float, float]: + """Compute the x-type fidelity and statistical uncertainty. + + Args: + mitigated: Whether to apply readout error mitigation. + + Returns: + Return the average of the x-type stabilizers and the uncertainty of the average. + """ + x_outcomes = [ + res.mitigated_expectation if mitigated else res.unmitigated_expectation + for res in self.data[self.num_z_type :] + ] + assert len(x_outcomes) == self.num_x_type + return float(np.mean(x_outcomes)), float(np.std(x_outcomes) / np.sqrt(self.num_x_type)) + + def compute_fidelity(self, mitigated: bool = True) -> tuple[float, float]: + """Compute the fidelity and statistical uncertainty. + + Args: + mitigated: Whether to apply readout error mitigation. + + Returns: + Return the average of the stabilizers and the uncertainty of the average. + """ + z, dz = self.compute_z_type_fidelity(mitigated) + x, dx = self.compute_x_type_fidelity(mitigated) + return (x + z) / 2, np.sqrt(dx**2 + dz**2) / 2 diff --git a/cirq-core/cirq/experiments/ghz/fidelity_test.py b/cirq-core/cirq/experiments/ghz/fidelity_test.py new file mode 100644 index 00000000000..4fea692a2d9 --- /dev/null +++ b/cirq-core/cirq/experiments/ghz/fidelity_test.py @@ -0,0 +1,17 @@ +import numpy as np + +import cirq.devices as devices +import cirq.experiments.ghz.fidelity as ghz_fidelity +import cirq.experiments.ghz.ghz_1d as ghz_1d +import cirq.sim as sim + + +def test_measure_ghz_fidelity(): + qubits = devices.LineQubit.range(4) + sampler = sim.Simulator() + circuit = ghz_1d.generate_1d_ghz_circuit(qubits) + rng = np.random.default_rng() + result = ghz_fidelity.measure_ghz_fidelity(circuit, 20, 20, rng, sampler) + f, df = result.compute_fidelity(mitigated=False) + assert f == 1.0 + assert df == 0.0 From e577b76e7f1eb1940d7945c33ea9889f3a3b8814 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Mon, 9 Mar 2026 23:14:23 +0000 Subject: [PATCH 02/16] Update test --- cirq-core/cirq/experiments/ghz/fidelity_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/experiments/ghz/fidelity_test.py b/cirq-core/cirq/experiments/ghz/fidelity_test.py index 4fea692a2d9..249ba45205a 100644 --- a/cirq-core/cirq/experiments/ghz/fidelity_test.py +++ b/cirq-core/cirq/experiments/ghz/fidelity_test.py @@ -7,7 +7,7 @@ def test_measure_ghz_fidelity(): - qubits = devices.LineQubit.range(4) + qubits = devices.LineQubit.range(10) sampler = sim.Simulator() circuit = ghz_1d.generate_1d_ghz_circuit(qubits) rng = np.random.default_rng() From f373cb0dbb2a7b4372d947c7c48e7ce6b7e5729b Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Mon, 9 Mar 2026 23:19:48 +0000 Subject: [PATCH 03/16] Add copyright notice --- cirq-core/cirq/experiments/ghz/fidelity.py | 14 ++++++++++++++ cirq-core/cirq/experiments/ghz/fidelity_test.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/cirq-core/cirq/experiments/ghz/fidelity.py b/cirq-core/cirq/experiments/ghz/fidelity.py index 7ba16212147..aeb72849de1 100644 --- a/cirq-core/cirq/experiments/ghz/fidelity.py +++ b/cirq-core/cirq/experiments/ghz/fidelity.py @@ -1,3 +1,17 @@ +# Copyright 2026 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from __future__ import annotations import numpy as np diff --git a/cirq-core/cirq/experiments/ghz/fidelity_test.py b/cirq-core/cirq/experiments/ghz/fidelity_test.py index 249ba45205a..07bbbd78ab1 100644 --- a/cirq-core/cirq/experiments/ghz/fidelity_test.py +++ b/cirq-core/cirq/experiments/ghz/fidelity_test.py @@ -1,3 +1,17 @@ +# Copyright 2026 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import numpy as np import cirq.devices as devices From 2dd2f030582fd530b793cb013a6b9489c12d09b6 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Mon, 9 Mar 2026 23:41:46 +0000 Subject: [PATCH 04/16] Update docstring --- cirq-core/cirq/experiments/ghz/fidelity.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cirq-core/cirq/experiments/ghz/fidelity.py b/cirq-core/cirq/experiments/ghz/fidelity.py index aeb72849de1..e091534ed43 100644 --- a/cirq-core/cirq/experiments/ghz/fidelity.py +++ b/cirq-core/cirq/experiments/ghz/fidelity.py @@ -68,6 +68,12 @@ def measure_ghz_fidelity( circuit: The circuit that prepares the GHZ state. num_z_type: The number of z-type stabilizers (all measured simultaneously) num_x_type: The number of x-type stabilizers + rng: The random number generator to use. + pauli_repetitions: The number of repetitions to use for measuring stabilizers. + readout_repetitions: The number of repetitions to use for benchmarking readout + (for readout error mitigation). + num_random_bitstrings: The number of random bitstrings for readout benchmarking + (for readout error mitigation). Set to 0 to skip readout benchmarking. """ qubits = list(circuit.all_qubits()) n_qubits = len(qubits) From 69e172aac87248a0a2cdc502318e818218cd5a99 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Mon, 9 Mar 2026 23:48:27 +0000 Subject: [PATCH 05/16] Update docstring --- cirq-core/cirq/experiments/ghz/fidelity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cirq-core/cirq/experiments/ghz/fidelity.py b/cirq-core/cirq/experiments/ghz/fidelity.py index e091534ed43..ff407efb310 100644 --- a/cirq-core/cirq/experiments/ghz/fidelity.py +++ b/cirq-core/cirq/experiments/ghz/fidelity.py @@ -68,6 +68,7 @@ def measure_ghz_fidelity( circuit: The circuit that prepares the GHZ state. num_z_type: The number of z-type stabilizers (all measured simultaneously) num_x_type: The number of x-type stabilizers + sampler: The simulator or hardware sampler on which to run. rng: The random number generator to use. pauli_repetitions: The number of repetitions to use for measuring stabilizers. readout_repetitions: The number of repetitions to use for benchmarking readout From 3c869daea3a5c3d328e84809a59ff7f6bbc9ce62 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Tue, 10 Mar 2026 01:22:37 +0000 Subject: [PATCH 06/16] types --- cirq-core/cirq/experiments/ghz/fidelity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/experiments/ghz/fidelity.py b/cirq-core/cirq/experiments/ghz/fidelity.py index ff407efb310..04c0c0e5dfa 100644 --- a/cirq-core/cirq/experiments/ghz/fidelity.py +++ b/cirq-core/cirq/experiments/ghz/fidelity.py @@ -43,7 +43,7 @@ def int_to_stabilizer(which_stabilizer: int, qubits: list[ops.Qid]) -> ops.Pauli basis_ops = [ops.Z(qubits[i]) * ops.Z(qubits[i + 1]) for i in range(num_qubits - 1)] + [XXX] which_to_include = np.binary_repr(which_stabilizer, num_qubits) - op_to_return = ops.I(qubits[0]) + op_to_return: ops.PauliString = ops.PauliString(ops.I(qubits[0])) for q in range(num_qubits): if which_to_include[-1 - q] == "1": op_to_return *= basis_ops[q] From 30b1b4ec6b66d18919abc3223f20a3acaaa8acf6 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Thu, 12 Mar 2026 04:29:01 +0000 Subject: [PATCH 07/16] Support sampling all stabilizers and improve efficiency of int_to_stabilizer (thanks ikd@ for the latter) --- cirq-core/cirq/experiments/ghz/fidelity.py | 97 +++++++++++++++---- .../cirq/experiments/ghz/fidelity_test.py | 7 ++ 2 files changed, 85 insertions(+), 19 deletions(-) diff --git a/cirq-core/cirq/experiments/ghz/fidelity.py b/cirq-core/cirq/experiments/ghz/fidelity.py index 04c0c0e5dfa..232dc7f2a68 100644 --- a/cirq-core/cirq/experiments/ghz/fidelity.py +++ b/cirq-core/cirq/experiments/ghz/fidelity.py @@ -14,6 +14,8 @@ from __future__ import annotations +from typing import cast, Sequence + import numpy as np import cirq.circuits as circuits @@ -22,7 +24,9 @@ import cirq.work as work -def int_to_stabilizer(which_stabilizer: int, qubits: list[ops.Qid]) -> ops.PauliString: +def int_to_stabilizer( + which_stabilizer: int, qubits: list[ops.Qid], basis_ops: list[ops.PauliString] +) -> ops.PauliString: """A mapping from the integers [0, ..., 2**num_qubits - 1] to GHZ stabilizers. First, `which_stabilizer` is converted to binary. The binary digits indicate whether @@ -31,26 +35,42 @@ def int_to_stabilizer(which_stabilizer: int, qubits: list[ops.Qid]) -> ops.Pauli Args: which_stabilizer: The integer to convert to a stabilizer operator. - qubits: The qubits in the GHZ state + qubits: The qubits in the GHZ state. + basis_ops: A choice of len(qubits) independent stabilizers. Returns: The stabilizer operator. """ num_qubits = len(qubits) - XXX = ops.X(qubits[0]) - for qubit in qubits[1:]: - XXX *= ops.X(qubit) - basis_ops = [ops.Z(qubits[i]) * ops.Z(qubits[i + 1]) for i in range(num_qubits - 1)] + [XXX] - which_to_include = np.binary_repr(which_stabilizer, num_qubits) - op_to_return: ops.PauliString = ops.PauliString(ops.I(qubits[0])) for q in range(num_qubits): - if which_to_include[-1 - q] == "1": + if (which_stabilizer >> q) & 1: op_to_return *= basis_ops[q] - return op_to_return +def generate_stabilizers( + stabilizer_ints: Sequence[int], qubits: list[ops.Qid] +) -> list[ops.PauliString]: + """Generate a list of stabilizers from a sequence of stabilizer integers. + + Args: + stabilizer_ints: The integers from which to generate the stabilizers. + qubits: The qubits in the GHZ state. + + Returns: + The list of stabilizers. + """ + num_qubits = len(qubits) + # Precompute basis_ops once + XXX: ops.PauliString = ops.PauliString({q: ops.X for q in qubits}) + basis_ops = [ + ops.PauliString({qubits[i]: ops.Z, qubits[i + 1]: ops.Z}) for i in range(num_qubits - 1) + ] + [XXX] + + return [int_to_stabilizer(i, qubits, basis_ops) for i in stabilizer_ints] + + def measure_ghz_fidelity( circuit: circuits.Circuit, num_z_type: int, @@ -80,13 +100,16 @@ def measure_ghz_fidelity( n_qubits = len(qubits) # pick random stabilizers - z_type_ints = rng.choice(2 ** (n_qubits - 1), replace=False, size=num_z_type) - x_type_ints = rng.choice(2 ** (len(qubits) - 1), replace=False, size=num_x_type) + 2 ** ( - len(qubits) - 1 + z_type_ints = cast( + Sequence, rng.choice(range(1, 2 ** (n_qubits - 1)), replace=False, size=num_z_type) + ) + x_type_ints = cast( + Sequence, + rng.choice(2 ** (len(qubits) - 1), replace=False, size=num_x_type) + 2 ** (len(qubits) - 1), ) - z_type_paulis = [int_to_stabilizer(i, qubits) for i in z_type_ints] - x_type_paulis = [int_to_stabilizer(i, qubits) for i in x_type_ints] + z_type_paulis = generate_stabilizers(z_type_ints, qubits) + x_type_paulis = generate_stabilizers(x_type_ints, qubits) paulis_to_measure = [z_type_paulis] + [[x] for x in x_type_paulis] circuits_to_pauli = {circuit.freeze(): paulis_to_measure} @@ -101,6 +124,7 @@ def measure_ghz_fidelity( )[0].results, num_z_type, num_x_type, + n_qubits, ) @@ -108,11 +132,16 @@ class GHZFidelityResult: """A class for storing and analyzing the results of a GHZ fidelity benchmarking experiment.""" def __init__( - self, data: list[psmrm.PauliStringMeasurementResult], num_z_type: int, num_x_type: int + self, + data: list[psmrm.PauliStringMeasurementResult], + num_z_type: int, + num_x_type: int, + n_qubits: int, ): self.data = data self.num_z_type = num_z_type self.num_x_type = num_x_type + self.n_qubits = n_qubits def compute_z_type_fidelity(self, mitigated: bool = True) -> tuple[float, float]: """Compute the z-type fidelity and statistical uncertainty. @@ -127,7 +156,21 @@ def compute_z_type_fidelity(self, mitigated: bool = True) -> tuple[float, float] res.mitigated_expectation if mitigated else res.unmitigated_expectation for res in self.data[: self.num_z_type] ] - return float(np.mean(z_outcomes)), float(np.std(z_outcomes) / np.sqrt(self.num_z_type)) + + if self.num_z_type < 2 ** (self.n_qubits - 1) - 1: + dz = float(np.std(z_outcomes) / np.sqrt(self.num_z_type)) + elif self.num_z_type == 2 ** (self.n_qubits - 1) - 1: + dz = ( + np.sqrt( + sum( + res.mitigated_stddev**2 if mitigated else res.unmitigated_stddev**2 + for res in self.data[: self.num_z_type] + ) + ) + / self.num_z_type + ) + + return float(np.mean(z_outcomes)), dz def compute_x_type_fidelity(self, mitigated: bool = True) -> tuple[float, float]: """Compute the x-type fidelity and statistical uncertainty. @@ -143,7 +186,21 @@ def compute_x_type_fidelity(self, mitigated: bool = True) -> tuple[float, float] for res in self.data[self.num_z_type :] ] assert len(x_outcomes) == self.num_x_type - return float(np.mean(x_outcomes)), float(np.std(x_outcomes) / np.sqrt(self.num_x_type)) + + if self.num_x_type < 2 ** (self.n_qubits - 1): + dx = float(np.std(x_outcomes) / np.sqrt(self.num_x_type)) + elif self.num_x_type == 2 ** (self.n_qubits - 1): + dx = ( + np.sqrt( + sum( + res.mitigated_stddev**2 if mitigated else res.unmitigated_stddev**2 + for res in self.data[self.num_z_type :] + ) + ) + / self.num_x_type + ) + + return float(np.mean(x_outcomes)), dx def compute_fidelity(self, mitigated: bool = True) -> tuple[float, float]: """Compute the fidelity and statistical uncertainty. @@ -156,4 +213,6 @@ def compute_fidelity(self, mitigated: bool = True) -> tuple[float, float]: """ z, dz = self.compute_z_type_fidelity(mitigated) x, dx = self.compute_x_type_fidelity(mitigated) - return (x + z) / 2, np.sqrt(dx**2 + dz**2) / 2 + return 1 / 2**self.n_qubits + (0.5 - 1 / 2**self.n_qubits) * z + 0.5 * x, np.sqrt( + ((0.5 - 1 / 2**self.n_qubits) * dz) ** 2 + (0.5 * dx) ** 2 + ) diff --git a/cirq-core/cirq/experiments/ghz/fidelity_test.py b/cirq-core/cirq/experiments/ghz/fidelity_test.py index 07bbbd78ab1..fdeaa6286f5 100644 --- a/cirq-core/cirq/experiments/ghz/fidelity_test.py +++ b/cirq-core/cirq/experiments/ghz/fidelity_test.py @@ -29,3 +29,10 @@ def test_measure_ghz_fidelity(): f, df = result.compute_fidelity(mitigated=False) assert f == 1.0 assert df == 0.0 + + qubits = devices.LineQubit.range(4) + circuit = ghz_1d.generate_1d_ghz_circuit(qubits) + result = ghz_fidelity.measure_ghz_fidelity(circuit, 2**3 - 1, 2**3, rng, sampler) + f, df = result.compute_fidelity(mitigated=False) + assert f == 1.0 + assert df == 0.0 From 4b24b8f1bc9a1bb0a8ebdeffc216263942b37bf5 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Thu, 12 Mar 2026 04:33:02 +0000 Subject: [PATCH 08/16] move to contrib --- cirq-core/cirq/{experiments => contrib}/ghz/__init__.py | 0 cirq-core/cirq/{experiments => contrib}/ghz/fidelity.py | 0 cirq-core/cirq/{experiments => contrib}/ghz/fidelity_test.py | 0 cirq-core/cirq/{experiments => contrib}/ghz/ghz_1d.py | 0 cirq-core/cirq/{experiments => contrib}/ghz/ghz_1d_test.py | 0 cirq-core/cirq/{experiments => contrib}/ghz/ghz_2d.py | 0 cirq-core/cirq/{experiments => contrib}/ghz/ghz_2d_test.py | 0 cirq-core/cirq/experiments/__init__.py | 5 +---- 8 files changed, 1 insertion(+), 4 deletions(-) rename cirq-core/cirq/{experiments => contrib}/ghz/__init__.py (100%) rename cirq-core/cirq/{experiments => contrib}/ghz/fidelity.py (100%) rename cirq-core/cirq/{experiments => contrib}/ghz/fidelity_test.py (100%) rename cirq-core/cirq/{experiments => contrib}/ghz/ghz_1d.py (100%) rename cirq-core/cirq/{experiments => contrib}/ghz/ghz_1d_test.py (100%) rename cirq-core/cirq/{experiments => contrib}/ghz/ghz_2d.py (100%) rename cirq-core/cirq/{experiments => contrib}/ghz/ghz_2d_test.py (100%) diff --git a/cirq-core/cirq/experiments/ghz/__init__.py b/cirq-core/cirq/contrib/ghz/__init__.py similarity index 100% rename from cirq-core/cirq/experiments/ghz/__init__.py rename to cirq-core/cirq/contrib/ghz/__init__.py diff --git a/cirq-core/cirq/experiments/ghz/fidelity.py b/cirq-core/cirq/contrib/ghz/fidelity.py similarity index 100% rename from cirq-core/cirq/experiments/ghz/fidelity.py rename to cirq-core/cirq/contrib/ghz/fidelity.py diff --git a/cirq-core/cirq/experiments/ghz/fidelity_test.py b/cirq-core/cirq/contrib/ghz/fidelity_test.py similarity index 100% rename from cirq-core/cirq/experiments/ghz/fidelity_test.py rename to cirq-core/cirq/contrib/ghz/fidelity_test.py diff --git a/cirq-core/cirq/experiments/ghz/ghz_1d.py b/cirq-core/cirq/contrib/ghz/ghz_1d.py similarity index 100% rename from cirq-core/cirq/experiments/ghz/ghz_1d.py rename to cirq-core/cirq/contrib/ghz/ghz_1d.py diff --git a/cirq-core/cirq/experiments/ghz/ghz_1d_test.py b/cirq-core/cirq/contrib/ghz/ghz_1d_test.py similarity index 100% rename from cirq-core/cirq/experiments/ghz/ghz_1d_test.py rename to cirq-core/cirq/contrib/ghz/ghz_1d_test.py diff --git a/cirq-core/cirq/experiments/ghz/ghz_2d.py b/cirq-core/cirq/contrib/ghz/ghz_2d.py similarity index 100% rename from cirq-core/cirq/experiments/ghz/ghz_2d.py rename to cirq-core/cirq/contrib/ghz/ghz_2d.py diff --git a/cirq-core/cirq/experiments/ghz/ghz_2d_test.py b/cirq-core/cirq/contrib/ghz/ghz_2d_test.py similarity index 100% rename from cirq-core/cirq/experiments/ghz/ghz_2d_test.py rename to cirq-core/cirq/contrib/ghz/ghz_2d_test.py diff --git a/cirq-core/cirq/experiments/__init__.py b/cirq-core/cirq/experiments/__init__.py index 5dbdba5d84f..00e86a001d6 100644 --- a/cirq-core/cirq/experiments/__init__.py +++ b/cirq-core/cirq/experiments/__init__.py @@ -89,7 +89,4 @@ from cirq.experiments.z_phase_calibration import ( z_phase_calibration_workflow as z_phase_calibration_workflow, calibrate_z_phases as calibrate_z_phases, -) - -from cirq.experiments.ghz.ghz_2d import generate_2d_ghz_circuit as generate_2d_ghz_circuit -from cirq.experiments.ghz.ghz_1d import generate_1d_ghz_circuit as generate_1d_ghz_circuit +) \ No newline at end of file From 63a00ecd21916589df124abf5f58527634e2548f Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Thu, 12 Mar 2026 04:33:56 +0000 Subject: [PATCH 09/16] black --- cirq-core/cirq/experiments/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/experiments/__init__.py b/cirq-core/cirq/experiments/__init__.py index 00e86a001d6..8df1351d500 100644 --- a/cirq-core/cirq/experiments/__init__.py +++ b/cirq-core/cirq/experiments/__init__.py @@ -89,4 +89,4 @@ from cirq.experiments.z_phase_calibration import ( z_phase_calibration_workflow as z_phase_calibration_workflow, calibrate_z_phases as calibrate_z_phases, -) \ No newline at end of file +) From 4cd9471dc16d2bb20af88daedc06dee2c51df5bb Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Thu, 12 Mar 2026 04:36:06 +0000 Subject: [PATCH 10/16] update tests --- cirq-core/cirq/contrib/ghz/fidelity_test.py | 4 ++-- cirq-core/cirq/contrib/ghz/ghz_1d_test.py | 2 +- cirq-core/cirq/contrib/ghz/ghz_2d_test.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cirq-core/cirq/contrib/ghz/fidelity_test.py b/cirq-core/cirq/contrib/ghz/fidelity_test.py index fdeaa6286f5..e61a678371e 100644 --- a/cirq-core/cirq/contrib/ghz/fidelity_test.py +++ b/cirq-core/cirq/contrib/ghz/fidelity_test.py @@ -14,9 +14,9 @@ import numpy as np +import cirq.contrib.ghz.fidelity as ghz_fidelity +import cirq.contrib.ghz.ghz_1d as ghz_1d import cirq.devices as devices -import cirq.experiments.ghz.fidelity as ghz_fidelity -import cirq.experiments.ghz.ghz_1d as ghz_1d import cirq.sim as sim diff --git a/cirq-core/cirq/contrib/ghz/ghz_1d_test.py b/cirq-core/cirq/contrib/ghz/ghz_1d_test.py index 43d29f54618..027062ed6a4 100644 --- a/cirq-core/cirq/contrib/ghz/ghz_1d_test.py +++ b/cirq-core/cirq/contrib/ghz/ghz_1d_test.py @@ -14,8 +14,8 @@ import numpy as np +import cirq.contrib.ghz.ghz_1d as ghz_1d import cirq.devices as devices -import cirq.experiments.ghz.ghz_1d as ghz_1d import cirq.sim as sim diff --git a/cirq-core/cirq/contrib/ghz/ghz_2d_test.py b/cirq-core/cirq/contrib/ghz/ghz_2d_test.py index ec760a6250f..55570d8d7c3 100644 --- a/cirq-core/cirq/contrib/ghz/ghz_2d_test.py +++ b/cirq-core/cirq/contrib/ghz/ghz_2d_test.py @@ -21,7 +21,7 @@ import pytest import cirq -import cirq.experiments.ghz.ghz_2d as ghz_2d +import cirq.contrib.ghz.ghz_2d as ghz_2d def _create_mock_graph() -> tuple[nx.Graph, cirq.GridQubit]: From 2917b1471ce8fe654595273932265664652c5b81 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Thu, 12 Mar 2026 04:38:48 +0000 Subject: [PATCH 11/16] ruff --- cirq-core/cirq/contrib/ghz/fidelity.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/contrib/ghz/fidelity.py b/cirq-core/cirq/contrib/ghz/fidelity.py index 232dc7f2a68..69f2881d205 100644 --- a/cirq-core/cirq/contrib/ghz/fidelity.py +++ b/cirq-core/cirq/contrib/ghz/fidelity.py @@ -14,7 +14,8 @@ from __future__ import annotations -from typing import cast, Sequence +from typing import cast +from collections.abc import Sequence import numpy as np @@ -63,7 +64,7 @@ def generate_stabilizers( """ num_qubits = len(qubits) # Precompute basis_ops once - XXX: ops.PauliString = ops.PauliString({q: ops.X for q in qubits}) + XXX: ops.PauliString = ops.PauliString(dict.fromkeys(qubits, ops.X)) basis_ops = [ ops.PauliString({qubits[i]: ops.Z, qubits[i + 1]: ops.Z}) for i in range(num_qubits - 1) ] + [XXX] From c999d79931847d0164bbb225fd0f94eed39545cf Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Thu, 12 Mar 2026 04:47:02 +0000 Subject: [PATCH 12/16] black --- cirq-core/cirq/contrib/ghz/fidelity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/contrib/ghz/fidelity.py b/cirq-core/cirq/contrib/ghz/fidelity.py index 69f2881d205..ce4268a0941 100644 --- a/cirq-core/cirq/contrib/ghz/fidelity.py +++ b/cirq-core/cirq/contrib/ghz/fidelity.py @@ -14,8 +14,8 @@ from __future__ import annotations -from typing import cast from collections.abc import Sequence +from typing import cast import numpy as np From 94a3e59cdf8d6636965137cd3590698cd7ac29cc Mon Sep 17 00:00:00 2001 From: Pavol Juhas Date: Thu, 12 Mar 2026 15:53:03 -0700 Subject: [PATCH 13/16] Use Sequence type for arguments that use only the Sequence interface --- cirq-core/cirq/contrib/ghz/fidelity.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cirq-core/cirq/contrib/ghz/fidelity.py b/cirq-core/cirq/contrib/ghz/fidelity.py index ce4268a0941..4d83919adc5 100644 --- a/cirq-core/cirq/contrib/ghz/fidelity.py +++ b/cirq-core/cirq/contrib/ghz/fidelity.py @@ -26,7 +26,7 @@ def int_to_stabilizer( - which_stabilizer: int, qubits: list[ops.Qid], basis_ops: list[ops.PauliString] + which_stabilizer: int, qubits: Sequence[ops.Qid], basis_ops: Sequence[ops.PauliString] ) -> ops.PauliString: """A mapping from the integers [0, ..., 2**num_qubits - 1] to GHZ stabilizers. @@ -51,7 +51,7 @@ def int_to_stabilizer( def generate_stabilizers( - stabilizer_ints: Sequence[int], qubits: list[ops.Qid] + stabilizer_ints: Sequence[int], qubits: Sequence[ops.Qid] ) -> list[ops.PauliString]: """Generate a list of stabilizers from a sequence of stabilizer integers. @@ -78,7 +78,7 @@ def measure_ghz_fidelity( num_x_type: int, rng: np.random.Generator, sampler: work.Sampler, - pauli_repetitions: int = 10000, + pauli_repetitions: int = 10_000, readout_repetitions: int = 10_000, num_random_bitstrings: int = 30, ) -> GHZFidelityResult: @@ -102,10 +102,10 @@ def measure_ghz_fidelity( # pick random stabilizers z_type_ints = cast( - Sequence, rng.choice(range(1, 2 ** (n_qubits - 1)), replace=False, size=num_z_type) + Sequence[int], rng.choice(range(1, 2 ** (n_qubits - 1)), replace=False, size=num_z_type) ) x_type_ints = cast( - Sequence, + Sequence[int], rng.choice(2 ** (len(qubits) - 1), replace=False, size=num_x_type) + 2 ** (len(qubits) - 1), ) From aece661ef16d545196ba372679a62552f58e2e26 Mon Sep 17 00:00:00 2001 From: Pavol Juhas Date: Thu, 12 Mar 2026 19:19:49 -0700 Subject: [PATCH 14/16] Make test smaller and faster And cover both mitigated and unmitigated variants. --- cirq-core/cirq/contrib/ghz/fidelity_test.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/cirq-core/cirq/contrib/ghz/fidelity_test.py b/cirq-core/cirq/contrib/ghz/fidelity_test.py index e61a678371e..7ede741e9b6 100644 --- a/cirq-core/cirq/contrib/ghz/fidelity_test.py +++ b/cirq-core/cirq/contrib/ghz/fidelity_test.py @@ -20,19 +20,17 @@ import cirq.sim as sim -def test_measure_ghz_fidelity(): +def test_measure_ghz_fidelity() -> None: qubits = devices.LineQubit.range(10) sampler = sim.Simulator() circuit = ghz_1d.generate_1d_ghz_circuit(qubits) rng = np.random.default_rng() - result = ghz_fidelity.measure_ghz_fidelity(circuit, 20, 20, rng, sampler) - f, df = result.compute_fidelity(mitigated=False) - assert f == 1.0 - assert df == 0.0 - - qubits = devices.LineQubit.range(4) - circuit = ghz_1d.generate_1d_ghz_circuit(qubits) - result = ghz_fidelity.measure_ghz_fidelity(circuit, 2**3 - 1, 2**3, rng, sampler) + result = ghz_fidelity.measure_ghz_fidelity( + circuit, 20, 20, rng, sampler, pauli_repetitions=100, readout_repetitions=1000 + ) f, df = result.compute_fidelity(mitigated=False) assert f == 1.0 assert df == 0.0 + f_m, df_m = result.compute_fidelity(mitigated=True) + assert f_m == 1.0 + assert df_m == 0.0 From 1dccdc5c4d1c33b858e5374e53bfbcbc79e1d3c7 Mon Sep 17 00:00:00 2001 From: Pavol Juhas Date: Thu, 12 Mar 2026 19:32:48 -0700 Subject: [PATCH 15/16] Adjust type annotation Use more general Sequence type which is covariant, ie, allowing subclass-type items. --- cirq-core/cirq/contrib/ghz/ghz_1d.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cirq-core/cirq/contrib/ghz/ghz_1d.py b/cirq-core/cirq/contrib/ghz/ghz_1d.py index 0fd43ec06cb..a32bc6f9714 100644 --- a/cirq-core/cirq/contrib/ghz/ghz_1d.py +++ b/cirq-core/cirq/contrib/ghz/ghz_1d.py @@ -12,12 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +from collections.abc import Sequence + import cirq.circuits as circuits import cirq.ops as ops import cirq.transformers as transformers -def _create_odd_ghz(qubits: list[ops.Qid]) -> circuits.Circuit: +def _create_odd_ghz(qubits: Sequence[ops.Qid]) -> circuits.Circuit: """Circuit to create a GHZ state on an odd number of qubits with 1D connectivity. Example: @@ -77,7 +79,7 @@ def _create_odd_ghz(qubits: list[ops.Qid]) -> circuits.Circuit: return circuits.Circuit.from_moments(*moments) -def _create_even_ghz(qubits: list[ops.Qid]) -> circuits.Circuit: +def _create_even_ghz(qubits: Sequence[ops.Qid]) -> circuits.Circuit: """Circuit to create a GHZ state on an even number of qubits with 1D connectivity. Example: @@ -141,7 +143,7 @@ def _create_even_ghz(qubits: list[ops.Qid]) -> circuits.Circuit: return circuits.Circuit.from_moments(*moments) -def _create_ghz_from_one_end(qubits: list[ops.Qid]) -> circuits.Circuit: +def _create_ghz_from_one_end(qubits: Sequence[ops.Qid]) -> circuits.Circuit: """Circuit to create a GHZ state from one end in a 1D chain. Example: @@ -183,7 +185,7 @@ def _create_ghz_from_one_end(qubits: list[ops.Qid]) -> circuits.Circuit: def generate_1d_ghz_circuit( - qubits: list[ops.Qid], + qubits: Sequence[ops.Qid], add_dd: bool = True, dd_sequence: tuple[ops.Gate, ...] = (ops.X, ops.Y, ops.X, ops.Y), from_one_end: bool = False, From ee77bed3c83f36281ff708af0f805047b6532c9c Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Fri, 13 Mar 2026 02:55:59 +0000 Subject: [PATCH 16/16] update test --- cirq-core/cirq/contrib/ghz/fidelity_test.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/contrib/ghz/fidelity_test.py b/cirq-core/cirq/contrib/ghz/fidelity_test.py index 7ede741e9b6..cb7f3819bd1 100644 --- a/cirq-core/cirq/contrib/ghz/fidelity_test.py +++ b/cirq-core/cirq/contrib/ghz/fidelity_test.py @@ -21,12 +21,22 @@ def test_measure_ghz_fidelity() -> None: - qubits = devices.LineQubit.range(10) + qubits = devices.LineQubit.range(4) sampler = sim.Simulator() circuit = ghz_1d.generate_1d_ghz_circuit(qubits) rng = np.random.default_rng() result = ghz_fidelity.measure_ghz_fidelity( - circuit, 20, 20, rng, sampler, pauli_repetitions=100, readout_repetitions=1000 + circuit, 3, 3, rng, sampler, pauli_repetitions=100, readout_repetitions=100 + ) + f, df = result.compute_fidelity(mitigated=False) + assert f == 1.0 + assert df == 0.0 + f_m, df_m = result.compute_fidelity(mitigated=True) + assert f_m == 1.0 + assert df_m == 0.0 + + result = ghz_fidelity.measure_ghz_fidelity( + circuit, 2**3 - 1, 2**3, rng, sampler, pauli_repetitions=100, readout_repetitions=100 ) f, df = result.compute_fidelity(mitigated=False) assert f == 1.0