superquantx 0.1.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.
- superquantx/__init__.py +321 -0
- superquantx/algorithms/__init__.py +55 -0
- superquantx/algorithms/base_algorithm.py +413 -0
- superquantx/algorithms/hybrid_classifier.py +628 -0
- superquantx/algorithms/qaoa.py +406 -0
- superquantx/algorithms/quantum_agents.py +1006 -0
- superquantx/algorithms/quantum_kmeans.py +575 -0
- superquantx/algorithms/quantum_nn.py +544 -0
- superquantx/algorithms/quantum_pca.py +499 -0
- superquantx/algorithms/quantum_svm.py +346 -0
- superquantx/algorithms/vqe.py +553 -0
- superquantx/algorithms.py +863 -0
- superquantx/backends/__init__.py +265 -0
- superquantx/backends/base_backend.py +321 -0
- superquantx/backends/braket_backend.py +420 -0
- superquantx/backends/cirq_backend.py +466 -0
- superquantx/backends/ocean_backend.py +491 -0
- superquantx/backends/pennylane_backend.py +419 -0
- superquantx/backends/qiskit_backend.py +451 -0
- superquantx/backends/simulator_backend.py +455 -0
- superquantx/backends/tket_backend.py +519 -0
- superquantx/circuits.py +447 -0
- superquantx/cli/__init__.py +28 -0
- superquantx/cli/commands.py +528 -0
- superquantx/cli/main.py +254 -0
- superquantx/client.py +298 -0
- superquantx/config.py +326 -0
- superquantx/exceptions.py +287 -0
- superquantx/gates.py +588 -0
- superquantx/logging_config.py +347 -0
- superquantx/measurements.py +702 -0
- superquantx/ml.py +936 -0
- superquantx/noise.py +760 -0
- superquantx/utils/__init__.py +83 -0
- superquantx/utils/benchmarking.py +523 -0
- superquantx/utils/classical_utils.py +575 -0
- superquantx/utils/feature_mapping.py +467 -0
- superquantx/utils/optimization.py +410 -0
- superquantx/utils/quantum_utils.py +456 -0
- superquantx/utils/visualization.py +654 -0
- superquantx/version.py +33 -0
- superquantx-0.1.0.dist-info/METADATA +365 -0
- superquantx-0.1.0.dist-info/RECORD +46 -0
- superquantx-0.1.0.dist-info/WHEEL +4 -0
- superquantx-0.1.0.dist-info/entry_points.txt +2 -0
- superquantx-0.1.0.dist-info/licenses/LICENSE +21 -0
superquantx/gates.py
ADDED
@@ -0,0 +1,588 @@
|
|
1
|
+
"""Advanced quantum gate implementations and utilities for SuperQuantX
|
2
|
+
"""
|
3
|
+
|
4
|
+
import math
|
5
|
+
from typing import Dict, List, Optional, Tuple, Union
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
from scipy.linalg import expm
|
9
|
+
|
10
|
+
|
11
|
+
class GateMatrix:
|
12
|
+
"""Quantum gate matrix representations and operations
|
13
|
+
"""
|
14
|
+
|
15
|
+
# Pauli matrices
|
16
|
+
I = np.array([[1, 0], [0, 1]], dtype=complex)
|
17
|
+
X = np.array([[0, 1], [1, 0]], dtype=complex)
|
18
|
+
Y = np.array([[0, -1j], [1j, 0]], dtype=complex)
|
19
|
+
Z = np.array([[1, 0], [0, -1]], dtype=complex)
|
20
|
+
|
21
|
+
# Common single-qubit gates
|
22
|
+
H = np.array([[1, 1], [1, -1]], dtype=complex) / math.sqrt(2)
|
23
|
+
S = np.array([[1, 0], [0, 1j]], dtype=complex)
|
24
|
+
T = np.array([[1, 0], [0, np.exp(1j * math.pi / 4)]], dtype=complex)
|
25
|
+
|
26
|
+
@staticmethod
|
27
|
+
def rx(theta: float) -> np.ndarray:
|
28
|
+
"""Rotation around X-axis"""
|
29
|
+
c = math.cos(theta / 2)
|
30
|
+
s = math.sin(theta / 2)
|
31
|
+
return np.array([[c, -1j * s], [-1j * s, c]], dtype=complex)
|
32
|
+
|
33
|
+
@staticmethod
|
34
|
+
def ry(theta: float) -> np.ndarray:
|
35
|
+
"""Rotation around Y-axis"""
|
36
|
+
c = math.cos(theta / 2)
|
37
|
+
s = math.sin(theta / 2)
|
38
|
+
return np.array([[c, -s], [s, c]], dtype=complex)
|
39
|
+
|
40
|
+
@staticmethod
|
41
|
+
def rz(theta: float) -> np.ndarray:
|
42
|
+
"""Rotation around Z-axis"""
|
43
|
+
return np.array([
|
44
|
+
[np.exp(-1j * theta / 2), 0],
|
45
|
+
[0, np.exp(1j * theta / 2)]
|
46
|
+
], dtype=complex)
|
47
|
+
|
48
|
+
@staticmethod
|
49
|
+
def u(theta: float, phi: float, lam: float) -> np.ndarray:
|
50
|
+
"""General single-qubit unitary gate U(θ,φ,λ)"""
|
51
|
+
return np.array([
|
52
|
+
[math.cos(theta / 2), -np.exp(1j * lam) * math.sin(theta / 2)],
|
53
|
+
[np.exp(1j * phi) * math.sin(theta / 2),
|
54
|
+
np.exp(1j * (phi + lam)) * math.cos(theta / 2)]
|
55
|
+
], dtype=complex)
|
56
|
+
|
57
|
+
@staticmethod
|
58
|
+
def phase(phi: float) -> np.ndarray:
|
59
|
+
"""Phase gate"""
|
60
|
+
return np.array([[1, 0], [0, np.exp(1j * phi)]], dtype=complex)
|
61
|
+
|
62
|
+
# Two-qubit gates
|
63
|
+
@staticmethod
|
64
|
+
def cnot() -> np.ndarray:
|
65
|
+
"""CNOT gate matrix"""
|
66
|
+
return np.array([
|
67
|
+
[1, 0, 0, 0],
|
68
|
+
[0, 1, 0, 0],
|
69
|
+
[0, 0, 0, 1],
|
70
|
+
[0, 0, 1, 0]
|
71
|
+
], dtype=complex)
|
72
|
+
|
73
|
+
@staticmethod
|
74
|
+
def cz() -> np.ndarray:
|
75
|
+
"""Controlled-Z gate matrix"""
|
76
|
+
return np.array([
|
77
|
+
[1, 0, 0, 0],
|
78
|
+
[0, 1, 0, 0],
|
79
|
+
[0, 0, 1, 0],
|
80
|
+
[0, 0, 0, -1]
|
81
|
+
], dtype=complex)
|
82
|
+
|
83
|
+
@staticmethod
|
84
|
+
def swap() -> np.ndarray:
|
85
|
+
"""SWAP gate matrix"""
|
86
|
+
return np.array([
|
87
|
+
[1, 0, 0, 0],
|
88
|
+
[0, 0, 1, 0],
|
89
|
+
[0, 1, 0, 0],
|
90
|
+
[0, 0, 0, 1]
|
91
|
+
], dtype=complex)
|
92
|
+
|
93
|
+
@staticmethod
|
94
|
+
def iswap() -> np.ndarray:
|
95
|
+
"""ISWAP gate matrix"""
|
96
|
+
return np.array([
|
97
|
+
[1, 0, 0, 0],
|
98
|
+
[0, 0, 1j, 0],
|
99
|
+
[0, 1j, 0, 0],
|
100
|
+
[0, 0, 0, 1]
|
101
|
+
], dtype=complex)
|
102
|
+
|
103
|
+
@staticmethod
|
104
|
+
def crx(theta: float) -> np.ndarray:
|
105
|
+
"""Controlled rotation around X-axis"""
|
106
|
+
c = math.cos(theta / 2)
|
107
|
+
s = math.sin(theta / 2)
|
108
|
+
return np.array([
|
109
|
+
[1, 0, 0, 0],
|
110
|
+
[0, 1, 0, 0],
|
111
|
+
[0, 0, c, -1j * s],
|
112
|
+
[0, 0, -1j * s, c]
|
113
|
+
], dtype=complex)
|
114
|
+
|
115
|
+
@staticmethod
|
116
|
+
def cry(theta: float) -> np.ndarray:
|
117
|
+
"""Controlled rotation around Y-axis"""
|
118
|
+
c = math.cos(theta / 2)
|
119
|
+
s = math.sin(theta / 2)
|
120
|
+
return np.array([
|
121
|
+
[1, 0, 0, 0],
|
122
|
+
[0, 1, 0, 0],
|
123
|
+
[0, 0, c, -s],
|
124
|
+
[0, 0, s, c]
|
125
|
+
], dtype=complex)
|
126
|
+
|
127
|
+
@staticmethod
|
128
|
+
def crz(theta: float) -> np.ndarray:
|
129
|
+
"""Controlled rotation around Z-axis"""
|
130
|
+
return np.array([
|
131
|
+
[1, 0, 0, 0],
|
132
|
+
[0, 1, 0, 0],
|
133
|
+
[0, 0, np.exp(-1j * theta / 2), 0],
|
134
|
+
[0, 0, 0, np.exp(1j * theta / 2)]
|
135
|
+
], dtype=complex)
|
136
|
+
|
137
|
+
@staticmethod
|
138
|
+
def xx(theta: float) -> np.ndarray:
|
139
|
+
"""XX interaction gate"""
|
140
|
+
c = math.cos(theta / 2)
|
141
|
+
s = math.sin(theta / 2)
|
142
|
+
return np.array([
|
143
|
+
[c, 0, 0, -1j * s],
|
144
|
+
[0, c, -1j * s, 0],
|
145
|
+
[0, -1j * s, c, 0],
|
146
|
+
[-1j * s, 0, 0, c]
|
147
|
+
], dtype=complex)
|
148
|
+
|
149
|
+
@staticmethod
|
150
|
+
def yy(theta: float) -> np.ndarray:
|
151
|
+
"""YY interaction gate"""
|
152
|
+
c = math.cos(theta / 2)
|
153
|
+
s = math.sin(theta / 2)
|
154
|
+
return np.array([
|
155
|
+
[c, 0, 0, 1j * s],
|
156
|
+
[0, c, -1j * s, 0],
|
157
|
+
[0, -1j * s, c, 0],
|
158
|
+
[1j * s, 0, 0, c]
|
159
|
+
], dtype=complex)
|
160
|
+
|
161
|
+
@staticmethod
|
162
|
+
def zz(theta: float) -> np.ndarray:
|
163
|
+
"""ZZ interaction gate"""
|
164
|
+
return np.array([
|
165
|
+
[np.exp(-1j * theta / 2), 0, 0, 0],
|
166
|
+
[0, np.exp(1j * theta / 2), 0, 0],
|
167
|
+
[0, 0, np.exp(1j * theta / 2), 0],
|
168
|
+
[0, 0, 0, np.exp(-1j * theta / 2)]
|
169
|
+
], dtype=complex)
|
170
|
+
|
171
|
+
# Three-qubit gates
|
172
|
+
@staticmethod
|
173
|
+
def toffoli() -> np.ndarray:
|
174
|
+
"""Toffoli (CCNOT) gate matrix"""
|
175
|
+
matrix = np.eye(8, dtype=complex)
|
176
|
+
matrix[6, 6] = 0
|
177
|
+
matrix[7, 7] = 0
|
178
|
+
matrix[6, 7] = 1
|
179
|
+
matrix[7, 6] = 1
|
180
|
+
return matrix
|
181
|
+
|
182
|
+
@staticmethod
|
183
|
+
def fredkin() -> np.ndarray:
|
184
|
+
"""Fredkin (CSWAP) gate matrix"""
|
185
|
+
matrix = np.eye(8, dtype=complex)
|
186
|
+
matrix[5, 5] = 0
|
187
|
+
matrix[6, 6] = 0
|
188
|
+
matrix[5, 6] = 1
|
189
|
+
matrix[6, 5] = 1
|
190
|
+
return matrix
|
191
|
+
|
192
|
+
|
193
|
+
class ParametricGate:
|
194
|
+
"""Parametric quantum gate with symbolic parameters
|
195
|
+
"""
|
196
|
+
|
197
|
+
def __init__(
|
198
|
+
self,
|
199
|
+
name: str,
|
200
|
+
num_qubits: int,
|
201
|
+
matrix_func: callable,
|
202
|
+
parameters: List[str],
|
203
|
+
description: Optional[str] = None
|
204
|
+
):
|
205
|
+
"""Initialize parametric gate
|
206
|
+
|
207
|
+
Args:
|
208
|
+
name: Gate name
|
209
|
+
num_qubits: Number of qubits the gate acts on
|
210
|
+
matrix_func: Function that returns gate matrix given parameters
|
211
|
+
parameters: List of parameter names
|
212
|
+
description: Optional gate description
|
213
|
+
|
214
|
+
"""
|
215
|
+
self.name = name
|
216
|
+
self.num_qubits = num_qubits
|
217
|
+
self.matrix_func = matrix_func
|
218
|
+
self.parameters = parameters
|
219
|
+
self.description = description or f"{name} gate"
|
220
|
+
|
221
|
+
def matrix(self, *args) -> np.ndarray:
|
222
|
+
"""Get gate matrix for given parameter values"""
|
223
|
+
if len(args) != len(self.parameters):
|
224
|
+
raise ValueError(f"Expected {len(self.parameters)} parameters, got {len(args)}")
|
225
|
+
return self.matrix_func(*args)
|
226
|
+
|
227
|
+
def __call__(self, *args) -> np.ndarray:
|
228
|
+
"""Shorthand for matrix method"""
|
229
|
+
return self.matrix(*args)
|
230
|
+
|
231
|
+
def __repr__(self) -> str:
|
232
|
+
params_str = ", ".join(self.parameters)
|
233
|
+
return f"{self.name}({params_str})"
|
234
|
+
|
235
|
+
|
236
|
+
class GateDecomposer:
|
237
|
+
"""Utility class for decomposing quantum gates into basic gate sets
|
238
|
+
"""
|
239
|
+
|
240
|
+
@staticmethod
|
241
|
+
def decompose_arbitrary_single_qubit(matrix: np.ndarray) -> List[Tuple[str, List[float]]]:
|
242
|
+
"""Decompose arbitrary single-qubit unitary into U3 gates
|
243
|
+
|
244
|
+
Args:
|
245
|
+
matrix: 2x2 unitary matrix
|
246
|
+
|
247
|
+
Returns:
|
248
|
+
List of (gate_name, parameters) tuples
|
249
|
+
|
250
|
+
"""
|
251
|
+
if matrix.shape != (2, 2):
|
252
|
+
raise ValueError("Matrix must be 2x2 for single-qubit decomposition")
|
253
|
+
|
254
|
+
# Extract parameters from SU(2) matrix
|
255
|
+
# U = e^(iα) * U3(θ, φ, λ)
|
256
|
+
det = np.linalg.det(matrix)
|
257
|
+
alpha = np.angle(det) / 2
|
258
|
+
|
259
|
+
# Normalize to SU(2)
|
260
|
+
su2_matrix = matrix / np.exp(1j * alpha)
|
261
|
+
|
262
|
+
# Extract U3 parameters
|
263
|
+
theta = 2 * np.arccos(np.abs(su2_matrix[0, 0]))
|
264
|
+
|
265
|
+
if np.abs(np.sin(theta / 2)) < 1e-10:
|
266
|
+
# θ ≈ 0, gate is just a phase
|
267
|
+
phi = 0
|
268
|
+
lam = np.angle(su2_matrix[0, 0]) * 2
|
269
|
+
else:
|
270
|
+
phi = np.angle(su2_matrix[1, 0]) - np.angle(su2_matrix[0, 1]) + np.pi
|
271
|
+
lam = np.angle(su2_matrix[1, 0]) + np.angle(su2_matrix[0, 1])
|
272
|
+
|
273
|
+
decomposition = []
|
274
|
+
if abs(alpha) > 1e-10:
|
275
|
+
decomposition.append(("global_phase", [alpha]))
|
276
|
+
|
277
|
+
decomposition.append(("u3", [theta, phi, lam]))
|
278
|
+
|
279
|
+
return decomposition
|
280
|
+
|
281
|
+
@staticmethod
|
282
|
+
def decompose_cnot_to_cz(control: int, target: int) -> List[Tuple[str, List[int], List[float]]]:
|
283
|
+
"""Decompose CNOT to CZ using Hadamard gates
|
284
|
+
|
285
|
+
Returns:
|
286
|
+
List of (gate_name, qubits, parameters) tuples
|
287
|
+
|
288
|
+
"""
|
289
|
+
return [
|
290
|
+
("h", [target], []),
|
291
|
+
("cz", [control, target], []),
|
292
|
+
("h", [target], [])
|
293
|
+
]
|
294
|
+
|
295
|
+
@staticmethod
|
296
|
+
def decompose_toffoli() -> List[Tuple[str, List[int], List[float]]]:
|
297
|
+
"""Decompose Toffoli gate into CNOT and single-qubit gates
|
298
|
+
|
299
|
+
Returns:
|
300
|
+
List of (gate_name, qubits, parameters) for qubits [0, 1, 2]
|
301
|
+
|
302
|
+
"""
|
303
|
+
return [
|
304
|
+
("h", [2], []),
|
305
|
+
("cnot", [1, 2], []),
|
306
|
+
("t", [2], []),
|
307
|
+
("cnot", [0, 2], []),
|
308
|
+
("tdg", [2], []),
|
309
|
+
("cnot", [1, 2], []),
|
310
|
+
("t", [2], []),
|
311
|
+
("cnot", [0, 2], []),
|
312
|
+
("tdg", [1], []),
|
313
|
+
("tdg", [2], []),
|
314
|
+
("cnot", [0, 1], []),
|
315
|
+
("h", [2], []),
|
316
|
+
("tdg", [0], []),
|
317
|
+
("t", [1], []),
|
318
|
+
("cnot", [0, 1], [])
|
319
|
+
]
|
320
|
+
|
321
|
+
@staticmethod
|
322
|
+
def decompose_fredkin() -> List[Tuple[str, List[int], List[float]]]:
|
323
|
+
"""Decompose Fredkin gate into CNOT and Toffoli gates
|
324
|
+
|
325
|
+
Returns:
|
326
|
+
List of (gate_name, qubits, parameters) for qubits [0, 1, 2]
|
327
|
+
|
328
|
+
"""
|
329
|
+
return [
|
330
|
+
("cnot", [2, 1], []),
|
331
|
+
("toffoli", [0, 1, 2], []),
|
332
|
+
("cnot", [2, 1], [])
|
333
|
+
]
|
334
|
+
|
335
|
+
|
336
|
+
class PauliString:
|
337
|
+
"""Represents a Pauli string for Hamiltonian construction
|
338
|
+
"""
|
339
|
+
|
340
|
+
def __init__(self, pauli_ops: str, coefficient: complex = 1.0):
|
341
|
+
"""Initialize Pauli string
|
342
|
+
|
343
|
+
Args:
|
344
|
+
pauli_ops: String of Pauli operators (e.g., "IXZY")
|
345
|
+
coefficient: Complex coefficient
|
346
|
+
|
347
|
+
"""
|
348
|
+
self.pauli_ops = pauli_ops.upper()
|
349
|
+
self.coefficient = coefficient
|
350
|
+
self.num_qubits = len(pauli_ops)
|
351
|
+
|
352
|
+
# Validate Pauli string
|
353
|
+
valid_ops = set("IXYZ")
|
354
|
+
if not all(op in valid_ops for op in self.pauli_ops):
|
355
|
+
raise ValueError("Pauli string must contain only I, X, Y, Z operators")
|
356
|
+
|
357
|
+
def matrix(self) -> np.ndarray:
|
358
|
+
"""Get the matrix representation of the Pauli string"""
|
359
|
+
matrices = {
|
360
|
+
'I': GateMatrix.I,
|
361
|
+
'X': GateMatrix.X,
|
362
|
+
'Y': GateMatrix.Y,
|
363
|
+
'Z': GateMatrix.Z
|
364
|
+
}
|
365
|
+
|
366
|
+
result = np.array([[1]], dtype=complex)
|
367
|
+
for op in self.pauli_ops:
|
368
|
+
result = np.kron(result, matrices[op])
|
369
|
+
|
370
|
+
return self.coefficient * result
|
371
|
+
|
372
|
+
def commutes_with(self, other: "PauliString") -> bool:
|
373
|
+
"""Check if this Pauli string commutes with another"""
|
374
|
+
if len(self.pauli_ops) != len(other.pauli_ops):
|
375
|
+
return False
|
376
|
+
|
377
|
+
anti_commuting_pairs = {('X', 'Y'), ('Y', 'X'), ('X', 'Z'), ('Z', 'X'), ('Y', 'Z'), ('Z', 'Y')}
|
378
|
+
anti_commutations = 0
|
379
|
+
|
380
|
+
for op1, op2 in zip(self.pauli_ops, other.pauli_ops):
|
381
|
+
if (op1, op2) in anti_commuting_pairs:
|
382
|
+
anti_commutations += 1
|
383
|
+
|
384
|
+
return anti_commutations % 2 == 0
|
385
|
+
|
386
|
+
def __mul__(self, other: Union[complex, "PauliString"]) -> "PauliString":
|
387
|
+
"""Multiply with scalar or another Pauli string"""
|
388
|
+
if isinstance(other, (int, float, complex)):
|
389
|
+
return PauliString(self.pauli_ops, self.coefficient * other)
|
390
|
+
elif isinstance(other, PauliString):
|
391
|
+
if len(self.pauli_ops) != len(other.pauli_ops):
|
392
|
+
raise ValueError("Pauli strings must have same length")
|
393
|
+
|
394
|
+
# Multiply Pauli operators
|
395
|
+
result_ops = []
|
396
|
+
phase = 1
|
397
|
+
|
398
|
+
for op1, op2 in zip(self.pauli_ops, other.pauli_ops):
|
399
|
+
if op1 == 'I':
|
400
|
+
result_ops.append(op2)
|
401
|
+
elif op2 == 'I':
|
402
|
+
result_ops.append(op1)
|
403
|
+
elif op1 == op2:
|
404
|
+
result_ops.append('I')
|
405
|
+
else:
|
406
|
+
# Anti-commuting case
|
407
|
+
pauli_order = {'X': 0, 'Y': 1, 'Z': 2}
|
408
|
+
ops = [op1, op2]
|
409
|
+
if pauli_order[ops[0]] > pauli_order[ops[1]]:
|
410
|
+
ops.reverse()
|
411
|
+
phase *= -1
|
412
|
+
|
413
|
+
if ops == ['X', 'Y']:
|
414
|
+
result_ops.append('Z')
|
415
|
+
phase *= 1j
|
416
|
+
elif ops == ['X', 'Z']:
|
417
|
+
result_ops.append('Y')
|
418
|
+
phase *= -1j
|
419
|
+
elif ops == ['Y', 'Z']:
|
420
|
+
result_ops.append('X')
|
421
|
+
phase *= 1j
|
422
|
+
|
423
|
+
return PauliString(''.join(result_ops), self.coefficient * other.coefficient * phase)
|
424
|
+
else:
|
425
|
+
return NotImplemented
|
426
|
+
|
427
|
+
def __rmul__(self, other: complex) -> "PauliString":
|
428
|
+
"""Right multiplication by scalar"""
|
429
|
+
return self * other
|
430
|
+
|
431
|
+
def __repr__(self) -> str:
|
432
|
+
if self.coefficient == 1:
|
433
|
+
return self.pauli_ops
|
434
|
+
elif self.coefficient == -1:
|
435
|
+
return f"-{self.pauli_ops}"
|
436
|
+
else:
|
437
|
+
return f"({self.coefficient})*{self.pauli_ops}"
|
438
|
+
|
439
|
+
|
440
|
+
class Hamiltonian:
|
441
|
+
"""Quantum Hamiltonian represented as sum of Pauli strings
|
442
|
+
"""
|
443
|
+
|
444
|
+
def __init__(self, pauli_strings: List[PauliString]):
|
445
|
+
"""Initialize Hamiltonian
|
446
|
+
|
447
|
+
Args:
|
448
|
+
pauli_strings: List of Pauli strings that sum to form the Hamiltonian
|
449
|
+
|
450
|
+
"""
|
451
|
+
self.pauli_strings = pauli_strings
|
452
|
+
if pauli_strings:
|
453
|
+
self.num_qubits = pauli_strings[0].num_qubits
|
454
|
+
# Validate all strings have same length
|
455
|
+
if not all(p.num_qubits == self.num_qubits for p in pauli_strings):
|
456
|
+
raise ValueError("All Pauli strings must have same length")
|
457
|
+
else:
|
458
|
+
self.num_qubits = 0
|
459
|
+
|
460
|
+
def matrix(self) -> np.ndarray:
|
461
|
+
"""Get matrix representation of the Hamiltonian"""
|
462
|
+
if not self.pauli_strings:
|
463
|
+
return np.zeros((1, 1), dtype=complex)
|
464
|
+
|
465
|
+
dim = 2 ** self.num_qubits
|
466
|
+
result = np.zeros((dim, dim), dtype=complex)
|
467
|
+
|
468
|
+
for pauli_string in self.pauli_strings:
|
469
|
+
result += pauli_string.matrix()
|
470
|
+
|
471
|
+
return result
|
472
|
+
|
473
|
+
def expectation_value(self, state: np.ndarray) -> complex:
|
474
|
+
"""Calculate expectation value ⟨ψ|H|ψ⟩"""
|
475
|
+
H = self.matrix()
|
476
|
+
return np.conj(state).T @ H @ state
|
477
|
+
|
478
|
+
def ground_state_energy(self) -> float:
|
479
|
+
"""Calculate ground state energy (lowest eigenvalue)"""
|
480
|
+
eigenvals = np.linalg.eigvals(self.matrix())
|
481
|
+
return float(np.min(np.real(eigenvals)))
|
482
|
+
|
483
|
+
def time_evolution(self, time: float) -> np.ndarray:
|
484
|
+
"""Generate time evolution operator U(t) = exp(-iHt)"""
|
485
|
+
H = self.matrix()
|
486
|
+
return expm(-1j * H * time)
|
487
|
+
|
488
|
+
def __add__(self, other: "Hamiltonian") -> "Hamiltonian":
|
489
|
+
"""Add two Hamiltonians"""
|
490
|
+
return Hamiltonian(self.pauli_strings + other.pauli_strings)
|
491
|
+
|
492
|
+
def __mul__(self, scalar: complex) -> "Hamiltonian":
|
493
|
+
"""Multiply Hamiltonian by scalar"""
|
494
|
+
return Hamiltonian([scalar * p for p in self.pauli_strings])
|
495
|
+
|
496
|
+
def __rmul__(self, scalar: complex) -> "Hamiltonian":
|
497
|
+
"""Right multiplication by scalar"""
|
498
|
+
return self * scalar
|
499
|
+
|
500
|
+
@classmethod
|
501
|
+
def from_dict(cls, hamiltonian_dict: Dict[str, complex]) -> "Hamiltonian":
|
502
|
+
"""Create Hamiltonian from dictionary
|
503
|
+
|
504
|
+
Args:
|
505
|
+
hamiltonian_dict: Dictionary mapping Pauli strings to coefficients
|
506
|
+
|
507
|
+
Example:
|
508
|
+
{"IXZI": 0.5, "YZII": -0.3, "ZXYX": 1.2j}
|
509
|
+
|
510
|
+
"""
|
511
|
+
pauli_strings = []
|
512
|
+
for pauli_ops, coeff in hamiltonian_dict.items():
|
513
|
+
pauli_strings.append(PauliString(pauli_ops, coeff))
|
514
|
+
return cls(pauli_strings)
|
515
|
+
|
516
|
+
@staticmethod
|
517
|
+
def heisenberg_model(
|
518
|
+
num_qubits: int,
|
519
|
+
Jx: float = 1.0,
|
520
|
+
Jy: float = 1.0,
|
521
|
+
Jz: float = 1.0,
|
522
|
+
periodic: bool = False
|
523
|
+
) -> "Hamiltonian":
|
524
|
+
"""Create Heisenberg model Hamiltonian
|
525
|
+
|
526
|
+
H = Σᵢ (Jₓ XᵢXᵢ₊₁ + Jᵧ YᵢYᵢ₊₁ + Jᵤ ZᵢZᵢ₊₁)
|
527
|
+
"""
|
528
|
+
pauli_strings = []
|
529
|
+
|
530
|
+
max_i = num_qubits if periodic else num_qubits - 1
|
531
|
+
|
532
|
+
for i in range(max_i):
|
533
|
+
j = (i + 1) % num_qubits
|
534
|
+
|
535
|
+
# XX term
|
536
|
+
xx_ops = ['I'] * num_qubits
|
537
|
+
xx_ops[i] = 'X'
|
538
|
+
xx_ops[j] = 'X'
|
539
|
+
pauli_strings.append(PauliString(''.join(xx_ops), Jx))
|
540
|
+
|
541
|
+
# YY term
|
542
|
+
yy_ops = ['I'] * num_qubits
|
543
|
+
yy_ops[i] = 'Y'
|
544
|
+
yy_ops[j] = 'Y'
|
545
|
+
pauli_strings.append(PauliString(''.join(yy_ops), Jy))
|
546
|
+
|
547
|
+
# ZZ term
|
548
|
+
zz_ops = ['I'] * num_qubits
|
549
|
+
zz_ops[i] = 'Z'
|
550
|
+
zz_ops[j] = 'Z'
|
551
|
+
pauli_strings.append(PauliString(''.join(zz_ops), Jz))
|
552
|
+
|
553
|
+
return Hamiltonian(pauli_strings)
|
554
|
+
|
555
|
+
@staticmethod
|
556
|
+
def ising_model(
|
557
|
+
num_qubits: int,
|
558
|
+
J: float = 1.0,
|
559
|
+
h: float = 0.0,
|
560
|
+
periodic: bool = False
|
561
|
+
) -> "Hamiltonian":
|
562
|
+
"""Create transverse field Ising model Hamiltonian
|
563
|
+
|
564
|
+
H = -J Σᵢ ZᵢZᵢ₊₁ - h Σᵢ Xᵢ
|
565
|
+
"""
|
566
|
+
pauli_strings = []
|
567
|
+
|
568
|
+
# ZZ interactions
|
569
|
+
max_i = num_qubits if periodic else num_qubits - 1
|
570
|
+
for i in range(max_i):
|
571
|
+
j = (i + 1) % num_qubits
|
572
|
+
zz_ops = ['I'] * num_qubits
|
573
|
+
zz_ops[i] = 'Z'
|
574
|
+
zz_ops[j] = 'Z'
|
575
|
+
pauli_strings.append(PauliString(''.join(zz_ops), -J))
|
576
|
+
|
577
|
+
# X fields
|
578
|
+
for i in range(num_qubits):
|
579
|
+
x_ops = ['I'] * num_qubits
|
580
|
+
x_ops[i] = 'X'
|
581
|
+
pauli_strings.append(PauliString(''.join(x_ops), -h))
|
582
|
+
|
583
|
+
return Hamiltonian(pauli_strings)
|
584
|
+
|
585
|
+
def __repr__(self) -> str:
|
586
|
+
if not self.pauli_strings:
|
587
|
+
return "0"
|
588
|
+
return " + ".join(str(p) for p in self.pauli_strings)
|