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_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_validate_backprop_method_invalid_device(self): """Test that the method for validating the backprop diff method tape raises an exception if the device does not support backprop.""" dev = qml.device("default.qubit", wires=1) with pytest.raises(qml.QuantumFunctionError, match="does not support native computations"): QNode._validate_backprop_method(dev, None)
def test_best_method(self, monkeypatch): """Test that the method for determining the best diff method for a given device and interface works correctly""" dev = qml.device("default.qubit", wires=1) monkeypatch.setitem(dev._capabilities, "passthru_interface", "some_interface") monkeypatch.setitem(dev._capabilities, "provides_jacobian", True) # backprop is given priority res = QNode.get_best_method(dev, "some_interface") assert res == (JacobianTape, None, "backprop") # device is the next priority res = QNode.get_best_method(dev, "another_interface") assert res == (JacobianTape, "another_interface", "device") # The next fallback is parameter-shift. monkeypatch.setitem(dev._capabilities, "provides_jacobian", False) res = QNode.get_best_method(dev, "another_interface") assert res == (QubitParamShiftTape, "another_interface", "best") # finally, if both fail, finite differences is the fallback def capabilities(cls): capabilities = cls._capabilities capabilities.update(model="None") return capabilities monkeypatch.setattr(qml.devices.DefaultQubit, "capabilities", capabilities) res = QNode.get_best_method(dev, "another_interface") assert res == (JacobianTape, "another_interface", "numeric")
def construct(self, args, kwargs): """New quantum tape construct method, that performs the transform on the tape in a define-by-run manner""" # the following global variable is defined simply for testing # purposes, so that we can easily extract the transformed operations # for verification. global t_op t_op = [] QNode.construct(self, args, kwargs) new_ops = [] for o in self.qtape.operations: # here, we loop through all tape operations, and make # the transformation if a RY gate is encountered. if isinstance(o, qml.RY): t_op.append(qml.RX(-a * framework.cos(o.data[0]), wires=o.wires)) new_ops.append(t_op[-1]) else: new_ops.append(o) self.qtape._ops = new_ops self.qtape._update()
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_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_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_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_validate_adjoint_invalid_device(self): """Test if a ValueError is raised when an invalid device is provided to _validate_adjoint_method""" dev = qml.device("default.gaussian", wires=1) with pytest.raises(ValueError, match="The default.gaussian device does not"): QNode._validate_adjoint_method(dev, "tf")
def test_validate_backprop_method_invalid_interface(self, monkeypatch): """Test that the method for validating the backprop diff method tape raises an exception if the wrong interface is provided""" dev = qml.device("default.qubit", wires=1) test_interface = "something" monkeypatch.setitem(dev._capabilities, "passthru_interface", test_interface) with pytest.raises(qml.QuantumFunctionError, match=f"when using the {test_interface}"): QNode._validate_backprop_method(dev, None)
def test_validate_backprop_child_method_wrong_interface(self, monkeypatch): """Test that the method for validating the backprop diff method tape raises an error if a child device supports backprop but using a different interface""" dev = qml.device("default.qubit", wires=1) test_interface = "something" orig_capabilities = dev.capabilities().copy() orig_capabilities["passthru_devices"] = {test_interface: "default.gaussian"} monkeypatch.setattr(dev, "capabilities", lambda: orig_capabilities) with pytest.raises(qml.QuantumFunctionError, match=r"when using the \['something'\] interface"): QNode._validate_backprop_method(dev, "another_interface")
def test_parameter_shift_tape_unknown_model(self, monkeypatch): """test that an unknown model raises an exception""" def capabilities(cls): capabilities = cls._capabilities capabilities.update(model="None") return capabilities monkeypatch.setattr(qml.devices.DefaultQubit, "capabilities", capabilities) dev = qml.device("default.qubit", wires=1) with pytest.raises(qml.QuantumFunctionError, match="does not support the parameter-shift rule"): QNode._get_parameter_shift_tape(dev)
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_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_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_validate_device_method(self, monkeypatch): """Test that the method for validating the device diff method tape works as expected""" dev = qml.device("default.qubit", wires=1) with pytest.raises( qml.QuantumFunctionError, match="does not provide a native method for computing the jacobian", ): QNode._validate_device_method(dev, None) monkeypatch.setitem(dev._capabilities, "provides_jacobian", True) tape_class, interface, method = QNode._validate_device_method(dev, "interface") assert tape_class is JacobianTape assert method == "device" assert interface == "interface"
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 construct(self, args, kwargs): """New quantum tape construct method, that performs the transform on the tape in a define-by-run manner""" t_op = [] QNode.construct(self, args, kwargs) new_ops = [] for o in self.qtape.operations: # here, we loop through all tape operations, and make # the transformation if a RY gate is encountered. if isinstance(o, qml.RY): t_op.append( qml.RX(-a * framework.cos(o.data[0]), wires=o.wires)) new_ops.append(t_op[-1]) else: new_ops.append(o) self.qtape._ops = new_ops self.qtape._update()
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_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_validate_backprop_method(self, monkeypatch): """Test that the method for validating the backprop diff method tape works as expected""" dev = qml.device("default.qubit", wires=1) test_interface = "something" monkeypatch.setitem(dev._capabilities, "passthru_interface", test_interface) tape_class, interface, method = QNode._validate_backprop_method(dev, test_interface) assert tape_class is JacobianTape assert method == "backprop" assert interface == None
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