emu-sv 2.6.0__py3-none-any.whl → 2.7.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
@@ -40,4 +40,4 @@ __all__ = [
40
40
  "SparseOperator",
41
41
  ]
42
42
 
43
- __version__ = "2.6.0"
43
+ __version__ = "2.7.0"
emu_sv/dense_operator.py CHANGED
@@ -20,7 +20,23 @@ dtype = torch.complex128
20
20
 
21
21
 
22
22
  class DenseOperator(Operator[complex, torch.Tensor, StateVector]):
23
- """DenseOperator in EMU-SV use dense matrices"""
23
+ """DenseOperator in emu-sv uses dense matrices. This class represents a
24
+ quantum operator backed by a dense PyTorch tensor for state-vector
25
+ simulation.
26
+
27
+ Args:
28
+ matrix (torch.Tensor): Square complex tensor of shape (2ⁿ, 2ⁿ)
29
+ representing the operator in the computational basis.
30
+ gpu (bool, optional): If True, place the operator on a CUDA device when
31
+ available. Default: True (and only 1 GPU).
32
+
33
+ Returns:
34
+ DenseOperator: An operator object wrapping the provided matrix.
35
+
36
+ Raises:
37
+ ValueError: If 'matrix' is not a 2-D square tensor.
38
+ RuntimeError: If gpu=True but CUDA is not available (if applicable).
39
+ """
24
40
 
25
41
  def __init__(
26
42
  self,
@@ -121,7 +137,8 @@ class DenseOperator(Operator[complex, torch.Tensor, StateVector]):
121
137
  Construct a DenseOperator from an operator representation.
122
138
 
123
139
  Args:
124
- eigenstates: the eigenstates of the basis to use, e.g. ("r", "g") or ("0", "1").
140
+ eigenstates: the eigenstates of the basis to use, e.g. ("r", "g")
141
+ or ("0", "1").
125
142
  n_qudits: number of qudits in the system.
126
143
  operations: which bitstrings make up the state with what weight.
127
144
 
@@ -15,7 +15,28 @@ dtype = torch.complex128
15
15
 
16
16
 
17
17
  class DensityMatrix(State[complex, torch.Tensor]):
18
- """Represents a density matrix in a computational basis."""
18
+ """Represents an n-qubit density matrix ρ in the computational (|g⟩, |r⟩)
19
+ basis. The input should be a square complex tensor with shape (2ⁿ, 2ⁿ),
20
+ where n is the number of atoms. ρ must be Hermitian, positive semidefinite,
21
+ and has trace 1.
22
+
23
+ Args:
24
+ matrix (torch.Tensor): Square complex tensor of shape (2ⁿ, 2ⁿ),
25
+ Hermitian with trace 1, that represents the state in the
26
+ computational basis.
27
+ gpu (bool, optional): If True, place the operator on a CUDA device when
28
+ available. Default: True.
29
+
30
+ Returns:
31
+ DensityMatrix: A density-matrix wrapper around the provided tensor."
32
+
33
+ Raises:
34
+ ValueError: If matrix is not a square 2D tensor of shape (2ⁿ, 2ⁿ) or
35
+ fails validation (e.g., not Hermitian / trace != 1) if validation
36
+ is performed.
37
+ RuntimeError: If gpu=True but CUDA is not available (if the
38
+ implementation moves tensors to CUDA).
39
+ """
19
40
 
20
41
  # for the moment no need to check positivity and trace 1
21
42
  def __init__(
@@ -65,12 +86,18 @@ class DensityMatrix(State[complex, torch.Tensor]):
65
86
  Returns:
66
87
  the inner product
67
88
 
68
- Example:
69
- >>> density_bell_state = (1/2* torch.tensor([[1, 0, 0, 1], [0, 0, 0, 0],
70
- ... [0, 0, 0, 0], [1, 0, 0, 1]],dtype=torch.complex128))
71
- >>> density_c = DensityMatrix(density_bell_state, gpu=False)
72
- >>> density_c.overlap(density_c)
73
- tensor(1.+0.j, dtype=torch.complex128)
89
+ Examples:
90
+ ```python
91
+ density_bell_state = 0.5 * torch.tensor([[1, 0, 0, 1], [0, 0, 0, 0],
92
+ [0, 0, 0, 0], [1, 0, 0, 1]],dtype=torch.complex128)
93
+ density_c = DensityMatrix(density_bell_state, gpu=False)
94
+ density_c.overlap(density_c)
95
+ ```
96
+
97
+ Output:
98
+ ```
99
+ tensor(1.+0.j, dtype=torch.complex128)
100
+ ```
74
101
  """
75
102
 
76
103
  assert isinstance(
@@ -86,22 +113,28 @@ class DensityMatrix(State[complex, torch.Tensor]):
86
113
 
87
114
  @classmethod
88
115
  def from_state_vector(cls, state: StateVector) -> DensityMatrix:
89
- """Convert a state vector to a density matrix.
116
+ """
117
+ Convert a state vector to a density matrix.
90
118
  This function takes a state vector |ψ❭ and returns the corresponding
91
119
  density matrix ρ = |ψ❭❬ψ| representing the pure state |ψ❭.
92
- Example:
93
- >>> from emu_sv import StateVector
94
- >>> import math
95
- >>> bell_state_vec = 1 / math.sqrt(2) * torch.tensor(
96
- ... [1.0, 0.0, 0.0, 1.0j],dtype=torch.complex128)
97
- >>> bell_state = StateVector(bell_state_vec, gpu=False)
98
- >>> density = DensityMatrix.from_state_vector(bell_state)
99
- >>> print(density.matrix)
100
- tensor([[0.5000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000-0.5000j],
120
+
121
+ Examples:
122
+ ```python
123
+ bell_state_vec = 0.7071 * torch.tensor([1.0, 0.0, 0.0, 1.0j],
124
+ dtype=torch.complex128)
125
+ bell_state = StateVector(bell_state_vec, gpu=False)
126
+ density = DensityMatrix.from_state_vector(bell_state)
127
+ print(density.matrix)
128
+ ```
129
+
130
+ Output:
131
+ ```
132
+ tensor([[0.5000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000-0.5000j],
101
133
  [0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j],
102
134
  [0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j],
103
135
  [0.0000+0.5000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.5000+0.0000j]],
104
136
  dtype=torch.complex128)
137
+ ```
105
138
  """
106
139
 
107
140
  return cls(
@@ -116,7 +149,8 @@ class DensityMatrix(State[complex, torch.Tensor]):
116
149
  n_qudits: int,
117
150
  amplitudes: Mapping[str, complex],
118
151
  ) -> tuple[DensityMatrix, Mapping[str, complex]]:
119
- """Transforms a state given by a string into a density matrix.
152
+ """
153
+ Transforms a state given by a string into a density matrix.
120
154
 
121
155
  Construct a state from the pulser abstract representation
122
156
  https://pulser.readthedocs.io/en/stable/conventions.html
@@ -124,22 +158,29 @@ class DensityMatrix(State[complex, torch.Tensor]):
124
158
  Args:
125
159
  basis: A tuple containing the basis states (e.g., ('r', 'g')).
126
160
  nqubits: the number of qubits.
127
- strings: A dictionary mapping state strings to complex or floats amplitudes.
161
+ strings: A dictionary mapping state strings to complex or floats
162
+ amplitudes.
128
163
 
129
164
  Returns:
130
165
  The resulting state.
131
166
 
132
- Examples:
133
- >>> eigenstates = ("r","g")
134
- >>> n = 2
135
- >>> dense_mat=DensityMatrix.from_state_amplitudes(eigenstates=eigenstates,
136
- ... amplitudes={"rr":1.0,"gg":1.0})
137
- >>> print(dense_mat.matrix)
167
+ Example:
168
+ ```python
169
+ eigenstates = ("r","g")
170
+ n = 2
171
+ dense_mat=DensityMatrix.from_state_amplitudes(eigenstates=eigenstates,
172
+ amplitudes={"rr":1.0,"gg":1.0})
173
+ print(dense_mat.matrix)
174
+ ```
175
+
176
+ Output:
177
+ ```
138
178
  tensor([[0.5000+0.j, 0.0000+0.j, 0.0000+0.j, 0.5000+0.j],
139
179
  [0.0000+0.j, 0.0000+0.j, 0.0000+0.j, 0.0000+0.j],
140
180
  [0.0000+0.j, 0.0000+0.j, 0.0000+0.j, 0.0000+0.j],
141
181
  [0.5000+0.j, 0.0000+0.j, 0.0000+0.j, 0.5000+0.j]],
142
182
  dtype=torch.complex128)
183
+ ```
143
184
  """
144
185
 
145
186
  state_vector, amplitudes = StateVector._from_state_amplitudes(
@@ -166,16 +207,21 @@ class DensityMatrix(State[complex, torch.Tensor]):
166
207
  Returns:
167
208
  the measured bitstrings, by count
168
209
 
169
- Example:
170
- >>> import math
171
- >>> torch.manual_seed(1234)
172
- >>> from emu_sv import StateVector
173
- >>> bell_vec = 1 / math.sqrt(2) * torch.tensor(
174
- ... [1.0, 0.0, 0.0, 1.0j],dtype=torch.complex128)
175
- >>> bell_state_vec = StateVector(bell_vec)
176
- >>> bell_density = DensityMatrix.from_state_vector(bell_state_vec)
177
- >>> bell_density.sample(1000)
178
- Counter({'00': 517, '11': 483})
210
+ Examples:
211
+ ```python
212
+ torch.manual_seed(1234)
213
+ from emu_sv import StateVector
214
+ bell_vec = 0.7071 * torch.tensor([1.0, 0.0, 0.0, 1.0j],
215
+ dtype=torch.complex128)
216
+ bell_state_vec = StateVector(bell_vec)
217
+ bell_density = DensityMatrix.from_state_vector(bell_state_vec)
218
+ bell_density.sample(1000)
219
+ ```
220
+
221
+ Output:
222
+ ```
223
+ Counter({'00': 517, '11': 483})
224
+ ```
179
225
  """
180
226
 
181
227
  probabilities = torch.abs(self.matrix.diagonal())
@@ -194,9 +240,3 @@ class DensityMatrix(State[complex, torch.Tensor]):
194
240
  p_false_neg=p_false_neg,
195
241
  )
196
242
  return counts
197
-
198
-
199
- if __name__ == "__main__":
200
- import doctest
201
-
202
- doctest.testmod()
emu_sv/sparse_operator.py CHANGED
@@ -40,12 +40,14 @@ def sparse_kron(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
40
40
 
41
41
 
42
42
  class SparseOperator(Operator[complex, torch.Tensor, StateVector]):
43
- """This operator is used to represent a sparse matrix in CSR (Compressed Sparse Row)
44
- format for efficient computation on the EMU-SV emulator
43
+ """This operator is used to represent a sparse matrix in CSR (Compressed
44
+ Sparse Row) format for efficient computation on the emu-sv emulator
45
45
 
46
46
  Args:
47
47
  matrix (torch.Tensor): The CSR matrix representation of the operator.
48
- gpu (bool): Use GPU for computation. True uses the CPU if GPU not available.
48
+
49
+ gpu (bool): If True (by default), run on GPU when available; otherwise
50
+ fall back to CPU. If False, always run on CPU.
49
51
  """
50
52
 
51
53
  def __init__(
emu_sv/state_vector.py CHANGED
@@ -26,9 +26,11 @@ class StateVector(State[complex, torch.Tensor]):
26
26
  manipulation, and measurement. The state vector must have a length
27
27
  that is a power of 2, representing 2ⁿ basis states for n qubits.
28
28
 
29
- Attributes:
29
+ Args:
30
30
  vector: 1D tensor representation of a state vector.
31
31
  gpu: store the vector on GPU if True, otherwise on CPU
32
+ eigenstates: sequence of eigenstates used as basis only qubit basis are
33
+ supported (default: ('r','g'))
32
34
  """
33
35
 
34
36
  def __init__(
@@ -88,8 +90,14 @@ class StateVector(State[complex, torch.Tensor]):
88
90
  The zero state
89
91
 
90
92
  Examples:
91
- >>> StateVector.zero(2,gpu=False)
93
+ ```python
94
+ StateVector.zero(2,gpu=False)
95
+ ```
96
+
97
+ Output:
98
+ ```
92
99
  tensor([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], dtype=torch.complex128)
100
+ ```
93
101
  """
94
102
 
95
103
  device = "cuda" if gpu and DEVICE_COUNT > 0 else "cpu"
@@ -109,10 +117,14 @@ class StateVector(State[complex, torch.Tensor]):
109
117
  The described state
110
118
 
111
119
  Examples:
112
- >>> StateVector.make(2,gpu=False)
113
- tensor([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], dtype=torch.complex128)
114
-
120
+ ```python
121
+ StateVector.make(2,gpu=False)
122
+ ```
115
123
 
124
+ Output:
125
+ ```
126
+ tensor([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], dtype=torch.complex128)
127
+ ```
116
128
  """
117
129
 
118
130
  result = cls.zero(num_sites=num_sites, gpu=gpu)
@@ -235,23 +247,27 @@ class StateVector(State[complex, torch.Tensor]):
235
247
 
236
248
  Args:
237
249
  eigenstates: A tuple containing the basis states (e.g., ('r', 'g')).
238
- amplitudes: A dictionary mapping state strings to complex or floats amplitudes.
250
+ amplitudes: A dictionary mapping state strings to complex or floats
251
+ amplitudes.
239
252
 
240
253
  Returns:
241
254
  The normalised resulting state.
242
255
 
243
256
  Examples:
244
- >>> basis = ("r","g")
245
- >>> n = 2
246
- >>> st=StateVector.from_state_string(basis=basis,
247
- ... nqubits=n,strings={"rr":1.0,"gg":1.0},gpu=False)
248
- >>> st = StateVector.from_state_amplitudes(
249
- ... eigenstates=basis,
250
- ... amplitudes={"rr": 1.0, "gg": 1.0}
251
- ... )
252
- >>> print(st)
257
+ ```python
258
+ basis = ("r","g")
259
+ st = StateVector.from_state_amplitudes(
260
+ eigenstates=basis,
261
+ amplitudes={"rr": 1.0, "gg": 1.0}
262
+ )
263
+ print(st)
264
+ ```
265
+
266
+ Output:
267
+ ```
253
268
  tensor([0.7071+0.j, 0.0000+0.j, 0.0000+0.j, 0.7071+0.j],
254
269
  dtype=torch.complex128)
270
+ ```
255
271
  """
256
272
  basis = set(eigenstates)
257
273
  if basis == {"r", "g"}:
@@ -291,22 +307,30 @@ def inner(left: StateVector, right: StateVector) -> torch.Tensor:
291
307
  the inner product
292
308
 
293
309
  Examples:
294
- >>> factor = math.sqrt(2.0)
295
- >>> basis = ("r","g")
296
- >>> string_state1 = {"gg":1.0,"rr":1.0}
297
- >>> state1 = StateVector.from_state_string(basis=basis,
298
- ... nqubits=nqubits,strings=string_state1)
299
- >>> string_state2 = {"gr":1.0/factor,"rr":1.0/factor}
300
- >>> state2 = StateVector.from_state_string(basis=basis,
301
- ... nqubits=nqubits,strings=string_state2)
302
-
303
- >>> state1 = StateVector.from_state_amplitudes(eigenstates=basis,
304
- ... amplitudes=string_state1)
305
- >>> string_state2 = {"gr":1.0/factor,"rr":1.0/factor}
306
- >>> state2 = StateVector.from_state_amplitudes(eigenstates=basis,
307
- ... amplitudes=string_state2)
308
- >>> inner(state1,state2).item()
310
+ ```python
311
+ factor = math.sqrt(2.0)
312
+ basis = ("r","g")
313
+ string_state1 = {"gg":1.0,"rr":1.0}
314
+ state1 = StateVector.from_state_string(basis=basis,
315
+ nqubits=nqubits,strings=string_state1)
316
+ string_state2 = {"gr":1.0/factor,"rr":1.0/factor}
317
+ state2 = StateVector.from_state_string(basis=basis,
318
+ nqubits=nqubits,strings=string_state2)
319
+ ```
320
+
321
+ ```python
322
+ state1 = StateVector.from_state_amplitudes(eigenstates=basis,
323
+ amplitudes=string_state1)
324
+ string_state2 = {"gr":1.0/factor,"rr":1.0/factor}
325
+ state2 = StateVector.from_state_amplitudes(eigenstates=basis,
326
+ amplitudes=string_state2)
327
+ inner(state1,state2).item()
328
+ ```
329
+
330
+ Output:
331
+ ```
309
332
  (0.49999999144286444+0j)
333
+ ```
310
334
  """
311
335
 
312
336
  assert (left.vector.shape == right.vector.shape) and (left.vector.dim() == 1), (
@@ -314,9 +338,3 @@ def inner(left: StateVector, right: StateVector) -> torch.Tensor:
314
338
  " the same and both need to be 1D tesnor",
315
339
  )
316
340
  return left.inner(right)
317
-
318
-
319
- if __name__ == "__main__":
320
- import doctest
321
-
322
- doctest.testmod()
emu_sv/sv_backend.py CHANGED
@@ -1,7 +1,7 @@
1
- from pulser.backend import EmulatorBackend
2
- from pulser.backend import Results
1
+ from pulser.backend import EmulatorBackend, Results, BitStrings
3
2
  from emu_sv.sv_config import SVConfig
4
3
  from emu_sv.sv_backend_impl import create_impl
4
+ from emu_base import PulserData
5
5
 
6
6
 
7
7
  class SVBackend(EmulatorBackend):
@@ -9,11 +9,14 @@ class SVBackend(EmulatorBackend):
9
9
  A backend for emulating Pulser sequences using state vectors and sparse matrices.
10
10
  Noisy simulation is supported by solving the Lindblad equation and using effective
11
11
  noise channel or jump operators
12
+
13
+ Args:
14
+ config (SVConfig): Configuration for the SV backend.
12
15
  """
13
16
 
14
- default_config = SVConfig()
17
+ default_config = SVConfig(observables=[BitStrings(evaluation_times=[1.0])])
15
18
 
16
- def run(self) -> Results:
19
+ def run(self) -> Results | list[Results]:
17
20
  """
18
21
  Emulates the given sequence.
19
22
 
@@ -21,6 +24,11 @@ class SVBackend(EmulatorBackend):
21
24
  the simulation results
22
25
  """
23
26
  assert isinstance(self._config, SVConfig)
24
-
25
- impl = create_impl(self._sequence, self._config)
26
- return impl._run()
27
+ pulser_data = PulserData(
28
+ sequence=self._sequence, config=self._config, dt=self._config.dt
29
+ )
30
+ results = []
31
+ for sequence_data in pulser_data.get_sequences():
32
+ impl = create_impl(sequence_data, self._config)
33
+ results.append(impl._run())
34
+ return Results.aggregate(results)
emu_sv/sv_backend_impl.py CHANGED
@@ -2,13 +2,13 @@ from abc import abstractmethod
2
2
  import time
3
3
  import typing
4
4
  import torch
5
+ import logging
5
6
 
6
7
  from emu_sv.hamiltonian import RydbergHamiltonian
7
8
  from emu_sv.lindblad_operator import RydbergLindbladian
8
- from pulser import Sequence
9
9
 
10
10
  from pulser.backend import Results, Observable, State, EmulationConfig
11
- from emu_base import PulserData, get_max_rss
11
+ from emu_base import SequenceData, get_max_rss
12
12
 
13
13
  from emu_sv.state_vector import StateVector
14
14
  from emu_sv.density_matrix_state import DensityMatrix
@@ -50,8 +50,7 @@ class Statistics(Observable):
50
50
  or isinstance(state, DensityMatrix)
51
51
  and state.matrix.is_cuda
52
52
  )
53
-
54
- config.logger.info(
53
+ logging.getLogger("emulators").info(
55
54
  f"step = {len(self.data)}/{self.timestep_count}, "
56
55
  + f"RSS = {max_mem:.3f} MB, "
57
56
  + f"Δt = {duration:.3f} s"
@@ -70,19 +69,22 @@ class BaseSVBackendImpl:
70
69
 
71
70
  well_prepared_qubits_filter: typing.Optional[torch.Tensor]
72
71
 
73
- def __init__(self, config: SVConfig, pulser_data: PulserData):
72
+ def __init__(self, config: SVConfig, data: SequenceData):
74
73
  self._config = config
75
- self._pulser_data = pulser_data
76
- self.target_times = pulser_data.target_times
77
- self.omega = pulser_data.omega
78
- self.delta = pulser_data.delta
79
- self.phi = pulser_data.phi
80
- self.nsteps = pulser_data.omega.shape[0]
81
- self.nqubits = pulser_data.omega.shape[1]
82
- self.full_interaction_matrix = pulser_data.full_interaction_matrix
74
+ self._data = data
75
+ self.target_times = data.target_times
76
+ self.omega = data.omega
77
+ self.delta = data.delta
78
+ self.phi = data.phi
79
+ self.nsteps = data.omega.shape[0]
80
+ self.nqubits = data.omega.shape[1]
81
+ self.full_interaction_matrix = data.full_interaction_matrix
83
82
  self.state: State
84
83
  self.time = time.time()
85
- self.results = Results(atom_order=(), total_duration=self.target_times[-1])
84
+ self.results = Results(
85
+ atom_order=data.qubit_ids,
86
+ total_duration=int(self.target_times[-1]),
87
+ )
86
88
  self.statistics = Statistics(
87
89
  evaluation_times=[t / self.target_times[-1] for t in self.target_times],
88
90
  data=[],
@@ -100,7 +102,7 @@ class BaseSVBackendImpl:
100
102
 
101
103
  if (
102
104
  self._config.initial_state is not None
103
- and self._config.noise_model.state_prep_error > 0.0
105
+ and self._data.noise_model.state_prep_error > 0.0
104
106
  ):
105
107
  raise NotImplementedError(
106
108
  "Initial state and state preparation error can not be together."
@@ -112,10 +114,10 @@ class BaseSVBackendImpl:
112
114
  self.resolved_gpu = requested_gpu
113
115
 
114
116
  def init_dark_qubits(self) -> None:
115
- if self._config.noise_model.state_prep_error > 0.0:
116
- bad_atoms = self._pulser_data.hamiltonian.bad_atoms
117
+ if self._data.noise_model.state_prep_error > 0.0:
118
+ bad_atoms = self._data.bad_atoms
117
119
  self.well_prepared_qubits_filter = torch.tensor(
118
- [bool(bad_atoms[x]) for x in self._pulser_data.qubit_ids]
120
+ [bool(bad_atoms[x]) for x in self._data.qubit_ids]
119
121
  )
120
122
  else:
121
123
  self.well_prepared_qubits_filter = None
@@ -132,6 +134,7 @@ class BaseSVBackendImpl:
132
134
  """One step of the evolution"""
133
135
  dt = self._compute_dt(step_idx)
134
136
  self._evolve_step(dt, step_idx)
137
+ step_idx += 1
135
138
  self._apply_observables(step_idx)
136
139
  self._save_statistics(step_idx)
137
140
 
@@ -142,9 +145,38 @@ class BaseSVBackendImpl:
142
145
  def _evolve_step(self, dt: float, step_idx: int) -> None:
143
146
  """One step evolution"""
144
147
 
148
+ def _is_evaluation_time(
149
+ self,
150
+ observable: Observable,
151
+ t: float,
152
+ tolerance: float = 1e-10,
153
+ ) -> bool:
154
+ """Return True if ``t`` is a genuine sampling time for this observable.
155
+
156
+ Filters out nearby points that are close to, but not in, the
157
+ observable's evaluation times (within ``tolerance``).
158
+ Prevent false matches by using Pulser's tolerance
159
+ tol = 0.5 / total_duration. (deep inside pulser Observable class)
160
+ """
161
+ times = observable.evaluation_times
162
+
163
+ is_observable_eval_time = (
164
+ times is not None
165
+ and self._config.is_time_in_evaluation_times(t, times, tol=tolerance)
166
+ )
167
+
168
+ is_default_eval_time = self._config.is_evaluation_time(t, tol=tolerance)
169
+
170
+ return is_observable_eval_time or is_default_eval_time
171
+
145
172
  def _apply_observables(self, step_idx: int) -> None:
146
- norm_time = self.target_times[step_idx + 1] / self.target_times[-1]
147
- for callback in self._config.observables:
173
+ norm_time = self.target_times[step_idx] / self.target_times[-1]
174
+ callbacks_for_current_time_step = [
175
+ callback
176
+ for callback in self._config.observables
177
+ if self._is_evaluation_time(callback, norm_time)
178
+ ]
179
+ for callback in callbacks_for_current_time_step:
148
180
  callback(
149
181
  self._config,
150
182
  norm_time,
@@ -154,7 +186,8 @@ class BaseSVBackendImpl:
154
186
  )
155
187
 
156
188
  def _save_statistics(self, step_idx: int) -> None:
157
- norm_time = self.target_times[step_idx + 1] / self.target_times[-1]
189
+ norm_time = self.target_times[step_idx] / self.target_times[-1]
190
+
158
191
  self.statistics.data.append(time.time() - self.time)
159
192
  self.statistics(
160
193
  self._config,
@@ -167,6 +200,7 @@ class BaseSVBackendImpl:
167
200
  self._current_H = None
168
201
 
169
202
  def _run(self) -> Results:
203
+ self._apply_observables(0) # at t == 0 for pulser compatibility
170
204
  for step in range(self.nsteps):
171
205
  self.step(step)
172
206
 
@@ -175,16 +209,16 @@ class BaseSVBackendImpl:
175
209
 
176
210
  class SVBackendImpl(BaseSVBackendImpl):
177
211
 
178
- def __init__(self, config: SVConfig, pulser_data: PulserData):
212
+ def __init__(self, config: SVConfig, data: SequenceData):
179
213
  """
180
214
  For running sequences without noise. The state will evolve according
181
215
  to e^(-iH t)
182
216
 
183
217
  Args:
184
218
  config: The configuration for the emulator.
185
- pulser_data: The data for the sequence to be emulated.
219
+ data: The data for the sequence to be emulated.
186
220
  """
187
- super().__init__(config, pulser_data)
221
+ super().__init__(config, data)
188
222
  self.state: StateVector = (
189
223
  StateVector.make(self.nqubits, gpu=self.resolved_gpu)
190
224
  if self._config.initial_state is None
@@ -210,7 +244,7 @@ class SVBackendImpl(BaseSVBackendImpl):
210
244
 
211
245
  class NoisySVBackendImpl(BaseSVBackendImpl):
212
246
 
213
- def __init__(self, config: SVConfig, pulser_data: PulserData):
247
+ def __init__(self, config: SVConfig, data: SequenceData):
214
248
  """
215
249
  Initializes the NoisySVBackendImpl, master equation version.
216
250
  This class handles the Lindblad operators and
@@ -218,12 +252,12 @@ class NoisySVBackendImpl(BaseSVBackendImpl):
218
252
 
219
253
  Args:
220
254
  config: The configuration for the emulator.
221
- pulser_data: The data for the sequence to be emulated.
255
+ data: The data for the sequence to be emulated.
222
256
  """
223
257
 
224
- super().__init__(config, pulser_data)
258
+ super().__init__(config, data)
225
259
 
226
- self.pulser_lindblads = pulser_data.lindblad_ops
260
+ self.pulser_lindblads = data.lindblad_ops
227
261
 
228
262
  self.state: DensityMatrix = (
229
263
  DensityMatrix.make(self.nqubits, gpu=self.resolved_gpu)
@@ -246,7 +280,7 @@ class NoisySVBackendImpl(BaseSVBackendImpl):
246
280
  )
247
281
 
248
282
 
249
- def create_impl(sequence: Sequence, config: SVConfig) -> BaseSVBackendImpl:
283
+ def create_impl(data: SequenceData, config: SVConfig) -> BaseSVBackendImpl:
250
284
  """
251
285
  Creates the backend implementation for the given sequence and config.
252
286
 
@@ -257,8 +291,7 @@ def create_impl(sequence: Sequence, config: SVConfig) -> BaseSVBackendImpl:
257
291
  Returns:
258
292
  An instance of SVBackendImpl.
259
293
  """
260
- pulse_data = PulserData(sequence=sequence, config=config, dt=config.dt)
261
- if pulse_data.has_lindblad_noise:
262
- return NoisySVBackendImpl(config, pulse_data)
294
+ if data.lindblad_ops:
295
+ return NoisySVBackendImpl(config, data)
263
296
  else:
264
- return SVBackendImpl(config, pulse_data)
297
+ return SVBackendImpl(config, data)
emu_sv/sv_config.py CHANGED
@@ -26,7 +26,6 @@ from pulser.backend import (
26
26
  EnergySecondMoment,
27
27
  EnergyVariance,
28
28
  Occupation,
29
- BitStrings,
30
29
  )
31
30
 
32
31
 
@@ -55,11 +54,13 @@ class SVConfig(EmulationConfig):
55
54
  kwargs: arguments that are passed to the base class
56
55
 
57
56
  Examples:
58
- >>> gpu = True
59
- >>> dt = 1 #this will impact the runtime
60
- >>> krylov_tolerance = 1e-8 #the simulation will be faster, but less accurate
61
- >>> SVConfig(gpu=gpu, dt=dt, krylov_tolerance=krylov_tolerance,
62
- >>> with_modulation=True) #the last arg is taken from the base class
57
+ ```python
58
+ gpu = True
59
+ dt = 1.0 #this will impact the runtime
60
+ krylov_tolerance = 1e-8 #the simulation will be faster, but less accurate
61
+ SVConfig(gpu=gpu, dt=dt, krylov_tolerance=krylov_tolerance,
62
+ with_modulation=True) #the last arg is taken from the base class
63
+ ```
63
64
  """
64
65
 
65
66
  # Whether to warn if unexpected kwargs are received
@@ -70,7 +71,7 @@ class SVConfig(EmulationConfig):
70
71
  def __init__(
71
72
  self,
72
73
  *,
73
- dt: int = 10,
74
+ dt: float = 10.0,
74
75
  max_krylov_dim: int = 100,
75
76
  krylov_tolerance: float = 1e-10,
76
77
  gpu: bool | None = None,
@@ -79,7 +80,6 @@ class SVConfig(EmulationConfig):
79
80
  log_file: pathlib.Path | None = None,
80
81
  **kwargs: Any,
81
82
  ):
82
- kwargs.setdefault("observables", [BitStrings(evaluation_times=[1.0])])
83
83
  super().__init__(
84
84
  dt=dt,
85
85
  max_krylov_dim=max_krylov_dim,
@@ -92,13 +92,13 @@ class SVConfig(EmulationConfig):
92
92
  )
93
93
 
94
94
  self.monkeypatch_observables()
95
- self.logger = init_logging(log_level, log_file)
95
+ logger = init_logging(log_level, log_file)
96
96
 
97
97
  if (self.noise_model.runs != 1 and self.noise_model.runs is not None) or (
98
98
  self.noise_model.samples_per_run != 1
99
99
  and self.noise_model.samples_per_run is not None
100
100
  ):
101
- self.logger.warning(
101
+ logger.warning(
102
102
  "Warning: The runs and samples_per_run "
103
103
  "values of the NoiseModel are ignored!"
104
104
  )
@@ -145,4 +145,4 @@ class SVConfig(EmulationConfig):
145
145
  obs_copy,
146
146
  )
147
147
  obs_list.append(obs_copy)
148
- self.observables = tuple(obs_list)
148
+ self._backend_options["observables"] = tuple(obs_list)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emu-sv
3
- Version: 2.6.0
3
+ Version: 2.7.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.6.0
28
+ Requires-Dist: emu-base==2.7.0
29
29
  Description-Content-Type: text/markdown
30
30
 
31
31
  <div align="center">
@@ -0,0 +1,16 @@
1
+ emu_sv/__init__.py,sha256=cdxnMYgeTP6kn5BsOW71XoSh8WpKWlxMC6Zq38iqJLA,837
2
+ emu_sv/custom_callback_implementations.py,sha256=_7XLIDzJ-p3DVqz-Jyv0eYbl8nih2x2p-pM4cBCLumA,6367
3
+ emu_sv/dense_operator.py,sha256=0x8FPaOym-36vF3UQtwrARA9mi-N__FIiGJj2F_4MAw,6558
4
+ emu_sv/density_matrix_state.py,sha256=byH5OGqQffr560CRmFngPGeV6fq33dYKEj3C0suhgdw,8351
5
+ emu_sv/hamiltonian.py,sha256=CqNGuWJlO2ZljK47wt130s-5uKiOldQUsC3tjwk1mKA,6106
6
+ emu_sv/lindblad_operator.py,sha256=pgjRNLBcvEM2-qxM8uy9wL74OtrD4A8trQeERi_AXH8,8892
7
+ emu_sv/sparse_operator.py,sha256=B0TRuzr0j8wx3l4a0Pd7RT4DhFs_s_vH-4mIj1077vM,7585
8
+ emu_sv/state_vector.py,sha256=r5VixGWuES_3qIhK2g4Qkn--3S8ZzMlPFQUmXw_NneM,10167
9
+ emu_sv/sv_backend.py,sha256=ZLyg3fL20ItgSZXxpgqYvFuABocclwpvuShB17mOsyY,1148
10
+ emu_sv/sv_backend_impl.py,sha256=01ibeAQDghpppRn28QdrAXvCDx6vo4O2xznigMlNng4,9898
11
+ emu_sv/sv_config.py,sha256=eb3zCUDJDpAkJrCNxgZgAeDym5drzGyp-MwQbEbIDRk,5364
12
+ emu_sv/time_evolution.py,sha256=Uy3qMdt3BlLB6Aq1-o5uajRTu_3fPuBCtcusHxFPPJc,13545
13
+ emu_sv/utils.py,sha256=t0nMDVo6DF5bQW-vbsyRMCmvkyNxCU-v0Enmns9aOAU,1151
14
+ emu_sv-2.7.0.dist-info/METADATA,sha256=yNM3g5QmB_01UWZXbewhPlRO-5drB1v4NGN6rKDZ16Y,3595
15
+ emu_sv-2.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
+ emu_sv-2.7.0.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- emu_sv/__init__.py,sha256=0ykYwInHIgbHkUig_cY9uFesmU4F8bc8D_DIV511yEo,837
2
- emu_sv/custom_callback_implementations.py,sha256=_7XLIDzJ-p3DVqz-Jyv0eYbl8nih2x2p-pM4cBCLumA,6367
3
- emu_sv/dense_operator.py,sha256=GvF0swsiFRqp83bpyaU_CXap2vm74-JLI5lHo-0Hbdk,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/sparse_operator.py,sha256=xHJapSAKaMCgT5nG0gzMXGk2fCfjHY03OTO_rysszns,7535
8
- emu_sv/state_vector.py,sha256=v4rqC_qBGc5vO5EMHcHR6BdASjeKujO6_sCdd3pGd0c,9990
9
- emu_sv/sv_backend.py,sha256=-soOkSEzEBK1dCKnYnbtvYjmNZtZra1_4jP3H1ROOtM,737
10
- emu_sv/sv_backend_impl.py,sha256=-xWE30B5RI32nOG2pUR8lL3q-wufwvzxegiJexW5g4w,8952
11
- emu_sv/sv_config.py,sha256=o1esIqflxfN1ZtdwoVBAIWlzZIf9B5X9pvsQe1zHfdg,5433
12
- emu_sv/time_evolution.py,sha256=Uy3qMdt3BlLB6Aq1-o5uajRTu_3fPuBCtcusHxFPPJc,13545
13
- emu_sv/utils.py,sha256=t0nMDVo6DF5bQW-vbsyRMCmvkyNxCU-v0Enmns9aOAU,1151
14
- emu_sv-2.6.0.dist-info/METADATA,sha256=YNa_iPn5JUl5N9FMdaHOwF1Pm000JwsYkLUUcKB-8FQ,3595
15
- emu_sv-2.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
- emu_sv-2.6.0.dist-info/RECORD,,
File without changes