emu-mps 1.2.6__py3-none-any.whl → 1.2.7__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.
emu_mps/__init__.py CHANGED
@@ -35,4 +35,4 @@ __all__ = [
35
35
  "SecondMomentOfEnergy",
36
36
  ]
37
37
 
38
- __version__ = "1.2.6"
38
+ __version__ = "1.2.7"
emu_mps/mps.py CHANGED
@@ -189,11 +189,58 @@ class MPS(State):
189
189
  """
190
190
  self.orthogonalize(0)
191
191
 
192
- num_qubits = len(self.factors)
193
- rnd_matrix = torch.rand(num_shots, num_qubits)
194
- bitstrings = Counter(
195
- self._sample_implementation(rnd_matrix[x, :]) for x in range(num_shots)
196
- )
192
+ rnd_matrix = torch.rand(num_shots, self.num_sites).to(self.factors[0].device)
193
+
194
+ bitstrings: Counter[str] = Counter()
195
+
196
+ # Shots are performed in batches.
197
+ # Larger max_batch_size is faster but uses more memory.
198
+ max_batch_size = 32
199
+
200
+ shots_done = 0
201
+ while shots_done < num_shots:
202
+ batch_size = min(max_batch_size, num_shots - shots_done)
203
+ batched_accumulator = torch.ones(
204
+ batch_size, 1, dtype=torch.complex128, device=self.factors[0].device
205
+ )
206
+
207
+ batch_outcomes = torch.empty(batch_size, self.num_sites, dtype=torch.bool)
208
+
209
+ for qubit, factor in enumerate(self.factors):
210
+ batched_accumulator = torch.tensordot(
211
+ batched_accumulator.to(factor.device), factor, dims=1
212
+ )
213
+
214
+ # Probability of measuring qubit == 0 for each shot in the batch
215
+ probas = (
216
+ torch.linalg.vector_norm(batched_accumulator[:, 0, :], dim=1) ** 2
217
+ )
218
+
219
+ outcomes = (
220
+ rnd_matrix[shots_done : shots_done + batch_size, qubit].to(
221
+ factor.device
222
+ )
223
+ > probas
224
+ )
225
+ batch_outcomes[:, qubit] = outcomes
226
+
227
+ # Batch collapse qubit
228
+ tmp = torch.stack((~outcomes, outcomes), dim=1).to(dtype=torch.complex128)
229
+
230
+ batched_accumulator = (
231
+ torch.tensordot(batched_accumulator, tmp, dims=([1], [1]))
232
+ .diagonal(dim1=0, dim2=2)
233
+ .transpose(1, 0)
234
+ )
235
+ batched_accumulator /= torch.sqrt(
236
+ (~outcomes) * probas + outcomes * (1 - probas)
237
+ ).unsqueeze(1)
238
+
239
+ shots_done += batch_size
240
+
241
+ for outcome in batch_outcomes:
242
+ bitstrings.update(["".join("0" if x == 0 else "1" for x in outcome)])
243
+
197
244
  if p_false_neg > 0 or p_false_pos > 0:
198
245
  bitstrings = apply_measurement_errors(
199
246
  bitstrings,
@@ -214,42 +261,6 @@ class MPS(State):
214
261
  torch.linalg.norm(self.factors[orthogonality_center].to("cpu")).item()
215
262
  )
216
263
 
217
- def _sample_implementation(self, rnd_vector: torch.Tensor) -> str:
218
- """
219
- Samples this MPS once, returning the resulting bitstring.
220
- """
221
- assert rnd_vector.shape == (self.num_sites,)
222
- assert self.orthogonality_center == 0
223
-
224
- num_qubits = len(self.factors)
225
-
226
- bitstring = ""
227
- acc_mps_j: torch.Tensor = self.factors[0]
228
-
229
- for qubit in range(num_qubits):
230
- # comp_basis is a projector: 0 is for ket |0> and 1 for ket |1>
231
- comp_basis = 0 # check if the qubit is in |0>
232
- # Measure the qubit j by applying the projector onto nth comp basis state
233
- tensorj_projected_n = acc_mps_j[:, comp_basis, :]
234
- probability_n = (tensorj_projected_n.norm() ** 2).item()
235
-
236
- if rnd_vector[qubit] > probability_n:
237
- # the qubit is in |1>
238
- comp_basis = 1
239
- tensorj_projected_n = acc_mps_j[:, comp_basis, :]
240
- probability_n = 1 - probability_n
241
-
242
- bitstring += str(comp_basis)
243
- if qubit < num_qubits - 1:
244
- acc_mps_j = torch.tensordot(
245
- tensorj_projected_n.to(device=self.factors[qubit + 1].device),
246
- self.factors[qubit + 1],
247
- dims=1,
248
- )
249
- acc_mps_j /= math.sqrt(probability_n)
250
-
251
- return bitstring
252
-
253
264
  def inner(self, other: State) -> float | complex:
254
265
  """
255
266
  Compute the inner product between this state and other.
@@ -54,7 +54,8 @@ class MPSBackendImpl:
54
54
 
55
55
  def __init__(self, mps_config: MPSConfig, pulser_data: PulserData):
56
56
  self.config = mps_config
57
- self.target_time = float(self.config.dt)
57
+ self.target_times = pulser_data.target_times
58
+ self.target_time = self.target_times[1]
58
59
  self.pulser_data = pulser_data
59
60
  self.qubit_count = pulser_data.qubit_count
60
61
  assert self.qubit_count >= 2
@@ -94,18 +95,16 @@ class MPSBackendImpl:
94
95
  return pathlib.Path(os.getcwd()) / (autosave_prefix + str(uuid.uuid1()) + ".dat")
95
96
 
96
97
  def init_dark_qubits(self) -> None:
97
- has_state_preparation_error: bool = (
98
+ # has_state_preparation_error
99
+ if (
98
100
  self.config.noise_model is not None
99
101
  and self.config.noise_model.state_prep_error > 0.0
100
- )
101
-
102
- self.well_prepared_qubits_filter = (
103
- pick_well_prepared_qubits(
102
+ ):
103
+ self.well_prepared_qubits_filter = pick_well_prepared_qubits(
104
104
  self.config.noise_model.state_prep_error, self.qubit_count
105
105
  )
106
- if has_state_preparation_error
107
- else None
108
- )
106
+ else:
107
+ self.well_prepared_qubits_filter = None
109
108
 
110
109
  if self.well_prepared_qubits_filter is not None:
111
110
  self.qubit_count = sum(1 for x in self.well_prepared_qubits_filter if x)
@@ -152,9 +151,11 @@ class MPSBackendImpl:
152
151
  too many factors are put in the Hamiltonian
153
152
  """
154
153
  self.hamiltonian = make_H(
155
- interaction_matrix=self.masked_interaction_matrix
156
- if self.is_masked
157
- else self.full_interaction_matrix,
154
+ interaction_matrix=(
155
+ self.masked_interaction_matrix
156
+ if self.is_masked
157
+ else self.full_interaction_matrix
158
+ ),
158
159
  hamiltonian_type=self.hamiltonian_type,
159
160
  num_gpus_to_use=self.config.num_gpus_to_use,
160
161
  )
@@ -176,6 +177,12 @@ class MPSBackendImpl:
176
177
  self.right_baths = right_baths(self.state, self.hamiltonian, final_qubit=2)
177
178
  assert len(self.right_baths) == self.qubit_count - 1
178
179
 
180
+ def get_current_right_bath(self) -> torch.Tensor:
181
+ return self.right_baths[-1]
182
+
183
+ def get_current_left_bath(self) -> torch.Tensor:
184
+ return self.left_baths[-1]
185
+
179
186
  def init(self) -> None:
180
187
  self.init_dark_qubits()
181
188
  self.init_initial_state(self.config.initial_state)
@@ -196,7 +203,7 @@ class MPSBackendImpl:
196
203
  """
197
204
  assert 1 <= len(indices) <= 2
198
205
 
199
- baths = (self.left_baths[-1], self.right_baths[-1])
206
+ baths = (self.get_current_left_bath(), self.get_current_right_bath())
200
207
 
201
208
  if len(indices) == 1:
202
209
  assert orth_center_right is None
@@ -268,10 +275,10 @@ class MPSBackendImpl:
268
275
  )
269
276
  self.left_baths.append(
270
277
  new_left_bath(
271
- self.left_baths[-1],
278
+ self.get_current_left_bath(),
272
279
  self.state.factors[self.tdvp_index],
273
280
  self.hamiltonian.factors[self.tdvp_index],
274
- )
281
+ ).to(self.state.factors[self.tdvp_index + 1].device)
275
282
  )
276
283
  self._evolve(self.tdvp_index + 1, dt=-delta_time / 2)
277
284
  self.right_baths.pop()
@@ -297,10 +304,10 @@ class MPSBackendImpl:
297
304
  assert self.tdvp_index <= self.qubit_count - 2
298
305
  self.right_baths.append(
299
306
  new_right_bath(
300
- self.right_baths[-1],
307
+ self.get_current_right_bath(),
301
308
  self.state.factors[self.tdvp_index + 1],
302
309
  self.hamiltonian.factors[self.tdvp_index + 1],
303
- )
310
+ ).to(self.state.factors[self.tdvp_index].device)
304
311
  )
305
312
  if not self.has_lindblad_noise:
306
313
  # Free memory because it won't be used anymore
@@ -333,7 +340,6 @@ class MPSBackendImpl:
333
340
  def timestep_complete(self) -> None:
334
341
  self.fill_results()
335
342
  self.timestep_index += 1
336
- self.target_time = float((self.timestep_index + 1) * self.config.dt)
337
343
  if self.is_masked and self.current_time >= self.slm_end_time:
338
344
  self.is_masked = False
339
345
  self.hamiltonian = make_H(
@@ -343,6 +349,7 @@ class MPSBackendImpl:
343
349
  )
344
350
 
345
351
  if not self.is_finished():
352
+ self.target_time = self.target_times[self.timestep_index + 1]
346
353
  update_H(
347
354
  hamiltonian=self.hamiltonian,
348
355
  omega=self.omega[self.timestep_index, :],
@@ -479,12 +486,15 @@ class NoisyMPSBackendImpl(MPSBackendImpl):
479
486
  self.aggregated_lindblad_ops = stacked.conj().transpose(1, 2) @ stacked
480
487
 
481
488
  self.lindblad_noise = compute_noise_from_lindbladians(self.lindblad_ops)
482
- self.jump_threshold = random.random()
489
+
490
+ def set_jump_threshold(self, bound: float) -> None:
491
+ self.jump_threshold = random.uniform(0.0, bound)
483
492
  self.norm_gap_before_jump = self.state.norm() ** 2 - self.jump_threshold
484
493
 
485
494
  def init(self) -> None:
486
- super().init()
487
495
  self.init_lindblad_noise()
496
+ super().init()
497
+ self.set_jump_threshold(1.0)
488
498
 
489
499
  def tdvp_complete(self) -> None:
490
500
  previous_time = self.current_time
@@ -515,7 +525,7 @@ class NoisyMPSBackendImpl(MPSBackendImpl):
515
525
 
516
526
  if self.root_finder.is_converged(tolerance=1):
517
527
  self.do_random_quantum_jump()
518
- self.target_time = (self.timestep_index + 1) * self.config.dt
528
+ self.target_time = self.target_times[self.timestep_index + 1]
519
529
  self.root_finder = None
520
530
  else:
521
531
  self.target_time = self.root_finder.get_next_abscissa()
@@ -534,11 +544,11 @@ class NoisyMPSBackendImpl(MPSBackendImpl):
534
544
  self.state.apply(jumped_qubit_index, jump_operator)
535
545
  self.state.orthogonalize(0)
536
546
  self.state *= 1 / self.state.norm()
547
+ self.init_baths()
537
548
 
538
549
  norm_after_normalizing = self.state.norm()
539
550
  assert math.isclose(norm_after_normalizing, 1, abs_tol=1e-10)
540
- self.jump_threshold = random.uniform(0.0, norm_after_normalizing**2)
541
- self.norm_gap_before_jump = norm_after_normalizing**2 - self.jump_threshold
551
+ self.set_jump_threshold(norm_after_normalizing**2)
542
552
 
543
553
  def fill_results(self) -> None:
544
554
  # Remove the noise from self.hamiltonian for the callbacks.
emu_mps/tdvp.py CHANGED
@@ -117,6 +117,7 @@ def evolve_pair(
117
117
 
118
118
  left_ham_factor = left_ham_factor.to(left_device)
119
119
  right_ham_factor = right_ham_factor.to(left_device)
120
+ right_bath = right_bath.to(left_device)
120
121
 
121
122
  combined_hamiltonian_factors = (
122
123
  torch.tensordot(left_ham_factor, right_ham_factor, dims=1)
emu_mps/utils.py CHANGED
@@ -130,13 +130,18 @@ def extended_mps_factors(
130
130
  bond_dimension = mps_factors[factor_index].shape[2]
131
131
  factor_index += 1
132
132
  elif factor_index == len(mps_factors):
133
- factor = torch.zeros(bond_dimension, 2, 1, dtype=torch.complex128)
133
+ factor = torch.zeros(
134
+ bond_dimension, 2, 1, dtype=torch.complex128
135
+ ) # FIXME: assign device
134
136
  factor[:, 0, :] = torch.eye(bond_dimension, 1)
135
137
  bond_dimension = 1
136
138
  result.append(factor)
137
139
  else:
138
140
  factor = torch.zeros(
139
- bond_dimension, 2, bond_dimension, dtype=torch.complex128
141
+ bond_dimension,
142
+ 2,
143
+ bond_dimension,
144
+ dtype=torch.complex128, # FIXME: assign device
140
145
  )
141
146
  factor[:, 0, :] = torch.eye(bond_dimension, bond_dimension)
142
147
  result.append(factor)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emu-mps
3
- Version: 1.2.6
3
+ Version: 1.2.7
4
4
  Summary: Pasqal MPS based pulse emulator built on PyTorch
5
5
  Project-URL: Documentation, https://pasqal-io.github.io/emulators/
6
6
  Project-URL: Repository, https://github.com/pasqal-io/emulators
@@ -25,7 +25,7 @@ Classifier: Programming Language :: Python :: 3.10
25
25
  Classifier: Programming Language :: Python :: Implementation :: CPython
26
26
  Classifier: Programming Language :: Python :: Implementation :: PyPy
27
27
  Requires-Python: >=3.10
28
- Requires-Dist: emu-base==1.2.6
28
+ Requires-Dist: emu-base==1.2.7
29
29
  Description-Content-Type: text/markdown
30
30
 
31
31
  <div align="center">
@@ -1,17 +1,17 @@
1
- emu_mps/__init__.py,sha256=KphiJUrBt3vjS0e2_tQejeB12X3YwqrEhxwfF1QRXQE,646
1
+ emu_mps/__init__.py,sha256=KSTZWIZSKHhjt0yt8-fS23rFFcFVQciNiXBgHS0pnHU,646
2
2
  emu_mps/algebra.py,sha256=ngPtTH-j2ZCBWoaJZXlkUyIlug7dY7Q92gzfnRlpPMA,5485
3
3
  emu_mps/hamiltonian.py,sha256=LcBs6CKBb643a1e9AAVtQoUfa4L_0dIhLOKecx5OOWs,15864
4
4
  emu_mps/mpo.py,sha256=H5vkJvz4AfXfnPbvgWznBWpMUO8LnGL3_NAP3IhxZzQ,8740
5
- emu_mps/mps.py,sha256=CcduX2BC4ArBwwF41w_FQCa6wqmynegQQC9zkK0EmgQ,17826
5
+ emu_mps/mps.py,sha256=J0I4oQP_F1woEKmnOqnXPOWxx2Y1addxNjosL3yhYAY,18214
6
6
  emu_mps/mps_backend.py,sha256=6fVaq-D4xyicYRjGjhqMEieC7---90LpfpbV7ZD7zkQ,2192
7
- emu_mps/mps_backend_impl.py,sha256=Rp7WbT0Dto1G4ArqSLEzSHkJAuMIEZfUqUMP9Dyz31M,20838
7
+ emu_mps/mps_backend_impl.py,sha256=XT2HccHWd6Y1gIAs070pBxjPUPIHBl-hFCuqXJaPS-E,21256
8
8
  emu_mps/mps_config.py,sha256=ydKN0OOaWCBcNd9V-4CU5ZZ4w1FRT-bbKyZQD2WCaME,3317
9
9
  emu_mps/noise.py,sha256=h4X2EFjoC_Ok0gZ8I9wN77RANXaVehTBbjkcbY_GAmY,784
10
- emu_mps/tdvp.py,sha256=TH4CcBNczRURXYGPXndWKDs0jWXz_x9ozM961uGiSOw,5711
11
- emu_mps/utils.py,sha256=n9BcpuIz4Kl6EYlATaK8TKsyF-T7FTwbBo6KSAQYzl8,8066
10
+ emu_mps/tdvp.py,sha256=pIQ2NXA2Mrkp3elhqQbX3pdJVbtKkG3c5r9fFlJo7pI,5755
11
+ emu_mps/utils.py,sha256=BqRJYAcXqprtZVJ0V_j954ON2bhTdtZiaTojsYyrWrg,8193
12
12
  emu_mps/optimatrix/__init__.py,sha256=lHWYNeThHp57ZrwTwXd0p8bNvcCv0w_AZ31iCWflBUo,226
13
13
  emu_mps/optimatrix/optimiser.py,sha256=cVMdm2r_4OpbthcQuFMrJ9rNR9WEJRga9c_lHrJFkhw,6687
14
14
  emu_mps/optimatrix/permutations.py,sha256=JRXGont8B4QgbkV9CzrA0w7uzLgBrmZ1J9aa0G52hPo,1979
15
- emu_mps-1.2.6.dist-info/METADATA,sha256=iUObGpVmQN3Y6GM_ScEFmFlY80htsWrIjo53ER2Flvw,3505
16
- emu_mps-1.2.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- emu_mps-1.2.6.dist-info/RECORD,,
15
+ emu_mps-1.2.7.dist-info/METADATA,sha256=-yHfBZrLmNsmc-tA-Yb0KfmxULGgVrcLVUbx4F37oA4,3505
16
+ emu_mps-1.2.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ emu_mps-1.2.7.dist-info/RECORD,,