def test_integration(self, C):
        """Test expected serialization for a random circuit"""
        with qml.tape.QuantumTape() as tape:
            qml.RX(0.4, wires=0)
            qml.RY(0.6, wires=1).inv().inv()
            qml.CNOT(wires=[0, 1])
            qml.QubitUnitary(np.eye(4), wires=[0, 1])
            qml.templates.QFT(wires=[0, 1, 2]).inv()
            qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0])
            qml.DoubleExcitationMinus(0.555, wires=[0, 1, 2, 3])
            qml.DoubleExcitationPlus(0.555, wires=[0, 1, 2, 3])

        s = _serialize_ops(tape, self.wires_dict)

        dtype = np.complex64 if C else np.complex128
        s_expected = (
            (
                [
                    "RX",
                    "RY",
                    "CNOT",
                    "QubitUnitary",
                    "QFT",
                    "DoubleExcitation",
                    "DoubleExcitationMinus",
                    "DoubleExcitationPlus",
                ],
                [[0.4], [0.6], [], [], [], [0.555], [0.555], [0.555]],
                [[0], [1], [0, 1], [0, 1], [0, 1, 2], [3, 2, 1, 0], [0, 1, 2, 3], [0, 1, 2, 3]],
                [False, False, False, False, False, False, False, False],
                [
                    [],
                    [],
                    [],
                    qml.matrix(qml.QubitUnitary(np.eye(4, dtype=dtype), wires=[0, 1])),
                    qml.matrix(qml.templates.QFT(wires=[0, 1, 2]).inv()),
                    [],
                    [],
                    [],
                ],
            ),
            False,
        )
        assert s[0][0] == s_expected[0][0]
        assert s[0][1] == s_expected[0][1]
        assert s[0][2] == s_expected[0][2]
        assert s[0][3] == s_expected[0][3]
        assert s[1] == s_expected[1]

        assert all(np.allclose(s1, s2) for s1, s2 in zip(s[0][4], s_expected[0][4]))
Ejemplo n.º 2
0
def _serialize_ops(
    tape: QuantumTape, wires_map: dict
) -> Tuple[List[List[str]], List[np.ndarray], List[List[int]], List[bool], List[np.ndarray]]:
    """Serializes the operations of an input tape.

    The state preparation operations are not included.

    Args:
        tape (QuantumTape): the input quantum tape
        wires_map (dict): a dictionary mapping input wires to the device's backend wires

    Returns:
        Tuple[list, list, list, list, list]: A serialization of the operations, containing a list
        of operation names, a list of operation parameters, a list of observable wires, a list of
        inverses, and a list of matrices for the operations that do not have a dedicated kernel.
    """
    names = []
    params = []
    wires = []
    inverses = []
    mats = []

    uses_stateprep = False

    for o in tape.operations:
        if isinstance(o, (BasisState, QubitStateVector)):
            uses_stateprep = True
            continue
        elif isinstance(o, Rot):
            op_list = o.expand().operations
        else:
            op_list = [o]

        for single_op in op_list:
            is_inverse = single_op.inverse

            name = single_op.name if not is_inverse else single_op.name[:-4]
            names.append(name)

            if not hasattr(StateVectorC128, name):
                params.append([])
                mats.append(qml.matrix(single_op))

                if is_inverse:
                    is_inverse = False
            else:
                params.append(single_op.parameters)
                mats.append([])

            wires_list = single_op.wires.tolist()
            wires.append([wires_map[w] for w in wires_list])
            inverses.append(is_inverse)

    return (names, params, wires, inverses, mats), uses_stateprep
    def apply_lightning(self, state, operations):
        """Apply a list of operations to the state tensor.

        Args:
            state (array[complex]): the input state tensor
            operations (list[~pennylane.operation.Operation]): operations to apply
            dtype (type): Type of numpy ``complex`` to be used. Can be important
            to specify for large systems for memory allocation purposes.

        Returns:
            array[complex]: the output state tensor
        """
        state_vector = np.ravel(state)

        if self.use_csingle:
            # use_csingle
            sim = StateVectorC64(state_vector)
        else:
            # self.C_DTYPE is np.complex128 by default
            sim = StateVectorC128(state_vector)

        # Skip over identity operations instead of performing
        # matrix multiplication with the identity.
        skipped_ops = ["Identity"]

        for o in operations:
            if o.base_name in skipped_ops:
                continue
            name = o.name.split(".")[
                0]  # The split is because inverse gates have .inv appended
            method = getattr(sim, name, None)

            wires = self.wires.indices(o.wires)

            if method is None:
                # Inverse can be set to False since qml.matrix(o) is already in inverted form
                method = getattr(sim, "applyMatrix")
                try:
                    method(qml.matrix(o), wires, False)
                except AttributeError:  # pragma: no cover
                    # To support older versions of PL
                    method(o.matrix, wires, False)
            else:
                inv = o.inverse
                param = o.parameters
                method(wires, inv, param)

        return np.reshape(state_vector, state.shape)
Ejemplo n.º 4
0
def _assert_decomposition(pl_op, params=None):
    num_wires = pl_op.num_wires
    dimension = 2**num_wires
    num_indices = 2 * num_wires
    wires = list(range(num_wires))

    contraction_parameters = [
        np.reshape(np.eye(dimension), [2] * num_indices),
        list(range(num_indices)),
    ]
    index_substitutions = {i: i + num_wires for i in range(num_wires)}
    next_index = num_indices

    # get decomposed gates
    decomposed_op = (pl_op.compute_decomposition(*params, wires=wires)
                     if params else pl_op.compute_decomposition(wires=wires))

    # Heterogeneous matrix chain multiplication using tensor contraction
    for gate in reversed(decomposed_op):
        gate_wires = gate.wires.tolist()

        # Upper indices, which will be traced out
        contravariant = [index_substitutions[i] for i in gate_wires]

        # Lower indices, which will replace the contracted indices in the matrix
        covariant = list(range(next_index, next_index + len(gate_wires)))

        indices = contravariant + covariant
        # `qml.matrix(gate)` as type-(len(contravariant), len(covariant)) tensor
        gate_tensor = np.reshape(qml.matrix(gate), [2] * len(indices))

        contraction_parameters += [gate_tensor, indices]
        next_index += len(gate_wires)
        index_substitutions.update(
            {gate_wires[i]: covariant[i]
             for i in range(len(gate_wires))})

    # Ensure matrix is in the correct order
    new_indices = wires + [index_substitutions[i] for i in range(num_wires)]
    contraction_parameters.append(new_indices)

    actual_matrix = np.reshape(np.einsum(*contraction_parameters),
                               [dimension, dimension])
    pl_op_matrix = pl_op.compute_matrix(
        *params) if params else pl_op.compute_matrix()

    assert np.allclose(actual_matrix, pl_op_matrix)
Ejemplo n.º 5
0
def _serialize_named_hermitian_ob(o, wires_map: dict, use_csingle: bool):
    """Serializes an observable (Named or Hermitian)"""
    assert not isinstance(o, Tensor)

    if use_csingle:
        ctype = np.complex64
        named_obs = NamedObsC64
        hermitian_obs = HermitianObsC64
    else:
        ctype = np.complex128
        named_obs = NamedObsC128
        hermitian_obs = HermitianObsC128

    wires_list = o.wires.tolist()
    wires = [wires_map[w] for w in wires_list]
    if _obs_has_kernel(o):
        return named_obs(o.name, wires)
    return hermitian_obs(qml.matrix(o).ravel().astype(ctype), wires)
Ejemplo n.º 6
0
def test_gate_unitary_correct(op, op_name):
    """Test if lightning.qubit correctly applies gates by reconstructing the unitary matrix and
    comparing to the expected version"""

    if op_name in ("BasisState", "QubitStateVector"):
        pytest.skip("Skipping operation because it is a state preparation")
    if op_name in (
        "ControlledQubitUnitary",
        "QubitUnitary",
        "MultiControlledX",
        "DiagonalQubitUnitary",
    ):
        pytest.skip("Skipping operation.")  # These are tested in the device test-suite
    if op == None:
        pytest.skip("Skipping operation.")

    wires = op.num_wires

    if wires == -1:  # This occurs for operations that do not have a predefined number of wires
        wires = 4

    dev = qml.device("lightning.qubit", wires=wires)
    num_params = op.num_params
    p = [0.1] * num_params

    op = getattr(qml, op_name)

    @qml.qnode(dev)
    def output(input):
        qml.BasisState(input, wires=range(wires))
        op(*p, wires=range(wires))
        return qml.state()

    unitary = np.zeros((2**wires, 2**wires), dtype=np.complex128)

    for i, input in enumerate(itertools.product([0, 1], repeat=wires)):
        out = output(np.array(input))
        unitary[:, i] = out

    unitary_expected = qml.matrix(op(*p, wires=range(wires)))

    assert np.allclose(unitary, unitary_expected)
Ejemplo n.º 7
0
def test_gate_generator(pl_op, braket_gate, angle):
    op = pl_op(angle, wires=[0, 1])
    if op.name != "PSWAP":
        assert np.allclose(
            qml.matrix(op),
            scipy.linalg.expm(1j * angle * qml.matrix(op.generator())))
Ejemplo n.º 8
0
def test_gate_adjoint_non_parametrized(pl_op, braket_gate):
    op = pl_op(wires=[0, 1])
    assert np.allclose(
        np.dot(pl_op.compute_matrix(), qml.matrix(pl_op.adjoint(op))),
        np.identity(4))
def _(h: qml.Hermitian):
    return observables.Hermitian(qml.matrix(h))
Ejemplo n.º 10
0
    def qubit_operator_string(self, observable):
        """Serializes a PennyLane observable to a string compatible with the
        openfermion.QubitOperator class.

        This method decomposes an observable into a sum of Pauli terms and
        identities, if needed.

        **Example**

        >>> dev = QeQiskitDevice(wires=2)
        >>> obs = qml.PauliZ(0)
        >>> op_str = dev.qubit_operator_string(obs)
        >>> print(op_str)
        1 [Z0]
        >>> obs = qml.Hadamard(0)
        >>> op_str = dev.qubit_operator_string(obs)
        >>> print(op_str)
        0.7071067811865475 [X0] + 0.7071067811865475 [Z0]

        Args:
            observable (pennylane.operation.Observable): the observable to serialize

        Returns:
            str: the ``openfermion.QubitOperator`` string representation
        """
        accepted_obs = {"PauliX", "PauliY", "PauliZ", "Identity"}

        if isinstance(observable, Tensor):
            need_decomposition = any(o.name not in accepted_obs
                                     for o in observable.obs)
        else:
            need_decomposition = observable.name not in accepted_obs

        if need_decomposition:
            original_observable = observable

            # Decompose the matrix of the observable
            # This removes information about the wire labels used and
            # consecutive integer wires are used
            coeffs, obs_list = decompose_hamiltonian(
                matrix(original_observable))

            for idx in range(len(obs_list)):
                obs = obs_list[idx]

                if not isinstance(obs, Tensor):
                    # Convert terms to Tensor such that _terms_to_qubit_operator
                    # can be used
                    obs_list[idx] = Tensor(obs)

                # Need to use the custom wire labels of the original observable
                original_wires = original_observable.wires.tolist()
                for o, mapped_w in zip(obs_list[idx].obs, original_wires):
                    o._wires = Wires(mapped_w)

        else:
            if not isinstance(observable, Tensor):
                # If decomposition is not needed and is not a Tensor, we need
                # to convert the single observable
                observable = Tensor(observable)

            coeffs = [1]
            obs_list = [observable]

        # Use consecutive integers as default wire_map
        wire_map = {v: idx for idx, v in enumerate(self.wires)}
        return _terms_to_qubit_operator_string(coeffs,
                                               obs_list,
                                               wires=wire_map)
    # Rotations need to be included
    (qml.PauliY(2), "[Z2]"),
    (qml.PauliX(0) @ qml.PauliY(2), "[Z0 Z2]"),
    (qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliY(2), "[Z0 Z1 Z2]"),
]

# More advanced examples for testing the correct wires
obs_decomposed_wires_check = [
    (qml.Hadamard(0), "0.7071067811865475 [X0] + 0.7071067811865475 [Z0]"),
    (qml.Hadamard(2), "0.7071067811865475 [X2] + 0.7071067811865475 [Z2]"),
    (
        qml.Hadamard(2) @ qml.Hadamard(1),
        "0.4999999999999999 [X2 X1] + 0.4999999999999999 [X2 Z1] + 0.4999999999999999 [Z2 X1] + 0.4999999999999999 [Z2 Z1]",
    ),
    (
        qml.Hermitian(qml.matrix(
            qml.PauliY(1) @ qml.PauliX(0) @ qml.PauliZ(2)),
                      wires=[1, 0, 2]),
        "1.0 [Y1 X0 Z2]",
    ),
    (qml.PauliY(2) @ qml.Hermitian(qml.matrix(qml.PauliX(1)), wires=[1]),
     "1.0 [Y2 X1]"),
]


class TestSerializeOperator:
    """Test the serialize_operator function"""
    @pytest.mark.parametrize("wires, expected", [([2], "[Z2]"),
                                                 ([0, 2], "[Z0 Z2]"),
                                                 ([0, 1, 2], "[Z0 Z1 Z2]")])
    def test_pauliz_operator_string(self, wires, expected):
        """Test that an operator is serialized correctly on a device with