Trajectree 0.0.4__py3-none-any.whl → 0.0.5__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.
- trajectree/fock_optics/devices.py +129 -27
- trajectree/fock_optics/light_sources.py +8 -2
- trajectree/fock_optics/measurement.py +73 -37
- trajectree/fock_optics/noise_models.py +112 -2
- trajectree/{optical_quant_info.py → fock_optics/optical_quant_info.py} +15 -5
- trajectree/fock_optics/outputs.py +1 -1
- trajectree/quant_info/circuit.py +251 -0
- trajectree/quant_info/noise_models.py +24 -0
- trajectree/sequence/swap.py +48 -24
- trajectree/trajectory.py +289 -63
- {trajectree-0.0.4.dist-info → trajectree-0.0.5.dist-info}/METADATA +2 -1
- trajectree-0.0.5.dist-info/RECORD +18 -0
- {trajectree-0.0.4.dist-info → trajectree-0.0.5.dist-info}/WHEEL +1 -1
- trajectree-0.0.4.dist-info/RECORD +0 -16
- {trajectree-0.0.4.dist-info → trajectree-0.0.5.dist-info}/licenses/LICENSE +0 -0
- {trajectree-0.0.4.dist-info → trajectree-0.0.5.dist-info}/top_level.txt +0 -0
trajectree/trajectory.py
CHANGED
|
@@ -2,25 +2,53 @@ import numpy as np
|
|
|
2
2
|
from quimb.tensor import MatrixProductOperator as mpo #type: ignore
|
|
3
3
|
from quimb.tensor.tensor_arbgeom import tensor_network_apply_op_vec #type: ignore
|
|
4
4
|
from .fock_optics.outputs import read_quantum_state
|
|
5
|
+
# from treelib import Tree
|
|
6
|
+
from matplotlib import pyplot as plt
|
|
7
|
+
import networkx as nx
|
|
8
|
+
from networkx.drawing.nx_pydot import graphviz_layout
|
|
9
|
+
from qutip import basis, expand_operator, Qobj
|
|
10
|
+
import heapq
|
|
11
|
+
import logging
|
|
12
|
+
import copy
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
logger.setLevel(logging.INFO)
|
|
15
|
+
f_handler = logging.FileHandler('log.log')
|
|
16
|
+
logger.addHandler(f_handler)
|
|
5
17
|
|
|
6
18
|
|
|
7
19
|
class quantum_channel:
|
|
8
|
-
def __init__(self, N, num_modes, formalism, kraus_ops_tuple = None,
|
|
20
|
+
def __init__(self, N, num_modes, formalism, kraus_ops_tuple = None, unitary_op = None, backend = "tensor", name = "quantum_channel"):
|
|
9
21
|
self.N = N
|
|
10
22
|
self.name = name
|
|
11
23
|
self.num_modes = num_modes
|
|
12
24
|
self.formalism = formalism
|
|
25
|
+
self.backend = backend
|
|
13
26
|
if self.formalism == 'kraus':
|
|
14
|
-
|
|
15
|
-
|
|
27
|
+
if self.backend == "tensor":
|
|
28
|
+
# Calculate the MPOs of the Kraus operators
|
|
29
|
+
self.kraus_ops = quantum_channel.find_quantum_channels_MPOs(kraus_ops_tuple, N, num_modes)
|
|
30
|
+
elif self.backend == 'statevector':
|
|
31
|
+
self.kraus_ops = self.create_qutip_ops(kraus_ops_tuple)
|
|
32
|
+
|
|
16
33
|
elif self.formalism == 'closed':
|
|
17
|
-
self.
|
|
34
|
+
if self.backend == "tensor":
|
|
35
|
+
self.unitary_op = unitary_op
|
|
36
|
+
elif self.backend == "statevector":
|
|
37
|
+
self.unitary_op = expand_operator(oper = unitary_op[1], dims = [self.N] * self.num_modes, targets = unitary_op[0])
|
|
38
|
+
|
|
18
39
|
|
|
19
|
-
def
|
|
40
|
+
def get_ops(self):
|
|
20
41
|
if self.formalism == 'closed':
|
|
21
|
-
return self.
|
|
42
|
+
return self.unitary_op
|
|
22
43
|
elif self.formalism == 'kraus':
|
|
23
|
-
return self.
|
|
44
|
+
return self.kraus_ops
|
|
45
|
+
|
|
46
|
+
def create_qutip_ops(self, ops_tuple):
|
|
47
|
+
(targets, ops) = ops_tuple
|
|
48
|
+
return_ops = []
|
|
49
|
+
for op in ops:
|
|
50
|
+
return_ops.append(expand_operator(oper = Qobj(op.toarray()), dims = [self.N] * self.num_modes, targets = targets))
|
|
51
|
+
return return_ops
|
|
24
52
|
|
|
25
53
|
@staticmethod
|
|
26
54
|
def find_quantum_channels_MPOs(ops_tuple, N, num_modes):
|
|
@@ -33,179 +61,377 @@ class quantum_channel:
|
|
|
33
61
|
def calc_mpos(ops, N, sites, num_modes):
|
|
34
62
|
MPOs = []
|
|
35
63
|
for op in ops:
|
|
64
|
+
# print("matrix norm:", np.linalg.norm(op.todense()))
|
|
36
65
|
MPO = mpo.from_dense(op.todense(), dims = N, sites = sites, L=num_modes, tags="op")
|
|
37
66
|
MPOs.append(MPO)
|
|
38
67
|
return MPOs
|
|
39
68
|
|
|
40
69
|
|
|
41
70
|
class trajectree_node:
|
|
42
|
-
def __init__(self,
|
|
43
|
-
self.
|
|
71
|
+
def __init__(self, weights, trajectories, trajectory_indices):
|
|
72
|
+
self.weights = weights
|
|
44
73
|
self.trajectories = trajectories
|
|
45
|
-
self.trajectory_indices = trajectory_indices
|
|
74
|
+
self.trajectory_indices = np.array(trajectory_indices)
|
|
75
|
+
self.accesses = 1
|
|
76
|
+
|
|
77
|
+
def __lt__(self, other):
|
|
78
|
+
return self.accesses < other.accesses
|
|
46
79
|
|
|
47
80
|
class trajectory_evaluator():
|
|
48
|
-
def __init__(self, quantum_channels, cache_size =
|
|
81
|
+
def __init__(self, quantum_channels, cache_size = 7, max_cache_nodes = -1, backend = "tensor", calc_expectation = False, observable_ops = []):
|
|
49
82
|
self.quantum_channels = quantum_channels
|
|
50
83
|
self.kraus_channels = []
|
|
51
84
|
for quantum_channel in self.quantum_channels:
|
|
52
85
|
if quantum_channel.formalism == 'kraus':
|
|
53
86
|
self.kraus_channels.append(quantum_channel)
|
|
54
87
|
|
|
88
|
+
self.backend = backend
|
|
55
89
|
self.trajectree = [{} for i in range(len(self.kraus_channels)+1)] # +1 because you also cache the end of the simulation so you prevent doing the final unitary operations multiple times.
|
|
56
90
|
self.traversed_nodes = ()
|
|
57
91
|
self.cache_size = cache_size
|
|
92
|
+
self.cache_heap = []
|
|
93
|
+
self.max_cache_nodes = max_cache_nodes
|
|
94
|
+
|
|
95
|
+
self.calc_expectation = calc_expectation
|
|
96
|
+
self.observable_ops = observable_ops
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Visualizing the Trajectree:
|
|
100
|
+
self.graph = nx.DiGraph()
|
|
101
|
+
# self.graph.create_node(str(self.traversed_nodes), str(self.traversed_nodes)) # root node
|
|
58
102
|
|
|
59
103
|
# for debugging only:
|
|
60
104
|
self.cache_hit = 0
|
|
61
105
|
self.cache_miss = 0
|
|
62
106
|
self.cache_partial_hit = 0
|
|
63
107
|
|
|
108
|
+
def apply_op(self, psi, op, error_tolerance):
|
|
109
|
+
if self.backend == 'tensor':
|
|
110
|
+
return tensor_network_apply_op_vec(op, psi, compress=True, contract = True, cutoff = error_tolerance)
|
|
111
|
+
if self.backend == 'statevector':
|
|
112
|
+
return op * psi
|
|
113
|
+
|
|
114
|
+
def calc_magnitude(self, psi):
|
|
115
|
+
if self.backend == 'tensor':
|
|
116
|
+
return np.real(psi.H @ psi)
|
|
117
|
+
if self.backend == 'statevector':
|
|
118
|
+
return np.real(psi.norm())
|
|
119
|
+
|
|
120
|
+
def calc_inner_product(self, psi1, psi2):
|
|
121
|
+
if self.backend == 'tensor':
|
|
122
|
+
return np.real(psi1.H @ psi2)
|
|
123
|
+
if self.backend == 'statevector':
|
|
124
|
+
return np.real(psi1.dag() * psi2)
|
|
64
125
|
|
|
65
|
-
|
|
66
|
-
|
|
126
|
+
|
|
127
|
+
def apply_kraus(self, psi, kraus_ops, error_tolerance, normalize = True):
|
|
128
|
+
trajectory_weights = np.array([])
|
|
67
129
|
trajectories = np.array([])
|
|
68
|
-
for kraus_MPO in kraus_MPOs:
|
|
69
130
|
|
|
70
|
-
|
|
71
|
-
|
|
131
|
+
# read_quantum_state(psi, N=3)
|
|
132
|
+
for kraus_op in kraus_ops:
|
|
133
|
+
|
|
134
|
+
trajectory = self.apply_op(psi, kraus_op, error_tolerance)
|
|
72
135
|
|
|
73
|
-
|
|
74
|
-
|
|
136
|
+
# trajectory.draw()
|
|
137
|
+
|
|
138
|
+
# this weight is almost arbitrary (can be greater than 1). This is because the Kraus operators themselves are not unitary.
|
|
139
|
+
trajectory_weight = self.calc_magnitude(trajectory)
|
|
140
|
+
# print("trajectory weight:", trajectory_weight)
|
|
141
|
+
# print("trajectory:")
|
|
142
|
+
# read_quantum_state(trajectory, N=3)
|
|
143
|
+
# print()
|
|
144
|
+
|
|
145
|
+
# if trajectory_weight < 1e-25: # Using 1e-25 arbitrarily. Trajectories with weight less than this are pruned.
|
|
146
|
+
# continue
|
|
75
147
|
|
|
76
148
|
if normalize:
|
|
77
|
-
trajectory.
|
|
78
|
-
|
|
149
|
+
# After this, the trajectory is always normalized.
|
|
150
|
+
if trajectory_weight < 1e-25:
|
|
151
|
+
trajectory = None
|
|
152
|
+
trajectory_weight = 0
|
|
153
|
+
else:
|
|
154
|
+
trajectory /= np.sqrt(trajectory_weight)
|
|
155
|
+
|
|
156
|
+
trajectory_weights = np.append(trajectory_weights, trajectory_weight)
|
|
79
157
|
trajectories = np.append(trajectories, trajectory)
|
|
158
|
+
assert len(np.nonzero(trajectory_weights)[0]) > 0, f"All trajectories have zero weight. The input state had magnitude: {self.calc_magnitude(psi)}"
|
|
159
|
+
# print("trajectories:")
|
|
160
|
+
# for i in range(len(trajectories)):
|
|
161
|
+
# read_quantum_state(trajectories[i], N=3)
|
|
80
162
|
|
|
81
|
-
return trajectories,
|
|
163
|
+
return trajectories, trajectory_weights
|
|
82
164
|
|
|
83
165
|
|
|
84
|
-
def cache_trajectree_node(self,
|
|
85
|
-
sorted_indices = np.argsort(
|
|
166
|
+
def cache_trajectree_node(self, trajectory_weights, trajectories):
|
|
167
|
+
sorted_indices = np.argsort(trajectory_weights)
|
|
86
168
|
|
|
87
|
-
# print("
|
|
169
|
+
# print("trajectory_weights", trajectory_weights)
|
|
170
|
+
|
|
171
|
+
if len(self.cache_heap) < self.max_cache_nodes or self.max_cache_nodes == -1:
|
|
172
|
+
push_node = True
|
|
173
|
+
cached_trajectory_indices = sorted_indices[-self.cache_size:]
|
|
174
|
+
else:
|
|
175
|
+
push_node = False
|
|
176
|
+
cached_trajectory_indices = []
|
|
88
177
|
|
|
89
|
-
cached_trajectory_indices = sorted_indices[-self.cache_size:]
|
|
90
178
|
cached_trajectories = np.array(trajectories)[cached_trajectory_indices]
|
|
91
179
|
|
|
92
|
-
new_node = trajectree_node(
|
|
180
|
+
new_node = trajectree_node(trajectory_weights, cached_trajectories, cached_trajectory_indices)
|
|
93
181
|
self.trajectree[len(self.traversed_nodes)][self.traversed_nodes] = new_node
|
|
182
|
+
if len(self.traversed_nodes) == 0: # root node
|
|
183
|
+
# self.graph.create_node(str(self.traversed_nodes), str(self.traversed_nodes)) # Tree lib implementation
|
|
184
|
+
self.graph.add_node(self.traversed_nodes) # NetworkX implementation
|
|
185
|
+
else:
|
|
186
|
+
# self.graph.create_node(str(self.traversed_nodes), str(self.traversed_nodes), parent = str(self.traversed_nodes[:-1]))
|
|
187
|
+
self.graph.add_edge(self.traversed_nodes[:-1], self.traversed_nodes)
|
|
94
188
|
|
|
95
189
|
self.last_cached_node = new_node
|
|
96
190
|
|
|
191
|
+
if push_node:
|
|
192
|
+
heapq.heappush(self.cache_heap, new_node)
|
|
193
|
+
|
|
97
194
|
return cached_trajectory_indices
|
|
98
195
|
|
|
99
196
|
|
|
100
|
-
def discover_trajectree_node(self, psi,
|
|
101
|
-
|
|
102
|
-
trajectories,
|
|
197
|
+
def discover_trajectree_node(self, psi, kraus_ops, error_tolerance, normalize = True, selected_trajectory_index = None):
|
|
198
|
+
# read_quantum_state(psi, N=3)
|
|
199
|
+
trajectories, trajectory_weights = self.apply_kraus(psi, kraus_ops, error_tolerance, normalize)
|
|
200
|
+
|
|
201
|
+
cached_trajectory_indices = self.cache_trajectree_node(trajectory_weights, trajectories) # cached_trajectory_indices is returned only for debugging.
|
|
103
202
|
|
|
104
|
-
|
|
203
|
+
# print("trajectory weights:", trajectory_weights)
|
|
105
204
|
|
|
106
205
|
if selected_trajectory_index == None:
|
|
107
|
-
|
|
206
|
+
try:
|
|
207
|
+
selected_trajectory_index = np.random.choice(a = len(trajectory_weights), p = trajectory_weights/sum(trajectory_weights))
|
|
208
|
+
logger.info("selected index while discovering was: %d", selected_trajectory_index)
|
|
209
|
+
except:
|
|
210
|
+
self.show_graph()
|
|
211
|
+
raise Exception(f"traversed nodes: {self.traversed_nodes} trajectory_weights: {trajectory_weights} invalid")
|
|
212
|
+
|
|
108
213
|
|
|
109
214
|
self.traversed_nodes = self.traversed_nodes + (selected_trajectory_index,)
|
|
110
215
|
|
|
111
216
|
return trajectories[selected_trajectory_index]
|
|
112
217
|
|
|
113
|
-
|
|
114
|
-
|
|
218
|
+
def show_graph(self, use_graphviz = False, node_descriptions = {}):
|
|
219
|
+
"""
|
|
220
|
+
A typical node description would look like: node_descriptions = {(0,0,1): "Root Node", (0,0,1,2): "Child Node"}
|
|
221
|
+
"""
|
|
222
|
+
if not use_graphviz: # You can either use the default layouts in NetworkX:
|
|
223
|
+
pos = nx.bfs_layout(self.graph, (), align = "vertical", scale = 1)
|
|
224
|
+
else: # For a pretty tree like graph, you need to have Graphviz installed:
|
|
225
|
+
pos = graphviz_layout(self.graph, prog="dot") # You need Graphviz installed for this to work
|
|
226
|
+
|
|
227
|
+
plt.figure(figsize=(8, 20))
|
|
228
|
+
nx.draw(self.graph, pos, with_labels=False, node_color='blue', node_size=50, alpha=0.5)
|
|
229
|
+
nx.draw_networkx_labels(self.graph, pos, labels=node_descriptions, font_size=12, font_color='black')
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def query_trajectree(self, psi, kraus_ops, error_tolerance, selected_trajectory_index = None, normalize = True):
|
|
115
233
|
self.skip_unitary = False
|
|
116
234
|
self.cache_unitary = False
|
|
117
|
-
|
|
118
|
-
if
|
|
119
|
-
|
|
235
|
+
# print("entering trajectree magnitude:", self.calc_magnitude(psi))
|
|
236
|
+
if self.cache_size == 0:
|
|
237
|
+
trajectories, trajectory_weights = self.apply_kraus(psi, kraus_ops, error_tolerance, normalize)
|
|
238
|
+
selected_trajectory_index = np.random.choice(a = len(trajectory_weights), p = trajectory_weights/sum(trajectory_weights))
|
|
239
|
+
logger.info("selected index while not caching was: %d", selected_trajectory_index)
|
|
240
|
+
# psi = self.apply_op(psi, self.kraus_channels[len(self.traversed_nodes)].get_ops()[selected_trajectory_index], error_tolerance)
|
|
120
241
|
self.traversed_nodes = self.traversed_nodes + (selected_trajectory_index,)
|
|
121
|
-
return
|
|
242
|
+
return trajectories[selected_trajectory_index]
|
|
122
243
|
|
|
123
244
|
if self.traversed_nodes in self.trajectree[len(self.traversed_nodes)]: # Check if the dictionary at level where the traversal is now, i.e., len(self.traversed_nodes)
|
|
124
245
|
# has the path that the present traversal has taken.
|
|
246
|
+
logger.info(f"selected_trajectory_index: {selected_trajectory_index}")
|
|
125
247
|
node = self.trajectree[len(self.traversed_nodes)][self.traversed_nodes] # If found, index that node into the node object to call the probabilities and trajectories cached inside it.
|
|
126
248
|
if selected_trajectory_index == None:
|
|
127
|
-
|
|
128
|
-
|
|
249
|
+
# print("cached weights:", node.weights, "at:", self.traversed_nodes)
|
|
250
|
+
selected_trajectory_index = np.random.choice(a = len(node.weights), p = node.weights/sum(node.weights)) # The cached nodes have all the weights, but not all the trajectories cache. So, we can select
|
|
251
|
+
logger.info(f"selected index while discovered node found: {selected_trajectory_index} with prob {node.weights[selected_trajectory_index]}") # what trajecory our traversal takes and later see if the actual trajectory has been cached or needs to be retrieved.
|
|
252
|
+
# print("cached weights:", node.weights, "at:", self.traversed_nodes, "selected trajectory:", selected_trajectory_index)
|
|
129
253
|
self.cache_unitary = False # If the node has been found, we do not cache the unitary. The unitary is either already cached or we don't need to cache it at all.
|
|
130
254
|
|
|
131
255
|
if selected_trajectory_index in node.trajectory_indices: # See if the selected trajectory's MPS has been cached or not.
|
|
132
256
|
self.skip_unitary = True # If we're skipping the unitary entirely, it just does not matter whether we cache the unitary or not.
|
|
133
257
|
self.cache_hit += 1
|
|
134
258
|
psi = node.trajectories[np.where(node.trajectory_indices == selected_trajectory_index)[0][0]]
|
|
259
|
+
logger.info(f"selected outcome: {psi}")
|
|
135
260
|
else:
|
|
136
261
|
self.skip_unitary = False # If the trajectory has not been cached, we will have to apply the unitary to it.
|
|
137
262
|
self.cache_partial_hit += 1
|
|
138
|
-
psi =
|
|
139
|
-
|
|
263
|
+
psi = self.apply_op(psi, self.kraus_channels[len(self.traversed_nodes)].get_ops()[selected_trajectory_index], error_tolerance) # If not, simply calculate that trajectory.
|
|
264
|
+
# You don't need to cache it since we have already cached what we had to.
|
|
265
|
+
# print("cache partial hit. new state:" )
|
|
266
|
+
# read_quantum_state(psi, N=3)
|
|
140
267
|
if normalize:
|
|
141
|
-
|
|
142
|
-
|
|
268
|
+
# After this, the trajectory is always normalized.
|
|
269
|
+
psi /= np.sqrt(node.weights[selected_trajectory_index])
|
|
270
|
+
if node in self.cache_heap and len(node.trajectory_indices) < self.cache_size:
|
|
271
|
+
node.trajectories = np.append(node.trajectories, psi)
|
|
272
|
+
# node.trajectories.append(psi)
|
|
273
|
+
node.trajectory_indices = np.append(node.trajectory_indices, selected_trajectory_index)
|
|
143
274
|
|
|
275
|
+
self.traversed_nodes = self.traversed_nodes + (selected_trajectory_index,)
|
|
144
276
|
|
|
145
|
-
|
|
277
|
+
# Here, we check if the node needs to be cached or not.
|
|
278
|
+
node.accesses += 1
|
|
279
|
+
if len(node.trajectory_indices) == 0: # If the node has been discovered but not cached
|
|
280
|
+
if node.accesses > self.cache_heap[0].accesses: # the new node has been accesses more often than the top of the heap, so, we can replace the top of the heap with the new node.
|
|
281
|
+
# print("in here!!!")
|
|
282
|
+
old_node = heapq.heapreplace(self.cache_heap, node)
|
|
283
|
+
del old_node.trajectories
|
|
284
|
+
old_node.trajectories = []
|
|
285
|
+
old_node.trajectory_indices = np.array([])
|
|
286
|
+
heapq.heapify(self.cache_heap)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
else: # If the node has not been discovered, we'll have to find all weights and cache the results.
|
|
290
|
+
# print("exploring new node at:", self.traversed_nodes)
|
|
291
|
+
# read_quantum_state(psi, N=3)
|
|
146
292
|
self.skip_unitary = False
|
|
147
293
|
self.cache_unitary = True
|
|
148
294
|
self.cache_miss += 1
|
|
149
|
-
psi = self.discover_trajectree_node(psi,
|
|
295
|
+
psi = self.discover_trajectree_node(psi, kraus_ops, error_tolerance, normalize, selected_trajectory_index = selected_trajectory_index)
|
|
150
296
|
|
|
297
|
+
# print("exitting trajectree magnitude:", self.calc_magnitude(psi))
|
|
298
|
+
# if psi == None:
|
|
299
|
+
# # raise Exception(f"traversed nodes: {self.traversed_nodes} trajectory_weights: {trajectory_weights} invalid")
|
|
300
|
+
# self.graph.show()
|
|
301
|
+
# raise Exception(f"Psi is None in query trajectree. traversed nodes: {self.traversed_nodes}")
|
|
151
302
|
return psi
|
|
152
303
|
|
|
153
|
-
def apply_unitary_MPOs(self, psi, unitary_MPOs, error_tolerance):
|
|
154
|
-
return tensor_network_apply_op_vec(unitary_MPOs, psi, compress=True, contract = True, cutoff = error_tolerance)
|
|
155
|
-
|
|
156
304
|
|
|
157
305
|
def calculate_density_matrix(self, psi, error_tolerance):
|
|
158
306
|
dm = 0
|
|
159
307
|
trajectree_indices_list = [[]]
|
|
160
308
|
for quantum_channel in self.quantum_channels:
|
|
161
309
|
if quantum_channel.formalism == 'kraus':
|
|
162
|
-
trajectree_indices_list = [[*i, j] for i in trajectree_indices_list for j in range(len(quantum_channel.
|
|
310
|
+
trajectree_indices_list = [[*i, j] for i in trajectree_indices_list for j in range(len(quantum_channel.get_ops()))]
|
|
163
311
|
for trajectree_indices in trajectree_indices_list:
|
|
164
|
-
psi_new_dense = self.perform_simulation(psi, error_tolerance,
|
|
312
|
+
psi_new_dense = self.perform_simulation(psi, error_tolerance, trajectree_indices = trajectree_indices, normalize = False).to_dense()
|
|
165
313
|
dm += psi_new_dense @ psi_new_dense.conj().T
|
|
166
314
|
return dm
|
|
167
315
|
|
|
168
|
-
def
|
|
316
|
+
def unitary_cached_trajectories(self, unitary_op, last_cached_node, error_tolerance):
|
|
317
|
+
for kraus_idx in range(len(last_cached_node.trajectories)):
|
|
318
|
+
if last_cached_node.trajectories[kraus_idx] is not None:
|
|
319
|
+
last_cached_node.trajectories[kraus_idx] = self.apply_op(last_cached_node.trajectories[kraus_idx], unitary_op, error_tolerance)
|
|
320
|
+
if self.calc_magnitude(last_cached_node.trajectories[kraus_idx]) < 1e-25:
|
|
321
|
+
last_cached_node.trajectories[kraus_idx] = None
|
|
322
|
+
|
|
323
|
+
def expectation_cached_trajectories(self, last_cached_node, temp_trajectories):
|
|
169
324
|
for kraus_idx in range(len(last_cached_node.trajectories)):
|
|
170
|
-
|
|
325
|
+
|
|
326
|
+
if last_cached_node.trajectories[kraus_idx] is not None:
|
|
327
|
+
ev = self.calc_inner_product(temp_trajectories[kraus_idx], last_cached_node.trajectories[kraus_idx])
|
|
328
|
+
if ev == None: ev = 0
|
|
329
|
+
last_cached_node.trajectories[kraus_idx] = ev
|
|
330
|
+
# if self.calc_magnitude(last_cached_node.trajectories[kraus_idx]) < 1e-25:
|
|
331
|
+
# last_cached_node.trajectories[kraus_idx] = None
|
|
171
332
|
|
|
172
333
|
|
|
173
334
|
|
|
174
|
-
def
|
|
335
|
+
# def nonunitary_cached_trajectories(self, ops, last_cached_node, error_tolerance):
|
|
336
|
+
# # for kraus_idx in range(len(last_discovered_node.trajectories)):
|
|
337
|
+
# for kraus_idx in range(len(last_cached_node.trajectories)):
|
|
338
|
+
|
|
339
|
+
# if last_cached_node.trajectories[kraus_idx] is not None:
|
|
340
|
+
# last_cached_node.trajectories[kraus_idx] = self.apply_op(last_cached_node.trajectories[kraus_idx], ops, error_tolerance)
|
|
341
|
+
# last_cached_node.weights[kraus_idx] = self.calc_magnitude(last_cached_node.trajectories[kraus_idx])
|
|
342
|
+
|
|
343
|
+
# if last_cached_node.weights[kraus_idx] < 1e-25:
|
|
344
|
+
# last_cached_node.trajectories[kraus_idx] = None
|
|
345
|
+
# last_cached_node.weights[kraus_idx] = 0
|
|
346
|
+
|
|
347
|
+
def get_trajectree_node(self, address):
|
|
348
|
+
return self.trajectree[len(address)][address]
|
|
349
|
+
|
|
350
|
+
# NOTE: USE NORMALIZE = TRUE FOR TRAJECTORY SIMULATIONS. USE NORMALIZE = FALSE FOR DENSITY MATRIX CALCULATIONS.
|
|
351
|
+
def perform_simulation(self, psi, error_tolerance, trajectree_indices = None, normalize = True):
|
|
175
352
|
self.traversed_nodes = ()
|
|
176
353
|
self.skip_unitary = False
|
|
177
354
|
self.cache_unitary = False
|
|
178
355
|
for quantum_channel in self.quantum_channels:
|
|
356
|
+
# print("operation:", quantum_channel.name, "formalism:", quantum_channel.formalism, "traversed nodes:", self.traversed_nodes)
|
|
179
357
|
if quantum_channel.formalism == 'kraus':
|
|
180
|
-
|
|
358
|
+
kraus_ops = quantum_channel.get_ops()
|
|
359
|
+
# print("kraus op:", quantum_channel.name, "number of kraus ops:", len(kraus_ops))
|
|
360
|
+
# print("before kraus ops:")
|
|
361
|
+
# read_quantum_state(psi, N=3)
|
|
181
362
|
if not trajectree_indices == None: # If the list of trajectoery indices is provided, we will use that to traverse the trajectree. The random number generators will not be used.
|
|
182
|
-
psi = self.query_trajectree(psi,
|
|
363
|
+
psi = self.query_trajectree(psi, kraus_ops, error_tolerance, trajectree_indices.pop(0), normalize)
|
|
183
364
|
else: # In this branch, you actually select the trajectory redomly and perform realistic simulations.
|
|
184
|
-
|
|
365
|
+
# read_quantum_state(psi, N=3)
|
|
366
|
+
psi = self.query_trajectree(psi, kraus_ops, error_tolerance, normalize = normalize)
|
|
367
|
+
# print("after kraus ops:")
|
|
368
|
+
# read_quantum_state(psi, N=3)
|
|
185
369
|
|
|
370
|
+
# In this case, the weights are not updated since the operations are just unitary.
|
|
186
371
|
elif quantum_channel.formalism == 'closed' and not self.skip_unitary:
|
|
187
|
-
|
|
372
|
+
# print("closed op:", quantum_channel.name)
|
|
373
|
+
unitary_op = quantum_channel.get_ops()
|
|
188
374
|
|
|
189
|
-
if
|
|
190
|
-
psi = self.
|
|
375
|
+
if self.cache_size == 0: # If we aren't aching the trajectories at all, simply apply the unitary ops to the state.
|
|
376
|
+
psi = self.apply_op(psi, unitary_op, error_tolerance)
|
|
191
377
|
continue
|
|
192
378
|
|
|
193
379
|
last_cached_node = self.trajectree[len(self.traversed_nodes)-1][self.traversed_nodes[:-1]]
|
|
194
380
|
|
|
195
381
|
if self.cache_unitary:
|
|
196
|
-
self.
|
|
382
|
+
self.unitary_cached_trajectories(unitary_op, last_cached_node, error_tolerance)
|
|
383
|
+
|
|
197
384
|
|
|
198
385
|
# This is where we are checking if the psi is cached or not. If it is, simply use the last cached node
|
|
199
|
-
# node to update psi. If not, apply the unitary
|
|
386
|
+
# node to update psi. If not, apply the unitary ops to psi.
|
|
200
387
|
traj_idx = np.where(last_cached_node.trajectory_indices == self.traversed_nodes[-1])
|
|
201
388
|
if traj_idx[0].size > 0:
|
|
202
389
|
psi = last_cached_node.trajectories[traj_idx[0][0]]
|
|
203
390
|
else:
|
|
204
|
-
psi = self.
|
|
391
|
+
psi = self.apply_op(psi, unitary_op, error_tolerance)
|
|
392
|
+
if self.calc_magnitude(psi) < 1e-25:
|
|
393
|
+
psi = None
|
|
205
394
|
|
|
206
395
|
else:
|
|
207
396
|
# print("unitary skipped:", self.traversed_nodes)
|
|
208
397
|
pass
|
|
209
|
-
|
|
398
|
+
if psi == None:
|
|
399
|
+
# print("Nothing returned")
|
|
400
|
+
return 0
|
|
401
|
+
# raise Exception("Psi is None")
|
|
402
|
+
# read_quantum_state(psi, N=6)
|
|
403
|
+
# print("next operation:")
|
|
404
|
+
|
|
405
|
+
if self.calc_expectation:
|
|
406
|
+
# print("state before expectation calculation:", psi)
|
|
407
|
+
if not self.skip_unitary:
|
|
408
|
+
|
|
409
|
+
# This is where we are checking if the psi is cached or not. If it is, simply use the last cached node
|
|
410
|
+
# node to update psi. If not, apply the unitary ops to psi.
|
|
411
|
+
last_cached_node = self.get_trajectree_node(self.traversed_nodes[:-1])
|
|
412
|
+
traj_idx = np.where(last_cached_node.trajectory_indices == self.traversed_nodes[-1])
|
|
210
413
|
|
|
414
|
+
temp_trajectories = copy.deepcopy(last_cached_node.trajectories)
|
|
415
|
+
|
|
416
|
+
for quantum_channel in self.observable_ops:
|
|
417
|
+
observable_op = quantum_channel.get_ops()
|
|
418
|
+
|
|
419
|
+
if self.cache_unitary:
|
|
420
|
+
self.unitary_cached_trajectories(observable_op, last_cached_node, error_tolerance)
|
|
421
|
+
|
|
422
|
+
if traj_idx[0].size > 0:
|
|
423
|
+
psi = last_cached_node.trajectories[traj_idx[0][0]]
|
|
424
|
+
else:
|
|
425
|
+
psi = self.apply_op(psi, observable_op, error_tolerance)
|
|
426
|
+
if self.calc_magnitude(psi) < 1e-25:
|
|
427
|
+
psi = None
|
|
428
|
+
|
|
429
|
+
# if traj_idx[0].size > 0:
|
|
430
|
+
self.expectation_cached_trajectories(last_cached_node, temp_trajectories)
|
|
431
|
+
psi = last_cached_node.trajectories[traj_idx[0][0]]
|
|
432
|
+
# if psi != None:
|
|
433
|
+
# last_cached_node.trajectories[traj_idx[0][0]] = temp_state.H @ psi
|
|
434
|
+
# else:
|
|
435
|
+
# last_cached_node.trajectories[traj_idx[0][0]] = 0
|
|
436
|
+
if psi == None: return 0
|
|
211
437
|
return psi
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Trajectree
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Trajectree is a quantum trajectory theory and tensor network based quantum optics simulator.
|
|
5
5
|
Author-email: Ansh Singal <asingal@u.northwestern.edu>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -12,6 +12,7 @@ License-File: LICENSE
|
|
|
12
12
|
Requires-Dist: matplotlib
|
|
13
13
|
Requires-Dist: qutip
|
|
14
14
|
Requires-Dist: trajectree-quimb
|
|
15
|
+
Requires-Dist: treelib
|
|
15
16
|
Dynamic: license-file
|
|
16
17
|
|
|
17
18
|
Trajectree is a quantum trajectory theory and tensor network based quantum optics simulator.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
trajectree/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
trajectree/trajectory.py,sha256=Bjj3_GZPhmTDLeMvT2vq7JAgI7cq7ur37yj9ewVUJeg,23227
|
|
3
|
+
trajectree/experimental/sparse.py,sha256=05aFsMcBSCUoBop1LJG_lB5kTULRSYhZhGT9RD-UH4Q,4851
|
|
4
|
+
trajectree/fock_optics/devices.py,sha256=3VALWdQ4NCNKXGMdt1d77GKKUs1E8Zw1vH8INtHvkpg,6017
|
|
5
|
+
trajectree/fock_optics/light_sources.py,sha256=VfoTXp-WNQbhrAdke6S-l_iptHS9UXbV7mbrH_BUdkk,5210
|
|
6
|
+
trajectree/fock_optics/measurement.py,sha256=e549Cr2QirfBZWMwQlVwNPhtTgWv7NMieZA77OyS8V8,17592
|
|
7
|
+
trajectree/fock_optics/noise_models.py,sha256=HMZV3D-_aWKPBk_HhkD9neqMgo84-cYYcgiVj_LOT8U,6528
|
|
8
|
+
trajectree/fock_optics/optical_quant_info.py,sha256=xnVbPdj7ARrve1ka__Jl8MoZiWJQ8jpHaTKc9-1i-uE,8484
|
|
9
|
+
trajectree/fock_optics/outputs.py,sha256=-Ncs6qdipUDqdGCSuw7trNfoq_bORwLOebyOOuXQX3g,2475
|
|
10
|
+
trajectree/fock_optics/utils.py,sha256=iIz4DUluzpmW_sk1IlBeNYCiWZd6_eTi6I1p93DGt8U,7172
|
|
11
|
+
trajectree/quant_info/circuit.py,sha256=7AmhSoJpiCT92duSwgVxXbFCDBF0JJeKy1A3CO_SlPc,13396
|
|
12
|
+
trajectree/quant_info/noise_models.py,sha256=anciN3d5EX_loVV8nfwNscq4PG4WZW6NbvqvLfinaPI,643
|
|
13
|
+
trajectree/sequence/swap.py,sha256=ASkVJp6k5ZWa1YfWy2Qb1Ttf3bkFWX8kWzEt9NtrNC4,6153
|
|
14
|
+
trajectree-0.0.5.dist-info/licenses/LICENSE,sha256=7EI8xVBu6h_7_JlVw-yPhhOZlpY9hP8wal7kHtqKT_E,1074
|
|
15
|
+
trajectree-0.0.5.dist-info/METADATA,sha256=AeNhG5JepuKp8sTJCyZzaOfH5G7GwIW80tobF8mnuhc,634
|
|
16
|
+
trajectree-0.0.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
17
|
+
trajectree-0.0.5.dist-info/top_level.txt,sha256=6x9i8aAZcVn5tZ9J-2IVQMTGdn4bw6DiSd8pR3v4VR8,11
|
|
18
|
+
trajectree-0.0.5.dist-info/RECORD,,
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
trajectree/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
trajectree/optical_quant_info.py,sha256=27LWg0jIWT2ZXWVK8HEd-2gqAJl0GzmsyYi7sxyYzvA,7589
|
|
3
|
-
trajectree/trajectory.py,sha256=mKyDZNDRyADouHL4Rf-JU3NYhEynVpUZmZgTl0EZLDE,11332
|
|
4
|
-
trajectree/experimental/sparse.py,sha256=05aFsMcBSCUoBop1LJG_lB5kTULRSYhZhGT9RD-UH4Q,4851
|
|
5
|
-
trajectree/fock_optics/devices.py,sha256=1gwl4AIktqu3obXmNhR1EoKR4iWulP4LfWD4xenKfx8,2130
|
|
6
|
-
trajectree/fock_optics/light_sources.py,sha256=qzBevi1Uqcxb6KuYMuVQ9AkJbHCCigBf21N6s01x-V0,4823
|
|
7
|
-
trajectree/fock_optics/measurement.py,sha256=n5syDcy8i_jYmPzAOV26wDg-Z98X0w_y5MmCBIYn3Gk,13222
|
|
8
|
-
trajectree/fock_optics/noise_models.py,sha256=nyU_jNqjJkkcafq7vIXI8IRDiwthiOr4ZQqf55knZMM,1895
|
|
9
|
-
trajectree/fock_optics/outputs.py,sha256=EMhs2gCwz6giLRyM09zxOLrpdbEuSG9axB4FFw6fuF4,2475
|
|
10
|
-
trajectree/fock_optics/utils.py,sha256=iIz4DUluzpmW_sk1IlBeNYCiWZd6_eTi6I1p93DGt8U,7172
|
|
11
|
-
trajectree/sequence/swap.py,sha256=_6KQyjGDp0Nf07GZVMi52CmFlDKanUk3hXe5kMDetm8,4382
|
|
12
|
-
trajectree-0.0.4.dist-info/licenses/LICENSE,sha256=7EI8xVBu6h_7_JlVw-yPhhOZlpY9hP8wal7kHtqKT_E,1074
|
|
13
|
-
trajectree-0.0.4.dist-info/METADATA,sha256=8y0dK7gLZyJ_uuRBHf8QVBE4REVOkmdSSwcBxC7MKvU,611
|
|
14
|
-
trajectree-0.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
-
trajectree-0.0.4.dist-info/top_level.txt,sha256=6x9i8aAZcVn5tZ9J-2IVQMTGdn4bw6DiSd8pR3v4VR8,11
|
|
16
|
-
trajectree-0.0.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|