Chapter 16 — Key Takeaways (PennyLane)

The PennyLane page. §16.6's barren plateau is the fact that governs whether any of this scales.

The QNode

dev = qml.device("default.qubit", wires=1)

@qml.qnode(dev)
def circuit(theta):
    qml.RY(theta, wires=0)
    return qml.expval(qml.PauliZ(0))

theta = pnp.array(0.7, requires_grad=True)
circuit(theta)            # 0.764842  = cos(0.7)
qml.grad(circuit)(theta)  # -0.644218 = -sin(0.7),  error 0.00e+00

No circuit object — the function body is the circuit. So parameters are plain Python arguments, and Chapter 8's ordering bug cannot occur here either (third distinct reason, after Cirq's dicts and Q#'s signatures).

The return declares the measurement: qml.expval · qml.probs · qml.sample · qml.state. Expectation values are the default because they are what you differentiate.

pennylane.numpy wraps NumPy to track gradients. wires not qubits — PennyLane also supports continuous-variable devices.

★ The parameter-shift rule

$$\frac{\partial f}{\partial\theta} = \frac{f(\theta + \pi/2) - f(\theta - \pi/2)}{2}$$

  manual parameter shift = -0.644217688
  qml.grad               = -0.644217688
  analytic               = -0.644217688      error 0.00e+00

EXACT, not an approximation. For $U(\theta)=e^{-i\theta P/2}$ with $P^2=I$, $f(\theta)=A\cos\theta+B\sin\theta$ exactly, so two samples a quarter-period apart give the derivative.

vs finite differences

      h        estimate         error
  1e-01   -0.643144528      1.07e-03
  1e-03   -0.644217580      1.07e-07
  1e-05   -0.644217687      1.98e-11    <- sweet spot
  1e-07   -0.644217688      2.91e-10
  1e-09   -0.644217679      8.04e-09    <- worse again

Finite differences have a sweet spot and degrade on both sides (truncation error above, floating-point cancellation below). The parameter-shift rule has no step size to tune — $\pi/2$ is a large shift, which is why it survives on noisy hardware.

⚠️ Does not apply to every gate. A generator with >2 distinct eigenvalues needs more shift points.

The cost: exactly $2n+1$

  parameters   executions   2n+1
           1            3      3
           4            9      9
          16           33     33
          32           65     65

$$\text{shots} = (2n+1) \times \text{iterations} \times \text{shots per circuit}$$

100 params × 200 iterations × 4096 shots = 1.6 × 10⁸ shots. Charged every iteration — Chapter 12 §12.5's session argument.

⚙️ PennyLane skips parameters that cannot matter. 64 params across 4 wires, observable PauliZ(0)33 executions, 16 nonzero gradients (not 129). Also an ansatz bug detector: unexpected gradient zeros mean parameters disconnected from your observable.

★★★ Barren plateaus

   qubits   params      Var[grad]    max|grad|
        2       36     1.0431e-01       0.8841
        4       72     2.7964e-02       0.6131
        6      108     8.0048e-03       0.3712
        8      144     1.9115e-03       0.1664
       10      180     4.6517e-04       0.0920

   fit: Var ~ exp(-0.676 n)  ->  x0.5086 per qubit
   halving every 1.03 qubits · Var(2)/Var(10) = 224x

×0.51 per qubit — within 2% of exactly one half. The textbook $\mathcal{O}(2^{-n})$ result, measured.

Why it is fatal, not annoying

Resolving a gradient of size $g$ needs $N \sim 1/g^2$ shots:

   qubits     Var[grad]      |grad|   shots to resolve
       10     4.856e-04   2.204e-02           2.06e+03
       30     6.523e-10   2.554e-05           1.53e+09
       50     8.763e-16   2.960e-08           1.14e+15

~10¹⁵ shots per parameter per iteration at 50 qubits. The optimizer is not slow — it is blind. And 50 qubits is small: the plateau arrives long before the interesting problems do.

Shallower ansätze do not escape it

   layers   Var(4 wires)   Var(8 wires)    ratio
        1     1.3842e-01     9.4414e-03    14.7x
        2     4.3994e-02     2.1485e-03    20.5x
        6     2.8436e-02     1.8937e-03    15.0x

You buy a constant factor in overall scale. The decay with width does not move.

🔬 What doesn't work: shallower ansätze (measured) · more shots (10¹⁵ is not a budget) · a better classical optimizer (the problem is absent information). What is proposed: problem-informed ansätze · smart initialization · layerwise training · local observables (real theoretical backing for shallow circuits — why §16.6 measures $Z_0Z_1$). None is a general solution. That is the honest state of the field.

★ Structurally zero ≠ barren plateau (CS1)

A first attempt sampled one parameter and got Var ~ 1e-33, |grad| ~ 1e-17 with a clean exponential fit — an artifact 18 orders of magnitude more severe than the real effect.

StronglyEntanglingLayers opens with Rot = $R_Z R_Y R_Z$, so the first $R_Z$ acts on $|0\rangle$:

$$R_Z(\phi)|0\rangle = e^{-i\phi/2}|0\rangle \quad\Rightarrow\quad \partial\langle O\rangle/\partial\phi \equiv 0$$

  g[0,0,0]  first RZ on |0>        = 9.714e-17     <- structurally zero
  g[0,:,0]  same angle, all wires  = [0. 0. 0.]
  g[2,1,1]  a middle-layer angle   = -0.143103     <- a real gradient
Cause Signature Response
Structural exactly ~1e-17 at every value and size fix ansatz / exclude and log
Disconnected exactly 0; gates on unmeasured wires ansatz bug
Plateau small but nonzero, varies per draw, shrinks with width the real problem

Discriminator: resample. A structural zero stays at 1e-17; a plateau gradient is a random variable that happens to be small.

A value near machine epsilon is an artifact until proven otherwise. And a convincing fit is not evidence of a correct measurement — the artifact was more internally consistent than the real signal, because floating-point noise is smoother than physics.

★ Diagnose the optimizer before the ansatz (CS2)

A 4-param ansatz stalled at −1.2485 (error 0.166) under GradientDescent(0.25), 40 steps.

   ansatz                                 energy        error
   shallow RY (4 params)                -1.414214     6.66e-16    <- FINE
   RY+RZ (8 params)                     -1.414212     1.33e-06
   StronglyEntanglingLayers x3 (18)     -1.414213     6.32e-08

The 4-parameter ansatz reaches machine precision — and is more accurate than either larger one.

   GradientDescent(0.25), 200 steps   -1.414214    2.28e-08    <- converges!
   GradientDescent(0.05), 200 steps   -1.259566    1.55e-01    <- WORSE
   Adam(0.1)                          -1.414214    8.76e-09
   Momentum(0.1)                      -1.414214    7.80e-10

It was simply too few steps — and reducing the step size, the instinctive fix, makes it worse.

Two diagnostics: 1. Is the trace still descending at the final step? If yes you ran out of steps — a stopping-criterion bug no extra parameters can fix. 2. Change the optimizer before the ansatz. If a better optimizer closes the gap, capacity was never the constraint.

Adding parameters to fix an optimizer problem moves every axis the wrong way: 4× the gradient bill (§16.4), a flatter landscape (§16.6), and a slightly worse answer.

Where PennyLane fits

Task PennyLane?
VQE, QAOA, quantum ML, gradients on hardware yes — built for it
PyTorch / JAX / TensorFlow integration yes
One codebase across backends yespennylane-qiskit, pennylane-cirq
Low-level circuit control · transpilation no — Qiskit
Explicit timing no — Cirq
Resource estimation no — Q#

Often best used as a differentiation layer on top of Qiskit or Cirq rather than as a replacement.

Common pitfalls

  • Sampling one parameter's gradient instead of all of them.
  • Treating a ~1e-17 value as a measurement.
  • Diagnosing a stall as ansatz capacity without changing the optimizer.
  • Reducing the step size in response to a stall.
  • Forgetting the gradient bill is $2n+1$ per iteration.
  • Expecting a shallower ansatz to escape the plateau.
  • Running without an independently-computed reference value.

Project piece added this chapter

vqelab/variational.pygradient_cost() and measure_gradient_cost() ($2n+1$, verified against the device tracker), classify_zero_gradient() (the three-way discriminator), measure_gradient_variance() (reports exclusions rather than hiding them), and optimize() returning a VariationalResult whose converged flag is false when the trace is still descending. 11 tests pass, including test_a_truncated_run_is_reported_as_NOT_converged and test_gradient_variance_decays_exponentially_with_width.