Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
Pengfei Chen committed May 10, 2024
1 parent af4f671 commit 4d54a01
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 44 deletions.
2 changes: 2 additions & 0 deletions unitary/alpha/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@
from unitary.alpha.qudit_effects import (
Cycle,
Flip,
Superpose,
QuditCycle,
QuditFlip,
QuditSuperpose,
)

from unitary.alpha.quantum_object import (
Expand Down
41 changes: 34 additions & 7 deletions unitary/alpha/qudit_effects.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@

import cirq

from unitary.alpha.qudit_gates import QuditPlusGate, QuditXGate
from unitary.alpha.qudit_gates import QuditPlusGate, QuditXGate, QuditHadamardGate
from unitary.alpha.quantum_effect import QuantumEffect


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 Expand Up @@ -127,3 +125,32 @@ class QuditFlip(Flip):

def __init__(self, dimension: int, state0: int, state1: int):
super().__init__(state0=state0, state1=state1)


class Superpose(QuantumEffect):
"""Transforms each pure state to a (equal, in terms of absolute magnitude) superposition of
all pure states.
"""

def __init__(self):
pass

def effect(self, *objects):
for q in objects:
if q.qubit.dimension == 2:
yield cirq.H(q.qubit)
else:
yield QuditHadamardGate(dimension=q.qubit.dimension)(
q.qubit
)


class QuditSuperpose(Superpose):
"""Equivalent to Superpose.
Exists only for backwards compatibiltity.
Will be removed in 2024.
"""

def __init__(self, dimension: int):
super().__init__()
11 changes: 11 additions & 0 deletions unitary/alpha/qudit_effects_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,14 @@ def test_qudit_flip(simulator, compile_to_qubits):
alpha.QuditFlip(3, 0, 1)(piece)
results = board.peek([piece], count=100)
assert all(result == [StopLight.GREEN] for result in results)


def test_qudit_superpose():
board = alpha.QuantumWorld(sampler=cirq.Simulator(), compile_to_qubits=False)
piece = alpha.QuantumObject("t", StopLight.GREEN)
board.add_object(piece)
alpha.QuditSuperpose(3)(piece)
results = board.peek([piece], count=100)
assert any(result == [StopLight.RED] for result in results)
assert any(result == [StopLight.YELLOW] for result in results)
assert any(result == [StopLight.GREEN] for result in results)
97 changes: 65 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 All @@ -202,3 +201,37 @@ def _circuit_diagram_info_(self, args):
return cirq.CircuitDiagramInfo(
wire_symbols=("iSwap", "iSwap"), exponent=self._diagram_exponent(args)
)


class QuditHadamardGate(cirq.Gate):
"""Performs a Hadamard opperation on the given qudit.
This is the equivalent of a H gate for qubits. When applied to a given pure state,
the state will be transformed to a (equal, in terms of absolute magnitude) superposition of
all pure states.
Args:
dimension: dimension of the qudits, for instance,
a dimension of 3 would be a qutrit.
"""

def __init__(self, dimension: int):
self.dimension = dimension

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

def _unitary_(self):
arr = 1.0 / np.sqrt(self.dimension) * np.ones((self.dimension, self.dimension), dtype=np.complex64)
w = np.exp(1j * 2 * np.pi / self.dimension)
# Note: this unitary matrice always has first row and first column elements equal to one,
# so we only do calculation for rest of the elements.
for i in range(1, self.dimension):
for j in range(1, self.dimension):
arr[i, j] *= w ** (i * j)
return arr

def _circuit_diagram_info_(self, args):
return cirq.CircuitDiagramInfo(
wire_symbols=("H", "H"), exponent=self._diagram_exponent(args)
)
50 changes: 45 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,18 @@ 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 +174,17 @@ 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 @@ -180,12 +203,14 @@ def test_control_of_0_x(dest: int):
qudit_gates.QuditISwapPowGate(3),
qudit_gates.QuditSwapPowGate(3, exponent=0.5),
qudit_gates.QuditISwapPowGate(3, exponent=0.5),
qudit_gates.QuditHadamardGate(2),
qudit_gates.QuditHadamardGate(3),
qudit_gates.QuditHadamardGate(4),
],
)
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 Expand Up @@ -295,3 +320,18 @@ def test_sqrt_iswap(q0: int, q1: int):
results = sim.run(c, repetitions=1000)
assert np.all(results.measurements["m0"] == q1)
assert np.all(results.measurements["m1"] == q0)


@pytest.mark.parametrize(
"d, q0", [(2, 0), (2, 1), (3, 0), (3, 1), (3, 2), (4, 0), (4, 1), (4, 2), (4, 3)]
)
def test_hadamard(d: int, q0: int):
qutrit0 = cirq.NamedQid("q0", dimension=d)
c = cirq.Circuit()
c.append(qudit_gates.QuditPlusGate(d, addend=q0)(qutrit0))
c.append(qudit_gates.QuditHadamardGate(d)(qutrit0))
c.append(cirq.measure(qutrit0, key="m0"))
sim = cirq.Simulator()
results = sim.run(c, repetitions=1000)
for each_possible_outcome in range(d):
assert np.any(results.measurements["m0"] == each_possible_outcome)

0 comments on commit 4d54a01

Please sign in to comment.