qmlkit 0.1.0__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.
- qmlkit/__init__.py +495 -0
- qmlkit/_aliases.py +135 -0
- qmlkit/algorithms/__init__.py +82 -0
- qmlkit/algorithms/adapt.py +297 -0
- qmlkit/algorithms/autoencoder.py +206 -0
- qmlkit/algorithms/chemistry.py +222 -0
- qmlkit/algorithms/clustering.py +149 -0
- qmlkit/algorithms/hamiltonians.py +143 -0
- qmlkit/algorithms/molecule.py +442 -0
- qmlkit/algorithms/qaoa.py +208 -0
- qmlkit/algorithms/rl.py +198 -0
- qmlkit/algorithms/vqe.py +198 -0
- qmlkit/ansatz/__init__.py +68 -0
- qmlkit/ansatz/blocks.py +348 -0
- qmlkit/ansatz/library.py +570 -0
- qmlkit/ansatz/reupload.py +168 -0
- qmlkit/baselines.py +604 -0
- qmlkit/budget.py +234 -0
- qmlkit/core/__init__.py +1 -0
- qmlkit/core/backends/__init__.py +22 -0
- qmlkit/core/backends/_sampling.py +43 -0
- qmlkit/core/backends/base.py +256 -0
- qmlkit/core/backends/cirq_backend.py +110 -0
- qmlkit/core/backends/cirq_density_backend.py +71 -0
- qmlkit/core/backends/noisy.py +86 -0
- qmlkit/core/backends/numpy_backend.py +276 -0
- qmlkit/core/backends/qiskit_aer_backend.py +79 -0
- qmlkit/core/backends/qiskit_backend.py +104 -0
- qmlkit/core/backends/registry.py +210 -0
- qmlkit/core/backends/spinqit_backend.py +233 -0
- qmlkit/core/backends/torch_backend.py +185 -0
- qmlkit/core/builder.py +189 -0
- qmlkit/core/execute.py +193 -0
- qmlkit/core/gates.py +243 -0
- qmlkit/core/ir.py +320 -0
- qmlkit/core/observables.py +269 -0
- qmlkit/datasets.py +178 -0
- qmlkit/diagnostics.py +719 -0
- qmlkit/draw.py +177 -0
- qmlkit/encoding/__init__.py +63 -0
- qmlkit/encoding/amplitude.py +178 -0
- qmlkit/encoding/angle.py +61 -0
- qmlkit/encoding/feature_maps.py +353 -0
- qmlkit/encoding/hamiltonian.py +206 -0
- qmlkit/encoding/pipeline.py +198 -0
- qmlkit/encoding/scaling.py +139 -0
- qmlkit/evaluate.py +686 -0
- qmlkit/fourier.py +124 -0
- qmlkit/generative.py +406 -0
- qmlkit/gradients/__init__.py +61 -0
- qmlkit/gradients/adjoint.py +138 -0
- qmlkit/gradients/batch.py +275 -0
- qmlkit/gradients/dispatch.py +247 -0
- qmlkit/gradients/hadamard.py +108 -0
- qmlkit/gradients/parameter_shift.py +142 -0
- qmlkit/gradients/rules.py +151 -0
- qmlkit/gradients/spsa.py +134 -0
- qmlkit/imbalance.py +335 -0
- qmlkit/info.py +153 -0
- qmlkit/interop.py +778 -0
- qmlkit/kernels/__init__.py +69 -0
- qmlkit/kernels/estimators.py +206 -0
- qmlkit/kernels/matrix.py +439 -0
- qmlkit/kernels/models.py +315 -0
- qmlkit/metrics.py +394 -0
- qmlkit/nn/__init__.py +18 -0
- qmlkit/nn/advanced.py +254 -0
- qmlkit/nn/layer.py +343 -0
- qmlkit/nn/losses.py +124 -0
- qmlkit/nn/models.py +245 -0
- qmlkit/optim.py +306 -0
- qmlkit/provenance.py +271 -0
- qmlkit/py.typed +0 -0
- qmlkit/search.py +561 -0
- qmlkit/shadows.py +117 -0
- qmlkit/utils/__init__.py +19 -0
- qmlkit/utils/errors.py +130 -0
- qmlkit/utils/shots.py +55 -0
- qmlkit-0.1.0.dist-info/METADATA +745 -0
- qmlkit-0.1.0.dist-info/RECORD +83 -0
- qmlkit-0.1.0.dist-info/WHEEL +4 -0
- qmlkit-0.1.0.dist-info/licenses/LICENSE +202 -0
- qmlkit-0.1.0.dist-info/licenses/NOTICE +4 -0
qmlkit/algorithms/rl.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Quantum policy gradient — a variational circuit as an RL policy.
|
|
2
|
+
|
|
3
|
+
REINFORCE, with the policy replaced by a circuit: encode the observation, measure a
|
|
4
|
+
few observables, softmax them into action probabilities, and push up the log-
|
|
5
|
+
probability of whatever earned reward.
|
|
6
|
+
|
|
7
|
+
policy = QuantumPolicy(n_observations=2, n_actions=2)
|
|
8
|
+
result = train_reinforce(policy, ContextualBandit(seed=0), n_episodes=200, seed=0)
|
|
9
|
+
|
|
10
|
+
The environment is an argument with a three-method protocol (``reset``, ``step``,
|
|
11
|
+
``n_observations``/``n_actions``), so a Gym environment wraps in a few lines and
|
|
12
|
+
nothing here needs Gym installed. The ansatz and feature map are arguments too, as
|
|
13
|
+
everywhere else.
|
|
14
|
+
|
|
15
|
+
The gradient is the ordinary policy-gradient one — ``grad log pi(a|s) * return`` —
|
|
16
|
+
computed through :func:`qmlkit.grad`, so it is exact rather than finite-differenced.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from collections.abc import Sequence
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from typing import Any, Protocol
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
import numpy.typing as npt
|
|
27
|
+
|
|
28
|
+
from qmlkit.ansatz.library import Ansatz, hardware_efficient
|
|
29
|
+
from qmlkit.core.execute import BackendLike, expval
|
|
30
|
+
from qmlkit.core.observables import Observable, Z
|
|
31
|
+
from qmlkit.encoding.feature_maps import AngleFeatureMap, FeatureMap
|
|
32
|
+
from qmlkit.gradients.dispatch import grad
|
|
33
|
+
|
|
34
|
+
__all__ = ["QuantumPolicy", "ContextualBandit", "train_reinforce", "ReinforceResult", "Environment"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Environment(Protocol):
|
|
38
|
+
"""The three things an RL loop needs. Deliberately not Gym."""
|
|
39
|
+
|
|
40
|
+
n_observations: int
|
|
41
|
+
n_actions: int
|
|
42
|
+
|
|
43
|
+
def reset(self) -> npt.NDArray[Any]: ...
|
|
44
|
+
|
|
45
|
+
def step(self, action: int) -> tuple[npt.NDArray[Any], float, bool]: ...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ContextualBandit:
|
|
49
|
+
"""A tiny environment with a known optimal policy, so training is checkable.
|
|
50
|
+
|
|
51
|
+
The observation is a random vector; the correct action is the sign of its first
|
|
52
|
+
coordinate. One step per episode, reward 1 for right and 0 for wrong — so the
|
|
53
|
+
optimal return is exactly 1.0 and "did it learn" has an unambiguous answer.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
n_observations = 2
|
|
57
|
+
n_actions = 2
|
|
58
|
+
|
|
59
|
+
def __init__(self, seed: int | None = None) -> None:
|
|
60
|
+
self._rng = np.random.default_rng(seed)
|
|
61
|
+
self._state: npt.NDArray[Any] = np.zeros(self.n_observations)
|
|
62
|
+
|
|
63
|
+
def reset(self) -> npt.NDArray[Any]:
|
|
64
|
+
self._state = self._rng.uniform(-1.0, 1.0, self.n_observations)
|
|
65
|
+
return self._state
|
|
66
|
+
|
|
67
|
+
def step(self, action: int) -> tuple[npt.NDArray[Any], float, bool]:
|
|
68
|
+
correct = int(self._state[0] > 0)
|
|
69
|
+
return self._state, float(action == correct), True
|
|
70
|
+
|
|
71
|
+
def optimal_return(self) -> float:
|
|
72
|
+
return 1.0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class QuantumPolicy:
|
|
76
|
+
"""A circuit policy: observation in, action probabilities out."""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
n_observations: int,
|
|
81
|
+
n_actions: int,
|
|
82
|
+
feature_map: FeatureMap | None = None,
|
|
83
|
+
ansatz: Ansatz | None = None,
|
|
84
|
+
observables: Sequence[Observable] | None = None,
|
|
85
|
+
n_layers: int = 2,
|
|
86
|
+
beta: float = 2.0,
|
|
87
|
+
backend: BackendLike = None,
|
|
88
|
+
seed: int | None = None,
|
|
89
|
+
) -> None:
|
|
90
|
+
n_qubits = max(n_observations, n_actions)
|
|
91
|
+
self.n_observations = n_observations
|
|
92
|
+
self.n_actions = n_actions
|
|
93
|
+
self.n_qubits = n_qubits
|
|
94
|
+
self.feature_map = feature_map or AngleFeatureMap(n_qubits, entangle=n_qubits > 1)
|
|
95
|
+
self.ansatz = ansatz or hardware_efficient(n_qubits, n_layers)
|
|
96
|
+
self.observables = list(observables) if observables else [Z(a) for a in range(n_actions)]
|
|
97
|
+
self.beta = beta # softmax inverse temperature
|
|
98
|
+
self.backend = backend
|
|
99
|
+
# Seeded from the argument, not from a constant: with seed baked in, every
|
|
100
|
+
# QuantumPolicy started from the same point and "average over seeds" was
|
|
101
|
+
# impossible to express.
|
|
102
|
+
self.theta = self.ansatz.init("small", seed=seed)
|
|
103
|
+
self._spec = self.feature_map.build_parametric().compose(self.ansatz.build())
|
|
104
|
+
self._n_inputs = self.feature_map.n_angles
|
|
105
|
+
|
|
106
|
+
def _full(self, observation: npt.NDArray[Any], theta: npt.NDArray[Any]) -> npt.NDArray[Any]:
|
|
107
|
+
padded = np.zeros(self.n_qubits)
|
|
108
|
+
padded[: self.n_observations] = np.asarray(observation, dtype=float).ravel()
|
|
109
|
+
return np.concatenate([self.feature_map.angles(padded), theta])
|
|
110
|
+
|
|
111
|
+
def logits(
|
|
112
|
+
self, observation: npt.NDArray[Any], theta: npt.NDArray[Any] | None = None
|
|
113
|
+
) -> npt.NDArray[Any]:
|
|
114
|
+
full = self._full(observation, self.theta if theta is None else theta)
|
|
115
|
+
return np.array(
|
|
116
|
+
[expval(self._spec, o, theta=full, backend=self.backend) for o in self.observables]
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def probabilities(
|
|
120
|
+
self, observation: npt.NDArray[Any], theta: npt.NDArray[Any] | None = None
|
|
121
|
+
) -> npt.NDArray[Any]:
|
|
122
|
+
z = self.beta * self.logits(observation, theta)
|
|
123
|
+
z = z - z.max()
|
|
124
|
+
e = np.exp(z)
|
|
125
|
+
return e / e.sum()
|
|
126
|
+
|
|
127
|
+
def sample(self, observation: npt.NDArray[Any], rng: np.random.Generator) -> int:
|
|
128
|
+
return int(rng.choice(self.n_actions, p=self.probabilities(observation)))
|
|
129
|
+
|
|
130
|
+
def grad_log_prob(self, observation: npt.NDArray[Any], action: int) -> npt.NDArray[Any]:
|
|
131
|
+
r"""``d/dtheta log pi(a|s)``.
|
|
132
|
+
|
|
133
|
+
For a softmax over measured observables this is
|
|
134
|
+
``beta * (dO_a/dtheta - sum_b pi_b dO_b/dtheta)`` — one exact circuit gradient
|
|
135
|
+
per action, no finite differences anywhere.
|
|
136
|
+
"""
|
|
137
|
+
full = self._full(observation, self.theta)
|
|
138
|
+
probs = self.probabilities(observation)
|
|
139
|
+
jac = np.stack(
|
|
140
|
+
[
|
|
141
|
+
grad(self._spec, full, o, backend=self.backend)[self._n_inputs :]
|
|
142
|
+
for o in self.observables
|
|
143
|
+
]
|
|
144
|
+
)
|
|
145
|
+
return self.beta * (jac[action] - probs @ jac)
|
|
146
|
+
|
|
147
|
+
def __repr__(self) -> str:
|
|
148
|
+
return (
|
|
149
|
+
f"QuantumPolicy(obs={self.n_observations}, actions={self.n_actions}, "
|
|
150
|
+
f"P={self.theta.size}, ansatz={self.ansatz.name!r})"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass
|
|
155
|
+
class ReinforceResult:
|
|
156
|
+
theta: npt.NDArray[Any]
|
|
157
|
+
returns: list[float] = field(default_factory=list)
|
|
158
|
+
|
|
159
|
+
def mean_return(self, last: int = 50) -> float:
|
|
160
|
+
return float(np.mean(self.returns[-last:])) if self.returns else 0.0
|
|
161
|
+
|
|
162
|
+
def __repr__(self) -> str:
|
|
163
|
+
return (
|
|
164
|
+
f"ReinforceResult(episodes={len(self.returns)}, "
|
|
165
|
+
f"final_mean_return={self.mean_return():.4f})"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def train_reinforce(
|
|
170
|
+
policy: QuantumPolicy,
|
|
171
|
+
env: Environment,
|
|
172
|
+
n_episodes: int = 200,
|
|
173
|
+
lr: float = 0.2,
|
|
174
|
+
baseline: bool = True,
|
|
175
|
+
seed: int | None = None,
|
|
176
|
+
) -> ReinforceResult:
|
|
177
|
+
"""REINFORCE with an optional moving-average baseline.
|
|
178
|
+
|
|
179
|
+
The baseline subtracts a running mean return before scaling the gradient. It does
|
|
180
|
+
not change what is being optimised, only the variance of the estimate — which for
|
|
181
|
+
a policy sampled one episode at a time is the thing that decides whether it
|
|
182
|
+
learns at all.
|
|
183
|
+
"""
|
|
184
|
+
rng = np.random.default_rng(seed)
|
|
185
|
+
returns: list[float] = []
|
|
186
|
+
running = 0.0
|
|
187
|
+
|
|
188
|
+
for episode in range(n_episodes):
|
|
189
|
+
observation = env.reset()
|
|
190
|
+
action = policy.sample(observation, rng)
|
|
191
|
+
_, reward, _ = env.step(action)
|
|
192
|
+
returns.append(float(reward))
|
|
193
|
+
|
|
194
|
+
advantage = reward - running if baseline else reward
|
|
195
|
+
policy.theta = policy.theta + lr * advantage * policy.grad_log_prob(observation, action)
|
|
196
|
+
running += (reward - running) / (episode + 1)
|
|
197
|
+
|
|
198
|
+
return ReinforceResult(theta=policy.theta, returns=returns)
|
qmlkit/algorithms/vqe.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""The variational quantum eigensolver.
|
|
2
|
+
|
|
3
|
+
VQE is a loop, not an architecture: prepare a parameterised state, measure an energy,
|
|
4
|
+
step downhill, repeat. Everything that makes one VQE differ from another — the
|
|
5
|
+
ansatz, the optimiser, the gradient method, the shot budget — is therefore an
|
|
6
|
+
argument, and the class itself is thin on purpose.
|
|
7
|
+
|
|
8
|
+
from qmlkit.algorithms import VQE, ising_hamiltonian
|
|
9
|
+
|
|
10
|
+
H = ising_hamiltonian(4, j=1.0, h=0.5)
|
|
11
|
+
result = VQE(H, n_qubits=4).run(seed=0)
|
|
12
|
+
print(result.energy, result.error_vs_exact)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections.abc import Callable, Sequence
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import numpy.typing as npt
|
|
23
|
+
|
|
24
|
+
from qmlkit.algorithms.hamiltonians import exact_ground_energy
|
|
25
|
+
from qmlkit.ansatz.library import Ansatz, hardware_efficient
|
|
26
|
+
from qmlkit.core.execute import BackendLike, expectation
|
|
27
|
+
from qmlkit.core.observables import Observable, observable_support
|
|
28
|
+
|
|
29
|
+
__all__ = ["VQE", "VQEResult", "OPTIMIZERS"]
|
|
30
|
+
|
|
31
|
+
#: ``fn(loss, theta0, **kwargs) -> (theta, history)`` — the shape every optimiser here
|
|
32
|
+
#: already has, so a custom one is a function rather than an adapter class.
|
|
33
|
+
Optimizer = Callable[..., "tuple[npt.NDArray[Any], list[float]]"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _rotosolve(loss, theta0, **kw): # type: ignore[no-untyped-def]
|
|
37
|
+
from qmlkit.optim import minimize_rotosolve
|
|
38
|
+
|
|
39
|
+
return minimize_rotosolve(loss, theta0, **kw)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _spsa(loss, theta0, **kw): # type: ignore[no-untyped-def]
|
|
43
|
+
from qmlkit.gradients.spsa import minimize_spsa
|
|
44
|
+
|
|
45
|
+
return minimize_spsa(loss, theta0, **kw)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _gradient_descent(loss, theta0, *, grad, n_steps=100, lr=0.1, **kw): # type: ignore[no-untyped-def]
|
|
49
|
+
theta = np.asarray(theta0, dtype=float).copy()
|
|
50
|
+
history = [float(loss(theta))]
|
|
51
|
+
for _ in range(n_steps):
|
|
52
|
+
theta = theta - lr * grad(theta)
|
|
53
|
+
history.append(float(loss(theta)))
|
|
54
|
+
return theta, history
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
OPTIMIZERS: dict[str, Optimizer] = {
|
|
58
|
+
"rotosolve": _rotosolve,
|
|
59
|
+
"spsa": _spsa,
|
|
60
|
+
"gradient-descent": _gradient_descent,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class VQEResult:
|
|
66
|
+
"""What a run produced, and how good it actually is."""
|
|
67
|
+
|
|
68
|
+
energy: float
|
|
69
|
+
theta: npt.NDArray[Any]
|
|
70
|
+
history: list[float] = field(default_factory=list)
|
|
71
|
+
exact: float | None = None
|
|
72
|
+
n_evaluations: int = 0
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def error_vs_exact(self) -> float | None:
|
|
76
|
+
"""Absolute error against dense diagonalisation, when that was computed."""
|
|
77
|
+
return None if self.exact is None else abs(self.energy - self.exact)
|
|
78
|
+
|
|
79
|
+
def __repr__(self) -> str:
|
|
80
|
+
tail = (
|
|
81
|
+
""
|
|
82
|
+
if self.exact is None
|
|
83
|
+
else f", exact={self.exact:.8f}, error={self.error_vs_exact:.2e}"
|
|
84
|
+
)
|
|
85
|
+
return f"VQEResult(energy={self.energy:.8f}{tail}, steps={len(self.history) - 1})"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class VQE:
|
|
89
|
+
"""Minimise ``<H>`` over a parameterised state.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
hamiltonian
|
|
94
|
+
Any observable. :mod:`qmlkit.algorithms.hamiltonians` has constructors.
|
|
95
|
+
ansatz
|
|
96
|
+
The trial state. Defaults to a hardware-efficient circuit wide enough for
|
|
97
|
+
the Hamiltonian's support — replaceable with anything, including one you
|
|
98
|
+
invented, because an ``Ansatz`` is the only contract.
|
|
99
|
+
optimizer
|
|
100
|
+
A name from :data:`OPTIMIZERS` or any ``fn(loss, theta0, **kw)`` returning
|
|
101
|
+
``(theta, history)``.
|
|
102
|
+
gradient
|
|
103
|
+
Passed through to :func:`qmlkit.grad`. Only consulted by gradient-based
|
|
104
|
+
optimisers; Rotosolve and SPSA never ask for one.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
hamiltonian: Observable,
|
|
110
|
+
ansatz: Ansatz | None = None,
|
|
111
|
+
n_qubits: int | None = None,
|
|
112
|
+
optimizer: str | Optimizer = "rotosolve",
|
|
113
|
+
gradient: str = "auto",
|
|
114
|
+
backend: BackendLike = None,
|
|
115
|
+
shots: int | None = None,
|
|
116
|
+
) -> None:
|
|
117
|
+
support = observable_support(hamiltonian)
|
|
118
|
+
width = n_qubits or (max(support) + 1 if support else 1)
|
|
119
|
+
if ansatz is not None and ansatz.n_qubits < width:
|
|
120
|
+
raise ValueError(
|
|
121
|
+
f"the ansatz has {ansatz.n_qubits} qubits but the Hamiltonian acts on {width}"
|
|
122
|
+
)
|
|
123
|
+
self.hamiltonian = hamiltonian
|
|
124
|
+
self.ansatz = ansatz or hardware_efficient(width, n_layers=2)
|
|
125
|
+
self.n_qubits = self.ansatz.n_qubits
|
|
126
|
+
self.optimizer = optimizer
|
|
127
|
+
self.gradient = gradient
|
|
128
|
+
self.backend = backend
|
|
129
|
+
self.shots = shots
|
|
130
|
+
self._spec = self.ansatz.build()
|
|
131
|
+
self.n_evaluations = 0
|
|
132
|
+
|
|
133
|
+
# ------------------------------------------------------------------ energy --
|
|
134
|
+
def energy(self, theta: Sequence[float]) -> float:
|
|
135
|
+
"""``<H>`` at these parameters."""
|
|
136
|
+
self.n_evaluations += 1
|
|
137
|
+
value = expectation(
|
|
138
|
+
self._spec,
|
|
139
|
+
self.hamiltonian,
|
|
140
|
+
theta=np.asarray(theta, dtype=float),
|
|
141
|
+
shots=self.shots,
|
|
142
|
+
backend=self.backend,
|
|
143
|
+
)
|
|
144
|
+
return float(value)
|
|
145
|
+
|
|
146
|
+
def gradient_of_energy(self, theta: Sequence[float]) -> npt.NDArray[Any]:
|
|
147
|
+
from qmlkit.gradients.dispatch import grad
|
|
148
|
+
|
|
149
|
+
return grad(
|
|
150
|
+
self._spec,
|
|
151
|
+
np.asarray(theta, dtype=float),
|
|
152
|
+
self.hamiltonian,
|
|
153
|
+
method=self.gradient,
|
|
154
|
+
backend=self.backend,
|
|
155
|
+
shots=self.shots,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# --------------------------------------------------------------------- run --
|
|
159
|
+
def run(
|
|
160
|
+
self,
|
|
161
|
+
theta0: Sequence[float] | None = None,
|
|
162
|
+
seed: int | None = None,
|
|
163
|
+
compare_exact: bool | None = None,
|
|
164
|
+
**optimizer_kwargs: Any,
|
|
165
|
+
) -> VQEResult:
|
|
166
|
+
"""Optimise, and report against the exact answer when that is affordable."""
|
|
167
|
+
start = (
|
|
168
|
+
np.asarray(theta0, dtype=float)
|
|
169
|
+
if theta0 is not None
|
|
170
|
+
else self.ansatz.init("small", seed=seed)
|
|
171
|
+
)
|
|
172
|
+
fn = OPTIMIZERS[self.optimizer] if isinstance(self.optimizer, str) else self.optimizer
|
|
173
|
+
if fn is _gradient_descent:
|
|
174
|
+
optimizer_kwargs.setdefault("grad", self.gradient_of_energy)
|
|
175
|
+
if fn is _spsa:
|
|
176
|
+
optimizer_kwargs.setdefault("seed", seed)
|
|
177
|
+
|
|
178
|
+
self.n_evaluations = 0
|
|
179
|
+
theta, history = fn(self.energy, start, **optimizer_kwargs)
|
|
180
|
+
|
|
181
|
+
# dense diagonalisation is exponential, so only offer it where it is cheap
|
|
182
|
+
if compare_exact is None:
|
|
183
|
+
compare_exact = self.n_qubits <= 12
|
|
184
|
+
exact = exact_ground_energy(self.hamiltonian, self.n_qubits) if compare_exact else None
|
|
185
|
+
|
|
186
|
+
return VQEResult(
|
|
187
|
+
energy=float(history[-1]),
|
|
188
|
+
theta=theta,
|
|
189
|
+
history=list(history),
|
|
190
|
+
exact=exact,
|
|
191
|
+
n_evaluations=self.n_evaluations,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def __repr__(self) -> str:
|
|
195
|
+
return (
|
|
196
|
+
f"VQE(n_qubits={self.n_qubits}, ansatz={self.ansatz.name!r}, "
|
|
197
|
+
f"P={self.ansatz.n_weights}, optimizer={self.optimizer!r})"
|
|
198
|
+
)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Ansatz: the trainable block, and the vocabulary for inventing new ones."""
|
|
2
|
+
|
|
3
|
+
from qmlkit.ansatz.blocks import (
|
|
4
|
+
Block,
|
|
5
|
+
BuildContext,
|
|
6
|
+
Custom,
|
|
7
|
+
EncodingLayer,
|
|
8
|
+
EntanglerLayer,
|
|
9
|
+
ParametricEntangler,
|
|
10
|
+
PoolLayer,
|
|
11
|
+
RotationLayer,
|
|
12
|
+
Sequential,
|
|
13
|
+
repeat,
|
|
14
|
+
share,
|
|
15
|
+
)
|
|
16
|
+
from qmlkit.ansatz.library import (
|
|
17
|
+
Ansatz,
|
|
18
|
+
basic_entangler,
|
|
19
|
+
conv_block,
|
|
20
|
+
get_ansatz,
|
|
21
|
+
hardware_efficient,
|
|
22
|
+
list_ansatze,
|
|
23
|
+
list_conv_filters,
|
|
24
|
+
mps_ansatz,
|
|
25
|
+
qaoa_ansatz,
|
|
26
|
+
qcnn_ansatz,
|
|
27
|
+
random_layers,
|
|
28
|
+
register_ansatz,
|
|
29
|
+
register_conv_filter,
|
|
30
|
+
simplified_two_design,
|
|
31
|
+
strongly_entangling,
|
|
32
|
+
tree_tensor_network,
|
|
33
|
+
two_local,
|
|
34
|
+
)
|
|
35
|
+
from qmlkit.ansatz.reupload import ReuploadModel, reupload
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"Ansatz",
|
|
39
|
+
"register_ansatz",
|
|
40
|
+
"get_ansatz",
|
|
41
|
+
"list_ansatze",
|
|
42
|
+
"Block",
|
|
43
|
+
"BuildContext",
|
|
44
|
+
"Sequential",
|
|
45
|
+
"RotationLayer",
|
|
46
|
+
"EntanglerLayer",
|
|
47
|
+
"ParametricEntangler",
|
|
48
|
+
"PoolLayer",
|
|
49
|
+
"EncodingLayer",
|
|
50
|
+
"Custom",
|
|
51
|
+
"repeat",
|
|
52
|
+
"share",
|
|
53
|
+
"hardware_efficient",
|
|
54
|
+
"strongly_entangling",
|
|
55
|
+
"simplified_two_design",
|
|
56
|
+
"tree_tensor_network",
|
|
57
|
+
"mps_ansatz",
|
|
58
|
+
"qcnn_ansatz",
|
|
59
|
+
"qaoa_ansatz",
|
|
60
|
+
"conv_block",
|
|
61
|
+
"list_conv_filters",
|
|
62
|
+
"register_conv_filter",
|
|
63
|
+
"reupload",
|
|
64
|
+
"ReuploadModel",
|
|
65
|
+
"basic_entangler",
|
|
66
|
+
"two_local",
|
|
67
|
+
"random_layers",
|
|
68
|
+
]
|