emu-base 2.3.0__py3-none-any.whl → 2.4.1__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_base/__init__.py CHANGED
@@ -4,15 +4,13 @@ from .math.brents_root_finding import find_root_brents
4
4
  from .math.krylov_exp import krylov_exp, DEFAULT_MAX_KRYLOV_DIM
5
5
  from .jump_lindblad_operators import compute_noise_from_lindbladians
6
6
  from .math.matmul import matmul_2x2_with_batched
7
- from .aggregators import AggregationType, aggregate
8
- from .utils import apply_measurement_errors
7
+ from .utils import get_max_rss, apply_measurement_errors, unix_like
9
8
 
10
9
  __all__ = [
11
10
  "__version__",
11
+ "get_max_rss",
12
12
  "compute_noise_from_lindbladians",
13
13
  "matmul_2x2_with_batched",
14
- "AggregationType",
15
- "aggregate",
16
14
  "PulserData",
17
15
  "find_root_brents",
18
16
  "krylov_exp",
@@ -20,6 +18,7 @@ __all__ = [
20
18
  "DEFAULT_MAX_KRYLOV_DIM",
21
19
  "DEVICE_COUNT",
22
20
  "apply_measurement_errors",
21
+ "unix_like",
23
22
  ]
24
23
 
25
- __version__ = "2.3.0"
24
+ __version__ = "2.4.1"
@@ -1,7 +1,7 @@
1
1
  import torch
2
2
  from typing import Callable, Tuple
3
3
 
4
- DEFAULT_MAX_KRYLOV_DIM: int = 100
4
+ DEFAULT_MAX_KRYLOV_DIM: int = 200
5
5
 
6
6
 
7
7
  def _lowest_eigen_pair(
@@ -1,268 +1,114 @@
1
- from typing import Tuple, Sequence, Any
1
+ from typing import Sequence
2
2
  from enum import Enum
3
3
  import torch
4
4
  import math
5
5
  import pulser
6
+ from pulser.sampler import SequenceSamples
6
7
  from pulser.noise_model import NoiseModel
7
- from pulser.register.base_register import BaseRegister, QubitId
8
+ from pulser.register.base_register import QubitId
8
9
  from pulser.backend.config import EmulationConfig
10
+ from pulser._hamiltonian_data import HamiltonianData
9
11
  from emu_base.jump_lindblad_operators import get_lindblad_operators
10
12
 
11
- KB_PER_RUBIDIUM_MASS = 95.17241379310344 # J/K/kg
12
- KEFF = 8.7 # µm^-1, conversion from atom velocity to detuning
13
-
14
13
 
15
14
  class HamiltonianType(Enum):
16
15
  Rydberg = 1
17
16
  XY = 2
18
17
 
19
18
 
20
- SUPPORTED_NOISES: dict = {
21
- HamiltonianType.Rydberg: {
22
- "amplitude",
23
- "dephasing",
24
- "relaxation",
25
- "depolarizing",
26
- "doppler",
27
- "eff_noise",
28
- "SPAM",
29
- # "leakage",
30
- },
31
- HamiltonianType.XY: {
32
- "dephasing",
33
- "depolarizing",
34
- "eff_noise",
35
- "SPAM",
36
- }, # , "leakage"},
37
- }
19
+ _NON_LINDBLADIAN_NOISE = {"SPAM", "doppler", "amplitude", "detuning", "register"}
38
20
 
39
21
 
40
- def _get_qubit_positions(
41
- register: BaseRegister,
22
+ def _get_all_lindblad_noise_operators(
23
+ noise_model: NoiseModel | None,
42
24
  ) -> list[torch.Tensor]:
43
- """Conversion from pulser Register to emu-mps register (torch type).
44
- Each element will be given as [Rx,Ry,Rz]"""
25
+ if noise_model is None:
26
+ return []
45
27
 
46
- positions = [
47
- position.as_tensor().to(dtype=torch.float64)
48
- for position in register.qubits.values()
28
+ return [
29
+ op
30
+ for noise_type in noise_model.noise_types
31
+ if noise_type not in _NON_LINDBLADIAN_NOISE
32
+ for op in get_lindblad_operators(noise_type=noise_type, noise_model=noise_model)
49
33
  ]
50
- if len(positions[0]) == 2:
51
- return [torch.cat((position, torch.zeros(1))) for position in positions]
52
- return positions
53
-
54
-
55
- def _rydberg_interaction(sequence: pulser.Sequence) -> torch.Tensor:
56
- """
57
- Returns the Rydberg interaction matrix from the qubit positions.
58
- Uᵢⱼ=C₆/|rᵢ-rⱼ|⁶
59
-
60
- see Pulser
61
- [documentation](https://pulser.readthedocs.io/en/stable/conventions.html#interaction-hamiltonian).
62
- """
63
-
64
- nqubits = len(sequence.register.qubit_ids)
65
- c6 = sequence.device.interaction_coeff
66
- positions = _get_qubit_positions(sequence.register)
67
34
 
68
- interaction_matrix = torch.zeros(nqubits, nqubits, dtype=torch.float64)
69
- for i in range(nqubits):
70
- for j in range(i + 1, nqubits):
71
- rij = torch.dist(positions[i], positions[j])
72
- interaction_matrix[[i, j], [j, i]] = c6 / rij**6
73
- return interaction_matrix
74
35
 
36
+ def _get_target_times(
37
+ sequence: pulser.Sequence, config: EmulationConfig, dt: int
38
+ ) -> list[int]:
39
+ sequence_duration = sequence.get_duration(include_fall_time=config.with_modulation)
75
40
 
76
- def _xy_interaction(sequence: pulser.Sequence) -> torch.Tensor:
77
- """
78
- Returns the XY interaction matrix from the qubit positions.
79
- Uᵢⱼ=C₃(1−3cos(𝜃ᵢⱼ)²)/|rᵢ-rⱼ|³
80
- with
81
- cos(𝜃ᵢⱼ) = (rᵢ-rⱼ)·m/|m||rᵢ-rⱼ|
82
-
83
- see Pulser
84
- [documentation](https://pulser.readthedocs.io/en/stable/conventions.html#interaction-hamiltonian).
85
- """
86
-
87
- nqubits = len(sequence.register.qubit_ids)
88
- c3 = sequence.device.interaction_coeff_xy
89
- mag_field = torch.tensor(sequence.magnetic_field, dtype=torch.float64)
90
- mag_field /= mag_field.norm()
91
- positions = _get_qubit_positions(sequence.register)
92
-
93
- interaction_matrix = torch.zeros(nqubits, nqubits, dtype=torch.float64)
94
- for i in range(nqubits):
95
- for j in range(i + 1, nqubits):
96
- rij = torch.dist(positions[i], positions[j])
97
- cos_ij = torch.dot(positions[i] - positions[j], mag_field) / rij
98
- interaction_matrix[[i, j], [j, i]] = c3 * (1 - 3 * cos_ij**2) / rij**3
99
- return interaction_matrix
100
-
101
-
102
- def _get_amp_factors(
103
- samples: pulser.sampler.SequenceSamples,
104
- amp_sigma: float,
105
- laser_waist: float | None,
106
- qubit_positions: list[torch.Tensor],
107
- q_ids: tuple[str, ...],
108
- ) -> dict[int, torch.Tensor]:
109
- def perp_dist(pos: torch.Tensor, axis: torch.Tensor) -> Any:
110
- return torch.linalg.vector_norm(pos - torch.vdot(pos, axis) * axis)
111
-
112
- times_to_amp_factors: dict[int, torch.Tensor] = {}
113
- for ch, ch_samples in samples.channel_samples.items():
114
- ch_obj = samples._ch_objs[ch]
115
- prop_dir = torch.tensor(
116
- ch_obj.propagation_dir or [0.0, 1.0, 0.0], dtype=torch.float64
117
- )
118
- prop_dir /= prop_dir.norm()
119
-
120
- # each channel has a noise on its laser amplitude
121
- # we assume each channel has the same noise amplitude currently
122
- # the hardware currently has only a global channel anyway
123
- sigma_factor = (
124
- 1.0
125
- if amp_sigma == 0.0
126
- else torch.max(torch.tensor(0), torch.normal(1.0, amp_sigma, (1,))).item()
127
- )
128
- for slot in ch_samples.slots:
129
- factors = (
130
- torch.tensor(
131
- [
132
- math.exp(-((perp_dist(x, prop_dir) / laser_waist) ** 2))
133
- for x in qubit_positions
134
- ],
135
- dtype=torch.float64,
136
- ) # the lasers have a gaussian profile perpendicular to the propagation direction
137
- if laser_waist and ch_obj.addressing == "Global"
138
- else torch.ones(
139
- len(q_ids), dtype=torch.float64
140
- ) # but for a local channel, this does not matter
141
- )
142
-
143
- # add the amplitude noise for the targeted qubits
144
- factors[[x in slot.targets for x in q_ids]] *= sigma_factor
145
-
146
- for i in range(slot.ti, slot.tf):
147
- if i in times_to_amp_factors: # multiple local channels at the same time
148
- # pulser enforces that no two lasers target the same qubit simultaneously
149
- # so only a single factor will be != 1.0 for each qubit
150
- times_to_amp_factors[i] = factors * times_to_amp_factors[i]
151
- else:
152
- times_to_amp_factors[i] = factors
153
- return times_to_amp_factors
154
-
41
+ observable_times = set(range(0, sequence_duration, dt))
42
+ observable_times.add(sequence_duration)
43
+ for obs in config.observables:
44
+ times: Sequence[float]
45
+ if obs.evaluation_times is not None:
46
+ times = obs.evaluation_times
47
+ elif config.default_evaluation_times != "Full":
48
+ times = config.default_evaluation_times.tolist() # type: ignore[union-attr,assignment]
49
+ observable_times |= set([round(time * sequence_duration) for time in times])
155
50
 
156
- def _get_delta_offset(nqubits: int, temperature: float) -> torch.Tensor:
157
- """
158
- The delta values are shifted due to atomic velocities.
159
- The atomic velocities follow the Maxwell distribution
160
- https://en.wikipedia.org/wiki/Maxwell%E2%80%93Boltzmann_distribution
161
- and then a given residual velocity is converted to a delta offset per
162
- https://en.wikipedia.org/wiki/Doppler_broadening
163
- """
164
- if temperature == 0.0:
165
- return torch.zeros(nqubits, dtype=torch.float64)
166
- t = temperature * 1e-6 # microKelvin -> Kelvin
167
- sigma = KEFF * math.sqrt(KB_PER_RUBIDIUM_MASS * t)
168
- return torch.normal(0.0, sigma, (nqubits,))
51
+ target_times: list[int] = list(observable_times)
52
+ target_times.sort()
53
+ return target_times
169
54
 
170
55
 
171
56
  def _extract_omega_delta_phi(
172
- *,
173
- sequence: pulser.Sequence,
57
+ noisy_samples: SequenceSamples,
58
+ qubit_ids: tuple[str, ...],
174
59
  target_times: list[int],
175
- with_modulation: bool,
176
- laser_waist: float | None,
177
- amp_sigma: float,
178
- temperature: float,
179
- ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
180
- """
181
- Samples the Pulser sequence and returns a tuple of tensors (omega, delta, phi)
182
- containing:
183
- - omega[i, q] = amplitude at time i * dt for qubit q
184
- - delta[i, q] = detuning at time i * dt for qubit q
185
- - phi[i, q] = phase at time i * dt for qubit q
186
-
187
- if laser_waist is w_0 != None, the omega values coming from the global pulse channel
188
- will me modulated as $\\Omega_i=\\Omega_i e^{-r_i^2/w_0^2}$
189
- """
190
-
191
- if with_modulation and sequence._slm_mask_targets:
192
- raise NotImplementedError(
193
- "Simulation of sequences combining an SLM mask and output "
194
- "modulation is not supported."
195
- )
196
-
197
- q_ids = sequence.register.qubit_ids
198
-
199
- samples = pulser.sampler.sample(
200
- sequence,
201
- modulation=with_modulation,
202
- extended_duration=sequence.get_duration(include_fall_time=with_modulation),
203
- )
204
- sequence_dict = samples.to_nested_dict(all_local=True, samples_type="tensor")["Local"]
205
-
206
- if "ground-rydberg" in sequence_dict and len(sequence_dict) == 1:
207
- locals_a_d_p = sequence_dict["ground-rydberg"]
208
- elif "XY" in sequence_dict and len(sequence_dict) == 1:
209
- locals_a_d_p = sequence_dict["XY"]
210
- else:
211
- raise ValueError("Only `ground-rydberg` and `mw_global` channels are supported.")
212
-
60
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
61
+ sequence_dict = noisy_samples.to_nested_dict(all_local=True, samples_type="tensor")[
62
+ "Local"
63
+ ]
213
64
  nsamples = len(target_times) - 1
214
65
  omega = torch.zeros(
215
66
  nsamples,
216
- len(q_ids),
67
+ len(qubit_ids),
217
68
  dtype=torch.complex128,
218
69
  )
219
-
220
70
  delta = torch.zeros(
221
71
  nsamples,
222
- len(q_ids),
72
+ len(qubit_ids),
223
73
  dtype=torch.complex128,
224
74
  )
225
75
  phi = torch.zeros(
226
76
  nsamples,
227
- len(q_ids),
77
+ len(qubit_ids),
228
78
  dtype=torch.complex128,
229
79
  )
230
- qubit_positions = _get_qubit_positions(sequence.register)
231
-
232
- times_to_amp_factors = _get_amp_factors(
233
- samples, amp_sigma, laser_waist, qubit_positions, q_ids
234
- )
235
-
236
- omega_1 = torch.zeros_like(omega[0])
237
- omega_2 = torch.zeros_like(omega[0])
238
- max_duration = sequence.get_duration(include_fall_time=with_modulation)
80
+ max_duration = noisy_samples.max_duration
239
81
 
82
+ if "ground-rydberg" in sequence_dict and len(sequence_dict) == 1:
83
+ locals_a_d_p = sequence_dict["ground-rydberg"]
84
+ elif "XY" in sequence_dict and len(sequence_dict) == 1:
85
+ locals_a_d_p = sequence_dict["XY"]
86
+ else:
87
+ raise ValueError("Only `ground-rydberg` and `mw_global` channels are supported.")
240
88
  for i in range(nsamples):
241
89
  t = (target_times[i] + target_times[i + 1]) / 2
242
90
  # The sampled values correspond to the start of each interval
243
91
  # To maximize the order of the solver, we need the values in the middle
244
92
  if math.ceil(t) < max_duration:
245
- # If we're not the final step, approximate this using linear interpolation
93
+ # If we're not the final step, approximate this using linear
94
+ # interpolation
246
95
  # Note that for dt even, t1=t2
247
96
  t1 = math.floor(t)
248
97
  t2 = math.ceil(t)
249
- for q_pos, q_id in enumerate(q_ids):
250
- omega_1[q_pos] = locals_a_d_p[q_id]["amp"][t1]
251
- omega_2[q_pos] = locals_a_d_p[q_id]["amp"][t2]
98
+ for q_pos, q_id in enumerate(qubit_ids):
99
+ omega[i, q_pos] = (
100
+ locals_a_d_p[q_id]["amp"][t1] + locals_a_d_p[q_id]["amp"][t2]
101
+ ) / 2.0
252
102
  delta[i, q_pos] = (
253
103
  locals_a_d_p[q_id]["det"][t1] + locals_a_d_p[q_id]["det"][t2]
254
104
  ) / 2.0
255
105
  phi[i, q_pos] = (
256
106
  locals_a_d_p[q_id]["phase"][t1] + locals_a_d_p[q_id]["phase"][t2]
257
107
  ) / 2.0
258
- # omegas at different times need to have the laser waist applied independently
259
- omega_1 *= times_to_amp_factors.get(t1, 1.0)
260
- omega_2 *= times_to_amp_factors.get(t2, 1.0)
261
- omega[i] = 0.5 * (omega_1 + omega_2)
262
108
  else:
263
109
  # We're in the final step and dt=1, approximate this using linear extrapolation
264
110
  # we can reuse omega_1 and omega_2 from before
265
- for q_pos, q_id in enumerate(q_ids):
111
+ for q_pos, q_id in enumerate(qubit_ids):
266
112
  delta[i, q_pos] = (
267
113
  3.0 * locals_a_d_p[q_id]["det"][t2] - locals_a_d_p[q_id]["det"][t1]
268
114
  ) / 2.0
@@ -270,50 +116,14 @@ def _extract_omega_delta_phi(
270
116
  3.0 * locals_a_d_p[q_id]["phase"][t2]
271
117
  - locals_a_d_p[q_id]["phase"][t1]
272
118
  ) / 2.0
273
- omega[i] = torch.clamp(0.5 * (3 * omega_2 - omega_1).real, min=0.0)
274
-
275
- doppler_offset = _get_delta_offset(len(q_ids), temperature)
276
- delta += doppler_offset
119
+ omega[i, q_pos] = max(
120
+ (3.0 * locals_a_d_p[q_id]["amp"][t2] - locals_a_d_p[q_id]["amp"][t1])
121
+ / 2.0,
122
+ 0.0,
123
+ )
277
124
  return omega, delta, phi
278
125
 
279
126
 
280
- _NON_LINDBLADIAN_NOISE = {"SPAM", "doppler", "amplitude"}
281
-
282
-
283
- def _get_all_lindblad_noise_operators(
284
- noise_model: NoiseModel | None,
285
- ) -> list[torch.Tensor]:
286
- if noise_model is None:
287
- return []
288
-
289
- return [
290
- op
291
- for noise_type in noise_model.noise_types
292
- if noise_type not in _NON_LINDBLADIAN_NOISE
293
- for op in get_lindblad_operators(noise_type=noise_type, noise_model=noise_model)
294
- ]
295
-
296
-
297
- def _get_target_times(
298
- sequence: pulser.Sequence, config: EmulationConfig, dt: int
299
- ) -> list[int]:
300
- sequence_duration = sequence.get_duration(include_fall_time=config.with_modulation)
301
- # the end value is exclusive, so add +1
302
- observable_times = set(range(0, sequence_duration + 1, dt))
303
- observable_times.add(sequence_duration)
304
- for obs in config.observables:
305
- times: Sequence[float]
306
- if obs.evaluation_times is not None:
307
- times = obs.evaluation_times
308
- elif config.default_evaluation_times != "Full":
309
- times = config.default_evaluation_times.tolist() # type: ignore[union-attr,assignment]
310
- observable_times |= set([round(time * sequence_duration) for time in times])
311
-
312
- target_times: list[int] = list(observable_times)
313
- target_times.sort()
314
- return target_times
315
-
316
-
317
127
  class PulserData:
318
128
  slm_end_time: float
319
129
  full_interaction_matrix: torch.Tensor
@@ -329,35 +139,23 @@ class PulserData:
329
139
  self.qubit_ids = sequence.register.qubit_ids
330
140
  self.qubit_count = len(self.qubit_ids)
331
141
  self.target_times = _get_target_times(sequence=sequence, config=config, dt=dt)
142
+ self.hamiltonian = HamiltonianData.from_sequence(
143
+ sequence,
144
+ with_modulation=config.with_modulation,
145
+ noise_model=config.noise_model,
146
+ )
332
147
 
333
- laser_waist = config.noise_model.laser_waist
334
- amp_sigma = config.noise_model.amp_sigma
335
- temperature = config.noise_model.temperature
336
148
  self.omega, self.delta, self.phi = _extract_omega_delta_phi(
337
- sequence=sequence,
338
- target_times=self.target_times,
339
- with_modulation=config.with_modulation,
340
- laser_waist=laser_waist,
341
- amp_sigma=amp_sigma,
342
- temperature=temperature,
149
+ self.hamiltonian.noisy_samples, self.qubit_ids, self.target_times
343
150
  )
344
151
 
345
- addressed_basis = sequence.get_addressed_bases()[0]
346
- if addressed_basis == "ground-rydberg": # for local and global
152
+ int_type = self.hamiltonian.interaction_type
153
+ if int_type == "ising": # for local and global
347
154
  self.hamiltonian_type = HamiltonianType.Rydberg
348
- elif addressed_basis == "XY":
155
+ elif int_type == "XY":
349
156
  self.hamiltonian_type = HamiltonianType.XY
350
157
  else:
351
- raise ValueError(f"Unsupported basis: {addressed_basis}")
352
-
353
- not_supported = (
354
- set(config.noise_model.noise_types) - SUPPORTED_NOISES[self.hamiltonian_type]
355
- )
356
- if not_supported:
357
- raise NotImplementedError(
358
- f"Interaction mode '{self.hamiltonian_type}' does not support "
359
- f"simulation of noise types: {', '.join(not_supported)}."
360
- )
158
+ raise ValueError(f"Unsupported basis: {int_type}")
361
159
 
362
160
  self.lindblad_ops = _get_all_lindblad_noise_operators(config.noise_model)
363
161
  self.has_lindblad_noise: bool = self.lindblad_ops != []
@@ -369,10 +167,11 @@ class PulserData:
369
167
  )
370
168
 
371
169
  self.full_interaction_matrix = config.interaction_matrix.as_tensor()
372
- elif self.hamiltonian_type == HamiltonianType.Rydberg:
373
- self.full_interaction_matrix = _rydberg_interaction(sequence)
374
- elif self.hamiltonian_type == HamiltonianType.XY:
375
- self.full_interaction_matrix = _xy_interaction(sequence)
170
+ else:
171
+ self.full_interaction_matrix = (
172
+ self.hamiltonian.noisy_interaction_matrix.as_tensor()
173
+ )
174
+
376
175
  self.full_interaction_matrix[
377
176
  torch.abs(self.full_interaction_matrix) < config.interaction_cutoff
378
177
  ] = 0.0
emu_base/utils.py CHANGED
@@ -1,6 +1,11 @@
1
1
  from collections import Counter
2
2
  import random
3
3
  import torch
4
+ import os
5
+
6
+ unix_like = os.name != "nt"
7
+ if unix_like:
8
+ from resource import RUSAGE_SELF, getrusage
4
9
 
5
10
 
6
11
  def deallocate_tensor(t: torch.Tensor) -> None:
@@ -37,6 +42,20 @@ def deallocate_tensor(t: torch.Tensor) -> None:
37
42
  t._base.set_(source=replacement_storage)
38
43
 
39
44
 
45
+ def get_max_rss(gpu: bool) -> float:
46
+ if gpu:
47
+ max_mem_per_device = (
48
+ torch.cuda.max_memory_allocated(device) * 1e-6
49
+ for device in range(torch.cuda.device_count())
50
+ )
51
+ max_mem = max(max_mem_per_device)
52
+ elif unix_like:
53
+ max_mem = getrusage(RUSAGE_SELF).ru_maxrss * 1e-3
54
+ else:
55
+ return 0.0
56
+ return max_mem
57
+
58
+
40
59
  def readout_with_error(c: str, *, p_false_pos: float, p_false_neg: float) -> str:
41
60
  # p_false_pos = false positive, p_false_neg = false negative
42
61
  r = random.random()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emu-base
3
- Version: 2.3.0
3
+ Version: 2.4.1
4
4
  Summary: Pasqal base classes for emulators
5
5
  Project-URL: Documentation, https://pasqal-io.github.io/emulators/
6
6
  Project-URL: Repository, https://github.com/pasqal-io/emulators
@@ -25,8 +25,8 @@ 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: pulser-core==1.5.*
29
- Requires-Dist: torch==2.7.0
28
+ Requires-Dist: pulser-core==1.6.*
29
+ Requires-Dist: torch>=2.8.0
30
30
  Description-Content-Type: text/markdown
31
31
 
32
32
  <div align="center">
@@ -0,0 +1,14 @@
1
+ emu_base/__init__.py,sha256=hxXLbvqH2yhva3QXRtTws6oSFnVPMndrgvENXzze9Rw,725
2
+ emu_base/constants.py,sha256=41LYkKLUCz-oxPbd-j7nUDZuhIbUrnez6prT0uR0jcE,56
3
+ emu_base/jump_lindblad_operators.py,sha256=Y30f8emVFS4Dazljc_Rh4lX9qU4QQY_AxPNahnzcsfY,2101
4
+ emu_base/pulser_adapter.py,sha256=bb529P2tspLgX_EcJxCT4bJjFM8SOIttgsFIznTeN58,6979
5
+ emu_base/utils.py,sha256=OoJ0GjL1K8m9Jq-BeBfmbWuFp9bvwXp8UcI3BMW94Js,2974
6
+ emu_base/math/__init__.py,sha256=6BbIytYV5uC-e5jLMtIErkcUl_PvfSNnhmVFY9Il8uQ,97
7
+ emu_base/math/brents_root_finding.py,sha256=AVx6L1Il6rpPJWrLJ7cn6oNmJyZOPRgEaaZaubC9lsU,3711
8
+ emu_base/math/double_krylov.py,sha256=X16dyCbyzdP7fFK-hmKS03Q-DJtC6TZ8sJrGTJ6akIc,3708
9
+ emu_base/math/krylov_energy_min.py,sha256=iR4hmE0eXptbAg3opikd5d4Zv7dhnDrawH-n_4KG-cc,4009
10
+ emu_base/math/krylov_exp.py,sha256=mGFddVQ8mEbwypbZtnlRPFpi4Nf8JZT6OKLHloIwCDQ,3934
11
+ emu_base/math/matmul.py,sha256=lEAnV0b5z_f1xEA-9p-WXxA8bM3QbShiHdXQ3ZkZFcQ,877
12
+ emu_base-2.4.1.dist-info/METADATA,sha256=8ikaKvc4pjqEikcqgoCUj4WnRUlClI5xBQfIPDRuFO8,3604
13
+ emu_base-2.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
+ emu_base-2.4.1.dist-info/RECORD,,
emu_base/aggregators.py DELETED
@@ -1,158 +0,0 @@
1
- import statistics
2
- import torch
3
- from typing import Any, Callable
4
- import collections
5
- from enum import Enum, auto
6
- from pulser.backend import (
7
- Results,
8
- )
9
- import logging
10
-
11
-
12
- _NUMERIC_TYPES = {int, float, complex}
13
-
14
-
15
- class AggregationType(Enum):
16
- """
17
- Defines how to combine multiple values from different simulation results.
18
- """
19
-
20
- MEAN = auto() # statistics.fmean or list/matrix-wise equivalent
21
- BAG_UNION = auto() # Counter.__add__
22
-
23
-
24
- def mean_aggregator(
25
- values: list[Any],
26
- ) -> (
27
- complex
28
- | float
29
- | list[complex]
30
- | list[float]
31
- | list[list[complex]]
32
- | list[list[float]]
33
- | torch.Tensor
34
- ): # FIXME: support tuples?
35
- if values == []:
36
- raise ValueError("Cannot average 0 samples")
37
-
38
- element_type = type(values[0])
39
-
40
- if element_type in _NUMERIC_TYPES:
41
- return statistics.fmean(values)
42
-
43
- if element_type == torch.Tensor:
44
- acc = torch.zeros_like(values[0])
45
- for ten in values:
46
- acc += ten
47
- return acc / len(values)
48
-
49
- if element_type != list:
50
- raise NotImplementedError("Cannot average this type of data")
51
-
52
- if values[0] == []:
53
- raise ValueError("Cannot average list of empty lists")
54
-
55
- sub_element_type = type(values[0][0])
56
-
57
- if sub_element_type in _NUMERIC_TYPES:
58
- dim = len(values[0])
59
- return [statistics.fmean(value[i] for value in values) for i in range(dim)]
60
-
61
- if sub_element_type != list: # FIXME: ABC.Iterable? Collection? subclass?
62
- raise ValueError(f"Cannot average list of lists of {sub_element_type}")
63
-
64
- if values[0][0] == []:
65
- raise ValueError("Cannot average list of matrices with no columns")
66
-
67
- if (sub_sub_element_type := type(values[0][0][0])) not in _NUMERIC_TYPES:
68
- raise ValueError(f"Cannot average list of matrices of {sub_sub_element_type}")
69
-
70
- dim1 = len(values[0])
71
- dim2 = len(values[0][0])
72
- return [
73
- [statistics.fmean(value[i][j] for value in values) for j in range(dim2)]
74
- for i in range(dim1)
75
- ]
76
-
77
-
78
- def bag_union_aggregator(values: list[collections.Counter]) -> collections.Counter:
79
- return sum(values, start=collections.Counter())
80
-
81
-
82
- aggregation_types_definitions: dict[AggregationType, Callable] = {
83
- AggregationType.MEAN: mean_aggregator,
84
- AggregationType.BAG_UNION: bag_union_aggregator,
85
- }
86
-
87
-
88
- def _get_aggregation_type(tag: str) -> AggregationType | None:
89
- if tag.startswith("bitstrings"):
90
- return AggregationType.BAG_UNION
91
- if tag.startswith("expectation"):
92
- return AggregationType.MEAN
93
- if tag.startswith("fidelity"):
94
- return AggregationType.MEAN
95
- if tag.startswith("correlation_matrix"):
96
- return AggregationType.MEAN
97
- if tag.startswith("occupation"):
98
- return AggregationType.MEAN
99
- if tag.startswith("energy"):
100
- return AggregationType.MEAN
101
- if tag.startswith("energy_second_moment"):
102
- return AggregationType.MEAN
103
- else:
104
- return None
105
-
106
-
107
- def aggregate(
108
- results_to_aggregate: list[Results],
109
- **aggregator_functions: Callable[[Any], Any],
110
- ) -> Results:
111
- if len(results_to_aggregate) == 0:
112
- raise ValueError("no results to aggregate")
113
- if len(results_to_aggregate) == 1:
114
- return results_to_aggregate[0]
115
- stored_callbacks = set(results_to_aggregate[0].get_result_tags())
116
- if not all(
117
- set(results.get_result_tags()) == stored_callbacks
118
- for results in results_to_aggregate
119
- ):
120
- raise ValueError(
121
- "Monte-Carlo results seem to provide from incompatible simulations: "
122
- "they do not all contain the same observables"
123
- )
124
- aggregated = Results(
125
- atom_order=results_to_aggregate[0].atom_order,
126
- total_duration=results_to_aggregate[0].total_duration,
127
- )
128
- for tag in stored_callbacks:
129
- aggregation_type = aggregator_functions.get(
130
- tag,
131
- _get_aggregation_type(tag),
132
- )
133
- if aggregation_type is None:
134
- logging.getLogger("global_logger").warning(f"Skipping aggregation of `{tag}`")
135
- continue
136
- aggregation_function: Any = (
137
- aggregation_type
138
- if callable(aggregation_type)
139
- else aggregation_types_definitions[aggregation_type]
140
- )
141
- evaluation_times = results_to_aggregate[0].get_result_times(tag)
142
- if not all(
143
- results.get_result_times(tag) == evaluation_times
144
- for results in results_to_aggregate
145
- ):
146
- raise ValueError(
147
- "Monte-Carlo results seem to provide from incompatible simulations: "
148
- "the callbacks are not stored at the same times"
149
- )
150
-
151
- uuid = results_to_aggregate[0]._find_uuid(tag)
152
- for t in results_to_aggregate[0].get_result_times(tag):
153
- v = aggregation_function(
154
- [result.get_result(tag, t) for result in results_to_aggregate]
155
- )
156
- aggregated._store_raw(uuid=uuid, tag=tag, time=t, value=v)
157
-
158
- return aggregated
emu_base/noise.py DELETED
@@ -1,11 +0,0 @@
1
- import torch
2
-
3
-
4
- def pick_dark_qubits(eta: float, n: int) -> torch.Tensor:
5
- """
6
- Randomly pick n booleans such that ℙ(True) = eta
7
-
8
- Returns:
9
- A BoolTensor of shape (n,) where each element is True with probability eta.
10
- """
11
- return torch.rand(n) < eta
@@ -1,16 +0,0 @@
1
- emu_base/__init__.py,sha256=wD_KJ7LfcWPbEbnskS46DOhToP4r6D_y2EyztecGMqg,757
2
- emu_base/aggregators.py,sha256=bB-rldoDAErxQMpL715K5lpiabGOpkCY0GyxW7mfHuc,5000
3
- emu_base/constants.py,sha256=41LYkKLUCz-oxPbd-j7nUDZuhIbUrnez6prT0uR0jcE,56
4
- emu_base/jump_lindblad_operators.py,sha256=Y30f8emVFS4Dazljc_Rh4lX9qU4QQY_AxPNahnzcsfY,2101
5
- emu_base/noise.py,sha256=h47DhEsFdAN783VnisJ50QJIiAPeRPKpU0SYtt8bAEQ,273
6
- emu_base/pulser_adapter.py,sha256=NlZ1nNuNdU7D8S0XJqtjgu1b-qW2jFVzXOCAroH4Vuk,14660
7
- emu_base/utils.py,sha256=17gca5uQfPaXiH3z4Leqd_rAJEZKQUqlSHj7OzFvCkU,2495
8
- emu_base/math/__init__.py,sha256=6BbIytYV5uC-e5jLMtIErkcUl_PvfSNnhmVFY9Il8uQ,97
9
- emu_base/math/brents_root_finding.py,sha256=AVx6L1Il6rpPJWrLJ7cn6oNmJyZOPRgEaaZaubC9lsU,3711
10
- emu_base/math/double_krylov.py,sha256=X16dyCbyzdP7fFK-hmKS03Q-DJtC6TZ8sJrGTJ6akIc,3708
11
- emu_base/math/krylov_energy_min.py,sha256=hm_B5qtBXHY1hl-r_LgDUKNDsdqVCDBHprQB3D-UFR8,4009
12
- emu_base/math/krylov_exp.py,sha256=mGFddVQ8mEbwypbZtnlRPFpi4Nf8JZT6OKLHloIwCDQ,3934
13
- emu_base/math/matmul.py,sha256=lEAnV0b5z_f1xEA-9p-WXxA8bM3QbShiHdXQ3ZkZFcQ,877
14
- emu_base-2.3.0.dist-info/METADATA,sha256=AAanXBVpBx2__3ojtFYDqPjgLF-9WqNmKcXH4V3JiJA,3604
15
- emu_base-2.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- emu_base-2.3.0.dist-info/RECORD,,