emu-mps 2.2.1__py3-none-any.whl → 2.3.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.
emu_mps/__init__.py CHANGED
@@ -37,4 +37,4 @@ __all__ = [
37
37
  "EntanglementEntropy",
38
38
  ]
39
39
 
40
- __version__ = "2.2.1"
40
+ __version__ = "2.3.0"
emu_mps/mps.py CHANGED
@@ -7,11 +7,10 @@ from typing import List, Optional, Sequence, TypeVar, Mapping
7
7
  import torch
8
8
 
9
9
  from pulser.backend.state import State, Eigenstate
10
- from emu_base import DEVICE_COUNT
10
+ from emu_base import DEVICE_COUNT, apply_measurement_errors
11
11
  from emu_mps import MPSConfig
12
12
  from emu_mps.algebra import add_factors, scale_factors
13
13
  from emu_mps.utils import (
14
- apply_measurement_errors,
15
14
  assign_devices,
16
15
  truncate_impl,
17
16
  tensor_trace,
@@ -26,12 +26,13 @@ from emu_mps.hamiltonian import make_H, update_H
26
26
  from emu_mps.mpo import MPO
27
27
  from emu_mps.mps import MPS
28
28
  from emu_mps.mps_config import MPSConfig
29
- from emu_mps.noise import pick_well_prepared_qubits
29
+ from emu_base.noise import pick_dark_qubits
30
30
  from emu_base.jump_lindblad_operators import compute_noise_from_lindbladians
31
31
  import emu_mps.optimatrix as optimat
32
32
  from emu_mps.solver_utils import (
33
33
  evolve_pair,
34
34
  evolve_single,
35
+ minimize_energy_pair,
35
36
  new_right_bath,
36
37
  right_baths,
37
38
  )
@@ -102,11 +103,11 @@ class MPSBackendImpl:
102
103
  current_time: float = (
103
104
  0.0 # While dt is an integer, noisy collapse can happen at non-integer times.
104
105
  )
105
- well_prepared_qubits_filter: Optional[list[bool]]
106
+ well_prepared_qubits_filter: Optional[torch.Tensor]
106
107
  hamiltonian: MPO
107
108
  state: MPS
108
109
  right_baths: list[torch.Tensor]
109
- tdvp_index: int
110
+ sweep_index: int
110
111
  swipe_direction: SwipeDirection
111
112
  timestep_index: int
112
113
  target_time: float
@@ -142,7 +143,7 @@ class MPSBackendImpl:
142
143
  self.left_baths: list[torch.Tensor]
143
144
  self.time = time.time()
144
145
  self.swipe_direction = SwipeDirection.LEFT_TO_RIGHT
145
- self.tdvp_index = 0
146
+ self.sweep_index = 0
146
147
  self.timestep_index = 0
147
148
  self.results = Results(
148
149
  atom_order=optimat.permute_tuple(
@@ -192,8 +193,10 @@ class MPSBackendImpl:
192
193
  def init_dark_qubits(self) -> None:
193
194
  # has_state_preparation_error
194
195
  if self.config.noise_model.state_prep_error > 0.0:
195
- self.well_prepared_qubits_filter = pick_well_prepared_qubits(
196
- self.config.noise_model.state_prep_error, self.qubit_count
196
+ self.well_prepared_qubits_filter = torch.logical_not(
197
+ pick_dark_qubits(
198
+ self.config.noise_model.state_prep_error, self.qubit_count
199
+ )
197
200
  )
198
201
  else:
199
202
  self.well_prepared_qubits_filter = None
@@ -241,7 +244,7 @@ class MPSBackendImpl:
241
244
 
242
245
  initial_state = MPS(
243
246
  # Deep copy of every tensor of the initial state.
244
- [f.clone().detach() for f in initial_state.factors],
247
+ [f.detach().clone() for f in initial_state.factors],
245
248
  config=self.config,
246
249
  num_gpus_to_use=self.config.num_gpus_to_use,
247
250
  eigenstates=initial_state.eigenstates,
@@ -347,7 +350,7 @@ class MPSBackendImpl:
347
350
  """
348
351
  Do one unit of simulation work given the current state.
349
352
  Update the state accordingly.
350
- The state of the simulation is stored in self.tdvp_index and self.swipe_direction.
353
+ The state of the simulation is stored in self.sweep_index and self.swipe_direction.
351
354
  """
352
355
  if self.is_finished():
353
356
  return
@@ -358,79 +361,79 @@ class MPSBackendImpl:
358
361
  if 1 <= self.qubit_count <= 2:
359
362
  # Corner case: only 1 or 2 qubits
360
363
  assert self.swipe_direction == SwipeDirection.LEFT_TO_RIGHT
361
- assert self.tdvp_index == 0
364
+ assert self.sweep_index == 0
362
365
 
363
366
  if self.qubit_count == 1:
364
367
  self._evolve(0, dt=delta_time)
365
368
  else:
366
369
  self._evolve(0, 1, dt=delta_time, orth_center_right=False)
367
370
 
368
- self.tdvp_complete()
371
+ self.sweep_complete()
369
372
 
370
373
  elif (
371
- self.tdvp_index < self.qubit_count - 2
374
+ self.sweep_index < self.qubit_count - 2
372
375
  and self.swipe_direction == SwipeDirection.LEFT_TO_RIGHT
373
376
  ):
374
377
  # Left-to-right swipe of TDVP
375
378
  self._evolve(
376
- self.tdvp_index,
377
- self.tdvp_index + 1,
379
+ self.sweep_index,
380
+ self.sweep_index + 1,
378
381
  dt=delta_time / 2,
379
382
  orth_center_right=True,
380
383
  )
381
384
  self.left_baths.append(
382
385
  new_left_bath(
383
386
  self.get_current_left_bath(),
384
- self.state.factors[self.tdvp_index],
385
- self.hamiltonian.factors[self.tdvp_index],
386
- ).to(self.state.factors[self.tdvp_index + 1].device)
387
+ self.state.factors[self.sweep_index],
388
+ self.hamiltonian.factors[self.sweep_index],
389
+ ).to(self.state.factors[self.sweep_index + 1].device)
387
390
  )
388
- self._evolve(self.tdvp_index + 1, dt=-delta_time / 2)
391
+ self._evolve(self.sweep_index + 1, dt=-delta_time / 2)
389
392
  self.right_baths.pop()
390
- self.tdvp_index += 1
393
+ self.sweep_index += 1
391
394
 
392
395
  elif (
393
- self.tdvp_index == self.qubit_count - 2
396
+ self.sweep_index == self.qubit_count - 2
394
397
  and self.swipe_direction == SwipeDirection.LEFT_TO_RIGHT
395
398
  ):
396
399
  # Time-evolution of the rightmost 2 tensors
397
400
  self._evolve(
398
- self.tdvp_index,
399
- self.tdvp_index + 1,
401
+ self.sweep_index,
402
+ self.sweep_index + 1,
400
403
  dt=delta_time,
401
404
  orth_center_right=False,
402
405
  )
403
406
  self.swipe_direction = SwipeDirection.RIGHT_TO_LEFT
404
407
 
405
408
  elif (
406
- 1 <= self.tdvp_index and self.swipe_direction == SwipeDirection.RIGHT_TO_LEFT
409
+ 1 <= self.sweep_index and self.swipe_direction == SwipeDirection.RIGHT_TO_LEFT
407
410
  ):
408
411
  # Right-to-left swipe of TDVP
409
- assert self.tdvp_index <= self.qubit_count - 2
412
+ assert self.sweep_index <= self.qubit_count - 2
410
413
  self.right_baths.append(
411
414
  new_right_bath(
412
415
  self.get_current_right_bath(),
413
- self.state.factors[self.tdvp_index + 1],
414
- self.hamiltonian.factors[self.tdvp_index + 1],
415
- ).to(self.state.factors[self.tdvp_index].device)
416
+ self.state.factors[self.sweep_index + 1],
417
+ self.hamiltonian.factors[self.sweep_index + 1],
418
+ ).to(self.state.factors[self.sweep_index].device)
416
419
  )
417
420
  if not self.has_lindblad_noise:
418
421
  # Free memory because it won't be used anymore
419
422
  deallocate_tensor(self.right_baths[-2])
420
423
 
421
- self._evolve(self.tdvp_index, dt=-delta_time / 2)
424
+ self._evolve(self.sweep_index, dt=-delta_time / 2)
422
425
  self.left_baths.pop()
423
426
 
424
427
  self._evolve(
425
- self.tdvp_index - 1,
426
- self.tdvp_index,
428
+ self.sweep_index - 1,
429
+ self.sweep_index,
427
430
  dt=delta_time / 2,
428
431
  orth_center_right=False,
429
432
  )
430
- self.tdvp_index -= 1
433
+ self.sweep_index -= 1
431
434
 
432
- if self.tdvp_index == 0:
433
- self.tdvp_complete()
435
+ if self.sweep_index == 0:
436
+ self.sweep_complete()
434
437
  self.swipe_direction = SwipeDirection.LEFT_TO_RIGHT
435
438
 
436
439
  else:
@@ -438,7 +441,7 @@ class MPSBackendImpl:
438
441
 
439
442
  self.save_simulation()
440
443
 
441
- def tdvp_complete(self) -> None:
444
+ def sweep_complete(self) -> None:
442
445
  self.current_time = self.target_time
443
446
  self.timestep_complete()
444
447
 
@@ -623,7 +626,7 @@ class NoisyMPSBackendImpl(MPSBackendImpl):
623
626
  super().init()
624
627
  self.set_jump_threshold(1.0)
625
628
 
626
- def tdvp_complete(self) -> None:
629
+ def sweep_complete(self) -> None:
627
630
  previous_time = self.current_time
628
631
  self.current_time = self.target_time
629
632
  previous_norm_gap_before_jump = self.norm_gap_before_jump
@@ -690,6 +693,106 @@ class NoisyMPSBackendImpl(MPSBackendImpl):
690
693
  super().fill_results()
691
694
 
692
695
 
696
+ class DMRGBackendImpl(MPSBackendImpl):
697
+ def __init__(
698
+ self,
699
+ mps_config: MPSConfig,
700
+ pulser_data: PulserData,
701
+ energy_tolerance: float = 1e-5,
702
+ max_sweeps: int = 999,
703
+ residual_tolerance: float = 1e-7,
704
+ ):
705
+ super().__init__(mps_config, pulser_data)
706
+ self.init()
707
+ self.state.orthogonalize(0)
708
+ self.previous_energy: Optional[float] = None
709
+ self.current_energy: Optional[float] = None
710
+ self.sweep_count: int = 0
711
+ self.energy_tolerance: float = energy_tolerance
712
+ self.max_sweeps: int = max_sweeps
713
+ self.residual_tolerance: float = residual_tolerance
714
+
715
+ def convergence_check(self, energy_tolerance: float) -> bool:
716
+ if self.previous_energy is None or self.current_energy is None:
717
+ return False
718
+ return abs(self.current_energy - self.previous_energy) < energy_tolerance
719
+
720
+ def progress(self) -> None:
721
+ if self.is_finished():
722
+ return
723
+
724
+ # perform one two-site energy minimization and update
725
+ idx = self.sweep_index
726
+ assert self.swipe_direction in (
727
+ SwipeDirection.LEFT_TO_RIGHT,
728
+ SwipeDirection.RIGHT_TO_LEFT,
729
+ ), "Unknown Swipe direction"
730
+
731
+ if self.swipe_direction == SwipeDirection.LEFT_TO_RIGHT:
732
+ left_idx, right_idx = idx, idx + 1
733
+ elif self.swipe_direction == SwipeDirection.RIGHT_TO_LEFT:
734
+ left_idx, right_idx = idx - 1, idx
735
+
736
+ orth_center_right = self.swipe_direction == SwipeDirection.LEFT_TO_RIGHT
737
+ new_L, new_R, energy = minimize_energy_pair(
738
+ state_factors=self.state.factors[left_idx : right_idx + 1],
739
+ ham_factors=self.hamiltonian.factors[left_idx : right_idx + 1],
740
+ baths=(self.left_baths[-1], self.right_baths[-1]),
741
+ orth_center_right=orth_center_right,
742
+ config=self.config,
743
+ residual_tolerance=self.residual_tolerance,
744
+ )
745
+ self.state.factors[left_idx], self.state.factors[right_idx] = new_L, new_R
746
+ self.state.orthogonality_center = right_idx if orth_center_right else left_idx
747
+ self.current_energy = energy
748
+
749
+ # updating baths and orthogonality center
750
+ if self.swipe_direction == SwipeDirection.LEFT_TO_RIGHT:
751
+ self.left_baths.append(
752
+ new_left_bath(
753
+ self.get_current_left_bath(),
754
+ self.state.factors[left_idx],
755
+ self.hamiltonian.factors[right_idx],
756
+ ).to(self.state.factors[right_idx].device)
757
+ )
758
+ self.right_baths.pop()
759
+ self.sweep_index += 1
760
+
761
+ if self.sweep_index == self.qubit_count - 1:
762
+ self.swipe_direction = SwipeDirection.RIGHT_TO_LEFT
763
+
764
+ elif self.swipe_direction == SwipeDirection.RIGHT_TO_LEFT:
765
+ self.right_baths.append(
766
+ new_right_bath(
767
+ self.get_current_right_bath(),
768
+ self.state.factors[right_idx],
769
+ self.hamiltonian.factors[right_idx],
770
+ ).to(self.state.factors[left_idx].device)
771
+ )
772
+ self.left_baths.pop()
773
+ self.sweep_index -= 1
774
+
775
+ if self.sweep_index == 0:
776
+ self.swipe_direction = SwipeDirection.LEFT_TO_RIGHT
777
+ self.sweep_count += 1
778
+ self.sweep_complete()
779
+
780
+ def sweep_complete(self) -> None:
781
+ # This marks the end of one full sweep: checking convergence
782
+ if self.convergence_check(self.energy_tolerance):
783
+ self.timestep_complete()
784
+ elif self.sweep_count + 1 > self.max_sweeps:
785
+ # not converged: restart a new sweep
786
+ raise RuntimeError(f"DMRG did not converge after {self.max_sweeps} sweeps")
787
+ else:
788
+ self.previous_energy = self.current_energy
789
+
790
+ assert self.sweep_index == 0
791
+ assert self.state.orthogonality_center == 0
792
+ assert self.swipe_direction == SwipeDirection.LEFT_TO_RIGHT
793
+ self.current_energy = None
794
+
795
+
693
796
  def create_impl(sequence: Sequence, config: MPSConfig) -> MPSBackendImpl:
694
797
  pulser_data = PulserData(sequence=sequence, config=config, dt=config.dt)
695
798
 
emu_mps/solver_utils.py CHANGED
@@ -229,9 +229,9 @@ def evolve_single(
229
229
 
230
230
  def minimize_energy_pair(
231
231
  *,
232
- state_factors: tuple[torch.Tensor],
232
+ state_factors: Sequence[torch.Tensor],
233
233
  baths: tuple[torch.Tensor, torch.Tensor],
234
- ham_factors: tuple[torch.Tensor],
234
+ ham_factors: Sequence[torch.Tensor],
235
235
  orth_center_right: bool,
236
236
  config: MPSConfig,
237
237
  residual_tolerance: float,
emu_mps/utils.py CHANGED
@@ -1,7 +1,5 @@
1
1
  from typing import List, Optional
2
2
  import torch
3
- import random
4
- from collections import Counter
5
3
 
6
4
  from emu_mps import MPSConfig
7
5
 
@@ -111,7 +109,7 @@ def assign_devices(tensors: List[torch.Tensor], num_gpus_to_use: int) -> None:
111
109
 
112
110
 
113
111
  def extended_mps_factors(
114
- mps_factors: list[torch.Tensor], where: list[bool]
112
+ mps_factors: list[torch.Tensor], where: torch.Tensor
115
113
  ) -> list[torch.Tensor]:
116
114
  """
117
115
  Given a valid list of MPS factors, accounting for qubits marked as `True` in `where`,
@@ -149,7 +147,7 @@ def extended_mps_factors(
149
147
 
150
148
 
151
149
  def extended_mpo_factors(
152
- mpo_factors: list[torch.Tensor], where: list[bool]
150
+ mpo_factors: list[torch.Tensor], where: torch.Tensor
153
151
  ) -> list[torch.Tensor]:
154
152
  """
155
153
  Given a valid list of MPO factors, accounting for qubits marked as `True` in `where`,
@@ -184,7 +182,7 @@ def extended_mpo_factors(
184
182
 
185
183
 
186
184
  def get_extended_site_index(
187
- where: list[bool], desired_index: Optional[int]
185
+ where: torch.Tensor, desired_index: Optional[int]
188
186
  ) -> Optional[int]:
189
187
  """
190
188
  Returns the index in `where` that has `desired_index` preceding True elements.
@@ -210,42 +208,6 @@ def get_extended_site_index(
210
208
  raise ValueError(f"Index {desired_index} does not exist")
211
209
 
212
210
 
213
- def readout_with_error(c: str, *, p_false_pos: float, p_false_neg: float) -> str:
214
- # p_false_pos = false positive, p_false_neg = false negative
215
- r = random.random()
216
- if c == "0" and r < p_false_pos:
217
- return "1"
218
-
219
- if c == "1" and r < p_false_neg:
220
- return "0"
221
-
222
- return c
223
-
224
-
225
- def apply_measurement_errors(
226
- bitstrings: Counter[str], *, p_false_pos: float, p_false_neg: float
227
- ) -> Counter[str]:
228
- """
229
- Given a bag of sampled bitstrings, returns another bag of bitstrings
230
- sampled with readout/measurement errors.
231
-
232
- p_false_pos: probability of false positive
233
- p_false_neg: probability of false negative
234
- """
235
-
236
- result: Counter[str] = Counter()
237
- for bitstring, count in bitstrings.items():
238
- for _ in range(count):
239
- bitstring_with_error = "".join(
240
- readout_with_error(c, p_false_pos=p_false_pos, p_false_neg=p_false_neg)
241
- for c in bitstring
242
- )
243
-
244
- result[bitstring_with_error] += 1
245
-
246
- return result
247
-
248
-
249
211
  n_operator: torch.Tensor = torch.tensor(
250
212
  [
251
213
  [0, 0],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emu-mps
3
- Version: 2.2.1
3
+ Version: 2.3.0
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==2.2.1
28
+ Requires-Dist: emu-base==2.3.0
29
29
  Description-Content-Type: text/markdown
30
30
 
31
31
  <div align="center">
@@ -1,19 +1,18 @@
1
- emu_mps/__init__.py,sha256=iXV15aC4QkDT-W_pEv2vLF1vcZAUtb7GP6D4kBEYxYk,734
1
+ emu_mps/__init__.py,sha256=vvVRxFPudsVALA-_YyqWf8rlIRhtI1WzBWakYk2vfB8,734
2
2
  emu_mps/algebra.py,sha256=78XP9HEbV3wGNUzIulcLU5HizW4XAYmcFdkCe1T1x-k,5489
3
3
  emu_mps/custom_callback_implementations.py,sha256=SZGKVyS8U5hy07L-3SqpWlCAqGGKFTlSlWexZwSmjrM,2408
4
4
  emu_mps/hamiltonian.py,sha256=gOPxNOBmk6jRPPjevERuCP_scGv0EKYeAJ0uxooihes,15622
5
5
  emu_mps/mpo.py,sha256=aWSVuEzZM-_7ZD5Rz3-tSJWX22ARP0tMIl3gUu-_4V4,7834
6
- emu_mps/mps.py,sha256=8i3Yz5C_cqHWeAJILOm7cz8P8cHDuNl6aBcLXtaO314,19964
6
+ emu_mps/mps.py,sha256=n5KPuWr2qCNW4Q1OKpUGrSuhu4IbVp1v1BPLvLILbGg,19960
7
7
  emu_mps/mps_backend.py,sha256=bS83qFxvdoK-c12_1WaPw6O7xUc7vdWifZNHUzNP5sM,2091
8
- emu_mps/mps_backend_impl.py,sha256=SGqL2KiICE1fLviycrRdVlw3LOYRjpF5B0XgQlElSBs,26047
8
+ emu_mps/mps_backend_impl.py,sha256=O3vbjw3jlgyj0hOYv3nMmAP0lINqxbt96mQPgwPZN5w,30198
9
9
  emu_mps/mps_config.py,sha256=PoSKZxJMhG6zfzgEjj4tIvyiyYRQywxkRgidh8MRBsA,8222
10
- emu_mps/noise.py,sha256=5BXthepWLKnuSTJfIFuPl2AcYPxUeTJdRc2b28ekkhg,208
11
10
  emu_mps/observables.py,sha256=7GQDH5kyaVNrwckk2f8ZJRV9Ca4jKhWWDsOCqYWsoEk,1349
12
- emu_mps/solver_utils.py,sha256=NWwg6AeCCOrx8a5_ysSojdAOmg73W1202FtYx2JEHH0,8544
13
- emu_mps/utils.py,sha256=PRPIe9B8n-6caVcUYn3uTFSvb3jAMkXX-63f8KtX5-U,8196
11
+ emu_mps/solver_utils.py,sha256=VQ02_RxvPcjyXippuIY4Swpx4EdqtoJTt8Ie70GgdqU,8550
12
+ emu_mps/utils.py,sha256=hgtaRUtBAzk76ab-S_wTVkvqfVOmaUks38zWame9GRQ,7132
14
13
  emu_mps/optimatrix/__init__.py,sha256=fBXQ7-rgDro4hcaBijCGhx3J69W96qcw5_3mWc7tND4,364
15
14
  emu_mps/optimatrix/optimiser.py,sha256=k9suYmKLKlaZ7ozFuIqvXHyCBoCtGgkX1mpen9GOdOo,6977
16
15
  emu_mps/optimatrix/permutations.py,sha256=9DDMZtrGGZ01b9F3GkzHR3paX4qNtZiPoI7Z_Kia3Lc,3727
17
- emu_mps-2.2.1.dist-info/METADATA,sha256=FLJwfRKhNCuaThXQ39P92HcV4Fpz5KxPqyJMddrV-K8,3587
18
- emu_mps-2.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
- emu_mps-2.2.1.dist-info/RECORD,,
16
+ emu_mps-2.3.0.dist-info/METADATA,sha256=WLLpx31eshWCwR95aB_B4yoM8BRAKT81UR2mjVR3998,3587
17
+ emu_mps-2.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ emu_mps-2.3.0.dist-info/RECORD,,
emu_mps/noise.py DELETED
@@ -1,9 +0,0 @@
1
- import random
2
-
3
-
4
- def pick_well_prepared_qubits(eta: float, n: int) -> list[bool]:
5
- """
6
- Randomly pick n booleans such that ℙ(False) = eta.
7
- """
8
-
9
- return [random.random() > eta for _ in range(n)]