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:
parent
82889436bb
commit
3a3b0915a5
100
11_experiments.py
Normal file
100
11_experiments.py
Normal 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|+e−iϕ/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
111
lib.py
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user