def circuit(w, x, y, z):
     """Test QNode"""
     qml.BasisState(np.array([0, 1]), wires=[0, 1])
     qml.Hadamard(wires=1)
     plf.CPHASE(w, 1, wires=[0, 1])
     qml.Rot(x, y, z, wires=0)
     plf.CPHASE(w, 3, wires=[0, 1])
     plf.CPHASE(w, 0, wires=[0, 1])
     return qml.expval(qml.PauliY(1))
Esempio n. 2
0
class TestQVMBasic(BaseTest):
    """Unit tests for the QVM simulator."""

    # pylint: disable=protected-access

    def test_identity_expectation(self, shots, qvm, compiler):
        """Test that identity expectation value (i.e. the trace) is 1"""
        theta = 0.432
        phi = 0.123

        dev = plf.QVMDevice(device="2q-qvm", shots=shots)

        with qml.tape.QuantumTape() as tape:
            qml.RX(theta, wires=[0])
            qml.RX(phi, wires=[1])
            qml.CNOT(wires=[0, 1])
            O1 = qml.expval(qml.Identity(wires=[0]))
            O2 = qml.expval(qml.Identity(wires=[1]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)

        dev._samples = dev.generate_samples()

        res = np.array([dev.expval(O1.obs), dev.expval(O2.obs)])

        # below are the analytic expectation values for this circuit (trace should always be 1)
        self.assertAllAlmostEqual(res,
                                  np.array([1, 1]),
                                  delta=3 / np.sqrt(shots))

    def test_pauliz_expectation(self, shots, qvm, compiler):
        """Test that PauliZ expectation value is correct"""
        theta = 0.432
        phi = 0.123

        dev = plf.QVMDevice(device="2q-qvm", shots=shots)

        with qml.tape.QuantumTape() as tape:
            qml.RX(theta, wires=[0])
            qml.RX(phi, wires=[1])
            qml.CNOT(wires=[0, 1])
            O1 = qml.expval(qml.PauliZ(wires=[0]))
            O2 = qml.expval(qml.PauliZ(wires=[1]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)
        dev._samples = dev.generate_samples()

        res = np.array([dev.expval(O1.obs), dev.expval(O2.obs)])

        # below are the analytic expectation values for this circuit
        self.assertAllAlmostEqual(
            res,
            np.array([np.cos(theta),
                      np.cos(theta) * np.cos(phi)]),
            delta=3 / np.sqrt(shots))

    def test_paulix_expectation(self, shots, qvm, compiler):
        """Test that PauliX expectation value is correct"""
        theta = 0.432
        phi = 0.123

        dev = plf.QVMDevice(device="2q-qvm", shots=shots)

        with qml.tape.QuantumTape() as tape:
            qml.RY(theta, wires=[0])
            qml.RY(phi, wires=[1])
            qml.CNOT(wires=[0, 1])
            O1 = qml.expval(qml.PauliX(wires=[0]))
            O2 = qml.expval(qml.PauliX(wires=[1]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)
        dev._samples = dev.generate_samples()

        res = np.array([dev.expval(O1.obs), dev.expval(O2.obs)])
        # below are the analytic expectation values for this circuit
        self.assertAllAlmostEqual(
            res,
            np.array([np.sin(theta) * np.sin(phi),
                      np.sin(phi)]),
            delta=3 / np.sqrt(shots))

    def test_pauliy_expectation(self, shots, qvm, compiler):
        """Test that PauliY expectation value is correct"""
        theta = 0.432
        phi = 0.123

        dev = plf.QVMDevice(device="2q-qvm", shots=shots)

        with qml.tape.QuantumTape() as tape:
            qml.RX(theta, wires=[0])
            qml.RX(phi, wires=[1])
            qml.CNOT(wires=[0, 1])
            O1 = qml.expval(qml.PauliY(wires=[0]))
            O2 = qml.expval(qml.PauliY(wires=[1]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)
        dev._samples = dev.generate_samples()

        res = np.array([dev.expval(O1.obs), dev.expval(O2.obs)])

        # below are the analytic expectation values for this circuit
        self.assertAllAlmostEqual(res,
                                  np.array([0, -np.cos(theta) * np.sin(phi)]),
                                  delta=3 / np.sqrt(shots))

    def test_hadamard_expectation(self, shots, qvm, compiler):
        """Test that Hadamard expectation value is correct"""
        theta = 0.432
        phi = 0.123

        dev = plf.QVMDevice(device="2q-qvm", shots=shots)

        with qml.tape.QuantumTape() as tape:
            qml.RY(theta, wires=[0])
            qml.RY(phi, wires=[1])
            qml.CNOT(wires=[0, 1])
            O1 = qml.expval(qml.Hadamard(wires=[0]))
            O2 = qml.expval(qml.Hadamard(wires=[1]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)
        dev._samples = dev.generate_samples()

        res = np.array([dev.expval(O1.obs), dev.expval(O2.obs)])

        # below are the analytic expectation values for this circuit
        expected = np.array([
            np.sin(theta) * np.sin(phi) + np.cos(theta),
            np.cos(theta) * np.cos(phi) + np.sin(phi)
        ]) / np.sqrt(2)
        self.assertAllAlmostEqual(res, expected, delta=3 / np.sqrt(shots))

    @flaky(max_runs=10, min_passes=3)
    def test_hermitian_expectation(self, shots, qvm, compiler):
        """Test that arbitrary Hermitian expectation values are correct.

        As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added.
        """

        theta = 0.432
        phi = 0.123

        dev = plf.QVMDevice(device="2q-qvm", shots=shots)

        with qml.tape.QuantumTape() as tape:
            qml.RY(theta, wires=[0])
            qml.RY(phi, wires=[1])
            qml.CNOT(wires=[0, 1])
            O1 = qml.expval(qml.Hermitian(H, wires=[0]))
            O2 = qml.expval(qml.Hermitian(H, wires=[1]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)
        dev._samples = dev.generate_samples()

        res = np.array([dev.expval(O1.obs), dev.expval(O2.obs)])

        # below are the analytic expectation values for this circuit with arbitrary
        # Hermitian observable H
        a = H[0, 0]
        re_b = H[0, 1].real
        d = H[1, 1]
        ev1 = ((a - d) * np.cos(theta) +
               2 * re_b * np.sin(theta) * np.sin(phi) + a + d) / 2
        ev2 = ((a - d) * np.cos(theta) * np.cos(phi) + 2 * re_b * np.sin(phi) +
               a + d) / 2
        expected = np.array([ev1, ev2])

        self.assertAllAlmostEqual(res, expected, delta=4 / np.sqrt(shots))

    def test_multi_qubit_hermitian_expectation(self, shots, qvm, compiler):
        """Test that arbitrary multi-qubit Hermitian expectation values are correct"""
        theta = 0.432
        phi = 0.123

        A = np.array([
            [-6, 2 + 1j, -3, -5 + 2j],
            [2 - 1j, 0, 2 - 1j, -5 + 4j],
            [-3, 2 + 1j, 0, -4 + 3j],
            [-5 - 2j, -5 - 4j, -4 - 3j, -6],
        ])

        dev = plf.QVMDevice(device="2q-qvm", shots=10 * shots)

        with qml.tape.QuantumTape() as tape:
            qml.RY(theta, wires=[0])
            qml.RY(phi, wires=[1])
            qml.CNOT(wires=[0, 1])
            O1 = qml.expval(qml.Hermitian(A, wires=[0, 1]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)
        dev._samples = dev.generate_samples()

        res = np.array([dev.expval(O1.obs)])
        # below is the analytic expectation value for this circuit with arbitrary
        # Hermitian observable A
        expected = 0.5 * (6 * np.cos(theta) * np.sin(phi) - np.sin(theta) *
                          (8 * np.sin(phi) + 7 * np.cos(phi) + 3) -
                          2 * np.sin(phi) - 6 * np.cos(phi) - 6)

        self.assertAllAlmostEqual(res, expected, delta=5 / np.sqrt(shots))

    def test_var(self, shots, qvm, compiler):
        """Tests for variance calculation"""
        dev = plf.QVMDevice(device="2q-qvm", shots=shots)

        phi = 0.543
        theta = 0.6543

        with qml.tape.QuantumTape() as tape:
            qml.RX(phi, wires=[0])
            qml.RY(theta, wires=[0])
            O1 = qml.var(qml.PauliZ(wires=[0]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)
        dev._samples = dev.generate_samples()

        var = np.array([dev.var(O1.obs)])
        expected = 0.25 * (3 - np.cos(2 * theta) -
                           2 * np.cos(theta)**2 * np.cos(2 * phi))

        self.assertAlmostEqual(var, expected, delta=3 / np.sqrt(shots))

    def test_var_hermitian(self, shots, qvm, compiler):
        """Tests for variance calculation using an arbitrary Hermitian observable"""
        dev = plf.QVMDevice(device="2q-qvm", shots=100 * shots)

        phi = 0.543
        theta = 0.6543

        A = np.array([[4, -1 + 6j], [-1 - 6j, 2]])

        with qml.tape.QuantumTape() as tape:
            qml.RX(phi, wires=[0])
            qml.RY(theta, wires=[0])
            O1 = qml.var(qml.Hermitian(A, wires=[0]))

        # test correct variance for <A> of a rotated state
        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)
        dev._samples = dev.generate_samples()

        var = np.array([dev.var(O1.obs)])
        expected = 0.5 * (2 * np.sin(2 * theta) * np.cos(phi)**2 +
                          24 * np.sin(phi) * np.cos(phi) *
                          (np.sin(theta) - np.cos(theta)) +
                          35 * np.cos(2 * phi) + 39)

        self.assertAlmostEqual(var, expected, delta=0.3)

    @pytest.mark.parametrize(
        "op",
        [
            qml.QubitUnitary(np.array(U), wires=0),
            qml.BasisState(np.array([1, 1, 1]), wires=list(range(3))),
            qml.PauliX(wires=0),
            qml.PauliY(wires=0),
            qml.PauliZ(wires=0),
            qml.S(wires=0),
            qml.T(wires=0),
            qml.RX(0.432, wires=0),
            qml.RY(0.432, wires=0),
            qml.RZ(0.432, wires=0),
            qml.Hadamard(wires=0),
            qml.Rot(0.432, 2, 0.324, wires=0),
            qml.Toffoli(wires=[0, 1, 2]),
            qml.SWAP(wires=[0, 1]),
            qml.CSWAP(wires=[0, 1, 2]),
            qml.CZ(wires=[0, 1]),
            qml.CNOT(wires=[0, 1]),
            qml.PhaseShift(0.432, wires=0),
            qml.CSWAP(wires=[0, 1, 2]),
            plf.CPHASE(0.432, 2, wires=[0, 1]),
            plf.ISWAP(wires=[0, 1]),
            plf.PSWAP(0.432, wires=[0, 1]),
        ],
    )
    def test_apply(self, op, apply_unitary, shots, qvm, compiler):
        """Test the application of gates to a state"""
        dev = plf.QVMDevice(device="3q-qvm",
                            shots=shots,
                            parametric_compilation=False)

        obs = qml.expval(qml.PauliZ(0))

        if op.name == "QubitUnitary":
            state = apply_unitary(U, 3)
        elif op.name == "BasisState":
            state = np.array([0, 0, 0, 0, 0, 0, 0, 1])
        elif op.name == "CPHASE":
            state = apply_unitary(test_operation_map["CPHASE"](0.432, 2), 3)
        elif op.name == "ISWAP":
            state = apply_unitary(test_operation_map["ISWAP"], 3)
        elif op.name == "PSWAP":
            state = apply_unitary(test_operation_map["PSWAP"](0.432), 3)
        else:
            state = apply_unitary(op.matrix, 3)

        with qml.tape.QuantumTape() as tape:
            qml.apply(op)
            obs

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)

        dev._samples = dev.generate_samples()

        res = dev.expval(obs.obs)
        expected = np.vdot(state, np.kron(np.kron(Z, I), I) @ state)

        # verify the device is now in the expected state
        # Note we have increased the tolerance here, since we are only
        # performing 1024 shots.
        self.assertAllAlmostEqual(res, expected, delta=3 / np.sqrt(shots))

    def test_sample_values(self, qvm, tol):
        """Tests if the samples returned by sample have
        the correct values
        """
        dev = plf.QVMDevice(device="1q-qvm", shots=10)

        with qml.tape.QuantumTape() as tape:
            qml.RX(1.5708, wires=[0])
            O1 = qml.expval(qml.PauliZ(wires=[0]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)

        dev._samples = dev.generate_samples()

        s1 = dev.sample(O1.obs)

        # s1 should only contain 1 and -1
        self.assertAllAlmostEqual(s1**2, 1, delta=tol)
        self.assertAllAlmostEqual(s1, 1 - 2 * dev._samples[:, 0], delta=tol)

    def test_sample_values_hermitian(self, qvm, tol):
        """Tests if the samples of a Hermitian observable returned by sample have
        the correct values
        """
        theta = 0.543
        shots = 1_000_000
        A = np.array([[1, 2j], [-2j, 0]])

        dev = plf.QVMDevice(device="1q-qvm", shots=shots)

        with qml.tape.QuantumTape() as tape:
            qml.RX(theta, wires=[0])
            O1 = qml.sample(qml.Hermitian(A, wires=[0]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)

        dev._samples = dev.generate_samples()

        s1 = dev.sample(O1.obs)

        # s1 should only contain the eigenvalues of
        # the hermitian matrix
        eigvals = np.linalg.eigvalsh(A)
        assert np.allclose(sorted(list(set(s1))),
                           sorted(eigvals),
                           atol=tol,
                           rtol=0)

        # the analytic mean is 2*sin(theta)+0.5*cos(theta)+0.5
        assert np.allclose(np.mean(s1),
                           2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5,
                           atol=0.1,
                           rtol=0)

        # the analytic variance is 0.25*(sin(theta)-4*cos(theta))^2
        assert np.allclose(np.var(s1),
                           0.25 * (np.sin(theta) - 4 * np.cos(theta))**2,
                           atol=0.1,
                           rtol=0)

    def test_sample_values_hermitian_multi_qubit(self, qvm, tol):
        """Tests if the samples of a multi-qubit Hermitian observable returned by sample have
        the correct values
        """
        theta = 0.543
        shots = 100_000

        A = np.array([
            [1, 2j, 1 - 2j, 0.5j],
            [-2j, 0, 3 + 4j, 1],
            [1 + 2j, 3 - 4j, 0.75, 1.5 - 2j],
            [-0.5j, 1, 1.5 + 2j, -1],
        ])

        dev = plf.QVMDevice(device="2q-qvm", shots=shots)

        with qml.tape.QuantumTape() as tape:
            qml.RX(theta, wires=[0])
            qml.RY(2 * theta, wires=[1])
            qml.CNOT(wires=[0, 1])
            O1 = qml.sample(qml.Hermitian(A, wires=[0, 1]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)

        dev._samples = dev.generate_samples()

        s1 = dev.sample(O1.obs)

        # s1 should only contain the eigenvalues of
        # the hermitian matrix
        eigvals = np.linalg.eigvalsh(A)
        assert np.allclose(sorted(list(set(s1))),
                           sorted(eigvals),
                           atol=tol,
                           rtol=0)

        # make sure the mean matches the analytic mean
        expected = (88 * np.sin(theta) + 24 * np.sin(2 * theta) -
                    40 * np.sin(3 * theta) + 5 * np.cos(theta) -
                    6 * np.cos(2 * theta) + 27 * np.cos(3 * theta) + 6) / 32
        assert np.allclose(np.mean(s1), expected, atol=0.1, rtol=0)

    def test_wires_argument(self):
        """Test that the wires argument gets processed correctly."""

        dev_no_wires = plf.QVMDevice(device="2q-qvm", shots=5)
        assert dev_no_wires.wires == Wires(range(2))

        with pytest.raises(ValueError, match="Device has a fixed number of"):
            plf.QVMDevice(device="2q-qvm", shots=5, wires=1000)

        dev_iterable_wires = plf.QVMDevice(device="2q-qvm",
                                           shots=5,
                                           wires=range(2))
        assert dev_iterable_wires.wires == Wires(range(2))

        with pytest.raises(ValueError, match="Device has a fixed number of"):
            plf.QVMDevice(device="2q-qvm", shots=5, wires=range(1000))

    @pytest.mark.parametrize("shots", list(range(0, -10, -1)))
    def test_raise_error_if_shots_is_not_positive(self, shots):
        """Test that instantiating a QVMDevice if the number of shots is not a postivie
        integer raises an error"""
        with pytest.raises(
                ValueError,
                match="Number of shots must be a positive integer."):
            dev = plf.QVMDevice(device="2q-qvm", shots=shots)

    def test_raise_error_if_shots_is_none(self, shots):
        """Test that instantiating a QVMDevice to be used for analytic computations raises an error"""
        with pytest.raises(
                ValueError,
                match="QVM device cannot be used for analytic computations."):
            dev = plf.QVMDevice(device="2q-qvm", shots=None)

    @pytest.mark.parametrize(
        "device", ["2q-qvm", np.random.choice(TEST_QPU_LATTICES)])
    def test_timeout_set_correctly(self, shots, device):
        """Test that the timeout attrbiute for the QuantumComputer stored by the QVMDevice
        is set correctly when passing a value as keyword argument"""
        dev = plf.QVMDevice(device=device, shots=shots, timeout=100)
        assert dev.qc.compiler.client.timeout == 100

    @pytest.mark.parametrize(
        "device", ["2q-qvm", np.random.choice(TEST_QPU_LATTICES)])
    def test_timeout_default(self, shots, device):
        """Test that the timeout attrbiute for the QuantumComputer stored by the QVMDevice
        is set correctly when passing a value as keyword argument"""
        dev = plf.QVMDevice(device=device, shots=shots)
        qc = pyquil.get_qc(device, as_qvm=True)

        # Check that the timeouts are equal (it has not been changed as a side effect of
        # instantiation
        assert dev.qc.compiler.client.timeout == qc.compiler.client.timeout

    def test_compiled_program_stored(self, qvm, monkeypatch):
        """Test that QVM device stores the latest compiled program."""
        dev = qml.device("forest.qvm", device="2q-qvm")

        dev.compiled_program is None

        theta = 0.432
        phi = 0.123

        with qml.tape.QuantumTape() as tape:
            qml.RX(theta, wires=[0])
            qml.RX(phi, wires=[1])
            qml.CNOT(wires=[0, 1])
            O1 = qml.expval(qml.Identity(wires=[0]))
            O2 = qml.expval(qml.Identity(wires=[1]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)

        dev.generate_samples()

        dev.compiled_program is not None

    def test_stored_compiled_program_correct(self, qvm, monkeypatch):
        """Test that QVM device stores the latest compiled program."""
        dev = qml.device("forest.qvm", device="2q-qvm")

        dev.compiled_program is None

        theta = 0.432

        with qml.tape.QuantumTape() as tape:
            qml.RZ(theta, wires=[0])
            qml.CZ(wires=[0, 1])
            O1 = qml.expval(qml.PauliZ(wires=[0]))

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)

        dev.generate_samples()

        dev.compiled_program.program == compiled_program
class TestWavefunctionBasic(BaseTest):
    """Unit tests for the NumPy wavefunction simulator."""
    def test_var(self, tol):
        """Tests for variance calculation"""
        dev = plf.NumpyWavefunctionDevice(wires=2)

        phi = 0.543
        theta = 0.6543

        with qml.tape.QuantumTape() as tape:
            qml.RX(phi, wires=[0])
            qml.RY(theta, wires=[0])
            O = qml.var(qml.PauliZ(wires=[0]))

        # test correct variance for <Z> of a rotated state
        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)

        var = dev.var(O)
        expected = 0.25 * (3 - np.cos(2 * theta) -
                           2 * np.cos(theta)**2 * np.cos(2 * phi))

        self.assertAlmostEqual(var, expected, delta=tol)

    def test_var_hermitian(self, tol):
        """Tests for variance calculation using an arbitrary Hermitian observable"""
        dev = plf.NumpyWavefunctionDevice(wires=2)

        phi = 0.543
        theta = 0.6543

        H = np.array([[4, -1 + 6j], [-1 - 6j, 2]])

        with qml.tape.QuantumTape() as tape:
            qml.RX(phi, wires=[0])
            qml.RY(theta, wires=[0])
            O = qml.var(qml.Hermitian(H, wires=[0]))

        # test correct variance for <Z> of a rotated state
        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)

        var = dev.var(O)

        # test correct variance for <H> of a rotated state
        expected = 0.5 * (2 * np.sin(2 * theta) * np.cos(phi)**2 +
                          24 * np.sin(phi) * np.cos(phi) *
                          (np.sin(theta) - np.cos(theta)) +
                          35 * np.cos(2 * phi) + 39)

        self.assertAlmostEqual(var, expected, delta=tol)

    @pytest.mark.parametrize(
        "op",
        [
            qml.QubitUnitary(np.array(U), wires=0),
            qml.BasisState(np.array([1, 1, 1]), wires=list(range(3))),
            qml.PauliX(wires=0),
            qml.PauliY(wires=0),
            qml.PauliZ(wires=0),
            qml.S(wires=0),
            qml.T(wires=0),
            qml.RX(0.432, wires=0),
            qml.RY(0.432, wires=0),
            qml.RZ(0.432, wires=0),
            qml.Hadamard(wires=0),
            qml.Rot(0.432, 2, 0.324, wires=0),
            qml.Toffoli(wires=[0, 1, 2]),
            qml.SWAP(wires=[0, 1]),
            qml.CSWAP(wires=[0, 1, 2]),
            qml.CZ(wires=[0, 1]),
            qml.CNOT(wires=[0, 1]),
            qml.PhaseShift(0.432, wires=0),
            qml.CSWAP(wires=[0, 1, 2]),
            plf.CPHASE(0.432, 2, wires=[0, 1]),
            plf.ISWAP(wires=[0, 1]),
            plf.PSWAP(0.432, wires=[0, 1]),
        ],
    )
    def test_apply(self, op, apply_unitary, tol):
        """Test the application of gates to a state"""
        dev = plf.NumpyWavefunctionDevice(wires=3)

        obs = qml.expval(qml.PauliZ(0))

        if op.name == "QubitUnitary":
            state = apply_unitary(U, 3)
        elif op.name == "BasisState":
            state = np.array([0, 0, 0, 0, 0, 0, 0, 1])
        elif op.name == "CPHASE":
            state = apply_unitary(test_operation_map["CPHASE"](0.432, 2), 3)
        elif op.name == "ISWAP":
            state = apply_unitary(test_operation_map["ISWAP"], 3)
        elif op.name == "PSWAP":
            state = apply_unitary(test_operation_map["PSWAP"](0.432), 3)
        else:
            state = apply_unitary(op.matrix, 3)

        with qml.tape.QuantumTape() as tape:
            qml.apply(op)
            obs

        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)

        # verify the device is now in the expected state
        self.assertAllAlmostEqual(dev._state, state, delta=tol)

    def test_sample_values(self, tol):
        """Tests if the samples returned by sample have
        the correct values
        """
        dev = plf.NumpyWavefunctionDevice(wires=1, shots=10)

        theta = 1.5708

        O = qml.sample(qml.PauliZ(0))

        with qml.tape.QuantumTape() as tape:
            qml.RX(theta, wires=[0])
            qml.sample(qml.PauliZ(0))

        dev.apply(tape._ops, rotations=tape.diagonalizing_gates)
        dev._samples = dev.generate_samples()
        s1 = dev.sample(O.obs)

        # s1 should only contain 1 and -1
        self.assertAllAlmostEqual(s1**2, 1, delta=tol)

    def test_sample_values_hermitian(self, tol):
        """Tests if the samples of a Hermitian observable returned by sample have
        the correct values
        """
        dev = plf.NumpyWavefunctionDevice(wires=1, shots=1_000_000)
        theta = 0.543

        A = np.array([[1, 2j], [-2j, 0]])

        circuit_operations = [qml.RX(theta, wires=[0])]

        O = qml.sample(qml.Hermitian(A, wires=[0]))

        with qml.tape.QuantumTape() as tape:
            qml.RX(theta, wires=[0])
            O = qml.sample(qml.Hermitian(A, wires=[0]))

        # test correct variance for <Z> of a rotated state
        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)

        dev._samples = dev.generate_samples()

        s1 = dev.sample(O.obs)

        # s1 should only contain the eigenvalues of
        # the hermitian matrix
        eigvals = np.linalg.eigvalsh(A)
        assert np.allclose(sorted(list(set(s1))),
                           sorted(eigvals),
                           atol=tol,
                           rtol=0)

        # the analytic mean is 2*sin(theta)+0.5*cos(theta)+0.5
        assert np.allclose(np.mean(s1),
                           2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5,
                           atol=0.1,
                           rtol=0)

        # the analytic variance is 0.25*(sin(theta)-4*cos(theta))^2
        assert np.allclose(np.var(s1),
                           0.25 * (np.sin(theta) - 4 * np.cos(theta))**2,
                           atol=0.1,
                           rtol=0)

    def test_sample_values_hermitian_multi_qubit(self, tol):
        """Tests if the samples of a multi-qubit Hermitian observable returned by sample have
        the correct values
        """
        shots = 1_000_000
        dev = plf.NumpyWavefunctionDevice(wires=2, shots=shots)
        theta = 0.543

        A = np.array([
            [1, 2j, 1 - 2j, 0.5j],
            [-2j, 0, 3 + 4j, 1],
            [1 + 2j, 3 - 4j, 0.75, 1.5 - 2j],
            [-0.5j, 1, 1.5 + 2j, -1],
        ])

        with qml.tape.QuantumTape() as tape:
            qml.RX(theta, wires=[0]),
            qml.RY(2 * theta, wires=[1]),
            qml.CNOT(wires=[0, 1]),
            O = qml.sample(qml.Hermitian(A, wires=[0, 1]))

        # test correct variance for <Z> of a rotated state
        dev.apply(tape.operations, rotations=tape.diagonalizing_gates)

        dev._samples = dev.generate_samples()

        s1 = dev.sample(O.obs)

        # s1 should only contain the eigenvalues of
        # the hermitian matrix
        eigvals = np.linalg.eigvalsh(A)
        assert np.allclose(sorted(list(set(s1))),
                           sorted(eigvals),
                           atol=tol,
                           rtol=0)

        # make sure the mean matches the analytic mean
        expected = (88 * np.sin(theta) + 24 * np.sin(2 * theta) -
                    40 * np.sin(3 * theta) + 5 * np.cos(theta) -
                    6 * np.cos(2 * theta) + 27 * np.cos(3 * theta) + 6) / 32
        assert np.allclose(np.mean(s1), expected, atol=0.1, rtol=0)