MethodLayer 1
Follow the steepest descent in the state's own geometry
Take the step that moves the state fastest, not the one that moves the parameters fastest. The two differ because equal changes in parameters do not make equal changes in the state, and the metric measuring that difference has to be estimated before every step.
A parameterised circuit family; an objective function of its parameters, evaluated only through estimates bought with a finite shot budget; a starting point; and a stopping rule — a tolerance, an iteration cap, or an exhausted budget.
A preparation routine for the state at the parameters the search stopped at, and the total number of objective evaluations it consumed. The routine is returned whether or not the search found a minimum; that it stopped is not evidence that it converged.
Same contract as the slot it fills.
This one, drawn
From Parameterised circuit family to State you can prepare
A circle is an object you are holding. This method is drawn heavier, opened into its own steps; the other lines between the same two ends are the alternatives recorded for the same slot. Circles are named on hover, and each one is a link.
Nothing drawn here has a recorded way through it that this figure leaves shut. See it on the map
What it fills
- Minimise the objective over the parameters
Search the parameters of a fixed circuit family for the ones that minimise a chosen objective, given that every evaluation of that objective is a noisy estimate someone paid shots for. The output is a routine that prepares one particular state — the family collapsed to a member.
When it applies
Stokes et al. state exactly what the step is taken with respect to: the optimization dynamics "is interpreted as moving in the steepest descent direction with respect to the Quantum Information Geometry, corresponding to the real part of the Quantum Geometric Tensor (QGT), also known as the Fubini-Study metric tensor". That tensor is the extra thing this method buys its better steps with, and the paper's own contribution is making it affordable rather than exact — "an efficient algorithm is presented for computing a block-diagonal approximation to the Fubini-Study metric tensor for parametrized quantum circuits". Block-diagonal is an approximation, and the abstract states no bound on what it costs in step quality, so none is quoted here.
Requires
Every step this method names moves its route along, so there is nothing it needs alongside them.
Example
given parametrized circuit U_L(theta) = V_L(theta_L) W_L ... V_1(theta_1) W_1
with theta = theta_1 (+) ... (+) theta_L in R^d split into L layers (Eq. 14)
layer l carrying m parameters with generators K_i, partial_i V_l = -i K_i V_l
fixed reference |0> and Hermitian observable H
objective L(theta) = (1/2) <psi_theta|H|psi_theta>, psi_theta = U_theta|0> (Eq. 9)
requires within every layer l, for distinct parameters i != j: partial_i K_j = 0
# the paper takes this as "the defining property of a layer" itself,
# not a general fact about circuits -- it is what forces the layer's
# generators to commute, [K_i, K_j] = 0 (Sec. 2.5)
# global optimization of L(theta) is impractical since it is nonconvex;
# this method only searches for a local optimum (Sec. 2.2)
# --- gradient, by parameter shift -------------------------------------------
grad_L(theta_t) <- parameter-shift evaluation of each of the d components (Sec. 3)
# 2d quantum evaluations total -- two shifted circuits per parameter
# --- block-diagonal Fubini-Study metric, one block per layer ----------------
for l = 1 .. L:
psi_l = U_[1:l](theta) |0> (Eq. 17)
rotate psi_l into the shared eigenbasis of {K_i : 1 <= i <= m} u
{K_i K_j : 1 <= i,j <= m} over that layer's own m parameters, and
measure all qubits in the Pauli-Z basis, once (Sec. 2.6, step 3a)
# one measurement suffices because every operator in that set
# commutes within the layer, by the requires clause above
# the paper's quoted saving -- naive count n(n+1)/2 collapsing to a
# single measurement -- is stated only for the family it narrows to
# "for simplicity of exposition": tensor products of single-qubit
# Pauli rotations, one per qubit, so that m = n there (Eqs. 29-32, Sec. 2.6)
# for a general layer the naive count is m(m+1)/2, not n(n+1)/2
classically recover <psi_l|K_i K_j|psi_l>, <psi_l|K_i|psi_l>, <psi_l|K_j|psi_l>
from that one measurement's statistics (Sec. 2.6, step 3a)
G(l)_ij = <psi_l|K_i K_j|psi_l> - <psi_l|K_i|psi_l> <psi_l|K_j|psi_l> (Eq. 27)
g(l)_ij = Re[G(l)_ij] = G(l)_ij
# K_i K_j is Hermitian here because [K_i,K_j] = 0, so nothing
# imaginary is being discarded to get the real part (Eq. 28)
assemble g(theta_t) as block-diagonal in g(1) .. g(L)
# cross-layer blocks are NOT estimated -- they are the paper's named
# approximation to the full metric, and no bound on the resulting
# step error is given (Eq. 18, abstract)
# --- step: solve the linear system, don't invert -----------------------------
solve g(theta_t) (theta_{t+1} - theta_t) = -eta * grad_L(theta_t) for theta_{t+1} (Eq. 12)
# equivalent in principle to theta_{t+1} = theta_t - eta * g+(theta_t) grad_L(theta_t)
# using the pseudo-inverse g+ of the metric (Eq. 13),
# but the paper avoids materializing g+ and solves (12) directly instead,
# "both more efficient and more numerically stable" (Sec. 2.2)
# the paper says nothing about g being singular at Eq. (13); its only
# "possible degeneracies" remark is about the classical Fisher
# Information Matrix, a different tensor (Sec. 2.1)
repeat until the stopping rule fires
return theta_t, and the evaluation count 2d + L spent per step (Sec. 3)
# a cheaper diagonal-only variant exists: replace each block g(l) above with
# just the per-parameter variances <K_i^2> - <K_i>^2 on its diagonal;
# the paper's own n = 11 run used this variant in place of the
# block-diagonal one purely to avoid the classical cost of computing the
# shared eigenbasis, not because the block-diagonal form had failed
# (Sec. 2.6 step 3b, Sec. 3)Cost, as the source states it
Stokes, Izaac, Killoran and Carleo: in their PennyLane implementation each optimization step requires quantum evaluations — parameter-shift evaluations for the gradient over parameters, plus one per parametrized layer for the block-diagonal Fubini-Study metric. Those are expectation values, each from shots ( in Figs. 1 to 3). The one-per-layer figure follows from the layer definition , which forces ; the naive count it replaces is specific to Eq. (29)'s single-qubit Pauli rotations. Classical work — the per-layer shared eigenbasis, the Eq. (12) linear solve — sits outside the count and dominated their runs: at they dropped the block-diagonal variant for the diagonal one, same evaluations, saving only classical time. Nothing bounds the step count; global optimization is called impractical, and convergence "in a small number of iterations" is measured — at (Fig. 1), at to (Figs. 2 to 3), block-diagonal only at — not proved.
Implementations
`QNGOptimizer`, the quantum natural gradient optimizer in PennyLane
This is the implementation the paper itself points at. Sec. 3 closes its description of the algorithm with "Finally, a Quantum Natural Gradient optimizer was implemented in PennyLane (see [35] for full source code)", and reference [35] is "Xanadu Quantum Technologies. PennyLane source code. https://github.com/XanaduAI/pennylane, 2019" — an address the GitHub API today resolves to `PennyLaneAI/pennylane`. Two of the four authors give the same affiliation, "Xanadu, 777 Bay Street, Toronto, Canada", and the module header reads "Copyright 2018-2021 Xanadu Quantum Technologies Inc." The class docstring carries the paper's own construction, with a slip at the first formula: its prose says the learning rate is "dependent on the pseudo-inverse of the Fubini-Study metric tensor", and the display beneath it then writes an ordinary inverse, , where the paper's Eq. (13) writes the pseudo-inverse and Sec. 2.2 declines to form it at all — "In practice, however, we avoid materializing the pseudo-inverse by directly solving the linear system (12) which is both more efficient and more numerically stable". Read the display as the pseudo-inverse its own surrounding prose names, which is also what the code computes. What follows it is the paper's: the per-layer block is with "the quantum state prior to the application of parametrized layer ", and the count is the paper's own: "Combining the quantum natural gradient optimizer with the analytic parameter-shift rule to optimize a variational circuit with parameters and layers, a total of quantum evaluations are required per optimization step." The citation given is the published version, "Quantum 4, 269", 2020, not the preprint. What the class accepts is narrow: "The QNG optimizer supports using a single :class:`~.QNode` as the objective function", and `step_and_cost` raises otherwise unless the caller supplies the metric — "The objective function must be encoded as a single QNode for the natural gradient to be automatically computed. Otherwise, metric_tensor_fn must be explicitly provided to the optimizer."
The constructor is `__init__(self, stepsize=0.01, approx="block-diag", lam=0)` and `step_and_cost` runs three stages. First the metric, behind the guard `if recompute_tensor or self.metric_tensor is None:` — so `recompute_tensor=False` reuses the previous step's tensor but cannot skip the first one, where `self.metric_tensor` is still `None`. What it builds is `metric_tensor(qnode, approx=self.approx)` from `pennylane/gradients/metric_tensor.py`, whose own signature defaults to `approx=None`. The block-diagonal restriction is therefore the optimizer's choice and not the transform's — and the transform's docstring says what the unrestricted branch costs beyond the paper's construction: "The block-diagonal part of the metric tensor always is computed using the covariance-based approach. If no approximation is selected, the off block-diagonal is computed using Hadamard tests", which "requires a device that has an additional wire as compared to the wires on which the original circuit was defined". The tensor then passes through `_reshape_and_regularize(tensor, lam)`, which flattens it to a square `(size, size)` matrix and adds `tensor += lam * math.eye(size, like=tensor)`; `lam` is a regularization the paper does not have, and it is off by default. Second the gradient, `self.compute_grad(qnode, args, kwargs, grad_fn=grad_fn)`, inherited from `GradientDescentOptimizer`. Third the step — and here the implementation departs from the paper it cites. `apply_grad` computes `update = pnp.linalg.pinv(mt[trained_index]) @ grad_flat`, materializing the pseudo-inverse the paper's Sec. 2.2 says it avoids, where Sec. 3 describes its own PennyLane optimizer as one that "updates the parameter values by classically solving the linear system (12)". The sibling class `QNGOptimizerQJIT`, in `pennylane/optimize/qng_qjit.py`, carries the same three defaults and takes the same route, `update_flat = math.linalg.pinv(mt) @ grad_flat`. One further approximation sits on top of the paper's block-diagonal one and is named in the docstring: "If the objective function takes multiple trainable arguments, ``QNGOptimizer`` applies the metric tensor for each argument individually. This means that "correlations" between parameters from different arguments are not taken into account."
No dataset and no hardware. The docstring's worked example is a two-parameter circuit whose gates both act on wire 0 — `qp.RX(params[0], wires=0)` then `qp.RY(params[1], wires=0)` — read out through an observable that spans two, `qp.expval(qp.X(0) + qp.X(1))`, on a device declaring three, `qp.device("default.qubit", wires=(0, 1, "aux"))`, from `init_params = np.array([0.011, 0.012])` at `eta = 0.01`. `tests/optimize/test_qng.py` stays at that scale: every device is `default.qubit`, five declared `wires=1`, one `wires=4`, three with no wire count given, and no device in the file sets `shots`, so every number the tests compare against is analytic rather than sampled.
`pennylane/optimize/qng.py` in https://github.com/PennyLaneAI/pennylane — Python, Apache License 2.0, header "Copyright 2018-2021 Xanadu Quantum Technologies Inc.", module docstring "Quantum natural gradient optimizer". The file is 368 lines and holds `class QNGOptimizer(GradientDescentOptimizer)` together with four module-level helpers, `_reshape_and_regularize`, `_flatten_np`, `_unflatten_np_dispatch` and its thin wrapper `_unflatten_np`. Read at tag `v0.45.1`, the release published 2026-06-26; that tag's copy of the file is byte-identical to `master` as of 2026-08-26. The documented entry point is `qp.QNGOptimizer`, used as `opt.step(circuit, init_params)` or `opt.step_and_cost(...)`, and the metric it calls into is `qp.metric_tensor` from `pennylane/gradients/metric_tensor.py` (882 lines at the same tag). The `jax.jit`-compatible variant the docstring points to, `QNGOptimizerQJIT`, is a separate 253-line file, `pennylane/optimize/qng_qjit.py`, and is not a subclass of this one.
The module reports no figures of its own; what is pinned is in `tests/optimize/test_qng.py`, 459 lines. `test_initialization_default` fixes the three defaults — `assert opt.stepsize == 0.01`, `assert opt.approx == "block-diag"`, `assert opt.lam == 0` — plus `assert opt.metric_tensor is None` before any step. `test_qubit_rotation` runs a two-gate one-qubit circuit, `qp.RX(params[0], wires=0)` then `qp.RY(params[1], wires=0)`, against `qp.expval(qp.PauliZ(0))` at `eta = 0.2` from `init_params = np.array([0.011, 0.012])` for `num_steps = 15`, and checks all three parts each step: the metric against the closed form `np.diag([0.25, (np.cos(theta[0]) ** 2) / 4])`, the update against `eta * sp.linalg.pinvh(exp) @ gradient(theta)` with the gradient supplied analytically, and at the end `assert np.allclose(circuit(theta), -1)`. Two further tests isolate the attributes the paper does not have, and neither runs that circuit: both prepend a fixed, non-trainable `qp.RY(eta, wires=0)` to the two trainable rotations, which is what gives the metric an off-diagonal to argue about at all — on the two-gate circuit above the file's own assertion is that the metric is exactly diagonal. `test_no_approx` sets `eta = 0.7` and `params = np.array([0.11, 0.412])`, checks the unrestricted tensor against a hand-derived `exp_mt` with `assert np.allclose(opt.metric_tensor, exp_mt)`, then asserts the default run keeps only its diagonal, `assert np.allclose(opt_with_approx.metric_tensor, np.diag(np.diag(exp_mt)))`, and that the two produce different parameters, `assert not np.allclose(new_params_no_approx, new_params_block_approx)`. Both trainable rotations sit in separate layers on the same wire here, so block-diagonal and diagonal coincide for this circuit; that is a property of the circuit, not an identity. `test_lam` takes the same three-gate circuit to `eta = np.pi` and `params = np.array([np.pi / 2, 0.412])`, where the test's own expression for `exp_mt` evaluates to and the second direction has no metric left, and shows the regularization is what rescues the step, in its own words: "# With regularization, y can be updated. Without regularization it can not." — `assert np.isclose(new_params_without_lam[1], y)` against `assert not np.isclose(new_params_with_lam[1], y, atol=1e-11, rtol=0.0)`, at `lam = 1e-9` and `stepsize=1.0`. Nothing in the file measures step counts, wall time or a comparison against another optimizer.
The QN-SPSA optimizer in qiskit-algorithms
`QNSPSA` exists to buy the natural-gradient step at a price that does not grow with the number of parameters, and its class docstring states the trade in one sentence: "Compared to natural gradients, which require expectation value evaluations for a circuit with parameters, QN-SPSA only requires and can therefore significantly speed up the natural gradient calculation by sacrificing some accuracy." It says how the geometry is reached — "This optimizer is based on SPSA but attempts to improve the convergence by sampling the **natural gradient** instead of the vanilla, first-order gradient. It achieves this by approximating Hessian of the ``fidelity`` of the ansatz circuit" — and it says the accuracy is recoverable rather than fixed: "The stochastic approximation of the natural gradient can be systematically improved by increasing the number of ``resamplings``. This leads to a Monte Carlo-style convergence to the exact, analytic value." The it measures itself against is the cost of the full tensor, which is the figure the cited paper's own abstract quotes — "Computing the full QFIM for a model with parameters, however, is computationally expensive and generally requires function evaluations" — and not the per-step count recorded for Stokes et al.'s block-diagonal construction. It ships in `qiskit-algorithms`, a community package whose README carries the warning "**Qiskit Algorithms is no longer officially supported by IBM**."
`class QNSPSA(SPSA)` reaches the metric through the fidelity rather than through layer structure, so none of the paper-side conditions on this method — layers, , commuting generators — appear anywhere in the file. `get_fidelity(circuit, sampler, ...)` is a static method that wraps `ComputeUncompute` and returns a handle to , and the optimizer takes that callable as its first constructor argument. Each sample, in `_point_sample(self, loss, x, eps, delta1, delta2)`, evaluates the loss at two points, `x + eps * delta1` and `x - eps * delta1`, and the fidelity at four pairs — `(x, x + eps * delta1)`, `(x, x - eps * delta1)`, `(x, x + eps * (delta1 + delta2))`, `(x, x + eps * (-delta1 + delta2))` — then charges all six at once, `self._nfev += 6`. The gradient is the ordinary two-point SPSA estimate, `(loss_values[0] - loss_values[1]) / (2 * eps) * delta1`; the preconditioner is a second difference of the fidelity, `diff / (2 * eps**2)` after `fidelity_values[2] - fidelity_values[0]` less `fidelity_values[3] - fidelity_values[1]`, turned into a symmetrized rank-one matrix, `hessian_estimate = -0.5 * diff * (rank_one + rank_one.T) / 2` with `rank_one = np.outer(delta1, delta2)`, whose sign the code explains in place — "# -0.5 factor comes from the fact that we need -0.5 * fidelity". The constructor then hands the parent class a fixed configuration rather than a choice: `second_order=True` always, and `trust_region=False` with the reason written beside it, "# trust region *must* be false for natural gradients to work". The two `settings` keys those correspond to are removed from the serialized dictionary, `settings.pop("trust_region")` and `settings.pop("second_order")`. One default is inverted relative to the parent: `blocking: bool = True` here, where `SPSA` leaves it `False`, so a QN-SPSA run rejects non-improving steps unless told not to, at the cost of a further loss evaluation per iteration. Everything else — the power-series gains, `resamplings`, `perturbation_dims`, the `regularization` that forces the smoothed Hessian symmetric positive definite, `hessian_delay`, `lse_solver` — is inherited unchanged from `SPSA`.
Toy circuits in simulation; no dataset and no hardware anywhere in the module or its tests. The docstring example minimizes `Pauli("ZZ")` on `pauli_two_design(2, reps=1, seed=2)` through a `StatevectorEstimator`, from `np.random.random(ansatz.num_parameters)`, with `QNSPSA(fidelity, maxiter=300)` and the fidelity built from a bare `StatevectorSampler()`. The tests run the same family one qubit larger, `pauli_two_design(3, reps=1, seed=1)` against `SparsePauliOp("ZZI")`, with the fidelity sampled rather than exact — `QNSPSA.get_fidelity(circuit, sampler=StatevectorSampler(seed=12, default_shots=10_000))` in `test_qnspsa_max_evals_grouped` — and one test with the circuits removed altogether: a fidelity stub returning ones over the identity objective, `def objective(x): return x`. Those circuits are the ones `test/optimizers/test_spsa.py` already uses for plain `SPSA`; this class has no test module of its own.
`qiskit_algorithms/optimizers/qnspsa.py` in https://github.com/qiskit-community/qiskit-algorithms — Python, Apache License 2.0, header "(C) Copyright IBM 2021, 2026", module docstring "The QN-SPSA optimizer." It is 283 lines on `main` and imports its parent from the sibling file, `from .spsa import SPSA, CALLBACK, TERMINATIONCHECKER, _batch_evaluate`, and its fidelity machinery from `qiskit_algorithms.state_fidelities` as `ComputeUncompute`. Used as `from qiskit_algorithms.optimizers import QNSPSA`, then `QNSPSA.get_fidelity(ansatz, sampler)` and `QNSPSA(fidelity, maxiter=300).minimize(loss, x0=initial_point)`. The module is in the released package — `qiskit-algorithms` 0.4.0 on PyPI, uploaded 2025-08-29, Apache-2.0, Python >= 3.9 — and the file at tag `0.4.0` differs from `main` in three places only: the copyright year, whether `Callable` is imported from `collections.abc` or `typing`, and two blank lines.
The module reports no accuracy figures of its own and has no test file of its own either: every assertion about it sits in `test/optimizers/test_spsa.py`, 267 lines, beside the `SPSA` tests. Four of that file's ten test methods reach this class. The largest numbers are in the shared one: `test_pauli_two_design`'s `"qnspsa"` arm runs `pauli_two_design(3, reps=1, seed=1)` against `SparsePauliOp("ZZI")` for `maxiter = 100` under `{"maxiter": 100, "blocking": True, "allowed_increase": 0}` with `regularization = 0.001`, `learning_rate = 0.05` and `perturbation = 0.05`, and asserts an accuracy floor and a price together — `self.assertLess(result.fun, -0.95)` and `expected_nfev = settings["maxiter"] * 7 + 1`, which is 701 evaluations. The three QN-SPSA-only tests all sit at `maxiter = 1`. `test_qnspsa_max_evals_grouped` pins a value, `self.assertAlmostEqual(result.fun[0], 0.473, places=3)`, and that same law's smallest case, `expected_nfev = 8 # 7 * maxiter + 1`, under `set_max_evals_grouped(50)`. `test_point_sample` reaches `expected_nfev = 8` again with the circuits stripped out entirely — the fidelity stub, the identity objective, a scalar `initial_point = 1.0` and a perturbation generator yielding 1 — so the count is a property of the code path rather than of the circuit. `test_qnspsa_fidelity_primitives` checks only that the fidelity handle is self-consistent, `fidelity(initial_point, initial_point)` against `self.assertAlmostEqual(result[0], 1)` on `pauli_two_design(2, reps=1, seed=2)`. Of the seven evaluations an iteration costs, the six this file is responsible for are the ones `_point_sample` charges at `self._nfev += 6`; the rest of the accounting is `spsa.py`'s. No test in the file compares QN-SPSA against an optimizer from outside the SPSA family, and none runs on hardware.
The "Accelerating VQEs with quantum natural gradient" PennyLane demonstration
A runnable tutorial that puts `QNGOptimizer` and `GradientDescentOptimizer` on the same problem from the same starting point and reports what each spends. Its own statement of purpose is "This tutorial showcases how one can apply quantum natural gradients (QNG) to accelerate the optimization step of the Variational Quantum Eigensolver (VQE) algorithm", and it does so twice — "We will implement two small examples: estimating the ground state energy of a single-qubit VQE problem, which we can visualize using the Bloch sphere, and the hydrogen molecule." The comparison is deliberately controlled: "To perform a fair comparison, we fix the initial parameters for the two optimizers." It carries its own caveat about what the step counts do not say — "While using QNG may help accelerate the VQE algorithm in terms of optimization steps, each QNG step is more costly than its vanilla gradient descent counterpart due to a greater number of calls to the quantum computer that are needed to compute the Fubini-Study metric tensor" — and about how far the evidence reaches: "While further benchmark studies are needed to better understand the advantages of quantum natural gradient, preliminary studies such as this tutorial show the potentials of the method."
The single-qubit arm minimizes , built in three lines as `coeffs = [1, 1]`, `obs = [qp.PauliX(0), qp.PauliZ(0)]` and `H = qp.Hamiltonian(coeffs, obs)`, over `qp.RX(params[0], wires=wires)` then `qp.RY(params[1], wires=wires)` on a one-wire `default.qubit`, from `init_params = np.array([3.97507603, 3.00854038], requires_grad=True)`, with `max_iterations = 500`, `conv_tol = 1e-06` and `step_size = 0.01` shared by both arms; the optimizers are `qp.GradientDescentOptimizer(stepsize=step_size)` and `qp.QNGOptimizer(stepsize=step_size, approx="block-diag")`, and the loop stops on `conv = np.abs(energy - prev_energy)` falling to `conv_tol`. The hydrogen arm keeps the same skeleton and changes four things: the ansatz is `qp.BasisState(hf_state, wires=[0, 1, 2, 3])` followed by `RZ`, `RY`, `RZ` on each of the four wires and then `CNOT` on `[2, 3]`, `[2, 0]` and `[3, 1]`, giving twelve parameters drawn as `np.random.uniform(low=0, high=2 * np.pi, size=12, requires_grad=True)` under `np.random.seed(0)`; the step size is 0.5; the QNG optimizer is constructed with the regularization on, `qp.QNGOptimizer(step_size, lam=0.001, approx="block-diag")`; and the Hamiltonian is rebuilt with `requires_grad=False` coefficients before the QNG run, which the text explains as "We also need to make the Hamiltonian coefficients non-differentiable by setting ``requires_grad=False``." A third experiment is described but not coded: ten random initializations at a fixed two hundred iterations, presented as a figure with the note "We show the result of this test below (after pre-computing)", so the file contains neither its loop nor its numbers.
at bond length 0.7, loaded rather than built — `dataset = qp.data.load('qchem', molname="H2", bondlength=0.7)[0]` — from which the demo takes both the Hamiltonian and the reference energy, `exact_value = dataset.fci_energy`, which the file records inline as `# -1.1361895496530567`. The register is initialized to the Hartree-Fock state, `hf_state = np.array([1, 1, 0, 0], requires_grad=False)`, described as encoding "the Hartree-Fock state of the hydrogen molecule described in the minimal basis", on a four-wire `default.qubit`. The single-qubit arm has no dataset at all: its Hamiltonian is two Pauli terms with unit coefficients, and its contour plot reads a pre-computed grid from `vqe_qng/param_landscape.npy` rather than evaluating one.
`demonstrations_v2/tutorial_vqe_qng/demo.py`, 472 lines, in the PennyLane demonstrations repository — Python, Apache License 2.0. The repository is https://github.com/PennyLaneAI/demos; the raw path under the older name `PennyLaneAI/qml` still resolves by redirect, and the GitHub API reports the current `full_name` as `PennyLaneAI/demos`, created 2019-10-09. The demo's own `metadata.json` gives the title "Accelerating VQEs with quantum natural gradient", the author usernames `mli`, `lbozanic` and `ssim`, `"dateOfPublication": "2020-11-06T00:00:00+00:00"` and `"dateOfLastModification": "2026-05-28T00:00:00+00:00"`, and lists three references — the Stokes paper, Yamamoto's arXiv:1909.05074 and the Peruzzo photonic-processor paper. The rendered page at https://pennylane.ai/qml/demos/tutorial_vqe_qng names Maggie Li, Lana Bozanic and Sukin Sim, and carries the same two dates in its structured data (`"datePublished":"2020-11-06T00:00:00+00:00"`, `"dateModified":"2026-05-28T00:00:00+00:00"`) while its visible byline renders each one day earlier: "Published: November 05, 2020. Last updated: May 27, 2026." The only dependencies the file imports are `matplotlib.pyplot`, `pennylane` and `pennylane.numpy`; no PennyLane version is pinned in the demo directory or stated on the page.
The source file hard-codes no output — every figure below is printed by the run and read off the rendered page. The single-qubit problem's Hamiltonian is with unit coefficients, whose eigenvalues are , so its exact ground energy is ; the demo never prints that number, and it is stated here as arithmetic on the Hamiltonian the file builds. Against it, gradient descent ends at "Final value of the energy = -1.41365468 Ha" with "Number of iterations = 499", the last index of its five-hundred-step budget, and its last printed diagnostic is "Iteration = 480, Energy = -1.41325360 Ha, Convergence parameter = 0.00002772 Ha" — still about twenty-eight times `conv_tol = 1e-06`. Quantum natural gradient ends at "Final value of the energy = -1.41420585 Ha" with "Number of iterations = 117". On both arms meet the tolerance and the gap is in the count and in the accuracy: gradient descent takes 130 iterations to "Final value of the ground-state energy = -1.13616408 Ha", which the run itself scores as "Accuracy with respect to the FCI energy: 0.00002547 Ha (0.01598211 kcal/mol)", against quantum natural gradient's 17 iterations to -1.13618947 Ha and "Accuracy with respect to the FCI energy: 0.00000008 Ha (0.00004853 kcal/mol)". The demo's own reading of the pair is "We see that by employing quantum natural gradients, it takes fewer steps to reach a ground state estimate and the optimized energy achieved by the optimizer is lower than that obtained using vanilla gradient descent." No count of quantum evaluations, and no wall time, is reported for either arm.
What it needs
Nobody has taken this apart yet. That is a gap in this graph, not a claim that the method has no parts.
Other ways to fill the same slot
Different approaches
- Conditional-value-at-risk objective
Change what the classical loop is minimising rather than how it minimises. Instead of averaging every measurement outcome into an expectation value, keep only the best tail of them and average that — which is defensible precisely when the answer is a single good bitstring rather than a physical average.
- Analytic-gradient parameter search
Get the gradient of the objective exactly, rather than by finite differences, by running the same circuit again at shifted parameter values. The direction is then not an estimate of a slope taken from two noisy numbers; it is the slope, estimated to whatever precision the shots allow.
- Minimise the energy variance
Minimise how much the energy fluctuates rather than the energy itself. Any eigenstate has zero variance, so the objective's own value tells you whether you have arrived — which the energy never does, since a low number is only low relative to a minimum nobody knows.
- Grow the circuit a layer at a time while training it
Do not settle the circuit before optimising it. Start shallow, train what is there, then hold most of it fixed and add the next layer on top — so every step of the search runs on a shallow circuit with few free parameters, which is where a gradient is still large enough to follow.
- Simultaneous-perturbation optimization
Perturb every parameter at once, in one random direction, and take the difference of two objective evaluations as the gradient estimate. The estimate is bad in any single round and unbiased across rounds, so the cost of a step stops growing with the number of parameters.
In the Atlas
- Quantum natural-gradient VQE
The Fubini–Study metric preconditions parameter updates according to circuit-state geometry.