oqubit 0.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.
- oqubit/__init__.py +47 -0
- oqubit/algorithms/__init__.py +5 -0
- oqubit/algorithms/bernstein_vazirani.py +60 -0
- oqubit/algorithms/deutsch.py +37 -0
- oqubit/algorithms/deutsch_joza.py +121 -0
- oqubit/algorithms/deutsch_jozsa.py +121 -0
- oqubit/algorithms/qft.py +87 -0
- oqubit/algorithms/superdense_coding.py +28 -0
- oqubit/circuit/__init__.py +3 -0
- oqubit/circuit/circuit.py +278 -0
- oqubit/circuit/instruction.py +64 -0
- oqubit/core/__init__.py +4 -0
- oqubit/core/operators.py +79 -0
- oqubit/core/qubit.py +68 -0
- oqubit/core/statevector.py +151 -0
- oqubit/gates/__init__.py +5 -0
- oqubit/gates/controlled.py +17 -0
- oqubit/gates/multi.py +33 -0
- oqubit/gates/single.py +17 -0
- oqubit/measurement/__init__.py +3 -0
- oqubit/measurement/measurement.py +124 -0
- oqubit/measurement/sampling.py +46 -0
- oqubit-0.1.dist-info/METADATA +489 -0
- oqubit-0.1.dist-info/RECORD +27 -0
- oqubit-0.1.dist-info/WHEEL +5 -0
- oqubit-0.1.dist-info/licenses/LICENSE +21 -0
- oqubit-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
from ..core.operators import Gate
|
|
2
|
+
from ..core.statevector import StateVector
|
|
3
|
+
from ..core.qubit import Qubit
|
|
4
|
+
from ..measurement import measure, sample
|
|
5
|
+
from .instruction import Instruction
|
|
6
|
+
class Circuit:
|
|
7
|
+
"""
|
|
8
|
+
Represents a quantum circuit.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
num_qubits : int
|
|
13
|
+
Number of qubits in the circuit.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, num_qubits):
|
|
16
|
+
if not isinstance(num_qubits, int):
|
|
17
|
+
raise TypeError("num_qubits must be an integer.")
|
|
18
|
+
if num_qubits < 1:
|
|
19
|
+
raise ValueError("num_qubits must be at least 1.")
|
|
20
|
+
self.num_qubits = num_qubits
|
|
21
|
+
self.instructions = []
|
|
22
|
+
def _validate_qubit(self, qubit):
|
|
23
|
+
if not isinstance(qubit, int):
|
|
24
|
+
raise TypeError("Qubit index must be an integer.")
|
|
25
|
+
if qubit < 0 or qubit >= self.num_qubits:
|
|
26
|
+
raise IndexError(
|
|
27
|
+
f"Qubit index {qubit} is out of range for "
|
|
28
|
+
f"{self.num_qubits} qubits."
|
|
29
|
+
)
|
|
30
|
+
def _validate_qubits(self, qubits):
|
|
31
|
+
for qubit in qubits:
|
|
32
|
+
self._validate_qubit(qubit)
|
|
33
|
+
def _add_instruction(self, gate, controls=None, targets=None):
|
|
34
|
+
instruction = Instruction(
|
|
35
|
+
gate=gate,
|
|
36
|
+
controls=controls,
|
|
37
|
+
targets=targets,
|
|
38
|
+
)
|
|
39
|
+
self._validate_qubits(instruction.qubits)
|
|
40
|
+
self.instructions.append(instruction)
|
|
41
|
+
return self
|
|
42
|
+
def append(self, gate, controls=None, targets=None):
|
|
43
|
+
return self._add_instruction(
|
|
44
|
+
gate,
|
|
45
|
+
controls=controls,
|
|
46
|
+
targets=targets,
|
|
47
|
+
)
|
|
48
|
+
def x(self, qubit):
|
|
49
|
+
from ..gates.single import X
|
|
50
|
+
|
|
51
|
+
return self._add_instruction(
|
|
52
|
+
X,
|
|
53
|
+
targets=qubit,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def y(self, qubit):
|
|
57
|
+
from ..gates.single import Y
|
|
58
|
+
|
|
59
|
+
return self._add_instruction(
|
|
60
|
+
Y,
|
|
61
|
+
targets=qubit,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def z(self, qubit):
|
|
65
|
+
from ..gates.single import Z
|
|
66
|
+
|
|
67
|
+
return self._add_instruction(
|
|
68
|
+
Z,
|
|
69
|
+
targets=qubit,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def h(self, qubit):
|
|
73
|
+
from ..gates.single import H
|
|
74
|
+
|
|
75
|
+
return self._add_instruction(
|
|
76
|
+
H,
|
|
77
|
+
targets=qubit,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def s(self, qubit):
|
|
81
|
+
from ..gates.single import S
|
|
82
|
+
|
|
83
|
+
return self._add_instruction(
|
|
84
|
+
S,
|
|
85
|
+
targets=qubit,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def t(self, qubit):
|
|
89
|
+
from ..gates.single import T
|
|
90
|
+
|
|
91
|
+
return self._add_instruction(
|
|
92
|
+
T,
|
|
93
|
+
targets=qubit,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def sx(self, qubit):
|
|
97
|
+
from ..gates.single import SX
|
|
98
|
+
|
|
99
|
+
return self._add_instruction(
|
|
100
|
+
SX,
|
|
101
|
+
targets=qubit,
|
|
102
|
+
)
|
|
103
|
+
def cx(self, control, target):
|
|
104
|
+
from ..gates.controlled import CX
|
|
105
|
+
|
|
106
|
+
return self._add_instruction(
|
|
107
|
+
CX,
|
|
108
|
+
controls=control,
|
|
109
|
+
targets=target,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def cy(self, control, target):
|
|
113
|
+
from ..gates.controlled import CY
|
|
114
|
+
|
|
115
|
+
return self._add_instruction(
|
|
116
|
+
CY,
|
|
117
|
+
controls=control,
|
|
118
|
+
targets=target,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def cz(self, control, target):
|
|
122
|
+
from ..gates.controlled import CZ
|
|
123
|
+
|
|
124
|
+
return self._add_instruction(
|
|
125
|
+
CZ,
|
|
126
|
+
controls=control,
|
|
127
|
+
targets=target,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def ch(self, control, target):
|
|
131
|
+
from ..gates.controlled import CH
|
|
132
|
+
|
|
133
|
+
return self._add_instruction(
|
|
134
|
+
CH,
|
|
135
|
+
controls=control,
|
|
136
|
+
targets=target,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def cs(self, control, target):
|
|
140
|
+
from ..gates.controlled import CS
|
|
141
|
+
|
|
142
|
+
return self._add_instruction(
|
|
143
|
+
CS,
|
|
144
|
+
controls=control,
|
|
145
|
+
targets=target,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def ct(self, control, target):
|
|
149
|
+
from ..gates.controlled import CT
|
|
150
|
+
|
|
151
|
+
return self._add_instruction(
|
|
152
|
+
CT,
|
|
153
|
+
controls=control,
|
|
154
|
+
targets=target,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def csx(self, control, target):
|
|
158
|
+
from ..gates.controlled import CSX
|
|
159
|
+
|
|
160
|
+
return self._add_instruction(
|
|
161
|
+
CSX,
|
|
162
|
+
controls=control,
|
|
163
|
+
targets=target,
|
|
164
|
+
)
|
|
165
|
+
def swap(self, qubit1, qubit2):
|
|
166
|
+
from ..gates.multi import SWAP
|
|
167
|
+
|
|
168
|
+
return self._add_instruction(
|
|
169
|
+
SWAP,
|
|
170
|
+
targets=[qubit1, qubit2],
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def iswap(self, qubit1, qubit2):
|
|
174
|
+
from ..gates.multi import ISWAP
|
|
175
|
+
|
|
176
|
+
return self._add_instruction(
|
|
177
|
+
ISWAP,
|
|
178
|
+
targets=[qubit1, qubit2],
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def sqrt_swap(self, qubit1, qubit2):
|
|
182
|
+
from ..gates.multi import SQRT_SWAP
|
|
183
|
+
|
|
184
|
+
return self._add_instruction(
|
|
185
|
+
SQRT_SWAP,
|
|
186
|
+
targets=[qubit1, qubit2],
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def ccx(self, control1, control2, target):
|
|
190
|
+
from ..gates.multi import CCX
|
|
191
|
+
|
|
192
|
+
return self._add_instruction(
|
|
193
|
+
CCX,
|
|
194
|
+
controls=[control1, control2],
|
|
195
|
+
targets=target,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def cswap(self, control, target1, target2):
|
|
199
|
+
from ..gates.multi import CSWAP
|
|
200
|
+
|
|
201
|
+
return self._add_instruction(
|
|
202
|
+
CSWAP,
|
|
203
|
+
controls=control,
|
|
204
|
+
targets=[target1, target2],
|
|
205
|
+
)
|
|
206
|
+
def initial_state(self):
|
|
207
|
+
qubits = [
|
|
208
|
+
Qubit.zero()
|
|
209
|
+
for _ in range(self.num_qubits)
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
return StateVector(qubits)
|
|
213
|
+
|
|
214
|
+
def run(self, state=None):
|
|
215
|
+
if state is None:
|
|
216
|
+
state = self.initial_state()
|
|
217
|
+
|
|
218
|
+
if not isinstance(state, StateVector):
|
|
219
|
+
raise TypeError("state must be a StateVector.")
|
|
220
|
+
|
|
221
|
+
if state.num_qubits != self.num_qubits:
|
|
222
|
+
raise ValueError(
|
|
223
|
+
f"Circuit requires {self.num_qubits} qubits, "
|
|
224
|
+
f"but state contains {state.num_qubits}."
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
for instruction in self.instructions:
|
|
228
|
+
state.apply(
|
|
229
|
+
instruction.gate,
|
|
230
|
+
controls=instruction.controls,
|
|
231
|
+
targets=instruction.targets,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
return state
|
|
235
|
+
def measure(self, state=None, qubits=None):
|
|
236
|
+
if state is None:
|
|
237
|
+
state = self.run()
|
|
238
|
+
|
|
239
|
+
return measure(
|
|
240
|
+
state,
|
|
241
|
+
qubits=qubits,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def sample(self, shots=1024, state=None, qubits=None):
|
|
245
|
+
if state is None:
|
|
246
|
+
state = self.run()
|
|
247
|
+
|
|
248
|
+
return sample(
|
|
249
|
+
state,
|
|
250
|
+
shots=shots,
|
|
251
|
+
qubits=qubits,
|
|
252
|
+
)
|
|
253
|
+
@property
|
|
254
|
+
def depth(self):
|
|
255
|
+
return len(self.instructions)
|
|
256
|
+
@property
|
|
257
|
+
def notation(self):
|
|
258
|
+
return self.run().notation
|
|
259
|
+
@property
|
|
260
|
+
def size(self):
|
|
261
|
+
return len(self.instructions)
|
|
262
|
+
def clear(self):
|
|
263
|
+
self.instructions.clear()
|
|
264
|
+
return self
|
|
265
|
+
def __repr__(self):
|
|
266
|
+
return (
|
|
267
|
+
f"Circuit("
|
|
268
|
+
f"num_qubits={self.num_qubits}, "
|
|
269
|
+
f"instructions={len(self.instructions)})"
|
|
270
|
+
)
|
|
271
|
+
def __len__(self):
|
|
272
|
+
return len(self.instructions)
|
|
273
|
+
def __str__(self):
|
|
274
|
+
return "\n".join(
|
|
275
|
+
str(instruction)
|
|
276
|
+
for instruction in self.instructions
|
|
277
|
+
)
|
|
278
|
+
__all__ = ["Circuit"]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from ..core.operators import Gate
|
|
2
|
+
class Instruction:
|
|
3
|
+
"""
|
|
4
|
+
Represents a single gate operation in a quantum circuit.
|
|
5
|
+
|
|
6
|
+
Parameters
|
|
7
|
+
----------
|
|
8
|
+
gate : Gate
|
|
9
|
+
Gate to apply.
|
|
10
|
+
controls : int | list[int] | None
|
|
11
|
+
Control qubit index or indices.
|
|
12
|
+
targets : int | list[int] | None
|
|
13
|
+
Target qubit index or indices.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, gate, controls=None, targets=None):
|
|
17
|
+
if not isinstance(gate, Gate):
|
|
18
|
+
raise TypeError("gate must be a Gate object.")
|
|
19
|
+
|
|
20
|
+
self.gate = gate
|
|
21
|
+
self.controls = self._normalize_qubits(controls)
|
|
22
|
+
self.targets = self._normalize_qubits(targets)
|
|
23
|
+
|
|
24
|
+
if len(self.controls) != gate.num_controls:
|
|
25
|
+
raise ValueError(
|
|
26
|
+
f"{gate.name} requires {gate.num_controls} control qubit(s), "
|
|
27
|
+
f"got {len(self.controls)}."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if len(self.targets) != gate.num_targets:
|
|
31
|
+
raise ValueError(
|
|
32
|
+
f"{gate.name} requires {gate.num_targets} target qubit(s), "
|
|
33
|
+
f"got {len(self.targets)}."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
qubits = self.controls + self.targets
|
|
37
|
+
|
|
38
|
+
if len(set(qubits)) != len(qubits):
|
|
39
|
+
raise ValueError(
|
|
40
|
+
"A qubit cannot be used more than once in the same instruction."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def _normalize_qubits(qubits):
|
|
45
|
+
if qubits is None:
|
|
46
|
+
return []
|
|
47
|
+
|
|
48
|
+
if isinstance(qubits, int):
|
|
49
|
+
return [qubits]
|
|
50
|
+
|
|
51
|
+
return list(qubits)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def qubits(self):
|
|
55
|
+
return self.controls + self.targets
|
|
56
|
+
|
|
57
|
+
def __repr__(self):
|
|
58
|
+
return (
|
|
59
|
+
f"Instruction("
|
|
60
|
+
f"gate={self.gate.name!r}, "
|
|
61
|
+
f"controls={self.controls}, "
|
|
62
|
+
f"targets={self.targets})"
|
|
63
|
+
)
|
|
64
|
+
__all__ = ["Instruction"]
|
oqubit/core/__init__.py
ADDED
oqubit/core/operators.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core operator definitions for OQubit.
|
|
3
|
+
|
|
4
|
+
This module provides the base Operator class for linear operators
|
|
5
|
+
and the Gate class for quantum gates.
|
|
6
|
+
"""
|
|
7
|
+
import numpy as np
|
|
8
|
+
class Operator:
|
|
9
|
+
"""
|
|
10
|
+
Represents a linear operator acting on a quantum state.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
matrix : array-like
|
|
15
|
+
Square matrix representing the operator.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, matrix):
|
|
19
|
+
self.matrix = np.asarray(matrix, dtype=complex)
|
|
20
|
+
if self.matrix.ndim != 2:
|
|
21
|
+
raise ValueError("Operator matrix must be two-dimensional.")
|
|
22
|
+
if self.matrix.shape[0] != self.matrix.shape[1]:
|
|
23
|
+
raise ValueError("Operator matrix must be square.")
|
|
24
|
+
@property
|
|
25
|
+
def dimension(self):
|
|
26
|
+
"""Return the dimension of the operator."""
|
|
27
|
+
return self.matrix.shape[0]
|
|
28
|
+
def __matmul__(self, other):
|
|
29
|
+
"""Apply the operator using the @ operator."""
|
|
30
|
+
return self.matrix @ other
|
|
31
|
+
def __repr__(self):
|
|
32
|
+
return (f"Operator("f"dimension={self.dimension})")
|
|
33
|
+
class Gate(Operator):
|
|
34
|
+
"""
|
|
35
|
+
Represents a quantum gate.
|
|
36
|
+
|
|
37
|
+
A Gate is a specialized Operator that also describes
|
|
38
|
+
how many qubits it acts on and its control/target structure.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
matrix : array-like
|
|
43
|
+
Square matrix representing the quantum gate.
|
|
44
|
+
|
|
45
|
+
name : str
|
|
46
|
+
Name of the gate.
|
|
47
|
+
|
|
48
|
+
num_qubits : int
|
|
49
|
+
Number of qubits the gate acts on.
|
|
50
|
+
|
|
51
|
+
num_controls : int, default=0
|
|
52
|
+
Number of control qubits.
|
|
53
|
+
|
|
54
|
+
num_targets : int, default=0
|
|
55
|
+
Number of target qubits.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self,matrix,name,num_qubits,num_controls=0,num_targets=0,):
|
|
59
|
+
super().__init__(matrix)
|
|
60
|
+
if not isinstance(name, str):
|
|
61
|
+
raise TypeError("Gate name must be a string.")
|
|
62
|
+
if not isinstance(num_qubits, int) or num_qubits < 1:
|
|
63
|
+
raise ValueError("num_qubits must be a positive integer.")
|
|
64
|
+
if not isinstance(num_controls, int) or num_controls < 0:
|
|
65
|
+
raise ValueError("num_controls must be a non-negative integer.")
|
|
66
|
+
if not isinstance(num_targets, int) or num_targets < 0:
|
|
67
|
+
raise ValueError("num_targets must be a non-negative integer.")
|
|
68
|
+
if num_controls + num_targets != num_qubits:
|
|
69
|
+
raise ValueError("num_controls + num_targets must equal num_qubits.")
|
|
70
|
+
expected_dimension = 2 ** num_qubits
|
|
71
|
+
if self.dimension != expected_dimension:
|
|
72
|
+
raise ValueError(f"{name} is a {num_qubits}-qubit gate, "f"so its matrix must be "f"{expected_dimension}x{expected_dimension}.")
|
|
73
|
+
self.name = name
|
|
74
|
+
self.num_qubits = num_qubits
|
|
75
|
+
self.num_controls = num_controls
|
|
76
|
+
self.num_targets = num_targets
|
|
77
|
+
def __repr__(self):
|
|
78
|
+
return (f"Gate("f"name='{self.name}', "f"num_qubits={self.num_qubits}, "f"num_controls={self.num_controls}, "f"num_targets={self.num_targets})")
|
|
79
|
+
__all__ = ["Operator","Gate",]
|
oqubit/core/qubit.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
class Qubit():
|
|
3
|
+
"""
|
|
4
|
+
Qubit represents a single-qubit quantum state of the form
|
|
5
|
+
|
|
6
|
+
|ψ⟩ = α|0⟩ + β|1⟩
|
|
7
|
+
|
|
8
|
+
where alpha and beta are the probability amplitudes.
|
|
9
|
+
The normalize parameter determines whether the state is
|
|
10
|
+
normalized during initialization.
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self,alpha=1,beta=0,normalize=True):
|
|
13
|
+
self.alpha=complex(alpha)
|
|
14
|
+
self.beta=complex(beta)
|
|
15
|
+
self.normalize=normalize
|
|
16
|
+
if (normalize==True):
|
|
17
|
+
self._normalize()
|
|
18
|
+
def _normalize(self):
|
|
19
|
+
norm = np.sqrt(abs(self.alpha) ** 2 +abs(self.beta) ** 2)
|
|
20
|
+
if np.isclose(norm, 0):
|
|
21
|
+
raise ValueError("Cannot normalize zero state.")
|
|
22
|
+
self.alpha /= norm
|
|
23
|
+
self.beta /= norm
|
|
24
|
+
def apply(self, gate):
|
|
25
|
+
if gate.num_qubits!=1:
|
|
26
|
+
raise ValueError(f"{gate.name} requires {gate.num_qubits} qubits. \n""Use StateVector.apply() for multi-qubit gates.")
|
|
27
|
+
result = gate @ self.state
|
|
28
|
+
self.alpha = result[0]
|
|
29
|
+
self.beta = result[1]
|
|
30
|
+
return self
|
|
31
|
+
@classmethod
|
|
32
|
+
def i(cls):
|
|
33
|
+
return cls(1 / np.sqrt(2), 1j / np.sqrt(2))
|
|
34
|
+
@classmethod
|
|
35
|
+
def minus_i(cls):
|
|
36
|
+
return cls(1 / np.sqrt(2), -1j / np.sqrt(2))
|
|
37
|
+
@classmethod
|
|
38
|
+
def plus(cls):
|
|
39
|
+
return cls(1/np.sqrt(2), 1/np.sqrt(2))
|
|
40
|
+
@classmethod
|
|
41
|
+
def minus(cls):
|
|
42
|
+
return cls(1/np.sqrt(2),-1/np.sqrt(2))
|
|
43
|
+
@classmethod
|
|
44
|
+
def zero(cls):
|
|
45
|
+
return cls(1,0)
|
|
46
|
+
@classmethod
|
|
47
|
+
def one(cls):
|
|
48
|
+
return cls(0,1)
|
|
49
|
+
@property
|
|
50
|
+
def state(self):
|
|
51
|
+
return np.array([self.alpha,self.beta],dtype=complex)
|
|
52
|
+
@property
|
|
53
|
+
def prob_0(self):
|
|
54
|
+
norm = np.sqrt(abs(self.alpha) ** 2 +abs(self.beta) ** 2)
|
|
55
|
+
if np.isclose(norm, 0):
|
|
56
|
+
raise ValueError("Cannot normalize zero state.")
|
|
57
|
+
alpha=abs(self.alpha)/norm
|
|
58
|
+
return (alpha)**2
|
|
59
|
+
@property
|
|
60
|
+
def prob_1(self):
|
|
61
|
+
norm = np.sqrt(abs(self.alpha) ** 2 +abs(self.beta) ** 2)
|
|
62
|
+
if np.isclose(norm, 0):
|
|
63
|
+
raise ValueError("Cannot normalize zero state.")
|
|
64
|
+
beta=abs(self.beta)/norm
|
|
65
|
+
return (beta)**2
|
|
66
|
+
def __repr__(self):
|
|
67
|
+
return f"Qubit:\t{self.alpha} |0\u27E9 \t {self.beta}|1\u27E9"
|
|
68
|
+
__all__=["Qubit"]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from .qubit import Qubit
|
|
3
|
+
from .operators import Gate
|
|
4
|
+
class StateVector:
|
|
5
|
+
"""
|
|
6
|
+
Represents the quantum state of an n-qubit system.
|
|
7
|
+
|
|
8
|
+
A state vector is a complex vector containing the probability
|
|
9
|
+
amplitudes of all computational basis states.
|
|
10
|
+
|
|
11
|
+
For example, a two-qubit state can be written as:
|
|
12
|
+
|
|
13
|
+
|ψ⟩ = α|00⟩ + β|01⟩ + γ|10⟩ + δ|11⟩
|
|
14
|
+
|
|
15
|
+
The amplitudes satisfy:
|
|
16
|
+
|
|
17
|
+
|α|² + |β|² + |γ|² + |δ|² = 1
|
|
18
|
+
|
|
19
|
+
for a normalized state.
|
|
20
|
+
|
|
21
|
+
Qubit ordering follows the order supplied to StateVector:
|
|
22
|
+
|
|
23
|
+
StateVector([q0, q1, q2])
|
|
24
|
+
|
|
25
|
+
corresponds to:
|
|
26
|
+
|
|
27
|
+
|q0 q1 q2⟩
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, qubits):
|
|
31
|
+
self.qubits = list(qubits)
|
|
32
|
+
if not self.qubits:
|
|
33
|
+
raise ValueError("StateVector requires at least one qubit.")
|
|
34
|
+
if not all(isinstance(q, Qubit) for q in self.qubits):
|
|
35
|
+
raise TypeError("All elements must be Qubit objects.")
|
|
36
|
+
self._state = self._build_state()
|
|
37
|
+
def _build_state(self):
|
|
38
|
+
state = self.qubits[0].state
|
|
39
|
+
for qubit in self.qubits[1:]:
|
|
40
|
+
state = np.kron(state, qubit.state)
|
|
41
|
+
return state
|
|
42
|
+
def apply(self, gate, controls=None, targets=None):
|
|
43
|
+
if not isinstance(gate, Gate):
|
|
44
|
+
raise TypeError("gate must be a Gate object.")
|
|
45
|
+
if gate.num_qubits > self.num_qubits:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"{gate.name} requires {gate.num_qubits} qubits, "
|
|
48
|
+
f"but the state contains only "
|
|
49
|
+
f"{self.num_qubits} qubits."
|
|
50
|
+
)
|
|
51
|
+
if controls is None:
|
|
52
|
+
controls = []
|
|
53
|
+
elif isinstance(controls, int):
|
|
54
|
+
controls = [controls]
|
|
55
|
+
else:
|
|
56
|
+
controls = list(controls)
|
|
57
|
+
if targets is None:
|
|
58
|
+
targets = []
|
|
59
|
+
elif isinstance(targets, int):
|
|
60
|
+
targets = [targets]
|
|
61
|
+
else:
|
|
62
|
+
targets = list(targets)
|
|
63
|
+
if len(controls) != gate.num_controls:
|
|
64
|
+
raise ValueError(f"{gate.name} requires "f"{gate.num_controls} control qubits, "f"but {len(controls)} were provided.")
|
|
65
|
+
if len(targets) != gate.num_targets:
|
|
66
|
+
raise ValueError(f"{gate.name} requires "f"{gate.num_targets} target qubits, "f"but {len(targets)} were provided.")
|
|
67
|
+
qubits = controls + targets
|
|
68
|
+
if len(qubits) != gate.num_qubits:
|
|
69
|
+
raise ValueError(f"{gate.name} requires "f"{gate.num_qubits} qubits.")
|
|
70
|
+
for index in qubits:
|
|
71
|
+
if not isinstance(index, int):
|
|
72
|
+
raise TypeError("Qubit indices must be integers.")
|
|
73
|
+
if index < 0 or index >= self.num_qubits:
|
|
74
|
+
raise IndexError(f"Qubit index {index} is out of range.")
|
|
75
|
+
if len(set(qubits)) != len(qubits):
|
|
76
|
+
raise ValueError("A qubit cannot be used more than once in the same gate.")
|
|
77
|
+
if gate.num_qubits == 1:
|
|
78
|
+
self._apply_single_qubit_gate(gate,targets[0])
|
|
79
|
+
return self
|
|
80
|
+
self._apply_multi_qubit_gate(gate,qubits)
|
|
81
|
+
return self
|
|
82
|
+
def _apply_single_qubit_gate(self, gate, target):
|
|
83
|
+
matrix = gate.matrix
|
|
84
|
+
step = 2 ** (self.num_qubits - target - 1)
|
|
85
|
+
block = step * 2
|
|
86
|
+
old_state = self._state.copy()
|
|
87
|
+
for start in range(0,len(old_state),block):
|
|
88
|
+
for offset in range(step):
|
|
89
|
+
i0 = start + offset
|
|
90
|
+
i1 = i0 + step
|
|
91
|
+
a = old_state[i0]
|
|
92
|
+
b = old_state[i1]
|
|
93
|
+
self._state[i0] = (matrix[0, 0] * a+ matrix[0, 1] * b)
|
|
94
|
+
self._state[i1] = (matrix[1, 0] * a+ matrix[1, 1] * b)
|
|
95
|
+
def _apply_multi_qubit_gate(self, gate, qubits):
|
|
96
|
+
n = self.num_qubits
|
|
97
|
+
k = gate.num_qubits
|
|
98
|
+
matrix = gate.matrix
|
|
99
|
+
old_state = self._state.copy()
|
|
100
|
+
new_state = np.zeros_like(old_state)
|
|
101
|
+
bit_positions = [n - qubit - 1
|
|
102
|
+
for qubit in qubits
|
|
103
|
+
]
|
|
104
|
+
untouched_positions = [position
|
|
105
|
+
for position in range(n)
|
|
106
|
+
if position not in bit_positions
|
|
107
|
+
]
|
|
108
|
+
for base in range(2 ** len(untouched_positions)):
|
|
109
|
+
base_index = 0
|
|
110
|
+
for i, position in enumerate(untouched_positions):
|
|
111
|
+
bit = (base >> i) & 1
|
|
112
|
+
base_index |= (bit << position)
|
|
113
|
+
indices = []
|
|
114
|
+
for local_index in range(2 ** k):
|
|
115
|
+
index = base_index
|
|
116
|
+
for j, position in enumerate(bit_positions):
|
|
117
|
+
bit = (local_index>> (k - j - 1)) & 1
|
|
118
|
+
index |= (bit << position)
|
|
119
|
+
indices.append(index)
|
|
120
|
+
amplitudes = old_state[indices]
|
|
121
|
+
transformed = (matrix @ amplitudes)
|
|
122
|
+
for i, index in enumerate(indices):
|
|
123
|
+
new_state[index] = (transformed[i])
|
|
124
|
+
self._state = new_state
|
|
125
|
+
@property
|
|
126
|
+
def state(self):
|
|
127
|
+
return self._state
|
|
128
|
+
@property
|
|
129
|
+
def num_qubits(self):
|
|
130
|
+
return len(self.qubits)
|
|
131
|
+
@property
|
|
132
|
+
def dimension(self):
|
|
133
|
+
return 2 ** self.num_qubits
|
|
134
|
+
def normalize(self):
|
|
135
|
+
norm = np.linalg.norm(self._state)
|
|
136
|
+
if np.isclose(norm, 0):
|
|
137
|
+
raise ValueError("Cannot normalize zero state.")
|
|
138
|
+
self._state = (self._state / norm)
|
|
139
|
+
return self
|
|
140
|
+
@property
|
|
141
|
+
def notation(self):
|
|
142
|
+
terms = []
|
|
143
|
+
for index, amplitude in enumerate(self._state):
|
|
144
|
+
if np.isclose(amplitude,0):
|
|
145
|
+
continue
|
|
146
|
+
basis = format(index,f"0{self.num_qubits}b")
|
|
147
|
+
terms.append(f"{amplitude}|{basis}⟩")
|
|
148
|
+
return " + ".join(terms)
|
|
149
|
+
def __repr__(self):
|
|
150
|
+
return (f"StateVector("f"num_qubits={self.num_qubits}, "f"dimension={self.dimension})")
|
|
151
|
+
__all__ = ["StateVector"]
|
oqubit/gates/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from ..core.operators import Operator,Gate
|
|
2
|
+
from .single import X,Y,Z,H,S,T,SX
|
|
3
|
+
from .controlled import CX,CY,CZ,CH,CS,CSX,CT
|
|
4
|
+
from .multi import SWAP,ISWAP,SQRT_SWAP,CCX,CSWAP
|
|
5
|
+
__all__=["X","Y","Z","H","S","T","SX","CX","CY","CZ","CH","CS","CSX","CT","SWAP","ISWAP","SQRT_SWAP","CCX","CSWAP"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ..core.operators import Gate,Operator
|
|
2
|
+
import numpy as np
|
|
3
|
+
"""
|
|
4
|
+
These are Qubit gates which are cotrolled and requires atleast one Qubit control state to change the state of the other Qubit.
|
|
5
|
+
|
|
6
|
+
Qubit gates such as CX (also known as CNOT), CY ,CZ , CH, CS, CT, CSX Gates are present.
|
|
7
|
+
|
|
8
|
+
I hope you will enjoy learning creating new logical interpretation by using various series of Quantum Gates to Encode and Decode Informations.
|
|
9
|
+
"""
|
|
10
|
+
CX = Gate([[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 0, 1],[0, 0, 1, 0],],"CX",num_qubits=2,num_controls=1,num_targets=1,)
|
|
11
|
+
CY = Gate([[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 0, -1j],[0, 0, 1j, 0],],"CY",num_qubits=2,num_controls=1,num_targets=1,)
|
|
12
|
+
CZ = Gate([[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 1, 0],[0, 0, 0, -1],],"CZ",num_qubits=2,num_controls=1,num_targets=1,)
|
|
13
|
+
CH = Gate([[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 1 / np.sqrt(2), 1 / np.sqrt(2)],[0, 0, 1 / np.sqrt(2), -1 / np.sqrt(2)],],"CH",num_qubits=2,num_controls=1,num_targets=1,)
|
|
14
|
+
CS = Gate([[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 1, 0],[0, 0, 0, 1j],],"CS",num_qubits=2,num_controls=1,num_targets=1,)
|
|
15
|
+
CT = Gate([[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 1, 0],[0, 0, 0, np.exp(1j * np.pi / 4)],],"CT",num_qubits=2,num_controls=1,num_targets=1,)
|
|
16
|
+
CSX = Gate([[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, (1 + 1j) / 2, (1 - 1j) / 2],[0, 0, (1 - 1j) / 2, (1 + 1j) / 2],],"CSX",num_qubits=2,num_controls=1,num_targets=1,)
|
|
17
|
+
__all__ = ["CX","CY","CZ","CH","CS","CT","CSX"]
|