bloqade-circuit 0.7.12__py3-none-any.whl → 0.8.0__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.

Potentially problematic release.


This version of bloqade-circuit might be problematic. Click here for more details.

Files changed (136) hide show
  1. bloqade/analysis/address/__init__.py +8 -4
  2. bloqade/analysis/address/analysis.py +119 -29
  3. bloqade/analysis/address/impls.py +290 -87
  4. bloqade/analysis/address/lattice.py +209 -24
  5. bloqade/analysis/fidelity/analysis.py +2 -2
  6. bloqade/analysis/measure_id/impls.py +3 -27
  7. bloqade/cirq_utils/__init__.py +3 -1
  8. bloqade/cirq_utils/emit/__init__.py +3 -0
  9. bloqade/cirq_utils/emit/base.py +243 -0
  10. bloqade/cirq_utils/emit/gate.py +104 -0
  11. bloqade/cirq_utils/emit/noise.py +90 -0
  12. bloqade/cirq_utils/emit/qubit.py +35 -0
  13. bloqade/cirq_utils/lowering.py +664 -0
  14. bloqade/native/__init__.py +0 -1
  15. bloqade/native/_prelude.py +3 -3
  16. bloqade/native/dialects/gate/__init__.py +2 -0
  17. bloqade/native/dialects/gate/_dialect.py +3 -0
  18. bloqade/native/dialects/{gates → gate}/_interface.py +5 -5
  19. bloqade/native/dialects/{gates → gate}/stmts.py +5 -5
  20. bloqade/native/stdlib/broadcast.py +19 -19
  21. bloqade/native/stdlib/simple.py +14 -13
  22. bloqade/native/upstream/__init__.py +5 -0
  23. bloqade/native/upstream/squin2native.py +136 -0
  24. bloqade/pyqrack/__init__.py +1 -2
  25. bloqade/pyqrack/device.py +6 -17
  26. bloqade/pyqrack/native.py +17 -17
  27. bloqade/pyqrack/reg.py +1 -6
  28. bloqade/pyqrack/squin/gate/__init__.py +1 -0
  29. bloqade/pyqrack/squin/gate/gate.py +136 -0
  30. bloqade/pyqrack/squin/noise/native.py +120 -54
  31. bloqade/pyqrack/squin/qubit.py +25 -41
  32. bloqade/pyqrack/target.py +2 -2
  33. bloqade/qasm2/dialects/core/address.py +21 -12
  34. bloqade/qasm2/dialects/noise/fidelity.py +2 -6
  35. bloqade/qasm2/dialects/noise/model.py +2 -1
  36. bloqade/qasm2/passes/parallel.py +3 -1
  37. bloqade/qasm2/rewrite/__init__.py +0 -1
  38. bloqade/qasm2/rewrite/noise/heuristic_noise.py +7 -17
  39. bloqade/qasm2/rewrite/parallel_to_glob.py +28 -15
  40. bloqade/qasm2/rewrite/parallel_to_uop.py +2 -8
  41. bloqade/qubit/__init__.py +12 -0
  42. bloqade/qubit/_dialect.py +3 -0
  43. bloqade/qubit/_interface.py +49 -0
  44. bloqade/qubit/_prelude.py +45 -0
  45. bloqade/qubit/analysis/__init__.py +1 -0
  46. bloqade/qubit/analysis/address_impl.py +40 -0
  47. bloqade/qubit/stdlib/__init__.py +2 -0
  48. bloqade/qubit/stdlib/_new.py +34 -0
  49. bloqade/qubit/stdlib/broadcast.py +62 -0
  50. bloqade/qubit/stdlib/simple.py +59 -0
  51. bloqade/qubit/stmts.py +60 -0
  52. bloqade/rewrite/passes/aggressive_unroll.py +2 -1
  53. bloqade/squin/__init__.py +44 -17
  54. bloqade/squin/analysis/__init__.py +0 -1
  55. bloqade/squin/analysis/schedule.py +2 -2
  56. bloqade/squin/gate/__init__.py +2 -0
  57. bloqade/squin/gate/_dialect.py +3 -0
  58. bloqade/squin/gate/_interface.py +98 -0
  59. bloqade/squin/gate/stmts.py +119 -0
  60. bloqade/squin/groups.py +4 -21
  61. bloqade/squin/noise/__init__.py +1 -9
  62. bloqade/squin/noise/_dialect.py +1 -1
  63. bloqade/squin/noise/_interface.py +45 -0
  64. bloqade/squin/noise/stmts.py +65 -29
  65. bloqade/squin/rewrite/U3_to_clifford.py +70 -51
  66. bloqade/squin/rewrite/__init__.py +0 -2
  67. bloqade/squin/rewrite/remove_dangling_qubits.py +2 -2
  68. bloqade/squin/rewrite/wrap_analysis.py +4 -35
  69. bloqade/squin/stdlib/broadcast/__init__.py +34 -0
  70. bloqade/squin/stdlib/broadcast/_qubit.py +4 -0
  71. bloqade/squin/stdlib/broadcast/gate.py +260 -0
  72. bloqade/squin/stdlib/broadcast/noise.py +144 -0
  73. bloqade/squin/stdlib/simple/__init__.py +33 -0
  74. bloqade/squin/stdlib/simple/gate.py +242 -0
  75. bloqade/squin/stdlib/simple/noise.py +126 -0
  76. bloqade/stim/__init__.py +1 -0
  77. bloqade/stim/_wrappers.py +6 -0
  78. bloqade/stim/dialects/noise/emit.py +6 -1
  79. bloqade/stim/dialects/noise/stmts.py +5 -3
  80. bloqade/stim/emit/stim_str.py +2 -0
  81. bloqade/stim/parse/lowering.py +12 -17
  82. bloqade/stim/passes/__init__.py +0 -1
  83. bloqade/stim/passes/flatten.py +26 -0
  84. bloqade/stim/passes/simplify_ifs.py +6 -1
  85. bloqade/stim/passes/squin_to_stim.py +4 -70
  86. bloqade/stim/rewrite/__init__.py +0 -4
  87. bloqade/stim/rewrite/ifs_to_stim.py +23 -29
  88. bloqade/stim/rewrite/qubit_to_stim.py +90 -41
  89. bloqade/stim/rewrite/squin_measure.py +9 -18
  90. bloqade/stim/rewrite/squin_noise.py +132 -108
  91. bloqade/stim/rewrite/util.py +5 -204
  92. bloqade/types.py +10 -0
  93. {bloqade_circuit-0.7.12.dist-info → bloqade_circuit-0.8.0.dist-info}/METADATA +2 -2
  94. {bloqade_circuit-0.7.12.dist-info → bloqade_circuit-0.8.0.dist-info}/RECORD +96 -100
  95. bloqade/native/dialects/gates/__init__.py +0 -3
  96. bloqade/native/dialects/gates/_dialect.py +0 -3
  97. bloqade/pyqrack/squin/op.py +0 -180
  98. bloqade/pyqrack/squin/runtime.py +0 -543
  99. bloqade/pyqrack/squin/wire.py +0 -51
  100. bloqade/squin/_typeinfer.py +0 -20
  101. bloqade/squin/analysis/address_impl.py +0 -71
  102. bloqade/squin/analysis/nsites/__init__.py +0 -9
  103. bloqade/squin/analysis/nsites/analysis.py +0 -50
  104. bloqade/squin/analysis/nsites/impls.py +0 -99
  105. bloqade/squin/analysis/nsites/lattice.py +0 -49
  106. bloqade/squin/cirq/__init__.py +0 -306
  107. bloqade/squin/cirq/emit/emit_circuit.py +0 -129
  108. bloqade/squin/cirq/emit/noise.py +0 -49
  109. bloqade/squin/cirq/emit/op.py +0 -176
  110. bloqade/squin/cirq/emit/qubit.py +0 -58
  111. bloqade/squin/cirq/emit/runtime.py +0 -242
  112. bloqade/squin/cirq/lowering.py +0 -439
  113. bloqade/squin/lowering.py +0 -80
  114. bloqade/squin/noise/_wrapper.py +0 -36
  115. bloqade/squin/noise/rewrite.py +0 -129
  116. bloqade/squin/op/__init__.py +0 -41
  117. bloqade/squin/op/_dialect.py +0 -3
  118. bloqade/squin/op/_wrapper.py +0 -121
  119. bloqade/squin/op/number.py +0 -5
  120. bloqade/squin/op/rewrite.py +0 -46
  121. bloqade/squin/op/stdlib.py +0 -62
  122. bloqade/squin/op/stmts.py +0 -300
  123. bloqade/squin/op/traits.py +0 -43
  124. bloqade/squin/op/types.py +0 -128
  125. bloqade/squin/parallel.py +0 -200
  126. bloqade/squin/qubit.py +0 -194
  127. bloqade/squin/rewrite/canonicalize.py +0 -60
  128. bloqade/squin/rewrite/desugar.py +0 -102
  129. bloqade/squin/stdlib/channel.py +0 -86
  130. bloqade/squin/stdlib/gate.py +0 -201
  131. bloqade/squin/types.py +0 -8
  132. bloqade/squin/wire.py +0 -201
  133. bloqade/stim/rewrite/wire_identity_elimination.py +0 -24
  134. bloqade/stim/rewrite/wire_to_stim.py +0 -57
  135. {bloqade_circuit-0.7.12.dist-info → bloqade_circuit-0.8.0.dist-info}/WHEEL +0 -0
  136. {bloqade_circuit-0.7.12.dist-info → bloqade_circuit-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,180 +0,0 @@
1
- import math
2
-
3
- from kirin import interp
4
-
5
- from bloqade.squin import op
6
- from bloqade.pyqrack.base import PyQrackInterpreter
7
-
8
- from .runtime import (
9
- SnRuntime,
10
- SpRuntime,
11
- U3Runtime,
12
- RotRuntime,
13
- KronRuntime,
14
- MultRuntime,
15
- ResetRuntime,
16
- ScaleRuntime,
17
- AdjointRuntime,
18
- ControlRuntime,
19
- PhaseOpRuntime,
20
- IdentityRuntime,
21
- OperatorRuntime,
22
- ProjectorRuntime,
23
- OperatorRuntimeABC,
24
- PauliStringRuntime,
25
- )
26
-
27
-
28
- @op.dialect.register(key="pyqrack")
29
- class PyQrackMethods(interp.MethodTable):
30
-
31
- @interp.impl(op.stmts.Kron)
32
- def kron(
33
- self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: op.stmts.Kron
34
- ) -> tuple[OperatorRuntimeABC]:
35
- lhs = frame.get(stmt.lhs)
36
- rhs = frame.get(stmt.rhs)
37
- return (KronRuntime(lhs, rhs),)
38
-
39
- @interp.impl(op.stmts.Mult)
40
- def mult(
41
- self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: op.stmts.Mult
42
- ) -> tuple[OperatorRuntimeABC]:
43
- lhs = frame.get(stmt.lhs)
44
- rhs = frame.get(stmt.rhs)
45
- return (MultRuntime(lhs, rhs),)
46
-
47
- @interp.impl(op.stmts.Adjoint)
48
- def adjoint(
49
- self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: op.stmts.Adjoint
50
- ) -> tuple[OperatorRuntimeABC]:
51
- op = frame.get(stmt.op)
52
- return (AdjointRuntime(op),)
53
-
54
- @interp.impl(op.stmts.Scale)
55
- def scale(
56
- self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: op.stmts.Scale
57
- ) -> tuple[OperatorRuntimeABC]:
58
- op = frame.get(stmt.op)
59
- factor = frame.get(stmt.factor)
60
- return (ScaleRuntime(op, factor),)
61
-
62
- @interp.impl(op.stmts.Control)
63
- def control(
64
- self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: op.stmts.Control
65
- ) -> tuple[OperatorRuntimeABC]:
66
- op = frame.get(stmt.op)
67
- n_controls = stmt.n_controls
68
- rt = ControlRuntime(
69
- op=op,
70
- n_controls=n_controls,
71
- )
72
- return (rt,)
73
-
74
- @interp.impl(op.stmts.Rot)
75
- def rot(
76
- self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: op.stmts.Rot
77
- ) -> tuple[OperatorRuntimeABC]:
78
- axis = frame.get(stmt.axis)
79
- angle = frame.get(stmt.angle)
80
- return (RotRuntime(axis, angle),)
81
-
82
- @interp.impl(op.stmts.Identity)
83
- def identity(
84
- self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: op.stmts.Identity
85
- ) -> tuple[OperatorRuntimeABC]:
86
- return (IdentityRuntime(sites=stmt.sites),)
87
-
88
- @interp.impl(op.stmts.PhaseOp)
89
- @interp.impl(op.stmts.ShiftOp)
90
- def phaseop(
91
- self,
92
- interp: PyQrackInterpreter,
93
- frame: interp.Frame,
94
- stmt: op.stmts.PhaseOp | op.stmts.ShiftOp,
95
- ) -> tuple[OperatorRuntimeABC]:
96
- theta = frame.get(stmt.theta)
97
- global_ = isinstance(stmt, op.stmts.PhaseOp)
98
- return (PhaseOpRuntime(theta, global_=global_),)
99
-
100
- @interp.impl(op.stmts.Reset)
101
- @interp.impl(op.stmts.ResetToOne)
102
- def reset(
103
- self,
104
- interp: PyQrackInterpreter,
105
- frame: interp.Frame,
106
- stmt: op.stmts.Reset | op.stmts.ResetToOne,
107
- ) -> tuple[OperatorRuntimeABC]:
108
- target_state = isinstance(stmt, op.stmts.ResetToOne)
109
- return (ResetRuntime(target_state=target_state),)
110
-
111
- @interp.impl(op.stmts.X)
112
- @interp.impl(op.stmts.Y)
113
- @interp.impl(op.stmts.Z)
114
- @interp.impl(op.stmts.H)
115
- @interp.impl(op.stmts.S)
116
- @interp.impl(op.stmts.T)
117
- def operator(
118
- self,
119
- interp: PyQrackInterpreter,
120
- frame: interp.Frame,
121
- stmt: (
122
- op.stmts.X | op.stmts.Y | op.stmts.Z | op.stmts.H | op.stmts.S | op.stmts.T
123
- ),
124
- ) -> tuple[OperatorRuntimeABC]:
125
- return (OperatorRuntime(method_name=stmt.name.lower()),)
126
-
127
- @interp.impl(op.stmts.SqrtX)
128
- @interp.impl(op.stmts.SqrtY)
129
- def sqrt(
130
- self,
131
- interp: PyQrackInterpreter,
132
- frame: interp.Frame,
133
- stmt: op.stmts.SqrtX | op.stmts.SqrtY,
134
- ):
135
- axis_name = "x" if isinstance(stmt, op.stmts.SqrtX) else "y"
136
- axis = OperatorRuntime(method_name=axis_name)
137
- return (RotRuntime(axis=axis, angle=-0.5 * math.pi),)
138
-
139
- @interp.impl(op.stmts.P0)
140
- @interp.impl(op.stmts.P1)
141
- def projector(
142
- self,
143
- interp: PyQrackInterpreter,
144
- frame: interp.Frame,
145
- stmt: op.stmts.P0 | op.stmts.P1,
146
- ) -> tuple[OperatorRuntimeABC]:
147
- state = isinstance(stmt, op.stmts.P1)
148
- return (ProjectorRuntime(to_state=state),)
149
-
150
- @interp.impl(op.stmts.Sp)
151
- def sp(
152
- self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: op.stmts.Sp
153
- ) -> tuple[OperatorRuntimeABC]:
154
- return (SpRuntime(),)
155
-
156
- @interp.impl(op.stmts.Sn)
157
- def sn(
158
- self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: op.stmts.Sn
159
- ) -> tuple[OperatorRuntimeABC]:
160
- return (SnRuntime(),)
161
-
162
- @interp.impl(op.stmts.U3)
163
- def u3(
164
- self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: op.stmts.U3
165
- ) -> tuple[OperatorRuntimeABC]:
166
- theta = frame.get(stmt.theta)
167
- phi = frame.get(stmt.phi)
168
- lam = frame.get(stmt.lam)
169
- return (U3Runtime(theta, phi, lam),)
170
-
171
- @interp.impl(op.stmts.PauliString)
172
- def clifford_string(
173
- self,
174
- interp: PyQrackInterpreter,
175
- frame: interp.Frame,
176
- stmt: op.stmts.PauliString,
177
- ) -> tuple[OperatorRuntimeABC]:
178
- string = stmt.string
179
- ops = [OperatorRuntime(method_name=name.lower()) for name in stmt.string]
180
- return (PauliStringRuntime(string, ops),)
@@ -1,543 +0,0 @@
1
- from typing import Any
2
- from dataclasses import field, dataclass
3
-
4
- import numpy as np
5
- from kirin.dialects import ilist
6
-
7
- from pyqrack.pauli import Pauli
8
- from bloqade.pyqrack import PyQrackQubit
9
-
10
-
11
- @dataclass(frozen=True)
12
- class OperatorRuntimeABC:
13
- """The number of sites the operator applies to (including controls)"""
14
-
15
- @property
16
- def n_sites(self) -> int: ...
17
-
18
- def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
19
- raise NotImplementedError(
20
- "Operator runtime base class should not be called directly, override the method"
21
- )
22
-
23
- def control_apply(
24
- self,
25
- controls: tuple[PyQrackQubit, ...],
26
- targets: tuple[PyQrackQubit, ...],
27
- adjoint: bool = False,
28
- ) -> None:
29
- raise RuntimeError(f"Can't apply controlled version of {self}")
30
-
31
- def broadcast_apply(
32
- self, qubit_lists: list[ilist.IList[PyQrackQubit, Any]], **kwargs
33
- ) -> None:
34
- n = self.n_sites
35
-
36
- if n != len(qubit_lists):
37
- raise RuntimeError(
38
- f"Cannot apply operator of size {n} to {len(qubit_lists)} qubits!"
39
- )
40
-
41
- m = len(qubit_lists[0])
42
- for qubit_list in qubit_lists:
43
- if m != len(qubit_list):
44
- raise RuntimeError(
45
- "Cannot broadcast operator on qubit lists of varying length!"
46
- )
47
-
48
- for qubits in zip(*qubit_lists):
49
- self.apply(*qubits, **kwargs)
50
-
51
-
52
- @dataclass(frozen=True)
53
- class ResetRuntime(OperatorRuntimeABC):
54
- """Reset the qubit to the target state"""
55
-
56
- target_state: bool
57
-
58
- @property
59
- def n_sites(self) -> int:
60
- return 1
61
-
62
- def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
63
- for qubit in qubits:
64
- if not qubit.is_active():
65
- continue
66
-
67
- res: bool = qubit.sim_reg.m(qubit.addr)
68
- if res != self.target_state:
69
- qubit.sim_reg.x(qubit.addr)
70
-
71
-
72
- @dataclass(frozen=True)
73
- class OperatorRuntime(OperatorRuntimeABC):
74
- method_name: str
75
-
76
- @property
77
- def n_sites(self) -> int:
78
- return 1
79
-
80
- def get_method_name(self, adjoint: bool, control: bool) -> str:
81
- method_name = ""
82
- if control:
83
- method_name += "mc"
84
-
85
- if adjoint and self.method_name in ("s", "t"):
86
- method_name += "adj"
87
-
88
- return method_name + self.method_name
89
-
90
- def apply(self, qubit: PyQrackQubit, adjoint: bool = False) -> None:
91
- if not qubit.is_active():
92
- return
93
- method_name = self.get_method_name(adjoint=adjoint, control=False)
94
- getattr(qubit.sim_reg, method_name)(qubit.addr)
95
-
96
- def control_apply(
97
- self,
98
- controls: tuple[PyQrackQubit, ...],
99
- targets: tuple[PyQrackQubit],
100
- adjoint: bool = False,
101
- ) -> None:
102
- target = targets[0]
103
- if not target.is_active():
104
- return
105
-
106
- ctrls: list[int] = []
107
- for qbit in controls:
108
- if not qbit.is_active():
109
- return
110
-
111
- ctrls.append(qbit.addr)
112
-
113
- method_name = self.get_method_name(adjoint=adjoint, control=True)
114
- getattr(target.sim_reg, method_name)(ctrls, target.addr)
115
-
116
-
117
- @dataclass(frozen=True)
118
- class ControlRuntime(OperatorRuntimeABC):
119
- op: OperatorRuntimeABC
120
- n_controls: int
121
-
122
- @property
123
- def n_sites(self) -> int:
124
- return self.op.n_sites + self.n_controls
125
-
126
- def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
127
- ctrls = qubits[: self.n_controls]
128
- targets = qubits[self.n_controls :]
129
-
130
- if len(targets) != self.op.n_sites:
131
- raise RuntimeError(
132
- f"Cannot apply operator {self.op} to {len(targets)} qubits! It applies to {self.op.n_sites}, check your inputs!"
133
- )
134
-
135
- self.op.control_apply(controls=ctrls, targets=targets, adjoint=adjoint)
136
-
137
-
138
- @dataclass(frozen=True)
139
- class ProjectorRuntime(OperatorRuntimeABC):
140
- to_state: bool
141
-
142
- @property
143
- def n_sites(self) -> int:
144
- return 1
145
-
146
- def apply(self, qubit: PyQrackQubit, adjoint: bool = False) -> None:
147
- if not qubit.is_active():
148
- return
149
- qubit.sim_reg.force_m(qubit.addr, self.to_state)
150
-
151
- def control_apply(
152
- self,
153
- controls: tuple[PyQrackQubit, ...],
154
- targets: tuple[PyQrackQubit],
155
- adjoint: bool = False,
156
- ) -> None:
157
- target = targets[0]
158
- if not target.is_active():
159
- return
160
-
161
- ctrls: list[int] = []
162
- for qbit in controls:
163
- if not qbit.is_active():
164
- return
165
-
166
- m = [not self.to_state, 0, 0, self.to_state]
167
- target.sim_reg.mcmtrx(ctrls, m, target.addr)
168
-
169
-
170
- @dataclass(frozen=True)
171
- class IdentityRuntime(OperatorRuntimeABC):
172
- # TODO: do we even need sites? The apply never does anything
173
- sites: int
174
-
175
- @property
176
- def n_sites(self) -> int:
177
- return self.sites
178
-
179
- def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
180
- pass
181
-
182
- def control_apply(
183
- self,
184
- controls: tuple[PyQrackQubit, ...],
185
- targets: tuple[PyQrackQubit, ...],
186
- adjoint: bool = False,
187
- ) -> None:
188
- pass
189
-
190
-
191
- @dataclass(frozen=True)
192
- class MultRuntime(OperatorRuntimeABC):
193
- lhs: OperatorRuntimeABC
194
- rhs: OperatorRuntimeABC
195
-
196
- @property
197
- def n_sites(self) -> int:
198
- if self.lhs.n_sites != self.rhs.n_sites:
199
- raise RuntimeError("Multiplication of operators with unequal size.")
200
-
201
- return self.lhs.n_sites
202
-
203
- def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
204
- if adjoint:
205
- # NOTE: inverted order
206
- self.lhs.apply(*qubits, adjoint=adjoint)
207
- self.rhs.apply(*qubits, adjoint=adjoint)
208
- else:
209
- self.rhs.apply(*qubits)
210
- self.lhs.apply(*qubits)
211
-
212
- def control_apply(
213
- self,
214
- controls: tuple[PyQrackQubit, ...],
215
- targets: tuple[PyQrackQubit, ...],
216
- adjoint: bool = False,
217
- ) -> None:
218
- if adjoint:
219
- self.lhs.control_apply(controls=controls, targets=targets, adjoint=adjoint)
220
- self.rhs.control_apply(controls=controls, targets=targets, adjoint=adjoint)
221
- else:
222
- self.rhs.control_apply(controls=controls, targets=targets, adjoint=adjoint)
223
- self.lhs.control_apply(controls=controls, targets=targets, adjoint=adjoint)
224
-
225
-
226
- @dataclass(frozen=True)
227
- class KronRuntime(OperatorRuntimeABC):
228
- lhs: OperatorRuntimeABC
229
- rhs: OperatorRuntimeABC
230
-
231
- @property
232
- def n_sites(self) -> int:
233
- return self.lhs.n_sites + self.rhs.n_sites
234
-
235
- def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
236
- self.lhs.apply(*qubits[: self.lhs.n_sites], adjoint=adjoint)
237
- self.rhs.apply(*qubits[self.lhs.n_sites :], adjoint=adjoint)
238
-
239
- def control_apply(
240
- self,
241
- controls: tuple[PyQrackQubit, ...],
242
- targets: tuple[PyQrackQubit, ...],
243
- adjoint: bool = False,
244
- ) -> None:
245
- self.lhs.control_apply(
246
- controls=controls,
247
- targets=tuple(targets[: self.lhs.n_sites]),
248
- adjoint=adjoint,
249
- )
250
- self.rhs.control_apply(
251
- controls=controls,
252
- targets=tuple(targets[self.lhs.n_sites :]),
253
- adjoint=adjoint,
254
- )
255
-
256
-
257
- @dataclass(frozen=True)
258
- class ScaleRuntime(OperatorRuntimeABC):
259
- op: OperatorRuntimeABC
260
- factor: complex
261
-
262
- @property
263
- def n_sites(self) -> int:
264
- return self.op.n_sites
265
-
266
- @staticmethod
267
- def mat(factor, adjoint: bool):
268
- if adjoint:
269
- return [np.conj(factor), 0, 0, factor]
270
- else:
271
- return [factor, 0, 0, factor]
272
-
273
- def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
274
- self.op.apply(*qubits, adjoint=adjoint)
275
-
276
- # NOTE: when applying to multiple qubits, we "spread" the factor evenly
277
- applied_factor = self.factor ** (1.0 / len(qubits))
278
- for qbit in qubits:
279
- if not qbit.is_active():
280
- continue
281
-
282
- # NOTE: just factor * eye(2)
283
- m = self.mat(applied_factor, adjoint)
284
-
285
- # TODO: output seems to always be normalized -- no-op?
286
- qbit.sim_reg.mtrx(m, qbit.addr)
287
-
288
- def control_apply(
289
- self,
290
- controls: tuple[PyQrackQubit, ...],
291
- targets: tuple[PyQrackQubit, ...],
292
- adjoint: bool = False,
293
- ) -> None:
294
-
295
- ctrls: list[int] = []
296
- for qbit in controls:
297
- if not qbit.is_active():
298
- return
299
-
300
- ctrls.append(qbit.addr)
301
-
302
- self.op.control_apply(controls=controls, targets=targets, adjoint=adjoint)
303
-
304
- applied_factor = self.factor ** (1.0 / len(targets))
305
- for target in targets:
306
- m = self.mat(applied_factor, adjoint=adjoint)
307
- target.sim_reg.mcmtrx(ctrls, m, target.addr)
308
-
309
-
310
- @dataclass(frozen=True)
311
- class MtrxOpRuntime(OperatorRuntimeABC):
312
- def mat(self, adjoint: bool) -> list[complex]:
313
- raise NotImplementedError("Override this method in the subclass!")
314
-
315
- @property
316
- def n_sites(self) -> int:
317
- # NOTE: pyqrack only supports 2x2 matrices, i.e. single qubit applications
318
- return 1
319
-
320
- def apply(self, target: PyQrackQubit, adjoint: bool = False) -> None:
321
- if not target.is_active():
322
- return
323
-
324
- m = self.mat(adjoint=adjoint)
325
- target.sim_reg.mtrx(m, target.addr)
326
-
327
- def control_apply(
328
- self,
329
- controls: tuple[PyQrackQubit, ...],
330
- targets: tuple[PyQrackQubit, ...],
331
- adjoint: bool = False,
332
- ) -> None:
333
- target = targets[0]
334
- if not target.is_active():
335
- return
336
-
337
- ctrls: list[int] = []
338
- for qbit in controls:
339
- if not qbit.is_active():
340
- return
341
-
342
- ctrls.append(qbit.addr)
343
-
344
- m = self.mat(adjoint=adjoint)
345
- target.sim_reg.mcmtrx(ctrls, m, target.addr)
346
-
347
-
348
- @dataclass(frozen=True)
349
- class SpRuntime(MtrxOpRuntime):
350
- def mat(self, adjoint: bool) -> list[complex]:
351
- if adjoint:
352
- return [0, 0, 0.5, 0]
353
- else:
354
- return [0, 0.5, 0, 0]
355
-
356
-
357
- @dataclass(frozen=True)
358
- class SnRuntime(MtrxOpRuntime):
359
- def mat(self, adjoint: bool) -> list[complex]:
360
- if adjoint:
361
- return [0, 0.5, 0, 0]
362
- else:
363
- return [0, 0, 0.5, 0]
364
-
365
-
366
- @dataclass(frozen=True)
367
- class PhaseOpRuntime(MtrxOpRuntime):
368
- theta: float
369
- global_: bool
370
-
371
- def mat(self, adjoint: bool) -> list[complex]:
372
- sign = (-1) ** (not adjoint)
373
- local_phase = np.exp(sign * 1j * self.theta)
374
-
375
- # NOTE: this is just 1 if we want a local shift
376
- global_phase = np.exp(sign * 1j * self.theta * self.global_)
377
-
378
- return [global_phase, 0, 0, local_phase]
379
-
380
-
381
- @dataclass(frozen=True)
382
- class RotRuntime(OperatorRuntimeABC):
383
- axis: OperatorRuntimeABC
384
- angle: float
385
- pyqrack_axis: Pauli = field(init=False)
386
-
387
- @property
388
- def n_sites(self) -> int:
389
- return 1
390
-
391
- def __post_init__(self):
392
- if not isinstance(self.axis, OperatorRuntime):
393
- raise RuntimeError(
394
- f"Rotation only supported for Pauli operators! Got {self.axis}"
395
- )
396
-
397
- try:
398
- axis = getattr(Pauli, "Pauli" + self.axis.method_name.upper())
399
- except KeyError:
400
- raise RuntimeError(
401
- f"Rotation only supported for Pauli operators! Got {self.axis}"
402
- )
403
-
404
- # NOTE: weird setattr for frozen dataclasses
405
- object.__setattr__(self, "pyqrack_axis", axis)
406
-
407
- def apply(self, target: PyQrackQubit, adjoint: bool = False) -> None:
408
- if not target.is_active():
409
- return
410
-
411
- sign = (-1) ** adjoint
412
- angle = sign * self.angle
413
- target.sim_reg.r(self.pyqrack_axis, angle, target.addr)
414
-
415
- def control_apply(
416
- self,
417
- controls: tuple[PyQrackQubit, ...],
418
- targets: tuple[PyQrackQubit, ...],
419
- adjoint: bool = False,
420
- ) -> None:
421
- target = targets[0]
422
- if not target.is_active():
423
- return
424
-
425
- ctrls: list[int] = []
426
- for qbit in controls:
427
- if not qbit.is_active():
428
- return
429
-
430
- ctrls.append(qbit.addr)
431
-
432
- sign = (-1) ** (not adjoint)
433
- angle = sign * self.angle
434
- target.sim_reg.mcr(self.pyqrack_axis, angle, ctrls, target.addr)
435
-
436
-
437
- @dataclass(frozen=True)
438
- class AdjointRuntime(OperatorRuntimeABC):
439
- op: OperatorRuntimeABC
440
-
441
- @property
442
- def n_sites(self) -> int:
443
- return self.op.n_sites
444
-
445
- def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
446
- # NOTE: to account for adjoint(adjoint(op))
447
- passed_on_adjoint = not adjoint
448
-
449
- self.op.apply(*qubits, adjoint=passed_on_adjoint)
450
-
451
- def control_apply(
452
- self,
453
- controls: tuple[PyQrackQubit, ...],
454
- targets: tuple[PyQrackQubit, ...],
455
- adjoint: bool = False,
456
- ) -> None:
457
- passed_on_adjoint = not adjoint
458
- self.op.control_apply(
459
- controls=controls, targets=targets, adjoint=passed_on_adjoint
460
- )
461
-
462
-
463
- @dataclass(frozen=True)
464
- class U3Runtime(OperatorRuntimeABC):
465
- theta: float
466
- phi: float
467
- lam: float
468
-
469
- @property
470
- def n_sites(self) -> int:
471
- return 1
472
-
473
- def angles(self, adjoint: bool) -> tuple[float, float, float]:
474
- if adjoint:
475
- # NOTE: adjoint(U(theta, phi, lam)) == U(-theta, -lam, -phi)
476
- return -self.theta, -self.lam, -self.phi
477
- else:
478
- return self.theta, self.phi, self.lam
479
-
480
- def apply(self, target: PyQrackQubit, adjoint: bool = False) -> None:
481
- if not target.is_active():
482
- return
483
-
484
- angles = self.angles(adjoint=adjoint)
485
- target.sim_reg.u(target.addr, *angles)
486
-
487
- def control_apply(
488
- self,
489
- controls: tuple[PyQrackQubit, ...],
490
- targets: tuple[PyQrackQubit, ...],
491
- adjoint: bool = False,
492
- ) -> None:
493
- target = targets[0]
494
- if not target.is_active():
495
- return
496
-
497
- ctrls: list[int] = []
498
- for qbit in controls:
499
- if not qbit.is_active():
500
- return
501
-
502
- ctrls.append(qbit.addr)
503
-
504
- angles = self.angles(adjoint=adjoint)
505
- target.sim_reg.mcu(ctrls, target.addr, *angles)
506
-
507
-
508
- @dataclass(frozen=True)
509
- class PauliStringRuntime(OperatorRuntimeABC):
510
- string: str
511
- ops: list[OperatorRuntime]
512
-
513
- @property
514
- def n_sites(self) -> int:
515
- return sum((op.n_sites for op in self.ops))
516
-
517
- def apply(self, *qubits: PyQrackQubit, adjoint: bool = False):
518
- if len(qubits) != self.n_sites:
519
- raise RuntimeError(
520
- f"Cannot apply Pauli string {self.string} to {len(qubits)} qubits! Make sure the number of qubits matches."
521
- )
522
-
523
- qubit_index = 0
524
- for op in self.ops:
525
- next_qubit_index = qubit_index + op.n_sites
526
- op.apply(*qubits[qubit_index:next_qubit_index], adjoint=adjoint)
527
- qubit_index = next_qubit_index
528
-
529
- def control_apply(
530
- self,
531
- controls: tuple[PyQrackQubit, ...],
532
- targets: tuple[PyQrackQubit, ...],
533
- adjoint: bool = False,
534
- ) -> None:
535
- if len(targets) != self.n_sites:
536
- raise RuntimeError(
537
- f"Cannot apply Pauli string {self.string} to {len(targets)} qubits! Make sure the number of qubits matches."
538
- )
539
-
540
- for i, op in enumerate(self.ops):
541
- # NOTE: this is fine as the size of each op is actually just 1 by definition
542
- target = targets[i]
543
- op.control_apply(controls=controls, targets=(target,))