def test_multidim_array_parameter(self, shape, tol): """Tests that arguments which are multidimensional arrays are properly evaluated and differentiated in QubitQNodes.""" n = np.prod(shape) base_array = np.linspace(-1.0, 1.0, n) multidim_array = np.reshape(base_array, shape) def circuit(w): for k in range(n): qml.RX(w[np.unravel_index(k, shape)], wires=k) # base_array[k] return tuple(qml.expval(qml.PauliZ(idx)) for idx in range(n)) dev = qml.device("default.qubit", wires=n) circuit = QubitQNode(circuit, dev) # circuit evaluations circuit_output = circuit(multidim_array) expected_output = np.cos(base_array) assert circuit_output == pytest.approx(expected_output, abs=tol) # circuit jacobians circuit_jacobian = circuit.jacobian([multidim_array]) expected_jacobian = -np.diag(np.sin(base_array)) assert circuit_jacobian == pytest.approx(expected_jacobian, abs=tol)
def test_differentiate_positional_multidim(self, tol): """Tests that all positional arguments are differentiated when they are multidimensional.""" def circuit(a, b): qml.RX(a[0], wires=0) qml.RX(a[1], wires=1) qml.RX(b[2, 1], wires=2) return qml.expval(qml.PauliZ(0)), qml.expval( qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) dev = qml.device("default.qubit", wires=3) circuit = QubitQNode(circuit, dev) a = np.array([-np.sqrt(2), -0.54]) b = np.array([np.pi / 7] * 6).reshape([3, 2]) circuit_output = circuit(a, b) expected_output = np.cos(np.array([a[0], a[1], b[-1, 0]])) assert circuit_output == pytest.approx(expected_output, abs=tol) # circuit jacobians circuit_jacobian = circuit.jacobian([a, b]) expected_jacobian = np.array([ [-np.sin(a[0])] + [0.0] * 7, # expval 0 [0.0, -np.sin(a[1])] + [0.0] * 6, # expval 1 [0.0] * 2 + [0.0] * 5 + [-np.sin(b[2, 1])], ]) # expval 2 assert circuit_jacobian == pytest.approx(expected_jacobian, abs=tol)
def test_evaluate_diag_metric_tensor(self, tol): """Test that a diagonal metric tensor evaluates correctly""" dev = qml.device("default.qubit", wires=2) def circuit(a, b, c): qml.RX(a, wires=0) qml.RY(b, wires=0) qml.CNOT(wires=[0, 1]) qml.PhaseShift(c, wires=1) return qml.expval(qml.PauliX(0)), qml.expval(qml.PauliX(1)) circuit = QubitQNode(circuit, dev) a = 0.432 b = 0.12 c = -0.432 # evaluate metric tensor g = circuit.metric_tensor((a, b, c)) # check that the metric tensor is correct expected = (np.array([ 1, np.cos(a)**2, (3 - 2 * np.cos(a)**2 * np.cos(2 * b) - np.cos(2 * a)) / 4 ]) / 4) assert np.allclose(g, np.diag(expected), atol=tol, rtol=0)
def test_construct_subcircuit(self): """Test correct subcircuits constructed""" dev = qml.device("default.qubit", wires=2) def circuit(a, b, c): qml.RX(a, wires=0) qml.RY(b, wires=0) qml.CNOT(wires=[0, 1]) qml.PhaseShift(c, wires=1) return qml.expval(qml.PauliX(0)), qml.expval(qml.PauliX(1)) circuit = QubitQNode(circuit, dev) circuit.metric_tensor([1, 1, 1], only_construct=True) res = circuit._metric_tensor_subcircuits # first parameter subcircuit assert len(res[(0, )]["queue"]) == 0 assert res[(0, )]["scale"] == [-0.5] assert isinstance(res[(0, )]["observable"][0], qml.PauliX) # second parameter subcircuit assert len(res[(1, )]["queue"]) == 1 assert res[(1, )]["scale"] == [-0.5] assert isinstance(res[(1, )]["queue"][0], qml.RX) assert isinstance(res[(1, )]["observable"][0], qml.PauliY) # third parameter subcircuit assert len(res[(2, )]["queue"]) == 3 assert res[(2, )]["scale"] == [1] assert isinstance(res[(2, )]["queue"][0], qml.RX) assert isinstance(res[(2, )]["queue"][1], qml.RY) assert isinstance(res[(2, )]["queue"][2], qml.CNOT) assert isinstance(res[(2, )]["observable"][0], qml.Hermitian) assert np.all(res[( 2, )]["observable"][0].params[0] == qml.PhaseShift.generator[0])
def test_fanout_multiple_params(self, reused_p, other_p, tol): """Tests that the correct gradient is computed for qnodes which use the same parameter in multiple gates.""" from pennylane.plugins.default_qubit import Rotx as Rx, Roty as Ry, Rotz as Rz def expZ(state): return np.abs(state[0])**2 - np.abs(state[1])**2 extra_param = 0.31 def circuit(reused_param, other_param): qml.RX(extra_param, wires=[0]) qml.RY(reused_param, wires=[0]) qml.RZ(other_param, wires=[0]) qml.RX(reused_param, wires=[0]) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=1) f = QubitQNode(circuit, dev) zero_state = np.array([1., 0.]) # analytic gradient grad_A = f.jacobian([reused_p, other_p]) # manual gradient grad_true0 = (expZ(Rx(reused_p) @ Rz(other_p) @ Ry(reused_p + np.pi / 2) @ Rx(extra_param) @ zero_state) \ -expZ(Rx(reused_p) @ Rz(other_p) @ Ry(reused_p - np.pi / 2) @ Rx(extra_param) @ zero_state)) / 2 grad_true1 = (expZ(Rx(reused_p + np.pi / 2) @ Rz(other_p) @ Ry(reused_p) @ Rx(extra_param) @ zero_state) \ -expZ(Rx(reused_p - np.pi / 2) @ Rz(other_p) @ Ry(reused_p) @ Rx(extra_param) @ zero_state)) / 2 grad_true = grad_true0 + grad_true1 # product rule assert grad_A[0, 0] == pytest.approx(grad_true, abs=tol)
def test_evaluate_subcircuits(self, tol): """Test subcircuits evaluate correctly""" dev = qml.device("default.qubit", wires=2) def circuit(a, b, c): qml.RX(a, wires=0) qml.RY(b, wires=0) qml.CNOT(wires=[0, 1]) qml.PhaseShift(c, wires=1) return qml.expval(qml.PauliX(0)), qml.expval(qml.PauliX(1)) circuit = QubitQNode(circuit, dev) a = 0.432 b = 0.12 c = -0.432 # evaluate subcircuits circuit.metric_tensor((a, b, c)) # first parameter subcircuit res = circuit._metric_tensor_subcircuits[(0, )]["result"] expected = 0.25 assert np.allclose(res, expected, atol=tol, rtol=0) # second parameter subcircuit res = circuit._metric_tensor_subcircuits[(1, )]["result"] expected = np.cos(a)**2 / 4 assert np.allclose(res, expected, atol=tol, rtol=0) # third parameter subcircuit res = circuit._metric_tensor_subcircuits[(2, )]["result"] expected = (3 - 2 * np.cos(a)**2 * np.cos(2 * b) - np.cos(2 * a)) / 16 assert np.allclose(res, expected, atol=tol, rtol=0)
def test_no_following_observable(self, operable_mock_device_2_wires): """Test that the gradient is 0 if no observables succeed""" def circuit(x): qml.RX(x, wires=[1]) return qml.expval(qml.PauliZ(0)) q = QubitQNode(circuit, operable_mock_device_2_wires) q._construct([1.0], {}) assert q.par_to_grad_method == {0: "0"}
def test_param_unused(self, operable_mock_device_2_wires): """Test that the gradient is 0 of an unused parameter""" def circuit(x, y): qml.RX(x, wires=[0]) return qml.expval(qml.PauliZ(0)) q = QubitQNode(circuit, operable_mock_device_2_wires) q._construct([1.0, 1.0], {}) assert q.par_to_grad_method == {0: "A", 1: "0"}
def test_not_differentiable(self, operable_mock_device_2_wires): """Test that an operation with grad_method=None is marked as non-differentiable""" def circuit(x): qml.BasisState(x, wires=[1]) return qml.expval(qml.PauliZ(0)) q = QubitQNode(circuit, operable_mock_device_2_wires) q._construct([np.array([1.0])], {}) assert q.par_to_grad_method == {0: None}
def test_pauli_rotation_gradient(self, G, theta, tol): """Tests that the automatic gradients of Pauli rotations are correct.""" def circuit(x): qml.RX(x, wires=[0]) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=1) circuit = QubitQNode(circuit, dev) autograd_val = circuit.jacobian([theta]) manualgrad_val = (circuit(theta + np.pi / 2) - circuit(theta - np.pi / 2)) / 2 assert autograd_val == pytest.approx(manualgrad_val, abs=tol)
def test_no_generator(self): """Test exception is raised if subcircuit contains an operation with no generator""" dev = qml.device("default.qubit", wires=1) def circuit(a): qml.Rot(a, 0, 0, wires=0) return qml.expval(qml.PauliX(0)) circuit = QubitQNode(circuit, dev) with pytest.raises(QuantumFunctionError, match="has no defined generator"): circuit.metric_tensor([1], only_construct=True)
def test_array_parameters_autograd(self, tol, qubit_device_2_wires): """Test that gradients of array parameters give same results as positional arguments.""" par = [0.5, 0.54, 0.3] 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)) 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 = to_autograd(QubitQNode(circuit1, qubit_device_2_wires)) grad1 = autograd.grad(circuit1, argnum=[0, 1, 2]) # three positional parameters jac = circuit1.jacobian(par) ag = grad1(*par) ag = np.array([ag]) assert jac == pytest.approx(ag, abs=tol) circuit2 = to_autograd(QubitQNode(circuit2, qubit_device_2_wires)) grad2 = autograd.grad(circuit2, argnum=[0, 1]) # one scalar, one array temp = [par[0], np.array(par[1:])] jac = circuit2.jacobian(temp) ag = grad2(*temp) ag = np.r_[ag][np.newaxis, :] assert jac == pytest.approx(ag, abs=tol) circuit3 = to_autograd(QubitQNode(circuit3, qubit_device_2_wires)) grad3 = autograd.grad(circuit3, argnum=0) # one array temp = [np.array(par)] jac = circuit3.jacobian(temp) ag = grad3(*temp)[np.newaxis, :] assert jac == pytest.approx(ag, abs=tol)
def test_hybrid_gradients_autograd_numpy(self, qubit_device_2_wires, tol): "Test the gradient of a hybrid computation requiring autograd.numpy functions." def circuit(x, y): "Quantum node." qml.RX(x, wires=[0]) qml.CNOT(wires=[0, 1]) qml.RY(y, wires=[0]) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) quantum = to_autograd(QubitQNode(circuit, qubit_device_2_wires)) def classical(p): "Classical node, requires autograd.numpy functions." return anp.exp(anp.sum(quantum(p[0], anp.log(p[1])))) def d_classical(a, b, method): "Gradient of classical computed symbolically, can use normal numpy functions." val = classical((a, b)) J = quantum.jacobian((a, np.log(b)), method=method) return val * np.array([J[0, 0] + J[1, 0], (J[0, 1] + J[1, 1]) / b]) param = np.array([-0.1259, 1.53]) y0 = classical(param) grad_classical = autograd.jacobian(classical) grad_auto = grad_classical(param) grad_fd1 = d_classical(*param, 'F') grad_angle = d_classical(*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_qfunc_gradients(self, qubit_device_2_wires, tol): "Tests that the various ways of computing the gradient of a qfunc all agree." def circuit(x, y, z): qml.RX(x, wires=[0]) qml.CNOT(wires=[0, 1]) qml.RY(-1.6, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[1, 0]) qml.RX(z, wires=[0]) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)) qnode = to_autograd(QubitQNode(circuit, qubit_device_2_wires)) params = np.array([0.1, -1.6, np.pi / 5]) # manual gradients grad_fd1 = qnode.jacobian(params, method='F', options={'order': 1}) grad_fd2 = qnode.jacobian(params, method='F', options={'order': 2}) grad_angle = qnode.jacobian(params, method='A') # automatic gradient grad_fn = autograd.grad(qnode, argnum=[0, 1, 2]) grad_auto = np.array([grad_fn(*params)]) # gradients computed with different methods must agree assert grad_fd1 == pytest.approx(grad_fd2, abs=tol) assert grad_fd1 == pytest.approx(grad_angle, abs=tol) assert grad_fd1 == pytest.approx(grad_auto, abs=tol)
def test_qnode_array_parameters_2_vector_return(self, qubit_device_2_wires, tol): """Test that QNode can take arrays as input arguments, and that they interact properly with Autograd. Test case for a circuit that returns a 2-vector.""" def circuit(dummy1, array, dummy2): qml.RY(0.5 * array[0, 1], wires=0) qml.RY(-0.5 * array[1, 1], wires=0) qml.RY(array[1, 0], wires=1) return qml.expval(qml.PauliX(0)), qml.expval(qml.PauliX(1)) node = to_autograd(QubitQNode(circuit, qubit_device_2_wires)) args = (0.46, np.array([[2.0, 3.0, 0.3], [7.0, 4.0, 2.1]]), -0.13) grad_target = ( np.array(1.0), np.array([[0.5, 0.43879, 0], [0, -0.43879, 0]]), np.array(-0.4), ) cost_target = 1.03257 def cost(x, array, y): c = node(0.111, array, 4.5)[0] return c + 0.5 * array[0, 0] + x - 0.4 * y cost_grad = autograd.grad(cost, argnum=[0, 1, 2]) computed_grad = cost_grad(*args) assert cost(*args) == pytest.approx(cost_target, abs=tol) assert computed_grad[0] == pytest.approx(grad_target[0], abs=tol) assert computed_grad[1] == pytest.approx(grad_target[1], abs=tol) assert computed_grad[2] == pytest.approx(grad_target[2], abs=tol)
def test_multiple_expectation_jacobian_positional(self, tol, qubit_device_2_wires): """Tests that qnodes using positional arguments return correct gradients for multiple expectation values.""" par = [0.5, 0.54, 0.3] def circuit(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)) circuit = to_autograd(QubitQNode(circuit, qubit_device_2_wires)) # compare our manual Jacobian computation to theoretical result expected_jac = self.expected_jacobian(*par) res = circuit.jacobian(par) assert expected_jac == pytest.approx(res, abs=tol) # compare our manual Jacobian computation to autograd # not sure if this is the intended usage of jacobian jac0 = autograd.jacobian(circuit, 0) jac1 = autograd.jacobian(circuit, 1) jac2 = autograd.jacobian(circuit, 2) res = np.stack([jac0(*par), jac1(*par), jac2(*par)]).T assert expected_jac == pytest.approx(res, abs=tol) #compare with what we get if argnum is a list jac = autograd.jacobian(circuit, argnum=[0, 1, 2])
def test_gradient_repeated_gate_parameters(self, tol): """Tests that repeated use of a free parameter in a multi-parameter gate yield correct gradients.""" par = [0.8, 1.3] def qf(x, y): qml.RX(np.pi / 4, wires=[0]) qml.Rot(y, x, 2 * x, wires=[0]) return qml.expval(qml.PauliX(0)) dev = qml.device("default.qubit", wires=1) q = QubitQNode(qf, dev) 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)
def test_generator_no_expval(self, monkeypatch): """Test exception is raised if subcircuit contains an operation with generator object that is not an observable""" dev = qml.device("default.qubit", wires=1) def circuit(a): qml.RX(a, wires=0) return qml.expval(qml.PauliX(0)) circuit = QubitQNode(circuit, dev) with monkeypatch.context() as m: m.setattr("pennylane.RX.generator", [qml.RX, 1]) with pytest.raises(QuantumFunctionError, match="no corresponding observable"): circuit.metric_tensor([1], only_construct=True)
def test_interface_str(self, qubit_device_2_wires): """Test that the interface string is correctly identified as numpy""" def circuit(x, y, z): 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)) assert circuit.interface == "autograd"
def test_expval_and_variance(self, tol): """Test that the qnode works for a combination of expectation values and variances""" dev = qml.device("default.qubit", wires=3) def circuit(a, b, c): qml.RX(a, wires=0) qml.RY(b, wires=1) qml.CNOT(wires=[1, 2]) qml.RX(c, wires=2) qml.CNOT(wires=[0, 1]) qml.RZ(c, wires=2) return qml.var(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.var( qml.PauliZ(2)) circuit = QubitQNode(circuit, dev) a = 0.54 b = -0.423 c = 0.123 var = circuit(a, b, c) expected = np.array([ np.sin(a)**2, np.cos(a) * np.cos(b), 0.25 * (3 - 2 * np.cos(b)**2 * np.cos(2 * c) - np.cos(2 * b)), ]) assert var == pytest.approx(expected, abs=tol) # # circuit jacobians gradA = circuit.jacobian([a, b, c], method="A") gradF = circuit.jacobian([a, b, c], method="F") expected = np.array([ [2 * np.cos(a) * np.sin(a), -np.cos(b) * np.sin(a), 0], [ 0, -np.cos(a) * np.sin(b), 0.5 * (2 * np.cos(b) * np.cos(2 * c) * np.sin(b) + np.sin(2 * b)), ], [0, 0, np.cos(b)**2 * np.sin(2 * c)], ]).T assert gradF == pytest.approx(expected, abs=tol) assert gradA == pytest.approx(expected, abs=tol)
def test_gradient_parameters_inside_array(self, tol): """Tests that free parameters inside an array passed to an Operation yield correct gradients.""" par = [0.8, 1.3] def qf(x, y): qml.RX(x, wires=[0]) qml.RY(x, wires=[0]) return qml.expval(qml.Hermitian(np.diag([y, 1]), 0)) dev = qml.device("default.qubit", wires=1) q = QubitQNode(qf, dev) grad = q.jacobian(par) 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 == pytest.approx(grad_F, abs=tol)
def test_differentiate_second_positional(self, tol): """Tests that the second positional arguments are differentiated.""" def circuit3(a, b): qml.RX(b, wires=0) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=2) circuit3 = QubitQNode(circuit3, dev) a = 0.7418 b = -5.0 circuit_output = circuit3(a, b) expected_output = np.cos(b) assert circuit_output == pytest.approx(expected_output, abs=tol) # circuit jacobians circuit_jacobian = circuit3.jacobian([a, b]) expected_jacobian = np.array([[0, -np.sin(b)]]) assert circuit_jacobian == pytest.approx(expected_jacobian, abs=tol)
def test_involutory_variance(self, tol): """Tests qubit observable that are involutory""" def circuit(a): qml.RX(a, wires=0) return qml.var(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=1) circuit = QubitQNode(circuit, dev) a = 0.54 var = circuit(a) expected = 1 - np.cos(a)**2 assert var == pytest.approx(expected, abs=tol) # circuit jacobians gradA = circuit.jacobian([a], method="A") gradF = circuit.jacobian([a], method="F") expected = 2 * np.sin(a) * np.cos(a) assert gradF == pytest.approx(expected, abs=tol) assert gradA == pytest.approx(expected, abs=tol)
def test_differentiate_all_positional(self, tol): """Tests that all positional arguments are differentiated.""" def circuit1(a, b, c): qml.RX(a, wires=0) qml.RX(b, wires=1) qml.RX(c, wires=2) return tuple(qml.expval(qml.PauliZ(idx)) for idx in range(3)) dev = qml.device("default.qubit", wires=3) circuit1 = QubitQNode(circuit1, dev) vals = np.array([np.pi, np.pi / 2, np.pi / 3]) circuit_output = circuit1(*vals) expected_output = np.cos(vals) assert circuit_output == pytest.approx(expected_output, abs=tol) # circuit jacobians circuit_jacobian = circuit1.jacobian(vals) expected_jacobian = -np.diag(np.sin(vals)) assert circuit_jacobian == pytest.approx(expected_jacobian, abs=tol)
def test_fanout(self, tol): """Tests qubit observable with repeated parameters""" def circuit(a): qml.RX(a, wires=0) qml.RY(a, wires=0) return qml.var(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=2) circuit = QubitQNode(circuit, dev) a = 0.54 var = circuit(a) expected = 0.5 * np.sin(a)**2 * (np.cos(2 * a) + 3) assert var == pytest.approx(expected, abs=tol) # circuit jacobians gradA = circuit.jacobian([a], method="A") gradF = circuit.jacobian([a], method="F") expected = 4 * np.sin(a) * np.cos(a)**3 assert gradA == pytest.approx(expected, abs=tol) assert gradF == pytest.approx(expected, abs=tol)
def test_Rot_gradient(self, theta, tol): """Tests that the automatic gradient of a arbitrary Euler-angle-parameterized gate is correct.""" def circuit(x, y, z): qml.Rot(x, y, z, wires=[0]) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=1) circuit = QubitQNode(circuit, dev) eye = np.eye(3) angle_inputs = np.array([theta, theta**3, np.sqrt(2) * theta]) autograd_val = circuit.jacobian(angle_inputs) manualgrad_val = np.zeros((1, 3)) for idx in range(3): onehot_idx = eye[idx] param1 = angle_inputs + np.pi / 2 * onehot_idx param2 = angle_inputs - np.pi / 2 * onehot_idx manualgrad_val[0, idx] = (circuit(*param1) - circuit(*param2)) / 2 assert autograd_val == pytest.approx(manualgrad_val, abs=tol)
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_non_involutory_variance(self, tol): """Tests a qubit Hermitian observable that is not involutory""" A = np.array([[4, -1 + 6j], [-1 - 6j, 2]]) def circuit(a): qml.RX(a, wires=0) return qml.var(qml.Hermitian(A, 0)) dev = qml.device("default.qubit", wires=1) circuit = QubitQNode(circuit, dev) a = 0.54 var = circuit(a) expected = (39 / 2) - 6 * np.sin(2 * a) + (35 / 2) * np.cos(2 * a) assert var == pytest.approx(expected, abs=tol) # circuit jacobians gradA = circuit.jacobian([a], method="A") gradF = circuit.jacobian([a], method="F") expected = -35 * np.sin(2 * a) - 12 * np.cos(2 * a) assert gradA == pytest.approx(expected, abs=tol) assert gradF == pytest.approx(expected, abs=tol)
def test_differentiate_second_third_positional(self, tol): """Tests that the second and third positional arguments are differentiated.""" def circuit4(a, b, c): qml.RX(b, wires=0) qml.RX(c, wires=1) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) dev = qml.device("default.qubit", wires=2) circuit4 = QubitQNode(circuit4, dev) a = 0.7418 b = -5.0 c = np.pi / 7 circuit_output = circuit4(a, b, c) expected_output = np.array([np.cos(b), np.cos(c)]) assert circuit_output == pytest.approx(expected_output, abs=tol) # circuit jacobians circuit_jacobian = circuit4.jacobian([a, b, c]) expected_jacobian = np.array([[0.0, -np.sin(b), 0.0], [0.0, 0.0, -np.sin(c)]]) assert circuit_jacobian == pytest.approx(expected_jacobian, 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)