decompositions for multi-qubit gates when partials are impossible

This commit is contained in:
Daniel Tsvetkov 2020-03-29 09:41:00 +02:00
parent 92ccea6469
commit 09d9b78683

43
lib.py
View File

@ -602,8 +602,8 @@ class HermitianMatrix(SquareMatrix):
class UnitaryOperator(LinearTransformation, UnitaryMatrix): class UnitaryOperator(LinearTransformation, UnitaryMatrix):
def __init__(self, m: ListOrNdarray, name: str = '', partials=None, *args, def __init__(self, m: ListOrNdarray, name: str = '', partials=None,
**kwargs): decomposition=None, *args, **kwargs):
"""UnitaryOperator inherits from both LinearTransformation and a """UnitaryOperator inherits from both LinearTransformation and a
Unitary matrix Unitary matrix
It is used to act on a State vector by defining the operator to be It is used to act on a State vector by defining the operator to be
@ -614,13 +614,15 @@ class UnitaryOperator(LinearTransformation, UnitaryMatrix):
the list is the Nth partial that is used, i.e. for the first - |0><0|, the list is the Nth partial that is used, i.e. for the first - |0><0|,
for the second - |1><1| for the second - |1><1|
""" """
if np.shape(m) != (2, 2) and partials is None: if np.shape(m) != (2, 2) and partials is None and decomposition is None:
raise Exception("Please define partials in a non-single operator") raise Exception("Please define partials or decomposition in "
"a non-single operator")
UnitaryMatrix.__init__(self, m=m, *args, **kwargs) UnitaryMatrix.__init__(self, m=m, *args, **kwargs)
LinearTransformation.__init__(self, m=m, func=self.operator_func, *args, LinearTransformation.__init__(self, m=m, func=self.operator_func, *args,
**kwargs) **kwargs)
self.name = name self.name = name
self.partials = partials self.partials = partials
self.decomposition = decomposition
def operator_func(self, other, which_qbit=None): def operator_func(self, other, which_qbit=None):
this_cols, other_rows = np.shape(self.m)[1], np.shape(other.m)[0] this_cols, other_rows = np.shape(self.m)[1], np.shape(other.m)[0]
@ -631,6 +633,8 @@ class UnitaryOperator(LinearTransformation, UnitaryMatrix):
"Please specify which_qubit to operate on".format( "Please specify which_qubit to operate on".format(
this_cols, other_rows)) this_cols, other_rows))
total_qbits = int(np.log2(other_rows)) total_qbits = int(np.log2(other_rows))
if type(which_qbit) is list and len(which_qbit) == 1:
which_qbit = which_qbit[0]
if type(which_qbit) is int: if type(which_qbit) is int:
# single qubit-gate # single qubit-gate
assert this_cols == 2 assert this_cols == 2
@ -639,6 +643,9 @@ class UnitaryOperator(LinearTransformation, UnitaryMatrix):
range(total_qbits)] range(total_qbits)]
new_m = np.prod(extended_m).m new_m = np.prod(extended_m).m
elif type(which_qbit) is list: elif type(which_qbit) is list:
if self.decomposition:
return self.decomposition(other, *which_qbit)
else:
# single or multiple qubit-gate # single or multiple qubit-gate
assert 1 <= len(which_qbit) <= total_qbits assert 1 <= len(which_qbit) <= total_qbits
assert len(which_qbit) == len(self.partials) assert len(which_qbit) == len(self.partials)
@ -669,9 +676,7 @@ class UnitaryOperator(LinearTransformation, UnitaryMatrix):
raise Exception( raise Exception(
"which_qubit needs to be either an int of N-th qubit or list") "which_qubit needs to be either an int of N-th qubit or list")
extended_op = UnitaryOperator(new_m, name=self.name, return State(np.dot(new_m, other.m))
partials=self.partials)
return State(np.dot(extended_op.m, other.m))
def __repr__(self): def __repr__(self):
if self.name: if self.name:
@ -836,8 +841,7 @@ T = lambda phi: Gate([[1, 0],
# #
# Decomposed CNOT : # Decomposed CNOT :
# reverse engineered from # reverse engineered from
# https://quantumcomputing.stackexchange.com/questions/4252/how-to-derive-the # https://quantumcomputing.stackexchange.com/questions/4252/how-to-derive-the-cnot-matrix-for-a-3-qbit-system-where-the-control-target-qbi
# -cnot-matrix-for-a-3-qbit-system-where-the-control-target-qbi
# #
# CNOT(q1, I, q2): # CNOT(q1, I, q2):
# |0><0| x I_2 x I_2 + |1><1| x I_2 x X # |0><0| x I_2 x I_2 + |1><1| x I_2 x X
@ -874,13 +878,17 @@ CZ = Gate([
[0, 0, 0, -1], [0, 0, 0, -1],
], name="CZ", partials=[C_partial, z_partial]) ], name="CZ", partials=[C_partial, z_partial])
# TODO: These are not the correct partials # Can't do partials for SWAP, but can be decomposed
# SWAP is decomposed into three CNOTs where the second one is flipped
SWAP = Gate([ SWAP = Gate([
[1, 0, 0, 0], [1, 0, 0, 0],
[0, 0, 1, 0], [0, 0, 1, 0],
[0, 1, 0, 0], [0, 1, 0, 0],
[0, 0, 0, 1], [0, 0, 0, 1],
], name="SWAP", partials=[C_partial, C_partial]) ], name="SWAP",
decomposition=lambda state, x, y:
CNOT.on(CNOT.on(CNOT.on(state, [x, y]), [y, x]), [x, y])
)
TOFF = Gate([ TOFF = Gate([
@ -1012,6 +1020,10 @@ def test_eigenstuff():
def test_partials(): def test_partials():
# test single qbit state
assert X.on(s("|000>"), which_qbit=1) == s("|010>")
assert X.on(s("|000>"), which_qbit=[1]) == s("|010>")
# normal 2 qbit state # normal 2 qbit state
assert CNOT.on(s("|00>")) == s("|00>") assert CNOT.on(s("|00>")) == s("|00>")
assert CNOT.on(s("|10>")) == s("|11>") assert CNOT.on(s("|10>")) == s("|11>")
@ -1024,9 +1036,12 @@ def test_partials():
assert CNOT.on(s("|01>"), which_qbit=[1, 0]) == s("|11>") assert CNOT.on(s("|01>"), which_qbit=[1, 0]) == s("|11>")
assert CNOT.on(s("|001>"), which_qbit=[2, 0]) == s("|101>") assert CNOT.on(s("|001>"), which_qbit=[2, 0]) == s("|101>")
# Test SWAP via 3 successive CNOTs, the second is flipped # Test SWAP via 3 successive CNOTs, the second CNOT is flipped
assert CNOT.on(CNOT.on(CNOT.on(s("|10>")), which_qbit=[1, 0])) == s("|01>") assert CNOT.on(CNOT.on(CNOT.on(s("|10>")), which_qbit=[1, 0])) == s("|01>")
# Test SWAP via 3 successive CNOTs, the 0<->2 are swapped
assert CNOT.on(CNOT.on(CNOT.on(s("|100>"), [0, 2]), [2, 0]), [0, 2]) == s("|001>")
# apply on 0, 1 of 3qbit state # apply on 0, 1 of 3qbit state
assert CNOT.on(s("|000>"), which_qbit=[0, 1]) == s("|000>") assert CNOT.on(s("|000>"), which_qbit=[0, 1]) == s("|000>")
assert CNOT.on(s("|100>"), which_qbit=[0, 1]) == s("|110>") assert CNOT.on(s("|100>"), which_qbit=[0, 1]) == s("|110>")
@ -1048,8 +1063,8 @@ def test_partials():
# test SWAP gate # test SWAP gate
assert SWAP.on(s("|10>")) == s("|01>") assert SWAP.on(s("|10>")) == s("|01>")
assert SWAP.on(s("|01>")) == s("|10>") assert SWAP.on(s("|01>")) == s("|10>")
# TODO: # Tests SWAP on far-away gates
# assert SWAP.on(s("|001>"), which_qbit=[0, 2]) == s("|100>") assert SWAP.on(s("|001>"), which_qbit=[0, 2]) == s("|100>")
# test Toffoli gate # test Toffoli gate
assert TOFF.on(s("|000>")) == s("|000>") assert TOFF.on(s("|000>")) == s("|000>")