Skip to content

Commit

Permalink
Merge pull request #191 from madcpf/update
Browse files Browse the repository at this point in the history
Update qudit gates and tests
  • Loading branch information
madcpf authored May 13, 2024
2 parents af4f671 + 0af5a50 commit 622f375
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 43 deletions.
10 changes: 4 additions & 6 deletions unitary/alpha/qudit_effects.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@


class Cycle(QuantumEffect):
"""Cycles a qubit from |0> to |1>, |1> to |2>, etc.
"""Cycles a qudit from |0> to |1>, |1> to |2>, etc.
Essentially adds `addend` to the state, where `addend`
is the parameter supplied at creation.
Expand Down Expand Up @@ -55,15 +55,13 @@ def __init__(self, dimension, num=1):


class Flip(QuantumEffect):
"""Flips two states of a qubit, leaving all other states unchanged.
"""Flips two states of a qudit, leaving all other states unchanged.
For instance, Flip(state0 = 0, state1=2) is a qutrit effect
For instance, Flip(state0 = 0, state1 = 2) is a qutrit effect
that flips |0> to |2>, |2> to |0> and leaves
|1> alone. This is also sometimes referred to as the X_0_2 gate.
|1> alone. This is also sometimes referred as the X_02 gate.
For a partial flip, use the `effect_fraction` argument.
Note that this is only applied so far on qubits and not yet for
qudits.
These effects will be cumulative. For instance, two quarter
flips (effect_fraction=0.25) will be equivalent to a half
Expand Down
63 changes: 31 additions & 32 deletions unitary/alpha/qudit_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class QuditXGate(cirq.Gate):
'destination_state' parameter that is passed in.
All other states are left alone.
For example, QuditXGate(dimension=3, state=1)
is a X_01 gate that leaves the |2〉 state alone.
For example, QuditXGate(dimension=3, source_state=0, destination_state=1)
is a X_01 gate that leaves the |2〉state alone.
"""

def __init__(
Expand All @@ -34,28 +34,32 @@ def __init__(
self.dimension = dimension
self.source_state = source_state
self.destination_state = destination_state
if self.source_state >= self.dimension:
raise ValueError("Source state must be smaller than dimension.")
if self.destination_state >= self.dimension:
raise ValueError("Destination state must be smaller than dimension.")

def _qid_shape_(self):
return (self.dimension,)

def _unitary_(self):
arr = np.zeros((self.dimension, self.dimension))
arr[self.source_state, self.destination_state] = 1
arr[self.destination_state, self.source_state] = 1
for i in range(self.dimension):
if i != self.source_state and i != self.destination_state:
arr[i, i] = 1
arr = np.eye(self.dimension)
if self.source_state != self.destination_state:
arr[self.source_state, self.source_state] = 0
arr[self.destination_state, self.destination_state] = 0
arr[self.source_state, self.destination_state] = 1
arr[self.destination_state, self.source_state] = 1
return arr

def _circuit_diagram_info_(self, args):
return f"X({self.source_state}_{self.destination_state})"


class QuditPlusGate(cirq.Gate):
"""Cycles all the states using a permutation gate.
"""Cycles all the states by `addend` using a permutation gate.
This gate adds a number to each state. For instance,
`QuditPlusGate(dimension=3, addend=1)`
This gate adds a number to each state. For instance,`QuditPlusGate(dimension=3, addend=1)`
will cycle state vector (a, b, c) to (c, a, b), and will cycle state |0> to |1>, |1> to |2>, |2> to |0>.
"""

def __init__(self, dimension: int, addend: int = 1):
Expand All @@ -78,19 +82,16 @@ def _circuit_diagram_info_(self, args):
class QuditControlledXGate(cirq.Gate):
"""A Qudit controlled-X gate.
This gate takes the dimension of the qudit (e.g. 3 for qutrits)
as well as the control and destination gates to produce a
This gate takes the dimension of the qudit as well as the control and destination states to produce a
controlled-X 2-qudit gate.
Note that there are two parameters for this gate. The first
is the control state, which determines when the X gate on the
second qudit is activated. For instance, if this is set to 2,
then the X gate will be activated when the first qudit is
in the |2> state.
The state parameter specifies the destination state of the
second qudit. For instance, if set to 1, it will perform a
X_01 gate when activated by the control.
Args:
dimension: dimension of the qudits, for instance, a dimension of 3 would be a qutrit.
control_state: the state of first qudit that when satisfied the X gate on the second qudit will be activated.
For instance, if `control_state` is set to 2, then the X gate will be
activated when the first qudit is in the |2> state.
state: the destination state of the second qudit. For instance, if set to 1, it will perform a
X_01 gate when activated by `control_state`.
"""

def __init__(self, dimension: int, control_state: int = 1, state: int = 1):
Expand All @@ -103,14 +104,12 @@ def _qid_shape_(self):

def _unitary_(self):
size = self.dimension * self.dimension
arr = np.zeros((size, size), dtype=np.complex64)
arr = np.eye(size, dtype=np.complex64)
control_block_offset = self.control_state * self.dimension
arr[control_block_offset, control_block_offset] = 0
arr[control_block_offset + self.state, control_block_offset + self.state] = 0
arr[control_block_offset, control_block_offset + self.state] = 1
arr[control_block_offset + self.state, control_block_offset] = 1
for x in range(self.dimension):
for y in range(self.dimension):
if x != self.control_state or (y != self.state and y != 0):
arr[x * self.dimension + y, x * self.dimension + y] = 1
return arr


Expand Down Expand Up @@ -139,14 +138,14 @@ def _qid_shape_(self):
def _unitary_(self):
size = self.dimension * self.dimension
arr = np.zeros((size, size), dtype=np.complex64)
g = np.exp(1j * np.pi * self.exponent / 2)
coeff = -1j * g * np.sin(np.pi * self.exponent / 2)
diag = g * np.cos(np.pi * self.exponent / 2)
for x in range(self.dimension):
for y in range(self.dimension):
if x == y:
arr[x * self.dimension + y][x * self.dimension + y] = 1
continue
g = np.exp(1j * np.pi * self.exponent / 2)
coeff = -1j * g * np.sin(np.pi * self.exponent / 2)
diag = g * np.cos(np.pi * self.exponent / 2)
arr[x * self.dimension + y, y * self.dimension + x] = coeff
arr[x * self.dimension + y, x * self.dimension + y] = diag
return arr
Expand Down Expand Up @@ -186,13 +185,13 @@ def _qid_shape_(self):
def _unitary_(self):
size = self.dimension * self.dimension
arr = np.zeros((size, size), dtype=np.complex64)
coeff = 1j * np.sin(np.pi * self.exponent / 2)
diag = np.cos(np.pi * self.exponent / 2)
for x in range(self.dimension):
for y in range(self.dimension):
if x == y:
arr[x * self.dimension + y][x * self.dimension + y] = 1
continue
coeff = 1j * np.sin(np.pi * self.exponent / 2)
diag = np.cos(np.pi * self.exponent / 2)
arr[x * self.dimension + y, y * self.dimension + x] = coeff
arr[x * self.dimension + y, x * self.dimension + y] = diag

Expand Down
34 changes: 29 additions & 5 deletions unitary/alpha/qudit_gates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import unitary.alpha.qudit_gates as qudit_gates


@pytest.mark.parametrize("state", [1, 2])
@pytest.mark.parametrize("state", [0, 1, 2])
def test_qutrit_x(state: int):
qutrit = cirq.NamedQid("a", dimension=3)
sim = cirq.Simulator()
Expand All @@ -38,7 +38,7 @@ def test_qutrit_x(state: int):
assert np.all(results.measurements["m"] == 0)


@pytest.mark.parametrize("num_gates", [1, 2, 3, 4, 5, 6])
@pytest.mark.parametrize("num_gates", [0, 1, 2, 3, 4, 5, 6])
def test_qutrit_plus_one(num_gates: int):
qutrit = cirq.NamedQid("a", dimension=3)
c = cirq.Circuit()
Expand All @@ -50,7 +50,7 @@ def test_qutrit_plus_one(num_gates: int):
assert np.all(results.measurements["m"] == num_gates % 3)


@pytest.mark.parametrize("num_gates", [1, 2, 3, 4, 5, 6])
@pytest.mark.parametrize("num_gates", [0, 1, 2, 3, 4, 5, 6])
def test_qutrit_plus_addend(num_gates: int):
qutrit = cirq.NamedQid("a", dimension=3)
c = cirq.Circuit()
Expand Down Expand Up @@ -101,7 +101,7 @@ def test_control_x(control: int, dest: int):
assert np.all(results.measurements["m1"] == 0)

# Control is excited to a non-controlling state and has no effect.
non_active = 2 - control + 1
non_active = 3 - control
c = cirq.Circuit(
qudit_gates.QuditXGate(3, 0, non_active)(qutrit0),
qudit_gates.QuditControlledXGate(3, control, dest)(qutrit0, qutrit1),
Expand All @@ -112,6 +112,19 @@ def test_control_x(control: int, dest: int):
assert np.all(results.measurements["m0"] == non_active)
assert np.all(results.measurements["m1"] == 0)

# 2nd qutrit is excited to a non-dest state and has no effect.
non_active = 3 - dest
c = cirq.Circuit(
qudit_gates.QuditXGate(3, 0, control)(qutrit0),
qudit_gates.QuditXGate(3, 0, non_active)(qutrit1),
qudit_gates.QuditControlledXGate(3, control, dest)(qutrit0, qutrit1),
cirq.measure(qutrit0, key="m0"),
cirq.measure(qutrit1, key="m1"),
)
results = sim.run(c, repetitions=1000)
assert np.all(results.measurements["m0"] == control)
assert np.all(results.measurements["m1"] == non_active)


@pytest.mark.parametrize("dest", [1, 2])
def test_control_of_0_x(dest: int):
Expand Down Expand Up @@ -162,6 +175,18 @@ def test_control_of_0_x(dest: int):
assert np.all(results.measurements["m0"] == 2)
assert np.all(results.measurements["m1"] == 0)

# 2nd qutrit is in the non-dest state and has no effect
non_active = 3 - dest
c = cirq.Circuit(
qudit_gates.QuditXGate(3, 0, non_active)(qutrit1),
qudit_gates.QuditControlledXGate(3, 0, dest)(qutrit0, qutrit1),
cirq.measure(qutrit0, key="m0"),
cirq.measure(qutrit1, key="m1"),
)
results = sim.run(c, repetitions=1000)
assert np.all(results.measurements["m0"] == 0)
assert np.all(results.measurements["m1"] == non_active)


@pytest.mark.parametrize(
"gate",
Expand All @@ -185,7 +210,6 @@ def test_control_of_0_x(dest: int):
def test_gates_are_unitary(gate: cirq.Gate):
m = cirq.unitary(gate)
np.set_printoptions(linewidth=200)
result = m.dot(m.T.conj())
assert np.allclose(np.eye(len(m)), m.dot(m.T.conj()), atol=1e-6)


Expand Down

0 comments on commit 622f375

Please sign in to comment.