qml-essentials 0.1.13__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.
- qml_essentials/__init__.py +0 -0
- qml_essentials/ansaetze.py +485 -0
- qml_essentials/coefficients.py +37 -0
- qml_essentials/entanglement.py +105 -0
- qml_essentials/expressibility.py +248 -0
- qml_essentials/model.py +539 -0
- qml_essentials-0.1.13.dist-info/LICENSE +7 -0
- qml_essentials-0.1.13.dist-info/METADATA +29 -0
- qml_essentials-0.1.13.dist-info/RECORD +10 -0
- qml_essentials-0.1.13.dist-info/WHEEL +4 -0
|
File without changes
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
import pennylane.numpy as np
|
|
4
|
+
import pennylane as qml
|
|
5
|
+
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
log = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Circuit(ABC):
|
|
14
|
+
def __init__(self):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def n_params_per_layer(n_qubits: int) -> int:
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def get_control_indices(self, n_qubits: int) -> List[int]:
|
|
23
|
+
"""
|
|
24
|
+
Returns the indices for the controlled rotation gates for one layer.
|
|
25
|
+
Indices should slice the list of all parameters for one layer as follows:
|
|
26
|
+
[indices[0]:indices[1]:indices[2]]
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
n_qubits : int
|
|
31
|
+
Number of qubits in the circuit
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
Optional[np.ndarray]
|
|
36
|
+
List of all controlled indices, or None if the circuit does not
|
|
37
|
+
contain controlled rotation gates.
|
|
38
|
+
"""
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
def get_control_angles(self, w: np.ndarray, n_qubits: int) -> Optional[np.ndarray]:
|
|
42
|
+
"""
|
|
43
|
+
Returns the angles for the controlled rotation gates from the list of
|
|
44
|
+
all parameters for one layer.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
w : np.ndarray
|
|
49
|
+
List of parameters for one layer
|
|
50
|
+
n_qubits : int
|
|
51
|
+
Number of qubits in the circuit
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
Optional[np.ndarray]
|
|
56
|
+
List of all controlled parameters, or None if the circuit does not
|
|
57
|
+
contain controlled rotation gates.
|
|
58
|
+
"""
|
|
59
|
+
indices = self.get_control_indices(n_qubits)
|
|
60
|
+
if indices is None:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
return w[indices[0] : indices[1] : indices[2]]
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def build(self, n_qubits: int, n_layers: int):
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
def __call__(self, *args: Any, **kwds: Any) -> Any:
|
|
70
|
+
self.build(*args, **kwds)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Ansaetze:
|
|
74
|
+
def get_available():
|
|
75
|
+
return [
|
|
76
|
+
Ansaetze.No_Ansatz,
|
|
77
|
+
Ansaetze.Circuit_1,
|
|
78
|
+
Ansaetze.Circuit_6,
|
|
79
|
+
Ansaetze.Circuit_9,
|
|
80
|
+
Ansaetze.Circuit_15,
|
|
81
|
+
Ansaetze.Circuit_18,
|
|
82
|
+
Ansaetze.Circuit_19,
|
|
83
|
+
Ansaetze.No_Entangling,
|
|
84
|
+
Ansaetze.Strongly_Entangling,
|
|
85
|
+
Ansaetze.Hardware_Efficient,
|
|
86
|
+
Ansaetze.Bansatz,
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
class No_Ansatz(Circuit):
|
|
90
|
+
@staticmethod
|
|
91
|
+
def n_params_per_layer(n_qubits: int) -> int:
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def build(w: np.ndarray, n_qubits: int):
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
class Hardware_Efficient(Circuit):
|
|
103
|
+
@staticmethod
|
|
104
|
+
def n_params_per_layer(n_qubits: int) -> int:
|
|
105
|
+
if n_qubits > 1:
|
|
106
|
+
return n_qubits * 3
|
|
107
|
+
else:
|
|
108
|
+
log.warning("Number of Qubits < 2, no entanglement available")
|
|
109
|
+
return 3
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def build(w: np.ndarray, n_qubits: int):
|
|
117
|
+
"""
|
|
118
|
+
Creates a Circuit19 ansatz.
|
|
119
|
+
|
|
120
|
+
Length of flattened vector must be n_qubits*3-1
|
|
121
|
+
because for >1 qubits there are three gates
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
w (np.ndarray): weight vector of size n_layers*(n_qubits*3-1)
|
|
125
|
+
n_qubits (int): number of qubits
|
|
126
|
+
"""
|
|
127
|
+
w_idx = 0
|
|
128
|
+
for q in range(n_qubits):
|
|
129
|
+
qml.RY(w[w_idx], wires=q)
|
|
130
|
+
w_idx += 1
|
|
131
|
+
qml.RZ(w[w_idx], wires=q)
|
|
132
|
+
w_idx += 1
|
|
133
|
+
qml.RY(w[w_idx], wires=q)
|
|
134
|
+
w_idx += 1
|
|
135
|
+
|
|
136
|
+
if n_qubits > 1:
|
|
137
|
+
for q in range(n_qubits // 2):
|
|
138
|
+
qml.CZ(wires=[(2 * q), (2 * q + 1)])
|
|
139
|
+
for q in range((n_qubits - 1) // 2):
|
|
140
|
+
qml.CZ(wires=[(2 * q + 1), (2 * q + 2)])
|
|
141
|
+
|
|
142
|
+
class Bansatz(Circuit):
|
|
143
|
+
@staticmethod
|
|
144
|
+
def n_params_per_layer(n_qubits: int) -> int:
|
|
145
|
+
if n_qubits > 1:
|
|
146
|
+
return n_qubits * 3
|
|
147
|
+
else:
|
|
148
|
+
log.warning("Number of Qubits < 2, no entanglement available")
|
|
149
|
+
return 3
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
|
|
153
|
+
if n_qubits > 1:
|
|
154
|
+
return [-n_qubits, None, None]
|
|
155
|
+
else:
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def build(w: np.ndarray, n_qubits: int):
|
|
160
|
+
"""
|
|
161
|
+
Creates a Circuit19 ansatz.
|
|
162
|
+
|
|
163
|
+
Length of flattened vector must be n_qubits*3-1
|
|
164
|
+
because for >1 qubits there are three gates
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
w (np.ndarray): weight vector of size n_layers*(n_qubits*3-1)
|
|
168
|
+
n_qubits (int): number of qubits
|
|
169
|
+
"""
|
|
170
|
+
w_idx = 0
|
|
171
|
+
for q in range(n_qubits):
|
|
172
|
+
qml.RY(w[w_idx], wires=q)
|
|
173
|
+
w_idx += 1
|
|
174
|
+
qml.RZ(w[w_idx], wires=q)
|
|
175
|
+
w_idx += 1
|
|
176
|
+
|
|
177
|
+
if n_qubits > 1:
|
|
178
|
+
for q in range(n_qubits // 2):
|
|
179
|
+
qml.CRX(w[w_idx], wires=[(2 * q), (2 * q + 1)])
|
|
180
|
+
w_idx += 1
|
|
181
|
+
|
|
182
|
+
for q in range((n_qubits - 1) // 2):
|
|
183
|
+
qml.CRX(w[w_idx], wires=[(2 * q + 1), (2 * q + 2)])
|
|
184
|
+
w_idx += 1
|
|
185
|
+
|
|
186
|
+
class Circuit_19(Circuit):
|
|
187
|
+
@staticmethod
|
|
188
|
+
def n_params_per_layer(n_qubits: int) -> int:
|
|
189
|
+
if n_qubits > 1:
|
|
190
|
+
return n_qubits * 3
|
|
191
|
+
else:
|
|
192
|
+
log.warning("Number of Qubits < 2, no entanglement available")
|
|
193
|
+
return 2
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
|
|
197
|
+
if n_qubits > 1:
|
|
198
|
+
return [-n_qubits, None, None]
|
|
199
|
+
else:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
@staticmethod
|
|
203
|
+
def build(w: np.ndarray, n_qubits: int):
|
|
204
|
+
"""
|
|
205
|
+
Creates a Circuit19 ansatz.
|
|
206
|
+
|
|
207
|
+
Length of flattened vector must be n_qubits*3-1
|
|
208
|
+
because for >1 qubits there are three gates
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
w (np.ndarray): weight vector of size n_layers*(n_qubits*3-1)
|
|
212
|
+
n_qubits (int): number of qubits
|
|
213
|
+
"""
|
|
214
|
+
w_idx = 0
|
|
215
|
+
for q in range(n_qubits):
|
|
216
|
+
qml.RX(w[w_idx], wires=q)
|
|
217
|
+
w_idx += 1
|
|
218
|
+
qml.RZ(w[w_idx], wires=q)
|
|
219
|
+
w_idx += 1
|
|
220
|
+
|
|
221
|
+
if n_qubits > 1:
|
|
222
|
+
for q in range(n_qubits):
|
|
223
|
+
qml.CRX(
|
|
224
|
+
w[w_idx],
|
|
225
|
+
wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits],
|
|
226
|
+
)
|
|
227
|
+
w_idx += 1
|
|
228
|
+
|
|
229
|
+
class Circuit_18(Circuit):
|
|
230
|
+
@staticmethod
|
|
231
|
+
def n_params_per_layer(n_qubits: int) -> int:
|
|
232
|
+
if n_qubits > 1:
|
|
233
|
+
return n_qubits * 3
|
|
234
|
+
else:
|
|
235
|
+
log.warning("Number of Qubits < 2, no entanglement available")
|
|
236
|
+
return 2
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
|
|
240
|
+
if n_qubits > 1:
|
|
241
|
+
return [-n_qubits, None, None]
|
|
242
|
+
else:
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
@staticmethod
|
|
246
|
+
def build(w: np.ndarray, n_qubits: int):
|
|
247
|
+
"""
|
|
248
|
+
Creates a Circuit18 ansatz.
|
|
249
|
+
|
|
250
|
+
Length of flattened vector must be n_qubits*3-1
|
|
251
|
+
because for >1 qubits there are three gates
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
w (np.ndarray): weight vector of size n_layers*(n_qubits*3-1)
|
|
255
|
+
n_qubits (int): number of qubits
|
|
256
|
+
"""
|
|
257
|
+
w_idx = 0
|
|
258
|
+
for q in range(n_qubits):
|
|
259
|
+
qml.RX(w[w_idx], wires=q)
|
|
260
|
+
w_idx += 1
|
|
261
|
+
qml.RZ(w[w_idx], wires=q)
|
|
262
|
+
w_idx += 1
|
|
263
|
+
|
|
264
|
+
if n_qubits > 1:
|
|
265
|
+
for q in range(n_qubits):
|
|
266
|
+
qml.CRZ(
|
|
267
|
+
w[w_idx],
|
|
268
|
+
wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits],
|
|
269
|
+
)
|
|
270
|
+
w_idx += 1
|
|
271
|
+
|
|
272
|
+
class Circuit_15(Circuit):
|
|
273
|
+
@staticmethod
|
|
274
|
+
def n_params_per_layer(n_qubits: int) -> int:
|
|
275
|
+
if n_qubits > 1:
|
|
276
|
+
return n_qubits * 3
|
|
277
|
+
else:
|
|
278
|
+
log.warning("Number of Qubits < 2, no entanglement available")
|
|
279
|
+
return 2
|
|
280
|
+
|
|
281
|
+
@staticmethod
|
|
282
|
+
def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
@staticmethod
|
|
286
|
+
def build(w: np.ndarray, n_qubits: int):
|
|
287
|
+
"""
|
|
288
|
+
Creates a Circuit15 ansatz.
|
|
289
|
+
|
|
290
|
+
Length of flattened vector must be n_qubits*3-1
|
|
291
|
+
because for >1 qubits there are three gates
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
w (np.ndarray): weight vector of size n_layers*(n_qubits*3-1)
|
|
295
|
+
n_qubits (int): number of qubits
|
|
296
|
+
"""
|
|
297
|
+
raise NotImplementedError # Did not figured out the entangling sequence yet
|
|
298
|
+
|
|
299
|
+
w_idx = 0
|
|
300
|
+
for q in range(n_qubits):
|
|
301
|
+
qml.RX(w[w_idx], wires=q)
|
|
302
|
+
w_idx += 1
|
|
303
|
+
|
|
304
|
+
if n_qubits > 1:
|
|
305
|
+
for q in range(n_qubits):
|
|
306
|
+
qml.CNOT(wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits])
|
|
307
|
+
|
|
308
|
+
for q in range(n_qubits):
|
|
309
|
+
qml.RZ(w[w_idx], wires=q)
|
|
310
|
+
w_idx += 1
|
|
311
|
+
|
|
312
|
+
class Circuit_9(Circuit):
|
|
313
|
+
@staticmethod
|
|
314
|
+
def n_params_per_layer(n_qubits: int) -> int:
|
|
315
|
+
return n_qubits
|
|
316
|
+
|
|
317
|
+
@staticmethod
|
|
318
|
+
def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def build(w: np.ndarray, n_qubits: int):
|
|
323
|
+
"""
|
|
324
|
+
Creates a Circuit19 ansatz.
|
|
325
|
+
|
|
326
|
+
Length of flattened vector must be n_qubits*3-1
|
|
327
|
+
because for >1 qubits there are three gates
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
w (np.ndarray): weight vector of size n_layers*(n_qubits*3-1)
|
|
331
|
+
n_qubits (int): number of qubits
|
|
332
|
+
"""
|
|
333
|
+
w_idx = 0
|
|
334
|
+
for q in range(n_qubits):
|
|
335
|
+
qml.Hadamard(wires=q)
|
|
336
|
+
|
|
337
|
+
if n_qubits > 1:
|
|
338
|
+
for q in range(n_qubits - 1):
|
|
339
|
+
qml.CZ(wires=[n_qubits - q - 2, n_qubits - q - 1])
|
|
340
|
+
|
|
341
|
+
for q in range(n_qubits):
|
|
342
|
+
qml.RX(w[w_idx], wires=q)
|
|
343
|
+
w_idx += 1
|
|
344
|
+
|
|
345
|
+
class Circuit_6(Circuit):
|
|
346
|
+
@staticmethod
|
|
347
|
+
def n_params_per_layer(n_qubits: int) -> int:
|
|
348
|
+
if n_qubits > 1:
|
|
349
|
+
return n_qubits * 3 + n_qubits**2
|
|
350
|
+
else:
|
|
351
|
+
log.warning("Number of Qubits < 2, no entanglement available")
|
|
352
|
+
return 4
|
|
353
|
+
|
|
354
|
+
@staticmethod
|
|
355
|
+
def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
|
|
356
|
+
if n_qubits > 1:
|
|
357
|
+
return [-n_qubits, None, None]
|
|
358
|
+
else:
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
@staticmethod
|
|
362
|
+
def build(w: np.ndarray, n_qubits: int):
|
|
363
|
+
"""
|
|
364
|
+
Creates a Circuit1 ansatz.
|
|
365
|
+
|
|
366
|
+
Length of flattened vector must be n_qubits*2
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
w (np.ndarray): weight vector of size n_layers*(n_qubits*2)
|
|
370
|
+
n_qubits (int): number of qubits
|
|
371
|
+
"""
|
|
372
|
+
w_idx = 0
|
|
373
|
+
for q in range(n_qubits):
|
|
374
|
+
qml.RX(w[w_idx], wires=q)
|
|
375
|
+
w_idx += 1
|
|
376
|
+
qml.RZ(w[w_idx], wires=q)
|
|
377
|
+
w_idx += 1
|
|
378
|
+
|
|
379
|
+
if n_qubits > 1:
|
|
380
|
+
for ql in range(n_qubits):
|
|
381
|
+
for q in range(n_qubits):
|
|
382
|
+
if q == ql:
|
|
383
|
+
continue
|
|
384
|
+
qml.CRX(
|
|
385
|
+
w[w_idx],
|
|
386
|
+
wires=[n_qubits - ql - 1, (n_qubits - q - 1) % n_qubits],
|
|
387
|
+
)
|
|
388
|
+
w_idx += 1
|
|
389
|
+
|
|
390
|
+
for q in range(n_qubits):
|
|
391
|
+
qml.RX(w[w_idx], wires=q)
|
|
392
|
+
w_idx += 1
|
|
393
|
+
qml.RZ(w[w_idx], wires=q)
|
|
394
|
+
w_idx += 1
|
|
395
|
+
|
|
396
|
+
class Circuit_1(Circuit):
|
|
397
|
+
@staticmethod
|
|
398
|
+
def n_params_per_layer(n_qubits: int) -> int:
|
|
399
|
+
return n_qubits * 2
|
|
400
|
+
|
|
401
|
+
@staticmethod
|
|
402
|
+
def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
|
|
403
|
+
return None
|
|
404
|
+
|
|
405
|
+
@staticmethod
|
|
406
|
+
def build(w: np.ndarray, n_qubits: int):
|
|
407
|
+
"""
|
|
408
|
+
Creates a Circuit1 ansatz.
|
|
409
|
+
|
|
410
|
+
Length of flattened vector must be n_qubits*2
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
w (np.ndarray): weight vector of size n_layers*(n_qubits*2)
|
|
414
|
+
n_qubits (int): number of qubits
|
|
415
|
+
"""
|
|
416
|
+
w_idx = 0
|
|
417
|
+
for q in range(n_qubits):
|
|
418
|
+
qml.RX(w[w_idx], wires=q)
|
|
419
|
+
w_idx += 1
|
|
420
|
+
qml.RZ(w[w_idx], wires=q)
|
|
421
|
+
w_idx += 1
|
|
422
|
+
|
|
423
|
+
class Strongly_Entangling(Circuit):
|
|
424
|
+
@staticmethod
|
|
425
|
+
def n_params_per_layer(n_qubits: int) -> int:
|
|
426
|
+
if n_qubits > 1:
|
|
427
|
+
return n_qubits * 6
|
|
428
|
+
else:
|
|
429
|
+
log.warning("Number of Qubits < 2, no entanglement available")
|
|
430
|
+
return 2
|
|
431
|
+
|
|
432
|
+
@staticmethod
|
|
433
|
+
def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
@staticmethod
|
|
437
|
+
def build(w: np.ndarray, n_qubits: int) -> None:
|
|
438
|
+
"""
|
|
439
|
+
Creates a StronglyEntanglingLayers ansatz.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
w (np.ndarray): weight vector of size n_layers*(n_qubits*3)
|
|
443
|
+
n_qubits (int): number of qubits
|
|
444
|
+
"""
|
|
445
|
+
w_idx = 0
|
|
446
|
+
for q in range(n_qubits):
|
|
447
|
+
qml.Rot(w[w_idx], w[w_idx + 1], w[w_idx + 2], wires=q)
|
|
448
|
+
w_idx += 3
|
|
449
|
+
|
|
450
|
+
if n_qubits > 1:
|
|
451
|
+
for q in range(n_qubits):
|
|
452
|
+
qml.CNOT(wires=[q, (q + 1) % n_qubits])
|
|
453
|
+
|
|
454
|
+
for q in range(n_qubits):
|
|
455
|
+
qml.Rot(w[w_idx], w[w_idx + 1], w[w_idx + 2], wires=q)
|
|
456
|
+
w_idx += 3
|
|
457
|
+
|
|
458
|
+
if n_qubits > 1:
|
|
459
|
+
for q in range(n_qubits):
|
|
460
|
+
qml.CNOT(wires=[q, (q + n_qubits // 2) % n_qubits])
|
|
461
|
+
|
|
462
|
+
class No_Entangling(Circuit):
|
|
463
|
+
@staticmethod
|
|
464
|
+
def n_params_per_layer(n_qubits: int) -> int:
|
|
465
|
+
return n_qubits * 3
|
|
466
|
+
|
|
467
|
+
@staticmethod
|
|
468
|
+
def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
|
|
469
|
+
return None
|
|
470
|
+
|
|
471
|
+
@staticmethod
|
|
472
|
+
def build(w: np.ndarray, n_qubits: int):
|
|
473
|
+
"""
|
|
474
|
+
Creates a circuit without entangling, but with U3 gates on all qubits
|
|
475
|
+
|
|
476
|
+
Length of flattened vector must be n_qubits*3
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
w (np.ndarray): weight vector of size n_layers*(n_qubits*3)
|
|
480
|
+
n_qubits (int): number of qubits
|
|
481
|
+
"""
|
|
482
|
+
w_idx = 0
|
|
483
|
+
for q in range(n_qubits):
|
|
484
|
+
qml.Rot(w[w_idx], w[w_idx + 1], w[w_idx + 2], wires=q)
|
|
485
|
+
w_idx += 3
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from qml_essentials.model import Model
|
|
2
|
+
from functools import partial
|
|
3
|
+
from pennylane.fourier import coefficients
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Coefficients:
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def sample_coefficients(model: Model, **kwargs) -> np.ndarray:
|
|
11
|
+
"""
|
|
12
|
+
Sample the Fourier coefficients of a given model
|
|
13
|
+
using Pennylane fourier.coefficients function.
|
|
14
|
+
|
|
15
|
+
Note that the coefficients are complex numbers, but the imaginary part
|
|
16
|
+
of the coefficients should be very close to zero, since the expectation
|
|
17
|
+
values of the Pauli operators are real numbers.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
model (Model): The model to sample.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
np.ndarray: The sampled Fourier coefficients.
|
|
24
|
+
"""
|
|
25
|
+
kwargs.setdefault("force_mean", True)
|
|
26
|
+
kwargs.setdefault("execution_type", "expval")
|
|
27
|
+
|
|
28
|
+
partial_circuit = partial(model, model.params, **kwargs)
|
|
29
|
+
coeffs = coefficients(partial_circuit, 1, model.degree)
|
|
30
|
+
|
|
31
|
+
if not np.isclose(np.sum(coeffs).imag, 0.0, rtol=1.0e-5):
|
|
32
|
+
raise ValueError(
|
|
33
|
+
f"Spectrum is not real. Imaginary part of coefficients is:\
|
|
34
|
+
{np.sum(coeffs).imag}"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return coeffs
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from typing import Callable, Optional, List, Any
|
|
2
|
+
import pennylane as qml
|
|
3
|
+
import pennylane.numpy as np
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Entanglement:
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def meyer_wallach(
|
|
14
|
+
model: Callable, # type: ignore
|
|
15
|
+
n_samples: int,
|
|
16
|
+
seed: Optional[int],
|
|
17
|
+
**kwargs: Any
|
|
18
|
+
) -> float:
|
|
19
|
+
"""
|
|
20
|
+
Calculates the entangling capacity of a given quantum circuit
|
|
21
|
+
using Meyer-Wallach measure.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
model (Callable): Function that models the quantum circuit. It must
|
|
25
|
+
have a `n_qubits` attribute representing the number of qubits.
|
|
26
|
+
It must accept a `params` argument representing the parameters
|
|
27
|
+
of the circuit.
|
|
28
|
+
n_samples (int): Number of samples per qubit.
|
|
29
|
+
seed (Optional[int]): Seed for the random number generator.
|
|
30
|
+
kwargs (Any): Additional keyword arguments for the model function.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
float: Entangling capacity of the given circuit. It is guaranteed
|
|
34
|
+
to be between 0.0 and 1.0.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def _meyer_wallach(
|
|
38
|
+
evaluate: Callable[[np.ndarray], np.ndarray],
|
|
39
|
+
n_qubits: int,
|
|
40
|
+
samples: int,
|
|
41
|
+
params: np.ndarray,
|
|
42
|
+
) -> float:
|
|
43
|
+
"""
|
|
44
|
+
Calculates the Meyer-Wallach sampling of the entangling capacity
|
|
45
|
+
of a quantum circuit.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
evaluate (Callable[[np.ndarray], np.ndarray]): Callable that
|
|
49
|
+
evaluates the quantum circuit It must accept a `params`
|
|
50
|
+
argument representing the parameters of the circuit and may
|
|
51
|
+
accept additional keyword arguments.
|
|
52
|
+
n_qubits (int): Number of qubits in the circuit
|
|
53
|
+
samples (int): Number of samples to be taken
|
|
54
|
+
params (np.ndarray): Parameters of the instructor. Shape:
|
|
55
|
+
(samples, *model.params.shape)
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
float: Entangling capacity of the given circuit. It is
|
|
59
|
+
guaranteed to be between 0.0 and 1.0
|
|
60
|
+
"""
|
|
61
|
+
assert (
|
|
62
|
+
params.shape[0] == samples
|
|
63
|
+
), "Number of samples does not match number of parameters"
|
|
64
|
+
|
|
65
|
+
mw_measure = np.zeros(samples, dtype=complex)
|
|
66
|
+
qb = list(range(n_qubits))
|
|
67
|
+
|
|
68
|
+
for i in range(samples):
|
|
69
|
+
# implicitly set input to none in case it's not needed
|
|
70
|
+
kwargs.setdefault("inputs", None)
|
|
71
|
+
# explicitly set execution type because everything else won't work
|
|
72
|
+
U = evaluate(params=params[i], execution_type="density", **kwargs)
|
|
73
|
+
|
|
74
|
+
entropy = 0
|
|
75
|
+
|
|
76
|
+
for j in range(n_qubits):
|
|
77
|
+
density = qml.math.partial_trace(U, qb[:j] + qb[j + 1 :])
|
|
78
|
+
entropy += np.trace((density @ density).real)
|
|
79
|
+
|
|
80
|
+
mw_measure[i] = 1 - entropy / n_qubits
|
|
81
|
+
|
|
82
|
+
mw = 2 * np.sum(mw_measure.real) / samples
|
|
83
|
+
|
|
84
|
+
# catch floating point errors
|
|
85
|
+
return min(max(mw, 0.0), 1.0)
|
|
86
|
+
|
|
87
|
+
if n_samples > 0:
|
|
88
|
+
assert seed is not None, "Seed must be provided when samples > 0"
|
|
89
|
+
# TODO: maybe switch to JAX rng
|
|
90
|
+
rng = np.random.default_rng(seed)
|
|
91
|
+
params = rng.uniform(0, 2 * np.pi, size=(n_samples, *model.params.shape))
|
|
92
|
+
else:
|
|
93
|
+
if seed is not None:
|
|
94
|
+
log.warning("Seed is ignored when samples is 0")
|
|
95
|
+
n_samples = 1
|
|
96
|
+
params = model.params.reshape(1, *model.params.shape)
|
|
97
|
+
|
|
98
|
+
entangling_capability = _meyer_wallach(
|
|
99
|
+
evaluate=model,
|
|
100
|
+
n_qubits=model.n_qubits,
|
|
101
|
+
samples=n_samples,
|
|
102
|
+
params=params,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return float(entangling_capability)
|