pyqrack-cpu 1.82.0__py3-none-macosx_15_0_arm64.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.
@@ -0,0 +1,595 @@
1
+ # (C) Daniel Strano and the Qrack contributors 2017-2025. All rights reserved.
2
+ #
3
+ # Use of this source code is governed by an MIT-style license that can be
4
+ # found in the LICENSE file or at https://opensource.org/licenses/MIT.
5
+
6
+ import ctypes
7
+
8
+ from .qrack_system import Qrack
9
+ from .quimb_circuit_type import QuimbCircuitType
10
+
11
+ _IS_QISKIT_AVAILABLE = True
12
+ try:
13
+ from qiskit.circuit.quantumcircuit import QuantumCircuit
14
+ from qiskit.compiler.transpiler import transpile
15
+ from qiskit.circuit.library import UCGate
16
+ import numpy as np
17
+ import math
18
+ import sys
19
+ except ImportError:
20
+ _IS_QISKIT_AVAILABLE = False
21
+
22
+ _IS_QUIMB_AVAILABLE = True
23
+ try:
24
+ import quimb as qu
25
+ import quimb.tensor as qtn
26
+ except ImportError:
27
+ _IS_QUIMB_AVAILABLE = False
28
+
29
+ _IS_TENSORCIRCUIT_AVAILABLE = True
30
+ try:
31
+ import tensorcircuit as tc
32
+ except ImportError:
33
+ _IS_TENSORCIRCUIT_AVAILABLE = False
34
+
35
+
36
+ class QrackCircuit:
37
+ """Class that exposes the QCircuit class of Qrack
38
+
39
+ QrackCircuit allows the user to specify a unitary circuit, before running it.
40
+ Upon running the state, the result is a QrackSimulator state. Currently,
41
+ measurement is not supported, but measurement can be run on the resultant
42
+ QrackSimulator.
43
+
44
+ Attributes:
45
+ cid(int): Qrack ID of this circuit
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ is_collapse=True,
51
+ is_near_clifford=False,
52
+ clone_cid=-1,
53
+ is_inverse=False,
54
+ past_light_cone=[],
55
+ ):
56
+ if clone_cid < 0:
57
+ self.cid = Qrack.qrack_lib.init_qcircuit(is_collapse, is_near_clifford)
58
+ elif is_inverse:
59
+ self.cid = Qrack.qrack_lib.qcircuit_inverse(clone_cid)
60
+ elif len(past_light_cone) > 0:
61
+ self.cid = Qrack.qrack_lib.qcircuit_past_light_cone(
62
+ clone_cid, len(past_light_cone), QrackCircuit._ulonglong_byref(past_light_cone)
63
+ )
64
+ else:
65
+ self.cid = Qrack.qrack_lib.init_qcircuit_clone(clone_cid)
66
+
67
+ def __del__(self):
68
+ if self.cid is not None:
69
+ Qrack.qrack_lib.destroy_qcircuit(self.cid)
70
+ self.cid = None
71
+
72
+ @staticmethod
73
+ def _ulonglong_byref(a):
74
+ return (ctypes.c_ulonglong * len(a))(*a)
75
+
76
+ @staticmethod
77
+ def _double_byref(a):
78
+ return (ctypes.c_double * len(a))(*a)
79
+
80
+ @staticmethod
81
+ def _complex_byref(a):
82
+ t = [(c.real, c.imag) for c in a]
83
+ return QrackCircuit._double_byref([float(item) for sublist in t for item in sublist])
84
+
85
+ @staticmethod
86
+ def _mtrx_to_u4(m):
87
+ nrm = abs(m[0])
88
+ if (nrm * nrm) < sys.float_info.epsilon:
89
+ phase = 1.0 + 0.0j
90
+ th = math.pi
91
+ else:
92
+ phase = m[0] / nrm
93
+ if nrm > 1.0:
94
+ nrm = 1.0
95
+ th = 2 * math.acos(nrm)
96
+
97
+ nrm1 = abs(m[1])
98
+ nrm2 = abs(m[2])
99
+ nrm1 *= nrm1
100
+ nrm2 *= nrm2
101
+ if (nrm1 < sys.float_info.epsilon) or (nrm2 < sys.float_info.epsilon):
102
+ ph = np.angle(m[3] / phase)
103
+ lm = 0.0
104
+ else:
105
+ ph = np.angle(m[2] / phase)
106
+ lm = np.angle(-m[1] / phase)
107
+
108
+ return th, ph, lm, np.angle(phase)
109
+
110
+ @staticmethod
111
+ def _u3_to_mtrx(params):
112
+ th = float(params[0])
113
+ ph = float(params[1])
114
+ lm = float(params[2])
115
+
116
+ c = math.cos(th / 2)
117
+ s = math.sin(th / 2)
118
+ el = np.exp(1j * lm)
119
+ ep = np.exp(1j * ph)
120
+
121
+ return [c + 0j, -el * s, ep * s, ep * el * c]
122
+
123
+ @staticmethod
124
+ def _u4_to_mtrx(params):
125
+ m = QrackCircuit._u3_to_mtrx(params)
126
+ g = np.exp(1j * float(params[3]))
127
+ for i in range(4):
128
+ m[i] *= g
129
+
130
+ return m
131
+
132
+ @staticmethod
133
+ def _make_mtrx_unitary(m):
134
+ return QrackCircuit._u4_to_mtrx(QrackCircuit._mtrx_to_u4(m))
135
+
136
+ def clone(self):
137
+ """Make a new circuit that is an exact clone of this circuit
138
+
139
+ Raises:
140
+ RuntimeError: QrackCircuit C++ library raised an exception.
141
+ """
142
+ return QrackCircuit(clone_cid=self.cid, is_inverse=False)
143
+
144
+ def inverse(self):
145
+ """Make a new circuit that is the exact inverse of this circuit
146
+
147
+ Raises:
148
+ RuntimeError: QrackCircuit C++ library raised an exception.
149
+ """
150
+ return QrackCircuit(clone_cid=self.cid, is_inverse=True)
151
+
152
+ def past_light_cone(self, q):
153
+ """Make a new circuit with just this circuits' past light cone for certain qubits.
154
+
155
+ Args:
156
+ q: list of qubit indices to include at beginning of past light cone
157
+
158
+ Raises:
159
+ RuntimeError: QrackCircuit C++ library raised an exception.
160
+ """
161
+ return QrackCircuit(clone_cid=self.cid, is_inverse=False, past_light_cone=q)
162
+
163
+ def get_qubit_count(self):
164
+ """Get count of qubits in circuit
165
+
166
+ Raises:
167
+ RuntimeError: QrackCircuit C++ library raised an exception.
168
+ """
169
+ return Qrack.qrack_lib.get_qcircuit_qubit_count(self.cid)
170
+
171
+ def swap(self, q1, q2):
172
+ """Add a 'Swap' gate to the circuit
173
+
174
+ Args:
175
+ q1: qubit index #1
176
+ q2: qubit index #2
177
+
178
+ Raises:
179
+ RuntimeError: QrackCircuit C++ library raised an exception.
180
+ """
181
+ Qrack.qrack_lib.qcircuit_swap(self.cid, q1, q2)
182
+
183
+ def mtrx(self, m, q):
184
+ """Operation from matrix.
185
+
186
+ Applies arbitrary operation defined by the given matrix.
187
+
188
+ Args:
189
+ m: row-major complex list representing the operator.
190
+ q: the qubit number on which the gate is applied to.
191
+
192
+ Raises:
193
+ ValueError: 2x2 matrix 'm' in QrackCircuit.mtrx() must contain at least 4 elements.
194
+ RuntimeError: QrackSimulator raised an exception.
195
+ """
196
+ if len(m) < 4:
197
+ raise ValueError(
198
+ "2x2 matrix 'm' in QrackCircuit.mtrx() must contain at least 4 elements."
199
+ )
200
+ Qrack.qrack_lib.qcircuit_append_1qb(self.cid, QrackCircuit._complex_byref(m), q)
201
+
202
+ def ucmtrx(self, c, m, q, p):
203
+ """Multi-controlled single-target-qubit gate
204
+
205
+ Specify a controlled gate by its control qubits, its single-qubit
206
+ matrix "payload," the target qubit, and the permutation of qubits
207
+ that activates the gate.
208
+
209
+ Args:
210
+ c: list of controlled qubits
211
+ m: row-major complex list representing the operator.
212
+ q: target qubit
213
+ p: permutation of target qubits
214
+
215
+ Raises:
216
+ ValueError: 2x2 matrix 'm' in QrackCircuit.ucmtrx() must contain at least 4 elements.
217
+ RuntimeError: QrackSimulator raised an exception.
218
+ """
219
+ if len(m) < 4:
220
+ raise ValueError(
221
+ "2x2 matrix 'm' in QrackCircuit.ucmtrx() must contain at least 4 elements."
222
+ )
223
+ Qrack.qrack_lib.qcircuit_append_mc(
224
+ self.cid, QrackCircuit._complex_byref(m), len(c), QrackCircuit._ulonglong_byref(c), q, p
225
+ )
226
+
227
+ def run(self, qsim):
228
+ """Run circuit on simulator
229
+
230
+ Run the encoded circuit on a specific simulator. The
231
+ result will remain in this simulator.
232
+
233
+ Args:
234
+ qsim: QrackSimulator on which to run circuit
235
+
236
+ Raises:
237
+ RuntimeError: QrackCircuit raised an exception.
238
+ """
239
+ qb_count = self.get_qubit_count()
240
+ sim_qb_count = qsim.num_qubits()
241
+ if sim_qb_count < qb_count:
242
+ for i in range(sim_qb_count, qb_count):
243
+ qsim.allocate_qubit(i)
244
+ Qrack.qrack_lib.qcircuit_run(self.cid, qsim.sid)
245
+ qsim._throw_if_error()
246
+
247
+ def out_to_file(self, filename):
248
+ """Output optimized circuit to file
249
+
250
+ Outputs the (optimized) circuit to a file named
251
+ according to the "filename" parameter.
252
+
253
+ Args:
254
+ filename: Name of file
255
+ """
256
+ Qrack.qrack_lib.qcircuit_out_to_file(self.cid, filename.encode("utf-8"))
257
+
258
+ def out_to_string(self):
259
+ """Output optimized circuit to string
260
+
261
+ Outputs the (optimized) circuit to a string.
262
+ """
263
+ string_length = Qrack.qrack_lib.qcircuit_out_to_string_length(self.cid)
264
+ out = ctypes.create_string_buffer(string_length)
265
+ Qrack.qrack_lib.qcircuit_out_to_string(self.cid, out)
266
+
267
+ return out.value.decode("utf-8")
268
+
269
+ def to_qiskit_circuit(self):
270
+ """Convert to a Qiskit circuit
271
+
272
+ Outputs a Qiskit circuit from a QrackCircuit.
273
+
274
+ Raises:
275
+ RuntimeErorr: Before trying to string_to_qiskit_circuit() with
276
+ QrackCircuit, you must install Qiskit, numpy, and math!
277
+ """
278
+ if not _IS_QISKIT_AVAILABLE:
279
+ raise RuntimeError(
280
+ "Before trying to_qiskit_circuit() with QrackCircuit, you must install Qiskit, numpy, and math!"
281
+ )
282
+
283
+ return QrackCircuit.string_to_qiskit_circuit(self.out_to_string())
284
+
285
+ @staticmethod
286
+ def in_from_file(filename):
287
+ """Read in optimized circuit from file
288
+
289
+ Reads in an (optimized) circuit from a file named
290
+ according to the "filename" parameter.
291
+
292
+ Args:
293
+ filename: Name of file
294
+ """
295
+ out = QrackCircuit()
296
+ Qrack.qrack_lib.qcircuit_in_from_file(out.cid, filename.encode("utf-8"))
297
+
298
+ return out
299
+
300
+ @staticmethod
301
+ def file_gate_count(filename):
302
+ """File gate count
303
+
304
+ Return the count of gates in a QrackCircuit file
305
+
306
+ Args:
307
+ filename: Name of file
308
+ """
309
+ tokens = []
310
+ with open(filename, "r") as file:
311
+ tokens = file.read().split()
312
+ return int(tokens[1])
313
+
314
+ @staticmethod
315
+ def file_to_qiskit_circuit(filename):
316
+ """Convert an output file to a Qiskit circuit
317
+
318
+ Reads in an (optimized) circuit from a file named
319
+ according to the "filename" parameter and outputs
320
+ a Qiskit circuit.
321
+
322
+ Args:
323
+ filename: Name of file
324
+
325
+ Raises:
326
+ RuntimeErorr: Before trying to file_to_qiskit_circuit() with
327
+ QrackCircuit, you must install Qiskit, numpy, and math!
328
+ """
329
+ if not _IS_QISKIT_AVAILABLE:
330
+ raise RuntimeError(
331
+ "Before trying to file_to_qiskit_circuit() with QrackCircuit, you must install Qiskit, numpy, and math!"
332
+ )
333
+
334
+ tokens = []
335
+ with open(filename, "r") as file:
336
+ return QrackCircuit.string_to_qiskit_circuit(file.read())
337
+
338
+ @staticmethod
339
+ def string_to_qiskit_circuit(circ_string):
340
+ """Convert an output string to a Qiskit circuit
341
+
342
+ Reads in an (optimized) circuit from a string
343
+ parameter and outputs a Qiskit circuit.
344
+
345
+ Args:
346
+ circ_string: String representation of circuit
347
+
348
+ Raises:
349
+ RuntimeErorr: Before trying to string_to_qiskit_circuit() with
350
+ QrackCircuit, you must install Qiskit, numpy, and math!
351
+ """
352
+ if not _IS_QISKIT_AVAILABLE:
353
+ raise RuntimeError(
354
+ "Before trying to string_to_qiskit_circuit() with QrackCircuit, you must install Qiskit, numpy, and math!"
355
+ )
356
+
357
+ tokens = circ_string.split()
358
+
359
+ i = 0
360
+ num_qubits = int(tokens[i])
361
+ i += 1
362
+ circ = QuantumCircuit(num_qubits)
363
+
364
+ num_gates = int(tokens[i])
365
+ i += 1
366
+
367
+ identity = np.eye(2, dtype=complex)
368
+ for g in range(num_gates):
369
+ target = int(tokens[i])
370
+ i += 1
371
+
372
+ control_count = int(tokens[i])
373
+ i += 1
374
+ controls = []
375
+ for j in range(control_count):
376
+ controls.append(int(tokens[i]))
377
+ i += 1
378
+
379
+ payload_count = int(tokens[i])
380
+ i += 1
381
+ payloads = {}
382
+ for j in range(payload_count):
383
+ key = int(tokens[i])
384
+ i += 1
385
+
386
+ mtrx = []
387
+ for _ in range(4):
388
+ amp = tokens[i].replace("(", "").replace(")", "").split(",")
389
+ mtrx.append(float(amp[0]) + float(amp[1]) * 1j)
390
+ i += 1
391
+
392
+ mtrx = QrackCircuit._make_mtrx_unitary(mtrx)
393
+
394
+ op = np.eye(2, dtype=complex)
395
+ op[0][0] = mtrx[0]
396
+ op[0][1] = mtrx[1]
397
+ op[1][0] = mtrx[2]
398
+ op[1][1] = mtrx[3]
399
+
400
+ payloads[key] = op
401
+
402
+ gate_list = []
403
+ for j in range(1 << control_count):
404
+ if j in payloads:
405
+ gate_list.append(payloads[j])
406
+ else:
407
+ gate_list.append(identity)
408
+ circ.append(UCGate(gate_list), [target] + controls)
409
+
410
+ return circ
411
+
412
+ @staticmethod
413
+ def in_from_qiskit_circuit(circ):
414
+ """Read a Qiskit circuit into a QrackCircuit
415
+
416
+ Reads in a circuit from a Qiskit `QuantumCircuit`
417
+
418
+ Args:
419
+ circ: Qiskit circuit
420
+
421
+ Raises:
422
+ RuntimeErorr: Before trying to file_to_qiskit_circuit() with
423
+ QrackCircuit, you must install Qiskit, numpy, and math!
424
+ """
425
+ if not _IS_QISKIT_AVAILABLE:
426
+ raise RuntimeError(
427
+ "Before trying to file_to_qiskit_circuit() with QrackCircuit, you must install Qiskit, numpy, and math!"
428
+ )
429
+
430
+ out = QrackCircuit()
431
+ basis_gates = ["x", "y", "z", "u", "cx", "cy", "cz", "cu"]
432
+ circ = transpile(circ, basis_gates=basis_gates, optimization_level=0)
433
+ for gate in circ.data:
434
+ o = gate.operation
435
+
436
+ op = []
437
+ if o.name in ["x", "cx"]:
438
+ op = [0, 1, 1, 0]
439
+ elif o.name in ["y", "cy"]:
440
+ op = [0, -1j, 1j, 0]
441
+ elif o.name in ["z", "cz"]:
442
+ op = [1, 0, 0, -1]
443
+ else:
444
+ op = QrackCircuit._u3_to_mtrx(o.params)
445
+
446
+ if o.name in ["x", "y", "z", "u"]:
447
+ out.mtrx(op, circ.find_bit(gate.qubits[0])[0])
448
+ else:
449
+ out.ucmtrx(
450
+ [circ.find_bit(gate.qubits[0])[0]],
451
+ op,
452
+ circ.find_bit(gate.qubits[1])[0],
453
+ 1,
454
+ )
455
+
456
+ return out
457
+
458
+ @staticmethod
459
+ def file_to_quimb_circuit(
460
+ filename,
461
+ circuit_type=QuimbCircuitType.Circuit,
462
+ psi0=None,
463
+ gate_opts=None,
464
+ tags=None,
465
+ psi0_dtype="complex128",
466
+ psi0_tag="PSI0",
467
+ bra_site_ind_id="b{}",
468
+ ):
469
+ """Convert an output file to a Quimb circuit
470
+
471
+ Reads in an (optimized) circuit from a file named
472
+ according to the "filename" parameter and outputs
473
+ a Quimb circuit.
474
+
475
+ Args:
476
+ filename: Name of file
477
+ circuit_type: "QuimbCircuitType" enum value specifying type of Quimb circuit
478
+ psi0: The initial state, assumed to be |00000....0> if not given. The state is always copied and the tag PSI0 added
479
+ gate_opts: Default keyword arguments to supply to each gate_TN_1D() call during the circuit
480
+ tags: Tag(s) to add to the initial wavefunction tensors (whether these are propagated to the rest of the circuit’s tensors
481
+ psi0_dtype: Ensure the initial state has this dtype.
482
+ psi0_tag: Ensure the initial state has this tag.
483
+ bra_site_ind_id: Use this to label ‘bra’ site indices when creating certain (mostly internal) intermediate tensor networks.
484
+
485
+ Raises:
486
+ RuntimeErorr: Before trying to file_to_quimb_circuit() with
487
+ QrackCircuit, you must install quimb, Qiskit, numpy, and math!
488
+ """
489
+ if not _IS_QUIMB_AVAILABLE:
490
+ raise RuntimeError(
491
+ "Before trying to file_to_quimb_circuit() with QrackCircuit, you must install quimb, Qiskit, numpy, and math!"
492
+ )
493
+
494
+ qcirc = QrackCircuit.file_to_qiskit_circuit(filename)
495
+ basis_gates = ["u", "cx"]
496
+ qcirc = transpile(qcirc, basis_gates=basis_gates, optimization_level=3)
497
+
498
+ tcirc = (
499
+ qtn.Circuit(
500
+ N=qcirc.num_qubits,
501
+ psi0=psi0,
502
+ gate_opts=gate_opts,
503
+ tags=tags,
504
+ psi0_dtype=psi0_dtype,
505
+ psi0_tag=psi0_tag,
506
+ bra_site_ind_id=bra_site_ind_id,
507
+ )
508
+ if circuit_type == QuimbCircuitType.Circuit
509
+ else (
510
+ qtn.CircuitDense(N=qcirc.num_qubits, psi0=psi0, gate_opts=gate_opts, tags=tags)
511
+ if circuit_type == QuimbCircuitType.CircuitDense
512
+ else qtn.CircuitMPS(
513
+ N=qcirc.num_qubits,
514
+ psi0=psi0,
515
+ gate_opts=gate_opts,
516
+ tags=tags,
517
+ psi0_dtype=psi0_dtype,
518
+ psi0_tag=psi0_tag,
519
+ bra_site_ind_id=bra_site_ind_id,
520
+ )
521
+ )
522
+ )
523
+ for gate in qcirc.data:
524
+ o = gate.operation
525
+ if o.name == "u":
526
+ th = float(o.params[0])
527
+ ph = float(o.params[1])
528
+ lm = float(o.params[2])
529
+
530
+ tcirc.apply_gate("U3", th, ph, lm, qcirc.find_bit(gate.qubits[0])[0])
531
+ else:
532
+ tcirc.apply_gate(
533
+ "CNOT",
534
+ qcirc.find_bit(gate.qubits[0])[0],
535
+ qcirc.find_bit(gate.qubits[1])[0],
536
+ )
537
+
538
+ return tcirc
539
+
540
+ @staticmethod
541
+ def file_to_tensorcircuit(filename, inputs=None, circuit_params=None, binding_params=None):
542
+ """Convert an output file to a TensorCircuit circuit
543
+
544
+ Reads in an (optimized) circuit from a file named
545
+ according to the "filename" parameter and outputs
546
+ a TensorCircuit circuit.
547
+
548
+ Args:
549
+ filename: Name of file
550
+ inputs: pass-through to tensorcircuit.Circuit.from_qiskit
551
+ circuit_params: pass-through to tensorcircuit.Circuit.from_qiskit
552
+ binding_params: pass-through to tensorcircuit.Circuit.from_qiskit
553
+
554
+ Raises:
555
+ RuntimeErorr: Before trying to file_to_quimb_circuit() with
556
+ QrackCircuit, you must install TensorCircuit, Qiskit, numpy, and math!
557
+ """
558
+ if not _IS_TENSORCIRCUIT_AVAILABLE:
559
+ raise RuntimeError(
560
+ "Before trying to file_to_tensorcircuit() with QrackCircuit, you must install TensorCircuit, Qiskit, numpy, and math!"
561
+ )
562
+
563
+ qcirc = QrackCircuit.file_to_qiskit_circuit(filename)
564
+ basis_gates = ["u", "cx"]
565
+ qcirc = transpile(qcirc, basis_gates=basis_gates, optimization_level=3)
566
+
567
+ return tc.Circuit.from_qiskit(
568
+ qcirc, qcirc.num_qubits, inputs, circuit_params, binding_params
569
+ )
570
+
571
+ @staticmethod
572
+ def in_from_tensorcircuit(tcirc, enable_instruction=False, enable_inputs=False):
573
+ """Convert a TensorCircuit circuit to a QrackCircuit
574
+
575
+ Accepts a TensorCircuit circuit and outputs an equivalent QrackCircuit
576
+
577
+ Args:
578
+ tcirc: TensorCircuit circuit
579
+ enable_instruction: whether to also export measurement and reset instructions
580
+ enable_inputs: whether to also export the inputs
581
+
582
+ Raises:
583
+ RuntimeErorr: Before trying to in_from_tensorcircuit() with
584
+ QrackCircuit, you must install TensorCircuit, Qiskit, numpy, and math!
585
+ """
586
+ if not _IS_TENSORCIRCUIT_AVAILABLE:
587
+ raise RuntimeError(
588
+ "Before trying to in_from_tensorcircuit() with QrackCircuit, you must install TensorCircuit, Qiskit, numpy, and math!"
589
+ )
590
+
591
+ # Convert from TensorCircuit to Qiskit
592
+ qcirc = tcirc.to_qiskit(enable_instruction, enable_inputs)
593
+
594
+ # Convert to QrackCircuit
595
+ return QrackCircuit.in_from_qiskit_circuit(qcirc)