bloqade-circuit 0.6.2__py3-none-any.whl → 0.9.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bloqade/analysis/address/__init__.py +8 -4
- bloqade/analysis/address/analysis.py +123 -33
- bloqade/analysis/address/impls.py +293 -90
- bloqade/analysis/address/lattice.py +209 -24
- bloqade/analysis/fidelity/analysis.py +11 -23
- bloqade/analysis/measure_id/__init__.py +4 -1
- bloqade/analysis/measure_id/analysis.py +29 -20
- bloqade/analysis/measure_id/impls.py +72 -31
- bloqade/annotate/__init__.py +6 -0
- bloqade/annotate/_dialect.py +3 -0
- bloqade/annotate/_interface.py +22 -0
- bloqade/annotate/stmts.py +29 -0
- bloqade/annotate/types.py +13 -0
- bloqade/cirq_utils/__init__.py +4 -2
- bloqade/cirq_utils/emit/__init__.py +3 -0
- bloqade/cirq_utils/emit/base.py +246 -0
- bloqade/cirq_utils/emit/gate.py +104 -0
- bloqade/cirq_utils/emit/noise.py +90 -0
- bloqade/cirq_utils/emit/qubit.py +35 -0
- bloqade/cirq_utils/lowering.py +660 -0
- bloqade/cirq_utils/noise/__init__.py +0 -2
- bloqade/cirq_utils/noise/_two_zone_utils.py +7 -15
- bloqade/cirq_utils/noise/model.py +151 -191
- bloqade/cirq_utils/noise/transform.py +2 -2
- bloqade/cirq_utils/parallelize.py +9 -6
- bloqade/gemini/__init__.py +1 -0
- bloqade/gemini/analysis/__init__.py +3 -0
- bloqade/gemini/analysis/logical_validation/__init__.py +1 -0
- bloqade/gemini/analysis/logical_validation/analysis.py +17 -0
- bloqade/gemini/analysis/logical_validation/impls.py +101 -0
- bloqade/gemini/groups.py +67 -0
- bloqade/native/__init__.py +23 -0
- bloqade/native/_prelude.py +45 -0
- bloqade/native/dialects/__init__.py +0 -0
- bloqade/native/dialects/gate/__init__.py +2 -0
- bloqade/native/dialects/gate/_dialect.py +3 -0
- bloqade/native/dialects/gate/_interface.py +32 -0
- bloqade/native/dialects/gate/stmts.py +31 -0
- bloqade/native/stdlib/__init__.py +0 -0
- bloqade/native/stdlib/broadcast.py +246 -0
- bloqade/native/stdlib/simple.py +220 -0
- bloqade/native/upstream/__init__.py +4 -0
- bloqade/native/upstream/squin2native.py +79 -0
- bloqade/pyqrack/__init__.py +2 -2
- bloqade/pyqrack/base.py +7 -1
- bloqade/pyqrack/device.py +190 -4
- bloqade/pyqrack/native.py +49 -0
- bloqade/pyqrack/reg.py +6 -6
- bloqade/pyqrack/squin/gate/__init__.py +1 -0
- bloqade/pyqrack/squin/gate/gate.py +136 -0
- bloqade/pyqrack/squin/noise/native.py +120 -54
- bloqade/pyqrack/squin/qubit.py +39 -36
- bloqade/pyqrack/target.py +5 -4
- bloqade/pyqrack/task.py +114 -7
- bloqade/qasm2/_qasm_loading.py +3 -3
- bloqade/qasm2/dialects/core/address.py +21 -12
- bloqade/qasm2/dialects/expr/_emit.py +19 -8
- bloqade/qasm2/dialects/expr/stmts.py +7 -7
- bloqade/qasm2/dialects/noise/fidelity.py +4 -8
- bloqade/qasm2/dialects/noise/model.py +2 -1
- bloqade/qasm2/emit/base.py +16 -11
- bloqade/qasm2/emit/gate.py +11 -8
- bloqade/qasm2/emit/main.py +103 -3
- bloqade/qasm2/emit/target.py +9 -5
- bloqade/qasm2/groups.py +3 -2
- bloqade/qasm2/parse/lowering.py +0 -1
- bloqade/qasm2/passes/fold.py +14 -73
- bloqade/qasm2/passes/glob.py +2 -2
- bloqade/qasm2/passes/noise.py +1 -1
- bloqade/qasm2/passes/parallel.py +7 -5
- bloqade/qasm2/rewrite/__init__.py +0 -1
- bloqade/qasm2/rewrite/noise/heuristic_noise.py +7 -17
- bloqade/qasm2/rewrite/parallel_to_glob.py +28 -15
- bloqade/qasm2/rewrite/parallel_to_uop.py +2 -8
- bloqade/qasm2/rewrite/register.py +2 -2
- bloqade/qasm2/rewrite/uop_to_parallel.py +4 -2
- bloqade/qbraid/lowering.py +1 -0
- bloqade/qbraid/schema.py +2 -2
- bloqade/qubit/__init__.py +12 -0
- bloqade/qubit/_dialect.py +3 -0
- bloqade/qubit/_interface.py +49 -0
- bloqade/qubit/_prelude.py +45 -0
- bloqade/qubit/analysis/__init__.py +1 -0
- bloqade/qubit/analysis/address_impl.py +40 -0
- bloqade/qubit/stdlib/__init__.py +2 -0
- bloqade/qubit/stdlib/_new.py +34 -0
- bloqade/qubit/stdlib/broadcast.py +62 -0
- bloqade/qubit/stdlib/simple.py +59 -0
- bloqade/qubit/stmts.py +60 -0
- bloqade/rewrite/passes/__init__.py +6 -0
- bloqade/rewrite/passes/aggressive_unroll.py +103 -0
- bloqade/rewrite/passes/callgraph.py +116 -0
- bloqade/rewrite/passes/canonicalize_ilist.py +20 -14
- bloqade/rewrite/rules/split_ifs.py +18 -1
- bloqade/squin/__init__.py +47 -14
- bloqade/squin/analysis/__init__.py +0 -1
- bloqade/squin/analysis/schedule.py +10 -11
- bloqade/squin/gate/__init__.py +2 -0
- bloqade/squin/gate/_dialect.py +3 -0
- bloqade/squin/gate/_interface.py +98 -0
- bloqade/squin/gate/stmts.py +125 -0
- bloqade/squin/groups.py +5 -22
- bloqade/squin/noise/__init__.py +1 -10
- bloqade/squin/noise/_dialect.py +1 -1
- bloqade/squin/noise/_interface.py +45 -0
- bloqade/squin/noise/stmts.py +66 -28
- bloqade/squin/rewrite/U3_to_clifford.py +70 -51
- bloqade/squin/rewrite/__init__.py +0 -2
- bloqade/squin/rewrite/remove_dangling_qubits.py +2 -2
- bloqade/squin/rewrite/wrap_analysis.py +4 -35
- bloqade/squin/stdlib/__init__.py +0 -0
- bloqade/squin/stdlib/broadcast/__init__.py +34 -0
- bloqade/squin/stdlib/broadcast/_qubit.py +4 -0
- bloqade/squin/stdlib/broadcast/gate.py +260 -0
- bloqade/squin/stdlib/broadcast/noise.py +144 -0
- bloqade/squin/stdlib/simple/__init__.py +33 -0
- bloqade/squin/stdlib/simple/gate.py +242 -0
- bloqade/squin/stdlib/simple/noise.py +126 -0
- bloqade/stim/__init__.py +1 -0
- bloqade/stim/_wrappers.py +6 -0
- bloqade/stim/dialects/auxiliary/emit.py +19 -18
- bloqade/stim/dialects/collapse/emit_str.py +7 -8
- bloqade/stim/dialects/gate/emit.py +9 -10
- bloqade/stim/dialects/noise/emit.py +17 -13
- bloqade/stim/dialects/noise/stmts.py +5 -3
- bloqade/stim/emit/__init__.py +1 -0
- bloqade/stim/emit/impls.py +16 -0
- bloqade/stim/emit/stim_str.py +48 -31
- bloqade/stim/groups.py +12 -2
- bloqade/stim/parse/lowering.py +14 -17
- bloqade/stim/passes/__init__.py +3 -1
- bloqade/stim/passes/flatten.py +26 -0
- bloqade/stim/passes/simplify_ifs.py +16 -2
- bloqade/stim/passes/squin_to_stim.py +18 -60
- bloqade/stim/rewrite/__init__.py +3 -4
- bloqade/stim/rewrite/get_record_util.py +24 -0
- bloqade/stim/rewrite/ifs_to_stim.py +29 -31
- bloqade/stim/rewrite/qubit_to_stim.py +90 -41
- bloqade/stim/rewrite/set_detector_to_stim.py +68 -0
- bloqade/stim/rewrite/set_observable_to_stim.py +52 -0
- bloqade/stim/rewrite/squin_measure.py +11 -79
- bloqade/stim/rewrite/squin_noise.py +134 -108
- bloqade/stim/rewrite/util.py +5 -192
- bloqade/test_utils.py +1 -1
- bloqade/types.py +10 -0
- bloqade/validation/__init__.py +2 -0
- bloqade/validation/analysis/__init__.py +5 -0
- bloqade/validation/analysis/analysis.py +41 -0
- bloqade/validation/analysis/lattice.py +58 -0
- bloqade/validation/kernel_validation.py +77 -0
- {bloqade_circuit-0.6.2.dist-info → bloqade_circuit-0.9.1.dist-info}/METADATA +5 -6
- bloqade_circuit-0.9.1.dist-info/RECORD +265 -0
- bloqade/pyqrack/squin/op.py +0 -166
- bloqade/pyqrack/squin/runtime.py +0 -535
- bloqade/pyqrack/squin/wire.py +0 -51
- bloqade/rewrite/rules/flatten_ilist.py +0 -51
- bloqade/rewrite/rules/inline_getitem_ilist.py +0 -31
- bloqade/squin/_typeinfer.py +0 -20
- bloqade/squin/analysis/address_impl.py +0 -71
- bloqade/squin/analysis/nsites/__init__.py +0 -9
- bloqade/squin/analysis/nsites/analysis.py +0 -50
- bloqade/squin/analysis/nsites/impls.py +0 -92
- bloqade/squin/analysis/nsites/lattice.py +0 -49
- bloqade/squin/cirq/__init__.py +0 -265
- bloqade/squin/cirq/emit/emit_circuit.py +0 -109
- bloqade/squin/cirq/emit/noise.py +0 -49
- bloqade/squin/cirq/emit/op.py +0 -125
- bloqade/squin/cirq/emit/qubit.py +0 -60
- bloqade/squin/cirq/emit/runtime.py +0 -242
- bloqade/squin/cirq/lowering.py +0 -440
- bloqade/squin/lowering.py +0 -54
- bloqade/squin/noise/_wrapper.py +0 -40
- bloqade/squin/noise/rewrite.py +0 -111
- bloqade/squin/op/__init__.py +0 -41
- bloqade/squin/op/_dialect.py +0 -3
- bloqade/squin/op/_wrapper.py +0 -121
- bloqade/squin/op/number.py +0 -5
- bloqade/squin/op/rewrite.py +0 -46
- bloqade/squin/op/stdlib.py +0 -62
- bloqade/squin/op/stmts.py +0 -276
- bloqade/squin/op/traits.py +0 -43
- bloqade/squin/op/types.py +0 -26
- bloqade/squin/qubit.py +0 -184
- bloqade/squin/rewrite/canonicalize.py +0 -60
- bloqade/squin/rewrite/desugar.py +0 -124
- bloqade/squin/types.py +0 -8
- bloqade/squin/wire.py +0 -201
- bloqade/stim/rewrite/wire_identity_elimination.py +0 -24
- bloqade/stim/rewrite/wire_to_stim.py +0 -57
- bloqade_circuit-0.6.2.dist-info/RECORD +0 -234
- {bloqade_circuit-0.6.2.dist-info → bloqade_circuit-0.9.1.dist-info}/WHEEL +0 -0
- {bloqade_circuit-0.6.2.dist-info → bloqade_circuit-0.9.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -264,8 +264,9 @@ def get_gate_error_channel(
|
|
|
264
264
|
moment: cirq.Moment,
|
|
265
265
|
sq_loc_rates: np.ndarray,
|
|
266
266
|
sq_glob_rates: np.ndarray,
|
|
267
|
-
|
|
267
|
+
two_qubit_pauli: cirq.Gate,
|
|
268
268
|
unp_cz_rates: np.ndarray,
|
|
269
|
+
nqubs: int,
|
|
269
270
|
):
|
|
270
271
|
"""Applies gate errors to the circuit
|
|
271
272
|
|
|
@@ -273,15 +274,15 @@ def get_gate_error_channel(
|
|
|
273
274
|
moment: A cirq.Moment object.
|
|
274
275
|
sq_loc_rates: single local qubit rotation Pauli noise channel parameters (px, py, pz)
|
|
275
276
|
sq_glob_rates: single global qubit rotation Pauli noise channel parameters (px,py,pz)
|
|
276
|
-
|
|
277
|
+
two_qubit_pauli: correlated two-qubit noise channel (ctrl_px, ctrl_py,ctrl_pz,tar_px,tar_py,tar_pz)
|
|
277
278
|
unp_cz_rates: Pauli noise channel parameters for qubits in the gate zone and outside blockade radius
|
|
279
|
+
nqubs: total number of qubits
|
|
278
280
|
Returns:
|
|
279
281
|
A new cirq.Moment object with the gate errors applied.
|
|
280
282
|
"""
|
|
281
283
|
# Check for the moment (layer) layout: global single qubit gates, or mixture of single qubit gates and two qubit gates
|
|
282
284
|
|
|
283
285
|
gates_in_layer = extract_u3_and_cz_qargs(moment)
|
|
284
|
-
# new_moment = cirq.Moment()
|
|
285
286
|
new_moments = cirq.Circuit()
|
|
286
287
|
|
|
287
288
|
if gates_in_layer["cz"] == []:
|
|
@@ -294,7 +295,7 @@ def get_gate_error_channel(
|
|
|
294
295
|
if all(
|
|
295
296
|
np.all(np.isclose(element, gates_in_layer["angles"][0]))
|
|
296
297
|
for element in gates_in_layer["angles"]
|
|
297
|
-
):
|
|
298
|
+
) and nqubs == len(gates_in_layer["u3"]):
|
|
298
299
|
pauli_channel = cirq.AsymmetricDepolarizingChannel(
|
|
299
300
|
p_x=sq_glob_rates[0], p_y=sq_glob_rates[1], p_z=sq_glob_rates[2]
|
|
300
301
|
)
|
|
@@ -314,12 +315,6 @@ def get_gate_error_channel(
|
|
|
314
315
|
|
|
315
316
|
else:
|
|
316
317
|
# there is at least one CZ gate...
|
|
317
|
-
ctrl_pauli_channel = cirq.AsymmetricDepolarizingChannel(
|
|
318
|
-
p_x=cz_rates[0], p_y=cz_rates[1], p_z=cz_rates[2]
|
|
319
|
-
)
|
|
320
|
-
tar_pauli_channel = cirq.AsymmetricDepolarizingChannel(
|
|
321
|
-
p_x=cz_rates[3], p_y=cz_rates[4], p_z=cz_rates[5]
|
|
322
|
-
)
|
|
323
318
|
loc_rot_pauli_channel = cirq.AsymmetricDepolarizingChannel(
|
|
324
319
|
p_x=sq_loc_rates[0], p_y=sq_loc_rates[1], p_z=sq_loc_rates[2]
|
|
325
320
|
)
|
|
@@ -327,18 +322,15 @@ def get_gate_error_channel(
|
|
|
327
322
|
p_x=unp_cz_rates[0], p_y=unp_cz_rates[1], p_z=unp_cz_rates[2]
|
|
328
323
|
)
|
|
329
324
|
|
|
325
|
+
# apply correlated noise to paired qubits
|
|
330
326
|
for qub in gates_in_layer["cz"]:
|
|
331
|
-
new_moments.append(
|
|
332
|
-
new_moments.append(tar_pauli_channel(qub[1]))
|
|
333
|
-
# new_moment=new_moment+ctrl_pauli_channel(qub[0])
|
|
334
|
-
# new_moment=new_moment+tar_pauli_channel(qub[1])
|
|
327
|
+
new_moments.append(two_qubit_pauli.on(qub[0], qub[1]))
|
|
335
328
|
|
|
336
329
|
for qub in gates_in_layer["u3"]:
|
|
337
330
|
new_moments.append(
|
|
338
331
|
unp_cz_pauli_channel(qub[0])
|
|
339
332
|
) ###qubits in the gate zone get unpaired_cz error
|
|
340
333
|
new_moments.append(loc_rot_pauli_channel(qub[0]))
|
|
341
|
-
# new_moment = new_moment + loc_rot_pauli_channel(qub[0])
|
|
342
334
|
|
|
343
335
|
return new_moments
|
|
344
336
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from typing import Iterable, Sequence
|
|
2
|
-
from dataclasses import
|
|
1
|
+
from typing import Iterable, Sequence, cast
|
|
2
|
+
from dataclasses import dataclass
|
|
3
3
|
|
|
4
4
|
import cirq
|
|
5
5
|
import numpy as np
|
|
@@ -11,6 +11,33 @@ from ..parallelize import parallelize
|
|
|
11
11
|
from .conflict_graph import OneZoneConflictGraph
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
def _default_cz_paired_correlated_rates() -> dict:
|
|
15
|
+
rates = np.array(
|
|
16
|
+
[
|
|
17
|
+
[9.93492628e-01, 2.27472300e-04, 2.27472300e-04, 1.51277730e-03],
|
|
18
|
+
[2.27472300e-04, 1.42864200e-04, 1.42864200e-04, 1.43082900e-04],
|
|
19
|
+
[2.27472300e-04, 1.42864200e-04, 1.42864200e-04, 1.43082900e-04],
|
|
20
|
+
[1.51277730e-03, 1.43082900e-04, 1.43082900e-04, 1.42813990e-03],
|
|
21
|
+
]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
return correlated_noise_array_to_dict(noise_rates=rates)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def correlated_noise_array_to_dict(noise_rates: np.ndarray) -> dict:
|
|
28
|
+
paulis = ("I", "X", "Y", "Z")
|
|
29
|
+
error_probabilities = {}
|
|
30
|
+
for idx1, p1 in enumerate(paulis):
|
|
31
|
+
for idx2, p2 in enumerate(paulis):
|
|
32
|
+
probability = noise_rates[idx1, idx2]
|
|
33
|
+
|
|
34
|
+
if probability > 0:
|
|
35
|
+
key = p1 + p2
|
|
36
|
+
error_probabilities[key] = probability
|
|
37
|
+
|
|
38
|
+
return error_probabilities
|
|
39
|
+
|
|
40
|
+
|
|
14
41
|
@dataclass(frozen=True)
|
|
15
42
|
class GeminiNoiseModelABC(cirq.NoiseModel, MoveNoiseModelABC):
|
|
16
43
|
"""Abstract base class for all Gemini noise models."""
|
|
@@ -22,6 +49,53 @@ class GeminiNoiseModelABC(cirq.NoiseModel, MoveNoiseModelABC):
|
|
|
22
49
|
|
|
23
50
|
"""
|
|
24
51
|
|
|
52
|
+
cz_paired_correlated_rates: np.ndarray | None = None
|
|
53
|
+
"""The correlated CZ error rates as a 4x4 array."""
|
|
54
|
+
|
|
55
|
+
cz_paired_error_probabilities: dict | None = None
|
|
56
|
+
"""The correlated CZ error rates as a dictionary"""
|
|
57
|
+
|
|
58
|
+
def __post_init__(self):
|
|
59
|
+
is_ambiguous = (
|
|
60
|
+
self.cz_paired_correlated_rates is not None
|
|
61
|
+
and self.cz_paired_error_probabilities is not None
|
|
62
|
+
)
|
|
63
|
+
if is_ambiguous:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
"Received both `cz_paired_correlated_rates` and `cz_paired_error_probabilities` as input. This is ambiguous, please only set one."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
use_default = (
|
|
69
|
+
self.cz_paired_correlated_rates is None
|
|
70
|
+
and self.cz_paired_error_probabilities is None
|
|
71
|
+
)
|
|
72
|
+
if use_default:
|
|
73
|
+
# NOTE: no input, set to default value; weird setattr for frozen dataclass
|
|
74
|
+
object.__setattr__(
|
|
75
|
+
self,
|
|
76
|
+
"cz_paired_error_probabilities",
|
|
77
|
+
_default_cz_paired_correlated_rates(),
|
|
78
|
+
)
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
if self.cz_paired_correlated_rates is not None:
|
|
82
|
+
if self.cz_paired_correlated_rates.shape != (4, 4):
|
|
83
|
+
raise ValueError(
|
|
84
|
+
"Expected a 4x4 array of probabilities for cz_paired_correlated_rates"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# NOTE: convert array to dict
|
|
88
|
+
object.__setattr__(
|
|
89
|
+
self,
|
|
90
|
+
"cz_paired_error_probabilities",
|
|
91
|
+
correlated_noise_array_to_dict(self.cz_paired_correlated_rates),
|
|
92
|
+
)
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
assert (
|
|
96
|
+
self.cz_paired_error_probabilities is not None
|
|
97
|
+
), "This error should not happen! Please report this issue."
|
|
98
|
+
|
|
25
99
|
@staticmethod
|
|
26
100
|
def validate_moments(moments: Iterable[cirq.Moment]):
|
|
27
101
|
allowed_target_gates: frozenset[cirq.GateFamily] = cirq.CZTargetGateset().gates
|
|
@@ -81,72 +155,33 @@ class GeminiNoiseModelABC(cirq.NoiseModel, MoveNoiseModelABC):
|
|
|
81
155
|
self.cz_unpaired_gate_pz,
|
|
82
156
|
)
|
|
83
157
|
|
|
158
|
+
@property
|
|
159
|
+
def two_qubit_pauli(self) -> cirq.AsymmetricDepolarizingChannel:
|
|
160
|
+
# NOTE: if this was None it would error when instantiating self
|
|
161
|
+
# quiet the linter for the copy below
|
|
162
|
+
error_probabilities = cast(dict, self.cz_paired_error_probabilities)
|
|
84
163
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
parallelize_circuit: bool = False
|
|
90
|
-
|
|
91
|
-
def noisy_moments(
|
|
92
|
-
self, moments: Iterable[cirq.Moment], system_qubits: Sequence[cirq.Qid]
|
|
93
|
-
) -> Sequence[cirq.OP_TREE]:
|
|
94
|
-
"""Adds possibly stateful noise to a series of moments.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
moments: The moments to add noise to.
|
|
98
|
-
system_qubits: A list of all qubits in the system.
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
A sequence of OP_TREEEs, with the k'th tree corresponding to the
|
|
102
|
-
noisy operations for the k'th moment.
|
|
103
|
-
"""
|
|
104
|
-
|
|
105
|
-
if self.check_input_circuit:
|
|
106
|
-
self.validate_moments(moments)
|
|
107
|
-
|
|
108
|
-
# Split into moments with only 1Q and 2Q gates
|
|
109
|
-
moments_1q = [
|
|
110
|
-
cirq.Moment([op for op in moment.operations if len(op.qubits) == 1])
|
|
111
|
-
for moment in moments
|
|
112
|
-
]
|
|
113
|
-
moments_2q = [
|
|
114
|
-
cirq.Moment([op for op in moment.operations if len(op.qubits) == 2])
|
|
115
|
-
for moment in moments
|
|
116
|
-
]
|
|
117
|
-
|
|
118
|
-
assert len(moments_1q) == len(moments_2q)
|
|
119
|
-
|
|
120
|
-
interleaved_moments = []
|
|
121
|
-
for idx, moment in enumerate(moments_1q):
|
|
122
|
-
interleaved_moments.append(moment)
|
|
123
|
-
interleaved_moments.append(moments_2q[idx])
|
|
124
|
-
|
|
125
|
-
interleaved_circuit = cirq.Circuit.from_moments(*interleaved_moments)
|
|
126
|
-
|
|
127
|
-
# Combine subsequent 1Q gates
|
|
128
|
-
compressed_circuit = cirq.merge_single_qubit_moments_to_phxz(
|
|
129
|
-
interleaved_circuit
|
|
130
|
-
)
|
|
131
|
-
if self.parallelize_circuit:
|
|
132
|
-
compressed_circuit = parallelize(compressed_circuit)
|
|
133
|
-
|
|
134
|
-
return self._noisy_moments_impl_moment(
|
|
135
|
-
compressed_circuit.moments, system_qubits
|
|
164
|
+
# NOTE: copy dict since cirq modifies it in-place somewhere
|
|
165
|
+
return cirq.AsymmetricDepolarizingChannel(
|
|
166
|
+
error_probabilities=error_probabilities.copy()
|
|
136
167
|
)
|
|
137
168
|
|
|
138
169
|
|
|
139
170
|
@dataclass(frozen=True)
|
|
140
|
-
class GeminiOneZoneNoiseModel(
|
|
171
|
+
class GeminiOneZoneNoiseModel(GeminiNoiseModelABC):
|
|
141
172
|
"""
|
|
142
173
|
A Cirq-compatible noise model for a one-zone implementation of the Gemini architecture.
|
|
143
174
|
|
|
144
175
|
This model introduces custom asymmetric depolarizing noise for both single- and two-qubit gates
|
|
145
176
|
depending on whether operations are global, local, or part of a CZ interaction. Since the model assumes all
|
|
146
|
-
atoms are in the entangling zone,
|
|
177
|
+
atoms are in the entangling zone, errors are applied that stem from application of Rydberg error, even for
|
|
147
178
|
qubits not actively involved in a gate operation.
|
|
179
|
+
|
|
180
|
+
Note, that the noise applied to entangling pairs is correlated.
|
|
148
181
|
"""
|
|
149
182
|
|
|
183
|
+
parallelize_circuit: bool = False
|
|
184
|
+
|
|
150
185
|
def _single_qubit_moment_noise_ops(
|
|
151
186
|
self, moment: cirq.Moment, system_qubits: Sequence[cirq.Qid]
|
|
152
187
|
) -> tuple[list, list]:
|
|
@@ -206,132 +241,6 @@ class GeminiOneZoneNoiseModel(GeminiOneZoneNoiseModelABC):
|
|
|
206
241
|
|
|
207
242
|
return [gate_noise_op], []
|
|
208
243
|
|
|
209
|
-
def noisy_moment(self, moment: cirq.Moment, system_qubits: Sequence[cirq.Qid]):
|
|
210
|
-
"""
|
|
211
|
-
Applies a structured noise model to a given moment depending on the type of operations it contains.
|
|
212
|
-
|
|
213
|
-
For single-qubit moments:
|
|
214
|
-
- If all gates are identical and act on all qubits, global noise is applied.
|
|
215
|
-
- Otherwise, local depolarizing noise is applied per qubit.
|
|
216
|
-
|
|
217
|
-
For two-qubit moments:
|
|
218
|
-
- Applies move error to move control qubits to target qubits before the gate and again to move back after
|
|
219
|
-
the gate.
|
|
220
|
-
- Applies gate error to control and target qubits.
|
|
221
|
-
- Adds 1q asymmetric noise to qubits that do not participate in a gate.
|
|
222
|
-
|
|
223
|
-
Args:
|
|
224
|
-
moment: A cirq.Moment containing the original quantum operations.
|
|
225
|
-
system_qubits: All qubits in the system (used to determine idleness and global operations).
|
|
226
|
-
|
|
227
|
-
Returns:
|
|
228
|
-
A list of cirq.Moment objects:
|
|
229
|
-
[pre-gate move noise, original moment, post-gate move noise, gate noise moment]
|
|
230
|
-
|
|
231
|
-
Raises:
|
|
232
|
-
ValueError: If the moment contains multi-qubit gates involving >2 qubits, which are unsupported.
|
|
233
|
-
"""
|
|
234
|
-
# Moment with original ops
|
|
235
|
-
original_moment = moment
|
|
236
|
-
|
|
237
|
-
# Check if the moment is empty
|
|
238
|
-
if len(moment.operations) == 0:
|
|
239
|
-
move_noise_ops = []
|
|
240
|
-
gate_noise_ops = []
|
|
241
|
-
# Check if the moment contains 1-qubit gates or 2-qubit gates
|
|
242
|
-
elif len(moment.operations[0].qubits) == 1:
|
|
243
|
-
gate_noise_ops, move_noise_ops = self._single_qubit_moment_noise_ops(
|
|
244
|
-
moment, system_qubits
|
|
245
|
-
)
|
|
246
|
-
elif len(moment.operations[0].qubits) == 2:
|
|
247
|
-
# Check if the moment only contains two qubit gates
|
|
248
|
-
assert np.all([len(op.qubits) == 2 for op in moment.operations])
|
|
249
|
-
|
|
250
|
-
control_qubits = [op.qubits[0] for op in moment.operations]
|
|
251
|
-
target_qubits = [op.qubits[1] for op in moment.operations]
|
|
252
|
-
gated_qubits = control_qubits + target_qubits
|
|
253
|
-
idle_atoms = list(set(system_qubits) - set(gated_qubits))
|
|
254
|
-
|
|
255
|
-
move_noise_ops = [
|
|
256
|
-
cirq.asymmetric_depolarize(*self.mover_pauli_rates).on_each(
|
|
257
|
-
control_qubits
|
|
258
|
-
),
|
|
259
|
-
cirq.asymmetric_depolarize(*self.sitter_pauli_rates).on_each(
|
|
260
|
-
target_qubits + idle_atoms
|
|
261
|
-
),
|
|
262
|
-
] # In this setting, we assume a 1 zone scheme where the controls move to the targets.
|
|
263
|
-
|
|
264
|
-
gate_noise_ops = [
|
|
265
|
-
cirq.asymmetric_depolarize(*self.cz_paired_pauli_rates).on_each(
|
|
266
|
-
gated_qubits
|
|
267
|
-
),
|
|
268
|
-
cirq.asymmetric_depolarize(*self.cz_unpaired_pauli_rates).on_each(
|
|
269
|
-
idle_atoms
|
|
270
|
-
),
|
|
271
|
-
] # In this 1 zone scheme, all unpaired atoms are in the entangling zone.
|
|
272
|
-
else:
|
|
273
|
-
raise ValueError(
|
|
274
|
-
"Moment contains operations with more than 2 qubits, which is not supported. "
|
|
275
|
-
"Correlated measurements should be added after the noise model is applied."
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
if move_noise_ops == []:
|
|
279
|
-
move_noise_moments = []
|
|
280
|
-
else:
|
|
281
|
-
move_noise_moments = [cirq.Moment(move_noise_ops)]
|
|
282
|
-
gate_noise_moment = cirq.Moment(gate_noise_ops)
|
|
283
|
-
|
|
284
|
-
return [
|
|
285
|
-
*move_noise_moments,
|
|
286
|
-
original_moment,
|
|
287
|
-
gate_noise_moment,
|
|
288
|
-
*move_noise_moments,
|
|
289
|
-
]
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
def _default_cz_paired_correlated_rates() -> np.ndarray:
|
|
293
|
-
return np.array(
|
|
294
|
-
[
|
|
295
|
-
[0.994000006, 0.000142857, 0.000142857, 0.001428570],
|
|
296
|
-
[0.000142857, 0.000142857, 0.000142857, 0.000142857],
|
|
297
|
-
[0.000142857, 0.000142857, 0.000142857, 0.000142857],
|
|
298
|
-
[0.001428570, 0.000142857, 0.000142857, 0.001428570],
|
|
299
|
-
]
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
@dataclass(frozen=True)
|
|
304
|
-
class GeminiOneZoneNoiseModelCorrelated(GeminiOneZoneNoiseModel):
|
|
305
|
-
"""
|
|
306
|
-
A Cirq noise model for implementing correlated two-qubit Pauli errors in a one-zone Gemini architecture.
|
|
307
|
-
"""
|
|
308
|
-
|
|
309
|
-
cz_paired_correlated_rates: np.ndarray = field(
|
|
310
|
-
default_factory=_default_cz_paired_correlated_rates
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
def __post_init__(self):
|
|
314
|
-
if self.cz_paired_correlated_rates.shape != (4, 4):
|
|
315
|
-
raise ValueError(
|
|
316
|
-
"Expected a 4x4 array of probabilities for cz_paired_correlated_rates"
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
@property
|
|
320
|
-
def two_qubit_pauli(self) -> cirq.AsymmetricDepolarizingChannel:
|
|
321
|
-
paulis = ("I", "X", "Y", "Z")
|
|
322
|
-
error_probabilities = {}
|
|
323
|
-
for idx1, p1 in enumerate(paulis):
|
|
324
|
-
for idx2, p2 in enumerate(paulis):
|
|
325
|
-
probability = self.cz_paired_correlated_rates[idx1, idx2]
|
|
326
|
-
|
|
327
|
-
if probability > 0:
|
|
328
|
-
key = p1 + p2
|
|
329
|
-
error_probabilities[key] = probability
|
|
330
|
-
|
|
331
|
-
return cirq.AsymmetricDepolarizingChannel(
|
|
332
|
-
error_probabilities=error_probabilities
|
|
333
|
-
)
|
|
334
|
-
|
|
335
244
|
def noisy_moment(self, moment, system_qubits):
|
|
336
245
|
# Moment with original ops
|
|
337
246
|
original_moment = moment
|
|
@@ -391,6 +300,53 @@ class GeminiOneZoneNoiseModelCorrelated(GeminiOneZoneNoiseModel):
|
|
|
391
300
|
*move_noise_moments,
|
|
392
301
|
]
|
|
393
302
|
|
|
303
|
+
def noisy_moments(
|
|
304
|
+
self, moments: Iterable[cirq.Moment], system_qubits: Sequence[cirq.Qid]
|
|
305
|
+
) -> Sequence[cirq.OP_TREE]:
|
|
306
|
+
"""Adds possibly stateful noise to a series of moments.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
moments: The moments to add noise to.
|
|
310
|
+
system_qubits: A list of all qubits in the system.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
A sequence of OP_TREEEs, with the k'th tree corresponding to the
|
|
314
|
+
noisy operations for the k'th moment.
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
if self.check_input_circuit:
|
|
318
|
+
self.validate_moments(moments)
|
|
319
|
+
|
|
320
|
+
# Split into moments with only 1Q and 2Q gates
|
|
321
|
+
moments_1q = [
|
|
322
|
+
cirq.Moment([op for op in moment.operations if len(op.qubits) == 1])
|
|
323
|
+
for moment in moments
|
|
324
|
+
]
|
|
325
|
+
moments_2q = [
|
|
326
|
+
cirq.Moment([op for op in moment.operations if len(op.qubits) == 2])
|
|
327
|
+
for moment in moments
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
assert len(moments_1q) == len(moments_2q)
|
|
331
|
+
|
|
332
|
+
interleaved_moments = []
|
|
333
|
+
for idx, moment in enumerate(moments_1q):
|
|
334
|
+
interleaved_moments.append(moment)
|
|
335
|
+
interleaved_moments.append(moments_2q[idx])
|
|
336
|
+
|
|
337
|
+
interleaved_circuit = cirq.Circuit.from_moments(*interleaved_moments)
|
|
338
|
+
|
|
339
|
+
# Combine subsequent 1Q gates
|
|
340
|
+
compressed_circuit = cirq.merge_single_qubit_moments_to_phxz(
|
|
341
|
+
interleaved_circuit
|
|
342
|
+
)
|
|
343
|
+
if self.parallelize_circuit:
|
|
344
|
+
compressed_circuit = parallelize(compressed_circuit)
|
|
345
|
+
|
|
346
|
+
return self._noisy_moments_impl_moment(
|
|
347
|
+
compressed_circuit.moments, system_qubits
|
|
348
|
+
)
|
|
349
|
+
|
|
394
350
|
|
|
395
351
|
@dataclass(frozen=True)
|
|
396
352
|
class GeminiOneZoneNoiseModelConflictGraphMoves(GeminiOneZoneNoiseModel):
|
|
@@ -407,9 +363,10 @@ class GeminiOneZoneNoiseModelConflictGraphMoves(GeminiOneZoneNoiseModel):
|
|
|
407
363
|
def noisy_moment(self, moment, system_qubits):
|
|
408
364
|
# Moment with original ops
|
|
409
365
|
original_moment = moment
|
|
410
|
-
assert np.all(
|
|
411
|
-
|
|
412
|
-
|
|
366
|
+
assert np.all([isinstance(q, cirq.GridQubit) for q in system_qubits]), (
|
|
367
|
+
"Found a qubit that is not a GridQubit. In order for the conflict graph to know the qubit geometry, "
|
|
368
|
+
"all qubits in the circuit must be defined as cirq.GridQubit objects."
|
|
369
|
+
)
|
|
413
370
|
# Check if the moment is empty
|
|
414
371
|
if len(moment.operations) == 0:
|
|
415
372
|
move_moments = []
|
|
@@ -452,14 +409,18 @@ class GeminiOneZoneNoiseModelConflictGraphMoves(GeminiOneZoneNoiseModel):
|
|
|
452
409
|
gated_qubits = control_qubits + target_qubits
|
|
453
410
|
idle_atoms = list(set(system_qubits) - set(gated_qubits))
|
|
454
411
|
|
|
412
|
+
# Add correlated noise channels for entangling pairs
|
|
413
|
+
two_qubit_pauli = self.two_qubit_pauli
|
|
455
414
|
gate_noise_ops = [
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
415
|
+
two_qubit_pauli.on(c, t) for c, t in zip(control_qubits, target_qubits)
|
|
416
|
+
]
|
|
417
|
+
|
|
418
|
+
# In this 1 zone scheme, all unpaired atoms are in the entangling zone.
|
|
419
|
+
gate_noise_ops.append(
|
|
459
420
|
cirq.asymmetric_depolarize(*self.cz_unpaired_pauli_rates).on_each(
|
|
460
421
|
idle_atoms
|
|
461
422
|
),
|
|
462
|
-
|
|
423
|
+
)
|
|
463
424
|
else:
|
|
464
425
|
raise ValueError(
|
|
465
426
|
"Moment contains operations with more than 2 qubits, which is not supported."
|
|
@@ -530,10 +491,9 @@ class GeminiTwoZoneNoiseModel(GeminiNoiseModelABC):
|
|
|
530
491
|
moments[i],
|
|
531
492
|
np.array(self.local_pauli_rates),
|
|
532
493
|
np.array(self.global_pauli_rates),
|
|
533
|
-
|
|
534
|
-
self.cz_paired_pauli_rates + self.cz_paired_pauli_rates
|
|
535
|
-
),
|
|
494
|
+
self.two_qubit_pauli,
|
|
536
495
|
np.array(self.cz_unpaired_pauli_rates),
|
|
496
|
+
nqubs,
|
|
537
497
|
).moments
|
|
538
498
|
if len(moment) > 0
|
|
539
499
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import cirq
|
|
2
2
|
|
|
3
|
-
from .model import GeminiOneZoneNoiseModel
|
|
3
|
+
from .model import GeminiOneZoneNoiseModel
|
|
4
4
|
from ..parallelize import transpile, parallelize
|
|
5
5
|
|
|
6
6
|
|
|
@@ -36,7 +36,7 @@ def transform_circuit(
|
|
|
36
36
|
|
|
37
37
|
# only parallelize here if we aren't parallelizing inside a one-zone model
|
|
38
38
|
parallelize_circuit_here = parallelize_circuit and not isinstance(
|
|
39
|
-
model,
|
|
39
|
+
model, GeminiOneZoneNoiseModel
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
system_qubits = sorted(circuit.all_qubits())
|
|
@@ -136,7 +136,7 @@ def auto_similarity(
|
|
|
136
136
|
return cirq.Circuit(flattened_circuit), weights
|
|
137
137
|
|
|
138
138
|
|
|
139
|
-
def
|
|
139
|
+
def remove_tags(circuit: cirq.Circuit) -> cirq.Circuit:
|
|
140
140
|
"""
|
|
141
141
|
Removes all tags from the circuit
|
|
142
142
|
|
|
@@ -146,10 +146,11 @@ def no_similarity(circuit: cirq.Circuit) -> cirq.Circuit:
|
|
|
146
146
|
Returns:
|
|
147
147
|
[0] - cirq.Circuit - the circuit with all tags removed.
|
|
148
148
|
"""
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
|
|
150
|
+
def remove_tag(op: cirq.Operation, _):
|
|
151
|
+
return op.untagged
|
|
152
|
+
|
|
153
|
+
return cirq.map_operations(circuit, remove_tag)
|
|
153
154
|
|
|
154
155
|
|
|
155
156
|
def to_dag_circuit(circuit: cirq.Circuit, can_reorder=None) -> nx.DiGraph:
|
|
@@ -399,4 +400,6 @@ def parallelize(
|
|
|
399
400
|
)
|
|
400
401
|
# Convert the epochs to a cirq circuit.
|
|
401
402
|
moments = map(cirq.Moment, epochs)
|
|
402
|
-
|
|
403
|
+
circuit = cirq.Circuit(moments)
|
|
404
|
+
|
|
405
|
+
return remove_tags(circuit)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .groups import logical as logical
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import impls as impls, analysis as analysis # NOTE: register methods
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from kirin import ir
|
|
2
|
+
|
|
3
|
+
from bloqade import squin
|
|
4
|
+
from bloqade.validation.analysis import ValidationFrame, ValidationAnalysis
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GeminiLogicalValidationAnalysis(ValidationAnalysis):
|
|
8
|
+
keys = ["gemini.validate.logical"]
|
|
9
|
+
|
|
10
|
+
first_gate = True
|
|
11
|
+
|
|
12
|
+
def eval_fallback(self, frame: ValidationFrame, node: ir.Statement):
|
|
13
|
+
if isinstance(node, squin.gate.stmts.Gate):
|
|
14
|
+
# NOTE: to validate that only the first encountered gate can be non-Clifford, we need to track this here
|
|
15
|
+
self.first_gate = False
|
|
16
|
+
|
|
17
|
+
return super().eval_fallback(frame, node)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from kirin import ir, interp as _interp
|
|
2
|
+
from kirin.analysis import const
|
|
3
|
+
from kirin.dialects import scf, func
|
|
4
|
+
|
|
5
|
+
from bloqade.squin import gate
|
|
6
|
+
from bloqade.validation.analysis import ValidationFrame
|
|
7
|
+
from bloqade.validation.analysis.lattice import Error
|
|
8
|
+
|
|
9
|
+
from .analysis import GeminiLogicalValidationAnalysis
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@scf.dialect.register(key="gemini.validate.logical")
|
|
13
|
+
class __ScfGeminiLogicalValidation(_interp.MethodTable):
|
|
14
|
+
|
|
15
|
+
@_interp.impl(scf.IfElse)
|
|
16
|
+
def if_else(
|
|
17
|
+
self,
|
|
18
|
+
interp: GeminiLogicalValidationAnalysis,
|
|
19
|
+
frame: ValidationFrame,
|
|
20
|
+
stmt: scf.IfElse,
|
|
21
|
+
):
|
|
22
|
+
frame.errors.append(
|
|
23
|
+
ir.ValidationError(
|
|
24
|
+
stmt, "If statements are not supported in logical Gemini programs!"
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
return (
|
|
28
|
+
Error(
|
|
29
|
+
message="If statements are not supported in logical Gemini programs!"
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@_interp.impl(scf.For)
|
|
34
|
+
def for_loop(
|
|
35
|
+
self,
|
|
36
|
+
interp: GeminiLogicalValidationAnalysis,
|
|
37
|
+
frame: ValidationFrame,
|
|
38
|
+
stmt: scf.For,
|
|
39
|
+
):
|
|
40
|
+
if isinstance(stmt.iterable.hints.get("const"), const.Value):
|
|
41
|
+
return (interp.lattice.top(),)
|
|
42
|
+
|
|
43
|
+
frame.errors.append(
|
|
44
|
+
ir.ValidationError(
|
|
45
|
+
stmt,
|
|
46
|
+
"Non-constant iterable in for loop is not supported in Gemini logical programs!",
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
Error(
|
|
52
|
+
message="Non-constant iterable in for loop is not supported in Gemini logical programs!"
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@func.dialect.register(key="gemini.validate.logical")
|
|
58
|
+
class __FuncGeminiLogicalValidation(_interp.MethodTable):
|
|
59
|
+
@_interp.impl(func.Invoke)
|
|
60
|
+
def invoke(
|
|
61
|
+
self,
|
|
62
|
+
interp: GeminiLogicalValidationAnalysis,
|
|
63
|
+
frame: ValidationFrame,
|
|
64
|
+
stmt: func.Invoke,
|
|
65
|
+
):
|
|
66
|
+
frame.errors.append(
|
|
67
|
+
ir.ValidationError(
|
|
68
|
+
stmt,
|
|
69
|
+
"Function invocations not supported in logical Gemini program!",
|
|
70
|
+
help="Make sure to decorate your function with `@logical(inline = True)` or `@logical(aggressive_unroll = True)` to inline function calls",
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return tuple(
|
|
75
|
+
Error(
|
|
76
|
+
message="Function invocations not supported in logical Gemini program!"
|
|
77
|
+
)
|
|
78
|
+
for _ in stmt.results
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@gate.dialect.register(key="gemini.validate.logical")
|
|
83
|
+
class __GateGeminiLogicalValidation(_interp.MethodTable):
|
|
84
|
+
@_interp.impl(gate.stmts.U3)
|
|
85
|
+
def u3(
|
|
86
|
+
self,
|
|
87
|
+
interp: GeminiLogicalValidationAnalysis,
|
|
88
|
+
frame: ValidationFrame,
|
|
89
|
+
stmt: gate.stmts.U3,
|
|
90
|
+
):
|
|
91
|
+
if interp.first_gate:
|
|
92
|
+
interp.first_gate = False
|
|
93
|
+
return ()
|
|
94
|
+
|
|
95
|
+
frame.errors.append(
|
|
96
|
+
ir.ValidationError(
|
|
97
|
+
stmt,
|
|
98
|
+
"U3 gate can only be used for initial state preparation, i.e. as the first gate!",
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
return ()
|