def test_ry_gradient(self, par, mocker, tol): """Test that the gradient of the RY gate matches the exact analytic formula. Further, make sure the correct gradient methods are being called.""" with ReversibleTape() as tape: qml.RY(par, wires=[0]) expval(qml.PauliX(0)) tape.trainable_params = {0} dev = qml.device("default.qubit", wires=1) spy_numeric = mocker.spy(tape, "numeric_pd") spy_analytic = mocker.spy(tape, "analytic_pd") # gradients exact = np.cos(par) grad_F = tape.jacobian(dev, method="numeric") spy_numeric.assert_called() spy_analytic.assert_not_called() spy_device = mocker.spy(tape, "execute_device") grad_A = tape.jacobian(dev, method="analytic") spy_analytic.assert_called() spy_device.assert_called_once( ) # check that the state was only pre-computed once # different methods must agree assert np.allclose(grad_F, exact, atol=tol, rtol=0) assert np.allclose(grad_A, exact, atol=tol, rtol=0)
def test_multiple_rx_gradient(self, tol): """Tests that the gradient of multiple RX gates in a circuit yeilds the correct result.""" dev = qml.device("default.qubit", wires=3) params = np.array([np.pi, np.pi / 2, np.pi / 3]) with ReversibleTape() as tape: qml.RX(params[0], wires=0) qml.RX(params[1], wires=1) qml.RX(params[2], wires=2) for idx in range(3): expval(qml.PauliZ(idx)) circuit_output = tape.execute(dev) expected_output = np.cos(params) assert np.allclose(circuit_output, expected_output, atol=tol, rtol=0) # circuit jacobians circuit_jacobian = tape.jacobian(dev, method="analytic") expected_jacobian = -np.diag(np.sin(params)) assert np.allclose(circuit_jacobian, expected_jacobian, atol=tol, rtol=0)
def test_classical_processing(self, mocker, tol): """Test classical processing within the quantum tape""" spy = mocker.spy(QuantumTape, "jacobian") a = tf.Variable(0.1, dtype=tf.float64) b = tf.constant(0.2, dtype=tf.float64) c = tf.Variable(0.3, dtype=tf.float64) dev = qml.device("default.qubit.tf", wires=1) with tf.GradientTape() as tape: with QuantumTape() as qtape: qml.RY(a * c, wires=0) qml.RZ(b, wires=0) qml.RX(c + c**2 + tf.sin(a), wires=0) expval(qml.PauliZ(0)) assert qtape.get_parameters() == [a * c, b, c + c**2 + tf.sin(a)] res = qtape.execute(dev) res = tape.jacobian(res, [a, b, c]) assert isinstance(res[0], tf.Tensor) assert res[1] is None assert isinstance(res[2], tf.Tensor) spy.assert_not_called()
def test_jacobian(self, mocker, tol): """Test jacobian calculation""" spy = mocker.spy(QuantumTape, "jacobian") a = tf.Variable(0.1, dtype=tf.float64) b = tf.Variable(0.2, dtype=tf.float64) dev = qml.device("default.qubit", wires=2) with tf.GradientTape() as tape: with TFInterface.apply(QuantumTape()) as qtape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) expval(qml.PauliY(1)) assert qtape.trainable_params == {0, 1} res = qtape.execute(dev) assert isinstance(res, tf.Tensor) assert res.shape == (2, ) expected = [tf.cos(a), -tf.cos(a) * tf.sin(b)] assert np.allclose(res, expected, atol=tol, rtol=0) res = tape.jacobian(res, [a, b]) expected = [[-tf.sin(a), tf.sin(a) * tf.sin(b)], [0, -tf.cos(a) * tf.cos(b)]] assert np.allclose(res, expected, atol=tol, rtol=0) spy.assert_called()
def test_jacobian_dtype(self, tol): """Test calculating the jacobian with a different datatype. Here, we specify tf.float32, as opposed to the default value of tf.float64.""" a = tf.Variable(0.1, dtype=tf.float32) b = tf.Variable(0.2, dtype=tf.float32) dev = qml.device("default.qubit", wires=2) with tf.GradientTape() as tape: with TFInterface.apply(QuantumTape(), dtype=tf.float32) as qtape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) expval(qml.PauliY(1)) assert qtape.trainable_params == {0, 1} res = qtape.execute(dev) assert isinstance(res, tf.Tensor) assert res.shape == (2, ) assert res.dtype is tf.float32 res = tape.jacobian(res, [a, b]) assert [r.dtype is tf.float32 for r in res]
def test_ragged_differentiation(self, tol): """Tests correct output shape and evaluation for a tape with prob and expval outputs""" dev = qml.device("default.qubit", wires=2) x = tf.Variable(0.543, dtype=tf.float64) y = tf.Variable(-0.654, dtype=tf.float64) with tf.GradientTape() as tape: with TFInterface.apply(QuantumTape()) as qtape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) probs(wires=[1]) res = qtape.execute(dev) expected = np.array([ tf.cos(x), (1 + tf.cos(x) * tf.cos(y)) / 2, (1 - tf.cos(x) * tf.cos(y)) / 2 ]) assert np.allclose(res, expected, atol=tol, rtol=0) res = tape.jacobian(res, [x, y]) expected = np.array([ [ -tf.sin(x), -tf.sin(x) * tf.cos(y) / 2, tf.cos(y) * tf.sin(x) / 2 ], [0, -tf.cos(x) * tf.sin(y) / 2, tf.cos(x) * tf.sin(y) / 2], ]) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_ragged_differentiation(self, tol): """Tests correct output shape and evaluation for a tape with prob and expval outputs""" dev = qml.device("default.qubit", wires=2) x_val = 0.543 y_val = -0.654 x = torch.tensor(x_val, requires_grad=True) y = torch.tensor(y_val, requires_grad=True) with TorchInterface.apply(QuantumTape()) as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) probs(wires=[1]) res = tape.execute(dev) expected = np.array([ np.cos(x_val), (1 + np.cos(x_val) * np.cos(y_val)) / 2, (1 - np.cos(x_val) * np.cos(y_val)) / 2, ]) assert np.allclose(res.detach().numpy(), expected, atol=tol, rtol=0) loss = torch.sum(res) loss.backward() expected = np.array([ -np.sin(x_val) + -np.sin(x_val) * np.cos(y_val) / 2 + np.cos(y_val) * np.sin(x_val) / 2, -np.cos(x_val) * np.sin(y_val) / 2 + np.cos(x_val) * np.sin(y_val) / 2, ]) assert np.allclose(x.grad, expected[0], atol=tol, rtol=0) assert np.allclose(y.grad, expected[1], atol=tol, rtol=0)
def test_classical_processing(self, tol): """Test classical processing within the quantum tape""" p_val = [0.1, 0.2] params = torch.tensor(p_val, requires_grad=True) dev = qml.device("default.qubit", wires=1) with TorchInterface.apply(QuantumTape()) as tape: qml.RY(params[0] * params[1], wires=0) qml.RZ(0.2, wires=0) qml.RX(params[1] + params[1]**2 + torch.sin(params[0]), wires=0) expval(qml.PauliZ(0)) assert tape.trainable_params == {0, 2} tape_params = [i.detach().numpy() for i in tape.get_parameters()] assert np.allclose( tape_params, [p_val[0] * p_val[1], p_val[1] + p_val[1]**2 + np.sin(p_val[0])], atol=tol, rtol=0, ) res = tape.execute(dev) res.backward() assert isinstance(params.grad, torch.Tensor) assert params.shape == (2, )
def test_jacobian_dtype(self, tol): """Test calculating the jacobian with a different datatype""" a_val = 0.1 b_val = 0.2 a = torch.tensor(a_val, requires_grad=True, dtype=torch.float32) b = torch.tensor(b_val, requires_grad=True, dtype=torch.float32) dev = qml.device("default.qubit", wires=2) with TorchInterface.apply(QuantumTape(), dtype=torch.float32) as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) expval(qml.PauliY(1)) assert tape.trainable_params == {0, 1} res = tape.execute(dev) assert isinstance(res, torch.Tensor) assert res.shape == (2, ) assert res.dtype is torch.float32 loss = torch.sum(res) loss.backward() assert a.grad.dtype is torch.float32 assert b.grad.dtype is torch.float32
def test_diff_circuit_construction(self, mocker): """Test that the diff circuit is correctly constructed""" dev = qml.device("default.qubit", wires=2) with ReversibleTape() as tape: qml.PauliX(wires=0) qml.RX(0.542, wires=0) qml.RY(0.542, wires=0) expval(qml.PauliZ(0)) spy = mocker.spy(dev, "execute") tape.jacobian(dev) tape0 = spy.call_args_list[0][0][0] tape1 = spy.call_args_list[1][0][0] tape2 = spy.call_args_list[2][0][0] assert tape0 is tape assert len(tape1.operations) == 3 assert not tape1.measurements assert tape1.operations[0].name == "RY.inv" assert tape1.operations[1].name == "PauliX" assert tape1.operations[2].name == "RY" assert len(tape2.operations) == 1 assert not tape2.measurements assert tape2.operations[0].name == "PauliY"
def test_beamsplitter_gradient(self, mocker, tol): """Test the gradient of the beamsplitter gate""" dev = qml.device("default.gaussian", wires=2, hbar=hbar) alpha = 0.5643 theta = 0.23354 with CVParamShiftTape() as tape: qml.Displacement(alpha, 0.0, wires=[0]) qml.Beamsplitter(theta, 0.0, wires=[0, 1]) expval(qml.X(0)) tape._update_gradient_info() tape.trainable_params = {2} spy1 = mocker.spy(CVParamShiftTape, "parameter_shift_first_order") spy2 = mocker.spy(CVParamShiftTape, "parameter_shift_second_order") grad_A = tape.jacobian(dev, method="analytic") spy1.assert_called() spy2.assert_not_called() grad_A2 = tape.jacobian(dev, method="analytic", force_order2=True) spy2.assert_called() expected = -hbar * alpha * np.sin(theta) assert np.allclose(grad_A, expected, atol=tol, rtol=0) assert np.allclose(grad_A2, expected, 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]) 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_multiple_contexts(self): """Test multiple contexts with a single tape.""" ops = [] obs = [] with QuantumTape() as tape: ops += [qml.RX(0.432, wires=0)] a = qml.Rot(0.543, 0, 0.23, wires=1) b = qml.CNOT(wires=[2, "a"]) with tape: ops += [qml.RX(0.133, wires=0)] obs += [qml.PauliX(wires="a")] expval(obs[0]) obs += [probs(wires=[0, "a"])] assert len(tape.queue) == 5 assert tape.operations == ops assert tape.observables == obs assert tape.output_dim == 5 assert a not in tape.operations assert b not in tape.operations assert tape.wires == qml.wires.Wires([0, "a"])
def cost(a, b, device): with AutogradInterface.apply(QuantumTape()) as tape: qml.RY(a, wires=0) qml.RX(b, wires=0) expval(qml.PauliZ(0)) assert tape.trainable_params == {0} return tape.execute(device)
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_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 cost(a, b, c, device): with QuantumTape() as tape: qml.RY(a * c, wires=0) qml.RZ(b, wires=0) qml.RX(c + c**2 + np.sin(a), wires=0) expval(qml.PauliZ(0)) return tape.execute(device)
def test_multiple_squeezing_gradient(self, mocker, tol): """Test that the gradient of a circuit with two squeeze gates is correct.""" dev = qml.device("default.gaussian", wires=2, hbar=hbar) r0, phi0, r1, phi1 = [0.4, -0.3, -0.7, 0.2] with CVParamShiftTape() as tape: qml.Squeezing(r0, phi0, wires=[0]) qml.Squeezing(r1, phi1, wires=[0]) expval(qml.NumberOperator(0)) # second order tape._update_gradient_info() spy2 = mocker.spy(CVParamShiftTape, "parameter_shift_second_order") grad_A2 = tape.jacobian(dev, method="analytic", force_order2=True) spy2.assert_called() # check against the known analytic formula expected = np.zeros([4]) expected[0] = np.cosh(2 * r1) * np.sinh(2 * r0) + np.cos(phi0 - phi1) * np.cosh( 2 * r0 ) * np.sinh(2 * r1) expected[1] = -0.5 * np.sin(phi0 - phi1) * np.sinh(2 * r0) * np.sinh(2 * r1) expected[2] = np.cos(phi0 - phi1) * np.cosh(2 * r1) * np.sinh(2 * r0) + np.cosh( 2 * r0 ) * np.sinh(2 * r1) expected[3] = 0.5 * np.sin(phi0 - phi1) * np.sinh(2 * r0) * np.sinh(2 * r1) assert np.allclose(grad_A2, expected, atol=tol, rtol=0)
def test_jacobian(self, mocker, tol): """Test jacobian calculation""" spy = mocker.spy(QuantumTape, "jacobian") a = np.array(0.1, requires_grad=True) b = np.array(0.2, requires_grad=True) def cost(a, b, device): with QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=0) expval(qml.PauliZ(0)) expval(qml.PauliY(0)) return tape.execute(device) dev = qml.device("default.qubit.autograd", wires=2) res = qml.jacobian(cost)(a, b, device=dev) spy.assert_not_called() assert res.shape == (2, 2) # compare to standard tape jacobian with QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=0) expval(qml.PauliZ(0)) expval(qml.PauliY(0)) expected = tape.jacobian(dev) assert expected.shape == (2, 2) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_displacement_gradient(self, mocker, tol): """Test the gradient of the displacement gate""" dev = qml.device("default.gaussian", wires=2, hbar=hbar) r = 0.5643 phi = 0.23354 with CVParamShiftTape() as tape: qml.Displacement(r, phi, wires=[0]) expval(qml.X(0)) tape._update_gradient_info() tape.trainable_params = {0, 1} spy1 = mocker.spy(CVParamShiftTape, "parameter_shift_first_order") spy2 = mocker.spy(CVParamShiftTape, "parameter_shift_second_order") grad_A = tape.jacobian(dev, method="analytic") spy1.assert_called() spy2.assert_not_called() grad_A2 = tape.jacobian(dev, method="analytic", force_order2=True) spy2.assert_called() expected = [hbar * np.cos(phi), -hbar * r * np.sin(phi)] assert np.allclose(grad_A, expected, atol=tol, rtol=0) assert np.allclose(grad_A2, expected, atol=tol, rtol=0)
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_no_poly_xp_support(self, mocker, monkeypatch, caplog): """Test that if a device does not support PolyXP and the second-order 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]) expval(qml.NumberOperator(0)) tape.trainable_params = {0} assert tape.analytic_pd == tape.parameter_shift spy1 = mocker.spy(tape, "parameter_shift_first_order") spy2 = mocker.spy(tape, "parameter_shift_second_order") spy_transform = mocker.spy(qml.operation.CVOperation, "heisenberg_tr") 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_called() spy_transform.assert_not_called() spy_numeric.assert_called()
def test_graph_creation(self, mocker): """Test that the circuit graph is correctly created""" spy = mocker.spy(NewCircuitGraph, "__init__") with QuantumTape() as tape: op = qml.RX(1.0, wires=0) obs = qml.PauliZ(1) expval(obs) # graph has not yet been created assert tape._graph is None spy.assert_not_called() # requesting the graph creates it g = tape.graph assert g.operations == [op] assert g.observables == [obs] assert tape._graph is not None spy.assert_called_once() # calling the graph property again does # not reconstruct the graph g2 = tape.graph assert g2 is g spy.assert_called_once()
def cost(a, b, device): with QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=0) expval(qml.PauliZ(0)) expval(qml.PauliY(0)) return tape.execute(device)
def test_interface_str(self): """Test that the interface string is correctly identified as autograd""" with AutogradInterface.apply(QuantumTape()) as tape: qml.RX(0.5, wires=0) expval(qml.PauliX(0)) assert tape.interface == "autograd" assert isinstance(tape, AutogradInterface)
def cost(a, device): with AutogradInterface.apply(QuantumTape()) as qtape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) expval(qml.PauliZ(0)) qtape.jacobian_options = {"h": 1e-8, "order": 2} return qtape.execute(dev)
def func(x, y): global op1, op2, op3, m1, m2 op1 = qml.RX(x, wires=0) op2 = qml.RY(y, wires=1) op3 = qml.CNOT(wires=[0, 1]) m1 = expval(qml.PauliZ(0)) m2 = expval(qml.PauliX(1)) return [m1, m2]
def cost(x, y, device): with QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(0)) probs(wires=[1]) return tape.execute(device)
def get_tape(caching): """Creates a simple quantum tape""" with QuantumTape(caching=caching) as tape: qml.QubitUnitary(np.eye(2), wires=0) qml.RX(0.1, wires=0) qml.RX(0.2, wires=1) qml.CNOT(wires=[0, 1]) expval(qml.PauliZ(wires=1)) return tape
def test_measurement_before_operation(self): """Test that an exception is raised if a measurement occurs before a operation""" with pytest.raises(ValueError, match="must occur prior to any measurements"): with QuantumTape() as tape: expval(qml.PauliZ(wires=1)) qml.RX(0.5, wires=0) expval(qml.PauliZ(wires=1))