def test_dot_product_qnodes_qnodes(self, qnodes, interface, tf_support, torch_support): """Test that the dot product of qnodes.qnodes can be applied using all interfaces""" if interface == "torch" and not torch_support: pytest.skip("Skipped, no torch support") if interface == "tf" and not tf_support: pytest.skip("Skipped, no tf support") qnode1, qnode2 = qnodes qc1 = qml.QNodeCollection([qnode1, qnode2]) qc2 = qml.QNodeCollection([qnode1, qnode2]) # test the dot product of qnodes, qnodes cost = qml.dot(qc1, qc2) params = [0.5643, -0.45] res = cost(params) qc1val = qc1(params) qc2val = qc2(params) if interface in ("tf", "torch"): res = res.numpy() qc1val = qc1val.numpy() qc2val = qc2val.numpy() expected = np.dot(qc1val, qc2val) assert np.all(res == expected)
def test_interface_property(self, interface, tf_support, torch_support): """Test that the interface property correctly resolves interfaces from the internal QNodes""" if interface == "torch" and not torch_support: pytest.skip("Skipped, no torch support") if interface == "tf" and not tf_support: pytest.skip("Skipped, no tf support") qc = qml.QNodeCollection() assert qc.interface is None def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=1) qnodes = [ qml.QNode(circuit, dev, interface=interface) for i in range(4) ] qc = qml.QNodeCollection(qnodes) if interface == "numpy": # Note: the "numpy" interface is deprecated, and # now resolves to "autograd" interface = "autograd" assert qc.interface == interface
def test_nested_apply(self, qnodes, interface, tf_support, torch_support, tol): """Test that nested apply can be done using all interfaces""" if interface == "torch" and not torch_support: pytest.skip("Skipped, no torch support") if interface == "tf" and not tf_support: pytest.skip("Skipped, no tf support") qnode1, qnode2 = qnodes qc = qml.QNodeCollection([qnode1, qnode2]) if interface == "tf": sinfn = tf.sin sfn = tf.reduce_sum elif interface == "torch": sinfn = torch.sin sfn = torch.sum elif interface == "jax": sinfn = jnp.sin sfn = jnp.sum else: sinfn = np.sin sfn = np.sum cost = qml.collections.apply(sfn, qml.collections.apply(sinfn, qc)) params = [0.5643, -0.45] res = cost(params) expected = sfn(sinfn(qc(params))) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_grad_tf(self, qnodes, skip_if_no_tf_support, parallel): """Test correct gradient of the QNodeCollection using the tf interface""" qnode1, qnode2 = qnodes # calculate the gradient of the collection using tf params = Variable([0.5643, -0.45]) qc = qml.QNodeCollection([qnode1, qnode2]) with tf.GradientTape() as tape: tape.watch(params) if parallel: with pytest.warns(UserWarning): cost = sum(qc(params, parallel=parallel)) else: cost = sum(qc(params, parallel=parallel)) # the gradient will be None res = tape.gradient(cost, params).numpy() # calculate the gradient of the QNodes individually using tf params = Variable([0.5643, -0.45]) with tf.GradientTape() as tape: tape.watch(params) cost = sum(qnode1(params) + qnode2(params)) expected = tape.gradient(cost, params).numpy() assert np.all(res == expected)
def test_dot_product_qnodes_tensor(self, qnodes, interface, tf_support, torch_support): """Test that the dot product of qnodes.tensor can be applied using all interfaces""" if interface == "torch" and not torch_support: pytest.skip("Skipped, no torch support") if interface == "tf" and not tf_support: pytest.skip("Skipped, no tf support") qnode1, _ = qnodes qc = qml.QNodeCollection([qnode1]) coeffs = [0.5, -0.1] if interface == "torch": coeffs = torch.tensor(coeffs, dtype=torch.float64) if interface == "tf": coeffs = tf.cast(coeffs, dtype=tf.float64) # test the dot product of qnodes, tensor cost = qml.dot(qc, coeffs) params = [0.5643, -0.45] res = cost(params) qcval = qc(params) if interface in ("tf", "torch"): res = res.numpy() qcval = qcval.numpy() coeffs = coeffs.numpy() expected = np.dot(qcval, coeffs) assert np.all(res == expected)
def test_mismatching_interface(self, monkeypatch): """Test exception raised if the interfaces don't match""" dev = qml.device("default.qubit", wires=1) @qml.qnode(dev, interface=None) def circuit1(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) @qml.qnode(dev, interface="autograd") def circuit2(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) qc1 = qml.QNodeCollection([circuit1]) qc2 = qml.QNodeCollection([circuit2]) with pytest.raises(ValueError, match="have non-matching interfaces"): qml.dot(qc1, qc2)
def test_eval_autograd(self, qnodes): """Test correct evaluation of the QNodeCollection using the Autograd interface""" qnode1, qnode2 = qnodes qc = qml.QNodeCollection([qnode1, qnode2]) params = [0.5643, -0.45] res = qc(params) expected = np.vstack([qnode1(params), qnode2(params)]) assert np.all(res == expected)
def test_eval_tf(self, qnodes, skip_if_no_tf_support): """Test correct evaluation of the QNodeCollection using the tf interface""" qnode1, qnode2 = qnodes qc = qml.QNodeCollection([qnode1, qnode2]) params = [0.5643, -0.45] res = qc(params).numpy() expected = np.vstack([qnode1(params), qnode2(params)]) assert np.all(res == expected)
def test_indexing(self): """Test that indexing into the QNodeCollection correctly works""" def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=1) qnodes = [qml.QNode(circuit, dev) for i in range(4)] qc = qml.QNodeCollection(qnodes) assert qc[2] == qnodes[2]
def test_init_with_qnodes(self): """Test that a QNode collection can be initialized with QNodes""" dev = qml.device("default.qubit", wires=1) def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) qnodes = [qml.QNode(circuit, dev) for i in range(4)] qc = qml.QNodeCollection(qnodes) assert qc.qnodes == qnodes assert len(qc) == 4
def test_unknown_interface(self, monkeypatch): """Test exception raised if the interface is unknown""" monkeypatch.setattr(qml.QNodeCollection, "interface", "invalid") dev = qml.device("default.qubit", wires=1) def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) qnodes = [qml.QNode(circuit, dev) for i in range(4)] qc = qml.QNodeCollection(qnodes) with pytest.raises(ValueError, match="Unknown interface invalid"): qml.sum(qc)
def test_extend_qnodes(self): """Test that a list of QNodes is correctly appended""" qc = qml.QNodeCollection() assert qc.qnodes == [] def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=1) qnodes = [qml.QNode(circuit, dev) for i in range(4)] qc.extend(qnodes) assert qc.qnodes == [] + qnodes
def test_append_qnode(self): """Test that a QNode is correctly appended""" qc = qml.QNodeCollection() assert qc.qnodes == [] def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=1) qnode = qml.QNode(circuit, dev) qc.append(qnode) assert qc.qnodes == [qnode]
def test_grad_autograd(self, qnodes): """Test correct gradient of the QNodeCollection using the Autograd interface""" qnode1, qnode2 = qnodes params = [0.5643, -0.45] qc = qml.QNodeCollection([qnode1, qnode2]) cost_qc = lambda params: np.sum(qc(params)) grad_qc = qml.grad(cost_qc, argnum=0) cost_expected = lambda params: np.sum(qnode1(params) + qnode2(params)) grad_expected = qml.grad(cost_expected, argnum=0) res = grad_qc(params) expected = grad_expected(params) assert np.all(res == expected)
def test_extend_multiple_interface_qnodes(self): """Test that an error is returned if QNodes with differing interfaces are attempted to be added to a QNodeCollection""" qc = qml.QNodeCollection() def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=1) qnodes = [ qml.QNode(circuit, dev, interface="autograd"), qml.QNode(circuit, dev, interface=None), ] with pytest.raises(ValueError, match="do not all use the same interface"): qc.extend(qnodes)
def test_extend_interface_mistmatch(self): """Test that an error is returned if QNodes with a differing interface to the QNode collection are appended""" qc = qml.QNodeCollection() def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) dev = qml.device("default.qubit", wires=1) qnode1 = qml.QNode(circuit, dev, interface="autograd") qnode2 = qml.QNode(circuit, dev, interface=None) qc.extend([qnode1]) with pytest.raises(ValueError, match="Interface mismatch"): qc.extend([qnode2])
def test_unknown_interface(self, monkeypatch): """Test exception raised if the interface is unknown""" monkeypatch.setattr(qml.QNodeCollection, "interface", "invalid") dev = qml.device("default.qubit", wires=1) @qml.qnode(dev) def circuit1(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) @qml.qnode(dev) def circuit2(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) qc = qml.QNodeCollection([circuit1, circuit2]) with pytest.raises(ValueError, match="Unknown interface invalid"): qml.dot([1, 2], qc)
def test_grad_torch(self, qnodes, skip_if_no_torch_support, parallel): """Test correct gradient of the QNodeCollection using the torch interface""" qnode1, qnode2 = qnodes # calculate the gradient of the collection using pytorch params = torch.autograd.Variable(torch.tensor([0.5643, -0.45]), requires_grad=True) qc = qml.QNodeCollection([qnode1, qnode2]) cost = torch.sum(qc(params, parallel=parallel)) cost.backward() res = params.grad.numpy() # calculate the gradient of the QNodes individually using pytorch params = torch.autograd.Variable(torch.tensor([0.5643, -0.45]), requires_grad=True) cost = torch.sum(qnode1(params) + qnode2(params)) cost.backward() expected = params.grad.numpy() assert np.all(res == expected)
def test_apply_summation(self, qnodes, interface, tf_support, torch_support, tol): """Test that summation can be applied using all interfaces""" if interface == "torch" and not torch_support: pytest.skip("Skipped, no torch support") if interface == "tf" and not tf_support: pytest.skip("Skipped, no tf support") qnode1, qnode2 = qnodes qc = qml.QNodeCollection([qnode1, qnode2]) cost = qml.sum(qc) params = [0.5643, -0.45] res = cost(params) expected = sum(qc[0](params) + qc[1](params)) if interface in ("tf", "torch"): res = res.numpy() expected = expected.numpy() assert np.allclose(res, expected, atol=tol, rtol=0)
def __init__(self, n_qubits, q_depth, q_delta=0.1): """ This is the quantum generator as described in https://arxiv.org/pdf/2010.06201.pdf """ super().__init__() self.q_params = nn.ParameterList([ nn.Parameter(q_delta * torch.randn(q_depth * n_qubits)) for i in range(8) ]) self.n_qubits = n_qubits self.q_depth = q_depth # Spread of the random parameters for the paramaterised quantum gates self.q_delta = q_delta device = qml.device('lightning.qubit', wires=self.n_qubits) self.quantum_sim = QuantumSim(n_qubits, q_depth) self.qnodes = qml.QNodeCollection([ qml.QNode(self.quantum_sim.circuit, device, interface="torch") for i in range(8) ])
return qml.expval(A1 @ B2) @qml.qnode(dev) def measure_A2B1(): bell_pair() return qml.expval(A2 @ B1) @qml.qnode(dev) def measure_A2B2(): bell_pair() return qml.expval(A2 @ B2) circuits = qml.QNodeCollection( [measure_A1B1, measure_A1B2, measure_A2B1, measure_A2B2]) # now we measure each circuit and construct the CHSH inequality expvals = circuits() # The CHSH operator is A1 @ B1 + A1 @ B2 + A2 @ B1 - A2 @ B2 CHSH_expval = np.sum(expvals[:3]) - expvals[3] print(CHSH_expval) ############################################################################## # The output here is :math:`2\sqrt{2}`, which is the maximal value of the # CHSH inequality. States which have a value # :math:`\langle CHSH \rangle \geq 2` can safely be considered # "quantum". # # .. note:: In this situation "quantum" means that there is
qml.CZ(wires=[0, 1]) qml.CZ(wires=[1, 2]) qml.CZ(wires=[1, 3]) for i in range(n_wires): qml.Rot(*params[0, 1, i], wires=i) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval( qml.PauliZ(2)) ############################################################################## # We finally combine the two devices into a :class:`~.pennylane.QNodeCollection` that uses the # PyTorch interface: qnodes = qml.QNodeCollection([ qml.QNode(circuit0, dev0, interface="torch"), qml.QNode(circuit1, dev1, interface="torch") ]) ############################################################################## # Postprocessing into a prediction # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # The ``predict_point`` function below allows us to find the ensemble prediction, as well as keeping # track of the individual predictions from each QPU. # # We include a ``parallel`` keyword argument for evaluating the :class:`~.pennylane.QNodeCollection` # in a parallel asynchronous manner. This feature requires the ``dask`` library, which can be # installed using ``pip install "dask[delayed]"``. When ``parallel=True``, we are able to make # predictions faster because we do not need to wait for one QPU to output before running on the # other.
def test_sequence(self): """Test that the QNodeCollection is a sequence type""" qc = qml.QNodeCollection() assert isinstance(qc, Sequence)
def test_empty_init(self): """Test that an empty QNode collection can be initialized""" qc = qml.QNodeCollection() assert qc.qnodes == [] assert len(qc) == 0