LennartNaeve_code/clean_diag/backend/boson_helpers.py
2025-04-25 20:52:11 +02:00

424 lines
14 KiB
Python

import numpy as np
import pickle
from scipy import constants as const
from scipy.optimize import root_scalar
from scipy.fft import fftn, ifftn, fftfreq
from trap_numerics import functional_hamiltonian
def import_results(line_number):
"""
Imports all stored results and variables from the calculation
in line "line_number" of data/overview.txt
out:
trap (object): trap object used for diagonalisation
func_ham (LinearOperator): hamiltonian used for diagonalisation
"""
with open("data/overview.txt", "r") as file:
for current_line_number, line in enumerate(file, start=1):
if current_line_number == line_number:
timecode = line[:19] # Get the first 19 characters
break # Stop reading once we get the desired line
data = np.load(f"data/{timecode}_results.npz",allow_pickle=True)
results = {key: data[key] for key in data.keys()}
with open(f"data/{timecode}_trap.npz", 'rb') as file:
trap = pickle.load(file)
func_ham = functional_hamiltonian(results["dvol"],results["pot"],
float(trap.subs(trap.m)))
return trap, func_ham, results
def get_localised_GS(state0, state1,
degenerate=True):
"""
Returns the localised ground states of left and right tweezer.
in: state0, state1 (3darray): first two states from diagonalisation
out: GS_left, GS_right (3d array): ground states of both tweezer
"""
size = state0.shape
#if states are not degenerate, they don't have to be rotated into each other
if not degenerate:
#then transform to localised wavefunctions
GS_LR1 = state0.real / np.sum(np.abs(state0))**2
GS_LR2 = state1.real / np.sum(np.abs(state1))**2
else:
#rotate resulting wavefunctions into symmetric and anti-symm. states
def f(alpha):
state_S1 = np.cos(alpha) * state0.real + np.sin(alpha)* state1.real
state_S1 /= np.sqrt(np.sum(np.abs(state_S1)**2))
state_A1 = np.sin(alpha) * state0.real - np.cos(alpha)* state1.real
state_A1 /= np.sqrt(np.sum(np.abs(state_A1)**2))
return np.sum(state_S1[int(size[0]/2):]) - np.sum(state_S1[:int(size[0]/2+1)])
#find optimal angle
# alpha = minimize_scalar(f,bounds=(-2*np.pi, 2*np.pi+0.1)).x
alpha = root_scalar(f, x0=0.0001).root
state_S1 = np.cos(alpha) * state0.real + np.sin(alpha)* state1.real
state_S1 /= np.sqrt(np.sum(np.abs(state_S1)**2))
state_A1 = np.sin(alpha) * state0.real - np.cos(alpha)* state1.real
state_A1 /= np.sqrt(np.sum(np.abs(state_A1)**2))
#then transform to localised wavefunctions
GS_LR1 = (state_S1 + state_A1)/np.sqrt(2)
GS_LR2 = (state_S1 - state_A1)/np.sqrt(2)
#asign left and right
if abs(np.sum(GS_LR1[int(size[0]/2):])) < abs(np.sum(GS_LR1[:int(size[0]/2)])):
GS_left = GS_LR1
GS_right = GS_LR2
else:
GS_left = GS_LR2
GS_right = GS_LR1
#state vectors are already real valued
#Choose phase such that they are also both of same sign
sign_left = np.sign(np.mean(np.sign(GS_left)))
sign_right = np.sign(np.mean(np.sign(GS_right)))
GS_left *= sign_left
GS_right *= sign_right
return GS_left, GS_right
def get_J(GS_left,GS_right, func_hamiltonian, dvol):
"""
Returns the hopping amplitude between GS_left and GS_right.
in: GS_left,GS_right (3d arrays): ground states of both tweezer
func_hamiltonian (scipy.sparse.linalg.LinearOperator): Hamiltonian of the used potential
dvol ((3,) array): grid spacing in all 3 directions
out: J (float): hopping amplitude in SI units
"""
#calculate volume element
dV = np.prod(dvol)
#make sure we're working with state vector and not wave function
GS_left /= np.sum(np.abs(GS_left)**2)
GS_right /= np.sum(np.abs(GS_right)**2)
#find shape of grid
size = GS_right.shape
#call Hamiltonian on one side
H_psi = np.reshape(func_hamiltonian(GS_right.flatten()), size)
#calculate integral
J = -(np.sum(GS_left.conj()/np.sqrt(dV)* H_psi/np.sqrt(dV))*dV)
#check imaginary part of result (should vanish)
if abs(J.real/J.imag) < 10:
raise Exception(f"imaginary part of J too big: J = {J}")
else:
J = J.real
return J
def get_U_s(trap, state, dvol):
"""
Returns the contact interaction energy for a tweezer state.
in: trap (object): trap object for parameters
state (3d array): ground state of one tweezer
dvol ((3,) array): grid spacing in all 3 directions
out: U_s (float): contact interaction energy in SI units
"""
#get paramters
a_s = float(trap.subs(trap.a_s))
mass = float(trap.subs(trap.m))
#calculate volume element
dV = np.prod(dvol)
#make sure we're working with state vector
state /= np.sum(np.abs(state)**2)
#calculate integral over psi^4 (divide by sqrt(dV) to convert to wavefunction)
U_s = np.sum(np.abs(state/np.sqrt(dV))**4)*dV
U_s *= 4*np.pi*const.hbar**2*a_s/mass
return U_s
def get_U_dd(trap, state, dvol):
"""
Returns the dipole-dipole interaction energy for a tweezer state.
in: trap (object): trap object for parameters
state (3d array): ground state of one tweezer
dvol ((3,) array): grid spacing in all 3 directions
out: U_dd (float): dipole-dipole interaction energy in SI units
"""
#get paramters
mu = float(trap.subs(trap.mu_b))
#get and normalise polarisation
B_x = float(trap.subs(trap.B_x))
B_y = float(trap.subs(trap.B_y))
B_z = float(trap.subs(trap.B_z))
polarisation = np.array([B_x, B_y, B_z])
polarisation /= np.sum(np.abs(polarisation)**2)
#calculate volume element
dV = np.prod(dvol)
#compute dipolar interactions using fourier transform
#density
rho = np.abs(state/np.sqrt(dV))**2
# Compute Fourier transform of density
rho_k = fftn(rho)
#find shape of grid
size = state.shape
#set up fourier space
kx = 2*np.pi*fftfreq(size[0], dvol[0])
ky = 2*np.pi*fftfreq(size[1], dvol[1])
kz = 2*np.pi*fftfreq(size[2], dvol[2])
KX, KY, KZ = np.meshgrid(kx, ky, kz, indexing='ij')
K2 = KX**2 + KY**2 + KZ**2
K2[K2 == 0] = np.inf # Avoid division by zero
# Compute dipolar interaction kernel in Fourier space
V_k = (-1 + 3* (polarisation[0]*KX + polarisation[1]*KY + polarisation[2]*KZ)**2 / K2) /3
# Compute convolution in Fourier space
U_dd_k = V_k * rho_k
# Transform back to real space
U_dd_real = ifftn(U_dd_k)
# Integrate
U_dd = np.sum(U_dd_real*rho) *dV
#multiply by prefactor C_dd (4pi already in kernel)
U_dd *= const.mu_0* mu**2
#check imaginary part of result (should vanish)
if abs(U_dd.real/U_dd.imag) < 10:
raise Exception(f"imaginary part of U_dd too big: U_dd = {U_dd}")
else:
U_dd = U_dd.real
return U_dd
def get_U(trap, state, dvol):
"""
Returns the total interaction energy for a tweezer state.
in: trap (object): trap object for parameters
state (3d array): ground state of one tweezer
dvol ((3,) array): grid spacing in all 3 directions
out: U_dd (float): contact interaction energy in SI units
"""
U_s = get_U_s(trap, state, dvol)
U_dd = get_U_dd(trap, state, dvol)
return U_s + U_dd
def get_NNI(trap, GS_left, GS_right, dvol):
"""
Returns the dipole-dipole nearest-neighbour interaction energy of the GSs.
in: trap (object): trap object for parameters
GS_left,GS_right (3d array): ground states of both tweezers
dvol ((3,) array): grid spacing in all 3 directions
out: DeltaJ_lr (float): NNI energy in SI units
"""
#get paramters
mu = float(trap.subs(trap.mu_b))
#get and normalise polarisation
B_x = float(trap.subs(trap.B_x))
B_y = float(trap.subs(trap.B_y))
B_z = float(trap.subs(trap.B_z))
polarisation = np.array([B_x, B_y, B_z])
polarisation /= np.sum(np.abs(polarisation)**2)
#calculate volume element
dV = np.prod(dvol)
#make sure we're working with state vector
GS_left /= np.sum(np.abs(GS_left)**2)
GS_right /= np.sum(np.abs(GS_right)**2)
#compute dipolar interactions using fourier transform
#density
rho_r = np.abs(GS_right/np.sqrt(dV))**2
rho_l = np.abs(GS_left/np.sqrt(dV))**2
# Compute Fourier transform of density
rho_k = fftn(rho_l)
#find shape of grid
size = GS_left.shape
#set up fourier space
kx = 2*np.pi*fftfreq(size[0], dvol[0])
ky = 2*np.pi*fftfreq(size[1], dvol[1])
kz = 2*np.pi*fftfreq(size[2], dvol[2])
KX, KY, KZ = np.meshgrid(kx, ky, kz, indexing='ij')
K2 = KX**2 + KY**2 + KZ**2
K2[K2 == 0] = np.inf # Avoid division by zero
# Compute dipolar interaction kernel in Fourier space
V_k = (-1 + 3* (polarisation[0]*KX + polarisation[1]*KY + polarisation[2]*KZ)**2 / K2) /3
# Compute convolution in Fourier space
V_l_k = V_k * rho_k
# Transform back to real space
V_l_real = ifftn(V_l_k)
# Integrate
V_lr = (np.sum(V_l_real*rho_r) * dV)
#multiply by prefactor C_dd (4pi already in kernel)
V_lr *= const.mu_0*mu**2
#check imaginary part of result (should vanish)
if abs(V_lr.real/V_lr.imag) < 10:
raise Exception(f"imaginary part of V_lr too big: V_lr = {V_lr}")
else:
V_lr = V_lr.real
return V_lr
def get_DIT_s(trap, GS_left, GS_right, dvol):
"""
Returns the density-induced tunneling from contact interactions between
the two tweezer groundstates.
in: trap (object): trap object for parameters
GS_left,GS_right (3d array): ground states of both tweezers
dvol ((3,) array): grid spacing in all 3 directions
out: DeltaJ_s (float): DIT energy in SI units
"""
#get paramters
a_s = float(trap.subs(trap.a_s))
mass = float(trap.subs(trap.m))
#calculate volume element
dV = np.prod(dvol)
#make sure we're working with state vector
GS_left /= np.sum(np.abs(GS_left)**2)
GS_right /= np.sum(np.abs(GS_right)**2)
#calculate integral (divide by sqrt(dV) to convert to wavefunction)
DeltaJ_s = np.sum(np.abs(GS_left)**2 *GS_left.conj() *GS_right /dV**2)*dV
DeltaJ_s *= -4*np.pi*const.hbar**2*a_s/mass
return DeltaJ_s
def get_DIT_dd(trap, GS_left, GS_right, dvol):
"""
Returns the density-induced tunneling due to dipole-dipole interactions
between the two GSs.
in: trap (object): trap object for parameters
GS_left,GS_right (3d array): ground states of both tweezers
dvol ((3,) array): grid spacing in all 3 directions
out: DeltaJ_lr (float): DIT energy in SI units
"""
#get paramters
mu = float(trap.subs(trap.mu_b))
#get and normalise polarisation
B_x = float(trap.subs(trap.B_x))
B_y = float(trap.subs(trap.B_y))
B_z = float(trap.subs(trap.B_z))
polarisation = np.array([B_x, B_y, B_z])
polarisation /= np.sum(np.abs(polarisation)**2)
#calculate volume element
dV = np.prod(dvol)
#make sure we're working with state vector
GS_left /= np.sum(np.abs(GS_left)**2)
GS_right /= np.sum(np.abs(GS_right)**2)
#compute dipolar interactions using fourier transform
#density
rho_l = np.abs(GS_left/np.sqrt(dV))**2
# Compute Fourier transform of density
rho_k = fftn(rho_l)
#find shape of grid
size = GS_left.shape
#set up fourier space
kx = 2*np.pi*fftfreq(size[0], dvol[0])
ky = 2*np.pi*fftfreq(size[1], dvol[1])
kz = 2*np.pi*fftfreq(size[2], dvol[2])
KX, KY, KZ = np.meshgrid(kx, ky, kz, indexing='ij')
K2 = KX**2 + KY**2 + KZ**2
K2[K2 == 0] = np.inf # Avoid division by zero
# Compute dipolar interaction kernel in Fourier space
V_k = (-1 + 3* (polarisation[0]*KX + polarisation[1]*KY + polarisation[2]*KZ)**2 / K2) /3
# Compute convolution in Fourier space
DeltaJ_l_k = V_k * rho_k
# Transform back to real space
DeltaJ_l_real = ifftn(DeltaJ_l_k)
# Integrate
DeltaJ_lr = (np.sum(DeltaJ_l_real* GS_left.conj()*GS_right /dV) * dV)
#multiply by prefactor C_dd (4pi already in kernel)
DeltaJ_lr *= -const.mu_0*mu**2
#check imaginary part of result (should vanish)
if abs(DeltaJ_lr.real/DeltaJ_lr.imag) < 10:
raise Exception(f"imaginary part of V_lr too big: V_lr = {DeltaJ_lr}")
else:
DeltaJ_lr = DeltaJ_lr.real
return DeltaJ_lr
def get_DIT(trap, GS_left, GS_right, dvol):
"""
Returns the total DIT term for two groundstates.
in: trap (object): trap object for parameters
GS_left,GS_right (3d array): ground states of both tweezers
dvol ((3,) array): grid spacing in all 3 directions
out: DeltaJ (float): DIT energy in SI units
"""
DeltaJ_s = get_DIT_s(trap, GS_left, GS_right, dvol)
DeltaJ_dd = get_DIT_dd(trap, GS_left, GS_right, dvol)
return DeltaJ_s + DeltaJ_dd
def analyse_diagonalisation(line_number,
n_angles=50,
state_nr0=0, state_nr1=1,
degenerate=True):
"""
For a given line number in overview.txt,
returns J, U_s, U_dd of the two groundstates of both tweezers
out:
J, U_s (float)
U_dds, angles ((n_angles,) arrays): arrays of angles(rad) of B-field and cor. U_dd
"""
#import results
trap, ham, res = import_results(line_number)
#find groundstates
GS_left, GS_right = get_localised_GS(res["states"][state_nr0]
,res["states"][state_nr1],
degenerate=degenerate)
#calculate hubbard parameters
J = get_J(GS_left, GS_right, ham, res["dvol"])
U_s = get_U_s(trap, GS_left, res["dvol"])
angles= np.deg2rad(np.linspace(0,90))
U_dds = np.zeros_like(angles)
V_lrs = np.zeros_like(angles)
DeltaJs = np.zeros_like(angles)
polarisations = np.zeros((len(angles),3))
polarisations[:,0] = np.sin(angles)
polarisations[:,2] = np.cos(angles)
for i, pol in enumerate(polarisations):
trap[trap.B_x] = pol[0]
trap[trap.B_y] = pol[1]
trap[trap.B_z] = pol[2]
U_dds[i] = get_U_dd(trap, GS_left, res["dvol"])
V_lrs[i] = get_NNI(trap, GS_left, GS_right, res["dvol"])
DeltaJs[i] = get_DIT_dd(trap, GS_left, GS_right, res["dvol"])
return J, U_s, U_dds, angles, V_lrs, DeltaJs