def test_correct_number_of_executions_torch(self): """Test that number of executions are tracked in the torch interface.""" torch = pytest.importorskip("torch") def func(): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=2) qn = QNode(func, dev, interface="torch") for i in range(2): qn() assert dev.num_executions == 2 qn2 = QNode(func, dev, interface="torch") for i in range(3): qn2() assert dev.num_executions == 5 # qubit of different interface qn3 = QNode(func, dev, interface="autograd") qn3() assert dev.num_executions == 6
def test_qnode(self, mocker, tol, dev): """Test that specifying diff_method allows the adjoint method to be selected""" args = np.array([0.54, 0.1, 0.5], requires_grad=True) def circuit(x, y, z): qml.Hadamard(wires=0) qml.RX(0.543, wires=0) qml.CNOT(wires=[0, 1]) qml.Rot(x, y, z, wires=0) qml.Rot(1.3, -2.3, 0.5, wires=[0]) qml.RZ(-0.5, wires=0) qml.RY(0.5, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliX(0) @ qml.PauliZ(1)) qnode1 = QNode(circuit, dev, diff_method="adjoint") spy = mocker.spy(dev, "adjoint_jacobian") grad_fn = qml.grad(qnode1) grad_A = grad_fn(*args) spy.assert_called() qnode2 = QNode(circuit, dev, diff_method="finite-diff") grad_fn = qml.grad(qnode2) grad_F = grad_fn(*args) assert np.allclose(grad_A, grad_F, atol=tol, rtol=0)
def test_interface_torch(self, dev): """Test if gradients agree between the adjoint and finite-diff methods when using the Torch interface""" torch = pytest.importorskip("torch") def f(params1, params2): qml.RX(0.4, wires=[0]) qml.RZ(params1 * torch.sqrt(params2), wires=[0]) qml.RY(torch.cos(params2), wires=[0]) return qml.expval(qml.PauliZ(0)) params1 = torch.tensor(0.3, requires_grad=True) params2 = torch.tensor(0.4, requires_grad=True) qnode1 = QNode(f, dev, interface="torch", diff_method="adjoint") qnode2 = QNode(f, dev, interface="torch", diff_method="finite-diff") res1 = qnode1(params1, params2) res1.backward() grad_adjoint = params1.grad, params2.grad res2 = qnode2(params1, params2) res2.backward() grad_fd = params1.grad, params2.grad assert np.allclose(grad_adjoint, grad_fd)
def test_returning_non_measurements(self): """Test that an exception is raised if a non-measurement is returned from the QNode.""" dev = qml.device("default.qubit", wires=2) def func(x, y): qml.RX(x, wires=0) qml.RY(y, wires=1) qml.CNOT(wires=[0, 1]) return 5 qn = QNode(func, dev) with pytest.raises( qml.QuantumFunctionError, match="must return either a single measurement" ): qn(5, 1) def func(x, y): qml.RX(x, wires=0) qml.RY(y, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), 5 qn = QNode(func, dev) with pytest.raises( qml.QuantumFunctionError, match="must return either a single measurement" ): qn(5, 1)
def test_qnode(self, mocker, tol): """Test that specifying diff_method allows the reversible method to be selected""" args = np.array([0.54, 0.1, 0.5], requires_grad=True) dev = qml.device("default.qubit", wires=2) def circuit(x, y, z): qml.Hadamard(wires=0) qml.RX(0.543, wires=0) qml.CNOT(wires=[0, 1]) qml.Rot(x, y, z, wires=0) qml.Rot(1.3, -2.3, 0.5, wires=[0]) qml.RZ(-0.5, wires=0) qml.RY(0.5, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliX(0) @ qml.PauliZ(1)) qnode1 = QNode(circuit, dev, diff_method="reversible") spy = mocker.spy(ReversibleTape, "analytic_pd") grad_fn = qml.grad(qnode1) grad_A = grad_fn(*args) spy.assert_called() assert isinstance(qnode1.qtape, ReversibleTape) qnode2 = QNode(circuit, dev, diff_method="finite-diff") grad_fn = qml.grad(qnode2) grad_F = grad_fn(*args) assert not isinstance(qnode2.qtape, ReversibleTape) assert np.allclose(grad_A, grad_F, atol=tol, rtol=0)
def test_interface_tf(self, dev): """Test if gradients agree between the adjoint and finite-diff methods when using the TensorFlow interface""" tf = pytest.importorskip("tensorflow") def f(params1, params2): qml.RX(0.4, wires=[0]) qml.RZ(params1 * tf.sqrt(params2), wires=[0]) qml.RY(tf.cos(params2), wires=[0]) return qml.expval(qml.PauliZ(0)) params1 = tf.Variable(0.3, dtype=tf.float64) params2 = tf.Variable(0.4, dtype=tf.float64) qnode1 = QNode(f, dev, interface="tf", diff_method="adjoint") qnode2 = QNode(f, dev, interface="tf", diff_method="finite-diff") with tf.GradientTape() as tape: res1 = qnode1(params1, params2) g1 = tape.gradient(res1, [params1, params2]) with tf.GradientTape() as tape: res2 = qnode2(params1, params2) g2 = tape.gradient(res2, [params1, params2]) assert np.allclose(g1, g2)
def test_gradient_repeated_gate_parameters(self, mocker, tol): """Tests that repeated use of a free parameter in a multi-parameter gate yield correct gradients.""" dev = qml.device("default.qubit", wires=1) params = np.array([0.8, 1.3], requires_grad=True) def circuit(params): qml.RX(np.array(np.pi / 4, requires_grad=False), wires=[0]) qml.Rot(params[1], params[0], 2 * params[0], wires=[0]) return qml.expval(qml.PauliX(0)) spy_numeric = mocker.spy(JacobianTape, "numeric_pd") spy_analytic = mocker.spy(ReversibleTape, "analytic_pd") cost = QNode(circuit, dev, diff_method="finite-diff") grad_fn = qml.grad(cost) grad_F = grad_fn(params) spy_numeric.assert_called() spy_analytic.assert_not_called() cost = QNode(circuit, dev, diff_method="reversible") grad_fn = qml.grad(cost) grad_A = grad_fn(params) spy_analytic.assert_called() # the different methods agree assert np.allclose(grad_A, grad_F, atol=tol, rtol=0)
def test_invalid_interface(self): """Test that an exception is raised for an invalid interface""" dev = qml.device("default.qubit", wires=1) with pytest.raises(qml.QuantumFunctionError, match="Unknown interface"): QNode(None, dev, interface="something")
def test_backprop_error(self): """Test if an error is raised when caching is used with the backprop diff_method""" dev = qml.device("default.qubit", wires=2, cache=10) with pytest.raises( qml.QuantumFunctionError, match="Device caching is incompatible with the backprop"): QNode(qfunc, dev, diff_method="backprop")
def test_multi_thread(self, enable_tape_mode): """Test that multi-threaded queuing in tape mode works correctly""" n_qubits = 4 n_batches = 5 dev = qml.device("default.qubit", wires=n_qubits) def circuit(inputs, weights): for index, input in enumerate(inputs): qml.RY(input, wires=index) for index in range(n_qubits - 1): qml.CNOT(wires=(index, index + 1)) for index, weight in enumerate(weights): qml.RX(weight, wires=index) return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)] weight_shapes = {"weights": (n_qubits)} try: qnode = QNodeCollection([QNode(circuit, dev) for _ in range(n_batches)]) except Exception as e: pytest.fail("QNodeCollection cannot be instantiated") x = np.random.rand(n_qubits).astype(np.float64) p = np.random.rand(weight_shapes["weights"]).astype(np.float64) try: for _ in range(10): qnode(x, p, parallel=True) except: pytest.fail("Multi-threading on QuantumTape failed")
def test_basic_tape_construction(self, tol): """Test that a quantum tape is properly constructed""" dev = qml.device("default.qubit", wires=2) def func(x, y): qml.RX(x, wires=0) qml.RY(y, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)) qn = QNode(func, dev) x = 0.12 y = 0.54 res = qn(x, y) assert isinstance(qn.qtape, JacobianTape) assert len(qn.qtape.operations) == 3 assert len(qn.qtape.observables) == 1 assert qn.qtape.num_params == 2 expected = qn.qtape.execute(dev) assert np.allclose(res, expected, atol=tol, rtol=0) # when called, a new quantum tape is constructed old_tape = qn.qtape res2 = qn(x, y) assert np.allclose(res, res2, atol=tol, rtol=0) assert qn.qtape is not old_tape
def test_unknown_diff_method(self): """Test that an exception is raised for an unknown differentiation method""" dev = qml.device("default.qubit", wires=1) with pytest.raises( qml.QuantumFunctionError, match="Differentiation method hello not recognized" ): QNode(None, dev, diff_method="hello")
def test_diff_method(self, mocker): """Test that a user-supplied diff-method correctly returns the right quantum tape, interface, and diff method.""" dev = qml.device("default.qubit", wires=1) mock_best = mocker.patch("pennylane.tape.QNode.get_best_method") mock_best.return_value = 1, 2, 3, {"method": "best"} mock_backprop = mocker.patch( "pennylane.tape.QNode._validate_backprop_method") mock_backprop.return_value = 4, 5, 6, {"method": "backprop"} mock_device = mocker.patch( "pennylane.tape.QNode._validate_device_method") mock_device.return_value = 7, 8, 9, {"method": "device"} qn = QNode(None, dev, diff_method="best") assert qn._tape == mock_best.return_value[0] assert qn.interface == mock_best.return_value[1] assert qn.diff_options["method"] == mock_best.return_value[3]["method"] qn = QNode(None, dev, diff_method="backprop") assert qn._tape == mock_backprop.return_value[0] assert qn.interface == mock_backprop.return_value[1] assert qn.diff_options["method"] == mock_backprop.return_value[3][ "method"] mock_backprop.assert_called_once() qn = QNode(None, dev, diff_method="device") assert qn._tape == mock_device.return_value[0] assert qn.interface == mock_device.return_value[1] assert qn.diff_options["method"] == mock_device.return_value[3][ "method"] mock_device.assert_called_once() qn = QNode(None, dev, diff_method="finite-diff") assert qn._tape == JacobianTape assert qn.diff_options["method"] == "numeric" qn = QNode(None, dev, diff_method="parameter-shift") assert qn._tape == QubitParamShiftTape assert qn.diff_options["method"] == "analytic" # check that get_best_method was only ever called once mock_best.assert_called_once()
def test_invalid_interface(self): """Test that an exception is raised for an invalid interface""" dev = qml.device("default.qubit", wires=1) test_interface = "something" expected_error = ( fr"Unknown interface {test_interface}\. Interface must be " r"one of \['autograd', 'torch', 'tf', 'jax'\]\.") with pytest.raises(qml.QuantumFunctionError, match=expected_error): QNode(None, dev, interface="something")
def test_caching(self, mocker): """Test that caching occurs when the cache attribute is above zero""" dev = qml.device("default.qubit", wires=2, cache=10) qn = QNode(qfunc, dev) qn(0.1, 0.2) spy = mocker.spy(DefaultQubit, "apply") qn(0.1, 0.2) spy.assert_not_called() assert len(dev._cache_execute) == 1
def test_no_caching(self, mocker): """Test that no caching occurs when the cache attribute is equal to zero""" dev = qml.device("default.qubit", wires=2, cache=0) qn = QNode(qfunc, dev) spy = mocker.spy(DefaultQubit, "apply") qn(0.1, 0.2) qn(0.1, 0.2) assert len(spy.call_args_list) == 2 assert len(dev._cache_execute) == 0
def test_gradient_autograd(self, mocker): """Test that caching works when calculating the gradient using the autograd interface""" dev = qml.device("default.qubit", wires=2, cache=10) qn = QNode(qfunc, dev, interface="autograd") d_qnode = qml.grad(qn) args = [0.1, 0.2] d_qnode(*args) spy = mocker.spy(DefaultQubit, "apply") d_qnode(*args) spy.assert_not_called()
def test_add_to_cache_execute(self): """Test that the _cache_execute attribute is added to when the device is executed""" dev = qml.device("default.qubit", wires=2, cache=10) qn = QNode(qfunc, dev) result = qn(0.1, 0.2) cache_execute = dev._cache_execute hashed = qn.qtape.graph.hash assert len(cache_execute) == 1 assert hashed in cache_execute assert np.allclose(cache_execute[hashed], result)
def test_drop_from_cache(self): """Test that the first entry of the _cache_execute dictionary is the first to be dropped from the dictionary once it becomes full""" dev = qml.device("default.qubit", wires=2, cache=2) qn = QNode(qfunc, dev) qn(0.1, 0.2) first_hash = list(dev._cache_execute.keys())[0] qn(0.1, 0.3) assert first_hash in dev._cache_execute qn(0.1, 0.4) assert first_hash not in dev._cache_execute
def test_correct_number_of_executions_autograd(self): """Test that number of executions are tracked in the autograd interface.""" qml.enable_tape() def func(): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=2) qn = QNode(func, dev, interface="autograd") for i in range(2): qn() assert dev.num_executions == 2 qn2 = QNode(func, dev, interface="autograd") for i in range(3): qn2() assert dev.num_executions == 5
def test_fill_cache(self): """Test that the cache is added to until it reaches its maximum size (in this case 10), and then maintains that size upon subsequent additions.""" dev = qml.device("default.qubit", wires=2, cache=10) qn = QNode(qfunc, dev) args = np.arange(20) for i, arg in enumerate(args[:10]): qn(0.1, arg) assert len(dev._cache_execute) == i + 1 for arg in args[10:]: qn(0.1, arg) assert len(dev._cache_execute) == 10
def test_diff_method_expansion(self, monkeypatch, mocker): """Test that a QNode with tape expansion during construction preserves the differentiation method.""" class MyDev(qml.devices.DefaultQubit): """Dummy device that supports device Jacobians""" @classmethod def capabilities(cls): capabilities = super().capabilities().copy() capabilities.update( provides_jacobian=True, ) return capabilities def jacobian(self, *args, **kwargs): return np.zeros((2, 4)) dev = MyDev(wires=2) def func(x, y): # the U2 operation is not supported on default.qubit # and is decomposed. qml.U2(x, y, wires=0) qml.CNOT(wires=[0, 1]) return qml.probs(wires=0) qn = QNode(func, dev, diff_method="device", h=1e-8, order=2) assert qn.diff_options["method"] == "device" assert qn.diff_options["h"] == 1e-8 assert qn.diff_options["order"] == 2 x = 0.12 y = 0.54 spy = mocker.spy(JacobianTape, "expand") res = qn(x, y) spy.assert_called_once() assert qn.qtape.jacobian_options["method"] == "device" assert qn.qtape.jacobian_options["h"] == 1e-8 assert qn.qtape.jacobian_options["order"] == 2 spy = mocker.spy(JacobianTape, "jacobian") jac = qml.jacobian(qn)(x, y) assert spy.call_args_list[0][1]["method"] == "device"
def test_caching_multiple_values(self, mocker): """Test that multiple device executions with different params are cached and accessed on subsequent executions""" dev = qml.device("default.qubit", wires=2, cache=10) qn = QNode(qfunc, dev) args = np.arange(10) for arg in args[:10]: qn(0.1, arg) spy = mocker.spy(DefaultQubit, "apply") for arg in args[:10]: qn(0.1, arg) spy.assert_not_called()
def test_gradient_torch(self, mocker): """Test that caching works when calculating the gradient using the Torch interface""" import torch dev = qml.device("default.qubit", wires=2, cache=10) qn = QNode(qfunc, dev, interface="torch") args0 = torch.tensor(0.1, requires_grad=True) args1 = torch.tensor(0.2) res = qn(args0, args1) res.backward() assert args0.grad is not None spy = mocker.spy(DefaultQubit, "apply") res = qn(args0, args1) res.backward() spy.assert_not_called()
def test_consistent_measurement_order(self): """Test evaluation exceeds as expected if measurements are returned in the same order to how they were queued on the tape""" dev = qml.device("default.qubit", wires=2) 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 = qml.expval(qml.PauliZ(0)) m2 = qml.expval(qml.PauliX(1)) return [m1, m2] qn = QNode(func, dev) qn(5, 1) # evaluate the QNode assert qn.qtape.operations == [op1, op2, op3] assert qn.qtape.measurements == [m1, m2]
def test_inconsistent_measurement_order(self): """Test that an exception is raised if measurements are returned in an order different to how they were queued on the tape""" dev = qml.device("default.qubit", wires=2) def func(x, y): qml.RX(x, wires=0) qml.RY(y, wires=1) qml.CNOT(wires=[0, 1]) m = qml.expval(qml.PauliZ(0)) return qml.expval(qml.PauliX(1)), m qn = QNode(func, dev) with pytest.raises( qml.QuantumFunctionError, match="measurements must be returned in the order they are measured", ): qn(5, 1)
def test_import_error(self, mocker): """Test that an exception is caught on import error""" mock = mocker.patch("pennylane.tape.interfaces.torch.TorchInterface.apply") mock.side_effect = ImportError() def func(x, y): qml.RX(x, wires=0) qml.RY(y, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=2) qn = QNode(func, dev, interface="torch") with pytest.raises( qml.QuantumFunctionError, match="PyTorch not found. Please install the latest version of PyTorch to enable the 'torch' interface", ): qn(0.1, 0.1)
def test_import_error(self, mocker): """Test that an exception is caught on import error""" tf = pytest.importorskip("tensorflow", minversion="2.1") mock = mocker.patch("pennylane.tape.interfaces.tf.TFInterface.apply") mock.side_effect = ImportError() def func(x, y): qml.RX(x, wires=0) qml.RY(y, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=2) qn = QNode(func, dev, interface="tf", diff_method="parameter-shift") with pytest.raises( qml.QuantumFunctionError, match="TensorFlow not found. Please install the latest version of TensorFlow to enable the 'tf' interface", ): qn(0.1, 0.1)
def test_jacobian(self, tol): """Test the jacobian computation""" dev = qml.device("default.qubit", wires=2) def func(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) qn = QNode(func, dev, h=1e-8, order=2) assert qn.diff_options["h"] == 1e-8 assert qn.diff_options["order"] == 2 x = 0.12 y = 0.54 res = qn(x, y) jac = qn.qtape.jacobian(dev, params=[0.45, 0.1]) assert jac.shape == (4, 2)
def test_gradient_tf(self, mocker): """Test that caching works when calculating the gradient using the TF interface""" import tensorflow as tf dev = qml.device("default.qubit", wires=2, cache=10) qn = QNode(qfunc, dev, interface="tf") args0 = tf.Variable(0.1) args1 = tf.Variable(0.2) with tf.GradientTape() as tape: res = qn(args0, args1) grad = tape.gradient(res, args0) assert grad is not None spy = mocker.spy(DefaultQubit, "apply") with tf.GradientTape() as tape: res = qn(args0, args1) tape.gradient(res, args0) spy.assert_not_called()