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_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 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_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 == (QuantumTape, None, "backprop") # device is the next priority res = QNode.get_best_method(dev, "another_interface") assert res == (QuantumTape, "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 == (QuantumTape, "another_interface", "numeric")
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 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 expval(qml.PauliX(0)) spy_numeric = mocker.spy(QuantumTape, "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_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_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_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_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 expval(qml.PauliZ(0)) qn = QNode(func, dev) x = 0.12 y = 0.54 res = qn(x, y) assert isinstance(qn.qtape, QuantumTape) 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.beta.tapes.QNode.get_best_method") mock_best.return_value = 1, 2, 3 mock_backprop = mocker.patch( "pennylane.beta.tapes.QNode._validate_backprop_method") mock_backprop.return_value = 4, 5, 6 mock_device = mocker.patch( "pennylane.beta.tapes.QNode._validate_device_method") mock_device.return_value = 7, 8, 9 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[2] 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[2] 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[2] mock_device.assert_called_once() qn = QNode(None, dev, diff_method="finite-diff") assert qn._tape == QuantumTape 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_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 QuantumTape assert method == "device" assert interface == "interface"
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 QuantumTape assert method == "backprop" assert interface == None
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 = expval(qml.PauliZ(0)) m2 = 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 = expval(qml.PauliZ(0)) return 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_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 probs(wires=0), 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_import_error(self, mocker): """Test that an exception is caught on import error""" mock = mocker.patch( "pennylane.beta.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 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_parameter_shift_tape_qubit_device(self): """Test that the get_parameter_shift_method method correctly and returns the correct tape for qubit devices.""" dev = qml.device("default.qubit", wires=1) tape_class = QNode._get_parameter_shift_tape(dev) assert tape_class is QubitParamShiftTape
def test_invalid_device(self): """Test that an exception is raised for an invalid device""" with pytest.raises(qml.QuantumFunctionError, match="Invalid device"): QNode(None, None)