def __init__( self, func, device, interface="autograd", diff_method="best", caching=0, **diff_options ): if interface is not None and interface not in self.INTERFACE_MAP: raise qml.QuantumFunctionError( f"Unknown interface {interface}. Interface must be " f"one of {self.INTERFACE_MAP.values()}." ) if not isinstance(device, Device): raise qml.QuantumFunctionError( "Invalid device. Device must be a valid PennyLane device." ) self.func = func self.device = device self.qtape = None self._tape, self.interface, self.diff_method = self.get_tape(device, interface, diff_method) self.diff_options = diff_options or {} self.diff_options["method"] = self.diff_method self.dtype = np.float64 self.max_expansion = 2 self._caching = caching """float: number of device executions to store in a cache to speed up subsequent executions. If set to zero, no caching occurs.""" if caching != 0 and self.diff_method == "backprop": raise ValueError('Caching mode is incompatible with the "backprop" diff_method') self._cache_execute = OrderedDict() """OrderedDict[int: Any]: A copy of the ``_cache_execute`` dictionary from the quantum
def _validate_backprop_method(device, interface): """Validates whether a particular device and QuantumTape interface supports the ``"backprop"`` differentiation method. Args: device (.Device): PennyLane device interface (str): name of the requested interface Returns: tuple[.QuantumTape, str, str]: tuple containing the compatible QuantumTape, the interface to apply, and the method argument to pass to the ``QuantumTape.jacobian`` method Raises: qml.QuantumFunctionError: if the device does not support backpropagation, or the interface provided is not compatible with the device """ # determine if the device supports backpropagation backprop_interface = device.capabilities().get("passthru_interface", None) if backprop_interface is not None: if interface == backprop_interface: return QuantumTape, None, "backprop" raise qml.QuantumFunctionError( f"Device {device.short_name} only supports diff_method='backprop' when using the " f"{backprop_interface} interface.") raise qml.QuantumFunctionError( f"The {device.short_name} device does not support native computations with " "autodifferentiation frameworks.")
def __init__(self, func, device, interface="autograd", diff_method="best", mutable=True, **diff_options): if interface is not None and interface not in self.INTERFACE_MAP: raise qml.QuantumFunctionError( f"Unknown interface {interface}. Interface must be " f"one of {list(self.INTERFACE_MAP.keys())}.") if not isinstance(device, Device): raise qml.QuantumFunctionError( "Invalid device. Device must be a valid PennyLane device.") self.mutable = mutable self.func = func self._original_device = device self.qtape = None self.qfunc_output = None # store the user-specified differentiation method self.diff_method = diff_method self._tape, self.interface, self.device, tape_diff_options = self.get_tape( device, interface, diff_method) # The arguments to be passed to JacobianTape.jacobian self.diff_options = diff_options or {} self.diff_options.update(tape_diff_options) self.dtype = np.float64 self.max_expansion = 2
def __init__(self, func, device, interface="autograd", diff_method="best", **diff_options): if interface is not None and interface not in self.INTERFACE_MAP: raise qml.QuantumFunctionError( f"Unknown interface {interface}. Interface must be " f"one of {list(self.INTERFACE_MAP.keys())}." ) if not isinstance(device, Device): raise qml.QuantumFunctionError( "Invalid device. Device must be a valid PennyLane device." ) self.func = func self._original_device = device self.qtape = None self.qfunc_output = None # store the user-specified differentiation method self.diff_method = diff_method self._tape, self.interface, diff_method, self.device = self.get_tape( device, interface, diff_method ) # The arguments to be passed to JacobianTape.jacobian self.diff_options = diff_options or {} # Store the differentiation method to be passed to JacobianTape.jacobian(). # Note that the tape accepts a different set of allowed methods than the QNode: # best, analytic, numeric, device self.diff_options["method"] = diff_method self.dtype = np.float64 self.max_expansion = 2
def __init__(self, func, device, interface="autograd", diff_method="best", **diff_options): if interface is not None and interface not in self.INTERFACE_MAP: raise qml.QuantumFunctionError( f"Unknown interface {interface}. Interface must be " f"one of {list(self.INTERFACE_MAP.keys())}.") if not isinstance(device, Device): raise qml.QuantumFunctionError( "Invalid device. Device must be a valid PennyLane device.") self.func = func self.device = device self.qtape = None self.qfunc_output = None self._tape, self.interface, self.diff_method = self.get_tape( device, interface, diff_method) self.diff_options = diff_options or {} self.diff_options["method"] = self.diff_method self.dtype = np.float64 self.max_expansion = 2
def access_state(self, wires=None): """Check that the device has access to an internal state and return it if available. Args: wires (Wires): wires of the reduced system Raises: QuantumFunctionError: if the device is not capable of returning the state Returns: array or tensor: the state or the density matrix of the device """ if not self.capabilities().get("returns_state"): raise qml.QuantumFunctionError( "The current device is not capable of returning the state") state = getattr(self, "state", None) if state is None: raise qml.QuantumFunctionError( "The state is not available in the current device") if wires: density_matrix = self.density_matrix(wires) return density_matrix return state
def construct(self, args, kwargs): """Call the quantum function with a tape context, ensuring the operations get queued.""" if self.interface == "autograd": # HOTFIX: to maintain backwards compatibility existing PennyLane code and demos, here we treat # all inputs that do not explicitly specify `requires_grad=False` # as trainable. This should be removed at some point, forcing users # to specify `requires_grad=True` for trainable parameters. args = [ qml.numpy.array(a, requires_grad=True) if not hasattr(a, "requires_grad") else a for a in args ] self._tape = qml.tape.JacobianTape() with self.tape: self._qfunc_output = self.func(*args, **kwargs) params = self.tape.get_parameters(trainable_only=False) self.tape.trainable_params = qml.math.get_trainable_indices(params) if not isinstance(self._qfunc_output, Sequence): measurement_processes = (self._qfunc_output,) else: measurement_processes = self._qfunc_output if not all(isinstance(m, qml.measure.MeasurementProcess) for m in measurement_processes): raise qml.QuantumFunctionError( "A quantum function must return either a single measurement, " "or a nonempty sequence of measurements." ) if not all(ret == m for ret, m in zip(measurement_processes, self.tape.measurements)): raise qml.QuantumFunctionError( "All measurements must be returned in the order they are measured." ) for obj in self.tape.operations + self.tape.observables: if getattr(obj, "num_wires", None) is qml.operation.WiresEnum.AllWires: # check here only if enough wires if len(obj.wires) != self.device.num_wires: raise qml.QuantumFunctionError( "Operator {} must act on all wires".format(obj.name) ) if isinstance(obj, qml.ops.qubit.SparseHamiltonian) and self.gradient_fn == "backprop": raise qml.QuantumFunctionError( "SparseHamiltonian observable must be used with the parameter-shift" " differentiation method" ) if self.expansion_strategy == "device": self._tape = self.device.expand_fn(self.tape, max_expansion=self.max_expansion) # If the gradient function is a transform, expand the tape so that # all operations are supported by the transform. if isinstance(self.gradient_fn, qml.gradients.gradient_transform): self._tape = self.gradient_fn.expand_fn(self._tape)
def _validate_backprop_method(device, interface): """Validates whether a particular device and JacobianTape interface supports the ``"backprop"`` differentiation method. Args: device (.Device): PennyLane device interface (str): name of the requested interface Returns: tuple[.JacobianTape, str, .Device, dict[str, str]]: Tuple containing the compatible JacobianTape, the interface to apply, the device to use, and the method argument to pass to the ``JacobianTape.jacobian`` method. Raises: qml.QuantumFunctionError: if the device does not support backpropagation, or the interface provided is not compatible with the device """ # determine if the device supports backpropagation backprop_interface = device.capabilities().get("passthru_interface", None) # determine if the device has any child devices that support backpropagation backprop_devices = device.capabilities().get("passthru_devices", None) if getattr(device, "cache", 0): raise qml.QuantumFunctionError( "Device caching is incompatible with the backprop diff_method") if backprop_interface is not None: # device supports backpropagation natively if interface == backprop_interface: return JacobianTape, interface, device, {"method": "backprop"} raise qml.QuantumFunctionError( f"Device {device.short_name} only supports diff_method='backprop' when using the " f"{backprop_interface} interface.") if device.shots is None and backprop_devices is not None: # device is analytic and has child devices that support backpropagation natively if interface in backprop_devices: # TODO: need a better way of passing existing device init options # to a new device? device = qml.device( backprop_devices[interface], wires=device.wires, shots=device.shots, ) return JacobianTape, interface, device, {"method": "backprop"} raise qml.QuantumFunctionError( f"Device {device.short_name} only supports diff_method='backprop' when using the " f"{list(backprop_devices.keys())} interfaces.") raise qml.QuantumFunctionError( f"The {device.short_name} device does not support native computations with " "autodifferentiation frameworks.")
def construct(self, args, kwargs): """Call the quantum function with a tape context, ensuring the operations get queued.""" self.qtape = self._tape() with self.qtape: self.qfunc_output = self.func(*args, **kwargs) if not isinstance(self.qfunc_output, Sequence): measurement_processes = (self.qfunc_output, ) else: measurement_processes = self.qfunc_output if not all( isinstance(m, qml.tape.MeasurementProcess) for m in measurement_processes): raise qml.QuantumFunctionError( "A quantum function must return either a single measurement, " "or a nonempty sequence of measurements.") state_returns = any( [m.return_type is State for m in measurement_processes]) # apply the interface (if any) if self.interface is not None: # pylint: disable=protected-access if state_returns and self.interface in ["torch", "tf"]: # The state is complex and we need to indicate this in the to_torch or to_tf # functions self.INTERFACE_MAP[self.interface](self, dtype=np.complex128) else: self.INTERFACE_MAP[self.interface](self) if not all(ret == m for ret, m in zip(measurement_processes, self.qtape.measurements)): raise qml.QuantumFunctionError( "All measurements must be returned in the order they are measured." ) # provide the jacobian options self.qtape.jacobian_options = self.diff_options stop_at = self.device.operations # pylint: disable=protected-access obs_on_same_wire = len(self.qtape._obs_sharing_wires) > 0 ops_not_supported = not {op.name for op in self.qtape.operations }.issubset(stop_at) # expand out the tape, if any operations are not supported on the device or multiple # observables are measured on the same wire if ops_not_supported or obs_on_same_wire: self.qtape = self.qtape.expand( depth=self.max_expansion, stop_at=lambda obj: obj.name in stop_at)
def construct(self, args, kwargs): """Call the quantum function with a tape context, ensuring the operations get queued.""" self.qtape = self._tape() with self.qtape: self.qfunc_output = self.func(*args, **kwargs) if not isinstance(self.qfunc_output, Sequence): measurement_processes = (self.qfunc_output, ) else: measurement_processes = self.qfunc_output if not all( isinstance(m, qml.tape.MeasurementProcess) for m in measurement_processes): raise qml.QuantumFunctionError( "A quantum function must return either a single measurement, " "or a nonempty sequence of measurements.") state_returns = any( [m.return_type is State for m in measurement_processes]) # apply the interface (if any) if self.interface is not None: # pylint: disable=protected-access if state_returns and self.interface in ["torch", "tf"]: # The state is complex and we need to indicate this in the to_torch or to_tf # functions self.INTERFACE_MAP[self.interface](self, dtype=np.complex128) else: self.INTERFACE_MAP[self.interface](self) if not all(ret == m for ret, m in zip(measurement_processes, self.qtape.measurements)): raise qml.QuantumFunctionError( "All measurements must be returned in the order they are measured." ) # provide the jacobian options self.qtape.jacobian_options = self.diff_options stop_at = self.device.operations # Hotfix that allows controlled rotations to return the correct gradients # when using the parameter shift rule. if isinstance(self.qtape, QubitParamShiftTape): # controlled rotations aren't supported by the parameter-shift rule stop_at = set( self.device.operations) - {"CRX", "CRZ", "CRY", "CRot"} # expand out the tape, if any operations are not supported on the device if not {op.name for op in self.qtape.operations}.issubset(stop_at): self.qtape = self.qtape.expand( depth=self.max_expansion, stop_at=lambda obj: obj.name in stop_at)
def _validate_backprop_method(device, interface): # determine if the device supports backpropagation backprop_interface = device.capabilities().get("passthru_interface", None) # determine if the device has any child devices that support backpropagation backprop_devices = device.capabilities().get("passthru_devices", None) if getattr(device, "cache", 0): # TODO: deprecate device caching, and replacing with QNode caching. raise qml.QuantumFunctionError( "Device caching is incompatible with the backprop diff_method" ) if backprop_interface is not None: # device supports backpropagation natively if interface == backprop_interface: return "backprop", {}, device raise qml.QuantumFunctionError( f"Device {device.short_name} only supports diff_method='backprop' when using the " f"{backprop_interface} interface." ) if backprop_devices is not None: if device.shots is None: # device is analytic and has child devices that support backpropagation natively if interface in backprop_devices: # TODO: need a better way of passing existing device init options # to a new device? expand_fn = device.expand_fn batch_transform = device.batch_transform device = qml.device( backprop_devices[interface], wires=device.wires, shots=device.shots, ) device.expand_fn = expand_fn device.batch_transform = batch_transform return "backprop", {}, device raise qml.QuantumFunctionError( f"Device {device.short_name} only supports diff_method='backprop' when using the " f"{list(backprop_devices.keys())} interfaces." ) raise qml.QuantumFunctionError("Backpropagation is only supported when shots=None.") raise qml.QuantumFunctionError( f"The {device.short_name} device does not support native computations with " "autodifferentiation frameworks." )
def __init__( self, func, device, interface="autograd", diff_method="best", mutable=True, max_expansion=10, **diff_options, ): if interface is not None and interface not in self.INTERFACE_MAP: raise qml.QuantumFunctionError( f"Unknown interface {interface}. Interface must be " f"one of {list(self.INTERFACE_MAP.keys())}." ) if not isinstance(device, Device): raise qml.QuantumFunctionError( "Invalid device. Device must be a valid PennyLane device." ) if "shots" in inspect.signature(func).parameters: warnings.warn( "Detected 'shots' as an argument to the given quantum function. " "The 'shots' argument name is reserved for overriding the number of shots " "taken by the device. Its use outside of this context should be avoided.", DeprecationWarning, ) self._qfunc_uses_shots_arg = True else: self._qfunc_uses_shots_arg = False self.mutable = mutable self.func = func self._original_device = device self.qtape = None self.qfunc_output = None # store the user-specified differentiation method self.diff_method = diff_method self._tape, self.interface, self.device, tape_diff_options = self.get_tape( device, interface, diff_method ) # The arguments to be passed to JacobianTape.jacobian self.diff_options = diff_options or {} self.diff_options.update(tape_diff_options) self.dtype = np.float64 self.max_expansion = max_expansion
def _validate_device_method(device, interface): """Validates whether a particular device and JacobianTape interface supports the ``"device"`` differentiation method. Args: device (.Device): PennyLane device interface (str): name of the requested interface Returns: tuple[.JacobianTape, str, str]: tuple containing the compatible JacobianTape, the interface to apply, and the method argument to pass to the ``JacobianTape.jacobian`` method Raises: qml.QuantumFunctionError: if the device does not provide a native method for computing the Jacobian """ # determine if the device provides its own jacobian method provides_jacobian = device.capabilities().get("provides_jacobian", False) if not provides_jacobian: raise qml.QuantumFunctionError( f"The {device.short_name} device does not provide a native " "method for computing the jacobian.") return JacobianTape, interface, "device"
def to_tf(self, dtype=None): """Apply the TensorFlow interface to the internal quantum tape. Args: dtype (tf.dtype): The dtype that the TensorFlow QNode should output. If not provided, the default is ``tf.float64``. Raises: .QuantumFunctionError: if TensorFlow >= 2.1 is not installed """ # pylint: disable=import-outside-toplevel try: import tensorflow as tf from pennylane.tape.interfaces.tf import TFInterface self.interface = "tf" if not isinstance(self.dtype, tf.DType): self.dtype = None self.dtype = dtype or self.dtype or TFInterface.dtype if self.qtape is not None: TFInterface.apply(self.qtape, dtype=tf.as_dtype(self.dtype)) except ImportError as e: raise qml.QuantumFunctionError( "TensorFlow not found. Please install the latest " "version of TensorFlow to enable the 'tf' interface.") from e
def to_jax(self): """Apply the JAX interface to the internal quantum tape. Args: dtype (tf.dtype): The dtype that the JAX QNode should output. If not provided, the default is ``jnp.float64``. Raises: .QuantumFunctionError: if TensorFlow >= 2.1 is not installed """ # pylint: disable=import-outside-toplevel try: from pennylane.tape.interfaces.jax import JAXInterface if self.interface != "jax" and self.interface is not None: # Since the interface is changing, need to re-validate the tape class. self._tape, interface, self.device, diff_options = self.get_tape( self._original_device, "jax", self.diff_method ) self.interface = interface self.diff_options.update(diff_options) else: self.interface = "jax" if self.qtape is not None: JAXInterface.apply(self.qtape) except ImportError as e: raise qml.QuantumFunctionError( "JAX not found. Please install the latest " "version of JAX to enable the 'jax' interface." ) from e
def var(op): r"""Variance of the supplied observable. **Example:** .. code-block:: python3 dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.Hadamard(wires=1) qml.CNOT(wires=[0, 1]) return qml.var(qml.PauliY(0)) Executing this QNode: >>> circuit(0.5) 0.7701511529340698 Args: op (Observable): a quantum observable object Raises: QuantumFunctionError: `op` is not an instance of :class:`~.Observable` """ if not isinstance(op, Observable): raise qml.QuantumFunctionError( "{} is not an observable: cannot be used with var".format(op.name)) return MeasurementProcess(Variance, obs=op)
def expval(op): r"""Expectation value of the supplied observable. **Example:** .. code-block:: python3 dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.Hadamard(wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliY(0)) Executing this QNode: >>> circuit(0.5) -0.4794255386042029 Args: op (Observable): a quantum observable object Raises: QuantumFunctionError: `op` is not an instance of :class:`~.Observable` """ if not isinstance(op, (Observable, qml.Hamiltonian)): raise qml.QuantumFunctionError( "{} is not an observable: cannot be used with expval".format( op.name)) return MeasurementProcess(Expectation, obs=op)
def apply(cls, tape, dtype=torch.float64): """Apply the Torch interface to an existing tape in-place. Args: tape (.JacobianTape): a quantum tape to apply the Torch interface to dtype (torch.dtype): the dtype that the returned quantum tape should output **Example** >>> with JacobianTape() as tape: ... qml.RX(0.5, wires=0) ... expval(qml.PauliZ(0)) >>> TorchInterface.apply(tape) >>> tape <TorchQuantumTape: wires=<Wires = [0]>, params=1> """ if (dtype is torch.complex64 or dtype is torch.complex128) and not COMPLEX_SUPPORT: raise qml.QuantumFunctionError( "Version 1.6.0 or above of PyTorch must be installed for complex support, " "which is required for quantum functions that return the state." ) tape_class = getattr(tape, "__bare__", tape.__class__) tape.__bare__ = tape_class tape.__class__ = type("TorchQuantumTape", (cls, tape_class), {"dtype": dtype}) tape._update_trainable_params() return tape
def to_torch(self, dtype=None): """Apply the Torch interface to the internal quantum tape. Args: dtype (tf.dtype): The dtype that the Torch QNode should output. If not provided, the default is ``torch.float64``. Raises: .QuantumFunctionError: if PyTorch >= 1.3 is not installed """ # pylint: disable=import-outside-toplevel try: import torch from pennylane.tape.interfaces.torch import TorchInterface self.interface = "torch" if not isinstance(self.dtype, torch.dtype): self.dtype = None self.dtype = dtype or self.dtype or TorchInterface.dtype if self.dtype is np.complex128: self.dtype = torch.complex128 if self.qtape is not None: TorchInterface.apply(self.qtape, dtype=self.dtype) except ImportError as e: raise qml.QuantumFunctionError( "PyTorch not found. Please install the latest " "version of PyTorch to enable the 'torch' interface.") from e
def to_jax(self): """Validation checks when a user expects to use the JAX interface.""" if self.diff_method != "backprop": raise qml.QuantumFunctionError( "The JAX interface can only be used with " "diff_method='backprop' on supported devices") self.interface = "jax"
def sample_basis_states(self, number_of_states, state_probability): """Sample from the computational basis states based on the state probability. This is an auxiliary method to the generate_samples method. Args: number_of_states (int): the number of basis states to sample from Returns: List[int]: the sampled basis states """ if self.shots is None: raise qml.QuantumFunctionError( "The number of shots has to be explicitly set on the device " "when using sample-based measurements." ) shots = self.shots if self._prng_key is None: # Assuming op-by-op, so we'll just make one. key = jax.random.PRNGKey(np.random.randint(0, 2 ** 31)) else: key = self._prng_key return jax.random.choice(key, number_of_states, shape=(shots,), p=state_probability)
def _get_parameter_shift_tape(device): """Validates whether a particular device supports the parameter-shift differentiation method, and returns the correct tape. Args: device (.Device): PennyLane device Returns: .JacobianTape: the compatible JacobianTape Raises: qml.QuantumFunctionError: if the device model does not have a corresponding parameter-shift rule """ # determine if the device provides its own jacobian method model = device.capabilities().get("model", None) if model == "qubit": return QubitParamShiftTape if model == "cv": return CVParamShiftTape raise qml.QuantumFunctionError( f"Device {device.short_name} uses an unknown model ('{model}') " "that does not support the parameter-shift rule.")
def get_gradient_fn(device, interface, diff_method="best"): """Determine the best differentiation method, interface, and device for a requested device, interface, and diff method. Args: device (.Device): PennyLane device interface (str): name of the requested interface diff_method (str or .gradient_transform): The requested method of differentiation. If a string, allowed options are ``"best"``, ``"backprop"``, ``"adjoint"``, ``"device"``, ``"parameter-shift"``, or ``"finite-diff"``. A gradient transform may also be passed here. Returns: tuple[str or .gradient_transform, dict, .Device: Tuple containing the ``gradient_fn``, ``gradient_kwargs``, and the device to use when calling the execute function. """ if diff_method == "best": return QNode.get_best_method(device, interface) if diff_method == "backprop": return QNode._validate_backprop_method(device, interface) if diff_method == "adjoint": return QNode._validate_adjoint_method(device) if diff_method == "device": return QNode._validate_device_method(device) if diff_method == "parameter-shift": return QNode._validate_parameter_shift(device) if diff_method == "finite-diff": return qml.gradients.finite_diff, {}, device if isinstance(diff_method, str): raise qml.QuantumFunctionError( f"Differentiation method {diff_method} not recognized. Allowed " "options are ('best', 'parameter-shift', 'backprop', 'finite-diff', 'device', 'reversible', 'adjoint')." ) if isinstance(diff_method, qml.gradients.gradient_transform): return diff_method, {}, device raise qml.QuantumFunctionError( f"Differentiation method {diff_method} must be a gradient transform or a string." )
def interface(self, value): if value not in SUPPORTED_INTERFACES: raise qml.QuantumFunctionError( f"Unknown interface {value}. Interface must be one of {SUPPORTED_INTERFACES}." ) self._interface = value self._update_gradient_fn()
def construct(self, args, kwargs): """Call the quantum function with a tape context, ensuring the operations get queued.""" self.qtape = self._tape() # apply the interface (if any) if self.interface is not None: self.INTERFACE_MAP[self.interface](self) with self.qtape: measurement_processes = self.func(*args, **kwargs) if not isinstance(measurement_processes, Sequence): measurement_processes = (measurement_processes, ) if not all( isinstance(m, MeasurementProcess) for m in measurement_processes): raise qml.QuantumFunctionError( "A quantum function must return either a single measurement, " "or a nonempty sequence of measurements.") if not all(ret == m for ret, m in zip(measurement_processes, self.qtape.measurements)): raise qml.QuantumFunctionError( "All measurements must be returned in the order they are measured." ) # provide the jacobian options self.qtape.jacobian_options = self.diff_options stop_at = self.device.operations # Hotfix that allows controlled rotations to return the correct gradients # when using the parameter shift rule. if isinstance(self.qtape, QubitParamShiftTape): # controlled rotations aren't supported by the parameter-shift rule stop_at = set( self.device.operations) - {"CRX", "CRZ", "CRY", "CRot"} # expand out the tape, if any operations are not supported on the device if not {op.name for op in self.qtape.operations}.issubset(stop_at): self.qtape = self.qtape.expand( depth=self.max_expansion, stop_at=lambda obj: obj.name in stop_at)
def specs(self): """Resource information about a quantum circuit. Returns: dict[str, Union[defaultdict,int]]: dictionaries that contain QNode specifications **Example** .. code-block:: python3 dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(x): qml.RX(x[0], wires=0) qml.RY(x[1], wires=1) qml.CNOT(wires=(0,1)) return qml.probs(wires=(0,1)) x = np.array([0.1, 0.2]) res = circuit(x) >>> circuit.specs {'gate_sizes': defaultdict(int, {1: 2, 2: 1}), 'gate_types': defaultdict(int, {'RX': 1, 'RY': 1, 'CNOT': 1}), 'num_operations': 3, 'num_observables': 1, 'num_diagonalizing_gates': 0, 'num_used_wires': 2, 'depth': 2, 'num_device_wires': 2, 'device_name': 'default.qubit.autograd', 'diff_method': 'backprop'} """ if self.qtape is None: raise qml.QuantumFunctionError( "The QNode specifications can only be calculated after its quantum tape has been constructed." ) info = self.qtape.specs.copy() info["num_device_wires"] = self.device.num_wires info["device_name"] = self.device.short_name # TODO: use self.diff_method when that value gets updated if self.diff_method != "best": info["diff_method"] = self.diff_method else: info["diff_method"] = self.qtape.jacobian_options["method"] # tapes do not accurately track parameters for backprop # TODO: calculate number of trainable parameters in backprop # find better syntax for determining if backprop if info["diff_method"] == "backprop": del info["num_trainable_params"] return info
def execute_device(self, params, device): """Execute the tape on a quantum device. This is a low-level method, intended to be called by an interface, and does not support autodifferentiation. For more details on differentiable tape execution, see :meth:`~.execute`. Args: device (~.Device): a PennyLane device that can execute quantum operations and return measurement statistics params (list[Any]): The quantum tape operation parameters. If not provided, the current tape parameter values are used (via :meth:`~.get_parameters`). """ if not all( len(o.diagonalizing_gates()) == 0 for o in self._obs_sharing_wires): raise qml.QuantumFunctionError( "Multiple observables are being evaluated on the same wire. Call tape.expand() " "prior to execution to support this.") device.reset() # backup the current parameters saved_parameters = self.get_parameters() # temporarily mutate the in-place parameters self.set_parameters(params) if isinstance(device, qml.QubitDevice): res = device.execute(self) else: res = device.execute(self.operations, self.observables, {}) # Update output dim if incorrect. # Note that we cannot assume the type of `res`, so # we use duck typing to catch any 'array like' object. try: if isinstance(res, np.ndarray) and res.dtype is np.dtype("object"): output_dim = sum([len(i) for i in res]) else: output_dim = np.prod(res.shape) if self.output_dim != output_dim: # update the inferred output dimension with the correct value self._output_dim = output_dim except (AttributeError, TypeError): # unable to determine the output dimension pass # restore original parameters self.set_parameters(saved_parameters) return res
def _validate_device_method(device): # determine if the device provides its own jacobian method provides_jacobian = device.capabilities().get("provides_jacobian", False) if not provides_jacobian: raise qml.QuantumFunctionError( f"The {device.short_name} device does not provide a native " "method for computing the jacobian." ) return "device", {}, device
def __init__(self, unitary, target_wires, estimation_wires, do_queue=True): self.target_wires = list(target_wires) self.estimation_wires = list(estimation_wires) wires = self.target_wires + self.estimation_wires if any(wire in self.target_wires for wire in self.estimation_wires): raise qml.QuantumFunctionError( "The target wires and estimation wires must be different") super().__init__(unitary, wires=wires, do_queue=do_queue)
def _validate_parameter_shift(device): model = device.capabilities().get("model", None) if model == "qubit": return qml.gradients.param_shift, {}, device if model == "cv": return qml.gradients.param_shift_cv, {"dev": device}, device raise qml.QuantumFunctionError( f"Device {device.short_name} uses an unknown model ('{model}') " "that does not support the parameter-shift rule.")