class RiemannianGradientOptimizer(circuit, stepsize=0.01, restriction=None, exact=False, trottersteps=1)[source]

Bases: object

Riemannian gradient descent algorithms can be used to optimize a function directly on a Lie group as opposed to on an Euclidean parameter space. Consider the function $$f(U) = \text{Tr}(U \rho_0 U^\dagger H)$$ for a given Hamiltonian $$H$$, unitary $$U\in \text{SU}(2^N)$$ and initial state $$\rho_0$$. One can show that this function is minimized by the flow equation

$\dot{U} = \text{grad}f(U)$

where $$\text{grad}$$ is the Riemannian gradient operator on $$\text{SU}(2^N)$$. By discretizing the flow above, we see that a step of this optimizer iterates the Riemannian gradient flow on $$\text{SU}(2^N)$$ as

$U^{(t+1)} = \exp\left\{\epsilon\: \text{grad}f(U^{(t)}) U^{(t)}\right\},$

where $$\epsilon$$ is a user-defined hyperparameter corresponding to the step size.

The Riemannian gradient in the Lie algebra is given by

$\text{grad}f(U^{(t)}) = -\left[U \rho U^\dagger, H\right] .$

Hence we see that subsequent steps of this optimizer will append the unitary generated by the Riemannian gradient and grow the circuit.

The exact Riemannian gradient flow on $$\text{SU}(2^N)$$ has desirable optimization properties that can guarantee convergence to global minima under mild assumptions. However, this comes at a cost. Since $$\text{dim}(\text{SU}(2^N)) = 4^N-1$$, we need an exponential number of parameters to calculate the gradient. This will not be problematic for small systems ($$N<5$$), but will quickly get out of control as the number of qubits increases.

To resolve this issue, we can restrict the Riemannian gradient to a subspace of the Lie algebra and calculate an approximate Riemannian gradient flow. The choice of restriction will affect the optimization behavior and quality of the final solution.

For more information on Riemannian gradient flows on Lie groups see T. Schulte-Herbrueggen et. al. (2008) and the application to quantum circuits Wiersema and Killoran (2022).

Parameters
• circuit (QNode) – a user defined circuit that does not take any arguments and returns the expectation value of a qml.Hamiltonian.

• stepsize (float) – the user-defined hyperparameter $$\epsilon$$.

• restriction (Hamiltonian) – Restrict the Lie algebra to a corresponding subspace of the full Lie algebra. This restriction should be passed in the form of a qml.Hamiltonian that consists only of Pauli words.

• exact (bool) – Flag that indicates wether we approximate the Riemannian gradient with a Trotterization or calculate the exact evolution via a matrix exponential. The latter is not hardware friendly and can only be done in simulation.

Examples

Define a Hamiltonian cost function to minimize:

>>> coeffs = [-1., -1., -1.]
>>> observables = [qml.PauliX(0), qml.PauliZ(1), qml.PauliY(0) @ qml.PauliX(1)]
>>> hamiltonian = qml.Hamiltonian(coeffs, observables)


Create an initial state and return the expectation value of the Hamiltonian:

>>> @qml.qnode(qml.device("default.qubit", wires=2))
... def quant_fun():
...     qml.RX(0.1, wires=)
...     qml.RY(0.5, wires=)
...     qml.CNOT(wires=[0,1])
...     qml.RY(0.6, wires=)
...     return qml.expval(hamiltonian)


Instantiate the optimizer with the initial circuit and the cost function and set the stepsize accordingly:

>>> opt = qml.RiemannianGradientOptimizer(circuit=quant_fun, stepsize=0.1)


Applying 5 steps gets us close the ground state of $$E\approx-2.23$$:

>>> for step in range(6):
...    circuit, cost = opt.step_and_cost()
...    print(f"Step {step} - cost {cost}")
Step 0 - cost -1.3351865007304005
Step 1 - cost -1.9937887238935206
Step 2 - cost -2.1524234485729834
Step 3 - cost -2.1955105378898487
Step 4 - cost -2.2137628169764256
Step 5 - cost -2.2234364822091575


The optimized circuit is returned at each step, and can be used as any other QNode:

>>> circuit()
-2.2283086057521713

 Measure the coefficients of the Riemannian gradient with respect to a Pauli word basis. get_su_n_operators(restriction) Get the SU(N) operators. Update the circuit with one step of the optimizer. Update the circuit with one step of the optimizer and return the corresponding objective function value prior to the step.
get_omegas()[source]

Measure the coefficients of the Riemannian gradient with respect to a Pauli word basis. We want to calculate the components of the Riemannian gradient in the Lie algebra with respect to a Pauli word basis. For a Hamiltonian of the form $$H = \sum_i c_i O_i$$, where $$c_i\in\mathbb{R}$$, this can be achieved by calculating

$\omega_{i,j} = \text{Tr}(c_i[\rho, O_i] P_j)$

where $$P_j$$ is a Pauli word in the set of Pauli monomials on $$N$$ qubits.

Via the parameter shift rule, the commutator can be calculated as

$[\rho, O_i] = \frac{1}{2}(V(\pi/2) \rho V^\dagger(\pi/2) - V(-\pi/2) \rho V^\dagger(-\pi/2))$

where $$V$$ is the unitary generated by the Pauli word $$V(\theta) = \exp\{-i\theta P_j\}$$.

Returns

array of omegas for each direction in the Lie algebra.

Return type

array

get_su_n_operators(restriction)[source]

Get the SU(N) operators. The dimension of the group is $$N^2-1$$.

Parameters

restriction (Hamiltonian) – Restrict the Riemannian gradient to a subspace.

Returns

list of $$N^2 \times N^2$$ NumPy complex arrays and corresponding Pauli words.

Return type

tuple[list[array[complex]], list[str]]

step()[source]

Update the circuit with one step of the optimizer.

Returns

the optimized circuit and the objective function output prior to the step.

Return type

float

step_and_cost()[source]

Update the circuit with one step of the optimizer and return the corresponding objective function value prior to the step.

Returns

the optimized circuit and the objective function output prior to the step.

Return type

tuple[QNode, float]