def test_beamsplitter_gradient(self, mocker, tol):
        """Test the gradient of the beamsplitter gate"""
        dev = qml.device("default.gaussian", wires=2, hbar=hbar)

        alpha = 0.5643
        theta = 0.23354

        with qml.tape.JacobianTape() as tape:
            qml.Displacement(alpha, 0.0, wires=[0])
            qml.Beamsplitter(theta, 0.0, wires=[0, 1])
            qml.expval(qml.X(0))

        tape.trainable_params = {2}

        spy2 = mocker.spy(qml.gradients.parameter_shift_cv,
                          "second_order_param_shift")

        tapes, fn = param_shift_cv(tape, dev)
        grad_A = fn(dev.batch_execute(tapes))
        spy2.assert_not_called()

        tapes, fn = param_shift_cv(tape, dev, force_order2=True)
        grad_A2 = fn(dev.batch_execute(tapes))
        spy2.assert_called()

        expected = -hbar * alpha * np.sin(theta)
        assert np.allclose(grad_A, expected, atol=tol, rtol=0)
        assert np.allclose(grad_A2, expected, atol=tol, rtol=0)
    def test_displacement_gradient(self, mocker, tol):
        """Test the gradient of the displacement gate"""
        dev = qml.device("default.gaussian", wires=2, hbar=hbar)

        r = 0.5643
        phi = 0.23354

        with qml.tape.JacobianTape() as tape:
            qml.Displacement(r, phi, wires=[0])
            qml.expval(qml.X(0))

        tape.trainable_params = {0, 1}

        spy2 = mocker.spy(qml.gradients.parameter_shift_cv,
                          "second_order_param_shift")

        tapes, fn = param_shift_cv(tape, dev)
        grad_A = fn(dev.batch_execute(tapes))
        spy2.assert_not_called()

        tapes, fn = param_shift_cv(tape, dev, force_order2=True)
        grad_A2 = fn(dev.batch_execute(tapes))
        spy2.assert_called()

        expected = [hbar * np.cos(phi), -hbar * r * np.sin(phi)]
        assert np.allclose(grad_A, expected, atol=tol, rtol=0)
        assert np.allclose(grad_A2, expected, atol=tol, rtol=0)
    def test_error_unsupported_grad_recipe(self, monkeypatch):
        """Test exception raised if attempting to use the second order rule for
        computing the gradient analytically of an expectation value that
        contains an operation with more than two terms in the gradient recipe"""
        class DummyOp(qml.operation.CVOperation):
            num_wires = 1
            num_params = 1
            par_domain = "R"
            grad_method = "A"
            grad_recipe = ([[1, 1, 1], [1, 1, 1], [1, 1, 1]], )

        dev = qml.device("default.gaussian", wires=1)

        dev.operations.add(DummyOp)

        with qml.tape.JacobianTape() as tape:
            DummyOp(1, wires=[0])
            qml.expval(qml.X(0))

        with monkeypatch.context() as m:
            m.setattr(tape, "_grad_method_validation", lambda *args: ("A", ))
            tape._par_info[0]["grad_method"] = "A"
            tape.trainable_params = {0}

            with pytest.raises(
                    NotImplementedError,
                    match=
                    r"analytic gradient for order-2 operators is unsupported"):
                param_shift_cv(tape, dev, force_order2=True)
    def test_gradients_gaussian_circuit(self, op, obs, tol):
        """Tests that the gradients of circuits of gaussian gates match between the
        finite difference and analytic methods."""
        tol = 1e-2

        with qml.tape.JacobianTape() as tape:
            qml.Displacement(0.5, 0, wires=0)
            qml.apply(op)
            qml.Beamsplitter(1.3, -2.3, wires=[0, 1])
            qml.Displacement(-0.5, 0.1, wires=0)
            qml.Squeezing(0.5, -1.5, wires=0)
            qml.Rotation(-1.1, wires=0)
            qml.expval(obs(wires=0))

        dev = qml.device("default.gaussian", wires=2)
        res = tape.execute(dev)

        tape.trainable_params = set(range(2, 2 + op.num_params))

        tapes, fn = qml.gradients.finite_diff(tape)
        grad_F = fn(dev.batch_execute(tapes))

        tapes, fn = param_shift_cv(tape, dev, force_order2=True)
        grad_A2 = fn(dev.batch_execute(tapes))

        # check that every parameter is analytic
        for i in range(op.num_params):
            assert tape._par_info[2 + i]["grad_method"][0] == "A"

        assert np.allclose(grad_A2, grad_F, atol=tol, rtol=0)

        if obs.ev_order == 1:
            tapes, fn = param_shift_cv(tape, dev)
            grad_A = fn(dev.batch_execute(tapes))
            assert np.allclose(grad_A, grad_F, atol=tol, rtol=0)
    def test_error_analytic_second_order(self):
        """Test exception raised if attempting to use a second
        order observable to compute the variance derivative analytically"""
        dev = qml.device("default.gaussian", wires=1)

        with qml.tape.JacobianTape() as tape:
            qml.Displacement(1.0, 0, wires=0)
            qml.var(qml.NumberOperator(0))

        tape.trainable_params = {0}

        with pytest.raises(ValueError, match=r"cannot be used with the argument\(s\) \{0\}"):
            param_shift_cv(tape, dev, fallback_fn=None)
    def test_multiple_squeezing_gradient(self, mocker, tol):
        """Test that the gradient of a circuit with two squeeze
        gates is correct."""
        dev = qml.device("default.gaussian", wires=2, hbar=hbar)

        r0, phi0, r1, phi1 = [0.4, -0.3, -0.7, 0.2]

        with qml.tape.JacobianTape() as tape:
            qml.Squeezing(r0, phi0, wires=[0])
            qml.Squeezing(r1, phi1, wires=[0])
            qml.expval(qml.NumberOperator(0))  # second order

        spy2 = mocker.spy(qml.gradients.parameter_shift_cv,
                          "second_order_param_shift")
        tapes, fn = param_shift_cv(tape, dev, force_order2=True)
        grad_A2 = fn(dev.batch_execute(tapes))
        spy2.assert_called()

        # check against the known analytic formula
        expected = np.zeros([4])
        expected[0] = np.cosh(2 * r1) * np.sinh(
            2 * r0) + np.cos(phi0 - phi1) * np.cosh(2 * r0) * np.sinh(2 * r1)
        expected[1] = -0.5 * np.sin(phi0 - phi1) * np.sinh(2 * r0) * np.sinh(
            2 * r1)
        expected[2] = np.cos(phi0 - phi1) * np.cosh(2 * r1) * np.sinh(
            2 * r0) + np.cosh(2 * r0) * np.sinh(2 * r1)
        expected[3] = 0.5 * np.sin(phi0 - phi1) * np.sinh(2 * r0) * np.sinh(
            2 * r1)

        assert np.allclose(grad_A2, expected, atol=tol, rtol=0)
    def test_first_order_observable(self, tol):
        """Test variance of a first order CV observable"""
        dev = qml.device("default.gaussian", wires=1)

        r = 0.543
        phi = -0.654

        with qml.tape.JacobianTape() as tape:
            qml.Squeezing(r, 0, wires=0)
            qml.Rotation(phi, wires=0)
            qml.var(qml.X(0))

        tape.trainable_params = {0, 2}

        res = tape.execute(dev)
        expected = np.exp(2 * r) * np.sin(phi)**2 + np.exp(
            -2 * r) * np.cos(phi)**2
        assert np.allclose(res, expected, atol=tol, rtol=0)

        # circuit jacobians
        tapes, fn = qml.gradients.finite_diff(tape)
        grad_F = fn(dev.batch_execute(tapes))

        tapes, fn = param_shift_cv(tape, dev)
        grad_A = fn(dev.batch_execute(tapes))

        expected = np.array([[
            2 * np.exp(2 * r) * np.sin(phi)**2 -
            2 * np.exp(-2 * r) * np.cos(phi)**2,
            2 * np.sinh(2 * r) * np.sin(2 * phi),
        ]])
        assert np.allclose(grad_A, expected, atol=tol, rtol=0)
        assert np.allclose(grad_F, expected, atol=tol, rtol=0)
    def test_expval_and_variance(self, tol):
        """Test that the gradient works for a combination of CV expectation
        values and variances"""
        dev = qml.device("default.gaussian", wires=3)

        a, b = [0.54, -0.423]

        with qml.tape.JacobianTape() as tape:
            qml.Displacement(0.5, 0, wires=0)
            qml.Squeezing(a, 0, wires=0)
            qml.Squeezing(b, 0, wires=1)
            qml.Beamsplitter(0.6, -0.3, wires=[0, 1])
            qml.Squeezing(-0.3, 0, wires=2)
            qml.Beamsplitter(1.4, 0.5, wires=[1, 2])
            qml.var(qml.X(0))
            qml.expval(qml.X(1))
            qml.var(qml.X(2))

        tape.trainable_params = {2, 4}

        # jacobians must match
        tapes, fn = qml.gradients.finite_diff(tape)
        grad_F = fn(dev.batch_execute(tapes))

        tapes, fn = param_shift_cv(tape, dev)
        grad_A = fn(dev.batch_execute(tapes))

        assert np.allclose(grad_A, grad_F, atol=tol, rtol=0)
    def test_squeezed_number_state_gradient(self, mocker, tol):
        """Test the numerical gradient of the squeeze gate with
        with number state expectation is correct"""
        dev = qml.device("default.gaussian", wires=2, hbar=hbar)

        r = 0.23354

        with qml.tape.JacobianTape() as tape:
            qml.Squeezing(r, 0.0, wires=[0])
            # the fock state projector is a 'non-Gaussian' observable
            qml.expval(qml.FockStateProjector(np.array([2, 0]), wires=[0, 1]))

        tape.trainable_params = {0}

        spy = mocker.spy(qml.gradients.parameter_shift_cv,
                         "second_order_param_shift")

        tapes, fn = param_shift_cv(tape, dev)
        grad = fn(dev.batch_execute(tapes))
        assert tape._par_info[0]["grad_method"] == "F"

        spy.assert_not_called()

        # (d/dr) |<2|S(r)>|^2 = 0.5 tanh(r)^3 (2 csch(r)^2 - 1) sech(r)
        expected = 0.5 * np.tanh(r)**3 * (2 / (np.sinh(r)**2) - 1) / np.cosh(r)
        assert np.allclose(grad, expected, atol=tol, rtol=0)
        def cost_fn(x):
            with AutogradInterface.apply(qml.tape.CVParamShiftTape()) as tape:
                qml.Squeezing(x[0], 0, wires=0)
                qml.Rotation(x[1], wires=0)
                qml.var(qml.X(wires=[0]))

            tapes, fn = param_shift_cv(tape, dev)
            return fn([t.execute(dev) for t in tapes])[0, 1]
    def test_squeezed_gradient(self, mocker, tol):
        """Test the gradient of the squeezed gate. We also
        ensure that the gradient is correct even when an operation
        with no Heisenberg representation is a descendent."""
        dev = qml.device("default.gaussian", wires=2, hbar=hbar)

        class Rotation(qml.operation.CVOperation):
            """Dummy operation that does not support
            heisenberg representation"""

            num_wires = 1
            num_params = 1
            par_domain = "R"
            grad_method = "A"

        alpha = 0.5643
        r = 0.23354

        with qml.tape.JacobianTape() as tape:
            qml.Displacement(alpha, 0.0, wires=[0])
            qml.Squeezing(r, 0.0, wires=[0])

            # The following two gates have no effect
            # on the circuit gradient and expectation value
            qml.Beamsplitter(0.0, 0.0, wires=[0, 1])
            Rotation(0.543, wires=[1])

            qml.expval(qml.X(0))

        tape.trainable_params = {2}

        spy2 = mocker.spy(qml.gradients.parameter_shift_cv,
                          "second_order_param_shift")

        tapes, fn = param_shift_cv(tape, dev)
        grad_A = fn(dev.batch_execute(tapes))
        spy2.assert_not_called()

        tapes, fn = param_shift_cv(tape, dev, force_order2=True)
        grad_A2 = fn(dev.batch_execute(tapes))
        spy2.assert_called()

        expected = -np.exp(-r) * hbar * alpha
        assert np.allclose(grad_A, expected, atol=tol, rtol=0)
        assert np.allclose(grad_A2, expected, atol=tol, rtol=0)
        def cost_fn(x):
            with qml.tape.JacobianTape() as tape:
                qml.Squeezing(x[0], 0, wires=0)
                qml.Rotation(x[1], wires=0)
                qml.var(qml.X(wires=[0]))

            tapes, fn = param_shift_cv(tape, dev)
            jac = fn(qml.execute(tapes, dev, param_shift_cv, gradient_kwargs={"dev": dev}))
            return jac[0, 2]
    def test_displaced_thermal_mean_photon_variance(self, tol):
        """Test gradient of the photon variance of a displaced thermal state"""
        dev = qml.device("default.gaussian", wires=1)

        n = 0.12
        a = 0.105

        with qml.tape.JacobianTape() as tape:
            qml.ThermalState(n, wires=0)
            qml.Displacement(a, 0, wires=0)
            qml.var(qml.TensorN(wires=[0]))

        tape.trainable_params = {0, 1}
        tapes, fn = param_shift_cv(tape, dev)
        grad = fn(dev.batch_execute(tapes))
        expected = np.array([2 * a**2 + 2 * n + 1, 2 * a * (2 * n + 1)])
        assert np.allclose(grad, expected, atol=tol, rtol=0)
    def test_multiple_second_order_observables(self, mocker, tol):
        """Test that the gradient of a circuit with multiple
        second order observables is correct"""

        dev = qml.device("default.gaussian", wires=2, hbar=hbar)
        r = [0.4, -0.7, 0.1, 0.2]
        p = [0.1, 0.2, 0.3, 0.4]

        with qml.tape.JacobianTape() as tape:
            qml.Squeezing(r[0], p[0], wires=[0])
            qml.Squeezing(r[1], p[1], wires=[0])
            qml.Squeezing(r[2], p[2], wires=[1])
            qml.Squeezing(r[3], p[3], wires=[1])
            qml.expval(qml.NumberOperator(0))  # second order
            qml.expval(qml.NumberOperator(1))  # second order

        spy2 = mocker.spy(qml.gradients.parameter_shift_cv,
                          "second_order_param_shift")
        tapes, fn = param_shift_cv(tape, dev)
        grad_A2 = fn(dev.batch_execute(tapes))
        spy2.assert_called()

        # check against the known analytic formula

        def expected_grad(r, p):
            return np.array([
                np.cosh(2 * r[1]) * np.sinh(2 * r[0]) +
                np.cos(p[0] - p[1]) * np.cosh(2 * r[0]) * np.sinh(2 * r[1]),
                -0.5 * np.sin(p[0] - p[1]) * np.sinh(2 * r[0]) *
                np.sinh(2 * r[1]),
                np.cos(p[0] - p[1]) * np.cosh(2 * r[1]) * np.sinh(2 * r[0]) +
                np.cosh(2 * r[0]) * np.sinh(2 * r[1]),
                0.5 * np.sin(p[0] - p[1]) * np.sinh(2 * r[0]) *
                np.sinh(2 * r[1]),
            ])

        expected = np.zeros([2, 8])
        expected[0, :4] = expected_grad(r[:2], p[:2])
        expected[1, 4:] = expected_grad(r[2:], p[2:])

        assert np.allclose(grad_A2, expected, atol=tol, rtol=0)
    def test_squeezed_mean_photon_variance(self, tol):
        """Test gradient of the photon variance of a displaced thermal state"""
        dev = qml.device("default.gaussian", wires=1)

        r = 0.12
        phi = 0.105

        with qml.tape.JacobianTape() as tape:
            qml.Squeezing(r, 0, wires=0)
            qml.Rotation(phi, wires=0)
            qml.var(qml.X(wires=[0]))

        tape.trainable_params = {0, 2}
        tapes, fn = param_shift_cv(tape, dev)
        grad = fn(dev.batch_execute(tapes))
        expected = np.array([
            2 * np.exp(2 * r) * np.sin(phi)**2 -
            2 * np.exp(-2 * r) * np.cos(phi)**2,
            2 * np.sinh(2 * r) * np.sin(2 * phi),
        ])
        assert np.allclose(grad, expected, atol=tol, rtol=0)
Beispiel #16
0
    def test_tf(self, tol):
        """Tests that the output of the parameter-shift CV transform
        can be executed using TF"""
        tf = pytest.importorskip("tensorflow")

        dev = qml.device("default.gaussian", wires=1)
        params = tf.Variable([0.543, -0.654], dtype=tf.float64)

        with tf.GradientTape() as t:
            with qml.tape.JacobianTape() as tape:
                qml.Squeezing(params[0], 0, wires=0)
                qml.Rotation(params[1], wires=0)
                qml.var(qml.X(wires=[0]))

            tape.trainable_params = {0, 2}
            tapes, fn = param_shift_cv(tape, dev)
            jac = fn(
                qml.execute(tapes,
                            dev,
                            param_shift_cv,
                            gradient_kwargs={"dev": dev},
                            interface="tf"))
            res = jac[0, 1]

        r, phi = 1.0 * params

        expected = np.array([
            2 * np.exp(2 * r) * np.sin(phi)**2 -
            2 * np.exp(-2 * r) * np.cos(phi)**2,
            2 * np.sinh(2 * r) * np.sin(2 * phi),
        ])
        assert np.allclose(jac, expected, atol=tol, rtol=0)

        grad = t.jacobian(res, params)
        expected = np.array([
            4 * np.cosh(2 * r) * np.sin(2 * phi),
            4 * np.cos(2 * phi) * np.sinh(2 * r)
        ])
        assert np.allclose(grad, expected, atol=tol, rtol=0)
    def test_interferometer(self, t, tol):
        """An integration test for CV gates that support analytic differentiation
        if succeeding the gate to be differentiated, but cannot be differentiated
        themselves (for example, they may be Gaussian but accept no parameters,
        or may accept a numerical array parameter.).

        This ensures that, assuming their _heisenberg_rep is defined, the quantum
        gradient analytic method can still be used, and returns the correct result.

        Currently, the only such operation is qml.Interferometer. In the future,
        we may consider adding a qml.GaussianTransfom operator.
        """

        if t == 1:
            pytest.xfail(
                "There is a bug in the second order CV parameter-shift rule; "
                "phase arguments return the incorrect derivative.")

            # Note: this bug currently affects PL core as well:
            #
            # dev = qml.device("default.gaussian", wires=2)
            #
            # U = np.array([[ 0.51310276+0.81702166j,  0.13649626+0.22487759j],
            #         [ 0.26300233+0.00556194j, -0.96414101-0.03508489j]])
            #
            # @qml.qnode(dev)
            # def circuit(r, phi):
            #     qml.Displacement(r, phi, wires=0)
            #     qml.Interferometer(U, wires=[0, 1])
            #     return qml.expval(qml.X(0))
            #
            # r = 0.543
            # phi = 0.
            #
            # >>> print(circuit.jacobian([r, phi], options={"force_order2":False}))
            # [[ 1.02620552 0.14823494]]
            # >>> print(circuit.jacobian([r, phi], options={"force_order2":True}))
            # [[ 1.02620552 -0.88728552]]

        U = np.array([
            [0.51310276 + 0.81702166j, 0.13649626 + 0.22487759j],
            [0.26300233 + 0.00556194j, -0.96414101 - 0.03508489j],
        ])

        with qml.tape.JacobianTape() as tape:
            qml.Displacement(0.543, 0, wires=0)
            qml.Interferometer(U, wires=[0, 1])
            qml.expval(qml.X(0))

        tape.trainable_params = {t}

        dev = qml.device("default.gaussian", wires=2)

        tapes, fn = qml.gradients.finite_diff(tape)
        grad_F = fn(dev.batch_execute(tapes))

        tapes, fn = param_shift_cv(tape, dev)
        grad_A = fn(dev.batch_execute(tapes))

        tapes, fn = param_shift_cv(tape, dev, force_order2=True)
        grad_A2 = fn(dev.batch_execute(tapes))

        assert tape._par_info[0]["grad_method"] == "A"
        assert tape._par_info[1]["grad_method"] == "A"

        # the different methods agree
        assert np.allclose(grad_A, grad_F, atol=tol, rtol=0)
        assert np.allclose(grad_A2, grad_F, atol=tol, rtol=0)