Sor0ush's picture
download
raw
8.33 kB
"""
Differentiable Lindblad Master Equation Simulator (PyTorch)
This module provides a fully differentiable implementation of the Lindblad
master equation using PyTorch.
© 2025 The MITRE Corporation, All Rights Reserved
"""
import torch
import torch.nn as nn
from typing import List, Tuple, Optional
class DifferentiableLindbladSimulator(nn.Module):
"""
PyTorch-native Lindblad simulator with full gradient support.
"""
def __init__(
self,
H0: torch.Tensor,
H_controls: List[torch.Tensor],
L_operators: List[torch.Tensor],
dt: float = 0.005,
method: str = 'rk4',
device: torch.device = torch.device('cpu'),
max_control_amp: float = 10.0
):
"""
Args:
H0: Drift Hamiltonian (d x d) complex tensor
H_controls: List of control Hamiltonians, each (d x d) complex
L_operators: List of Lindblad operators, each (d x d) complex
dt: Time step for integration (smaller = more accurate)
method: Integration method ('euler', 'rk4')
device: torch device
max_control_amp: Maximum allowed control amplitude (for stability)
"""
super().__init__()
self.device = device
self.method = method
self.dt = dt
self.max_control_amp = max_control_amp
self.H0 = H0.to(device).to(torch.complex64)
self.H_controls = [H.to(device).to(torch.complex64) for H in H_controls]
self.L_operators = [L.to(device).to(torch.complex64) for L in L_operators]
self.d = H0.shape[0]
self.n_controls = len(H_controls)
self.n_lindblad = len(L_operators)
self.anti_commutators = [
L.conj().T @ L for L in self.L_operators
]
def lindbladian(
self,
rho: torch.Tensor,
u: torch.Tensor
) -> torch.Tensor:
"""
Compute Lindblad time derivative: ρ̇ = L[ρ].
Args:
rho: Density matrix (d x d) complex tensor
u: Control amplitudes (n_controls,) real tensor
Returns:
drho_dt: Time derivative (d x d) complex tensor
"""
u = torch.clamp(u, -self.max_control_amp, self.max_control_amp)
H_total = self.H0 + sum(u_k * H_k for u_k, H_k in zip(u, self.H_controls))
hamiltonian_term = -1j * (H_total @ rho - rho @ H_total)
dissipation_term = torch.zeros_like(rho)
for j, L_j in enumerate(self.L_operators):
dissipation_term = dissipation_term + L_j @ rho @ L_j.conj().T
dissipation_term = dissipation_term - 0.5 * (
self.anti_commutators[j] @ rho + rho @ self.anti_commutators[j]
)
return hamiltonian_term + dissipation_term
def step_euler(
self,
rho: torch.Tensor,
u: torch.Tensor,
dt: float
) -> torch.Tensor:
"""
Single Euler integration step: ρ(t+dt) = ρ(t) + dt * ρ̇(t).
Args:
rho: Current density matrix
u: Control amplitudes
dt: Time step
Returns:
rho_next: Density matrix at next time step
"""
drho = self.lindbladian(rho, u)
return rho + dt * drho
def step_rk4(
self,
rho: torch.Tensor,
u: torch.Tensor,
dt: float
) -> torch.Tensor:
"""
Single RK4 integration step (4th order Runge-Kutta).
More accurate than Euler, still fully differentiable.
Args:
rho: Current density matrix
u: Control amplitudes
dt: Time step
Returns:
rho_next: Density matrix at next time step
"""
k1 = self.lindbladian(rho, u)
k2 = self.lindbladian(rho + 0.5 * dt * k1, u)
k3 = self.lindbladian(rho + 0.5 * dt * k2, u)
k4 = self.lindbladian(rho + dt * k3, u)
rho_next = rho + (dt / 6.0) * (k1 + 2*k2 + 2*k3 + k4)
return rho_next
def evolve(
self,
rho0: torch.Tensor,
control_sequence: torch.Tensor,
T: float,
return_trajectory: bool = False,
normalize: bool = True
) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
"""
Evolve state from t=0 to t=T under piecewise-constant controls.
FULLY DIFFERENTIABLE - gradients flow through entire simulation!
Args:
rho0: Initial state (d x d) complex tensor
control_sequence: Control pulses (n_segments, n_controls) real tensor
T: Total evolution time
return_trajectory: If True, return full trajectory
normalize: If True, renormalize density matrix to maintain trace=1
Returns:
rho_final: Final state (d x d)
trajectory: Optional trajectory tensor (n_segments+1, d, d)
"""
n_segments = control_sequence.shape[0]
t_segment = T / n_segments
# Ensure rho0 is complex
rho = rho0.to(torch.complex64).to(self.device)
trace0 = torch.trace(rho).real
# Renormalize initial state if needed
if torch.abs(trace0 - 1.0) > 0.01 and trace0 > 1e-10:
rho = rho / trace0
if return_trajectory:
trajectory = [rho.clone()]
for seg_idx in range(n_segments):
u_seg = control_sequence[seg_idx]
n_substeps = max(1, int(t_segment / self.dt))
dt_substep = t_segment / n_substeps
for substep in range(n_substeps):
if self.method == 'euler':
rho = self.step_euler(rho, u_seg, dt_substep)
elif self.method == 'rk4':
rho = self.step_rk4(rho, u_seg, dt_substep)
else:
raise ValueError(f"Unknown method: {self.method}")
if normalize:
trace = torch.trace(rho).real
if trace > 1e-10:
rho = rho / trace
else:
rho = rho0.clone()
if return_trajectory:
trajectory.append(rho.clone())
return rho, (torch.stack(trajectory) if return_trajectory else None)
if torch.isnan(rho).any() or torch.isinf(rho).any():
rho = rho0.clone()
if return_trajectory:
trajectory.append(rho.clone())
trace_final = torch.trace(rho).real
if trace_final > 1e-10:
rho = rho / trace_final
if return_trajectory:
trajectory_tensor = torch.stack(trajectory)
return rho, trajectory_tensor
else:
return rho, None
def forward(
self,
rho0: torch.Tensor,
control_sequence: torch.Tensor,
T: float
) -> torch.Tensor:
"""
Forward pass for nn.Module compatibility.
Args:
rho0: Initial state (d x d)
control_sequence: Controls (n_segments, n_controls)
T: Evolution time
Returns:
rho_final: Final state (d x d)
"""
rho_final, _ = self.evolve(rho0, control_sequence, T, return_trajectory=False)
return rho_final
def numpy_to_torch_complex(array, device='cpu'):
"""
Convert NumPy complex array to PyTorch complex tensor.
Args:
array: NumPy array (complex)
device: Target device
Returns:
tensor: PyTorch complex tensor
"""
import numpy as np
if np.iscomplexobj(array):
real_part = torch.from_numpy(array.real).float()
imag_part = torch.from_numpy(array.imag).float()
tensor = torch.complex(real_part, imag_part)
else:
tensor = torch.from_numpy(array).float()
tensor = tensor.to(torch.complex64)
return tensor.to(device)
def torch_to_numpy_complex(tensor):
"""
Convert PyTorch complex tensor to NumPy complex array.
Args:
tensor: PyTorch complex tensor
Returns:
array: NumPy complex array
"""
import numpy as np
tensor_cpu = tensor.cpu()
real_part = tensor_cpu.real.numpy()
imag_part = tensor_cpu.imag.numpy()
return real_part + 1j * imag_part

Xet Storage Details

Size:
8.33 kB
·
Xet hash:
d108ad0c61f191756c0e27ac8395bf45929aa979901c2377a3ea30eb566cf8ac

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.