vqe-pennylane 0.2.2__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.
- qpe/__init__.py +77 -0
- qpe/__main__.py +233 -0
- qpe/core.py +317 -0
- qpe/hamiltonian.py +100 -0
- qpe/io_utils.py +132 -0
- qpe/noise.py +47 -0
- qpe/visualize.py +212 -0
- vqe/__init__.py +118 -0
- vqe/__main__.py +318 -0
- vqe/ansatz.py +420 -0
- vqe/core.py +907 -0
- vqe/engine.py +390 -0
- vqe/hamiltonian.py +260 -0
- vqe/io_utils.py +265 -0
- vqe/optimizer.py +58 -0
- vqe/ssvqe.py +271 -0
- vqe/visualize.py +308 -0
- vqe_pennylane-0.2.2.dist-info/METADATA +239 -0
- vqe_pennylane-0.2.2.dist-info/RECORD +28 -0
- vqe_pennylane-0.2.2.dist-info/WHEEL +5 -0
- vqe_pennylane-0.2.2.dist-info/entry_points.txt +3 -0
- vqe_pennylane-0.2.2.dist-info/licenses/LICENSE +21 -0
- vqe_pennylane-0.2.2.dist-info/top_level.txt +3 -0
- vqe_qpe_common/__init__.py +67 -0
- vqe_qpe_common/geometry.py +52 -0
- vqe_qpe_common/hamiltonian.py +58 -0
- vqe_qpe_common/molecules.py +107 -0
- vqe_qpe_common/plotting.py +167 -0
vqe/ansatz.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"""
|
|
2
|
+
vqe.ansatz
|
|
3
|
+
----------
|
|
4
|
+
Library of parameterized quantum circuits (ansatzes) used in the VQE workflow.
|
|
5
|
+
|
|
6
|
+
Includes
|
|
7
|
+
--------
|
|
8
|
+
- Simple 2-qubit toy ansatzes:
|
|
9
|
+
* TwoQubit-RY-CNOT
|
|
10
|
+
* Minimal
|
|
11
|
+
* RY-CZ
|
|
12
|
+
- Hardware-efficient template:
|
|
13
|
+
* StronglyEntanglingLayers
|
|
14
|
+
- Chemistry-inspired UCC family:
|
|
15
|
+
* UCC-S (singles only)
|
|
16
|
+
* UCC-D (doubles only)
|
|
17
|
+
* UCCSD (singles + doubles)
|
|
18
|
+
|
|
19
|
+
All chemistry ansatzes are constructed to mirror the legacy
|
|
20
|
+
`excitation_ansatz(..., excitation_type=...)` behaviour from the old notebooks,
|
|
21
|
+
while keeping the interface compatible with `vqe.engine.build_ansatz(...)`.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import pennylane as qml
|
|
27
|
+
from pennylane import numpy as np
|
|
28
|
+
from pennylane import qchem
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ================================================================
|
|
32
|
+
# BASIC / TOY ANSATZES
|
|
33
|
+
# ================================================================
|
|
34
|
+
def two_qubit_ry_cnot(params, wires):
|
|
35
|
+
"""
|
|
36
|
+
Scalable version of the original 2-qubit RY-CNOT motif.
|
|
37
|
+
|
|
38
|
+
Applies the motif to every adjacent pair of qubits:
|
|
39
|
+
RY(param) on wire i
|
|
40
|
+
CNOT(i → i+1)
|
|
41
|
+
RY(-param) on wire i+1
|
|
42
|
+
CNOT(i → i+1)
|
|
43
|
+
|
|
44
|
+
Number of parameters = len(wires) - 1.
|
|
45
|
+
"""
|
|
46
|
+
if len(params) != len(wires) - 1:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"TwoQubit-RY-CNOT expects {len(wires)-1} parameters for {len(wires)} wires, "
|
|
49
|
+
f"got {len(params)}."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
for i in range(len(wires) - 1):
|
|
53
|
+
w0, w1 = wires[i], wires[i + 1]
|
|
54
|
+
theta = params[i]
|
|
55
|
+
|
|
56
|
+
qml.RY(theta, wires=w0)
|
|
57
|
+
qml.CNOT(wires=[w0, w1])
|
|
58
|
+
qml.RY(-theta, wires=w1)
|
|
59
|
+
qml.CNOT(wires=[w0, w1])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def ry_cz(params, wires):
|
|
63
|
+
"""
|
|
64
|
+
Single-layer RY rotations followed by a CZ chain.
|
|
65
|
+
|
|
66
|
+
Matches the legacy `vqe_utils.ry_cz` used in H₂ optimizer / ansatz
|
|
67
|
+
comparison notebooks.
|
|
68
|
+
|
|
69
|
+
Shape:
|
|
70
|
+
params.shape == (len(wires),)
|
|
71
|
+
"""
|
|
72
|
+
if len(params) != len(wires):
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"RY-CZ expects one parameter per wire "
|
|
75
|
+
f"(got {len(params)} vs {len(wires)})"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Local rotations
|
|
79
|
+
for theta, w in zip(params, wires):
|
|
80
|
+
qml.RY(theta, wires=w)
|
|
81
|
+
|
|
82
|
+
# Entangling CZ chain
|
|
83
|
+
for w0, w1 in zip(wires[:-1], wires[1:]):
|
|
84
|
+
qml.CZ(wires=[w0, w1])
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def minimal(params, wires):
|
|
88
|
+
"""
|
|
89
|
+
Minimal 2-qubit circuit: RY rotation + CNOT.
|
|
90
|
+
|
|
91
|
+
Matches the legacy vqe_utils.minimal used in H₂ ansatz comparisons.
|
|
92
|
+
|
|
93
|
+
Behaviour:
|
|
94
|
+
- Uses the first two wires from the provided wire list.
|
|
95
|
+
- Requires at least 2 wires, but can be embedded in a larger register.
|
|
96
|
+
"""
|
|
97
|
+
if len(wires) < 2:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"Minimal ansatz expects at least 2 wires, got {len(wires)}"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
qml.RY(params[0], wires=wires[0])
|
|
103
|
+
qml.CNOT(wires=[wires[0], wires[1]])
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def hardware_efficient_ansatz(params, wires):
|
|
107
|
+
"""
|
|
108
|
+
Standard hardware-efficient ansatz using StronglyEntanglingLayers.
|
|
109
|
+
|
|
110
|
+
Convention:
|
|
111
|
+
params.shape = (n_layers, len(wires), 3)
|
|
112
|
+
"""
|
|
113
|
+
qml.templates.StronglyEntanglingLayers(params, wires=wires)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ================================================================
|
|
117
|
+
# UCC-STYLE CHEMISTRY ANSATZES
|
|
118
|
+
# ================================================================
|
|
119
|
+
def _ucc_cache_key(symbols, coordinates, basis: str):
|
|
120
|
+
"""Build a hashable cache key from molecular data."""
|
|
121
|
+
coords = np.array(coordinates, dtype=float).flatten().tolist()
|
|
122
|
+
return (tuple(symbols), tuple(coords), basis.upper())
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _build_ucc_data(symbols, coordinates, basis: str = "STO-3G"):
|
|
126
|
+
"""
|
|
127
|
+
Compute (singles, doubles, hf_state) for a given molecule and cache them.
|
|
128
|
+
|
|
129
|
+
This mirrors the legacy notebook logic based on:
|
|
130
|
+
- qchem.hf_state(electrons, spin_orbitals)
|
|
131
|
+
- qchem.excitations(electrons, spin_orbitals)
|
|
132
|
+
|
|
133
|
+
Notes
|
|
134
|
+
-----
|
|
135
|
+
* We intentionally keep the call signature minimal: (symbols, coordinates, basis)
|
|
136
|
+
so that `vqe.engine.build_ansatz(...)` can pass through the values it has
|
|
137
|
+
without needing charge / multiplicity.
|
|
138
|
+
* The cache lives on the function object so repeated calls are cheap.
|
|
139
|
+
"""
|
|
140
|
+
if symbols is None or coordinates is None:
|
|
141
|
+
raise ValueError(
|
|
142
|
+
"UCC ansatz requires symbols and coordinates. "
|
|
143
|
+
"Make sure build_hamiltonian(...) is used and passed through."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
key = _ucc_cache_key(symbols, coordinates, basis)
|
|
147
|
+
|
|
148
|
+
if not hasattr(_build_ucc_data, "_cache"):
|
|
149
|
+
_build_ucc_data._cache = {}
|
|
150
|
+
|
|
151
|
+
if key not in _build_ucc_data._cache:
|
|
152
|
+
try:
|
|
153
|
+
mol = qchem.Molecule(symbols, coordinates, charge=0, basis=basis)
|
|
154
|
+
except TypeError:
|
|
155
|
+
# Backwards-compat for older PennyLane versions without basis kwarg
|
|
156
|
+
mol = qchem.Molecule(symbols, coordinates, charge=0)
|
|
157
|
+
|
|
158
|
+
electrons = mol.n_electrons
|
|
159
|
+
spin_orbitals = 2 * mol.n_orbitals
|
|
160
|
+
|
|
161
|
+
singles, doubles = qchem.excitations(electrons, spin_orbitals)
|
|
162
|
+
hf_state = qchem.hf_state(electrons, spin_orbitals)
|
|
163
|
+
|
|
164
|
+
# Store as simple Python containers so that using them inside QNodes
|
|
165
|
+
# is cheap and avoids unnecessary object conversions.
|
|
166
|
+
singles = [tuple(ex) for ex in singles]
|
|
167
|
+
doubles = [tuple(ex) for ex in doubles]
|
|
168
|
+
hf_state = np.array(hf_state, dtype=int)
|
|
169
|
+
|
|
170
|
+
_build_ucc_data._cache[key] = (singles, doubles, hf_state)
|
|
171
|
+
|
|
172
|
+
return _build_ucc_data._cache[key]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _apply_ucc_layers(
|
|
176
|
+
params,
|
|
177
|
+
wires,
|
|
178
|
+
*,
|
|
179
|
+
singles,
|
|
180
|
+
doubles,
|
|
181
|
+
hf_state,
|
|
182
|
+
use_singles: bool,
|
|
183
|
+
use_doubles: bool,
|
|
184
|
+
):
|
|
185
|
+
"""
|
|
186
|
+
Shared helper to apply HF preparation + selected UCC excitation layers.
|
|
187
|
+
|
|
188
|
+
Parameter ordering convention (matches legacy notebooks):
|
|
189
|
+
- singles parameters first (if used)
|
|
190
|
+
- doubles parameters after that
|
|
191
|
+
"""
|
|
192
|
+
wires = list(wires)
|
|
193
|
+
num_wires = len(wires)
|
|
194
|
+
|
|
195
|
+
if len(hf_state) != num_wires:
|
|
196
|
+
raise ValueError(
|
|
197
|
+
f"HF state length ({len(hf_state)}) does not match number of wires "
|
|
198
|
+
f"({num_wires})."
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Prepare Hartree–Fock reference
|
|
202
|
+
qml.BasisState(hf_state, wires=wires)
|
|
203
|
+
|
|
204
|
+
# Determine how many parameters we expect
|
|
205
|
+
n_singles = len(singles) if use_singles else 0
|
|
206
|
+
n_doubles = len(doubles) if use_doubles else 0
|
|
207
|
+
expected = n_singles + n_doubles
|
|
208
|
+
|
|
209
|
+
if len(params) != expected:
|
|
210
|
+
raise ValueError(
|
|
211
|
+
f"UCC ansatz expects {expected} parameters, got {len(params)}."
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Apply singles
|
|
215
|
+
offset = 0
|
|
216
|
+
if use_singles:
|
|
217
|
+
for i, exc in enumerate(singles):
|
|
218
|
+
qml.SingleExcitation(params[offset + i], wires=list(exc))
|
|
219
|
+
offset += n_singles
|
|
220
|
+
|
|
221
|
+
# Apply doubles
|
|
222
|
+
if use_doubles:
|
|
223
|
+
for j, exc in enumerate(doubles):
|
|
224
|
+
qml.DoubleExcitation(params[offset + j], wires=list(exc))
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def uccsd_ansatz(params, wires, symbols=None, coordinates=None, basis: str = "STO-3G"):
|
|
228
|
+
"""
|
|
229
|
+
Unitary Coupled Cluster Singles and Doubles (UCCSD) ansatz.
|
|
230
|
+
|
|
231
|
+
Behaviour is chosen to match the legacy usage:
|
|
232
|
+
|
|
233
|
+
excitation_ansatz(
|
|
234
|
+
params,
|
|
235
|
+
wires=range(qubits),
|
|
236
|
+
hf_state=hf,
|
|
237
|
+
excitations=(singles, doubles),
|
|
238
|
+
excitation_type="both",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
Args
|
|
242
|
+
----
|
|
243
|
+
params
|
|
244
|
+
1D array of length len(singles) + len(doubles)
|
|
245
|
+
wires
|
|
246
|
+
Sequence of qubit wires
|
|
247
|
+
symbols, coordinates, basis
|
|
248
|
+
Molecular information (must be provided for chemistry simulations)
|
|
249
|
+
"""
|
|
250
|
+
singles, doubles, hf_state = _build_ucc_data(symbols, coordinates, basis=basis)
|
|
251
|
+
|
|
252
|
+
_apply_ucc_layers(
|
|
253
|
+
params,
|
|
254
|
+
wires=wires,
|
|
255
|
+
singles=singles,
|
|
256
|
+
doubles=doubles,
|
|
257
|
+
hf_state=hf_state,
|
|
258
|
+
use_singles=True,
|
|
259
|
+
use_doubles=True,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def uccd_ansatz(params, wires, symbols=None, coordinates=None, basis: str = "STO-3G"):
|
|
264
|
+
"""
|
|
265
|
+
UCC-D / UCCD: doubles-only UCC ansatz.
|
|
266
|
+
|
|
267
|
+
Designed to mirror the LiH notebook behaviour where we used
|
|
268
|
+
`excitation_ansatz(..., excitation_type="double")` with zero initial params.
|
|
269
|
+
|
|
270
|
+
Args
|
|
271
|
+
----
|
|
272
|
+
params
|
|
273
|
+
1D array of length len(doubles)
|
|
274
|
+
wires
|
|
275
|
+
Sequence of qubit wires
|
|
276
|
+
symbols, coordinates, basis
|
|
277
|
+
Molecular information
|
|
278
|
+
"""
|
|
279
|
+
singles, doubles, hf_state = _build_ucc_data(symbols, coordinates, basis=basis)
|
|
280
|
+
|
|
281
|
+
_apply_ucc_layers(
|
|
282
|
+
params,
|
|
283
|
+
wires=wires,
|
|
284
|
+
singles=singles,
|
|
285
|
+
doubles=doubles,
|
|
286
|
+
hf_state=hf_state,
|
|
287
|
+
use_singles=False,
|
|
288
|
+
use_doubles=True,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def uccs_ansatz(params, wires, symbols=None, coordinates=None, basis: str = "STO-3G"):
|
|
293
|
+
"""
|
|
294
|
+
UCC-S: singles-only UCC ansatz.
|
|
295
|
+
|
|
296
|
+
Matches the structure of UCCSD/UCCD and the legacy
|
|
297
|
+
`excitation_ansatz(..., excitation_type="single")` behaviour.
|
|
298
|
+
"""
|
|
299
|
+
singles, doubles, hf_state = _build_ucc_data(symbols, coordinates, basis=basis)
|
|
300
|
+
|
|
301
|
+
_apply_ucc_layers(
|
|
302
|
+
params,
|
|
303
|
+
wires=wires,
|
|
304
|
+
singles=singles,
|
|
305
|
+
doubles=doubles,
|
|
306
|
+
hf_state=hf_state,
|
|
307
|
+
use_singles=True,
|
|
308
|
+
use_doubles=False,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# ================================================================
|
|
313
|
+
# REGISTRY
|
|
314
|
+
# ================================================================
|
|
315
|
+
ANSATZES = {
|
|
316
|
+
"TwoQubit-RY-CNOT": two_qubit_ry_cnot,
|
|
317
|
+
"RY-CZ": ry_cz,
|
|
318
|
+
"Minimal": minimal,
|
|
319
|
+
"StronglyEntanglingLayers": hardware_efficient_ansatz,
|
|
320
|
+
"UCCSD": uccsd_ansatz,
|
|
321
|
+
"UCC-SD": uccsd_ansatz, # alias
|
|
322
|
+
"UCC-D": uccd_ansatz,
|
|
323
|
+
"UCCD": uccd_ansatz, # alias
|
|
324
|
+
"UCC-S": uccs_ansatz,
|
|
325
|
+
"UCCS": uccs_ansatz, # alias
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def get_ansatz(name: str):
|
|
330
|
+
"""
|
|
331
|
+
Return ansatz function by name.
|
|
332
|
+
|
|
333
|
+
This is the entry point used by `vqe.engine.build_ansatz(...)`.
|
|
334
|
+
"""
|
|
335
|
+
if name not in ANSATZES:
|
|
336
|
+
available = ", ".join(sorted(ANSATZES.keys()))
|
|
337
|
+
raise ValueError(f"Unknown ansatz '{name}'. Available: {available}")
|
|
338
|
+
return ANSATZES[name]
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# ================================================================
|
|
342
|
+
# PARAMETER INITIALISATION
|
|
343
|
+
# ================================================================
|
|
344
|
+
def init_params(
|
|
345
|
+
ansatz_name: str,
|
|
346
|
+
num_wires: int,
|
|
347
|
+
scale: float = 0.01,
|
|
348
|
+
requires_grad: bool = True,
|
|
349
|
+
symbols=None,
|
|
350
|
+
coordinates=None,
|
|
351
|
+
basis: str = "STO-3G",
|
|
352
|
+
seed: int = 0,
|
|
353
|
+
):
|
|
354
|
+
"""
|
|
355
|
+
Initialise variational parameters for a given ansatz.
|
|
356
|
+
|
|
357
|
+
Design choices (kept consistent with the legacy notebooks):
|
|
358
|
+
|
|
359
|
+
- TwoQubit-RY-CNOT / Minimal
|
|
360
|
+
* 1 parameter, small random normal ~ N(0, scale²)
|
|
361
|
+
|
|
362
|
+
- RY-CZ
|
|
363
|
+
* `num_wires` parameters, random normal ~ N(0, scale²)
|
|
364
|
+
|
|
365
|
+
- StronglyEntanglingLayers
|
|
366
|
+
* params.shape = (1, num_wires, 3), normal with width ~ π
|
|
367
|
+
|
|
368
|
+
- UCC family (UCC-S / UCC-D / UCCSD and aliases)
|
|
369
|
+
* **All zeros**, starting from θ = 0 as in the original chemistry notebooks.
|
|
370
|
+
The length of the vector is determined from the excitation lists.
|
|
371
|
+
|
|
372
|
+
Returns
|
|
373
|
+
-------
|
|
374
|
+
np.ndarray
|
|
375
|
+
Parameter array with `requires_grad=True`
|
|
376
|
+
"""
|
|
377
|
+
np.random.seed(seed)
|
|
378
|
+
|
|
379
|
+
# --- Toy ansatzes --------------------------------------------------------
|
|
380
|
+
if ansatz_name == "TwoQubit-RY-CNOT":
|
|
381
|
+
# scalable: one parameter per adjacent pair
|
|
382
|
+
if num_wires < 2:
|
|
383
|
+
raise ValueError("TwoQubit-RY-CNOT requires at least 2 wires.")
|
|
384
|
+
vals = scale * np.random.randn(num_wires - 1)
|
|
385
|
+
|
|
386
|
+
elif ansatz_name == "Minimal":
|
|
387
|
+
# still a 1-parameter global circuit
|
|
388
|
+
vals = scale * np.random.randn(1)
|
|
389
|
+
|
|
390
|
+
elif ansatz_name == "RY-CZ":
|
|
391
|
+
vals = scale * np.random.randn(num_wires)
|
|
392
|
+
|
|
393
|
+
# --- Chemistry ansatzes (UCC family) ------------------------------------
|
|
394
|
+
elif ansatz_name in ["UCCSD", "UCC-SD", "UCC-D", "UCCD", "UCC-S", "UCCS"]:
|
|
395
|
+
if symbols is None or coordinates is None:
|
|
396
|
+
raise ValueError(
|
|
397
|
+
f"Ansatz '{ansatz_name}' requires symbols/coordinates "
|
|
398
|
+
"to determine excitation count. Ensure you are using "
|
|
399
|
+
"build_hamiltonian(...) and engine.build_ansatz(...)."
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
singles, doubles, _ = _build_ucc_data(symbols, coordinates, basis=basis)
|
|
403
|
+
|
|
404
|
+
if ansatz_name in ["UCC-D", "UCCD"]:
|
|
405
|
+
# doubles-only
|
|
406
|
+
vals = np.zeros(len(doubles))
|
|
407
|
+
|
|
408
|
+
elif ansatz_name in ["UCC-S", "UCCS"]:
|
|
409
|
+
# singles-only
|
|
410
|
+
vals = np.zeros(len(singles))
|
|
411
|
+
|
|
412
|
+
else:
|
|
413
|
+
# UCCSD / UCC-SD: singles + doubles
|
|
414
|
+
vals = np.zeros(len(singles) + len(doubles))
|
|
415
|
+
|
|
416
|
+
else:
|
|
417
|
+
available = ", ".join(sorted(ANSATZES.keys()))
|
|
418
|
+
raise ValueError(f"Unknown ansatz '{ansatz_name}'. Available: {available}")
|
|
419
|
+
|
|
420
|
+
return np.array(vals, requires_grad=requires_grad)
|