-
Notifications
You must be signed in to change notification settings - Fork 549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added a custom gate application for Toffoli in default.qubit #1249
Changes from 23 commits
f220751
01ab68b
3c843aa
5dbd7f9
bf12f0d
1b88829
be46b9d
e2af541
e78225a
208babb
ee6e96d
d9daa23
cf45a76
fdf59b2
798acad
4244ee1
066dc66
401ddd1
e487604
835f5c3
81699ef
b870e6b
7638d1c
501c847
da281ab
0cd56a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -151,6 +151,7 @@ def __init__(self, wires, *, shots=None, cache=0, analytic=None): | |
"CNOT": self._apply_cnot, | ||
"SWAP": self._apply_swap, | ||
"CZ": self._apply_cz, | ||
"Toffoli": self._apply_toffoli, | ||
} | ||
|
||
def map_wires(self, wires): | ||
|
@@ -336,6 +337,43 @@ def _apply_cnot(self, state, axes, **kwargs): | |
state_x = self._apply_x(state[sl_1], axes=target_axes) | ||
return self._stack([state[sl_0], state_x], axis=axes[0]) | ||
|
||
def _apply_toffoli(self, state, axes, **kwargs): | ||
"""Applies a Toffoli gate by slicing along the axis of the greater control qubit, slicing | ||
each of the resulting sub-arrays along the axis of the smaller control qubit, and then applying | ||
an X transformation along the axis of the target qubit of the fourth sub-sub-array. | ||
|
||
By performing two consecutive slices in this way, we are able to select all of the amplitudes with | ||
a corresponding :math:`|11\rangle` for the two control qubits. 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 | ||
|
||
Returns: | ||
array[complex]: output state | ||
""" | ||
cntrl_max = np.argmax(axes[:2]) | ||
sl_a0 = _get_slice(0, axes[cntrl_max], self.num_wires) | ||
sl_a1 = _get_slice(1, axes[cntrl_max], self.num_wires) | ||
sl_b0 = _get_slice(0, axes[cntrl_max ^ 1], self.num_wires - 1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe worth adding There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just added that change, it does look better! |
||
sl_b1 = _get_slice(1, axes[cntrl_max ^ 1], self.num_wires - 1) | ||
|
||
# If both controls are smaller than the target, shift the target axis down by two. If one | ||
# control is greater and one control is smaller than the target, shift the target axis | ||
# down by one. If both controls are greater than the target, leave the target axis as-is. | ||
if axes[cntrl_max ^ 1] > axes[2]: | ||
target_axes = [axes[2]] | ||
elif axes[cntrl_max] > axes[2]: | ||
target_axes = [axes[2] - 1] | ||
else: | ||
target_axes = [axes[2] - 2] | ||
|
||
# state[sl_a1][sl_b1] gives us all of the amplitudes with a |11> for the two control qubits. | ||
state_x = self._apply_x(state[sl_a1][sl_b1], axes=target_axes) | ||
state_stacked_a1 = self._stack([state[sl_a1][sl_b0], state_x], axis=axes[cntrl_max ^ 1]) | ||
return self._stack([state[sl_a0], state_stacked_a1], axis=axes[cntrl_max]) | ||
|
||
def _apply_swap(self, state, axes, **kwargs): | ||
"""Applies a SWAP gate by performing a partial transposition along the specified axes. | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1909,8 +1909,8 @@ 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) | ||||||||||||
state = np.arange(2 ** 4).reshape((2, 2, 2, 2)) | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How come we had to go for a device with 4 qubits? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed it to a device with 4 qubits so we can test wire orderings in which the two controlled bits and the target bit are not all adjacent to one another. This helps ensure that |
||||||||||||
dev = qml.device("default.qubit", wires=4) | ||||||||||||
single_qubit_ops = [ | ||||||||||||
(qml.PauliX, dev._apply_x), | ||||||||||||
(qml.PauliY, dev._apply_y), | ||||||||||||
|
@@ -1925,14 +1925,17 @@ class TestApplyOps: | |||||||||||
(qml.SWAP, dev._apply_swap), | ||||||||||||
(qml.CZ, dev._apply_cz), | ||||||||||||
] | ||||||||||||
three_qubit_ops = [ | ||||||||||||
(qml.Toffoli, dev._apply_toffoli), | ||||||||||||
] | ||||||||||||
|
||||||||||||
@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) | ||||||||||||
state_out_einsum = np.einsum("ab,ibjk->iajk", matrix, self.state) | ||||||||||||
assert np.allclose(state_out, state_out_einsum) | ||||||||||||
|
||||||||||||
@pytest.mark.parametrize("op, method", two_qubit_ops) | ||||||||||||
|
@@ -1942,7 +1945,7 @@ def test_apply_two_qubit_op(self, op, method, inverse): | |||||||||||
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) | ||||||||||||
state_out_einsum = np.einsum("abcd,cdjk->abjk", matrix, self.state) | ||||||||||||
assert np.allclose(state_out, state_out_einsum) | ||||||||||||
|
||||||||||||
@pytest.mark.parametrize("op, method", two_qubit_ops) | ||||||||||||
|
@@ -1953,7 +1956,40 @@ def test_apply_two_qubit_op_reverse(self, op, method, inverse): | |||||||||||
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) | ||||||||||||
state_out_einsum = np.einsum("abcd,idck->ibak", matrix, self.state) | ||||||||||||
assert np.allclose(state_out, state_out_einsum) | ||||||||||||
|
||||||||||||
@pytest.mark.parametrize("op, method", three_qubit_ops) | ||||||||||||
def test_apply_three_qubit_op_controls_smaller(self, op, method, inverse): | ||||||||||||
"""Test if the application of three qubit operations is correct when both control wires are | ||||||||||||
smaller than the target wire.""" | ||||||||||||
state_out = method(self.state, axes=[0, 2, 3]) | ||||||||||||
op = op(wires=[0, 2, 3]) | ||||||||||||
Comment on lines
+1966
to
+1967
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be less error-prone upon future modification and updates if it's explicit that these two variables are the same thing. Same for the other tests. Also, we could pull out that variable and parameterize the test over wire order. It would make the code more concise and easier to maintain.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've started pulling out the wires variables and parameterizing the tests over the wire order, but I'm having trouble elegantly replacing the
which gets even more involved for higher dimensions, and therefore is hard to generalize without basically copying the Edit: I might have just found a workound using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry this didn't work out. The idea was to make the tests simpler, but this sounds like it just makes the tests more complicated. It was more of a suggestion than a need, so I'm going ahead with changing my review to "approve". Thanks for trying it out anyway 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I think that's right the call. The parameterized version of
|
||||||||||||
matrix = op.inv().matrix if inverse else op.matrix | ||||||||||||
matrix = matrix.reshape((2, 2) * 3) | ||||||||||||
state_out_einsum = np.einsum("abcdef,dkef->akbc", matrix, self.state) | ||||||||||||
assert np.allclose(state_out, state_out_einsum) | ||||||||||||
|
||||||||||||
@pytest.mark.parametrize("op, method", three_qubit_ops) | ||||||||||||
def test_apply_three_qubit_op_controls_greater(self, op, method, inverse): | ||||||||||||
"""Test if the application of three qubit operations is correct when both control wires are | ||||||||||||
greater than the target wire.""" | ||||||||||||
state_out = method(self.state, axes=[2, 1, 0]) | ||||||||||||
op = op(wires=[2, 1, 0]) | ||||||||||||
matrix = op.inv().matrix if inverse else op.matrix | ||||||||||||
matrix = matrix.reshape((2, 2) * 3) | ||||||||||||
state_out_einsum = np.einsum("abcdef,fedk->cbak", matrix, self.state) | ||||||||||||
assert np.allclose(state_out, state_out_einsum) | ||||||||||||
|
||||||||||||
@pytest.mark.parametrize("op, method", three_qubit_ops) | ||||||||||||
def test_apply_three_qubit_op_controls_split(self, op, method, inverse): | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for adding this test case! |
||||||||||||
"""Test if the application of three qubit operations is correct when one control wire is smaller | ||||||||||||
and one control wire is greater than the target wire.""" | ||||||||||||
state_out = method(self.state, axes=[3, 1, 2]) | ||||||||||||
op = op(wires=[3, 1, 2]) | ||||||||||||
matrix = op.inv().matrix if inverse else op.matrix | ||||||||||||
matrix = matrix.reshape((2, 2) * 3) | ||||||||||||
state_out_einsum = np.einsum("abcdef,kdfe->kacb", matrix, self.state) | ||||||||||||
assert np.allclose(state_out, state_out_einsum) | ||||||||||||
|
||||||||||||
|
||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like the use of
^
! very clever way of doing this 👍