def test_jacobian_agrees_backprop_parameter_shift(self, tol): """Test that jacobian of a QNode with an attached default.qubit.autograd device gives the correct result with respect to the parameter-shift method""" p = np.array([0.43316321, 0.2162158, 0.75110998, 0.94714242], requires_grad=True) def circuit(x): for i in range(0, len(p), 2): qml.RX(x[i], wires=0) qml.RY(x[i + 1], wires=1) for i in range(2): qml.CNOT(wires=[i, i + 1]) return qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(1)) dev1 = qml.device("default.qubit.autograd", wires=3) dev2 = qml.device("default.qubit.autograd", wires=3) circuit1 = qml.QNode(circuit, dev1, diff_method="backprop", interface="autograd") circuit2 = qml.QNode(circuit, dev2, diff_method="parameter-shift") assert circuit1.diff_options["method"] == "backprop" assert circuit2.diff_options["method"] == "analytic" res = circuit1(p) assert np.allclose(res, circuit2(p), atol=tol, rtol=0) grad_fn = qml.jacobian(circuit1, 0) res = grad_fn(p) assert np.allclose(res, qml.jacobian(circuit2)(p), atol=tol, rtol=0)
def test_numerical_analytic_diff_agree(init_state, tol): """Test that the finite difference and parameter shift rule provide the same Jacobian.""" w = 4 dev = qml.device("default.qubit", wires=w) state = init_state(w) def circuit(x, y, z): for i in range(w): qml.RX(x, wires=i) qml.PhaseShift(z, wires=i) qml.RY(y, wires=i) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[1, 2]) qml.CNOT(wires=[2, 3]) return qml.probs(wires=[1, 3]) params = [0.543, -0.765, -0.3] circuit_F = qml.QNode(circuit, dev, diff_method="finite-diff") circuit_A = qml.QNode(circuit, dev, diff_method="parameter-shift") res_F = qml.jacobian(circuit_F)(*params) res_A = qml.jacobian(circuit_A)(*params) # Both jacobians should be of shape (2**prob.wires, num_params) assert res_F.shape == (2**2, 3) assert res_F.shape == (2**2, 3) # Check that they agree up to numeric tolerance assert np.allclose(res_F, res_A, atol=tol, rtol=0)
def test_tensor_number_displaced(self, dev, tol): """Test the variance of the TensorN observable for a displaced state""" @qml.qnode(dev) def circuit(a, phi): qml.Displacement(a, phi, wires=0) qml.Displacement(a, phi, wires=1) return qml.var(qml.TensorN(wires=[0, 1])) a = 0.4 phi = -0.12 expected = a ** 4 * (1 + 2 * a ** 2) var = circuit(a, phi) assert np.allclose(var, expected, atol=tol, rtol=0) # differentiate with respect to parameter a res = qml.jacobian(circuit, argnum=0)(a, phi).flat expected_gradient = 4 * (a ** 3 + 3 * a ** 5) assert np.allclose(res, expected_gradient, atol=tol, rtol=0) # differentiate with respect to parameter phi res = qml.jacobian(circuit, argnum=1)(a, phi).flat expected_gradient = 0 assert np.allclose(res, expected_gradient, atol=tol, rtol=0)
def test_hessian_ragged(self, dev_name, diff_method, mocker, tol): """Test hessian calculation of a ragged QNode""" if diff_method not in {"parameter-shift", "backprop"}: pytest.skip("Test only supports parameter-shift or backprop") dev = qml.device(dev_name, wires=2) @qnode(dev, diff_method=diff_method, interface="autograd") def circuit(x): qml.RY(x[0], wires=0) qml.RX(x[1], wires=0) qml.RY(x[0], wires=1) qml.RX(x[1], wires=1) return qml.expval(qml.PauliZ(0)), qml.probs(wires=1) x = np.array([1.0, 2.0], requires_grad=True) res = circuit(x) a, b = x expected_res = [ np.cos(a) * np.cos(b), 0.5 + 0.5 * np.cos(a) * np.cos(b), 0.5 - 0.5 * np.cos(a) * np.cos(b), ] assert np.allclose(res, expected_res, atol=tol, rtol=0) jac_fn = qml.jacobian(circuit) g = jac_fn(x) expected_g = [ [-np.sin(a) * np.cos(b), -np.cos(a) * np.sin(b)], [-0.5 * np.sin(a) * np.cos(b), -0.5 * np.cos(a) * np.sin(b)], [0.5 * np.sin(a) * np.cos(b), 0.5 * np.cos(a) * np.sin(b)], ] assert np.allclose(g, expected_g, atol=tol, rtol=0) spy = mocker.spy(JacobianTape, "hessian") hess = qml.jacobian(jac_fn)(x) if diff_method == "backprop": spy.assert_not_called() elif diff_method == "parameter-shift": spy.assert_called_once() expected_hess = [ [ [-np.cos(a) * np.cos(b), np.sin(a) * np.sin(b)], [np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)], ], [ [-0.5 * np.cos(a) * np.cos(b), 0.5 * np.sin(a) * np.sin(b)], [0.5 * np.sin(a) * np.sin(b), -0.5 * np.cos(a) * np.cos(b)], ], [ [0.5 * np.cos(a) * np.cos(b), -0.5 * np.sin(a) * np.sin(b)], [-0.5 * np.sin(a) * np.sin(b), 0.5 * np.cos(a) * np.cos(b)], ], ] assert np.allclose(hess, expected_hess, atol=tol, rtol=0)
def test_backward_mode(self, mocker): """Test that backward mode uses the `device.batch_execute` and `device.gradients` pathway""" dev = qml.device("default.qubit", wires=1) spy_execute = mocker.spy(qml.devices.DefaultQubit, "batch_execute") spy_gradients = mocker.spy(qml.devices.DefaultQubit, "gradients") def cost(a): with qml.tape.JacobianTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) return execute( [tape], dev, gradient_fn="device", mode="backward", gradient_kwargs={"method": "adjoint_jacobian"}, )[0] a = np.array([0.1, 0.2], requires_grad=True) cost(a) assert dev.num_executions == 1 spy_execute.assert_called() spy_gradients.assert_not_called() qml.jacobian(cost)(a) spy_gradients.assert_called()
def test_advanced_classical_processing_arguments(self, tol): """Test that a gradient transform acts on QNodes correctly when the QNode arguments are classically processed, and the input weights and the output weights have weird shape.""" dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(weights): qml.RX(weights[0, 0] ** 2, wires=[0]) qml.RY(weights[0, 1], wires=[1]) qml.CNOT(wires=[0, 1]) return qml.probs(wires=[0, 1]) w = np.array([[0.543, -0.654], [0.0, 0.0]], requires_grad=True) res = qml.gradients.param_shift(circuit)(w) assert res.shape == (4, 2, 2) expected = qml.jacobian(circuit)(w) assert np.allclose(res, expected, atol=tol, rtol=0) # when executed with hybrid=False, only the quantum jacobian is returned res = qml.gradients.param_shift(circuit, hybrid=False)(w) assert res.shape == (4, 2) @qml.qnode(dev) def circuit(weights): qml.RX(weights[0], wires=[0]) qml.RY(weights[1], wires=[1]) qml.CNOT(wires=[0, 1]) return qml.probs(wires=[0, 1]) w = np.array([0.543 ** 2, -0.654], requires_grad=True) expected = qml.jacobian(circuit)(w) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_caching_adjoint_backward(self): """Test that caching reduces the number of adjoint evaluations when mode=backward""" dev = qml.device("default.qubit", wires=2) params = np.array([0.1, 0.2, 0.3]) def cost(a, cache): with qml.tape.JacobianTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.RY(a[2], wires=0) qml.expval(qml.PauliZ(0)) qml.expval(qml.PauliZ(1)) return execute( [tape], dev, gradient_fn="device", cache=cache, mode="backward", gradient_kwargs={"method": "adjoint_jacobian"}, )[0] # Without caching, 3 evaluations are required. # 1 for the forward pass, and one per output dimension # on the backward pass. qml.jacobian(cost)(params, cache=None) assert dev.num_executions == 3 # With caching, only 2 evaluations are required. One # for the forward pass, and one for the backward pass. dev._num_executions = 0 jac_fn = qml.jacobian(cost) grad1 = jac_fn(params, cache=True) assert dev.num_executions == 2
def test_hessian_transform_is_differentiable_torch(self): """Test that the 3rd derivate can be calculated via auto-differentiation in Torch (1d -> 1d)""" torch = pytest.importorskip("torch") dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, diff_method="parameter-shift", max_diff=3) def circuit(x): qml.RX(x[1], wires=0) qml.RY(x[0], wires=0) qml.CNOT(wires=[0, 1]) return qml.probs(wires=[0, 1]) x = np.array([0.1, 0.2], requires_grad=True) x_torch = torch.tensor([0.1, 0.2], dtype=torch.float64, requires_grad=True) expected = qml.jacobian(qml.jacobian(qml.jacobian(circuit)))(x) circuit.interface = "torch" jacobian_fn = torch.autograd.functional.jacobian torch_deriv = jacobian_fn(qml.gradients.param_shift_hessian(circuit), x_torch)[0] assert np.allclose(expected, torch_deriv)
def test_fewer_device_invocations_vector_output(self): """Test that the hessian invokes less hardware executions than double differentiation (1d -> 1d)""" dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, diff_method="parameter-shift", max_diff=2) def circuit(x): qml.RX(x[0], wires=0) qml.CNOT(wires=[0, 1]) qml.RY(x[1], wires=0) qml.RZ(x[2], wires=1) return qml.probs(wires=[0, 1]) x = np.array([0.1, 0.2, 0.3], requires_grad=True) with qml.Tracker(dev) as tracker: hessian = qml.gradients.param_shift_hessian(circuit)(x) hessian_qruns = tracker.totals["executions"] expected = qml.jacobian(qml.jacobian(circuit))(x) jacobian_qruns = tracker.totals["executions"] - hessian_qruns assert np.allclose(hessian, expected) assert hessian_qruns < jacobian_qruns assert hessian_qruns <= 2**2 * 6 # 6 = (3+2-1)C(2) assert hessian_qruns <= 3**3
def test_integration_chunk_observables(): """Integration tests that compare to default.qubit for a large circuit with multiple expectation values. Expvals are generated in parallelized chunks.""" dev_def = qml.device("default.qubit", wires=range(4)) dev_lightning = qml.device("lightning.qubit", wires=range(4)) dev_lightning_batched = qml.device("lightning.qubit", wires=range(4), batch_obs=True) def circuit(params): circuit_ansatz(params, wires=range(4)) return [qml.expval(qml.PauliZ(i)) for i in range(4)] n_params = 30 params = np.linspace(0, 10, n_params) qnode_def = qml.QNode(circuit, dev_def) qnode_lightning = qml.QNode(circuit, dev_lightning, diff_method="adjoint") qnode_lightning_batched = qml.QNode(circuit, dev_lightning_batched, diff_method="adjoint") j_def = qml.jacobian(qnode_def)(params) j_lightning = qml.jacobian(qnode_lightning)(params) j_lightning_batched = qml.jacobian(qnode_lightning_batched)(params) assert np.allclose(j_def, j_lightning) assert np.allclose(j_def, j_lightning_batched)
def test_autograd_with_other_device(self): """Test passing an extra device to the QNode wrapper.""" ansatz = fubini_ansatz2 params = fubini_params[2] exp_fn = autodiff_metric_tensor(ansatz, self.num_wires) expected = qml.jacobian(exp_fn)(*params) dev = qml.device("default.qubit", wires=self.num_wires) dev2 = qml.device("default.qubit.autograd", wires=self.num_wires) @qml.qnode(dev, interface="autograd") def circuit(*params): """Circuit with dummy output to create a QNode.""" ansatz(*params, dev.wires) return qml.expval(qml.PauliZ(0)) mt = qml.jacobian(qml.adjoint_metric_tensor(circuit, device=dev2))(*params) if isinstance(mt, tuple): assert all( qml.math.allclose(_mt, _exp) for _mt, _exp in zip(mt, expected)) else: assert qml.math.allclose(mt, expected)
def test_unwrap_autograd_backward(): """Test that unwrapping a tape with Autograd parameters works as expected during a backwards pass""" from pennylane import numpy as anp from autograd.numpy.numpy_boxes import ArrayBox p = [ anp.tensor([0.1, 0.5, 0.3], requires_grad=True), anp.tensor(0.2, requires_grad=False), ] def cost(*p): with qml.tape.QuantumTape() as tape: qml.RX(p[0][0], wires=0) qml.RY(p[1], wires=0) qml.PhaseShift(p[0][1], wires=0) qml.RZ(p[0][2], wires=0) with tape.unwrap() as unwrapped_tape: # inside the context manager, all parameters # will be unwrapped to NumPy arrays params = tape.get_parameters(trainable_only=False) assert all(isinstance(i, float) for i in params) assert np.allclose(params, [0.1, 0.2, 0.5, 0.3]) assert tape.trainable_params == {0, 2, 3} # outside the context, the original parameters have been restored. params = tape.get_parameters(trainable_only=False) assert any(isinstance(i, ArrayBox) for i in params) return p[0][0] * p[1]**2 * anp.sin(p[0][1]) * anp.exp(-0.5 * p[0][2]) qml.grad(cost)(*p) qml.jacobian(qml.grad(cost))(*p)
def test_tensor_number_displaced_squeezed_pd_squeezing( self, dev, disp_sq_circuit, pars, reverted_pars, tol ): """Test the variance of the TensorN observable for a squeezed displaced state The analytic expression for the partial derivate wrt r of the second squeezing operation can be obtained by passing the parameters of operations acting on the second mode first (using reverted_pars). """ def pd_sr(rs0, phis0, rd0, phid0, rs1, phis1, rd1, phid1): """Analytic expression for the partial derivative with respect to the r argument of the first squeezing operation (rs0)""" return ( ( 0.25 + rd0 ** 2 * (-0.25 - 2 * rd1 ** 2 + 2 * rd1 ** 4) + (-(rd1 ** 2) + rd0 ** 2 * (-1 + 6 * rd1 ** 2)) * np.cosh(2 * rs1) + (-0.25 + 1.25 * rd0 ** 2) * np.cosh(4 * rs1) ) * np.sinh(2 * rs0) + ( -(rd1 ** 2) + rd1 ** 4 + (-0.5 + 2.5 * rd1 ** 2) * np.cosh(2 * rs1) + 0.5 * np.cosh(4 * rs1) ) * np.sinh(4 * rs0) + rd1 ** 2 * np.cos(2 * phid1 - phis1) * ((1 - 4 * rd0 ** 2) * np.sinh(2 * rs0) - 1.5 * np.sinh(4 * rs0)) * np.sinh(2 * rs1) + rd0 ** 2 * np.cos(2 * phid0 - phis0) * np.cosh(2 * rs0) * ( -0.25 + 2 * rd1 ** 2 - 2 * rd1 ** 4 + (1 - 4 * rd1 ** 2) * np.cosh(2 * rs1) - 0.75 * np.cosh(4 * rs1) + 2 * rd1 ** 2 * np.cos(2 * phid1 - phis1) * np.sinh(2 * rs1) ) ) # differentiate wrt r of the first squeezing operation (rs0) grad = qml.jacobian(disp_sq_circuit, argnum=0)(*pars) expected_gradient = pd_sr(*pars) assert np.allclose(grad, expected_gradient, atol=tol, rtol=0) # differentiate wrt r of the second squeezing operation (rs1) grad = qml.jacobian(disp_sq_circuit, argnum=4)(*pars) # expected_gradient = pd_sr(*reverted_pars) assert np.allclose(grad, expected_gradient, atol=tol, rtol=0)
def test_sin_warns_jacobian(self, tol): """Test that a warning is raised if a positional argument without the requires_grad attribute set is passed when differentiating a classical vector valued function.""" arr = onp.array([0.1, 0.2, 0.3]) with pytest.warns( UserWarning, match="inputs have to explicitly specify requires_grad=True"): qml.jacobian(np.sin)(arr)
def test_hessian_vector_valued_separate_args(self, dev_name, diff_method, mocker, tol): """Test hessian calculation of a vector valued QNode that has separate input arguments""" if diff_method not in {"parameter-shift", "backprop"}: pytest.skip("Test only supports parameter-shift or backprop") dev = qml.device(dev_name, wires=1) @qnode(dev, diff_method=diff_method, interface="autograd") def circuit(a, b): qml.RY(a, wires=0) qml.RX(b, wires=0) return qml.probs(wires=0) a = np.array(1.0, requires_grad=True) b = np.array(2.0, requires_grad=True) res = circuit(a, b) expected_res = [ 0.5 + 0.5 * np.cos(a) * np.cos(b), 0.5 - 0.5 * np.cos(a) * np.cos(b) ] assert np.allclose(res, expected_res, atol=tol, rtol=0) jac_fn = qml.jacobian(circuit) g = jac_fn(a, b) expected_g = [ [-0.5 * np.sin(a) * np.cos(b), -0.5 * np.cos(a) * np.sin(b)], [0.5 * np.sin(a) * np.cos(b), 0.5 * np.cos(a) * np.sin(b)], ] assert np.allclose(g, expected_g, atol=tol, rtol=0) spy = mocker.spy(JacobianTape, "hessian") hess = qml.jacobian(jac_fn)(a, b) if diff_method == "backprop": spy.assert_not_called() elif diff_method == "parameter-shift": # Hessian will have been called 4 times, for every permutation of a, b # since autograd will treat them as separate arguments. assert spy.call_count == 4 expected_hess = [ [ [-0.5 * np.cos(a) * np.cos(b), 0.5 * np.sin(a) * np.sin(b)], [0.5 * np.cos(a) * np.cos(b), -0.5 * np.sin(a) * np.sin(b)], ], [ [0.5 * np.sin(a) * np.sin(b), -0.5 * np.cos(a) * np.cos(b)], [-0.5 * np.sin(a) * np.sin(b), 0.5 * np.cos(a) * np.cos(b)], ], ] assert np.allclose(hess, expected_hess, atol=tol, rtol=0)
def _jacobian(*args, **kwargs): if argnum is None: jac = qml.jacobian(classical_preprocessing)(*args, **kwargs) elif np.isscalar(argnum): jac = qml.jacobian(classical_preprocessing, argnum=argnum)(*args, **kwargs) else: jac = tuple((qml.jacobian(classical_preprocessing, argnum=i)(*args, **kwargs) for i in argnum)) return jac
def test_caching_param_shift_hessian(self, num_params, tol): """Test that, when using parameter-shift transform, caching reduces the number of evaluations to their optimum when computing Hessians.""" dev = qml.device("default.qubit", wires=2) params = np.arange(1, num_params + 1) / 10 N = len(params) def cost(x, cache): with qml.tape.JacobianTape() as tape: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) for i in range(2, num_params): qml.RZ(x[i], wires=[i % 2]) qml.CNOT(wires=[0, 1]) qml.var(qml.PauliZ(0) @ qml.PauliX(1)) return execute([tape], dev, gradient_fn=param_shift, cache=cache, max_diff=2)[0] # No caching: number of executions is not ideal hess1 = qml.jacobian(qml.grad(cost))(params, cache=False) if num_params == 2: # compare to theoretical result x, y, *_ = params expected = np.array( [ [2 * np.cos(2 * x) * np.sin(y) ** 2, np.sin(2 * x) * np.sin(2 * y)], [np.sin(2 * x) * np.sin(2 * y), -2 * np.cos(x) ** 2 * np.cos(2 * y)], ] ) assert np.allclose(expected, hess1, atol=tol, rtol=0) expected_runs = 1 # forward pass expected_runs += 2 * N # Jacobian expected_runs += 4 * N + 1 # Hessian diagonal expected_runs += 4 * N ** 2 # Hessian off-diagonal assert dev.num_executions == expected_runs # Use caching: number of executions is ideal dev._num_executions = 0 hess2 = qml.jacobian(qml.grad(cost))(params, cache=True) assert np.allclose(hess1, hess2, atol=tol, rtol=0) expected_runs_ideal = 1 # forward pass expected_runs_ideal += 2 * N # Jacobian expected_runs_ideal += N + 1 # Hessian diagonal expected_runs_ideal += 4 * N * (N - 1) // 2 # Hessian off-diagonal assert dev.num_executions == expected_runs_ideal assert expected_runs_ideal < expected_runs
def test_autograd_trainable_argnum_no_warn_jacobian(self, recwarn): """Test that no warning is raised if positional arguments are marked as trainable using the argnum argument.""" dev = qml.device("default.qubit", wires=5) @qml.qnode(dev) def test(x): qml.RZ(x, wires=[0]) return qml.probs(wires=[0]) par = np.array(0.3) qml.jacobian(test, argnum=0)(par) assert len(recwarn) == 0
def test_hessian_at_zero(self, x, shift): """Tests that the Hessian at vanishing state vector amplitudes is correct.""" dev = qml.device("default.qubit.autograd", wires=1) @qml.qnode(dev, interface="autograd", diff_method="backprop") def circuit(x): qml.RY(shift, wires=0) qml.RY(x, wires=0) return qml.expval(qml.PauliZ(0)) assert qml.math.isclose(qml.jacobian(circuit)(x), 0.0) assert qml.math.isclose(qml.jacobian(qml.jacobian(circuit))(x), -1.0) assert qml.math.isclose(qml.grad(qml.grad(circuit))(x), -1.0)
def test_simple_operation_autograd(self, invert): """Test differentiability for a simple operation with Autograd.""" device = qml.device("default.qubit.autograd", wires=2) x = np.array(self.x, requires_grad=True) r_fn = lambda x: qml.math.real( _apply_operations(device._state, qml.RX(x, wires=0), device, invert )) i_fn = lambda x: qml.math.imag( _apply_operations(device._state, qml.RX(x, wires=0), device, invert )) out = qml.jacobian(r_fn)(x) + 1j * qml.jacobian(i_fn)(x) exp = (np.array([[-np.sin(self.x / 2), 0.0], [-1j * (-1)**invert * np.cos(self.x / 2), 0.0]]) / 2) assert np.allclose(out, exp)
def test_autograd_positional_non_trainable_warns_jacobian(self): """Test that a warning is raised if a positional argument without the requires_grad attribute set is passed when differentiating a QNode with a vector output.""" dev = qml.device("default.qubit", wires=5) @qml.qnode(dev) def test(x): qml.RY(x, wires=[0]) return qml.probs(wires=[0]) with pytest.warns( UserWarning, match="inputs have to explicitly specify requires_grad=True"): qml.jacobian(test)(0.3)
def test_autograd_gradient(self, tol): """Tests that the output of the parameter-shift CV transform can be differentiated using autograd, yielding second derivatives.""" dev = qml.device("default.gaussian", wires=1) from pennylane.interfaces.autograd import AutogradInterface r = 0.12 phi = 0.105 def cost_fn(x): with AutogradInterface.apply(qml.tape.CVParamShiftTape()) as tape: qml.Squeezing(x[0], 0, wires=0) qml.Rotation(x[1], wires=0) qml.var(qml.X(wires=[0])) tapes, fn = param_shift_cv(tape, dev) return fn([t.execute(dev) for t in tapes])[0, 1] params = np.array([r, phi], requires_grad=True) grad = qml.jacobian(cost_fn)(params) expected = np.array([ 4 * np.cosh(2 * r) * np.sin(2 * phi), 4 * np.cos(2 * phi) * np.sinh(2 * r) ]) assert np.allclose(grad, expected, atol=tol, rtol=0)
def test_autograd(self, approx_order, strategy, tol): """Tests that the output of the finite-difference transform can be differentiated using autograd, yielding second derivatives.""" dev = qml.device("default.qubit.autograd", wires=2) params = np.array([0.543, -0.654], requires_grad=True) def cost_fn(x): with qml.tape.JacobianTape() as tape: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) tape.trainable_params = {0, 1} tapes, fn = finite_diff(tape, n=1, approx_order=approx_order, strategy=strategy) jac = fn(dev.batch_execute(tapes)) return jac res = qml.jacobian(cost_fn)(params) x, y = params expected = np.array( [ [-np.cos(x) * np.sin(y), -np.cos(y) * np.sin(x)], [-np.cos(y) * np.sin(x), -np.cos(x) * np.sin(y)], ] ) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_no_trainable_parameters(self, dev_name, diff_method, tol): """Test evaluation and Jacobian if there are no trainable parameters""" dev = qml.device(dev_name, wires=2) @qnode(dev, diff_method=diff_method, interface="autograd") def circuit(a, b): qml.RY(a, wires=0) qml.RX(b, wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) a = np.array(0.1, requires_grad=False) b = np.array(0.2, requires_grad=False) res = circuit(a, b) if diff_method == "finite-diff": assert circuit.qtape.trainable_params == set() assert res.shape == (2,) assert isinstance(res, np.ndarray) assert not qml.jacobian(circuit)(a, b) def cost(a, b): return np.sum(circuit(a, b)) with pytest.warns(UserWarning, match="Output seems independent of input"): grad = qml.grad(cost)(a, b) assert grad == tuple()
def test_jacobian_no_evaluate(self, dev_name, diff_method, mocker, tol): """Test jacobian calculation when no prior circuit evaluation has been performed""" spy = mocker.spy(JacobianTape, "jacobian") a = np.array(0.1, requires_grad=True) b = np.array(0.2, requires_grad=True) dev = qml.device(dev_name, wires=2) @qnode(dev, diff_method=diff_method, interface="autograd") def circuit(a, b): qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) return [qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))] jac_fn = qml.jacobian(circuit) res = jac_fn(a, b) expected = [[-np.sin(a), 0], [np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)]] assert np.allclose(res, expected, atol=tol, rtol=0) if diff_method == "finite-diff": spy.assert_called() elif diff_method == "backprop": spy.assert_not_called() # call the Jacobian with new parameters a = np.array(0.6, requires_grad=True) b = np.array(0.832, requires_grad=True) res = jac_fn(a, b) expected = [[-np.sin(a), 0], [np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)]] assert np.allclose(res, expected, atol=tol, rtol=0)
def test_jacobian(self, dev_name, diff_method, mocker, tol): """Test jacobian calculation""" spy = mocker.spy(JacobianTape, "jacobian") a = np.array(0.1, requires_grad=True) b = np.array(0.2, requires_grad=True) dev = qml.device(dev_name, wires=2) @qnode(dev, diff_method=diff_method, interface="autograd") def circuit(a, b): qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) return [qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))] res = circuit(a, b) assert circuit.qtape.trainable_params == {0, 1} assert res.shape == (2,) expected = [np.cos(a), -np.cos(a) * np.sin(b)] assert np.allclose(res, expected, atol=tol, rtol=0) res = qml.jacobian(circuit)(a, b) expected = [[-np.sin(a), 0], [np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)]] assert np.allclose(res, expected, atol=tol, rtol=0) if diff_method == "finite-diff": spy.assert_called() elif diff_method == "backprop": spy.assert_not_called()
def test_probability_differentiation(self, dev_name, diff_method, tol): """Tests correct output shape and evaluation for a tape with prob and expval outputs""" dev = qml.device(dev_name, wires=2) x = np.array(0.543, requires_grad=True) y = np.array(-0.654, requires_grad=True) @qnode(dev, diff_method=diff_method, interface="autograd") def circuit(x, y): qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) return qml.probs(wires=[0]), qml.probs(wires=[1]) res = circuit(x, y) expected = np.array([ [np.cos(x / 2)**2, np.sin(x / 2)**2], [(1 + np.cos(x) * np.cos(y)) / 2, (1 - np.cos(x) * np.cos(y)) / 2], ]) assert np.allclose(res, expected, atol=tol, rtol=0) res = qml.jacobian(circuit)(x, y) expected = np.array([ [[-np.sin(x) / 2, 0], [-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2]], [ [np.sin(x) / 2, 0], [np.cos(y) * np.sin(x) / 2, np.cos(x) * np.sin(y) / 2], ], ]) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_jacobian_repeated(self, torch_support, rep, tol): """Test that qnode.jacobian applied to the tensornet.tf device gives the correct result in the case of repeated parameters""" x = 0.43316321 y = 0.2162158 z = 0.75110998 p = np.array([x, y, z]) dev = qml.device("default.tensor.tf", wires=1, representation=rep) @qml.qnode(dev) def circuit(x): qml.RX(x[1], wires=0) qml.Rot(x[0], x[1], x[2], wires=0) return qml.expval(qml.PauliZ(0)) res = circuit(p) expected = np.cos(y)**2 - np.sin(x) * np.sin(y)**2 assert np.allclose(res, expected, atol=tol, rtol=0) res = qml.jacobian(circuit)(p) expected = np.array([ -np.cos(x) * np.sin(y)**2, -2 * (np.sin(x) + 1) * np.sin(y) * np.cos(y), 0, ]) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_jacobian_variable_multiply(self, torch_support, rep, tol): """Test that qnode.jacobian applied to the tensornet.tf device gives the correct result in the case of parameters multiplied by scalars""" x = 0.43316321 y = 0.2162158 z = 0.75110998 dev = qml.device("default.tensor.tf", wires=1, representation=rep) @qml.qnode(dev) def circuit(p): qml.RX(3 * p[0], wires=0) qml.RY(p[1], wires=0) qml.RX(p[2] / 2, wires=0) return qml.expval(qml.PauliZ(0)) res = circuit([x, y, z]) expected = np.cos(3 * x) * np.cos(y) * np.cos(z / 2) - np.sin( 3 * x) * np.sin(z / 2) assert np.allclose(res, expected, atol=tol, rtol=0) res = qml.jacobian(circuit)(np.array([x, y, z])) expected = np.array([ -3 * (np.sin(3 * x) * np.cos(y) * np.cos(z / 2) + np.cos(3 * x) * np.sin(z / 2)), -np.cos(3 * x) * np.sin(y) * np.cos(z / 2), -0.5 * (np.sin(3 * x) * np.cos(z / 2) + np.cos(3 * x) * np.cos(y) * np.sin(z / 2)), ]) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_gradient_with_passthru_autograd(self): """Test that the gradient of the state is accessible when using default.qubit.autograd with the backprop diff_method.""" from pennylane import numpy as anp dev = qml.device("default.qubit.autograd", wires=1) @qnode(dev, interface="autograd", diff_method="backprop") def func(x): qml.RY(x, wires=0) return state() x = anp.array(0.1, requires_grad=True) def loss_fn(x): res = func(x) return anp.real( res ) # This errors without the real. Likely an issue with complex # numbers in autograd d_loss_fn = qml.jacobian(loss_fn) grad = d_loss_fn(x) expected = np.array([-0.5 * np.sin(x / 2), 0.5 * np.cos(x / 2)]) assert np.allclose(grad, expected)