... | ... |
@@ -2,74 +2,30 @@ from functools import reduce |
2 | 2 |
|
3 | 3 |
from hypothesis import given |
4 | 4 |
import hypothesis.strategies as st |
5 |
-import hypothesis.extra.numpy as hnp |
|
6 | 5 |
import numpy as np |
7 | 6 |
import pytest |
8 | 7 |
|
9 | 8 |
import qsim.gate |
10 | 9 |
|
11 |
- |
|
12 |
-# -- Strategies for generating values -- |
|
13 |
- |
|
14 |
- |
|
15 |
-n_qubits = st.shared(st.integers(min_value=1, max_value=6)) |
|
16 |
- |
|
17 |
- |
|
18 |
-# Choose which qubits from 'n_qubits' to operate on with a gate that |
|
19 |
-# operates on 'gate_size' qubits |
|
20 |
-def select_n_qubits(gate_size): |
|
21 |
- def _strat(n_qubits): |
|
22 |
- assert n_qubits >= gate_size |
|
23 |
- possible_qubits = st.integers(0, n_qubits - 1) |
|
24 |
- return st.lists( |
|
25 |
- possible_qubits, min_size=gate_size, max_size=gate_size, unique=True |
|
26 |
- ).map(tuple) |
|
27 |
- |
|
28 |
- return _strat |
|
29 |
- |
|
30 |
- |
|
31 |
-valid_complex = st.complex_numbers( |
|
32 |
- max_magnitude=1e10, allow_infinity=False, allow_nan=False |
|
33 |
-) |
|
34 |
-phases = st.floats( |
|
35 |
- min_value=0, max_value=2 * np.pi, allow_nan=False, allow_infinity=False |
|
10 |
+from .common import ( |
|
11 |
+ ket, |
|
12 |
+ single_qubit_gates, |
|
13 |
+ n_qubit_gates, |
|
14 |
+ valid_states, |
|
15 |
+ n_qubits, |
|
16 |
+ phases, |
|
17 |
+ select_n_qubits, |
|
36 | 18 |
) |
37 | 19 |
|
38 | 20 |
|
39 |
-def unitary(n_qubits): |
|
40 |
- size = 1 << n_qubits |
|
41 |
- return ( |
|
42 |
- hnp.arrays(complex, (size, size), elements=valid_complex) |
|
43 |
- .map(lambda a: np.linalg.qr(a)[0]) |
|
44 |
- .filter(lambda u: np.all(np.isfinite(u))) |
|
45 |
- ) |
|
46 |
- |
|
47 |
- |
|
48 |
-def ket(n_qubits): |
|
49 |
- size = 1 << n_qubits |
|
50 |
- return ( |
|
51 |
- hnp.arrays(complex, (size,), elements=valid_complex) |
|
52 |
- .filter(lambda v: np.linalg.norm(v) > 0) # vectors must be normalizable |
|
53 |
- .map(lambda v: v / np.linalg.norm(v)) |
|
54 |
- ) |
|
55 |
- |
|
56 |
- |
|
57 |
-single_qubit_gates = unitary(1) |
|
58 |
-two_qubit_gates = unitary(2) |
|
59 |
-n_qubit_gates = n_qubits.flatmap(unitary) |
|
60 |
- |
|
61 |
-# Projectors on the single qubit computational basis |
|
62 |
-project_zero = np.array([[1, 0], [0, 0]]) |
|
63 |
-project_one = np.array([[0, 0], [0, 1]]) |
|
64 |
- |
|
65 |
- |
|
66 | 21 |
def product_gate(single_qubit_gates): |
67 | 22 |
# We reverse so that 'single_qubit_gates' can be indexed by the qubit |
68 | 23 |
# identifier; e.g. qubit #0 is actually the least-significant qubit |
69 | 24 |
return reduce(np.kron, reversed(single_qubit_gates)) |
70 | 25 |
|
71 | 26 |
|
72 |
-# -- Tests -- |
|
27 |
+project_zero = np.array([[1, 0], [0, 0]]) |
|
28 |
+project_one = np.array([[0, 0], [0, 1]]) |
|
73 | 29 |
|
74 | 30 |
|
75 | 31 |
@given(n_qubits, n_qubit_gates) |
... | ... |
@@ -145,7 +101,7 @@ def test_swap(): |
145 | 101 |
assert np.all(qsim.gate.swap @ qsim.gate.swap == np.identity(4)) |
146 | 102 |
|
147 | 103 |
|
148 |
-@given(single_qubit_gates, n_qubits.flatmap(ket), n_qubits.flatmap(select_n_qubits(1))) |
|
104 |
+@given(single_qubit_gates, valid_states, n_qubits.flatmap(select_n_qubits(1))) |
|
149 | 105 |
def test_applying_single_gates(gate, state, selected): |
150 | 106 |
(qubit,) = selected |
151 | 107 |
n_qubits = state.shape[0].bit_length() - 1 |
... | ... |
@@ -167,11 +123,13 @@ def test_applying_single_gates(gate, state, selected): |
167 | 123 |
def test_applying_controlled_single_qubit_gates(gate, state, selected): |
168 | 124 |
control, qubit = selected |
169 | 125 |
n_qubits = state.shape[0].bit_length() - 1 |
170 |
- # When control qubit is |0⟩ the controlled gate acts like the identity on the other qubit |
|
126 |
+ # When control qubit is |0⟩ the controlled gate acts |
|
127 |
+ # like the identity on the other qubit |
|
171 | 128 |
parts_zero = [np.identity(2)] * n_qubits |
172 | 129 |
parts_zero[control] = project_zero |
173 | 130 |
parts_zero[qubit] = np.identity(2) |
174 |
- # When control qubit is |1⟩ the controlled gate acts like the original gate on the other qubit |
|
131 |
+ # When control qubit is |1⟩ the controlled gate acts |
|
132 |
+ # like the original gate on the other qubit |
|
175 | 133 |
parts_one = [np.identity(2)] * n_qubits |
176 | 134 |
parts_one[control] = project_one |
177 | 135 |
parts_one[qubit] = gate |
This will be necessary for defining measurements also
Joseph Weston authored on 15/09/2021 17:22:20... | ... |
@@ -28,7 +28,9 @@ def select_n_qubits(gate_size): |
28 | 28 |
return _strat |
29 | 29 |
|
30 | 30 |
|
31 |
-valid_complex = st.complex_numbers(allow_infinity=False, allow_nan=False) |
|
31 |
+valid_complex = st.complex_numbers( |
|
32 |
+ max_magnitude=1e10, allow_infinity=False, allow_nan=False |
|
33 |
+) |
|
32 | 34 |
phases = st.floats( |
33 | 35 |
min_value=0, max_value=2 * np.pi, allow_nan=False, allow_infinity=False |
34 | 36 |
) |
... | ... |
@@ -89,11 +91,6 @@ def test_n_qubits_invalid(gate): |
89 | 91 |
# Not size 2**n, n > 0 |
90 | 92 |
with pytest.raises(ValueError): |
91 | 93 |
qsim.gate.n_qubits(gate[:-1, :-1]) |
92 |
- # Not unitary |
|
93 |
- nonunitary_part = np.zeros_like(gate) |
|
94 |
- nonunitary_part[0, -1] = 1j |
|
95 |
- with pytest.raises(ValueError): |
|
96 |
- qsim.gate.n_qubits(gate + nonunitary_part) |
|
97 | 94 |
|
98 | 95 |
|
99 | 96 |
@given(n_qubits, n_qubit_gates) |
... | ... |
@@ -21,7 +21,9 @@ def select_n_qubits(gate_size): |
21 | 21 |
def _strat(n_qubits): |
22 | 22 |
assert n_qubits >= gate_size |
23 | 23 |
possible_qubits = st.integers(0, n_qubits - 1) |
24 |
- return st.lists(possible_qubits, gate_size, gate_size, unique=True).map(tuple) |
|
24 |
+ return st.lists( |
|
25 |
+ possible_qubits, min_size=gate_size, max_size=gate_size, unique=True |
|
26 |
+ ).map(tuple) |
|
25 | 27 |
|
26 | 28 |
return _strat |
27 | 29 |
|
... | ... |
@@ -35,7 +37,7 @@ phases = st.floats( |
35 | 37 |
def unitary(n_qubits): |
36 | 38 |
size = 1 << n_qubits |
37 | 39 |
return ( |
38 |
- hnp.arrays(complex, (size, size), valid_complex) |
|
40 |
+ hnp.arrays(complex, (size, size), elements=valid_complex) |
|
39 | 41 |
.map(lambda a: np.linalg.qr(a)[0]) |
40 | 42 |
.filter(lambda u: np.all(np.isfinite(u))) |
41 | 43 |
) |
... | ... |
@@ -44,7 +46,7 @@ def unitary(n_qubits): |
44 | 46 |
def ket(n_qubits): |
45 | 47 |
size = 1 << n_qubits |
46 | 48 |
return ( |
47 |
- hnp.arrays(complex, (size,), valid_complex) |
|
49 |
+ hnp.arrays(complex, (size,), elements=valid_complex) |
|
48 | 50 |
.filter(lambda v: np.linalg.norm(v) > 0) # vectors must be normalizable |
49 | 51 |
.map(lambda v: v / np.linalg.norm(v)) |
50 | 52 |
) |
... | ... |
@@ -148,7 +150,7 @@ def test_swap(): |
148 | 150 |
|
149 | 151 |
@given(single_qubit_gates, n_qubits.flatmap(ket), n_qubits.flatmap(select_n_qubits(1))) |
150 | 152 |
def test_applying_single_gates(gate, state, selected): |
151 |
- qubit, = selected |
|
153 |
+ (qubit,) = selected |
|
152 | 154 |
n_qubits = state.shape[0].bit_length() - 1 |
153 | 155 |
parts = [np.identity(2)] * n_qubits |
154 | 156 |
parts[qubit] = gate |
For the moment we only test single-qubit and controlled single-qubit
gates, as these gates can be easily expressed on the full Hilbert
space. A single qubit gate is just a tensor product, and a
controlled single-qubit gate is the sum of 2 tensor products.
The only real room for error when implementing gate application
will be in the ordering of the qubits, and these two tests are
already sensitive enough to catch this. Nevertheless it will
be useful later to add more tests, probably by building larger
gates by combining single-qubit and controlled single-qubit gates.
... | ... |
@@ -144,3 +144,42 @@ def test_deutch(): |
144 | 144 |
|
145 | 145 |
def test_swap(): |
146 | 146 |
assert np.all(qsim.gate.swap @ qsim.gate.swap == np.identity(4)) |
147 |
+ |
|
148 |
+ |
|
149 |
+@given(single_qubit_gates, n_qubits.flatmap(ket), n_qubits.flatmap(select_n_qubits(1))) |
|
150 |
+def test_applying_single_gates(gate, state, selected): |
|
151 |
+ qubit, = selected |
|
152 |
+ n_qubits = state.shape[0].bit_length() - 1 |
|
153 |
+ parts = [np.identity(2)] * n_qubits |
|
154 |
+ parts[qubit] = gate |
|
155 |
+ big_gate = product_gate(parts) |
|
156 |
+ |
|
157 |
+ should_be = big_gate @ state |
|
158 |
+ state = qsim.gate.apply(gate, [qubit], state) |
|
159 |
+ |
|
160 |
+ assert np.allclose(state, should_be) |
|
161 |
+ |
|
162 |
+ |
|
163 |
+@given( |
|
164 |
+ single_qubit_gates, |
|
165 |
+ n_qubits.filter(lambda n: n > 1).flatmap(ket), |
|
166 |
+ n_qubits.filter(lambda n: n > 1).flatmap(select_n_qubits(2)), |
|
167 |
+) |
|
168 |
+def test_applying_controlled_single_qubit_gates(gate, state, selected): |
|
169 |
+ control, qubit = selected |
|
170 |
+ n_qubits = state.shape[0].bit_length() - 1 |
|
171 |
+ # When control qubit is |0⟩ the controlled gate acts like the identity on the other qubit |
|
172 |
+ parts_zero = [np.identity(2)] * n_qubits |
|
173 |
+ parts_zero[control] = project_zero |
|
174 |
+ parts_zero[qubit] = np.identity(2) |
|
175 |
+ # When control qubit is |1⟩ the controlled gate acts like the original gate on the other qubit |
|
176 |
+ parts_one = [np.identity(2)] * n_qubits |
|
177 |
+ parts_one[control] = project_one |
|
178 |
+ parts_one[qubit] = gate |
|
179 |
+ # The total controlled gate is then the sum of these 2 product gates |
|
180 |
+ big_gate = product_gate(parts_zero) + product_gate(parts_one) |
|
181 |
+ |
|
182 |
+ should_be = big_gate @ state |
|
183 |
+ state = qsim.gate.apply(qsim.gate.controlled(gate), [control, qubit], state) |
|
184 |
+ |
|
185 |
+ assert np.allclose(state, should_be) |
... | ... |
@@ -1,3 +1,5 @@ |
1 |
+from functools import reduce |
|
2 |
+ |
|
1 | 3 |
from hypothesis import given |
2 | 4 |
import hypothesis.strategies as st |
3 | 5 |
import hypothesis.extra.numpy as hnp |
... | ... |
@@ -57,6 +59,12 @@ project_zero = np.array([[1, 0], [0, 0]]) |
57 | 59 |
project_one = np.array([[0, 0], [0, 1]]) |
58 | 60 |
|
59 | 61 |
|
62 |
+def product_gate(single_qubit_gates): |
|
63 |
+ # We reverse so that 'single_qubit_gates' can be indexed by the qubit |
|
64 |
+ # identifier; e.g. qubit #0 is actually the least-significant qubit |
|
65 |
+ return reduce(np.kron, reversed(single_qubit_gates)) |
|
66 |
+ |
|
67 |
+ |
|
60 | 68 |
# -- Tests -- |
61 | 69 |
|
62 | 70 |
|
These will be needed when explicitly constructing controlled gates.
Joseph Weston authored on 15/11/2019 19:00:28... | ... |
@@ -52,6 +52,11 @@ single_qubit_gates = unitary(1) |
52 | 52 |
two_qubit_gates = unitary(2) |
53 | 53 |
n_qubit_gates = n_qubits.flatmap(unitary) |
54 | 54 |
|
55 |
+# Projectors on the single qubit computational basis |
|
56 |
+project_zero = np.array([[1, 0], [0, 0]]) |
|
57 |
+project_one = np.array([[0, 0], [0, 1]]) |
|
58 |
+ |
|
59 |
+ |
|
55 | 60 |
# -- Tests -- |
56 | 61 |
|
57 | 62 |
|
... | ... |
@@ -13,6 +13,17 @@ import qsim.gate |
13 | 13 |
n_qubits = st.shared(st.integers(min_value=1, max_value=6)) |
14 | 14 |
|
15 | 15 |
|
16 |
+# Choose which qubits from 'n_qubits' to operate on with a gate that |
|
17 |
+# operates on 'gate_size' qubits |
|
18 |
+def select_n_qubits(gate_size): |
|
19 |
+ def _strat(n_qubits): |
|
20 |
+ assert n_qubits >= gate_size |
|
21 |
+ possible_qubits = st.integers(0, n_qubits - 1) |
|
22 |
+ return st.lists(possible_qubits, gate_size, gate_size, unique=True).map(tuple) |
|
23 |
+ |
|
24 |
+ return _strat |
|
25 |
+ |
|
26 |
+ |
|
16 | 27 |
valid_complex = st.complex_numbers(allow_infinity=False, allow_nan=False) |
17 | 28 |
phases = st.floats( |
18 | 29 |
min_value=0, max_value=2 * np.pi, allow_nan=False, allow_infinity=False |
... | ... |
@@ -28,6 +28,15 @@ def unitary(n_qubits): |
28 | 28 |
) |
29 | 29 |
|
30 | 30 |
|
31 |
+def ket(n_qubits): |
|
32 |
+ size = 1 << n_qubits |
|
33 |
+ return ( |
|
34 |
+ hnp.arrays(complex, (size,), valid_complex) |
|
35 |
+ .filter(lambda v: np.linalg.norm(v) > 0) # vectors must be normalizable |
|
36 |
+ .map(lambda v: v / np.linalg.norm(v)) |
|
37 |
+ ) |
|
38 |
+ |
|
39 |
+ |
|
31 | 40 |
single_qubit_gates = unitary(1) |
32 | 41 |
two_qubit_gates = unitary(2) |
33 | 42 |
n_qubit_gates = n_qubits.flatmap(unitary) |
This is a pure refactoring
Joseph Weston authored on 15/11/2019 18:26:27... | ... |
@@ -7,22 +7,32 @@ import pytest |
7 | 7 |
import qsim.gate |
8 | 8 |
|
9 | 9 |
|
10 |
-def unitary(n): |
|
11 |
- valid_complex = st.complex_numbers(allow_infinity=False, allow_nan=False) |
|
12 |
- return ( |
|
13 |
- hnp.arrays(complex, (n, n), valid_complex) |
|
14 |
- .map(lambda a: np.linalg.qr(a)[0]) |
|
15 |
- .filter(lambda u: np.all(np.isfinite(u))) |
|
16 |
- ) |
|
10 |
+# -- Strategies for generating values -- |
|
17 | 11 |
|
18 | 12 |
|
19 | 13 |
n_qubits = st.shared(st.integers(min_value=1, max_value=6)) |
14 |
+ |
|
15 |
+ |
|
16 |
+valid_complex = st.complex_numbers(allow_infinity=False, allow_nan=False) |
|
20 | 17 |
phases = st.floats( |
21 | 18 |
min_value=0, max_value=2 * np.pi, allow_nan=False, allow_infinity=False |
22 | 19 |
) |
23 |
-single_qubit_gates = unitary(2) |
|
24 |
-two_qubit_gates = unitary(4) |
|
25 |
-n_qubit_gates = n_qubits.map(lambda n: 2 ** n).flatmap(unitary) |
|
20 |
+ |
|
21 |
+ |
|
22 |
+def unitary(n_qubits): |
|
23 |
+ size = 1 << n_qubits |
|
24 |
+ return ( |
|
25 |
+ hnp.arrays(complex, (size, size), valid_complex) |
|
26 |
+ .map(lambda a: np.linalg.qr(a)[0]) |
|
27 |
+ .filter(lambda u: np.all(np.isfinite(u))) |
|
28 |
+ ) |
|
29 |
+ |
|
30 |
+ |
|
31 |
+single_qubit_gates = unitary(1) |
|
32 |
+two_qubit_gates = unitary(2) |
|
33 |
+n_qubit_gates = n_qubits.flatmap(unitary) |
|
34 |
+ |
|
35 |
+# -- Tests -- |
|
26 | 36 |
|
27 | 37 |
|
28 | 38 |
@given(n_qubits, n_qubit_gates) |
This idiom is used in other places in the code, so we should
remain consistent.
... | ... |
@@ -53,7 +53,7 @@ def test_n_qubits_invalid(gate): |
53 | 53 |
|
54 | 54 |
@given(n_qubits, n_qubit_gates) |
55 | 55 |
def test_controlled(n, gate): |
56 |
- nq = 2 ** n |
|
56 |
+ nq = 1 << n |
|
57 | 57 |
controlled_gate = qsim.gate.controlled(gate) |
58 | 58 |
assert controlled_gate.shape[0] == 2 * nq |
59 | 59 |
assert np.all(controlled_gate[:nq, :nq] == np.identity(nq)) |
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,103 @@ |
1 |
+from hypothesis import given |
|
2 |
+import hypothesis.strategies as st |
|
3 |
+import hypothesis.extra.numpy as hnp |
|
4 |
+import numpy as np |
|
5 |
+import pytest |
|
6 |
+ |
|
7 |
+import qsim.gate |
|
8 |
+ |
|
9 |
+ |
|
10 |
+def unitary(n): |
|
11 |
+ valid_complex = st.complex_numbers(allow_infinity=False, allow_nan=False) |
|
12 |
+ return ( |
|
13 |
+ hnp.arrays(complex, (n, n), valid_complex) |
|
14 |
+ .map(lambda a: np.linalg.qr(a)[0]) |
|
15 |
+ .filter(lambda u: np.all(np.isfinite(u))) |
|
16 |
+ ) |
|
17 |
+ |
|
18 |
+ |
|
19 |
+n_qubits = st.shared(st.integers(min_value=1, max_value=6)) |
|
20 |
+phases = st.floats( |
|
21 |
+ min_value=0, max_value=2 * np.pi, allow_nan=False, allow_infinity=False |
|
22 |
+) |
|
23 |
+single_qubit_gates = unitary(2) |
|
24 |
+two_qubit_gates = unitary(4) |
|
25 |
+n_qubit_gates = n_qubits.map(lambda n: 2 ** n).flatmap(unitary) |
|
26 |
+ |
|
27 |
+ |
|
28 |
+@given(n_qubits, n_qubit_gates) |
|
29 |
+def test_n_qubits(n, gate): |
|
30 |
+ assert qsim.gate.n_qubits(gate) == n |
|
31 |
+ |
|
32 |
+ |
|
33 |
+@given(n_qubit_gates) |
|
34 |
+def test_n_qubits_invalid(gate): |
|
35 |
+ # Not a numpy array |
|
36 |
+ with pytest.raises(ValueError): |
|
37 |
+ qsim.gate.n_qubits(list(map(list, gate))) |
|
38 |
+ # Not complex |
|
39 |
+ with pytest.raises(ValueError): |
|
40 |
+ qsim.gate.n_qubits(gate.real) |
|
41 |
+ # Not square |
|
42 |
+ with pytest.raises(ValueError): |
|
43 |
+ qsim.gate.n_qubits(gate[:-2]) |
|
44 |
+ # Not size 2**n, n > 0 |
|
45 |
+ with pytest.raises(ValueError): |
|
46 |
+ qsim.gate.n_qubits(gate[:-1, :-1]) |
|
47 |
+ # Not unitary |
|
48 |
+ nonunitary_part = np.zeros_like(gate) |
|
49 |
+ nonunitary_part[0, -1] = 1j |
|
50 |
+ with pytest.raises(ValueError): |
|
51 |
+ qsim.gate.n_qubits(gate + nonunitary_part) |
|
52 |
+ |
|
53 |
+ |
|
54 |
+@given(n_qubits, n_qubit_gates) |
|
55 |
+def test_controlled(n, gate): |
|
56 |
+ nq = 2 ** n |
|
57 |
+ controlled_gate = qsim.gate.controlled(gate) |
|
58 |
+ assert controlled_gate.shape[0] == 2 * nq |
|
59 |
+ assert np.all(controlled_gate[:nq, :nq] == np.identity(nq)) |
|
60 |
+ assert np.all(controlled_gate[nq:, nq:] == gate) |
|
61 |
+ |
|
62 |
+ |
|
63 |
+@given(phases) |
|
64 |
+def test_phase_gate_inverse(phi): |
|
65 |
+ assert np.allclose( |
|
66 |
+ qsim.gate.phase_shift(phi) @ qsim.gate.phase_shift(-phi), np.identity(2) |
|
67 |
+ ) |
|
68 |
+ |
|
69 |
+ |
|
70 |
+@given(phases, st.integers()) |
|
71 |
+def test_phase_gate_periodic(phi, n): |
|
72 |
+ atol = np.finfo(complex).resolution * abs(n) |
|
73 |
+ assert np.allclose( |
|
74 |
+ qsim.gate.phase_shift(phi), |
|
75 |
+ qsim.gate.phase_shift(phi + 2 * np.pi * n), |
|
76 |
+ atol=atol, |
|
77 |
+ ) |
|
78 |
+ |
|
79 |
+ |
|
80 |
+@given(single_qubit_gates) |
|
81 |
+def test_id(gate): |
|
82 |
+ assert np.all(qsim.gate.id @ gate == gate) |
|
83 |
+ assert np.all(gate @ qsim.gate.id == gate) |
|
84 |
+ |
|
85 |
+ |
|
86 |
+def test_pauli_gates_are_involutary(): |
|
87 |
+ pauli_gates = [qsim.gate.x, qsim.gate.y, qsim.gate.z] |
|
88 |
+ assert np.all(qsim.gate.x == qsim.gate.not_) |
|
89 |
+ for gate in pauli_gates: |
|
90 |
+ assert np.all(gate @ gate == qsim.gate.id) |
|
91 |
+ assert np.all(-1j * qsim.gate.x @ qsim.gate.y @ qsim.gate.z == qsim.gate.id) |
|
92 |
+ |
|
93 |
+ |
|
94 |
+def test_sqrt_not(): |
|
95 |
+ assert np.all(qsim.gate.sqrt_not @ qsim.gate.sqrt_not == qsim.gate.not_) |
|
96 |
+ |
|
97 |
+ |
|
98 |
+def test_deutch(): |
|
99 |
+ assert np.allclose(qsim.gate.deutsch(np.pi / 2), qsim.gate.toffoli) |
|
100 |
+ |
|
101 |
+ |
|
102 |
+def test_swap(): |
|
103 |
+ assert np.all(qsim.gate.swap @ qsim.gate.swap == np.identity(4)) |