def test_first_order_observable(self, tol): """Test variance of a first order CV observable""" dev = qml.device("default.gaussian", wires=1) r = 0.543 phi = -0.654 with CVParamShiftTape() as tape: qml.Squeezing(r, 0, wires=0) qml.Rotation(phi, wires=0) var(qml.X(0)) tape.trainable_params = {0, 2} res = tape.execute(dev) expected = np.exp(2 * r) * np.sin(phi) ** 2 + np.exp(-2 * r) * np.cos(phi) ** 2 assert np.allclose(res, expected, atol=tol, rtol=0) # circuit jacobians grad_F = tape.jacobian(dev, method="numeric") grad_A = tape.jacobian(dev, method="analytic") 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 np.allclose(grad_A, expected, atol=tol, rtol=0) assert np.allclose(grad_F, expected, atol=tol, rtol=0)
def test_measurement_expansion(self): """Test that measurement expansion works as expected""" with QuantumTape() as tape: # expands into 2 PauliX qml.BasisState(np.array([1, 1]), wires=[0, "a"]) qml.CNOT(wires=[0, "a"]) qml.RY(0.2, wires="a") probs(wires=0) # expands into RY on wire b expval(qml.PauliZ("a") @ qml.Hadamard("b")) # expands into QubitUnitary on wire 0 var(qml.Hermitian(np.array([[1, 2], [2, 4]]), wires=[0])) new_tape = tape.expand(expand_measurements=True) assert len(new_tape.operations) == 6 expected = [ qml.operation.Probability, qml.operation.Expectation, qml.operation.Variance ] assert [ m.return_type is r for m, r in zip(new_tape.measurements, expected) ] expected = [None, None, None] assert [m.obs is r for m, r in zip(new_tape.measurements, expected)] expected = [None, [1, -1, -1, 1], [0, 5]] assert [ m.eigvals is r for m, r in zip(new_tape.measurements, expected) ]
def test_no_poly_xp_support_variance(self, mocker, monkeypatch, caplog): """Test that if a device does not support PolyXP and the variance parameter-shift rule is required, we fallback to finite differences.""" dev = qml.device("default.gaussian", wires=1) monkeypatch.delitem(dev._observable_map, "PolyXP") with CVParamShiftTape() as tape: qml.Rotation(1.0, wires=[0]) var(qml.X(0)) tape.trainable_params = {0} assert tape.analytic_pd == tape.parameter_shift_var spy1 = mocker.spy(tape, "parameter_shift_first_order") spy2 = mocker.spy(tape, "parameter_shift_second_order") spy_numeric = mocker.spy(tape, "numeric_pd") with pytest.warns(UserWarning, match="does not support the PolyXP observable"): tape.jacobian(dev, method="analytic") spy1.assert_not_called() spy2.assert_not_called() spy_numeric.assert_called()
def test_gradients_gaussian_circuit(self, op, obs, mocker, tol): """Tests that the gradients of circuits of gaussian gates match between the finite difference and analytic methods.""" tol = 1e-2 args = np.linspace(0.2, 0.5, op.num_params) with CVParamShiftTape() as tape: qml.Displacement(0.5, 0, wires=0) op(*args, wires=range(op.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) var(obs(wires=0)) dev = qml.device("default.gaussian", wires=2) res = tape.execute(dev) tape._update_gradient_info() tape.trainable_params = set(range(2, 2 + op.num_params)) # check that every parameter is analytic for i in range(op.num_params): assert tape._par_info[2 + i]["grad_method"][0] == "A" spy = mocker.spy(CVParamShiftTape, "parameter_shift_first_order") grad_F = tape.jacobian(dev, method="numeric") grad_A = tape.jacobian(dev, method="analytic") grad_A2 = tape.jacobian(dev, method="analytic", force_order2=True) assert np.allclose(grad_A2, grad_F, atol=tol, rtol=0) assert np.allclose(grad_A, grad_F, atol=tol, rtol=0)
def test_error_analytic_second_order(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) with CVParamShiftTape() as tape: qml.Displacement(1.0, 0, wires=0) var(qml.NumberOperator(0)) tape.trainable_params = {0} with pytest.raises(ValueError, match=r"cannot be used with the argument\(s\) \{0\}"): tape.jacobian(dev, method="analytic")
def test_var_exception(self): """Tests that an exception is raised when variance is used with the ReversibleTape.""" # TODO: remove this test when this support is added dev = qml.device("default.qubit", wires=2) with ReversibleTape() as tape: qml.PauliX(wires=0) qml.RX(0.542, wires=0) var(qml.PauliZ(0)) with pytest.raises(ValueError, match="Variance is not supported"): tape.jacobian(dev)
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]) expval(qml.PauliZ(0)), 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_displaced_thermal_mean_photon_variance(self, tol): """Test gradient of the photon variance of a displaced thermal state""" dev = qml.device("default.gaussian", wires=1) n = 0.12 a = 0.105 with CVParamShiftTape() as tape: qml.ThermalState(n, wires=0) qml.Displacement(a, 0, wires=0) var(qml.TensorN(wires=[0])) tape.trainable_params = {0, 1} grad = tape.jacobian(dev) expected = np.array([2 * a ** 2 + 2 * n + 1, 2 * a * (2 * n + 1)]) assert np.allclose(grad, expected, atol=tol, rtol=0)
def test_involutory_and_noninvolutory_variance(self, mocker, tol): """Tests a qubit Hermitian observable that is not involutory alongside a involutory observable.""" 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=2) A = np.array([[4, -1 + 6j], [-1 - 6j, 2]]) a = 0.54 with QubitParamShiftTape() as tape: qml.RX(a, wires=0) qml.RX(a, wires=1) var(qml.PauliZ(0)) 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_var_expectation_values(self, tol): """Tests correct output shape and evaluation for a tape with expval and var 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]) expval(qml.PauliZ(0)) var(qml.PauliX(1)) res = tape.jacobian(dev, method="analytic") assert res.shape == (2, 2) expected = np.array([[-np.sin(x), 0], [0, -2 * np.cos(y) * np.sin(y)]]) assert np.allclose(res, expected, atol=tol, rtol=0)
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]) var(qml.PauliZ(0)) expval(qml.PauliZ(1)) 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_squeezed_mean_photon_variance(self, tol): """Test gradient of the photon variance of a displaced thermal state""" dev = qml.device("default.gaussian", wires=1) r = 0.12 phi = 0.105 with CVParamShiftTape() as tape: qml.Squeezing(r, 0, wires=0) qml.Rotation(phi, wires=0) var(qml.X(wires=[0])) tape.trainable_params = {0, 2} grad = tape.jacobian(dev, method="analytic") 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 np.allclose(grad, expected, atol=tol, rtol=0)
def test_variance(self): """If the variance of the observable is first order, then parameter-shift is supported. If the observable is second order, however, only finite-differences is supported.""" with CVParamShiftTape() as tape: qml.Rotation(1.0, wires=[0]) var(qml.P(0)) # first order assert tape._grad_method(0) == "A" with CVParamShiftTape() as tape: qml.Rotation(1.0, wires=[0]) var(qml.NumberOperator(0)) # second order assert tape._grad_method(0) == "F" with CVParamShiftTape() as tape: qml.Rotation(1.0, wires=[0]) qml.Rotation(1.0, wires=[1]) qml.Beamsplitter(0.5, 0.0, wires=[0, 1]) var(qml.NumberOperator(0)) # second order expval(qml.NumberOperator(1)) assert tape._grad_method(0) == "F" assert tape._grad_method(1) == "F" assert tape._grad_method(2) == "F" assert tape._grad_method(3) == "F"
def test_second_order_cv(self, tol): """Test variance of a second order CV expectation value""" dev = qml.device("default.gaussian", wires=1) n = 0.12 a = 0.765 with CVParamShiftTape() as tape: qml.ThermalState(n, wires=0) qml.Displacement(a, 0, wires=0) var(qml.NumberOperator(0)) tape.trainable_params = {0, 1} res = tape.execute(dev) expected = n ** 2 + n + np.abs(a) ** 2 * (1 + 2 * n) assert np.allclose(res, expected, atol=tol, rtol=0) # circuit jacobians grad_F = tape.jacobian(dev, method="numeric") expected = np.array([[2 * a ** 2 + 2 * n + 1, 2 * a * (2 * n + 1)]]) assert np.allclose(grad_F, expected, atol=tol, rtol=0)
def test_expval_and_variance(self, tol): """Test that the gradient works for a combination of CV expectation values and variances""" dev = qml.device("default.gaussian", wires=3) a, b = [0.54, -0.423] with CVParamShiftTape() as tape: 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]) var(qml.X(0)) expval(qml.X(1)) var(qml.X(2)) tape.trainable_params = {2, 4} # jacobians must match grad_F = tape.jacobian(dev, method="numeric") grad_A = tape.jacobian(dev, method="analytic") assert np.allclose(grad_A, grad_F, atol=tol, rtol=0)
def test_involutory_variance(self, mocker, tol): """Tests qubit observable that are 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 = 0.54 with QubitParamShiftTape() as tape: qml.RX(a, wires=0) 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_tensor_observables_tensor_matmul(self): """Test that tensor observables are correctly processed from the annotated queue". Here, wetest multiple tensor observables constructed via matmul between two tensor observables.""" with QuantumTape() as tape: op = qml.RX(1.0, wires=0) t_obs1 = qml.PauliZ(0) @ qml.PauliX(1) t_obs2 = qml.PauliY(2) @ qml.PauliZ(3) t_obs = t_obs1 @ t_obs2 m = var(t_obs) assert tape.operations == [op] assert tape.observables == [t_obs] assert tape.measurements[0].return_type is qml.operation.Variance assert tape.measurements[0].obs is t_obs