Home | Algorithms | Commercialization | Data Science | Information Theories | Quantum Theories | Lab | Linear Algebra |
<< Hadamard on Multi-Qubits | KRT Universal Quantum Gates >> |
$\require{cancel} \newcommand{\Ket}[1]{\left|{#1}\right\rangle} \newcommand{\Bra}[1]{\left\langle{#1}\right|} \newcommand{\Braket}[1]{\left\langle{#1}\right\rangle} \newcommand{\Rsr}[1]{\frac{1}{\sqrt{#1}}} \newcommand{\RSR}[1]{1/\sqrt{#1}} \newcommand{\Verti}{\rvert} \newcommand{\HAT}[1]{\hat{\,#1~}} \DeclareMathOperator{\Tr}{Tr}$
First created in August 2018
The challenge from the IBM Q Experience - Basic Circuit Identities and Larger Circuits page is
Arbitrarily good approximations exist, so can you find a better one? How might you use these circuits to construct an approximate controlled-$T$ unitary transformation?
This page will answer the two challenges, followed by some illustration through programming.
# Preamble
import numpy as np
import math
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, execute
from qiskit.tools.visualization import plot_histogram, plot_state, plot_bloch_vector, circuit_drawer
from IPython.display import Math
# e.g. Math('3 \cdot \\frac{\pi}{2} + e^{\\frac{I*x}{x^2 + y}}')
# m = Math(...)
# display(m)
shots = 1024
decimals = 4
def run_job(qc_run, shots=shots):
job = execute(qc_run, backend = 'local_qasm_simulator', shots=shots)
result = job.result()
data = result.get_counts(qc_run)
for i in range(8):
dataIndex = str(bin(i+8))[-3:]
try:
p = data[dataIndex]/shots
print("P%s=%.3f" % (dataIndex, p))
except:
p = 0
plot_histogram(job.result().get_counts(qc_run))
return job
# Normalise
def normal_vec(vec):
ret = np.copy(vec)
vc = np.conj(ret[0])
ret[0] = np.multiply(ret[0], vc)
ret[1] = np.multiply(ret[1], vc)
vec_norm = np.sqrt(np.square(np.abs(ret[0]))+np.square(np.abs(ret[1])))
ret[0] = np.divide(ret[0], vec_norm)
ret[1] = np.divide(ret[1], vec_norm)
return ret
# Vector length is arbitrary.
precDP = 3 # Precision is 3 decimal places
tooSmallIgnored = 1E-5 # ignored if under 10^(-5)
def print_vec(vec):
vecLen = len(vec)
qbCount = int(math.log(vecLen, 2))
vecRnd = np.around(vec, precDP) # 3 decimal points
jn = ''
for i in range(vecLen):
ket = str(bin(i+vecLen))[-qbCount:]
if np.abs(vecRnd[i]) >= tooSmallIgnored: # 3 decimal points
print("%s%s|%s>" % (jn, vecRnd[i], ket), end='')
jn = ' + '
print()
return vecRnd
# Find the Bloch Vector and return an array of <X>, <Y> and <Z>
def bloch_vector(qc, q, c, decimals=decimals):
# Meaasurement circuits
meas_x = QuantumCircuit(q, c)
meas_x.barrier()
meas_x.h(q)
meas_x.measure(q, c)
meas_y = QuantumCircuit(q, c)
meas_y.barrier()
meas_y.s(q).inverse()
meas_y.h(q)
meas_y.measure(q, c)
meas_z = QuantumCircuit(q, c)
meas_z.barrier()
meas_z.measure(q, c)
# Bloch Vector to return
bloch = [0, 0, 0]
# 3 circuits to measure X, Y and Z
circuits = []
circuits.append(qc + meas_x)
circuits.append(qc + meas_y)
circuits.append(qc + meas_z)
# Run them all
job = execute(circuits, backend = 'local_qasm_simulator', shots=shots)
result = job.result()
# Get the measurement
for bloch_index in range(3):
data = result.get_counts(circuits[bloch_index])
#print(data)
try:
p0 = data['0']/shots
except KeyError:
p0 = 0
try:
p1 = data['1']/shots
except KeyError:
p1 = 0
bloch[bloch_index] = np.round(p0 - p1, decimals)
return bloch
def bloch_angles(bv, show="", decimals=decimals):
(x, y, z) = bv
# cos(theta)=z; theta in [-1, 1]
if (z > 1):
z = 1.0
elif (z < -1):
z = -1.0
theta = np.arccos(z)
# Projection on x-y in [0, 1]
xy = np.sin(theta)
phi = 0.0
if (xy > 0):
# phi in [0, pi]
cosphi = x/xy
if (cosphi > 1):
cosphi = 1.0
elif (cosphi < -1):
cosphi = -1.0
phi = np.arccos(cosphi)
if (y < 0 and phi > 0):
phi = np.pi * 2 - phi
theta = np.round(theta, decimals)
phi = np.round(phi, decimals)
thetaPI = str(np.round(theta/np.pi, decimals))
thetaPI2 = str(np.round(theta/np.pi/2, decimals))
phiPI = str(np.round(phi/np.pi, decimals))
if (show == "pi"):
return thetaPI + "pi, " + phiPI + "pi"
elif (show == "math"):
return Math("\cos(" + thetaPI2 + "\pi)\Ket0 + e^{i~" + phiPI + "\pi}\sin(" + thetaPI2 + "\pi)\Ket1")
elif (show == "pion"):
thetaPIon = "pi/" + str(np.round(np.pi/theta, 2))
phiPIon = "pi/" + str(np.round(np.pi/phi, 2))
return thetaPIon + ", " + phiPIon
else:
return [theta, phi]
$\large\sqrt T\Ket0=\cos{\pi\over4}\Ket0+e^{i\pi/8}\sin{\pi\over4}\Ket1 \approx 0.7071\Ket0+(0.6533+i~0.2706)\Ket1 .$
Bloch Vector: $\large\left(\sqrt{2+\sqrt2},\sqrt{2-\sqrt2},0\right)\approx(0.92388,0.38268,0)$
$Q=HTHTHSTHTHSTHTHTH$.
# Number of qubits
qbNum = 1
# Define the Quantum and Classical Registers
q = QuantumRegister(qbNum)
c = ClassicalRegister(qbNum)
qc = QuantumCircuit(q, c)
# Bloch Vector expected
bloch_expected = [0.92388, 0.38268, 0]
# Preparation
qc.h(q)
# Build
qc.h(q)
qc.t(q)
qc.h(q)
qc.t(q)
qc.h(q)
qc.t(q)
qc.s(q)
qc.h(q)
qc.t(q)
qc.h(q)
qc.t(q)
qc.s(q)
qc.h(q)
qc.t(q)
qc.h(q)
qc.t(q)
qc.h(q)
# Circuit building
# ...
# Finalisation
# ...
shots = 8192
# State Vector
job = execute(qc, backend = 'local_statevector_simulator', shots=shots)
data = np.round(job.result().get_statevector(qc), decimals)
print("Raw vector:", end=" ")
print(data)
vec_norm = np.round(normal_vec(data), decimals)
print("Normalised:", end=" ")
print(vec_norm)
print("Ket form: ", end=" ")
print_vec(vec_norm)
print()
# Block Vector
bloch = bloch_vector(qc, q, c)
print("Bloch Vector:")
print("Expected:", end=" ")
print(np.round(bloch_expected, decimals))
print("Measured:", end=" ")
print(np.round(bloch, decimals))
print("M-E: ", end=" ")
print(np.round(np.subtract(bloch, bloch_expected), decimals))
print("M/E-100%:", end=" ")
#print("%s %f%% on x, %f%% on y" %
# (np.round(bloch, decimals), \
# np.round((bloch[0]/bloch_expected[0]-1)*100, decimals+2), \
# np.round((bloch[1]/bloch_expected[1]-1)*100, decimals+2)))
#print(np.subtract(np.round(bloch, decimals), np.round(bloch_expected, decimals)))
print("%f%%" % np.round((bloch[0]/bloch_expected[0]-1)*100, decimals+2), end=" ")
print("%f%%" % np.round((bloch[1]/bloch_expected[1]-1)*100, decimals+2), end=" n/a")
print()
#print(bloch_angles(bloch, show="pi"))
display(bloch_angles(bloch, show="math", decimals=3))
plot_bloch_vector(bloch)
# Histogram
#qc.measure(q, c)
#job = execute(qc, backend = 'local_qasm_simulator', shots=shots)
#data = job.result().get_counts(qc)
#plot_histogram(data)
# Check the circuit (must be last or it will not show)
#circuit_drawer(qc)
bvmax = 0.0
showWhat = ""
bvLastStr = ""
def bloch_step(op, cap="Initial State"):
global stepNum, bvmax, bvLastStr
stepNum += 1
print("Step %d: %s" % (stepNum, cap))
op(q)
# Ket Form
job = execute(qc, backend = 'local_statevector_simulator', shots=shots)
data = np.round(job.result().get_statevector(qc), decimals)
bv = bloch_vector(qc, q, c)
# Normalised Vector
vec_norm = np.round(normal_vec(data), decimals)
print_vec(vec_norm)
# Math
display(bloch_angles(bv, show="math", decimals=3))
# Theta and Phi
print(bloch_angles(bv, show="pi"))
# Bloch Vector with max
if (bvLastStr != ""):
print("%s: %s" % (cap, bvLastStr), end=" -> ")
print(bv, end=" max=")
bvmax = np.amax(bv)
print(bvmax)
bvLastStr = bv
# Bloch Sphere
plot_bloch_vector(bv)
bvLast = bv
approxNum = 0
def bloch_approx():
global approxNum
approxNum += 1
print("Approximation %d: max=%f\n\n" % (approxNum, bvmax))
def sqrtT():
#bloch_step(qc.iden, "I")
bloch_step(qc.iden)
bloch_step(qc.h, "H")
bloch_step(qc.t, "T")
bloch_step(qc.h, "H")
bloch_step(qc.t, "T Marked")
bloch_step(qc.h, "H")
bloch_step(qc.t, "T")
bloch_step(qc.s, "S")
bloch_step(qc.h, "H")
bloch_step(qc.t, "T Marked")
bloch_step(qc.h, "H")
bloch_step(qc.t, "T")
bloch_step(qc.s, "S")
bloch_step(qc.h, "H")
bloch_step(qc.t, "T Marked")
bloch_step(qc.h, "H")
bloch_step(qc.t, "T")
bloch_step(qc.h, "H Final")
qbNum = 1
q = QuantumRegister(qbNum)
c = ClassicalRegister(qbNum)
qc = QuantumCircuit(q, c)
stepNum = -1
shots = 8192
# Initial state -> Final state
# Ket0 -> Ket0
#sqrtT()
# Ket+ -> Ket0+e^{i pi/4}Ket1
qc.h(q)
sqrtT()
Let us take the transformation step by step, and imagine what $\Ket0$ would do under the same transformation.
Nothing seems to have happened at step 3, but if you look at $\Ket0$ in terms of ($\theta,\phi$), it went from the initial state $(0,0)$ to H1:$(\pi/2,0)$, T2:$(\pi/2,\pi/4)$, to H3:$(\pi/4,3\pi/2)$.
Following the sequence, the final state H17 would see $\Ket0$ back to itself, which is required.
Now back to $\Ket+$ or $(\pi/2,0)$. If we subject $\Ket\psi=\cos\theta/2\Ket0+e^{i\phi}\sin\theta/2\Ket1\mapsto(\theta,\phi)$ to the three gates $T$, $S$ and $H$, the results are:
$T\Ket\psi=(\theta,~\phi+\pi/4).~~~ S\Ket\psi=(\theta,~\phi+\pi/2) .$
With $H$, it is easier to use the Bloch Vector presentation.
$\Ket\psi=(\sin\theta\cos\phi,\sin\theta\sin\phi,\cos\theta).~~ H\Ket\psi=(\cos\theta,-\sin\theta\sin\phi,\sin\theta\cos\phi) .$
In short, after $H$, $\Braket{X}$ and $\Braket{Z}$ swap and $\Braket{Y}$ negated.
KETP
$H\Ket\psi =\cos\theta/2\Rsr2(\Ket0+\Ket1)+e^{i\phi}\sin\theta/2\Rsr2(\Ket0-\Ket1) =\Rsr2\left((\cos\theta/2+e^{i\phi}\sin\theta/2)\Ket0+(\cos\theta/2-e^{i\phi}\sin\theta/2)\Ket1\right) .$
<< Hadamard on Multi-Qubits | Top | KRT Universal Quantum Gates >> |