qml.RotosolveOptimizer

class RotosolveOptimizer[source]

Bases: object

Rotosolve gradient-free optimizer.

The Rotosolve optimizer minimizes an objective function with respect to the parameters of a quantum circuit without the need for calculating the gradient of the function. The algorithm updates the parameters \(\boldsymbol{\theta} = \theta_1, \dots, \theta_D\) by separately reconstructing the cost function with respect to each circuit parameter, while keeping all other parameters fixed.

For each parameter, a purely classical one-dimensional global optimization over the interval \((-\pi,\pi]\) is performed, which can be replaced by a closed-form expression for the optimal value if the \(d^{th}\) parametrized gate has only two eigenvalues. In this case, the optimal value \(\theta^*_d\) is given by

\[\begin{split}\theta^*_d &= \underset{\theta_d}{\text{argmin}}\left<H\right>_{\theta_d}\\ &= -\frac{\pi}{2} - \text{arctan2}\left(2\left<H\right>_{\theta_d=0} - \left<H\right>_{\theta_d=\pi/2} - \left<H\right>_{\theta_d=-\pi/2}, \left<H\right>_{\theta_d=\pi/2} - \left<H\right>_{\theta_d=-\pi/2}\right),\end{split}\]

where \(\left<H\right>_{\theta_d}\) is the expectation value of the objective function restricted to only depend on the parameter \(\theta_d\).

The algorithm is described in further detail in Vidal and Theis (2018) and Ostaszewski et al. (2019), and the reconstruction method used for more general operations is described in Wierichs et al. (2021).

Example:

Initialize the optimizer and set the number of steps to optimize over.

>>> opt = qml.optimize.RotosolveOptimizer()
>>> num_steps = 10

Next, we create a QNode we wish to optimize:

dev = qml.device('default.qubit', wires=3, shots=None)

@qml.qnode(dev)
def cost_function(rot_param, layer_par, crot_param):
    for i, par in enumerate(rot_param):
        qml.RX(par, wires=i)

    for w in dev.wires:
        qml.RX(layer_par, wires=w)

    for i, par in enumerate(crot_param):
        qml.CRY(par, wires=[i, (i+1)%3])

    return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2))

This QNode is defined simply by measuring the expectation value of the tensor product of PauliZ operators on all qubits. It takes three parameters:

  • rot_param controls three Pauli rotations with three parameters (one frequency each),

  • layer_par feeds into a layer of rotations with a single parameter (three frequencies), and

  • crot_param feeds three parameters into three controlled Pauli rotations (two frequencies each).

We also initialize a set of parameters for all these operations, and summarize the numbers of frequencies in num_freqs.

init_param = [
    np.array([0.3, 0.2, 0.67], requires_grad=True),
    np.array(1.1, requires_grad=True),
    np.array([-0.2, 0.1, -2.5], requires_grad=True),
]

num_freqs = [[1, 1, 1], 3, [2, 2, 2]]

The keyword argument requires_grad can be used to determine whether the respective parameter should be optimized or not, following the behaviour of gradient computations and gradient-based optimizers when using Autograd.

In addition, the optimization technique for the Rotosolve substeps can be chosen via the optimizer and optimizer_kwargs keyword arguments. As an extra feature, the minimized cost of the intermediate univariate reconstructions can be read out via full_output, including the cost after the full Rotosolve step:

param = init_param.copy()
cost_rotosolve = []
for step in range(num_steps):
    param, cost, sub_cost = opt.step_and_cost(
        cost_function,
        *param,
        num_freqs=num_freqs,
        full_output=True,
    )
    print(f"Cost before step: {cost}")
    print(f"Minimization substeps: {np.round(sub_cost, 6)}")
    cost_rotosolve.extend(sub_cost)

The optimized values for x are now stored in param and (sub)steps-vs-cost can be assessed by plotting cost_rotosolve. The full_output feature is available for both, step and step_and_cost.

The most general form RotosolveOptimizer is designed to tackle currently is any trigonometric cost function with integer frequencies up to the given value of num_freqs per parameter. Not all of the integers up to num_freqs have to be present in the frequency spectrum. In order to tackle equidistant but non-integer frequencies, we recommend rescaling the argument of the function of interest.

full_reconstruction_equ(fun, num_frequency)

Reconstruct a univariate trigonometric function using trigonometric interpolation.

step(objective_fn, *args[, num_freqs, …])

Update args with one step of the optimizer.

step_and_cost(objective_fn, *args[, …])

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

static full_reconstruction_equ(fun, num_frequency, fun_at_zero=None)[source]

Reconstruct a univariate trigonometric function using trigonometric interpolation. See Vidal and Theis (2018) or Wierichs et al. (2021).

Parameters
  • fun (callable) – the function to reconstruct

  • num_frequency (int) – the number of (integer) frequencies present in fun.

  • fun_at_zero (float) – The value of fun at 0. Computed if not provided.

Returns

The reconstruction function with num_frequency frequencies, coinciding with fun on the same number of points.

Return type

callable

step(objective_fn, *args, num_freqs=None, optimizer=None, optimizer_kwargs=None, full_output=False, **kwargs)[source]

Update args with one step of the optimizer.

Parameters
  • objective_fn (function) – the objective function for optimization. It should take a sequence of the values *args and a list of the gates generators as inputs, and return a single value.

  • *args – variable length sequence containing the initial values of the variables to be optimized over or a single float with the initial value.

  • num_freqs (int or array[int]) – The number of frequencies in the objective_fn per parameter. If an int, the same number is used for all parameters; if array[int], the shape of args and num_freqs has to coincide. Defaults to num_freqs=1, corresponding to Pauli rotation gates.

  • optimizer (callable or str) – the optimization method used for the univariate minimization if there is more than one frequency with respect to the respective parameter. If a callable, should have the signature (fun, **kwargs) -> x_min, y_min, where y_min is tracked and returned if full_output==True but is not relevant to the optimization. If "brute" or "shgo", the corresponding global optimizer of SciPy is used. Defaults to "brute".

  • optimizer_kwargs – keyword arguments for the optimizer. For "brute" and "shgo", these kwargs are passed to the respective SciPy implementation. Has to be given as one dictionary, not variable length.

  • full_output (bool) – whether to return the intermediate minimized energy values from the univariate optimization steps.

  • **kwargs – variable length keyword arguments for the objective function.

Returns

the new variable values \(x^{(t+1)}\). If a single arg is provided, list [array] is replaced by array. list [float]: the intermediate energy values, only returned if full_output=True.

Return type

list [array]

step_and_cost(objective_fn, *args, num_freqs=None, optimizer=None, optimizer_kwargs=None, full_output=False, **kwargs)[source]

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

Parameters
  • objective_fn (function) – the objective function for optimization. It should take a sequence of the values *args and a list of the gates generators as inputs, and return a single value.

  • *args – variable length sequence containing the initial values of the variables to be optimized over or a single float with the initial value.

  • num_freqs (int or array[int]) – The number of frequencies in the objective_fn per parameter. If an int, the same number is used for all parameters; if array[int], the shape of args and num_freqs has to coincide. Defaults to num_freqs=1, corresponding to Pauli rotation gates.

  • optimizer (callable or str) – the optimization method used for the univariate minimization if there is more than one frequency with respect to the respective parameter. If a callable, should have the signature (fun, **kwargs) -> x_min, y_min, where y_min is tracked and returned if full_output==True but is not relevant to the optimization. If "brute" or "shgo", the corresponding global optimizer of SciPy is used. Defaults to "brute".

  • optimizer_kwargs – keyword arguments for the optimizer. For "brute" and "shgo", these kwargs are passed to the respective SciPy implementation. Has to be given as one dictionary, not variable length.

  • full_output (bool) – whether to return the intermediate minimized energy values from the univariate optimization steps.

  • **kwargs – variable length keyword arguments for the objective function.

Returns

the new variable values \(x^{(t+1)}\) and the objective function output prior to the step. If a single arg is provided, list [array] is replaced by array. list [float]: the intermediate energy values, only returned if full_output=True.

Return type

tuple(list [array] or array, float)

Contents

Using PennyLane

Development

API