DefaultHybrid¶
- class hybridlane.devices.default_hybrid.device.DefaultHybrid(fock_level=None, wire_dims=None, wires=None, seed='global', shots=None, max_workers=None)¶
Bases:
pennylane.devices.device_api.DeviceA hybridlane device written in Python capable of backpropagation
- Parameters:
fock_level (int | None) – The default truncation level for all qumodes.
wire_dims (Mapping[Any, int] | None) – A mapping from wires to their dimensions. Use this to provide non-uniform truncation levels across qumodes. Note that only one of fock_level or wire_dims may be specified.
max_workers (int | None) – The maximum number of worker processes to use when executing multiple circuits in parallel. If None, execution will be performed serially in the main process.
seed (Any) – The seed for the random number generator. This can be an integer or a
jax.Array. If “global”, the seed will be drawn from the global random state of numpy.wires (pennylane.wires.WiresLike | None)
shots (int | None)
Example
import pennylane as qp import hybridlane as hl def circuit(alpha): qp.CatState(alpha, 0, 0, wires=0) hl.D(alpha, 0, 0) # |0> + |2α> qp.H(1) hl.SQR(np.pi, np.pi / 2, 0, wires=[1, 0]) # Ry(pi)|0><0| qp.H(1) return hl.expval(qp.Z(1))
>>> tape = qp.tape.make_qscript(circuit)(0.123) >>> dev = DefaultHybrid(fock_level=8) >>> program, execution_config = dev.preprocess() >>> new_batch, postprocessing_fn = program([tape]) >>> results = dev.execute(new_batch, execution_config=execution_config) >>> postprocessing_fn(results) (np.float64(-0.970195190896443),)
This device supports backpropagation:
>>> from pennylane.devices import ExecutionConfig >>> dev.supports_derivatives(ExecutionConfig(gradient_method="backprop")) True
It is mostly compatible with Jax and can be used to take gradients
import jax def circuit(alpha): hl.D(alpha, 0, wires=0) return hl.expval(hl.X(0)) @jax.jit def f(x): tape = qp.tape.make_qscript(circuit)(x) program, execution_config = dev.preprocess() new_batch, postprocessing_fn = program([tape]) results = dev.execute(new_batch, execution_config=execution_config) return postprocessing_fn(results)[0]
>>> f(jnp.array(0.123)) Array(0.1739, dtype=float64) >>> jax.grad(f)(jnp.array(0.123)) Array(1.4142, dtype=float64, weak_type=True)
Details
This device performs dense statevector simulation in Fock space, and is therefore unlikely to be scalable. However, it serves as a useful reference implementation for testing and debugging, and also provides a template for how to implement a hybrid device using the PennyLane device API.
Supported measurements
Measurement
numpy
jax.jit
expval (analytic)
✅
✅
expval (finite)
✅
✅
var (analytic)
✅
✅
var (finite)
✅
✅
state
✅
✅
density_matrix
✅
✅
sample
✅
❌
Currently the device does not support shot partitioning.
Other limitations
Mid-circuit measurements aren’t supported yet. #51
Operator batching isn’t supported. If you want to batch operations, consider using
jax.vmap. #52
- name = 'default.hybrid'¶
The name of the device or set of devices.
This property can either be the name of the class, or an alias to be used in the
device()constructor, such as"default.qubit"or"lightning.qubit".
- author = 'PNNL'¶
- version¶
- pennylane_requires = '>=0.45.0'¶
- supports_derivatives(execution_config=None, circuit=None)¶
Determine whether or not a device provided derivative is potentially available.
Default behaviour assumes first order device derivatives for all circuits exist if
compute_derivatives()is overriden.- Parameters:
execution_config (ExecutionConfig) – A description of the hyperparameters for the desired computation.
circuit (None, QuantumTape) – A specific circuit to check differentation for.
- Returns:
Bool
- Return type:
The device can support multiple different types of “device derivatives”, chosen via
execution_config.gradient_method. For example, a device can natively calculate"parameter-shift"derivatives, in which casecompute_derivatives()will be called for the derivative instead ofexecute()with a batch of circuits.>>> config = ExecutionConfig(gradient_method="parameter-shift") >>> custom_device.supports_derivatives(config) True
In this case,
compute_derivatives()orexecute_and_compute_derivatives()will be called instead ofexecute()with a batch of circuits.If
circuitis not provided, then the method should return whether or not device derivatives exist for any circuit.Example:
For example, the Python device will support device differentiation via the adjoint differentiation algorithm if the order is
1and the execution occurs with no shots (shots=None).>>> config = ExecutionConfig(derivative_order=1, gradient_method="adjoint") >>> dev.supports_derivatives(config) True >>> circuit_analytic = qp.tape.QuantumScript([qp.RX(0.1, wires=0)], [qp.expval(qp.Z(0))], shots=None) >>> dev.supports_derivatives(config, circuit=circuit_analytic) True >>> circuit_finite_shots = qp.tape.QuantumScript([qp.RX(0.1, wires=0)], [qp.expval(qp.Z(0))], shots=10) >>> dev.supports_derivatives(config, circuit = circuit_finite_shots) False
>>> config = ExecutionConfig(derivative_order=2, gradient_method="adjoint") >>> dev.supports_derivatives(config) False
Adjoint differentiation will only be supported for circuits with expectation value measurements. If a circuit is provided and it cannot be converted to a form supported by differentiation method by
preprocess(), thensupports_derivativesshould return False.>>> config = ExecutionConfig(derivative_order=1, gradient_method="adjoint") >>> circuit = qp.tape.QuantumScript([qp.RX(2.0, wires=0)], [qp.probs(wires=(0,1))]) >>> dev.supports_derivatives(config, circuit=circuit) False
If the circuit is not natively supported by the differentiation method but can be converted into a form that is supported, it should still return
True. For example,Rotgates are not natively supported by adjoint differentation, as they do not have a generator, but they can be compiled into operations supported by adjoint differentiation. Therefore this method may reproduce compilation and validation steps performed bypreprocess().>>> config = ExecutionConfig(derivative_order=1, gradient_method="adjoint") >>> circuit = qp.tape.QuantumScript([qp.Rot(1.2, 2.3, 3.4, wires=0)], [qp.expval(qp.Z(0))]) >>> dev.supports_derivatives(config, circuit=circuit) True
Backpropagation:
This method is also used be to validate support for backpropagation derivatives. Backpropagation is only supported if the device is transparent to the machine learning framework from start to finish.
>>> config = ExecutionConfig(gradient_method="backprop") >>> python_device.supports_derivatives(config) True >>> cpp_device.supports_derivatives(config) False
- setup_execution_config(config=None, circuit=None)¶
Sets up an
ExecutionConfigthat configures the execution behaviour.The execution config stores information on how the device should perform the execution, as well as how PennyLane should interact with the device. See
ExecutionConfigfor all available options and what they mean.An
ExecutionConfigis constructed from arguments passed to theQNode, and this method allows the device to update the config object based on device-specific requirements or preferences. See Execution Config for more details.- Parameters:
config (ExecutionConfig) – The initial ExecutionConfig object that describes the parameters needed to configure the execution behaviour.
circuit (QuantumScript) – The quantum circuit to customize the execution config for.
- Returns:
The updated ExecutionConfig object
- Return type:
ExecutionConfig
- preprocess_transforms(execution_config=None)¶
Returns the compile pileline to preprocess a circuit for execution.
- Parameters:
execution_config (ExecutionConfig) – The execution configuration object
- Returns:
A compile pileline that is called before execution
- Return type:
CompilePipeline
The compile pileline is composed of a list of individual transforms, which may include:
Decomposition of operations and measurements to what is supported by the device.
Splitting a circuit with measurements of non-commuting observables or Hamiltonians into multiple executions.
Splitting a circuit with batched parameters into multiple executions.
Validation of wires, measurements, and observables.
Gradient specific preprocessing, such as making sure trainable operators have generators.
Example
All transforms that are part of the preprocessing compile pileline need to respect the transform contract defined in
pennylane.transform().from pennylane.tape import QuantumScriptBatch from pennylane.typing import PostprocessingFn @qp.transform def my_preprocessing_transform(tape: qp.tape.QuantumScript) -> tuple[QuantumScriptBatch, PostprocessingFn]: # e.g. valid the measurements, expand the tape for the hardware execution, ... def blank_processing_fn(results): return results[0] return [tape], processing_fn
A compile pileline can hold an arbitrary number of individual transforms:
def preprocess(self, config): program = CompilePipeline() program.add_transform(my_preprocessing_transform) return program
See also
transform()andCompilePipeline
- execute(circuits, execution_config=None)¶
Execute a circuit or a batch of circuits and turn it into results.
- Parameters:
circuits (Union[QuantumTape, Sequence[QuantumTape]]) – the quantum circuits to be executed
execution_config (ExecutionConfig) – a datastructure with additional information required for execution
- Returns:
A numeric result of the computation.
- Return type:
Interface parameters:
The provided
circuitsmay contain interface specific data-types liketorch.Tensororjax.Arraywhengradient_methodof"backprop"is requested. If the gradient method is not backpropagation, then only vanilla numpy parameters or builtins will be present in the circuits.