From bdaa1029f54c3de19fb2794741ea90a78db59f81 Mon Sep 17 00:00:00 2001 From: madcpf Date: Sun, 12 May 2024 21:11:46 -0700 Subject: [PATCH 1/5] update --- unitary/alpha/qudit_effects.py | 2 +- unitary/alpha/qudit_gates.py | 63 ++++++++++++++++--------------- unitary/alpha/qudit_gates_test.py | 33 ++-------------- 3 files changed, 37 insertions(+), 61 deletions(-) diff --git a/unitary/alpha/qudit_effects.py b/unitary/alpha/qudit_effects.py index 96753ea6..e03d7c71 100644 --- a/unitary/alpha/qudit_effects.py +++ b/unitary/alpha/qudit_effects.py @@ -18,7 +18,7 @@ import cirq -from unitary.alpha.qudit_gates import QuditPlusGate, QuditXGate, QuditHadamardGate +from unitary.alpha.qudit_gates import QuditPlusGate, QuditXGate from unitary.alpha.quantum_effect import QuantumEffect diff --git a/unitary/alpha/qudit_gates.py b/unitary/alpha/qudit_gates.py index d9c8b1c3..cc3291ff 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, source_state=0, destination_state=1) - is a X_01 gate that leaves the |2〉state alone. + For example, QuditXGate(dimension=3, state=1) + is a X_01 gate that leaves the |2〉 state alone. """ def __init__( @@ -34,21 +34,17 @@ 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.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 + 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 return arr def _circuit_diagram_info_(self, args): @@ -56,10 +52,10 @@ def _circuit_diagram_info_(self, args): class QuditPlusGate(cirq.Gate): - """Cycles all the states by `addend` using a permutation gate. + """Cycles all the states 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>. + This gate adds a number to each state. For instance, + `QuditPlusGate(dimension=3, addend=1)` """ def __init__(self, dimension: int, addend: int = 1): @@ -82,16 +78,19 @@ def _circuit_diagram_info_(self, args): class QuditControlledXGate(cirq.Gate): """A Qudit controlled-X gate. - This gate takes the dimension of the qudit as well as the control and destination states to produce a + This gate takes the dimension of the qudit (e.g. 3 for qutrits) + as well as the control and destination gates to produce a controlled-X 2-qudit gate. - 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`. + 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. """ def __init__(self, dimension: int, control_state: int = 1, state: int = 1): @@ -104,12 +103,14 @@ def _qid_shape_(self): def _unitary_(self): size = self.dimension * self.dimension - arr = np.eye(size, dtype=np.complex64) + arr = np.zeros((size, 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 @@ -138,14 +139,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 @@ -185,13 +186,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 117459d6..bb34dafc 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", [0, 1, 2]) +@pytest.mark.parametrize("state", [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", [0, 1, 2, 3, 4, 5, 6]) +@pytest.mark.parametrize("num_gates", [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", [0, 1, 2, 3, 4, 5, 6]) +@pytest.mark.parametrize("num_gates", [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 = 3 - control + non_active = 2 - control + 1 c = cirq.Circuit( qudit_gates.QuditXGate(3, 0, non_active)(qutrit0), qudit_gates.QuditControlledXGate(3, control, dest)(qutrit0, qutrit1), @@ -112,19 +112,6 @@ 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): @@ -175,18 +162,6 @@ 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", From 3b9f2ed8bacc1835337dfb19fbd6ed44a172f3c5 Mon Sep 17 00:00:00 2001 From: madcpf Date: Sun, 12 May 2024 21:20:02 -0700 Subject: [PATCH 2/5] update --- unitary/alpha/qudit_effects.py | 128 +-------------------------------- 1 file changed, 2 insertions(+), 126 deletions(-) diff --git a/unitary/alpha/qudit_effects.py b/unitary/alpha/qudit_effects.py index e03d7c71..105280a3 100644 --- a/unitary/alpha/qudit_effects.py +++ b/unitary/alpha/qudit_effects.py @@ -1,127 +1,3 @@ -# Copyright 2023 The Unitary Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from typing import Iterator, Optional, Sequence, Union -import abc -import enum - -import cirq - -from unitary.alpha.qudit_gates import QuditPlusGate, QuditXGate -from unitary.alpha.quantum_effect import QuantumEffect - - -class Cycle(QuantumEffect): - """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. - """ - - def __init__(self, num=1): - self.addend = num - - def effect(self, *objects): - for q in objects: - if q.qubit.dimension == 2: - if self.addend % 2: - yield cirq.X(q.qubit) - else: - yield QuditPlusGate(dimension=q.qubit.dimension, addend=self.addend)( - q.qubit + new_dict[new_world.get_object_by_name(key_obj.name)] = ( + new_world.get_object_by_name(value_obj.name) ) - - -class QuditCycle(Cycle): - """Equivalent to Cycle. - - Exists only for backwards compatibiltity. - Will be removed in 2024. - """ - - def __init__(self, dimension, num=1): - super().__init__(num) - - -class Flip(QuantumEffect): - """Flips two states of a qudit, leaving all other states unchanged. - - 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 as the X_02 gate. - - For a partial flip, use the `effect_fraction` argument. - - These effects will be cumulative. For instance, two quarter - flips (effect_fraction=0.25) will be equivalent to a half - flip (effect_fraction=0.5). - - Note that most fractions will not produce the corresponding - probabiltity distribution. For instance, a effect_fraction - of 0.25 will not produce a 25%/75% probability distribution - of outcomes. - - Args: - effect_fraction: Amount of flip effect to perform. - This results in an exponentiation of the flip (X) - effect. A fraction of 1.0 corresponds to a full - flip (this is the default). A fraction of 0.5 - corresponds to a half flip (square root of NOT) - state0: The source state to be flipped. For instance, - if state0=1, this will flip the |1> state to the - state specified in state1. - state1: The destination state to be flipped to. - """ - - def __init__(self, effect_fraction: float = 1.0, state0: int = 0, state1: int = 1): - self.state0 = state0 - self.state1 = state1 - self.effect_fraction = effect_fraction - - def effect(self, *objects): - for q in objects: - if q.qubit.dimension == 2: - yield cirq.X(q.qubit) ** self.effect_fraction - else: - yield QuditXGate( - dimension=q.qubit.dimension, - source_state=self.state0, - destination_state=self.state1, - )(q.qubit) - - def __str__(self): - if self.effect_fraction == 1: - return "Flip" - if self.state0 == 0 and self.state1 == 1: - return f"Flip(effect_fraction={self.effect_fraction})" - return ( - f"Flip(effect_fraction={self.effect_fraction}, " - "state0={self.state0}, state1={self.state1})" - ) - - def __eq__(self, other): - if isinstance(other, Flip): - return self.effect_fraction == other.effect_fraction - return NotImplemented - - -class QuditFlip(Flip): - """Equivalent to Flip. - - Exists only for backwards compatibiltity. - Will be removed in 2024. - """ - - def __init__(self, dimension: int, state0: int, state1: int): - super().__init__(state0=state0, state1=state1) From ec37dd3793520573541797fea31572c6672a4e7a Mon Sep 17 00:00:00 2001 From: madcpf Date: Sun, 12 May 2024 21:20:54 -0700 Subject: [PATCH 3/5] update --- unitary/alpha/qudit_effects.py | 130 ++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 2 deletions(-) diff --git a/unitary/alpha/qudit_effects.py b/unitary/alpha/qudit_effects.py index 105280a3..632a724f 100644 --- a/unitary/alpha/qudit_effects.py +++ b/unitary/alpha/qudit_effects.py @@ -1,3 +1,129 @@ - new_dict[new_world.get_object_by_name(key_obj.name)] = ( - new_world.get_object_by_name(value_obj.name) +# Copyright 2023 The Unitary Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from typing import Iterator, Optional, Sequence, Union +import abc +import enum + +import cirq + +from unitary.alpha.qudit_gates import QuditPlusGate, QuditXGate +from unitary.alpha.quantum_effect import QuantumEffect + + +class Cycle(QuantumEffect): + """Cycles a qubit from |0> to |1>, |1> to |2>, etc. + + Essentially adds `addend` to the state, where `addend` + is the parameter supplied at creation. + """ + + def __init__(self, num=1): + self.addend = num + + def effect(self, *objects): + for q in objects: + if q.qubit.dimension == 2: + if self.addend % 2: + yield cirq.X(q.qubit) + else: + yield QuditPlusGate(dimension=q.qubit.dimension, addend=self.addend)( + q.qubit ) + + +class QuditCycle(Cycle): + """Equivalent to Cycle. + + Exists only for backwards compatibiltity. + Will be removed in 2024. + """ + + def __init__(self, dimension, num=1): + super().__init__(num) + + +class Flip(QuantumEffect): + """Flips two states of a qubit, leaving all other states unchanged. + + 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. + + 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 + flip (effect_fraction=0.5). + + Note that most fractions will not produce the corresponding + probabiltity distribution. For instance, a effect_fraction + of 0.25 will not produce a 25%/75% probability distribution + of outcomes. + + Args: + effect_fraction: Amount of flip effect to perform. + This results in an exponentiation of the flip (X) + effect. A fraction of 1.0 corresponds to a full + flip (this is the default). A fraction of 0.5 + corresponds to a half flip (square root of NOT) + state0: The source state to be flipped. For instance, + if state0=1, this will flip the |1> state to the + state specified in state1. + state1: The destination state to be flipped to. + """ + + def __init__(self, effect_fraction: float = 1.0, state0: int = 0, state1: int = 1): + self.state0 = state0 + self.state1 = state1 + self.effect_fraction = effect_fraction + + def effect(self, *objects): + for q in objects: + if q.qubit.dimension == 2: + yield cirq.X(q.qubit) ** self.effect_fraction + else: + yield QuditXGate( + dimension=q.qubit.dimension, + source_state=self.state0, + destination_state=self.state1, + )(q.qubit) + + def __str__(self): + if self.effect_fraction == 1: + return "Flip" + if self.state0 == 0 and self.state1 == 1: + return f"Flip(effect_fraction={self.effect_fraction})" + return ( + f"Flip(effect_fraction={self.effect_fraction}, " + "state0={self.state0}, state1={self.state1})" + ) + + def __eq__(self, other): + if isinstance(other, Flip): + return self.effect_fraction == other.effect_fraction + return NotImplemented + + +class QuditFlip(Flip): + """Equivalent to Flip. + + Exists only for backwards compatibiltity. + Will be removed in 2024. + """ + + def __init__(self, dimension: int, state0: int, state1: int): + super().__init__(state0=state0, state1=state1) From 3de50d5c697c947ccaee2adf2d05c7e1b83d12a1 Mon Sep 17 00:00:00 2001 From: madcpf Date: Sun, 12 May 2024 21:23:19 -0700 Subject: [PATCH 4/5] update --- unitary/alpha/quantum_world.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/unitary/alpha/quantum_world.py b/unitary/alpha/quantum_world.py index 1f272785..416ac0a7 100644 --- a/unitary/alpha/quantum_world.py +++ b/unitary/alpha/quantum_world.py @@ -116,9 +116,9 @@ def copy(self) -> "QuantumWorld": for remap in self.qubit_remapping_dict: new_dict = {} for key_obj, value_obj in remap.items(): - new_dict[ - new_world.get_object_by_name(key_obj.name) - ] = new_world.get_object_by_name(value_obj.name) + new_dict[new_world.get_object_by_name(key_obj.name)] = ( + new_world.get_object_by_name(value_obj.name) + ) new_world.qubit_remapping_dict.append(new_dict) new_world.qubit_remapping_dict_length = self.qubit_remapping_dict_length.copy() return new_world From 8129a51aed0071e2798f1a55c33783eb0071cd43 Mon Sep 17 00:00:00 2001 From: madcpf Date: Sun, 12 May 2024 21:27:57 -0700 Subject: [PATCH 5/5] Update quantum_world.py --- unitary/alpha/quantum_world.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/unitary/alpha/quantum_world.py b/unitary/alpha/quantum_world.py index 1f272785..416ac0a7 100644 --- a/unitary/alpha/quantum_world.py +++ b/unitary/alpha/quantum_world.py @@ -116,9 +116,9 @@ def copy(self) -> "QuantumWorld": for remap in self.qubit_remapping_dict: new_dict = {} for key_obj, value_obj in remap.items(): - new_dict[ - new_world.get_object_by_name(key_obj.name) - ] = new_world.get_object_by_name(value_obj.name) + new_dict[new_world.get_object_by_name(key_obj.name)] = ( + new_world.get_object_by_name(value_obj.name) + ) new_world.qubit_remapping_dict.append(new_dict) new_world.qubit_remapping_dict_length = self.qubit_remapping_dict_length.copy() return new_world