From 7b74de0e94744bf7d7600b9c7aaae768c38523a5 Mon Sep 17 00:00:00 2001 From: madcpf Date: Sat, 6 Jul 2024 22:51:32 -0700 Subject: [PATCH] Update qudit gates and tests --- unitary/alpha/qudit_effects.py | 8 ++-- unitary/alpha/qudit_gates.py | 64 +++++++++++++++---------------- unitary/alpha/qudit_gates_test.py | 33 ++++++++++++++-- 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/unitary/alpha/qudit_effects.py b/unitary/alpha/qudit_effects.py index 632a724f..a8c29e1c 100644 --- a/unitary/alpha/qudit_effects.py +++ b/unitary/alpha/qudit_effects.py @@ -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. @@ -55,11 +55,11 @@ 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 diff --git a/unitary/alpha/qudit_gates.py b/unitary/alpha/qudit_gates.py index a2446b4b..93240b57 100644 --- a/unitary/alpha/qudit_gates.py +++ b/unitary/alpha/qudit_gates.py @@ -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__( @@ -34,17 +34,21 @@ 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): @@ -52,10 +56,9 @@ def _circuit_diagram_info_(self, args): class QuditPlusGate(cirq.Gate): - """Cycles all the states using a permutation gate. - - This gate adds a number to each state. For instance, - `QuditPlusGate(dimension=3, addend=1)` + """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)` + 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): @@ -78,19 +81,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): @@ -103,14 +103,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 @@ -139,14 +137,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 @@ -186,13 +184,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 diff --git a/unitary/alpha/qudit_gates_test.py b/unitary/alpha/qudit_gates_test.py index bb34dafc..117459d6 100644 --- a/unitary/alpha/qudit_gates_test.py +++ b/unitary/alpha/qudit_gates_test.py @@ -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() @@ -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() @@ -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() @@ -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), @@ -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): @@ -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",