Skill Tracer
Skills › Scientific Agent Skills › pennylane › optimization.md

optimization.md

165 lines · skills/pennylane/references/optimization.md at 5f2bc03

Open on GitHub
1# Optimization and objective conventions
2
3Targets PennyLane 0.45.1. Each block is self-contained and exercised numerically.
4

Optimization and objective conventions

Targets PennyLane 0.45.1. Each block is self-contained and exercised numerically.

Raw lines 1 to 4
5## Gradients before optimization
6
7```python
8import pennylane as qml
9from pennylane import numpy as np
11dev = qml.device("default.qubit", wires=1)
12def quantum_function(x):
13 qml.RY(x, 0)
14 return qml.expval(qml.Z(0))
15x = np.array(0.4, requires_grad=True)
16for method in ["backprop", "parameter-shift", "adjoint", "finite-diff"]:
17 circuit = qml.QNode(quantum_function, dev, diff_method=method)
18 assert np.allclose(qml.grad(circuit)(x), -np.sin(x), atol=1e-6)
19second_order = qml.QNode(quantum_function, dev, diff_method="parameter-shift", max_diff=2)
20assert np.allclose(qml.jacobian(qml.grad(second_order))(x), -np.cos(x))
21```
23Backprop/adjoint require supporting simulators and compatible measurements;
24finite-shot gradients typically use parameter-shift or stochastic estimates.
25A parameter-shift gradient's circuit count depends on generators, decomposition,
26measurements and batching; do not assume exactly two circuits for every parameter.
27`max_diff=2` enables the higher-order derivative path above.
29Use the optimizer from the chosen interface. PennyLane's
30`GradientDescentOptimizer`, `AdamOptimizer`, `MomentumOptimizer`,
31`NesterovMomentumOptimizer`, `AdagradOptimizer` and `RMSPropOptimizer` work with
32Autograd arrays. Torch uses `torch.optim`; JAX uses its own updates or Optax.
33`step_and_cost` gives the **old** cost and **new** parameters. Recompute final cost.

Gradients before optimization

import pennylane as qml
from pennylane import numpy as np

dev = qml.device("default.qubit", wires=1)
def quantum_function(x):
    qml.RY(x, 0)
    return qml.expval(qml.Z(0))
x = np.array(0.4, requires_grad=True)
for method in ["backprop", "parameter-shift", "adjoint", "finite-diff"]:
    circuit = qml.QNode(quantum_function, dev, diff_method=method)
    assert np.allclose(qml.grad(circuit)(x), -np.sin(x), atol=1e-6)
second_order = qml.QNode(quantum_function, dev, diff_method="parameter-shift", max_diff=2)
assert np.allclose(qml.jacobian(qml.grad(second_order))(x), -np.cos(x))

Backprop/adjoint require supporting simulators and compatible measurements; finite-shot gradients typically use parameter-shift or stochastic estimates. A parameter-shift gradient's circuit count depends on generators, decomposition, measurements and batching; do not assume exactly two circuits for every parameter. max_diff=2 enables the higher-order derivative path above.

Use the optimizer from the chosen interface. PennyLane's GradientDescentOptimizer, AdamOptimizer, MomentumOptimizer, NesterovMomentumOptimizer, AdagradOptimizer and RMSPropOptimizer work with Autograd arrays. Torch uses torch.optim; JAX uses its own updates or Optax. step_and_cost gives the old cost and new parameters. Recompute final cost.

Raw lines 5 to 34
35## SPSA and quantum natural gradient
37```python
38import pennylane as qml
39from pennylane import numpy as np
41np.random.seed(19)
42dev = qml.device("default.qubit", wires=1)
43@qml.qnode(dev)
44def cost(x):
45 qml.RY(x[0], 0)
46 return qml.expval(qml.Z(0))
47x = np.array([0.5], requires_grad=True)
48spsa = qml.SPSAOptimizer(maxiter=80, a=0.3, c=0.1)
49for _ in range(80):
50 x = spsa.step(cost, x)
51assert cost(x) < cost(np.array([0.5], requires_grad=True))
53qng = qml.QNGOptimizer(stepsize=0.1, approx="block-diag", lam=0.01)
54y = np.array([0.5], requires_grad=True)
55y = qng.step(cost, y)
56assert cost(y) < cost(np.array([0.5], requires_grad=True))
57```
59SPSA has `step`/`step_and_cost`, not `minimize`. Its optimizer estimates gradients
60itself; setting the QNode to `diff_method='spsa'` as well is unnecessary here.
61QNG needs a compatible QNode/metric; regularization handles near-singularity but
62changes the update. Full `qml.metric_tensor` can need an auxiliary wire;
63block-diagonal approximations have different cost/geometry.
64`QNSPSAOptimizer` is a stochastic natural-gradient approximation, not quantum
65analytic descent. `RotosolveOptimizer` needs per-argument frequency information
66(`nums_frequency` or `spectra`) and is not a universal drop-in optimizer.

SPSA and quantum natural gradient

import pennylane as qml
from pennylane import numpy as np

np.random.seed(19)
dev = qml.device("default.qubit", wires=1)
@qml.qnode(dev)
def cost(x):
    qml.RY(x[0], 0)
    return qml.expval(qml.Z(0))
x = np.array([0.5], requires_grad=True)
spsa = qml.SPSAOptimizer(maxiter=80, a=0.3, c=0.1)
for _ in range(80):
    x = spsa.step(cost, x)
assert cost(x) < cost(np.array([0.5], requires_grad=True))

qng = qml.QNGOptimizer(stepsize=0.1, approx="block-diag", lam=0.01)
y = np.array([0.5], requires_grad=True)
y = qng.step(cost, y)
assert cost(y) < cost(np.array([0.5], requires_grad=True))

SPSA has step/step_and_cost, not minimize. Its optimizer estimates gradients itself; setting the QNode to diff_method='spsa' as well is unnecessary here. QNG needs a compatible QNode/metric; regularization handles near-singularity but changes the update. Full qml.metric_tensor can need an auxiliary wire; block-diagonal approximations have different cost/geometry. QNSPSAOptimizer is a stochastic natural-gradient approximation, not quantum analytic descent. RotosolveOptimizer needs per-argument frequency information (nums_frequency or spectra) and is not a universal drop-in optimizer.

Raw lines 35 to 67
68## MaxCut: minimize negative cut size
70```python
71import pennylane as qml
72from pennylane import numpy as np
73import networkx as nx
75graph = nx.cycle_graph(3)
76cost_h, mixer_h = qml.qaoa.maxcut(graph)
77dev = qml.device("default.qubit", wires=list(graph.nodes))
78@qml.qnode(dev)
79def qaoa_energy(params):
80 for wire in graph.nodes:
81 qml.Hadamard(wire)
82 for gamma, beta in params:
83 qml.qaoa.cost_layer(gamma, cost_h)
84 qml.qaoa.mixer_layer(beta, mixer_h)
85 return qml.expval(cost_h)
87params = np.array([[0.3, 0.2]], requires_grad=True)
88initial_energy = float(qaoa_energy(params))
89opt = qml.AdamOptimizer(stepsize=0.1)
90for _ in range(80):
91 params = opt.step(qaoa_energy, params)
92final_energy = float(qaoa_energy(params))
93assert final_energy < initial_energy
94assert final_energy >= -2.0 - 1e-8
95assert -final_energy > 1.99
96```
98`maxcut(graph)` has no `constrained` keyword. Its Hamiltonian is
99`sum((Zi Zj - I)/2)`, so each cut edge contributes **-1**. Negating this objective
100before minimizing favors the wrong answer. The measured mean cut score is not
101the best sampled bit string; evaluate each bit string with the classical graph
102objective, record sample frequencies, and compare with exact small instances.
103Node labels determine wires. Weighted or constrained problems require a matching
104cost construction; do not assume unweighted MaxCut reads edge weights.

MaxCut: minimize negative cut size

import pennylane as qml
from pennylane import numpy as np
import networkx as nx

graph = nx.cycle_graph(3)
cost_h, mixer_h = qml.qaoa.maxcut(graph)
dev = qml.device("default.qubit", wires=list(graph.nodes))
@qml.qnode(dev)
def qaoa_energy(params):
    for wire in graph.nodes:
        qml.Hadamard(wire)
    for gamma, beta in params:
        qml.qaoa.cost_layer(gamma, cost_h)
        qml.qaoa.mixer_layer(beta, mixer_h)
    return qml.expval(cost_h)

params = np.array([[0.3, 0.2]], requires_grad=True)
initial_energy = float(qaoa_energy(params))
opt = qml.AdamOptimizer(stepsize=0.1)
for _ in range(80):
    params = opt.step(qaoa_energy, params)
final_energy = float(qaoa_energy(params))
assert final_energy < initial_energy
assert final_energy >= -2.0 - 1e-8
assert -final_energy > 1.99

maxcut(graph) has no constrained keyword. Its Hamiltonian is sum((Zi Zj - I)/2), so each cut edge contributes -1. Negating this objective before minimizing favors the wrong answer. The measured mean cut score is not the best sampled bit string; evaluate each bit string with the classical graph objective, record sample frequencies, and compare with exact small instances. Node labels determine wires. Weighted or constrained problems require a matching cost construction; do not assume unweighted MaxCut reads edge weights.

Raw lines 68 to 105
106## QUBO: include linear terms, offset and both matrix triangles
108For the declared convention `f(x) = x.T @ Q @ x`, with binary `x`, substitute
109`x_i=(I-Z_i)/2`. A symmetric matrix includes off-diagonal contributions twice.
110Keep the constant to reproduce absolute objectives.
112```python
113import itertools
114import pennylane as qml
115import numpy as np
117def qubo_hamiltonian(Q):
118 Q = np.asarray(Q, dtype=float)
119 if Q.ndim != 2 or Q.shape[0] != Q.shape[1] or not len(Q) or not np.isfinite(Q).all():
120 raise ValueError("Q must be a nonempty finite square real matrix")
121 n = len(Q)
122 offset = np.trace(Q) / 2
123 linear = -np.diag(Q).copy() / 2
124 coeffs, observables = [], []
125 for i in range(n):
126 for j in range(i + 1, n):
127 pair = Q[i, j] + Q[j, i]
128 offset += pair / 4
129 linear[i] -= pair / 4
130 linear[j] -= pair / 4
131 coeffs.append(pair / 4)
132 observables.append(qml.Z(i) @ qml.Z(j))
133 coeffs.extend(linear)
134 observables.extend(qml.Z(i) for i in range(n))
135 coeffs.append(offset)
136 observables.append(qml.I(0))
137 return qml.Hamiltonian(coeffs, observables)
139Q = np.array([[1.0, -2.0], [-2.0, 1.0]])
140H = qubo_hamiltonian(Q)
141expected = [np.asarray(x) @ Q @ np.asarray(x) for x in itertools.product([0, 1], repeat=2)]
142assert np.allclose(np.diag(qml.matrix(H, wire_order=[0, 1])), expected)
143```
145Use this diagonal `H` with `qml.qaoa.cost_layer`, an X mixer and the same minimization
146convention. For a polynomial written with each `i<j` term only once, convert it
147to the stated matrix convention first. Enumerate small problems before training.

QUBO: include linear terms, offset and both matrix triangles

For the declared convention f(x) = x.T @ Q @ x, with binary x, substitute x_i=(I-Z_i)/2. A symmetric matrix includes off-diagonal contributions twice. Keep the constant to reproduce absolute objectives.

import itertools
import pennylane as qml
import numpy as np

def qubo_hamiltonian(Q):
    Q = np.asarray(Q, dtype=float)
    if Q.ndim != 2 or Q.shape[0] != Q.shape[1] or not len(Q) or not np.isfinite(Q).all():
        raise ValueError("Q must be a nonempty finite square real matrix")
    n = len(Q)
    offset = np.trace(Q) / 2
    linear = -np.diag(Q).copy() / 2
    coeffs, observables = [], []
    for i in range(n):
        for j in range(i + 1, n):
            pair = Q[i, j] + Q[j, i]
            offset += pair / 4
            linear[i] -= pair / 4
            linear[j] -= pair / 4
            coeffs.append(pair / 4)
            observables.append(qml.Z(i) @ qml.Z(j))
    coeffs.extend(linear)
    observables.extend(qml.Z(i) for i in range(n))
    coeffs.append(offset)
    observables.append(qml.I(0))
    return qml.Hamiltonian(coeffs, observables)

Q = np.array([[1.0, -2.0], [-2.0, 1.0]])
H = qubo_hamiltonian(Q)
expected = [np.asarray(x) @ Q @ np.asarray(x) for x in itertools.product([0, 1], repeat=2)]
assert np.allclose(np.diag(qml.matrix(H, wire_order=[0, 1])), expected)

Use this diagonal H with qml.qaoa.cost_layer, an X mixer and the same minimization convention. For a polynomial written with each i<j term only once, convert it to the stated matrix convention first. Enumerate small problems before training.

Raw lines 106 to 148
149## Training and scientific diagnostics
151Keep optimizer state between batches/epochs; recreating Adam resets its moments.
152Compute validation loss on held-out data and retain the best parameters. Monitor
153finite loss, gradient norm, objective improvement and several seeded restarts.
154For barren-plateau studies, estimate each parameter's gradient distribution across
155independent initializations and system sizes; variance across different parameters
156at one initialization plus a universal threshold is not evidence of exponential
157concentration. Compare analytic and finite-shot gradients to separate shot noise.

Training and scientific diagnostics

Keep optimizer state between batches/epochs; recreating Adam resets its moments. Compute validation loss on held-out data and retain the best parameters. Monitor finite loss, gradient norm, objective improvement and several seeded restarts. For barren-plateau studies, estimate each parameter's gradient distribution across independent initializations and system sizes; variance across different parameters at one initialization plus a universal threshold is not evidence of exponential concentration. Compare analytic and finite-shot gradients to separate shot noise.

Raw lines 149 to 158
159## Sources
161- [Gradient interfaces](https://docs.pennylane.ai/en/stable/introduction/interfaces.html)
162- [SPSAOptimizer](https://docs.pennylane.ai/en/stable/code/api/pennylane.SPSAOptimizer.html)
163- [QNGOptimizer](https://docs.pennylane.ai/en/stable/code/api/pennylane.QNGOptimizer.html)
164- [RotosolveOptimizer](https://docs.pennylane.ai/en/stable/code/api/pennylane.RotosolveOptimizer.html)
165- [MaxCut](https://docs.pennylane.ai/en/stable/code/api/pennylane.qaoa.cost.maxcut.html)