def test_evaluate_qnode_default_input(self, get_circuit, output_dim, n_qubits): """Test if the _evaluate_qnode() method works correctly when the inputs argument is a default argument, i.e., that it gives the same result as calling the QNode directly""" c, w = get_circuit @qml.qnode(qml.device("default.qubit", wires=n_qubits), interface="torch") def c_default(w1, w2, w3, w4, w5, w6, w7, inputs=None): """Version of the circuit with inputs as a default argument""" qml.templates.AngleEmbedding(inputs, wires=list(range(n_qubits))) qml.templates.StronglyEntanglingLayers(w1, wires=list(range(n_qubits))) qml.RX(w2[0], wires=0) qml.RX(w3, wires=0) qml.Rot(*w4, wires=0) qml.templates.StronglyEntanglingLayers(w5, wires=list(range(n_qubits))) qml.Rot(*w6, wires=0) qml.RX(w7, wires=0) return [qml.expval(qml.PauliZ(i)) for i in range(output_dim)] layer = TorchLayer(c_default, w) x = torch.Tensor(np.ones(n_qubits)) layer_out = layer._evaluate_qnode(x).detach().numpy() weights = [layer.qnode_weights[weight].detach().numpy() for weight in ordered_weights] circuit_out = c(x, *weights) assert np.allclose(layer_out, circuit_out)
def test_str_repr(self, get_circuit): """Test the __str__ and __repr__ representations""" c, w = get_circuit layer = TorchLayer(c, w) assert layer.__str__() == "<Quantum Torch Layer: func=circuit>" assert layer.__repr__() == "<Quantum Torch Layer: func=circuit>"
def test_evaluate_qnode_shuffled_args(self, get_circuit, output_dim, n_qubits): """Test if the _evaluate_qnode() method works correctly when the inputs argument is not the first positional argument, i.e., that it gives the same result as calling the QNode directly""" c, w = get_circuit @qml.qnode(qml.device("default.qubit", wires=n_qubits), interface="torch") def c_shuffled(w1, inputs, w2, w3, w4, w5, w6, w7): """Version of the circuit with a shuffled signature""" qml.templates.AngleEmbedding(inputs, wires=list(range(n_qubits))) qml.templates.StronglyEntanglingLayers(w1, wires=list(range(n_qubits))) qml.RX(w2[0], wires=0) qml.RX(w3, wires=0) qml.Rot(*w4, wires=0) qml.templates.StronglyEntanglingLayers(w5, wires=list(range(n_qubits))) qml.Rot(*w6, wires=0) qml.RX(w7, wires=0) return [qml.expval(qml.PauliZ(i)) for i in range(output_dim)] layer = TorchLayer(c_shuffled, w) x = torch.Tensor(np.ones(n_qubits)) layer_out = layer._evaluate_qnode(x) weights = layer.qnode_weights.values() circuit_out = c(x, *weights).type(x.dtype) assert torch.allclose(layer_out, circuit_out)
def __init__(self): super().__init__() self.clayer1 = torch.nn.Linear(n_qubits, n_qubits) self.clayer2 = torch.nn.Linear(output_dim, n_qubits) self.clayer3 = torch.nn.Linear(output_dim, output_dim) self.qlayer1 = TorchLayer(c, w) self.qlayer2 = TorchLayer(c, w)
def test_var_keyword(self, n_qubits, output_dim): """Test that variable number of keyword arguments works""" dev = qml.device("default.qubit", wires=n_qubits) w = { "w1": (3, n_qubits, 3), "w2": (1,), "w3": 1, "w4": [3], "w5": (2, n_qubits, 3), "w6": 3, "w7": 0, } @qml.qnode(dev, interface="torch") def c(inputs, **kwargs): """A circuit that embeds data using the AngleEmbedding and then performs a variety of operations. The output is a PauliZ measurement on the first output_dim qubits. One set of parameters, w5, are specified as non-trainable.""" qml.templates.AngleEmbedding(inputs, wires=list(range(n_qubits))) qml.templates.StronglyEntanglingLayers(kwargs["w1"], wires=list(range(n_qubits))) qml.RX(kwargs["w2"][0], wires=0 % n_qubits) qml.RX(kwargs["w3"], wires=1 % n_qubits) qml.Rot(*kwargs["w4"], wires=2 % n_qubits) qml.templates.StronglyEntanglingLayers(kwargs["w5"], wires=list(range(n_qubits))) qml.Rot(*kwargs["w6"], wires=3 % n_qubits) qml.RX(kwargs["w7"], wires=4 % n_qubits) return [qml.expval(qml.PauliZ(i)) for i in range(output_dim)] layer = TorchLayer(c, w) x = torch.ones(n_qubits) layer_out = layer._evaluate_qnode(x) circuit_out = c(x, **layer.qnode_weights).type(x.dtype) assert torch.allclose(layer_out, circuit_out)
def test_forward_single_input(self, get_circuit, output_dim, n_qubits): """Test if the forward() method accepts a single input (i.e., not with an extra batch dimension) and returns a tensor of the right shape""" c, w = get_circuit layer = TorchLayer(c, w) x = torch.Tensor(np.ones(n_qubits)) layer_out = layer.forward(x) assert layer_out.shape == torch.Size((output_dim,))
def test_forward(self, get_circuit, output_dim, n_qubits): """Test if the forward() method accepts a batched input and returns a tensor of the right shape""" c, w = get_circuit layer = TorchLayer(c, w) x = torch.Tensor(np.ones((2, n_qubits))) layer_out = layer.forward(x) assert layer_out.shape == torch.Size((2, output_dim))
def test_evaluate_qnode(self, get_circuit, n_qubits): """Test if the _evaluate_qnode() method works correctly, i.e., that it gives the same result as calling the QNode directly""" c, w = get_circuit layer = TorchLayer(c, w) x = torch.ones(n_qubits) layer_out = layer._evaluate_qnode(x) weights = layer.qnode_weights.values() circuit_out = c(x, *weights).type(x.dtype) assert torch.allclose(layer_out, circuit_out)
def test_evaluate_qnode(self, get_circuit, n_qubits): """Test if the _evaluate_qnode() method works correctly, i.e., that it gives the same result as calling the QNode directly""" c, w = get_circuit layer = TorchLayer(c, w) x = torch.ones(n_qubits) layer_out = layer._evaluate_qnode(x).detach().numpy() weights = [layer.qnode_weights[weight].detach().numpy() for weight in ordered_weights] circuit_out = c(x, *weights) assert np.allclose(layer_out, circuit_out)
def test_weight_shape_unspecified(self, get_circuit): """Test if a ValueError is raised when instantiated with a weight missing from the weight_shapes dictionary""" c, w = get_circuit del w["w1"] with pytest.raises(ValueError, match="Must specify a shape for every non-input parameter"): TorchLayer(c, w)
def test_forward_broadcasting(self, get_circuit, output_dim, middle_dim, batch_size, n_qubits): """Test if the forward() method accepts a batched input with multiple dimensions and returns a tensor of the right shape by broadcasting. Also tests if gradients are still backpropagated correctly.""" c, w = get_circuit layer = TorchLayer(c, w) x = torch.Tensor(np.ones((batch_size, middle_dim, n_qubits))) weights = layer.qnode_weights.values() layer_out = layer.forward(x) layer_out.backward(torch.ones_like(layer_out)) g_layer = [w.grad for w in weights] assert g_layer.count(None) == 0 assert layer_out.shape == torch.Size((batch_size, middle_dim, output_dim))
def test_no_input(self, get_circuit): """Test if a TypeError is raised when instantiated with a QNode that does not have an argument with name equal to the input_arg class attribute of TorchLayer""" c, w = get_circuit del c.func.sig[qml.qnn.torch.TorchLayer._input_arg] with pytest.raises(TypeError, match="QNode must include an argument with name"): TorchLayer(c, w)
def test_no_torch(self, get_circuit, monkeypatch): """Test if an ImportError is raised when instantiated without PyTorch""" c, w = get_circuit with monkeypatch.context() as m: m.setattr(qml.qnn.torch, "TORCH_IMPORTED", False) with pytest.raises(ImportError, match="TorchLayer requires PyTorch"): TorchLayer(c, w)
def test_deprecation_warning(self, get_circuit): """Test if deprecation warning is raised""" if int(qml.__version__.split(".")[1]) >= 16: pytest.fail( "Deprecation warnings for the qnn module should be removed") c, w = get_circuit with pytest.warns(DeprecationWarning, match=WARNING_STRING): TorchLayer(c, w)
def test_qnode_weights_registered(self, get_circuit, n_qubits): """Test if the weights in qnode_weights are registered to the internal _parameters dictionary and that requires_grad == True""" c, w = get_circuit layer = TorchLayer(c, w) for name, weight in layer.qnode_weights.items(): assert torch.allclose(weight, layer._parameters[name]) assert weight.requires_grad
def test_nonspecified_init(self, get_circuit, n_qubits, monkeypatch): """Test if weights are initialized according to the uniform distribution in [0, 2 pi]""" c, w = get_circuit uniform_ = mock.MagicMock(return_value=torch.Tensor(1)) with monkeypatch.context() as m: m.setattr(torch.nn.init, "uniform_", uniform_) TorchLayer(c, w) kwargs = uniform_.call_args[1] assert kwargs["b"] == 2 * math.pi
def test_non_input_defaults_tape_mode(self, n_qubits, output_dim): """Test that everything works when default arguments that are not the input argument are present in the QNode in tape mode""" if not qml.tape_mode_active(): pytest.skip("This functionality is only supported in tape mode.") dev = qml.device("default.qubit", wires=n_qubits) w = { "w1": (3, n_qubits, 3), "w2": (1, ), "w3": 1, "w4": [3], "w5": (2, n_qubits, 3), "w6": 3, "w7": 0, } @qml.qnode(dev, interface="torch") def c(inputs, w1, w2, w4, w5, w6, w7, w3=0.5): """A circuit that embeds data using the AngleEmbedding and then performs a variety of operations. The output is a PauliZ measurement on the first output_dim qubits. One set of parameters, w5, are specified as non-trainable.""" qml.templates.AngleEmbedding(inputs, wires=list(range(n_qubits))) qml.templates.StronglyEntanglingLayers(w1, wires=list(range(n_qubits))) qml.RX(w2[0], wires=0 % n_qubits) qml.RX(w3, wires=1 % n_qubits) qml.Rot(*w4, wires=2 % n_qubits) qml.templates.StronglyEntanglingLayers(w5, wires=list(range(n_qubits))) qml.Rot(*w6, wires=3 % n_qubits) qml.RX(w7, wires=4 % n_qubits) return [qml.expval(qml.PauliZ(i)) for i in range(output_dim)] layer = TorchLayer(c, w) x = torch.ones(n_qubits) layer_out = layer._evaluate_qnode(x) circuit_out = c(x, **layer.qnode_weights).type(x.dtype) assert torch.allclose(layer_out, circuit_out)
def test_no_input(self): """Test if a TypeError is raised when instantiated with a QNode that does not have an argument with name equal to the input_arg class attribute of TorchLayer""" dev = qml.device("default.qubit", wires=1) weight_shapes = {"w1": (3, 3), "w2": 1} @qml.qnode(dev, interface="torch") def circuit(w1, w2): return qml.expval(qml.PauliZ(0)) with pytest.raises(TypeError, match="QNode must include an argument with name"): TorchLayer(circuit, weight_shapes)
def test_input_in_weight_shapes(self, get_circuit, n_qubits): """Test if a ValueError is raised when instantiated with a weight_shapes dictionary that contains the shape of the input argument given by the input_arg class attribute of TorchLayer""" c, w = get_circuit w[qml.qnn.torch.TorchLayer._input_arg] = n_qubits with pytest.raises( ValueError, match="{} argument should not have its dimension".format( qml.qnn.torch.TorchLayer._input_arg), ): TorchLayer(c, w)
def test_var_pos(self): """Test if a TypeError is raised when instantiated with a variable number of positional arguments""" dev = qml.device("default.qubit", wires=1) weight_shapes = {"w1": (3, 3), "w2": 1} @qml.qnode(dev, interface="torch") def circuit(inputs, w1, w2, *args): return qml.expval(qml.PauliZ(0)) with pytest.raises(TypeError, match="Cannot have a variable number of positional"): TorchLayer(circuit, weight_shapes)
def test_gradients(self, get_circuit, n_qubits): """Test if the gradients of the TorchLayer are equal to the gradients of the circuit when taken with respect to the trainable variables""" c, w = get_circuit layer = TorchLayer(c, w) x = torch.ones(n_qubits) weights = [layer.qnode_weights[weight] for weight in ordered_weights] out_layer = layer(x) out_layer.backward() g_layer = [w.grad.numpy() for w in weights] out_circuit = c(x, *weights) out_circuit.backward() g_circuit = [w.grad.numpy() for w in weights] for g1, g2 in zip(g_layer, g_circuit): assert np.allclose(g1, g2) assert len(weights) == len(list(layer.parameters()))
def test_non_input_defaults(self, get_circuit, n_qubits): """Test if a TypeError is raised when default arguments that are not the input argument are present in the QNode""" c, w = get_circuit @qml.qnode(qml.device("default.qubit", wires=n_qubits), interface="torch") def c_dummy(inputs, w1, w2, w3, w4, w5, w6, w7, w8=None): """Dummy version of the circuit with a default argument""" return c(inputs, w1, w2, w3, w4, w5, w6, w7) with pytest.raises( TypeError, match="Only the argument {} is permitted".format(qml.qnn.torch.TorchLayer._input_arg), ): TorchLayer(c_dummy, {**w, **{"w8": 1}})
def test_var_keyword(self, get_circuit, monkeypatch): """Test if a TypeError is raised when instantiated with a variable number of keyword arguments""" c, w = get_circuit class FuncPatch: """Patch for variable number of keyword arguments""" sig = c.func.sig var_pos = False var_keyword = True with monkeypatch.context() as m: m.setattr(c, "func", FuncPatch) with pytest.raises(TypeError, match="Cannot have a variable number of keyword"): TorchLayer(c, w)
def test_qnode_weights_shapes(self, get_circuit, n_qubits): """Test if the weights in qnode_weights have the correct shape""" c, w = get_circuit layer = TorchLayer(c, w) ideal_shapes = { "w1": torch.Size((3, n_qubits, 3)), "w2": torch.Size((1,)), "w3": torch.Size([]), "w4": torch.Size((3,)), "w5": torch.Size((2, n_qubits, 3)), "w6": torch.Size((3,)), "w7": torch.Size([]), } for name, weight in layer.qnode_weights.items(): assert weight.shape == ideal_shapes[name]
def test_var_keyword(self, get_circuit, monkeypatch): """Test if a TypeError is raised when instantiated with a variable number of keyword arguments""" if qml.tape_mode_active(): pytest.skip("This functionality is supported in tape mode.") c, w = get_circuit class FuncPatch: """Patch for variable number of keyword arguments""" sig = c.func.sig var_pos = False var_keyword = True with monkeypatch.context() as m: m.setattr(c, "func", FuncPatch) with pytest.raises(TypeError, match="Cannot have a variable number of keyword"): TorchLayer(c, w)
def test_interface_conversion(get_circuit, skip_if_no_tf_support): """Test if input QNodes with all types of interface are converted internally to the PyTorch interface""" c, w = get_circuit layer = TorchLayer(c, w) assert layer.qnode.interface == "torch"