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.
Files changed (46) hide show
  1. superquantx/__init__.py +321 -0
  2. superquantx/algorithms/__init__.py +55 -0
  3. superquantx/algorithms/base_algorithm.py +413 -0
  4. superquantx/algorithms/hybrid_classifier.py +628 -0
  5. superquantx/algorithms/qaoa.py +406 -0
  6. superquantx/algorithms/quantum_agents.py +1006 -0
  7. superquantx/algorithms/quantum_kmeans.py +575 -0
  8. superquantx/algorithms/quantum_nn.py +544 -0
  9. superquantx/algorithms/quantum_pca.py +499 -0
  10. superquantx/algorithms/quantum_svm.py +346 -0
  11. superquantx/algorithms/vqe.py +553 -0
  12. superquantx/algorithms.py +863 -0
  13. superquantx/backends/__init__.py +265 -0
  14. superquantx/backends/base_backend.py +321 -0
  15. superquantx/backends/braket_backend.py +420 -0
  16. superquantx/backends/cirq_backend.py +466 -0
  17. superquantx/backends/ocean_backend.py +491 -0
  18. superquantx/backends/pennylane_backend.py +419 -0
  19. superquantx/backends/qiskit_backend.py +451 -0
  20. superquantx/backends/simulator_backend.py +455 -0
  21. superquantx/backends/tket_backend.py +519 -0
  22. superquantx/circuits.py +447 -0
  23. superquantx/cli/__init__.py +28 -0
  24. superquantx/cli/commands.py +528 -0
  25. superquantx/cli/main.py +254 -0
  26. superquantx/client.py +298 -0
  27. superquantx/config.py +326 -0
  28. superquantx/exceptions.py +287 -0
  29. superquantx/gates.py +588 -0
  30. superquantx/logging_config.py +347 -0
  31. superquantx/measurements.py +702 -0
  32. superquantx/ml.py +936 -0
  33. superquantx/noise.py +760 -0
  34. superquantx/utils/__init__.py +83 -0
  35. superquantx/utils/benchmarking.py +523 -0
  36. superquantx/utils/classical_utils.py +575 -0
  37. superquantx/utils/feature_mapping.py +467 -0
  38. superquantx/utils/optimization.py +410 -0
  39. superquantx/utils/quantum_utils.py +456 -0
  40. superquantx/utils/visualization.py +654 -0
  41. superquantx/version.py +33 -0
  42. superquantx-0.1.0.dist-info/METADATA +365 -0
  43. superquantx-0.1.0.dist-info/RECORD +46 -0
  44. superquantx-0.1.0.dist-info/WHEEL +4 -0
  45. superquantx-0.1.0.dist-info/entry_points.txt +2 -0
  46. 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)