Browse code

moved to src directory structure

This avoids problems with testing against the wrong package version

Joseph Weston authored on 30/11/2019 18:31:57
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,172 @@
1
+"""Quantum gate operations
2
+
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
+    "apply",
11
+    "n_qubits",
12
+    "controlled",
13
+    # -- Single qubit gates --
14
+    "id",
15
+    "x",
16
+    "y",
17
+    "z",
18
+    "not_",
19
+    "sqrt_not",
20
+    "phase_shift",
21
+    # -- 2 qubit gates --
22
+    "cnot",
23
+    "swap",
24
+    # -- 3 qubit gates --
25
+    "toffoli",
26
+    "cswap",
27
+    "fredkin",
28
+    "deutsch",
29
+]  # type: ignore
30
+
31
+
32
+def apply(gate, qubits, state):
33
+    """Apply a gate to the specified qubits of a state
34
+
35
+    Parameters
36
+    ----------
37
+    gate : ndarray[complex]
38
+    qubits : sequence of int
39
+        The qubits on which to act. Qubit 0 is the least significant qubit.
40
+    state : ndarray[complex]
41
+
42
+    Returns
43
+    -------
44
+    new_state : ndarray[complex]
45
+    """
46
+    n_gate_qubits = gate.shape[0].bit_length() - 1
47
+    n_state_qubits = state.shape[0].bit_length() - 1
48
+    assert len(qubits) == n_gate_qubits
49
+
50
+    # We can view an n-qubit gate as a 2*n-tensor (n contravariant and n contravariant
51
+    # indices) and an n-qubit state as an n-tensor (contravariant indices)
52
+    # with each axis having length 2 (the state space of a single qubit).
53
+    gate = gate.reshape((2,) * 2 * n_gate_qubits)
54
+    state = state.reshape((2,) * n_state_qubits)
55
+
56
+    # Our qubits are labeled from least significant to most significant, i.e. our
57
+    # computational basis (for e.g. 2 qubits) is ordered like |00⟩, |01⟩, |10⟩, |11⟩.
58
+    # We represent the state as a tensor in *row-major* order; this means that the
59
+    # axis ordering is *backwards* compared to the qubit ordering (the least significant
60
+    # qubit corresponds to the *last* axis in the tensor etc.)
61
+    qubit_axes = tuple(n_state_qubits - 1 - np.asarray(qubits))
62
+
63
+    # Applying the gate to the state vector is then the tensor product over the appropriate axes
64
+    axes = (np.arange(n_gate_qubits, 2 * n_gate_qubits), qubit_axes)
65
+    new_state = np.tensordot(gate, state, axes=axes)
66
+
67
+    # tensordot effectively re-orders the qubits such that the qubits we operated
68
+    # on are in the most-significant positions (i.e. their axes come first). We
69
+    # thus need to transpose the axes to place them back into their original positions.
70
+    untouched_axes = tuple(
71
+        idx for idx in range(n_state_qubits) if idx not in qubit_axes
72
+    )
73
+    inverse_permutation = np.argsort(qubit_axes + untouched_axes)
74
+    return np.transpose(new_state, inverse_permutation).reshape(-1)
75
+
76
+
77
+def _check_valid_gate(gate):
78
+    if not (
79
+        # is an array
80
+        isinstance(gate, np.ndarray)
81
+        # is complex
82
+        and np.issubdtype(gate.dtype, np.complex128)
83
+        # is square
84
+        and gate.shape[0] == gate.shape[1]
85
+        # has size 2**n, n > 1
86
+        and np.log2(gate.shape[0]).is_integer()
87
+        and gate.shape[0].bit_length() > 1
88
+        # is unitary
89
+        and np.allclose(gate @ gate.conjugate().transpose(), np.identity(gate.shape[0]))
90
+    ):
91
+        raise ValueError("Gate is not valid")
92
+
93
+
94
+def n_qubits(gate):
95
+    """Return the number of qubits that a gate acts on.
96
+
97
+    Raises ValueError if 'gate' does not have a shape that is
98
+    an integer power of 2.
99
+    """
100
+    _check_valid_gate(gate)
101
+    return gate.shape[0].bit_length() - 1
102
+
103
+
104
+def controlled(gate):
105
+    """Return a controlled quantum gate, given a quantum gate.
106
+
107
+    If 'gate' operates on :math:`n` qubits, then the controlled gate operates
108
+    on :math:`n+1` qubits, where the most-significant qubit is the control.
109
+
110
+    Parameters
111
+    ----------
112
+    gate : np.ndarray[complex]
113
+        A quantum gate acting on :math:`n` qubits;
114
+        a :math:`2^n×2^n` unitary matrix in the computational basis.
115
+
116
+    Returns
117
+    -------
118
+    controlled_gate : np.ndarray[(2**(n+1), 2**(n+1)), complex]
119
+    """
120
+    _check_valid_gate(gate)
121
+    n = gate.shape[0]
122
+    zeros = np.zeros((n, n))
123
+    return np.block([[np.identity(n), zeros], [zeros, gate]])
124
+
125
+
126
+# -- Single qubit gates --
127
+
128
+#: The identity gate on 1 qubit
129
+id = np.identity(2, complex)
130
+#: Pauli X gate
131
+x = np.array([[0, 1], [1, 0]], complex)
132
+#: NOT gate
133
+not_ = x
134
+#: Pauli Y gate
135
+y = np.array([[0, -1j], [1j, 0]], complex)
136
+#: Pauli Z gate
137
+z = np.array([[1, 0], [0, -1]], complex)
138
+#: SQRT(NOT) gate
139
+sqrt_not = 0.5 * (1 + 1j * id - 1j * x)
140
+#: Hadamard gate
141
+hadamard = np.sqrt(0.5) * (x + z)
142
+
143
+
144
+def phase_shift(phi):
145
+    "Return a gate that shifts the phase of :math:`|1⟩` by :math:`φ`."
146
+    return np.array([[1, 0], [0, np.exp(1j * phi)]])
147
+
148
+
149
+# -- Two qubit gates --
150
+
151
+#: Controlled NOT gate
152
+cnot = controlled(x)
153
+#: SWAP gate
154
+swap = np.identity(4, complex)[:, (0, 2, 1, 3)]
155
+
156
+# -- Three qubit gates --
157
+
158
+#: Toffoli (CCNOT) gate
159
+toffoli = controlled(cnot)
160
+#: Controlled SWAP gate
161
+cswap = controlled(swap)
162
+#: Fredkin gate
163
+fredkin = cswap
164
+
165
+
166
+def deutsch(phi):
167
+    "Return a Deutsch gate for angle :math:`φ`."
168
+    gate = np.identity(8, complex)
169
+    gate[-2:, -2:] = np.array(
170
+        [[1j * np.cos(phi), np.sin(phi)], [np.sin(phi), 1j * np.cos(phi)]]
171
+    )
172
+    return gate