qml-essentials 0.1.13__tar.gz
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-0.1.13/LICENSE +7 -0
- qml_essentials-0.1.13/PKG-INFO +29 -0
- qml_essentials-0.1.13/README.md +16 -0
- qml_essentials-0.1.13/pyproject.toml +25 -0
- qml_essentials-0.1.13/qml_essentials/__init__.py +0 -0
- qml_essentials-0.1.13/qml_essentials/ansaetze.py +485 -0
- qml_essentials-0.1.13/qml_essentials/coefficients.py +37 -0
- qml_essentials-0.1.13/qml_essentials/entanglement.py +105 -0
- qml_essentials-0.1.13/qml_essentials/expressibility.py +248 -0
- qml_essentials-0.1.13/qml_essentials/model.py +539 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2024, Copyright Owner: Ostbayerische Technische Hochschule Regensburg (OTH Regensburg), Karlsruhe Institute of Technology (KIT). Author: Maja Franz (OTH), Melvin Strobl (KIT). Contact: melvin.strobl@kit.edu, Scientific Computing Center (SCC)
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: qml-essentials
|
|
3
|
+
Version: 0.1.13
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Melvin Strobl
|
|
6
|
+
Author-email: lc3267@kit.edu
|
|
7
|
+
Requires-Python: >=3.11,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Requires-Dist: pennylane (>=0.37.0,<0.38.0)
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# QML Essentials
|
|
15
|
+
|
|
16
|
+
[](https://ea3a0fbb-599f-4d83-86f1-0e71abe27513.ka.bw-cloud-instance.org/lc3267/quantum/) [](https://servers.stroblme.de/status/open) [](https://github.com/cirKITers/qml-essentials/actions/workflows/python-app.yml) [](https://github.com/cirKITers/qml-essentials/actions/workflows/pages/pages-build-deployment)
|
|
17
|
+
|
|
18
|
+
## :scroll: About
|
|
19
|
+
|
|
20
|
+
This repo contains some of the commonly used Ansaetze and coding stuff required for working with QML and Data-Reuploading models.
|
|
21
|
+
There are also dedicated classes to calculate entanglement and expressiblity of a provided model as well as its Fourier coefficients.
|
|
22
|
+
|
|
23
|
+
## :rocket: Getting Started
|
|
24
|
+
|
|
25
|
+
You can find installation instructions and documentation on the corresponding [Github Page](https://cirkiters.github.io/qml-essentials/).
|
|
26
|
+
|
|
27
|
+
## :construction: Contributing
|
|
28
|
+
|
|
29
|
+
Contributions are very welcome! See [Contribution Guidelines](https://github.com/cirKITers/qml-essentials/blob/main/CONTRIBUTING.md).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# QML Essentials
|
|
2
|
+
|
|
3
|
+
[](https://ea3a0fbb-599f-4d83-86f1-0e71abe27513.ka.bw-cloud-instance.org/lc3267/quantum/) [](https://servers.stroblme.de/status/open) [](https://github.com/cirKITers/qml-essentials/actions/workflows/python-app.yml) [](https://github.com/cirKITers/qml-essentials/actions/workflows/pages/pages-build-deployment)
|
|
4
|
+
|
|
5
|
+
## :scroll: About
|
|
6
|
+
|
|
7
|
+
This repo contains some of the commonly used Ansaetze and coding stuff required for working with QML and Data-Reuploading models.
|
|
8
|
+
There are also dedicated classes to calculate entanglement and expressiblity of a provided model as well as its Fourier coefficients.
|
|
9
|
+
|
|
10
|
+
## :rocket: Getting Started
|
|
11
|
+
|
|
12
|
+
You can find installation instructions and documentation on the corresponding [Github Page](https://cirkiters.github.io/qml-essentials/).
|
|
13
|
+
|
|
14
|
+
## :construction: Contributing
|
|
15
|
+
|
|
16
|
+
Contributions are very welcome! See [Contribution Guidelines](https://github.com/cirKITers/qml-essentials/blob/main/CONTRIBUTING.md).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "qml-essentials"
|
|
3
|
+
version = "0.1.13"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = ["Melvin Strobl <lc3267@kit.edu>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
|
|
8
|
+
[tool.poetry.dependencies]
|
|
9
|
+
python = "^3.11"
|
|
10
|
+
pennylane = "^0.37.0"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
[tool.poetry.group.dev.dependencies]
|
|
14
|
+
devpi-client = "^7.0.3"
|
|
15
|
+
black = "^24.4.2"
|
|
16
|
+
pytest = "^8.2.2"
|
|
17
|
+
flake8 = "^7.1.1"
|
|
18
|
+
mkdocs = "^1.6.0"
|
|
19
|
+
mkdocs-material = "^9.5.31"
|
|
20
|
+
mkdocstrings-python = "^1.10.7"
|
|
21
|
+
markdown-include = "^0.8.1"
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["poetry-core"]
|
|
25
|
+
build-backend = "poetry.core.masonry.api"
|
|
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
|