Esempio n. 1
0
    def test_hybrid_gradients(self, qubit_device_2_wires, tol):
        "Tests that the various ways of computing the gradient of a hybrid computation all agree."

        # input data is the first parameter
        def classifier_circuit(in_data, x):
            qml.RX(in_data, wires=[0])
            qml.CNOT(wires=[0, 1])
            qml.RY(-1.6, wires=[0])
            qml.RY(in_data, wires=[1])
            qml.CNOT(wires=[1, 0])
            qml.RX(x, wires=[0])
            qml.CNOT(wires=[0, 1])
            return qml.expval(qml.PauliZ(0))

        classifier = to_autograd(
            QubitQNode(classifier_circuit, qubit_device_2_wires))

        param = -0.1259
        in_data = np.array([-0.1, -0.88, np.exp(0.5)])
        out_data = np.array([1.5, np.pi / 3, 0.0])

        def error(p):
            "Total square error of classifier predictions."
            ret = 0
            for d_in, d_out in zip(in_data, out_data):
                square_diff = (classifier(d_in, p) - d_out)**2
                ret = ret + square_diff
            return ret

        def d_error(p, grad_method):
            "Gradient of error, computed manually."
            ret = 0
            for d_in, d_out in zip(in_data, out_data):
                args = (d_in, p)
                diff = (classifier(*args) - d_out)
                ret = ret + 2 * diff * classifier.jacobian(
                    args, wrt=[1], method=grad_method)
            return ret

        y0 = error(param)
        grad = autograd.grad(error)
        grad_auto = grad(param)

        grad_fd1 = d_error(param, 'F')
        grad_angle = d_error(param, 'A')

        # gradients computed with different methods must agree
        assert grad_fd1 == pytest.approx(grad_angle, abs=tol)
        assert grad_fd1 == pytest.approx(grad_auto, abs=tol)
        assert grad_angle == pytest.approx(grad_auto, abs=tol)
Esempio n. 2
0
    def test_gradient_gate_with_multiple_parameters(self, tol):
        """Tests that gates with multiple free parameters yield correct gradients."""
        par = [0.5, 0.3, -0.7]

        def qf(x, y, z):
            qml.RX(0.4, wires=[0])
            qml.Rot(x, y, z, wires=[0])
            qml.RY(-0.2, wires=[0])
            return qml.expval(qml.PauliZ(0))

        dev = qml.device("default.qubit", wires=1)
        q = QubitQNode(qf, dev)
        value = q(*par)
        grad_A = q.jacobian(par, method="A")
        grad_F = q.jacobian(par, method="F")

        # analytic method works for every parameter
        assert q.par_to_grad_method == {0: "A", 1: "A", 2: "A"}
        # gradient has the correct shape and every element is nonzero
        assert grad_A.shape == (1, 3)
        assert np.count_nonzero(grad_A) == 3
        # the different methods agree
        assert grad_A == pytest.approx(grad_F, abs=tol)
    def test_rotation_gradient(self, theta, tol):
        """Tests that the automatic gradient of a phase space rotation is correct."""
        def circuit(y):
            qml.Displacement(alpha, 0., wires=[0])
            qml.Rotation(y, wires=[0])
            return qml.expval(qml.X(0))

        dev = qml.device('default.gaussian', wires=1)
        circuit = to_autograd(QubitQNode(circuit, dev))
        grad_fn = autograd.grad(circuit)

        autograd_val = grad_fn(theta)
        # qfunc evalutes to hbar * alpha * cos(theta)
        manualgrad_val = -hbar * alpha * np.sin(theta)
        assert autograd_val == pytest.approx(manualgrad_val, abs=tol)
Esempio n. 4
0
    def test_controlled_RZ_gradient(self, tol):
        """Test gradient of controlled RZ gate"""
        dev = qml.device("default.qubit", wires=2)

        def circuit(x):
            qml.PauliX(wires=0)
            qml.CRZ(x, wires=[0, 1])
            return qml.expval(qml.PauliZ(0))

        circuit = QubitQNode(circuit, dev)

        a = 0.542  # any value of a should give zero gradient

        # get the analytic gradient
        gradA = circuit.jacobian([a], method="A")
        # get the finite difference gradient
        gradF = circuit.jacobian([a], method="F")

        # the expected gradient
        expected = 0

        assert gradF == pytest.approx(expected, abs=tol)
        assert gradA == pytest.approx(expected, abs=tol)

        def circuit1(x):
            qml.RX(x, wires=0)
            qml.CRZ(x, wires=[0, 1])
            return qml.expval(qml.PauliZ(0))

        circuit1 = QubitQNode(circuit1, dev)

        b = 0.123  # gradient is -sin(x)

        # get the analytic gradient
        gradA = circuit1.jacobian([b], method="A")
        # get the finite difference gradient
        gradF = circuit1.jacobian([b], method="F")

        # the expected gradient
        expected = -np.sin(b)

        assert gradF == pytest.approx(expected, abs=tol)
        assert gradA == pytest.approx(expected, abs=tol)
Esempio n. 5
0
    def test_multiple_expectation_jacobian_array(self, tol, qubit_device_2_wires):
        """Tests that qnodes using an array argument return correct gradients
        for multiple expectation values."""
        par = np.array([0.5, 0.54, 0.3])

        def circuit(weights):
            qml.QubitStateVector(np.array([1, 0, 1, 1]) / np.sqrt(3), wires=[0, 1])
            qml.Rot(weights[0], weights[1], weights[2], wires=0)
            qml.CNOT(wires=[0, 1])
            return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))

        circuit = to_autograd(QubitQNode(circuit, qubit_device_2_wires))

        expected_jac = self.expected_jacobian(*par)
        res = circuit.jacobian([par])
        assert expected_jac == pytest.approx(res, abs=tol)

        jac = autograd.jacobian(circuit, 0)
        res = jac(par)
        assert expected_jac == pytest.approx(res, abs=tol)
Esempio n. 6
0
    def sample_circuit(self):
        """Sample variational circuit fixture used in the
        next couple of tests"""
        dev = qml.device("default.qubit", wires=3)

        def non_parametrized_layer(a, b, c):
            qml.RX(a, wires=0)
            qml.RX(b, wires=1)
            qml.RX(c, wires=1)
            qml.CNOT(wires=[0, 1])
            qml.CNOT(wires=[1, 2])
            qml.RZ(a, wires=0)
            qml.Hadamard(wires=1)
            qml.CNOT(wires=[0, 1])
            qml.RZ(b, wires=1)
            qml.Hadamard(wires=0)

        a = 0.5
        b = 0.1
        c = 0.5

        def final(x, y, z, h, g, f):
            non_parametrized_layer(a, b, c)
            qml.RX(x, wires=0)
            qml.RY(y, wires=1)
            qml.RZ(z, wires=2)
            non_parametrized_layer(a, b, c)
            qml.RY(f, wires=1)
            qml.RZ(g, wires=2)
            qml.RX(h, wires=1)
            return qml.expval(qml.PauliX(0)), qml.expval(
                qml.PauliX(1)), qml.expval(qml.PauliX(2))

        final = QubitQNode(final, dev)

        return dev, final, non_parametrized_layer, a, b, c
Esempio n. 7
0
    def test_keywordarg_not_differentiated(self, tol):
        """Tests that qnodes do not differentiate w.r.t. keyword arguments."""
        par = np.array([0.5, 0.54])

        def circuit1(weights, x=0.3):
            qml.QubitStateVector(np.array([1, 0, 1, 1]) / np.sqrt(3), wires=[0, 1])
            qml.Rot(weights[0], weights[1], x, wires=0)
            qml.CNOT(wires=[0, 1])
            return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))

        dev = qml.device("default.qubit", wires=2)
        circuit1 = QubitQNode(circuit1, dev)

        def circuit2(weights):
            qml.QubitStateVector(np.array([1, 0, 1, 1]) / np.sqrt(3), wires=[0, 1])
            qml.Rot(weights[0], weights[1], 0.3, wires=0)
            qml.CNOT(wires=[0, 1])
            return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))

        circuit2 = QubitQNode(circuit2, dev)

        res1 = circuit1.jacobian([par])
        res2 = circuit2.jacobian([par])
        assert res1 == pytest.approx(res2, abs=tol)
Esempio n. 8
0
    def test_array_parameters_evaluate(self, tol):
        """Tests that array parameters gives same result as positional arguments."""
        a, b, c = 0.5, 0.54, 0.3
        dev = qml.device("default.qubit", wires=2)

        def ansatz(x, y, z):
            qml.QubitStateVector(np.array([1, 0, 1, 1]) / np.sqrt(3), wires=[0, 1])
            qml.Rot(x, y, z, wires=0)
            qml.CNOT(wires=[0, 1])
            return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))

        def circuit1(x, y, z):
            return ansatz(x, y, z)

        def circuit2(x, array):
            return ansatz(x, array[0], array[1])

        def circuit3(array):
            return ansatz(*array)

        circuit1 = QubitQNode(circuit1, dev)
        circuit2 = QubitQNode(circuit2, dev)
        circuit3 = QubitQNode(circuit3, dev)

        positional_res = circuit1(a, b, c)
        positional_grad = circuit1.jacobian([a, b, c])

        array_res = circuit2(a, np.array([b, c]))
        array_grad = circuit2.jacobian([a, np.array([b, c])])

        assert positional_res == pytest.approx(array_res, abs=tol)
        assert positional_grad == pytest.approx(array_grad, abs=tol)

        list_res = circuit2(a, [b, c])
        list_grad = circuit2.jacobian([a, [b, c]])

        assert positional_res == pytest.approx(list_res, abs=tol)
        assert positional_grad == pytest.approx(list_grad, abs=tol)

        array_res = circuit3(np.array([a, b, c]))
        array_grad = circuit3.jacobian([np.array([a, b, c])])

        list_res = circuit3([a, b, c])
        list_grad = circuit3.jacobian([[a, b, c]])

        assert positional_res == pytest.approx(array_res, abs=tol)
        assert positional_grad == pytest.approx(array_grad, abs=tol)
Esempio n. 9
0
    def test_construct_subcircuit_layers(self):
        """Test correct subcircuits constructed
        when a layer structure exists"""
        dev = qml.device("default.qubit", wires=3)

        def circuit(params):
            # section 1
            qml.RX(params[0], wires=0)
            # section 2
            qml.RY(params[1], wires=0)
            qml.CNOT(wires=[0, 1])
            qml.CNOT(wires=[1, 2])
            # section 3
            qml.RX(params[2], wires=0)
            qml.RY(params[3], wires=1)
            qml.RZ(params[4], wires=2)
            qml.CNOT(wires=[0, 1])
            qml.CNOT(wires=[1, 2])
            # section 4
            qml.RX(params[5], wires=0)
            qml.RY(params[6], wires=1)
            qml.RZ(params[7], wires=2)
            qml.CNOT(wires=[0, 1])
            qml.CNOT(wires=[1, 2])
            return qml.expval(qml.PauliX(0)), qml.expval(
                qml.PauliX(1)), qml.expval(qml.PauliX(2))

        circuit = QubitQNode(circuit, dev)

        params = np.ones([8])
        circuit.metric_tensor([params], only_construct=True)
        res = circuit._metric_tensor_subcircuits

        # this circuit should split into 4 independent
        # sections or layers when constructing subcircuits
        assert len(res) == 4

        # first layer subcircuit
        layer = res[(0, )]
        assert len(layer["queue"]) == 0
        assert len(layer["observable"]) == 1
        assert isinstance(layer["observable"][0], qml.PauliX)

        # second layer subcircuit
        layer = res[(1, )]
        assert len(layer["queue"]) == 1
        assert len(layer["observable"]) == 1
        assert isinstance(layer["queue"][0], qml.RX)
        assert isinstance(layer["observable"][0], qml.PauliY)

        # third layer subcircuit
        layer = res[(2, 3, 4)]
        assert len(layer["queue"]) == 4
        assert len(layer["observable"]) == 3
        assert isinstance(layer["queue"][0], qml.RX)
        assert isinstance(layer["queue"][1], qml.RY)
        assert isinstance(layer["queue"][2], qml.CNOT)
        assert isinstance(layer["queue"][3], qml.CNOT)
        assert isinstance(layer["observable"][0], qml.PauliX)
        assert isinstance(layer["observable"][1], qml.PauliY)
        assert isinstance(layer["observable"][2], qml.PauliZ)

        # fourth layer subcircuit
        layer = res[(5, 6, 7)]
        assert len(layer["queue"]) == 9
        assert len(layer["observable"]) == 3
        assert isinstance(layer["queue"][0], qml.RX)
        assert isinstance(layer["queue"][1], qml.RY)
        assert isinstance(layer["queue"][2], qml.CNOT)
        assert isinstance(layer["queue"][3], qml.CNOT)
        assert isinstance(layer["queue"][4], qml.RX)
        assert isinstance(layer["queue"][5], qml.RY)
        assert isinstance(layer["queue"][6], qml.RZ)
        assert isinstance(layer["queue"][7], qml.CNOT)
        assert isinstance(layer["queue"][8], qml.CNOT)
        assert isinstance(layer["observable"][0], qml.PauliX)
        assert isinstance(layer["observable"][1], qml.PauliY)
        assert isinstance(layer["observable"][2], qml.PauliZ)
Esempio n. 10
0
    def test_evaluate_diag_approx_metric_tensor(self, sample_circuit, tol):
        """Test that a metric tensor under the
        diagonal approximation evaluates correctly."""
        dev, circuit, non_parametrized_layer, a, b, c = sample_circuit
        params = [-0.282203, 0.145554, 0.331624, -0.163907, 0.57662, 0.081272]
        x, y, z, h, g, f = params

        G = circuit.metric_tensor(params, diag_approx=True)

        # ============================================
        # Test block diag metric tensor of first layer is correct.
        # We do this by comparing against the known analytic result.
        # First layer includes the non_parametrized_layer,
        # followed by observables corresponding to generators of:
        #   qml.RX(x, wires=0)
        #   qml.RY(y, wires=1)
        #   qml.RZ(z, wires=2)

        G1 = np.zeros([3, 3])

        # diag elements
        G1[0, 0] = np.sin(a)**2 / 4
        G1[1,
           1] = (16 * np.cos(a)**2 * np.sin(b)**3 * np.cos(b) * np.sin(2 * c) +
                 np.cos(2 * b) *
                 (2 - 8 * np.cos(a)**2 * np.sin(b)**2 * np.cos(2 * c)) +
                 np.cos(2 * (a - b)) + np.cos(2 * (a + b)) -
                 2 * np.cos(2 * a) + 14) / 64
        G1[2,
           2] = (3 - np.cos(2 * a) - 2 * np.cos(a)**2 * np.cos(2 *
                                                               (b + c))) / 16

        assert np.allclose(G[:3, :3], G1, atol=tol, rtol=0)

        # =============================================
        # Test metric tensor of second layer is correct.
        # We do this by computing the required expectation values
        # numerically.
        # The second layer includes the non_parametrized_layer,
        # RX, RY, RZ gates (x, y, z params), a 2nd non_parametrized_layer,
        # followed by the qml.RY(f, wires=2) operation.
        #
        # Observable is simply generator of:
        #   qml.RY(f, wires=2)
        #
        # Note: since this layer only consists of a single parameter,
        # only need to compute a single diagonal element.

        def layer2_diag(x, y, z, h, g, f):
            non_parametrized_layer(a, b, c)
            qml.RX(x, wires=0)
            qml.RY(y, wires=1)
            qml.RZ(z, wires=2)
            non_parametrized_layer(a, b, c)
            qml.RY(f, wires=2)
            return qml.var(qml.PauliX(1))

        layer2_diag = QubitQNode(layer2_diag, dev)
        G2 = layer2_diag(x, y, z, h, g, f) / 4
        assert np.allclose(G[3:4, 3:4], G2, atol=tol, rtol=0)

        # =============================================
        # Test block diag metric tensor of third layer is correct.
        # We do this by computing the required expectation values
        # numerically using multiple circuits.
        # The second layer includes the non_parametrized_layer,
        # RX, RY, RZ gates (x, y, z params), and a 2nd non_parametrized_layer.
        #
        # Observables are the generators of:
        #   qml.RY(f, wires=1)
        #   qml.RZ(g, wires=2)
        G3 = np.zeros([2, 2])

        def layer3_diag(x, y, z, h, g, f):
            non_parametrized_layer(a, b, c)
            qml.RX(x, wires=0)
            qml.RY(y, wires=1)
            qml.RZ(z, wires=2)
            non_parametrized_layer(a, b, c)
            return qml.var(qml.PauliZ(2)), qml.var(qml.PauliY(1))

        layer3_diag = QubitQNode(layer3_diag, dev)

        # calculate the diagonal terms
        varK0, varK1 = layer3_diag(x, y, z, h, g, f)
        G3[0, 0] = varK0 / 4
        G3[1, 1] = varK1 / 4

        assert np.allclose(G[4:6, 4:6], G3, atol=tol, rtol=0)

        # ============================================
        # Finally, double check that the entire metric
        # tensor is as computed.

        G_expected = block_diag(G1, G2, G3)
        assert np.allclose(G, G_expected, atol=tol, rtol=0)