emu-base 1.2.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.
@@ -0,0 +1,248 @@
1
+ import pulser
2
+ from typing import Tuple
3
+ import torch
4
+ import math
5
+ from pulser.noise_model import NoiseModel
6
+ from enum import Enum
7
+
8
+ from emu_base.base_classes.config import BackendConfig
9
+ from emu_base.lindblad_operators import get_lindblad_operators
10
+ from emu_base.utils import dist2, dist3
11
+
12
+
13
+ class HamiltonianType(Enum):
14
+ Rydberg = 1
15
+ XY = 2
16
+
17
+
18
+ def _get_qubit_positions(
19
+ register: pulser.Register,
20
+ ) -> list[torch.Tensor]:
21
+ """Conversion from pulser Register to emu-mps register (torch type).
22
+ Each element will be given as [Rx,Ry,Rz]"""
23
+
24
+ positions = [position.as_tensor() for position in register.qubits.values()]
25
+
26
+ if len(positions[0]) == 2:
27
+ return [torch.cat((position, torch.zeros(1))) for position in positions]
28
+ return positions
29
+
30
+
31
+ def _rydberg_interaction(sequence: pulser.Sequence) -> torch.Tensor:
32
+ """
33
+ Computes the Ising interaction matrix from the qubit positions.
34
+ Hᵢⱼ=C₆/Rᵢⱼ⁶ (nᵢ⊗ nⱼ)
35
+ """
36
+
37
+ num_qubits = len(sequence.register.qubit_ids)
38
+
39
+ c6 = sequence.device.interaction_coeff
40
+
41
+ qubit_positions = _get_qubit_positions(sequence.register)
42
+ interaction_matrix = torch.zeros(num_qubits, num_qubits)
43
+
44
+ for numi in range(len(qubit_positions)):
45
+ for numj in range(numi + 1, len(qubit_positions)):
46
+ interaction_matrix[numi][numj] = (
47
+ c6 / dist2(qubit_positions[numi], qubit_positions[numj]) ** 3
48
+ )
49
+ interaction_matrix[numj, numi] = interaction_matrix[numi, numj]
50
+ return interaction_matrix
51
+
52
+
53
+ def _xy_interaction(sequence: pulser.Sequence) -> torch.Tensor:
54
+ """
55
+ Computes the XY interaction matrix from the qubit positions.
56
+ C₃ (1−3 cos(𝜃ᵢⱼ)²)/ Rᵢⱼ³ (𝜎ᵢ⁺ 𝜎ⱼ⁻ + 𝜎ᵢ⁻ 𝜎ⱼ⁺)
57
+ """
58
+ num_qubits = len(sequence.register.qubit_ids)
59
+
60
+ c3 = sequence.device.interaction_coeff_xy
61
+
62
+ qubit_positions = _get_qubit_positions(sequence.register)
63
+ interaction_matrix = torch.zeros(num_qubits, num_qubits)
64
+ mag_field = torch.tensor(sequence.magnetic_field) # by default [0.0,0.0,30.0]
65
+ mag_norm = torch.norm(mag_field)
66
+
67
+ for numi in range(len(qubit_positions)):
68
+ for numj in range(numi + 1, len(qubit_positions)):
69
+ cosine = 0
70
+ if mag_norm >= 1e-8: # selected by hand
71
+ cosine = torch.dot(
72
+ (qubit_positions[numi] - qubit_positions[numj]), mag_field
73
+ ) / (torch.norm(qubit_positions[numi] - qubit_positions[numj]) * mag_norm)
74
+
75
+ interaction_matrix[numi][numj] = (
76
+ c3 # check this value with pulser people
77
+ * (1 - 3 * cosine**2)
78
+ / dist3(qubit_positions[numi], qubit_positions[numj])
79
+ )
80
+ interaction_matrix[numj, numi] = interaction_matrix[numi, numj]
81
+
82
+ return interaction_matrix
83
+
84
+
85
+ def _extract_omega_delta_phi(
86
+ *,
87
+ sequence: pulser.Sequence,
88
+ dt: int,
89
+ with_modulation: bool,
90
+ laser_waist: float | None,
91
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
92
+ """
93
+ Samples the Pulser sequence and returns a tuple of tensors (omega, delta, phi)
94
+ containing:
95
+ - omega[i, q] = amplitude at time i * dt for qubit q
96
+ - delta[i, q] = detuning at time i * dt for qubit q
97
+ - phi[i, q] = phase at time i * dt for qubit q
98
+
99
+ if laser_waist is w_0 != None, the omega values coming from the global pulse channel
100
+ will me modulated as $\\Omega_i=\\Omega_i e^{-r_i^2/w_0^2}$
101
+ """
102
+
103
+ if with_modulation and sequence._slm_mask_targets:
104
+ raise NotImplementedError(
105
+ "Simulation of sequences combining an SLM mask and output "
106
+ "modulation is not supported."
107
+ )
108
+
109
+ samples = pulser.sampler.sample(
110
+ sequence,
111
+ modulation=with_modulation,
112
+ extended_duration=sequence.get_duration(include_fall_time=with_modulation),
113
+ )
114
+ sequence_dict = samples.to_nested_dict(all_local=True, samples_type="tensor")["Local"]
115
+
116
+ if "ground-rydberg" in sequence_dict and len(sequence_dict) == 1:
117
+ locals_a_d_p = sequence_dict["ground-rydberg"]
118
+ elif "XY" in sequence_dict and len(sequence_dict) == 1:
119
+ locals_a_d_p = sequence_dict["XY"]
120
+ else:
121
+ raise ValueError("Emu-MPS only accepts ground-rydberg or mw_global channels")
122
+
123
+ max_duration = sequence.get_duration(include_fall_time=with_modulation)
124
+
125
+ nsamples = math.ceil(max_duration / dt - 1 / 2)
126
+ omega = torch.zeros(
127
+ nsamples,
128
+ len(sequence.register.qubit_ids),
129
+ dtype=torch.complex128,
130
+ )
131
+
132
+ delta = torch.zeros(
133
+ nsamples,
134
+ len(sequence.register.qubit_ids),
135
+ dtype=torch.complex128,
136
+ )
137
+ phi = torch.zeros(
138
+ nsamples,
139
+ len(sequence.register.qubit_ids),
140
+ dtype=torch.complex128,
141
+ )
142
+
143
+ if laser_waist:
144
+ qubit_positions = _get_qubit_positions(sequence.register)
145
+ waist_factors = torch.tensor(
146
+ [math.exp(-((x[:2].norm() / laser_waist) ** 2)) for x in qubit_positions]
147
+ )
148
+ else:
149
+ waist_factors = torch.ones(len(sequence.register.qubit_ids))
150
+
151
+ global_times = set()
152
+ for ch, ch_samples in samples.channel_samples.items():
153
+ if samples._ch_objs[ch].addressing == "Global":
154
+ for slot in ch_samples.slots:
155
+ global_times |= set(i for i in range(slot.ti, slot.tf))
156
+
157
+ step = 0
158
+ t = int((step + 1 / 2) * dt)
159
+
160
+ while t < max_duration:
161
+ for q_pos, q_id in enumerate(sequence.register.qubit_ids):
162
+ omega[step, q_pos] = locals_a_d_p[q_id]["amp"][t]
163
+ delta[step, q_pos] = locals_a_d_p[q_id]["det"][t]
164
+ phi[step, q_pos] = locals_a_d_p[q_id]["phase"][t]
165
+ if t in global_times:
166
+ omega[step] *= waist_factors
167
+ step += 1
168
+ t = int((step + 1 / 2) * dt)
169
+
170
+ return omega, delta, phi
171
+
172
+
173
+ _NON_LINDBLADIAN_NOISE = {"SPAM", "doppler", "amplitude"}
174
+
175
+
176
+ def _get_all_lindblad_noise_operators(
177
+ noise_model: NoiseModel | None,
178
+ ) -> list[torch.Tensor]:
179
+ if noise_model is None:
180
+ return []
181
+
182
+ return [
183
+ op
184
+ for noise_type in noise_model.noise_types
185
+ if noise_type not in _NON_LINDBLADIAN_NOISE
186
+ for op in get_lindblad_operators(noise_type=noise_type, noise_model=noise_model)
187
+ ]
188
+
189
+
190
+ class PulserData:
191
+ slm_end_time: int
192
+ full_interaction_matrix: torch.Tensor
193
+ masked_interaction_matrix: torch.Tensor
194
+ omega: torch.Tensor
195
+ delta: torch.Tensor
196
+ phi: torch.Tensor
197
+ hamiltonian_type: HamiltonianType
198
+ lindblad_ops: list[torch.Tensor]
199
+
200
+ def __init__(self, *, sequence: pulser.Sequence, config: BackendConfig, dt: int):
201
+ self.qubit_count = len(sequence.register.qubit_ids)
202
+
203
+ laser_waist = (
204
+ config.noise_model.laser_waist if config.noise_model is not None else None
205
+ )
206
+ self.omega, self.delta, self.phi = _extract_omega_delta_phi(
207
+ sequence=sequence,
208
+ dt=dt,
209
+ with_modulation=config.with_modulation,
210
+ laser_waist=laser_waist,
211
+ )
212
+ self.lindblad_ops = _get_all_lindblad_noise_operators(config.noise_model)
213
+ self.has_lindblad_noise: bool = self.lindblad_ops != []
214
+
215
+ addressed_basis = sequence.get_addressed_bases()[0]
216
+ if addressed_basis == "ground-rydberg": # for local and global
217
+ self.hamiltonian_type = HamiltonianType.Rydberg
218
+ elif addressed_basis == "XY":
219
+ self.hamiltonian_type = HamiltonianType.XY
220
+ else:
221
+ raise ValueError(f"Unsupported basis: {addressed_basis}")
222
+
223
+ if config.interaction_matrix is not None:
224
+ assert len(config.interaction_matrix) == self.qubit_count, (
225
+ "The number of qubits in the register should be the same as the size of "
226
+ "the interaction matrix"
227
+ )
228
+
229
+ self.full_interaction_matrix = torch.tensor(
230
+ config.interaction_matrix, dtype=torch.float64
231
+ )
232
+ elif self.hamiltonian_type == HamiltonianType.Rydberg:
233
+ self.full_interaction_matrix = _rydberg_interaction(sequence)
234
+ elif self.hamiltonian_type == HamiltonianType.XY:
235
+ self.full_interaction_matrix = _xy_interaction(sequence)
236
+ self.full_interaction_matrix[
237
+ torch.abs(self.full_interaction_matrix) < config.interaction_cutoff
238
+ ] = 0.0
239
+ self.masked_interaction_matrix = self.full_interaction_matrix.clone()
240
+
241
+ slm_targets = sequence._slm_mask_targets
242
+ self.slm_end_time = (
243
+ sequence._slm_mask_time[1] if len(sequence._slm_mask_time) > 1 else 0.0
244
+ )
245
+
246
+ for target in slm_targets:
247
+ self.masked_interaction_matrix[target] = 0.0
248
+ self.masked_interaction_matrix[:, target] = 0.0
emu_base/utils.py ADDED
@@ -0,0 +1,9 @@
1
+ import torch
2
+
3
+
4
+ def dist2(left: torch.tensor, right: torch.tensor) -> torch.Tensor:
5
+ return torch.norm(left - right) ** 2
6
+
7
+
8
+ def dist3(left: torch.tensor, right: torch.tensor) -> torch.Tensor:
9
+ return torch.norm(left - right) ** 3
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: emu-base
3
+ Version: 1.2.1
4
+ Summary: Pasqal base classes for emulators
5
+ 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>
6
+ License: Proprietary
7
+ Classifier: License :: Other/Proprietary License
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: Implementation :: CPython
10
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: pulser-core==1.1.*
13
+ Requires-Dist: torch==2.5.0
14
+ Description-Content-Type: text/markdown
15
+
16
+ <div align="center">
17
+ <img src="docs/logos/LogoTaglineSoftGreen.svg">
18
+
19
+ # Emu-MPS
20
+ </div>
21
+
22
+ **EMU-MPS** is a Pulser backend, designed to **EMU**late the dynamics of programmable arrays of neutral atoms, with matrix product states (**MPS**). It allows users to increase the number of qubits and reduce computation time.
23
+
24
+ Join us on [Slack](https://pasqalworkspace.slack.com/archives/C07MUV5K7EU) or by [e-mail](mailto:emulation@pasqal.com) to give us feedback about how you plan to use Emu-MPS or if you require specific feature-upgrades.
25
+
26
+
27
+ ## Getting started
28
+
29
+ You can install from source, or download the package from the private pypi registry that pasqal maintains in gitlab.
30
+ For developers, we recommend installing from source, for users we recommend installing from the registry.
31
+
32
+ **Warning:** installing emu-mps will update pulser-core
33
+
34
+ We always recommend using a virtual environment.
35
+
36
+ <details>
37
+ <summary>Click me to see how it is done</summary>
38
+
39
+ #### Create a virtual environment using python
40
+
41
+ ```
42
+ python -m venv .venv
43
+ ```
44
+
45
+ Or
46
+
47
+ ```
48
+ python -m venv /path/to/new/virtual/environment
49
+ ```
50
+
51
+ Replace `/path/to/new/virtual/environment` with your desired directory path.
52
+
53
+ Then activate the environment On linux or MacOS
54
+
55
+ ```
56
+ source /path/to/new/virtual/environment/bin/activate
57
+ ```
58
+
59
+ While on Windows it's
60
+
61
+ ```
62
+ C:\> /path/to/new/virtual/environment/Scripts/activate
63
+ ```
64
+
65
+ Remember to replace `/path/to/new/virtual/environment` with the actual path to your virtual environment. Once the environment is activated, you can clone emu_mps and install it using
66
+
67
+ </details>
68
+
69
+ ### installing from the registry
70
+
71
+ When pip is configured to know about the pasqal registry, Emu-MPS installs as
72
+
73
+ ```bash
74
+ pip install emu-mps
75
+ ```
76
+ When pip is not already configured, the easiest way to do so, is to add a file `~/.config/pip/pip.conf` containing:
77
+
78
+ ```
79
+ [global]
80
+ extra-index-url=https://gitlab.pasqal.com/api/v4/projects/597/packages/pypi/simple
81
+ possible.other.urls
82
+ ```
83
+
84
+ As this shows, it is also possible to have multiple extra repositories configured. Note that the order is not important.
85
+
86
+ It is also possible to add the `extra-index-url` to the `pip install` command directly, if you somehow don't want to create a `pip.conf` file.
87
+
88
+ ### installing from source
89
+ git clone this [repository ](https://gitlab.pasqal.com/emulation/rydberg-atoms/emu-ct) or download
90
+
91
+
92
+ Then, `cd` into the root folder of the repo and type
93
+
94
+ ```bash
95
+ pip install -e .
96
+ ```
97
+
98
+ <details>
99
+ <summary>Guidelines for developers </summary>
100
+ We recommend using an environment, git clone the repository, then inside the `emu_mps` folder
101
+
102
+ ```bash
103
+ pip install -e .
104
+ ```
105
+
106
+ Also, the installation of pytest, nbmake, pre-commit.
107
+
108
+ Do not forget to run the unit test suite by simply running `pytest` command.
109
+
110
+ Another way can be using hatch.
111
+
112
+ #### virtual environment with `hatch`
113
+
114
+ ```bash
115
+ python -m pip install hatch
116
+ python -m hatch -v shell
117
+ ```
118
+
119
+ When inside the shell with development dependencies, install first the pre-commit hook:
120
+ ```
121
+ pre-commit install
122
+ ```
123
+ </details>
124
+
125
+ ## Check the tutorial notebooks and example scripts
126
+
127
+ For more information, you can check the tutorials and examples located in the [examples folder](https://gitlab.pasqal.com/emulation/rydberg-atoms/emu-ct/-/tree/main/examples?ref_type=heads)
128
+
129
+ ## Documentation
130
+
131
+ Please check the [documentation](./docs/index.md) page for more info about contributing, the API, benchmarks, etc.
132
+
133
+
134
+ ![Code Coverage](https://img.shields.io/badge/Coverage-95%25-brightgreen.svg)
@@ -0,0 +1,19 @@
1
+ emu_base/__init__.py,sha256=afeGAvgNi3tn7J0cWS2JBQxYZ-HYWS6o1skt0Ev319c,1130
2
+ emu_base/lindblad_operators.py,sha256=eXkXsLt_fV7jhF31EkxzYFU04rOJV3uWXsl6t1aTAqs,1562
3
+ emu_base/pulser_adapter.py,sha256=AjBT9CM0HX5vh8JaU1wRZq1QgXVAryAsYMNKmtPk9x4,8730
4
+ emu_base/utils.py,sha256=YCi7UngUzZYSOONhecUdFSB_OkH_aadMTvixzqHYYAc,235
5
+ emu_base/base_classes/__init__.py,sha256=Su6fHtjCyg0fw-7y7e7nbMfDASppNRQs8iGaAOkO3c4,570
6
+ emu_base/base_classes/aggregators.py,sha256=bcvoGfZCkPKv-CI29gTma6HBphGh7bjBq2Ome77eRJM,1840
7
+ emu_base/base_classes/backend.py,sha256=7tnwb9MnRbwRN1_JTqliYftjqExuOE-Rrwz9AU2Pc4c,1645
8
+ emu_base/base_classes/callback.py,sha256=JXah_ZDNM8iyPWy7IOwW481qRFyqVvlSM-0OkjBzV0A,3055
9
+ emu_base/base_classes/config.py,sha256=e7ZRNcBBTEfD_TrODZqCRncvOTUs07TivH04dKaiXrI,3333
10
+ emu_base/base_classes/default_callbacks.py,sha256=F44kkuwWdVcvMGZ9vJ2q7ug-_P8IQyJv-SVxSVWHW_w,9940
11
+ emu_base/base_classes/operator.py,sha256=MJjuDUTwJLbaSJzSNCKDWGvmGCGAEIEWISLoPSSzNsU,3501
12
+ emu_base/base_classes/results.py,sha256=7-Mz3jmFy19hd3PIA5idK610mC3b5jOf3EKBmV14Jv4,5569
13
+ emu_base/base_classes/state.py,sha256=7iIyZmBqqJ6G4SyYZ3kyylWjAqiYIx0aW5B0T74EPZI,2707
14
+ emu_base/math/__init__.py,sha256=6BbIytYV5uC-e5jLMtIErkcUl_PvfSNnhmVFY9Il8uQ,97
15
+ emu_base/math/brents_root_finding.py,sha256=AVx6L1Il6rpPJWrLJ7cn6oNmJyZOPRgEaaZaubC9lsU,3711
16
+ emu_base/math/krylov_exp.py,sha256=TKvlByRKfCNZdS_YcfCtTk4D7YGhJ-2eIylLVT-X7i4,3784
17
+ emu_base-1.2.1.dist-info/METADATA,sha256=NJK1z5qdfuhJj4B-7f3jfJ4IgxyUp3JHGBqvdxTPhXA,4220
18
+ emu_base-1.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
+ emu_base-1.2.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any