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
deleted file mode 100644
... ...
@@ -1,172 +0,0 @@
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
Browse code

Add docstring to qsim.gate.apply

Joseph Weston authored on 15/11/2019 21:14:53
Showing 1 changed files
... ...
@@ -30,6 +30,19 @@ __all__ = [
30 30
 
31 31
 
32 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
+    """
33 46
     n_gate_qubits = gate.shape[0].bit_length() - 1
34 47
     n_state_qubits = state.shape[0].bit_length() - 1
35 48
     assert len(qubits) == n_gate_qubits
Browse code

Implement application of a gate to a state.

Joseph Weston authored on 15/11/2019 20:41:21
Showing 1 changed files
... ...
@@ -7,6 +7,7 @@ matrix written in the computational basis.
7 7
 import numpy as np
8 8
 
9 9
 __all__ = [
10
+    "apply",
10 11
     "n_qubits",
11 12
     "controlled",
12 13
     # -- Single qubit gates --
... ...
@@ -28,6 +29,38 @@ __all__ = [
28 29
 ]  # type: ignore
29 30
 
30 31
 
32
+def apply(gate, qubits, state):
33
+    n_gate_qubits = gate.shape[0].bit_length() - 1
34
+    n_state_qubits = state.shape[0].bit_length() - 1
35
+    assert len(qubits) == n_gate_qubits
36
+
37
+    # We can view an n-qubit gate as a 2*n-tensor (n contravariant and n contravariant
38
+    # indices) and an n-qubit state as an n-tensor (contravariant indices)
39
+    # with each axis having length 2 (the state space of a single qubit).
40
+    gate = gate.reshape((2,) * 2 * n_gate_qubits)
41
+    state = state.reshape((2,) * n_state_qubits)
42
+
43
+    # Our qubits are labeled from least significant to most significant, i.e. our
44
+    # computational basis (for e.g. 2 qubits) is ordered like |00⟩, |01⟩, |10⟩, |11⟩.
45
+    # We represent the state as a tensor in *row-major* order; this means that the
46
+    # axis ordering is *backwards* compared to the qubit ordering (the least significant
47
+    # qubit corresponds to the *last* axis in the tensor etc.)
48
+    qubit_axes = tuple(n_state_qubits - 1 - np.asarray(qubits))
49
+
50
+    # Applying the gate to the state vector is then the tensor product over the appropriate axes
51
+    axes = (np.arange(n_gate_qubits, 2 * n_gate_qubits), qubit_axes)
52
+    new_state = np.tensordot(gate, state, axes=axes)
53
+
54
+    # tensordot effectively re-orders the qubits such that the qubits we operated
55
+    # on are in the most-significant positions (i.e. their axes come first). We
56
+    # thus need to transpose the axes to place them back into their original positions.
57
+    untouched_axes = tuple(
58
+        idx for idx in range(n_state_qubits) if idx not in qubit_axes
59
+    )
60
+    inverse_permutation = np.argsort(qubit_axes + untouched_axes)
61
+    return np.transpose(new_state, inverse_permutation).reshape(-1)
62
+
63
+
31 64
 def _check_valid_gate(gate):
32 65
     if not (
33 66
         # is an array
Browse code

Compute number of qubits by using bit length rather than logarithms

This is more idiomatic.

Joseph Weston authored on 15/11/2019 17:32:29
Showing 1 changed files
... ...
@@ -38,7 +38,7 @@ def _check_valid_gate(gate):
38 38
         and gate.shape[0] == gate.shape[1]
39 39
         # has size 2**n, n > 1
40 40
         and np.log2(gate.shape[0]).is_integer()
41
-        and np.log2(gate.shape[0]) > 0
41
+        and gate.shape[0].bit_length() > 1
42 42
         # is unitary
43 43
         and np.allclose(gate @ gate.conjugate().transpose(), np.identity(gate.shape[0]))
44 44
     ):
... ...
@@ -52,9 +52,7 @@ def n_qubits(gate):
52 52
     an integer power of 2.
53 53
     """
54 54
     _check_valid_gate(gate)
55
-    n = np.log2(gate.shape[0])
56
-    assert n.is_integer()
57
-    return int(n)
55
+    return gate.shape[0].bit_length() - 1
58 56
 
59 57
 
60 58
 def controlled(gate):
Browse code

Add basic quantum gates

Joseph Weston authored on 10/11/2019 16:33:08
Showing 1 changed files
... ...
@@ -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
Browse code

Add mypy configuration

Joseph Weston authored on 09/11/2019 16:36:39
Showing 1 changed files
... ...
@@ -1,3 +1,3 @@
1 1
 """Quantum gate operations"""
2 2
 
3
-__all__ = []
3
+__all__ = []  # type: ignore
Browse code

Add bare package and pyproject.toml

Joseph Weston authored on 09/11/2019 00:46:10
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,3 @@
1
+"""Quantum gate operations"""
2
+
3
+__all__ = []