From e20d5fbcac9d8e81e40def8c8be71c449a8c29a0 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 11:37:18 -0400 Subject: [PATCH 01/36] Apply SWAP --- pennylane/_qubit_device.py | 1 + pennylane/devices/default_qubit.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pennylane/_qubit_device.py b/pennylane/_qubit_device.py index eb1785aac0e..f102da26df2 100644 --- a/pennylane/_qubit_device.py +++ b/pennylane/_qubit_device.py @@ -84,6 +84,7 @@ class QubitDevice(Device): _tensordot = staticmethod(np.tensordot) _conj = staticmethod(np.conj) _imag = staticmethod(np.imag) + _transpose = staticmethod(np.transpose) @staticmethod def _scatter(indices, array, new_dimensions): diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 8473c2e49bc..eb29d50fda1 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -24,7 +24,7 @@ import numpy as np -from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState +from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState, SWAP from pennylane.operation import DiagonalOperation ABC_ARRAY = np.array(list(ABC)) @@ -132,6 +132,10 @@ def _apply_operation(self, operation): self._apply_basis_state(operation.parameters[0], wires) return + if isinstance(operation, SWAP): + self._apply_SWAP(wires) + return + matrix = self._get_unitary_matrix(operation) if isinstance(operation, DiagonalOperation): @@ -142,6 +146,16 @@ def _apply_operation(self, operation): else: self._apply_unitary(matrix, wires) + def _apply_SWAP(self, wires): + """Applies swap gate by performing a partial transposition along the axes specified by + ``wires``. + + Args: + wires (Wires): target wires + """ + axes = [self.wires.index(w) for w in wires] + self._state = self._transpose(self._state, axes) + def _get_unitary_matrix(self, unitary): # pylint: disable=no-self-use """Return the matrix representing a unitary operation. From eae685da960b7c87fb0e5c30797dd539fca096a1 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 11:38:45 -0400 Subject: [PATCH 02/36] Add swap properly --- pennylane/devices/default_qubit.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index eb29d50fda1..d75f8337788 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -153,7 +153,10 @@ def _apply_SWAP(self, wires): Args: wires (Wires): target wires """ - axes = [self.wires.index(w) for w in wires] + swap_axes = [self.wires.index(wire) for wire in wires] + axes = list(range(self.num_wires)) + axes[swap_axes[0]] = swap_axes[1] + axes[swap_axes[1]] = swap_axes[0] self._state = self._transpose(self._state, axes) def _get_unitary_matrix(self, unitary): # pylint: disable=no-self-use From e2c6a277d3b4c50083a6b8bbcf09169e419ca930 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 11:39:22 -0400 Subject: [PATCH 03/36] Remove extra transpose --- pennylane/_qubit_device.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane/_qubit_device.py b/pennylane/_qubit_device.py index f102da26df2..eb1785aac0e 100644 --- a/pennylane/_qubit_device.py +++ b/pennylane/_qubit_device.py @@ -84,7 +84,6 @@ class QubitDevice(Device): _tensordot = staticmethod(np.tensordot) _conj = staticmethod(np.conj) _imag = staticmethod(np.imag) - _transpose = staticmethod(np.transpose) @staticmethod def _scatter(indices, array, new_dimensions): From ff96b9dcc2e504e3865e7fffd4d0624c666184b8 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 11:54:16 -0400 Subject: [PATCH 04/36] Update UI --- pennylane/devices/default_qubit.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index d75f8337788..30863569aad 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -24,7 +24,7 @@ import numpy as np -from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState, SWAP +from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState, SWAP, Hadamard from pennylane.operation import DiagonalOperation ABC_ARRAY = np.array(list(ABC)) @@ -133,7 +133,7 @@ def _apply_operation(self, operation): return if isinstance(operation, SWAP): - self._apply_SWAP(wires) + self._state = self._apply_swap(self._state, wires) return matrix = self._get_unitary_matrix(operation) @@ -146,18 +146,22 @@ def _apply_operation(self, operation): else: self._apply_unitary(matrix, wires) - def _apply_SWAP(self, wires): - """Applies swap gate by performing a partial transposition along the axes specified by + def _apply_swap(self, state, wires): + """Applies SWAP gate by performing a partial transposition along the axes specified by ``wires``. Args: + state (array[complex]): input state wires (Wires): target wires + + Returns: + array[complex]: output state """ swap_axes = [self.wires.index(wire) for wire in wires] axes = list(range(self.num_wires)) axes[swap_axes[0]] = swap_axes[1] axes[swap_axes[1]] = swap_axes[0] - self._state = self._transpose(self._state, axes) + return self._transpose(state, axes) def _get_unitary_matrix(self, unitary): # pylint: disable=no-self-use """Return the matrix representing a unitary operation. From 351cb1e46ae7a54ec02633bfa6572314ff331264 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 11:59:05 -0400 Subject: [PATCH 05/36] Add PauliX --- pennylane/_qubit_device.py | 1 + pennylane/devices/default_qubit.py | 19 ++++++++++++++++++- pennylane/devices/default_qubit_autograd.py | 1 + pennylane/devices/default_qubit_tf.py | 1 + 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pennylane/_qubit_device.py b/pennylane/_qubit_device.py index eb1785aac0e..74c0893762f 100644 --- a/pennylane/_qubit_device.py +++ b/pennylane/_qubit_device.py @@ -84,6 +84,7 @@ class QubitDevice(Device): _tensordot = staticmethod(np.tensordot) _conj = staticmethod(np.conj) _imag = staticmethod(np.imag) + _roll = staticmethod(np.roll) @staticmethod def _scatter(indices, array, new_dimensions): diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 30863569aad..d6e4b48e98f 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -24,7 +24,7 @@ import numpy as np -from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState, SWAP, Hadamard +from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState, SWAP, PauliX from pennylane.operation import DiagonalOperation ABC_ARRAY = np.array(list(ABC)) @@ -132,6 +132,10 @@ def _apply_operation(self, operation): self._apply_basis_state(operation.parameters[0], wires) return + if isinstance(operation, PauliX): + self._state = self._apply_x(self._state, wires[0]) + return + if isinstance(operation, SWAP): self._state = self._apply_swap(self._state, wires) return @@ -146,6 +150,19 @@ def _apply_operation(self, operation): else: self._apply_unitary(matrix, wires) + def _apply_x(self, state, wire): + """Applies PauliX gate by rolling 1 unit along the axis specified by ``wire``. + + Args: + state (array[complex]): input state + wire (Wires): target wire + + Returns: + array[complex]: output state + """ + axis = self.wires.index(wire) + return self._roll(state, 1, axis) + def _apply_swap(self, state, wires): """Applies SWAP gate by performing a partial transposition along the axes specified by ``wires``. diff --git a/pennylane/devices/default_qubit_autograd.py b/pennylane/devices/default_qubit_autograd.py index 969ebf9ef59..d64cea8ac60 100644 --- a/pennylane/devices/default_qubit_autograd.py +++ b/pennylane/devices/default_qubit_autograd.py @@ -106,6 +106,7 @@ class DefaultQubitAutograd(DefaultQubit): _tensordot = staticmethod(np.tensordot) _conj = staticmethod(np.conj) _imag = staticmethod(np.imag) + _roll = staticmethod(np.roll) @staticmethod def _scatter(indices, array, new_dimensions): diff --git a/pennylane/devices/default_qubit_tf.py b/pennylane/devices/default_qubit_tf.py index bb8c4fc594f..ec327b4e718 100644 --- a/pennylane/devices/default_qubit_tf.py +++ b/pennylane/devices/default_qubit_tf.py @@ -156,6 +156,7 @@ class DefaultQubitTF(DefaultQubit): _tensordot = staticmethod(tf.tensordot) _conj = staticmethod(tf.math.conj) _imag = staticmethod(tf.math.conj) + _roll = staticmethod(tf.roll) @staticmethod def _scatter(indices, array, new_dimensions): From 4dcccf3e0c983a7596a16b05f778a374e18cebdb Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 12:57:57 -0400 Subject: [PATCH 06/36] Work on hadamard --- pennylane/_qubit_device.py | 1 + pennylane/devices/default_qubit.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pennylane/_qubit_device.py b/pennylane/_qubit_device.py index 74c0893762f..9218feaf02b 100644 --- a/pennylane/_qubit_device.py +++ b/pennylane/_qubit_device.py @@ -85,6 +85,7 @@ class QubitDevice(Device): _conj = staticmethod(np.conj) _imag = staticmethod(np.imag) _roll = staticmethod(np.roll) + _stack = staticmethod(np.stack) @staticmethod def _scatter(indices, array, new_dimensions): diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index d6e4b48e98f..e4ccd49bd85 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -24,13 +24,14 @@ import numpy as np -from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState, SWAP, PauliX +from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState, SWAP, PauliX, Hadamard from pennylane.operation import DiagonalOperation ABC_ARRAY = np.array(list(ABC)) # tolerance for numerical errors tolerance = 1e-10 +SQRT2INV = 1 / np.sqrt(2) class DefaultQubit(QubitDevice): @@ -136,6 +137,10 @@ def _apply_operation(self, operation): self._state = self._apply_x(self._state, wires[0]) return + if isinstance(operation, Hadamard): + self._state = self._apply_hadamard(self._state, wires[0]) + return + if isinstance(operation, SWAP): self._state = self._apply_swap(self._state, wires) return @@ -163,6 +168,14 @@ def _apply_x(self, state, wire): axis = self.wires.index(wire) return self._roll(state, 1, axis) + def _apply_hadamard(self, state, wire): + state_x = self._apply_x(state, wire) + + + + state = self._stack(state) + return SQRT2INV * (state + state_x) + def _apply_swap(self, state, wires): """Applies SWAP gate by performing a partial transposition along the axes specified by ``wires``. From 79101409bb0c5fc717272f78d9c11bf328f5594e Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 13:03:34 -0400 Subject: [PATCH 07/36] Use stack --- pennylane/devices/default_qubit.py | 13 +++++++++++-- pennylane/devices/default_qubit_autograd.py | 1 + pennylane/devices/default_qubit_tf.py | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index e4ccd49bd85..69e39d43252 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -34,6 +34,13 @@ SQRT2INV = 1 / np.sqrt(2) +def get_slice(index, axis, num_axes): + """TODO""" + idx = [slice(None)] * num_axes + idx[axis] = index + return tuple(idx) + + class DefaultQubit(QubitDevice): """Default qubit device for PennyLane. @@ -169,11 +176,13 @@ def _apply_x(self, state, wire): return self._roll(state, 1, axis) def _apply_hadamard(self, state, wire): + axis = self.wires.index(wire) state_x = self._apply_x(state, wire) + sl_0 = get_slice(0, axis, self.num_wires) + sl_1 = get_slice(1, axis, self.num_wires) - - state = self._stack(state) + state = self._stack([state[sl_0], -state[sl_1]], axis=axis) return SQRT2INV * (state + state_x) def _apply_swap(self, state, wires): diff --git a/pennylane/devices/default_qubit_autograd.py b/pennylane/devices/default_qubit_autograd.py index d64cea8ac60..f136c065d36 100644 --- a/pennylane/devices/default_qubit_autograd.py +++ b/pennylane/devices/default_qubit_autograd.py @@ -107,6 +107,7 @@ class DefaultQubitAutograd(DefaultQubit): _conj = staticmethod(np.conj) _imag = staticmethod(np.imag) _roll = staticmethod(np.roll) + _stack = staticmethod(np.stack) @staticmethod def _scatter(indices, array, new_dimensions): diff --git a/pennylane/devices/default_qubit_tf.py b/pennylane/devices/default_qubit_tf.py index ec327b4e718..5d5c8087e1a 100644 --- a/pennylane/devices/default_qubit_tf.py +++ b/pennylane/devices/default_qubit_tf.py @@ -157,6 +157,7 @@ class DefaultQubitTF(DefaultQubit): _conj = staticmethod(tf.math.conj) _imag = staticmethod(tf.math.conj) _roll = staticmethod(tf.roll) + _stack = staticmethod(tf.stack) @staticmethod def _scatter(indices, array, new_dimensions): From badb3564bd61ced07881e6f417f388eba59900eb Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 13:08:11 -0400 Subject: [PATCH 08/36] Add PauliZ --- pennylane/devices/default_qubit.py | 34 +++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 69e39d43252..517934c167d 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -24,7 +24,8 @@ import numpy as np -from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState, SWAP, PauliX, Hadamard +from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState, SWAP, PauliX, \ + Hadamard, PauliZ from pennylane.operation import DiagonalOperation ABC_ARRAY = np.array(list(ABC)) @@ -144,6 +145,10 @@ def _apply_operation(self, operation): self._state = self._apply_x(self._state, wires[0]) return + if isinstance(operation, PauliZ): + self._state = self._apply_z(self._state, wires[0]) + return + if isinstance(operation, Hadamard): self._state = self._apply_hadamard(self._state, wires[0]) return @@ -162,6 +167,24 @@ def _apply_operation(self, operation): else: self._apply_unitary(matrix, wires) + def _apply_z(self, state, wire): + """Applies PauliZ gate by adding a negative sign to the 1 index along the axis specified + by ``wire`` + + Args: + state (array[complex]): input state + wire (Wires): target wire + + Returns: + array[complex]: output state + """ + axis = self.wires.index(wire) + + sl_0 = get_slice(0, axis, self.num_wires) + sl_1 = get_slice(1, axis, self.num_wires) + + return self._stack([state[sl_0], -state[sl_1]], axis=axis) + def _apply_x(self, state, wire): """Applies PauliX gate by rolling 1 unit along the axis specified by ``wire``. @@ -176,14 +199,9 @@ def _apply_x(self, state, wire): return self._roll(state, 1, axis) def _apply_hadamard(self, state, wire): - axis = self.wires.index(wire) state_x = self._apply_x(state, wire) - - sl_0 = get_slice(0, axis, self.num_wires) - sl_1 = get_slice(1, axis, self.num_wires) - - state = self._stack([state[sl_0], -state[sl_1]], axis=axis) - return SQRT2INV * (state + state_x) + state_z = self._apply_z(state, wire) + return SQRT2INV * (state_x + state_z) def _apply_swap(self, state, wires): """Applies SWAP gate by performing a partial transposition along the axes specified by From 56c295c0b0b3489635160d46cb6574372c623344 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 13:25:19 -0400 Subject: [PATCH 09/36] Add PauliY --- pennylane/devices/default_qubit.py | 52 +++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 517934c167d..7a21aa03a0c 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -25,7 +25,7 @@ import numpy as np from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState, SWAP, PauliX, \ - Hadamard, PauliZ + Hadamard, PauliZ, S, T, PauliY from pennylane.operation import DiagonalOperation ABC_ARRAY = np.array(list(ABC)) @@ -33,6 +33,7 @@ # tolerance for numerical errors tolerance = 1e-10 SQRT2INV = 1 / np.sqrt(2) +TPHASE = np.exp(1j * np.pi / 4) def get_slice(index, axis, num_axes): @@ -145,10 +146,22 @@ def _apply_operation(self, operation): self._state = self._apply_x(self._state, wires[0]) return + if isinstance(operation, PauliY): + self._state = self._apply_y(self._state, wires[0]) + return + if isinstance(operation, PauliZ): self._state = self._apply_z(self._state, wires[0]) return + if isinstance(operation, S): + self._state = self._apply_phase(self._state, wires[0], 1j, operation.inverse) + return + + if isinstance(operation, T): + self._state = self._apply_phase(self._state, wires[0], TPHASE, operation.inverse) + return + if isinstance(operation, Hadamard): self._state = self._apply_hadamard(self._state, wires[0]) return @@ -167,13 +180,14 @@ def _apply_operation(self, operation): else: self._apply_unitary(matrix, wires) - def _apply_z(self, state, wire): - """Applies PauliZ gate by adding a negative sign to the 1 index along the axis specified - by ``wire`` + def _apply_phase(self, state, wire, phase, inverse=False): + """Applies a phase onto the 1 index along the axis specified by ``wire``. Args: state (array[complex]): input state wire (Wires): target wire + phase (float): phase to apply + inverse (bool): whether to apply the inverse phase Returns: array[complex]: output state @@ -183,7 +197,35 @@ def _apply_z(self, state, wire): sl_0 = get_slice(0, axis, self.num_wires) sl_1 = get_slice(1, axis, self.num_wires) - return self._stack([state[sl_0], -state[sl_1]], axis=axis) + phase = self._conj(phase) if inverse else phase + + return self._stack([state[sl_0], phase * state[sl_1]], axis=axis) + + def _apply_y(self, state, wire): + """Applies PauliY gate by adding a negative sign to the 1 index along the axis specified + by ``wire``, rolling one unit along the same axis, and multiplying the result by 1j. + + Args: + state (array[complex]): input state + wire (Wires): target wire + + Returns: + array[complex]: output state + """ + return 1j * self._apply_x(self._apply_z(state, wire), wire) + + def _apply_z(self, state, wire): + """Applies PauliZ gate by adding a negative sign to the 1 index along the axis specified + by ``wire``. + + Args: + state (array[complex]): input state + wire (Wires): target wire + + Returns: + array[complex]: output state + """ + return self._apply_phase(state, wire, -1) def _apply_x(self, state, wire): """Applies PauliX gate by rolling 1 unit along the axis specified by ``wire``. From 9c70204a7b44b9854790ab0ff4b0c354edd1f1e5 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 13:30:21 -0400 Subject: [PATCH 10/36] Add T --- pennylane/devices/default_qubit.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 7a21aa03a0c..f6470f4c4df 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -36,8 +36,17 @@ TPHASE = np.exp(1j * np.pi / 4) -def get_slice(index, axis, num_axes): - """TODO""" +def _get_slice(index, axis, num_axes): + """Allows slicing into the index of an array or tensor along an arbitrary axis. + + Args: + index (int): the index to access + axis (int): the axis to slice into + num_axes (int): total number of axes + + Returns: + tuple[slice or int]: a tuple that can be used to slice into an array or tensor + """ idx = [slice(None)] * num_axes idx[axis] = index return tuple(idx) @@ -194,8 +203,8 @@ def _apply_phase(self, state, wire, phase, inverse=False): """ axis = self.wires.index(wire) - sl_0 = get_slice(0, axis, self.num_wires) - sl_1 = get_slice(1, axis, self.num_wires) + sl_0 = _get_slice(0, axis, self.num_wires) + sl_1 = _get_slice(1, axis, self.num_wires) phase = self._conj(phase) if inverse else phase From 945f48aec0d36c798173ab5d3fdf5b2892abfbe3 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 13:59:42 -0400 Subject: [PATCH 11/36] Move toward more static method --- pennylane/devices/default_qubit.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index f6470f4c4df..95853e1d313 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -142,6 +142,7 @@ def _apply_operation(self, operation): operation (~.Operation): operation to apply on the device """ wires = operation.wires + axes = self.wires.indices(wires) if isinstance(operation, QubitStateVector): self._apply_state_vector(operation.parameters[0], wires) @@ -176,7 +177,7 @@ def _apply_operation(self, operation): return if isinstance(operation, SWAP): - self._state = self._apply_swap(self._state, wires) + self._state = self._apply_swap(self._state, axes) return matrix = self._get_unitary_matrix(operation) @@ -254,21 +255,19 @@ def _apply_hadamard(self, state, wire): state_z = self._apply_z(state, wire) return SQRT2INV * (state_x + state_z) - def _apply_swap(self, state, wires): - """Applies SWAP gate by performing a partial transposition along the axes specified by - ``wires``. + def _apply_swap(self, state, target_axes): + """Applies SWAP gate by performing a partial transposition along the specified axes. Args: state (array[complex]): input state - wires (Wires): target wires + target_axes (List[int]): axes to apply transformation Returns: array[complex]: output state """ - swap_axes = [self.wires.index(wire) for wire in wires] - axes = list(range(self.num_wires)) - axes[swap_axes[0]] = swap_axes[1] - axes[swap_axes[1]] = swap_axes[0] + axes = list(range(len(state.shape))) + axes[target_axes[0]] = target_axes[1] + axes[target_axes[1]] = target_axes[0] return self._transpose(state, axes) def _get_unitary_matrix(self, unitary): # pylint: disable=no-self-use From be23c6568be50eae906a912526d0654e8f4d29f0 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 14:46:55 -0400 Subject: [PATCH 12/36] Use a common UI --- pennylane/devices/default_qubit.py | 115 ++++++++++++++--------------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 95853e1d313..3de6f8b32ba 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -114,6 +114,16 @@ def __init__(self, wires, *, shots=1000, analytic=True): self._state = self._create_basis_state(0) self._pre_rotated_state = self._state + self._ops_map = { + "PauliX": self._apply_x, + "PauliY": self._apply_y, + "PauliZ": self._apply_z, + "Hadamard": self._apply_hadamard, + "SWAP": self._apply_swap, + "S": self._apply_s, + "T": self._apply_t, + } + def apply(self, operations, rotations=None, **kwargs): rotations = rotations or [] @@ -152,32 +162,9 @@ def _apply_operation(self, operation): self._apply_basis_state(operation.parameters[0], wires) return - if isinstance(operation, PauliX): - self._state = self._apply_x(self._state, wires[0]) - return - - if isinstance(operation, PauliY): - self._state = self._apply_y(self._state, wires[0]) - return - - if isinstance(operation, PauliZ): - self._state = self._apply_z(self._state, wires[0]) - return - - if isinstance(operation, S): - self._state = self._apply_phase(self._state, wires[0], 1j, operation.inverse) - return - - if isinstance(operation, T): - self._state = self._apply_phase(self._state, wires[0], TPHASE, operation.inverse) - return - - if isinstance(operation, Hadamard): - self._state = self._apply_hadamard(self._state, wires[0]) - return - - if isinstance(operation, SWAP): - self._state = self._apply_swap(self._state, axes) + apply_func = self._ops_map.get(operation.name, None) + if apply_func: + self._state = apply_func(self._state, axes, inverse=operation.inverse) return matrix = self._get_unitary_matrix(operation) @@ -190,85 +177,97 @@ def _apply_operation(self, operation): else: self._apply_unitary(matrix, wires) - def _apply_phase(self, state, wire, phase, inverse=False): - """Applies a phase onto the 1 index along the axis specified by ``wire``. + def _apply_s(self, state, axes, inverse=False): + return self._apply_phase(state, axes, 1j, inverse) + + def _apply_t(self, state, axes, inverse=False): + return self._apply_phase(state, axes, TPHASE, inverse) + + def _apply_phase(self, state, axes, parameters, inverse=False): + """Applies a phase onto the 1 index along the axis specified in ``axes``. Args: state (array[complex]): input state - wire (Wires): target wire - phase (float): phase to apply + axes (List[int]): target axes to apply transformation + parameters (float): phase to apply inverse (bool): whether to apply the inverse phase Returns: array[complex]: output state """ - axis = self.wires.index(wire) + sl_0 = _get_slice(0, axes[0], self.num_wires) + sl_1 = _get_slice(1, axes[0], self.num_wires) - sl_0 = _get_slice(0, axis, self.num_wires) - sl_1 = _get_slice(1, axis, self.num_wires) + phase = self._conj(parameters) if inverse else parameters - phase = self._conj(phase) if inverse else phase + return self._stack([state[sl_0], phase * state[sl_1]], axis=axes[0]) - return self._stack([state[sl_0], phase * state[sl_1]], axis=axis) - - def _apply_y(self, state, wire): + def _apply_y(self, state, axes, **kwargs): """Applies PauliY gate by adding a negative sign to the 1 index along the axis specified - by ``wire``, rolling one unit along the same axis, and multiplying the result by 1j. + in ``axes``, rolling one unit along the same axis, and multiplying the result by 1j. Args: state (array[complex]): input state - wire (Wires): target wire + axes (List[int]): target axes to apply transformation Returns: array[complex]: output state """ - return 1j * self._apply_x(self._apply_z(state, wire), wire) + return 1j * self._apply_x(self._apply_z(state, axes), axes) - def _apply_z(self, state, wire): + def _apply_z(self, state, axes, **kwargs): """Applies PauliZ gate by adding a negative sign to the 1 index along the axis specified - by ``wire``. + in ``axes``. Args: state (array[complex]): input state - wire (Wires): target wire + axes (List[int]): target axes to apply transformation Returns: array[complex]: output state """ - return self._apply_phase(state, wire, -1) + return self._apply_phase(state, axes, -1) - def _apply_x(self, state, wire): - """Applies PauliX gate by rolling 1 unit along the axis specified by ``wire``. + def _apply_x(self, state, axes, **kwargs): + """Applies PauliX gate by rolling 1 unit along the axis specified in ``axes``. Args: state (array[complex]): input state - wire (Wires): target wire + axes (List[int]): target axes to apply transformation Returns: array[complex]: output state """ - axis = self.wires.index(wire) - return self._roll(state, 1, axis) + return self._roll(state, 1, axes[0]) + + def _apply_hadamard(self, state, axes, **kwargs): + """Apply the Hadamard gate by combining the results of applying the PauliX and PauliZ gates. - def _apply_hadamard(self, state, wire): - state_x = self._apply_x(state, wire) - state_z = self._apply_z(state, wire) + Args: + state (array[complex]): input state + axes (List[int]): target axes to apply transformation + + Returns: + array[complex]: output state + """ + state_x = self._apply_x(state, axes) + state_z = self._apply_z(state, axes) return SQRT2INV * (state_x + state_z) - def _apply_swap(self, state, target_axes): + def _apply_swap(self, state, axes, **kwargs): """Applies SWAP gate by performing a partial transposition along the specified axes. Args: state (array[complex]): input state - target_axes (List[int]): axes to apply transformation + axes (List[int]): target axes to apply transformation Returns: array[complex]: output state """ - axes = list(range(len(state.shape))) - axes[target_axes[0]] = target_axes[1] - axes[target_axes[1]] = target_axes[0] - return self._transpose(state, axes) + all_axes = list(range(len(state.shape))) + all_axes[axes[0]] = axes[1] + all_axes[axes[1]] = axes[0] + return self._transpose(state, all_axes) def _get_unitary_matrix(self, unitary): # pylint: disable=no-self-use """Return the matrix representing a unitary operation. From 9f0fc29c28032eb695233e18e010eaaa05525f05 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 15:03:38 -0400 Subject: [PATCH 13/36] Add CNOT and CZ --- pennylane/devices/default_qubit.py | 52 ++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 3de6f8b32ba..95472181d0e 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -122,6 +122,8 @@ def __init__(self, wires, *, shots=1000, analytic=True): "SWAP": self._apply_swap, "S": self._apply_s, "T": self._apply_t, + "CNOT": self._apply_cnot, + "CZ": self._apply_cz, } def apply(self, operations, rotations=None, **kwargs): @@ -183,6 +185,50 @@ def _apply_s(self, state, axes, inverse=False): def _apply_t(self, state, axes, inverse=False): return self._apply_phase(state, axes, TPHASE, inverse) + def _apply_cnot(self, state, axes, **kwargs): + """Applies a CNOT gate by slicing along the first axis specified in ``axes`` and then + applying an X transformation along the second axis. + + Args: + state (array[complex]): input state + axes (List[int]): target axes to apply transformation + + Returns: + array[complex]: output state + """ + sl_0 = _get_slice(0, axes[0], self.num_wires) + sl_1 = _get_slice(1, axes[0], self.num_wires) + + if axes[1] > axes[0]: + target_axes = [axes[1] - 1] + else: + target_axes = [axes[1]] + + state_x = self._apply_x(state[sl_1], axes=target_axes) + return self._stack([state[sl_0], state_x], axis=axes[0]) + + def _apply_cz(self, state, axes, **kwargs): + """Applies a CZ gate by slicing along the first axis specified in ``axes`` and then + applying a Z transformation along the second axis. + + Args: + state (array[complex]): input state + axes (List[int]): target axes to apply transformation + + Returns: + array[complex]: output state + """ + sl_0 = _get_slice(0, axes[0], self.num_wires) + sl_1 = _get_slice(1, axes[0], self.num_wires) + + if axes[1] > axes[0]: + target_axes = [axes[1] - 1] + else: + target_axes = [axes[1]] + + state_z = self._apply_z(state[sl_1], axes=target_axes) + return self._stack([state[sl_0], state_z], axis=axes[0]) + def _apply_phase(self, state, axes, parameters, inverse=False): """Applies a phase onto the 1 index along the axis specified in ``axes``. @@ -195,11 +241,11 @@ def _apply_phase(self, state, axes, parameters, inverse=False): Returns: array[complex]: output state """ - sl_0 = _get_slice(0, axes[0], self.num_wires) - sl_1 = _get_slice(1, axes[0], self.num_wires) + num_wires = len(state.shape) + sl_0 = _get_slice(0, axes[0], num_wires) + sl_1 = _get_slice(1, axes[0], num_wires) phase = self._conj(parameters) if inverse else parameters - return self._stack([state[sl_0], phase * state[sl_1]], axis=axes[0]) def _apply_y(self, state, axes, **kwargs): From 022794c03d29d72b5b4a01f3875c95f9c9ae70a1 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 15:07:20 -0400 Subject: [PATCH 14/36] apply pylint --- pennylane/devices/default_qubit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 95472181d0e..108ec788d99 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -24,8 +24,7 @@ import numpy as np -from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState, SWAP, PauliX, \ - Hadamard, PauliZ, S, T, PauliY +from pennylane import QubitDevice, DeviceError, QubitStateVector, BasisState from pennylane.operation import DiagonalOperation ABC_ARRAY = np.array(list(ABC)) @@ -52,6 +51,7 @@ def _get_slice(index, axis, num_axes): return tuple(idx) +# pylint: disable=unused-import class DefaultQubit(QubitDevice): """Default qubit device for PennyLane. From 65866d9fe86a5f92df53bebf25cdd88ccbcb7762 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 15:08:29 -0400 Subject: [PATCH 15/36] fix pylint --- pennylane/devices/default_qubit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 108ec788d99..f31d3a39321 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -51,7 +51,7 @@ def _get_slice(index, axis, num_axes): return tuple(idx) -# pylint: disable=unused-import +# pylint: disable=unused-argument class DefaultQubit(QubitDevice): """Default qubit device for PennyLane. From a2e14e73bedec942201e84621ae72b65522eec05 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 17:02:10 -0400 Subject: [PATCH 16/36] Improve wording of docstring --- pennylane/devices/default_qubit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index f31d3a39321..68aaa68c79c 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -36,7 +36,7 @@ def _get_slice(index, axis, num_axes): - """Allows slicing into the index of an array or tensor along an arbitrary axis. + """Allows slicing along an arbitrary axis of an array or tensor. Args: index (int): the index to access From 8009cf6bc6854e2609d7e06a4b72b49d6cc0f567 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 17:05:06 -0400 Subject: [PATCH 17/36] Update order --- pennylane/devices/default_qubit.py | 130 ++++++++++++++--------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 68aaa68c79c..fe750773f57 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -119,10 +119,10 @@ def __init__(self, wires, *, shots=1000, analytic=True): "PauliY": self._apply_y, "PauliZ": self._apply_z, "Hadamard": self._apply_hadamard, - "SWAP": self._apply_swap, "S": self._apply_s, "T": self._apply_t, "CNOT": self._apply_cnot, + "SWAP": self._apply_swap, "CZ": self._apply_cz, } @@ -179,15 +179,8 @@ def _apply_operation(self, operation): else: self._apply_unitary(matrix, wires) - def _apply_s(self, state, axes, inverse=False): - return self._apply_phase(state, axes, 1j, inverse) - - def _apply_t(self, state, axes, inverse=False): - return self._apply_phase(state, axes, TPHASE, inverse) - - def _apply_cnot(self, state, axes, **kwargs): - """Applies a CNOT gate by slicing along the first axis specified in ``axes`` and then - applying an X transformation along the second axis. + def _apply_x(self, state, axes, **kwargs): + """Applies PauliX gate by rolling 1 unit along the axis specified in ``axes``. Args: state (array[complex]): input state @@ -196,20 +189,11 @@ def _apply_cnot(self, state, axes, **kwargs): Returns: array[complex]: output state """ - sl_0 = _get_slice(0, axes[0], self.num_wires) - sl_1 = _get_slice(1, axes[0], self.num_wires) - - if axes[1] > axes[0]: - target_axes = [axes[1] - 1] - else: - target_axes = [axes[1]] - - state_x = self._apply_x(state[sl_1], axes=target_axes) - return self._stack([state[sl_0], state_x], axis=axes[0]) + return self._roll(state, 1, axes[0]) - def _apply_cz(self, state, axes, **kwargs): - """Applies a CZ gate by slicing along the first axis specified in ``axes`` and then - applying a Z transformation along the second axis. + def _apply_y(self, state, axes, **kwargs): + """Applies PauliY gate by adding a negative sign to the 1 index along the axis specified + in ``axes``, rolling one unit along the same axis, and multiplying the result by 1j. Args: state (array[complex]): input state @@ -218,39 +202,23 @@ def _apply_cz(self, state, axes, **kwargs): Returns: array[complex]: output state """ - sl_0 = _get_slice(0, axes[0], self.num_wires) - sl_1 = _get_slice(1, axes[0], self.num_wires) - - if axes[1] > axes[0]: - target_axes = [axes[1] - 1] - else: - target_axes = [axes[1]] - - state_z = self._apply_z(state[sl_1], axes=target_axes) - return self._stack([state[sl_0], state_z], axis=axes[0]) + return 1j * self._apply_x(self._apply_z(state, axes), axes) - def _apply_phase(self, state, axes, parameters, inverse=False): - """Applies a phase onto the 1 index along the axis specified in ``axes``. + def _apply_z(self, state, axes, **kwargs): + """Applies PauliZ gate by adding a negative sign to the 1 index along the axis specified + in ``axes``. Args: state (array[complex]): input state axes (List[int]): target axes to apply transformation - parameters (float): phase to apply - inverse (bool): whether to apply the inverse phase Returns: array[complex]: output state """ - num_wires = len(state.shape) - sl_0 = _get_slice(0, axes[0], num_wires) - sl_1 = _get_slice(1, axes[0], num_wires) - - phase = self._conj(parameters) if inverse else parameters - return self._stack([state[sl_0], phase * state[sl_1]], axis=axes[0]) + return self._apply_phase(state, axes, -1) - def _apply_y(self, state, axes, **kwargs): - """Applies PauliY gate by adding a negative sign to the 1 index along the axis specified - in ``axes``, rolling one unit along the same axis, and multiplying the result by 1j. + def _apply_hadamard(self, state, axes, **kwargs): + """Apply the Hadamard gate by combining the results of applying the PauliX and PauliZ gates. Args: state (array[complex]): input state @@ -259,11 +227,19 @@ def _apply_y(self, state, axes, **kwargs): Returns: array[complex]: output state """ - return 1j * self._apply_x(self._apply_z(state, axes), axes) + state_x = self._apply_x(state, axes) + state_z = self._apply_z(state, axes) + return SQRT2INV * (state_x + state_z) - def _apply_z(self, state, axes, **kwargs): - """Applies PauliZ gate by adding a negative sign to the 1 index along the axis specified - in ``axes``. + def _apply_s(self, state, axes, inverse=False): + return self._apply_phase(state, axes, 1j, inverse) + + def _apply_t(self, state, axes, inverse=False): + return self._apply_phase(state, axes, TPHASE, inverse) + + def _apply_cnot(self, state, axes, **kwargs): + """Applies a CNOT gate by slicing along the first axis specified in ``axes`` and then + applying an X transformation along the second axis. Args: state (array[complex]): input state @@ -272,10 +248,19 @@ def _apply_z(self, state, axes, **kwargs): Returns: array[complex]: output state """ - return self._apply_phase(state, axes, -1) + sl_0 = _get_slice(0, axes[0], self.num_wires) + sl_1 = _get_slice(1, axes[0], self.num_wires) - def _apply_x(self, state, axes, **kwargs): - """Applies PauliX gate by rolling 1 unit along the axis specified in ``axes``. + if axes[1] > axes[0]: + target_axes = [axes[1] - 1] + else: + target_axes = [axes[1]] + + state_x = self._apply_x(state[sl_1], axes=target_axes) + return self._stack([state[sl_0], state_x], axis=axes[0]) + + def _apply_swap(self, state, axes, **kwargs): + """Applies SWAP gate by performing a partial transposition along the specified axes. Args: state (array[complex]): input state @@ -284,10 +269,14 @@ def _apply_x(self, state, axes, **kwargs): Returns: array[complex]: output state """ - return self._roll(state, 1, axes[0]) + all_axes = list(range(len(state.shape))) + all_axes[axes[0]] = axes[1] + all_axes[axes[1]] = axes[0] + return self._transpose(state, all_axes) - def _apply_hadamard(self, state, axes, **kwargs): - """Apply the Hadamard gate by combining the results of applying the PauliX and PauliZ gates. + def _apply_cz(self, state, axes, **kwargs): + """Applies a CZ gate by slicing along the first axis specified in ``axes`` and then + applying a Z transformation along the second axis. Args: state (array[complex]): input state @@ -296,24 +285,35 @@ def _apply_hadamard(self, state, axes, **kwargs): Returns: array[complex]: output state """ - state_x = self._apply_x(state, axes) - state_z = self._apply_z(state, axes) - return SQRT2INV * (state_x + state_z) + sl_0 = _get_slice(0, axes[0], self.num_wires) + sl_1 = _get_slice(1, axes[0], self.num_wires) - def _apply_swap(self, state, axes, **kwargs): - """Applies SWAP gate by performing a partial transposition along the specified axes. + if axes[1] > axes[0]: + target_axes = [axes[1] - 1] + else: + target_axes = [axes[1]] + + state_z = self._apply_z(state[sl_1], axes=target_axes) + return self._stack([state[sl_0], state_z], axis=axes[0]) + + def _apply_phase(self, state, axes, parameters, inverse=False): + """Applies a phase onto the 1 index along the axis specified in ``axes``. Args: state (array[complex]): input state axes (List[int]): target axes to apply transformation + parameters (float): phase to apply + inverse (bool): whether to apply the inverse phase Returns: array[complex]: output state """ - all_axes = list(range(len(state.shape))) - all_axes[axes[0]] = axes[1] - all_axes[axes[1]] = axes[0] - return self._transpose(state, all_axes) + num_wires = len(state.shape) + sl_0 = _get_slice(0, axes[0], num_wires) + sl_1 = _get_slice(1, axes[0], num_wires) + + phase = self._conj(parameters) if inverse else parameters + return self._stack([state[sl_0], phase * state[sl_1]], axis=axes[0]) def _get_unitary_matrix(self, unitary): # pylint: disable=no-self-use """Return the matrix representing a unitary operation. From d4ca1f2b050161459a51b090001047f2eb3aa3e4 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 21 Aug 2020 17:11:40 -0400 Subject: [PATCH 18/36] Improve wording --- pennylane/devices/default_qubit.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index fe750773f57..1c1ae3cb258 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -180,7 +180,7 @@ def _apply_operation(self, operation): self._apply_unitary(matrix, wires) def _apply_x(self, state, axes, **kwargs): - """Applies PauliX gate by rolling 1 unit along the axis specified in ``axes``. + """Applies a PauliX gate by rolling 1 unit along the axis specified in ``axes``. Args: state (array[complex]): input state @@ -192,7 +192,7 @@ def _apply_x(self, state, axes, **kwargs): return self._roll(state, 1, axes[0]) def _apply_y(self, state, axes, **kwargs): - """Applies PauliY gate by adding a negative sign to the 1 index along the axis specified + """Applies a PauliY gate by adding a negative sign to the 1 index along the axis specified in ``axes``, rolling one unit along the same axis, and multiplying the result by 1j. Args: @@ -205,7 +205,7 @@ def _apply_y(self, state, axes, **kwargs): return 1j * self._apply_x(self._apply_z(state, axes), axes) def _apply_z(self, state, axes, **kwargs): - """Applies PauliZ gate by adding a negative sign to the 1 index along the axis specified + """Applies a PauliZ gate by adding a negative sign to the 1 index along the axis specified in ``axes``. Args: @@ -260,7 +260,7 @@ def _apply_cnot(self, state, axes, **kwargs): return self._stack([state[sl_0], state_x], axis=axes[0]) def _apply_swap(self, state, axes, **kwargs): - """Applies SWAP gate by performing a partial transposition along the specified axes. + """Applies a SWAP gate by performing a partial transposition along the specified axes. Args: state (array[complex]): input state From 88db003a10587d869b3c261011cffaef71d7e4de Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 24 Aug 2020 10:14:37 -0400 Subject: [PATCH 19/36] Add test for DiagonalOperation --- tests/devices/test_default_qubit_autograd.py | 5 ++++- tests/devices/test_default_qubit_tf.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/devices/test_default_qubit_autograd.py b/tests/devices/test_default_qubit_autograd.py index 66a0cb31813..649d152d378 100644 --- a/tests/devices/test_default_qubit_autograd.py +++ b/tests/devices/test_default_qubit_autograd.py @@ -63,12 +63,15 @@ def test_correct_state(self, tol): @qml.qnode(dev, interface="autograd", diff_method="backprop") def circuit(): qml.Hadamard(wires=0) + qml.RZ(np.pi / 4, wires=0) return qml.expval(qml.PauliZ(0)) circuit() state = dev.state - expected = np.array([1.0, 0, 1.0, 0]) / np.sqrt(2) + amplitude = np.exp(-1j * np.pi / 8) / np.sqrt(2) + + expected = np.array([amplitude, 0, np.conj(amplitude), 0]) assert np.allclose(state, expected, atol=tol, rtol=0) diff --git a/tests/devices/test_default_qubit_tf.py b/tests/devices/test_default_qubit_tf.py index f98cc6780f4..25193b5db9c 100644 --- a/tests/devices/test_default_qubit_tf.py +++ b/tests/devices/test_default_qubit_tf.py @@ -831,12 +831,15 @@ def test_correct_state(self, tol): @qml.qnode(dev, interface="tf", diff_method="backprop") def circuit(): qml.Hadamard(wires=0) + qml.RZ(np.pi / 4, wires=0) return qml.expval(qml.PauliZ(0)) circuit() state = dev.state - expected = tf.constant([1.0, 0, 1.0, 0]) / np.sqrt(2) + amplitude = np.exp(-1j * np.pi / 8) / np.sqrt(2) + + expected = np.array([amplitude, 0, np.conj(amplitude), 0]) assert np.allclose(state, expected, atol=tol, rtol=0) From 0c74f19d2d65733b37fe27023267582ee8d15f2f Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 24 Aug 2020 10:45:40 -0400 Subject: [PATCH 20/36] Add _get_slice test --- tests/devices/test_default_qubit.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/devices/test_default_qubit.py b/tests/devices/test_default_qubit.py index c257282d213..9cbdd53491b 100644 --- a/tests/devices/test_default_qubit.py +++ b/tests/devices/test_default_qubit.py @@ -21,6 +21,7 @@ import pytest import pennylane as qml from pennylane import numpy as np, DeviceError +from pennylane.devices.default_qubit import _get_slice from pennylane.operation import Operation U = np.array( @@ -1733,3 +1734,15 @@ def test_wires_expval(self, wires1, wires2, tol): circuit2 = self.make_circuit_expval(wires2) assert np.allclose(circuit1(), circuit2(), tol) + + +def test_get_slice(): + """Test that the _get_slice function returns the expected slice and allows us to slice + correctly into an array.""" + + sl = _get_slice(1, 1, 3) + array = np.arange(27).reshape((3, 3, 3)) + target = array[:, 1, :] + + assert sl == (slice(None, None, None), 1, slice(None, None, None)) + assert np.allclose(array[sl], target) From 4630ba36bfe5d94124b9fad5021e1476c415edc1 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 26 Aug 2020 15:19:33 -0400 Subject: [PATCH 21/36] Update dispatch style --- pennylane/devices/default_qubit.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 1c1ae3cb258..138a1625222 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -114,7 +114,7 @@ def __init__(self, wires, *, shots=1000, analytic=True): self._state = self._create_basis_state(0) self._pre_rotated_state = self._state - self._ops_map = { + self._apply_ops = { "PauliX": self._apply_x, "PauliY": self._apply_y, "PauliZ": self._apply_z, @@ -164,9 +164,8 @@ def _apply_operation(self, operation): self._apply_basis_state(operation.parameters[0], wires) return - apply_func = self._ops_map.get(operation.name, None) - if apply_func: - self._state = apply_func(self._state, axes, inverse=operation.inverse) + if operation.name in self._apply_ops: + self._state = self._apply_ops[operation.name](self._state, axes, inverse=operation.inverse) return matrix = self._get_unitary_matrix(operation) From 9efce28eb9604ae7deed94db4de6d7c87df422ef Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 27 Aug 2020 07:37:18 -0400 Subject: [PATCH 22/36] Add explanation --- pennylane/devices/default_qubit.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 138a1625222..a2b0fb2d9a8 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -250,6 +250,12 @@ def _apply_cnot(self, state, axes, **kwargs): sl_0 = _get_slice(0, axes[0], self.num_wires) sl_1 = _get_slice(1, axes[0], self.num_wires) + # If axis[1] is larger than axis[0], then state[sl_1] will have self.num_wires - 1 axes with + # the target axes shifted down by one. Otherwise, if axis[1] is less than axis[0] then its + # axis number in state[sl_1] remains unchanged. For example: state has axes [0, 1, 2, 3] and + # axis[0] = 1 and axis[1] = 3. Then, state[sl_1] has axes [0, 1, 2] so that the target axis + # has shifted from 3 to 2. If axis[0] = 2 and axis[1] = 1, then state[sl_1] has axes + # [0, 1, 2] but with the target axis remaining unchanged. if axes[1] > axes[0]: target_axes = [axes[1] - 1] else: From 555d48b597c2ce3e7578268b79ec1441237a4b25 Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 27 Aug 2020 07:44:27 -0400 Subject: [PATCH 23/36] Add to tests: --- tests/devices/test_default_qubit.py | 52 ++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/tests/devices/test_default_qubit.py b/tests/devices/test_default_qubit.py index 9cbdd53491b..d7c4f5ddfdd 100644 --- a/tests/devices/test_default_qubit.py +++ b/tests/devices/test_default_qubit.py @@ -1736,13 +1736,49 @@ def test_wires_expval(self, wires1, wires2, tol): assert np.allclose(circuit1(), circuit2(), tol) -def test_get_slice(): - """Test that the _get_slice function returns the expected slice and allows us to slice - correctly into an array.""" +class TestGetSlice: + """Tests for the _get_slice function.""" - sl = _get_slice(1, 1, 3) - array = np.arange(27).reshape((3, 3, 3)) - target = array[:, 1, :] + def test_get_slice(self): + """Test that the _get_slice function returns the expected slice and allows us to slice + correctly into an array.""" - assert sl == (slice(None, None, None), 1, slice(None, None, None)) - assert np.allclose(array[sl], target) + sl = _get_slice(1, 1, 3) + array = np.arange(27).reshape((3, 3, 3)) + target = array[:, 1, :] + + assert sl == (slice(None, None, None), 1, slice(None, None, None)) + assert np.allclose(array[sl], target) + + def test_get_slice_first(self): + """Test that the _get_slice function returns the expected slice when accessing the first + axis of an array.""" + + sl = _get_slice(2, 0, 3) + array = np.arange(27).reshape((3, 3, 3)) + target = array[2] + + assert sl == (2, slice(None, None, None), slice(None, None, None)) + assert np.allclose(array[sl], target) + + def test_get_slice_last(self): + """Test that the _get_slice function returns the expected slice when accessing the last + axis of an array.""" + + sl = _get_slice(0, 2, 3) + array = np.arange(27).reshape((3, 3, 3)) + target = array[:, :, 0] + + assert sl == (slice(None, None, None), slice(None, None, None), 0) + assert np.allclose(array[sl], target) + + def test_get_slice_1d(self): + """Test that the _get_slice function returns the expected slice when accessing a + 1-dimensional array.""" + + sl = _get_slice(2, 0, 1) + array = np.arange(27) + target = array[2] + + assert sl == (2,) + assert np.allclose(array[sl], target) From e89563573a4c5966d3464fc2386970b6f8442cdd Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 27 Aug 2020 10:42:25 -0400 Subject: [PATCH 24/36] Add tests for methods --- tests/devices/test_default_qubit.py | 52 +++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/devices/test_default_qubit.py b/tests/devices/test_default_qubit.py index d7c4f5ddfdd..931db5ea635 100644 --- a/tests/devices/test_default_qubit.py +++ b/tests/devices/test_default_qubit.py @@ -1782,3 +1782,55 @@ def test_get_slice_1d(self): assert sl == (2,) assert np.allclose(array[sl], target) + + +@pytest.mark.parametrize("inverse", [True, False]) +class TestApplyOps: + """Tests for special methods listed in _apply_ops that use array manipulation tricks to apply + gates in DefaultQubit.""" + + state = np.arange(2 ** 3).reshape((2, 2, 2)) + dev = qml.device("default.qubit", wires=3) + single_qubit_ops = [ + (qml.PauliX, dev._apply_x), + (qml.PauliY, dev._apply_y), + (qml.PauliZ, dev._apply_z), + (qml.Hadamard, dev._apply_hadamard), + (qml.S, dev._apply_s), + (qml.T, dev._apply_t), + ] + two_qubit_ops = [ + (qml.CNOT, dev._apply_cnot), + (qml.SWAP, dev._apply_swap), + (qml.CZ, dev._apply_cz), + ] + + @pytest.mark.parametrize("op, method", single_qubit_ops) + def test_apply_single_qubit_op(self, op, method, inverse): + """Test if the application of single qubit operations is correct.""" + state_out = method(self.state, axes=[1], inverse=inverse) + op = op(wires=[1]) + matrix = op.inv().matrix if inverse else op.matrix + state_out_einsum = np.einsum("ab,ibk->iak", matrix, self.state) + assert np.allclose(state_out, state_out_einsum) + + @pytest.mark.parametrize("op, method", two_qubit_ops) + def test_apply_two_qubit_op(self, op, method, inverse): + """Test if the application of two qubit operations is correct.""" + state_out = method(self.state, axes=[0, 1]) + op = op(wires=[0, 1]) + matrix = op.inv().matrix if inverse else op.matrix + matrix = matrix.reshape((2, 2, 2, 2)) + state_out_einsum = np.einsum("abcd,cdk->abk", matrix, self.state) + assert np.allclose(state_out, state_out_einsum) + + @pytest.mark.parametrize("op, method", two_qubit_ops) + def test_apply_two_qubit_op_reverse(self, op, method, inverse): + """Test if the application of two qubit operations is correct when the applied wires are + reversed.""" + state_out = method(self.state, axes=[2, 1]) + op = op(wires=[2, 1]) + matrix = op.inv().matrix if inverse else op.matrix + matrix = matrix.reshape((2, 2, 2, 2)) + state_out_einsum = np.einsum("abcd,idc->iba", matrix, self.state) + assert np.allclose(state_out, state_out_einsum) From 0d5edb4d00ada06908a35b8cfc39a2d6ef15e242 Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 27 Aug 2020 11:22:55 -0400 Subject: [PATCH 25/36] Remove slower gates from autograd --- pennylane/devices/default_qubit_autograd.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pennylane/devices/default_qubit_autograd.py b/pennylane/devices/default_qubit_autograd.py index f136c065d36..b79b773d7a3 100644 --- a/pennylane/devices/default_qubit_autograd.py +++ b/pennylane/devices/default_qubit_autograd.py @@ -109,6 +109,12 @@ class DefaultQubitAutograd(DefaultQubit): _roll = staticmethod(np.roll) _stack = staticmethod(np.stack) + def __init__(self, wires, *, shots=1000, analytic=True): + super().__init__(wires, shots=shots, analytic=analytic) + del self._apply_ops["PauliY"] + del self._apply_ops["Hadamard"] + del self._apply_ops["CZ"] + @staticmethod def _scatter(indices, array, new_dimensions): new_array = np.zeros(new_dimensions, dtype=array.dtype.type) From 1355e97bb958ebf3f13845befbd3bf12865d7e0b Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 27 Aug 2020 11:26:00 -0400 Subject: [PATCH 26/36] Remove CZ from tf --- pennylane/devices/default_qubit_tf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pennylane/devices/default_qubit_tf.py b/pennylane/devices/default_qubit_tf.py index 5d5c8087e1a..a12e23eef71 100644 --- a/pennylane/devices/default_qubit_tf.py +++ b/pennylane/devices/default_qubit_tf.py @@ -159,6 +159,10 @@ class DefaultQubitTF(DefaultQubit): _roll = staticmethod(tf.roll) _stack = staticmethod(tf.stack) + def __init__(self, wires, *, shots=1000, analytic=True): + super().__init__(wires, shots=shots, analytic=analytic) + del self._apply_ops["CZ"] + @staticmethod def _scatter(indices, array, new_dimensions): indices = np.expand_dims(indices, 1) From 9571c0c56d5677a4c4ba54c43a1c842af321c8a3 Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 27 Aug 2020 13:55:39 -0400 Subject: [PATCH 27/36] Run black: --- pennylane/devices/default_qubit.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 02324b695f6..74a4eb15c74 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -165,7 +165,9 @@ def _apply_operation(self, operation): return if operation.name in self._apply_ops: - self._state = self._apply_ops[operation.name](self._state, axes, inverse=operation.inverse) + self._state = self._apply_ops[operation.name]( + self._state, axes, inverse=operation.inverse + ) return matrix = self._get_unitary_matrix(operation) @@ -480,13 +482,11 @@ def _apply_unitary_einsum(self, mat, wires): ) # We now put together the indices in the notation numpy's einsum requires - einsum_indices = ( - "{new_indices}{affected_indices},{state_indices}->{new_state_indices}".format( - affected_indices=affected_indices, - state_indices=state_indices, - new_indices=new_indices, - new_state_indices=new_state_indices, - ) + einsum_indices = "{new_indices}{affected_indices},{state_indices}->{new_state_indices}".format( + affected_indices=affected_indices, + state_indices=state_indices, + new_indices=new_indices, + new_state_indices=new_state_indices, ) self._state = self._einsum(einsum_indices, mat, self._state) From 4c8178da41a2688354c720bc01d24488500fd90f Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 27 Aug 2020 14:07:28 -0400 Subject: [PATCH 28/36] Apply new version of black: --- pennylane/devices/default_qubit.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 74a4eb15c74..38979df4044 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -482,11 +482,13 @@ def _apply_unitary_einsum(self, mat, wires): ) # We now put together the indices in the notation numpy's einsum requires - einsum_indices = "{new_indices}{affected_indices},{state_indices}->{new_state_indices}".format( - affected_indices=affected_indices, - state_indices=state_indices, - new_indices=new_indices, - new_state_indices=new_state_indices, + einsum_indices = ( + "{new_indices}{affected_indices},{state_indices}->{new_state_indices}".format( + affected_indices=affected_indices, + state_indices=state_indices, + new_indices=new_indices, + new_state_indices=new_state_indices, + ) ) self._state = self._einsum(einsum_indices, mat, self._state) From c19c9c6cbfbf2f00b6fd17770898c9efb5ca6b85 Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 27 Aug 2020 15:18:45 -0400 Subject: [PATCH 29/36] Move axes definition --- pennylane/devices/default_qubit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 38979df4044..a9a1bb64930 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -154,7 +154,7 @@ def _apply_operation(self, operation): operation (~.Operation): operation to apply on the device """ wires = operation.wires - axes = self.wires.indices(wires) + if isinstance(operation, QubitStateVector): self._apply_state_vector(operation.parameters[0], wires) @@ -165,6 +165,7 @@ def _apply_operation(self, operation): return if operation.name in self._apply_ops: + axes = self.wires.indices(wires) self._state = self._apply_ops[operation.name]( self._state, axes, inverse=operation.inverse ) From 82fc011d59e30767be07c18496184dde76b8cedb Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 27 Aug 2020 15:29:42 -0400 Subject: [PATCH 30/36] Run black --- pennylane/devices/default_qubit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index a9a1bb64930..39196d48ad8 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -155,7 +155,6 @@ def _apply_operation(self, operation): """ wires = operation.wires - if isinstance(operation, QubitStateVector): self._apply_state_vector(operation.parameters[0], wires) return From ee99223a23f8c8b7795e2a3747aca62b81f9cccd Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 28 Aug 2020 09:45:25 -0400 Subject: [PATCH 31/36] Add to changelog --- .github/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 105acbb24e3..8b07ba6a8c4 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -4,6 +4,11 @@

Improvements

+* Sped up the application of certain gates in ``default.qubit`` by using array/tensor + manipulation tricks. The following gates are affected: ``PauliX``, ``PauliY``, ``PauliZ``, + ``Hadamard``, ``SWAP``, ``S``, ``T``, ``CNOT``, ``CZ``. + [(#772)](https://github.com/PennyLaneAI/pennylane/pull/772) +

Breaking changes

Bug fixes

From 9efdbd5c71399cb913e41f7459ac4a0cf3b7c0f9 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 28 Aug 2020 16:05:47 -0400 Subject: [PATCH 32/36] Add example --- pennylane/devices/default_qubit.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 39196d48ad8..f6b8d3402b7 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -45,6 +45,19 @@ def _get_slice(index, axis, num_axes): Returns: tuple[slice or int]: a tuple that can be used to slice into an array or tensor + + **Example:** + + Accessing the 2 index along axis 1 of a 3-axis array: + + >>> sl = _get_slice(2, 1, 3) + >>> sl + (slice(None, None, None), 2, slice(None, None, None)) + >>> a = np.arange(27).reshape((3, 3, 3)) + >>> a[sl] + array([[ 6, 7, 8], + [15, 16, 17], + [24, 25, 26]]) """ idx = [slice(None)] * num_axes idx[axis] = index From 789a724bbd847a160c633ecf77d872c61c2c9c48 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 28 Aug 2020 16:19:08 -0400 Subject: [PATCH 33/36] Add explanation --- pennylane/devices/default_qubit.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index f6b8d3402b7..221fce9cc56 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -196,6 +196,11 @@ def _apply_operation(self, operation): def _apply_x(self, state, axes, **kwargs): """Applies a PauliX gate by rolling 1 unit along the axis specified in ``axes``. + Rolling by 1 unit along the axis means that the :math:`|0 \rangle` state with index ``0`` is + shifted to the :math:`|1 \rangle` state with index ``1``. Likewise, since rolling beyond + the last index loops back to the first, :math:`|1 \rangle` is transformed to + :math:`|0\rangle`. + Args: state (array[complex]): input state axes (List[int]): target axes to apply transformation From b05513676aac6e800695a4729d05b7ef15cc3ce4 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 28 Aug 2020 16:22:12 -0400 Subject: [PATCH 34/36] Fix axis/axes wording --- pennylane/devices/default_qubit.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 221fce9cc56..2d7c07c6ced 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -270,11 +270,11 @@ def _apply_cnot(self, state, axes, **kwargs): sl_0 = _get_slice(0, axes[0], self.num_wires) sl_1 = _get_slice(1, axes[0], self.num_wires) - # If axis[1] is larger than axis[0], then state[sl_1] will have self.num_wires - 1 axes with - # the target axes shifted down by one. Otherwise, if axis[1] is less than axis[0] then its + # If axes[1] is larger than axes[0], then state[sl_1] will have self.num_wires - 1 axes with + # the target axes shifted down by one. Otherwise, if axes[1] is less than axes[0] then its # axis number in state[sl_1] remains unchanged. For example: state has axes [0, 1, 2, 3] and - # axis[0] = 1 and axis[1] = 3. Then, state[sl_1] has axes [0, 1, 2] so that the target axis - # has shifted from 3 to 2. If axis[0] = 2 and axis[1] = 1, then state[sl_1] has axes + # axes[0] = 1 and axes[1] = 3. Then, state[sl_1] has axes [0, 1, 2] so that the target axis + # has shifted from 3 to 2. If axes[0] = 2 and axes[1] = 1, then state[sl_1] has axes # [0, 1, 2] but with the target axis remaining unchanged. if axes[1] > axes[0]: target_axes = [axes[1] - 1] From 26e9ab3b4268bae09c2abc3671f2b129ff1ae5c8 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 28 Aug 2020 16:25:07 -0400 Subject: [PATCH 35/36] Clarify exclusion of gates --- pennylane/devices/default_qubit_autograd.py | 3 +++ pennylane/devices/default_qubit_tf.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/pennylane/devices/default_qubit_autograd.py b/pennylane/devices/default_qubit_autograd.py index b79b773d7a3..6106c504084 100644 --- a/pennylane/devices/default_qubit_autograd.py +++ b/pennylane/devices/default_qubit_autograd.py @@ -111,6 +111,9 @@ class DefaultQubitAutograd(DefaultQubit): def __init__(self, wires, *, shots=1000, analytic=True): super().__init__(wires, shots=shots, analytic=analytic) + + # prevent using special apply methods for these gates due to slowdown in Autograd + # implementation del self._apply_ops["PauliY"] del self._apply_ops["Hadamard"] del self._apply_ops["CZ"] diff --git a/pennylane/devices/default_qubit_tf.py b/pennylane/devices/default_qubit_tf.py index a12e23eef71..40930906f89 100644 --- a/pennylane/devices/default_qubit_tf.py +++ b/pennylane/devices/default_qubit_tf.py @@ -161,6 +161,8 @@ class DefaultQubitTF(DefaultQubit): def __init__(self, wires, *, shots=1000, analytic=True): super().__init__(wires, shots=shots, analytic=analytic) + + # prevent using special apply method for this gate due to slowdown in TF implementation del self._apply_ops["CZ"] @staticmethod From 7059c5096147436bd933caa1d4410b7956736b32 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 28 Aug 2020 16:32:50 -0400 Subject: [PATCH 36/36] Alter explanation --- pennylane/devices/default_qubit.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 2d7c07c6ced..bfdf62ab39f 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -260,6 +260,10 @@ def _apply_cnot(self, state, axes, **kwargs): """Applies a CNOT gate by slicing along the first axis specified in ``axes`` and then applying an X transformation along the second axis. + By slicing along the first axis, we are able to select all of the amplitudes with a + corresponding :math:`|1\rangle` for the control qubit. This means we then just need to apply + a :class:`~.PauliX` (NOT) gate to the result. + Args: state (array[complex]): input state axes (List[int]): target axes to apply transformation @@ -270,12 +274,12 @@ def _apply_cnot(self, state, axes, **kwargs): sl_0 = _get_slice(0, axes[0], self.num_wires) sl_1 = _get_slice(1, axes[0], self.num_wires) - # If axes[1] is larger than axes[0], then state[sl_1] will have self.num_wires - 1 axes with - # the target axes shifted down by one. Otherwise, if axes[1] is less than axes[0] then its - # axis number in state[sl_1] remains unchanged. For example: state has axes [0, 1, 2, 3] and - # axes[0] = 1 and axes[1] = 3. Then, state[sl_1] has axes [0, 1, 2] so that the target axis - # has shifted from 3 to 2. If axes[0] = 2 and axes[1] = 1, then state[sl_1] has axes - # [0, 1, 2] but with the target axis remaining unchanged. + # We will be slicing into the state according to state[sl_1], giving us all of the + # amplitudes with a |1> for the control qubit. The resulting array has lost an axis + # relative to state and we need to be careful about the axis we apply the PauliX rotation + # to. If axes[1] is larger than axes[0], then we need to shift the target axis down by + # one, otherwise we can leave as-is. For example: a state has [0, 1, 2, 3], control=1, + # target=3. Then, state[sl_1] has 3 axes and target=3 now corresponds to the second axis. if axes[1] > axes[0]: target_axes = [axes[1] - 1] else: