emu-sv 2.0.4__py3-none-any.whl → 2.2.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
@@ -15,6 +15,7 @@ from pulser.backend import (
15
15
  from .dense_operator import DenseOperator
16
16
  from .sv_backend import SVBackend, SVConfig
17
17
  from .state_vector import StateVector, inner
18
+ from .density_matrix_state import DensityMatrix
18
19
 
19
20
 
20
21
  __all__ = [
@@ -34,6 +35,7 @@ __all__ = [
34
35
  "StateResult",
35
36
  "StateVector",
36
37
  "inner",
38
+ "DensityMatrix",
37
39
  ]
38
40
 
39
- __version__ = "2.0.4"
41
+ __version__ = "2.2.0"
@@ -8,11 +8,13 @@ from pulser.backend import (
8
8
  Occupation,
9
9
  Energy,
10
10
  )
11
-
11
+ from emu_sv.density_matrix_state import DensityMatrix
12
12
  from emu_sv.state_vector import StateVector
13
13
  from emu_sv.dense_operator import DenseOperator
14
14
  from emu_sv.hamiltonian import RydbergHamiltonian
15
15
 
16
+ dtype = torch.float64
17
+
16
18
 
17
19
  def qubit_occupation_sv_impl(
18
20
  self: Occupation,
@@ -25,7 +27,7 @@ def qubit_occupation_sv_impl(
25
27
  Custom implementation of the occupation ❬ψ|nᵢ|ψ❭ for the state vector solver.
26
28
  """
27
29
  nqubits = state.n_qudits
28
- occupation = torch.zeros(nqubits, dtype=torch.float64, device=state.vector.device)
30
+ occupation = torch.zeros(nqubits, dtype=dtype, device=state.vector.device)
29
31
  for i in range(nqubits):
30
32
  state_tensor = state.vector.view(2**i, 2, -1)
31
33
  # nᵢ is a projector and therefore nᵢ == nᵢnᵢ
@@ -34,6 +36,33 @@ def qubit_occupation_sv_impl(
34
36
  return occupation.cpu()
35
37
 
36
38
 
39
+ def qubit_occupation_sv_den_mat_impl(
40
+ self: Occupation,
41
+ *,
42
+ config: EmulationConfig,
43
+ state: DensityMatrix,
44
+ hamiltonian: DenseOperator,
45
+ ) -> torch.Tensor:
46
+ """
47
+ Custom implementation of the occupation nᵢ observable for density matrix.
48
+ The observable nᵢ is given by: I ⊗ ... ⊗ nᵢ ⊗ ... ⊗I
49
+ where nᵢ is the occupation operator for qubit i.
50
+ The expectation value is given by: <nᵢ> = Tr(ρ nᵢ).
51
+
52
+ The output will be a tensor of size (nqubits,), where each element will be the
53
+ expectation value of the occupation operator for each qubit.
54
+ In case of 3 atoms, the output will be a tensor of size (3,), where each element
55
+ will be <nᵢ> = Tr(ρnᵢ), or [ <n₁>, <n₂>, <n₃> ].
56
+ """
57
+ nqubits = state.n_qudits
58
+ occupation = torch.zeros(nqubits, dtype=dtype, device=state.matrix.device)
59
+ diag_state_tensor = state.matrix.diagonal()
60
+ for i in range(nqubits):
61
+ state_tensor = diag_state_tensor.view(2**i, 2, 2 ** (nqubits - i - 1))[:, 1, :]
62
+ occupation[i] = state_tensor.sum().real
63
+ return occupation.cpu()
64
+
65
+
37
66
  def correlation_matrix_sv_impl(
38
67
  self: CorrelationMatrix,
39
68
  *,
@@ -47,24 +76,44 @@ def correlation_matrix_sv_impl(
47
76
  TODO: extend to arbitrary two-point correlation ❬ψ|AᵢBⱼ|ψ❭
48
77
  """
49
78
  nqubits = state.n_qudits
50
- correlation = torch.zeros(
51
- nqubits, nqubits, dtype=torch.float64, device=state.vector.device
52
- )
79
+ correlation = torch.zeros(nqubits, nqubits, dtype=dtype, device=state.vector.device)
53
80
 
54
81
  for i in range(nqubits):
55
82
  select_i = state.vector.view(2**i, 2, -1)
56
83
  select_i = select_i[:, 1]
57
- for j in range(i, nqubits): # select the upper triangle
58
- if i == j:
59
- value = torch.linalg.vector_norm(select_i) ** 2
60
- correlation[j, j] = value
61
- else:
62
- select_i = select_i.view(2**i, 2 ** (j - i - 1), 2, -1)
63
- select_ij = select_i[:, :, 1, :]
64
- value = torch.linalg.vector_norm(select_ij) ** 2
65
- correlation[i, j] = value
66
- correlation[j, i] = value
84
+ correlation[i, i] = torch.linalg.vector_norm(select_i) ** 2
85
+ for j in range(i + 1, nqubits): # select the upper triangle
86
+ select_i = select_i.view(2**i, 2 ** (j - i - 1), 2, -1)
87
+ select_ij = select_i[:, :, 1, :]
88
+ correlation[i, j] = torch.linalg.vector_norm(select_ij) ** 2
89
+ correlation[j, i] = correlation[i, j]
90
+
91
+ return correlation.cpu()
67
92
 
93
+
94
+ def correlation_matrix_sv_den_mat_impl(
95
+ self: CorrelationMatrix,
96
+ *,
97
+ config: EmulationConfig,
98
+ state: DensityMatrix,
99
+ hamiltonian: DenseOperator,
100
+ ) -> torch.Tensor:
101
+ """
102
+ Custom implementation of the density-density correlation <nᵢnⱼ> = Tr(ρ nᵢnⱼ)
103
+ in the case of Lindblad noise
104
+ """
105
+ nqubits = state.n_qudits
106
+ correlation = torch.zeros(nqubits, nqubits, dtype=dtype)
107
+ state_diag_matrix = state.matrix.diagonal()
108
+ for i in range(nqubits): # applying ni
109
+ shapei = (2**i, 2, 2 ** (nqubits - i - 1))
110
+ state_diag_ni = state_diag_matrix.view(*shapei)[:, 1, :]
111
+ correlation[i, i] = state_diag_ni.sum().real # diagonal
112
+ for j in range(i + 1, nqubits):
113
+ shapeij = (2**i, 2 ** (j - i - 1), 2, 2 ** (nqubits - 1 - j))
114
+ state_diag_ni_nj = state_diag_ni.view(*shapeij)[:, :, 1, :]
115
+ correlation[i, j] = state_diag_ni_nj.sum().real
116
+ correlation[j, i] = correlation[i, j]
68
117
  return correlation.cpu()
69
118
 
70
119
 
@@ -111,6 +111,7 @@ class DensityMatrix(State[complex, torch.Tensor]):
111
111
  cls: Type[DensityMatrixType],
112
112
  *,
113
113
  eigenstates: Sequence[Eigenstate],
114
+ n_qudits: int,
114
115
  amplitudes: Mapping[str, complex],
115
116
  ) -> tuple[DensityMatrix, Mapping[str, complex]]:
116
117
  """Transforms a state given by a string into a density matrix.
@@ -140,7 +141,7 @@ class DensityMatrix(State[complex, torch.Tensor]):
140
141
  """
141
142
 
142
143
  state_vector, amplitudes = StateVector._from_state_amplitudes(
143
- eigenstates=eigenstates, amplitudes=amplitudes
144
+ eigenstates=eigenstates, n_qudits=n_qudits, amplitudes=amplitudes
144
145
  )
145
146
 
146
147
  return DensityMatrix.from_state_vector(state_vector), amplitudes
@@ -1,5 +1,5 @@
1
1
  import torch
2
- from emu_base.jump_lindblad_operators import compute_noise_from_lindbladians
2
+ from emu_base import compute_noise_from_lindbladians, matmul_2x2_with_batched
3
3
 
4
4
 
5
5
  dtype = torch.complex128
@@ -17,6 +17,10 @@ class RydbergLindbladian:
17
17
  where A_k is a jump operator and H is the Rydberg Hamiltonian.
18
18
  The complex -𝑖, will be multiplied in the evolution.
19
19
 
20
+ Only works with effective noise channels, i.e., the jump or collapse
21
+ operators. For more information, see:
22
+ https://pulser.readthedocs.io/en/stable/tutorials/effective_noise.html
23
+
20
24
  Attributes:
21
25
  nqubits (int): number of qubits in the system.
22
26
  omegas (torch.Tensor): amplited frequencies Ωⱼ for each qubit, divided by 2.
@@ -96,7 +100,10 @@ class RydbergLindbladian:
96
100
 
97
101
  orignal_shape = density_matrix.shape
98
102
  density_matrix = density_matrix.view(2**target_qubit, 2, -1)
99
- density_matrix = local_op @ density_matrix
103
+ if density_matrix.is_cpu:
104
+ density_matrix = local_op @ density_matrix
105
+ else:
106
+ density_matrix = matmul_2x2_with_batched(local_op, density_matrix)
100
107
 
101
108
  return density_matrix.view(orignal_shape)
102
109
 
@@ -114,9 +121,11 @@ class RydbergLindbladian:
114
121
  """
115
122
 
116
123
  orignal_shape = density_matrix.shape
117
-
118
124
  density_matrix = density_matrix.view(2 ** (target_qubit + self.nqubits), 2, -1)
119
- density_matrix = local_op.conj() @ density_matrix
125
+ if density_matrix.is_cpu:
126
+ density_matrix = local_op.conj() @ density_matrix
127
+ else:
128
+ density_matrix = matmul_2x2_with_batched(local_op.conj(), density_matrix)
120
129
 
121
130
  return density_matrix.view(orignal_shape)
122
131
 
emu_sv/state_vector.py CHANGED
@@ -51,7 +51,7 @@ class StateVector(State[complex, torch.Tensor]):
51
51
  @property
52
52
  def n_qudits(self) -> int:
53
53
  """The number of qudits in the state."""
54
- nqudits = math.log2(self.vector.reshape(-1).shape[0])
54
+ nqudits = math.log2(self.vector.view(-1).shape[0])
55
55
  return int(nqudits)
56
56
 
57
57
  def _normalize(self) -> None:
@@ -220,6 +220,7 @@ class StateVector(State[complex, torch.Tensor]):
220
220
  cls: Type[StateVectorType],
221
221
  *,
222
222
  eigenstates: Sequence[Eigenstate],
223
+ n_qudits: int,
223
224
  amplitudes: Mapping[str, complex],
224
225
  ) -> tuple[StateVector, Mapping[str, complex]]:
225
226
  """Transforms a state given by a string into a state vector.
@@ -257,8 +258,7 @@ class StateVector(State[complex, torch.Tensor]):
257
258
  else:
258
259
  raise ValueError("Unsupported basis provided")
259
260
 
260
- nqubits = cls._validate_amplitudes(amplitudes=amplitudes, eigenstates=eigenstates)
261
- accum_state = StateVector.zero(num_sites=nqubits, eigenstates=eigenstates)
261
+ accum_state = StateVector.zero(num_sites=n_qudits, eigenstates=eigenstates)
262
262
 
263
263
  for state, amplitude in amplitudes.items():
264
264
  bin_to_int = int(
emu_sv/sv_backend.py CHANGED
@@ -1,23 +1,14 @@
1
- import torch
2
- from resource import RUSAGE_SELF, getrusage
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
1
+ from pulser.backend import EmulatorBackend
2
+ from pulser.backend import Results
11
3
  from emu_sv.sv_config import SVConfig
12
- from emu_sv.time_evolution import do_time_step
13
-
14
-
15
- _TIME_CONVERSION_COEFF = 0.001 # Omega and delta are given in rad/μs, dt in ns
4
+ from emu_sv.sv_backend_impl import create_impl
16
5
 
17
6
 
18
7
  class SVBackend(EmulatorBackend):
19
8
  """
20
9
  A backend for emulating Pulser sequences using state vectors and sparse matrices.
10
+ Noisy simulation is supported by solving the Lindblad equation and using effective
11
+ noise channel or jump operators
21
12
  """
22
13
 
23
14
  default_config = SVConfig()
@@ -31,112 +22,5 @@ class SVBackend(EmulatorBackend):
31
22
  """
32
23
  assert isinstance(self._config, SVConfig)
33
24
 
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
40
-
41
- nsteps = omega.shape[0]
42
- nqubits = omega.shape[1]
43
-
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
- )
50
-
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)
56
-
57
- for step in range(nsteps):
58
- dt = self.target_times[step + 1] - self.target_times[step]
59
-
60
- state.vector, H = do_time_step(
61
- dt * _TIME_CONVERSION_COEFF,
62
- omega[step],
63
- delta[step],
64
- phi[step],
65
- pulser_data.full_interaction_matrix,
66
- state.vector,
67
- self._config.krylov_tolerance,
68
- )
69
-
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:
76
- callback(
77
- self._config,
78
- self.target_times[step + 1] / self.target_times[-1],
79
- state,
80
- H, # type: ignore[arg-type]
81
- self.results,
82
- )
83
-
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,
91
- )
92
- self.time = time.time()
93
- del H
94
-
95
- return self.results
96
-
97
-
98
- class Statistics(Observable):
99
- def __init__(
100
- self,
101
- evaluation_times: typing.Sequence[float] | None,
102
- data: list[float],
103
- timestep_count: int,
104
- ):
105
- super().__init__(evaluation_times=evaluation_times)
106
- self.data = data
107
- self.timestep_count = timestep_count
108
-
109
- @property
110
- def _base_tag(self) -> str:
111
- return "statistics"
112
-
113
- def apply(
114
- self,
115
- *,
116
- config: EmulationConfig,
117
- state: State,
118
- **kwargs: typing.Any,
119
- ) -> dict:
120
- """Calculates the observable to store in the Results."""
121
- assert isinstance(state, StateVector)
122
- assert isinstance(config, SVConfig)
123
- duration = self.data[-1]
124
- if state.vector.is_cuda:
125
- max_mem_per_device = (
126
- torch.cuda.max_memory_allocated(device) * 1e-6
127
- for device in range(torch.cuda.device_count())
128
- )
129
- max_mem = max(max_mem_per_device)
130
- else:
131
- max_mem = getrusage(RUSAGE_SELF).ru_maxrss * 1e-3
132
-
133
- config.logger.info(
134
- f"step = {len(self.data)}/{self.timestep_count}, "
135
- + f"RSS = {max_mem:.3f} MB, "
136
- + f"Δt = {duration:.3f} s"
137
- )
138
-
139
- return {
140
- "RSS": max_mem,
141
- "duration": duration,
142
- }
25
+ impl = create_impl(self._sequence, self._config)
26
+ return impl._run()
@@ -0,0 +1,241 @@
1
+ from abc import abstractmethod
2
+ import time
3
+ import typing
4
+
5
+ from emu_sv.hamiltonian import RydbergHamiltonian
6
+ from emu_sv.lindblad_operator import RydbergLindbladian
7
+ from pulser import Sequence
8
+ import torch
9
+ from resource import RUSAGE_SELF, getrusage
10
+
11
+ from pulser.backend import Results, Observable, State, EmulationConfig
12
+ from emu_base import PulserData
13
+
14
+ from emu_sv.state_vector import StateVector
15
+ from emu_sv.density_matrix_state import DensityMatrix
16
+ from emu_sv.sv_config import SVConfig
17
+ from emu_sv.time_evolution import EvolveStateVector, EvolveDensityMatrix
18
+
19
+ _TIME_CONVERSION_COEFF = 0.001 # Omega and delta are given in rad/μs, dt in ns
20
+
21
+
22
+ class Statistics(Observable):
23
+ def __init__(
24
+ self,
25
+ evaluation_times: typing.Sequence[float] | None,
26
+ data: list[float],
27
+ timestep_count: int,
28
+ ):
29
+ super().__init__(evaluation_times=evaluation_times)
30
+ self.data = data
31
+ self.timestep_count = timestep_count
32
+
33
+ @property
34
+ def _base_tag(self) -> str:
35
+ return "statistics"
36
+
37
+ def apply(
38
+ self,
39
+ *,
40
+ config: EmulationConfig,
41
+ state: State,
42
+ **kwargs: typing.Any,
43
+ ) -> dict:
44
+ """Calculates the observable to store in the Results."""
45
+ assert isinstance(state, StateVector | DensityMatrix)
46
+ assert isinstance(config, SVConfig)
47
+ 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
62
+
63
+ config.logger.info(
64
+ f"step = {len(self.data)}/{self.timestep_count}, "
65
+ + f"RSS = {max_mem:.3f} MB, "
66
+ + f"Δt = {duration:.3f} s"
67
+ )
68
+
69
+ return {
70
+ "RSS": max_mem,
71
+ "duration": duration,
72
+ }
73
+
74
+
75
+ class BaseSVBackendImpl:
76
+ """
77
+ This class is used to handle the state vector and density matrix evolution.
78
+ """
79
+
80
+ def __init__(self, config: SVConfig, pulser_data: PulserData):
81
+ self._config = config
82
+ self._pulser_data = pulser_data
83
+ self.target_times = pulser_data.target_times
84
+ self.omega = pulser_data.omega
85
+ self.delta = pulser_data.delta
86
+ self.phi = pulser_data.phi
87
+ self.nsteps = pulser_data.omega.shape[0]
88
+ self.nqubits = pulser_data.omega.shape[1]
89
+ self.state: State
90
+ self.time = time.time()
91
+ self.results = Results(atom_order=(), total_duration=self.target_times[-1])
92
+ self.statistics = Statistics(
93
+ evaluation_times=[t / self.target_times[-1] for t in self.target_times],
94
+ data=[],
95
+ timestep_count=self.nsteps,
96
+ )
97
+ self._current_H: None | RydbergLindbladian | RydbergHamiltonian = None
98
+
99
+ if self._config.initial_state is not None and (
100
+ self._config.initial_state.n_qudits != self.nqubits
101
+ ):
102
+ raise ValueError(
103
+ "Mismatch in number of atoms: initial state has "
104
+ f"{self._config.initial_state.n_qudits} and the sequence has {self.nqubits}"
105
+ )
106
+
107
+ def step(self, step_idx: int) -> None:
108
+ """One step of the evolution"""
109
+ dt = self._compute_dt(step_idx)
110
+ self._evolve_step(dt, step_idx)
111
+ self._apply_observables(step_idx)
112
+ self._save_statistics(step_idx)
113
+
114
+ def _compute_dt(self, step_idx: int) -> float:
115
+ return self.target_times[step_idx + 1] - self.target_times[step_idx]
116
+
117
+ @abstractmethod
118
+ def _evolve_step(self, dt: float, step_idx: int) -> None:
119
+ """One step evolution"""
120
+
121
+ def _apply_observables(self, step_idx: int) -> None:
122
+ norm_time = self.target_times[step_idx + 1] / self.target_times[-1]
123
+ for callback in self._config.observables:
124
+ callback(
125
+ self._config,
126
+ norm_time,
127
+ self.state,
128
+ self._current_H, # type: ignore[arg-type]
129
+ self.results,
130
+ )
131
+
132
+ def _save_statistics(self, step_idx: int) -> None:
133
+ norm_time = self.target_times[step_idx + 1] / self.target_times[-1]
134
+ self.statistics.data.append(time.time() - self.time)
135
+ self.statistics(
136
+ self._config,
137
+ norm_time,
138
+ self.state,
139
+ self._current_H, # type: ignore[arg-type]
140
+ self.results,
141
+ )
142
+ self.time = time.time()
143
+ self._current_H = None
144
+
145
+ def _run(self) -> Results:
146
+ for step in range(self.nsteps):
147
+ self.step(step)
148
+
149
+ return self.results
150
+
151
+
152
+ class SVBackendImpl(BaseSVBackendImpl):
153
+
154
+ def __init__(self, config: SVConfig, pulser_data: PulserData):
155
+ """
156
+ For running sequences without noise. The state will evolve accoring
157
+ to e^(-iH t)
158
+
159
+ Args:
160
+ config: The configuration for the emulator.
161
+ pulser_data: The data for the sequence to be emulated.
162
+ """
163
+ super().__init__(config, pulser_data)
164
+
165
+ self.state: StateVector = (
166
+ StateVector.make(self.nqubits, gpu=self._config.gpu)
167
+ if self._config.initial_state is None
168
+ else StateVector(
169
+ self._config.initial_state.vector.clone(),
170
+ gpu=self._config.gpu,
171
+ )
172
+ )
173
+
174
+ self.stepper = EvolveStateVector.apply
175
+
176
+ def _evolve_step(self, dt: float, step_idx: int) -> None:
177
+ self.state.vector, self._current_H = self.stepper(
178
+ dt * _TIME_CONVERSION_COEFF,
179
+ self.omega[step_idx],
180
+ self.delta[step_idx],
181
+ self.phi[step_idx],
182
+ self._pulser_data.full_interaction_matrix,
183
+ self.state.vector,
184
+ self._config.krylov_tolerance,
185
+ )
186
+
187
+
188
+ class NoisySVBackendImpl(BaseSVBackendImpl):
189
+
190
+ def __init__(self, config: SVConfig, pulser_data: PulserData):
191
+ """
192
+ Initializes the NoisySVBackendImpl, master equation version.
193
+ This class handles the Lindblad operators and
194
+ solves the Lindblad master equation
195
+
196
+ Args:
197
+ config: The configuration for the emulator.
198
+ pulser_data: The data for the sequence to be emulated.
199
+ """
200
+
201
+ super().__init__(config, pulser_data)
202
+
203
+ self.pulser_lindblads = pulser_data.lindblad_ops
204
+
205
+ self.state: DensityMatrix = (
206
+ DensityMatrix.make(self.nqubits, gpu=self._config.gpu)
207
+ if self._config.initial_state is None
208
+ else DensityMatrix(
209
+ self._config.initial_state.matrix.clone(), gpu=self._config.gpu
210
+ )
211
+ )
212
+
213
+ def _evolve_step(self, dt: float, step_idx: int) -> None:
214
+ self.state.matrix, self._current_H = EvolveDensityMatrix.evolve(
215
+ dt * _TIME_CONVERSION_COEFF,
216
+ self.omega[step_idx],
217
+ self.delta[step_idx],
218
+ self.phi[step_idx],
219
+ self._pulser_data.full_interaction_matrix,
220
+ self.state.matrix,
221
+ self._config.krylov_tolerance,
222
+ self.pulser_lindblads,
223
+ )
224
+
225
+
226
+ def create_impl(sequence: Sequence, config: SVConfig) -> BaseSVBackendImpl:
227
+ """
228
+ Creates the backend implementation for the given sequence and config.
229
+
230
+ Args:
231
+ sequence: The sequence to be emulated.
232
+ config: configuration for the emulator.
233
+
234
+ Returns:
235
+ An instance of SVBackendImpl.
236
+ """
237
+ pulse_data = PulserData(sequence=sequence, config=config, dt=config.dt)
238
+ if pulse_data.has_lindblad_noise:
239
+ return NoisySVBackendImpl(config, pulse_data)
240
+ else:
241
+ return SVBackendImpl(config, pulse_data)
emu_sv/sv_config.py CHANGED
@@ -7,9 +7,11 @@ from typing import Any, ClassVar
7
7
 
8
8
  from emu_sv.custom_callback_implementations import (
9
9
  correlation_matrix_sv_impl,
10
+ correlation_matrix_sv_den_mat_impl,
10
11
  energy_second_moment_sv_impl,
11
12
  energy_variance_sv_impl,
12
13
  qubit_occupation_sv_impl,
14
+ qubit_occupation_sv_den_mat_impl,
13
15
  )
14
16
 
15
17
  from pulser.backend import (
@@ -37,6 +39,10 @@ class SVConfig(EmulationConfig):
37
39
  the Lanczos algorithm uses this as the convergence tolerance
38
40
  gpu: Use 1 gpu if True, and a GPU is available, otherwise, cpu.
39
41
  Will cause errors if True when a gpu is not available
42
+ interaction_cutoff: Set interaction coefficients below this value to `0`.
43
+ Potentially improves runtime and memory consumption.
44
+ log_level: How much to log. Set to `logging.WARN` to get rid of the timestep info.
45
+ log_file: If specified, log to this file rather than stout.
40
46
  kwargs: arguments that are passed to the base class
41
47
 
42
48
  Examples:
@@ -97,6 +103,10 @@ class SVConfig(EmulationConfig):
97
103
  "Warning: The runs and samples_per_run "
98
104
  "values of the NoiseModel are ignored!"
99
105
  )
106
+ if "SPAM" in self.noise_model.noise_types:
107
+ raise NotImplementedError(
108
+ "SPAM errors are currently not supported in emu-sv."
109
+ )
100
110
 
101
111
  def _expected_kwargs(self) -> set[str]:
102
112
  return super()._expected_kwargs() | {
@@ -111,24 +121,41 @@ class SVConfig(EmulationConfig):
111
121
 
112
122
  def monkeypatch_observables(self) -> None:
113
123
  obs_list = []
124
+
114
125
  for _, obs in enumerate(self.observables): # monkey patch
115
126
  obs_copy = copy.deepcopy(obs)
127
+
116
128
  if isinstance(obs, Occupation):
117
129
  obs_copy.apply = MethodType( # type: ignore[method-assign]
118
- qubit_occupation_sv_impl, obs_copy
130
+ (
131
+ qubit_occupation_sv_impl
132
+ if self.noise_model.noise_types == ()
133
+ else qubit_occupation_sv_den_mat_impl
134
+ ),
135
+ obs_copy,
136
+ )
137
+ if isinstance(obs, CorrelationMatrix):
138
+ obs_copy.apply = MethodType( # type: ignore[method-assign]
139
+ (
140
+ correlation_matrix_sv_impl
141
+ if self.noise_model.noise_types == ()
142
+ else correlation_matrix_sv_den_mat_impl
143
+ ),
144
+ obs_copy,
119
145
  )
120
- elif isinstance(obs, EnergyVariance):
146
+ if isinstance(obs, EnergyVariance):
147
+ if self.noise_model.noise_types != ():
148
+ raise Exception("Not implemented for density matrix")
121
149
  obs_copy.apply = MethodType( # type: ignore[method-assign]
122
150
  energy_variance_sv_impl, obs_copy
123
151
  )
124
152
  elif isinstance(obs, EnergySecondMoment):
153
+ if self.noise_model.noise_types != ():
154
+ raise Exception("Not implemented for density matrix")
155
+
125
156
  obs_copy.apply = MethodType( # type: ignore[method-assign]
126
157
  energy_second_moment_sv_impl, obs_copy
127
158
  )
128
- elif isinstance(obs, CorrelationMatrix):
129
- obs_copy.apply = MethodType( # type: ignore[method-assign]
130
- correlation_matrix_sv_impl, obs_copy
131
- )
132
159
  obs_list.append(obs_copy)
133
160
  self.observables = tuple(obs_list)
134
161
 
emu_sv/time_evolution.py CHANGED
@@ -1,33 +1,366 @@
1
1
  import torch
2
-
2
+ from typing import Any, no_type_check
3
3
  from emu_base.math.krylov_exp import krylov_exp
4
+ from emu_base.math.double_krylov import double_krylov
4
5
  from emu_sv.hamiltonian import RydbergHamiltonian
6
+ from emu_sv.lindblad_operator import RydbergLindbladian
7
+
5
8
 
9
+ def _apply_omega_real(
10
+ result: torch.Tensor,
11
+ i: int,
12
+ inds: torch.Tensor,
13
+ source: torch.Tensor,
14
+ alpha: complex,
15
+ ) -> None:
16
+ """Accumulate to `result` the application of ασˣᵢ on `source`"""
17
+ result.index_add_(i, inds, source, alpha=alpha)
6
18
 
7
- def do_time_step(
8
- dt: float,
9
- omegas: torch.Tensor,
10
- deltas: torch.Tensor,
11
- phis: torch.Tensor,
12
- full_interaction_matrix: torch.Tensor,
13
- state_vector: torch.Tensor,
14
- krylov_tolerance: float,
15
- ) -> tuple[torch.Tensor, RydbergHamiltonian]:
16
- ham = RydbergHamiltonian(
17
- omegas=omegas,
18
- deltas=deltas,
19
- phis=phis,
20
- interaction_matrix=full_interaction_matrix,
21
- device=state_vector.device,
19
+
20
+ def _apply_omega_complex(
21
+ result: torch.Tensor,
22
+ i: int,
23
+ inds: torch.Tensor,
24
+ source: torch.Tensor,
25
+ alpha: complex,
26
+ ) -> None:
27
+ """Accumulate to `result` the application of ασ⁺ᵢ + α*σ⁻ᵢ on `source`"""
28
+ result.index_add_(i, inds[0], source.select(i, 0).unsqueeze(i), alpha=alpha)
29
+ result.index_add_(
30
+ i,
31
+ inds[1],
32
+ source.select(i, 1).unsqueeze(2),
33
+ alpha=alpha.conjugate(),
22
34
  )
23
- op = lambda x: -1j * dt * (ham * x)
24
35
 
25
- return (
26
- krylov_exp(
36
+
37
+ class DHDOmegaSparse:
38
+ """
39
+ Derivative of the RydbergHamiltonian respect to Omega.
40
+ ∂H/∂Ωₖ = 0.5[cos(ϕₖ)σˣₖ + sin(ϕₖ)σʸₖ]
41
+
42
+ If ϕₖ=0, simplifies to ∂H/∂Ωₖ = 0.5σˣₖ
43
+ """
44
+
45
+ def __init__(self, index: int, device: torch.device, nqubits: int, phi: torch.Tensor):
46
+ self.index = index
47
+ self.shape = (2**index, 2, 2 ** (nqubits - index - 1))
48
+ self.inds = torch.tensor([1, 0], device=device) # flips the state, for 𝜎ₓ
49
+ self.alpha = 0.5 * torch.exp(1j * phi).item()
50
+ if phi.is_nonzero():
51
+ self._apply_sigmas = _apply_omega_complex
52
+ else: # ∂H/∂Ωₖ = 0.5σˣₖ
53
+ self._apply_sigmas = _apply_omega_real
54
+
55
+ def __matmul__(self, vec: torch.Tensor) -> torch.Tensor:
56
+ vec = vec.view(vec.shape[0], *self.shape) # add batch dimension
57
+ result = torch.zeros_like(vec)
58
+ self._apply_sigmas(result, 2, self.inds, vec, alpha=self.alpha)
59
+ return result.view(vec.shape[0], -1)
60
+
61
+
62
+ class DHDPhiSparse:
63
+ """
64
+ Derivative of the RydbergHamiltonian respect to Phi.
65
+ ∂H/∂ϕₖ = 0.5Ωₖ[cos(ϕₖ+π/2)σˣₖ + sin(ϕₖ+π/2)σʸₖ]
66
+ """
67
+
68
+ def __init__(
69
+ self,
70
+ index: int,
71
+ device: torch.device,
72
+ nqubits: int,
73
+ omega: torch.Tensor,
74
+ phi: torch.Tensor,
75
+ ):
76
+ self.index = index
77
+ self.shape = (2**index, 2, 2 ** (nqubits - index - 1))
78
+ self.alpha = 0.5 * (omega * torch.exp(1j * (phi + torch.pi / 2))).item()
79
+ self.inds = torch.tensor([1, 0], device=device) # flips the state, for 𝜎ₓ
80
+
81
+ def __matmul__(self, vec: torch.Tensor) -> torch.Tensor:
82
+ vec = vec.view(vec.shape[0], *self.shape) # add batch dimension
83
+ result = torch.zeros_like(vec)
84
+ _apply_omega_complex(result, 2, self.inds, vec, alpha=self.alpha)
85
+ return result.view(vec.shape[0], -1)
86
+
87
+
88
+ class DHDDeltaSparse:
89
+ """
90
+ Derivative of the Rydberg Hamiltonian respect to Delta:
91
+ ∂H/∂Δᵢ = -nᵢ
92
+ """
93
+
94
+ def __init__(self, i: int, nqubits: int):
95
+ self.nqubits = nqubits
96
+ self.shape = (2**i, 2, 2 ** (nqubits - i - 1))
97
+
98
+ def __matmul__(self, vec: torch.Tensor) -> torch.Tensor:
99
+ result = vec.clone()
100
+ result = result.view(vec.shape[0], *self.shape)
101
+ result[:, :, 0] = 0.0
102
+ return -result.view(vec.shape[0], 2**self.nqubits)
103
+
104
+
105
+ class DHDUSparse:
106
+ """
107
+ Derivative of the Rydberg Hamiltonian respect to the interaction matrix:
108
+ ∂H/∂Uᵢⱼ = nᵢnⱼ
109
+ """
110
+
111
+ def __init__(self, i: int, j: int, nqubits: int):
112
+ self.shape = (2**i, 2, 2 ** (j - i - 1), 2, 2 ** (nqubits - j - 1))
113
+ self.nqubits = nqubits
114
+
115
+ def __matmul__(self, vec: torch.Tensor) -> torch.Tensor:
116
+ result = vec.clone()
117
+ result = result.view(vec.shape[0], *self.shape)
118
+ result[:, :, 0] = 0.0
119
+ result[:, :, 1, :, 0] = 0.0
120
+ return result.view(vec.shape[0], 2**self.nqubits)
121
+
122
+
123
+ class EvolveStateVector(torch.autograd.Function):
124
+ """Custom autograd implementation of a step in the time evolution."""
125
+
126
+ @staticmethod
127
+ def evolve(
128
+ dt: float,
129
+ omegas: torch.Tensor,
130
+ deltas: torch.Tensor,
131
+ phis: torch.Tensor,
132
+ interaction_matrix: torch.Tensor,
133
+ state: torch.Tensor,
134
+ krylov_tolerance: float,
135
+ ) -> tuple[torch.Tensor, RydbergHamiltonian]:
136
+ ham = RydbergHamiltonian(
137
+ omegas=omegas,
138
+ deltas=deltas,
139
+ phis=phis,
140
+ interaction_matrix=interaction_matrix,
141
+ device=state.device,
142
+ )
143
+ op = lambda x: -1j * dt * (ham * x)
144
+ res = krylov_exp(
27
145
  op,
28
- state_vector,
146
+ state,
29
147
  norm_tolerance=krylov_tolerance,
30
148
  exp_tolerance=krylov_tolerance,
31
- ),
32
- ham,
33
- )
149
+ is_hermitian=True,
150
+ )
151
+ return res, ham
152
+
153
+ @staticmethod
154
+ def forward(
155
+ ctx: Any,
156
+ dt: float,
157
+ omegas: torch.Tensor,
158
+ deltas: torch.Tensor,
159
+ phis: torch.Tensor,
160
+ interaction_matrix: torch.Tensor,
161
+ state: torch.Tensor,
162
+ krylov_tolerance: float,
163
+ ) -> tuple[torch.Tensor, RydbergHamiltonian]:
164
+ """
165
+ Returns the time evolved state
166
+ |ψ(t+dt)〉= exp(-i dt H)|ψ(t)〉
167
+ under the Hamiltonian H built from the input Tensor parameters, omegas, deltas, phis and
168
+ the interaction matrix.
169
+
170
+ Args:
171
+ ctx (Any): context object to stash information for backward computation.
172
+ dt (float): timestep
173
+ omegas (torch.Tensor): 1D tensor of driving strengths for each qubit.
174
+ deltas (torch.Tensor): 1D tensor of detuning values for each qubit.
175
+ phis (torch.Tensor): 1D tensor of phase values for each qubit.
176
+ interaction_matrix (torch.Tensor): matrix representing the interaction
177
+ strengths between each pair of qubits.
178
+ state (Tensor): input state to be evolved
179
+ krylov_tolerance (float):
180
+ """
181
+ res, ham = EvolveStateVector.evolve(
182
+ dt, omegas, deltas, phis, interaction_matrix, state, krylov_tolerance
183
+ )
184
+ ctx.save_for_backward(omegas, deltas, phis, interaction_matrix, state)
185
+ ctx.dt = dt
186
+ ctx.tolerance = krylov_tolerance
187
+ return res, ham
188
+
189
+ # mypy complains and I don't know why
190
+ # backward expects same number of gradients as output of forward, gham is unused
191
+ @no_type_check
192
+ @staticmethod
193
+ def backward(ctx: Any, grad_state_out: torch.Tensor, gham: None) -> tuple[
194
+ None,
195
+ torch.Tensor | None,
196
+ torch.Tensor | None,
197
+ torch.Tensor | None,
198
+ torch.Tensor | None,
199
+ torch.Tensor | None,
200
+ None,
201
+ ]:
202
+ """
203
+ In the backward pass we receive a Tensor containing the gradient of the loss L
204
+ with respect to the output
205
+ |gψ(t+dt)〉= ∂L/∂|ψ(t+dt)〉,
206
+ and return the gradients of the loss with respect to the input tensors parameters
207
+ - gΩⱼ = ∂L/∂Ωⱼ =〈gψ(t+dt)|dU(H,∂H/∂Ωⱼ)|ψ(t)〉
208
+ - gΔⱼ = ∂L/∂Δⱼ = ...
209
+ - |gψ(t)〉= ∂L/∂|ψ(t)〉= exp(i dt H)|gψ(t+dt)〉
210
+
211
+ Args:
212
+ ctx (Any): context object to stash information for backward computation.
213
+ grad_state_out (torch.Tensor): |gψ(t+dt)〉
214
+
215
+ Return:
216
+ grad_omegas (torch.Tensor): 1D tensor of gradients with respect to Ωⱼ for each qubit.
217
+ grad_deltas (torch.Tensor): 1D tensor of gradients with respect to Δⱼ for each qubit.
218
+ grad_phis (torch.Tensor): 1D tensor of gradients with respect to φⱼ for each qubit.
219
+ grad_state_in (torch.Tensor): 1D tensor gradient with respect to the input state.
220
+
221
+ Notes:
222
+ Gradients are obtained by matching the total variations
223
+ 〈gψ(t+dt)|d|ψ(t+dt)〉= ∑ⱼgΔⱼ*dΔⱼ + ∑ⱼgΩⱼ*dΩⱼ + ∑ⱼgφ*dφⱼ +〈gψ(t)|d|ψ(t)〉 (1)
224
+
225
+ For the exponential map U = exp(-i dt H), differentiating reads:
226
+ d|ψ(t+dt)〉= dU|ψ(t)〉+ Ud|ψ(t)〉
227
+ dU = ∑ⱼdU(H,∂H/∂Δⱼ) + ∑ⱼdU(H,∂H/∂Ωⱼ) + ∑ⱼdU(H,∂H/∂φⱼ) (2)
228
+
229
+ where dU(H,E) is the Fréchet derivative of the exponential map
230
+ along the direction E:
231
+ - https://eprints.maths.manchester.ac.uk/1218/1/covered/MIMS_ep2008_26.pdf
232
+ - https://en.wikipedia.org/wiki/Derivative_of_the_exponential_map
233
+
234
+ Substituting (2) into (1) leads to the expressions of the gradients
235
+ with respect to the input tensors above.
236
+
237
+ Variations with respect to the Hamiltonian parameters are computed as
238
+ gΩ = 〈gψ(t+dt)|dU(H,∂H/∂Ω)|ψ(t)〉
239
+ = Tr( -i dt ∂H/∂Ω @ dU(H,|ψ(t)〉〈gψ(t+dt)|) ),
240
+ where under the trace sign, ∂H/∂Ω and |ψ(t)〉〈gψ(t+dt)| can be switched.
241
+
242
+ - The Fréchet derivative is computed in a Arnoldi-Gram-Schmidt
243
+ decomposition in the `double_krylov` method:
244
+ dU(H,|a〉〈b|) = Va @ dS @ Vb*
245
+ where Va,Vb are orthogonal Krylov basis associated
246
+ with |a〉and |b〉respectively.
247
+
248
+ - The action of the derivatives of the Hamiltonian with
249
+ respect to the input parameters are implemented separately in
250
+ - ∂H/∂Ω: `DHDOmegaSparse`
251
+ - ∂H/∂Δ: `DHDDeltaSparse`
252
+ - ∂H/∂φ: `DHDPhiSparse`
253
+ - ∂H/∂Uᵢⱼ `DHDUSparse`
254
+
255
+ Then, the resulting gradient respect to a generic parameter reads:
256
+ gΩ = Tr( -i dt ∂H/∂Ω @ Vs @ dS @ Vg* )
257
+ """
258
+ omegas, deltas, phis, interaction_matrix, state = ctx.saved_tensors
259
+ dt = ctx.dt
260
+ tolerance = ctx.tolerance
261
+ nqubits = len(omegas)
262
+
263
+ grad_omegas, grad_deltas, grad_phis = None, None, None
264
+ grad_int_mat = None
265
+ grad_state_in = None
266
+
267
+ ham = RydbergHamiltonian(
268
+ omegas=omegas,
269
+ deltas=deltas,
270
+ phis=phis,
271
+ interaction_matrix=interaction_matrix,
272
+ device=state.device,
273
+ )
274
+
275
+ if any(ctx.needs_input_grad[1:5]):
276
+ op = lambda x: -1j * dt * (ham * x)
277
+ lanczos_vectors_state, dS, lanczos_vectors_grad = double_krylov(
278
+ op, state, grad_state_out, tolerance
279
+ )
280
+ # TODO: explore returning directly the basis in matrix form
281
+ Vs = torch.stack(lanczos_vectors_state)
282
+ del lanczos_vectors_state
283
+ Vg = torch.stack(lanczos_vectors_grad)
284
+ del lanczos_vectors_grad
285
+ e_l = dS.mT @ Vs
286
+
287
+ if ctx.needs_input_grad[1]:
288
+ grad_omegas = torch.zeros_like(omegas)
289
+ for i in range(nqubits):
290
+ # dh as per the docstring
291
+ dho = DHDOmegaSparse(i, e_l.device, nqubits, phis[i])
292
+ # compute the trace
293
+ v = dho @ e_l
294
+ grad_omegas[i] = (-1j * dt * torch.tensordot(Vg.conj(), v)).real
295
+
296
+ if ctx.needs_input_grad[2]:
297
+ grad_deltas = torch.zeros_like(deltas)
298
+ for i in range(nqubits):
299
+ dhd = DHDDeltaSparse(i, nqubits)
300
+ v = dhd @ e_l
301
+ grad_deltas[i] = (-1j * dt * torch.tensordot(Vg.conj(), v)).real
302
+
303
+ if ctx.needs_input_grad[3]:
304
+ grad_phis = torch.zeros_like(phis)
305
+ for i in range(nqubits):
306
+ dhp = DHDPhiSparse(i, e_l.device, nqubits, omegas[i], phis[i])
307
+ v = dhp @ e_l
308
+ grad_phis[i] = (-1j * dt * torch.tensordot(Vg.conj(), v)).real
309
+
310
+ if ctx.needs_input_grad[4]:
311
+ grad_int_mat = torch.zeros_like(interaction_matrix)
312
+ for i in range(nqubits):
313
+ for j in range(i + 1, nqubits):
314
+ dhu = DHDUSparse(i, j, nqubits)
315
+ v = dhu @ e_l
316
+ grad_int_mat[i, j] = (-1j * dt * torch.tensordot(Vg.conj(), v)).real
317
+
318
+ if ctx.needs_input_grad[5]:
319
+ op = lambda x: (1j * dt) * (ham * x)
320
+ grad_state_in = krylov_exp(op, grad_state_out.detach(), tolerance, tolerance)
321
+
322
+ return (
323
+ None,
324
+ grad_omegas,
325
+ grad_deltas,
326
+ grad_phis,
327
+ grad_int_mat,
328
+ grad_state_in,
329
+ None,
330
+ )
331
+
332
+
333
+ class EvolveDensityMatrix:
334
+ """Evolution of a density matrix under a Lindbladian operator."""
335
+
336
+ @staticmethod
337
+ def evolve(
338
+ dt: float,
339
+ omegas: torch.Tensor,
340
+ deltas: torch.Tensor,
341
+ phis: torch.Tensor,
342
+ full_interaction_matrix: torch.Tensor,
343
+ density_matrix: torch.Tensor,
344
+ krylov_tolerance: float,
345
+ pulser_lindblads: list[torch.Tensor],
346
+ ) -> tuple[torch.Tensor, RydbergLindbladian]:
347
+ ham = RydbergLindbladian(
348
+ omegas=omegas,
349
+ deltas=deltas,
350
+ phis=phis,
351
+ pulser_linblads=pulser_lindblads,
352
+ interaction_matrix=full_interaction_matrix,
353
+ device=density_matrix.device,
354
+ )
355
+
356
+ op = lambda x: -1j * dt * (ham @ x)
357
+ return (
358
+ krylov_exp(
359
+ op,
360
+ density_matrix,
361
+ norm_tolerance=krylov_tolerance,
362
+ exp_tolerance=krylov_tolerance,
363
+ is_hermitian=False,
364
+ ),
365
+ ham,
366
+ )
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emu-sv
3
- Version: 2.0.4
3
+ Version: 2.2.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
7
7
  Project-URL: Issues, https://github.com/pasqal-io/emulators/issues
8
- Author-email: Anton Quelle <anton.quelle@pasqal.com>, Mauro Mendizabal <mauro.mendizabal-pico@pasqal.com>, Stefano Grava <stefano.grava@pasqal.com>, Pablo Le Henaff <pablo.le-henaff@pasqal.com>
8
+ Author-email: Kemal Bidzhiev <kemal.bidzhiev@pasqal.com>, Stefano Grava <stefano.grava@pasqal.com>, Pablo Le Henaff <pablo.le-henaff@pasqal.com>, Mauro Mendizabal <mauro.mendizabal-pico@pasqal.com>, Elie Merhej <elie.merhej@pasqal.com>, Anton Quelle <anton.quelle@pasqal.com>
9
9
  License: PASQAL OPEN-SOURCE SOFTWARE LICENSE AGREEMENT (MIT-derived)
10
10
 
11
11
  The author of the License is:
@@ -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.0.4
28
+ Requires-Dist: emu-base==2.2.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=vVU7zIUsHPgFXUBWMn1lUtkWUg-avdFRqLUrtbmPvDI,771
2
+ emu_sv/custom_callback_implementations.py,sha256=j_G5x-xSnFSyKE81QN_DKoiTaF9JvNWaM1N6k2_t0Oo,5501
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=Rlxh24dXVUAutSrSacNO2ilNVlxKix8pfFt7h2CfVOg,7893
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=ixMTgDXKll4bXsYtAe4a_9Gng0bhwCgS42KKMwZCFHI,6308
11
+ emu_sv/time_evolution.py,sha256=_VH4f2RF6lGKzO08WxTYJ5Aw8_pTTMRKcyMnIuxH03I,13382
12
+ emu_sv/utils.py,sha256=-axfQ2tqw0C7I9yw-28g7lytyk373DNBjDALh4kLBrM,302
13
+ emu_sv-2.2.0.dist-info/METADATA,sha256=aw71GDNhy2P9-zjXjXszkLyaOY95qCpPkOmG2lAmYZg,3595
14
+ emu_sv-2.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ emu_sv-2.2.0.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- emu_sv/__init__.py,sha256=Tzc6RlABZ1ZVKt1mPUes9djq0eiK6FTgPagEHuFhF6Q,702
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=CqNGuWJlO2ZljK47wt130s-5uKiOldQUsC3tjwk1mKA,6106
6
- emu_sv/lindblad_operator.py,sha256=KmaNCahpAi8SIXh-TrFD-ggmGpa1zklp8DMWVK9Y_J4,7433
7
- emu_sv/state_vector.py,sha256=lqSbv4BMtDtgY0YUPuhIUNJxrlVa7vUWuN_XqwpG5sQ,9823
8
- emu_sv/sv_backend.py,sha256=AkEtI6-SY20D0ORro3Kv8tHDRUc8gxejSiRa6d--vBE,4452
9
- emu_sv/sv_config.py,sha256=QRy0VbCugmY6TQZ48nD6RxPJbpu0wzN7-E1Sud7YxLQ,5106
10
- emu_sv/time_evolution.py,sha256=obV7DcHot0jtnEmjR1ilYiSyDcJ5rTvThRB8hFjP-2s,797
11
- emu_sv/utils.py,sha256=-axfQ2tqw0C7I9yw-28g7lytyk373DNBjDALh4kLBrM,302
12
- emu_sv-2.0.4.dist-info/METADATA,sha256=paHoVgW22OONoxvlwaypB-UF01zi0giZqUtvAz7fhmw,3513
13
- emu_sv-2.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
- emu_sv-2.0.4.dist-info/RECORD,,
File without changes