emu-sv 1.0.0__py3-none-any.whl → 2.0.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/sv_backend.py CHANGED
@@ -1,98 +1,125 @@
1
- from emu_base.base_classes.backend import Backend, BackendConfig
2
- from emu_base.base_classes.results import Results
3
- from emu_sv.sv_config import SVConfig
4
- from pulser import Sequence
5
- from emu_base.pulser_adapter import PulserData
6
- from emu_sv.time_evolution import do_time_step
7
- from emu_sv import StateVector
8
1
  import torch
9
- from time import time
10
2
  from resource import RUSAGE_SELF, getrusage
11
- from emu_base import DEVICE_COUNT
3
+ import time
4
+ import typing
5
+
6
+ from pulser.backend import EmulatorBackend, Results, Observable, State, EmulationConfig
7
+
8
+ from emu_base import PulserData
9
+
10
+ from emu_sv.state_vector import StateVector
11
+ from emu_sv.sv_config import SVConfig
12
+ from emu_sv.time_evolution import do_time_step
12
13
 
13
14
 
14
- class SVBackend(Backend):
15
+ _TIME_CONVERSION_COEFF = 0.001 # Omega and delta are given in rad/ms, dt in ns
16
+
17
+
18
+ class SVBackend(EmulatorBackend):
15
19
  """
16
20
  A backend for emulating Pulser sequences using state vectors and sparse matrices.
17
21
  """
18
22
 
19
- def run(self, sequence: Sequence, sv_config: BackendConfig) -> Results:
23
+ default_config = SVConfig()
24
+
25
+ def run(self) -> Results:
20
26
  """
21
27
  Emulates the given sequence.
22
28
 
23
- Args:
24
- sequence: a Pulser sequence to simulate
25
- sv_config: the backends config. Should be of type SVConfig
26
-
27
29
  Returns:
28
30
  the simulation results
29
31
  """
30
- assert isinstance(sv_config, SVConfig)
31
-
32
- self.validate_sequence(sequence)
33
-
34
- results = Results()
32
+ assert isinstance(self._config, SVConfig)
35
33
 
36
- data = PulserData(sequence=sequence, config=sv_config, dt=sv_config.dt)
37
- omega, delta, phi = data.omega, data.delta, data.phi
34
+ pulser_data = PulserData(
35
+ sequence=self._sequence, config=self._config, dt=self._config.dt
36
+ )
37
+ self.target_times = pulser_data.target_times
38
+ self.time = time.time()
39
+ omega, delta, phi = pulser_data.omega, pulser_data.delta, pulser_data.phi
38
40
 
39
41
  nsteps = omega.shape[0]
40
42
  nqubits = omega.shape[1]
41
- device = "cuda" if sv_config.gpu and DEVICE_COUNT > 0 else "cpu"
42
43
 
43
- if sv_config.initial_state is not None:
44
- state = sv_config.initial_state
45
- state.vector = state.vector.to(device)
46
- else:
47
- state = StateVector.make(nqubits, gpu=sv_config.gpu)
44
+ self.results = Results(atom_order=(), total_duration=self.target_times[-1])
45
+ self.statistics = Statistics(
46
+ evaluation_times=[t / self.target_times[-1] for t in self.target_times],
47
+ data=[],
48
+ timestep_count=nsteps,
49
+ )
48
50
 
49
- dt = sv_config.dt * 1e-3 # ns to µs
51
+ if self._config.initial_state is not None:
52
+ state = self._config.initial_state
53
+ state = StateVector(state.vector.clone(), gpu=state.vector.is_cuda)
54
+ else:
55
+ state = StateVector.make(nqubits, gpu=self._config.gpu)
50
56
 
51
57
  for step in range(nsteps):
52
-
53
- start = time()
58
+ dt = self.target_times[step + 1] - self.target_times[step]
54
59
 
55
60
  state.vector, H = do_time_step(
56
- dt,
61
+ dt * _TIME_CONVERSION_COEFF,
57
62
  omega[step],
58
63
  delta[step],
59
64
  phi[step],
60
- data.full_interaction_matrix,
65
+ pulser_data.full_interaction_matrix,
61
66
  state.vector,
62
- sv_config.krylov_tolerance,
67
+ self._config.krylov_tolerance,
63
68
  )
64
69
 
65
- for callback in sv_config.callbacks:
70
+ # callbacks in observables and self.statistics in H
71
+ # have "# type: ignore[arg-type]" because H has it's own type
72
+ # meaning H is not inherited from Operator class.
73
+ # We decided that ignore[arg-type] is better compared to
74
+ # having many unused NotImplemented methods
75
+ for callback in self._config.observables:
66
76
  callback(
67
- sv_config,
68
- (step + 1) * sv_config.dt,
77
+ self._config,
78
+ self.target_times[step + 1] / self.target_times[-1],
69
79
  state,
70
80
  H, # type: ignore[arg-type]
71
- results,
81
+ self.results,
72
82
  )
73
83
 
74
- end = time()
75
- self.log_step_statistics(
76
- results,
77
- step=step,
78
- duration=end - start,
79
- timestep_count=nsteps,
80
- state=state,
81
- sv_config=sv_config,
84
+ self.statistics.data.append(time.time() - self.time)
85
+ self.statistics(
86
+ self._config,
87
+ self.target_times[step + 1] / self.target_times[-1],
88
+ state,
89
+ H, # type: ignore[arg-type]
90
+ self.results,
82
91
  )
92
+ self.time = time.time()
83
93
 
84
- return results
94
+ return self.results
85
95
 
86
- @staticmethod
87
- def log_step_statistics(
88
- results: Results,
89
- *,
90
- step: int,
91
- duration: float,
96
+
97
+ class Statistics(Observable):
98
+ def __init__(
99
+ self,
100
+ evaluation_times: typing.Sequence[float] | None,
101
+ data: list[float],
92
102
  timestep_count: int,
93
- state: StateVector,
94
- sv_config: SVConfig,
95
- ) -> None:
103
+ ):
104
+ super().__init__(evaluation_times=evaluation_times)
105
+ self.data = data
106
+ self.timestep_count = timestep_count
107
+
108
+ @property
109
+ def _base_tag(self) -> str:
110
+ return "statistics"
111
+
112
+ def apply(
113
+ self,
114
+ *,
115
+ config: EmulationConfig,
116
+ state: State,
117
+ **kwargs: typing.Any,
118
+ ) -> dict:
119
+ """Calculates the observable to store in the Results."""
120
+ assert isinstance(state, StateVector)
121
+ assert isinstance(config, SVConfig)
122
+ duration = self.data[-1]
96
123
  if state.vector.is_cuda:
97
124
  max_mem_per_device = (
98
125
  torch.cuda.max_memory_allocated(device) * 1e-6
@@ -102,22 +129,13 @@ class SVBackend(Backend):
102
129
  else:
103
130
  max_mem = getrusage(RUSAGE_SELF).ru_maxrss * 1e-3
104
131
 
105
- sv_config.logger.info(
106
- f"step = {step + 1}/{timestep_count}, "
132
+ config.logger.info(
133
+ f"step = {len(self.data)}/{self.timestep_count}, "
107
134
  + f"RSS = {max_mem:.3f} MB, "
108
135
  + f"Δt = {duration:.3f} s"
109
136
  )
110
137
 
111
- if results.statistics is None:
112
- assert step == 0
113
- results.statistics = {"steps": []}
114
-
115
- assert "steps" in results.statistics
116
- assert len(results.statistics["steps"]) == step
117
-
118
- results.statistics["steps"].append(
119
- {
120
- "RSS": max_mem,
121
- "duration": duration,
122
- }
123
- )
138
+ return {
139
+ "RSS": max_mem,
140
+ "duration": duration,
141
+ }
emu_sv/sv_config.py CHANGED
@@ -1,35 +1,34 @@
1
- from emu_base.base_classes import (
2
- CorrelationMatrix,
3
- QubitDensity,
4
- EnergyVariance,
5
- SecondMomentOfEnergy,
6
- )
7
-
8
1
  import copy
9
-
10
-
11
- from emu_base import BackendConfig
12
- from emu_sv import StateVector
13
- from typing import Any
2
+ import logging
3
+ import pathlib
4
+ import sys
5
+ from types import MethodType
6
+ from typing import Any, ClassVar
14
7
 
15
8
  from emu_sv.custom_callback_implementations import (
16
- qubit_density_sv_impl,
17
- energy_variance_sv_impl,
18
- second_moment_sv_impl,
19
9
  correlation_matrix_sv_impl,
10
+ energy_second_moment_sv_impl,
11
+ energy_variance_sv_impl,
12
+ qubit_occupation_sv_impl,
20
13
  )
21
14
 
22
- from types import MethodType
15
+ from pulser.backend import (
16
+ CorrelationMatrix,
17
+ EmulationConfig,
18
+ EnergySecondMoment,
19
+ EnergyVariance,
20
+ Occupation,
21
+ BitStrings,
22
+ )
23
23
 
24
24
 
25
- class SVConfig(BackendConfig):
25
+ class SVConfig(EmulationConfig):
26
26
  """
27
27
  The configuration of the emu-sv SVBackend. The kwargs passed to this class
28
28
  are passed on to the base class.
29
29
  See the API for that class for a list of available options.
30
30
 
31
31
  Args:
32
- initial_state: the initial state to use in the simulation
33
32
  dt: the timestep size that the solver uses. Note that observables are
34
33
  only calculated if the evaluation_times are divisible by dt.
35
34
  max_krylov_dim:
@@ -48,43 +47,101 @@ class SVConfig(BackendConfig):
48
47
  >>> with_modulation=True) #the last arg is taken from the base class
49
48
  """
50
49
 
50
+ # Whether to warn if unexpected kwargs are received
51
+ _enforce_expected_kwargs: ClassVar[bool] = True
52
+
51
53
  def __init__(
52
54
  self,
53
55
  *,
54
- initial_state: StateVector | None = None,
55
56
  dt: int = 10,
56
57
  max_krylov_dim: int = 100,
57
58
  krylov_tolerance: float = 1e-10,
58
59
  gpu: bool = True,
60
+ interaction_cutoff: float = 0.0,
61
+ log_level: int = logging.INFO,
62
+ log_file: pathlib.Path | None = None,
59
63
  **kwargs: Any,
60
64
  ):
61
- super().__init__(**kwargs)
65
+ kwargs.setdefault("observables", [BitStrings(evaluation_times=[1.0])])
66
+ super().__init__(
67
+ dt=dt,
68
+ max_krylov_dim=max_krylov_dim,
69
+ gpu=gpu,
70
+ krylov_tolerance=krylov_tolerance,
71
+ interaction_cutoff=interaction_cutoff,
72
+ log_level=log_level,
73
+ log_file=log_file,
74
+ **kwargs,
75
+ )
76
+
77
+ self.monkeypatch_observables()
62
78
 
63
- self.initial_state = initial_state
64
- self.dt = dt
65
- self.max_krylov_dim = max_krylov_dim
66
- self.gpu = gpu
67
- self.krylov_tolerance = krylov_tolerance
79
+ self.logger = logging.getLogger("global_logger")
80
+ if log_file is None:
81
+ logging.basicConfig(
82
+ level=log_level, format="%(message)s", stream=sys.stdout, force=True
83
+ ) # default to stream = sys.stderr
84
+ else:
85
+ logging.basicConfig(
86
+ level=log_level,
87
+ format="%(message)s",
88
+ filename=str(log_file),
89
+ filemode="w",
90
+ force=True,
91
+ )
92
+ if (self.noise_model.runs != 1 and self.noise_model.runs is not None) or (
93
+ self.noise_model.samples_per_run != 1
94
+ and self.noise_model.samples_per_run is not None
95
+ ):
96
+ self.logger.warning(
97
+ "Warning: The runs and samples_per_run "
98
+ "values of the NoiseModel are ignored!"
99
+ )
68
100
 
69
- for num, obs in enumerate(self.callbacks): # monkey patch
101
+ def _expected_kwargs(self) -> set[str]:
102
+ return super()._expected_kwargs() | {
103
+ "dt",
104
+ "max_krylov_dim",
105
+ "krylov_tolerance",
106
+ "gpu",
107
+ "interaction_cutoff",
108
+ "log_level",
109
+ "log_file",
110
+ }
111
+
112
+ def monkeypatch_observables(self) -> None:
113
+ obs_list = []
114
+ for _, obs in enumerate(self.observables): # monkey patch
70
115
  obs_copy = copy.deepcopy(obs)
71
- if isinstance(obs, QubitDensity):
116
+ if isinstance(obs, Occupation):
72
117
  obs_copy.apply = MethodType( # type: ignore[method-assign]
73
- qubit_density_sv_impl, obs
118
+ qubit_occupation_sv_impl, obs_copy
74
119
  )
75
- self.callbacks[num] = obs_copy
76
120
  elif isinstance(obs, EnergyVariance):
77
121
  obs_copy.apply = MethodType( # type: ignore[method-assign]
78
- energy_variance_sv_impl, obs
122
+ energy_variance_sv_impl, obs_copy
79
123
  )
80
- self.callbacks[num] = obs_copy
81
- elif isinstance(obs, SecondMomentOfEnergy):
124
+ elif isinstance(obs, EnergySecondMoment):
82
125
  obs_copy.apply = MethodType( # type: ignore[method-assign]
83
- second_moment_sv_impl, obs
126
+ energy_second_moment_sv_impl, obs_copy
84
127
  )
85
- self.callbacks[num] = obs_copy
86
128
  elif isinstance(obs, CorrelationMatrix):
87
129
  obs_copy.apply = MethodType( # type: ignore[method-assign]
88
- correlation_matrix_sv_impl, obs
130
+ correlation_matrix_sv_impl, obs_copy
89
131
  )
90
- self.callbacks[num] = obs_copy
132
+ obs_list.append(obs_copy)
133
+ self.observables = tuple(obs_list)
134
+
135
+ def init_logging(self) -> None:
136
+ if self.log_file is None:
137
+ logging.basicConfig(
138
+ level=self.log_level, format="%(message)s", stream=sys.stdout, force=True
139
+ ) # default to stream = sys.stderr
140
+ else:
141
+ logging.basicConfig(
142
+ level=self.log_level,
143
+ format="%(message)s",
144
+ filename=str(self.log_file),
145
+ filemode="w",
146
+ force=True,
147
+ )
emu_sv/utils.py ADDED
@@ -0,0 +1,8 @@
1
+ def index_to_bitstring(nqubits: int, index: int) -> str:
2
+ """
3
+ Convert an integer index into its corresponding bitstring representation.
4
+ """
5
+
6
+ msg = f"index {index} can not exceed Hilbert space size d**{nqubits}"
7
+ assert index < 2**nqubits, msg
8
+ return format(index, f"0{nqubits}b")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emu-sv
3
- Version: 1.0.0
3
+ Version: 2.0.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==1.2.6
28
+ Requires-Dist: emu-base==2.0.0
29
29
  Description-Content-Type: text/markdown
30
30
 
31
31
  <div align="center">
@@ -0,0 +1,13 @@
1
+ emu_sv/__init__.py,sha256=Is45XApGdpLeYmDxl3FZQWXuM5ETprl9sFI_idvGTzg,701
2
+ emu_sv/custom_callback_implementations.py,sha256=zvsSiDIc56gwybKq87VFZyKsniTDye6-oFd2-R0shpg,3447
3
+ emu_sv/dense_operator.py,sha256=NfgzVpnNitc5ZSM4RlfpAc5Ls2wFPNsTxdeFdhJSg1o,6909
4
+ emu_sv/density_matrix_state.py,sha256=6UBLUXaJaUdzOhflrKolcnH8737JszX7sry1WmbyakI,6993
5
+ emu_sv/hamiltonian.py,sha256=zZq-_yUVhhafHxKkTZN0hivzzWFwC9JkTEk4MV1fBag,6168
6
+ emu_sv/state_vector.py,sha256=iX_33hGzn5ZsmG-LWE_Tjhu-Tvy4_lBOjHieT0C-VbA,9335
7
+ emu_sv/sv_backend.py,sha256=LE8tfoo-5-o6O39ixUMZAGo-xM8m9Xwcl_HmM1pSYtw,4433
8
+ emu_sv/sv_config.py,sha256=QRy0VbCugmY6TQZ48nD6RxPJbpu0wzN7-E1Sud7YxLQ,5106
9
+ emu_sv/time_evolution.py,sha256=48C0DL_SOu7Jdjk2QKBNPsevOpQlgsPYUHE7cScY-ZM,796
10
+ emu_sv/utils.py,sha256=-axfQ2tqw0C7I9yw-28g7lytyk373DNBjDALh4kLBrM,302
11
+ emu_sv-2.0.0.dist-info/METADATA,sha256=_9dp9luBQFQmHig_BiSD2B4yxXnS0WreTO2jqev9R9A,3513
12
+ emu_sv-2.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
13
+ emu_sv-2.0.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- emu_sv/__init__.py,sha256=Ji7PePRQka8XrVMw3N3i2k_sBwMQIj6LBOZFGZ0HNvs,845
2
- emu_sv/custom_callback_implementations.py,sha256=H7fy0trUtY00wid6VeRJ2inUazFbkMlgavTfm8QtGq8,2754
3
- emu_sv/dense_operator.py,sha256=69rv1J5jsHSRoPsgZqKJZnttCgMLIk4tDCBsOaOBVR8,7034
4
- emu_sv/hamiltonian.py,sha256=uzR7XHsv0QXBbcNuk93-phrO-On-WfJCINWz7Zofc90,6270
5
- emu_sv/state_vector.py,sha256=XsbHDNu6LDBop_qVVUoaw8blVPOystP8MOQ9demME6g,8125
6
- emu_sv/sv_backend.py,sha256=cghNpJq8ALdcoHCnx0xHcZBM9x8URLRFo0_sZ5CXXvY,3576
7
- emu_sv/sv_config.py,sha256=NUAxqYG0NTWbarVreFr1Tb-8FzB0LVQ0fYnb2QSCzxo,3161
8
- emu_sv/time_evolution.py,sha256=48C0DL_SOu7Jdjk2QKBNPsevOpQlgsPYUHE7cScY-ZM,796
9
- emu_sv-1.0.0.dist-info/METADATA,sha256=yqdJOFSlS5RqKiBC-Tx_LSL2n3hFnkt1unkv3UMNJG8,3513
10
- emu_sv-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
- emu_sv-1.0.0.dist-info/RECORD,,
File without changes