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)
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)
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)
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)
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
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)
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)
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)
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)