示例#1
0
def _preprocess(weights, initial_layer_weights, wires):
    """Validate and pre-process inputs as follows:

    * Check the shapes of the two weights tensors.

    Args:
        weights (tensor_like): trainable parameters of the template
        initial_layer_weights (tensor_like): weight tensor for the initial rotation block, shape ``(M,)``
        wires (Wires): wires that template acts on

    Returns:
        int: number of times that the ansatz is repeated
    """

    if qml.tape_mode_active():

        shape = qml.math.shape(weights)
        repeat = shape[0]

        if len(shape) > 1:
            if shape[1] != len(wires) - 1:
                raise ValueError(
                    f"Weights tensor must have second dimension of length {len(wires) - 1}; got {shape[1]}"
                )

            if shape[2] != 2:
                raise ValueError(
                    f"Weights tensor must have third dimension of length 2; got {shape[2]}"
                )

        shape2 = qml.math.shape(initial_layer_weights)
        if shape2 != (len(wires), ):
            raise ValueError(
                f"Initial layer weights must be of shape {(len(wires),)}; got {shape2}"
            )

    else:
        repeat = check_number_of_layers([weights])

        expected_shape_initial = (len(wires), )
        check_shape(
            initial_layer_weights,
            expected_shape_initial,
            msg="Initial layer weights must be of shape {}; got {}"
            "".format(expected_shape_initial,
                      get_shape(initial_layer_weights)),
        )

        if len(wires) in [0, 1]:
            expected_shape_weights = (0, )
        else:
            expected_shape_weights = (repeat, len(wires) - 1, 2)

        check_shape(
            weights,
            expected_shape_weights,
            msg="Weights tensor must be of shape {}; got {}"
            "".format(expected_shape_weights, get_shape(weights)),
        )
    return repeat
示例#2
0
def _preprocess(weights):
    """Validate and pre-process inputs as follows:

    * Check that the weights tensor is 2-dimensional.

    Args:
        weights (tensor_like): trainable parameters of the template

    Returns:
        int: number of times that the ansatz is repeated
    """

    if qml.tape_mode_active():

        shape = qml.math.shape(weights)

        if len(shape) != 2:
            raise ValueError(
                f"Weights tensor must be 2-dimensional; got shape {shape}")

        repeat = shape[0]

    else:
        repeat = check_number_of_layers([weights])
        n_rots = get_shape(weights)[1]

        expected_shape = (repeat, n_rots)
        check_shape(
            weights,
            expected_shape,
            msg="'weights' must be of shape {}; got {}"
            "".format(expected_shape, get_shape(weights)),
        )

    return repeat
示例#3
0
def _preprocess(weights, wires, init_state):
    """Validate and pre-process inputs as follows:

    * Check that the weights tensor has the correct shape.
    * Extract a wire list for the subroutines of this template.
    * Cast initial state to a numpy array.

    Args:
        weights (tensor_like): trainable parameters of the template
        wires (Wires): wires that template acts on
        init_state (tensor_like): shape ``(len(wires),)`` tensor

    Returns:
        int, list[Wires], array: number of times that the ansatz is repeated, wires pattern,
            and preprocessed initial state
    """
    if len(wires) < 2:
        raise ValueError(
            "This template requires the number of qubits to be greater than one;"
            "got a wire sequence with {} elements".format(len(wires)))

    if qml.tape_mode_active():

        shape = qml.math.shape(weights)

        if len(shape) != 3:
            raise ValueError(
                f"Weights tensor must be 3-dimensional; got shape {shape}")

        if shape[1] != len(wires) - 1:
            raise ValueError(
                f"Weights tensor must have second dimension of length {len(wires) - 1}; got {shape[1]}"
            )

        if shape[2] != 2:
            raise ValueError(
                f"Weights tensor must have third dimension of length 2; got {shape[2]}"
            )

        repeat = shape[0]

    else:
        repeat = get_shape(weights)[0]

        expected_shape = (repeat, len(wires) - 1, 2)
        check_shape(
            weights,
            expected_shape,
            msg="Weights tensor must be of shape {}; got {}".format(
                expected_shape, get_shape(weights)),
        )

    nm_wires = [wires.subset([l, l + 1]) for l in range(0, len(wires) - 1, 2)]
    nm_wires += [wires.subset([l, l + 1]) for l in range(1, len(wires) - 1, 2)]
    # we can extract the numpy representation here
    # since init_state can never be differentiable
    init_state = qml.math.toarray(init_state)
    return repeat, nm_wires, init_state
示例#4
0
def _preprocess(weight, wires):
    """Validate and pre-process inputs as follows:

    * Check the shape of the weights tensor.
    * Check that there are at least 2 wires.

    Args:
        weight (tensor_like): trainable parameters of the template
        wires (Wires): wires that template acts on
    """
    if len(wires) < 2:
        raise ValueError("expected at least two wires; got {}".format(
            len(wires)))

    if qml.tape_mode_active():

        shape = qml.math.shape(weight)
        if shape != ():
            raise ValueError(
                f"Weight must be a scalar tensor {()}; got shape {shape}.")

    else:
        expected_shape = ()
        check_shape(
            weight,
            expected_shape,
            msg="Weight must be a scalar; got shape {}".format(
                expected_shape, get_shape(weight)),
        )
示例#5
0
def _preprocess(weights, wires):
    """Validate and pre-process inputs as follows:

    * Check the shape of the weights tensor.

    Args:
        weights (tensor_like): trainable parameters of the template
        wires (Wires): wires that template acts on
    """

    if qml.tape_mode_active():

        shape = qml.math.shape(weights)
        if shape != (4**len(wires) - 1, ):
            raise ValueError(
                f"Weights tensor must be of shape {(4 ** len(wires) - 1,)}; got {shape}."
            )

    else:
        expected_shape = (4**len(wires) - 1, )
        check_shape(
            weights,
            expected_shape,
            msg="Weights tensor must be of shape {}; got {}."
            "".format(expected_shape, get_shape(weights)),
        )
示例#6
0
def ArbitraryUnitary(weights, wires):
    """Implements an arbitrary unitary on the specified wires.

    An arbitrary unitary on :math:`n` wires is parametrized by :math:`4^n - 1`
    independent real parameters. This templates uses Pauli word rotations to
    parametrize the unitary.

    **Example**

    ArbitraryUnitary can be used as a building block, e.g. to parametrize arbitrary
    two-qubit operations in a circuit:

    .. code-block:: python

        @qml.template
        def arbitrary_nearest_neighbour_interaction(weights, wires):
            qml.broadcast(unitary=ArbitraryUnitary, pattern="double", wires=wires, params=weights)

    Args:
        weights (array[float]): The angles of the Pauli word rotations, needs to have length :math:`4^n - 1`
            where :math:`n` is the number of wires the template acts upon.
        wires (List[int]): The wires on which the arbitrary unitary acts.
    """
    wires = check_wires(wires)

    n_wires = len(wires)
    expected_shape = (4 ** n_wires - 1,)
    check_shape(
        weights,
        expected_shape,
        msg="'weights' must be of shape {}; got {}." "".format(expected_shape, get_shape(weights)),
    )

    for i, pauli_word in enumerate(_all_pauli_words_but_identity(len(wires))):
        qml.PauliRot(weights[i], pauli_word, wires=wires)
示例#7
0
def _preprocess(features, wires):
    """Validate and pre-process inputs as follows:

    * Check that the features tensor is one-dimensional.
    * Check that the first dimension of the features tensor
      has length :math:`n`, where :math:`n` is the number of qubits.
    * Check that the entries of the features tensor are zeros and ones.

    Args:
        features (tensor_like): input features to pre-process
        wires (Wires): wires that template acts on

    Returns:
        array: numpy array representation of the features tensor
    """

    if qml.tape_mode_active():

        shape = qml.math.shape(features)

        if len(shape) != 1:
            raise ValueError(
                f"Features must be one-dimensional; got shape {shape}.")

        n_features = shape[0]
        if n_features != len(wires):
            raise ValueError(
                f"Features must be of length {len(wires)}; got length {n_features}."
            )

        features = list(qml.math.toarray(features))

        if set(features) != {0, 1}:
            raise ValueError(
                f"Basis state must only consist of 0s and 1s; got {features}")

        return features

    # non-tape mode
    check_type(
        features,
        [Iterable],
        msg="Features must be iterable; got type {}".format(type(features)),
    )

    expected_shape = (len(wires), )
    check_shape(
        features,
        expected_shape,
        msg="Features must be of shape {}; got {}"
        "".format(expected_shape, get_shape(features)),
    )

    if any([b not in [0, 1] for b in features]):
        raise ValueError(
            "Basis state must only consist of 0s and 1s; got {}".format(
                features))

    return features
示例#8
0
def _preprocess(parameters, pattern, wires):
    """Validate and pre-process inputs as follows:

    * Check that pattern is recognised, or use default pattern if None.
    * Check the dimension of the parameters
    * Create wire sequence of the pattern.

    Args:
        parameters (tensor_like): trainable parameters of the template
        pattern (str): specifies the wire pattern
        wires (Wires): wires that template acts on

    Returns:
        wire_sequence, parameters: preprocessed pattern and parameters
    """

    if isinstance(pattern, str):
        _wires = wires
        if pattern not in OPTIONS:
            raise ValueError(f"did not recognize pattern {pattern}".format())
    else:
        # turn custom pattern into list of Wires objects
        _wires = [Wires(w) for w in pattern]
        # set "pattern" to "custom", indicating that custom settings have to be used
        pattern = "custom"

    # check that there are enough parameters for pattern
    if parameters is not None:

        if qml.tape_mode_active():
            shape = qml.math.shape(parameters)

            # expand dimension so that parameter sets for each unitary can be unpacked
            if len(shape) == 1:
                parameters = qml.math.expand_dims(parameters, 1)

        else:
            shape = get_shape(parameters)

            # expand dimension so that parameter sets for each unitary can be unpacked
            if len(shape) == 1:
                parameters = [[p] for p in parameters]

        # specific error message for ring edge case of 2 wires
        if (pattern == "ring") and (len(wires) == 2) and (shape[0] != 1):
            raise ValueError(
                "the ring pattern with 2 wires is an exception and only applies one unitary"
            )
        num_params = PATTERN_TO_NUM_PARAMS[pattern](_wires)
        if shape[0] != num_params:
            raise ValueError(
                "Parameters must contain entries for {} unitaries; got {} entries".format(
                    num_params, shape[0]
                )
            )

    wire_sequence = PATTERN_TO_WIRES[pattern](_wires)
    return wire_sequence, parameters
示例#9
0
def AngleEmbedding(features, wires, rotation="X"):
    r"""
    Encodes :math:`N` features into the rotation angles of :math:`n` qubits, where :math:`N \leq n`.

    The rotations can be chosen as either :class:`~pennylane.ops.RX`, :class:`~pennylane.ops.RY`
    or :class:`~pennylane.ops.RZ` gates, as defined by the ``rotation`` parameter:

    * ``rotation='X'`` uses the features as angles of RX rotations

    * ``rotation='Y'`` uses the features as angles of RY rotations

    * ``rotation='Z'`` uses the features as angles of RZ rotations

    The length of ``features`` has to be smaller or equal to the number of qubits. If there are fewer entries in
    ``features`` than rotations, the circuit does not apply the remaining rotation gates.

    Args:
        features (array): input array of shape ``(N,)``, where N is the number of input features to embed,
            with :math:`N\leq n`
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        rotation (str): Type of rotations used

    Raises:
        ValueError: if inputs do not have the correct format
    """

    #############
    # Input checks

    wires = Wires(wires)

    check_shape(
        features,
        (len(wires),),
        bound="max",
        msg="'features' must be of shape {} or smaller; "
        "got {}.".format((len(wires),), get_shape(features)),
    )
    check_type(rotation, [str], msg="'rotation' must be a string; got {}".format(rotation))

    check_is_in_options(
        rotation,
        ["X", "Y", "Z"],
        msg="did not recognize option {} for 'rotation'.".format(rotation),
    )

    ###############

    if rotation == "X":
        broadcast(unitary=RX, pattern="single", wires=wires, parameters=features)

    elif rotation == "Y":
        broadcast(unitary=RY, pattern="single", wires=wires, parameters=features)

    elif rotation == "Z":
        broadcast(unitary=RZ, pattern="single", wires=wires, parameters=features)
示例#10
0
def _preprocess(weights, wires, ranges):
    """Validate and pre-process inputs as follows:

    * Check the shape of the weights tensor.
    * If ranges is None, define a default.

    Args:
        weights (tensor_like): trainable parameters of the template
        wires (Wires): wires that template acts on
        ranges (Sequence[int]): range for each subsequent layer

    Returns:
        int, list[int]: number of times that the ansatz is repeated and preprocessed ranges
    """

    if qml.tape_mode_active():

        shape = qml.math.shape(weights)
        repeat = shape[0]

        if len(shape) != 3:
            raise ValueError(
                f"Weights tensor must be 3-dimensional; got shape {shape}")

        if shape[1] != len(wires):
            raise ValueError(
                f"Weights tensor must have second dimension of length {len(wires)}; got {shape[1]}"
            )

        if shape[2] != 3:
            raise ValueError(
                f"Weights tensor must have third dimension of length 3; got {shape[2]}"
            )

    else:

        repeat = check_number_of_layers([weights])

        expected_shape = (repeat, len(wires), 3)
        check_shape(
            weights,
            expected_shape,
            msg="Weights tensor must be of shape {}; got {}"
            "".format(expected_shape, get_shape(weights)),
        )

    if len(wires) > 1:
        if ranges is None:
            # tile ranges with iterations of range(1, n_wires)
            ranges = [(l % (len(wires) - 1)) + 1 for l in range(repeat)]
    else:
        ranges = [0] * repeat

    return repeat, ranges
示例#11
0
def BasisStatePreparation(basis_state, wires):
    r"""
    Prepares a basis state on the given wires using a sequence of Pauli X gates.

    .. warning::

        ``basis_state`` influences the circuit architecture and is therefore incompatible with
        gradient computations. Ensure that ``basis_state`` is not passed to the qnode by positional
        arguments.

    Args:
        basis_state (array): Input array of shape ``(N,)``, where N is the number of wires
            the state preparation acts on. ``N`` must be smaller or equal to the total
            number of wires of the device.
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.

    Raises:
        ValueError: if inputs do not have the correct format
    """

    ######################
    # Input checks

    wires = Wires(wires)

    expected_shape = (len(wires),)
    check_shape(
        basis_state,
        expected_shape,
        msg=" 'basis_state' must be of shape {}; got {}."
        "".format(expected_shape, get_shape(basis_state)),
    )

    # basis_state cannot be trainable
    check_no_variable(
        basis_state,
        msg="'basis_state' cannot be differentiable; must be passed as a keyword argument "
        "to the quantum node",
    )

    # basis_state is guaranteed to be a list of binary values
    if any([b not in [0, 1] for b in basis_state]):
        raise ValueError(
            "'basis_state' must only contain values of 0 and 1; got {}".format(basis_state)
        )

    ######################

    wires = wires.tolist()  # TODO: remove when operator takes Wires object

    for wire, state in zip(wires, basis_state):
        if state == 1:
            qml.PauliX(wire)
示例#12
0
def BasisEmbedding(features, wires):
    r"""Encodes :math:`n` binary features into a basis state of :math:`n` qubits.

    For example, for ``features=np.array([0, 1, 0])``, the quantum system will be
    prepared in state :math:`|010 \rangle`.

    .. warning::

        ``BasisEmbedding`` calls a circuit whose architecture depends on the binary features.
        The ``features`` argument is therefore not differentiable when using the template, and
        gradients with respect to the argument cannot be computed by PennyLane.

    Args:
        features (array): binary input array of shape ``(n, )``
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.

    Raises:
        ValueError: if inputs do not have the correct format
    """

    #############
    # Input checks

    wires = Wires(wires)

    check_type(features, [Iterable],
               msg="'features' must be iterable; got type {}".format(
                   type(features)))

    expected_shape = (len(wires), )
    check_shape(
        features,
        expected_shape,
        msg="'features' must be of shape {}; got {}"
        "".format(expected_shape, get_shape(features)),
    )

    if any([b not in [0, 1] for b in features]):
        raise ValueError(
            "'basis_state' must only consist of 0s and 1s; got {}".format(
                features))

    ###############

    wires = wires.tolist()  # TODO: Remove when operators take Wires objects

    for wire, bit in zip(wires, features):
        if bit == 1:
            qml.PauliX(wire)
示例#13
0
def _preprocess(basis_state, wires):
    """Validate and pre-process inputs as follows:

    * Check the shape of the basis state.
    * Cast basis state to a numpy array.

    Args:
        basis_state (tensor_like): basis state to prepare
        wires (Wires): wires that template acts on

    Returns:
        array: preprocessed basis state
    """

    if qml.tape_mode_active():

        shape = qml.math.shape(basis_state)

        if len(shape) != 1:
            raise ValueError(f"Basis state must be one-dimensional; got shape {shape}.")

        n_bits = shape[0]
        if n_bits != len(wires):
            raise ValueError(f"Basis state must be of length {len(wires)}; got length {n_bits}.")

        basis_state = list(qml.math.toarray(basis_state))

        if not all(bit in [0, 1] for bit in basis_state):
            raise ValueError(f"Basis state must only consist of 0s and 1s; got {basis_state}")

        # we return the input as a list of values, since
        # it is not differentiable
        return basis_state

    expected_shape = (len(wires),)
    check_shape(
        basis_state,
        expected_shape,
        msg="Basis state must be of shape {}; got {}."
        "".format(expected_shape, get_shape(basis_state)),
    )

    # basis_state is guaranteed to be a list of binary values
    if any([b not in [0, 1] for b in basis_state]):
        raise ValueError(
            "Basis state must only contain values of 0 and 1; got {}".format(basis_state)
        )

    return basis_state
示例#14
0
def ArbitraryStatePreparation(weights, wires):
    """Implements an arbitrary state preparation on the specified wires.

    An arbitrary state on :math:`n` wires is parametrized by :math:`2^{n+1} - 2`
    independent real parameters. This templates uses Pauli word rotations to
    parametrize the unitary.

    **Example**

    ArbitraryStatePreparation can be used to train state preparations,
    for example using a circuit with some measurement observable ``H``:

    .. code-block:: python

        dev = qml.device("default.qubit", wires=4)

        @qml.qnode(dev)
        def vqe(weights):
            qml.ArbitraryStatePreparations(weights, wires=[0, 1, 2, 3])

            return qml.expval(qml.Hermitian(H, wires=[0, 1, 2, 3]))

    Args:
        weights (array[float]): The angles of the Pauli word rotations, needs to have length :math:`2^(n+1) - 2`
            where :math:`n` is the number of wires the template acts upon.
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
    """

    wires = Wires(wires)

    n_wires = len(wires)
    expected_shape = (2**(n_wires + 1) - 2, )
    check_shape(
        weights,
        expected_shape,
        msg="'weights' must be of shape {}; got {}."
        "".format(expected_shape, get_shape(weights)),
    )

    wires = wires.tolist()  # Todo: remove when ops take Wires object

    for i, pauli_word in enumerate(_state_preparation_pauli_words(len(wires))):
        qml.PauliRot(weights[i], pauli_word, wires=wires)
示例#15
0
def _preprocess(theta, phi, varphi, wires):
    """Validate and pre-process inputs as follows:

    * Check the shape of the three weight tensors.

    Args:
        theta (tensor_like): trainable parameters of the template
        phi (tensor_like): trainable parameters of the template
        varphi (tensor_like): trainable parameters of the template
        wires (Wires): wires that the template acts on

    Returns:
        tuple: shape of varphi tensor
    """

    n_wires = len(wires)
    n_if = n_wires * (n_wires - 1) // 2

    if qml.tape_mode_active():

        shape = qml.math.shape(theta)
        if shape != (n_if, ):
            raise ValueError(f"Theta must be of shape {(n_if,)}; got {shape}.")

        shape = qml.math.shape(phi)
        if shape != (n_if, ):
            raise ValueError(f"Phi must be of shape {(n_if,)}; got {shape}.")

        shape_varphi = qml.math.shape(varphi)
        if shape_varphi != (n_wires, ):
            raise ValueError(
                f"Varphi must be of shape {(n_wires,)}; got {shape_varphi}.")

    else:
        weights_list = [theta, phi, varphi]

        expected_shapes = [(n_if, ), (n_if, ), (n_wires, )]
        check_shapes(weights_list,
                     expected_shapes,
                     msg="wrong shape of weight input(s) detected")
        shape_varphi = get_shape(varphi)

    return shape_varphi
示例#16
0
def _preprocess(features, wires):
    """Validate and pre-process inputs as follows:

    * Check that the features tensor is one-dimensional.
    * Check that the first dimension of the features tensor
      has length :math:`n` or less, where :math:`n` is the number of qubits.

    Args:
        features (tensor_like): input features to pre-process
        wires (Wires): wires that template acts on

    Returns:
        int: number of features
    """

    if qml.tape_mode_active():

        shape = qml.math.shape(features)

        if len(shape) != 1:
            raise ValueError(
                f"Features must be a one-dimensional tensor; got shape {shape}."
            )

        n_features = shape[0]
        if n_features > len(wires):
            raise ValueError(
                f"Features must be of length {len(wires)} or less; got length {n_features}."
            )

    else:

        shp = check_shape(
            features,
            (len(wires), ),
            bound="max",
            msg="Features must be of shape {} or smaller; "
            "got {}.".format((len(wires), ), get_shape(features)),
        )
        n_features = shp[0]

    return n_features
def _preprocess(weight, wires1, wires2):
    """Validate and pre-process inputs as follows:

    * Check the shape of the weights tensor.
    * Check that both wire sets have at least 2 wires.

    Args:
        weight (tensor_like): trainable parameters of the template
        wires1 (Wires): first set of wires
        wires2 (Wires): second set of wires
    """

    if len(wires1) < 2:
        raise ValueError(
            "expected at least two wires representing the occupied orbitals; "
            "got {}".format(len(wires1)))
    if len(wires2) < 2:
        raise ValueError(
            "expected at least two wires representing the unoccupied orbitals; "
            "got {}".format(len(wires2)))

    if qml.tape_mode_active():

        shape = qml.math.shape(weight)
        if shape != ():
            raise ValueError(f"Weight must be a scalar; got shape {shape}.")

    else:

        expected_shape = ()
        check_shape(
            weight,
            expected_shape,
            msg="Weight must be a scalar; got shape {}".format(
                expected_shape, get_shape(weight)),
        )
示例#18
0
def SqueezingEmbedding(features, wires, method="amplitude", c=0.1):
    r"""Encodes :math:`N` features into the squeezing amplitudes :math:`r \geq 0` or phases :math:`\phi \in [0, 2\pi)`
    of :math:`M` modes, where :math:`N\leq M`.

    The mathematical definition of the squeezing gate is given by the operator

    .. math::

        S(z) = \exp\left(\frac{r}{2}\left(e^{-i\phi}\a^2 -e^{i\phi}{\ad}^{2} \right) \right),

    where :math:`\a` and :math:`\ad` are the bosonic creation and annihilation operators.

    ``features`` has to be an iterable of at most ``len(wires)`` floats. If there are fewer entries in
    ``features`` than wires, the circuit does not apply the remaining squeezing gates.

    Args:
        features (array): Array of features of size (N,)
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        method (str): ``'phase'`` encodes the input into the phase of single-mode squeezing, while
            ``'amplitude'`` uses the amplitude
        c (float): value of the phase of all squeezing gates if ``execution='amplitude'``, or the
            amplitude of all squeezing gates if ``execution='phase'``

    Raises:
        ValueError: if inputs do not have the correct format
    """

    #############
    # Input checks

    wires = Wires(wires)

    check_no_variable(method, msg="'method' cannot be differentiable")
    check_no_variable(c, msg="'c' cannot be differentiable")

    check_type(c, [float, int], msg="'c' must be of type float or integer; got {}".format(type(c)))

    expected_shape = (len(wires),)
    check_shape(
        features,
        expected_shape,
        bound="max",
        msg="'features' must be of shape {} or smaller; got {}"
        "".format(expected_shape, get_shape(features)),
    )

    check_is_in_options(
        method,
        ["amplitude", "phase"],
        msg="did not recognize option {} for 'method'".format(method),
    )

    ##############

    constants = [c] * len(features)

    if method == "amplitude":
        broadcast(
            unitary=Squeezing,
            pattern="single",
            wires=wires,
            parameters=list(zip(features, constants)),
        )

    elif method == "phase":
        broadcast(
            unitary=Squeezing,
            pattern="single",
            wires=wires,
            parameters=list(zip(constants, features)),
        )
示例#19
0
def _preprocess(features, wires, pattern, n_repeats):
    """Validate and pre-process inputs as follows:

    * Check that the features tensor is one-dimensional.
    * Check that the first dimension of the features tensor
      has length :math:`n`, where :math:`n` is the number of qubits.
    * If pattern is None, create default pattern. Else cast wires in user-provided pattern to Wires objects.

    Args:
        features (tensor_like): input features to pre-process
        wires (Wires): wires that template acts on
        pattern (list[int]): specifies the wires and features of the entanglers
        n_repeats (int): number of times the basic embedding is repeated

    Returns:
        list[Wires]: preprocessed pattern
    """

    if qml.tape_mode_active():

        shape = qml.math.shape(features)

        if len(shape) != 1:
            raise ValueError(
                f"Features must be a one-dimensional tensor; got shape {shape}."
            )

        n_features = shape[0]
        if n_features != len(wires):
            raise ValueError(
                f"Features must be of length {len(wires)}; got length {n_features}."
            )

        if pattern is None:
            # default is an all-to-all pattern
            pattern = [
                Wires(wire_pair) for wire_pair in combinations(wires, 2)
            ]
        else:
            # convert wire pairs to Wires object
            pattern = [Wires(wire_pair) for wire_pair in pattern]

    else:
        expected_shape = (len(wires), )
        check_shape(
            features,
            expected_shape,
            msg="Features must be of shape {}; got {}"
            "".format(expected_shape, get_shape(features)),
        )

        check_type(
            n_repeats,
            [int],
            msg="'n_repeats' must be an integer; got type {}".format(
                type(n_repeats)),
        )

        if pattern is None:
            # default is an all-to-all pattern
            pattern = [
                Wires(wire_pair) for wire_pair in combinations(wires, 2)
            ]
        else:
            # do some checks
            check_type(
                pattern,
                [Iterable, type(None)],
                msg="'pattern' must be a list of pairs of wires; got {}".
                format(pattern),
            )
            shape = get_shape(pattern)
            if len(shape) != 2 or shape[1] != 2:
                raise ValueError(
                    "'pattern' must be a list of pairs of wires; got {}".
                    format(pattern))

            # convert wire pairs to Wires object
            pattern = [Wires(wire_pair) for wire_pair in pattern]

    return pattern
def SingleExcitationUnitary(weight, wires=None):
    r"""Circuit to exponentiate the tensor product of Pauli matrices representing the
    single-excitation operator entering the Unitary Coupled-Cluster Singles
    and Doubles (UCCSD) ansatz. UCCSD is a VQE ansatz commonly used to run quantum
    chemistry simulations.

    The CC single-excitation operator is given by

    .. math::

        \hat{U}_{pr}(\theta) = \mathrm{exp} \{ \theta_{pr} (\hat{c}_p^\dagger \hat{c}_r
        -\mathrm{H.c.}) \},

    where :math:`\hat{c}` and :math:`\hat{c}^\dagger` are the fermionic annihilation and
    creation operators and the indices :math:`r` and :math:`p` run over the occupied and
    unoccupied molecular orbitals, respectively. Using the `Jordan-Wigner transformation
    <https://arxiv.org/abs/1208.5986>`_ the fermionic operator defined above can be written
    in terms of Pauli matrices (for more details see
    `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_).

    .. math::

        \hat{U}_{pr}(\theta) = \mathrm{exp} \Big\{ \frac{i\theta}{2}
        \bigotimes_{a=r+1}^{p-1}\hat{Z}_a (\hat{Y}_r \hat{X}_p) \Big\}
        \mathrm{exp} \Big\{ -\frac{i\theta}{2}
        \bigotimes_{a=r+1}^{p-1} \hat{Z}_a (\hat{X}_r \hat{Y}_p) \Big\}.

    The quantum circuit to exponentiate the tensor product of Pauli matrices entering
    the latter equation is shown below (see `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_):

    |

    .. figure:: ../../_static/templates/subroutines/single_excitation_unitary.png
        :align: center
        :width: 60%
        :target: javascript:void(0);

    |

    As explained in `Seely et al. (2012) <https://arxiv.org/abs/1208.5986>`_,
    the exponential of a tensor product of Pauli-Z operators can be decomposed in terms of
    :math:`2(n-1)` CNOT gates and a single-qubit Z-rotation referred to as :math:`U_\theta` in
    the figure above. If there are :math:`X` or :math:`Y` Pauli matrices in the product,
    the Hadamard (:math:`H`) or :math:`R_x` gate has to be applied to change to the
    :math:`X` or :math:`Y` basis, respectively. The latter operations are denoted as
    :math:`U_1` and :math:`U_2` in the figure above. See the Usage Details section for more
    information.

    Args:
        weight (float): angle :math:`\theta` entering the Z rotation acting on wire ``p``
        wires (Iterable or Wires): Wires that the template acts on.
            The wires represent the subset of orbitals in the interval ``[r, p]``. Must be of
            minimum length 2. The first wire is interpreted as ``r`` and the last wire as ``p``.
            Wires in between are acted on with CNOT gates to compute the parity of the set
            of qubits.

    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        Notice that:

        #. :math:`\hat{U}_{pr}(\theta)` involves two exponentiations where :math:`\hat{U}_1`,
           :math:`\hat{U}_2`, and :math:`\hat{U}_\theta` are defined as follows,

           .. math::
               [U_1, U_2, U_{\theta}] = \Bigg\{\bigg[R_x(-\pi/2), H, R_z(\theta/2)\bigg],
               \bigg[H, R_x(-\frac{\pi}{2}), R_z(-\theta/2) \bigg] \Bigg\}

        #. For a given pair ``[r, p]``, ten single-qubit and ``4*(len(wires)-1)`` CNOT
           operations are applied. Notice also that CNOT gates act only on qubits
           ``wires[1]`` to ``wires[-2]``. The operations performed across these qubits
           are shown in dashed lines in the figure above.

        An example of how to use this template is shown below:

        .. code-block:: python

            import pennylane as qml
            from pennylane.templates import SingleExcitationUnitary

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

            @qml.qnode(dev)
            def circuit(weight, wires=None):
                SingleExcitationUnitary(weight, wires=wires)
                return qml.expval(qml.PauliZ(0))

            weight = 0.56
            print(circuit(weight, wires=[0, 1, 2]))

    """

    ##############
    # Input checks

    wires = Wires(wires)

    if len(wires) < 2:
        raise ValueError("expected at least two wires; got {}".format(
            len(wires)))

    expected_shape = ()
    check_shape(
        weight,
        expected_shape,
        msg="'weight' must be of shape {}; got {}".format(
            expected_shape, get_shape(weight)),
    )

    ###############

    # Interpret first and last wire as r and p
    r = wires[0]
    p = wires[-1]

    # Sequence of the wires entering the CNOTs between wires 'r' and 'p'
    set_cnot_wires = [wires.subset([l, l + 1]) for l in range(len(wires) - 1)]

    # ------------------------------------------------------------------
    # Apply the first layer

    # U_1, U_2 acting on wires 'r' and 'p'
    RX(-np.pi / 2, wires=r)
    Hadamard(wires=p)

    # Applying CNOTs between wires 'r' and 'p'
    for cnot_wires in set_cnot_wires:
        CNOT(wires=cnot_wires)

    # Z rotation acting on wire 'p'
    RZ(weight / 2, wires=p)

    # Applying CNOTs in reverse order
    for cnot_wires in reversed(set_cnot_wires):
        CNOT(wires=cnot_wires)

    # U_1^+, U_2^+ acting on wires 'r' and 'p'
    RX(np.pi / 2, wires=r)
    Hadamard(wires=p)

    # ------------------------------------------------------------------
    # Apply the second layer

    # U_1, U_2 acting on wires 'r' and 'p'
    Hadamard(wires=r)
    RX(-np.pi / 2, wires=p)

    # Applying CNOTs between wires 'r' and 'p'
    for cnot_wires in set_cnot_wires:
        CNOT(wires=cnot_wires)

    # Z rotation acting on wire 'p'
    RZ(-weight / 2, wires=p)

    # Applying CNOTs in reverse order
    for cnot_wires in reversed(set_cnot_wires):
        CNOT(wires=cnot_wires)

    # U_1^+, U_2^+ acting on wires 'r' and 'p'
    Hadamard(wires=r)
    RX(np.pi / 2, wires=p)
示例#21
0
def _preprocess(features, wires, method, c):
    """Validate and pre-process inputs as follows:

    * Check that the features tensor is one-dimensional.
    * Check that the first dimension of the features tensor
      has length :math:`n` or less, where :math:`n` is the number of qubits.
    * Create a parameter tensor which combines the features with a tensor of constants.

    Args:
        features (tensor_like): input features to pre-process
        wires (Wires): wires that template acts on
        method (str): indicates whether amplitude or phase encoding is used
        c (float): value of constant

    Returns:
        tensor_like: 2-dimensional tensor containing the features and constants
    """

    if qml.tape_mode_active():
        shape = qml.math.shape(features)
        constants = [c] * shape[0]

        if len(shape) != 1:
            raise ValueError(
                f"Features must be a one-dimensional tensor; got shape {shape}."
            )

        n_features = shape[0]
        if n_features != len(wires):
            raise ValueError(
                f"Features must be of length {len(wires)}; got length {n_features}."
            )

        if method == "amplitude":
            pars = qml.math.stack([features, constants], axis=1)

        elif method == "phase":
            pars = qml.math.stack([constants, features], axis=1)

        else:
            raise ValueError(f"did not recognize method {method}")

    else:

        expected_shape = (len(wires), )
        check_shape(
            features,
            expected_shape,
            bound="max",
            msg="Features must be of shape {} or smaller; got {}."
            "".format(expected_shape, get_shape(features)),
        )

        check_is_in_options(
            method,
            ["amplitude", "phase"],
            msg="did not recognize option {} for 'method'"
            "".format(method),
        )

        constants = [c] * len(features)

        if method == "amplitude":
            pars = list(zip(features, constants))

        elif method == "phase":
            pars = list(zip(constants, features))

    return pars
示例#22
0
def RandomLayers(weights, wires, ratio_imprim=0.3, imprimitive=CNOT, rotations=None, seed=42):
    r"""Layers of randomly chosen single qubit rotations and 2-qubit entangling gates, acting
    on randomly chosen qubits.

    .. warning::
        This template uses random number generation inside qnodes. Find more
        details about how to invoke the desired random behaviour in the "Usage Details" section below.

    The argument ``weights`` contains the weights for each layer. The number of layers :math:`L` is therefore derived
    from the first dimension of ``weights``.

    The two-qubit gates of type ``imprimitive`` and the rotations are distributed randomly in the circuit.
    The number of random rotations is derived from the second dimension of ``weights``. The number of
    two-qubit gates is determined by ``ratio_imprim``. For example, a ratio of ``0.3`` with ``30`` rotations
    will lead to the use of ``10`` two-qubit gates.

    .. note::
        If applied to one qubit only, this template will use no imprimitive gates.

    This is an example of two 4-qubit random layers with four Pauli-Y/Pauli-Z rotations :math:`R_y, R_z`,
    controlled-Z gates as imprimitives, as well as ``ratio_imprim=0.3``:

    .. figure:: ../../_static/layer_rnd.png
        :align: center
        :width: 60%
        :target: javascript:void(0);

    Args:
        weights (array[float]): array of weights of shape ``(L, k)``,
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        ratio_imprim (float): value between 0 and 1 that determines the ratio of imprimitive to rotation gates
        imprimitive (pennylane.ops.Operation): two-qubit gate to use, defaults to :class:`~pennylane.ops.CNOT`
        rotations (list[pennylane.ops.Operation]): List of Pauli-X, Pauli-Y and/or Pauli-Z gates. The frequency
            determines how often a particular rotation type is used. Defaults to the use of all three
            rotations with equal frequency.
        seed (int): seed to generate random architecture, defaults to 42

    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        **Default seed**

        ``RandomLayers`` always uses a seed to initialize the construction of a random circuit. This means
        that the template creates the same circuit every time it is called. If no seed is provided, the default
        seed of ``42`` is used.

        .. code-block:: python

            import pennylane as qml
            import numpy as np
            from pennylane.templates.layers import RandomLayers

            dev = qml.device("default.qubit", wires=2)
            weights = [[0.1, -2.1, 1.4]]

            @qml.qnode(dev)
            def circuit1(weights):
                RandomLayers(weights=weights, wires=range(2))
                return qml.expval(qml.PauliZ(0))

            @qml.qnode(dev)
            def circuit2(weights):
                RandomLayers(weights=weights, wires=range(2))
                return qml.expval(qml.PauliZ(0))

        >>> np.allclose(circuit1(weights), circuit2(weights))
        >>> True

        You can verify this by drawing the circuits.

            >>> print(circuit1.draw())
            >>>  0: ──RX(0.1)──RX(-2.1)──╭X──╭X───────────┤ ⟨Z⟩
            ...  1: ─────────────────────╰C──╰C──RZ(1.4)──┤

            >>> print(circuit2.draw())
            >>>  0: ──RX(0.1)──RX(-2.1)──╭X──╭X───────────┤ ⟨Z⟩
            ...  1: ─────────────────────╰C──╰C──RZ(1.4)──┤

        **Changing the seed**

        To change the randomly generated circuit architecture, you have to change the seed passed to the template.
        For example, these two calls of ``RandomLayers`` *do not* create the same circuit:

        .. code-block:: python

            @qml.qnode(dev)
            def circuit_9(weights):
                RandomLayers(weights=weights, wires=range(2), seed=9)
                return qml.expval(qml.PauliZ(0))

            @qml.qnode(dev)
            def circuit_12(weights):
                RandomLayers(weights=weights, wires=range(2), seed=12)
                return qml.expval(qml.PauliZ(0))

        >>> np.allclose(circuit_9(weights), circuit_12(weights))
        >>> False

        >>> print(circuit_9.draw())
        >>>  0: ──╭X──RY(-2.1)──RX(1.4)──┤ ⟨Z⟩
        ...  1: ──╰C──RX(0.1)────────────┤

        >>> print(circuit_12.draw())
        >>>  0: ──╭X──RX(-2.1)──╭C──╭X──RZ(1.4)──┤ ⟨Z⟩
        ...  1: ──╰C──RZ(0.1)───╰X──╰C───────────┤


        **Automatically creating random circuits**

        To automate the process of creating different circuits with ``RandomLayers``,
        you can set ``seed=None`` to avoid specifying a seed. However, in this case care needs
        to be taken. In the default setting, a quantum node is **mutable**, which means that the quantum function is
        re-evaluated every time it is called. This means that the circuit is re-constructed from scratch
        each time you call the qnode:

        .. code-block:: python

            @qml.qnode(dev)
            def circuit_rnd(weights):
                RandomLayers(weights=weights, wires=range(2), seed=None)
                return qml.expval(qml.PauliZ(0))

            first_call = circuit_rnd(weights)
            second_call = circuit_rnd(weights)

        >>> np.allclose(first_call, second_call)
        >>> False

        This can be rectified by making the quantum node **immutable**.

        .. code-block:: python

            @qml.qnode(dev, mutable=False)
            def circuit_rnd(weights):
                RandomLayers(weights=weights, wires=range(2), seed=None)
                return qml.expval(qml.PauliZ(0))

            first_call = circuit_rnd(weights)
            second_call = circuit_rnd(weights)

        >>> np.allclose(first_call, second_call)
        >>> True
    """
    if seed is not None:
        np.random.seed(seed)

    if rotations is None:
        rotations = [RX, RY, RZ]

    #############
    # Input checks

    wires = Wires(wires)

    check_no_variable(ratio_imprim, msg="'ratio_imprim' cannot be differentiable")
    check_no_variable(imprimitive, msg="'imprimitive' cannot be differentiable")
    check_no_variable(rotations, msg="'rotations' cannot be differentiable")
    check_no_variable(seed, msg="'seed' cannot be differentiable")

    repeat = check_number_of_layers([weights])
    n_rots = get_shape(weights)[1]

    expected_shape = (repeat, n_rots)
    check_shape(
        weights,
        expected_shape,
        msg="'weights' must be of shape {}; got {}" "".format(expected_shape, get_shape(weights)),
    )

    check_type(
        ratio_imprim,
        [float, type(None)],
        msg="'ratio_imprim' must be a float; got {}".format(ratio_imprim),
    )
    check_type(n_rots, [int, type(None)], msg="'n_rots' must be an integer; got {}".format(n_rots))
    # TODO: Check that 'rotations' contains operations
    check_type(
        rotations,
        [list, type(None)],
        msg="'rotations' must be a list of PennyLane operations; got {}" "".format(rotations),
    )
    check_type(seed, [int, type(None)], msg="'seed' must be an integer; got {}.".format(seed))

    ###############

    for l in range(repeat):
        random_layer(
            weights=weights[l],
            wires=wires,
            ratio_imprim=ratio_imprim,
            imprimitive=imprimitive,
            rotations=rotations,
            seed=seed,
        )
示例#23
0
def ParticleConservingU1(weights, wires, init_state=None):
    r"""Implements the heuristic VQE ansatz for quantum chemistry simulations using the
    particle-conserving gate :math:`U_{1,\mathrm{ex}}` proposed by Barkoutsos *et al.* in
    `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_.

    This template prepares :math:`N`-qubit trial states by applying :math:`D` layers of the
    entangler block :math:`U_\mathrm{ent}(\vec{\phi}, \vec{\theta})` to the Hartree-Fock
    state

    .. math::

        \vert \Psi(\vec{\phi}, \vec{\theta}) \rangle = \hat{U}^{(D)}_\mathrm{ent}(\vec{\phi}_D,
        \vec{\theta}_D) \dots \hat{U}^{(2)}_\mathrm{ent}(\vec{\phi}_2, \vec{\theta}_2)
        \hat{U}^{(1)}_\mathrm{ent}(\vec{\phi}_1, \vec{\theta}_1) \vert \mathrm{HF}\rangle.

    The circuit implementing the entangler blocks is shown in the figure below:

    |

    .. figure:: ../../_static/templates/layers/particle_conserving_u1.png
        :align: center
        :width: 50%
        :target: javascript:void(0);

    |

    The repeated units across several qubits are shown in dotted boxes. Each layer
    contains :math:`N-1` particle-conserving two-parameter exchange gates
    :math:`U_{1,\mathrm{ex}}(\phi, \theta)` that act on pairs of nearest neighbors qubits.
    The unitary matrix representing :math:`U_{1,\mathrm{ex}}(\phi, \theta)`
    is given by (see `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_),

    .. math::

        U_{1, \mathrm{ex}}(\phi, \theta) = \left(\begin{array}{cccc}
        1 & 0 & 0 & 0 \\
        0 & \mathrm{cos}(\theta) & e^{i\phi} \mathrm{sin}(\theta) & 0 \\
        0 & e^{-i\phi} \mathrm{sin}(\theta) & -\mathrm{cos}(\theta) & 0 \\
        0 & 0 & 0 & 1 \\
        \end{array}\right).

    The figure below shows the circuit decomposing :math:`U_{1, \mathrm{ex}}` in
    elementary gates. The Pauli matrix :math:`\sigma_z` and single-qubit rotation
    :math:`R(0, 2 \theta, 0)` apply the Pauli Z operator and an arbitrary rotation
    on the qubit ``n`` with qubit ``m`` bein the control qubit,

    |

    .. figure:: ../../_static/templates/layers/u1_decomposition.png
        :align: center
        :width: 80%
        :target: javascript:void(0);

    |

    :math:`U_A(\phi)` is the unitary matrix

    .. math::

        U_A(\phi) = \left(\begin{array}{cc} 0 & e^{-i\phi} \\ e^{-i\phi} & 0 \\ \end{array}\right),

    which is applied controlled on the state of qubit ``m`` and can be further decomposed in
    terms of the
    `quantum operations <https://pennylane.readthedocs.io/en/stable/introduction/operations.html>`_
    supported by Pennylane,

    |

    .. figure:: ../../_static/templates/layers/ua_decomposition.png
        :align: center
        :width: 70%
        :target: javascript:void(0);

    |

    where,

    |

    .. figure:: ../../_static/templates/layers/phaseshift_decomposition.png
        :align: center
        :width: 65%
        :target: javascript:void(0);

    |

    The quantum circuits above decomposing the unitaries :math:`U_{1,\mathrm{ex}}(\phi, \theta)`
    and :math:`U_A(\phi)` are implemented by the ``u1_ex_gate`` and ``decompose_ua``
    functions, respectively. :math:`R_\phi` refers to the ``PhaseShift`` gate in the
    circuit diagram.

    Args:
        weights (array[float]): Array of weights of shape ``(D, M, 2)``.
            ``D`` is the number of entangler block layers and :math:`M=N-1`
            is the number of exchange gates :math:`U_{1,\mathrm{ex}}` per layer.
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers
            or strings, or a Wires object.
        init_state (array[int]): length ``len(wires)`` vector representing the Hartree-Fock state
            used to initialize the wires

    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        #. The number of wires :math:`N` has to be equal to the number of
           spin orbitals included in the active space.

        #. The number of trainable parameters scales linearly with the number of layers as
           :math:`2D(N-1)`.

        An example of how to use this template is shown below:

        .. code-block:: python

            import pennylane as qml
            from pennylane.templates import ParticleConservingU1
            from functools import partial

            # Build the electronic Hamiltonian from a local .xyz file
            h, qubits = qml.qchem.molecular_hamiltonian("h2", "h2.xyz")

            # Define the Hartree-Fock state
            electrons = 2
            ref_state = qml.qchem.hf_state(electrons, qubits)

            # Define the device
            dev = qml.device('default.qubit', wires=qubits)

            # Define the ansatz
            ansatz = partial(ParticleConservingU1, init_state=ref_state)

            # Define the cost function
            cost_fn = qml.ExpvalCost(ansatz, h, dev)

            # Compute the expectation value of 'h'
            layers = 2
            params = qml.init.particle_conserving_u1_normal(layers, qubits)
            print(cost_fn(params))
    """

    wires = Wires(wires)

    layers = weights.shape[0]

    if len(wires) < 2:
        raise ValueError(
            "This template requires the number of qubits to be greater than one; a wire sequence with {} elements"
            .format(len(wires)))

    expected_shape = (layers, len(wires) - 1, 2)
    check_shape(
        weights,
        expected_shape,
        msg="'weights' must be of shape {}; got {}".format(
            expected_shape, get_shape(weights)),
    )

    nm_wires = [wires.subset([l, l + 1]) for l in range(0, len(wires) - 1, 2)]
    nm_wires += [wires.subset([l, l + 1]) for l in range(1, len(wires) - 1, 2)]

    qml.BasisState(init_state, wires=wires)

    for l in range(layers):
        for i, wires_ in enumerate(nm_wires):
            u1_ex_gate(weights[l, i, 0], weights[l, i, 1], wires=wires_)
示例#24
0
def MottonenStatePreparation(state_vector, wires):
    r"""
    Prepares an arbitrary state on the given wires using a decomposition into gates developed
    by Möttönen et al. (Quantum Info. Comput., 2005).

    The state is prepared via a sequence
    of "uniformly controlled rotations". A uniformly controlled rotation on a target qubit is
    composed from all possible controlled rotations on said qubit and can be used to address individual
    elements of the state vector. In the work of Mottonen et al., the inverse of their state preparation
    is constructed by first equalizing the phases of the state vector via uniformly controlled Z rotations
    and then rotating the now real state vector into the direction of the state :math:`|0\rangle` via
    uniformly controlled Y rotations.

    This code is adapted from code written by Carsten Blank for PennyLane-Qiskit.

    Args:
        state_vector (array): Input array of shape ``(2^N,)``, where N is the number of wires
            the state preparation acts on. ``N`` must be smaller or equal to the total
            number of wires.
        wires (Sequence[int]): sequence of qubit indices that the template acts on

    Raises:
        ValueError: if inputs do not have the correct format
    """

    ###############
    # Input checks

    wires = check_wires(wires)

    n_wires = len(wires)
    expected_shape = (2**n_wires, )
    check_shape(
        state_vector,
        expected_shape,
        msg="'state_vector' must be of shape {}; got {}."
        "".format(expected_shape, get_shape(state_vector)),
    )

    # check if state_vector is normalized
    if isinstance(state_vector[0], Variable):
        state_vector_values = [s.val for s in state_vector]
        norm = np.sum(np.abs(state_vector_values)**2)
    else:
        norm = np.sum(np.abs(state_vector)**2)
    if not np.isclose(norm, 1.0, atol=1e-3):
        raise ValueError(
            "'state_vector' has to be of length 1.0, got {}".format(norm))

    #######################

    # Change ordering of indices, original code was for IBM machines
    state_vector = np.array(state_vector).reshape(
        [2] * n_wires).T.flatten()[:, np.newaxis]
    state_vector = sparse.dok_matrix(state_vector)

    wires = np.array(wires)

    a = sparse.dok_matrix(state_vector.shape)
    omega = sparse.dok_matrix(state_vector.shape)

    for (i, j), v in state_vector.items():
        if isinstance(v, Variable):
            a[i, j] = np.absolute(v.val)
            omega[i, j] = np.angle(v.val)
        else:
            a[i, j] = np.absolute(v)
            omega[i, j] = np.angle(v)
    # This code is directly applying the inverse of Carsten Blank's
    # code to avoid inverting at the end

    # Apply y rotations
    for k in range(n_wires, 0, -1):
        alpha_y_k = _get_alpha_y(a, n_wires, k)  # type: sparse.dok_matrix
        control = wires[k:]
        target = wires[k - 1]
        _uniform_rotation_y_dagger(alpha_y_k, control, target)

    # Apply z rotations
    for k in range(n_wires, 0, -1):
        alpha_z_k = _get_alpha_z(omega, n_wires, k)
        control = wires[k:]
        target = wires[k - 1]
        if len(alpha_z_k) > 0:
            _uniform_rotation_z_dagger(alpha_z_k, control, target)
示例#25
0
def DisplacementEmbedding(features, wires, method="amplitude", c=0.1):
    r"""Encodes :math:`N` features into the displacement amplitudes :math:`r` or phases :math:`\phi` of :math:`M` modes,
    where :math:`N\leq M`.

    The mathematical definition of the displacement gate is given by the operator

    .. math::
            D(\alpha) = \exp(r (e^{i\phi}\ad -e^{-i\phi}\a)),

    where :math:`\a` and :math:`\ad` are the bosonic creation and annihilation operators.

    ``features`` has to be an array of at most ``len(wires)`` floats. If there are fewer entries in
    ``features`` than wires, the circuit does not apply the remaining displacement gates.

    Args:
        features (array): Array of features of size (N,)
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        method (str): ``'phase'`` encodes the input into the phase of single-mode displacement, while
            ``'amplitude'`` uses the amplitude
        c (float): value of the phase of all displacement gates if ``execution='amplitude'``, or
            the amplitude of all displacement gates if ``execution='phase'``

    Raises:
        ValueError: if inputs do not have the correct format
    """

    #############
    # Input checks

    wires = Wires(wires)

    expected_shape = (len(wires), )
    check_shape(
        features,
        expected_shape,
        bound="max",
        msg="'features' must be of shape {} or smaller; got {}."
        "".format(expected_shape, get_shape(features)),
    )

    check_is_in_options(
        method,
        ["amplitude", "phase"],
        msg="did not recognize option {} for 'method'"
        "".format(method),
    )

    #############

    constants = [c] * len(features)

    if method == "amplitude":
        broadcast(
            unitary=Displacement,
            pattern="single",
            wires=wires,
            parameters=list(zip(features, constants)),
        )

    elif method == "phase":
        broadcast(
            unitary=Displacement,
            pattern="single",
            wires=wires,
            parameters=list(zip(constants, features)),
        )
示例#26
0
def StronglyEntanglingLayers(weights, wires, ranges=None, imprimitive=CNOT):
    r"""Layers consisting of single qubit rotations and entanglers, inspired by the circuit-centric classifier design
    `arXiv:1804.00633 <https://arxiv.org/abs/1804.00633>`_.

    The argument ``weights`` contains the weights for each layer. The number of layers :math:`L` is therefore derived
    from the first dimension of ``weights``.

    The 2-qubit gates, whose type is specified by the ``imprimitive`` argument,
    act chronologically on the :math:`M` wires, :math:`i = 1,...,M`. The second qubit of each gate is given by
    :math:`(i+r)\mod M`, where :math:`r` is a  hyperparameter called the *range*, and :math:`0 < r < M`.
    If applied to one qubit only, this template will use no imprimitive gates.

    This is an example of two 4-qubit strongly entangling layers (ranges :math:`r=1` and :math:`r=2`, respectively) with
    rotations :math:`R` and CNOTs as imprimitives:

    .. figure:: ../../_static/layer_sec.png
        :align: center
        :width: 60%
        :target: javascript:void(0);

    Args:

        weights (array[float]): array of weights of shape ``(L, M, 3)``
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        ranges (Sequence[int]): sequence determining the range hyperparameter for each subsequent layer; if None
                                using :math:`r=l \mod M` for the :math:`l`th layer and :math:`M` wires.
        imprimitive (pennylane.ops.Operation): two-qubit gate to use, defaults to :class:`~pennylane.ops.CNOT`

    Raises:
        ValueError: if inputs do not have the correct format
    """

    #############
    # Input checks

    wires = Wires(wires)

    check_no_variable(ranges, msg="'ranges' cannot be differentiable")
    check_no_variable(imprimitive,
                      msg="'imprimitive' cannot be differentiable")

    repeat = check_number_of_layers([weights])

    expected_shape = (repeat, len(wires), 3)
    check_shape(
        weights,
        expected_shape,
        msg="'weights' must be of shape {}; got {}"
        "".format(expected_shape, get_shape(weights)),
    )

    if len(wires) > 1:
        if ranges is None:
            # tile ranges with iterations of range(1, n_wires)
            ranges = [(l % (len(wires) - 1)) + 1 for l in range(repeat)]

        expected_shape = (repeat, )
        check_shape(
            ranges,
            expected_shape,
            msg="'ranges' must be of shape {}; got {}"
            "".format(expected_shape, get_shape(weights)),
        )

        check_type(ranges, [list],
                   msg="'ranges' must be a list; got {}"
                   "".format(ranges))
        for r in ranges:
            check_type(r, [int],
                       msg="'ranges' must be a list of integers; got {}"
                       "".format(ranges))
        if any((r >= len(wires) or r == 0) for r in ranges):
            raise ValueError(
                "the range for all layers needs to be smaller than the number of "
                "qubits; got ranges {}.".format(ranges))
    else:
        ranges = [0] * repeat

    ###############

    for l in range(repeat):

        strongly_entangling_layer(weights=weights[l],
                                  wires=wires,
                                  r=ranges[l],
                                  imprimitive=imprimitive)
示例#27
0
def UCCSD(weights, wires, ph=None, pphh=None, init_state=None):
    r"""Implements the Unitary Coupled-Cluster Singles and Doubles (UCCSD) ansatz.

    The UCCSD ansatz calls the
    :func:`~.SingleExcitationUnitary` and :func:`~.DoubleExcitationUnitary`
    templates to exponentiate the coupled-cluster excitation operator. UCCSD is a VQE ansatz
    commonly used to run quantum chemistry simulations.

    The UCCSD unitary, within the first-order Trotter approximation, is given by:

    .. math::

        \hat{U}(\vec{\theta}) =
        \prod_{p > r} \mathrm{exp} \Big\{\theta_{pr}
        (\hat{c}_p^\dagger \hat{c}_r-\mathrm{H.c.}) \Big\}
        \prod_{p > q > r > s} \mathrm{exp} \Big\{\theta_{pqrs}
        (\hat{c}_p^\dagger \hat{c}_q^\dagger \hat{c}_r \hat{c}_s-\mathrm{H.c.}) \Big\}

    where :math:`\hat{c}` and :math:`\hat{c}^\dagger` are the fermionic annihilation and
    creation operators and the indices :math:`r, s` and :math:`p, q` run over the occupied and
    unoccupied molecular orbitals, respectively. Using the `Jordan-Wigner transformation
    <https://arxiv.org/abs/1208.5986>`_ the UCCSD unitary defined above can be written in terms
    of Pauli matrices as follows (for more details see
    `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_):

    .. math::

        \hat{U}(\vec{\theta}) = && \prod_{p > r} \mathrm{exp} \Big\{ \frac{i\theta_{pr}}{2}
        \bigotimes_{a=r+1}^{p-1} \hat{Z}_a (\hat{Y}_r \hat{X}_p - \mathrm{H.c.}) \Big\} \\
        && \times \prod_{p > q > r > s} \mathrm{exp} \Big\{ \frac{i\theta_{pqrs}}{8}
        \bigotimes_{b=s+1}^{r-1} \hat{Z}_b \bigotimes_{a=q+1}^{p-1}
        \hat{Z}_a (\hat{X}_s \hat{X}_r \hat{Y}_q \hat{X}_p +
        \hat{Y}_s \hat{X}_r \hat{Y}_q \hat{Y}_p +
        \hat{X}_s \hat{Y}_r \hat{Y}_q \hat{Y}_p +
        \hat{X}_s \hat{X}_r \hat{X}_q \hat{Y}_p -
        \{\mathrm{H.c.}\}) \Big\}.

    Args:
        weights (array): length ``len(ph)+len(pphh)`` vector containing the parameters
            :math:`\theta_{pr}` and :math:`\theta_{pqrs}` entering the Z rotation in
            :func:`~.SingleExcitationUnitary`
            and
            :func:`~.DoubleExcitationUnitary`. These parameters are precisely the coupled-cluster
            amplitudes that need to be optimized for each configuration generated by the
            excitation operator.
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        ph (Sequence[list]): Sequence of two-element lists containing the indices ``r, p`` that
            define the 1particle-1hole (ph) configuration :math:`\vert \mathrm{ph} \rangle =
            \hat{c}_p^\dagger \hat{c}_r \vert \mathrm{HF} \rangle`,
            where :math:`\vert \mathrm{HF} \rangle` denotes the Hartee-Fock (HF) reference state.
        pphh (Sequence[list]): Sequence of four-elements lists containing the indices
            ``s, r, q, p`` that define the 2particle-2hole configuration (pphh)
            :math:`\vert \mathrm{pphh} \rangle = \hat{c}_p^\dagger \hat{c}_q^\dagger \hat{c}_r
            \hat{c}_s \vert \mathrm{HF} \rangle`.
        init_state (array[int]): Length ``len(wires)`` occupation-number vector representing the
            HF state. ``init_state`` is used to initialize the qubit register.

    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        Notice that:

        #. The number of wires has to be equal to the number of spin-orbitals included in
           the active space.

        #. The ``ph`` and ``pphh`` lists can be generated with the function
           :func:`~.sd_excitations`. See example below.

        #. The vector of parameters ``weights`` is a one-dimensional array of size
           ``len(ph)+len(pphh)``


        An example of how to use this template is shown below:

        .. code-block:: python

            import pennylane as qml
            from pennylane.templates import UCCSD

            # Number of active electrons
            n_active_electrons = 2

            # Number of active spin-orbitals
            n_active_orbitals = 4

            # Set the ``ref_state`` array to encode the HF state
            ref_state = qml.qchem.hf_state(n_active_electrons, n_active_orbitals)

            # Spin-projection selection rule to generate the excitations
            DSz = 0

            # Generate the particle-hole configurations
            ph, pphh = qml.qchem.sd_excitations(n_active_electrons, n_active_orbitals, DS=DSz)

            dev = qml.device('default.qubit', wires=n_active_orbitals)

            @qml.qnode(dev)
            def circuit(weights, wires, ph=ph, pphh=pphh, init_state=ref_state):
                UCCSD(weights, wires, ph=ph, pphh=pphh, init_state=ref_state)
                return qml.expval(qml.PauliZ(0))

            params = np.random.normal(0, np.pi, len(ph) + len(pphh))
            print(circuit(params, wires, ph=ph, pphh=pphh, init_state=ref_state))

    """

    ##############
    # Input checks

    wires = Wires(wires)

    if (not ph) and (not pphh):
        raise ValueError(
            "'ph' and 'pphh' lists can not be both empty; got ph={}, pphh={}".format(ph, pphh)
        )

    check_type(ph, [list], msg="'ph' must be a list; got {}".format(ph))
    expected_shape = (2,)
    for i_ph in ph:
        check_type(i_ph, [list], msg="Each element of 'ph' must be a list; got {}".format(i_ph))
        check_shape(
            i_ph,
            expected_shape,
            msg="Elements of 'ph' must be of shape {}; got {}".format(
                expected_shape, get_shape(i_ph)
            ),
        )
        for i in i_ph:
            check_type(
                i, [int], msg="Each element of 'ph' must be a list of integers; got {}".format(i_ph)
            )

    check_type(pphh, [list], msg="'pphh' must be a list; got {}".format(pphh))
    expected_shape = (4,)
    for i_pphh in pphh:
        check_type(
            i_pphh, [list], msg="Each element of 'pphh' must be a list; got {}".format(i_pphh)
        )
        check_shape(
            i_pphh,
            expected_shape,
            msg="Elements of 'pphh' must be of shape {}; got {}".format(
                expected_shape, get_shape(i_pphh)
            ),
        )
        for i in i_pphh:
            check_type(
                i,
                [int],
                msg="Each element of 'pphh' must be a list of integers; got {}".format(i_pphh),
            )

    check_type(
        init_state,
        [np.ndarray],
        msg="'init_state' must be a Numpy array; got {}".format(init_state),
    )
    for i in init_state:
        check_type(
            i,
            [int, np.int64],
            msg="Elements of 'init_state' must be integers; got {}".format(init_state),
        )

    expected_shape = (len(ph) + len(pphh),)
    check_shape(
        weights,
        expected_shape,
        msg="'weights' must be of shape {}; got {}".format(expected_shape, get_shape(weights)),
    )

    expected_shape = (len(wires),)
    check_shape(
        init_state,
        expected_shape,
        msg="'init_state' must be of shape {}; got {}".format(
            expected_shape, get_shape(init_state)
        ),
    )

    ###############

    wires = wires.tolist()  # TODO: Remove when ops accept wires

    qml.BasisState(np.flip(init_state), wires=wires)

    for d, i_pphh in enumerate(pphh):
        DoubleExcitationUnitary(weights[len(ph) + d], wires=i_pphh)

    for s, i_ph in enumerate(ph):
        SingleExcitationUnitary(weights[s], wires=i_ph)
示例#28
0
def AmplitudeEmbedding(features, wires, pad=None, normalize=False):
    r"""Encodes :math:`2^n` features into the amplitude vector of :math:`n` qubits.

    By setting ``pad`` to a real or complex number, ``features`` is automatically padded to dimension
    :math:`2^n` where :math:`n` is the number of qubits used in the embedding.

    To represent a valid quantum state vector, the L2-norm of ``features`` must be one.
    The argument ``normalize`` can be set to ``True`` to automatically normalize the features.

    If both automatic padding and normalization are used, padding is executed *before* normalizing.

    .. note::

        On some devices, ``AmplitudeEmbedding`` must be the first operation of a quantum node.


    .. warning::

        ``AmplitudeEmbedding`` calls a circuit that involves non-trivial classical processing of the
        features. The ``features`` argument is therefore **not differentiable** when using the template, and
        gradients with respect to the features cannot be computed by PennyLane.

    Args:
        features (array): input array of shape ``(2^n,)``
        wires (Sequence[int] or int): :math:`n` qubit indices that the template acts on
        pad (float or complex): if not None, the input is padded with this constant to size :math:`2^n`
        normalize (Boolean): controls the activation of automatic normalization

    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        Amplitude embedding encodes a normalized :math:`2^n`-dimensional feature vector into the state
        of :math:`n` qubits:

        .. code-block:: python

            import pennylane as qml
            from pennylane.templates import AmplitudeEmbedding

            dev = qml.device('default.qubit', wires=2)

            @qml.qnode(dev)
            def circuit(f=None):
                AmplitudeEmbedding(features=f, wires=range(2))
                return qml.expval(qml.PauliZ(0))

            circuit(f=[1/2, 1/2, 1/2, 1/2])

        Checking the final state of the device, we find that it is equivalent to the input passed to the circuit:

        >>> dev._state
        [0.5+0.j 0.5+0.j 0.5+0.j 0.5+0.j]

        **Passing features as positional arguments to a quantum node**

        The ``features`` argument of ``AmplitudeEmbedding`` can in principle also be passed to the quantum node
        as a positional argument:

        .. code-block:: python

            @qml.qnode(dev)
            def circuit(f):
                AmplitudeEmbedding(features=f, wires=range(2))
                return qml.expval(qml.PauliZ(0))

        However, due to non-trivial classical processing to construct the state preparation circuit,
        the features argument is **not differentiable**.

        >>> g = qml.grad(circuit, argnum=0)
        >>> g([1,1,1,1])
        ValueError: Cannot differentiate wrt parameter(s) {0, 1, 2, 3}.


        **Normalization**

        The template will raise an error if the feature input is not normalized.
        One can set ``normalize=True`` to automatically normalize it:

        .. code-block:: python

            @qml.qnode(dev)
            def circuit(f=None):
                AmplitudeEmbedding(features=f, wires=range(2), normalize=True)
                return qml.expval(qml.PauliZ(0))

            circuit(f=[15, 15, 15, 15])

        The re-normalized feature vector is encoded into the quantum state vector:

        >>> dev._state
        [0.5 + 0.j, 0.5 + 0.j, 0.5 + 0.j, 0.5 + 0.j]

        **Padding**

        If the dimension of the feature vector is smaller than the number of amplitudes,
        one can automatically pad it with a constant for the missing dimensions using the ``pad`` option:

        .. code-block:: python

            from math import sqrt

            @qml.qnode(dev)
            def circuit(f=None):
                AmplitudeEmbedding(features=f, wires=range(2), pad=0.)
                return qml.expval(qml.PauliZ(0))

            circuit(f=[1/sqrt(2), 1/sqrt(2)])

        >>> dev._state
        [0.70710678 + 0.j, 0.70710678 + 0.j, 0.0 + 0.j, 0.0 + 0.j]

        **Operations before the embedding**

        On some devices, ``AmplitudeEmbedding`` must be the first operation in the quantum node.
        For example, ``'default.qubit'`` complains when running the following circuit:

        .. code-block:: python

            dev = qml.device('default.qubit', wires=2)

            @qml.qnode(dev)
            def circuit(f=None):
                qml.Hadamard(wires=0)
                AmplitudeEmbedding(features=f, wires=range(2))
                return qml.expval(qml.PauliZ(0))


        >>> circuit(f=[1/2, 1/2, 1/2, 1/2])
        pennylane._device.DeviceError: Operation QubitStateVector cannot be used
        after other Operations have already been applied on a default.qubit device.

    """

    #############
    # Input checks

    check_no_variable(pad, msg="'pad' cannot be differentiable")
    check_no_variable(normalize, msg="'normalize' cannot be differentiable")

    wires = check_wires(wires)

    n_amplitudes = 2 ** len(wires)
    expected_shape = (n_amplitudes,)
    if pad is None:
        shape = check_shape(
            features,
            expected_shape,
            msg="'features' must be of shape {}; got {}. Use the 'pad' "
            "argument for automated padding."
            "".format(expected_shape, get_shape(features)),
        )
    else:
        shape = check_shape(
            features,
            expected_shape,
            bound="max",
            msg="'features' must be of shape {} or smaller "
            "to be padded; got {}"
            "".format(expected_shape, get_shape(features)),
        )

    check_type(
        pad,
        [float, complex, type(None)],
        msg="'pad' must be a float or complex; got {}".format(pad),
    )
    check_type(normalize, [bool], msg="'normalize' must be a boolean; got {}".format(normalize))

    ###############

    #############
    # Preprocessing

    # pad
    n_features = shape[0]
    if pad is not None and n_amplitudes > n_features:
        features = np.pad(
            features, (0, n_amplitudes - n_features), mode="constant", constant_values=pad
        )

    # normalize
    if isinstance(features[0], Variable):
        feature_values = [s.val for s in features]
        norm = np.sum(np.abs(feature_values) ** 2)
    else:
        norm = np.sum(np.abs(features) ** 2)

    if not np.isclose(norm, 1.0, atol=TOLERANCE):
        if normalize or pad:
            features = features / np.sqrt(norm)
        else:
            raise ValueError(
                "'features' must be a vector of length 1.0; got length {}."
                "Use 'normalization=True' to automatically normalize.".format(norm)
            )

    ###############

    features = np.array(features)
    QubitStateVector(features, wires=wires)
示例#29
0
def broadcast(unitary, wires, pattern, parameters=None, kwargs=None):
    r"""Applies a unitary multiple times to a specific pattern of wires.

    The unitary, defined by the argument ``unitary``, is either a quantum operation
    (such as :meth:`~.pennylane.ops.RX`), or a
    user-supplied template. Depending on the chosen pattern, ``unitary`` is applied to a wire or a subset of wires:

    * ``pattern="single"`` applies a single-wire unitary to each one of the :math:`M` wires:

      .. figure:: ../../_static/templates/broadcast_single.png
            :align: center
            :width: 20%
            :target: javascript:void(0);

    * ``pattern="double"`` applies a two-wire unitary to :math:`\lfloor \frac{M}{2} \rfloor`
      subsequent pairs of wires:

      .. figure:: ../../_static/templates/broadcast_double.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

    * ``pattern="double_odd"`` applies a two-wire unitary to :math:`\lfloor \frac{M-1}{2} \rfloor`
      subsequent pairs of wires, starting with the second wire:

      .. figure:: ../../_static/templates/broadcast_double_odd.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

    * ``pattern="chain"`` applies a two-wire unitary to all :math:`M-1` neighbouring pairs of wires:

      .. figure:: ../../_static/templates/broadcast_chain.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

    * ``pattern="ring"`` applies a two-wire unitary to all :math:`M` neighbouring pairs of wires,
      where the last wire is considered to be a neighbour to the first one:

      .. figure:: ../../_static/templates/broadcast_ring.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

      .. note:: For 2 wires, the ring pattern is automatically replaced by ``pattern = 'chain'`` to avoid
                a mere repetition of the unitary.

    * ``pattern="pyramid"`` applies a two-wire unitary to wire pairs shaped in a pyramid declining to the right:

      .. figure:: ../../_static/templates/broadcast_pyramid.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

    * ``pattern="all_to_all"`` applies a two-wire unitary to wire pairs that connect all wires to each other:

      .. figure:: ../../_static/templates/broadcast_alltoall.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

    * A custom pattern can be passed by providing a list of wire lists to ``pattern``. The ``unitary`` is applied
      to each set of wires specified in the list.

      .. figure:: ../../_static/templates/broadcast_custom.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

    Each ``unitary`` may depend on a different set of parameters. These are passed as a list by the ``parameters``
    argument.

    For more details, see *Usage Details* below.

    Args:
        unitary (func): quantum gate or template
        pattern (str): specifies the wire pattern of the broadcast
        parameters (list): sequence of parameters for each gate applied
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        kwargs (dict): dictionary of auxilliary parameters for ``unitary``

    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        **Broadcasting single gates**

        In the simplest case the unitary is typically an :meth:`~.pennylane.operation.Operation` object
        implementing a quantum gate.

        .. code-block:: python

            import pennylane as qml
            from pennylane import broadcast

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

            @qml.qnode(dev)
            def circuit(pars):
                broadcast(unitary=qml.RX, pattern="single", wires=[0,1,2], parameters=pars)
                return qml.expval(qml.PauliZ(0))

            circuit([1, 1, 2])

        This is equivalent to the following circuit:

        .. code-block:: python

            @qml.qnode(dev)
            def circuit(pars):
                qml.RX(pars[0], wires=[0])
                qml.RX(pars[1], wires=[1])
                qml.RX(pars[2], wires=[2])
                return qml.expval(qml.PauliZ(0))

            circuit([1, 1, 2])

        **Broadcasting templates**

        Alternatively, one can broadcast a built-in or user-defined template:

        .. code-block:: python

            from pennylane.templates import template

            @template
            def mytemplate(pars, wires):
                qml.Hadamard(wires=wires)
                qml.RY(pars, wires=wires)

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

            @qml.qnode(dev)
            def circuit(pars):
                broadcast(unitary=mytemplate, pattern="single", wires=[0,1,2], parameters=pars)
                return qml.expval(qml.PauliZ(0))

            print(circuit([1, 1, 0.1]))

        **Constant unitaries**

        If the ``unitary`` argument does not take parameters, no ``parameters`` argument is passed to
        :func:`~.pennylane.broadcast`:

        .. code-block:: python

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

            @qml.qnode(dev)
            def circuit():
                broadcast(unitary=qml.Hadamard, pattern="single", wires=[0,1,2])
                return qml.expval(qml.PauliZ(0))

            circuit()

        **Multiple parameters in unitary**

        The unitary, whether it is a single gate or a user-defined template,
        can take multiple parameters. For example:

        .. code-block:: python

            from pennylane.templates import template

            @template
            def mytemplate(pars1, pars2, wires):
                qml.Hadamard(wires=wires)
                qml.RY(pars1, wires=wires)
                qml.RX(pars2, wires=wires)

            @qml.qnode(dev)
            def circuit(pars):
                broadcast(unitary=mytemplate, pattern="single", wires=[0,1,2], parameters=pars)
                return qml.expval(qml.PauliZ(0))

            circuit([[1, 1], [2, 1], [0.1, 1]])

        In general, the unitary takes D parameters and **must** have the following signature:

        .. code-block:: python

            unitary(parameter1, parameter2, ... parameterD, wires, **kwargs)

        If ``unitary`` does not depend on parameters (:math:`D=0`), the signature is

        .. code-block:: python

            unitary(wires, **kwargs)

        As a result, ``parameters`` must be a list or array of length-:math:`D` lists or arrays.

        If :math:`D` becomes large, the signature can be simplified by wrapping each entry in ``parameters``:

        .. code-block:: python

            @template
            def mytemplate(pars, wires):
                qml.Hadamard(wires=wires)
                qml.RY(pars[0], wires=wires)
                qml.RX(pars[1], wires=wires)

            @qml.qnode(dev)
            def circuit(pars):
                broadcast(unitary=mytemplate, pattern="single", wires=[0,1,2], parameters=pars)
                return qml.expval(qml.PauliZ(0))

            print(circuit([[[1, 1]], [[2, 1]], [[0.1, 1]]]))

        If the number of parameters for each wire does not match the unitary, an error gets thrown:

        .. code-block:: python

            @template
            def mytemplate(pars1, pars2, wires):
                qml.Hadamard(wires=wires)
                qml.RY(pars1, wires=wires)
                qml.RX(pars2, wires=wires)

            @qml.qnode(dev)
            def circuit(pars):
                broadcast(unitary=mytemplate, pattern="single", wires=[0, 1, 2], parameters=pars)
                return qml.expval(qml.PauliZ(0))

        >>> circuit([1, 2, 3]))
        TypeError: mytemplate() missing 1 required positional argument: 'pars2'

        **Keyword arguments**

        The unitary can be a template that takes additional keyword arguments.

        .. code-block:: python

            @template
            def mytemplate(wires, h=True):
                if h:
                    qml.Hadamard(wires=wires)
                qml.T(wires=wires)

            @qml.qnode(dev)
            def circuit(hadamard=None):
                broadcast(unitary=mytemplate, pattern="single", wires=[0, 1, 2], kwargs={'h': hadamard})
                return qml.expval(qml.PauliZ(0))

            circuit(hadamard=False)

        **Different patterns**

        The basic usage of the different patterns works as follows:

        * Double pattern

          .. code-block:: python

              dev = qml.device('default.qubit', wires=4)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='double',
                            wires=[0,1,2,3], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [-1, 2.5, 3]
              pars2 = [-1, 4, 2]

              circuit([pars1, pars2])

        * Double-odd pattern

          .. code-block:: python

              dev = qml.device('default.qubit', wires=4)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='double_odd',
                            wires=[0,1,2,3], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [-5.3, 2.3, 3]

              circuit([pars1])

        * Chain pattern

          .. code-block:: python

              dev = qml.device('default.qubit', wires=4)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='chain',
                            wires=[0,1,2,3], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [1.8, 2, 3]
              pars2 = [-1, 3, 1]
              pars3 = [2, -1.2, 4]

              circuit([pars1, pars2, pars3])

        * Ring pattern

          In general, the number of parameter sequences has to match
          the number of wires:

          .. code-block:: python

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

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='ring',
                            wires=[0,1,2], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [1, -2.2, 3]
              pars2 = [-1, 3, 1]
              pars3 = [2.6, 1, 4]

              circuit([pars1, pars2, pars3])

          However, there is an exception for 2 wires, where only one set of parameters is needed.
          This avoids repeating a gate over the
          same wires twice:

          .. code-block:: python

              dev = qml.device('default.qubit', wires=2)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='ring',
                            wires=[0,1], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [-3.2, 2, 1.2]

              circuit([pars1])

        * Pyramid pattern

          .. code-block:: python

              dev = qml.device('default.qubit', wires=4)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='pyramid',
                            wires=[0,1,2,3], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [1.1, 2, 3]
              pars2 = [-1, 3, 1]
              pars3 = [2, 1, 4.2]

              circuit([pars1, pars2, pars3])

        * All-to-all pattern

          .. code-block:: python

              dev = qml.device('default.qubit', wires=4)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='ring',
                            wires=[0,1,2,3], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [1, 2, 3]
              pars2 = [-1, 3, 1]
              pars3 = [2, 1, 4]
              pars4 = [-1, -2, -3]
              pars5 = [2, 1, 4]
              pars6 = [3, -2, -3]

              circuit([pars1, pars2, pars3, pars4, pars5, pars6])

        * Custom pattern

          For a custom pattern, the wire lists for each application of the unitary is
          passed to ``pattern``:

          .. code-block:: python

              dev = qml.device('default.qubit', wires=5)

              pattern = [[0, 1], [3, 4]]

              @qml.qnode(dev)
              def circuit():
                  broadcast(unitary=qml.CNOT, pattern=pattern,
                            wires=range(5))
                  return qml.expval(qml.PauliZ(0))

              circuit()

          When using a parametrized unitary, make sure that the number of wire lists in ``pattern`` corresponds to the
          number of parameters in ``parameters``.

          .. code-block:: python

                pattern = [[0, 1], [3, 4]]

                @qml.qnode(dev)
                def circuit(pars):
                    broadcast(unitary=qml.CRot, pattern=pattern,
                              wires=range(5), parameters=pars)
                    return qml.expval(qml.PauliZ(0))

                pars1 = [1, 2, 3]
                pars2 = [-1, 3, 1]
                pars = [pars1, pars2]

                assert len(pars) == len(pattern)

                circuit(pars)
    """

    OPTIONS = [
        "single", "double", "double_odd", "chain", "ring", "pyramid",
        "all_to_all", "custom"
    ]

    #########
    # Input checks

    wires = Wires(wires)

    check_type(
        parameters,
        [Iterable, type(None)],
        msg="'parameters' must be either of type None or "
        "Iterable; got {}".format(type(parameters)),
    )

    if kwargs is None:
        kwargs = {}

    check_type(
        kwargs,
        [dict],
        msg="'kwargs' must be a dictionary; got {}".format(type(kwargs)),
    )

    custom_pattern = None

    if isinstance(pattern, str):
        check_is_in_options(
            pattern,
            OPTIONS,
            msg="did not recognize option {} for 'pattern'".format(pattern),
        )
    else:
        # turn custom pattern into list of Wires objects
        custom_pattern = [Wires(w) for w in pattern]
        # set "pattern" to "custom", indicating that custom settings have to be used
        pattern = "custom"

    n_parameters = {
        "single":
        len(wires),
        "double":
        0 if len(wires) in [0, 1] else len(wires) // 2,
        "double_odd":
        0 if len(wires) in [0, 1] else (len(wires) - 1) // 2,
        "chain":
        0 if len(wires) in [0, 1] else len(wires) - 1,
        "ring":
        0 if len(wires) in [0, 1] else (1 if len(wires) == 2 else len(wires)),
        "pyramid":
        0 if len(wires) in [0, 1] else sum(i + 1
                                           for i in range(len(wires) // 2)),
        "all_to_all":
        0 if len(wires) in [0, 1] else len(wires) * (len(wires) - 1) // 2,
        "custom":
        len(custom_pattern) if custom_pattern is not None else None,
    }

    # check that there are enough parameters for pattern
    if parameters is not None:
        shape = get_shape(parameters)

        # specific error message for ring edge case of 2 wires
        if (pattern == "ring") and (len(wires) == 2) and (shape[0] != 1):
            raise ValueError(
                "the ring pattern with 2 wires is an exception and only applies one unitary"
            )

        if shape[0] != n_parameters[pattern]:
            raise ValueError(
                "'parameters' must contain entries for {} unitaries; got {} entries"
                .format(n_parameters[pattern], shape[0]))

        # repackage for consistent unpacking
        if len(shape) == 1:
            parameters = [[p] for p in parameters]
    else:
        parameters = [[] for _ in range(n_parameters[pattern])]

    #########

    # define wire sequences for patterns
    wire_sequence = {
        "single": [wires[i] for i in range(len(wires))],
        "double":
        [wires.subset([i, i + 1]) for i in range(0,
                                                 len(wires) - 1, 2)],
        "double_odd":
        [wires.subset([i, i + 1]) for i in range(1,
                                                 len(wires) - 1, 2)],
        "chain": [wires.subset([i, i + 1]) for i in range(len(wires) - 1)],
        "ring":
        wires_ring(wires),
        "pyramid":
        wires_pyramid(wires),
        "all_to_all":
        wires_all_to_all(wires),
        "custom":
        custom_pattern,
    }

    # broadcast the unitary
    for wires, pars in zip(wire_sequence[pattern], parameters):
        wires = wires.tolist(
        )  # TODO: Delete once operator takes Wires objects
        unitary(*pars, wires=wires, **kwargs)
示例#30
0
def DoubleExcitationUnitary(weight, wires1=None, wires2=None):
    r"""Circuit to exponentiate the tensor product of Pauli matrices representing the
    fermionic double-excitation operator entering the Unitary Coupled-Cluster Singles
    and Doubles (UCCSD) ansatz. UCCSD is a VQE ansatz commonly used to run quantum
    chemistry simulations.

    The CC double-excitation operator is given by

    .. math::

        \hat{U}_{pqrs}(\theta) = \mathrm{exp} \{ \theta (\hat{c}_p^\dagger \hat{c}_q^\dagger
        \hat{c}_r \hat{c}_s - \mathrm{H.c.}) \},

    where :math:`\hat{c}` and :math:`\hat{c}^\dagger` are the fermionic annihilation and
    creation operators and the indices :math:`r, s` and :math:`p, q` run over the occupied and
    unoccupied molecular orbitals, respectively. Using the `Jordan-Wigner transformation
    <https://arxiv.org/abs/1208.5986>`_ the fermionic operator defined above can be written
    in terms of Pauli matrices (for more details see
    `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_):

    .. math::

        \hat{U}_{pqrs}(\theta) = \mathrm{exp} \Big\{
        \frac{i\theta}{8} \bigotimes_{b=s+1}^{r-1} \hat{Z}_b \bigotimes_{a=q+1}^{p-1}
        \hat{Z}_a (\hat{X}_s \hat{X}_r \hat{Y}_q \hat{X}_p +
        \hat{Y}_s \hat{X}_r \hat{Y}_q \hat{Y}_p + \hat{X}_s \hat{Y}_r \hat{Y}_q \hat{Y}_p +
        \hat{X}_s \hat{X}_r \hat{X}_q \hat{Y}_p - \mathrm{H.c.}  ) \Big\}

    The quantum circuit to exponentiate the tensor product of Pauli matrices entering
    the latter equation is shown below:

    |

    .. figure:: ../../_static/templates/subroutines/double_excitation_unitary.png

    |
        :align: center
        :width: 60%
        :target: javascript:void(0);

    As explained in `Seely et al. (2012) <https://arxiv.org/abs/1208.5986>`_,
    the exponential of a tensor product of Pauli-Z operators can be decomposed in terms of
    :math:`2(n-1)` CNOT gates and a single-qubit Z-rotation. If there are :math:`X` or
    :math:`Y` Pauli matrices in the product, the Hadamard (:math:`H`) or :math:`R_x` gate has
    to be applied to change to the :math:`X` or :math:`Y` basis, respectively.

    Args:
        weight (float): angle :math:`\theta` entering the Z rotation acting on wire ``p``
        wires1 (Iterable or Wires): Wires of the qubits representing the subset of occupied orbitals
            in the interval ``[s, r]``. Accepts an iterable of numbers or strings, or a Wires object,
            with minimum length 2. The first wire is interpreted as ``s`` and the last wire as ``r``.
            Wires in between are acted on with CNOT gates to compute the parity of the set of qubits.
        wires2 (Iterable or Wires): Wires of the qubits representing the subset of virtual orbitals
            in the interval ``[q, p]``. Accepts an iterable of numbers or strings, or a Wires object.
            Must be of minimum length 2. The first wire is interpreted as ``q`` and the last wire is
            interpreted as ``p``. Wires in between are acted on with CNOT gates to compute the parity
            of the set of qubits.

    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        Notice that:

        #. :math:`\hat{U}_{pqrs}(\theta)` involves eight exponentiations where
           :math:`\hat{U}_1`, :math:`\hat{U}_2`, :math:`\hat{U}_3`, :math:`\hat{U}_4` and
           :math:`\hat{U}_\theta` are defined as follows,

           .. math::

               [U_1, && U_2, U_3, U_4, U_{\theta}] = \\
               && \Bigg\{\bigg[H, H, R_x(-\frac{\pi}{2}), H, R_z(\theta/8)\bigg],
               \bigg[R_x(-\frac{\pi}{2}), H, R_x(-\frac{\pi}{2}), R_x(-\frac{\pi}{2}),
               R_z(\frac{\theta}{8}) \bigg], \\
               && \bigg[H, R_x(-\frac{\pi}{2}), R_x(-\frac{\pi}{2}), R_x(-\frac{\pi}{2}),
               R_z(\frac{\theta}{8}) \bigg], \bigg[H, H, H, R_x(-\frac{\pi}{2}),
               R_z(\frac{\theta}{8}) \bigg], \\
               && \bigg[R_x(-\frac{\pi}{2}), H, H, H, R_z(-\frac{\theta}{8}) \bigg],
               \bigg[H, R_x(-\frac{\pi}{2}), H, H, R_z(-\frac{\theta}{8}) \bigg], \\
               && \bigg[R_x(-\frac{\pi}{2}), R_x(-\frac{\pi}{2}), R_x(-\frac{\pi}{2}),
               H, R_z(-\frac{\theta}{8}) \bigg], \bigg[R_x(-\frac{\pi}{2}), R_x(-\frac{\pi}{2}),
               H, R_x(-\frac{\pi}{2}), R_z(-\frac{\theta}{8}) \bigg] \Bigg\}

        #. For a given quadruple ``[s, r, q, p]`` with :math:`p>q>r>s`, seventy-two single-qubit
           operations are applied. Notice also that consecutive CNOT gates act on qubits with
           indices between ``s`` and ``r`` and ``q`` and ``p`` while a single CNOT acts on wires
           ``r`` and ``q``. The operations performed across these qubits are shown in dashed lines
           in the figure above.

        An example of how to use this template is shown below:

        .. code-block:: python

            import pennylane as qml
            from pennylane.templates import DoubleExcitationUnitary

            dev = qml.device('default.qubit', wires=5)

            @qml.qnode(dev)
            def circuit(weight, pphh=None):
                DoubleExcitationUnitary(weight, wires=pphh)
                return qml.expval(qml.PauliZ(0))

            weight = 1.34817
            double_excitation = [0, 1, 3, 4]
            print(circuit(weight, pphh=double_excitation))

    """

    ##############
    # Input checks

    wires1 = Wires(wires1)
    wires2 = Wires(wires2)

    if len(wires1) < 2:
        raise ValueError(
            "expected at least two wires representing the occupied orbitals; "
            "got {}".format(len(wires1))
        )
    if len(wires2) < 2:
        raise ValueError(
            "expected at least two wires representing the unoccupied orbitals; "
            "got {}".format(len(wires2))
        )

    expected_shape = ()
    check_shape(
        weight,
        expected_shape,
        msg="'weight' must be of shape {}; got {}".format(expected_shape, get_shape(weight)),
    )

    ###############

    s = wires1[0]
    r = wires1[-1]
    q = wires2[0]
    p = wires2[-1]

    # Sequence of the wires entering the CNOTs
    cnots_occ = [wires1.subset([l, l + 1]) for l in range(len(wires1) - 1)]
    cnots_unocc = [wires2.subset([l, l + 1]) for l in range(len(wires2) - 1)]

    set_cnot_wires = cnots_occ + [Wires([r, q])] + cnots_unocc

    # Apply the first layer
    _layer1(weight, s, r, q, p, set_cnot_wires)

    # Apply the second layer
    _layer2(weight, s, r, q, p, set_cnot_wires)

    # Apply the third layer
    _layer3(weight, s, r, q, p, set_cnot_wires)

    # Apply the fourth layer
    _layer4(weight, s, r, q, p, set_cnot_wires)

    # Apply the fifth layer
    _layer5(weight, s, r, q, p, set_cnot_wires)

    # Apply the sixth layer
    _layer6(weight, s, r, q, p, set_cnot_wires)

    # Apply the seventh layer
    _layer7(weight, s, r, q, p, set_cnot_wires)

    # Apply the eighth layer
    _layer8(weight, s, r, q, p, set_cnot_wires)