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 +4 -5
- emu_base/math/krylov_energy_min.py +1 -1
- emu_base/pulser_adapter.py +73 -274
- emu_base/utils.py +19 -0
- {emu_base-2.3.0.dist-info → emu_base-2.4.1.dist-info}/METADATA +3 -3
- emu_base-2.4.1.dist-info/RECORD +14 -0
- emu_base/aggregators.py +0 -158
- emu_base/noise.py +0 -11
- emu_base-2.3.0.dist-info/RECORD +0 -16
- {emu_base-2.3.0.dist-info → emu_base-2.4.1.dist-info}/WHEEL +0 -0
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 .
|
|
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.
|
|
24
|
+
__version__ = "2.4.1"
|
emu_base/pulser_adapter.py
CHANGED
|
@@ -1,268 +1,114 @@
|
|
|
1
|
-
from typing import
|
|
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
|
|
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
|
-
|
|
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
|
|
41
|
-
|
|
22
|
+
def _get_all_lindblad_noise_operators(
|
|
23
|
+
noise_model: NoiseModel | None,
|
|
42
24
|
) -> list[torch.Tensor]:
|
|
43
|
-
|
|
44
|
-
|
|
25
|
+
if noise_model is None:
|
|
26
|
+
return []
|
|
45
27
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
for
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
57
|
+
noisy_samples: SequenceSamples,
|
|
58
|
+
qubit_ids: tuple[str, ...],
|
|
174
59
|
target_times: list[int],
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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(
|
|
67
|
+
len(qubit_ids),
|
|
217
68
|
dtype=torch.complex128,
|
|
218
69
|
)
|
|
219
|
-
|
|
220
70
|
delta = torch.zeros(
|
|
221
71
|
nsamples,
|
|
222
|
-
len(
|
|
72
|
+
len(qubit_ids),
|
|
223
73
|
dtype=torch.complex128,
|
|
224
74
|
)
|
|
225
75
|
phi = torch.zeros(
|
|
226
76
|
nsamples,
|
|
227
|
-
len(
|
|
77
|
+
len(qubit_ids),
|
|
228
78
|
dtype=torch.complex128,
|
|
229
79
|
)
|
|
230
|
-
|
|
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
|
|
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(
|
|
250
|
-
|
|
251
|
-
|
|
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(
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
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
|
-
|
|
346
|
-
if
|
|
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
|
|
155
|
+
elif int_type == "XY":
|
|
349
156
|
self.hamiltonian_type = HamiltonianType.XY
|
|
350
157
|
else:
|
|
351
|
-
raise ValueError(f"Unsupported 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
|
-
|
|
373
|
-
self.full_interaction_matrix =
|
|
374
|
-
|
|
375
|
-
|
|
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
|
+
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.
|
|
29
|
-
Requires-Dist: torch
|
|
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
|
emu_base-2.3.0.dist-info/RECORD
DELETED
|
@@ -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,,
|
|
File without changes
|