emu-mps 2.0.1__py3-none-any.whl → 2.0.2__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_mps/hamiltonian.py CHANGED
@@ -3,309 +3,345 @@ This file deals with creation of the MPO corresponding
3
3
  to the Hamiltonian of a neutral atoms quantum processor.
4
4
  """
5
5
 
6
+ from abc import abstractmethod, ABC
7
+ from typing import Iterator
8
+
6
9
  from emu_base import HamiltonianType
7
10
  import torch
8
-
9
11
  from emu_mps.mpo import MPO
10
12
 
11
- dtype = torch.complex128 # always complex128
12
- iden_op = torch.eye(2, 2, dtype=dtype) # dtype is always complex128
13
- n_op = torch.tensor([[0.0, 0.0], [0.0, 1.0]], dtype=dtype)
14
- creation_op = torch.tensor([[0.0, 1.0], [0.0, 0.0]], dtype=dtype)
15
- sx = torch.tensor([[0.0, 0.5], [0.5, 0.0]], dtype=dtype)
16
- sy = torch.tensor([[0.0, -0.5j], [0.5j, 0.0]], dtype=dtype)
17
- pu = torch.tensor([[0.0, 0.0], [0.0, 1.0]], dtype=dtype)
13
+ dtype = torch.complex128
18
14
 
19
15
 
20
- def truncate_factor(
21
- factor: torch.Tensor,
22
- left_interactions: torch.Tensor,
23
- right_interactions: torch.Tensor,
24
- hamiltonian_type: HamiltonianType,
25
- ) -> torch.Tensor:
26
- if hamiltonian_type == HamiltonianType.XY:
27
- left_interactions = torch.stack(
28
- (left_interactions, left_interactions), dim=-1
29
- ).reshape(-1)
30
- right_interactions = torch.stack(
31
- (right_interactions, right_interactions), dim=-1
32
- ).reshape(-1)
33
- padding = torch.tensor([True] * 2)
34
- trunc = factor[torch.cat((padding, left_interactions))]
35
- return trunc[:, :, :, torch.cat((padding, right_interactions))]
36
-
37
-
38
- def _first_factor_rydberg(interaction: bool) -> torch.Tensor:
39
- """
40
- Creates the first Ising Hamiltonian factor.
41
- """
42
- fac = torch.zeros(1, 2, 2, 3 if interaction else 2, dtype=dtype)
43
- fac[0, :, :, 1] = iden_op
44
- if interaction:
45
- fac[0, :, :, 2] = n_op # number operator
16
+ class Operators:
17
+ id = torch.eye(2, dtype=dtype)
18
+ n = torch.tensor([[0.0, 0.0], [0.0, 1.0]], dtype=dtype)
19
+ creation = torch.tensor([[0.0, 1.0], [0.0, 0.0]], dtype=dtype)
20
+ sx = torch.tensor([[0.0, 0.5], [0.5, 0.0]], dtype=dtype)
21
+ sy = torch.tensor([[0.0, -0.5j], [0.5j, 0.0]], dtype=dtype)
22
+ pu = torch.tensor([[0.0, 0.0], [0.0, 1.0]], dtype=dtype)
46
23
 
47
- return fac
48
24
 
25
+ class HamiltonianMPOFactors(ABC):
26
+ def __init__(self, interaction_matrix: torch.Tensor):
27
+ assert interaction_matrix.ndim == 2, "interaction matrix is not a matrix"
28
+ assert (
29
+ interaction_matrix.shape[0] == interaction_matrix.shape[1]
30
+ ), "interaction matrix is not square"
49
31
 
50
- def _first_factor_xy(interaction: bool) -> torch.Tensor:
51
- """
52
- Creates the first XY Hamiltonian factor.
53
- """
54
- fac = torch.zeros(1, 2, 2, 4 if interaction else 2, dtype=dtype)
55
- fac[0, :, :, 1] = iden_op
56
- if interaction:
57
- fac[0, :, :, 2] = creation_op
58
- fac[0, :, :, 3] = creation_op.T
32
+ self.interaction_matrix = interaction_matrix.clone()
33
+ self.interaction_matrix.fill_diagonal_(0.0) # or assert
34
+ self.qubit_count = self.interaction_matrix.shape[0]
35
+ self.middle = self.qubit_count // 2
59
36
 
60
- return fac
37
+ def __iter__(self) -> Iterator[torch.Tensor]:
38
+ yield self.first_factor()
61
39
 
40
+ for n in range(1, self.middle):
41
+ yield self.left_factor(n)
62
42
 
63
- def _last_factor_rydberg(scale: float | complex) -> torch.Tensor:
64
- """
65
- Creates the last Ising Hamiltonian factor.
66
- """
67
- fac = torch.zeros(3 if scale != 0.0 else 2, 2, 2, 1, dtype=dtype)
68
- fac[0, :, :, 0] = iden_op
69
- if scale != 0:
70
- fac[2, :, :, 0] = scale * n_op
43
+ if self.qubit_count >= 3:
44
+ yield self.middle_factor()
71
45
 
72
- return fac
46
+ for n in range(self.middle + 1, self.qubit_count - 1):
47
+ yield self.right_factor(n)
73
48
 
49
+ yield self.last_factor()
74
50
 
75
- def _last_factor_xy(scale: float | complex) -> torch.Tensor:
76
- """
77
- Creates the last XY Hamiltonian factor.
78
- """
79
- fac = torch.zeros(4 if scale != 0.0 else 2, 2, 2, 1, dtype=dtype)
80
- fac[0, :, :, 0] = iden_op
81
- if scale != 0:
82
- fac[2, :, :, 0] = scale * creation_op.T
83
- fac[3, :, :, 0] = scale * creation_op
51
+ @abstractmethod
52
+ def first_factor(self) -> torch.Tensor:
53
+ pass
84
54
 
85
- return fac
55
+ @abstractmethod
56
+ def left_factor(self, n: int) -> torch.Tensor:
57
+ pass
86
58
 
59
+ @abstractmethod
60
+ def middle_factor(self) -> torch.Tensor:
61
+ pass
87
62
 
88
- def _left_factor_rydberg(
89
- scales: torch.Tensor,
90
- left_interactions: torch.Tensor,
91
- right_interactions: torch.Tensor,
92
- ) -> torch.Tensor:
93
- """
94
- Creates the Ising Hamiltonian factors in the left half of the MPS, excepted the first factor.
95
- """
96
- index = len(scales)
97
- fac = torch.zeros(index + 2, 2, 2, index + 3, dtype=dtype)
98
- fac[2 : scales.shape[0] + 2, :, :, 0] = (
99
- scales.reshape(-1, 1, 1) * n_op
100
- ) # interaction with previous qubits
101
- fac[1, :, :, index + 2] = n_op # interaction with next qubits
102
- for i in range(index + 2):
103
- fac[i, :, :, i] = iden_op # identity matrix to carry the gates of other qubits
104
-
105
- return truncate_factor(
106
- fac,
107
- left_interactions,
108
- right_interactions,
109
- hamiltonian_type=HamiltonianType.Rydberg,
110
- )
111
-
112
-
113
- def _left_factor_xy(
114
- scales: torch.Tensor,
115
- left_interactions: torch.Tensor,
116
- right_interactions: torch.Tensor,
117
- ) -> torch.Tensor:
118
- """
119
- Creates the XY Hamiltonian factors in the left half of the MPS, excepted the first factor.
120
- """
121
- index = len(scales)
122
- fac = torch.zeros(2 * index + 2, 2, 2, 2 * index + 4, dtype=dtype)
123
-
124
- fac[2 : 2 * scales.shape[0] + 2 : 2, :, :, 0] = (
125
- scales.reshape(-1, 1, 1) * creation_op.T
126
- ) # sigma-
127
- fac[3 : 2 * scales.shape[0] + 3 : 2, :, :, 0] = (
128
- scales.reshape(-1, 1, 1) * creation_op
129
- ) # sigma+
130
- fac[1, :, :, -2] = creation_op
131
- fac[1, :, :, -1] = creation_op.T
132
- for i in range(2 * index + 2):
133
- fac[i, :, :, i] = iden_op # identity to carry the gates of other qubits
134
-
135
- # duplicate each bool, because each interaction term occurs twice
136
- return truncate_factor(
137
- fac, left_interactions, right_interactions, hamiltonian_type=HamiltonianType.XY
138
- )
139
-
140
-
141
- def _right_factor_rydberg(
142
- scales: torch.Tensor,
143
- left_interactions: torch.Tensor,
144
- right_interactions: torch.Tensor,
145
- ) -> torch.Tensor:
146
- """
147
- Creates the Ising Hamiltonian factors in the right half of the MPS, excepted the last factor.
148
- """
149
- index = len(scales)
150
- fac = torch.zeros(index + 3, 2, 2, index + 2, dtype=dtype)
151
- fac[1, :, :, 2 : scales.shape[0] + 2] = scales * n_op.reshape(
152
- 2, 2, 1
153
- ) # XY interaction with previous qubits
154
- fac[2, :, :, 0] = n_op # XY interaction with next qubits
155
- for i in range(2, index + 2):
156
- fac[i + 1, :, :, i] = iden_op
157
- fac[0, :, :, 0] = iden_op # identity to carry the next gates to the previous qubits
158
- fac[1, :, :, 1] = iden_op # identity to carry previous gates to next qubits
159
-
160
- return truncate_factor(
161
- fac,
162
- left_interactions,
163
- right_interactions,
164
- hamiltonian_type=HamiltonianType.Rydberg,
165
- )
166
-
167
-
168
- def _right_factor_xy(
169
- scales: torch.Tensor,
170
- left_interactions: torch.Tensor,
171
- right_interactions: torch.Tensor,
172
- ) -> torch.Tensor:
173
- """
174
- Creates the XY Hamiltonian factors in the right half of the MPS, excepted the last factor.
175
- """
176
- index = len(scales)
177
- fac = torch.zeros(2 * index + 4, 2, 2, 2 * index + 2, dtype=dtype)
178
- fac[1, :, :, 2 : 2 * scales.shape[0] + 2 : 2] = scales * creation_op.reshape(
179
- 2, 2, 1
180
- ) # XY interaction with previous qubits
181
- fac[1, :, :, 3 : 2 * scales.shape[0] + 3 : 2] = scales * creation_op.T.reshape(
182
- 2, 2, 1
183
- )
184
- fac[2, :, :, 0] = creation_op.T # s- with next qubits
185
- fac[3, :, :, 0] = creation_op # s+ with next qubits
186
- for i in range(2, index + 2):
187
- fac[2 * i, :, :, 2 * i - 2] = iden_op
188
- fac[2 * i + 1, :, :, 2 * i - 1] = iden_op
189
-
190
- # identity to carry the next gates to the previous qubits
191
- fac[0, :, :, 0] = iden_op
192
- # identity to carry previous gates to next qubits
193
- fac[1, :, :, 1] = iden_op
194
-
195
- # duplicate each bool, because each interaction term occurs twice
196
- return truncate_factor(
197
- fac, left_interactions, right_interactions, hamiltonian_type=HamiltonianType.XY
198
- )
199
-
200
-
201
- def _middle_factor_rydberg(
202
- scales_l: torch.Tensor,
203
- scales_r: torch.Tensor,
204
- scales_mat: torch.Tensor,
205
- left_interactions: torch.Tensor,
206
- right_interactions: torch.Tensor,
207
- ) -> torch.Tensor:
208
- """
209
- Creates the Ising Hamiltonian factor at index ⌊n/2⌋ of the n-qubit MPO.
210
- """
211
- assert len(scales_mat) == len(scales_l)
212
- assert all(len(x) == len(scales_r) for x in scales_mat)
213
-
214
- fac = torch.zeros(len(scales_l) + 2, 2, 2, len(scales_r) + 2, dtype=dtype)
215
- fac[1, :, :, 2 : scales_r.shape[0] + 2] = scales_r * n_op.reshape(
216
- 2, 2, 1
217
- ) # rydberg interaction with previous qubits
218
- fac[2 : scales_l.shape[0] + 2, :, :, 0] = (
219
- scales_l.reshape(-1, 1, 1) * n_op
220
- ) # rydberg interaction with next qubits
221
- x_shape, y_shape = scales_mat.shape
222
- fac[2 : x_shape + 2, :, :, 2 : y_shape + 2] = scales_mat.reshape(
223
- x_shape, 1, 1, y_shape
224
- ) * iden_op.reshape(
225
- 1, 2, 2, 1
226
- ) # rydberg interaction of previous with next qubits
227
- fac[0, :, :, 0] = iden_op # identity to carry the next gates to the previous qubits
228
- fac[1, :, :, 1] = iden_op # identity to carry previous gates to next qubits
229
-
230
- return truncate_factor(
231
- fac,
232
- left_interactions,
233
- right_interactions,
234
- hamiltonian_type=HamiltonianType.Rydberg,
235
- )
236
-
237
-
238
- def _middle_factor_xy(
239
- scales_l: torch.Tensor,
240
- scales_r: torch.Tensor,
241
- scales_mat: torch.Tensor,
242
- left_interactions: torch.Tensor,
243
- right_interactions: torch.Tensor,
244
- ) -> torch.Tensor:
245
- """
246
- Creates the XY Hamiltonian factor at index ⌊n/2⌋ of the n-qubit MPO.
247
- """
248
- assert len(scales_mat) == len(scales_l)
249
- assert all(len(x) == len(scales_r) for x in scales_mat)
250
-
251
- fac = torch.zeros(2 * len(scales_l) + 2, 2, 2, 2 * len(scales_r) + 2, dtype=dtype)
252
- fac[1, :, :, 2 : 2 * scales_r.shape[0] + 2 : 2] = scales_r * creation_op.reshape(
253
- 2, 2, 1
254
- ) # XY interaction with previous qubits
255
- fac[1, :, :, 3 : 2 * scales_r.shape[0] + 3 : 2] = scales_r * creation_op.T.reshape(
256
- 2, 2, 1
257
- ) # XY interaction with previous qubits
258
- fac[2 : 2 * scales_l.shape[0] + 2 : 2, :, :, 0] = (
259
- scales_l.reshape(-1, 1, 1) * creation_op.T
260
- ) # XY interaction with next qubits
261
- fac[3 : 2 * scales_l.shape[0] + 3 : 2, :, :, 0] = (
262
- scales_l.reshape(-1, 1, 1) * creation_op
263
- ) # XY interaction with next qubits
264
- x_shape, y_shape = scales_mat.shape
265
- fac[2 : 2 * x_shape + 2 : 2, :, :, 2 : 2 * y_shape + 2 : 2] = scales_mat.reshape(
266
- x_shape, 1, 1, y_shape
267
- ) * iden_op.reshape(
268
- 1, 2, 2, 1
269
- ) # XY interaction of previous with next qubits
270
- fac[3 : 2 * x_shape + 3 : 2, :, :, 3 : 2 * y_shape + 3 : 2] = scales_mat.reshape(
271
- x_shape, 1, 1, y_shape
272
- ) * iden_op.reshape(
273
- 1, 2, 2, 1
274
- ) # XY interaction of previous with next qubits
275
- fac[0, :, :, 0] = iden_op # identity to carry the next gates to the previous qubits
276
- fac[1, :, :, 1] = iden_op # identity to carry previous gates to next qubits
277
-
278
- return truncate_factor(
279
- fac, left_interactions, right_interactions, hamiltonian_type=HamiltonianType.XY
280
- )
281
-
282
-
283
- def _get_interactions_to_keep(interaction_matrix: torch.Tensor) -> list[torch.Tensor]:
284
- """
285
- returns a list of bool valued tensors,
286
- indicating which interaction terms to keep for each bond in the MPO
287
- """
288
- interaction_matrix = interaction_matrix.clone()
289
- nqubits = interaction_matrix.size(dim=1)
290
- middle = nqubits // 2
291
- interaction_matrix += torch.eye(
292
- nqubits, nqubits, dtype=interaction_matrix.dtype
293
- ) # below line fails on all zeros
294
- interaction_boundaries = torch.tensor(
295
- [torch.max(torch.nonzero(interaction_matrix[i])) for i in range(middle)]
296
- )
297
- interactions_to_keep = [interaction_boundaries[: i + 1] > i for i in range(middle)]
298
-
299
- interaction_boundaries = torch.tensor(
300
- [
301
- torch.min(torch.nonzero(interaction_matrix[j]))
302
- for j in range(middle + 1, nqubits)
303
- ]
304
- )
305
- interactions_to_keep += [
306
- interaction_boundaries[i - middle :] <= i for i in range(middle, nqubits - 1)
307
- ]
308
- return interactions_to_keep
63
+ @abstractmethod
64
+ def right_factor(self, n: int) -> torch.Tensor:
65
+ pass
66
+
67
+ @abstractmethod
68
+ def last_factor(self) -> torch.Tensor:
69
+ pass
70
+
71
+
72
+ class RydbergHamiltonianMPOFactors(HamiltonianMPOFactors):
73
+ def first_factor(self) -> torch.Tensor:
74
+ has_right_interaction = self.interaction_matrix[0, 1:].any()
75
+ fac = torch.zeros(1, 2, 2, 3 if has_right_interaction else 2, dtype=dtype)
76
+ fac[0, :, :, 1] = Operators.id
77
+ if has_right_interaction:
78
+ fac[0, :, :, 2] = Operators.n
79
+
80
+ return fac
81
+
82
+ def left_factor(self, n: int) -> torch.Tensor:
83
+ has_right_interaction = self.interaction_matrix[n, n + 1 :].any()
84
+ current_left_interactions = self.interaction_matrix[:n, n:].any(dim=1)
85
+ left_interactions_to_keep = self.interaction_matrix[:n, n + 1 :].any(dim=1)
86
+
87
+ fac = torch.zeros(
88
+ int(current_left_interactions.sum().item() + 2),
89
+ 2,
90
+ 2,
91
+ int(left_interactions_to_keep.sum().item() + int(has_right_interaction) + 2),
92
+ dtype=dtype,
93
+ )
94
+
95
+ fac[0, :, :, 0] = Operators.id
96
+ fac[1, :, :, 1] = Operators.id
97
+ if has_right_interaction:
98
+ fac[1, :, :, -1] = Operators.n
99
+
100
+ fac[2:, :, :, 0] = (
101
+ self.interaction_matrix[:n][current_left_interactions, n, None, None]
102
+ * Operators.n
103
+ )
104
+
105
+ i = 2
106
+ j = 2
107
+ for current_left_interaction in current_left_interactions.nonzero().flatten():
108
+ if left_interactions_to_keep[current_left_interaction]:
109
+ fac[i, :, :, j] = Operators.id
110
+ j += 1
111
+ i += 1
112
+ return fac
113
+
114
+ def middle_factor(self) -> torch.Tensor:
115
+ n = self.middle
116
+ current_left_interactions = self.interaction_matrix[:n, n:].any(dim=1)
117
+ current_right_interactions = self.interaction_matrix[n + 1 :, : n + 1].any(dim=1)
118
+
119
+ fac = torch.zeros(
120
+ int(current_left_interactions.sum().item() + 2),
121
+ 2,
122
+ 2,
123
+ int(current_right_interactions.sum().item() + 2),
124
+ dtype=dtype,
125
+ )
126
+
127
+ fac[0, :, :, 0] = Operators.id
128
+ fac[1, :, :, 1] = Operators.id
129
+
130
+ fac[2:, :, :, 0] = (
131
+ self.interaction_matrix[:n][current_left_interactions, n, None, None]
132
+ * Operators.n
133
+ )
134
+
135
+ fac[1, :, :, 2:] = self.interaction_matrix[n + 1 :][
136
+ None, None, current_right_interactions, n
137
+ ] * Operators.n.unsqueeze(-1)
138
+
139
+ fac[2:, :, :, 2:] = (
140
+ self.interaction_matrix[:n, n + 1 :][current_left_interactions, :][
141
+ :, None, None, current_right_interactions
142
+ ]
143
+ * Operators.id[None, ..., None]
144
+ )
145
+
146
+ return fac
147
+
148
+ def right_factor(self, n: int) -> torch.Tensor:
149
+ has_left_interaction = self.interaction_matrix[n, :n].any()
150
+ current_right_interactions = self.interaction_matrix[n + 1 :, : n + 1].any(dim=1)
151
+ right_interactions_to_keep = self.interaction_matrix[n + 1 :, :n].any(dim=1)
152
+
153
+ fac = torch.zeros(
154
+ int(right_interactions_to_keep.sum().item() + int(has_left_interaction) + 2),
155
+ 2,
156
+ 2,
157
+ int(current_right_interactions.sum().item() + 2),
158
+ dtype=dtype,
159
+ )
160
+
161
+ fac[0, :, :, 0] = Operators.id
162
+ fac[1, :, :, 1] = Operators.id
163
+ if has_left_interaction:
164
+ fac[2, :, :, 0] = Operators.n
165
+
166
+ fac[1, :, :, 2:] = self.interaction_matrix[n + 1 :][
167
+ None, None, current_right_interactions, n
168
+ ] * Operators.n.unsqueeze(-1)
169
+
170
+ i = 3 if has_left_interaction else 2
171
+ j = 2
172
+ for current_right_interaction in current_right_interactions.nonzero().flatten():
173
+ if right_interactions_to_keep[current_right_interaction]:
174
+ fac[i, :, :, j] = Operators.id
175
+ i += 1
176
+ j += 1
177
+ return fac
178
+
179
+ def last_factor(self) -> torch.Tensor:
180
+ has_left_interaction = self.interaction_matrix[-1, :-1].any()
181
+ fac = torch.zeros(3 if has_left_interaction else 2, 2, 2, 1, dtype=dtype)
182
+ fac[0, :, :, 0] = Operators.id
183
+ if has_left_interaction:
184
+ if self.qubit_count >= 3:
185
+ fac[2, :, :, 0] = Operators.n
186
+ else:
187
+ fac[2, :, :, 0] = self.interaction_matrix[0, 1] * Operators.n
188
+
189
+ return fac
190
+
191
+
192
+ class XYHamiltonianMPOFactors(HamiltonianMPOFactors):
193
+ def first_factor(self) -> torch.Tensor:
194
+ has_right_interaction = self.interaction_matrix[0, 1:].any()
195
+ fac = torch.zeros(1, 2, 2, 4 if has_right_interaction else 2, dtype=dtype)
196
+ fac[0, :, :, 1] = Operators.id
197
+ if has_right_interaction:
198
+ fac[0, :, :, 2] = Operators.creation
199
+ fac[0, :, :, 3] = Operators.creation.T
200
+
201
+ return fac
202
+
203
+ def left_factor(self, n: int) -> torch.Tensor:
204
+ has_right_interaction = self.interaction_matrix[n, n + 1 :].any()
205
+ current_left_interactions = self.interaction_matrix[:n, n:].any(dim=1)
206
+ left_interactions_to_keep = self.interaction_matrix[:n, n + 1 :].any(dim=1)
207
+
208
+ fac = torch.zeros(
209
+ int(2 * current_left_interactions.sum().item() + 2),
210
+ 2,
211
+ 2,
212
+ int(
213
+ 2 * left_interactions_to_keep.sum().item()
214
+ + 2 * int(has_right_interaction)
215
+ + 2
216
+ ),
217
+ dtype=dtype,
218
+ )
219
+
220
+ fac[0, :, :, 0] = Operators.id
221
+ fac[1, :, :, 1] = Operators.id
222
+ if has_right_interaction:
223
+ fac[1, :, :, -2] = Operators.creation
224
+ fac[1, :, :, -1] = Operators.creation.T
225
+
226
+ fac[2::2, :, :, 0] = (
227
+ self.interaction_matrix[:n][current_left_interactions, n, None, None]
228
+ * Operators.creation.T
229
+ )
230
+ fac[3::2, :, :, 0] = (
231
+ self.interaction_matrix[:n][current_left_interactions, n, None, None]
232
+ * Operators.creation
233
+ )
234
+
235
+ i = 2
236
+ j = 2
237
+ for current_left_interaction in current_left_interactions.nonzero().flatten():
238
+ if left_interactions_to_keep[current_left_interaction]:
239
+ fac[i, :, :, j] = Operators.id
240
+ fac[i + 1, :, :, j + 1] = Operators.id
241
+ j += 2
242
+ i += 2
243
+ return fac
244
+
245
+ def middle_factor(self) -> torch.Tensor:
246
+ n = self.middle
247
+ current_left_interactions = self.interaction_matrix[:n, n:].any(dim=1)
248
+ current_right_interactions = self.interaction_matrix[n + 1 :, : n + 1].any(dim=1)
249
+
250
+ fac = torch.zeros(
251
+ int(2 * current_left_interactions.sum().item() + 2),
252
+ 2,
253
+ 2,
254
+ int(2 * current_right_interactions.sum().item() + 2),
255
+ dtype=dtype,
256
+ )
257
+
258
+ fac[0, :, :, 0] = Operators.id
259
+ fac[1, :, :, 1] = Operators.id
260
+
261
+ fac[2::2, :, :, 0] = (
262
+ self.interaction_matrix[:n][current_left_interactions, n, None, None]
263
+ * Operators.creation.T
264
+ )
265
+ fac[3::2, :, :, 0] = (
266
+ self.interaction_matrix[:n][current_left_interactions, n, None, None]
267
+ * Operators.creation
268
+ )
269
+
270
+ fac[1, :, :, 2::2] = self.interaction_matrix[n + 1 :][
271
+ None, None, current_right_interactions, n
272
+ ] * Operators.creation.unsqueeze(-1)
273
+ fac[1, :, :, 3::2] = self.interaction_matrix[n + 1 :][
274
+ None, None, current_right_interactions, n
275
+ ] * Operators.creation.T.unsqueeze(-1)
276
+
277
+ fac[2::2, :, :, 2::2] = (
278
+ self.interaction_matrix[:n, n + 1 :][current_left_interactions, :][
279
+ :, None, None, current_right_interactions
280
+ ]
281
+ * Operators.id[None, ..., None]
282
+ )
283
+ fac[3::2, :, :, 3::2] = (
284
+ self.interaction_matrix[:n, n + 1 :][current_left_interactions, :][
285
+ :, None, None, current_right_interactions
286
+ ]
287
+ * Operators.id[None, ..., None]
288
+ )
289
+
290
+ return fac
291
+
292
+ def right_factor(self, n: int) -> torch.Tensor:
293
+ has_left_interaction = self.interaction_matrix[n, :n].any()
294
+ current_right_interactions = self.interaction_matrix[n + 1 :, : n + 1].any(dim=1)
295
+ right_interactions_to_keep = self.interaction_matrix[n + 1 :, :n].any(dim=1)
296
+
297
+ fac = torch.zeros(
298
+ int(
299
+ 2 * right_interactions_to_keep.sum().item()
300
+ + 2 * int(has_left_interaction)
301
+ + 2
302
+ ),
303
+ 2,
304
+ 2,
305
+ int(2 * current_right_interactions.sum().item() + 2),
306
+ dtype=dtype,
307
+ )
308
+
309
+ fac[0, :, :, 0] = Operators.id
310
+ fac[1, :, :, 1] = Operators.id
311
+ if has_left_interaction:
312
+ fac[2, :, :, 0] = Operators.creation.T
313
+ fac[3, :, :, 0] = Operators.creation
314
+
315
+ fac[1, :, :, 2::2] = self.interaction_matrix[n + 1 :][
316
+ None, None, current_right_interactions, n
317
+ ] * Operators.creation.unsqueeze(-1)
318
+ fac[1, :, :, 3::2] = self.interaction_matrix[n + 1 :][
319
+ None, None, current_right_interactions, n
320
+ ] * Operators.creation.T.unsqueeze(-1)
321
+
322
+ i = 4 if has_left_interaction else 2
323
+ j = 2
324
+ for current_right_interaction in current_right_interactions.nonzero().flatten():
325
+ if right_interactions_to_keep[current_right_interaction]:
326
+ fac[i, :, :, j] = Operators.id
327
+ fac[i + 1, :, :, j + 1] = Operators.id
328
+ i += 2
329
+ j += 2
330
+ return fac
331
+
332
+ def last_factor(self) -> torch.Tensor:
333
+ has_left_interaction = self.interaction_matrix[-1, :-1].any()
334
+ fac = torch.zeros(4 if has_left_interaction else 2, 2, 2, 1, dtype=dtype)
335
+ fac[0, :, :, 0] = Operators.id
336
+ if has_left_interaction:
337
+ if self.qubit_count >= 3:
338
+ fac[2, :, :, 0] = Operators.creation.T
339
+ fac[3, :, :, 0] = Operators.creation
340
+ else:
341
+ fac[2, :, :, 0] = self.interaction_matrix[0, 1] * Operators.creation.T
342
+ fac[3, :, :, 0] = self.interaction_matrix[0, 1] * Operators.creation
343
+
344
+ return fac
309
345
 
310
346
 
311
347
  def make_H(
@@ -342,69 +378,19 @@ def make_H(
342
378
  [Pulser documentation](https://pulser.readthedocs.io/en/stable/conventions.html#hamiltonians).
343
379
 
344
380
  """
345
-
346
381
  if hamiltonian_type == HamiltonianType.Rydberg:
347
- _first_factor = _first_factor_rydberg
348
- _last_factor = _last_factor_rydberg
349
- _left_factor = _left_factor_rydberg
350
- _right_factor = _right_factor_rydberg
351
- _middle_factor = _middle_factor_rydberg
352
- elif hamiltonian_type == HamiltonianType.XY:
353
- _first_factor = _first_factor_xy
354
- _last_factor = _last_factor_xy
355
- _left_factor = _left_factor_xy
356
- _right_factor = _right_factor_xy
357
- _middle_factor = _middle_factor_xy
358
- else:
359
- raise ValueError(f"Unsupported hamiltonian type {hamiltonian_type}")
360
-
361
- nqubits = interaction_matrix.size(dim=1)
362
- middle = nqubits // 2
363
- interactions_to_keep = _get_interactions_to_keep(interaction_matrix)
364
-
365
- cores = [_first_factor(interactions_to_keep[0].item() != 0.0)]
366
-
367
- if nqubits > 2:
368
- for i in range(1, middle):
369
- cores.append(
370
- _left_factor(
371
- interaction_matrix[:i, i],
372
- left_interactions=interactions_to_keep[i - 1],
373
- right_interactions=interactions_to_keep[i],
374
- )
375
- )
376
-
377
- i = middle
378
- cores.append(
379
- _middle_factor(
380
- interaction_matrix[:i, i],
381
- interaction_matrix[i, i + 1 :],
382
- interaction_matrix[:i, i + 1 :],
383
- interactions_to_keep[i - 1],
384
- interactions_to_keep[i],
385
- )
382
+ return MPO(
383
+ list(RydbergHamiltonianMPOFactors(interaction_matrix)),
384
+ num_gpus_to_use=num_gpus_to_use,
386
385
  )
387
386
 
388
- for i in range(middle + 1, nqubits - 1):
389
- cores.append(
390
- _right_factor(
391
- interaction_matrix[i, i + 1 :],
392
- interactions_to_keep[i - 1],
393
- interactions_to_keep[i],
394
- )
395
- )
396
- if nqubits == 2:
397
- scale = interaction_matrix[0, 1].item()
398
- elif interactions_to_keep[-1][0]:
399
- scale = 1.0
400
- else:
401
- scale = 0.0
402
- cores.append(
403
- _last_factor(
404
- scale,
387
+ if hamiltonian_type == HamiltonianType.XY:
388
+ return MPO(
389
+ list(XYHamiltonianMPOFactors(interaction_matrix)),
390
+ num_gpus_to_use=num_gpus_to_use,
405
391
  )
406
- )
407
- return MPO(cores, num_gpus_to_use=num_gpus_to_use)
392
+
393
+ raise ValueError(f"Unsupported hamiltonian type {hamiltonian_type}")
408
394
 
409
395
 
410
396
  def update_H(
@@ -437,9 +423,9 @@ def update_H(
437
423
  assert noise.shape == (2, 2)
438
424
  nqubits = omega.size(dim=0)
439
425
 
440
- a = torch.tensordot(omega * torch.cos(phi), sx, dims=0)
441
- c = torch.tensordot(delta, pu, dims=0)
442
- b = torch.tensordot(omega * torch.sin(phi), sy, dims=0)
426
+ a = torch.tensordot(omega * torch.cos(phi), Operators.sx, dims=0)
427
+ c = torch.tensordot(delta, Operators.pu, dims=0)
428
+ b = torch.tensordot(omega * torch.sin(phi), Operators.sy, dims=0)
443
429
 
444
430
  single_qubit_terms = a + b - c + noise
445
431
  factors = hamiltonian.factors