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.Z(0) @ qml.Z(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)

Contents

Using PennyLane

Development

API

Internals