qml.workflow.jacobian_products.DeviceDerivatives

class DeviceDerivatives(device, execution_config=None, gradient_kwargs=None)[source]

Bases: pennylane.workflow.jacobian_products.JacobianProductCalculator

Calculate jacobian products via a device provided jacobian. This class relies on either qml.Device.gradients or qml.devices.Device.compute_derivatives.

Parameters
  • device (Union[pennylane.Device, pennylane.devices.Device]) – the device for execution and derivatives. Must support first order gradients with the requested configuration.

  • execution_config (pennylane.devices.ExecutionConfig) – a datastructure containing the options needed to fully describe the execution. Only used with pennylane.devices.Device from the new device interface.

  • gradient_kwargs (dict) – a dictionary of keyword arguments for the gradients. Only used with a Device from the old device interface.

Examples:

>>> device = qml.device('default.qubit')
>>> config = qml.devices.ExecutionConfig(gradient_method="adjoint")
>>> jpc = DeviceDerivatives(device, config, {})

This same class can also be used with the old device interface.

>>> device = qml.device('lightning.qubit', wires=5)
>>> gradient_kwargs = {"method": "adjoint_jacobian"}
>>> jpc_lightning = DeviceDerivatives(device, gradient_kwargs=gradient_kwargs)

Technical comments on caching and calculating the gradients on execution:

In order to store results and Jacobians for the backward pass during the forward pass, the _jacs_cache and _results_cache properties are LRUCache objects with a maximum size of 10. In the current execution pipeline, only one batch will be used per instance, but a size of 10 adds some extra flexibility for future uses.

Note that batches of identically looking QuantumScript s that are different instances will be cached separately. This is because the hash of QuantumScript is expensive, as it requires inspecting all its constituents, which is not worth the effort in this case.

When a forward pass with execute_and_cache_jacobian() is called, both the results and the jacobian for the object are stored.

>>> tape = qml.tape.QuantumScript([qml.RX(1.0, wires=0)], [qml.expval(qml.Z(0))])
>>> batch = (tape, )
>>> with device.tracker:
...     results = jpc.execute_and_cache_jacobian(batch )
>>> results
(0.5403023058681398,)
>>> device.tracker.totals
{'execute_and_derivative_batches': 1, 'executions': 1, 'derivatives': 1}
>>> jpc._jacs_cache
LRUCache({5660934048: (array(-0.84147098),)}, maxsize=10, currsize=1)

Then when the vjp, jvp, or jacobian is requested, that cached value is used instead of requesting from the device again.

>>> with device.tracker:
...     vjp = jpc.compute_vjp(batch , (0.5, ) )
>>> vjp
(array([-0.42073549]),)
>>> device.tracker.totals
{}

compute_jacobian(tapes)

Compute the full Jacobian for a batch of tapes.

compute_vjp(tapes, dy)

Compute the vjp for a given batch of tapes.

execute_and_cache_jacobian(tapes)

Forward pass used to cache the results and jacobians.

execute_and_compute_jacobian(tapes)

Compute the results and the full Jacobian for a batch of tapes.

execute_and_compute_jvp(tapes, tangents)

Calculate both the results for a batch of tapes and the jvp.

compute_jacobian(tapes)[source]

Compute the full Jacobian for a batch of tapes.

This method is required to compute Jacobians in the jax-jit interface

Parameters

tapes – the batch of tapes to take the Jacobian of

Returns

the full jacobian

Return type

TensorLike

Side Effects:

caches the newly computed jacobian if it wasn’t already present in the cache.

Examples:

For an instance of DeviceDerivatives jpc, we have:

>>> tape0 = qml.tape.QuantumScript([qml.RX(0.1, wires=0)], [qml.expval(qml.Z(0))])
>>> tape1 = qml.tape.QuantumScript([qml.RY(0.2, wires=0)], [qml.expval(qml.Z(0)), qml.expval(qml.X(0))])
>>> batch = (tape0, tape1)
>>> jpc.compute_jacobian(batch)
(array(-0.09983342), (array(-0.19866933), array(0.98006658)))

While this method could support non-scalar parameters in theory, no implementation currently supports jacobians with non-scalar parameters.

compute_vjp(tapes, dy)[source]

Compute the vjp for a given batch of tapes.

This method is used by autograd, torch, and tensorflow to compute VJPs.

Parameters
  • tapes (tuple[~.QuantumScript]) – the batch of tapes to take the derivatives of

  • dy (tuple[tuple[TensorLike]]) – the derivatives of the results of an execution. The i th entry (cotangent) corresponds to the i th tape, and the j th entry of the i th cotangent corresponds to the j th return value of the i th tape.

Returns

the vector jacobian product.

Return type

TensorLike

Side Effects:

caches the newly computed jacobian if it wasn’t already present in the cache.

Examples:

For an instance of DeviceDerivatives jpc, we have:

>>> tape0 = qml.tape.QuantumScript([qml.RX(0.1, wires=0)], [qml.expval(qml.Z(0))])
>>> tape1 = qml.tape.QuantumScript([qml.RY(0.2, wires=0)], [qml.expval(qml.Z(0)), qml.expval(qml.X(0))])
>>> batch = (tape0, tape1)
>>> dy0 = (0.5, )
>>> dy1 = (2.0, 3.0)
>>> dys = (dy0, dy1)
>>> vjps = jpc.compute_vjp(batch, dys)
>>> vjps
(array([-0.04991671]), array([2.54286107]))
>>> expected_vjp0 = 0.5 * -np.sin(0.1)
>>> qml.math.allclose(vjps[0], expected_vjp0)
True
>>> expected_jvp1 = 2.0 * -np.sin(0.2) + 3.0 * np.cos(0.2)
>>> qml.math.allclose(vjps[1], expected_vjp1)
True

While this method could support non-scalar parameters in theory, no implementation currently supports jacobians with non-scalar parameters.

execute_and_cache_jacobian(tapes)[source]

Forward pass used to cache the results and jacobians.

Parameters

tapes (tuple[~.QuantumScript]) – the batch of tapes to execute and take derivatives of

Returns

the results of the execution.

Return type

ResultBatch

Side Effects:

Caches both the results and jacobian into _results_cache and _jacs_cache.

execute_and_compute_jacobian(tapes)[source]

Compute the results and the full Jacobian for a batch of tapes.

This method is required to compute Jacobians in the jax-jit interface

Parameters

tapes (tuple[QuantumScript]) – the batch of tapes to take the derivatives of

Examples:

For an instance of JacobianProductCalculator jpc, we have:

>>> tape0 = qml.tape.QuantumScript([qml.RX(0.1, wires=0)], [qml.expval(qml.Z(0))])
>>> tape1 = qml.tape.QuantumScript([qml.RY(0.2, wires=0)], [qml.expval(qml.Z(0)), qml.expval(qml.X(0))])
>>> batch = (tape0, tape1)
>>> results, jacs = jpc.execute_and_compute_jacobian(batch)
>>> results
(0.9950041652780258, (0.9800665778412417, 0.19866933079506116))
>>> jacs
(array(-0.09983342), (array(-0.19866933), array(0.98006658)))

While this method could support non-scalar parameters in theory, no implementation currently supports jacobians with non-scalar parameters.

execute_and_compute_jvp(tapes, tangents)[source]

Calculate both the results for a batch of tapes and the jvp.

This method is required to compute JVPs in the JAX interface.

Parameters
  • tapes (tuple[~.QuantumScript]) – The batch of tapes to take the derivatives of

  • tangents (Sequence[Sequence[TensorLike]]) – the tangents for the parameters of the tape. The i th tangent corresponds to the i th tape, and the j th entry into a tangent entry corresponds to the j th trainable parameter of the tape.

Returns

the results of the execution and the jacobian vector product

Return type

ResultBatch, TensorLike

Side Effects:

caches newly computed results or jacobians if they were not already cached.

Examples:

For an instance of DeviceDerivatives jpc, we have:

>>> tape0 = qml.tape.QuantumScript([qml.RX(0.1, wires=0)], [qml.expval(qml.Z(0))])
>>> tape1 = qml.tape.QuantumScript([qml.RY(0.2, wires=0)], [qml.expval(qml.Z(0))])
>>> batch = (tape0, tape1)
>>> tangents0 = (1.5, )
>>> tangents1 = (2.0, )
>>> tangents = (tangents0, tangents1)
>>> results, jvps = jpc.execute_and_compute_jvp(batch, tangents)
>>> expected_results = (np.cos(0.1), np.cos(0.2))
>>> qml.math.allclose(results, expected_results)
True
>>> jvps
(array(-0.14975012), array(-0.39733866))
>>> expected_jvps = 1.5 * -np.sin(0.1), 2.0 * -np.sin(0.2)
>>> qml.math.allclose(jvps, expected_jvps)
True

While this method could support non-scalar parameters in theory, no implementation currently supports jacobians with non-scalar parameters.