wath 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wath/graph/__init__.py +1 -0
- wath/graph/mst.py +115 -0
- wath/math/__init__.py +4 -0
- wath/math/bayes.py +474 -0
- wath/math/fibheap.py +225 -0
- wath/math/fit/__init__.py +16 -0
- wath/math/fit/_fit.py +74 -0
- wath/math/fit/delay.py +124 -0
- wath/math/fit/geo.py +85 -0
- wath/math/fit/peak.py +94 -0
- wath/math/fit/qubit_dynamics.py +102 -0
- wath/math/fit/readout.py +429 -0
- wath/math/fit/resonator.py +284 -0
- wath/math/fit/simple.py +332 -0
- wath/math/fit/spectrum.py +122 -0
- wath/math/fit/symmetry.py +145 -0
- wath/math/func.py +11 -0
- wath/math/hmm.py +53 -0
- wath/math/paulis.py +154 -0
- wath/math/signal/__init__.py +6 -0
- wath/math/signal/demodulate.py +59 -0
- wath/math/signal/distortion.py +348 -0
- wath/math/signal/func.py +308 -0
- wath/signal/__init__.py +6 -0
- wath/signal/demodulate.py +59 -0
- wath/signal/distortion.py +348 -0
- wath/signal/func.py +308 -0
- wath-1.0.0.dist-info/LICENSE +674 -0
- wath-1.0.0.dist-info/METADATA +28 -0
- wath-1.0.0.dist-info/RECORD +32 -0
- wath-1.0.0.dist-info/WHEEL +5 -0
- wath-1.0.0.dist-info/top_level.txt +1 -0
wath/graph/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .mst import minimum_spanning_tree, graphs
|
wath/graph/mst.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from typing import Hashable
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class _Graph():
|
|
5
|
+
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self._edges = []
|
|
8
|
+
self._nodes = set()
|
|
9
|
+
|
|
10
|
+
def add_edge(self, a, b):
|
|
11
|
+
self._edges.append((a, b))
|
|
12
|
+
self._nodes.add(a)
|
|
13
|
+
self._nodes.add(b)
|
|
14
|
+
|
|
15
|
+
def __or__(self, other):
|
|
16
|
+
self._edges.extend(other._edges)
|
|
17
|
+
self._nodes.update(other._nodes)
|
|
18
|
+
return self
|
|
19
|
+
|
|
20
|
+
def __contains__(self, item):
|
|
21
|
+
return item in self._nodes
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def graphs(
|
|
25
|
+
edges: list[tuple[Hashable, Hashable]]
|
|
26
|
+
) -> list[list[tuple[Hashable, Hashable]]]:
|
|
27
|
+
"""
|
|
28
|
+
graphs
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
edges: list of edges
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
list of graphs
|
|
35
|
+
"""
|
|
36
|
+
graphs: list[_Graph] = []
|
|
37
|
+
for a, b in edges:
|
|
38
|
+
extended = []
|
|
39
|
+
for i, g in enumerate(graphs):
|
|
40
|
+
if a in g and b in g:
|
|
41
|
+
g.add_edge(a, b)
|
|
42
|
+
break
|
|
43
|
+
elif a in g or b in g:
|
|
44
|
+
g.add_edge(a, b)
|
|
45
|
+
extended.append(i)
|
|
46
|
+
else:
|
|
47
|
+
if len(extended) == 0:
|
|
48
|
+
g = _Graph()
|
|
49
|
+
g.add_edge(a, b)
|
|
50
|
+
graphs.append(g)
|
|
51
|
+
if len(extended) == 2:
|
|
52
|
+
graphs[extended[0]] = graphs[extended[0]] | graphs[extended[1]]
|
|
53
|
+
del graphs[extended[1]]
|
|
54
|
+
|
|
55
|
+
return [[tuple(e) for e in g._edges] for g in graphs]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def minimum_spanning_tree(
|
|
59
|
+
edges: list[tuple[Hashable, Hashable]]
|
|
60
|
+
| list[tuple[Hashable, Hashable, float]]
|
|
61
|
+
) -> list[tuple[Hashable, Hashable]]:
|
|
62
|
+
"""
|
|
63
|
+
minimum spanning tree
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
edges: list of edges
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
list of edges in minimum spanning tree
|
|
70
|
+
"""
|
|
71
|
+
if not edges:
|
|
72
|
+
return []
|
|
73
|
+
if len(edges) == 1:
|
|
74
|
+
return [(edges[0][0], edges[0][1])]
|
|
75
|
+
|
|
76
|
+
edges = [(u, v, 0 if len(w) == 0 else w[0]) for u, v, *w in edges]
|
|
77
|
+
|
|
78
|
+
# Create a set of nodes and map each node to an index
|
|
79
|
+
nodes = set()
|
|
80
|
+
for u, v, _ in edges:
|
|
81
|
+
nodes.add(u)
|
|
82
|
+
nodes.add(v)
|
|
83
|
+
nodes = list(nodes)
|
|
84
|
+
nodes_map = {n: i for i, n in enumerate(nodes)}
|
|
85
|
+
|
|
86
|
+
edges = [(nodes_map[u], nodes_map[v], w) for u, v, w in edges]
|
|
87
|
+
|
|
88
|
+
# Kruskal's algorithm
|
|
89
|
+
edges.sort(key=lambda e: e[2])
|
|
90
|
+
tree = []
|
|
91
|
+
parent = list(range(len(nodes)))
|
|
92
|
+
rank = [0] * len(nodes)
|
|
93
|
+
|
|
94
|
+
def find(u):
|
|
95
|
+
if parent[u] != u:
|
|
96
|
+
parent[u] = find(parent[u])
|
|
97
|
+
return parent[u]
|
|
98
|
+
|
|
99
|
+
def union(u, v):
|
|
100
|
+
u = find(u)
|
|
101
|
+
v = find(v)
|
|
102
|
+
if u == v:
|
|
103
|
+
return
|
|
104
|
+
if rank[u] < rank[v]:
|
|
105
|
+
u, v = v, u
|
|
106
|
+
parent[v] = u
|
|
107
|
+
if rank[u] == rank[v]:
|
|
108
|
+
rank[u] += 1
|
|
109
|
+
|
|
110
|
+
for u, v, _ in edges:
|
|
111
|
+
if find(u) != find(v):
|
|
112
|
+
union(u, v)
|
|
113
|
+
tree.append((nodes[u], nodes[v]))
|
|
114
|
+
|
|
115
|
+
return tree
|
wath/math/__init__.py
ADDED
wath/math/bayes.py
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import math
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from scipy.linalg import logm
|
|
7
|
+
|
|
8
|
+
from .fibheap import FibHeap, FibNode
|
|
9
|
+
from .paulis import string_to_matrices
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def input_state_set(n):
|
|
13
|
+
"""
|
|
14
|
+
Input states for CTMP calibration.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
n: number of qubits
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
a (M x n) array, each row is a state
|
|
21
|
+
M is the smallest integer such that
|
|
22
|
+
comb(M, M // 2) >= n
|
|
23
|
+
"""
|
|
24
|
+
r = 2
|
|
25
|
+
while True:
|
|
26
|
+
if math.comb(2 * r - 1, r - 1) >= n:
|
|
27
|
+
break
|
|
28
|
+
r += 1
|
|
29
|
+
ret = np.zeros((2 * r, n), dtype=np.int8)
|
|
30
|
+
|
|
31
|
+
for i, x in enumerate(itertools.combinations(range(2 * r), r=r)):
|
|
32
|
+
if i >= n:
|
|
33
|
+
break
|
|
34
|
+
ret[x, i] = 1
|
|
35
|
+
return ret
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _generate_standrad_information(key, bayes_matrices):
|
|
39
|
+
ret = []
|
|
40
|
+
for i, k in enumerate(key):
|
|
41
|
+
tmp = sorted([(p, j) for j, p in enumerate(bayes_matrices[i][k])],
|
|
42
|
+
key=lambda x: abs(x[0]),
|
|
43
|
+
reverse=True)
|
|
44
|
+
ret.append((tuple(tmp), i))
|
|
45
|
+
return tuple(ret)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _generate_population(std_info, population):
|
|
49
|
+
state, ret = [0] * len(std_info), population
|
|
50
|
+
for i in range(len(std_info)):
|
|
51
|
+
state[std_info[i][-1]] = std_info[i][0][0][1]
|
|
52
|
+
ret *= std_info[i][0][0][0]
|
|
53
|
+
return ret, tuple(state)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _add_std_info_to_heap(state, population, heap, heap_nodes, max_n, min_p):
|
|
57
|
+
if state in heap_nodes.keys():
|
|
58
|
+
new_key = heap_nodes[state].key[0] * heap_nodes[state].key[
|
|
59
|
+
-1] + population
|
|
60
|
+
heap.change(heap_nodes[state],
|
|
61
|
+
(abs(new_key), state, (-1 if new_key < 0 else 1)))
|
|
62
|
+
elif heap.key_number < max_n:
|
|
63
|
+
heap_nodes[state] = FibNode(
|
|
64
|
+
(abs(population), state, (-1 if population < 0 else 1)))
|
|
65
|
+
heap.insert(heap_nodes[state])
|
|
66
|
+
else:
|
|
67
|
+
if abs(population) < max(min_p, heap.min_node.key[0]):
|
|
68
|
+
return False
|
|
69
|
+
trash = heap.extract_min()
|
|
70
|
+
heap_nodes.pop(trash.key[1])
|
|
71
|
+
heap_nodes[state] = FibNode(
|
|
72
|
+
(abs(population), state, (-1 if population < 0 else 1)))
|
|
73
|
+
heap.insert(heap_nodes[state])
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _update_count(population, std_info, heap, heap_nodes, max_n, min_p):
|
|
78
|
+
queue = FibHeap()
|
|
79
|
+
|
|
80
|
+
p, state = _generate_population(std_info, population)
|
|
81
|
+
queue.insert(FibNode((-abs(p), state, std_info, 1 if p > 0 else -1)))
|
|
82
|
+
|
|
83
|
+
for _ in range(max_n):
|
|
84
|
+
node = queue.extract_min()
|
|
85
|
+
if node is None or not _add_std_info_to_heap(
|
|
86
|
+
node.key[1], -node.key[0] * node.key[-1], heap, heap_nodes,
|
|
87
|
+
max_n, min_p):
|
|
88
|
+
break
|
|
89
|
+
|
|
90
|
+
tmp_info = sorted(list(node.key[2]),
|
|
91
|
+
reverse=True,
|
|
92
|
+
key=lambda x:
|
|
93
|
+
(len(x[0]), abs(x[0][1][0] / x[0][0][0])
|
|
94
|
+
if len(x[0]) > 1 else 0))
|
|
95
|
+
|
|
96
|
+
for i in range(len(std_info)):
|
|
97
|
+
if len(tmp_info[i][0]) == 1:
|
|
98
|
+
break
|
|
99
|
+
new_info = tuple([
|
|
100
|
+
*tmp_info[:i], (tmp_info[i][0][1:], tmp_info[i][1]),
|
|
101
|
+
*tmp_info[i + 1:]
|
|
102
|
+
])
|
|
103
|
+
p, state = _generate_population(new_info, population)
|
|
104
|
+
if heap.key_number < max_n or p > min(min_p, heap.min_node.key[0]):
|
|
105
|
+
queue.insert(
|
|
106
|
+
FibNode((-abs(p), state, new_info, (-1 if p < 0 else 1))))
|
|
107
|
+
elif abs(p) < max(min_p, heap.min_node.key[0]):
|
|
108
|
+
break
|
|
109
|
+
heap.consolidate()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def bayesian_correction_automatic_trimming(state,
|
|
113
|
+
correction_matrices,
|
|
114
|
+
size_lim=None,
|
|
115
|
+
eps=None):
|
|
116
|
+
from .fit.readout import count_state
|
|
117
|
+
|
|
118
|
+
counts = count_state(state)
|
|
119
|
+
shots = len(state)
|
|
120
|
+
|
|
121
|
+
if size_lim is None:
|
|
122
|
+
size_lim = shots * 4
|
|
123
|
+
if eps is None:
|
|
124
|
+
eps = 0.5 / size_lim
|
|
125
|
+
|
|
126
|
+
heap_nodes = {}
|
|
127
|
+
heap = FibHeap()
|
|
128
|
+
|
|
129
|
+
for key, value in sorted(counts.items(), key=lambda x: x[1],
|
|
130
|
+
reverse=False):
|
|
131
|
+
std_info = _generate_standrad_information(key, correction_matrices)
|
|
132
|
+
_update_count(value / shots, std_info, heap, heap_nodes, size_lim, eps)
|
|
133
|
+
|
|
134
|
+
result = {}
|
|
135
|
+
while heap.key_number:
|
|
136
|
+
node = heap.extract_min()
|
|
137
|
+
if node is None:
|
|
138
|
+
break
|
|
139
|
+
result[node.key[1]] = node.key[0] * node.key[-1]
|
|
140
|
+
return result
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def bayesian_correction_in_subspace(state, correction_matrices, subspace):
|
|
144
|
+
"""Apply a correction matrix to a state.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
state (np.array, dtype=int): The state to be corrected.
|
|
148
|
+
correction_matrices (np.array): A list of correction matrices.
|
|
149
|
+
subspace (np.array, dtype=int): The basis of subspace.
|
|
150
|
+
|
|
151
|
+
See also:
|
|
152
|
+
https://doi.org/10.1103/PhysRevA.103.042605
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
np.array: The corrected state.
|
|
156
|
+
|
|
157
|
+
Examples:
|
|
158
|
+
>>> state = np.random.randint(2, size = (101, 1024, 4))
|
|
159
|
+
>>> PgPe = np.array([[0.1, 0.8], [0.03, 0.91], [0.02, 0.87], [0.05, 0.9]])
|
|
160
|
+
>>> correction_matrices = np.array(
|
|
161
|
+
[np.array([[Pe, Pe - 1], [-Pg, 1 - Pg]]) / (Pe - Pg) for Pg, Pe in PgPe])
|
|
162
|
+
>>> subspace = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [1, 1, 0, 0]])
|
|
163
|
+
>>> result = bayesian_correction(state, correction_matrices, subspace)
|
|
164
|
+
>>> result.shape
|
|
165
|
+
(101, 5)
|
|
166
|
+
"""
|
|
167
|
+
num_qubits = state.shape[-1]
|
|
168
|
+
site_index = np.arange(num_qubits)
|
|
169
|
+
|
|
170
|
+
shape = tuple([*state.shape[:-1], len(subspace)])
|
|
171
|
+
state = state.reshape(-1, num_qubits)
|
|
172
|
+
|
|
173
|
+
if len(subspace) < len(state):
|
|
174
|
+
ret = []
|
|
175
|
+
for target_state in subspace:
|
|
176
|
+
A = np.prod(correction_matrices[site_index, target_state, state],
|
|
177
|
+
axis=-1)
|
|
178
|
+
ret.append(A)
|
|
179
|
+
ret = np.array(ret).T.reshape(shape)
|
|
180
|
+
else:
|
|
181
|
+
ret = []
|
|
182
|
+
for bit_string in state:
|
|
183
|
+
A = np.prod(correction_matrices[site_index, subspace, bit_string],
|
|
184
|
+
axis=-1)
|
|
185
|
+
ret.append(A)
|
|
186
|
+
ret = np.array(ret).reshape(shape)
|
|
187
|
+
ret = ret.mean(axis=-2)
|
|
188
|
+
return ret
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def bayesian_correction(state,
|
|
192
|
+
correction_matrices,
|
|
193
|
+
*,
|
|
194
|
+
subspace=None,
|
|
195
|
+
size_lim=1024,
|
|
196
|
+
eps=1e-6):
|
|
197
|
+
"""Apply a correction matrix to a state.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
state (np.array, dtype=int): The state to be corrected.
|
|
201
|
+
correction_matrices (np.array): A list of correction matrices.
|
|
202
|
+
subspace (np.array, dtype=int): The basis of subspace.
|
|
203
|
+
size_lim (int): The maximum size of the heap.
|
|
204
|
+
eps (float): The minimum probability of the state.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
np.array: The corrected state.
|
|
208
|
+
|
|
209
|
+
Examples:
|
|
210
|
+
>>> state = np.random.randint(2, size = (101, 1024, 4))
|
|
211
|
+
>>> PgPe = np.array([[0.1, 0.8], [0.03, 0.91], [0.02, 0.87], [0.05, 0.9]])
|
|
212
|
+
>>> correction_matrices = np.array(
|
|
213
|
+
[np.array([[Pe, Pe - 1], [-Pg, 1 - Pg]]) / (Pe - Pg) for Pg, Pe in PgPe])
|
|
214
|
+
>>> result = bayesian_correction(state, correction_matrices)
|
|
215
|
+
>>> result.shape
|
|
216
|
+
(101, 1024)
|
|
217
|
+
"""
|
|
218
|
+
if subspace is None:
|
|
219
|
+
state = np.array(state)
|
|
220
|
+
if state.ndim == 2:
|
|
221
|
+
return bayesian_correction_automatic_trimming(
|
|
222
|
+
state, correction_matrices, size_lim, eps)
|
|
223
|
+
shape = state.shape[:-2]
|
|
224
|
+
state = state.reshape(-1, state.shape[-2], state.shape[-1])
|
|
225
|
+
ret = []
|
|
226
|
+
for s in state:
|
|
227
|
+
ret.append(
|
|
228
|
+
bayesian_correction_automatic_trimming(s, correction_matrices,
|
|
229
|
+
size_lim, eps))
|
|
230
|
+
return np.array(ret).reshape(shape)
|
|
231
|
+
else:
|
|
232
|
+
return bayesian_correction_in_subspace(state, correction_matrices,
|
|
233
|
+
subspace)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def extract_matrices(input_states, output_states):
|
|
237
|
+
"""
|
|
238
|
+
Extract stochastic matrices from input and output states.
|
|
239
|
+
|
|
240
|
+
The stochastic matrix A is defined as
|
|
241
|
+
P_out = A P_in
|
|
242
|
+
where P_in and P_out are the probability vectors of prepared states
|
|
243
|
+
and measured states, respectively. P_in and P_out are column vectors,
|
|
244
|
+
and the order of the basis is |00>, |01>, |10>, |11>. A is a (4x4)
|
|
245
|
+
stochastic matrix.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
input_states: a (N x n) array, each row is a state
|
|
249
|
+
output_states: a (N x M x n) array, each row is a state
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
a dictionary of (4x4) stochastic matrices
|
|
253
|
+
the key is a tuple of two qubit indices
|
|
254
|
+
"""
|
|
255
|
+
num_qubits = input_states.shape[-1]
|
|
256
|
+
matrices = {}
|
|
257
|
+
for i, j in itertools.combinations(range(num_qubits), r=2):
|
|
258
|
+
x = input_states[:, [i, j]]
|
|
259
|
+
y = output_states[:, :, [i, j]]
|
|
260
|
+
|
|
261
|
+
data = [[], [], [], []]
|
|
262
|
+
for k, (input_state, output_state) in enumerate(zip(x, y)):
|
|
263
|
+
data[2 * input_state[0] + input_state[1]].extend(
|
|
264
|
+
list(2 * output_state[:, 0] + output_state[:, 1]))
|
|
265
|
+
mat = np.zeros((4, 4), dtype=float)
|
|
266
|
+
for k, d in enumerate(data):
|
|
267
|
+
col = np.array(
|
|
268
|
+
[np.count_nonzero(np.array(d) == l) for l in range(4)])
|
|
269
|
+
mat[:, k] = col / col.sum()
|
|
270
|
+
matrices[(i, j)] = mat
|
|
271
|
+
return matrices
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def get_error_rates(matrices, num_qubits):
|
|
275
|
+
"""
|
|
276
|
+
Get the error rates from the stochastic matrices.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
matrices: a dictionary of (4x4) stochastic matrices
|
|
280
|
+
num_qubits: number of qubits
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
gamma: the total error rate
|
|
284
|
+
rates1: a dictionary of single-qubit error rates
|
|
285
|
+
rates2: a dictionary of two-qubit error rates
|
|
286
|
+
"""
|
|
287
|
+
rates1 = defaultdict(lambda: np.array([0.0, 0.0]))
|
|
288
|
+
rates2 = {}
|
|
289
|
+
swap = np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]])
|
|
290
|
+
|
|
291
|
+
for i, j in itertools.combinations(range(num_qubits), r=2):
|
|
292
|
+
if (i, j) in matrices:
|
|
293
|
+
G = logm(matrices[(i, j)])
|
|
294
|
+
elif (j, i) in matrices:
|
|
295
|
+
G = swap @ logm(matrices[(j, i)]) @ swap
|
|
296
|
+
else:
|
|
297
|
+
continue
|
|
298
|
+
G = np.clip(G.real, 0, np.inf)
|
|
299
|
+
rates2[(i, j)] = np.diag(G[::-1, :])
|
|
300
|
+
rates1[i] += np.array([G[2, 0] + G[3, 1], G[0, 2] + G[1, 3]
|
|
301
|
+
]) / (2 * (num_qubits - 1))
|
|
302
|
+
rates1[j] += np.array([G[1, 0] + G[3, 2], G[0, 1] + G[2, 3]
|
|
303
|
+
]) / (2 * (num_qubits - 1))
|
|
304
|
+
|
|
305
|
+
gamma = np.max(list(rates1.values()), axis=-1).sum() + np.max(
|
|
306
|
+
list(rates2.values()), axis=-1).sum()
|
|
307
|
+
return gamma, dict(rates1), rates2
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _flip(x, gamma, rate1, *rates):
|
|
311
|
+
seed = np.random.random()
|
|
312
|
+
for qubit, r in rate1.items():
|
|
313
|
+
seed -= r[x[qubit]] / gamma
|
|
314
|
+
if seed < 0:
|
|
315
|
+
x[qubit] ^= 1
|
|
316
|
+
return
|
|
317
|
+
for order, rate in enumerate(rates, start=2):
|
|
318
|
+
for qubits, r in rate.items():
|
|
319
|
+
index = 0
|
|
320
|
+
for qubit in qubits:
|
|
321
|
+
index = (index << 1) + x[qubit]
|
|
322
|
+
seed -= r[index] / gamma
|
|
323
|
+
if seed < 0:
|
|
324
|
+
for qubit in qubits:
|
|
325
|
+
x[qubit] ^= 1
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def exception(state,
|
|
330
|
+
e_ops: np.ndarray | list[str],
|
|
331
|
+
correction_matrices: np.ndarray | None = None,
|
|
332
|
+
gamma=None,
|
|
333
|
+
rates1=None,
|
|
334
|
+
rates2=None):
|
|
335
|
+
"""Calculate the exceptions of the operators.
|
|
336
|
+
|
|
337
|
+
Consider a simple case when A is a tensor product of 2 x 2 stochastic matrices.
|
|
338
|
+
and observable operator has a tensor product form.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
state (np.array, dtype=int): The bit string of the state. The shape
|
|
342
|
+
should be (..., shots, num_qubits).
|
|
343
|
+
e_ops (np.array): A list of operators. Each operator should be diagonal.
|
|
344
|
+
correction_matrices (np.array): A list of correction matrices.
|
|
345
|
+
|
|
346
|
+
See also:
|
|
347
|
+
https://doi.org/10.1103/PhysRevA.103.042605
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
np.array: The exceptions of the operators.
|
|
351
|
+
|
|
352
|
+
Examples:
|
|
353
|
+
>>> state = np.random.randint(2, size = (101, 1024, 4))
|
|
354
|
+
>>> errors = [[0.05, 0.1], [0.02, 0.03], [0.01, 0.08], [0.02, 0.03]]
|
|
355
|
+
>>> correction_matrices = np.array([
|
|
356
|
+
np.linalg.inv(np.array([[1 - eps, eta], [eps, 1 - eta]]))
|
|
357
|
+
for eps, eta in errors
|
|
358
|
+
])
|
|
359
|
+
>>> ops = ['0III', '0II1', 'ZIZZ']
|
|
360
|
+
>>> result = exception(state, ops, correction_matrices)
|
|
361
|
+
>>> result.shape
|
|
362
|
+
(101, 3)
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
*datashape, shots, num_qubits = state.shape
|
|
366
|
+
site_index = np.arange(num_qubits)
|
|
367
|
+
|
|
368
|
+
if e_ops and isinstance(e_ops[0], str):
|
|
369
|
+
e_ops = [string_to_matrices(s, diag=True) for s in e_ops]
|
|
370
|
+
e_ops = np.asarray(e_ops)
|
|
371
|
+
|
|
372
|
+
*n_ops, num_qubits_, _, _ = e_ops.shape
|
|
373
|
+
assert num_qubits == num_qubits_
|
|
374
|
+
|
|
375
|
+
if gamma is not None and rates1 is not None:
|
|
376
|
+
if rates2 is None:
|
|
377
|
+
if correction_matrices is None:
|
|
378
|
+
correction_matrices = []
|
|
379
|
+
for i in range(num_qubits):
|
|
380
|
+
r0, r1 = rates1.get(i, (0, 0))
|
|
381
|
+
if r0 + r1 == 0:
|
|
382
|
+
correction_matrices.append(np.eye(2))
|
|
383
|
+
else:
|
|
384
|
+
eps = r0 / (r0 + r1) * (1 - np.exp(-r0 - r1))
|
|
385
|
+
eta = r1 / (r0 + r1) * (1 - np.exp(-r0 - r1))
|
|
386
|
+
correction_matrices.append(
|
|
387
|
+
np.linalg.inv(
|
|
388
|
+
np.array([[1 - eps, eta], [eps, 1 - eta]])))
|
|
389
|
+
else:
|
|
390
|
+
e_ops = e_ops[..., [0, 1], [0, 1]]
|
|
391
|
+
alpha = np.random.poisson(gamma, (*datashape, shots))
|
|
392
|
+
state = np.copy(state).reshape(-1, num_qubits)
|
|
393
|
+
for n, s in zip(alpha.reshape(-1), state):
|
|
394
|
+
for _ in range(n):
|
|
395
|
+
_flip(s, gamma, rates1, rates2)
|
|
396
|
+
state = state.reshape(*datashape, shots, num_qubits)
|
|
397
|
+
sign = (-1)**(alpha & 1)
|
|
398
|
+
return np.exp(2 * gamma) * np.moveaxis(
|
|
399
|
+
(sign * e_ops[..., site_index, state].prod(axis=-1)).mean(
|
|
400
|
+
axis=-1), 0, -1)
|
|
401
|
+
|
|
402
|
+
if correction_matrices is None:
|
|
403
|
+
M = e_ops
|
|
404
|
+
else:
|
|
405
|
+
correction_matrices = np.asarray(correction_matrices)
|
|
406
|
+
num_qubits_, _, _ = correction_matrices.shape
|
|
407
|
+
assert num_qubits == num_qubits_
|
|
408
|
+
M = e_ops @ correction_matrices
|
|
409
|
+
|
|
410
|
+
return M[..., site_index, :,
|
|
411
|
+
state.reshape(-1, num_qubits).astype(np.int8)].sum(axis=-1).prod(
|
|
412
|
+
axis=1).reshape(*datashape, shots,
|
|
413
|
+
*n_ops).mean(axis=len(datashape))
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def measure(op: str):
|
|
417
|
+
"""
|
|
418
|
+
Measure the operator.
|
|
419
|
+
|
|
420
|
+
Covnert the operator to the form of 'I', 'Z' by
|
|
421
|
+
adding '-X/2' and 'Y/2' gates.
|
|
422
|
+
return the covnerted operator and the circuit.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
op: the operator to be measured
|
|
426
|
+
the operator is a string of 'I', 'X', 'Y', 'Z'
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
operator, circuit
|
|
430
|
+
the operator is a string of 'I', 'Z'
|
|
431
|
+
the circuit is a list of (gate, qubit_index)
|
|
432
|
+
|
|
433
|
+
Examples:
|
|
434
|
+
>>> measure('X')
|
|
435
|
+
('Z', [('-Y/2', 0)])
|
|
436
|
+
>>> measure('Y')
|
|
437
|
+
('Z', [('X/2', 0)])
|
|
438
|
+
>>> measure('Z')
|
|
439
|
+
('Z', [])
|
|
440
|
+
>>> measure('I')
|
|
441
|
+
('I', [])
|
|
442
|
+
"""
|
|
443
|
+
e_op = []
|
|
444
|
+
circ = []
|
|
445
|
+
|
|
446
|
+
sign = 1
|
|
447
|
+
op = op.strip()
|
|
448
|
+
if op.startswith('+'):
|
|
449
|
+
op = op[1:]
|
|
450
|
+
if op.startswith('-'):
|
|
451
|
+
sign = -1
|
|
452
|
+
op = op[1:]
|
|
453
|
+
if op.startswith('i'):
|
|
454
|
+
op = op[1:]
|
|
455
|
+
sign *= 1j
|
|
456
|
+
if sign == -1:
|
|
457
|
+
e_op.append('-')
|
|
458
|
+
elif sign == 1j:
|
|
459
|
+
e_op.append('i')
|
|
460
|
+
elif sign == -1j:
|
|
461
|
+
e_op.append('-i')
|
|
462
|
+
|
|
463
|
+
for i, c in enumerate(op):
|
|
464
|
+
if c == 'X':
|
|
465
|
+
e_op.append('Z')
|
|
466
|
+
circ.append(('-Y/2', i))
|
|
467
|
+
elif c == 'Y':
|
|
468
|
+
e_op.append('Z')
|
|
469
|
+
circ.append(('X/2', i))
|
|
470
|
+
elif c in ['I', 'Z', '0', '1']:
|
|
471
|
+
e_op.append(c)
|
|
472
|
+
else:
|
|
473
|
+
raise ValueError(f"Unknown operator {c}")
|
|
474
|
+
return ''.join(e_op), circ
|