def test_expand_three(self, tol): """Test that a 3 qubit gate correctly expands to 4 qubits.""" dev = plf.NumpyWavefunctionDevice(wires=4) # test applied to wire 0,1,2 res = dev.expand(U_toffoli, [0, 1, 2]) expected = np.kron(U_toffoli, I) self.assertAllAlmostEqual(res, expected, delta=tol) # test applied to wire 1,2,3 res = dev.expand(U_toffoli, [1, 2, 3]) expected = np.kron(I, U_toffoli) self.assertAllAlmostEqual(res, expected, delta=tol) # test applied to wire 0,2,3 res = dev.expand(U_toffoli, [0, 2, 3]) expected = (np.kron(SWAP, np.kron(I, I)) @ np.kron(I, U_toffoli) @ np.kron(SWAP, np.kron(I, I))) self.assertAllAlmostEqual(res, expected, delta=tol) # test applied to wire 0,1,3 res = dev.expand(U_toffoli, [0, 1, 3]) expected = (np.kron(np.kron(I, I), SWAP) @ np.kron(U_toffoli, I) @ np.kron(np.kron(I, I), SWAP)) self.assertAllAlmostEqual(res, expected, delta=tol) # test applied to wire 3, 1, 2 res = dev.expand(U_toffoli, [3, 1, 2]) # change the control qubit on the Toffoli gate rows = np.array([0, 4, 1, 5, 2, 6, 3, 7]) expected = np.kron(I, U_toffoli[:, rows][rows]) self.assertAllAlmostEqual(res, expected, delta=tol) # test applied to wire 3, 0, 2 res = dev.expand(U_toffoli, [3, 0, 2]) # change the control qubit on the Toffoli gate rows = np.array([0, 4, 1, 5, 2, 6, 3, 7]) expected = (np.kron(SWAP, np.kron(I, I)) @ np.kron( I, U_toffoli[:, rows][rows]) @ np.kron(SWAP, np.kron(I, I))) self.assertAllAlmostEqual(res, expected, delta=tol)
# ~~~~~~~ # Next, we create a quantum device with 4 qubits. dev = qml.device("default.qubit", wires=n_wires, analytic=True, shots=1) ############################################################################## # We also require a quantum node which will apply the operators according to the # angle parameters, and return the expectation value of the observable # :math:`\sigma_z^{j}\sigma_z^{k}` to be used in each term of the objective function later on. The # argument ``edge`` specifies the chosen edge term in the objective function, :math:`(j,k)`. # Once optimized, the same quantum node can be used for sampling an approximately optimal bitstring # if executed with the ``edge`` keyword set to ``None``. Additionally, we specify the number of layers # (repeated applications of :math:`U_BU_C`) using the keyword ``n_layers``. pauli_z = [[1, 0], [0, -1]] pauli_z_2 = np.kron(pauli_z, pauli_z) @qml.qnode(dev) def circuit(gammas, betas, edge=None, n_layers=1): # apply Hadamards to get the n qubit |+> state for wire in range(n_wires): qml.Hadamard(wires=wire) # p instances of unitary operators for i in range(n_layers): U_C(gammas[i]) U_B(betas[i]) if edge is None: # measurement phase return qml.sample(comp_basis_measurement(range(n_wires))) # during the optimization phase we are evaluating a term
# Since the specific problem considered in this tutorial has a small size, we can also # solve it in a classical way and then compare the results with our quantum solution. # ############################################################################## # Classical algorithm # ^^^^^^^^^^^^^^^^^^^ # To solve the problem in a classical way, we use the explicit matrix representation in # terms of numerical NumPy arrays. Id = np.identity(2) Z = np.array([[1, 0], [0, -1]]) X = np.array([[0, 1], [1, 0]]) A_0 = np.identity(8) A_1 = np.kron(np.kron(X, Z), Id) A_2 = np.kron(np.kron(X, Id), Id) A_num = c[0] * A_0 + c[1] * A_1 + c[2] * A_2 b = np.ones(8) / np.sqrt(8) ############################################################################## # We can print the explicit values of :math:`A` and :math:`b`: print("A = \n", A_num) print("b = \n", b) ############################################################################## # The solution can be computed via a matrix inversion: A_inv = np.linalg.inv(A_num)
# qml.PauliZ(wires=wires[0]) # qml.RZ(-z/2, wires=wires[0]) # # # def U(z, wires): # oper_A(wires=[wires[0], wires[1]]) # oper_B(wires=[wires[2], wires[3]]) # oper_C(z, wires=[wires[1], wires[2], wires[3]]) # oper_D(z, wires=wires) # oper_A(wires=[wires[0], wires[1]]) # Construction of U # Basis states s0 = np.asanyarray([[1, 0]]) s1 = np.asanyarray([[0, 1]]) s00 = np.kron(s0, s0) s01 = np.kron(s0, s1) s10 = np.kron(s1, s0) s11 = np.kron(s1, s1) # Bell states sp = (s01 + s10) / np.sqrt(2) # (|01> + |10>)/√2 sm = (s01 - s10) / np.sqrt(2) # (|01> - |10>)/√2 o0000 = s00.transpose() * s00 o1111 = s11.transpose() * s11 opp = sp.transpose() * sp omm = sm.transpose() * sm def U0_(t, wires):
rotated_probs = circuit_qwc(weights) print(rotated_probs) ############################################################################## # We're not quite there yet; we have only calculated the probabilities of the variational circuit # rotated into the shared eigenbasis---the :math:`|\langle \phi_n |\psi\rangle|^2`. To recover the # *expectation values* of the two QWC observables from the probabilities, recall that we need one # final piece of information: their eigenvalues :math:`\lambda_{A, n}` and :math:`\lambda_{B, n}`. # # We know that the single-qubit Pauli operators each have eigenvalues :math:`(1, -1)`, while the identity # operator has eigenvalues :math:`(1, 1)`. We can make use of ``np.kron`` to quickly # generate the eigenvalues of the full Pauli terms, making sure that the order # of the eigenvalues in the Kronecker product corresponds to the tensor product. eigenvalues_XYI = np.kron(np.kron([1, -1], [1, -1]), [1, 1]) eigenvalues_XIZ = np.kron(np.kron([1, -1], [1, 1]), [1, -1]) # Taking the linear combination of the eigenvalues and the probabilities print("Expectation value of XYI = ", np.dot(eigenvalues_XYI, rotated_probs)) print("Expectation value of XIZ = ", np.dot(eigenvalues_XIZ, rotated_probs)) ############################################################################## # Compare this to the result when we used two circuit evaluations. We have successfully used a # single circuit evaluation to recover both expectation values! # # Luckily, PennyLane automatically performs this QWC grouping under the hood. We simply # return the two QWC Pauli terms from the QNode: @qml.qnode(dev)
# To perform "doubly stochastic" gradient descent, we simply apply the stochastic # gradient descent approach from above, but in addition also uniformly sample # a subset of the terms for the Hamiltonian expectation at each optimization step. # This inserts another element of stochasticity into the system---all the while # convergence continues to be guaranteed! # # Let's create a QNode that randomly samples a single term from the above # Hamiltonian as the observable to be measured. I = np.identity(2) X = np.array([[0, 1], [1, 0]]) Y = np.array([[0, -1j], [1j, 0]]) Z = np.array([[1, 0], [0, -1]]) terms = np.array([ 2 * np.kron(I, X), 4 * np.kron(I, Z), -np.kron(X, X), 5 * np.kron(Y, Y), 2 * np.kron(Z, X) ]) @qml.qnode(dev_stochastic) def circuit(params, n=None): StronglyEntanglingLayers(weights=params, wires=[0, 1]) idx = np.random.choice(np.arange(5), size=n, replace=False) A = np.sum(terms[idx], axis=0) return expval(qml.Hermitian(A, wires=[0, 1])) def loss(params): return 4 + (5 / 1) * circuit(params, n=1)
def test_hermitian(self, device, tol, skip_if): """Test that a tensor product involving qml.Hermitian works correctly""" n_wires = 3 dev = device(n_wires) if dev.shots is None: pytest.skip("Device is in analytic mode, cannot test sampling.") if "Hermitian" not in dev.observables: pytest.skip( "Skipped because device does not support the Hermitian observable." ) skip_if(dev, {"supports_tensor_observables": False}) theta = 0.432 phi = 0.123 varphi = -0.543 A_ = 0.1 * np.array([ [-6, 2 + 1j, -3, -5 + 2j], [2 - 1j, 0, 2 - 1j, -5 + 4j], [-3, 2 + 1j, 0, -4 + 3j], [-5 - 2j, -5 - 4j, -4 - 3j, -6], ]) @qml.qnode(dev) def circuit(): qml.RX(theta, wires=[0]) qml.RX(phi, wires=[1]) qml.RX(varphi, wires=[2]) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[1, 2]) return qml.sample( qml.PauliZ(wires=[0]) @ qml.Hermitian(A_, wires=[1, 2])) res = circuit() # res should only contain the eigenvalues of # the hermitian matrix tensor product Z Z = np.diag([1, -1]) eigvals = np.linalg.eigvalsh(np.kron(Z, A_)) assert np.allclose(sorted(np.unique(res)), sorted(eigvals), atol=tol(False)) mean = np.mean(res) expected = (0.1 * 0.5 * (-6 * np.cos(theta) * (np.cos(varphi) + 1) - 2 * np.sin(varphi) * (np.cos(theta) + np.sin(phi) - 2 * np.cos(phi)) + 3 * np.cos(varphi) * np.sin(phi) + np.sin(phi))) assert np.allclose(mean, expected, atol=tol(False)) var = np.var(res) expected = ( 0.01 * (1057 - np.cos(2 * phi) + 12 * (27 + np.cos(2 * phi)) * np.cos(varphi) - 2 * np.cos(2 * varphi) * np.sin(phi) * (16 * np.cos(phi) + 21 * np.sin(phi)) + 16 * np.sin(2 * phi) - 8 * (-17 + np.cos(2 * phi) + 2 * np.sin(2 * phi)) * np.sin(varphi) - 8 * np.cos(2 * theta) * (3 + 3 * np.cos(varphi) + np.sin(varphi))**2 - 24 * np.cos(phi) * (np.cos(phi) + 2 * np.sin(phi)) * np.sin(2 * varphi) - 8 * np.cos(theta) * (4 * np.cos(phi) * (4 + 8 * np.cos(varphi) + np.cos(2 * varphi) - (1 + 6 * np.cos(varphi)) * np.sin(varphi)) + np.sin(phi) * (15 + 8 * np.cos(varphi) - 11 * np.cos(2 * varphi) + 42 * np.sin(varphi) + 3 * np.sin(2 * varphi)))) / 16) assert np.allclose(var, expected, atol=tol(False))
# ~~~~~~~ # Next, we create a quantum device with 4 qubits. dev = qml.device("default.qubit", wires=n_wires, shots=1) ############################################################################## # We also require a quantum node which will apply the operators according to the # angle parameters, and return the expectation value of the observable # :math:`\sigma_z^{j}\sigma_z^{k}` to be used in each term of the objective function later on. The # argument ``edge`` specifies the chosen edge term in the objective function, :math:`(j,k)`. # Once optimized, the same quantum node can be used for sampling an approximately optimal bitstring # if executed with the ``edge`` keyword set to ``None``. Additionally, we specify the number of layers # (repeated applications of :math:`U_BU_C`) using the keyword ``n_layers``. pauli_z = [[1, 0], [0, -1]] pauli_z_2 = np.kron(pauli_z, pauli_z, requires_grad=False) @qml.qnode(dev) def circuit(gammas, betas, edge=None, n_layers=1): # apply Hadamards to get the n qubit |+> state for wire in range(n_wires): qml.Hadamard(wires=wire) # p instances of unitary operators for i in range(n_layers): U_C(gammas[i]) U_B(betas[i]) if edge is None: # measurement phase return qml.sample(comp_basis_measurement(range(n_wires))) # during the optimization phase we are evaluating a term
def test_hermitian(self, theta, phi, varphi, monkeypatch, tol): """Test that a tensor product involving qml.Hermitian works correctly""" dev = qml.device("expt.tensornet", wires=3) dev.reset() dev.apply("RX", wires=[0], par=[theta]) dev.apply("RX", wires=[1], par=[phi]) dev.apply("RX", wires=[2], par=[varphi]) dev.apply("CNOT", wires=[0, 1], par=[]) dev.apply("CNOT", wires=[1, 2], par=[]) A = np.array( [ [-6, 2 + 1j, -3, -5 + 2j], [2 - 1j, 0, 2 - 1j, -5 + 4j], [-3, 2 + 1j, 0, -4 + 3j], [-5 - 2j, -5 - 4j, -4 - 3j, -6], ] ) with monkeypatch.context() as m: m.setattr("numpy.random.choice", lambda x, y, p: (x, p)) s1, p = dev.sample(["PauliZ", "Hermitian"], [[0], [1, 2]], [[], [A]]) # s1 should only contain the eigenvalues of # the hermitian matrix tensor product Z Z = np.diag([1, -1]) eigvals = np.linalg.eigvalsh(np.kron(Z, A)) assert set(np.round(s1, 8)).issubset(set(np.round(eigvals, 8))) mean = s1 @ p expected = 0.5 * ( -6 * np.cos(theta) * (np.cos(varphi) + 1) - 2 * np.sin(varphi) * (np.cos(theta) + np.sin(phi) - 2 * np.cos(phi)) + 3 * np.cos(varphi) * np.sin(phi) + np.sin(phi) ) assert np.allclose(mean, expected, atol=tol, rtol=0) var = (s1 ** 2) @ p - (s1 @ p).real ** 2 expected = ( 1057 - np.cos(2 * phi) + 12 * (27 + np.cos(2 * phi)) * np.cos(varphi) - 2 * np.cos(2 * varphi) * np.sin(phi) * (16 * np.cos(phi) + 21 * np.sin(phi)) + 16 * np.sin(2 * phi) - 8 * (-17 + np.cos(2 * phi) + 2 * np.sin(2 * phi)) * np.sin(varphi) - 8 * np.cos(2 * theta) * (3 + 3 * np.cos(varphi) + np.sin(varphi)) ** 2 - 24 * np.cos(phi) * (np.cos(phi) + 2 * np.sin(phi)) * np.sin(2 * varphi) - 8 * np.cos(theta) * ( 4 * np.cos(phi) * ( 4 + 8 * np.cos(varphi) + np.cos(2 * varphi) - (1 + 6 * np.cos(varphi)) * np.sin(varphi) ) + np.sin(phi) * ( 15 + 8 * np.cos(varphi) - 11 * np.cos(2 * varphi) + 42 * np.sin(varphi) + 3 * np.sin(2 * varphi) ) ) ) / 16 assert np.allclose(var, expected, atol=tol, rtol=0)
def test_ev(self): """Test that expectation values are calculated correctly""" self.logTestName() self.dev.reset() # loop through all supported observables for name, fn in self.dev._expectation_map.items(): print(name) log.debug("\tTesting %s observable...", name) # start in the state |00> self.dev._state = np.array([1, 0, 1, 1]) / np.sqrt(3) # get the equivalent pennylane operation class op = qml.expval.__getattribute__(name) if op.par_domain == "A": # the parameter is an array p = [H] else: # the parameter is a float p = [0.432423, -0.12312, 0.324][:op.num_params] if callable(fn): # if the default.qubit is an operation accepting parameters, # initialise it using the parameters generated above. O = fn(*p) else: # otherwise, the operation is simply an array. O = fn print("op.num_wires=" + str(op.num_wires)) # calculate the expected output if op.num_wires == 1 or op.num_wires == 0: expected_out = self.dev._state.conj() @ np.kron( O, I) @ self.dev._state elif op.num_wires == 2: expected_out = self.dev._state.conj() @ O @ self.dev._state else: raise NotImplementedError( "Test for operations with num_wires=" + op.num_wires + " not implemented.") res = self.dev.ev(O, wires=[0]) # verify the device is now in the expected state self.assertAllAlmostEqual(res, expected_out, delta=self.tol) # text exception raised if matrix is not 2x2 or 4x4 with self.assertRaisesRegex( ValueError, "Only one and two-qubit expectation is supported."): self.dev.ev(U_toffoli, [0]) # text warning raised if matrix is complex with self.assertLogs(level="WARNING") as l: self.dev.ev(H + 1j, [0]) self.assertEqual(len(l.output), 1) self.assertEqual(len(l.records), 1) self.assertIn("Nonvanishing imaginary part", l.output[0])
qml.grad(circuit_dq)(params), tol) @pytest.mark.parametrize( "returns", [ qml.PauliZ(0), qml.PauliX(2), qml.PauliZ(0) @ qml.PauliY(3), qml.Hadamard(2), qml.Hadamard(3) @ qml.PauliZ(2), # qml.Projector([0, 1], wires=[0, 2]) @ qml.Hadamard(3) # qml.Projector([0, 0], wires=[2, 0]) qml.PauliX(0) @ qml.PauliY(3), qml.PauliY(0) @ qml.PauliY(2) @ qml.PauliY(3), qml.Hermitian(np.kron(qml.PauliY.compute_matrix(), qml.PauliZ.compute_matrix()), wires=[3, 2]), qml.Hermitian(np.array([[0, 1], [1, 0]], requires_grad=False), wires=0), qml.Hermitian(np.array([[0, 1], [1, 0]], requires_grad=False), wires=0) @ qml.PauliZ(2), ], ) def test_integration(returns): """Integration tests that compare to default.qubit for a large circuit containing parametrized operations""" dev_def = qml.device("default.qubit", wires=range(4)) dev_lightning = qml.device("lightning.qubit", wires=range(4)) def circuit(params): circuit_ansatz(params, wires=range(4))
def Generator(theta1, theta2, theta3): G = theta1.item() * np.kron(X, I) - \ theta2 * np.kron(Z, X) + \ theta3 * np.kron(I, X) return G
def test_apply(self, gate, apply_unitary, shots, qvm, compiler): """Test the application of gates""" dev = plf.QVMDevice(device="3q-qvm", shots=shots, parametric_compilation=False) try: # get the equivalent pennylane operation class op = getattr(qml.ops, gate) except AttributeError: # get the equivalent pennylane-forest operation class op = getattr(plf, gate) # the list of wires to apply the operation to w = list(range(op.num_wires)) if op.par_domain == "A": # the parameter is an array if gate == "QubitUnitary": p = np.array(U) w = [0] state = apply_unitary(U, 3) elif gate == "BasisState": p = np.array([1, 1, 1]) state = np.array([0, 0, 0, 0, 0, 0, 0, 1]) w = list(range(dev.num_wires)) with qml.tape.QuantumTape() as tape: op(p, wires=w) obs = qml.expval(qml.PauliZ(0)) else: p = [0.432_423, 2, 0.324][:op.num_params] fn = test_operation_map[gate] if callable(fn): # if the default.qubit is an operation accepting parameters, # initialise it using the parameters generated above. O = fn(*p) else: # otherwise, the operation is simply an array. O = fn # calculate the expected output state = apply_unitary(O, 3) # Creating the tape using a parametrized operation if p: with qml.tape.QuantumTape() as tape: op(*p, wires=w) obs = qml.expval(qml.PauliZ(0)) # Creating the tape using an operation that take no parameters else: with qml.tape.QuantumTape() as tape: op(wires=w) obs = qml.expval(qml.PauliZ(0)) dev.apply(tape.operations, rotations=tape.diagonalizing_gates) dev._samples = dev.generate_samples() res = dev.expval(obs.obs) expected = np.vdot(state, np.kron(np.kron(Z, I), I) @ state) # verify the device is now in the expected state # Note we have increased the tolerance here, since we are only # performing 1024 shots. self.assertAllAlmostEqual(res, expected, delta=3 / np.sqrt(shots))
def test_expand_three(self): """Test that a 3 qubit gate correctly expands to 4 qubits.""" self.logTestName() dev = DefaultQubit(wires=4) # test applied to wire 0,1,2 res = dev.expand(U_toffoli, [0, 1, 2]) expected = np.kron(U_toffoli, I) self.assertAllAlmostEqual(res, expected, delta=self.tol) # test applied to wire 1,2,3 res = dev.expand(U_toffoli, [1, 2, 3]) expected = np.kron(I, U_toffoli) self.assertAllAlmostEqual(res, expected, delta=self.tol) # test applied to wire 0,2,3 res = dev.expand(U_toffoli, [0, 2, 3]) expected = np.kron(U_swap, np.kron(I, I)) @ np.kron(I, U_toffoli) @ np.kron(U_swap, np.kron(I, I)) self.assertAllAlmostEqual(res, expected, delta=self.tol) # test applied to wire 0,1,3 res = dev.expand(U_toffoli, [0, 1, 3]) expected = np.kron(np.kron(I, I), U_swap) @ np.kron(U_toffoli, I) @ np.kron(np.kron(I, I), U_swap) self.assertAllAlmostEqual(res, expected, delta=self.tol) # test applied to wire 3, 1, 2 res = dev.expand(U_toffoli, [3, 1, 2]) # change the control qubit on the Toffoli gate rows = np.array([0, 4, 1, 5, 2, 6, 3, 7]) expected = np.kron(I, U_toffoli[:, rows][rows]) self.assertAllAlmostEqual(res, expected, delta=self.tol) # test applied to wire 3, 0, 2 res = dev.expand(U_toffoli, [3, 0, 2]) # change the control qubit on the Toffoli gate rows = np.array([0, 4, 1, 5, 2, 6, 3, 7]) expected = np.kron(U_swap, np.kron(I, I)) @ np.kron(I, U_toffoli[:, rows][rows]) @ np.kron(U_swap, np.kron(I, I)) self.assertAllAlmostEqual(res, expected, delta=self.tol)