emu-sv 2.2.1__py3-none-any.whl → 2.4.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_sv/__init__.py CHANGED
@@ -38,4 +38,4 @@ __all__ = [
38
38
  "DensityMatrix",
39
39
  ]
40
40
 
41
- __version__ = "2.2.1"
41
+ __version__ = "2.4.0"
emu_sv/dense_operator.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import itertools
4
3
  from functools import reduce
5
4
 
6
5
  import torch
@@ -20,25 +19,6 @@ from pulser.backend.state import Eigenstate
20
19
  dtype = torch.complex128
21
20
 
22
21
 
23
- def _validate_operator_targets(operations: FullOp, nqubits: int) -> None:
24
- """Check for `operator_for_string` method"""
25
- for tensorop in operations:
26
- target_qids = (factor[1] for factor in tensorop[1])
27
- target_qids_list = list(itertools.chain(*target_qids))
28
- target_qids_set = set(target_qids_list)
29
- if len(target_qids_set) < len(target_qids_list):
30
- # Either the qubit id has been defined twice in an operation:
31
- for qids in target_qids:
32
- if len(set(qids)) < len(qids):
33
- raise ValueError("Duplicate atom ids in argument list.")
34
- # Or it was defined in two different operations
35
- raise ValueError("Each qubit can be targeted by only one operation.")
36
- if max(target_qids_set) >= nqubits:
37
- raise ValueError(
38
- "The operation targets more qubits than there are in the register."
39
- )
40
-
41
-
42
22
  class DenseOperator(Operator[complex, torch.Tensor, StateVector]):
43
23
  """DenseOperator in EMU-SV use dense matrices"""
44
24
 
@@ -149,7 +129,6 @@ class DenseOperator(Operator[complex, torch.Tensor, StateVector]):
149
129
  A DenseOperator instance corresponding to the given representation.
150
130
  """
151
131
 
152
- _validate_operator_targets(operations, n_qudits)
153
132
  assert len(set(eigenstates)) == 2, "Only qubits are supported in EMU-SV."
154
133
 
155
134
  operators_with_tensors: dict[str, torch.Tensor | QuditOp] = dict()
@@ -4,7 +4,7 @@ import math
4
4
  from typing import Mapping, TypeVar, Type, Sequence
5
5
  import torch
6
6
  from pulser.backend import State
7
- from emu_base import DEVICE_COUNT
7
+ from emu_base import DEVICE_COUNT, apply_measurement_errors
8
8
  from emu_sv.state_vector import StateVector
9
9
  from emu_sv.utils import index_to_bitstring
10
10
  from pulser.backend.state import Eigenstate
@@ -80,7 +80,9 @@ class DensityMatrix(State[complex, torch.Tensor]):
80
80
  self.matrix.shape == other.matrix.shape
81
81
  ), "States do not have the same number of sites"
82
82
 
83
- return torch.vdot(self.matrix.flatten(), other.matrix.flatten())
83
+ return torch.vdot(
84
+ self.matrix.flatten(), other.matrix.to(self.matrix.device).flatten()
85
+ )
84
86
 
85
87
  @classmethod
86
88
  def from_state_vector(cls, state: StateVector) -> DensityMatrix:
@@ -176,8 +178,6 @@ class DensityMatrix(State[complex, torch.Tensor]):
176
178
  Counter({'00': 517, '11': 483})
177
179
  """
178
180
 
179
- assert p_false_neg == p_false_pos == 0.0, "Error rates must be 0.0"
180
-
181
181
  probabilities = torch.abs(self.matrix.diagonal())
182
182
 
183
183
  outcomes = torch.multinomial(probabilities, num_shots, replacement=True)
@@ -187,6 +187,12 @@ class DensityMatrix(State[complex, torch.Tensor]):
187
187
  [index_to_bitstring(self.n_qudits, outcome) for outcome in outcomes]
188
188
  )
189
189
 
190
+ if p_false_neg > 0 or p_false_pos > 0:
191
+ counts = apply_measurement_errors(
192
+ counts,
193
+ p_false_pos=p_false_pos,
194
+ p_false_neg=p_false_neg,
195
+ )
190
196
  return counts
191
197
 
192
198
 
emu_sv/state_vector.py CHANGED
@@ -8,7 +8,7 @@ import torch
8
8
 
9
9
  from emu_sv.utils import index_to_bitstring
10
10
 
11
- from emu_base import DEVICE_COUNT
11
+ from emu_base import DEVICE_COUNT, apply_measurement_errors
12
12
  from pulser.backend import State
13
13
  from pulser.backend.state import Eigenstate
14
14
 
@@ -134,7 +134,7 @@ class StateVector(State[complex, torch.Tensor]):
134
134
  ), "States do not have the same shape"
135
135
 
136
136
  # by our internal convention inner and norm return to cpu
137
- return torch.vdot(self.vector, other.vector).cpu()
137
+ return torch.vdot(self.vector, other.vector.to(self.vector.device)).cpu()
138
138
 
139
139
  def sample(
140
140
  self,
@@ -155,7 +155,6 @@ class StateVector(State[complex, torch.Tensor]):
155
155
  Returns:
156
156
  the measured bitstrings, by count
157
157
  """
158
- assert p_false_neg == p_false_pos == 0.0, "Error rates must be 0.0"
159
158
 
160
159
  probabilities = torch.abs(self.vector) ** 2
161
160
 
@@ -166,7 +165,12 @@ class StateVector(State[complex, torch.Tensor]):
166
165
  [index_to_bitstring(self.n_qudits, outcome) for outcome in outcomes]
167
166
  )
168
167
 
169
- # NOTE: false positives and negatives
168
+ if p_false_neg > 0 or p_false_pos > 0:
169
+ counts = apply_measurement_errors(
170
+ counts,
171
+ p_false_pos=p_false_pos,
172
+ p_false_neg=p_false_neg,
173
+ )
170
174
  return counts
171
175
 
172
176
  def __add__(self, other: State) -> StateVector:
emu_sv/sv_backend_impl.py CHANGED
@@ -1,15 +1,14 @@
1
1
  from abc import abstractmethod
2
2
  import time
3
3
  import typing
4
+ import torch
4
5
 
5
6
  from emu_sv.hamiltonian import RydbergHamiltonian
6
7
  from emu_sv.lindblad_operator import RydbergLindbladian
7
8
  from pulser import Sequence
8
- import torch
9
- from resource import RUSAGE_SELF, getrusage
10
9
 
11
10
  from pulser.backend import Results, Observable, State, EmulationConfig
12
- from emu_base import PulserData
11
+ from emu_base import PulserData, get_max_rss
13
12
 
14
13
  from emu_sv.state_vector import StateVector
15
14
  from emu_sv.density_matrix_state import DensityMatrix
@@ -45,20 +44,12 @@ class Statistics(Observable):
45
44
  assert isinstance(state, StateVector | DensityMatrix)
46
45
  assert isinstance(config, SVConfig)
47
46
  duration = self.data[-1]
48
- if isinstance(state, StateVector) and state.vector.is_cuda:
49
- max_mem_per_device = (
50
- torch.cuda.max_memory_allocated(device) * 1e-6
51
- for device in range(torch.cuda.device_count())
52
- )
53
- max_mem = max(max_mem_per_device)
54
- elif isinstance(state, DensityMatrix) and state.matrix.is_cuda:
55
- max_mem_per_device = (
56
- torch.cuda.max_memory_allocated(device) * 1e-6
57
- for device in range(torch.cuda.device_count())
58
- )
59
- max_mem = max(max_mem_per_device)
60
- else:
61
- max_mem = getrusage(RUSAGE_SELF).ru_maxrss * 1e-3
47
+ max_mem = get_max_rss(
48
+ isinstance(state, StateVector)
49
+ and state.vector.is_cuda
50
+ or isinstance(state, DensityMatrix)
51
+ and state.matrix.is_cuda
52
+ )
62
53
 
63
54
  config.logger.info(
64
55
  f"step = {len(self.data)}/{self.timestep_count}, "
@@ -77,6 +68,8 @@ class BaseSVBackendImpl:
77
68
  This class is used to handle the state vector and density matrix evolution.
78
69
  """
79
70
 
71
+ well_prepared_qubits_filter: typing.Optional[torch.Tensor]
72
+
80
73
  def __init__(self, config: SVConfig, pulser_data: PulserData):
81
74
  self._config = config
82
75
  self._pulser_data = pulser_data
@@ -86,6 +79,7 @@ class BaseSVBackendImpl:
86
79
  self.phi = pulser_data.phi
87
80
  self.nsteps = pulser_data.omega.shape[0]
88
81
  self.nqubits = pulser_data.omega.shape[1]
82
+ self.full_interaction_matrix = pulser_data.full_interaction_matrix
89
83
  self.state: State
90
84
  self.time = time.time()
91
85
  self.results = Results(atom_order=(), total_duration=self.target_times[-1])
@@ -95,7 +89,6 @@ class BaseSVBackendImpl:
95
89
  timestep_count=self.nsteps,
96
90
  )
97
91
  self._current_H: None | RydbergLindbladian | RydbergHamiltonian = None
98
-
99
92
  if self._config.initial_state is not None and (
100
93
  self._config.initial_state.n_qudits != self.nqubits
101
94
  ):
@@ -103,6 +96,32 @@ class BaseSVBackendImpl:
103
96
  "Mismatch in number of atoms: initial state has "
104
97
  f"{self._config.initial_state.n_qudits} and the sequence has {self.nqubits}"
105
98
  )
99
+ self.init_dark_qubits()
100
+
101
+ if (
102
+ self._config.initial_state is not None
103
+ and self._config.noise_model.state_prep_error > 0.0
104
+ ):
105
+ raise NotImplementedError(
106
+ "Initial state and state preparation error can not be together."
107
+ )
108
+
109
+ def init_dark_qubits(self) -> None:
110
+ if self._config.noise_model.state_prep_error > 0.0:
111
+ bad_atoms = self._pulser_data.hamiltonian.bad_atoms
112
+ self.well_prepared_qubits_filter = torch.tensor(
113
+ [bool(bad_atoms[x]) for x in self._pulser_data.qubit_ids]
114
+ )
115
+ else:
116
+ self.well_prepared_qubits_filter = None
117
+
118
+ if self.well_prepared_qubits_filter is not None:
119
+
120
+ self.full_interaction_matrix[self.well_prepared_qubits_filter, :] = 0.0
121
+ self.full_interaction_matrix[:, self.well_prepared_qubits_filter] = 0.0
122
+ self.omega[:, self.well_prepared_qubits_filter] = 0.0
123
+ self.delta[:, self.well_prepared_qubits_filter] = 0.0
124
+ self.phi[:, self.well_prepared_qubits_filter] = 0.0
106
125
 
107
126
  def step(self, step_idx: int) -> None:
108
127
  """One step of the evolution"""
@@ -153,7 +172,7 @@ class SVBackendImpl(BaseSVBackendImpl):
153
172
 
154
173
  def __init__(self, config: SVConfig, pulser_data: PulserData):
155
174
  """
156
- For running sequences without noise. The state will evolve accoring
175
+ For running sequences without noise. The state will evolve according
157
176
  to e^(-iH t)
158
177
 
159
178
  Args:
@@ -179,7 +198,7 @@ class SVBackendImpl(BaseSVBackendImpl):
179
198
  self.omega[step_idx],
180
199
  self.delta[step_idx],
181
200
  self.phi[step_idx],
182
- self._pulser_data.full_interaction_matrix,
201
+ self.full_interaction_matrix,
183
202
  self.state.vector,
184
203
  self._config.krylov_tolerance,
185
204
  )
@@ -216,7 +235,7 @@ class NoisySVBackendImpl(BaseSVBackendImpl):
216
235
  self.omega[step_idx],
217
236
  self.delta[step_idx],
218
237
  self.phi[step_idx],
219
- self._pulser_data.full_interaction_matrix,
238
+ self.full_interaction_matrix,
220
239
  self.state.matrix,
221
240
  self._config.krylov_tolerance,
222
241
  self.pulser_lindblads,
emu_sv/sv_config.py CHANGED
@@ -5,15 +5,17 @@ import sys
5
5
  from types import MethodType
6
6
  from typing import Any, ClassVar
7
7
 
8
+ from emu_sv.utils import choose
9
+
8
10
  from emu_sv.custom_callback_implementations import (
11
+ qubit_occupation_sv_impl,
12
+ qubit_occupation_sv_den_mat_impl,
9
13
  correlation_matrix_sv_impl,
10
14
  correlation_matrix_sv_den_mat_impl,
11
15
  energy_second_moment_sv_impl,
12
16
  energy_second_moment_den_mat_impl,
13
17
  energy_variance_sv_impl,
14
18
  energy_variance_sv_den_mat_impl,
15
- qubit_occupation_sv_impl,
16
- qubit_occupation_sv_den_mat_impl,
17
19
  )
18
20
 
19
21
  from pulser.backend import (
@@ -105,10 +107,6 @@ class SVConfig(EmulationConfig):
105
107
  "Warning: The runs and samples_per_run "
106
108
  "values of the NoiseModel are ignored!"
107
109
  )
108
- if "SPAM" in self.noise_model.noise_types:
109
- raise NotImplementedError(
110
- "SPAM errors are currently not supported in emu-sv."
111
- )
112
110
 
113
111
  def _expected_kwargs(self) -> set[str]:
114
112
  return super()._expected_kwargs() | {
@@ -129,37 +127,25 @@ class SVConfig(EmulationConfig):
129
127
 
130
128
  if isinstance(obs, Occupation):
131
129
  obs_copy.apply = MethodType( # type: ignore[method-assign]
132
- (
133
- qubit_occupation_sv_impl
134
- if self.noise_model.noise_types == ()
135
- else qubit_occupation_sv_den_mat_impl
136
- ),
130
+ choose(qubit_occupation_sv_impl, qubit_occupation_sv_den_mat_impl),
137
131
  obs_copy,
138
132
  )
139
133
  if isinstance(obs, CorrelationMatrix):
140
134
  obs_copy.apply = MethodType( # type: ignore[method-assign]
141
- (
142
- correlation_matrix_sv_impl
143
- if self.noise_model.noise_types == ()
144
- else correlation_matrix_sv_den_mat_impl
135
+ choose(
136
+ correlation_matrix_sv_impl, correlation_matrix_sv_den_mat_impl
145
137
  ),
146
138
  obs_copy,
147
139
  )
148
140
  if isinstance(obs, EnergyVariance):
149
141
  obs_copy.apply = MethodType( # type: ignore[method-assign]
150
- (
151
- energy_variance_sv_impl
152
- if self.noise_model.noise_types == ()
153
- else energy_variance_sv_den_mat_impl
154
- ),
142
+ choose(energy_variance_sv_impl, energy_variance_sv_den_mat_impl),
155
143
  obs_copy,
156
144
  )
157
145
  elif isinstance(obs, EnergySecondMoment):
158
146
  obs_copy.apply = MethodType( # type: ignore[method-assign]
159
- (
160
- energy_second_moment_sv_impl
161
- if self.noise_model.noise_types == ()
162
- else energy_second_moment_den_mat_impl
147
+ choose(
148
+ energy_second_moment_sv_impl, energy_second_moment_den_mat_impl
163
149
  ),
164
150
  obs_copy,
165
151
  )
emu_sv/utils.py CHANGED
@@ -1,3 +1,8 @@
1
+ from typing import Callable
2
+
3
+ from pyparsing import Any
4
+
5
+
1
6
  def index_to_bitstring(nqubits: int, index: int) -> str:
2
7
  """
3
8
  Convert an integer index into its corresponding bitstring representation.
@@ -6,3 +11,24 @@ def index_to_bitstring(nqubits: int, index: int) -> str:
6
11
  msg = f"index {index} can not exceed Hilbert space size d**{nqubits}"
7
12
  assert index < 2**nqubits, msg
8
13
  return format(index, f"0{nqubits}b")
14
+
15
+
16
+ def choose(
17
+ state_vector_version: Callable,
18
+ density_matrix_version: Callable,
19
+ ) -> Callable:
20
+ """Returns the observable result function that chooses the correct
21
+ implementation based on the type of state (StateVector or DensityMatrix).
22
+ """
23
+ from emu_sv.state_vector import StateVector
24
+ from emu_sv.density_matrix_state import DensityMatrix
25
+
26
+ def result(self: Any, *, state: StateVector | DensityMatrix, **kwargs: Any) -> Any:
27
+ if isinstance(state, StateVector):
28
+ return state_vector_version(self, state=state, **kwargs)
29
+ elif isinstance(state, DensityMatrix):
30
+ return density_matrix_version(self, state=state, **kwargs)
31
+ else:
32
+ raise TypeError(f"Unsupported state: {type(state).__name__}")
33
+
34
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emu-sv
3
- Version: 2.2.1
3
+ Version: 2.4.0
4
4
  Summary: Pasqal State Vector 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.4.0
29
29
  Description-Content-Type: text/markdown
30
30
 
31
31
  <div align="center">
@@ -0,0 +1,15 @@
1
+ emu_sv/__init__.py,sha256=Oc9_UlpE7Nioggmg-pX4_8iQnh7_o9jl8n_wBTu5NJg,771
2
+ emu_sv/custom_callback_implementations.py,sha256=_7XLIDzJ-p3DVqz-Jyv0eYbl8nih2x2p-pM4cBCLumA,6367
3
+ emu_sv/dense_operator.py,sha256=AvgntJNwwtf3Wl66CIWSwUezVYV3vignCL24SQQQwQg,5901
4
+ emu_sv/density_matrix_state.py,sha256=6QmLZvqEHLR64r0nD7D2jZIiAYOgciNVCjh3ywfvIs0,7243
5
+ emu_sv/hamiltonian.py,sha256=CqNGuWJlO2ZljK47wt130s-5uKiOldQUsC3tjwk1mKA,6106
6
+ emu_sv/lindblad_operator.py,sha256=pgjRNLBcvEM2-qxM8uy9wL74OtrD4A8trQeERi_AXH8,8892
7
+ emu_sv/state_vector.py,sha256=dkAnxEQxK-4Fn6UxhDbcb1z1EsHFUpOyVy0mjHkKFBI,9894
8
+ emu_sv/sv_backend.py,sha256=-soOkSEzEBK1dCKnYnbtvYjmNZtZra1_4jP3H1ROOtM,737
9
+ emu_sv/sv_backend_impl.py,sha256=OYsJK_DJ8dmq3QZLKiBejXrq22R5Auyye-pai07NeeY,8798
10
+ emu_sv/sv_config.py,sha256=J4yb9-Na5hAbShDySzPfbt0WXvL8JpfrTEikTK8nH9s,5943
11
+ emu_sv/time_evolution.py,sha256=_VH4f2RF6lGKzO08WxTYJ5Aw8_pTTMRKcyMnIuxH03I,13382
12
+ emu_sv/utils.py,sha256=t0nMDVo6DF5bQW-vbsyRMCmvkyNxCU-v0Enmns9aOAU,1151
13
+ emu_sv-2.4.0.dist-info/METADATA,sha256=HKTJmI_Ra3dcFw2QthE2TEvEiYcvcZmrHkrFMuGY794,3595
14
+ emu_sv-2.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ emu_sv-2.4.0.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- emu_sv/__init__.py,sha256=sREepAk-h3a3-e7PGexVNmYmj5hf4fVIrKk78vnDKiE,771
2
- emu_sv/custom_callback_implementations.py,sha256=_7XLIDzJ-p3DVqz-Jyv0eYbl8nih2x2p-pM4cBCLumA,6367
3
- emu_sv/dense_operator.py,sha256=NfgzVpnNitc5ZSM4RlfpAc5Ls2wFPNsTxdeFdhJSg1o,6909
4
- emu_sv/density_matrix_state.py,sha256=5W_UKIAYHb0k3ryRLQ2dbFUgrb5ju5jceDGAekM2gNE,7035
5
- emu_sv/hamiltonian.py,sha256=CqNGuWJlO2ZljK47wt130s-5uKiOldQUsC3tjwk1mKA,6106
6
- emu_sv/lindblad_operator.py,sha256=pgjRNLBcvEM2-qxM8uy9wL74OtrD4A8trQeERi_AXH8,8892
7
- emu_sv/state_vector.py,sha256=zKHCdgl_eRIOPE4qVKO53ig9UyYTQ7a_guNFXgynU7g,9753
8
- emu_sv/sv_backend.py,sha256=-soOkSEzEBK1dCKnYnbtvYjmNZtZra1_4jP3H1ROOtM,737
9
- emu_sv/sv_backend_impl.py,sha256=mdPWBLDwH0q7EEwQTmLNLLx5tycMmsCQbUifIHvciMk,8059
10
- emu_sv/sv_config.py,sha256=0geciKkrF3h8pU_UAQ8R-G6WxfYb_X5XIrxZavGxK5Q,6511
11
- emu_sv/time_evolution.py,sha256=_VH4f2RF6lGKzO08WxTYJ5Aw8_pTTMRKcyMnIuxH03I,13382
12
- emu_sv/utils.py,sha256=-axfQ2tqw0C7I9yw-28g7lytyk373DNBjDALh4kLBrM,302
13
- emu_sv-2.2.1.dist-info/METADATA,sha256=_NvCSEGYdw9ZXATsZKME-GX0ECeiJcDy8MthY9C5ZUU,3595
14
- emu_sv-2.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- emu_sv-2.2.1.dist-info/RECORD,,
File without changes