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