From 419233be6fa4c4845191d71a8f902d19324795c3 Mon Sep 17 00:00:00 2001 From: tcochran-quera Date: Mon, 27 Oct 2025 21:56:10 -0400 Subject: [PATCH 01/15] Allow measurements and resets into Gemini noise model. --- src/bloqade/cirq_utils/noise/model.py | 37 ++++++++++++++++++--------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/bloqade/cirq_utils/noise/model.py b/src/bloqade/cirq_utils/noise/model.py index 4e3c064d..ab694f0f 100644 --- a/src/bloqade/cirq_utils/noise/model.py +++ b/src/bloqade/cirq_utils/noise/model.py @@ -106,7 +106,7 @@ def validate_moments(moments: Iterable[cirq.Moment]): continue gate = operation.gate - for allowed_family in allowed_target_gates: + for allowed_family in set(allowed_target_gates).union({cirq.GateFamily(gate=cirq.ops.common_channels.ResetChannel, ignore_global_phase=True)}): if gate in allowed_family: break else: @@ -246,14 +246,18 @@ def noisy_moment(self, moment, system_qubits): original_moment = moment # Check if the moment is empty - if len(moment.operations) == 0: + if len(moment.operations) == 0 or cirq.is_measurement(moment.operations[0]): move_noise_ops = [] gate_noise_ops = [] # Check if the moment contains 1-qubit gates or 2-qubit gates elif len(moment.operations[0].qubits) == 1: - gate_noise_ops, move_noise_ops = self._single_qubit_moment_noise_ops( - moment, system_qubits - ) + if (isinstance(moment.operations[0].gate, cirq.ResetChannel)) or (cirq.is_measurement(moment.operations[0])): + move_noise_ops = [] + gate_noise_ops = [] + else: + gate_noise_ops, move_noise_ops = self._single_qubit_moment_noise_ops( + moment, system_qubits + ) elif len(moment.operations[0].qubits) == 2: control_qubits = [op.qubits[0] for op in moment.operations] target_qubits = [op.qubits[1] for op in moment.operations] @@ -319,20 +323,26 @@ def noisy_moments( # Split into moments with only 1Q and 2Q gates moments_1q = [ - cirq.Moment([op for op in moment.operations if len(op.qubits) == 1]) + cirq.Moment([op for op in moment.operations if (len(op.qubits) == 1) and (not cirq.is_measurement(op)) and (not isinstance(op.gate, cirq.ResetChannel))]) for moment in moments ] moments_2q = [ - cirq.Moment([op for op in moment.operations if len(op.qubits) == 2]) + cirq.Moment([op for op in moment.operations if (len(op.qubits) == 2) and (not cirq.is_measurement(op))]) for moment in moments ] - assert len(moments_1q) == len(moments_2q) + moments_measurement = [ + cirq.Moment([op for op in moment.operations if (cirq.is_measurement(op)) or (isinstance(op.gate, cirq.ResetChannel))]) + for moment in moments + ] + + assert len(moments_1q) == len(moments_2q) == len(moments_measurement) interleaved_moments = [] for idx, moment in enumerate(moments_1q): interleaved_moments.append(moment) interleaved_moments.append(moments_2q[idx]) + interleaved_moments.append(moments_measurement[idx]) interleaved_circuit = cirq.Circuit.from_moments(*interleaved_moments) @@ -368,14 +378,17 @@ def noisy_moment(self, moment, system_qubits): "all qubits in the circuit must be defined as cirq.GridQubit objects." ) # Check if the moment is empty - if len(moment.operations) == 0: + if len(moment.operations) == 0 or cirq.is_measurement(moment.operations[0]): move_moments = [] gate_noise_ops = [] # Check if the moment contains 1-qubit gates or 2-qubit gates elif len(moment.operations[0].qubits) == 1: - gate_noise_ops, _ = self._single_qubit_moment_noise_ops( - moment, system_qubits - ) + if (isinstance(moment.operations[0].gate, cirq.ResetChannel)) or (cirq.is_measurement(moment.operations[0])): + gate_noise_ops = [] + else: + gate_noise_ops, _ = self._single_qubit_moment_noise_ops( + moment, system_qubits + ) move_moments = [] elif len(moment.operations[0].qubits) == 2: cg = OneZoneConflictGraph(moment) From ea033bf7b2c373e81cad8ec7f3720a860a97c4d3 Mon Sep 17 00:00:00 2001 From: tcochran-quera Date: Thu, 13 Nov 2025 13:43:51 -0500 Subject: [PATCH 02/15] David's allows_gates_family comment --- src/bloqade/cirq_utils/noise/model.py | 38 ++++++++++++--------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/bloqade/cirq_utils/noise/model.py b/src/bloqade/cirq_utils/noise/model.py index ab694f0f..5ede68d6 100644 --- a/src/bloqade/cirq_utils/noise/model.py +++ b/src/bloqade/cirq_utils/noise/model.py @@ -56,29 +56,21 @@ class GeminiNoiseModelABC(cirq.NoiseModel, MoveNoiseModelABC): """The correlated CZ error rates as a dictionary""" def __post_init__(self): - is_ambiguous = ( - self.cz_paired_correlated_rates is not None - and self.cz_paired_error_probabilities is not None - ) - if is_ambiguous: - raise ValueError( - "Received both `cz_paired_correlated_rates` and `cz_paired_error_probabilities` as input. This is ambiguous, please only set one." - ) - - use_default = ( + if ( self.cz_paired_correlated_rates is None and self.cz_paired_error_probabilities is None - ) - if use_default: + ): # NOTE: no input, set to default value; weird setattr for frozen dataclass object.__setattr__( self, "cz_paired_error_probabilities", _default_cz_paired_correlated_rates(), ) - return + elif ( + self.cz_paired_correlated_rates is not None + and self.cz_paired_error_probabilities is None + ): - if self.cz_paired_correlated_rates is not None: if self.cz_paired_correlated_rates.shape != (4, 4): raise ValueError( "Expected a 4x4 array of probabilities for cz_paired_correlated_rates" @@ -90,15 +82,19 @@ def __post_init__(self): "cz_paired_error_probabilities", correlated_noise_array_to_dict(self.cz_paired_correlated_rates), ) - return - - assert ( - self.cz_paired_error_probabilities is not None - ), "This error should not happen! Please report this issue." + elif ( + self.cz_paired_correlated_rates is not None + and self.cz_paired_error_probabilities is not None + ): + raise ValueError( + "Received both `cz_paired_correlated_rates` and `cz_paired_correlated_rates` as input. This is ambiguous, please only set one." + ) @staticmethod def validate_moments(moments: Iterable[cirq.Moment]): - allowed_target_gates: frozenset[cirq.GateFamily] = cirq.CZTargetGateset().gates + reset_family = cirq.GateFamily(gate=cirq.ResetChannel, ignore_global_phase=True) + allowed_target_gates: frozenset[cirq.GateFamily] = cirq.CZTargetGateset(additional_gates=[reset_family]).gates + # allowed_target_gates: frozenset[cirq.GateFamily] = cirq.CZTargetGateset().gates for moment in moments: for operation in moment: @@ -106,7 +102,7 @@ def validate_moments(moments: Iterable[cirq.Moment]): continue gate = operation.gate - for allowed_family in set(allowed_target_gates).union({cirq.GateFamily(gate=cirq.ops.common_channels.ResetChannel, ignore_global_phase=True)}): + for allowed_family in allowed_target_gates: if gate in allowed_family: break else: From 21739063b7acbbe579536993c407504cb3bf03e7 Mon Sep 17 00:00:00 2001 From: tcochran-quera Date: Mon, 17 Nov 2025 12:21:41 -0500 Subject: [PATCH 03/15] Add bitflip error depending on amount of time qubits will sit idle. --- src/bloqade/cirq_utils/noise/model.py | 85 +++++++++++++++++++-------- 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/src/bloqade/cirq_utils/noise/model.py b/src/bloqade/cirq_utils/noise/model.py index 5ede68d6..e3078f6d 100644 --- a/src/bloqade/cirq_utils/noise/model.py +++ b/src/bloqade/cirq_utils/noise/model.py @@ -67,8 +67,8 @@ def __post_init__(self): _default_cz_paired_correlated_rates(), ) elif ( - self.cz_paired_correlated_rates is not None - and self.cz_paired_error_probabilities is None + self.cz_paired_correlated_rates is not None + and self.cz_paired_error_probabilities is None ): if self.cz_paired_correlated_rates.shape != (4, 4): @@ -83,8 +83,8 @@ def __post_init__(self): correlated_noise_array_to_dict(self.cz_paired_correlated_rates), ) elif ( - self.cz_paired_correlated_rates is not None - and self.cz_paired_error_probabilities is not None + self.cz_paired_correlated_rates is not None + and self.cz_paired_error_probabilities is not None ): raise ValueError( "Received both `cz_paired_correlated_rates` and `cz_paired_correlated_rates` as input. This is ambiguous, please only set one." @@ -113,7 +113,7 @@ def validate_moments(moments: Iterable[cirq.Moment]): ) def parallel_cz_errors( - self, ctrls: list[int], qargs: list[int], rest: list[int] + self, ctrls: list[int], qargs: list[int], rest: list[int] ) -> dict[tuple[float, float, float, float], list[int]]: raise NotImplementedError( "This noise model doesn't support rewrites on bloqade kernels, but should be used with cirq." @@ -179,7 +179,7 @@ class GeminiOneZoneNoiseModel(GeminiNoiseModelABC): parallelize_circuit: bool = False def _single_qubit_moment_noise_ops( - self, moment: cirq.Moment, system_qubits: Sequence[cirq.Qid] + self, moment: cirq.Moment, system_qubits: Sequence[cirq.Qid] ) -> tuple[list, list]: """ Helper function to determine the noise operations for a single qubit moment. @@ -211,7 +211,7 @@ def _single_qubit_moment_noise_ops( op.qubits[0] for op in moment.operations if not ( - np.isclose(op.gate.x_exponent, 0) and np.isclose(op.gate.z_exponent, 0) + np.isclose(op.gate.x_exponent, 0) and np.isclose(op.gate.z_exponent, 0) ) ] @@ -247,7 +247,8 @@ def noisy_moment(self, moment, system_qubits): gate_noise_ops = [] # Check if the moment contains 1-qubit gates or 2-qubit gates elif len(moment.operations[0].qubits) == 1: - if (isinstance(moment.operations[0].gate, cirq.ResetChannel)) or (cirq.is_measurement(moment.operations[0])): + if (isinstance(moment.operations[0].gate, cirq.ResetChannel)) or ( + cirq.is_measurement(moment.operations[0])) or (isinstance(moment.operations[0].gate, cirq.BitFlipChannel)): move_noise_ops = [] gate_noise_ops = [] else: @@ -301,7 +302,7 @@ def noisy_moment(self, moment, system_qubits): ] def noisy_moments( - self, moments: Iterable[cirq.Moment], system_qubits: Sequence[cirq.Qid] + self, moments: Iterable[cirq.Moment], system_qubits: Sequence[cirq.Qid] ) -> Sequence[cirq.OP_TREE]: """Adds possibly stateful noise to a series of moments. @@ -319,7 +320,8 @@ def noisy_moments( # Split into moments with only 1Q and 2Q gates moments_1q = [ - cirq.Moment([op for op in moment.operations if (len(op.qubits) == 1) and (not cirq.is_measurement(op)) and (not isinstance(op.gate, cirq.ResetChannel))]) + cirq.Moment([op for op in moment.operations if (len(op.qubits) == 1) and (not cirq.is_measurement(op)) and ( + not isinstance(op.gate, cirq.ResetChannel))]) for moment in moments ] moments_2q = [ @@ -328,16 +330,46 @@ def noisy_moments( ] moments_measurement = [ - cirq.Moment([op for op in moment.operations if (cirq.is_measurement(op)) or (isinstance(op.gate, cirq.ResetChannel))]) + cirq.Moment([op for op in moment.operations if + (cirq.is_measurement(op)) or (isinstance(op.gate, cirq.ResetChannel))]) for moment in moments ] assert len(moments_1q) == len(moments_2q) == len(moments_measurement) interleaved_moments = [] + + def count_remaining_cz_moments(moments_2q): + cz = cirq.CZ + remaining_cz_counts = [] + count = 0 + for m in moments_2q[::-1]: + if any(isinstance(op.gate, type(cz)) for op in m.operations): + count += 1 + remaining_cz_counts = [count] + remaining_cz_counts + return remaining_cz_counts + + remaining_cz_moments = count_remaining_cz_moments(moments_2q) + + pm = 2 * self.sitter_pauli_rates[0] + ps = 2 * self.cz_unpaired_pauli_rates[0] + + #probability of a bitflip error for a sitting, unpaired qubit during a move/cz/move cycle. + heuristic_1step_bitflip_error: float = 2 * pm * (1 - ps) * (1- pm) + (1 - pm)**2 * ps + pm**2 * ps + for idx, moment in enumerate(moments_1q): interleaved_moments.append(moment) interleaved_moments.append(moments_2q[idx]) + # Measurements on Gemini will be at the end, so for circuits with mid-circuit measurements we will insert a + # bitflip error proportional to the number of moments left in the circuit to account for the decoherence + # that will happen before the final terminal measurement. + measured_qubits = [] + for op in moments_measurement[idx].operations: + if cirq.is_measurement(op): + measured_qubits += list(op.qubits) + # probability of a bitflip error should be Binomial(moments_left,heuristic_1step_bitflip_error) + delayed_measurement_error = (1 - (1 - 2 * heuristic_1step_bitflip_error) ** (remaining_cz_moments[idx])) / 2 + interleaved_moments.append(cirq.Moment(cirq.bit_flip(delayed_measurement_error).on_each(measured_qubits))) interleaved_moments.append(moments_measurement[idx]) interleaved_circuit = cirq.Circuit.from_moments(*interleaved_moments) @@ -379,7 +411,8 @@ def noisy_moment(self, moment, system_qubits): gate_noise_ops = [] # Check if the moment contains 1-qubit gates or 2-qubit gates elif len(moment.operations[0].qubits) == 1: - if (isinstance(moment.operations[0].gate, cirq.ResetChannel)) or (cirq.is_measurement(moment.operations[0])): + if (isinstance(moment.operations[0].gate, cirq.ResetChannel)) or ( + cirq.is_measurement(moment.operations[0])) or (isinstance(moment.operations[0].gate, cirq.BitFlipChannel)): gate_noise_ops = [] else: gate_noise_ops, _ = self._single_qubit_moment_noise_ops( @@ -449,7 +482,7 @@ def noisy_moment(self, moment, system_qubits): @dataclass(frozen=True) class GeminiTwoZoneNoiseModel(GeminiNoiseModelABC): def noisy_moments( - self, moments: Iterable[cirq.Moment], system_qubits: Sequence[cirq.Qid] + self, moments: Iterable[cirq.Moment], system_qubits: Sequence[cirq.Qid] ) -> Sequence[cirq.OP_TREE]: """Adds possibly stateful noise to a series of moments. @@ -481,12 +514,12 @@ def noisy_moments( [ moment for moment in _two_zone_utils.get_move_error_channel_two_zoned( - moments[i], - prev_moment, - np.array(self.mover_pauli_rates), - np.array(self.sitter_pauli_rates), - nqubs, - ).moments + moments[i], + prev_moment, + np.array(self.mover_pauli_rates), + np.array(self.sitter_pauli_rates), + nqubs, + ).moments if len(moment) > 0 ] ) @@ -497,13 +530,13 @@ def noisy_moments( [ moment for moment in _two_zone_utils.get_gate_error_channel( - moments[i], - np.array(self.local_pauli_rates), - np.array(self.global_pauli_rates), - self.two_qubit_pauli, - np.array(self.cz_unpaired_pauli_rates), - nqubs, - ).moments + moments[i], + np.array(self.local_pauli_rates), + np.array(self.global_pauli_rates), + self.two_qubit_pauli, + np.array(self.cz_unpaired_pauli_rates), + nqubs, + ).moments if len(moment) > 0 ] ) From e14b7ac0283494444cfaca41e68bab85e38602b2 Mon Sep 17 00:00:00 2001 From: tcochran-quera Date: Mon, 17 Nov 2025 12:22:09 -0500 Subject: [PATCH 04/15] Allow parallelize to have nonunitary gate operations for measurement and error gates. --- src/bloqade/cirq_utils/parallelize.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/bloqade/cirq_utils/parallelize.py b/src/bloqade/cirq_utils/parallelize.py index 4fcf91a1..35d1c211 100644 --- a/src/bloqade/cirq_utils/parallelize.py +++ b/src/bloqade/cirq_utils/parallelize.py @@ -119,7 +119,11 @@ def auto_similarity( flattened_circuit: list[GateOperation] = list(cirq.flatten_op_tree(circuit)) weights = {} for i in range(len(flattened_circuit)): + if not cirq.has_unitary(flattened_circuit[i]): + continue for j in range(i + 1, len(flattened_circuit)): + if not cirq.has_unitary(flattened_circuit[j]): + continue op1 = flattened_circuit[i] op2 = flattened_circuit[j] if can_be_parallel(op1, op2): @@ -297,14 +301,20 @@ def colorize( for epoch in epochs: oneq_gates = [] twoq_gates = [] + nonunitary_gates = [] for gate in epoch: - if len(gate.val.qubits) == 1: + if not cirq.has_unitary(gate.val): + nonunitary_gates.append(gate.val) + elif len(gate.val.qubits) == 1: oneq_gates.append(gate.val) elif len(gate.val.qubits) == 2: twoq_gates.append(gate.val) else: raise RuntimeError("Unsupported gate type") + if len(nonunitary_gates) > 0: + yield nonunitary_gates + if len(oneq_gates) > 0: yield oneq_gates From ed9c587562b57bf1c65cb4cad4495de73b4a616e Mon Sep 17 00:00:00 2001 From: tcochran-quera Date: Mon, 17 Nov 2025 13:41:18 -0500 Subject: [PATCH 05/15] unit tests for measure measurement, reset, and error gates in cirq_utils.parallelize. --- test/cirq_utils/test_parallelize.py | 45 +++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/test/cirq_utils/test_parallelize.py b/test/cirq_utils/test_parallelize.py index 8bb6be59..9fad8011 100644 --- a/test/cirq_utils/test_parallelize.py +++ b/test/cirq_utils/test_parallelize.py @@ -26,14 +26,53 @@ def test1(): ) circuit_m, _ = moment_similarity(circuit, weight=1.0) - # print(circuit_m) circuit_b, _ = block_similarity(circuit, weight=1.0, block_id=1) circuit_m2 = remove_tags(circuit_m) - print(circuit_m2) circuit2 = parallelize(circuit) - # print(circuit2) assert len(circuit2.moments) == 7 +def test_measurement_and_reset(): + qubits = cirq.LineQubit.range(4) + circuit = cirq.Circuit( + cirq.H(qubits[0]), + cirq.CX(qubits[0], qubits[1]), + cirq.measure(qubits[1]), + cirq.reset(qubits[1]), + cirq.CX(qubits[1], qubits[2]), + cirq.measure(qubits[2]), + cirq.reset(qubits[2]), + cirq.CX(qubits[2], qubits[3]), + cirq.measure(qubits[0]), + cirq.reset(qubits[0]), + ) + + circuit_m, _ = moment_similarity(circuit, weight=1.0) + circuit_b, _ = block_similarity(circuit, weight=1.0, block_id=1) + circuit_m2 = remove_tags(circuit_m) + + parallelized_circuit = parallelize(circuit) + + assert len(parallelized_circuit.moments) == 11 + + #this circuit should deterministically return all qubits to |0> + #let's check: + simulator = cirq.Simulator() + for _ in range(20): #one in a million chance we miss an error + state_vector = simulator.simulate(parallelized_circuit).state_vector() + assert np.all(np.isclose(np.abs(state_vector), np.concatenate((np.array([1]),np.zeros(2**4-1))))) + +def test_nonunitary_error_gate(): + qubits = cirq.LineQubit.range(2) + circuit = cirq.Circuit( + cirq.H(qubits[0]), + cirq.CX(qubits[0], qubits[1]), + cirq.amplitude_damp(0.5).on(qubits[1]), + cirq.CX(qubits[1], qubits[0]), + ) + + parallelized_circuit = parallelize(circuit) + + assert len(parallelized_circuit.moments) == 7 RNG_STATE = np.random.RandomState(1902833) From c9468bf01a55d26aa8b6a1fc93ba5efd017fb1fe Mon Sep 17 00:00:00 2001 From: tcochran-quera Date: Mon, 17 Nov 2025 14:05:29 -0500 Subject: [PATCH 06/15] unit tests for noise model, inputing circuit with measurement and reset. --- test/cirq_utils/noise/test_noise_models.py | 59 ++++++++++++++++------ 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/test/cirq_utils/noise/test_noise_models.py b/test/cirq_utils/noise/test_noise_models.py index e50c8b3d..b8b88722 100644 --- a/test/cirq_utils/noise/test_noise_models.py +++ b/test/cirq_utils/noise/test_noise_models.py @@ -14,7 +14,7 @@ ) -def create_ghz_circuit(qubits): +def create_ghz_circuit(qubits, measurements:bool=False): n = len(qubits) circuit = cirq.Circuit() @@ -24,26 +24,41 @@ def create_ghz_circuit(qubits): # Step 2: CNOT chain from qubit i to i+1 for i in range(n - 1): circuit.append(cirq.CNOT(qubits[i], qubits[i + 1])) + if measurements: + circuit.append(cirq.measure(qubits[i])) + circuit.append(cirq.reset(qubits[i])) + + if measurements: + circuit.append(cirq.measure(qubits[-1])) + circuit.append(cirq.reset(qubits[-1])) return circuit @pytest.mark.parametrize( - "model,qubits", + "model,qubits,measurements", [ - (GeminiOneZoneNoiseModel(), None), + (GeminiOneZoneNoiseModel(), None,False), + ( + GeminiOneZoneNoiseModelConflictGraphMoves(), + cirq.GridQubit.rect(rows=1, cols=2), + False + ), + (GeminiTwoZoneNoiseModel(), None, False), + (GeminiOneZoneNoiseModel(), None, True), ( GeminiOneZoneNoiseModelConflictGraphMoves(), cirq.GridQubit.rect(rows=1, cols=2), + True ), - (GeminiTwoZoneNoiseModel(), None), + (GeminiTwoZoneNoiseModel(), None, True), ], ) -def test_simple_model(model: cirq.NoiseModel, qubits): +def test_simple_model(model: cirq.NoiseModel, qubits, measurements:bool): if qubits is None: qubits = cirq.LineQubit.range(2) - circuit = create_ghz_circuit(qubits) + circuit = create_ghz_circuit(qubits, measurements=measurements) with pytest.raises(ValueError): # make sure only native gate set is supported @@ -74,13 +89,25 @@ def test_simple_model(model: cirq.NoiseModel, qubits): for i in range(4): pops_bloqade[i] += abs(ket[i]) ** 2 / nshots - for pops in (pops_bloqade, pops_cirq): - assert math.isclose(pops[0], 0.5, abs_tol=1e-1) - assert math.isclose(pops[3], 0.5, abs_tol=1e-1) - assert math.isclose(pops[1], 0.0, abs_tol=1e-1) - assert math.isclose(pops[2], 0.0, abs_tol=1e-1) - - assert pops[0] < 0.5001 - assert pops[3] < 0.5001 - assert pops[1] >= 0.0 - assert pops[2] >= 0.0 + if measurements is True: + for pops in (pops_bloqade, pops_cirq): + assert math.isclose(pops[0], 1.0, abs_tol=1e-1) + assert math.isclose(pops[3], 0.0, abs_tol=1e-1) + assert math.isclose(pops[1], 0.0, abs_tol=1e-1) + assert math.isclose(pops[2], 0.0, abs_tol=1e-1) + + assert pops[0] > 0.99 + assert pops[3] >= 0.0 + assert pops[1] >= 0.0 + assert pops[2] >= 0.0 + else: + for pops in (pops_bloqade, pops_cirq): + assert math.isclose(pops[0], 0.5, abs_tol=1e-1) + assert math.isclose(pops[3], 0.5, abs_tol=1e-1) + assert math.isclose(pops[1], 0.0, abs_tol=1e-1) + assert math.isclose(pops[2], 0.0, abs_tol=1e-1) + + assert pops[0] < 0.5001 + assert pops[3] < 0.5001 + assert pops[1] >= 0.0 + assert pops[2] >= 0.0 From 3c21aaeb8591fe3aed0a0268bdb0747f2d44058f Mon Sep 17 00:00:00 2001 From: tcochran-quera Date: Mon, 24 Nov 2025 16:48:15 -0500 Subject: [PATCH 07/15] remove tests for measurement/reset and pyqrack. --- test/cirq_utils/noise/test_noise_models.py | 23 +++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/test/cirq_utils/noise/test_noise_models.py b/test/cirq_utils/noise/test_noise_models.py index b8b88722..32274f85 100644 --- a/test/cirq_utils/noise/test_noise_models.py +++ b/test/cirq_utils/noise/test_noise_models.py @@ -76,21 +76,22 @@ def test_simple_model(model: cirq.NoiseModel, qubits, measurements:bool): dm = cirq_sim.simulate(noisy_circuit).final_density_matrix pops_cirq = np.real(np.diag(dm)) - kernel = load_circuit(noisy_circuit) - pyqrack_sim = StackMemorySimulator( - min_qubits=2, rng_state=np.random.default_rng(1234) - ) + if not measurements: + kernel = load_circuit(noisy_circuit) + pyqrack_sim = StackMemorySimulator( + min_qubits=2, rng_state=np.random.default_rng(1234) + ) - pops_bloqade = [0.0] * 4 + pops_bloqade = [0.0] * 4 - nshots = 500 - for _ in range(nshots): - ket = pyqrack_sim.state_vector(kernel) - for i in range(4): - pops_bloqade[i] += abs(ket[i]) ** 2 / nshots + nshots = 500 + for _ in range(nshots): + ket = pyqrack_sim.state_vector(kernel) + for i in range(4): + pops_bloqade[i] += abs(ket[i]) ** 2 / nshots if measurements is True: - for pops in (pops_bloqade, pops_cirq): + for pops in [pops_cirq]: assert math.isclose(pops[0], 1.0, abs_tol=1e-1) assert math.isclose(pops[3], 0.0, abs_tol=1e-1) assert math.isclose(pops[1], 0.0, abs_tol=1e-1) From 31b4d631bbfafa5907fd3e37103213b6cce5c5fb Mon Sep 17 00:00:00 2001 From: tcochran-quera Date: Mon, 24 Nov 2025 16:58:38 -0500 Subject: [PATCH 08/15] update test assertion. --- test/cirq_utils/test_parallelize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cirq_utils/test_parallelize.py b/test/cirq_utils/test_parallelize.py index 9fad8011..5624da42 100644 --- a/test/cirq_utils/test_parallelize.py +++ b/test/cirq_utils/test_parallelize.py @@ -52,7 +52,7 @@ def test_measurement_and_reset(): parallelized_circuit = parallelize(circuit) - assert len(parallelized_circuit.moments) == 11 + assert len(parallelized_circuit.moments) == 13 #this circuit should deterministically return all qubits to |0> #let's check: From aab06de01c5668ac2a8aff6b4b5ae100d5d4432c Mon Sep 17 00:00:00 2001 From: tcochran-quera Date: Mon, 24 Nov 2025 17:04:02 -0500 Subject: [PATCH 09/15] formatting and remove assertion --- src/bloqade/cirq_utils/noise/model.py | 14 ++++++++------ test/cirq_utils/noise/test_noise_models.py | 18 +++++++++--------- test/cirq_utils/test_parallelize.py | 13 +++++++------ 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/bloqade/cirq_utils/noise/model.py b/src/bloqade/cirq_utils/noise/model.py index e3078f6d..8e8a9f41 100644 --- a/src/bloqade/cirq_utils/noise/model.py +++ b/src/bloqade/cirq_utils/noise/model.py @@ -57,8 +57,8 @@ class GeminiNoiseModelABC(cirq.NoiseModel, MoveNoiseModelABC): def __post_init__(self): if ( - self.cz_paired_correlated_rates is None - and self.cz_paired_error_probabilities is None + self.cz_paired_correlated_rates is None + and self.cz_paired_error_probabilities is None ): # NOTE: no input, set to default value; weird setattr for frozen dataclass object.__setattr__( @@ -248,7 +248,8 @@ def noisy_moment(self, moment, system_qubits): # Check if the moment contains 1-qubit gates or 2-qubit gates elif len(moment.operations[0].qubits) == 1: if (isinstance(moment.operations[0].gate, cirq.ResetChannel)) or ( - cirq.is_measurement(moment.operations[0])) or (isinstance(moment.operations[0].gate, cirq.BitFlipChannel)): + cirq.is_measurement(moment.operations[0])) or ( + isinstance(moment.operations[0].gate, cirq.BitFlipChannel)): move_noise_ops = [] gate_noise_ops = [] else: @@ -354,8 +355,8 @@ def count_remaining_cz_moments(moments_2q): pm = 2 * self.sitter_pauli_rates[0] ps = 2 * self.cz_unpaired_pauli_rates[0] - #probability of a bitflip error for a sitting, unpaired qubit during a move/cz/move cycle. - heuristic_1step_bitflip_error: float = 2 * pm * (1 - ps) * (1- pm) + (1 - pm)**2 * ps + pm**2 * ps + # probability of a bitflip error for a sitting, unpaired qubit during a move/cz/move cycle. + heuristic_1step_bitflip_error: float = 2 * pm * (1 - ps) * (1 - pm) + (1 - pm) ** 2 * ps + pm ** 2 * ps for idx, moment in enumerate(moments_1q): interleaved_moments.append(moment) @@ -412,7 +413,8 @@ def noisy_moment(self, moment, system_qubits): # Check if the moment contains 1-qubit gates or 2-qubit gates elif len(moment.operations[0].qubits) == 1: if (isinstance(moment.operations[0].gate, cirq.ResetChannel)) or ( - cirq.is_measurement(moment.operations[0])) or (isinstance(moment.operations[0].gate, cirq.BitFlipChannel)): + cirq.is_measurement(moment.operations[0])) or ( + isinstance(moment.operations[0].gate, cirq.BitFlipChannel)): gate_noise_ops = [] else: gate_noise_ops, _ = self._single_qubit_moment_noise_ops( diff --git a/test/cirq_utils/noise/test_noise_models.py b/test/cirq_utils/noise/test_noise_models.py index 32274f85..3cbba854 100644 --- a/test/cirq_utils/noise/test_noise_models.py +++ b/test/cirq_utils/noise/test_noise_models.py @@ -14,7 +14,7 @@ ) -def create_ghz_circuit(qubits, measurements:bool=False): +def create_ghz_circuit(qubits, measurements: bool = False): n = len(qubits) circuit = cirq.Circuit() @@ -38,23 +38,23 @@ def create_ghz_circuit(qubits, measurements:bool=False): @pytest.mark.parametrize( "model,qubits,measurements", [ - (GeminiOneZoneNoiseModel(), None,False), + (GeminiOneZoneNoiseModel(), None, False), ( - GeminiOneZoneNoiseModelConflictGraphMoves(), - cirq.GridQubit.rect(rows=1, cols=2), - False + GeminiOneZoneNoiseModelConflictGraphMoves(), + cirq.GridQubit.rect(rows=1, cols=2), + False ), (GeminiTwoZoneNoiseModel(), None, False), (GeminiOneZoneNoiseModel(), None, True), ( - GeminiOneZoneNoiseModelConflictGraphMoves(), - cirq.GridQubit.rect(rows=1, cols=2), - True + GeminiOneZoneNoiseModelConflictGraphMoves(), + cirq.GridQubit.rect(rows=1, cols=2), + True ), (GeminiTwoZoneNoiseModel(), None, True), ], ) -def test_simple_model(model: cirq.NoiseModel, qubits, measurements:bool): +def test_simple_model(model: cirq.NoiseModel, qubits, measurements: bool): if qubits is None: qubits = cirq.LineQubit.range(2) diff --git a/test/cirq_utils/test_parallelize.py b/test/cirq_utils/test_parallelize.py index 5624da42..a672e573 100644 --- a/test/cirq_utils/test_parallelize.py +++ b/test/cirq_utils/test_parallelize.py @@ -31,6 +31,7 @@ def test1(): circuit2 = parallelize(circuit) assert len(circuit2.moments) == 7 + def test_measurement_and_reset(): qubits = cirq.LineQubit.range(4) circuit = cirq.Circuit( @@ -52,14 +53,13 @@ def test_measurement_and_reset(): parallelized_circuit = parallelize(circuit) - assert len(parallelized_circuit.moments) == 13 - - #this circuit should deterministically return all qubits to |0> - #let's check: + # this circuit should deterministically return all qubits to |0> + # let's check: simulator = cirq.Simulator() - for _ in range(20): #one in a million chance we miss an error + for _ in range(20): # one in a million chance we miss an error state_vector = simulator.simulate(parallelized_circuit).state_vector() - assert np.all(np.isclose(np.abs(state_vector), np.concatenate((np.array([1]),np.zeros(2**4-1))))) + assert np.all(np.isclose(np.abs(state_vector), np.concatenate((np.array([1]), np.zeros(2 ** 4 - 1))))) + def test_nonunitary_error_gate(): qubits = cirq.LineQubit.range(2) @@ -74,6 +74,7 @@ def test_nonunitary_error_gate(): assert len(parallelized_circuit.moments) == 7 + RNG_STATE = np.random.RandomState(1902833) From b057ea8ca1ab579732d3b6c07e9b3bd9bf503b46 Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Tue, 25 Nov 2025 09:38:43 +0100 Subject: [PATCH 10/15] Revert "remove tests for measurement/reset and pyqrack." This reverts commit 3c21aaeb8591fe3aed0a0268bdb0747f2d44058f. --- src/bloqade/cirq_utils/noise/model.py | 112 ++++++++++++++------- test/cirq_utils/noise/test_noise_models.py | 35 ++++--- test/cirq_utils/test_parallelize.py | 13 ++- 3 files changed, 100 insertions(+), 60 deletions(-) diff --git a/src/bloqade/cirq_utils/noise/model.py b/src/bloqade/cirq_utils/noise/model.py index 8e8a9f41..ab335a84 100644 --- a/src/bloqade/cirq_utils/noise/model.py +++ b/src/bloqade/cirq_utils/noise/model.py @@ -57,8 +57,8 @@ class GeminiNoiseModelABC(cirq.NoiseModel, MoveNoiseModelABC): def __post_init__(self): if ( - self.cz_paired_correlated_rates is None - and self.cz_paired_error_probabilities is None + self.cz_paired_correlated_rates is None + and self.cz_paired_error_probabilities is None ): # NOTE: no input, set to default value; weird setattr for frozen dataclass object.__setattr__( @@ -67,8 +67,8 @@ def __post_init__(self): _default_cz_paired_correlated_rates(), ) elif ( - self.cz_paired_correlated_rates is not None - and self.cz_paired_error_probabilities is None + self.cz_paired_correlated_rates is not None + and self.cz_paired_error_probabilities is None ): if self.cz_paired_correlated_rates.shape != (4, 4): @@ -83,8 +83,8 @@ def __post_init__(self): correlated_noise_array_to_dict(self.cz_paired_correlated_rates), ) elif ( - self.cz_paired_correlated_rates is not None - and self.cz_paired_error_probabilities is not None + self.cz_paired_correlated_rates is not None + and self.cz_paired_error_probabilities is not None ): raise ValueError( "Received both `cz_paired_correlated_rates` and `cz_paired_correlated_rates` as input. This is ambiguous, please only set one." @@ -93,7 +93,9 @@ def __post_init__(self): @staticmethod def validate_moments(moments: Iterable[cirq.Moment]): reset_family = cirq.GateFamily(gate=cirq.ResetChannel, ignore_global_phase=True) - allowed_target_gates: frozenset[cirq.GateFamily] = cirq.CZTargetGateset(additional_gates=[reset_family]).gates + allowed_target_gates: frozenset[cirq.GateFamily] = cirq.CZTargetGateset( + additional_gates=[reset_family] + ).gates # allowed_target_gates: frozenset[cirq.GateFamily] = cirq.CZTargetGateset().gates for moment in moments: @@ -113,7 +115,7 @@ def validate_moments(moments: Iterable[cirq.Moment]): ) def parallel_cz_errors( - self, ctrls: list[int], qargs: list[int], rest: list[int] + self, ctrls: list[int], qargs: list[int], rest: list[int] ) -> dict[tuple[float, float, float, float], list[int]]: raise NotImplementedError( "This noise model doesn't support rewrites on bloqade kernels, but should be used with cirq." @@ -179,7 +181,7 @@ class GeminiOneZoneNoiseModel(GeminiNoiseModelABC): parallelize_circuit: bool = False def _single_qubit_moment_noise_ops( - self, moment: cirq.Moment, system_qubits: Sequence[cirq.Qid] + self, moment: cirq.Moment, system_qubits: Sequence[cirq.Qid] ) -> tuple[list, list]: """ Helper function to determine the noise operations for a single qubit moment. @@ -211,7 +213,7 @@ def _single_qubit_moment_noise_ops( op.qubits[0] for op in moment.operations if not ( - np.isclose(op.gate.x_exponent, 0) and np.isclose(op.gate.z_exponent, 0) + np.isclose(op.gate.x_exponent, 0) and np.isclose(op.gate.z_exponent, 0) ) ] @@ -247,9 +249,11 @@ def noisy_moment(self, moment, system_qubits): gate_noise_ops = [] # Check if the moment contains 1-qubit gates or 2-qubit gates elif len(moment.operations[0].qubits) == 1: - if (isinstance(moment.operations[0].gate, cirq.ResetChannel)) or ( - cirq.is_measurement(moment.operations[0])) or ( - isinstance(moment.operations[0].gate, cirq.BitFlipChannel)): + if ( + (isinstance(moment.operations[0].gate, cirq.ResetChannel)) + or (cirq.is_measurement(moment.operations[0])) + or (isinstance(moment.operations[0].gate, cirq.BitFlipChannel)) + ): move_noise_ops = [] gate_noise_ops = [] else: @@ -303,7 +307,7 @@ def noisy_moment(self, moment, system_qubits): ] def noisy_moments( - self, moments: Iterable[cirq.Moment], system_qubits: Sequence[cirq.Qid] + self, moments: Iterable[cirq.Moment], system_qubits: Sequence[cirq.Qid] ) -> Sequence[cirq.OP_TREE]: """Adds possibly stateful noise to a series of moments. @@ -321,18 +325,37 @@ def noisy_moments( # Split into moments with only 1Q and 2Q gates moments_1q = [ - cirq.Moment([op for op in moment.operations if (len(op.qubits) == 1) and (not cirq.is_measurement(op)) and ( - not isinstance(op.gate, cirq.ResetChannel))]) + cirq.Moment( + [ + op + for op in moment.operations + if (len(op.qubits) == 1) + and (not cirq.is_measurement(op)) + and (not isinstance(op.gate, cirq.ResetChannel)) + ] + ) for moment in moments ] moments_2q = [ - cirq.Moment([op for op in moment.operations if (len(op.qubits) == 2) and (not cirq.is_measurement(op))]) + cirq.Moment( + [ + op + for op in moment.operations + if (len(op.qubits) == 2) and (not cirq.is_measurement(op)) + ] + ) for moment in moments ] moments_measurement = [ - cirq.Moment([op for op in moment.operations if - (cirq.is_measurement(op)) or (isinstance(op.gate, cirq.ResetChannel))]) + cirq.Moment( + [ + op + for op in moment.operations + if (cirq.is_measurement(op)) + or (isinstance(op.gate, cirq.ResetChannel)) + ] + ) for moment in moments ] @@ -356,7 +379,9 @@ def count_remaining_cz_moments(moments_2q): ps = 2 * self.cz_unpaired_pauli_rates[0] # probability of a bitflip error for a sitting, unpaired qubit during a move/cz/move cycle. - heuristic_1step_bitflip_error: float = 2 * pm * (1 - ps) * (1 - pm) + (1 - pm) ** 2 * ps + pm ** 2 * ps + heuristic_1step_bitflip_error: float = ( + 2 * pm * (1 - ps) * (1 - pm) + (1 - pm) ** 2 * ps + pm**2 * ps + ) for idx, moment in enumerate(moments_1q): interleaved_moments.append(moment) @@ -369,8 +394,15 @@ def count_remaining_cz_moments(moments_2q): if cirq.is_measurement(op): measured_qubits += list(op.qubits) # probability of a bitflip error should be Binomial(moments_left,heuristic_1step_bitflip_error) - delayed_measurement_error = (1 - (1 - 2 * heuristic_1step_bitflip_error) ** (remaining_cz_moments[idx])) / 2 - interleaved_moments.append(cirq.Moment(cirq.bit_flip(delayed_measurement_error).on_each(measured_qubits))) + delayed_measurement_error = ( + 1 + - (1 - 2 * heuristic_1step_bitflip_error) ** (remaining_cz_moments[idx]) + ) / 2 + interleaved_moments.append( + cirq.Moment( + cirq.bit_flip(delayed_measurement_error).on_each(measured_qubits) + ) + ) interleaved_moments.append(moments_measurement[idx]) interleaved_circuit = cirq.Circuit.from_moments(*interleaved_moments) @@ -412,9 +444,11 @@ def noisy_moment(self, moment, system_qubits): gate_noise_ops = [] # Check if the moment contains 1-qubit gates or 2-qubit gates elif len(moment.operations[0].qubits) == 1: - if (isinstance(moment.operations[0].gate, cirq.ResetChannel)) or ( - cirq.is_measurement(moment.operations[0])) or ( - isinstance(moment.operations[0].gate, cirq.BitFlipChannel)): + if ( + (isinstance(moment.operations[0].gate, cirq.ResetChannel)) + or (cirq.is_measurement(moment.operations[0])) + or (isinstance(moment.operations[0].gate, cirq.BitFlipChannel)) + ): gate_noise_ops = [] else: gate_noise_ops, _ = self._single_qubit_moment_noise_ops( @@ -484,7 +518,7 @@ def noisy_moment(self, moment, system_qubits): @dataclass(frozen=True) class GeminiTwoZoneNoiseModel(GeminiNoiseModelABC): def noisy_moments( - self, moments: Iterable[cirq.Moment], system_qubits: Sequence[cirq.Qid] + self, moments: Iterable[cirq.Moment], system_qubits: Sequence[cirq.Qid] ) -> Sequence[cirq.OP_TREE]: """Adds possibly stateful noise to a series of moments. @@ -516,12 +550,12 @@ def noisy_moments( [ moment for moment in _two_zone_utils.get_move_error_channel_two_zoned( - moments[i], - prev_moment, - np.array(self.mover_pauli_rates), - np.array(self.sitter_pauli_rates), - nqubs, - ).moments + moments[i], + prev_moment, + np.array(self.mover_pauli_rates), + np.array(self.sitter_pauli_rates), + nqubs, + ).moments if len(moment) > 0 ] ) @@ -532,13 +566,13 @@ def noisy_moments( [ moment for moment in _two_zone_utils.get_gate_error_channel( - moments[i], - np.array(self.local_pauli_rates), - np.array(self.global_pauli_rates), - self.two_qubit_pauli, - np.array(self.cz_unpaired_pauli_rates), - nqubs, - ).moments + moments[i], + np.array(self.local_pauli_rates), + np.array(self.global_pauli_rates), + self.two_qubit_pauli, + np.array(self.cz_unpaired_pauli_rates), + nqubs, + ).moments if len(moment) > 0 ] ) diff --git a/test/cirq_utils/noise/test_noise_models.py b/test/cirq_utils/noise/test_noise_models.py index 3cbba854..ba2374cb 100644 --- a/test/cirq_utils/noise/test_noise_models.py +++ b/test/cirq_utils/noise/test_noise_models.py @@ -40,16 +40,16 @@ def create_ghz_circuit(qubits, measurements: bool = False): [ (GeminiOneZoneNoiseModel(), None, False), ( - GeminiOneZoneNoiseModelConflictGraphMoves(), - cirq.GridQubit.rect(rows=1, cols=2), - False + GeminiOneZoneNoiseModelConflictGraphMoves(), + cirq.GridQubit.rect(rows=1, cols=2), + False, ), (GeminiTwoZoneNoiseModel(), None, False), (GeminiOneZoneNoiseModel(), None, True), ( - GeminiOneZoneNoiseModelConflictGraphMoves(), - cirq.GridQubit.rect(rows=1, cols=2), - True + GeminiOneZoneNoiseModelConflictGraphMoves(), + cirq.GridQubit.rect(rows=1, cols=2), + True, ), (GeminiTwoZoneNoiseModel(), None, True), ], @@ -76,22 +76,21 @@ def test_simple_model(model: cirq.NoiseModel, qubits, measurements: bool): dm = cirq_sim.simulate(noisy_circuit).final_density_matrix pops_cirq = np.real(np.diag(dm)) - if not measurements: - kernel = load_circuit(noisy_circuit) - pyqrack_sim = StackMemorySimulator( - min_qubits=2, rng_state=np.random.default_rng(1234) - ) + kernel = load_circuit(noisy_circuit) + pyqrack_sim = StackMemorySimulator( + min_qubits=2, rng_state=np.random.default_rng(1234) + ) - pops_bloqade = [0.0] * 4 + pops_bloqade = [0.0] * 4 - nshots = 500 - for _ in range(nshots): - ket = pyqrack_sim.state_vector(kernel) - for i in range(4): - pops_bloqade[i] += abs(ket[i]) ** 2 / nshots + nshots = 500 + for _ in range(nshots): + ket = pyqrack_sim.state_vector(kernel) + for i in range(4): + pops_bloqade[i] += abs(ket[i]) ** 2 / nshots if measurements is True: - for pops in [pops_cirq]: + for pops in (pops_bloqade, pops_cirq): assert math.isclose(pops[0], 1.0, abs_tol=1e-1) assert math.isclose(pops[3], 0.0, abs_tol=1e-1) assert math.isclose(pops[1], 0.0, abs_tol=1e-1) diff --git a/test/cirq_utils/test_parallelize.py b/test/cirq_utils/test_parallelize.py index a672e573..b12a12e4 100644 --- a/test/cirq_utils/test_parallelize.py +++ b/test/cirq_utils/test_parallelize.py @@ -27,7 +27,7 @@ def test1(): circuit_m, _ = moment_similarity(circuit, weight=1.0) circuit_b, _ = block_similarity(circuit, weight=1.0, block_id=1) - circuit_m2 = remove_tags(circuit_m) + remove_tags(circuit_m) circuit2 = parallelize(circuit) assert len(circuit2.moments) == 7 @@ -49,16 +49,23 @@ def test_measurement_and_reset(): circuit_m, _ = moment_similarity(circuit, weight=1.0) circuit_b, _ = block_similarity(circuit, weight=1.0, block_id=1) - circuit_m2 = remove_tags(circuit_m) + remove_tags(circuit_m) parallelized_circuit = parallelize(circuit) + assert len(parallelized_circuit.moments) == 11 + # this circuit should deterministically return all qubits to |0> # let's check: simulator = cirq.Simulator() for _ in range(20): # one in a million chance we miss an error state_vector = simulator.simulate(parallelized_circuit).state_vector() - assert np.all(np.isclose(np.abs(state_vector), np.concatenate((np.array([1]), np.zeros(2 ** 4 - 1))))) + assert np.all( + np.isclose( + np.abs(state_vector), + np.concatenate((np.array([1]), np.zeros(2**4 - 1))), + ) + ) def test_nonunitary_error_gate(): From fbf38267f8cfbc3347ec9d45b1a7b492204c7ab8 Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Thu, 27 Nov 2025 09:33:01 +0100 Subject: [PATCH 11/15] Debugging output on failing test --- test/cirq_utils/test_parallelize.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/cirq_utils/test_parallelize.py b/test/cirq_utils/test_parallelize.py index b12a12e4..5996a621 100644 --- a/test/cirq_utils/test_parallelize.py +++ b/test/cirq_utils/test_parallelize.py @@ -53,6 +53,8 @@ def test_measurement_and_reset(): parallelized_circuit = parallelize(circuit) + print(parallelized_circuit) + assert len(parallelized_circuit.moments) == 11 # this circuit should deterministically return all qubits to |0> From 03cd75ae15ab9093a904985fa4c0fe1ca3543152 Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Thu, 27 Nov 2025 09:42:05 +0100 Subject: [PATCH 12/15] Fix test assert --- test/cirq_utils/test_parallelize.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/cirq_utils/test_parallelize.py b/test/cirq_utils/test_parallelize.py index 5996a621..d9b608f5 100644 --- a/test/cirq_utils/test_parallelize.py +++ b/test/cirq_utils/test_parallelize.py @@ -55,7 +55,10 @@ def test_measurement_and_reset(): print(parallelized_circuit) - assert len(parallelized_circuit.moments) == 11 + # NOTE: depending on hardware, cirq produces differing, but unitary equivalent + # native circuits; in some cases, there is a PhZX gate with a negative phase + # which cannot be combined with others in the parallelization leading to a longer circuit + assert len(parallelized_circuit.moments) in (11, 13) # this circuit should deterministically return all qubits to |0> # let's check: From 06641fd8b3a54163e5d04e5d9d89bc65d5322c9d Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Thu, 27 Nov 2025 10:14:47 +0100 Subject: [PATCH 13/15] Undo changes to __post_init__ --- src/bloqade/cirq_utils/noise/model.py | 32 ++++++++++++++++----------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/bloqade/cirq_utils/noise/model.py b/src/bloqade/cirq_utils/noise/model.py index ab335a84..53dc3875 100644 --- a/src/bloqade/cirq_utils/noise/model.py +++ b/src/bloqade/cirq_utils/noise/model.py @@ -56,21 +56,29 @@ class GeminiNoiseModelABC(cirq.NoiseModel, MoveNoiseModelABC): """The correlated CZ error rates as a dictionary""" def __post_init__(self): - if ( + is_ambiguous = ( + self.cz_paired_correlated_rates is not None + and self.cz_paired_error_probabilities is not None + ) + if is_ambiguous: + raise ValueError( + "Received both `cz_paired_correlated_rates` and `cz_paired_error_probabilities` as input. This is ambiguous, please only set one." + ) + + use_default = ( self.cz_paired_correlated_rates is None and self.cz_paired_error_probabilities is None - ): + ) + if use_default: # NOTE: no input, set to default value; weird setattr for frozen dataclass object.__setattr__( self, "cz_paired_error_probabilities", _default_cz_paired_correlated_rates(), ) - elif ( - self.cz_paired_correlated_rates is not None - and self.cz_paired_error_probabilities is None - ): + return + if self.cz_paired_correlated_rates is not None: if self.cz_paired_correlated_rates.shape != (4, 4): raise ValueError( "Expected a 4x4 array of probabilities for cz_paired_correlated_rates" @@ -82,13 +90,11 @@ def __post_init__(self): "cz_paired_error_probabilities", correlated_noise_array_to_dict(self.cz_paired_correlated_rates), ) - elif ( - self.cz_paired_correlated_rates is not None - and self.cz_paired_error_probabilities is not None - ): - raise ValueError( - "Received both `cz_paired_correlated_rates` and `cz_paired_correlated_rates` as input. This is ambiguous, please only set one." - ) + return + + assert ( + self.cz_paired_error_probabilities is not None + ), "This error should not happen! Please report this issue." @staticmethod def validate_moments(moments: Iterable[cirq.Moment]): From e16e42cc9305eeb442713d9c2c8e14ee1fe208db Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Thu, 27 Nov 2025 10:15:47 +0100 Subject: [PATCH 14/15] Fix typing override issue --- src/bloqade/cirq_utils/noise/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bloqade/cirq_utils/noise/model.py b/src/bloqade/cirq_utils/noise/model.py index 53dc3875..07925238 100644 --- a/src/bloqade/cirq_utils/noise/model.py +++ b/src/bloqade/cirq_utils/noise/model.py @@ -121,7 +121,7 @@ def validate_moments(moments: Iterable[cirq.Moment]): ) def parallel_cz_errors( - self, ctrls: list[int], qargs: list[int], rest: list[int] + self, ctrls: Sequence[int], qargs: Sequence[int], rest: Sequence[int] ) -> dict[tuple[float, float, float, float], list[int]]: raise NotImplementedError( "This noise model doesn't support rewrites on bloqade kernels, but should be used with cirq." From dafceb37ece3b150c280b87d814043912e54d946 Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Thu, 27 Nov 2025 10:29:10 +0100 Subject: [PATCH 15/15] Simplify type check --- src/bloqade/cirq_utils/noise/model.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/bloqade/cirq_utils/noise/model.py b/src/bloqade/cirq_utils/noise/model.py index 07925238..5b767a4b 100644 --- a/src/bloqade/cirq_utils/noise/model.py +++ b/src/bloqade/cirq_utils/noise/model.py @@ -102,7 +102,6 @@ def validate_moments(moments: Iterable[cirq.Moment]): allowed_target_gates: frozenset[cirq.GateFamily] = cirq.CZTargetGateset( additional_gates=[reset_family] ).gates - # allowed_target_gates: frozenset[cirq.GateFamily] = cirq.CZTargetGateset().gates for moment in moments: for operation in moment: @@ -370,11 +369,10 @@ def noisy_moments( interleaved_moments = [] def count_remaining_cz_moments(moments_2q): - cz = cirq.CZ remaining_cz_counts = [] count = 0 for m in moments_2q[::-1]: - if any(isinstance(op.gate, type(cz)) for op in m.operations): + if any(isinstance(op.gate, cirq.CZPowGate) for op in m.operations): count += 1 remaining_cz_counts = [count] + remaining_cz_counts return remaining_cz_counts