def test_involutory_variance(self, mocker, tol): """Tests qubit observable that are involutory""" dev = qml.device("default.qubit", wires=1) a = 0.54 spy_analytic_var = mocker.spy(QubitParamShiftTape, "parameter_shift_var") spy_numeric = mocker.spy(QubitParamShiftTape, "numeric_pd") spy_execute = mocker.spy(dev, "execute") with QubitParamShiftTape() as tape: qml.RX(a, wires=0) qml.var(qml.PauliZ(0)) res = tape.execute(dev) expected = 1 - np.cos(a) ** 2 assert np.allclose(res, expected, atol=tol, rtol=0) spy_execute.call_args_list = [] # circuit jacobians gradA = tape.jacobian(dev, method="analytic") spy_analytic_var.assert_called() spy_numeric.assert_not_called() assert len(spy_execute.call_args_list) == 1 + 2 * 1 spy_execute.call_args_list = [] gradF = tape.jacobian(dev, method="numeric") spy_numeric.assert_called() assert len(spy_execute.call_args_list) == 2 expected = 2 * np.sin(a) * np.cos(a) assert gradF == pytest.approx(expected, abs=tol) assert gradA == pytest.approx(expected, abs=tol)
def test_variance_gradients_agree_finite_differences(self, mocker, tol): """Tests that the variance parameter-shift rule agrees with the first and second order finite differences""" params = np.array([0.1, -1.6, np.pi / 5]) with QubitParamShiftTape() as tape: qml.RX(params[0], wires=[0]) qml.CNOT(wires=[0, 1]) qml.RY(-1.6, wires=[0]) qml.RY(params[1], wires=[1]) qml.CNOT(wires=[1, 0]) qml.RX(params[2], wires=[0]) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)), qml.var(qml.PauliX(1)) tape.trainable_params = {0, 2, 3} dev = qml.device("default.qubit", wires=2) spy_numeric = mocker.spy(tape, "numeric_pd") spy_analytic = mocker.spy(tape, "analytic_pd") grad_F1 = tape.jacobian(dev, method="numeric", order=1) grad_F2 = tape.jacobian(dev, method="numeric", order=2) spy_numeric.assert_called() spy_analytic.assert_not_called() grad_A = tape.jacobian(dev, method="analytic") spy_analytic.assert_called() # gradients computed with different methods must agree assert np.allclose(grad_A, grad_F1, atol=tol, rtol=0) assert np.allclose(grad_A, grad_F2, atol=tol, rtol=0)
def test_pauli_rotation_hessian(self, s1, s2, G, tol): """Tests that the automatic Hessian of Pauli rotations are correct.""" theta = np.array([0.234, 2.443]) dev = qml.device("default.qubit", wires=2) with QubitParamShiftTape() as tape: qml.QubitStateVector(np.array([1., -1., 1., -1.]) / np.sqrt(4), wires=[0, 1]) G(theta[0], wires=[0]) G(theta[1], wires=[1]) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) tape.trainable_params = {1, 2} autograd_val = tape.hessian(dev, s1=s1, s2=s2) assert autograd_val.shape == (len(theta), len(theta)) shift = np.eye(len(theta)) manualgrad_val = np.zeros((len(theta), len(theta))) for i in range(len(theta)): for j in range(len(theta)): manualgrad_val[i, j] = ( tape.execute( dev, params=theta + s1 * shift[i] + s2 * shift[j]) - tape.execute( dev, params=theta - s1 * shift[i] + s2 * shift[j]) - tape.execute( dev, params=theta + s1 * shift[i] - s2 * shift[j]) + tape.execute( dev, params=theta - s1 * shift[i] - s2 * shift[j])) / ( 4 * np.sin(s1) * np.sin(s2)) assert np.allclose(autograd_val, manualgrad_val, atol=tol, rtol=0)
def test_vector_output(self, tol): """Tests that a vector valued output tape has a hessian with the proper result.""" dev = qml.device("default.qubit", wires=1) x = np.array([1.0, 2.0]) with QubitParamShiftTape() as tape: qml.RY(x[0], wires=0) qml.RX(x[1], wires=0) qml.probs(wires=[0]) hess = tape.hessian(dev) expected_hess = expected_hess = np.array( [ [ [-0.5 * np.cos(x[0]) * np.cos(x[1]), 0.5 * np.cos(x[0]) * np.cos(x[1])], [0.5 * np.sin(x[0]) * np.sin(x[1]), -0.5 * np.sin(x[0]) * np.sin(x[1])], ], [ [0.5 * np.sin(x[0]) * np.sin(x[1]), -0.5 * np.sin(x[0]) * np.sin(x[1])], [-0.5 * np.cos(x[0]) * np.cos(x[1]), 0.5 * np.cos(x[0]) * np.cos(x[1])], ], ] ) assert np.allclose(hess, expected_hess, atol=tol, rtol=0)
def test_Rot_gradient(self, mocker, theta, shift, tol): """Tests that the automatic gradient of a arbitrary Euler-angle-parameterized gate is correct.""" spy = mocker.spy(QubitParamShiftTape, "parameter_shift") dev = qml.device("default.qubit", wires=1) params = np.array([theta, theta**3, np.sqrt(2) * theta]) with QubitParamShiftTape() as tape: qml.QubitStateVector(np.array([1., -1.]) / np.sqrt(2), wires=0) qml.Rot(*params, wires=[0]) qml.expval(qml.PauliZ(0)) tape.trainable_params = {1, 2, 3} autograd_val = tape.jacobian(dev, shift=shift, method="analytic") manualgrad_val = np.zeros_like(autograd_val) for idx in list(np.ndindex(*params.shape)): s = np.zeros_like(params) s[idx] += np.pi / 2 forward = tape.execute(dev, params=params + s) backward = tape.execute(dev, params=params - s) manualgrad_val[0, idx] = (forward - backward) / 2 assert np.allclose(autograd_val, manualgrad_val, atol=tol, rtol=0) assert spy.call_args[1]["shift"] == shift # compare to finite differences numeric_val = tape.jacobian(dev, shift=shift, method="numeric") assert np.allclose(autograd_val, numeric_val, atol=tol, rtol=0)
def test_CRot_gradient(self, mocker, theta, tol): """Tests that the automatic gradient of an arbitrary controlled Euler-angle-parameterized gate is correct.""" spy = mocker.spy(QubitParamShiftTape, "parameter_shift") dev = qml.device("default.qubit", wires=2) a, b, c = np.array([theta, theta**3, np.sqrt(2) * theta]) with QubitParamShiftTape() as tape: qml.QubitStateVector(np.array([1., -1.]) / np.sqrt(2), wires=0) qml.CRot(a, b, c, wires=[0, 1]) qml.expval(qml.PauliX(0)) tape.trainable_params = {1, 2, 3} res = tape.execute(dev) expected = -np.cos(b / 2) * np.cos(0.5 * (a + c)) assert np.allclose(res, expected, atol=tol, rtol=0) grad = tape.jacobian(dev, method="analytic") expected = np.array([[ 0.5 * np.cos(b / 2) * np.sin(0.5 * (a + c)), 0.5 * np.sin(b / 2) * np.cos(0.5 * (a + c)), 0.5 * np.cos(b / 2) * np.sin(0.5 * (a + c)), ]]) assert np.allclose(grad, expected, atol=tol, rtol=0) # compare to finite differences numeric_val = tape.jacobian(dev, method="numeric") assert np.allclose(grad, numeric_val, atol=tol, rtol=0)
def test_no_trainable_params_hessian(self): """Test that an empty Hessian is returned when there are no trainable parameters.""" dev = qml.device("default.qubit", wires=1) with QubitParamShiftTape() as tape: qml.RX(0.224, wires=[0]) qml.expval(qml.PauliZ(0)) tape.trainable_params = {} hessian = tape.hessian(dev) assert hessian.shape == (0, 0)
def test_finite_diff(self, monkeypatch): """If an op has grad_method=F, this should be respected by the QubitParamShiftTape""" monkeypatch.setattr(qml.RX, "grad_method", "F") psi = np.array([1, 0, 1, 0]) / np.sqrt(2) with QubitParamShiftTape() as tape: qml.QubitStateVector(psi, wires=[0, 1]) qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) qml.CNOT(wires=[0, 1]) qml.probs(wires=[0, 1]) assert tape._grad_method(0) is None assert tape._grad_method(1) == "F" assert tape._grad_method(2) == "A"
def test_involutory_and_noninvolutory_variance(self, mocker, tol): """Tests a qubit Hermitian observable that is not involutory alongside a involutory observable.""" dev = qml.device("default.qubit", wires=2) A = np.array([[4, -1 + 6j], [-1 - 6j, 2]]) a = 0.54 spy_analytic_var = mocker.spy(QubitParamShiftTape, "parameter_shift_var") spy_numeric = mocker.spy(QubitParamShiftTape, "numeric_pd") spy_execute = mocker.spy(dev, "execute") with QubitParamShiftTape() as tape: qml.RX(a, wires=0) qml.RX(a, wires=1) qml.var(qml.PauliZ(0)) qml.var(qml.Hermitian(A, 1)) tape.trainable_params = {0, 1} res = tape.execute(dev) expected = [ 1 - np.cos(a)**2, (39 / 2) - 6 * np.sin(2 * a) + (35 / 2) * np.cos(2 * a) ] assert np.allclose(res, expected, atol=tol, rtol=0) spy_execute.call_args_list = [] # circuit jacobians gradA = tape.jacobian(dev, method="analytic") spy_analytic_var.assert_called() spy_numeric.assert_not_called() assert len(spy_execute.call_args_list) == 1 + 2 * 4 spy_execute.call_args_list = [] gradF = tape.jacobian(dev, method="numeric") spy_numeric.assert_called() assert len(spy_execute.call_args_list) == 1 + 2 expected = [ 2 * np.sin(a) * np.cos(a), -35 * np.sin(2 * a) - 12 * np.cos(2 * a) ] assert np.diag(gradA) == pytest.approx(expected, abs=tol) assert np.diag(gradF) == pytest.approx(expected, abs=tol)
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) a = 0.54 b = -0.423 c = 0.123 with QubitParamShiftTape() as tape: 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.var(qml.PauliZ(0)) qml.expval(qml.PauliZ(1)) qml.var(qml.PauliZ(2)) res = tape.execute(dev) 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 np.allclose(res, expected, atol=tol, rtol=0) # # circuit jacobians gradA = tape.jacobian(dev, method="analytic") gradF = tape.jacobian(dev, method="numeric") 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 gradA == pytest.approx(expected, abs=tol) assert gradF == pytest.approx(expected, abs=tol)
def test_controlled_rotation_error(self, G, tol): """Test that attempting to perform the parameter-shift rule on the controlled rotation gates results in an error.""" dev = qml.device("default.qubit", wires=2) b = 0.123 with QubitParamShiftTape() as tape: qml.QubitStateVector(np.array([1.0, -1.0]) / np.sqrt(2), wires=0) G(b, wires=[0, 1]) qml.expval(qml.PauliX(0)) tape.trainable_params = {1} res = tape.execute(dev) assert np.allclose(res, -np.cos(b / 2), atol=tol, rtol=0) with pytest.raises(ValueError, match="not supported for the parameter-shift Hessian"): tape.hessian(dev, method="analytic")
def test_single_expectation_value(self, tol): """Tests correct output shape and evaluation for a tape with a single expval output""" dev = qml.device("default.qubit", wires=2) x = 0.543 y = -0.654 with QubitParamShiftTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) res = tape.jacobian(dev, method="analytic") assert res.shape == (1, 2) expected = np.array([[-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]]) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_non_differentiable_controlled_rotation(self, tol): """Tests that a non-differentiable controlled operation does not affect the Hessian computation.""" dev = qml.device("default.qubit", wires=2) x = 0.6 with QubitParamShiftTape() as tape: qml.RY(x, wires=0) qml.CRY(np.pi / 2, wires=[0, 1]) qml.expval(qml.PauliX(0)) tape.trainable_params = {0} hess = tape.hessian(dev) expected_hess = np.array([-np.sin(x) / np.sqrt(2)]) assert np.allclose(hess, expected_hess, atol=tol, rtol=0)
def test_prob_expectation_values(self, tol): """Tests correct output shape and evaluation for a tape with prob and expval outputs""" dev = qml.device("default.qubit", wires=2) x = 0.543 y = -0.654 with QubitParamShiftTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) qml.probs(wires=[0, 1]) res = tape.jacobian(dev, method="analytic") assert res.shape == (5, 2) expected = ( np.array( [ [-2 * np.sin(x), 0], [ -(np.cos(y / 2) ** 2 * np.sin(x)), -(np.cos(x / 2) ** 2 * np.sin(y)), ], [ -(np.sin(x) * np.sin(y / 2) ** 2), (np.cos(x / 2) ** 2 * np.sin(y)), ], [ (np.sin(x) * np.sin(y / 2) ** 2), (np.sin(x / 2) ** 2 * np.sin(y)), ], [ (np.cos(y / 2) ** 2 * np.sin(x)), -(np.sin(x / 2) ** 2 * np.sin(y)), ], ] ) / 2 ) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_independent(self): """Test that an independent variable is properly marked as having a zero gradient""" with QubitParamShiftTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) qml.expval(qml.PauliY(0)) assert tape._grad_method(0) == "A" assert tape._grad_method(1) == "0" tape._update_gradient_info() assert tape._par_info[0]["grad_method"] == "A" assert tape._par_info[1]["grad_method"] == "0" # in non-graph mode, it is impossible to determine # if a parameter is independent or not tape._graph = None assert tape._grad_method(1, use_graph=False) == "A"
def test_non_differentiable(self): """Test that a non-differentiable parameter is correctly marked""" psi = np.array([1, 0, 1, 0]) / np.sqrt(2) with QubitParamShiftTape() as tape: qml.QubitStateVector(psi, wires=[0, 1]) qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) qml.CNOT(wires=[0, 1]) qml.probs(wires=[0, 1]) assert tape._grad_method(0) is None assert tape._grad_method(1) == "A" assert tape._grad_method(2) == "A" tape._update_gradient_info() assert tape._par_info[0]["grad_method"] is None assert tape._par_info[1]["grad_method"] == "A" assert tape._par_info[2]["grad_method"] == "A"
def test_controlled_rotation_gradient(self, G, tol): """Test gradient of controlled rotation gates""" dev = qml.device("default.qubit", wires=2) b = 0.123 with QubitParamShiftTape() as tape: qml.QubitStateVector(np.array([1., -1.]) / np.sqrt(2), wires=0) G(b, wires=[0, 1]) qml.expval(qml.PauliX(0)) tape.trainable_params = {1} res = tape.execute(dev) assert np.allclose(res, -np.cos(b / 2), atol=tol, rtol=0) grad = tape.jacobian(dev, method="analytic") expected = np.sin(b / 2) / 2 assert np.allclose(grad, expected, atol=tol, rtol=0) # compare to finite differences numeric_val = tape.jacobian(dev, method="numeric") assert np.allclose(grad, numeric_val, atol=tol, rtol=0)
def test_pauli_rotation_gradient(self, mocker, G, theta, shift, tol): """Tests that the automatic gradients of Pauli rotations are correct.""" spy = mocker.spy(QubitParamShiftTape, "parameter_shift") dev = qml.device("default.qubit", wires=1) with QubitParamShiftTape() as tape: qml.QubitStateVector(np.array([1., -1.]) / np.sqrt(2), wires=0) G(theta, wires=[0]) qml.expval(qml.PauliZ(0)) tape.trainable_params = {1} autograd_val = tape.jacobian(dev, shift=shift, method="analytic") manualgrad_val = (tape.execute(dev, params=[theta + np.pi / 2]) - tape.execute(dev, params=[theta - np.pi / 2])) / 2 assert np.allclose(autograd_val, manualgrad_val, atol=tol, rtol=0) assert spy.call_args[1]["shift"] == shift # compare to finite differences numeric_val = tape.jacobian(dev, shift=shift, method="numeric") assert np.allclose(autograd_val, numeric_val, atol=tol, rtol=0)
def test_non_involutory_variance(self, mocker, tol): """Tests a qubit Hermitian observable that is not involutory""" spy_analytic_var = mocker.spy(QubitParamShiftTape, "parameter_shift_var") spy_numeric = mocker.spy(QubitParamShiftTape, "numeric_pd") spy_execute = mocker.spy(QubitParamShiftTape, "execute_device") dev = qml.device("default.qubit", wires=1) A = np.array([[4, -1 + 6j], [-1 - 6j, 2]]) a = 0.54 with QubitParamShiftTape() as tape: qml.RX(a, wires=0) qml.var(qml.Hermitian(A, 0)) tape.trainable_params = {0} res = tape.execute(dev) expected = (39 / 2) - 6 * np.sin(2 * a) + (35 / 2) * np.cos(2 * a) assert np.allclose(res, expected, atol=tol, rtol=0) spy_execute.call_args_list = [] # circuit jacobians gradA = tape.jacobian(dev, method="analytic") spy_analytic_var.assert_called() spy_numeric.assert_not_called() assert len(spy_execute.call_args_list) == 1 + 4 * 1 spy_execute.call_args_list = [] gradF = tape.jacobian(dev, method="numeric") spy_numeric.assert_called() assert len(spy_execute.call_args_list) == 2 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)