pennylane-qrack 0.24.1__py3-none-win_amd64.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,744 @@
1
+ # Copyright 2020 Xanadu Quantum Technologies Inc.
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """
15
+ Base device class for PennyLane-Qrack.
16
+ """
17
+ from functools import reduce
18
+ import cmath, math
19
+ import os
20
+ import pathlib
21
+ import sys
22
+ import itertools as it
23
+
24
+ import numpy as np
25
+
26
+ # PennyLane v0.42 introduced the `exceptions` module and will raise
27
+ # deprecation warnings if they are imported from the top-level module.
28
+
29
+ # This ensures backwards compatibility with older versions of PennyLane.
30
+ try:
31
+ from pennylane.exceptions import DeviceError, QuantumFunctionError
32
+ except (ModuleNotFoundError, ImportError) as import_error:
33
+ from pennylane import DeviceError, QuantumFunctionError
34
+
35
+ from pennylane.devices import QubitDevice
36
+ from pennylane.ops import (
37
+ StatePrep,
38
+ BasisState,
39
+ QubitUnitary,
40
+ Adjoint,
41
+ )
42
+ from pennylane.wires import Wires
43
+
44
+ from pyqrack import QrackSimulator, Pauli
45
+
46
+ from ._version import __version__
47
+ from sys import platform as _platform
48
+
49
+ # tolerance for numerical errors
50
+ tolerance = 1e-10
51
+
52
+
53
+ class QrackDevice(QubitDevice):
54
+ """Qrack device"""
55
+
56
+ name = "Qrack device"
57
+ short_name = "qrack.simulator"
58
+ pennylane_requires = ">=0.11.0"
59
+ version = __version__
60
+ author = "Daniel Strano, adapted from Steven Oud and Xanadu"
61
+
62
+ _capabilities = {
63
+ "model": "qubit",
64
+ "tensor_observables": True,
65
+ "inverse_operations": True,
66
+ "returns_state": True,
67
+ }
68
+
69
+ _observable_map = {
70
+ "PauliX": Pauli.PauliX,
71
+ "PauliY": Pauli.PauliY,
72
+ "PauliZ": Pauli.PauliZ,
73
+ "Identity": Pauli.PauliI,
74
+ "Hadamard": None,
75
+ "Hermitian": None,
76
+ "Prod": None,
77
+ # "Sum": None,
78
+ # "SProd": None,
79
+ # "Exp": None,
80
+ # "Projector": None,
81
+ # "Hamiltonian": None,
82
+ # "SparseHamiltonian": None
83
+ }
84
+
85
+ observables = _observable_map.keys()
86
+ operations = {
87
+ "Identity",
88
+ "C(Identity)",
89
+ "MultiRZ",
90
+ "C(MultiRZ)",
91
+ "Toffoli",
92
+ "C(Toffoli)",
93
+ "CSWAP",
94
+ "C(CSWAP)",
95
+ "CRX",
96
+ "C(CRX)",
97
+ "CRY",
98
+ "C(CRY)",
99
+ "CRZ",
100
+ "C(CRZ)",
101
+ "CRot",
102
+ "C(CRot)",
103
+ "SWAP",
104
+ "C(SWAP)",
105
+ "ISWAP",
106
+ "C(ISWAP)",
107
+ "PSWAP",
108
+ "C(PSWAP)",
109
+ "CNOT",
110
+ "C(CNOT)",
111
+ "CY",
112
+ "C(CY)",
113
+ "CZ",
114
+ "C(CZ)",
115
+ "S",
116
+ "C(S)",
117
+ "T",
118
+ "C(T)",
119
+ "RX",
120
+ "C(RX)",
121
+ "RY",
122
+ "C(RY)",
123
+ "RZ",
124
+ "C(RZ)",
125
+ "PauliX",
126
+ "C(PauliX)",
127
+ "PauliY",
128
+ "C(PauliY)",
129
+ "PauliZ",
130
+ "C(PauliZ)",
131
+ "Hadamard",
132
+ "C(Hadamard)",
133
+ "SX",
134
+ "C(SX)",
135
+ "PhaseShift",
136
+ "C(PhaseShift)",
137
+ "U3",
138
+ "C(U3)",
139
+ "Rot",
140
+ "C(Rot)",
141
+ "ControlledPhaseShift",
142
+ "CPhase",
143
+ "C(ControlledPhaseShift)",
144
+ "C(CPhase)",
145
+ "MultiControlledX",
146
+ "C(MultiControlledX)",
147
+ }
148
+
149
+ config_filepath = pathlib.Path(
150
+ os.path.dirname(sys.modules[__name__].__file__) + "/QrackDeviceConfig.toml"
151
+ )
152
+
153
+ # Use "hybrid" stabilizer optimization? (Default is "true"; non-Clifford circuits will fall back to near-Clifford or universal simulation)
154
+ isStabilizerHybrid = False
155
+ # Use "tensor network" optimization? (Default is "true"; prevents dynamic qubit de-allocation; might function sub-optimally with "hybrid" stabilizer enabled)
156
+ isTensorNetwork = True
157
+ # Use Schmidt decomposition optimizations? (Default is "true")
158
+ isSchmidtDecompose = True
159
+ # Distribute Schmidt-decomposed qubit subsystems to multiple GPUs or accelerators, if available? (Default is "true"; mismatched device capacities might hurt overall performance)
160
+ isSchmidtDecomposeMulti = True
161
+ # Use "quantum binary decision diagram" ("QBDD") methods? (Default is "false"; note that QBDD is CPU-only)
162
+ isBinaryDecisionTree = False
163
+ # Use GPU acceleration? (Default is "true")
164
+ isOpenCL = True
165
+ # Use multi-GPU (or "multi-page") acceleration? (Default is "false")
166
+ isPaged = True
167
+ # Use CPU/GPU method hybridization? (Default is "false")
168
+ isCpuGpuHybrid = True
169
+ # Allocate GPU buffer from general host heap? (Default is "false"; "true" might improve performance or reliability in certain cases, like if using an Intel HD as accelerator)
170
+ isHostPointer = True if os.environ.get("PYQRACK_HOST_POINTER_DEFAULT_ON") else False
171
+ # For CPU-based simulation, use sparse state vectors (Default is "false")
172
+ isSparse = False
173
+ # Noise parameter. (Default is "0"; depolarizing noise intensity can also be controlled by "QRACK_GATE_DEPOLARIZATION" environment variable)
174
+ noise = 0
175
+
176
+ @staticmethod
177
+ def get_c_interface():
178
+ shared_lib_path = os.path.dirname(sys.modules[__name__].__file__) + "/libqrack_device.so"
179
+ if _platform == "win32":
180
+ shared_lib_path = os.path.dirname(sys.modules[__name__].__file__) + "/qrack_device.dll"
181
+ elif _platform == "darwin":
182
+ shared_lib_path = (
183
+ os.path.dirname(sys.modules[__name__].__file__) + "/libqrack_device.dylib"
184
+ )
185
+
186
+ return ("QrackDevice", shared_lib_path)
187
+
188
+ def __init__(self, wires=0, shots=None, **kwargs):
189
+ options = dict(kwargs)
190
+ if "isStabilizerHybrid" in options:
191
+ self.isStabilizerHybrid = options["isStabilizerHybrid"]
192
+ if "isTensorNetwork" in options:
193
+ self.isTensorNetwork = options["isTensorNetwork"]
194
+ if "isSchmidtDecompose" in options:
195
+ self.isSchmidtDecompose = options["isSchmidtDecompose"]
196
+ if "isBinaryDecisionTree" in options:
197
+ self.isBinaryDecisionTree = options["isBinaryDecisionTree"]
198
+ if "isOpenCL" in options:
199
+ self.isOpenCL = options["isOpenCL"]
200
+ if "isPaged" in options:
201
+ self.isPaged = options["isPaged"]
202
+ if "isCpuGpuHybrid" in options:
203
+ self.isCpuGpuHybrid = options["isCpuGpuHybrid"]
204
+ if "isHostPointer" in options:
205
+ self.isHostPointer = options["isHostPointer"]
206
+ if "isSparse" in options:
207
+ self.isSparse = options["isSparse"]
208
+ if "noise" in options:
209
+ self.noise = options["noise"]
210
+ if (self.noise != 0) and (shots is None):
211
+ raise ValueError("Shots must be finite for noisy simulation (not analytical mode).")
212
+ super().__init__(wires=wires, shots=shots)
213
+ self.shots = shots
214
+ self._state = QrackSimulator(
215
+ self.num_wires,
216
+ isStabilizerHybrid=self.isStabilizerHybrid,
217
+ isTensorNetwork=self.isTensorNetwork,
218
+ isSchmidtDecompose=self.isSchmidtDecompose,
219
+ isBinaryDecisionTree=self.isBinaryDecisionTree,
220
+ isOpenCL=self.isOpenCL,
221
+ isCpuGpuHybrid=self.isCpuGpuHybrid,
222
+ isHostPointer=self.isHostPointer,
223
+ isSparse=self.isSparse,
224
+ noise=self.noise,
225
+ )
226
+ self.device_kwargs = {
227
+ "is_hybrid_stabilizer": self.isStabilizerHybrid,
228
+ "is_tensor_network": self.isTensorNetwork,
229
+ "is_schmidt_decompose": self.isSchmidtDecompose,
230
+ "is_schmidt_decompose_parallel": self.isSchmidtDecomposeMulti,
231
+ "is_qpdd": self.isBinaryDecisionTree,
232
+ "is_gpu": self.isOpenCL,
233
+ "is_paged": self.isPaged,
234
+ "is_hybrid_cpu_gpu": self.isCpuGpuHybrid,
235
+ "is_host_pointer": self.isHostPointer,
236
+ "is_sparse": self.isSparse,
237
+ "noise": self.noise,
238
+ }
239
+ self._circuit = []
240
+
241
+ def _reverse_state(self):
242
+ end = self.num_wires - 1
243
+ mid = self.num_wires >> 1
244
+ for i in range(mid):
245
+ self._state.swap(i, end - i)
246
+
247
+ def apply(self, operations, **kwargs):
248
+ """Apply the circuit operations to the state.
249
+
250
+ This method serves as an auxiliary method to :meth:`~.QrackDevice.apply`.
251
+
252
+ Args:
253
+ operations (List[pennylane.Operation]): operations to be applied
254
+ """
255
+
256
+ self._circuit = self._circuit + operations
257
+ if self.noise == 0:
258
+ self._apply()
259
+ self._circuit = []
260
+ # else: Defer application until shots or expectation values are requested
261
+
262
+ def _apply(self):
263
+ for op in self._circuit:
264
+ if isinstance(op, StatePrep):
265
+ self._apply_state_prep(op)
266
+ elif isinstance(op, BasisState):
267
+ self._apply_basis_state(op)
268
+ elif isinstance(op, QubitUnitary):
269
+ if len(op.wires) > 1:
270
+ raise DeviceError(
271
+ f"Operation {op.name} is not supported on a {self.short_name} device, except for single wires."
272
+ )
273
+ self._apply_qubit_unitary(op)
274
+ else:
275
+ self._apply_gate(op)
276
+
277
+ def _expand_state(self, state_vector, wires):
278
+ """Expands state vector to more wires"""
279
+ basis_states = np.array(list(it.product([0, 1], repeat=len(wires))))
280
+
281
+ # get basis states to alter on full set of qubits
282
+ unravelled_indices = np.zeros((2 ** len(wires), self.num_wires), dtype=int)
283
+ unravelled_indices[:, wires] = basis_states
284
+
285
+ # get indices for which the state is changed to input state vector elements
286
+ ravelled_indices = np.ravel_multi_index(unravelled_indices.T, [2] * self.num_wires)
287
+
288
+ state = np.zeros([2**self.num_wires], dtype=np.complex128)
289
+ state[ravelled_indices] = state_vector
290
+ state_vector = state.reshape([2] * self.num_wires)
291
+
292
+ return state_vector.flatten()
293
+
294
+ def _apply_state_prep(self, op):
295
+ """Initialize state with a state vector"""
296
+ wires = op.wires
297
+ input_state = op.parameters[0]
298
+
299
+ if len(input_state) != 2 ** len(wires):
300
+ raise ValueError("State vector must be of length 2**wires.")
301
+ if input_state.ndim != 1 or len(input_state) != 2 ** len(wires):
302
+ raise ValueError("State vector must be of length 2**wires.")
303
+ if not np.isclose(np.linalg.norm(input_state, 2), 1.0, atol=tolerance):
304
+ raise ValueError("Sum of amplitudes-squared does not equal one.")
305
+
306
+ self._reverse_state()
307
+ if len(wires) != self.num_wires or sorted(wires, reverse=True) != wires:
308
+ input_state = self._expand_state(input_state, wires)
309
+
310
+ # call qrack' state initialization
311
+ self._state.in_ket(input_state)
312
+ self._reverse_state()
313
+
314
+ def _apply_basis_state(self, op):
315
+ """Initialize a basis state"""
316
+ wires = self.map_wires(Wires(op.wires))
317
+ par = op.parameters[0]
318
+ wire_count = len(wires)
319
+ n_basis_state = len(par)
320
+
321
+ if not set(par).issubset({0, 1}):
322
+ raise ValueError("BasisState parameter must consist of 0 or 1 integers.")
323
+ if n_basis_state != wire_count:
324
+ raise ValueError("BasisState parameter and wires must be of equal length.")
325
+
326
+ for i in range(wire_count):
327
+ index = wires.labels[i]
328
+ if par[i] != self._state.m(index):
329
+ self._state.x(index)
330
+
331
+ def _apply_gate(self, op):
332
+ """Apply native qrack gate"""
333
+
334
+ opname = op.name
335
+ if isinstance(op, Adjoint):
336
+ op = op.base
337
+ opname = op.name + ".inv"
338
+
339
+ par = op.parameters
340
+
341
+ if opname == "MultiRZ":
342
+ device_wires = self.map_wires(op.wires)
343
+ for q in device_wires:
344
+ self._state.r(Pauli.PauliZ, par[0], q)
345
+ return
346
+
347
+ if opname == "C(MultiRZ)":
348
+ device_wires = self.map_wires(op.wires)
349
+ control_wires = self.map_wires(op.control_wires)
350
+ for q in device_wires:
351
+ self._state.mcr(Pauli.PauliZ, par[0], control_wires, q)
352
+ return
353
+
354
+ # translate op wire labels to consecutive wire labels used by the device
355
+ device_wires = self.map_wires(
356
+ (op.control_wires + op.wires) if op.control_wires else op.wires
357
+ )
358
+
359
+ if opname in [
360
+ "".join(p)
361
+ for p in it.product(
362
+ [
363
+ "Toffoli",
364
+ "C(Toffoli)",
365
+ "CNOT",
366
+ "C(CNOT)",
367
+ "MultiControlledX",
368
+ "C(PauliX)",
369
+ ],
370
+ ["", ".inv"],
371
+ )
372
+ ]:
373
+ self._state.mcx(device_wires.labels[:-1], device_wires.labels[-1])
374
+ elif opname in ["C(PauliY)", "C(PauliY).inv"]:
375
+ self._state.mcy(device_wires.labels[:-1], device_wires.labels[-1])
376
+ elif opname in ["C(PauliZ)", "C(PauliZ).inv"]:
377
+ self._state.mcz(device_wires.labels[:-1], device_wires.labels[-1])
378
+ elif opname in ["C(Hadamard)", "C(Hadamard).inv"]:
379
+ self._state.mch(device_wires.labels[:-1], device_wires.labels[-1])
380
+ elif opname in [
381
+ "CSWAP",
382
+ "CSWAP.inv",
383
+ "C(SWAP)",
384
+ "C(SWAP).inv",
385
+ "C(CSWAP)",
386
+ "C(CSWAP).inv",
387
+ ]:
388
+ self._state.cswap(
389
+ device_wires.labels[:-2],
390
+ device_wires.labels[-2],
391
+ device_wires.labels[-1],
392
+ )
393
+ elif opname in ["CRX", "C(RX)", "C(CRX)"]:
394
+ self._state.mcr(Pauli.PauliX, par[0], device_wires.labels[:-1], device_wires.labels[-1])
395
+ elif opname in ["CRX.inv", "C(RX).inv", "C(CRX).inv"]:
396
+ self._state.mcr(
397
+ Pauli.PauliX, -par[0], device_wires.labels[:-1], device_wires.labels[-1]
398
+ )
399
+ elif opname in ["CRY", "C(RY)", "C(CRY)"]:
400
+ self._state.mcr(Pauli.PauliY, par[0], device_wires.labels[:-1], device_wires.labels[-1])
401
+ elif opname in ["CRY.inv", "C(RY).inv", "C(CRY).inv"]:
402
+ self._state.mcr(
403
+ Pauli.PauliY, -par[0], device_wires.labels[:-1], device_wires.labels[-1]
404
+ )
405
+ elif opname in ["CRZ", "C(RZ)", "C(CRZ)"]:
406
+ self._state.mcr(Pauli.PauliZ, par[0], device_wires.labels[:-1], device_wires.labels[-1])
407
+ elif opname in ["CRZ.inv", "C(RZ).inv", "C(CRZ).inv"]:
408
+ self._state.mcr(
409
+ Pauli.PauliZ, -par[0], device_wires.labels[:-1], device_wires.labels[-1]
410
+ )
411
+ elif opname in [
412
+ "CRot",
413
+ "CRot.inv",
414
+ "C(Rot)",
415
+ "C(Rot).inv",
416
+ "C(CRot)",
417
+ "C(CRot).inv",
418
+ ]:
419
+ phi = par[0]
420
+ theta = par[1]
421
+ omega = par[2]
422
+ if ".inv" in opname:
423
+ tmp = phi
424
+ phi = -omega
425
+ theta = -theta
426
+ omega = -phi
427
+ c = math.cos(theta / 2)
428
+ s = math.sin(theta / 2)
429
+ mtrx = [
430
+ cmath.exp(-0.5j * (phi + omega)) * c,
431
+ cmath.exp(0.5j * (phi - omega)) * s,
432
+ cmath.exp(-0.5j * (phi - omega)) * s,
433
+ cmath.exp(0.5j * (phi + omega)) * np.cos(theta / 2),
434
+ ]
435
+ self._state.mcmtrx(device_wires.labels[:-1], mtrx, device_wires.labels[-1])
436
+ elif opname in ["SWAP", "SWAP.inv"]:
437
+ self._state.swap(device_wires.labels[0], device_wires.labels[1])
438
+ elif opname == "ISWAP":
439
+ self._state.iswap(device_wires.labels[0], device_wires.labels[1])
440
+ elif opname == "ISWAP.inv":
441
+ self._state.adjiswap(device_wires.labels[0], device_wires.labels[1])
442
+ elif opname == "C(ISWAP)":
443
+ self._state.mcu(device_wires.labels[:-1], device_wires.labels[-1], 0, 0, 1j)
444
+ self._state.cswap(
445
+ device_wires.labels[:-2],
446
+ device_wires.labels[-2],
447
+ device_wires.labels[-1],
448
+ )
449
+ self._state.mcu(device_wires.labels[:-1], device_wires.labels[-1], 0, 0, 1j)
450
+ elif opname == "C(ISWAP).inv":
451
+ self._state.mcu(device_wires.labels[:-1], device_wires.labels[-1], 0, 0, -1j)
452
+ self._state.cswap(
453
+ device_wires.labels[:-2],
454
+ device_wires.labels[-2],
455
+ device_wires.labels[-1],
456
+ )
457
+ self._state.mcu(device_wires.labels[:-1], device_wires.labels[-1], 0, 0, -1j)
458
+ elif opname == "C(PSWAP)":
459
+ self._state.mcu(device_wires.labels[:-1], device_wires.labels[-1], 0, 0, par[0])
460
+ self._state.cswap(
461
+ device_wires.labels[:-2],
462
+ device_wires.labels[-2],
463
+ device_wires.labels[-1],
464
+ )
465
+ self._state.mcu(device_wires.labels[:-1], device_wires.labels[-1], 0, 0, par[0])
466
+ elif opname == "C(PSWAP).inv":
467
+ self._state.mcu(device_wires.labels[:-1], device_wires.labels[-1], 0, 0, -par[0])
468
+ self._state.cswap(
469
+ device_wires.labels[:-2],
470
+ device_wires.labels[-2],
471
+ device_wires.labels[-1],
472
+ )
473
+ self._state.mcu(device_wires.labels[:-1], device_wires.labels[-1], 0, 0, -par[0])
474
+ elif opname in ["CY", "CY.inv", "C(CY)", "C(CY).inv"]:
475
+ self._state.mcy(device_wires.labels[:-1], device_wires.labels[-1])
476
+ elif opname in ["CZ", "CZ.inv", "C(CZ)", "C(CZ).inv"]:
477
+ self._state.mcz(device_wires.labels[:-1], device_wires.labels[-1])
478
+ elif opname == "S":
479
+ for label in device_wires.labels:
480
+ self._state.s(label)
481
+ elif opname == "S.inv":
482
+ for label in device_wires.labels:
483
+ self._state.adjs(label)
484
+ elif opname == "C(S)":
485
+ self._state.mcs(device_wires.labels[:-1], device_wires.labels[-1])
486
+ elif opname == "C(S).inv":
487
+ self._state.mcadjs(device_wires.labels[:-1], device_wires.labels[-1])
488
+ elif opname == "T":
489
+ for label in device_wires.labels:
490
+ self._state.t(label)
491
+ elif opname == "T.inv":
492
+ for label in device_wires.labels:
493
+ self._state.adjt(label)
494
+ elif opname == "C(T)":
495
+ self._state.mct(device_wires.labels[:-1], device_wires.labels[-1])
496
+ elif opname == "C(T).inv":
497
+ self._state.mcadjt(device_wires.labels[:-1], device_wires.labels[-1])
498
+ elif opname == "RX":
499
+ for label in device_wires.labels:
500
+ self._state.r(Pauli.PauliX, par[0], label)
501
+ elif opname == "RX.inv":
502
+ for label in device_wires.labels:
503
+ self._state.r(Pauli.PauliX, -par[0], label)
504
+ elif opname in ["CRX", "C(RX)", "C(CRX)"]:
505
+ self._state.mcr(Pauli.PauliX, par[0], device_wires.labels[:-1], device_wires.labels[-1])
506
+ elif opname in ["CRX.inv", "C(RX).inv", "C(CRX).inv"]:
507
+ self._state.mcr(Pauli.PauliX, par[0], device_wires.labels[:-1], device_wires.labels[-1])
508
+ elif opname == "RY":
509
+ for label in device_wires.labels:
510
+ self._state.r(Pauli.PauliY, par[0], label)
511
+ elif opname == "RY.inv":
512
+ for label in device_wires.labels:
513
+ self._state.r(Pauli.PauliY, -par[0], label)
514
+ elif opname in ["CRY", "C(RY)", "C(CRY)"]:
515
+ self._state.mcr(Pauli.PauliY, par[0], device_wires.labels[:-1], device_wires.labels[-1])
516
+ elif opname in ["CRY.inv", "C(RY).inv", "C(CRY).inv"]:
517
+ self._state.mcr(Pauli.PauliY, par[0], device_wires.labels[:-1], device_wires.labels[-1])
518
+ elif opname == "RZ":
519
+ for label in device_wires.labels:
520
+ self._state.r(Pauli.PauliZ, par[0], label)
521
+ elif opname == "RZ.inv":
522
+ for label in device_wires.labels:
523
+ self._state.r(Pauli.PauliZ, -par[0], label)
524
+ elif opname in ["CRZ", "C(RZ)", "C(CRZ)"]:
525
+ self._state.mcr(Pauli.PauliZ, par[0], device_wires.labels[:-1], device_wires.labels[-1])
526
+ elif opname in ["CRZ.inv", "C(RZ).inv", "C(CRZ).inv"]:
527
+ self._state.mcr(Pauli.PauliY, par[0], device_wires.labels[:-1], device_wires.labels[-1])
528
+ elif opname in ["PauliX", "PauliX.inv"]:
529
+ for label in device_wires.labels:
530
+ self._state.x(label)
531
+ elif opname in ["PauliY", "PauliY.inv"]:
532
+ for label in device_wires.labels:
533
+ self._state.y(label)
534
+ elif opname in ["PauliZ", "PauliZ.inv"]:
535
+ for label in device_wires.labels:
536
+ self._state.z(label)
537
+ elif opname in ["Hadamard", "Hadamard.inv"]:
538
+ for label in device_wires.labels:
539
+ self._state.h(label)
540
+ elif opname == "SX":
541
+ sx_mtrx = [(1 + 1j) / 2, (1 - 1j) / 2, (1 - 1j) / 2, (1 + 1j) / 2]
542
+ for label in device_wires.labels:
543
+ self._state.mtrx(sx_mtrx, label)
544
+ elif opname == "SX.inv":
545
+ isx_mtrx = [(1 - 1j) / 2, (1 + 1j) / 2, (1 + 1j) / 2, (1 - 1j) / 2]
546
+ for label in device_wires.labels:
547
+ self._state.mtrx(isx_mtrx, label)
548
+ elif opname == "C(SX)":
549
+ self._state.mcmtrx(
550
+ device_wires.labels[:-1],
551
+ [(1 + 1j) / 2, (1 - 1j) / 2, (1 - 1j) / 2, (1 + 1j) / 2],
552
+ device_wires.labels[-1],
553
+ )
554
+ elif opname == "PhaseShift":
555
+ p_mtrx = [1, 0, 0, cmath.exp(1j * par[0])]
556
+ for label in device_wires.labels:
557
+ self._state.mtrx(p_mtrx, label)
558
+ elif opname == "PhaseShift.inv":
559
+ ip_mtrx = [1, 0, 0, cmath.exp(1j * -par[0])]
560
+ for label in device_wires.labels:
561
+ self._state.mtrx(ip_mtrx, label)
562
+ elif opname == "C(PhaseShift)":
563
+ self._state.mtrx(
564
+ device_wires.labels[:-1],
565
+ [1, 0, 0, cmath.exp(1j * par[0])],
566
+ device_wires.labels[-1],
567
+ )
568
+ elif opname == "C(PhaseShift).inv":
569
+ self._state.mtrx(
570
+ device_wires.labels[:-1],
571
+ [1, 0, 0, cmath.exp(1j * -par[0])],
572
+ device_wires.labels[-1],
573
+ )
574
+ elif opname in [
575
+ "ControlledPhaseShift",
576
+ "C(ControlledPhaseShift)",
577
+ "CPhase",
578
+ "C(CPhase)",
579
+ ]:
580
+ self._state.mcmtrx(
581
+ device_wires.labels[:-1],
582
+ [1, 0, 0, cmath.exp(1j * par[0])],
583
+ device_wires.labels[-1],
584
+ )
585
+ elif opname in [
586
+ "ControlledPhaseShift.inv",
587
+ "C(ControlledPhaseShift).inv",
588
+ "CPhase.inv",
589
+ "C(CPhase).inv",
590
+ ]:
591
+ self._state.mcmtrx(
592
+ device_wires.labels[:-1],
593
+ [1, 0, 0, cmath.exp(1j * -par[0])],
594
+ device_wires.labels[-1],
595
+ )
596
+ elif opname == "U3":
597
+ for label in device_wires.labels:
598
+ self._state.u(label, par[0], par[1], par[2])
599
+ elif opname == "U3.inv":
600
+ for label in device_wires.labels:
601
+ self._state.u(label, -par[0], -par[2], -par[1])
602
+ elif opname == "Rot":
603
+ for label in device_wires.labels:
604
+ self._state.r(Pauli.PauliZ, par[0], label)
605
+ self._state.r(Pauli.PauliY, par[1], label)
606
+ self._state.r(Pauli.PauliZ, par[2], label)
607
+ elif opname == "Rot.inv":
608
+ for label in device_wires.labels:
609
+ self._state.r(Pauli.PauliZ, -par[2], label)
610
+ self._state.r(Pauli.PauliY, -par[1], label)
611
+ self._state.r(Pauli.PauliZ, -par[0], label)
612
+ elif opname == "C(U3)":
613
+ self._state.mcu(
614
+ device_wires.labels[:-1],
615
+ device_wires.labels[-1],
616
+ par[0],
617
+ par[1],
618
+ par[2],
619
+ )
620
+ elif opname == "C(U3).inv":
621
+ self._state.mcu(
622
+ device_wires.labels[:-1],
623
+ device_wires.labels[-1],
624
+ -par[0],
625
+ -par[2],
626
+ -par[1],
627
+ )
628
+ elif opname not in [
629
+ "Identity",
630
+ "Identity.inv",
631
+ "C(Identity)",
632
+ "C(Identity).inv",
633
+ ]:
634
+ raise DeviceError(f"Operation {opname} is not supported on a {self.short_name} device.")
635
+
636
+ def _apply_qubit_unitary(self, op):
637
+ """Apply unitary to state"""
638
+ # translate op wire labels to consecutive wire labels used by the device
639
+ device_wires = self.map_wires(op.wires)
640
+ par = op.parameters
641
+
642
+ if len(par[0]) != 2 ** len(device_wires):
643
+ raise ValueError("Unitary matrix must be of shape (2**wires, 2**wires).")
644
+
645
+ if isinstance(op, Adjoint):
646
+ par[0] = par[0].conj().T
647
+
648
+ matrix = par[0].flatten().tolist()
649
+ self._state.mtrx(matrix, device_wires.labels[0])
650
+
651
+ def analytic_probability(self, wires=None):
652
+ """Return the (marginal) analytic probability of each computational basis state."""
653
+ if self._state is None:
654
+ return None
655
+
656
+ all_probs = self._abs(self.state) ** 2
657
+ prob = self.marginal_prob(all_probs, wires)
658
+
659
+ if (not "QRACK_FPPOW" in os.environ) or (6 > int(os.environ.get("QRACK_FPPOW"))):
660
+ tot_prob = 0
661
+ for p in prob:
662
+ tot_prob = tot_prob + p
663
+
664
+ if tot_prob != 1.0:
665
+ for i in range(len(prob)):
666
+ prob[i] = prob[i] / tot_prob
667
+
668
+ return prob
669
+
670
+ def expval(self, observable, **kwargs):
671
+ if self.shots is None:
672
+ if isinstance(observable.name, list):
673
+ b = [self._observable_map[obs] for obs in observable.name]
674
+ elif observable.name == "Prod":
675
+ b = [self._observable_map[obs.name] for obs in observable.operands]
676
+ else:
677
+ b = [self._observable_map[observable.name]]
678
+
679
+ if None not in b:
680
+ q = self.map_wires(observable.wires)
681
+ return self._state.pauli_expectation(q, b)
682
+
683
+ # exact expectation value
684
+ if callable(observable.eigvals):
685
+ eigvals = self._asarray(observable.eigvals(), dtype=self.R_DTYPE)
686
+ else: # older version of pennylane
687
+ eigvals = self._asarray(observable.eigvals, dtype=self.R_DTYPE)
688
+ prob = self.probability(wires=observable.wires)
689
+ return self._dot(eigvals, prob)
690
+
691
+ # estimate the ev
692
+ return np.mean(self.sample(observable))
693
+
694
+ def _generate_sample(self):
695
+ rev_sample = self._state.m_all()
696
+ sample = 0
697
+ for i in range(self.num_wires):
698
+ if (rev_sample & (1 << i)) > 0:
699
+ sample |= 1 << (self.num_wires - (i + 1))
700
+ return sample
701
+
702
+ def generate_samples(self):
703
+ if self.shots is None:
704
+ raise QuantumFunctionError(
705
+ "The number of shots has to be explicitly set on the device "
706
+ "when using sample-based measurements."
707
+ )
708
+
709
+ if self.noise != 0:
710
+ samples = []
711
+ for _ in range(self.shots):
712
+ self._state.reset_all()
713
+ self._apply()
714
+ samples.append(self._generate_sample())
715
+ self._samples = QubitDevice.states_to_binary(np.array(samples), self.num_wires)
716
+ self._circuit = []
717
+
718
+ return self._samples
719
+
720
+ if self.shots == 1:
721
+ self._samples = QubitDevice.states_to_binary(
722
+ np.array([self._generate_sample()]), self.num_wires
723
+ )
724
+
725
+ return self._samples
726
+
727
+ # QubitDevice.states_to_binary() doesn't work for >64qb. (Fix by Elara, the custom OpenAI GPT)
728
+ samples = self._state.measure_shots(list(range(self.num_wires - 1, -1, -1)), self.shots)
729
+ self._samples = np.array(
730
+ [list(format(b, f"0{self.num_wires}b")) for b in samples], dtype=np.int8
731
+ )
732
+
733
+ return self._samples
734
+
735
+ @property
736
+ def state(self):
737
+ # returns the state after all operations are applied
738
+ self._reverse_state()
739
+ o = self._state.out_ket()
740
+ self._reverse_state()
741
+ return o
742
+
743
+ def reset(self):
744
+ self._state.reset_all()