Quantum Computing 1#
Author:
Date:
Time Spent on this Assignment:
!pip install qiskit[visualization];
!pip install qutip
!pip install qiskit_aer;
Collecting qiskit[visualization]
Downloading qiskit-1.2.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (12 kB)
Collecting rustworkx>=0.15.0 (from qiskit[visualization])
Downloading rustworkx-0.15.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (9.9 kB)
Requirement already satisfied: numpy<3,>=1.17 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit[visualization]) (1.24.4)
Requirement already satisfied: scipy>=1.5 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit[visualization]) (1.10.1)
Collecting sympy>=1.3 (from qiskit[visualization])
Downloading sympy-1.13.3-py3-none-any.whl.metadata (12 kB)
Collecting dill>=0.3 (from qiskit[visualization])
Downloading dill-0.3.9-py3-none-any.whl.metadata (10 kB)
Requirement already satisfied: python-dateutil>=2.8.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit[visualization]) (2.9.0.post0)
Collecting stevedore>=3.0.0 (from qiskit[visualization])
Downloading stevedore-5.3.0-py3-none-any.whl.metadata (2.3 kB)
Requirement already satisfied: typing-extensions in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit[visualization]) (4.12.2)
Collecting symengine<0.14,>=0.11 (from qiskit[visualization])
Downloading symengine-0.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.2 kB)
Requirement already satisfied: matplotlib>=3.3 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit[visualization]) (3.7.5)
Collecting pydot (from qiskit[visualization])
Downloading pydot-3.0.3-py3-none-any.whl.metadata (10 kB)
Requirement already satisfied: Pillow>=4.2.1 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit[visualization]) (10.4.0)
Collecting pylatexenc>=1.4 (from qiskit[visualization])
Downloading pylatexenc-2.10.tar.gz (162 kB)
Preparing metadata (setup.py) ... ?25l-
done
?25hCollecting seaborn>=0.9.0 (from qiskit[visualization])
Downloading seaborn-0.13.2-py3-none-any.whl.metadata (5.4 kB)
Requirement already satisfied: contourpy>=1.0.1 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from matplotlib>=3.3->qiskit[visualization]) (1.1.1)
Requirement already satisfied: cycler>=0.10 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from matplotlib>=3.3->qiskit[visualization]) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from matplotlib>=3.3->qiskit[visualization]) (4.55.2)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from matplotlib>=3.3->qiskit[visualization]) (1.4.7)
Requirement already satisfied: packaging>=20.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from matplotlib>=3.3->qiskit[visualization]) (24.2)
Requirement already satisfied: pyparsing>=2.3.1 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from matplotlib>=3.3->qiskit[visualization]) (3.1.4)
Requirement already satisfied: importlib-resources>=3.2.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from matplotlib>=3.3->qiskit[visualization]) (6.4.5)
Requirement already satisfied: six>=1.5 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from python-dateutil>=2.8.0->qiskit[visualization]) (1.17.0)
Requirement already satisfied: pandas>=1.2 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from seaborn>=0.9.0->qiskit[visualization]) (2.0.3)
Collecting pbr>=2.0.0 (from stevedore>=3.0.0->qiskit[visualization])
Downloading pbr-6.1.0-py2.py3-none-any.whl.metadata (3.4 kB)
Collecting mpmath<1.4,>=1.1.0 (from sympy>=1.3->qiskit[visualization])
Downloading mpmath-1.3.0-py3-none-any.whl.metadata (8.6 kB)
Requirement already satisfied: zipp>=3.1.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from importlib-resources>=3.2.0->matplotlib>=3.3->qiskit[visualization]) (3.20.2)
Requirement already satisfied: pytz>=2020.1 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from pandas>=1.2->seaborn>=0.9.0->qiskit[visualization]) (2024.2)
Requirement already satisfied: tzdata>=2022.1 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from pandas>=1.2->seaborn>=0.9.0->qiskit[visualization]) (2024.2)
Downloading dill-0.3.9-py3-none-any.whl (119 kB)
Downloading rustworkx-0.15.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB)
?25l ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/2.0 MB ? eta -:--:--
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.0/2.0 MB 52.4 MB/s eta 0:00:00
?25hDownloading seaborn-0.13.2-py3-none-any.whl (294 kB)
Downloading stevedore-5.3.0-py3-none-any.whl (49 kB)
Downloading symengine-0.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (49.7 MB)
?25l ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/49.7 MB ? eta -:--:--
━━━━━━━━━━━━━━━━━━━━━━━━━╺━━━━━━━━━━━━━━ 31.5/49.7 MB 165.3 MB/s eta 0:00:01
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 49.7/49.7 MB 127.0 MB/s eta 0:00:00
?25hDownloading sympy-1.13.3-py3-none-any.whl (6.2 MB)
?25l ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/6.2 MB ? eta -:--:--
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.2/6.2 MB 146.7 MB/s eta 0:00:00
?25hDownloading pydot-3.0.3-py3-none-any.whl (35 kB)
Downloading qiskit-1.2.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.8 MB)
?25l ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/4.8 MB ? eta -:--:--
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.8/4.8 MB 131.0 MB/s eta 0:00:00
?25h
Downloading mpmath-1.3.0-py3-none-any.whl (536 kB)
?25l ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/536.2 kB ? eta -:--:--
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 536.2/536.2 kB 55.4 MB/s eta 0:00:00
?25hDownloading pbr-6.1.0-py2.py3-none-any.whl (108 kB)
Building wheels for collected packages: pylatexenc
Building wheel for pylatexenc (setup.py) ... ?25l-
\
done
?25h Created wheel for pylatexenc: filename=pylatexenc-2.10-py3-none-any.whl size=136824 sha256=2d943caa9388ca28b4f2a813fffe701a69c0e117dc761bb6fd0e22f645a3bca0
Stored in directory: /home/runner/.cache/pip/wheels/72/99/be/81d9bcdf5dd5ee5acd8119a9dd5bc07204c9ce205fd341b021
Successfully built pylatexenc
Installing collected packages: pylatexenc, mpmath, sympy, symengine, rustworkx, pydot, pbr, dill, stevedore, seaborn, qiskit
Successfully installed dill-0.3.9 mpmath-1.3.0 pbr-6.1.0 pydot-3.0.3 pylatexenc-2.10 qiskit-1.2.4 rustworkx-0.15.1 seaborn-0.13.2 stevedore-5.3.0 symengine-0.13.0 sympy-1.13.3
Collecting qutip
Downloading qutip-5.0.1.tar.gz (6.4 MB)
?25l ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/6.4 MB ? eta -:--:--
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.4/6.4 MB 56.8 MB/s eta 0:00:00
?25h
Installing build dependencies ... ?25l-
\
|
/
-
\
|
/
-
\
|
done
^C
?25h Getting requirements to build wheel ... ?25l?25hcanceled
ERROR: Operation cancelled by user
Collecting qiskit_aer
Downloading qiskit_aer-0.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (8.0 kB)
Requirement already satisfied: qiskit>=1.1.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit_aer) (1.2.4)
Requirement already satisfied: numpy>=1.16.3 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit_aer) (1.24.4)
Requirement already satisfied: scipy>=1.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit_aer) (1.10.1)
Requirement already satisfied: psutil>=5 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit_aer) (6.1.0)
Requirement already satisfied: rustworkx>=0.15.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit>=1.1.0->qiskit_aer) (0.15.1)
Requirement already satisfied: sympy>=1.3 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit>=1.1.0->qiskit_aer) (1.13.3)
Requirement already satisfied: dill>=0.3 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit>=1.1.0->qiskit_aer) (0.3.9)
Requirement already satisfied: python-dateutil>=2.8.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit>=1.1.0->qiskit_aer) (2.9.0.post0)
Requirement already satisfied: stevedore>=3.0.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit>=1.1.0->qiskit_aer) (5.3.0)
Requirement already satisfied: typing-extensions in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit>=1.1.0->qiskit_aer) (4.12.2)
Requirement already satisfied: symengine<0.14,>=0.11 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from qiskit>=1.1.0->qiskit_aer) (0.13.0)
Requirement already satisfied: six>=1.5 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from python-dateutil>=2.8.0->qiskit>=1.1.0->qiskit_aer) (1.17.0)
Requirement already satisfied: pbr>=2.0.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from stevedore>=3.0.0->qiskit>=1.1.0->qiskit_aer) (6.1.0)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from sympy>=1.3->qiskit>=1.1.0->qiskit_aer) (1.3.0)
Downloading qiskit_aer-0.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (12.3 MB)
?25l ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/12.3 MB ? eta -:--:--
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.3/12.3 MB 118.9 MB/s eta 0:00:00
?25h
Installing collected packages: qiskit_aer
Successfully installed qiskit_aer-0.15.1
import numpy as np
import qiskit
import qutip
from qiskit import QuantumCircuit, QuantumRegister, transpile
from qiskit.visualization import plot_histogram
from qiskit.circuit.library.standard_gates import ZGate, XGate
from qiskit.circuit.quantumregister import AncillaRegister
import pylab as plt
from qiskit.quantum_info import Statevector, partial_trace
from qiskit import QuantumCircuit, QuantumRegister, transpile
import qutip
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit_aer import AerSimulator
/usr/local/lib/python3.10/dist-packages/qutip/__init__.py:65: UserWarning: The new version of Cython, (>= 3.0.0) is not supported.
warnings.warn(
simulator = AerSimulator()
def RunMe(qc,num_shots=1024):
backend = BasicAer.get_backend('qasm_simulator')
job = execute(qc, backend,shots=num_shots)
result = job.result()
return result
def RunMeState(qc):
backend = BasicAer.get_backend('statevector_simulator')
job = execute(qc, backend,shots=1024)
result = job.result()
return result
def RunMeQC(qc):
provider = IBMQ.get_provider(hub='ibm-q')
from qiskit.providers.ibmq import least_busy
small_devices = provider.backends(filters=lambda x: x.configuration().n_qubits == 5
and not x.configuration().simulator)
backend = least_busy(small_devices)
jobReal = execute(qc, backend)
return jobReal
def RunCircuit(circuit):
circuit.save_statevector()
compiled_circuit = transpile(circuit, simulator)
result = simulator.run(compiled_circuit).result()
out_state = result.get_statevector()
return out_state
def StateToBinary(b):
wires=int(round(np.log2(len(b))))
for i in range(0,2**wires):
myFormat="0"+str(wires)+"b"
if np.abs(b[i])!=0:
print(b[i],'|',format(i, myFormat)[::-1],'>')
def RunMe(circuit):
circuit.save_statevector(label='myStateVector')
compiled_circuit = transpile(circuit, simulator)
resultA = simulator.run(compiled_circuit).result()
numQubits=circuit.num_qubits
forward=list(range(0,numQubits))
reverse=forward[::-1]
circuit.measure(forward,reverse)
compiled_circuit = transpile(circuit, simulator)
resultB = simulator.run(compiled_circuit).result()
return resultA.data()['myStateVector'],resultB.data()['counts']
def PrintDirac(out_state_a):
out_state=np.asarray(out_state_a)
l=len(out_state)
num_qubits=int(round(np.log(len(out_state))/np.log(2)))
for i in range(0,l):
if not np.isclose(out_state[i],0):
print(str(out_state[i])+'|'+bin(i)[2:].zfill(num_qubits)[::-1]+'>',end=' + ')
print()
def PlotState(v,plotAll=True,myTitle=""):
if plotAll:
N=len(np.asarray(v))
else:
N=len(np.asarray(v))//2
vv=np.zeros(N,dtype=complex)
vv[:]=np.asarray(v)[0:N]
vv=vv*np.exp(-1.j*np.angle(vv[0]))
plt.axhline(0)
for idx,i in enumerate(vv):
plt.plot([int(idx),int(idx)],[0,np.real(i)],color='red')
plt.plot([int(idx)],[np.real(i)],'o',color='red')
for idx,i in enumerate(vv):
plt.plot([idx+0.1,idx+0.1],[0,np.imag(i)],color='blue')
plt.plot([idx+0.1],[np.imag(i)],'o',color='blue')
avg=np.average(vv)
plt.axhline(avg,linestyle='--')
plt.ylim(-1,1)
plt.title(myTitle)
plt.show()
def MakeState(v):
numWires=int(round(np.log2(len(v))))
qc1=QuantumCircuit(numWires,numWires)
qc1.initialize(v)
a,_=RunMe(qc1)
return a
def PlotBloch(v,bb):
vv=np.asarray(v)
#print(vv)
a=vv[0]*np.conj(vv[0])
b=vv[1]*np.conj(vv[0])
x = np.real(2.0 * b.real)
y = np.real(2.0 * b.imag)
z = np.real(2.0 * a - 1.0)
#print(x,y,z)
bb.add_vectors([x,y,z])
return #plot_bloch_vector([x,y,z])
def Mark(r,N):
circuit=QuantumCircuit(N,N)
circuit.barrier()
myString=np.binary_repr(r,width=N)[::-1]
for i in range(0,len(myString)):
if myString[i]=='0':
circuit.x(i)
circuit.barrier()
circuit.h(N-1)
circuit.mcx(list(range(0,N-1)), N-1,mode='noancilla')
circuit.h(N-1)
circuit.barrier()
for i in range(0,len(myString)):
if myString[i]=='0':
circuit.x(i)
circuit.barrier()
return circuit
def InitializeCircuitRandom(N):
r=np.random.random(2**N)
r=r/np.linalg.norm(r)
circuit=QuantumCircuit(N,N)
circuit.initialize(list(r))
return circuit
def RunMe(circuit,a=None):
numQubits=circuit.num_qubits
if a!=None:
numQubits=max(numQubits,a.num_qubits)
initCircuit=QuantumCircuit(a.num_qubits,a.num_qubits)
initCircuit.initialize(a)
circuit=AddCircuits([initCircuit,circuit])
circuit.save_statevector(label='myStateVector')
compiled_circuit = transpile(circuit, simulator)
resultA = simulator.run(compiled_circuit).result()
forward=list(range(0,numQubits))
reverse=forward[::-1]
circuit.measure(forward,reverse)
compiled_circuit = transpile(circuit, simulator)
resultB = simulator.run(compiled_circuit).result()
return resultA.data()['myStateVector'],resultB.data()['counts']
def AddCircuits(theCircuits):
numQubits=np.array([c.num_qubits for c in theCircuits])
numQubits=np.max(numQubits)
circuit=QuantumCircuit(numQubits,numQubits)
for i in range(0,len(theCircuits)):
circuit=circuit.compose(theCircuits[i],qubits=list(range(0,theCircuits[i].num_qubits)))
return circuit
def RunMeQC(qc,num_shots=1024):
numQubits=qc.num_qubits
forward=list(range(0,numQubits))
reverse=forward[::-1]
qc.measure(forward,reverse)
backend = provider.get_backend('ibm_lagos')
job = execute(qc, backend,shots=num_shots)
#result = job.result()
return job
Exercise 1: A Single Qubit (33 points)#
a. A one qubit state#
A quantum state consists of a certain fraction of \(|0\rangle\) and a certain fraction of \(|1\rangle\) - i.e. \(\sqrt{0.3} |0\rangle - \sqrt{0.7} |1\rangle\).
We can plot this state in a variety of ways.
state=np.array([np.sqrt(0.3),-np.sqrt(0.7)])
state=MakeState(state)
PrintDirac(state)
PlotState(state)
b=qutip.Bloch()
PlotBloch(state,b)
b.render()
b.show()
Go ahead and try it out
state=np.array([np.sqrt(0.3),-np.sqrt(0.7)])
state=MakeState(state)
PrintDirac(state)
PlotState(state)
b=qutip.Bloch()
PlotBloch(state,b)
b.render()
b.show()
(0.5477225575051661+0j)|0> + (-0.8366600265340756+0j)|1> +
Let’s go ahead and plot these three states
\(|0\rangle \equiv [1,0]\)
\(|1\rangle \equiv [0,1]\)
\(1/\sqrt{2} |0\rangle + 1/\sqrt{2} |1\rangle \equiv [1/\sqrt{2},1/\sqrt{2}]\)
Pay special attention to where those three states are on the Bloch Sphere
## Plot the three states here
b. One qubit gates#
Quantum circuits are made out of quantum gates. Let us start by considering 1-qubit gates. The 1-qubit gate rotates states around the Bloch sphere. Three special gates are rx
, ry
, and rz
which respectively rotate around the X, Y, and Z axis. These gates take an angle and a wire - i.e. rx(0.3*np.pi,0)
will rotate \(1/3 \pi\) around the X axis.
To set up your circuit you can do
circuit=QuantumCircuit(1,1)
#Build your circuit here
state,measure = RunMe(circuit) #<--- This runs the circuit
# now you can plot the circuit
Go ahead and figure out how to use rx
gate to move from a \(|0\rangle\) to a \(|1\rangle\) state.
## ANSWER HERE
Now we want to go ahead and use ry
to go from \(|0\rangle \rightarrow 1/\sqrt{2}|0\rangle + 1/\sqrt{2}|1\rangle\). Write such a circuit. Then check to see what your circuit does to the state \(|1\rangle\) (you can get this using your other circuit from above).
So far we’ve seen we can get half \(|0\rangle\) and half \(|1\rangle\). We can also adjust states to get more of \(|1\rangle\) then \(|0\rangle\) (or visa versa). Generically we can get a state \(\cos(\theta) |0\rangle + \sin(\theta) e^{i\phi} |1\rangle\).
If we had such a state, after measurement we get “0” with probability \(\cos^2\theta\) and “1” with probability \(\sin^2\theta\).
Notice that \(\cos^2\theta + \sin^2\theta =1\) so we either get “0” or “1”.
To produce this state with \(\phi=0\), we can use the gate qc.ry(2*theta,wire)
which takes \(|0\rangle \rightarrow \cos \theta |0\rangle + \sin \theta |1\rangle\)
Produce some plots with \(\theta=0.5\) and check to make sure it gives you the right amplitudes.
## ANSWER HERE
Now, make a circuit that rotates rx
and rz
both by \(0.3 \pi\).
## ANSWER HERE
Notice that by smartly choosing the angles around rx
and rz
and then rx
again you should be able to take \(|0\rangle\) to any state and \(|1\rangle\) to any state which is orthogonal to \(|0\rangle\).
c. Seeing the rotation#
We would like to plot the rotation around the bloch sphere. We can do this as follows:
b=qutip.Bloch()
for angle in np.arange(0,2*np.pi*0.8,0.1):
quantumWires=1
qc = QuantumCircuit(quantumWires,quantumWires)
# rotate around the x-axis by angle theta
vec,output=RunMe(qc)
PlotBloch(vec,b)
b.render()
b.show()
## ANSWER HERE
d. Measuring the state#
So far we’ve been cheating by looking at the quantum state. In the real world, you can’t do that. Instead you have to measure at the end of your circuit. The RunMe circuit returns two parameters. The second one is measurement outcomes - i.e.
vector,measure=RunMe(circuit)
can then plot the measurement outcomes by doing
plot_histgoram(measure)
Go ahead and measure the outcomes of a state rotated around the X axis by \(0.3\pi\). See that it gives you the expected raction of 0 and 1.
## ANSWER HERE
Exercise 2: Two qubits (34 points)#
In this exercise we will see how to build quantum states of two qubits.
a. Control-not gates#
The controlled-not gate: The key gate for two qubits is the control-not gate (qc.cx(0,1)
. The key gate “nots” the second wire (wire 1) if the first wire is “1” (wire 0). Let’s check it out. Apply the “control-not” to a state \(|00\rangle\) and to a state \(|10\rangle\) and print out the state (i.e. RunMe
). Don’t put in a measurement.
To apply a control-not gate you want to do circuit.cx(wire)
### Circuit here applying control-not to |00>. Print the state when you're done.
(1+0j) | 00 >
### Circuit here applying control-not to |10>. Print the state when you're done.
(1+0j) | 11 >
b. Build an EPR Pair#
The most interesting two qubit state is an EPR pair, \(\frac{1}{\sqrt{2}}\left(|00\rangle + |11\rangle\right)\). You can build it with a Hadamard and a CNOT. Go ahead and try different things and figure out how to build yourself an EPR pair. You can start out with
quantumWires=2
classicalWires=2
qc=QuantumCircuit(quantumWires,classicalWires)
to start out a quantum circuit with two wires. You can’t plot this on the bloch sphere but you can still plot it with PlotState
. Go ahead and do this.
### Make an EPR Pair. It can be done with Hadamards and CNOTs. Run it and look at the state when you're done.
c. Measuring EPR Pairs#
Let’s go ahead and measure your EPR circuit above plotting the histogram of your measurements.
### Do the same thing with the EPR Pair but now look at the measurements.
There is something very powerful here. If you get a “0” on the top wire, then you always get a “0” on the bottom wire. If you get a “1” on the top wire, then you get a “1” on bottom wire. This is even the case if these wires are taken miles apart before you measure.
d. Entanglement (Extra Credit: 5 points)#
The EPR pair is called entangled.
Not entangled means
Pr(wires measures “00”) = Pr(wire 0 measures “0”) \(\times\) Pr(wire 1 measures “0”)
Note that when you check things you aren’t going to get this exactly even if things are independent because things are stochastic. If you’re careful you can compute error bars but otherwise use reasonable judgement
We want to check if things are entangled. To accomplish this, let’s write a function to measure the probability that wire \(i\) is 0 - i.e. def wire_i_probability(myCounts,wire_i)
(you can assume wire_i
will either be 0 or 1).
Now write a function def wires_probability(myCounts)
which returns the probability that the two wires are “00”.
Given these two functions we want to see which circuits produce entangled states. In particular check
The EPR Pair
A circuit you generate which has no two wire gates in it (i.e. no CNOT) and demonstrate what you find.
# Measure the probability of getting "0" on the wire `wire`
def Probability(myCounts,wire):
zeroProb=0.0
if wire==0:
elif wire==1:
return zeroProb/1024.
File "<ipython-input-8-9fe96e3ec3ad>", line 6
elif wire==1:
^
IndentationError: expected an indented block after 'if' statement on line 4
## Now check the entanglement of the EPR pair and a state you make only using 1-qubit gates.
Exercise 3: Grover’s Algorithm#
In this exercise, we are going to implement Grover’s algorithm. Grover’s algorithm takes a unitary which marks an element \(i\) and uses that as a subroutine with more circuit elements so that, after measurementt, you sample the marked element \(i\).
To do this we are going to need to put together a lot of different circuit elements. Let’s start with the unitary which does the marking which we will give you:
## Check Mark
circuit=InitializeCircuitRandom(5)
a,b=RunMe(circuit)
PlotState(a,True,myTitle="Initial Random State")
circuit=Mark(3,5)
a,b=RunMe(circuit,a)
PlotState(a,True,myTitle="Hopefully marked state")
## Check Mark
circuit=InitializeCircuitRandom(5)
a,b=RunMe(circuit)
PlotState(a,True,myTitle="Initial Random State")
circuit=Mark(1,5)
a,b=RunMe(circuit,a)
PlotState(a,True,myTitle="Hopefully marked state")
a. Control-Control-Control Z#
Define a function controlZ(N)
which generates the control-control-control-control Z on \(n\) qubits. The first \(n-1\) qubits are targets which have to be 1 for the last Z qubit to happen.
This is an example for controlZ(5)
.
You can get a control-control-control-control-x from qiskit by doing the following:
circuit.mcx(list(range(0,N-1)), N-1,mode='noancilla')
where \(N-1\) is the numer of target wires.
Test out your circuit by using the following code:
initCircuit=InitializeCircuit('11110',5)
#initCircuit=InitializeCircuit('11010',5)
initCircuit.h(4)
circuit=AddCircuits([initCircuit,ControlZ(5)])
print(circuit)
a,b=RunMe(circuit)
PrintDirac(a)
You should get something close to
(0.7071067811865475+8.659560562354932e-17j)|11110> + (-0.7071067811865476-8.659560562354934e-17j)|11111>
and then if you comment out the second initialization with 11010
you will get
(0.7071067811865476+0j)|11010> + (0.7071067811865475+0j)|11011>
b. All Hadamards#
Define a function AllHadamard(n)
which builds \(n\) wires of Hadamards
Check it by the following test:
circuit=AllHadamard(5)
a,b=RunMe(circuit)
PlotState(a,True)
and seeing that it produces a state which is uniform. This is what we will use for the start of Grover’s algorithm.
c. FlipAllButZero#
Define FlipAllButZero(n)
. which should flip all but the zero configurations on \(n\) bits. It will us \(n+1\) wires (you need an ancilla). The example below is the output for FlipAllbutZero(4)
You might want to add some circuits together. To add these circuits you can do
AddCircuits([circuit1,circuit2,circuit3])
You can test it your result by doing
circuit=InitializeCircuitRandom(4)
a,b=RunMe(circuit)
PlotState(a,True)
circuit=FlipAllButZero(4)
a,b=RunMe(circuit,a)
PlotState(a,False)
You should find that everything but the zero flipped. It is ok if you are finding the zero flipped and nothing else. Quantum states aren’t defined up to a global phase and so the flipping of everything is equivalent to doing nothing.
d. Invert#
Define the Invert(N)
function. This will also be on \(N+1\) wires. Here you need to add together the AllHadamard and FlipAllButZero and AllHadamard again.
You can test it in the following way:
circuit=InitializeCircuitRandom(4)
a,b=RunMe(circuit)
PlotState(a,True)
circuit=Invert(4)
a,b=RunMe(circuit,a)
PlotState(a,False)
This will generate a random state and then invert it around the mean (shown in a blue dotted line). Your mean before and after should be the same and everything else should flip around that dotted line. (Also everything totally flipping is still allowed)
e. Running Grover’s Algorithm#
Put everything together to run Grover’s algorithm.
Exercise 4 Quantum Key Distribution (EC - 20 points)#
a. One time pads#
If Alice and Bob have two identical secret books of bits which agree, they could send secret messages to each other. Here’s a way to make a bunch of secret bits by sharing a quantum state. After the quantum state is produced, they can separate their wires to be far apart.
def MakeSinglet(qc):
aliceWire=0
bobWire=1
qc.h(aliceWire)
qc.cx(aliceWire,bobWire)
def MakeSharedState(qc):
MakeSinglet(qc)
qc.barrier()
return qc
qc=QuantumCircuit(2,2)
qc=MakeSharedState(qc)
qc.draw(output='mpl')
Let AliceWire=0
and BobWire=1
.
Add some measurement to the circuit above
execute this circuit 100 times (use
RunMe(qc,1)
so you only run one copy of it)store a list of Alice and Bob’s bits and report what fraction of the time they agree.
For this latter task I find the following helpful: theKey=list(result.get_counts().keys())[0]
. theKey
will be the bitstring of measured state in reversed order (i.e. theKey[0]
is q0, theKey[1]
is q1, etc)
aliceBits=[]
bobBits=[]
qc.measure([0,1],[0,1])
## After measurement how often do Alice and Bob's bits agree.
print("They agree this fraction of the time: " ,amountAgree)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-11-f96726f9186e> in <module>
2 bobBits=[]
3
----> 4 qc.measure([0,1],[0,1])
5 ## After measurement how often do Alice and Bob's bits agree.
6
NameError: name 'qc' is not defined
b. Eve’s arrival#
Unfortunately this approach has a problem. Suppose that you’re sending an EPR pair to Alice and the eavesdropper Eve gets a hold of it. Is their a way to have Eve sneakily get the message without Alice and Bob noticing? Add some circuit for Eve. Assume that she can do anything she wants to the state (and her wire) after the shared state is made but before it’s shipped off to Alice and Bob.
Then, measure what fraction of the time Alice and Bob agree and what fraction of the time Eve has successfully figured out the bit.
AliceBits=[]
EveBits=[]
BobBits=[]
eveWire=2
qc=QuantumCircuit(3,3)
qc=MakeSharedState(qc)
###What can Eve do here so that she knows the secret bit and can't be detected by Alice and Bob
qc.cx(0,2)
qc.measure([2],[2])
qc.barrier()
### Eve is done now.
qc.measure([0,1],[0,1]) #this is your measure from earlier.
###How often do Alice and Bob's bits agree.
### How often do Alice and Eve's bits agree.
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-12-35d1d42d0720> in <module>
3 BobBits=[]
4 eveWire=2
----> 5 qc=QuantumCircuit(3,3)
6 qc=MakeSharedState(qc)
7 ###What can Eve do here so that she knows the secret bit and can't be detected by Alice and Bob
NameError: name 'QuantumCircuit' is not defined
If you’ve done it correctly you should find that Alice and Bob always agree but also Alice and Eve also always agree. So our protocol was a good way to share some secrete bits but doesn’t work if Even can get a hold of the state before Alice and Bob gert it. We need an improved protocol.
c. An improved protocol#
Let’s remove Eve for the moment and work on an improved protocol.
Suppose Alice and Bob agree that for each of the 100 trials, Alice and Bob are going to flip a shared coin and decide to randomly put a Hadamard on both their wires if you get heads (np.random.random()>0.5
) and leave it alone otherwise (Yes - I know flipping a shared coin is cheating. We will fix it later). Emulate this new protocol and see how often Alice and Bob’s bits agree? Store also an array which specifies when you decided to add the Hadamard.
### Build here the improved protocol and see how often Alice and Bob's bits agree.
d. Eve in the improved protocol#
Now add Eve back using your approach from (b). How often do Alice and Bob agree? How often do Alice and Eve agree? Suppose Alice and Bob were willing to talk some - can they catch Eve’s eavsdropping and still have some useful random bits left if Eve wasn’t listening.
# Use the same Eve technique as you did in (b) and see how often they agree. If Alice and Bob's bits don't
# always agree is their a way for Alice and Bob to then detect Eve's eavesdropping while still having
# some one-time pad bits left.
Alice and Bob Agree: 0.74
Alice and Eve Agree: 0.71
Exercise 5: Building up tools (EC - 10 points)#
Now we are going to build up some tools and intuition so we can learn to build more complicated circuits.
a. Initializing a binary number#
Often it is useful to get some binary state (like \(|0110\rangle\)) into your quantum computer. Write a function def Init(qc,myBinaryNumber)
which generates the state \(|\textrm{myBinaryNumber}\rangle\). Run
Init(qc,'011')
qc.draw(output='mpl')
def Init(qc,myString='000'):
#write me
qc=QuantumCircuit(3,3)
Init(qc,'011')
qc.draw(output='mpl')
File "<ipython-input-16-64a1f88e0e9c>", line 4
qc=QuantumCircuit(3,3)
^
IndentationError: expected an indented block
b. A sum over all states#
A useful state to be able to make is $\(\frac{1}{\sqrt{N}}\sum_{i=1}^N |i\rangle\)$
Write a function def AllSum(qc,w)
which adds this circuit to qc for \(N=2^w\). Run it with \(w=5\) and draw out your circuit.
qr = QuantumRegister(5)
cr = ClassicalRegister(5)
qc=QuantumCircuit(qr,cr)
AllSum(qc,5)
qc.measure(qr,cr)
qc.draw(output='mpl')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-17-7c39755fd900> in <module>
----> 1 qr = QuantumRegister(5)
2 cr = ClassicalRegister(5)
3 qc=QuantumCircuit(qr,cr)
4 AllSum(qc,5)
5 qc.measure(qr,cr)
NameError: name 'QuantumRegister' is not defined
Now run the state on your simulator and measure how often you get various states.
# Run me looking at the measurements and see what happens
c. Angle of a random gate#
In this section, we are going to start out with a \(R_z(\theta)\) gate but we don’t know what \(\theta\) is. Your goal is to use other gates to figure out \(\theta\). Write some code which returns your guess of \(\theta\). To add your random gate to your circuit, you may call RandomGate(qc).
def RandomGateHelp():
theta=np.random.random()*2*np.pi
def RandomGate(qc):
qc.rz(theta,0)
return RandomGate
RandomGate=RandomGateHelp()
def ReturnTheta():
qr=QuantumRegister(1)
cr=ClassicalRegister(1)
qc=QuantumCircuit(qr,cr)
#change this function somehow
RandomGate(qc)
qc.measure(0,0)
result=RunMe(qc)
myCounts=result.get_counts()
print(myCounts)
return 0.0
print("Theta is ",ReturnTheta())
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-19-1a8f8b58a905> in <module>
10 print(myCounts)
11 return 0.0
---> 12 print("Theta is ",ReturnTheta())
<ipython-input-19-1a8f8b58a905> in ReturnTheta()
1 def ReturnTheta():
----> 2 qr=QuantumRegister(1)
3 cr=ClassicalRegister(1)
4 qc=QuantumCircuit(qr,cr)
5 #change this function somehow
NameError: name 'QuantumRegister' is not defined
d. Inverting a CNOT#
Suppose you have a quantum computer which can generate a CNOT where the control wire is smaller then the NOT wire - i.e. qc.cx(i,j)
where \(i<j\). Now, you need the gate qc.cx(j,i)
. Figure out how to use 1-qubit gates and qc.cx(i,j)
to build the gate qc.cx(j,i)
and demonstrate that it works. Hint: You can do this with one CNOT some number of Hadamards.
# Write code here
e. Deferred Measurements#
Suppose you have a calculation where you do a measurement in the middle of your simulation.
qr=QuantumRegister(1)
cr=ClassicalRegister(1)
qc=QuantumCircuit(qr,cr)
qc.h(0)
qc.rx(np.pi/2.,0)
qc.rz(np.pi/3.,0)
qc.rx(np.pi/5.,0)
qc.measure(0,0)
qc.h(0)
qc.rz(np.pi/5.,0)
qc.ry(np.pi/2.,0)
qc.measure(0,0)
qc.draw(output='mpl')
We can measure the probabilities of the output:
plot_histogram(RunMe(qc).get_counts())
We want to rewrite the circuit on two wires so that you can do all the measurements at the end. Figure out how to pull this off and show that you get the same probability distribution over the first wire. You should be able to do this essentially independently of the gates that I include on the top wire. Hint: You want to essentially copy what would have been the result of the measurement to the bottom wire. Then you can measure the bottom wire whenever since there are no gates there.
qr=QuantumRegister(2)
cr=ClassicalRegister(2)
qc=QuantumCircuit(qr,cr)
# do stuff before the measurement which is the last thing you do
qc.measure([0,1],[0,1])
qc.draw(output='mpl')
#only plot the probability of wire 1. You want to get the same result.
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-22-58c437e115d3> in <module>
----> 1 qr=QuantumRegister(2)
2 cr=ClassicalRegister(2)
3 qc=QuantumCircuit(qr,cr)
4 # do stuff before the measurement which is the last thing you do
5 qc.measure([0,1],[0,1])
NameError: name 'QuantumRegister' is not defined
Acknowledgement:
Bryan Clark and Ryan Level (original)
Copyright: 2021