def cost_fn(a, p, device): tape = JacobianTape() with tape: qml.RX(a, wires=0) U3(*p, wires=0) qml.expval(qml.PauliX(0)) tape = tape.expand() assert [i.name for i in tape.operations] == ["RX", "Rot", "PhaseShift"] assert np.all( tape.get_parameters() == [a, p[2], p[0], -p[2], p[1] + p[2]]) return tape.execute(device=device)
def test_get_parameters(self): """Test that the get parameters function correctly sets and returns the trainable parameters""" a = torch.tensor(0.1, requires_grad=True) b = torch.tensor(0.2) c = torch.tensor(0.3, requires_grad=True) d = 0.4 with TorchInterface.apply(JacobianTape()) as tape: qml.Rot(a, b, c, wires=0) qml.RX(d, wires=1) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliX(0)) assert tape.trainable_params == {0, 2} assert np.all(tape.get_parameters() == [a, c])
def test_execution(self): """Test execution""" a = tf.Variable(0.1) dev = qml.device("default.qubit", wires=1) with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape()) as qtape: qml.RY(a, wires=0) qml.RX(0.2, wires=0) qml.expval(qml.PauliZ(0)) assert qtape.trainable_params == {0} res = qtape.execute(dev) assert isinstance(res, tf.Tensor) assert res.shape == (1, )
def test_return_state_hessian_error(self): """Test error raised if circuit returns the state.""" psi = np.array([1, 0, 1, 0]) / np.sqrt(2) with JacobianTape() 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.state() with pytest.raises( ValueError, match=r"The Hessian method does not support circuits that return the state", ): tape.hessian(None)
def test_ragged_output(self): """Test that the Jacobian is correctly returned for a tape with ragged output""" dev = qml.device("default.qubit", wires=3) params = [1.0, 1.0, 1.0] with JacobianTape() as tape: qml.RX(params[0], wires=[0]) qml.RY(params[1], wires=[1]) qml.RZ(params[2], wires=[2]) qml.CNOT(wires=[0, 1]) qml.probs(wires=0) qml.probs(wires=[1, 2]) res = tape.jacobian(dev) assert res.shape == (6, 3)
def test_numeric_unknown_order(self): """Test that an exception is raised if the finite-difference order is not supported""" dev = qml.device("default.qubit", wires=2) params = [0.1, 0.2] with JacobianTape() as tape: qml.RX(1, wires=[0]) qml.RY(1, wires=[1]) qml.RZ(1, wires=[2]) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliZ(2)) with pytest.raises(ValueError, match="Order must be 1 or 2"): tape.jacobian(dev, order=3)
def test_repeated_interface_construction(self): """Test that the interface is correctly applied multiple times""" with TFInterface.apply(JacobianTape()) as tape: qml.RX(0.5, wires=0) qml.expval(qml.PauliX(0)) assert tape.interface == "tf" assert isinstance(tape, TFInterface) assert tape.__bare__ == JacobianTape assert tape.dtype is tf.float64 TFInterface.apply(tape, dtype=tf.float32) assert tape.interface == "tf" assert isinstance(tape, TFInterface) assert tape.__bare__ == JacobianTape assert tape.dtype is tf.float32
def test_get_parameters(self): """Test that the get_parameters function correctly gets the trainable parameters and all parameters, depending on the trainable_only argument""" a = np.array(0.1, requires_grad=True) b = np.array(0.2, requires_grad=False) c = np.array(0.3, requires_grad=True) d = np.array(0.4, requires_grad=False) with AutogradInterface.apply(JacobianTape()) as tape: qml.Rot(a, b, c, wires=0) qml.RX(d, wires=1) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliX(0)) assert tape.trainable_params == [0, 2] assert np.all(tape.get_parameters(trainable_only=True) == [a, c]) assert np.all(tape.get_parameters(trainable_only=False) == [a, b, c, d])
def test_execution_on_tf_device(self): """Test execution on a TF device""" tf = pytest.importorskip("tensorflow") a = torch.tensor(0.1, requires_grad=True) dev = qml.device("default.qubit.tf", wires=1) with TorchInterface.apply(JacobianTape()) as tape: qml.RY(a, wires=0) qml.RX(torch.tensor(0.2), wires=0) qml.expval(qml.PauliZ(0)) assert tape.trainable_params == [0] res = tape.execute(dev) assert isinstance(res, torch.Tensor) assert res.shape == (1, )
def test_analytic_method(self, mocker): """Test that calling the Jacobian with method=analytic correctly calls the analytic_pd method""" mock = mocker.patch("pennylane.tape.JacobianTape._grad_method") mock.return_value = "A" with JacobianTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[0]) qml.expval(qml.PauliY(0)) dev = qml.device("default.qubit", wires=1) tape.analytic_pd = mocker.Mock() tape.analytic_pd.return_value = [[QuantumTape()], lambda res: np.array([1.0])] tape.jacobian(dev, method="analytic") assert len(tape.analytic_pd.call_args_list) == 2
def test_non_differentiable_error(self): """Test error raised if attempting to differentiate with respect to a non-differentiable argument""" psi = np.array([1, 0, 1, 0]) / np.sqrt(2) with JacobianTape() 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]) # by default all parameters are assumed to be trainable with pytest.raises( ValueError, match=r"Cannot differentiate with respect to parameter\(s\) {0}" ): tape.hessian(None)
def expand(self): """Expand the measurement of an observable to a unitary rotation and a measurement in the computational basis. Returns: .JacobianTape: a quantum tape containing the operations required to diagonalize the observable **Example** Consider a measurement process consisting of the expectation value of an Hermitian observable: >>> H = np.array([[1, 2], [2, 4]]) >>> obs = qml.Hermitian(H, wires=['a']) >>> m = MeasurementProcess(Expectation, obs=obs) Expanding this out: >>> tape = m.expand() We can see that the resulting tape has the qubit unitary applied, and a measurement process with no observable, but the eigenvalues specified: >>> print(tape.operations) [QubitUnitary(array([[-0.89442719, 0.4472136 ], [ 0.4472136 , 0.89442719]]), wires=['a'])] >>> print(tape.measurements[0].eigvals) [0. 5.] >>> print(tape.measurements[0].obs) None """ if self.obs is None: raise NotImplementedError( "Cannot expand a measurement process with no observable.") from pennylane.tape import JacobianTape # pylint: disable=import-outside-toplevel with JacobianTape() as tape: self.obs.diagonalizing_gates() MeasurementProcess(self.return_type, wires=self.obs.wires, eigvals=self.obs.eigvals) return tape
def test_device_method(self, mocker): """Test that calling the Jacobian with method=device correctly calls the device_pd method""" with JacobianTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[0]) qml.expval(qml.PauliY(0)) dev = qml.device("default.qubit", wires=1) dev.jacobian = mocker.Mock() tape.device_pd(dev) dev.jacobian.assert_called_once() dev.jacobian = mocker.Mock() tape.jacobian(dev, method="device") dev.jacobian.assert_called_once()
def test_get_parameters(self): """Test that the get parameters function correctly sets and returns the trainable parameters""" a = tf.Variable(0.1) b = tf.constant(0.2) c = tf.Variable(0.3) d = 0.4 with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape()) as qtape: qml.Rot(a, b, c, wires=0) qml.RX(d, wires=1) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliX(0)) assert qtape.trainable_params == [0, 2] assert np.all(qtape.get_parameters() == [a, c])
def test_ragged_differentiation(self, monkeypatch, tol): """Tests correct output shape and evaluation for a tape with prob and expval outputs""" dev = qml.device("default.qubit.tf", wires=2) x = tf.Variable(0.543, dtype=tf.float64) y = tf.Variable(-0.654, dtype=tf.float64) def _asarray(args, dtype=tf.float64): res = [tf.reshape(i, [-1]) for i in args] res = tf.concat(res, axis=0) return tf.cast(res, dtype=dtype) # The current DefaultQubitTF device provides an _asarray method that does # not work correctly for ragged arrays. For ragged arrays, we would like _asarray to # flatten the array. Here, we patch the _asarray method on the device to achieve this # behaviour; once the tape has moved from the beta folder, we should implement # this change directly in the device. monkeypatch.setattr(dev, "_asarray", _asarray) with tf.GradientTape() as tape: with JacobianTape() as qtape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) qml.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_single_output_value(self, tol): """Tests correct Jacobian and output shape for a CV tape with a single output""" dev = qml.device("default.gaussian", wires=2) n = 0.543 a = -0.654 with JacobianTape() as tape: qml.ThermalState(n, wires=0) qml.Displacement(a, 0, wires=0) qml.var(qml.NumberOperator(0)) tape.trainable_params = {0, 1} res = tape.jacobian(dev) assert res.shape == (1, 2) expected = np.array([2 * a**2 + 2 * n + 1, 2 * a * (2 * n + 1)]) assert np.allclose(res, expected, atol=tol, rtol=0)
def cost_fn(a, p, device): tape = JacobianTape() with tape: qml.RX(a, wires=0) U3(*p, wires=0) qml.expval(qml.PauliX(0)) tape = AutogradInterface.apply(tape.expand()) assert tape.trainable_params == {1, 2, 3, 4} assert [i.name for i in tape.operations] == ["RX", "Rot", "PhaseShift"] assert np.all( np.array(tape.get_parameters()) == [p[2], p[0], -p[2], p[1] + p[2]]) return tape.execute(device=device)
def test_no_trainable_parameters(self, mocker, tol): """Test evaluation if there are no trainable parameters""" spy = mocker.spy(JacobianTape, "jacobian") dev = qml.device("default.qubit.tf", wires=2) with tf.GradientTape() as tape: with JacobianTape() as qtape: qml.RY(0.2, wires=0) qml.RX(tf.constant(0.1), wires=0) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) qml.expval(qml.PauliZ(1)) res = qtape.execute(dev) assert res.shape == (2, ) assert isinstance(res, tf.Tensor) spy.assert_not_called()
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 JacobianTape() 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) 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_single_expectation_value_with_argnum_all(self, tol): """Tests correct output shape and evaluation for a tape with a single expval output where all parameters are chose to compute the jacobian""" dev = qml.device("default.qubit", wires=2) x = 0.543 y = -0.654 with JacobianTape() 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, argnum=[0, 1]) # <--- we choose both trainable parameters 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_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 JacobianTape() 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) 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_multiple_output_values(self, tol): """Tests correct output shape and evaluation for a tape with multiple outputs""" dev = qml.device("default.gaussian", wires=2) n = 0.543 a = -0.654 with JacobianTape() as tape: qml.ThermalState(n, wires=0) qml.Displacement(a, 0, wires=0) qml.expval(qml.NumberOperator(1)) qml.var(qml.NumberOperator(0)) tape.trainable_params = {0, 1} res = tape.jacobian(dev) assert res.shape == (2, 2) expected = np.array([[0, 0], [2 * a ** 2 + 2 * n + 1, 2 * a * (2 * n + 1)]]) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_no_trainable_parameters(self, tol): """Test evaluation if there are no trainable parameters""" dev = qml.device("default.qubit", wires=2) with tf.GradientTape() as tape: with TFInterface.apply(JacobianTape()) as qtape: qml.RY(0.2, wires=0) qml.RX(tf.constant(0.1), wires=0) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) qml.expval(qml.PauliZ(1)) assert qtape.trainable_params == [] res = qtape.execute(dev) assert res.shape == (2, ) assert isinstance(res, tf.Tensor)
def test_matrix_parameter(self, U, mocker, tol): """Test that the TF interface works correctly with a matrix parameter""" spy = mocker.spy(JacobianTape, "jacobian") a = tf.Variable(0.1, dtype=tf.float64) dev = qml.device("default.qubit.tf", wires=2) with tf.GradientTape() as tape: with JacobianTape() as qtape: qml.QubitUnitary(U, wires=0) qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) res = qtape.execute(dev) assert np.allclose(res, -tf.cos(a), atol=tol, rtol=0) res = tape.jacobian(res, a) assert np.allclose(res, tf.sin(a), atol=tol, rtol=0) spy.assert_not_called()
def test_y0(self, mocker): """Test that if first order finite differences is used, then the tape is executed only once using the current parameter values.""" dev = qml.device("default.qubit", wires=2) execute_spy = mocker.spy(dev, "execute") numeric_spy = mocker.spy(JacobianTape, "numeric_pd") with JacobianTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[0]) qml.expval(qml.PauliZ(0)) res = tape.jacobian(dev, order=1) # the execute device method is called once per parameter, # plus one global call assert len(execute_spy.call_args_list) == tape.num_params + 1 assert "y0" in numeric_spy.call_args_list[0][1] assert "y0" in numeric_spy.call_args_list[1][1]
def test_no_trainable_parameters(self, mocker): """Test that if the tape has no trainable parameters, no subroutines are called and the returned Jacobian is empty""" numeric_spy = mocker.spy(JacobianTape, "numeric_pd") analytic_spy = mocker.spy(JacobianTape, "analytic_pd") with JacobianTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=2) tape.trainable_params = {} res = tape.jacobian(dev) assert res.size == 0 assert np.all(res == np.array([[]])) numeric_spy.assert_not_called() analytic_spy.assert_not_called()
def test_choose_params_and_methods(self, diff_methods, argnum): """Test that the _choose_params_and_methods helper method returns expected results""" tape = JacobianTape() tape._trainable_params = list(range(len(diff_methods))) res = list(tape._choose_params_with_methods(diff_methods, argnum)) num_all_params = len(diff_methods) assert all(k in range(num_all_params) for k, _ in res) assert all(v in diff_methods for _, v in res) if argnum is None: num_params = num_all_params elif isinstance(argnum, int): num_params = 1 else: num_params = len(argnum) assert len(res) == num_params
def test_jacobian_options(self, mocker, tol): """Test setting jacobian options""" spy = mocker.spy(JacobianTape, "numeric_pd") a = torch.tensor([0.1, 0.2], requires_grad=True) dev = qml.device("default.qubit", wires=1) with TorchInterface.apply(JacobianTape()) as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) tape.jacobian_options = {"h": 1e-8, "order": 2} res = tape.execute(dev) res.backward() for args in spy.call_args_list: assert args[1]["order"] == 2 assert args[1]["h"] == 1e-8
def test_matrix_parameter(self, U, tol): """Test that the Torch interface works correctly with a matrix parameter""" a_val = 0.1 a = torch.tensor(a_val, requires_grad=True) dev = qml.device("default.qubit", wires=2) with TorchInterface.apply(JacobianTape()) as tape: qml.QubitUnitary(U, wires=0) qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) assert tape.trainable_params == {1} res = tape.execute(dev) assert np.allclose(res.detach().numpy(), -np.cos(a_val), atol=tol, rtol=0) res.backward() assert np.allclose(a.grad, np.sin(a_val), atol=tol, rtol=0)
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 JacobianTape() 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) == "F" tape._update_gradient_info() assert tape._par_info[0]["grad_method"] is None assert tape._par_info[1]["grad_method"] == "F" assert tape._par_info[2]["grad_method"] == "F"