pennylane-qrack 0.10.2__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.

Potentially problematic release.


This version of pennylane-qrack might be problematic. Click here for more details.

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