diff --git a/unitary/alpha/quantum_effect_test.py b/unitary/alpha/quantum_effect_test.py index 2b2623ca..04044e57 100644 --- a/unitary/alpha/quantum_effect_test.py +++ b/unitary/alpha/quantum_effect_test.py @@ -91,4 +91,4 @@ def test_no_qutrits(compile_to_qubits): piece = alpha.QuantumObject("q0", 2) board.add_object(piece) with pytest.raises(ValueError, match="Cannot apply effect to qids"): - alpha.Superposition()(piece) + alpha.Phase()(piece) diff --git a/unitary/alpha/quantum_world.py b/unitary/alpha/quantum_world.py index 416ac0a7..1f272785 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 diff --git a/unitary/alpha/qubit_effects.py b/unitary/alpha/qubit_effects.py index 4776e42b..f786046b 100644 --- a/unitary/alpha/qubit_effects.py +++ b/unitary/alpha/qubit_effects.py @@ -19,6 +19,7 @@ import cirq from unitary.alpha.quantum_effect import QuantumEffect +from unitary.alpha.qudit_gates import QuditHadamardGate class Phase(QuantumEffect): @@ -67,14 +68,16 @@ def __eq__(self, other): class Superposition(QuantumEffect): - """Takes a qubit in a basis state into a superposition.""" - - def num_dimension(self) -> Optional[int]: - return 2 + """Transforms a qubit (or qudit) in a basis state into a (equal, in terms of + absolute magnitude) superposition of all basis states. + """ def effect(self, *objects): for q in objects: - yield cirq.H(q.qubit) + if q.qubit.dimension == 2: + yield cirq.H(q.qubit) + else: + yield QuditHadamardGate(dimension=q.qubit.dimension)(q.qubit) def __eq__(self, other): return isinstance(other, Superposition) or NotImplemented diff --git a/unitary/alpha/qubit_effects_test.py b/unitary/alpha/qubit_effects_test.py index 4d35e6ca..90d627e1 100644 --- a/unitary/alpha/qubit_effects_test.py +++ b/unitary/alpha/qubit_effects_test.py @@ -13,6 +13,7 @@ # limitations under the License. # +import enum import pytest import cirq @@ -20,6 +21,12 @@ import unitary.alpha as alpha +class StopLight(enum.Enum): + RED = 0 + YELLOW = 1 + GREEN = 2 + + @pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, alpha.SparseSimulator]) def test_flip(simulator, compile_to_qubits): @@ -78,7 +85,7 @@ def test_partial_phase(simulator, compile_to_qubits): @pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, alpha.SparseSimulator]) -def test_superposition(simulator, compile_to_qubits): +def test_qubit_superposition(simulator, compile_to_qubits): board = alpha.QuantumWorld(sampler=simulator(), compile_to_qubits=compile_to_qubits) piece = alpha.QuantumObject("t", 0) board.add_object(piece) @@ -87,6 +94,17 @@ def test_superposition(simulator, compile_to_qubits): assert str(alpha.Superposition()) == "Superposition" +def test_qudit_superposition(): + board = alpha.QuantumWorld(sampler=cirq.Simulator(), compile_to_qubits=False) + piece = alpha.QuantumObject("t", StopLight.GREEN) + board.add_object(piece) + alpha.Superposition()(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) + + @pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, alpha.SparseSimulator]) def test_move(simulator, compile_to_qubits): diff --git a/unitary/alpha/qudit_gates.py b/unitary/alpha/qudit_gates.py index 628d9a45..a2446b4b 100644 --- a/unitary/alpha/qudit_gates.py +++ b/unitary/alpha/qudit_gates.py @@ -202,3 +202,39 @@ 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 operation 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) + ) diff --git a/unitary/alpha/qudit_gates_test.py b/unitary/alpha/qudit_gates_test.py index a228da33..bb34dafc 100644 --- a/unitary/alpha/qudit_gates_test.py +++ b/unitary/alpha/qudit_gates_test.py @@ -180,6 +180,9 @@ 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): @@ -294,3 +297,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)