Пример #1
0
    def test_cv_gradients_gaussian_circuit(self, G, O, tol):
        """Tests that the gradients of circuits of gaussian gates match between the finite difference and analytic methods."""
        gaussian_dev = qml.device("default.gaussian", wires=2)

        tol = 1e-5
        par = [0.4]

        def circuit(x):
            args = [0.3] * G.num_params
            args[0] = x
            qml.Displacement(0.5, 0, wires=0)
            G(*args, wires=range(G.num_wires))
            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)
            return qml.expval(O(wires=0))

        q = CVQNode(circuit, gaussian_dev)
        val = q.evaluate(par, {})

        grad_F = q.jacobian(par, method="F")
        grad_A2 = q.jacobian(par, method="A", options={'force_order2': True})
        if O.ev_order == 1:
            grad_A = q.jacobian(par, method="A")
            # the different methods agree
            assert grad_A == pytest.approx(grad_F, abs=tol)

        # analytic method works for every parameter
        assert q.par_to_grad_method == {0: "A"}
        # the different methods agree
        assert grad_A2 == pytest.approx(grad_F, abs=tol)
Пример #2
0
    def test_cv_gradients_parameters_inside_array(self, tol):
        """Tests that free parameters inside an array passed to an Operation yield correct gradients."""
        gaussian_dev = qml.device("default.gaussian", wires=2)
        par = [0.4, 1.3]

        def qf(x, y):
            qml.Displacement(0.5, 0, wires=[0])
            qml.Squeezing(x, 0, wires=[0])
            M = np.zeros((5, 5), dtype=object)
            M[1, 1] = y
            M[1, 2] = 1.0
            M[2, 1] = 1.0
            return qml.expval(qml.PolyXP(M, [0, 1]))

        q = CVQNode(qf, gaussian_dev)

        grad_best = q.jacobian(par)
        grad_best2 = q.jacobian(par, options={"force_order2": True})
        grad_F = q.jacobian(par, method="F")

        # par[0] can use the "A" method, par[1] cannot
        assert q.par_to_grad_method == {0: "A", 1: "F"}
        # the different methods agree
        assert grad_best == pytest.approx(grad_F, abs=tol)
        assert grad_best2 == pytest.approx(grad_F, abs=tol)
Пример #3
0
    def test_cv_gradients_multiple_gate_parameters(self, tol):
        """Tests that gates with multiple free parameters yield correct gradients."""

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

        def qf(r0, phi0, r1, phi1):
            qml.Squeezing(r0, phi0, wires=[0])
            qml.Squeezing(r1, phi1, wires=[0])
            return qml.expval(qml.NumberOperator(0))

        q = CVQNode(qf, gaussian_dev)
        par = [0.4, -0.3, -0.7, 0.2]

        grad_F = q.jacobian(par, method="F")
        grad_A = q.jacobian(par, method="A")
        grad_A2 = q.jacobian(par, method="A", options={'force_order2': True})
        # analytic method works for every parameter
        assert q.par_to_grad_method == {i: "A" for i in range(4)}
        # the different methods agree
        assert grad_A == pytest.approx(grad_F, abs=tol)
        assert grad_A2 == pytest.approx(grad_F, abs=tol)

        # check against the known analytic formula
        r0, phi0, r1, phi1 = par
        dn = np.zeros([4])
        dn[0] = np.cosh(2 * r1) * np.sinh(
            2 * r0) + np.cos(phi0 - phi1) * np.cosh(2 * r0) * np.sinh(2 * r1)
        dn[1] = -0.5 * np.sin(phi0 - phi1) * np.sinh(2 * r0) * np.sinh(2 * r1)
        dn[2] = np.cos(phi0 - phi1) * np.cosh(2 * r1) * np.sinh(
            2 * r0) + np.cosh(2 * r0) * np.sinh(2 * r1)
        dn[3] = 0.5 * np.sin(phi0 - phi1) * np.sinh(2 * r0) * np.sinh(2 * r1)
        assert dn[np.newaxis, :] == pytest.approx(grad_F, abs=tol)
Пример #4
0
def test_transform_observable_incorrect_heisenberg_size():
    """The number of dimensions of a CV observable Heisenberg representation does
    not match the ev_order attribute."""
    class P(CVObservable):
        """Dummy CV observable with incorrect ev_order"""
        num_wires = 1
        num_params = 0
        par_domain = None
        ev_order = 2

        @staticmethod
        def _heisenberg_rep(p):
            return np.array([0, 1, 0])

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

    def circuit(x):
        qml.Displacement(x, 0.1, wires=0)
        return qml.expval(P(0))

    node = CVQNode(circuit, dev)

    with pytest.raises(QuantumFunctionError,
                       match="Mismatch between the polynomial order"):
        node.jacobian([0.5])
Пример #5
0
    def test_error_analytic_second_order_cv(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)

        def circuit(a):
            qml.Displacement(a, 0, wires=0)
            return qml.var(qml.NumberOperator(0))

        circuit = CVQNode(circuit, dev)

        with pytest.raises(ValueError,
                           match=r"cannot be used with the parameters \{0\}"):
            circuit.jacobian([1.0], method="A")
Пример #6
0
    def test_non_gaussian_obs_predecessor(self, gaussian_device, tol):
        """Parshift differentiation method is allowed and matches finite diff
        if a non-Gaussian gate precedes an observable but is not preceded by the parametrized gate."""
        def circuit(x):
            qml.Squeezing(x, 0, wires=[0])
            qml.Kerr(0.54, wires=[1])  # nongaussian
            qml.Beamsplitter(1.1, 0, wires=[0, 1])
            return qml.expval(qml.NumberOperator(0))

        node = CVQNode(circuit, gaussian_device)
        par = [0.321]

        grad_A = node.jacobian(par, wrt=[0], method="A")
        grad_F = node.jacobian(par, method="F")
        assert grad_A == pytest.approx(grad_F, abs=tol)
        assert node.par_to_grad_method == {0: "A"}
Пример #7
0
    def test_second_order_obs_not_following_gate(self, tol):
        """Parshift differentiation method matches finite diff and analytical result
        when we have order-2 observables that do not follow the parametrized gate.
        """
        num_wires = 2
        dev = qml.device("default.gaussian", wires=2)

        def circuit(params):
            for i in range(num_wires):
                qml.Squeezing(params[i], 0, wires=i)
            return [
                qml.expval(qml.NumberOperator(wires=i))
                for i in range(num_wires)
            ]

        node = CVQNode(circuit, dev)
        par = [0.321, -0.184]

        res = node(par)
        res_true = np.sinh(np.abs(par))**2  # analytical result
        assert res == pytest.approx(res_true, abs=tol)

        grad_A = node.jacobian([par], method="A")
        grad_F = node.jacobian([par], method="F")
        grad_true = np.diag(np.sinh(2 * np.abs(par)) *
                            np.sign(par))  # analytical gradient
        assert grad_A == pytest.approx(grad_F, abs=tol)
        assert grad_A == pytest.approx(grad_true, abs=tol)
Пример #8
0
    def test_first_order_cv(self, tol):
        """Test variance of a first order CV expectation value"""
        dev = qml.device("default.gaussian", wires=1)

        def circuit(r, phi):
            qml.Squeezing(r, 0, wires=0)
            qml.Rotation(phi, wires=0)
            return qml.var(qml.X(0))

        circuit = CVQNode(circuit, dev)

        r = 0.543
        phi = -0.654
        var = circuit(r, phi)
        expected = np.exp(2 * r) * np.sin(phi)**2 + np.exp(
            -2 * r) * np.cos(phi)**2
        assert var == pytest.approx(expected, abs=tol)

        # circuit jacobians
        gradA = circuit.jacobian([r, phi], method="A")
        gradF = circuit.jacobian([r, phi], method="F")
        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 gradA == pytest.approx(expected, abs=tol)
        assert gradF == pytest.approx(expected, abs=tol)
Пример #9
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 an 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._operation_map["DummyOp"] = None

        def circuit(a):
            DummyOp(a, wires=[0])
            return qml.expval(qml.NumberOperator(0))

        with monkeypatch.context() as m:
            circuit = CVQNode(circuit, dev, force_order2=True)

            m.setattr(circuit, "_best_method", lambda arg: "A")
            with pytest.raises(
                    NotImplementedError,
                    match=
                    r"analytic gradient for order-2 operators is unsupported"):
                grad_A = circuit.jacobian(0,
                                          method="A",
                                          options={'force_order2': True})
Пример #10
0
    def test_gaussian_successors_fails(self, operable_mock_CV_device_2_wires):
        """Tests that the parameter-shift differentiation method is not allowed
        if a non-gaussian gate is between a differentiable gaussian gate and an observable."""
        def circuit(x):
            qml.Squeezing(x, 0, wires=[0])
            qml.Beamsplitter(np.pi / 4, 0, wires=[0, 1])
            qml.Kerr(0.54, wires=[1])
            return qml.expval(qml.NumberOperator(1))

        node = CVQNode(circuit, operable_mock_CV_device_2_wires)

        with pytest.raises(
                ValueError,
                match="analytic gradient method cannot be used with"):
            node.jacobian([0.321], method="A")

        assert node.par_to_grad_method == {0: "F"}
Пример #11
0
    def test_CVOperation_with_heisenberg_and_no_parshift(self, name, tol):
        """An integration test for Gaussian CV gates that have a Heisenberg representation
        but cannot be differentiated using the parameter-shift method themselves
        (for example, they may accept no parameters, or have no gradient recipe).

        Tests that the parameter-shift method can still be used with other gates in the circuit.
        """
        gaussian_dev = qml.device("default.gaussian", wires=2)

        cls = getattr(qml.ops, name)
        if cls.supports_heisenberg and (not cls.supports_parameter_shift):
            U = np.array(
                [[0.51310276 + 0.81702166j, 0.13649626 + 0.22487759j],
                 [0.26300233 + 0.00556194j, -0.96414101 - 0.03508489j]])

            if cls.num_wires <= 0:
                w = list(range(2))
            else:
                w = list(range(cls.num_wires))

            def circuit(x):
                qml.Displacement(x, 0, wires=0)

                if cls.par_domain == "A":
                    cls(U, wires=w)
                else:
                    cls(wires=w)
                return qml.expval(qml.X(0))

            qnode = CVQNode(circuit, gaussian_dev)
            grad_F = qnode.jacobian(0.5, method="F")
            grad_A = qnode.jacobian(0.5, method="A")
            grad_A2 = qnode.jacobian(0.5,
                                     method="A",
                                     options={'force_order2': True})

            # par[0] can use the "A" method
            assert qnode.par_to_grad_method == {0: "A"}

            # the different methods agree
            assert grad_A == pytest.approx(grad_F, abs=tol)
            assert grad_A2 == pytest.approx(grad_F, abs=tol)
Пример #12
0
    def test_cv_gradients_repeated_gate_parameters(self, tol):
        """Tests that repeated use of a free parameter in a multi-parameter gate yield correct gradients."""
        gaussian_dev = qml.device("default.gaussian", wires=2)
        par = [0.2, 0.3]

        def qf(x, y):
            qml.Displacement(x, 0, wires=[0])
            qml.Squeezing(y, -1.3 * y, wires=[0])
            return qml.expval(qml.X(0))

        q = CVQNode(qf, gaussian_dev)
        grad_F = q.jacobian(par, method="F")
        grad_A = q.jacobian(par, method="A")
        grad_A2 = q.jacobian(par, method="A", options={'force_order2': True})

        # analytic method works for every parameter
        assert q.par_to_grad_method == {0: "A", 1: "A"}
        # the different methods agree
        assert grad_A == pytest.approx(grad_F, abs=tol)
        assert grad_A2 == pytest.approx(grad_F, abs=tol)
Пример #13
0
    def test_quadoperator(self, tol):
        """Test the differentiation of CV observables that depend on positional qfunc parameters."""
        def circuit(a):
            qml.Displacement(1.0, 0, wires=0)
            return qml.expval(qml.QuadOperator(a, 0))

        gaussian_dev = qml.device("default.gaussian", wires=1)
        qnode = CVQNode(circuit, gaussian_dev)

        par = [0.6]
        grad_F = qnode.jacobian(par, method='F')
        grad_A = qnode.jacobian(par, method='A')
        grad_A2 = qnode.jacobian(par,
                                 method='A',
                                 options={'force_order2': True})

        # par 0 can use the 'A' method
        assert qnode.par_to_grad_method == {0: 'A'}
        # the different methods agree
        assert grad_A == pytest.approx(grad_F, abs=tol)
        assert grad_A2 == pytest.approx(grad_F, abs=tol)
Пример #14
0
    def test_cv_gradient_fanout(self, tol):
        """Tests that CV qnodes can compute the correct gradient when the same parameter is used
        in multiple gates."""
        gaussian_dev = qml.device("default.gaussian", wires=2)
        par = [0.5, 1.3]

        def circuit(x, y):
            qml.Displacement(x, 0, wires=[0])
            qml.Rotation(y, wires=[0])
            qml.Displacement(0, x, wires=[0])
            return qml.expval(qml.X(0))

        q = CVQNode(circuit, gaussian_dev)
        grad_F = q.jacobian(par, method="F")
        grad_A = q.jacobian(par, method="A")
        grad_A2 = q.jacobian(par, method="A", options={'force_order2': True})

        # analytic method works for every parameter
        assert q.par_to_grad_method == {0: "A", 1: "A"}
        # the different methods agree
        assert grad_A == pytest.approx(grad_F, abs=tol)
        assert grad_A2 == pytest.approx(grad_F, abs=tol)
Пример #15
0
    def test_keywordarg_with_positional_arg_immutable_second_order_cv(
            self, tol):
        """Non-differentiable keyword arguments appear in the same op with differentiable arguments,
        qfunc is immutable so kwargs are passed as Variables."""

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

        def circuit(x, *, k=0.0):
            qml.Displacement(0.5, 0, wires=0)
            qml.Squeezing(x, k, wires=0)
            return qml.expval(qml.X(0))

        node = CVQNode(circuit, dev, mutable=False)
        par = [0.39]
        aux = {'k': -0.7}

        # circuit jacobians
        grad_A = node.jacobian(par,
                               aux,
                               method="A",
                               options={'force_order2': True})
        grad_F = node.jacobian(par, aux, method="F")
        assert grad_A == pytest.approx(grad_F, abs=tol)
Пример #16
0
    def test_gradient_gate_with_two_parameters(self, tol):
        """Gates with two parameters yield the correct parshift gradient."""

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

        def qf(r0, phi0, r1, phi1):
            qml.Squeezing(r0, phi0, wires=[0])
            qml.Squeezing(r1, phi1, wires=[0])
            return qml.expval(qml.NumberOperator(0))

        q = CVQNode(qf, dev)
        par = [0.543, 0.123, 0.654, -0.629]

        grad_A = q.jacobian(par, method="A")
        grad_F = q.jacobian(par, method="F")
        # the different methods agree
        assert grad_A == pytest.approx(grad_F, abs=tol)
Пример #17
0
    def test_keywordarg_second_order_cv(self, tol):
        """Non-differentiable keyword arguments with a second order CV expectation value."""

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

        def circuit(x, *, k=0.0):
            qml.Displacement(x, 0, wires=0)
            qml.Rotation(k, wires=0)
            return qml.expval(qml.PolyXP(np.diag([0, 1, 0]), wires=0))  # X^2

        node = CVQNode(circuit, dev)
        par = [0.62]
        aux = {'k': 0.4}

        # circuit jacobians
        grad_A = node.jacobian(par, aux, method="A")
        grad_F = node.jacobian(par, aux, method="F")
        expected = np.array([[8 * par[0] * np.cos(aux['k'])**2]])
        assert grad_A == pytest.approx(grad_F, abs=tol)
        assert grad_A == pytest.approx(expected, abs=tol)
Пример #18
0
    def test_expval_and_variance_cv(self, tol):
        """Test that the qnode works for a combination of CV expectation
        values and variances"""
        dev = qml.device("default.gaussian", wires=3)

        def circuit(a, b):
            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])
            return qml.var(qml.X(0)), qml.expval(qml.X(1)), qml.var(qml.X(2))

        node = CVQNode(circuit, dev)
        par = [0.54, -0.423]

        # jacobians must match
        gradA = node.jacobian(par, method="A")
        gradF = node.jacobian(par, method="F")
        assert gradA == pytest.approx(gradF, abs=tol)
Пример #19
0
    def test_second_order_cv(self, tol):
        """Test variance of a second order CV expectation value"""
        dev = qml.device("default.gaussian", wires=1)

        def circuit(n, a):
            qml.ThermalState(n, wires=0)
            qml.Displacement(a, 0, wires=0)
            return qml.var(qml.NumberOperator(0))

        circuit = CVQNode(circuit, dev)

        n = 0.12
        a = 0.765
        var = circuit(n, a)
        expected = n**2 + n + np.abs(a)**2 * (1 + 2 * n)
        assert var == pytest.approx(expected, abs=tol)

        # circuit jacobians
        gradF = circuit.jacobian([n, a], method="F")
        expected = np.array([[2 * a**2 + 2 * n + 1, 2 * a * (2 * n + 1)]])
        assert gradF == pytest.approx(expected, abs=tol)