Browse code

Add basic quantum gates

Joseph Weston authored on 10/11/2019 16:33:08
Showing 3 changed files
... ...
@@ -30,7 +30,7 @@ test = [
30 30
     "pytest-flake8",
31 31
     "pytest-mypy",
32 32
     "pytest-black",
33
-    "hypothesis",
33
+    "hypothesis[numpy]",
34 34
     "tox",
35 35
     "flake8-per-file-ignores",
36 36
 ]
... ...
@@ -1,3 +1,128 @@
1
-"""Quantum gate operations"""
1
+"""Quantum gate operations
2 2
 
3
-__all__ = []  # type: ignore
3
+A quantum gate acting on :math:`n` qubits is a :math:`2^n×2^n` unitary
4
+matrix written in the computational basis.
5
+"""
6
+
7
+import numpy as np
8
+
9
+__all__ = [
10
+    "n_qubits",
11
+    "controlled",
12
+    # -- Single qubit gates --
13
+    "id",
14
+    "x",
15
+    "y",
16
+    "z",
17
+    "not_",
18
+    "sqrt_not",
19
+    "phase_shift",
20
+    # -- 2 qubit gates --
21
+    "cnot",
22
+    "swap",
23
+    # -- 3 qubit gates --
24
+    "toffoli",
25
+    "cswap",
26
+    "fredkin",
27
+    "deutsch",
28
+]  # type: ignore
29
+
30
+
31
+def _check_valid_gate(gate):
32
+    if not (
33
+        # is an array
34
+        isinstance(gate, np.ndarray)
35
+        # is complex
36
+        and np.issubdtype(gate.dtype, np.complex128)
37
+        # is square
38
+        and gate.shape[0] == gate.shape[1]
39
+        # has size 2**n, n > 1
40
+        and np.log2(gate.shape[0]).is_integer()
41
+        and np.log2(gate.shape[0]) > 0
42
+        # is unitary
43
+        and np.allclose(gate @ gate.conjugate().transpose(), np.identity(gate.shape[0]))
44
+    ):
45
+        raise ValueError("Gate is not valid")
46
+
47
+
48
+def n_qubits(gate):
49
+    """Return the number of qubits that a gate acts on.
50
+
51
+    Raises ValueError if 'gate' does not have a shape that is
52
+    an integer power of 2.
53
+    """
54
+    _check_valid_gate(gate)
55
+    n = np.log2(gate.shape[0])
56
+    assert n.is_integer()
57
+    return int(n)
58
+
59
+
60
+def controlled(gate):
61
+    """Return a controlled quantum gate, given a quantum gate.
62
+
63
+    If 'gate' operates on :math:`n` qubits, then the controlled gate operates
64
+    on :math:`n+1` qubits, where the most-significant qubit is the control.
65
+
66
+    Parameters
67
+    ----------
68
+    gate : np.ndarray[complex]
69
+        A quantum gate acting on :math:`n` qubits;
70
+        a :math:`2^n×2^n` unitary matrix in the computational basis.
71
+
72
+    Returns
73
+    -------
74
+    controlled_gate : np.ndarray[(2**(n+1), 2**(n+1)), complex]
75
+    """
76
+    _check_valid_gate(gate)
77
+    n = gate.shape[0]
78
+    zeros = np.zeros((n, n))
79
+    return np.block([[np.identity(n), zeros], [zeros, gate]])
80
+
81
+
82
+# -- Single qubit gates --
83
+
84
+#: The identity gate on 1 qubit
85
+id = np.identity(2, complex)
86
+#: Pauli X gate
87
+x = np.array([[0, 1], [1, 0]], complex)
88
+#: NOT gate
89
+not_ = x
90
+#: Pauli Y gate
91
+y = np.array([[0, -1j], [1j, 0]], complex)
92
+#: Pauli Z gate
93
+z = np.array([[1, 0], [0, -1]], complex)
94
+#: SQRT(NOT) gate
95
+sqrt_not = 0.5 * (1 + 1j * id - 1j * x)
96
+#: Hadamard gate
97
+hadamard = np.sqrt(0.5) * (x + z)
98
+
99
+
100
+def phase_shift(phi):
101
+    "Return a gate that shifts the phase of :math:`|1⟩` by :math:`φ`."
102
+    return np.array([[1, 0], [0, np.exp(1j * phi)]])
103
+
104
+
105
+# -- Two qubit gates --
106
+
107
+#: Controlled NOT gate
108
+cnot = controlled(x)
109
+#: SWAP gate
110
+swap = np.identity(4, complex)[:, (0, 2, 1, 3)]
111
+
112
+# -- Three qubit gates --
113
+
114
+#: Toffoli (CCNOT) gate
115
+toffoli = controlled(cnot)
116
+#: Controlled SWAP gate
117
+cswap = controlled(swap)
118
+#: Fredkin gate
119
+fredkin = cswap
120
+
121
+
122
+def deutsch(phi):
123
+    "Return a Deutsch gate for angle :math:`φ`."
124
+    gate = np.identity(8, complex)
125
+    gate[-2:, -2:] = np.array(
126
+        [[1j * np.cos(phi), np.sin(phi)], [np.sin(phi), 1j * np.cos(phi)]]
127
+    )
128
+    return gate
4 129
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))