added double slit and which way experiments, plus ability for a gate to act on less-dimensional state if specified which qbit

This commit is contained in:
Daniel Tsvetkov 2020-03-28 10:13:21 +01:00
parent 82889436bb
commit 3a3b0915a5
2 changed files with 178 additions and 33 deletions

100
11_experiments.py Normal file
View File

@ -0,0 +1,100 @@
import matplotlib.pyplot as plt
from lib import *
def plot(t, s):
fig, ax = plt.subplots()
ax.plot(t, s)
ax.set(xlabel='angle', ylabel='result', title='plot')
ax.grid()
fig.savefig("test.png")
plt.show()
# Double-slit, which-way
# https://physics.stackexchange.com/questions/229895/understanding-the-quantum-eraser-from-a-quantum-information-stand-point
# Delayed-choice quantum eraser:
# https://quantumcomputing.stackexchange.com/questions/1568/what-is-the-quantum-circuit-equivalent-of-a-delayed-choice-quantum-eraser
# The matrix
# Λ=eiϕ/2|0⟩⟨0|+eiϕ/2|1⟩⟨1|
# introduces a phase shift between the two paths (this can e.g. be a
# function of the position on the screen, or the relative length of
# the interferometer arms)
# See note on the post:
# you could use quirk to provide interactive versions of the quantum
# circuits. For example, your first circuit is this
# https://algassert.com/quirk#circuit={%22cols%22:[[%22H%22],[%22Z^t%22],
# [%22H%22],[%22Measure%22]]}
# , while your second is
# https://algassert.com/quirk#circuit=%7B%22cols%22:%5B%5B%22H%22%5D,
# %5B%22%E2%80%A2%22,%22X%22%5D,%5B%22Z%5Et%22%5D,%5B%22H%22%5D%5D%7D
# this.
# Note(pisquared): The difference seems in the Lambda operator which
# seems to be actually a [T-Gate](
# https://www.quantum-inspire.com/kbase/t-gate/)
# In quirk, the Z^t keeps |0><0| while the description
# in the post spins |0><0| to oposite degrees which
# cancels after application of the second H-gate
def double_slit():
"""
#
:return:
"""
ms = list()
angles = np.arange(-4 * np.pi, 4 * np.pi, 0.1)
for theta in angles:
# Here, the first H puts the qubit in the superposition of both
# paths/slits -- the states |0⟩ and |1⟩ correspond to the two
# paths/slits
phi1 = H.on(s("|0>"))
phi2 = T(theta).on(phi1)
# and the second H makes the two paths interfere.
phi3 = H.on(phi2)
# If the output qubit is measured in state |0⟩,
# the interference is constructive, otherwise desctructive.
#
# You can easily check that this yields an interference pattern which
# varies like prob(0)=cos(ϕ)2.
ms.append(phi3.get_prob(0))
# ms.append(phi3.measure())
plot(angles, ms)
def which_way():
ms = list()
angles = np.arange(-4 * np.pi, 4 * np.pi, 0.1)
for theta in angles:
# Here, the first H puts the qubit in the superposition of both
# paths/slits -- the states |0⟩ and |1⟩ correspond to the two
# paths/slits
phi1 = H.on(s("|0>"))
# Now imagine that we want to copy the which-way information:
# Then, what we do is
q2 = s("|0>")
phi2 = CNOT.on(phi1 * q2)
phi3 = T(theta).on(phi2, 0)
phi4 = H.on(phi3, 0)
# this is, we copy the which-way information before traversing the
# interferometer onto qubit c. It is easy to see that this destroys
# the interference pattern, i.e., prob(0)=1/2.
ms.append(phi4.get_prob(0))
# ms.append(phi3.measure())
plot(angles, ms)
if __name__ == "__main__":
double_slit()
which_way()

111
lib.py
View File

@ -261,10 +261,11 @@ class State(Vector):
def get_computational_basis_vector(self, j):
return Vector([[1] if i == int(j) else [0] for i in range(len(self.m))])
def get_prob(self, j, basis=None):
def get_prob(self, j=None, basis=None):
"""pr(j) = |<e_j|self>|^2
"""
if basis is None:
assert j is not None
basis = self.get_computational_basis_vector(j)
m = MeasurementOperator.create_from_basis(basis)
return m.get_prob(self)
@ -408,11 +409,18 @@ def normalize_state(vector: Vector):
def s(q, name=None):
"""Helper method for creating state easily"""
if type(q) == str:
# e.g. |000>
if q[0] == '|' and q[-1] == '>':
# e.g. |000>
state = State.from_string(q)
state.name = name
return state
elif q[0] == '|' and q[-1] == '|' and "><" in q:
# e.g. |0><0|
f_qbit, s_qbit = q.split("<")[0], q.split(">")[1]
if len(f_qbit) == len(s_qbit):
s_qbit = '|' + s_qbit[1:-1] + '>'
fs, ss = State.from_string(f_qbit), State.from_string(s_qbit)
return DensityMatrix(fs.x(ss).m, name=name)
elif type(q) == list:
# e.g. s([1,0]) => |0>
return State(np.reshape(q, (len(q), 1)), name=name)
@ -567,7 +575,17 @@ class UnitaryOperator(LinearTransformation, UnitaryMatrix):
LinearTransformation.__init__(self, m=m, func=self.operator_func, *args, **kwargs)
self.name = name
def operator_func(self, other):
def operator_func(self, other, which_qbit=None):
this_cols, other_rows = np.shape(self.m)[1], np.shape(other.m)[0]
if this_cols != other_rows:
if which_qbit is None:
raise Exception("Operating dim-{} operator on a dim-{} state. "
"Please specify which_qubit to operate on".format(
this_cols, other_rows))
extended_m = [self if i == which_qbit else I for i in range(int(np.log2(other_rows)))]
extended_op = UnitaryOperator(np.prod(extended_m).m)
return State(np.dot(extended_op.m, other.m))
return State(np.dot(self.m, other.m))
def __repr__(self):
@ -576,9 +594,15 @@ class UnitaryOperator(LinearTransformation, UnitaryMatrix):
return str(self.m)
class Gate(UnitaryOperator):
def __init__(self, *args, **kwargs):
"""Alias: Gates are Unitary Operators"""
UnitaryOperator.__init__(self, *args, **kwargs)
class HermitianOperator(LinearTransformation, HermitianMatrix):
def __init__(self, m: ListOrNdarray, name: str = '', *args, **kwargs):
"""HermitianMatrix inherits from both LinearTransformation and a Hermitian matrix
"""HermitianOperator inherits from both LinearTransformation and a Hermitian matrix
It is used to act on a State vector by defining the operator to be the dot product"""
self.name = name
HermitianMatrix.__init__(self, m=m, *args, **kwargs)
@ -594,6 +618,25 @@ class HermitianOperator(LinearTransformation, HermitianMatrix):
return str(self.m)
class DensityMatrix(HermitianOperator):
def __init__(self, m: ListOrNdarray, name: str = '', *args, **kwargs):
"""The density matrix is a representation of a linear operator called
the density operator. The density matrix is obtained from the density
operator by choice of basis in the underlying space. In practice,
the terms density matrix and density operator are often used
interchangeably. Both matrix and operator are self-adjoint
(or Hermitian), positive semi-definite, of trace one, and may
be infinite-dimensional."""
self.name = name
HermitianOperator.__init__(self, m=m, *args, **kwargs)
if not self._is_density():
raise TypeError("Not a Density matrix")
def _is_density(self):
"""Checks if it trace is 1"""
return np.isclose(np.trace(self.m), 1.0)
# How to add a CNOT gate to the Quantum Processor?
# Imagine if I have to act on a 3-qubit computer and CNOT(q1, q3)
#
@ -712,21 +755,21 @@ b_psi_m = State([[0],
well_known_states = [_p, _m, b_phi_p, b_psi_p, b_phi_m, b_psi_m]
_ = I = UnitaryOperator([[1, 0],
[0, 1]],
name="-")
_ = I = Gate([[1, 0],
[0, 1]],
name="-")
X = UnitaryOperator([[0, 1],
[1, 0]],
name="X")
X = Gate([[0, 1],
[1, 0]],
name="X")
Y = UnitaryOperator([[0, -1j],
[1j, 0]],
name="Y")
Y = Gate([[0, -1j],
[1j, 0]],
name="Y")
Z = UnitaryOperator([[1, 0],
[0, -1]],
name="Z")
Z = Gate([[1, 0],
[0, -1]],
name="Z")
# These are rotations that are specified commonly e.g. in
@ -736,27 +779,31 @@ Z = UnitaryOperator([[1, 0],
# However - they are correct up to a global phase which is all that matters for measurement purposes
#
def Rx(theta):
return UnitaryOperator([[np.cos(theta / 2), -1j * np.sin(theta / 2)],
[-1j * np.sin(theta / 2), np.cos(theta / 2)]],
name="Rx")
H = Gate([[1 / np.sqrt(2), 1 / np.sqrt(2)],
[1 / np.sqrt(2), -1 / np.sqrt(2)], ],
name="H")
# Rotation operators
# https://www.quantum-inspire.com/kbase/rx-gate/
Rx = lambda theta: Gate([[np.cos(theta / 2), -1j * np.sin(theta / 2)],
[-1j * np.sin(theta / 2), np.cos(theta / 2)]],
name="Rx")
def Ry(theta):
return UnitaryOperator([[np.cos(theta / 2), -np.sin(theta / 2)],
[np.sin(theta / 2), np.cos(theta / 2)]],
name="Ry")
Ry = lambda theta: Gate([[np.cos(theta / 2), -np.sin(theta / 2)],
[np.sin(theta / 2), np.cos(theta / 2)]],
name="Ry")
def Rz(theta):
return UnitaryOperator([[np.power(np.e, -1j * theta / 2), 0],
[0, np.power(np.e, 1j * theta / 2)]],
name="Rz")
Rz = lambda theta: Gate([[np.power(np.e, -1j * theta / 2), 0],
[0, np.power(np.e, 1j * theta / 2)]],
name="Rz")
H = UnitaryOperator([[1 / np.sqrt(2), 1 / np.sqrt(2)],
[1 / np.sqrt(2), -1 / np.sqrt(2)], ],
name="H")
# See [T-Gate](https://www.quantum-inspire.com/kbase/t-gate/)
T = lambda phi: Gate([[1, 0],
[0, np.power(np.e, 1j * phi / 2)]],
name="T")
CNOT = TwoQubitOperator([
[1, 0, 0, 0],
@ -997,8 +1044,6 @@ def test():
# ALL FOUR NOW - Create a Bell state (I) that has H|0> (II), measures (III) and composition in CNOT (IV)
# Bell state
# First - create a superposition
H = UnitaryOperator([[1 / np.sqrt(2), 1 / np.sqrt(2)],
[1 / np.sqrt(2), -1 / np.sqrt(2)], ])
superpos = H.on(_0)
assert superpos == _p