# qml.QNSPSAOptimizer¶

class QNSPSAOptimizer(stepsize=0.001, regularization=0.001, finite_diff_step=0.01, resamplings=1, blocking=True, history_length=5, seed=None)[source]

Bases: object

Quantum natural SPSA (QNSPSA) optimizer. QNSPSA is a second-order SPSA algorithm, which updates the ansatz parameters with the following equation:

$\mathbf{x}^{(t + 1)} = \mathbf{x}^{(t)} - \eta \widehat{\mathbf{g}}^{-1}(\mathbf{x}^{(t)})\widehat{\nabla f}(\mathbf{x}^{(t)}),$

where $$f(\mathbf{x})$$ is the objective function with input parameters $$\mathbf{x}$$, while $$\nabla f$$ is the gradient, $$\mathbf{g}$$ is the second-order Fubini-Study metric tensor. With QNSPSA algorithm, both the gradient and the metric tensor are estimated stochastically, with $$\widehat{\nabla f}$$ and $$\widehat{\mathbf{g}}$$. This stochastic approach requires only a fixed number of circuit executions per optimization step, independent of the problem size. This preferred scaling makes it a promising candidate for the optimization tasks for high-dimensional ansatzes. On the other hand, the introduction of the Fubini-Study metric into the optimization helps to find better minima and allows for faster convergence.

The gradient is estimated similarly as the SPSA optimizer, with a pair of perturbations:

$\widehat{\nabla f}(\mathbf{x}) = \widehat{\nabla f}(\mathbf{x}, \mathbf{h}) \approx \frac{1}{2\epsilon}\big(f(\mathbf{x} + \epsilon \mathbf{h}) - f(\mathbf{x} - \epsilon \mathbf{h})\big),$

where $$\epsilon$$ is the finite-difference step size specified by the user, and $$\mathbf{h}$$ is a randomly sampled direction vector to perform the perturbation.

The Fubini-Study metric tensor is estimated with another two pairs of perturbations along randomly sampled directions $$\mathbf{h_1}$$ and $$\mathbf{h_2}$$:

$\widehat{\mathbf{g}}(\mathbf{x}) = \widehat{\mathbf{g}}(\mathbf{x}, \mathbf{h}_1, \mathbf{h}_2) \approx \frac{\delta F}{8 \epsilon^2}\Big(\mathbf{h}_1 \mathbf{h}_2^\intercal + \mathbf{h}_2 \mathbf{h}_1^\intercal\Big),$

where $$F(\mathbf{x}', \mathbf{x}) = \bigr\rvert\langle \phi(\mathbf{x}') | \phi(\mathbf{x}) \rangle \bigr\rvert ^ 2$$ measures the state overlap between $$\phi(\mathbf{x}')$$ and $$\phi(\mathbf{x})$$, where $$\phi$$ is the parameterized ansatz. The finite difference $$\delta F$$ is computed from the two perturbations:

$\delta F = F(\mathbf{x, \mathbf{x} + \epsilon \mathbf{h}_1} + \epsilon \mathbf{h}_2) - F (\mathbf{x, \mathbf{x} + \epsilon \mathbf{h}_1}) - F(\mathbf{x, \mathbf{x} - \epsilon \mathbf{h}_1} + \epsilon \mathbf{h}_2) + F(\mathbf{x, \mathbf{x} - \epsilon \mathbf{h}_1}).$

For more details, see:

Julien Gacon, Christa Zoufal, Giuseppe Carleo, and Stefan Woerner. “Simultaneous Perturbation Stochastic Approximation of the Quantum Fisher Information.” Quantum, 5, 567, 2021.

You can also find a walkthrough of the implementation in this tutorial.

Examples:

For VQE/VQE-like problems, the objective function can be defined within a qnode:

>>> num_qubits = 2
>>> dev = qml.device("default.qubit", wires=num_qubits)
>>> @qml.qnode(dev)
... def cost(params):
...     qml.RX(params[0], wires=0)
...     qml.CRY(params[1], wires=[0, 1])
...     return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))


Once constructed, the qnode can be passed directly to the step or step_and_cost function of the optimizer.

>>> params = np.random.rand(2)
>>> opt = QNSPSAOptimizer(stepsize=5e-2)
>>> for i in range(51):
>>> params, loss = opt.step_and_cost(cost, params)
>>> if i % 10 == 0:
...     print(f"Step {i}: cost = {loss:.4f}")
Step 0: cost = 0.9987
Step 10: cost = 0.9841
Step 20: cost = 0.8921
Step 30: cost = 0.0910
Step 40: cost = -0.9369
Step 50: cost = -0.9984

Keyword Arguments
• stepsize (float) – the user-defined hyperparameter $$\eta$$ for learning rate (default: 1e-3)

• regularization (float) – regularitzation term $$\beta$$ to the Fubini-Study metric tensor for numerical stability (default: 1e-3)

• finite_diff_step (float) – step size $$\epsilon$$ to compute the finite difference gradient and the Fubini-Study metric tensor (default: 1e-2)

• resamplings (int) – the number of samples to average for each parameter update (default: 1)

• blocking (boolean) – when set to be True, the optimizer only accepts updates that lead to a loss value no larger than the loss value before update, plus a tolerance. The tolerance is set with the hyperparameter history_length. The blocking option is observed to help the optimizer to converge significantly faster (default: True)

• history_length (int) – when blocking is True, the tolerance is set to be the average of the cost values in the last history_length steps (default: 5)

• seed (int) – seed for the random sampling (default: None)

 step(cost, *args, **kwargs) Update trainable arguments with one step of the optimizer. step_and_cost(cost, *args, **kwargs) Update trainable parameters with one step of the optimizer and return the corresponding objective function value after the step.
step(cost, *args, **kwargs)[source]

Update trainable arguments with one step of the optimizer.

Note

When blocking is set to be True, step calls step_and_cost on the backend, as loss measurements are required by the algorithm in this scenario.

Parameters
• cost (QNode) – the QNode wrapper for the objective function for optimization

• args – variable length argument list for qnode

• kwargs – variable length of keyword arguments for the qnode

Returns

the new variable values after step-wise update $$x^{(t+1)}$$

Return type

np.array

step_and_cost(cost, *args, **kwargs)[source]

Update trainable parameters with one step of the optimizer and return the corresponding objective function value after the step.

Parameters
• cost (QNode) – the QNode wrapper for the objective function for optimization

• args – variable length argument list for qnode

• kwargs – variable length of keyword arguments for the qnode

Returns

the new variable values $$x^{(t+1)}$$ and the objective function output prior to the step

Return type

(np.array, float)