Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update qudit gates and tests #191

Merged
merged 6 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading