From 671ba7c7596ae852c523fb23b2c7cdb6ca1e8d76 Mon Sep 17 00:00:00 2001 From: Lana <166859256+lanafs@users.noreply.github.com> Date: Thu, 20 Jun 2024 19:25:08 +0000 Subject: [PATCH 1/6] Add the qudit phase gate, Z_d, from arxiv.org/abls/2008.00959 --- unitary/alpha/qudit_gates.py | 51 +++++++++++++++++++++++++++++++ unitary/alpha/qudit_gates_test.py | 14 +++++++++ 2 files changed, 65 insertions(+) diff --git a/unitary/alpha/qudit_gates.py b/unitary/alpha/qudit_gates.py index 628d9a45..5173da2c 100644 --- a/unitary/alpha/qudit_gates.py +++ b/unitary/alpha/qudit_gates.py @@ -12,6 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # + + +from typing import List, Dict, Tuple + import numpy as np import cirq @@ -51,6 +55,53 @@ def _circuit_diagram_info_(self, args): return f"X({self.source_state}_{self.destination_state})" +class QuditRzGate(cirq.EigenGate): + """Phase shifts a single state basis of the qudit. + + A generalization of the phase shift gate to qudits. + https://en.wikipedia.org/wiki/Quantum_logic_gate#Phase_shift_gates + + Implements Z_d as defined in eqn (5) of https://arxiv.org/abs/2008.00959 + + For a qudit of dimensionality d, shifts the phase of |d-1> by radians. + + Args: + dimension: dimension of the qudits, for instance, + a dimension of 3 would be a qutrit. + radians: The phase shift applied to basis d-1, measured in radians. + """ + + _cached_eigencomponents: Dict[int, List[Tuple[float, np.ndarray]]] = {} + + def __init__(self, dimension: int, radians: float = np.pi): + super().__init__(exponent=radians / np.pi, global_shift=0) + self.dimension = dimension + + def _qid_shape_(self): + return (self.dimension,) + + def _eigen_components(self) -> List[Tuple[float, np.ndarray]]: + if self.dimension not in QuditRzGate._cached_eigencomponents: + components = [] + for i in range(self.dimension): + half_turns = 0 + m = np.zeros((self.dimension, self.dimension)) + m[i][i] = 1 + if i == self.dimension - 1: + half_turns = 1 + components.append((half_turns, m)) + QuditRzGate._cached_eigencomponents[self.dimension] = components + return QuditRzGate._cached_eigencomponents[self.dimension] + + def _circuit_diagram_info_(self, args): + return cirq.CircuitDiagramInfo( + wire_symbols=("Z_d"), exponent=self._format_exponent_as_angle(args) + ) + + def _with_exponent(self, exponent: float) -> "QuditRzGate": + return QuditRzGate(rads=exponent * np.pi) + + class QuditPlusGate(cirq.Gate): """Cycles all the states using a permutation gate. diff --git a/unitary/alpha/qudit_gates_test.py b/unitary/alpha/qudit_gates_test.py index a228da33..1f273c1a 100644 --- a/unitary/alpha/qudit_gates_test.py +++ b/unitary/alpha/qudit_gates_test.py @@ -224,6 +224,20 @@ def test_iswap(q0: int, q1: int): assert np.all(results.measurements["m1"] == q0) +@pytest.mark.parametrize("dimension, phase_rads", [(2, np.pi), (3, 1), (4, np.pi * 2)]) +def test_rz_unitary(dimension: float, phase_rads: float): + rz = qudit_gates.QuditRzGate(dimension=dimension, radians=phase_rads) + expected_unitary = np.identity(n=dimension, dtype=np.complex64) + + # 1j = e ^ ( j * ( pi / 2 )), so we multiply phase_rads by 2 / pi. + expected_unitary[dimension - 1][dimension - 1] = 1j ** (phase_rads * 2 / np.pi) + + assert np.isclose(phase_rads / np.pi, rz._exponent) + rz_unitary = cirq.unitary(rz) + assert np.allclose(cirq.unitary(rz), expected_unitary) + assert np.allclose(np.eye(len(rz_unitary)), rz_unitary.dot(rz_unitary.T.conj())) + + @pytest.mark.parametrize( "q0, q1", [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] ) From bb6b5e27fcbdbab9b32a2e39e4a032d12ff6ff7e Mon Sep 17 00:00:00 2001 From: Lana <166859256+lanafs@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:18:34 +0000 Subject: [PATCH 2/6] Add HZH-identity tests. --- unitary/alpha/qudit_gates.py | 34 +++++++++++++++++++++---------- unitary/alpha/qudit_gates_test.py | 24 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/unitary/alpha/qudit_gates.py b/unitary/alpha/qudit_gates.py index d5b2d7b0..4f430a73 100644 --- a/unitary/alpha/qudit_gates.py +++ b/unitary/alpha/qudit_gates.py @@ -14,7 +14,7 @@ # -from typing import List, Dict, Tuple +from typing import List, Dict, Optional, Tuple import numpy as np import cirq @@ -66,36 +66,48 @@ class QuditRzGate(cirq.EigenGate): https://en.wikipedia.org/wiki/Quantum_logic_gate#Phase_shift_gates Implements Z_d as defined in eqn (5) of https://arxiv.org/abs/2008.00959 - - For a qudit of dimensionality d, shifts the phase of |d-1> by radians. + with the addition of a state parameter for convenience. + For a qudit of dimensionality d, shifts the phase of |phased_state> by radians. Args: - dimension: dimension of the qudits, for instance, - a dimension of 3 would be a qutrit. - radians: The phase shift applied to basis d-1, measured in radians. + dimension: Dimension of the qudits: for instance, a dimension of 3 + would be a qutrit. + radians: The phase shift applied to the |phased_state>, measured in + radians. + phased_state: Optional index of the state to be phase shifted. Defaults + to phase shifting the state |dimension-1>. """ _cached_eigencomponents: Dict[int, List[Tuple[float, np.ndarray]]] = {} - def __init__(self, dimension: int, radians: float = np.pi): + def __init__(self, dimension: int, radians: float = np.pi, + phased_state: Optional[int] = None): super().__init__(exponent=radians / np.pi, global_shift=0) self.dimension = dimension + if phased_state is not None: + if phased_state >= dimension or phased_state < 0: + raise ValueError(f'state {phased_state} is not valid for a qudit of' + f' dimension {dimension}.') + self.phased_state = phased_state + else: + self.phased_state = self.dimension - 1 def _qid_shape_(self): return (self.dimension,) def _eigen_components(self) -> List[Tuple[float, np.ndarray]]: - if self.dimension not in QuditRzGate._cached_eigencomponents: + eigen_key = (self.dimension, self.phased_state) + if eigen_key not in QuditRzGate._cached_eigencomponents: components = [] for i in range(self.dimension): half_turns = 0 m = np.zeros((self.dimension, self.dimension)) m[i][i] = 1 - if i == self.dimension - 1: + if i == self.phased_state: half_turns = 1 components.append((half_turns, m)) - QuditRzGate._cached_eigencomponents[self.dimension] = components - return QuditRzGate._cached_eigencomponents[self.dimension] + QuditRzGate._cached_eigencomponents[eigen_key] = components + return QuditRzGate._cached_eigencomponents[eigen_key] def _circuit_diagram_info_(self, args): return cirq.CircuitDiagramInfo( diff --git a/unitary/alpha/qudit_gates_test.py b/unitary/alpha/qudit_gates_test.py index 2f3442b9..e59ec27f 100644 --- a/unitary/alpha/qudit_gates_test.py +++ b/unitary/alpha/qudit_gates_test.py @@ -265,6 +265,30 @@ def test_rz_unitary(dimension: float, phase_rads: float): assert np.allclose(cirq.unitary(rz), expected_unitary) assert np.allclose(np.eye(len(rz_unitary)), rz_unitary.dot(rz_unitary.T.conj())) +@pytest.mark.parametrize( + "phase_1, phase_2, addend, expected_state", [(0, 0, 1, 2), + (np.pi*2/3, np.pi*4/3, 0, 2), + (np.pi*4/3, np.pi*2/3, 0, 1)] +) +def test_X_HZH_qudit_identity(phase_1: float, phase_2: float, addend:int, expected_state: int): + # For d=3, there are three identities: one for each swap. + # HH is equivalent to swapping |1> with |2> + # Applying a 1/3 turn to |1> and a 2/3 turn to |2> results in swapping + # |0> and |2> + # Applying a 2/3 turn to |1> and a 1/3 turn to |2> results in swapping + # |0> and |1> + qutrit = cirq.NamedQid("q0", dimension=3) + c = cirq.Circuit() + c.append(qudit_gates.QuditPlusGate(3, addend=addend)(qutrit)) + c.append(qudit_gates.QuditHadamardGate(dimension=3)(qutrit)) + c.append(qudit_gates.QuditRzGate(dimension=3, radians = phase_1, phased_state=1)(qutrit)) + c.append(qudit_gates.QuditRzGate(dimension=3, radians = phase_2, phased_state=2)(qutrit)) + c.append(qudit_gates.QuditHadamardGate(dimension=3)(qutrit)) + c.append(cirq.measure(qutrit, key="m")) + sim = cirq.Simulator() + results = sim.run(c, repetitions=1000) + assert np.all(results.measurements["m"] == expected_state) + @pytest.mark.parametrize( "q0, q1", [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] From 329bcc49abd858e7434d475bf73a802f1f2dc6b1 Mon Sep 17 00:00:00 2001 From: Lana <166859256+lanafs@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:18:34 +0000 Subject: [PATCH 3/6] Add HZH-identity tests. --- unitary/alpha/qudit_gates.py | 13 +++++++----- unitary/alpha/qudit_gates_test.py | 34 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/unitary/alpha/qudit_gates.py b/unitary/alpha/qudit_gates.py index 4f430a73..70e6eebd 100644 --- a/unitary/alpha/qudit_gates.py +++ b/unitary/alpha/qudit_gates.py @@ -70,7 +70,7 @@ class QuditRzGate(cirq.EigenGate): For a qudit of dimensionality d, shifts the phase of |phased_state> by radians. Args: - dimension: Dimension of the qudits: for instance, a dimension of 3 + dimension: Dimension of the qudits: for instance, a dimension of 3 would be a qutrit. radians: The phase shift applied to the |phased_state>, measured in radians. @@ -80,14 +80,17 @@ class QuditRzGate(cirq.EigenGate): _cached_eigencomponents: Dict[int, List[Tuple[float, np.ndarray]]] = {} - def __init__(self, dimension: int, radians: float = np.pi, - phased_state: Optional[int] = None): + def __init__( + self, dimension: int, radians: float = np.pi, phased_state: Optional[int] = None + ): super().__init__(exponent=radians / np.pi, global_shift=0) self.dimension = dimension if phased_state is not None: if phased_state >= dimension or phased_state < 0: - raise ValueError(f'state {phased_state} is not valid for a qudit of' - f' dimension {dimension}.') + raise ValueError( + f"state {phased_state} is not valid for a qudit of" + f" dimension {dimension}." + ) self.phased_state = phased_state else: self.phased_state = self.dimension - 1 diff --git a/unitary/alpha/qudit_gates_test.py b/unitary/alpha/qudit_gates_test.py index e59ec27f..71b43bce 100644 --- a/unitary/alpha/qudit_gates_test.py +++ b/unitary/alpha/qudit_gates_test.py @@ -290,6 +290,40 @@ def test_X_HZH_qudit_identity(phase_1: float, phase_2: float, addend:int, expect assert np.all(results.measurements["m"] == expected_state) +@pytest.mark.parametrize( + "phase_1, phase_2, addend, expected_state", + [ + (0, 0, 1, 2), + (np.pi * 2 / 3, np.pi * 4 / 3, 0, 2), + (np.pi * 4 / 3, np.pi * 2 / 3, 0, 1), + ], +) +def test_X_HZH_qudit_identity( + phase_1: float, phase_2: float, addend: int, expected_state: int +): + # For d=3, there are three identities: one for each swap. + # HH is equivalent to swapping |1> with |2> + # Applying a 1/3 turn to |1> and a 2/3 turn to |2> results in swapping + # |0> and |2> + # Applying a 2/3 turn to |1> and a 1/3 turn to |2> results in swapping + # |0> and |1> + qutrit = cirq.NamedQid("q0", dimension=3) + c = cirq.Circuit() + c.append(qudit_gates.QuditPlusGate(3, addend=addend)(qutrit)) + c.append(qudit_gates.QuditHadamardGate(dimension=3)(qutrit)) + c.append( + qudit_gates.QuditRzGate(dimension=3, radians=phase_1, phased_state=1)(qutrit) + ) + c.append( + qudit_gates.QuditRzGate(dimension=3, radians=phase_2, phased_state=2)(qutrit) + ) + c.append(qudit_gates.QuditHadamardGate(dimension=3)(qutrit)) + c.append(cirq.measure(qutrit, key="m")) + sim = cirq.Simulator() + results = sim.run(c, repetitions=1000) + assert np.all(results.measurements["m"] == expected_state) + + @pytest.mark.parametrize( "q0, q1", [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] ) From d927d93d6219705f7b815e0e113e5a3a0724a38b Mon Sep 17 00:00:00 2001 From: Lana <166859256+lanafs@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:49:32 +0000 Subject: [PATCH 4/6] Reformat qudit_gates_test --- unitary/alpha/qudit_gates_test.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/unitary/alpha/qudit_gates_test.py b/unitary/alpha/qudit_gates_test.py index 71b43bce..3a52deeb 100644 --- a/unitary/alpha/qudit_gates_test.py +++ b/unitary/alpha/qudit_gates_test.py @@ -265,24 +265,34 @@ def test_rz_unitary(dimension: float, phase_rads: float): assert np.allclose(cirq.unitary(rz), expected_unitary) assert np.allclose(np.eye(len(rz_unitary)), rz_unitary.dot(rz_unitary.T.conj())) + @pytest.mark.parametrize( - "phase_1, phase_2, addend, expected_state", [(0, 0, 1, 2), - (np.pi*2/3, np.pi*4/3, 0, 2), - (np.pi*4/3, np.pi*2/3, 0, 1)] + "phase_1, phase_2, addend, expected_state", + [ + (0, 0, 1, 2), + (np.pi * 2 / 3, np.pi * 4 / 3, 0, 2), + (np.pi * 4 / 3, np.pi * 2 / 3, 0, 1), + ], ) -def test_X_HZH_qudit_identity(phase_1: float, phase_2: float, addend:int, expected_state: int): +def test_X_HZH_qudit_identity( + phase_1: float, phase_2: float, addend: int, expected_state: int +): # For d=3, there are three identities: one for each swap. # HH is equivalent to swapping |1> with |2> - # Applying a 1/3 turn to |1> and a 2/3 turn to |2> results in swapping + # Applying a 1/3 turn to |1> and a 2/3 turn to |2> results in swapping # |0> and |2> - # Applying a 2/3 turn to |1> and a 1/3 turn to |2> results in swapping + # Applying a 2/3 turn to |1> and a 1/3 turn to |2> results in swapping # |0> and |1> qutrit = cirq.NamedQid("q0", dimension=3) c = cirq.Circuit() c.append(qudit_gates.QuditPlusGate(3, addend=addend)(qutrit)) c.append(qudit_gates.QuditHadamardGate(dimension=3)(qutrit)) - c.append(qudit_gates.QuditRzGate(dimension=3, radians = phase_1, phased_state=1)(qutrit)) - c.append(qudit_gates.QuditRzGate(dimension=3, radians = phase_2, phased_state=2)(qutrit)) + c.append( + qudit_gates.QuditRzGate(dimension=3, radians=phase_1, phased_state=1)(qutrit) + ) + c.append( + qudit_gates.QuditRzGate(dimension=3, radians=phase_2, phased_state=2)(qutrit) + ) c.append(qudit_gates.QuditHadamardGate(dimension=3)(qutrit)) c.append(cirq.measure(qutrit, key="m")) sim = cirq.Simulator() From eaac6bd752c83f922203dbe9dc35daa0ab4d99e6 Mon Sep 17 00:00:00 2001 From: Lana <166859256+lanafs@users.noreply.github.com> Date: Tue, 30 Jul 2024 22:31:10 +0000 Subject: [PATCH 5/6] Fix minor comments on PR. --- unitary/alpha/qudit_gates.py | 2 +- unitary/alpha/qudit_gates_test.py | 33 ------------------------------- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/unitary/alpha/qudit_gates.py b/unitary/alpha/qudit_gates.py index 70e6eebd..7cb581ee 100644 --- a/unitary/alpha/qudit_gates.py +++ b/unitary/alpha/qudit_gates.py @@ -70,7 +70,7 @@ class QuditRzGate(cirq.EigenGate): For a qudit of dimensionality d, shifts the phase of |phased_state> by radians. Args: - dimension: Dimension of the qudits: for instance, a dimension of 3 + dimension: Dimension of the qudits. For instance, a dimension of 3 would be a qutrit. radians: The phase shift applied to the |phased_state>, measured in radians. diff --git a/unitary/alpha/qudit_gates_test.py b/unitary/alpha/qudit_gates_test.py index 3a52deeb..f95e7c78 100644 --- a/unitary/alpha/qudit_gates_test.py +++ b/unitary/alpha/qudit_gates_test.py @@ -300,39 +300,6 @@ def test_X_HZH_qudit_identity( assert np.all(results.measurements["m"] == expected_state) -@pytest.mark.parametrize( - "phase_1, phase_2, addend, expected_state", - [ - (0, 0, 1, 2), - (np.pi * 2 / 3, np.pi * 4 / 3, 0, 2), - (np.pi * 4 / 3, np.pi * 2 / 3, 0, 1), - ], -) -def test_X_HZH_qudit_identity( - phase_1: float, phase_2: float, addend: int, expected_state: int -): - # For d=3, there are three identities: one for each swap. - # HH is equivalent to swapping |1> with |2> - # Applying a 1/3 turn to |1> and a 2/3 turn to |2> results in swapping - # |0> and |2> - # Applying a 2/3 turn to |1> and a 1/3 turn to |2> results in swapping - # |0> and |1> - qutrit = cirq.NamedQid("q0", dimension=3) - c = cirq.Circuit() - c.append(qudit_gates.QuditPlusGate(3, addend=addend)(qutrit)) - c.append(qudit_gates.QuditHadamardGate(dimension=3)(qutrit)) - c.append( - qudit_gates.QuditRzGate(dimension=3, radians=phase_1, phased_state=1)(qutrit) - ) - c.append( - qudit_gates.QuditRzGate(dimension=3, radians=phase_2, phased_state=2)(qutrit) - ) - c.append(qudit_gates.QuditHadamardGate(dimension=3)(qutrit)) - c.append(cirq.measure(qutrit, key="m")) - sim = cirq.Simulator() - results = sim.run(c, repetitions=1000) - assert np.all(results.measurements["m"] == expected_state) - @pytest.mark.parametrize( "q0, q1", [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] From 6a0fb1156d2676ac92f95550c5919ea05f11ec26 Mon Sep 17 00:00:00 2001 From: Lana <166859256+lanafs@users.noreply.github.com> Date: Tue, 30 Jul 2024 22:33:25 +0000 Subject: [PATCH 6/6] Linter --- unitary/alpha/qudit_gates_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/unitary/alpha/qudit_gates_test.py b/unitary/alpha/qudit_gates_test.py index f95e7c78..50ae6d09 100644 --- a/unitary/alpha/qudit_gates_test.py +++ b/unitary/alpha/qudit_gates_test.py @@ -300,7 +300,6 @@ def test_X_HZH_qudit_identity( assert np.all(results.measurements["m"] == expected_state) - @pytest.mark.parametrize( "q0, q1", [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] )