Skip to content

Commit c67d9ef

Browse files
fix energy observables at time 0. (#218)
1 parent e4ef079 commit c67d9ef

28 files changed

+302
-275
lines changed

docs/emu_sv/notebooks/getting_started.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@
192192
},
193193
{
194194
"cell_type": "code",
195-
"execution_count": 6,
195+
"execution_count": null,
196196
"metadata": {},
197197
"outputs": [
198198
{
@@ -211,7 +211,7 @@
211211
],
212212
"source": [
213213
"last_elem = -1\n",
214-
"results.state[last_elem].vector[:10]"
214+
"results.state[last_elem].data[:10]"
215215
]
216216
},
217217
{

emu_mps/mps_backend.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ def run(self) -> Results:
7171

7272
@staticmethod
7373
def _run(impl: MPSBackendImpl) -> Results:
74-
impl.fill_results() # at t == 0 for pulser compatibility
7574
while not impl.is_finished():
7675
impl.progress()
7776

emu_mps/mps_backend_impl.py

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ def init_initial_state(self, initial_state: State | None = None) -> None:
260260
self.state = initial_state
261261
self.state.orthogonalize(0)
262262

263-
def init_hamiltonian(self) -> None:
263+
def init_noiseless_hamiltonian(self) -> None:
264264
"""
265265
Must be called AFTER init_dark_qubits otherwise,
266266
too many factors are put in the Hamiltonian
@@ -275,7 +275,9 @@ def init_hamiltonian(self) -> None:
275275
num_gpus_to_use=self.resolved_num_gpus,
276276
dim=self.dim,
277277
)
278+
self.update_H_no_noise()
278279

280+
def update_H(self) -> None:
279281
update_H(
280282
hamiltonian=self.hamiltonian,
281283
omega=self.omega[self.timestep_index, :],
@@ -284,6 +286,15 @@ def init_hamiltonian(self) -> None:
284286
noise=self.lindblad_noise,
285287
)
286288

289+
def update_H_no_noise(self) -> None:
290+
update_H(
291+
hamiltonian=self.hamiltonian,
292+
omega=self.omega[self.timestep_index, :],
293+
delta=self.delta[self.timestep_index, :],
294+
phi=self.phi[self.timestep_index, :],
295+
noise=torch.zeros(self.dim, self.dim, dtype=dtype), # no noise
296+
)
297+
287298
def init_baths(self) -> None:
288299
self.left_baths = [
289300
torch.ones(1, 1, 1, dtype=dtype, device=self.state.factors[0].device)
@@ -300,7 +311,9 @@ def get_current_left_bath(self) -> torch.Tensor:
300311
def init(self) -> None:
301312
self.init_dark_qubits()
302313
self.init_initial_state(self.config.initial_state)
303-
self.init_hamiltonian()
314+
self.init_noiseless_hamiltonian()
315+
self.fill_results() # at t == 0 for pulser compatibility
316+
self.update_H()
304317
self.init_baths()
305318

306319
def is_finished(self) -> bool:
@@ -466,13 +479,7 @@ def timestep_complete(self) -> None:
466479

467480
if not self.is_finished():
468481
self.target_time = self.target_times[self.timestep_index + 1]
469-
update_H(
470-
hamiltonian=self.hamiltonian,
471-
omega=self.omega[self.timestep_index, :],
472-
delta=self.delta[self.timestep_index, :],
473-
phi=self.phi[self.timestep_index, :],
474-
noise=self.lindblad_noise,
475-
)
482+
self.update_H()
476483
self.init_baths()
477484

478485
self.statistics.data.append(time.time() - self.time)
@@ -709,19 +716,8 @@ def do_random_quantum_jump(self) -> None:
709716
assert math.isclose(norm_after_normalizing, 1, abs_tol=1e-10)
710717
self.set_jump_threshold(norm_after_normalizing**2)
711718

712-
def remove_noise_from_hamiltonian(self) -> None:
713-
# Remove the noise from self.hamiltonian for the callbacks.
714-
# Since update_H is called at the start of do_time_step this is safe.
715-
update_H(
716-
hamiltonian=self.hamiltonian,
717-
omega=self.omega[self.timestep_index - 1, :], # Meh
718-
delta=self.delta[self.timestep_index - 1, :],
719-
phi=self.phi[self.timestep_index - 1, :],
720-
noise=torch.zeros(self.dim, self.dim, dtype=dtype), # no noise
721-
)
722-
723719
def timestep_complete(self) -> None:
724-
self.remove_noise_from_hamiltonian()
720+
self.update_H_no_noise()
725721
super().timestep_complete()
726722

727723

emu_sv/custom_callback_implementations.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ def qubit_occupation_sv_impl(
2727
Custom implementation of the occupation ❬ψ|nᵢ|ψ❭ for the state vector solver.
2828
"""
2929
nqubits = state.n_qudits
30-
occupation = torch.zeros(nqubits, dtype=dtype, device=state.vector.device)
30+
occupation = torch.zeros(nqubits, dtype=dtype, device=state.data.device)
3131
for i in range(nqubits):
32-
state_tensor = state.vector.view(2**i, 2, -1)
32+
state_tensor = state.data.view(2**i, 2, -1)
3333
# nᵢ is a projector and therefore nᵢ == nᵢnᵢ
3434
# ❬ψ|nᵢ|ψ❭ == ❬ψ|nᵢnᵢ|ψ❭ == ❬ψ|nᵢ * nᵢ|ψ❭ == ❬ϕ|ϕ❭ == |ϕ|**2
3535
occupation[i] = torch.linalg.vector_norm(state_tensor[:, 1]) ** 2
@@ -55,8 +55,8 @@ def qubit_occupation_sv_den_mat_impl(
5555
will be <nᵢ> = Tr(ρnᵢ), or [ <n₁>, <n₂>, <n₃> ].
5656
"""
5757
nqubits = state.n_qudits
58-
occupation = torch.zeros(nqubits, dtype=dtype, device=state.matrix.device)
59-
diag_state_tensor = state.matrix.diagonal()
58+
occupation = torch.zeros(nqubits, dtype=dtype, device=state.data.device)
59+
diag_state_tensor = state.data.diagonal()
6060
for i in range(nqubits):
6161
state_tensor = diag_state_tensor.view(2**i, 2, 2 ** (nqubits - i - 1))[:, 1, :]
6262
occupation[i] = state_tensor.sum().real
@@ -76,10 +76,10 @@ def correlation_matrix_sv_impl(
7676
TODO: extend to arbitrary two-point correlation ❬ψ|AᵢBⱼ|ψ❭
7777
"""
7878
nqubits = state.n_qudits
79-
correlation = torch.zeros(nqubits, nqubits, dtype=dtype, device=state.vector.device)
79+
correlation = torch.zeros(nqubits, nqubits, dtype=dtype, device=state.data.device)
8080

8181
for i in range(nqubits):
82-
select_i = state.vector.view(2**i, 2, -1)
82+
select_i = state.data.view(2**i, 2, -1)
8383
select_i = select_i[:, 1]
8484
correlation[i, i] = torch.linalg.vector_norm(select_i) ** 2
8585
for j in range(i + 1, nqubits): # select the upper triangle
@@ -104,7 +104,7 @@ def correlation_matrix_sv_den_mat_impl(
104104
"""
105105
nqubits = state.n_qudits
106106
correlation = torch.zeros(nqubits, nqubits, dtype=dtype)
107-
state_diag_matrix = state.matrix.diagonal()
107+
state_diag_matrix = state.data.diagonal()
108108
for i in range(nqubits): # applying ni
109109
shapei = (2**i, 2, 2 ** (nqubits - i - 1))
110110
state_diag_ni = state_diag_matrix.view(*shapei)[:, 1, :]
@@ -127,9 +127,9 @@ def energy_variance_sv_impl(
127127
"""
128128
Custom implementation of the energy variance ❬ψ|H²|ψ❭-❬ψ|H|ψ❭² for the state vector solver.
129129
"""
130-
hstate = hamiltonian * state.vector
130+
hstate = hamiltonian * state.data
131131
h_squared = torch.vdot(hstate, hstate).real
132-
energy = torch.vdot(state.vector, hstate).real
132+
energy = torch.vdot(state.data, hstate).real
133133
en_var: torch.Tensor = h_squared - energy**2
134134
return en_var.cpu()
135135

@@ -145,8 +145,8 @@ def energy_variance_sv_den_mat_impl(
145145
Custom implementation of the energy variance tr(ρH²)-tr(ρH)² for the
146146
lindblad equation solver.
147147
"""
148-
h_dense_matrix = hamiltonian.h_eff(state.matrix) # Hρ
149-
gpu = state.matrix.is_cuda
148+
h_dense_matrix = hamiltonian.h_eff(state.data) # Hρ
149+
gpu = state.data.is_cuda
150150
h_squared_dense_mat = hamiltonian.expect(
151151
DensityMatrix(h_dense_matrix, gpu=gpu)
152152
) # tr(ρH²)
@@ -165,7 +165,7 @@ def energy_second_moment_sv_impl(
165165
Custom implementation of the second moment of energy ❬ψ|H²|ψ❭
166166
for the state vector solver.
167167
"""
168-
hstate = hamiltonian * state.vector
168+
hstate = hamiltonian * state.data
169169
en_2_mom: torch.Tensor = torch.vdot(hstate, hstate).real
170170
return en_2_mom.cpu()
171171

@@ -181,8 +181,8 @@ def energy_second_moment_den_mat_impl(
181181
Custom implementation of the second moment of energy tr(ρH²) for the
182182
lindblad equation solver.
183183
"""
184-
h_dense_matrix = hamiltonian.h_eff(state.matrix) # Hρ
185-
gpu = state.matrix.is_cuda
184+
h_dense_matrix = hamiltonian.h_eff(state.data) # Hρ
185+
gpu = state.data.is_cuda
186186

187187
return hamiltonian.expect(
188188
DensityMatrix(h_dense_matrix, gpu=gpu)

emu_sv/dense_operator.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ def __init__(
4545
gpu: bool = True,
4646
):
4747
device = "cuda" if gpu and DEVICE_COUNT > 0 else "cpu"
48-
self.matrix = matrix.to(dtype=dtype, device=device)
48+
self.data = matrix.to(dtype=dtype, device=device)
4949

5050
def __repr__(self) -> str:
51-
return repr(self.matrix)
51+
return repr(self.data)
5252

5353
def __matmul__(self, other: Operator) -> DenseOperator:
5454
"""
@@ -63,7 +63,7 @@ def __matmul__(self, other: Operator) -> DenseOperator:
6363
assert isinstance(
6464
other, DenseOperator
6565
), "DenseOperator can only be multiplied with a DenseOperator."
66-
return DenseOperator(self.matrix @ other.matrix)
66+
return DenseOperator(self.data @ other.data)
6767

6868
def __add__(self, other: Operator) -> DenseOperator:
6969
"""
@@ -78,7 +78,7 @@ def __add__(self, other: Operator) -> DenseOperator:
7878
assert isinstance(
7979
other, DenseOperator
8080
), "DenseOperator can only be added to another DenseOperator."
81-
return DenseOperator(self.matrix + other.matrix)
81+
return DenseOperator(self.data + other.data)
8282

8383
def __rmul__(self, scalar: complex) -> DenseOperator:
8484
"""
@@ -91,7 +91,7 @@ def __rmul__(self, scalar: complex) -> DenseOperator:
9191
A new DenseOperator scaled by the given scalar.
9292
"""
9393

94-
return DenseOperator(scalar * self.matrix)
94+
return DenseOperator(scalar * self.data)
9595

9696
def apply_to(self, other: State) -> StateVector:
9797
"""
@@ -107,7 +107,7 @@ def apply_to(self, other: State) -> StateVector:
107107
other, StateVector
108108
), "DenseOperator can only be applied to a StateVector."
109109

110-
return StateVector(self.matrix @ other.vector)
110+
return StateVector(self.data @ other.data)
111111

112112
def expect(self, state: State) -> torch.Tensor:
113113
"""
@@ -123,7 +123,7 @@ def expect(self, state: State) -> torch.Tensor:
123123
state, StateVector
124124
), "Only expectation values of StateVectors are supported."
125125

126-
return torch.vdot(state.vector, self.apply_to(state).vector).cpu()
126+
return torch.vdot(state.data, self.apply_to(state).data).cpu()
127127

128128
@classmethod
129129
def _from_operator_repr(

emu_sv/density_matrix_state.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ def __init__(
4848
# NOTE: this accepts also zero matrices.
4949

5050
device = "cuda" if gpu and DEVICE_COUNT > 0 else "cpu"
51-
self.matrix = matrix.to(dtype=dtype, device=device)
51+
self.data = matrix.to(dtype=dtype, device=device)
5252

5353
@property
5454
def n_qudits(self) -> int:
5555
"""The number of qudits in the state."""
56-
nqudits = math.log2(self.matrix.shape[0])
56+
nqudits = math.log2(self.data.shape[0])
5757
return int(nqudits)
5858

5959
@classmethod
@@ -72,9 +72,9 @@ def __rmul__(self, scalar: complex) -> DensityMatrix:
7272
def _normalize(self) -> None:
7373
# NOTE: use this in the callbacks
7474
"""Normalize the density matrix state"""
75-
matrix_trace = torch.trace(self.matrix)
75+
matrix_trace = torch.trace(self.data)
7676
if not torch.allclose(matrix_trace, torch.tensor(1.0, dtype=torch.float64)):
77-
self.matrix = self.matrix / matrix_trace
77+
self.data = self.data / matrix_trace
7878

7979
def overlap(self, other: State) -> torch.Tensor:
8080
"""
@@ -104,12 +104,10 @@ def overlap(self, other: State) -> torch.Tensor:
104104
other, DensityMatrix
105105
), "Other state also needs to be a DensityMatrix"
106106
assert (
107-
self.matrix.shape == other.matrix.shape
107+
self.data.shape == other.data.shape
108108
), "States do not have the same number of sites"
109109

110-
return torch.vdot(
111-
self.matrix.flatten(), other.matrix.to(self.matrix.device).flatten()
112-
)
110+
return torch.vdot(self.data.flatten(), other.data.to(self.data.device).flatten())
113111

114112
@classmethod
115113
def from_state_vector(cls, state: StateVector) -> DensityMatrix:
@@ -124,7 +122,7 @@ def from_state_vector(cls, state: StateVector) -> DensityMatrix:
124122
dtype=torch.complex128)
125123
bell_state = StateVector(bell_state_vec, gpu=False)
126124
density = DensityMatrix.from_state_vector(bell_state)
127-
print(density.matrix)
125+
print(density.data)
128126
```
129127
130128
Output:
@@ -137,9 +135,7 @@ def from_state_vector(cls, state: StateVector) -> DensityMatrix:
137135
```
138136
"""
139137

140-
return cls(
141-
torch.outer(state.vector, state.vector.conj()), gpu=state.vector.is_cuda
142-
)
138+
return cls(torch.outer(state.data, state.data.conj()), gpu=state.data.is_cuda)
143139

144140
@classmethod
145141
def _from_state_amplitudes(
@@ -170,7 +166,7 @@ def _from_state_amplitudes(
170166
n = 2
171167
dense_mat=DensityMatrix.from_state_amplitudes(eigenstates=eigenstates,
172168
amplitudes={"rr":1.0,"gg":1.0})
173-
print(dense_mat.matrix)
169+
print(dense_mat.data)
174170
```
175171
176172
Output:
@@ -224,7 +220,7 @@ def sample(
224220
```
225221
"""
226222

227-
probabilities = torch.abs(self.matrix.diagonal())
223+
probabilities = torch.abs(self.data.diagonal())
228224

229225
outcomes = torch.multinomial(probabilities, num_shots, replacement=True)
230226

emu_sv/hamiltonian.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,6 @@ def expect(self, state: StateVector) -> torch.Tensor:
150150
assert isinstance(
151151
state, StateVector
152152
), "Currently, only expectation values of StateVectors are supported"
153-
en = torch.vdot(state.vector, self * state.vector)
153+
en = torch.vdot(state.data, self * state.data)
154154
assert torch.allclose(en.imag, torch.zeros_like(en.imag), atol=1e-8)
155155
return en.real

emu_sv/lindblad_operator.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class RydbergLindbladian:
2828
phis (torch.Tensor): phases 𝜙ᵢ for each qubit.
2929
interaction_matrix (torch.Tensor): interaction_matrix (torch.Tensor): matrix Uᵢⱼ
3030
representing pairwise Rydberg interaction strengths between qubits.
31-
pulser_linblads (list[torch.Tensor]): List of 2x2 local Lindblad (jump)
31+
pulser_lindblads (list[torch.Tensor]): List of 2x2 local Lindblad (jump)
3232
operators acting on each qubit.
3333
device (torch.device): device on which tensors are allocated. cpu or gpu: cuda.
3434
complex (bool): flag indicating whether any drive phase is nonzero
@@ -52,7 +52,7 @@ def __init__(
5252
omegas: torch.Tensor,
5353
deltas: torch.Tensor,
5454
phis: torch.Tensor,
55-
pulser_linblads: list[torch.Tensor],
55+
pulser_lindblads: list[torch.Tensor],
5656
interaction_matrix: torch.Tensor,
5757
device: torch.device,
5858
):
@@ -61,7 +61,7 @@ def __init__(
6161
self.deltas: torch.Tensor = deltas
6262
self.phis: torch.Tensor = phis
6363
self.interaction_matrix: torch.Tensor = interaction_matrix
64-
self.pulser_linblads: list[torch.Tensor] = pulser_linblads
64+
self.pulser_lindblads: list[torch.Tensor] = pulser_lindblads
6565
self.device: torch.device = device
6666
self.complex = self.phis.any()
6767

@@ -185,7 +185,7 @@ def __matmul__(self, density_matrix: torch.Tensor) -> torch.Tensor:
185185
"""
186186

187187
# compute -0.5i ∑ₖ Lₖ† Lₖ
188-
sum_lindblad_local = compute_noise_from_lindbladians(self.pulser_linblads).to(
188+
sum_lindblad_local = compute_noise_from_lindbladians(self.pulser_lindblads).to(
189189
self.device
190190
)
191191
# Heff = Hρ -0.5i ∑ₖ Lₖ† Lₖ ρ
@@ -204,14 +204,14 @@ def __matmul__(self, density_matrix: torch.Tensor) -> torch.Tensor:
204204
qubit,
205205
)
206206
for qubit in range(self.nqubits)
207-
for L in self.pulser_linblads
207+
for L in self.pulser_lindblads
208208
)
209209

210210
return H_den_matrix + 1.0j * L_den_matrix_Ldag
211211

212212
def expect(self, state: DensityMatrix) -> torch.Tensor:
213213
"""Return the energy expectation value E=tr(H𝜌)"""
214-
en = (self.h_eff(state.matrix)).trace()
214+
en = (self.h_eff(state.data)).trace()
215215

216216
assert torch.allclose(en.imag, torch.zeros_like(en.imag), atol=1e-8)
217217
return en.real

0 commit comments

Comments
 (0)