Example #1
0
    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
Example #2
0
    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.")
Example #3
0
    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
Example #4
0
    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
Example #5
0
    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
Example #6
0
    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
Example #7
0
    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)
Example #8
0
    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.")
Example #9
0
    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)
Example #10
0
    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)
Example #11
0
    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."
        )
Example #12
0
    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
Example #13
0
    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"
Example #14
0
    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
Example #15
0
    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
Example #16
0
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)
Example #17
0
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)
Example #18
0
    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
Example #19
0
    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
Example #20
0
 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"
Example #21
0
    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)
Example #22
0
    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.")
Example #23
0
    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."
        )
Example #24
0
    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()
Example #25
0
    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)
Example #26
0
    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
Example #27
0
    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
Example #28
0
    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
Example #29
0
    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)
Example #30
0
    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.")