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/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, unitary_MPOs = None, name = "quantum_channel"):
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
- # Calculate the MPOs of the Kraus operators
15
- self.kraus_MPOs = quantum_channel.find_quantum_channels_MPOs(kraus_ops_tuple, N, num_modes)
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.unitary_MPOs = unitary_MPOs
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 get_MPOs(self):
40
+ def get_ops(self):
20
41
  if self.formalism == 'closed':
21
- return self.unitary_MPOs
42
+ return self.unitary_op
22
43
  elif self.formalism == 'kraus':
23
- return self.kraus_MPOs
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, probs, trajectories, trajectory_indices):
43
- self.probs = probs
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 = 2):
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
- def apply_kraus(self, psi, kraus_MPOs, error_tolerance, normalize = True):
66
- trajectory_probs = np.array([])
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
- trajectory = tensor_network_apply_op_vec(kraus_MPO, psi, compress=True, contract = True, cutoff = error_tolerance)
71
- trajectory_prob = np.real(trajectory.H @ trajectory)
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
- if trajectory_prob < 1e-25: # Using 1e-25 arbitrarily. Trajectories with probability less than this are pruned.
74
- continue
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.normalize()
78
- trajectory_probs = np.append(trajectory_probs, trajectory_prob)
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, trajectory_probs
163
+ return trajectories, trajectory_weights
82
164
 
83
165
 
84
- def cache_trajectree_node(self, trajectory_probs, trajectories):
85
- sorted_indices = np.argsort(trajectory_probs)
166
+ def cache_trajectree_node(self, trajectory_weights, trajectories):
167
+ sorted_indices = np.argsort(trajectory_weights)
86
168
 
87
- # print("trajectory_probs", trajectory_probs)
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(trajectory_probs, cached_trajectories, cached_trajectory_indices)
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, kraus_MPOs, error_tolerance, normalize = True, selected_trajectory_index = None):
101
-
102
- trajectories, trajectory_probs = self.apply_kraus(psi, kraus_MPOs, error_tolerance, normalize)
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
- cached_trajectory_indices = self.cache_trajectree_node(trajectory_probs, trajectories) # cached_trajectory_indices is returned only for debugging.
203
+ # print("trajectory weights:", trajectory_weights)
105
204
 
106
205
  if selected_trajectory_index == None:
107
- selected_trajectory_index = np.random.choice(a = len(trajectory_probs), p = trajectory_probs/sum(trajectory_probs))
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
- def query_trajectree(self, psi, kraus_MPOs, error_tolerance, cache = True, selected_trajectory_index = None, normalize = True):
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 cache == False:
119
- psi = tensor_network_apply_op_vec(self.kraus_channels[len(self.traversed_nodes)].get_MPOs()[selected_trajectory_index], psi, compress=True, contract = True, cutoff = error_tolerance)
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 psi
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
- selected_trajectory_index = np.random.choice(a = len(node.probs), p = node.probs/sum(node.probs)) # The cached nodes have all the probabilities, but not all the trajectories cache. So, we can select
128
- # what trajecory our traversal takes and later see if the actual trajectory has been cached or needs to be retrieved.
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 = tensor_network_apply_op_vec(self.kraus_channels[len(self.traversed_nodes)].get_MPOs()[selected_trajectory_index], psi, compress=True, contract = True, cutoff = error_tolerance) # If not, simply calculate that trajectory.
139
- # You don't need to cache it since we have already cached what we had to.
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
- psi.normalize()
142
- self.traversed_nodes = self.traversed_nodes + (selected_trajectory_index,)
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
- else: # If the node has not been discovered, we'll have to find all probabilities and cache the results.
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, kraus_MPOs, error_tolerance, normalize, selected_trajectory_index = selected_trajectory_index)
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.get_MPOs()))]
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, cache = True, trajectree_indices = trajectree_indices, normalize = False).to_dense()
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 update_cached_node(self, unitary_MPOs, last_cached_node, error_tolerance):
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
- last_cached_node.trajectories[kraus_idx] = self.apply_unitary_MPOs(last_cached_node.trajectories[kraus_idx], unitary_MPOs, error_tolerance)
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 perform_simulation(self, psi, error_tolerance, cache = True, trajectree_indices = None, normalize = True):
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
- kraus_MPOs = quantum_channel.get_MPOs()
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, kraus_MPOs, error_tolerance, cache, trajectree_indices.pop(0), normalize)
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
- psi = self.query_trajectree(psi, kraus_MPOs, error_tolerance, cache = cache, normalize = normalize)
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
- unitary_MPOs = quantum_channel.get_MPOs()
372
+ # print("closed op:", quantum_channel.name)
373
+ unitary_op = quantum_channel.get_ops()
188
374
 
189
- if not cache: # If we aren't aching the trajectories at all, simply apply the unitary MPOs to the state.
190
- psi = self.apply_unitary_MPOs(psi, unitary_MPOs, error_tolerance)
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.update_cached_node(unitary_MPOs, last_cached_node, error_tolerance)
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 MPOs to psi.
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.apply_unitary_MPOs(psi, unitary_MPOs, error_tolerance)
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
- pass
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.4
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,