Hadamard Test¶
Consider the observable \(O\) and two generic quantum states \(\ket{\psi}\) and \(\ket{\phi}\). We want to calculate the quantity
\[\bra{\psi} O \ket{\phi}.\]
where \(O\) is a Pauli operator.
First of all we shall prepare the states \(\ket{\psi}\) and \(\ket{\phi}\) using a quantum circuit for each of them. So we have
\[\ket{\psi} = U_{\psi}\ket{0} \qquad \ket{\phi} = U_{\phi}\ket{0}\]
Let’s define an observable we want to use:
\[O = X_1X_2\]
Now we can evaluate the matrix element using the following fact:
\[\bra{\psi}O\ket{\phi} = \bra{0}U_\psi^\dagger O U_\phi\ket{0}\]
This is just an expectation value which can be solved with a simple Hadamard test. The probability to measure \(0\) or \(1\) in the ancilla qubit is
\[P(0) = \frac{1}{2} \left[ I + Re \bra{\psi} O \ket{\phi} \right]\]
\[P(1) = \frac{1}{2} \left[ I - Re \bra{\psi} O \ket{\phi} \right]\]
The difference between the probability of \(0\) and \(1\) gives
\[P(0)-P(1) = Re \bra{\psi} O \ket{\phi}\]
A- Numerical result as a reference:¶
[18]:
import cudaq
import numpy as np
from functools import reduce
cudaq.set_target('nvidia')
qubit_num = 2
@cudaq.kernel
def psi(num:int):
q = cudaq.qvector(num)
h(q[1])
@cudaq.kernel
def phi(n:int):
q = cudaq.qvector(n)
x(q[0])
psi_state = cudaq.get_state(psi, qubit_num)
print('Psi state: ', psi_state)
phi_state=cudaq.get_state(phi, qubit_num)
print('Phi state: ', phi_state)
ham=cudaq.spin.x(0) * cudaq.spin.x(1)
ham_matrix = ham.to_matrix()
print('hamiltonian: ', np.array(ham_matrix), '\n')
exp_val=reduce(np.dot,(np.array(psi_state).conj().T, ham_matrix, phi_state))
print('Numerical expectation value: ', exp_val)
Psi state: (0.707107,0)
(0,0)
(0.707107,0)
(0,0)
Phi state: (0,0)
(1,0)
(0,0)
(0,0)
hamiltonian: [[0.+0.j 0.+0.j 0.+0.j 1.+0.j]
[0.+0.j 0.+0.j 1.+0.j 0.+0.j]
[0.+0.j 1.+0.j 0.+0.j 0.+0.j]
[1.+0.j 0.+0.j 0.+0.j 0.+0.j]]
Numerical expectation value: (0.7071067690849304+0j)
B- Using sample
algorithmic primitive to sample the ancilla qubit and compute the expectation value.¶
[19]:
import cudaq
cudaq.set_target('nvidia')
@cudaq.kernel
def U_psi(q:cudaq.qview):
h(q[1])
@cudaq.kernel
def U_phi(q:cudaq.qview):
x(q[0])
@cudaq.kernel
def ham_cir(q:cudaq.qview):
x(q[0])
x(q[1])
@cudaq.kernel
def kernel(n:int):
ancilla=cudaq.qubit()
q = cudaq.qvector(n)
h(ancilla)
cudaq.control(U_phi,ancilla,q)
cudaq.control(ham_cir,ancilla,q)
cudaq.control(U_psi,ancilla,q)
h(ancilla)
mz(ancilla)
shots = 100000
qubit_num=2
count = cudaq.sample(kernel, qubit_num, shots_count = shots)
print(count)
mean_val = (count['0']-count['1']) / shots
error = np.sqrt(2* count['0'] * count['1'] / shots) / shots
print('Observable QC: ', mean_val,'+ -', error)
print('Numerical result', np.real(exp_val))
{ 0:85356 1:14644 }
Observable QC: 0.70712 + - 0.0015811092713661505
Numerical result 0.7071067690849304
C- Use multi-GPUs to compute the matrix elements¶
[20]:
import cudaq
cudaq.set_target("nvidia-mqpu")
target = cudaq.get_target()
qpu_count = target.num_qpus()
print("Number of QPUs:", qpu_count)
@cudaq.kernel
def U_psi(q:cudaq.qview, theta:float):
ry(theta, q[1])
@cudaq.kernel
def U_phi(q:cudaq.qview, theta: float):
rx(theta, q[0])
@cudaq.kernel
def ham_cir(q:cudaq.qview):
x(q[0])
x(q[1])
@cudaq.kernel
def kernel(n:int, angle:float, theta:float):
ancilla = cudaq.qubit()
q = cudaq.qvector(n)
h(ancilla)
cudaq.control(U_phi, ancilla, q, theta)
cudaq.control(ham_cir, ancilla, q)
cudaq.control(U_psi, ancilla, q, angle)
h(ancilla)
mz(ancilla)
shots = 100000
angle = [0.0, 1.5,3.14,0.7]
theta = [0.6, 1.2 ,2.2 ,3.0]
qubit_num = 2
result = []
for i in range(4):
count = cudaq.sample_async(kernel, qubit_num, angle[i], theta[i], shots_count = shots, qpu_id = i%qpu_count)
result.append(count)
mean_val = np.zeros(len(angle))
i = 0
for count in result:
print(i)
i_result = count.get()
print(i_result)
mean_val[i] = (i_result['0'] - i_result['1']) / shots
error = np.sqrt(2 * i_result['0'] * i_result['1'] / shots) / shots
print('Observable QC: ', mean_val[i],'+ -', error)
i += 1
Number of QPUs: 5
0
{ 0:63807 1:36193 }
Observable QC: 0.27614 + - 0.0021491238917289066
1
{ 0:49929 1:50071 }
Observable QC: -0.00142 + - 0.0022360657230949183
2
{ 0:50041 1:49959 }
Observable QC: 0.00082 + - 0.0022360672257336093
3
{ 0:50276 1:49724 }
Observable QC: 0.00552 + - 0.0022360339102974265
Diagonalize the matrix using for example Numpy or CuPy. In this example, since we are having 2x2 matrix, we use numpy.¶
[21]:
import numpy as np
my_mat = np.zeros((2,2),dtype=float)
m = 0
for k in range(2):
for j in range(2):
my_mat[k,j] = mean_val[m]
m += 1
print(my_mat)
E,V = np.linalg.eigh(my_mat)
print('Eigen values: ')
print(E)
print('Eigenvector: ')
print(V)
[[ 0.27614 -0.00142]
[ 0.00082 0.00552]]
Eigen values:
[0.00551752 0.27614248]
Eigenvector:
[[ 0.00303004 -0.99999541]
[-0.99999541 -0.00303004]]