first commit

This commit is contained in:
Lennart Naeve 2025-04-25 20:52:11 +02:00
parent ceb26c0fa0
commit c1254d4dd5
106 changed files with 61827 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/merging_tweezer_code/bosons/data

View File

@ -0,0 +1,424 @@
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

View File

@ -0,0 +1,549 @@
import matplotlib.pyplot as plt
import numpy as np
import sympy as sp
from scipy import constants as const
from scipy.integrate import quad
from scipy.optimize import root_scalar
from tqdm import tqdm
import trap_units as si
def plot_solutions(trap,n_levels,left_cutoff,right_cutoff,n_pot_steps=1000,display_plot=-1,state_mult=1e4,plot=True,ret_num=False):
"""Plot the potential and solutions for a given trap object
(diplay_plot=-1 for all, 0,...,n for individual ones and -2 for none)
"""
x, y, z = trap.x, trap.y, trap.z
axial_width = trap.get_tweezer_rayleigh()
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(axial_width)),
fprime=pot_diff2_ax_numpy,
xtol=1e-18,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
if plot:
z_np = np.linspace(left_cutoff, right_cutoff, num=n_pot_steps)
ax: plt.Axes
fig, ax = plt.subplots(figsize=(2.5, 2.5))
# ax.set_title("Axial")
abs_min = np.min(potential(z_np))
ax.fill_between(
z_np / si.um,
potential(z_np) / const.h / si.kHz,
abs_min / const.h / si.kHz,
alpha=0.5,
)
count = 0
for i, bound in enumerate(true_bound_states):
if not bound:
continue
energy = energies[i]
state = states[i]
ax.plot(
z_np / si.um,
np.where(
(energy > potential(z_np)) & (z_np < barrier),
energy / const.h / si.kHz,
np.nan,
),
c="k",
lw=0.5,
marker="None",
)
if display_plot == -1 or display_plot == count:
ax.plot(z_np/si.um, state**2 *state_mult, marker="None", c="k")
count += 1
ax.plot(z_np / si.um, potential(z_np) / const.h / si.kHz, marker="None")
ax.vlines(barrier/ si.um,np.min(potential(z_np) / const.h/ si.kHz),np.max(potential(z_np) / const.h/ si.kHz))
ax.vlines(minimum/ si.um,np.min(potential(z_np) / const.h/ si.kHz),np.max(potential(z_np) / const.h/ si.kHz))
ax.set_title(f"{np.sum(true_bound_states)} bound solutions, power: {trap.subs(trap.power_tweezer)/si.mW}mW")
ax.set_xlabel(r"z ($\mathrm{\mu m}$)")
ax.set_ylabel(r"E / h (kHz)")
if ret_num:
return np.sum(true_bound_states)
def solutions_power(trap,power,n_levels,left_cutoff,right_cutoff):
trap[trap.power_tweezer] = power
return plot_solutions(trap,n_levels,left_cutoff,right_cutoff,plot=False, ret_num=True)
def root_power(trap,num_atoms,bracket,n_levels,left_cutoff,right_cutoff):
f = lambda x: solutions_power(trap,x,n_levels,left_cutoff,right_cutoff) - num_atoms
return root_scalar(f,bracket=bracket).root
def sweep_power(trap, gradient,n_levels,left_cutoff,right_cutoff,t_spill=25*si.ms,n_pot_steps=1000,max_atoms=10,n_plotpoints=100):
"""
Sweeps over power and gets the power for 0 atom boundary and the precision level
"""
x, y, z = trap.x, trap.y, trap.z
zr = trap.get_tweezer_rayleigh()
trap[trap.grad_z] = gradient
initial_power = float(4/3/np.sqrt(3)*trap.subs(zr) * np.pi* trap.subs(trap.m * trap.g + trap.mu_b * trap.grad_z) * trap.subs(trap.waist_tweezer**2/trap.a))
final_power = root_power(trap,max_atoms,[initial_power,initial_power*5],n_levels,left_cutoff,right_cutoff)
powers = np.linspace(initial_power,final_power,n_plotpoints)
atom_number = np.zeros_like(powers,dtype=float)
for i, power in enumerate(tqdm(powers)):
trap[trap.power_tweezer] = power
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# Determine the potential and its derivatives
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(zr)),
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
if t_spill == 0:
atom_num = np.sum(true_bound_states)
atom_number = np.append(atom_number,atom_num)
continue
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(zr))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
tunneling_rate = (
transmission_probability * np.abs(energies - potential(minimum)) / const.h
)
atom_number[i] = np.sum(np.exp(-t_spill * tunneling_rate[true_bound_states]))
return powers, atom_number
def sweep_power_old(trap, gradient,dp,n_levels,left_cutoff,right_cutoff,t_spill=25*si.ms,max_spill_steps=200,n_pot_steps=1000,max_atoms=10):
"""
Sweeps over power and gets the power for 0 atom boundary and the precision level
"""
x, y, z = trap.x, trap.y, trap.z
zr = trap.get_tweezer_rayleigh()
trap[trap.grad_z] = gradient
initial_power = 4/3/np.sqrt(3)*trap.subs(zr) * np.pi* trap.subs(trap.m * trap.g + trap.mu_b * trap.grad_z) * trap.subs(trap.waist_tweezer**2/trap.a)
trap[trap.power_tweezer] = initial_power
powers = np.array([initial_power],dtype=float)
atom_number = np.array([0.0],dtype=float)
i = 0
pbar = tqdm(disable=True)
while atom_number[i] <max_atoms and i<max_spill_steps:
#print(i)
trap[trap.power_tweezer] = initial_power + (i+1)*dp
powers = np.append(powers, initial_power + (i+1)*dp)
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# Determine the potential and its derivatives
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(zr)),
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
if t_spill == 0:
atom_num = np.sum(true_bound_states)
atom_number = np.append(atom_number,atom_num)
continue
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(zr))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
tunneling_rate = (
transmission_probability * np.abs(energies - potential(minimum)) / const.h
)
atom_num = np.sum(np.exp(-t_spill * tunneling_rate[true_bound_states]))
atom_number = np.append(atom_number,atom_num)
i += 1
pbar.update(1)
pbar.close()
if i == max_spill_steps:
print(f"{max_atoms} atoms not reached")
return powers,atom_number
raise Exception(f"{max_atoms} atoms not reached")
return powers, atom_number
def solutions_gradient(trap,gradient,n_levels,left_cutoff,right_cutoff):
trap[trap.grad_z] = gradient
return plot_solutions(trap,n_levels,left_cutoff,right_cutoff,plot=False, ret_num=True)
def root_gradient(trap,num_atoms,bracket,n_levels,left_cutoff,right_cutoff):
f = lambda x: solutions_gradient(trap,x,n_levels,left_cutoff,right_cutoff) - num_atoms
return root_scalar(f,bracket=bracket).root
def sweep_gradient(trap, power,n_levels,left_cutoff,right_cutoff,t_spill=25*si.ms,n_pot_steps=1000,max_atoms=10,n_plotpoints=100):
"""
Sweeps over gradient and gets the gradient for 0 atom boundary and the precision level
"""
x, y, z = trap.x, trap.y, trap.z
zr = trap.get_tweezer_rayleigh()
trap[trap.power_tweezer] = power
initial_grad = float(trap.subs(1/trap.mu_b * (3*np.sqrt(3)/4 * power * trap.a/np.pi/trap.waist_tweezer**2/zr - trap.m * trap.g)))
final_grad = root_gradient(trap,max_atoms,[-2.89*si.G/si.cm,initial_grad],n_levels,left_cutoff,right_cutoff)
gradients = np.linspace(initial_grad,final_grad,n_plotpoints,dtype=float)
atom_number = np.zeros_like(gradients,dtype=float)
for i, gradient in enumerate(tqdm(gradients)):
#print(i)
trap[trap.grad_z] = gradient
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# Determine the potential and its derivatives
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(zr)),
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
if t_spill == 0:
atom_num = np.sum(true_bound_states)
atom_number = np.append(atom_number,atom_num)
continue
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(zr))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
tunneling_rate = (
transmission_probability * np.abs(energies - potential(minimum)) / const.h
)
atom_number[i] = np.sum(np.exp(-t_spill * tunneling_rate[true_bound_states]))
return gradients, atom_number
def sweep_gradient_old(trap, power,dgrad,n_levels,left_cutoff,right_cutoff,t_spill=25*si.ms,max_spill_steps=200,n_pot_steps=1000,max_atoms=10):
"""
Sweeps over gradient and gets the gradient for 0 atom boundary and the precision level
"""
x, y, z = trap.x, trap.y, trap.z
zr = trap.get_tweezer_rayleigh()
trap[trap.power_tweezer] = power
initial_grad = float(trap.subs(1/trap.mu_b * (3*np.sqrt(3)/4 * power * trap.a/np.pi/trap.waist_tweezer**2/zr - trap.m * trap.g)))
trap[trap.grad_z] = initial_grad
gradients = np.array([initial_grad],dtype=float)
atom_number = np.array([0.0],dtype=float)
i = 0
pbar = tqdm(disable=True)
while atom_number[i] <max_atoms and i<max_spill_steps:
#print(i)
trap[trap.grad_z] = initial_grad - (i+1)*dgrad
gradients = np.append(gradients, initial_grad - (i+1)*dgrad)
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# Determine the potential and its derivatives
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(zr)),
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
if t_spill == 0:
atom_num = np.sum(true_bound_states)
atom_number = np.append(atom_number,atom_num)
continue
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(zr))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
tunneling_rate = (
transmission_probability * np.abs(energies - potential(minimum)) / const.h
)
atom_num = np.sum(np.exp(-t_spill * tunneling_rate[true_bound_states]))
atom_number = np.append(atom_number,atom_num)
i += 1
pbar.update(1)
pbar.close()
if i == max_spill_steps:
print(f"{max_atoms} atoms not reached")
return gradients,atom_number
raise Exception(f"{max_atoms} atoms not reached")
return gradients, atom_number
def calculate_stepsize(powers, atom_number,plot=False,max_atoms=10,cutoff=0.1):
"""
Given an array of powers (or gradients) and atom_numbers, returns the average length of steps.
The step length is the area where atom number is within cutoff percent of an integer.
"""
bool_array = np.logical_and(np.abs(atom_number - np.round(atom_number,0)) < cutoff, atom_number > cutoff) #find points where atomnumber is close to integer
bool_array = np.logical_and(bool_array, atom_number < (max_atoms-0.5))
if plot:
plt.plot(powers,atom_number)
plt.plot(powers[bool_array],atom_number[bool_array],"b.")
diff = np.diff(bool_array.astype(int)) # Convert to int to use np.diff
start_indices = np.where(diff == 1)[0] + 1 # Indices where blobs start
end_indices = np.where(diff == -1)[0] + 1 # Indices where blobs end
# Special case: handle if the array starts or ends with a True
if bool_array[0]:
start_indices = np.insert(start_indices, 0, 0) # Add the start of the array
if bool_array[-1]:
end_indices = np.append(end_indices, len(bool_array)) # Add the end of the array
#step length
step_len = np.abs(np.mean(np.diff(powers)))
# Blob lengths
blob_lengths = float(np.mean((end_indices - start_indices)*step_len))
# Results
return blob_lengths

View File

@ -0,0 +1,181 @@
import numpy as np
import numpy.typing as npt
from numpy.fft import fft, ifft, fftfreq
from scipy import constants as const
from scipy import sparse
from scipy import linalg
def discrete_hamiltonian(
dx: tuple[float, ...] | float, potential: npt.NDArray, mass: float
) -> npt.NDArray:
"""Derive the hamiltonian for an arbitrary potential using the
discretized laplace operator. The function is implemented for 1 to
3 dimensions.
:param dx: Step size used for the coordinates. If the potential is
one-dimensional, this can simply be the step size of the
coordinate used in :param:`potential`.
For larger dimensions, this parameter must be a tuple with the
same size as the dimension.
:param potential: A (multidimensional) potential.
:param mass: The mass of the particle, used for the kinetic term.
"""
ndim = potential.ndim
if np.shape(dx) != (ndim,):
if ndim != 1 or np.shape(dx) != ():
raise ValueError(f"Expected list of {ndim} values for 'dx'")
else:
dx = np.array([dx])
grid_points = potential.shape
laplace_d2_1 = sparse.diags(
[1, -2, 1], [-1, 0, 1], shape=(grid_points[0], grid_points[0])
) / (dx[0] ** 2)
if ndim == 1:
laplace = laplace_d2_1
elif ndim >= 2:
laplace_d2_2 = sparse.diags(
[1, -2, 1], [-1, 0, 1], shape=(grid_points[1], grid_points[1])
) / (dx[1] ** 2)
if ndim == 2:
# laplace = sparse.kron(laplace_d2_1, eye_2) + sparse.kron(
# eye_1, laplace_d2_2
# )
laplace = sparse.kronsum(laplace_d2_1, laplace_d2_2)
if ndim == 3:
laplace_d2_3 = sparse.diags(
[1, -2, 1], [-1, 0, 1], shape=(grid_points[2], grid_points[2])
) / (dx[2] ** 2)
laplace = sparse.kronsum(
sparse.kronsum(laplace_d2_1, laplace_d2_2), laplace_d2_3
)
else:
raise NotImplementedError(f"Number of dimensions not implemented: '{ndim}'")
t = -(const.hbar**2) / (2 * mass) * laplace
v = sparse.diags([potential.flatten()], [0])
return t + v
def functional_hamiltonian(
dx: tuple[float, ...] | float, potential: npt.NDArray, mass: float
) -> npt.NDArray:
"""Returns a scipy.sparse.linalg LinearOperator for the hamiltonian of
an arbitrary potential using the discretized laplace operator.
The function is implemented for 1 to 3 dimensions.
:param dx: Step size used for the coordinates. If the potential is
one-dimensional, this can simply be the step size of the
coordinate used in :param:`potential`.
For larger dimensions, this parameter must be a tuple with the
same size as the dimension.
:param potential: A (multidimensional) potential.
:param mass: The mass of the particle, used for the kinetic term.
"""
ndim = potential.ndim
if np.shape(dx) != (ndim,):
if ndim != 1 or np.shape(dx) != ():
raise ValueError(f"Expected list of {ndim} values for 'dx'")
else:
dx = np.array([dx])
grid_points = potential.shape
if ndim != 3:
raise Exception(f"Number of dimensions ({ndim}) is not implemented.")
kx = 2*np.pi*fftfreq(grid_points[0], dx[0])
ky = 2*np.pi*fftfreq(grid_points[1], dx[1])
kz = 2*np.pi*fftfreq(grid_points[2], dx[2])
KX, KY, KZ = np.meshgrid(kx, ky, kz, indexing='ij')
def mv(psi):
"""Function for action of hamiltonian on wavefunction"""
psi_nonflat = psi.reshape(grid_points)
#calculate the laplacian with a fourier transform
laplace_psi = (ifft(-KX**2*fft(psi_nonflat, axis = 0), axis = 0) +
ifft(-KY**2*fft(psi_nonflat, axis = 1), axis = 1) +
ifft(-KZ**2*fft(psi_nonflat, axis = 2), axis = 2))
v = sparse.diags([potential.flatten()], [0])
return -(const.hbar**2) / (2 * mass) *laplace_psi.flatten() + v*psi
len_psi = np.prod(grid_points)
return sparse.linalg.LinearOperator((len_psi,len_psi), matvec=mv)
def nstationary_solution(
dx: tuple[float, ...] | float, potential: npt.NDArray, mass: float, k: int = 1,
method="sparse") -> tuple[npt.NDArray, npt.NDArray]:
"""Derive the stationary solution for a discrete potential of 1 to
3 dimensions numerically.
:param dx: Step size used for the coordinates. If the potential is
one-dimensional, this can simply be the step size of the
coordinate used in :param:`potential`.
For larger dimensions, this parameter must be a tuple with the
same size as the dimension.
:param potential: A (multidimensional) potential.
:param mass: The mass of the particle, used for the kinetic term.
:param k: Number of eigenvalues and eigenstates to return.
:returns: Tuple of eigenvalues and eigenvectors. Note that the
eigenvectors are transposed so that they are of shape
``(k, np.size(potential))``
"""
e_min = np.min(potential)
if method == "matrix_free":
eigenvals, eigenvects = sparse.linalg.eigsh(
functional_hamiltonian(dx, potential, mass), k=k, which="LM", sigma=e_min)
elif method == "sparse":
eigenvals, eigenvects = sparse.linalg.eigsh(
discrete_hamiltonian(dx, potential, mass), k=k, which="LM", sigma=e_min)
else:
raise ValueError(f'Method "{method}" does not exist.')
return eigenvals, eigenvects.T
def nstationary_solution_export(path,
dx: tuple[float, ...] | float, potential: npt.NDArray, mass: float, k: int = 1
) -> tuple[npt.NDArray, npt.NDArray]:
"""Derive the stationary solution for a discrete potential of 1 to
3 dimensions numerically.
:param dx: Step size used for the coordinates. If the potential is
one-dimensional, this can simply be the step size of the
coordinate used in :param:`potential`.
For larger dimensions, this parameter must be a tuple with the
same size as the dimension.
:param potential: A (multidimensional) potential.
:param mass: The mass of the particle, used for the kinetic term.
:param k: Number of eigenvalues and eigenstates to return.
:returns: Tuple of eigenvalues and eigenvectors. Note that the
eigenvectors are transposed so that they are of shape
``(k, np.size(potential))``
"""
e_min = np.min(potential)
sparse.save_npz(path + r"\hamiltonian.npz",discrete_hamiltonian(dx, potential, mass))
return e_min
def nstationary_solution_mod(
dx: tuple[float, ...] | float, potential: npt.NDArray, mass: float, k: int = 1
) -> tuple[npt.NDArray, npt.NDArray]:
"""
Modified function to only compute solutions in the local minimum.
Derive the stationary solution for a discrete potential of 1 to
3 dimensions numerically.
:param dx: Step size used for the coordinates. If the potential is
one-dimensional, this can simply be the step size of the
coordinate used in :param:`potential`.
For larger dimensions, this parameter must be a tuple with the
same size as the dimension.
:param potential: A (multidimensional) potential.
:param mass: The mass of the particle, used for the kinetic term.
:param k: Number of eigenvalues and eigenstates to return.
:returns: Tuple of eigenvalues and eigenvectors. Note that the
eigenvectors are transposed so that they are of shape
``(k, np.size(potential))``
"""
e_min = np.min(potential)
eigenvals, eigenvects = linalg.eigh(
discrete_hamiltonian(dx, potential, mass),subset_by_index=(0,k-1),subset_by_value=(e_min,np.inf)
)
return eigenvals, eigenvects.T

View File

@ -0,0 +1,76 @@
from numbers import Number
import sympy as sp
x, y, z = sp.symbols("x, y, z", real=True, finite=True)
#: Beam waist in x-direction (collimated).
waist_x0 = sp.Symbol("W_{x0}", real=True, positive=True, finite=True, nonzero=True)
#: Beam waist in z-direction at the focus.
waist_z0 = sp.Symbol("W_{z0}", real=True, positive=True, finite=True, nonzero=True)
#: Offset from waist at origin
#: Power per beam. Total power is 2x.
power = sp.Symbol("P", real=True, positive=True, finite=True, nonzero=True)
#: Magnetic field gradients.
grad_r, grad_z = sp.symbols(
"\\nabla{B_x}, \\nabla{B_z}", real=True, finite=True, nonzero=True
)
#: Real part of polarizability :math:`\alpha`
a = sp.Symbol("a", real=True, finite=True, nonzero=True)
d = sp.Symbol("d", real=True, positive=True, finite=True, nonzero=True)
mu_b = sp.Symbol("mu_b", real=True, positive=True, finite=True, nonzero=True)
#: Wavelength
wvl = sp.Symbol("lambda", real=True, positive=True, finite=True, nonzero=True)
#: Angle of the incident beams
theta = sp.Symbol("theta", real=True, positive=True, finite=True, nonzero=True)
def BeamWaistZ(length: sp.Symbol | Number) -> sp.Expr: # noqa: N802
"""The beam is focused in z-direction, leading to a waist that
depends on the distance :math:`l` from the lens.
Depending on which beam (upper or lower), the distance from the
lens :math:`l` is given by
:math:`y \\cos(\\theta) \\mp z \\sin(\\theta)`
:param length: Distance :math:`l` from the lens.
"""
return waist_z0 * sp.sqrt(1 + (wvl * length / (sp.pi * waist_z0**2)) ** 2)
def UpperBeamIntensity() -> sp.Expr: # noqa: N802
"""Intensity distribution of the upper beam"""
length = y * sp.cos(theta) - z * sp.sin(theta)
waist_z = BeamWaistZ(length)
# Intensity of the beam at the origin
i_0 = 2 * power / (sp.pi * waist_x0 * waist_z0)
# Turn off automatic formatting to preserve readability
# fmt: off
return i_0 * waist_z0 / waist_z * sp.exp(
- 2 * x**2 / waist_x0**2
- 2 * (z * sp.cos(theta) + y * sp.sin(theta)) ** 2 / waist_z**2
) # fmt: on
def LowerBeamIntensity() -> sp.Expr: # noqa: N802
"""Intensity distribution of the lower beam"""
length = y * sp.cos(theta) + z * sp.sin(theta)
waist_z = BeamWaistZ(length)
# Intensity of the beam at the origin
i_0 = 2 * power / (sp.pi * waist_x0 * waist_z0)
# Turn off automatic formatting to preserve readability
# fmt: off
return i_0 * waist_z0 / waist_z * sp.exp(
- 2 * x**2 / waist_x0**2
- 2 * (z * sp.cos(theta) - y * sp.sin(theta)) ** 2 / waist_z**2
) # fmt: on
def TotalIntensity() -> sp.Expr: # noqa: N802
i_1 = UpperBeamIntensity()
i_2 = LowerBeamIntensity()
return i_1 + i_2 + 2 * sp.sqrt(i_1 * i_2) * sp.cos(2 * sp.pi * z / d)
def TotalPotential() -> sp.Expr: # noqa: N802
return -a * TotalIntensity()

View File

@ -0,0 +1,27 @@
import numpy as np
import trap_units as si
from scipy import constants as const
waist_trap_r = 135 * const.micro # waist of the trap in radial direction
waist_trap_z = 17 * const.micro # waist of the trap in axial direction
waist_rep = 100 * const.micro # waist of the repulsion beam
omega_trap_r = 150 * 2 * np.pi # radial trap frequency
grad_r = 0.33 * si.G / si.cm
grad_z = 50 * si.G / si.cm
wavelength_resonance = 671 * const.nano
wavelength_rep = 650 * const.nano
wavelength_trap = 1064 * const.nano
omega_0 = 2 * np.pi * const.c / wavelength_resonance
omega_rep = 2 * np.pi * const.c / wavelength_rep
omega_trap = 2 * np.pi * const.c / wavelength_trap
# Einstein coefficient
a_21 = gamma = 2 * np.pi * 5.8724 * const.mega
# 2d beam angle
theta = np.deg2rad(7.35)
d = wavelength_trap / (2 * waist_trap_z) * waist_trap_r
mass_lithium6 = 6.0151228 * const.value("atomic mass constant")

View File

@ -0,0 +1,5 @@
import numpy as np
import numpy.typing as npt
# Type that allows both a number or an array
float_or_array = float | npt.NDArray[np.double]

View File

@ -0,0 +1,75 @@
import numpy as np
import scipy.constants as cs
# mass
kg = 1000
g = 1
mg = 0.001
# length
m = 1
dm = 0.1
cm = 0.01
mm = 0.001
um = 10**-6
nm = 10**-9
# time
s = 1
ms = 0.001
us = 10**-6
# frequency
Hz = 1
kHz = 1000
MHz = 10 ** 6
GHz = 10 ** 9
THz = 10 ** 12
Hz_rad = 2 * np.pi
kHz_rad = Hz_rad * 1000
MHz_rad = Hz_rad * 10 ** 6
mm_Hz = cs.c / mm
um_Hz = cs.c / um
nm_Hz = cs.c / nm
# magnetic field
T = 1
G = 10**-4
# power
W = 1
mW = 10**-3
uW = 10**-6
# angle
rad = 1
deg = np.pi / 180
# temperature
K = 1
mK = 10 ** -3
uK = 10 ** -6
nK = 10 ** -9
# energy
J = 1
eV = 1.602 * 10 ** - 19
meV = eV / 1000
keV = eV * 1000
Hz_J = cs.h * Hz
kHz_J = cs.h * kHz
MHz_J = cs.h * MHz
GHz_J = cs.h * GHz
THz_J = cs.h * THz
mm_J = cs.h * mm_Hz
um_J = cs.h * um_Hz
nm_J = cs.h * nm_Hz
K_J = cs.Boltzmann * K
mK_J = K_J / 1000
uK_J = mK_J / 1000
nK_J = uK_J / 1000

View File

@ -0,0 +1,921 @@
import warnings
from collections.abc import Callable
from functools import cache
from itertools import permutations
from numbers import Number
from typing import Any
import datetime
import pickle
import numpy as np
import numpy.typing as npt
import sympy as sp
from scipy import constants as const
from scipy.sparse import save_npz
from scipy.optimize import minimize_scalar
from sympy import LT, default_sort_key, topological_sort
from sympy.core.numbers import Zero
import trap_units as si
from trap_numerics import (nstationary_solution,
nstationary_solution_export,
discrete_hamiltonian,
functional_hamiltonian)
import trap_potential as pot
import trap_properties as p
from trap_typing import float_or_array
Numeric = int | float | complex | np.number | sp.Number
ExprNum = sp.Expr | Numeric
class PancakeTrap:
#: Spacial coordinates
x, y, z = sp.symbols("x, y, z", real=True, finite=True)
#: Offset from the waist location in z
d_0 = sp.symbols("d_0", real=True, finite=True)
#: Beam waist in x-direction (collimated).
waist_x0 = sp.Symbol("W_{x0}", real=True, positive=True, finite=True, nonzero=True)
#: Beam waist in z-direction at the focus.
waist_z0 = sp.Symbol("W_{z0}", real=True, positive=True, finite=True, nonzero=True)
#: Waist of the tweezer (at the focus)
waist_tweezer = sp.Symbol(
"W_t", real=True, positive=True, finite=True, nonzero=True
)
#: Power per beam. Total power is 2x.
power = sp.Symbol("P", real=True, positive=True, finite=True, nonzero=True)
#: Power of the tweezer beam
power_tweezer = sp.Symbol(
"P_t", real=True, positive=True, finite=True, nonzero=True
)
#magnetic offset field
B_x, B_y, B_z = sp.symbols(
"B_x, B_y, B_z", real=True, finite=True
)
#: Magnetic field gradients.
grad_r, grad_z = sp.symbols(
"\\nabla{B_x}, \\nabla{B_z}", real=True, finite=True, nonzero=True
)
#: Real part of polarizability :math:`\alpha`
a = sp.Symbol("a", real=True, finite=True, nonzero=True, positive=True)
#: Pancake spacing
d = sp.Symbol("d", real=True, positive=True, finite=True, nonzero=True)
#: Bohr magneton
mu_b = sp.Symbol("mu_b", real=True, positive=True, finite=True, nonzero=True)
#: Wavelength
wvl = sp.Symbol("lambda", real=True, positive=True, finite=True, nonzero=True)
#: Angle of the incident beams
theta = sp.Symbol("theta", real=True, positive=True, finite=True, nonzero=True)
#: Mass of the trapped atoms
m = sp.Symbol("m", real=True, positive=True, finite=True, nonzero=True)
#: contact-interaction scattering length of atoms
a_s = sp.Symbol("a_s", real=True, positive=True, finite=True, nonzero=True)
#: Trap frequency in each spacial direction
omega_x, omega_y, omega_z = sp.symbols(
"omega_x, omega_y, omega_z", real=True, positive=True, finite=True, nonzero=True
)
#: Trap frequency of the tweezer
omega_r_tweezer, omega_ax_tweezer = sp.symbols(
"omega_t_r, omega_t_ax", real=True, positive=True, finite=True, nonzero=True
)
#: Resonance frequency
omega_0 = sp.Symbol("omega_0", real=True, positive=True, finite=True, nonzero=True)
#: Laser frequency
omega_l = sp.Symbol("omega_l", real=True, positive=True, finite=True, nonzero=True)
#: Decay rate gamma
gamma = sp.Symbol("Gamma", real=True, positive=True, finite=True, nonzero=True)
#: Angle of the magnetic field gradient
beta = sp.Symbol("beta", real=True, positive=True, finite=True)
#: gravitational acceleration (set 0 if not needed)
g = sp.Symbol("g", real=True, positive=True, finite=True)
#: Default substitutions applied using :meth:`.subs`
_defaults: dict[str, Any] = {
#"a": (
# (3 * sp.pi * const.c**2)
# / (2 * omega_0**3)
# * (gamma / (omega_0 - omega_l) + gamma / (omega_0 + omega_l))
#),
"d": wvl / (2 * sp.sin(theta)),
"omega_l": 2 * np.pi * const.c / wvl,
#"omega_0": 2 * np.pi * const.c / (626 * si.nm),
"d_0": 0,
"wvl": 532 * si.nm, #1064 * si.nm,
"theta": 0, #np.deg2rad(7.35),
"beta": 0,
"gamma": 2 * np.pi * 135 * const.kilo, #2 * np.pi * 5.8724 * const.mega,
"grad_r": 0.0066 * grad_z, # see labbook 2023-11-21
"grad_z": 85 * si.G / si.cm,
"B_x": 0*si.G,
"B_y": 0*si.G,
"B_z": 1e-14*si.G, #apply small field to avoid zero
"m": 164 * const.value("atomic mass constant"), #6.0151228 * const.value("atomic mass constant"),
"a_s": 85* const.value("Bohr radius"),
"mu_b": 9.93 * const.value("Bohr magneton" ), #using mu_b as mu for the atom
"g": const.g,
}
def __init__(self, **values: Numeric):
self._values: dict[sp.Symbol, Numeric] = {
self._get_key_symbol(key): value for key, value in self._defaults.items()
}
for key, value in values.items():
self[key] = value
self._omega_solution = {}
def subs(self, expr: sp.Expr, symbolic_only: bool = False) -> sp.Expr:
# Only substitute symbols that have a value of type Expr,
# i.e. they are expressed through other symbols.
symbolic_values: list[tuple[sp.Symbol, sp.Expr | Numeric]] = [
(key, value)
for key, value in self._values.items()
if isinstance(value, sp.Expr) or value == 0
]
# Find all edges where an expression contains the symbol of
# a different subsitution.
edges = [
(i, j)
for i, j in permutations(symbolic_values, 2)
if isinstance(i[1], sp.Expr) and i[1].has(j[0])
]
# Sort the substitutions topologically such that
sorted_symbolic_values = topological_sort(
[symbolic_values, edges], default_sort_key
)
expr_symbolic = expr.subs(sorted_symbolic_values)
if symbolic_only:
return expr_symbolic
else:
# Also substitute all non-symbolic values
return expr_symbolic.subs(
[
(key, value)
for key, value in self._values.items()
if not isinstance(value, sp.Expr)
]
)
def __setitem__(self, key: str | sp.Symbol, value):
self._values[self._get_key_symbol(key)] = value
def __getitem__(self, item: str | sp.Symbol):
return self._values[self._get_key_symbol(item)]
def _get_key_symbol(self, key: str | sp.Symbol) -> sp.Symbol:
if isinstance(key, sp.Symbol):
return key
if not isinstance(key, str):
raise ValueError(
f"Expected type 'str' or 'sympy.Symbol' but got {type(key)!s}"
)
if not hasattr(self, key):
raise ValueError(f"Unknown attribute '{key!s}'")
key_symbol = getattr(self, key)
if not isinstance(getattr(self, key), sp.Symbol):
raise ValueError(f"Attribute '{key!s}' is not a symbol")
return key_symbol
def get_tweezer_rayleigh(self) -> sp.Expr:
return sp.pi * self.waist_tweezer**2 / self.wvl
def get_tweezer_intensity(self) -> sp.Expr:
rayleigh_length = self.get_tweezer_rayleigh()
waist = self.waist_tweezer * sp.sqrt(1 + (self.z / rayleigh_length) ** 2)
i_0 = 2 * self.power_tweezer / (sp.pi * self.waist_tweezer**2)
r = sp.sqrt(self.x**2 + self.y**2)
return i_0 * (self.waist_tweezer / waist) ** 2 * sp.exp(-2 * r**2 / (waist**2))
def get_tweezer_potential(self) -> sp.Expr:
return -self.a * self.get_tweezer_intensity()
def get_omega_tweezer(self, omega: sp.Symbol) -> sp.Expr:
"""Get tweezer trap frequency"""
return self.get_omega(omega, with_tweezer=True, with_2dtrap=False)
def get_omega_r_tweezer(self) -> sp.Expr:
return self.get_omega_tweezer(self.omega_r_tweezer)
def get_omega_ax_tweezer(self) -> sp.Expr:
return self.get_omega_tweezer(self.omega_ax_tweezer)
def get_beam_waist_z(self, length: sp.Symbol | Number) -> sp.Expr:
"""The beam is focused in z-direction, leading to a waist that
depends on the distance :math:`l` from the lens.
Depending on which beam (upper or lower), the distance from the
lens :math:`l` is given by
:math:`y \\cos(\\theta) \\mp z \\sin(\\theta)`
:param length: Distance :math:`l` from the lens.
"""
z_r = sp.pi * self.waist_z0**2 / self.wvl
return self.waist_z0 * sp.sqrt(1 + ((length - self.d_0) / z_r) ** 2)
def get_upper_beam_intensity(self) -> sp.Expr:
"""Intensity distribution of the upper beam"""
length = self.y * sp.cos(self.theta) - self.z * sp.sin(self.theta)
waist_z = self.get_beam_waist_z(length)
# Intensity of th0e beam at the origin
i_0 = 2 * self.power / (sp.pi * self.waist_x0 * self.waist_z0)
# Turn off automatic formatting to preserve readability
# fmt: off
return i_0 * self.waist_z0 / waist_z * sp.exp(
- 2 * self.x ** 2 / self.waist_x0 ** 2
- 2 * (self.z * sp.cos(self.theta) + self.y * sp.sin(self.theta)) ** 2
/ waist_z ** 2
) # fmt: on
def get_lower_beam_intensity(self) -> sp.Expr:
"""Intensity distribution of the upper beam"""
length = self.y * sp.cos(self.theta) + self.z * sp.sin(self.theta)
waist_z = self.get_beam_waist_z(length)
# Intensity of the beam at the origin
i_0 = 2 * self.power / (sp.pi * self.waist_x0 * self.waist_z0)
# Turn off automatic formatting to preserve readability
# fmt: off
return i_0 * self.waist_z0 / waist_z * sp.exp(
- 2 * self.x ** 2 / self.waist_x0 ** 2
- 2 * (self.z * sp.cos(self.theta) - self.y * sp.sin(self.theta)) ** 2
/ waist_z ** 2
) # fmt: on
def get_total_intensity(
self, with_tweezer: bool = True, with_2dtrap: bool = True
) -> sp.Expr:
"""Total intensity distribution of both beams"""
i_1 = self.get_upper_beam_intensity()
i_2 = self.get_lower_beam_intensity()
i_tweezer = int(with_tweezer) * self.get_tweezer_intensity()
i_2dtrap = int(with_2dtrap) * (
i_1 + i_2 + 2 * sp.sqrt(i_1 * i_2) * sp.cos(2 * sp.pi * self.z / self.d)
)
return i_2dtrap + i_tweezer
def get_potential(
self,
with_tweezer: bool = True,
with_2dtrap: bool = True,
with_grad: bool = True,
apply_zero_offset: bool = True,
) -> sp.Expr:
"""Get the potential of the 2d trap.
The total potential consists of the laser induced optical dipole
potential and the magnetic field potential.
"""
grad_r_coordinate = self.x * sp.cos(self.beta) - self.y * sp.sin(self.beta)
pot = -self.a * self.get_total_intensity(
with_tweezer=with_tweezer, with_2dtrap=with_2dtrap
) - int(with_grad) * self.mu_b * (
self.grad_r * grad_r_coordinate + self.grad_z * self.z
) - self.m * self.g * self.z
offset = int(apply_zero_offset) * pot.subs({self.x: 0, self.y: 0, self.z: 0})
return pot - offset
@cache
def get_omega(
self,
omega: sp.Symbol,
with_tweezer: bool = True,
with_2dtrap: bool = True,
) -> sp.Expr:
"""Calculate trap frequency for one spacial direction.
Uses :func:`sympy.series`, equates the 2nd order term with
the model of a harmonic oscillator and solves for omega.
:param omega: Which spacial trap frequency to calculate.
One of :attr:`.omega_x`, :attr:`.omega_y`,
or :attr:`.omega_z`.
:param force: Force re-evaluation of the trap frequency. The
analytic solution contains many symbols and is thus rather
lengthy. To save time, the result is cached in the
:attr:`._omega_solution` attribute.
:param with_tweezer: Whether or not to include tweezer potential
in the calculation.
:param with_2dtrap: Whether or not to include 2D trap potential
in the calculation.
:return: Trap frequency as a sympy expression.
"""
others: set[sp.Symbol] = {self.x, self.y, self.z}
variable: sp.Symbol
if omega == self.omega_x or omega == self.omega_r_tweezer:
variable = self.x
elif omega == self.omega_y:
variable = self.y
elif omega == self.omega_z or omega == self.omega_ax_tweezer:
variable = self.z
else:
raise ValueError(f"Unknown omega '{omega!r}'")
others.remove(variable)
series = self.subs(
self.get_total_intensity(
with_tweezer=with_tweezer, with_2dtrap=with_2dtrap
).subs({symbol: 0 for symbol in others}),
# Only substitute symbols with other symbols
symbolic_only=True,
).series(x=variable, x0=0, n=3)
quad_term = sp.LT(series.removeO(), gens=(variable,))
equation = sp.Eq(-self.a * quad_term, self.m * omega**2 * variable**2 / 2)
solution = sp.solve(equation, omega)
if len(solution) == 0:
raise RuntimeError(f"Unable to solve for '{omega}'")
return solution[0]
def get_omega_x(
self, with_tweezer: bool = True, with_2dtrap: bool = True
) -> sp.Expr:
"""Calculate trap frequency in x.
See :meth:`.get_omega`.
"""
return self.get_omega(
self.omega_x, with_tweezer=with_tweezer, with_2dtrap=with_2dtrap
)
def get_omega_y(
self, with_tweezer: bool = True, with_2dtrap: bool = True
) -> sp.Expr:
"""Calculate trap frequency in y.
See :meth:`.get_omega`.
"""
return self.get_omega(
self.omega_y, with_tweezer=with_tweezer, with_2dtrap=with_2dtrap
)
def get_omega_z(
self, with_tweezer: bool = True, with_2dtrap: bool = True
) -> sp.Expr:
"""Calculate trap frequency in z.
See :meth:`.get_omega`.
"""
return self.get_omega(
self.omega_z, with_tweezer=with_tweezer, with_2dtrap=with_2dtrap
)
def waist_z_to_waist_y(self) -> sp.Expr:
"""Calculate waist in y from :math:`W_z`"""
return self.waist_z0 / sp.sin(self.theta)
def waist_z_to_waist_ax(self) -> sp.Expr:
"""Calculates the axial waist from :math:`W_z`"""
return self.waist_z0 / sp.cos(self.theta)
def nstationary_solution(
self,
dims: tuple[sp.Symbol, ...],
extend: tuple[tuple[ExprNum, ExprNum], ...] | tuple[ExprNum, ExprNum],
size: tuple[int, ...],
k: int,
method="sparse",
export=False
):
"""Numeric stationary solution"""
if isinstance(dims, sp.Expr):
dims = (dims,)
ndims = len(dims)
if not (np.shape(extend) != (2,) or np.shape(extend) != (ndims, 2)):
raise ValueError(
f"Shape of parameter 'extend' has to be either (2,) or ({ndims}, 2), "
f"but is {np.shape(extend)}."
)
if not (isinstance(size, int) or len(size) == ndims):
raise ValueError(
"Parameter 'size' has to be either of type 'int' or "
f"a tuple of length {ndims}."
)
# Make sure 'size' is always a tuple, even if all dimensions
# have the same size.
if isinstance(size, int):
size = (size,) * ndims
others: set[sp.Symbol] = {self.x, self.y, self.z}
coordinates: dict[sp.Symbol, npt.NDArray] = {}
for i, dim in enumerate(dims):
if dim not in others:
raise ValueError(f"Symbols '{dim}' unsupported or used multiple times")
others.remove(dim)
# Check if a single extend is used for all dimensions. This
# boolean is later used to determine if the extend should
# be overwritten. This avoids re-evaluating the same extend
# multiple times.
single_extend = np.shape(extend) == (2,)
# The extend is casted to a list to make the items mutable
dim_extend: list[ExprNum, ExprNum] = (
list(extend) if single_extend else list(extend[i])
)
# Make sure that all limits in the extend are numbers that
# numpy can work with.
for j, lim in enumerate(dim_extend):
if isinstance(lim, sp.Expr):
new_lim = self.subs(lim).evalf()
if not isinstance(new_lim, (float, sp.Float)):
raise ValueError(
f"Unable to convert '{lim}' to float (using substitutions)"
)
dim_extend[j] = float(new_lim)
if single_extend:
# To avoid re-evaluation of the symbolic extends, fix
# the extends to the calculated value
extend = tuple(dim_extend) # cast list to tuple again
coordinates[dim] = np.linspace(*dim_extend, num=size[i])
# Calculate size of the volume elements
"""
coords = np.array(list(coordinates.values()))
dvol: npt.NDArray = np.mean(coords[:, 1:] - coords[:, :-1], axis=1)
assert dvol.shape == (ndims,)"""
dvol = np.array([])
for coord in coordinates.values():
dvol = np.append(dvol,np.mean(np.diff(coord)))
# Create potential array
pot = self.subs(self.get_potential(apply_zero_offset=False).subs({k: 0 for k in others}))
pot_numpy = sp.lambdify(tuple(coordinates.keys()), pot, cse=True)
if ndims > 1:
meshgrid = np.meshgrid(*coordinates.values(),indexing="ij")
potential = pot_numpy(*meshgrid)
else:
potential = pot_numpy(*coordinates.values())
# Perform the diagonalization of the Hamiltonian
energies, states = nstationary_solution(
tuple(dvol), potential, float(self.subs(self.m).evalf()), k=k,
method=method)
states = states.reshape((k, *size))
#export hamiltonian and parameters
if export:
#check if we are trying to solve DoubleTweezer and save corr. parameters
current_time = datetime.datetime.now()
#add to overview the key parameters of this run
if isinstance(self,DoubleTweezer):
distance_tweezers = float(self.subs(self.distance_tweezers).evalf())
power1 = float(self.subs(self.power_tweezer1).evalf())
power2 = float(self.subs(self.power_tweezer2).evalf())
waist1 = float(self.subs(self.waist_tweezer1).evalf())
waist2 = float(self.subs(self.waist_tweezer2).evalf())
wvl = float(self.subs(self.wvl).evalf())
omega_x1, omega_x2 = self.get_both_omega(self.x)
omega_x1 = float(self.subs(omega_x1).evalf())
omega_x2 = float(self.subs(omega_x2).evalf())
with open('data/overview.txt', 'a') as f:
f.write(f'{current_time.strftime("%Y-%m-%d_%H-%M-%S")}; {size}; {wvl}; {power1}; {power2}; {waist1}; {waist2}; {distance_tweezers}; {omega_x1/2/np.pi}; {omega_x2/2/np.pi} \n')
elif isinstance(self,TwoSiteLattice):
lattice_spacing_x = float(self.subs(self.lattice_spacing_x).evalf())
lattice_spacing_y = float(self.subs(self.lattice_spacing_y).evalf())
lattice_spacing_z = float(self.subs(self.lattice_spacing_z).evalf())
omega_x1, omega_x2 = self.get_both_omega(self.x)
omega_z1, omega_z2 = self.get_both_omega(self.z)
aspect_ratio = float(self.subs(sp.sqrt(omega_x1/omega_z1)))
wvl = float(self.subs(self.wvl).evalf())
with open('data/overview.txt', 'a') as f:
f.write(f'{current_time.strftime("%Y-%m-%d_%H-%M-%S")}; {size}; {wvl}; {lattice_spacing_x}; {lattice_spacing_y}; {lattice_spacing_z}; {aspect_ratio}; TwoSiteLattice\n')
#save results as npz
x3D,y3D,z3D = np.meshgrid(coordinates[self.x],coordinates[self.y],coordinates[self.z],indexing="ij")
np.savez(f"data/{current_time.strftime("%Y-%m-%d_%H-%M-%S")}_results.npz",
energies=energies, states=states, dvol=dvol,
k=k, size=size, extend=extend, pot=pot_numpy(x3D,y3D,z3D),
x=coordinates[self.x],y=coordinates[self.y],z=coordinates[self.z])
#save hamiltonian as sparse matrix
save_npz(f"data/{current_time.strftime("%Y-%m-%d_%H-%M-%S")}_hamiltonian.npz",
discrete_hamiltonian(tuple(dvol), potential, float(self.subs(self.m).evalf())))
#save trap object
with open(f"data/{current_time.strftime("%Y-%m-%d_%H-%M-%S")}_trap.npz", 'wb') as file:
pickle.dump(self, file)
print(f"files saved with ...._{current_time.strftime("%Y-%m-%d_%H-%M-%S")}")
return energies, states, pot_numpy, coordinates
def nstationary_solution_export(
self,path,
dims: tuple[sp.Symbol, ...],
extend: tuple[tuple[ExprNum, ExprNum], ...] | tuple[ExprNum, ExprNum],
size: tuple[int, ...],
k: int,
):
"""Numeric stationary solution"""
if isinstance(dims, sp.Expr):
dims = (dims,)
ndims = len(dims)
if not (np.shape(extend) != (2,) or np.shape(extend) != (ndims, 2)):
raise ValueError(
f"Shape of parameter 'extend' has to be either (2,) or ({ndims}, 2), "
f"but is {np.shape(extend)}."
)
if not (isinstance(size, int) or len(size) == ndims):
raise ValueError(
"Parameter 'size' has to be either of type 'int' or "
f"a tuple of length {ndims}."
)
# Make sure 'size' is always a tuple, even if all dimensions
# have the same size.
if isinstance(size, int):
size = (size,) * ndims
others: set[sp.Symbol] = {self.x, self.y, self.z}
coordinates: dict[sp.Symbol, npt.NDArray] = {}
for i, dim in enumerate(dims):
if dim not in others:
raise ValueError(f"Symbols '{dim}' unsupported or used multiple times")
others.remove(dim)
# Check if a single extend is used for all dimensions. This
# boolean is later used to determine if the extend should
# be overwritten. This avoids re-evaluating the same extend
# multiple times.
single_extend = np.shape(extend) == (2,)
# The extend is casted to a list to make the items mutable
dim_extend: list[ExprNum, ExprNum] = (
list(extend) if single_extend else list(extend[i])
)
# Make sure that all limits in the extend are numbers that
# numpy can work with.
for j, lim in enumerate(dim_extend):
if isinstance(lim, sp.Expr):
new_lim = self.subs(lim).evalf()
if not isinstance(new_lim, (float, sp.Float)):
raise ValueError(
f"Unable to convert '{lim}' to float (using substitutions)"
)
dim_extend[j] = float(new_lim)
if single_extend:
# To avoid re-evaluation of the symbolic extends, fix
# the extends to the calculated value
extend = tuple(dim_extend) # cast list to tuple again
coordinates[dim] = np.linspace(*dim_extend, num=size[i])
# Calculate size of the volume elements
"""
coords = np.array(list(coordinates.values()))
dvol: npt.NDArray = np.mean(coords[:, 1:] - coords[:, :-1], axis=1)
assert dvol.shape == (ndims,)"""
dvol = np.array([])
for coord in coordinates.values():
dvol = np.append(dvol,np.mean(np.diff(coord)))
# Create potential array
pot = self.subs(self.get_potential(apply_zero_offset=False).subs({k: 0 for k in others}))
pot_numpy = sp.lambdify(tuple(coordinates.keys()), pot, cse=True)
if ndims > 1:
meshgrid = np.meshgrid(*coordinates.values(),indexing="ij")
potential = pot_numpy(*meshgrid)
else:
potential = pot_numpy(*coordinates.values())
# Perform the diagonalization of the Hamiltonian
e_min = nstationary_solution_export(path,
tuple(dvol), potential, float(self.subs(self.m).evalf()), k=k)
x3D,y3D,z3D = np.meshgrid(coordinates[self.x],coordinates[self.y],coordinates[self.z],indexing="ij")
#check if we are trying to solve DoubleTweezer and save corr. parameters
if isinstance(self,DoubleTweezer):
np.savez(path + r"\parameters.npz",e_min=e_min, k=k, size=size, pot=pot_numpy(x3D,y3D,z3D),
x=coordinates[self.x],y=coordinates[self.y],z=coordinates[self.z],
mass=self.m, mu=self.mu_b,distance_tweezers=self.distance_tweezers,
power1=self.power_tweezer1, power2=self.power_tweezer2,
waist1=self.waist_tweezer1, waist2=self.waist_tweezer2)
else:
np.savez(path + r"\parameters.npz",e_min=e_min, k=k, size=size, pot=pot_numpy(x3D,y3D,z3D),
x=coordinates[self.x],y=coordinates[self.y],z=coordinates[self.z],
mass=self.m, mu=self.mu_b)
print("files saved under " + path)
class TrueHarmonicTweezer(PancakeTrap):
"""Pancake Trap but with a truly harmonic tweezer (i.e. quadratic
in z and r). Note that spilling is not possible, as the gradient
simply shifts the center of the trap.
"""
def get_tweezer_intensity(self) -> sp.Expr:
i_xz = super().get_tweezer_intensity().subs({self.y: 0})
series_x = i_xz.subs({self.z: 0}).series(x=self.x, x0=0, n=3).removeO()
series_z = i_xz.subs({self.x: 0}).series(x=self.z, x0=0, n=3).removeO()
series_r = series_x.subs(self.x, sp.sqrt(self.x**2 + self.y**2))
quad_term_z = sp.LT(series_z, gens=(self.z,))
return series_r + quad_term_z
class DoubleTweezer(PancakeTrap):
"""
Pancake trap but with two tweezers.
Each waist and power, and their spacing can be adjusted.
"""
def __init__(self,**values):
self.power_tweezer1 = sp.Symbol("P_t1", real=True, positive=True, finite=True, nonzero=True)
self.power_tweezer2 = sp.Symbol("P_t2", real=True, positive=True, finite=True, nonzero=True)
self.waist_tweezer1 = sp.Symbol("W_t1", real=True, positive=True, finite=True, nonzero=True)
self.waist_tweezer2 = sp.Symbol("W_t2", real=True, positive=True, finite=True, nonzero=True)
self.distance_tweezers = sp.Symbol("d_t", real=True, positive=True, finite=True, nonzero=True)
super().__init__(**values)
def get_tweezer_rayleigh1(self):
return sp.pi * self.waist_tweezer1**2 / self.wvl
def get_tweezer_rayleigh2(self):
return sp.pi * self.waist_tweezer2**2 / self.wvl
def get_tweezer_intensity1(self,x_offset) -> sp.Expr:
rayleigh_length = self.get_tweezer_rayleigh1()
waist = self.waist_tweezer1 * sp.sqrt(1 + (self.z / rayleigh_length) ** 2)
i_0 = 2 * self.power_tweezer1 / (sp.pi * self.waist_tweezer1**2)
r = sp.sqrt((self.x-x_offset)**2 + self.y**2)
return i_0 * (self.waist_tweezer1 / waist) ** 2 * sp.exp(-2 * r**2 / (waist**2))
def get_tweezer_intensity2(self,x_offset) -> sp.Expr:
rayleigh_length = self.get_tweezer_rayleigh2()
waist = self.waist_tweezer2 * sp.sqrt(1 + (self.z / rayleigh_length) ** 2)
i_0 = 2 * self.power_tweezer2 / (sp.pi * self.waist_tweezer2**2)
r = sp.sqrt((self.x-x_offset)**2 + self.y**2)
return i_0 * (self.waist_tweezer2 / waist) ** 2 * sp.exp(-2 * r**2 / (waist**2))
def get_tweezer_intensity(self) -> sp.Expr:
return self.get_tweezer_intensity1(-self.distance_tweezers/2) + self.get_tweezer_intensity2(self.distance_tweezers/2)
def get_both_omega(self, dim: sp.Symbol):
"""
Returns omega_1, omega_2 the frequency in direction dim (sympy coord)
of the left(1) and right(2) tweezer
"""
V = self.subs(self.get_potential(apply_zero_offset=False))
a = float(self.subs(self.distance_tweezers))
#find minima of potential
def V_func(x):
return float(V.subs(self.x,x).subs(self.y,0).subs(self.z,0))
x_right = minimize_scalar(V_func,bracket=[0,a]).x
x_left = minimize_scalar(V_func,bracket=[-a,0]).x
#catch case where both potentials have already merged
tunneling_dist = abs(x_right-x_left)
if tunneling_dist < 1e-20:
raise Exception("potential has only one minmum")
#trapping frequencies through second derivative
V_prime = sp.diff(V, dim)
V_double_prime = sp.diff(V_prime, dim)
omega_1 = sp.sqrt(V_double_prime.subs({self.x:x_left, self.y:0, self.z:0})/self.m)
omega_2 = sp.sqrt(V_double_prime.subs({self.x:x_left, self.y:0, self.z:0})/self.m)
return omega_1, omega_2
class TwoSiteLattice(PancakeTrap):
"""
Trap with lattice potential that has just two sites
"""
def __init__(self,**values):
self.lattice_depth_x = sp.Symbol("V0_x", real=True, positive=True, finite=True, nonzero=True)
self.lattice_depth_y = sp.Symbol("V0_y", real=True, positive=True, finite=True, nonzero=True)
self.lattice_depth_z = sp.Symbol("V0_z", real=True, positive=True, finite=True, nonzero=True)
#lattice spacings are normaly half the wavelength in that direction
self.lattice_spacing_x = sp.Symbol("d_x", real=True, positive=True, finite=True, nonzero=True)
self.lattice_spacing_y = sp.Symbol("d_y", real=True, positive=True, finite=True, nonzero=True)
self.lattice_spacing_z = sp.Symbol("d_z", real=True, positive=True, finite=True, nonzero=True)
super().__init__(**values)
def get_potential(
self,
with_tweezer: bool = True,
with_2dtrap: bool = True,
with_lattice: bool = True,
with_grad: bool = True,
apply_zero_offset: bool = True,
) -> sp.Expr:
"""Get the potential of the 2d trap.
The total potential consists of the laser induced optical dipole
potential and the magnetic field potential.
"""
a = self.lattice_spacing_x
b = self.lattice_spacing_y
c = self.lattice_spacing_z
#use sine in x direction to have two wells around the origin
pot_lattice = (self.lattice_depth_x* sp.sin(np.pi/a*self.x)**2 +
self.lattice_depth_y* sp.cos(np.pi/b*self.y)**2 +
self.lattice_depth_z* sp.cos(np.pi/c*self.z)**2)
grad_r_coordinate = self.x * sp.cos(self.beta) - self.y * sp.sin(self.beta)
pot = -self.a * self.get_total_intensity(
with_tweezer=with_tweezer, with_2dtrap=with_2dtrap
) - int(with_grad) * self.mu_b * (
self.grad_r * grad_r_coordinate + self.grad_z * self.z
) - int(with_lattice)* (pot_lattice
) - self.m * self.g * self.z
offset = int(apply_zero_offset) * pot.subs({self.x: 0, self.y: 0, self.z: 0})
return pot - offset
def get_both_omega(self, dim: sp.Symbol):
"""
Returns omega_1, omega_2 the frequency in direction dim (sympy coord)
of the left(1) and right(2) tweezer
"""
V = self.subs(self.get_potential(apply_zero_offset=False))
a = float(self.subs(self.lattice_spacing_x))
#find minima of potential
x_right = a/2
x_left = -a/2
#catch case where both potentials have already merged
tunneling_dist = abs(x_right-x_left)
if tunneling_dist < 1e-20:
raise Exception("potential has only one minmum")
#trapping frequencies through second derivative
V_prime = sp.diff(V, dim)
V_double_prime = sp.diff(V_prime, dim)
omega_1 = sp.sqrt(V_double_prime.subs({self.x:x_left, self.y:0, self.z:0})/self.m)
omega_2 = sp.sqrt(V_double_prime.subs({self.x:x_left, self.y:0, self.z:0})/self.m)
return omega_1, omega_2
def _optional_a(
a: float_or_array | None, wavelength: float_or_array | None
) -> float_or_array:
if a is None:
if wavelength is None:
raise ValueError("Either 'a' or 'wavelength' has to be specified.")
a = get_a(wavelength)
elif wavelength is not None:
warnings.warn(
"Both 'a' and 'wavelength' were provided. "
"Keyword argument 'wavelength' will be ignored.",
RuntimeWarning,
stacklevel=2,
)
return a
def get_d(
waist_r: float_or_array | None = None,
waist_z: float_or_array | None = None,
wavelength: float_or_array = p.wavelength_trap,
theta: float_or_array | None = p.theta, # in rad
) -> float_or_array:
"""Get fringe spacing (in z-direction) for the 2D trap pancakes.
If the waists are specified, the angle theta is ignored as it can
be substituted using the waists.
Either both waists or the angle have to be specified.
:param waist_r:
:param waist_z:
:param wavelength:
:param theta:
:return:
"""
if waist_r is not None and waist_z is not None:
return wavelength / (2 * waist_z) * waist_r
elif theta is not None:
return wavelength / (2 * np.cos(theta))
else:
raise ValueError(
"Either 'waist_r' and 'waist_z', or 'theta' have to be specified."
)
def get_a(wavelength: float_or_array) -> float_or_array:
"""Get real part of the polarizability (Re{alpha}) for a
:param wavelength: Laser wavelength
:return:
"""
omega_laser = 2 * np.pi * const.c / wavelength
return (
(3 * np.pi * const.c**2)
/ (2 * p.omega_0**3)
* (p.gamma / (p.omega_0 - omega_laser) + p.gamma / (p.omega_0 + omega_laser))
)
def get_power_radial_gaussian(
omega: float_or_array,
waist: float_or_array,
a: float_or_array | None = None,
wavelength: float_or_array | None = None,
) -> float_or_array:
"""Calculate the required power for a radial gaussian beam with a
given desired trap frequency and waist.
:param omega: Desired trap frequency to compensate.
:param waist: Waist of the repulsion beam.
:param a: Pre-factor 'a' for the trap depth.
:param wavelength: Optional to calculate 'a'.
:return: Power of the repulsion beam.
"""
a = _optional_a(a, wavelength)
return np.abs(omega**2 * np.pi * waist**4 * p.mass_lithium6 / (8 * a))
def get_omega_r_trap(
power: float_or_array,
waist_r: float_or_array,
waist_z: float_or_array,
a: float_or_array | None = None,
wavelength: float_or_array | None = None,
) -> float_or_array:
"""
:param power: Power of the trapping beam.
:param waist_r: Radial waist, denoted :math:`W_{0x}` in Ram-Janik's
thesis. Optional.
:param waist_z: Waist in z-direction, denoted :math:`W_{0z}`.
Optional.
:param a: Pre-factor 'a' for the trap depth.
:param wavelength: Optional to calculate 'a'.
:return: Trapping frequency trap :math:`\\omega_{x}`.
"""
a = _optional_a(a, wavelength)
return np.sqrt(32 * a * power / (np.pi * p.mass_lithium6 * waist_r**3 * waist_z))
def get_omega_z_trap(
power_trap: float_or_array,
waist_r: float_or_array,
waist_z: float_or_array,
theta: float_or_array = p.theta, # in rad
wavelength: float_or_array = p.wavelength_trap,
) -> float_or_array:
a = get_a(wavelength)
return np.sqrt(
16
* a
* power_trap
/ (np.pi * p.mass_lithium6 * waist_r * waist_z)
* (
2 * np.sin(theta) ** 2 / waist_z**2
+ (wavelength * np.sin(theta) / np.pi) ** 2
* (1 / waist_r**4 + 1 / waist_z**4)
+ (np.pi / get_d(waist_r, waist_z, wavelength)) ** 2
)
)
def get_power_omega_r(
omega_r: float_or_array,
waist_r: float_or_array = p.waist_trap_r,
waist_z: float_or_array = p.waist_trap_z,
a: float_or_array | None = None,
wavelength: float_or_array | None = None,
) -> float_or_array:
"""Calculate the power of the trap based upon the radial properties.
:param omega_r: Radial trapping frequency.
:param waist_r: Radial waist, denoted :math:`W_{0x}` in Ram-Janik's
thesis. Optional.
:param waist_z: Waist in z-direction, denoted :math:`W_{0z}`.
Optional.
:param a: Pre-factor 'a' for the trap depth.
:param wavelength: Optional to calculate 'a'.
:return: Power of the trapping beam :math:`P_{1,2}`.
"""
intensity = pot.TotalIntensity()
series_x = intensity.subs(pot.z, 0).subs(pot.y, 0).series(x=pot.x, x0=0, n=3)
a = _optional_a(a, wavelength)
return omega_r**2 * np.pi * waist_r**3 * waist_z * p.mass_lithium6 / (32 * a)
def get_power_omega_z(
omega_z: float_or_array,
waist_r: float_or_array = p.waist_trap_r,
waist_z: float_or_array = p.waist_trap_z,
a: float_or_array | None = None,
wavelength: float_or_array | None = None,
) -> float_or_array:
"""Calculate the power of the trap based upon the axial properties.
:param omega_z: Axial trapping frequency.
:param waist_r: Radial waist, denoted :math:`W_{0x}` in Ram-Janik's
thesis. Optional.
:param waist_z: Waist in z-direction, denoted :math:`W_{0z}`.
Optional.
:param a: Pre-factor 'a' for the trap depth.
:param wavelength: Optional to calculate 'a'.
:return: Power of the trapping beam :math:`P_{1,2}`.
"""
a = _optional_a(a, wavelength)
return
def gaussian_beam_1d(
r: float_or_array, power: float_or_array, waist: float_or_array
) -> float_or_array:
"""Gaussian beam profile in 1d.
:param r: Radial coordinate
:param power: Power of the beam
:param waist: Waist
"""
return 2 * power / (np.pi * waist**2) * np.exp(-2 * r**2 / waist**2)

201
clean_diag/test.ipynb Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,258 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Try to use cupy and use GPU for faster compute (It does not work on my PC, since I don't have a NVIDIA GPU):"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import matplotlib.animation as animation\n",
"#import numpy as np\n",
"import cupy as np\n",
"import sympy as sp\n",
"from IPython.display import Math, display\n",
"from matplotlib.axes import Axes\n",
"from scipy import constants as const\n",
"from scipy.integrate import quad\n",
"from scipy.optimize import root_scalar\n",
"from scipy.signal import argrelmax,argrelmin,find_peaks\n",
"from tqdm import tqdm\n",
"from typing import Union\n",
"\n",
"import fewfermions.analysis.units as si\n",
"from fewfermions.simulate.traps.twod.trap import DoubleTweezer"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"initial_power = 50* si.uW\n",
"initial_waist = 1.1*si.uW\n",
"initial_distance = 2*si.um\n",
"\n",
"trap: DoubleTweezer = DoubleTweezer(\n",
" power=0, # Set pancake laser power to 0, no 2D trap\n",
" grad_z= 0*si.G/si.cm,\n",
" grad_r=0,\n",
" power_tweezer1 = initial_power, #stationary\n",
" power_tweezer2 = initial_power, #transfer tweezer\n",
" waist_tweezer1 = initial_waist, #stationary\n",
" waist_tweezer2 = initial_waist, #transfer tweezer\n",
" distance_tweezers = initial_distance,\n",
"\n",
" a=180*(4 * np.pi * const.epsilon_0 * const.value(\"Bohr radius\")**3)/(2 * const.epsilon_0 * const.c),\n",
" wvl = 532 * si.nm,\n",
"\n",
" g = 0,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Try with cupy:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"import cupy as np\n",
"\n",
"n_pot_steps = [40,40,40]\n",
"n_levels = 4\n",
"\n",
"left_cutoff = -0.5*initial_distance-2*max([float(trap.subs(trap.waist_tweezer1)),float(trap.subs(trap.waist_tweezer2))])\n",
"right_cutoff = 0.5*initial_distance+2*max([float(trap.subs(trap.waist_tweezer1)),float(trap.subs(trap.waist_tweezer2))])\n",
"back_cutoff = -2*max([float(trap.subs(trap.waist_tweezer1)),float(trap.subs(trap.waist_tweezer2))])\n",
"front_cutoff = 2*max([float(trap.subs(trap.waist_tweezer1)),float(trap.subs(trap.waist_tweezer2))])\n",
"bottom_cutoff = -2*max([float(trap.subs(trap.get_tweezer_rayleigh1())),float(trap.subs(trap.get_tweezer_rayleigh2()))])\n",
"top_cutoff = 2*max([float(trap.subs(trap.get_tweezer_rayleigh1())),float(trap.subs(trap.get_tweezer_rayleigh2()))])\n",
"\n",
"extend = [(left_cutoff,right_cutoff),\n",
" (back_cutoff,front_cutoff),\n",
" (bottom_cutoff,top_cutoff)]\n",
"\n",
"\n",
"# Solve the hamiltonian numerically\n",
"energies, states, potential, coords = trap.nstationary_solution(\n",
" [trap.x,trap.y,trap.z], extend, n_pot_steps, k=n_levels)\n",
"\n",
"#x3D,y3D,z3D = np.meshgrid(coords[trap.x],coords[trap.y],coords[trap.z])\n",
"np.savez(\"data/test_3D.npz\",energies=energies, states=states, pot=potential(x3D,y3D,z3D), x=coords[trap.x],y=coords[trap.y],z=coords[trap.z])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Try with regular numpy:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"ename": "",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[1;31mThe Kernel crashed while executing code in the current cell or a previous cell. \n",
"\u001b[1;31mPlease review the code in the cell(s) to identify a possible cause of the failure. \n",
"\u001b[1;31mClick <a href='https://aka.ms/vscodeJupyterKernelCrash'>here</a> for more info. \n",
"\u001b[1;31mView Jupyter <a href='command:jupyter.viewOutput'>log</a> for further details."
]
}
],
"source": [
"import numpy as np\n",
"\n",
"n_pot_steps = [40,40,40]\n",
"n_levels = 4\n",
"\n",
"left_cutoff = -0.5*initial_distance-2*max([float(trap.subs(trap.waist_tweezer1)),float(trap.subs(trap.waist_tweezer2))])\n",
"right_cutoff = 0.5*initial_distance+2*max([float(trap.subs(trap.waist_tweezer1)),float(trap.subs(trap.waist_tweezer2))])\n",
"back_cutoff = -2*max([float(trap.subs(trap.waist_tweezer1)),float(trap.subs(trap.waist_tweezer2))])\n",
"front_cutoff = 2*max([float(trap.subs(trap.waist_tweezer1)),float(trap.subs(trap.waist_tweezer2))])\n",
"bottom_cutoff = -2*max([float(trap.subs(trap.get_tweezer_rayleigh1())),float(trap.subs(trap.get_tweezer_rayleigh2()))])\n",
"top_cutoff = 2*max([float(trap.subs(trap.get_tweezer_rayleigh1())),float(trap.subs(trap.get_tweezer_rayleigh2()))])\n",
"\n",
"extend = [(left_cutoff,right_cutoff),\n",
" (back_cutoff,front_cutoff),\n",
" (bottom_cutoff,top_cutoff)]\n",
"\n",
"\n",
"# Solve the hamiltonian numerically\n",
"energies, states, potential, coords = trap.nstationary_solution(\n",
" [trap.x,trap.y,trap.z], extend, n_pot_steps, k=n_levels)\n",
"\n",
"#x3D,y3D,z3D = np.meshgrid(coords[trap.x],coords[trap.y],coords[trap.z])\n",
"np.savez(\"data/test_3D.npz\",energies=energies, states=states, pot=potential(x3D,y3D,z3D), x=coords[trap.x],y=coords[trap.y],z=coords[trap.z])"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"ename": "CUDARuntimeError",
"evalue": "cudaErrorInsufficientDriver: CUDA driver version is insufficient for CUDA runtime version",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mCUDARuntimeError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[1;32mIn[12], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;66;03m# Generate spatial grid\u001b[39;00m\n\u001b[1;32m----> 2\u001b[0m x \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mlinspace(\u001b[38;5;241m*\u001b[39mextend[\u001b[38;5;241m0\u001b[39m], n_pot_steps[\u001b[38;5;241m0\u001b[39m])\n\u001b[0;32m 3\u001b[0m y \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mlinspace(\u001b[38;5;241m*\u001b[39mextend[\u001b[38;5;241m1\u001b[39m], n_pot_steps[\u001b[38;5;241m1\u001b[39m])\n\u001b[0;32m 4\u001b[0m z \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mlinspace(\u001b[38;5;241m*\u001b[39mextend[\u001b[38;5;241m2\u001b[39m], n_pot_steps[\u001b[38;5;241m2\u001b[39m])\n",
"File \u001b[1;32mc:\\Users\\naeve\\anaconda3\\Lib\\site-packages\\cupy\\_creation\\ranges.py:161\u001b[0m, in \u001b[0;36mlinspace\u001b[1;34m(start, stop, num, endpoint, retstep, dtype, axis)\u001b[0m\n\u001b[0;32m 159\u001b[0m scalar_stop \u001b[38;5;241m=\u001b[39m cupy\u001b[38;5;241m.\u001b[39misscalar(stop)\n\u001b[0;32m 160\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m scalar_start \u001b[38;5;129;01mand\u001b[39;00m scalar_stop:\n\u001b[1;32m--> 161\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m _linspace_scalar(start, stop, num, endpoint, retstep, dtype)\n\u001b[0;32m 163\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m scalar_start:\n\u001b[0;32m 164\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28misinstance\u001b[39m(start, cupy\u001b[38;5;241m.\u001b[39mndarray) \u001b[38;5;129;01mand\u001b[39;00m start\u001b[38;5;241m.\u001b[39mdtype\u001b[38;5;241m.\u001b[39mkind \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m'\u001b[39m):\n",
"File \u001b[1;32mc:\\Users\\naeve\\anaconda3\\Lib\\site-packages\\cupy\\_creation\\ranges.py:91\u001b[0m, in \u001b[0;36m_linspace_scalar\u001b[1;34m(start, stop, num, endpoint, retstep, dtype)\u001b[0m\n\u001b[0;32m 87\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m dtype \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m 88\u001b[0m \u001b[38;5;66;03m# In actual implementation, only float is used\u001b[39;00m\n\u001b[0;32m 89\u001b[0m dtype \u001b[38;5;241m=\u001b[39m dt\n\u001b[1;32m---> 91\u001b[0m ret \u001b[38;5;241m=\u001b[39m cupy\u001b[38;5;241m.\u001b[39mempty((num,), dtype\u001b[38;5;241m=\u001b[39mdt)\n\u001b[0;32m 92\u001b[0m div \u001b[38;5;241m=\u001b[39m (num \u001b[38;5;241m-\u001b[39m \u001b[38;5;241m1\u001b[39m) \u001b[38;5;28;01mif\u001b[39;00m endpoint \u001b[38;5;28;01melse\u001b[39;00m num\n\u001b[0;32m 93\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m div \u001b[38;5;241m<\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m:\n",
"File \u001b[1;32mc:\\Users\\naeve\\anaconda3\\Lib\\site-packages\\cupy\\_creation\\basic.py:32\u001b[0m, in \u001b[0;36mempty\u001b[1;34m(shape, dtype, order)\u001b[0m\n\u001b[0;32m 13\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mempty\u001b[39m(\n\u001b[0;32m 14\u001b[0m shape: _ShapeLike,\n\u001b[0;32m 15\u001b[0m dtype: DTypeLike \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mfloat\u001b[39m,\n\u001b[0;32m 16\u001b[0m order: _OrderCF \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mC\u001b[39m\u001b[38;5;124m'\u001b[39m,\n\u001b[0;32m 17\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m NDArray[Any]:\n\u001b[0;32m 18\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Returns an array without initializing the elements.\u001b[39;00m\n\u001b[0;32m 19\u001b[0m \n\u001b[0;32m 20\u001b[0m \u001b[38;5;124;03m Args:\u001b[39;00m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 30\u001b[0m \n\u001b[0;32m 31\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[1;32m---> 32\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m cupy\u001b[38;5;241m.\u001b[39mndarray(shape, dtype, order\u001b[38;5;241m=\u001b[39morder)\n",
"File \u001b[1;32mcupy\\\\_core\\\\core.pyx:151\u001b[0m, in \u001b[0;36mcupy._core.core.ndarray.__new__\u001b[1;34m()\u001b[0m\n",
"File \u001b[1;32mcupy\\\\_core\\\\core.pyx:239\u001b[0m, in \u001b[0;36mcupy._core.core._ndarray_base._init\u001b[1;34m()\u001b[0m\n",
"File \u001b[1;32mcupy\\\\cuda\\\\memory.pyx:738\u001b[0m, in \u001b[0;36mcupy.cuda.memory.alloc\u001b[1;34m()\u001b[0m\n",
"File \u001b[1;32mcupy\\\\cuda\\\\memory.pyx:1424\u001b[0m, in \u001b[0;36mcupy.cuda.memory.MemoryPool.malloc\u001b[1;34m()\u001b[0m\n",
"File \u001b[1;32mcupy\\\\cuda\\\\memory.pyx:1444\u001b[0m, in \u001b[0;36mcupy.cuda.memory.MemoryPool.malloc\u001b[1;34m()\u001b[0m\n",
"File \u001b[1;32mcupy\\\\cuda\\\\device.pyx:40\u001b[0m, in \u001b[0;36mcupy.cuda.device.get_device_id\u001b[1;34m()\u001b[0m\n",
"File \u001b[1;32mcupy_backends\\\\cuda\\\\api\\\\runtime.pyx:202\u001b[0m, in \u001b[0;36mcupy_backends.cuda.api.runtime.getDevice\u001b[1;34m()\u001b[0m\n",
"File \u001b[1;32mcupy_backends\\\\cuda\\\\api\\\\runtime.pyx:146\u001b[0m, in \u001b[0;36mcupy_backends.cuda.api.runtime.check_status\u001b[1;34m()\u001b[0m\n",
"\u001b[1;31mCUDARuntimeError\u001b[0m: cudaErrorInsufficientDriver: CUDA driver version is insufficient for CUDA runtime version"
]
}
],
"source": [
"# Generate spatial grid\n",
"x = np.linspace(*extend[0], n_pot_steps[0])\n",
"y = np.linspace(*extend[1], n_pot_steps[1])\n",
"z = np.linspace(*extend[2], n_pot_steps[2])\n",
"\n",
"x3D, y3D, z3D = np.meshgrid(x, y, z) # Ensure correct indexing\n",
"\n",
"# Compute potential (Replace with actual function)\n",
"pot = potential(x3D, y3D, z3D)\n",
"\n",
"state_number = 0\n",
"\n",
"# Create figure and axis\n",
"fig, ax = plt.subplots()\n",
"im = ax.imshow(states[state_number, :, :, 0], extent=[*extend[0], *extend[1]], origin=\"lower\",\n",
" vmin=np.min(states[state_number]), vmax=np.max(states[state_number]))\n",
"\n",
"plt.xlabel(\"x\")\n",
"plt.ylabel(\"y\")\n",
"plt.colorbar(im)\n",
"\n",
"# Initialize contour as None before defining it globally\n",
"contour = None\n",
"\n",
"# Animation update function\n",
"def update(frame):\n",
" global contour # Ensure we're modifying the global variable\n",
"\n",
" im.set_data(states[state_number, :, :, frame]) # Update image data\n",
" ax.set_title(f\"z={z[frame]/si.um:.3f}um\") # Update title\n",
"\n",
" # Remove old contours if they exist\n",
" if contour is not None:\n",
" for c in contour.collections:\n",
" c.remove()\n",
"\n",
" # Redraw contour plot\n",
" contour = ax.contour(pot[:, :, frame], levels=10, colors='white', linewidths=0.7, extent=[*extend[0], *extend[1]])\n",
"\n",
"# Create the first contour plot after defining update()\n",
"contour = ax.contour(pot[:, :, 0], levels=10, colors='white', linewidths=0.7, extent=[*extend[0], *extend[1]])\n",
"\n",
"# Create animation\n",
"frames = n_pot_steps[2] # Number of slices\n",
"ani = animation.FuncAnimation(fig, update, frames=frames, interval=100)\n",
"\n",
"ani.save(f\"animations/test{state_number}.gif\", writer=\"pillow\", fps=frames/5) # Save as GIF\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.7"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,123 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"#add relative path to backend\n",
"import sys\n",
"sys.path.append('../../clean_diag/backend')\n",
"\n",
"import trap_units as si"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A simple lens with f=30mm requires frequency spacing: $ \\Delta \\nu \\approx \\frac{v_s}{\\lambda f} \\Delta d $"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"frequency difference: 186.842 kHz\n"
]
}
],
"source": [
"print(f\"frequency difference: {4260*si.m/si.s / (532*si.nm * 30*si.mm) * 700*si.nm /si.kHz:.3f} kHz\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In Kaufmann paper they report function spacing of 0.209(3)um/MHz\n",
"\n",
"For our spacing >700nm this means:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"frequency difference: 3349.282 kHz\n"
]
}
],
"source": [
"print(f\"frequency difference: {0.7*si.um /(0.209*si.um/si.MHz) /si.kHz:.3f} kHz\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Try to reproduce their function spacing: $\\lambda f / v_s$:\n",
"\n",
"Only found the working distance, so assume it's close to focal length.\n",
"\n",
"Factor 20 comes from 20x beam expander in front of objective."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"function spacing: 0.2100 um/MHz\n"
]
}
],
"source": [
"print(f\"function spacing: {852*si.nm * 21*si.mm / (4260*si.m/si.s) /(si.um/si.MHz) /20:.4f} um/MHz\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,189 @@
import matplotlib.pyplot as plt
import numpy as np
import sympy as sp
from IPython.display import Math, display
from matplotlib.axes import Axes
from scipy import constants as const
from scipy.integrate import quad
from scipy.optimize import root_scalar
from scipy.signal import argrelmax,argrelmin,find_peaks
from tqdm import tqdm
import fewfermions.analysis.units as si
from fewfermions.simulate.traps.twod.trap import DoubleTweezer
def plot_solutions(trap,n_levels,left_cutoff,right_cutoff,n_pot_steps=1000,display_plot=-1,state_mult=1e4,plot=True,ret_results=False):
"""Plot the potential and solutions for a given trap object
(diplay_plot=-1 for all, 0,...,n for individual ones and -2 for none)
"""
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.x, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# States that are below the potential barrier
bound_states = energies < potential(left_cutoff)
if plot:
z_np = np.linspace(left_cutoff, right_cutoff, num=n_pot_steps)
ax: plt.Axes
fig, ax = plt.subplots(figsize=(5, 5))
ax.plot(z_np / si.um, potential(z_np) / const.h / si.kHz,color="cornflowerblue" ,marker="None")
ax.set_title(f"{np.sum(bound_states)} bound solutions, tweezer distance: {trap.subs(trap.distance_tweezers)/si.um}um")
ax.set_xlabel(r"x ($\mathrm{\mu m}$)")
ax.set_ylabel(r"E / h (kHz)")
abs_min = np.min(potential(z_np))
ax.fill_between(
z_np / si.um,
potential(z_np) / const.h / si.kHz,
abs_min / const.h / si.kHz,
alpha=0.5,
color="cornflowerblue"
)
count = 0
for i, bound in enumerate(bound_states):
if not bound:
continue
energy = energies[i]
state = states[i]
ax.plot(
z_np / si.um,
np.where(
(energy > potential(z_np)),
energy / const.h / si.kHz,
np.nan,
),
c="k",
lw=0.5,
marker="None",
)
if display_plot == -1 or display_plot == count:
ax.plot(z_np/si.um, state *state_mult, marker="None",label=f"state {count}")#, c="k")
count += 1
plt.legend()
if ret_results:
return np.sum(bound_states), energies[bound_states], states[bound_states], potential
def loop_distances(trap,distances,n_levels=10,n_pot_steps=2000):
"""
returns unsorted arrays for energies, states and the potential for a given array of tweezer distances
"""
left_cutoff = -0.5*np.max(distances)-3*np.max([float(trap.subs(trap.waist_tweezer1)),float(trap.subs(trap.waist_tweezer2))])
right_cutoff = 0.5*np.max(distances)+3*np.max([float(trap.subs(trap.waist_tweezer1)),float(trap.subs(trap.waist_tweezer2))])
x_np = np.linspace(left_cutoff,right_cutoff,n_pot_steps)
energies = np.zeros((len(distances),n_levels))
states = np.zeros((len(distances),n_levels,n_pot_steps))
potential = np.zeros((len(distances),n_pot_steps))
#diagonalise hamiltonian for all distances
for i, dist in enumerate(distances):
trap[trap.distance_tweezers] = dist
num, ener, state, pot = plot_solutions(trap,n_levels,np.min(x_np),np.max(x_np),display_plot=0,state_mult=450,n_pot_steps=n_pot_steps,plot=False,ret_results=True)
# fill in arrays
energies[i,:len(ener)] = ener
states[i,:len(ener)] = state
potential[i] = pot(x_np)
return energies, states, potential
def find_crossovers_topstate(energies):
"""
returns the indices(distance) of all crossovers (with calculated and not calculated states) of the largest calculated state
"""
arr = np.abs(np.gradient(np.gradient(energies[:,-1])))
index = find_peaks(arr,width=(1,4))[0]
return index
def find_crossovers(energies,mult_min_energy=0.01):
"""
returns the indices(distance) of all crossovers between two calculated states
(very dependent on mult_min_energy)
"""
n_levels = len(energies[0,:])
index = np.array([],dtype=int)
swap_index =np.empty((0,2),dtype=int)
for i in range(n_levels):
for j in range(i+1,n_levels):
diff = np.abs(energies[:,i] - energies[:,j])
mask = np.where(diff < mult_min_energy*np.max(diff),diff,mult_min_energy*np.max(diff))
peaks = find_peaks(-mask)[0]
index = np.append(index,peaks)
for k in range(len(peaks)):
swap_index = np.append(swap_index,[[i,j]],axis=0)
return index, swap_index
def swapped_loop_distance(distances, energies, states, potentials):
"""
returns the results of loop_distance() with energies and states matching to individual states
"""
index_top = find_crossovers_topstate(energies)
index, swap_index = find_crossovers(energies)
new_energies = np.full((energies.shape[0],energies.shape[1]+len(index_top)),np.nan)
new_states = np.full((states.shape[0],states.shape[1]+len(index_top),states.shape[2]),np.nan)
swapped_index = np.arange(0,energies.shape[1])
for i, dist in enumerate(distances):
if np.any(i == index):
j = np.where(index == i)
swapped_index[swap_index[j]] = swapped_index[np.roll(swap_index[j],1)]
#print(f"crossover of states {swap_index[j]} at {dist/si.um:.2f}um")
elif np.any(i == index_top):
#print(f"crossover of top state at {dist/si.um:.2f}um")
swapped_index[-1] = np.max(swapped_index)+1
new_energies[i,swapped_index] = energies[i]
new_states[i,swapped_index] = states[i]
return new_energies, new_states, potentials, index_top, index, swap_index
def find_ass_tweezer(new_energies,new_states,return_deltaE=True):
"""
Sorts energies and states into those coming from left and right tweezer.
Returns the minimum energy difference of the groundstate of the shallow tweezer to any of the other tweezers eigenstates.
Also returns the occupation number when the groundstates of both tweezers were initially occupied (when return_deltaE=True)
"""
#seperate energies and states corresponding to the initial tweezer they belonged to
mask_right = np.where(np.sum(new_states[0,:,:int(new_states.shape[2]/2)]**2,axis=1) < 0.5,True, False)
mask_left = np.logical_not(mask_right)
energies_right = new_energies[:,mask_right]
energies_left = new_energies[:,mask_left]
states_right = new_states[:,mask_right,:]
states_left = new_states[:,mask_left,:]
#identify GS of shallower tweezer and calculated minimal energy difference to other states
if mask_left[np.nanargmin(new_energies[0])]:
energy_GS = energies_right[:,0]
state_GS = states_right[:,0]
#smallest energy difference
delta_E_min = np.nanmin(np.abs(energy_GS[:,np.newaxis] - energies_left))
#final occupation if two GS are involved
occ_numbers = np.array([0,np.searchsorted(energies_left[-1],energy_GS[-1])])
else:
energy_GS = energies_left[:,0]
state_GS = states_left[:,0]
#smallest energy difference
delta_E_min = np.nanmin(np.abs(energy_GS[:,np.newaxis] - energies_right))
#final occupation if two GS are involved
occ_numbers = np.array([0,np.searchsorted(energies_right[-1],energy_GS[-1])])
if return_deltaE:
return energies_left, energies_right, states_left, states_right, delta_E_min, occ_numbers
else:
return energies_left, energies_right, states_left, states_right

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,250 @@
% Created on 20210203
% By Guoxian Su
clear
close all
% clc
figure('Position',[2000 100 600 150])
%% Basis Generation
initialstate=[1 1];
title_ini=num2str(initialstate);
% initialstate=sparse(initialstate);
periotic=0;
NParticle=sum(initialstate);
NSite=length(initialstate);
% Basis Generation
Partition=intpartitions(NParticle,NSite);
basis=[];
for ii=1:length(Partition)
tmp=cell2mat(Partition(ii));
if max(tmp)<=6
if max(tmp)>1
C=nchoosek(1:NSite,length(tmp));
tmp=perms(tmp);
tmp=unique(tmp,'rows');
basisTmp=[];
for jj=1:size(C,1)
basisTmp1=zeros(size(tmp,1),NSite);
for kk=1:size(basisTmp1,1)
basisTmp1(kk,C(jj,:))=tmp(kk,:);
end
basisTmp=cat(1,basisTmp,basisTmp1);
end
basisTmp=unique(basisTmp,'rows');
else
C=nchoosek(1:NSite,NSite-length(tmp));
basisTmp=ones(size(C,1),NSite);
for jj=1:size(C,1)
basisTmp(jj,C(jj,:))=0;
end
end
% tmp=padarray(tmp,(NSite-length(tmp)),'post')';
% basisTmp=perms(tmp);
% basisTmp=unique(basisTmp,'rows');
basis=cat(1,basis,basisTmp);
end
end
basis=unique(basis,'rows');
HSpaceSize=size(basis,1);
disp(['Hspace=',num2str(HSpaceSize)])
disp(' ')
inindex=find(sum(abs(basis-initialstate),2)==0);
initialstate=zeros(HSpaceSize,1);
initialstate(inindex)=1;
%% H constraction
tic
% Hopping
T=zeros(HSpaceSize);
% T=sparse(T);
for ii=1:HSpaceSize
for jj=1:HSpaceSize
tmp=basis(ii,:)-basis(jj,:);
if length(find(tmp))==2
% if periotic==1
% hopping=[tmp(2:end),tmp(1)].*tmp;
% else
hopping=[tmp(2:end),0].*tmp;
% end
if length(find(hopping))==1
if sum(hopping)==-1
sit1=find(tmp==1);
sit2=find(tmp==-1);
% if (sit1+sit2==5)||(sit1+sit2==9)
T(ii,jj)=-sqrt(max(basis(ii,sit1),basis(jj,sit1)))*...
sqrt(max(basis(ii,sit2),basis(jj,sit2)));
% disp([ii,jj,T(ii,jj)])
% else
% T(ii,jj)=-sqrt(max(basis(ii,sit1),basis(jj,sit1)))*...
% sqrt(max(basis(ii,sit2),basis(jj,sit2)))*t2;
% end
end
end
end
end
end
% Interection
U=zeros(HSpaceSize);
% U=sparse(U);
for ii=1:HSpaceSize
tmp=basis(ii,:);
U(ii,ii)=sum(tmp.*(tmp-1))*0.5;
end
disp('H Constracted')
toc
disp(' ')
%% Evolution
t1_l=[12];%between
t2_l=t1_l;%inside
u_l=20;
Delta=0;
delta_insideDW_l=0;
% gauge (2,0) or (1,3)
up=(mod(1:NSite,4)==2).*(basis==0)+(mod(1:NSite,4)==0).*(basis==2);
down=(mod(1:NSite,4)==2).*(basis==2)+(mod(1:NSite,4)==0).*(basis==0);
phi_total=[];
for qq=1:length(t1_l)
t1=t1_l(qq);
t2=t1;
u=u_l(qq);
delta_insideDW=delta_insideDW_l(qq);
T1=T*t1;
U1=U*u;
% Stagger
D=zeros(HSpaceSize);
D=sparse(D);
for ii=1:HSpaceSize
tmp=basis(ii,:);
D(ii,ii)=...
+sum((NSite-1:-1:0).*tmp)*Delta...
+sum(-(~mod(1:NSite,2)).*tmp+mod(1:NSite,2).*tmp)*delta_insideDW*0.5;
% sum([1 1 0 0 0 0 0 0].*tmp.*2+[0 0 1 1].*tmp)*delta+...
end
H=T1+U1+D;
[V,E]=eig(H);
E=diag(E);
tspan=0.5;
Nt=200;
time=linspace(0,tspan,Nt);
tic
Evolution=expm(-1i*2*pi*H*time(2));
disp('Transfer Matrix Caculated')
toc
disp(' ')
%%
phi=zeros(HSpaceSize,length(time));
odd=zeros(1,length(time));
even=zeros(1,length(time));
eps=zeros(1,length(time));
g=zeros(1,length(time));
oddSingle=zeros(1,length(time));
singleOccu=(mod(basis,2));
tic
tmpState=initialstate;
for tt=1:length(time)
tmp=abs(tmpState).^2;
g(tt)=sum(tmpState.*initialstate);
% tmp=abs(expm(-1i*2*pi*H*time(tt))*initialstate).^2;
phi(:,tt)=tmp;
odd(tt)=sum(sum(singleOccu.*mod(1:NSite,2),2).*tmp);
even(tt)=sum(sum(singleOccu.*(~mod(1:NSite,2)),2).*tmp);
% disp(sum((up-down).*tmp,1))
eps(tt)=sum(sum((-up+down).*tmp,1))*0.5;
oddSingle(tt)=sum(sum(singleOccu.*mod(1:NSite,2).*tmp,1));
% Num(tt,:)=sum(tmp.*basis,1);
plot(time,phi(1,:),'--','LineWidth',1.2)
hold on
plot(time,phi(2,:),'-','LineWidth',1.2)
hold on
plot(time,phi(3,:),'--','LineWidth',1.2)
% Add labels
xlabel('time') % Label for x-axis
ylabel('probability') % Label for y-axis
title(sprintf('J: %.1f , U: %.1f , Delta: %.1f', t1_l(1),u_l(1),Delta))
% Add legend
legend('20', '11', '02')
grid on
hold off
ylim([0 1])
drawnow
tmpState=Evolution*tmpState;
end
disp('Evolution Finished')
toc
%%
L=abs(g).^2;
lambda=-log(L)/(2*NSite);
phi_total=cat(3,phi_total,phi);
end
%%
yticks([0:0.5:1])
xticks([0:0.125:0.5])
xticklabels({})
% legend('left','right')
set(findall(gcf,'-property','FontSize'),'FontSize',18)
set(findall(gcf,'-property','FontName'),'FontName','Times New Roman')
savingdir='C:\Users\naeve\Ferdy-Repo\merging_tweezer_code\hubbard_matlab';
set(gcf,'Renderer','painters')
print(gcf,[savingdir,'\','HOM',title_ini,'.svg'],'-dsvg')
%
% figure('Position',[0 0 500 500])
% imagesc(T)
% axis equal
% axis off
% colormap jet
% figure(2)
% hold on
% errorbar(expdata65(:,1),expdata65(:,2)/2,expdata65(:,3)/2,'o')
%
% figure(1)
% hold on
% % plot(linspace(0,120,199),RBData(:,2)*1.5,'-')
% plot(zhoudata(:,1),zhoudata(:,2),'-')
%
% xlim([0,20])
% errorbar(expdata109(:,1),expdata109(:,2),expdata109(:,3),'o')

View File

@ -0,0 +1,151 @@
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.0//EN'
'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd'>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" style="fill-opacity:1; color-rendering:auto; color-interpolation:auto; text-rendering:auto; stroke:black; stroke-linecap:square; stroke-miterlimit:10; shape-rendering:auto; stroke-opacity:1; fill:black; stroke-dasharray:none; font-weight:normal; stroke-width:1; font-family:'Dialog'; font-style:normal; stroke-linejoin:miter; font-size:12px; stroke-dashoffset:0; image-rendering:auto;" width="600" height="150" xmlns="http://www.w3.org/2000/svg"
><!--Generated by the Batik Graphics2D SVG Generator--><defs id="genericDefs"
/><g
><defs id="defs1"
><clipPath clipPathUnits="userSpaceOnUse" id="clipPath1"
><path d="M0 0 L600 0 L600 150 L0 150 L0 0 Z"
/></clipPath
><font horiz-adv-x="77.7832" id="font1"
><font-face ascent="91.23535" descent="19.506836" units-per-em="100" style="font-style:normal; font-family:Times New Roman; font-weight:normal;"
/><missing-glyph horiz-adv-x="77.7832" d="M13.875 0 L13.875 62.5 L63.875 62.5 L63.875 0 L13.875 0 ZM15.4375 1.5625 L62.3125 1.5625 L62.3125 60.9375 L15.4375 60.9375 L15.4375 1.5625 Z"
/><glyph unicode="e" horiz-adv-x="44.384766" d="M10.6406 27.875 Q10.5938 17.9219 15.4844 12.25 Q20.3594 6.5938 26.9531 6.5938 Q31.3438 6.5938 34.5938 9.0078 Q37.8438 11.4219 40.0469 17.2812 L41.5469 16.3125 Q40.5312 9.625 35.6016 4.125 Q30.6719 -1.375 23.25 -1.375 Q15.1875 -1.375 9.4531 4.9062 Q3.7188 11.1875 3.7188 21.7812 Q3.7188 33.25 9.6016 39.6719 Q15.4844 46.0938 24.3594 46.0938 Q31.8906 46.0938 36.7188 41.1406 Q41.5469 36.1875 41.5469 27.875 L10.6406 27.875 ZM10.6406 30.7188 L31.3438 30.7188 Q31.1094 35.0156 30.3281 36.7656 Q29.1094 39.5 26.6875 41.0625 Q24.2656 42.625 21.625 42.625 Q17.5781 42.625 14.3828 39.4766 Q11.1875 36.3281 10.6406 30.7188 Z"
/><glyph unicode="m" horiz-adv-x="77.7832" d="M16.4062 36.5312 Q21.2969 41.4062 22.1719 42.1406 Q24.3594 44 26.8984 45.0234 Q29.4375 46.0469 31.9375 46.0469 Q36.1406 46.0469 39.1641 43.6016 Q42.1875 41.1562 43.2188 36.5312 Q48.25 42.3906 51.7109 44.2188 Q55.1719 46.0469 58.8438 46.0469 Q62.4062 46.0469 65.1641 44.2188 Q67.9219 42.3906 69.5312 38.2344 Q70.6094 35.4062 70.6094 29.3438 L70.6094 10.1094 Q70.6094 5.9062 71.2344 4.3438 Q71.7344 3.2656 73.0469 2.5156 Q74.3594 1.7656 77.3438 1.7656 L77.3438 0 L55.2812 0 L55.2812 1.7656 L56.2031 1.7656 Q59.0781 1.7656 60.6875 2.875 Q61.8125 3.6562 62.3125 5.375 Q62.5 6.2031 62.5 10.1094 L62.5 29.3438 Q62.5 34.8125 61.1875 37.0625 Q59.2812 40.1875 55.0781 40.1875 Q52.4844 40.1875 49.875 38.8906 Q47.2656 37.5938 43.5625 34.0781 L43.4531 33.5469 L43.5625 31.4531 L43.5625 10.1094 Q43.5625 5.5156 44.0703 4.3906 Q44.5781 3.2656 45.9922 2.5156 Q47.4062 1.7656 50.8281 1.7656 L50.8281 0 L28.2188 0 L28.2188 1.7656 Q31.9375 1.7656 33.3281 2.6406 Q34.7188 3.5156 35.25 5.2812 Q35.5 6.1094 35.5 10.1094 L35.5 29.3438 Q35.5 34.8125 33.8906 37.2031 Q31.7344 40.3281 27.875 40.3281 Q25.25 40.3281 22.6562 38.9219 Q18.6094 36.7656 16.4062 34.0781 L16.4062 10.1094 Q16.4062 5.7188 17.0156 4.3984 Q17.625 3.0781 18.8203 2.4219 Q20.0156 1.7656 23.6875 1.7656 L23.6875 0 L1.5625 0 L1.5625 1.7656 Q4.6406 1.7656 5.8594 2.4219 Q7.0781 3.0781 7.7109 4.5156 Q8.3438 5.9531 8.3438 10.1094 L8.3438 27.2031 Q8.3438 34.5781 7.9062 36.7188 Q7.5625 38.3281 6.8359 38.9375 Q6.1094 39.5469 4.8281 39.5469 Q3.4688 39.5469 1.5625 38.8125 L0.8281 40.5781 L14.3125 46.0469 L16.4062 46.0469 L16.4062 36.5312 Z"
/><glyph unicode="i" horiz-adv-x="27.783203" d="M14.5 69.4375 Q16.5469 69.4375 17.9922 67.9922 Q19.4375 66.5469 19.4375 64.5 Q19.4375 62.4531 17.9922 60.9844 Q16.5469 59.5156 14.5 59.5156 Q12.4531 59.5156 10.9844 60.9844 Q9.5156 62.4531 9.5156 64.5 Q9.5156 66.5469 10.9609 67.9922 Q12.4062 69.4375 14.5 69.4375 ZM18.5625 46.0469 L18.5625 10.1094 Q18.5625 5.9062 19.1719 4.5156 Q19.7812 3.125 20.9766 2.4453 Q22.1719 1.7656 25.3438 1.7656 L25.3438 0 L3.6094 0 L3.6094 1.7656 Q6.8906 1.7656 8.0078 2.3984 Q9.125 3.0312 9.7891 4.4922 Q10.4531 5.9531 10.4531 10.1094 L10.4531 27.3438 Q10.4531 34.625 10.0156 36.7656 Q9.6719 38.3281 8.9375 38.9375 Q8.2031 39.5469 6.9375 39.5469 Q5.5625 39.5469 3.6094 38.8125 L2.9375 40.5781 L16.4062 46.0469 L18.5625 46.0469 Z"
/><glyph unicode="t" horiz-adv-x="27.783203" d="M16.1094 59.4219 L16.1094 44.7344 L26.5625 44.7344 L26.5625 41.3125 L16.1094 41.3125 L16.1094 12.3125 Q16.1094 7.9531 17.3594 6.4453 Q18.6094 4.9375 20.5625 4.9375 Q22.1719 4.9375 23.6875 5.9375 Q25.2031 6.9375 26.0312 8.8906 L27.9375 8.8906 Q26.2188 4.1094 23.0938 1.6875 Q19.9688 -0.7344 16.6562 -0.7344 Q14.4062 -0.7344 12.2578 0.5156 Q10.1094 1.7656 9.0859 4.0781 Q8.0625 6.3906 8.0625 11.2344 L8.0625 41.3125 L0.9844 41.3125 L0.9844 42.9219 Q3.6562 44 6.4688 46.5625 Q9.2812 49.125 11.4688 52.6406 Q12.5938 54.5 14.5938 59.4219 L16.1094 59.4219 Z"
/><glyph unicode="0" horiz-adv-x="50.0" d="M3.6094 32.7188 Q3.6094 44.0469 7.0312 52.2266 Q10.4531 60.4062 16.1094 64.4062 Q20.5156 67.5781 25.2031 67.5781 Q32.8125 67.5781 38.875 59.8125 Q46.4375 50.2031 46.4375 33.7344 Q46.4375 22.2188 43.1172 14.1641 Q39.7969 6.1094 34.6484 2.4688 Q29.5 -1.1719 24.7031 -1.1719 Q15.2344 -1.1719 8.9375 10.0156 Q3.6094 19.4375 3.6094 32.7188 ZM13.1875 31.5 Q13.1875 17.8281 16.5469 9.1875 Q19.3438 1.9062 24.8594 1.9062 Q27.4844 1.9062 30.3203 4.2734 Q33.1562 6.6406 34.625 12.2031 Q36.8594 20.6094 36.8594 35.8906 Q36.8594 47.2188 34.5156 54.7812 Q32.7656 60.4062 29.9844 62.75 Q27.9844 64.3594 25.1406 64.3594 Q21.8281 64.3594 19.2344 61.375 Q15.7188 57.3281 14.4531 48.6328 Q13.1875 39.9375 13.1875 31.5 Z"
/><glyph unicode="5" horiz-adv-x="50.0" d="M43.4062 66.2188 L39.5938 57.9062 L19.6719 57.9062 L15.3281 49.0312 Q28.2656 47.125 35.8438 39.4062 Q42.3281 32.7656 42.3281 23.7812 Q42.3281 18.5625 40.2109 14.1172 Q38.0938 9.6719 34.8672 6.5469 Q31.6406 3.4219 27.6875 1.5156 Q22.0781 -1.1719 16.1562 -1.1719 Q10.2031 -1.1719 7.4922 0.8516 Q4.7812 2.875 4.7812 5.3281 Q4.7812 6.6875 5.9062 7.7422 Q7.0312 8.7969 8.7344 8.7969 Q10.0156 8.7969 10.9688 8.4062 Q11.9219 8.0156 14.2031 6.3906 Q17.875 3.8594 21.625 3.8594 Q27.3438 3.8594 31.6641 8.1797 Q35.9844 12.5 35.9844 18.7031 Q35.9844 24.7031 32.125 29.9062 Q28.2656 35.1094 21.4844 37.9375 Q16.1562 40.1406 6.9844 40.4844 L19.6719 66.2188 L43.4062 66.2188 Z"
/><glyph unicode="." horiz-adv-x="25.0" d="M12.5 9.4688 Q14.7969 9.4688 16.3594 7.8828 Q17.9219 6.2969 17.9219 4.0469 Q17.9219 1.8125 16.3359 0.2188 Q14.75 -1.375 12.5 -1.375 Q10.25 -1.375 8.6641 0.2188 Q7.0781 1.8125 7.0781 4.0469 Q7.0781 6.3438 8.6641 7.9062 Q10.25 9.4688 12.5 9.4688 Z"
/><glyph unicode="1" horiz-adv-x="50.0" d="M11.7188 59.7188 L27.8281 67.5781 L29.4375 67.5781 L29.4375 11.6719 Q29.4375 6.1094 29.9062 4.7422 Q30.375 3.375 31.8359 2.6406 Q33.2969 1.9062 37.7969 1.8125 L37.7969 0 L12.8906 0 L12.8906 1.8125 Q17.5781 1.9062 18.9453 2.6172 Q20.3125 3.3281 20.8516 4.5234 Q21.3906 5.7188 21.3906 11.6719 L21.3906 47.4062 Q21.3906 54.6406 20.9062 56.6875 Q20.5625 58.25 19.6562 58.9844 Q18.75 59.7188 17.4844 59.7188 Q15.6719 59.7188 12.4531 58.2031 L11.7188 59.7188 Z"
/><glyph unicode="y" horiz-adv-x="50.0" d="M0.5938 44.7344 L21.4375 44.7344 L21.4375 42.9219 L20.4062 42.9219 Q18.2188 42.9219 17.1172 41.9688 Q16.0156 41.0156 16.0156 39.5938 Q16.0156 37.7031 17.625 34.3281 L28.5156 11.7656 L38.5312 36.4688 Q39.3594 38.4844 39.3594 40.4375 Q39.3594 41.3125 39.0156 41.75 Q38.625 42.2812 37.7969 42.6016 Q36.9688 42.9219 34.8594 42.9219 L34.8594 44.7344 L49.4219 44.7344 L49.4219 42.9219 Q47.6094 42.7188 46.6328 42.1328 Q45.6562 41.5469 44.4844 39.9375 Q44.0469 39.2656 42.8281 36.1875 L24.6094 -8.4531 Q21.9688 -14.9375 17.6953 -18.2578 Q13.4219 -21.5781 9.4688 -21.5781 Q6.5938 -21.5781 4.7344 -19.9219 Q2.875 -18.2656 2.875 -16.1094 Q2.875 -14.0625 4.2188 -12.8203 Q5.5625 -11.5781 7.9062 -11.5781 Q9.5156 -11.5781 12.3125 -12.6406 Q14.2656 -13.375 14.75 -13.375 Q16.2188 -13.375 17.9453 -11.8672 Q19.6719 -10.3594 21.4375 -6 L24.6094 1.7656 L8.5469 35.5 Q7.8125 37.0156 6.2031 39.2656 Q4.9844 40.9688 4.2031 41.5469 Q3.0781 42.3281 0.5938 42.9219 L0.5938 44.7344 Z"
/><glyph unicode="l" horiz-adv-x="27.783203" d="M18.5 69.4375 L18.5 10.1094 Q18.5 5.9062 19.1172 4.5391 Q19.7344 3.1719 21 2.4688 Q22.2656 1.7656 25.7344 1.7656 L25.7344 0 L3.8125 0 L3.8125 1.7656 Q6.8906 1.7656 8.0078 2.3984 Q9.125 3.0312 9.7656 4.4922 Q10.4062 5.9531 10.4062 10.1094 L10.4062 50.7344 Q10.4062 58.2969 10.0625 60.0312 Q9.7188 61.7656 8.9609 62.3984 Q8.2031 63.0312 7.0312 63.0312 Q5.7656 63.0312 3.8125 62.25 L2.9844 63.9688 L16.3125 69.4375 L18.5 69.4375 Z"
/><glyph unicode="a" horiz-adv-x="44.384766" d="M28.4688 6.4531 Q21.5781 1.125 19.8281 0.2969 Q17.1875 -0.9219 14.2031 -0.9219 Q9.5781 -0.9219 6.5703 2.25 Q3.5625 5.4219 3.5625 10.5938 Q3.5625 13.875 5.0312 16.2656 Q7.0312 19.5781 11.9844 22.5078 Q16.9375 25.4375 28.4688 29.6406 L28.4688 31.3906 Q28.4688 38.0938 26.3438 40.5781 Q24.2188 43.0625 20.1719 43.0625 Q17.0938 43.0625 15.2812 41.4062 Q13.4219 39.75 13.4219 37.5938 L13.5312 34.7656 Q13.5312 32.5156 12.3828 31.2969 Q11.2344 30.0781 9.375 30.0781 Q7.5625 30.0781 6.4219 31.3516 Q5.2812 32.625 5.2812 34.8125 Q5.2812 39.0156 9.5781 42.5312 Q13.875 46.0469 21.625 46.0469 Q27.5938 46.0469 31.3906 44.0469 Q34.2812 42.5312 35.6406 39.3125 Q36.5312 37.2031 36.5312 30.7188 L36.5312 15.5312 Q36.5312 9.125 36.7734 7.6875 Q37.0156 6.25 37.5781 5.7656 Q38.1406 5.2812 38.875 5.2812 Q39.6562 5.2812 40.2344 5.6094 Q41.2656 6.25 44.1875 9.1875 L44.1875 6.4531 Q38.7188 -0.875 33.7344 -0.875 Q31.3438 -0.875 29.9297 0.7812 Q28.5156 2.4375 28.4688 6.4531 ZM28.4688 9.625 L28.4688 26.6562 Q21.0938 23.7344 18.9531 22.5156 Q15.0938 20.3594 13.4297 18.0156 Q11.7656 15.6719 11.7656 12.8906 Q11.7656 9.375 13.8672 7.0547 Q15.9688 4.7344 18.7031 4.7344 Q22.4062 4.7344 28.4688 9.625 Z"
/><glyph unicode="b" horiz-adv-x="50.0" d="M15.375 37.0156 Q21.875 46.0469 29.3906 46.0469 Q36.2812 46.0469 41.4062 40.1641 Q46.5312 34.2812 46.5312 24.0781 Q46.5312 12.1562 38.625 4.8906 Q31.8438 -1.375 23.4844 -1.375 Q19.5781 -1.375 15.5547 0.0469 Q11.5312 1.4688 7.3281 4.2969 L7.3281 50.6406 Q7.3281 58.25 6.9609 60.0078 Q6.5938 61.7656 5.8125 62.3984 Q5.0312 63.0312 3.8594 63.0312 Q2.4844 63.0312 0.4375 62.25 L-0.25 63.9688 L13.1875 69.4375 L15.375 69.4375 L15.375 37.0156 ZM15.375 33.8906 L15.375 7.125 Q17.875 4.6875 20.5312 3.4453 Q23.1875 2.2031 25.9844 2.2031 Q30.4219 2.2031 34.2578 7.0859 Q38.0938 11.9688 38.0938 21.2969 Q38.0938 29.8906 34.2578 34.5 Q30.4219 39.1094 25.5312 39.1094 Q22.9531 39.1094 20.3594 37.7969 Q18.4062 36.8125 15.375 33.8906 Z"
/><glyph unicode="o" horiz-adv-x="50.0" d="M25 46.0469 Q35.1562 46.0469 41.3125 38.3281 Q46.5312 31.7344 46.5312 23.1875 Q46.5312 17.1875 43.6484 11.0391 Q40.7656 4.8906 35.7188 1.7578 Q30.6719 -1.375 24.4688 -1.375 Q14.3594 -1.375 8.4062 6.6875 Q3.375 13.4844 3.375 21.9219 Q3.375 28.0781 6.4219 34.1562 Q9.4688 40.2344 14.4531 43.1406 Q19.4375 46.0469 25 46.0469 ZM23.4844 42.875 Q20.9062 42.875 18.2891 41.3359 Q15.6719 39.7969 14.0625 35.9375 Q12.4531 32.0781 12.4531 26.0312 Q12.4531 16.2656 16.3359 9.1797 Q20.2188 2.0938 26.5625 2.0938 Q31.2969 2.0938 34.375 6 Q37.4531 9.9062 37.4531 19.4375 Q37.4531 31.3438 32.3281 38.1875 Q28.8594 42.875 23.4844 42.875 Z"
/><glyph unicode="r" horiz-adv-x="33.30078" d="M16.2188 46.0469 L16.2188 35.9844 Q21.8281 46.0469 27.7344 46.0469 Q30.4219 46.0469 32.1797 44.4141 Q33.9375 42.7812 33.9375 40.625 Q33.9375 38.7188 32.6641 37.3984 Q31.3906 36.0781 29.6406 36.0781 Q27.9375 36.0781 25.8125 37.7656 Q23.6875 39.4531 22.6562 39.4531 Q21.7812 39.4531 20.75 38.4844 Q18.5625 36.4688 16.2188 31.8906 L16.2188 10.4531 Q16.2188 6.7344 17.1406 4.8281 Q17.7812 3.5156 19.3906 2.6406 Q21 1.7656 24.0312 1.7656 L24.0312 0 L1.125 0 L1.125 1.7656 Q4.5469 1.7656 6.2031 2.8281 Q7.4219 3.6094 7.9062 5.3281 Q8.1562 6.1562 8.1562 10.0625 L8.1562 27.3906 Q8.1562 35.2031 7.8359 36.6953 Q7.5156 38.1875 6.6641 38.8672 Q5.8125 39.5469 4.5469 39.5469 Q3.0312 39.5469 1.125 38.8125 L0.6406 40.5781 L14.1562 46.0469 L16.2188 46.0469 Z"
/><glyph unicode="p" horiz-adv-x="50.0" d="M-0.0938 40.2812 L13.6719 45.8438 L15.5312 45.8438 L15.5312 35.4062 Q19 41.3125 22.4922 43.6797 Q25.9844 46.0469 29.8281 46.0469 Q36.5781 46.0469 41.0625 40.7656 Q46.5781 34.3281 46.5781 23.9688 Q46.5781 12.4062 39.9375 4.8281 Q34.4688 -1.375 26.1719 -1.375 Q22.5625 -1.375 19.9219 -0.3438 Q17.9688 0.3906 15.5312 2.5938 L15.5312 -11.0312 Q15.5312 -15.625 16.0938 -16.8672 Q16.6562 -18.1094 18.0469 -18.8438 Q19.4375 -19.5781 23.0938 -19.5781 L23.0938 -21.3906 L-0.3438 -21.3906 L-0.3438 -19.5781 L0.875 -19.5781 Q3.5625 -19.625 5.4688 -18.5625 Q6.3906 -18.0156 6.9062 -16.8203 Q7.4219 -15.625 7.4219 -10.75 L7.4219 31.5469 Q7.4219 35.8906 7.0312 37.0625 Q6.6406 38.2344 5.7891 38.8203 Q4.9375 39.4062 3.4688 39.4062 Q2.2969 39.4062 0.4844 38.7188 L-0.0938 40.2812 ZM15.5312 32.5156 L15.5312 15.8281 Q15.5312 10.4062 15.9688 8.6875 Q16.6562 5.8594 19.3125 3.7109 Q21.9688 1.5625 26.0312 1.5625 Q30.9062 1.5625 33.9375 5.375 Q37.8906 10.3594 37.8906 19.3906 Q37.8906 29.6406 33.4062 35.1562 Q30.2812 38.9688 25.9844 38.9688 Q23.6406 38.9688 21.3438 37.7969 Q19.5781 36.9219 15.5312 32.5156 Z"
/><glyph unicode="2" horiz-adv-x="50.0" d="M45.8438 12.75 L41.2188 0 L2.1562 0 L2.1562 1.8125 Q19.3906 17.5312 26.4219 27.4922 Q33.4531 37.4531 33.4531 45.7031 Q33.4531 52 29.5938 56.0547 Q25.7344 60.1094 20.3594 60.1094 Q15.4844 60.1094 11.6016 57.25 Q7.7188 54.3906 5.8594 48.875 L4.0469 48.875 Q5.2812 57.9062 10.3281 62.7422 Q15.375 67.5781 22.9531 67.5781 Q31 67.5781 36.3984 62.4062 Q41.7969 57.2344 41.7969 50.2031 Q41.7969 45.1719 39.4531 40.1406 Q35.8438 32.2344 27.7344 23.3906 Q15.5781 10.1094 12.5469 7.375 L29.8281 7.375 Q35.1094 7.375 37.2344 7.7656 Q39.3594 8.1562 41.0703 9.3516 Q42.7812 10.5469 44.0469 12.75 L45.8438 12.75 Z"
/></font
><font horiz-adv-x="77.7832" id="font2"
><font-face ascent="91.23535" descent="19.506836" units-per-em="100" style="font-style:normal; font-family:Times New Roman; font-weight:bold;"
/><missing-glyph horiz-adv-x="77.7832" d="M13.875 0 L13.875 62.5 L63.875 62.5 L63.875 0 L13.875 0 ZM15.4375 1.5625 L62.3125 1.5625 L62.3125 60.9375 L15.4375 60.9375 L15.4375 1.5625 Z"
/><glyph unicode="a" horiz-adv-x="50.0" d="M28.5625 6.7344 Q20.2188 -0.6406 13.5781 -0.6406 Q9.6719 -0.6406 7.0859 1.9297 Q4.5 4.5 4.5 8.3438 Q4.5 13.5781 8.9922 17.75 Q13.4844 21.9219 28.5625 28.8594 L28.5625 33.4531 Q28.5625 38.625 28 39.9688 Q27.4375 41.3125 25.875 42.3125 Q24.3125 43.3125 22.3594 43.3125 Q19.1875 43.3125 17.1406 41.8906 Q15.875 41.0156 15.875 39.8438 Q15.875 38.8125 17.2344 37.3125 Q19.0938 35.2031 19.0938 33.25 Q19.0938 30.8594 17.3125 29.1719 Q15.5312 27.4844 12.6406 27.4844 Q9.5781 27.4844 7.5 29.3438 Q5.4219 31.2031 5.4219 33.6875 Q5.4219 37.2031 8.2031 40.4062 Q10.9844 43.6094 15.9688 45.3125 Q20.9531 47.0156 26.3125 47.0156 Q32.8125 47.0156 36.5938 44.2578 Q40.375 41.5 41.5 38.2812 Q42.1875 36.2344 42.1875 28.8594 L42.1875 11.1406 Q42.1875 8.0156 42.4297 7.2031 Q42.6719 6.3906 43.1641 6 Q43.6562 5.6094 44.2812 5.6094 Q45.5625 5.6094 46.875 7.4219 L48.3438 6.25 Q45.9062 2.6406 43.2891 1 Q40.6719 -0.6406 37.3594 -0.6406 Q33.4531 -0.6406 31.25 1.1953 Q29.0469 3.0312 28.5625 6.7344 ZM28.5625 10.2969 L28.5625 25.5938 Q22.6562 22.125 19.7812 18.1719 Q17.875 15.5312 17.875 12.8438 Q17.875 10.5938 19.4844 8.8906 Q20.7031 7.5625 22.9062 7.5625 Q25.3438 7.5625 28.5625 10.2969 Z"
/><glyph unicode="t" horiz-adv-x="33.30078" d="M21.4844 62.3594 L21.4844 45.6562 L32.3281 45.6562 L32.3281 40.8281 L21.4844 40.8281 L21.4844 12.6406 Q21.4844 8.6875 21.8516 7.5391 Q22.2188 6.3906 23.1484 5.6875 Q24.0781 4.9844 24.8594 4.9844 Q28.0312 4.9844 30.8594 9.8125 L32.3281 8.7344 Q28.375 -0.6406 19.4844 -0.6406 Q15.1406 -0.6406 12.1328 1.7812 Q9.125 4.2031 8.2969 7.1719 Q7.8125 8.8438 7.8125 16.1562 L7.8125 40.8281 L1.8594 40.8281 L1.8594 42.5312 Q8.0156 46.875 12.3359 51.6641 Q16.6562 56.4531 19.875 62.3594 L21.4844 62.3594 Z"
/><glyph unicode="l" horiz-adv-x="27.783203" d="M21.2344 66.2188 L21.2344 9.4688 Q21.2344 4.6406 22.3594 3.2969 Q23.4844 1.9531 26.7656 1.7656 L26.7656 0 L2.0938 0 L2.0938 1.7656 Q5.125 1.8594 6.5938 3.5156 Q7.5625 4.6406 7.5625 9.4688 L7.5625 56.7344 Q7.5625 61.5312 6.4453 62.8672 Q5.3281 64.2031 2.0938 64.4062 L2.0938 66.2188 L21.2344 66.2188 Z"
/><glyph unicode="e" horiz-adv-x="44.384766" d="M42.0469 24.4688 L17 24.4688 Q17.4375 15.375 21.8281 10.1094 Q25.2031 6.0625 29.9375 6.0625 Q32.8594 6.0625 35.25 7.6953 Q37.6406 9.3281 40.375 13.5781 L42.0469 12.5 Q38.3281 4.9375 33.8359 1.7812 Q29.3438 -1.375 23.4375 -1.375 Q13.2812 -1.375 8.0625 6.4531 Q3.8594 12.75 3.8594 22.0781 Q3.8594 33.5 10.0391 40.2578 Q16.2188 47.0156 24.5156 47.0156 Q31.4531 47.0156 36.5547 41.3281 Q41.6562 35.6406 42.0469 24.4688 ZM30.0312 27.7344 Q30.0312 35.5938 29.1797 38.5234 Q28.3281 41.4531 26.5156 42.9688 Q25.4844 43.8438 23.7812 43.8438 Q21.2344 43.8438 19.625 41.3594 Q16.75 37.0156 16.75 29.4375 L16.75 27.7344 L30.0312 27.7344 Z"
/><glyph unicode="D" horiz-adv-x="72.2168" d="M1.3125 0 L1.3125 1.8125 L3.5156 1.8125 Q6.3906 1.8125 7.9297 2.7109 Q9.4688 3.6094 10.2031 5.1719 Q10.6406 6.2031 10.6406 11.3281 L10.6406 54.8906 Q10.6406 59.9688 10.1094 61.2344 Q9.5781 62.5 7.9375 63.4531 Q6.2969 64.4062 3.5156 64.4062 L1.3125 64.4062 L1.3125 66.2188 L30.9531 66.2188 Q42.8281 66.2188 50.0469 62.9844 Q58.8906 59.0312 63.4531 51.0234 Q68.0156 43.0156 68.0156 32.9062 Q68.0156 25.9219 65.7734 20.0391 Q63.5312 14.1562 59.9609 10.3281 Q56.3906 6.5 51.7344 4.1797 Q47.0781 1.8594 40.3281 0.5938 Q37.3594 0 30.9531 0 L1.3125 0 ZM26.5625 62.4531 L26.5625 10.7969 Q26.5625 6.6875 26.9531 5.7578 Q27.3438 4.8281 28.2656 4.3438 Q29.5938 3.6094 32.0781 3.6094 Q40.2344 3.6094 44.5312 9.1875 Q50.3906 16.7031 50.3906 32.5156 Q50.3906 45.2656 46.3906 52.875 Q43.2188 58.8438 38.2344 60.9844 Q34.7188 62.5 26.5625 62.4531 Z"
/><glyph unicode="U" horiz-adv-x="72.2168" d="M2.3438 66.2188 L36.5312 66.2188 L36.5312 64.4062 L34.8125 64.4062 Q30.9531 64.4062 29.5156 63.6016 Q28.0781 62.7969 27.4688 61.3828 Q26.8594 59.9688 26.8594 54.2969 L26.8594 21.875 Q26.8594 12.9844 28.2031 10.1094 Q29.5469 7.2344 32.6172 5.3281 Q35.6875 3.4219 40.375 3.4219 Q45.75 3.4219 49.5391 5.8359 Q53.3281 8.25 55.2031 12.5 Q57.0781 16.75 57.0781 27.2969 L57.0781 54.2969 Q57.0781 58.7344 56.1484 60.6406 Q55.2188 62.5469 53.8125 63.2812 Q51.6094 64.4062 47.6094 64.4062 L47.6094 66.2188 L70.5156 66.2188 L70.5156 64.4062 L69.1406 64.4062 Q66.3594 64.4062 64.5 63.2812 Q62.6406 62.1562 61.8125 59.9062 Q61.1875 58.3438 61.1875 54.2969 L61.1875 29.1562 Q61.1875 17.4844 59.6484 12.3047 Q58.1094 7.125 52.1484 2.7812 Q46.1875 -1.5625 35.8906 -1.5625 Q27.2969 -1.5625 22.6094 0.7344 Q16.2188 3.8594 13.5781 8.7422 Q10.9375 13.625 10.9375 21.875 L10.9375 54.2969 Q10.9375 60.0156 10.3047 61.4062 Q9.6719 62.7969 8.1094 63.625 Q6.5469 64.4531 2.3438 64.4062 L2.3438 66.2188 Z"
/><glyph unicode="," horiz-adv-x="25.0" d="M4.9844 -17.5312 L4.9844 -15.375 Q10.6406 -12.9375 13.2031 -9.0547 Q15.7656 -5.1719 15.7656 -1.125 Q15.7656 -0.2969 15.4375 0.1406 Q15.0938 0.5312 14.75 0.5312 Q14.4531 0.5312 13.875 0.2031 Q12.4531 -0.6406 10.3594 -0.6406 Q7.4688 -0.6406 5.3438 1.6094 Q3.2188 3.8594 3.2188 6.8906 Q3.2188 10.1094 5.6641 12.5469 Q8.1094 14.9844 11.5781 14.9844 Q15.7656 14.9844 18.75 11.7422 Q21.7344 8.5 21.7344 2.8281 Q21.7344 -4.1562 17.5078 -9.5703 Q13.2812 -14.9844 4.9844 -17.5312 Z"
/><glyph unicode="0" horiz-adv-x="50.0" d="M46.3438 33.1094 Q46.3438 23.1875 43.5625 14.5938 Q41.8906 9.3281 39.0859 5.9609 Q36.2812 2.5938 32.7188 0.6094 Q29.1562 -1.375 24.9531 -1.375 Q20.1719 -1.375 16.3125 1.0703 Q12.4531 3.5156 9.4688 8.0625 Q7.3281 11.375 5.7188 16.8906 Q3.6094 24.3594 3.6094 32.3281 Q3.6094 43.1094 6.6406 52.1562 Q9.125 59.625 14.2031 63.6016 Q19.2812 67.5781 24.9531 67.5781 Q30.7188 67.5781 35.7188 63.6484 Q40.7188 59.7188 43.0625 53.0781 Q46.3438 43.9531 46.3438 33.1094 ZM31.2969 33.2031 Q31.2969 50.5938 31.1094 53.4688 Q30.6094 60.25 28.7656 62.6406 Q27.5469 64.2031 24.8125 64.2031 Q22.7031 64.2031 21.4844 63.0312 Q19.6719 61.3281 19.0391 57.0078 Q18.4062 52.6875 18.4062 26.8594 Q18.4062 12.7969 19.3906 8.0156 Q20.125 4.5469 21.4922 3.375 Q22.8594 2.2031 25.0938 2.2031 Q27.5469 2.2031 28.7656 3.7656 Q30.8125 6.5 31.1094 12.2031 L31.2969 33.2031 Z"
/><glyph unicode="." horiz-adv-x="25.0" d="M12.5 15.0469 Q15.9219 15.0469 18.2891 12.6484 Q20.6562 10.25 20.6562 6.8906 Q20.6562 3.5156 18.2656 1.1484 Q15.875 -1.2188 12.5 -1.2188 Q9.125 -1.2188 6.7578 1.1484 Q4.3906 3.5156 4.3906 6.8906 Q4.3906 10.25 6.7578 12.6484 Q9.125 15.0469 12.5 15.0469 Z"
/><glyph unicode="2" horiz-adv-x="50.0" d="M41.4062 0 L2.4375 0 L2.4375 1.0781 Q20.3594 22.4062 24.1953 29.7812 Q28.0312 37.1562 28.0312 44.1875 Q28.0312 49.3125 24.8594 52.7109 Q21.6875 56.1094 17.0938 56.1094 Q9.5781 56.1094 5.4219 48.5781 L3.6094 49.2188 Q6.25 58.5938 11.625 63.0859 Q17 67.5781 24.0312 67.5781 Q29.0469 67.5781 33.2031 65.2344 Q37.3594 62.8906 39.7031 58.8125 Q42.0469 54.7344 42.0469 51.1719 Q42.0469 44.6719 38.4219 37.9844 Q33.5 28.9531 16.8906 12.7969 L31.2031 12.7969 Q36.4688 12.7969 38.0625 13.2344 Q39.6562 13.6719 40.6797 14.7188 Q41.7031 15.7656 43.3594 19.1406 L45.125 19.1406 L41.4062 0 Z"
/><glyph unicode="1" horiz-adv-x="50.0" d="M32.5625 67.5781 L32.5625 13.4219 Q32.5625 7.3281 33.1016 5.6406 Q33.6406 3.9531 35.3516 2.8828 Q37.0625 1.8125 40.8281 1.8125 L42.3281 1.8125 L42.3281 0 L7.4688 0 L7.4688 1.8125 L9.2344 1.8125 Q13.4844 1.8125 15.2891 2.7891 Q17.0938 3.7656 17.7266 5.4688 Q18.3594 7.1719 18.3594 13.4219 L18.3594 47.75 Q18.3594 52.3438 17.9219 53.4922 Q17.4844 54.6406 16.2891 55.4453 Q15.0938 56.25 13.5312 56.25 Q11.0312 56.25 7.4688 54.6875 L6.5938 56.4531 L30.9531 67.5781 L32.5625 67.5781 Z"
/><glyph unicode=" " horiz-adv-x="25.0" d=""
/><glyph unicode=":" horiz-adv-x="33.30078" d="M16.7031 46.9688 Q20.0625 46.9688 22.4375 44.6016 Q24.8125 42.2344 24.8125 38.875 Q24.8125 35.5 22.4375 33.1328 Q20.0625 30.7656 16.7031 30.7656 Q13.3281 30.7656 10.9609 33.1328 Q8.5938 35.5 8.5938 38.875 Q8.5938 42.2344 10.9609 44.6016 Q13.3281 46.9688 16.7031 46.9688 ZM16.6562 15.0469 Q20.0625 15.0469 22.4375 12.6484 Q24.8125 10.25 24.8125 6.8906 Q24.8125 3.5156 22.4141 1.1484 Q20.0156 -1.2188 16.6562 -1.2188 Q13.2812 -1.2188 10.9141 1.1484 Q8.5469 3.5156 8.5469 6.8906 Q8.5469 10.25 10.9141 12.6484 Q13.2812 15.0469 16.6562 15.0469 Z"
/><glyph unicode="J" horiz-adv-x="50.0" d="M14.2031 64.4062 L14.2031 66.2188 L49.4219 66.2188 L49.4219 64.4062 L47.2656 64.4062 Q44.3438 64.4062 42.5781 63.375 Q41.3594 62.7031 40.625 61.0312 Q40.0938 59.8594 40.0938 54.8906 L40.0938 22.4688 Q40.0938 12.9375 38.1875 8.6172 Q36.2812 4.2969 31.4219 1.3906 Q26.5625 -1.5156 19.7344 -1.5156 Q11.375 -1.5156 6.2266 2.8828 Q1.0781 7.2812 1.0781 12.5938 Q1.0781 16.0156 3.1797 18.1406 Q5.2812 20.2656 8.2969 20.2656 Q11.2812 20.2656 13.3047 18.3594 Q15.3281 16.4531 15.3281 13.6719 Q15.3281 12.3125 14.9844 11.2812 Q14.7969 10.7969 13.4531 8.8203 Q12.1094 6.8438 12.1094 6 Q12.1094 4.7344 13.375 3.8125 Q15.2344 2.4375 18.2188 2.4375 Q20.3125 2.4375 21.6797 3.5156 Q23.0469 4.5938 23.6094 6.8594 Q24.1719 9.125 24.1719 19.4844 L24.1719 54.8906 Q24.1719 59.9688 23.6094 61.2344 Q23.0469 62.5 21.4141 63.4531 Q19.7812 64.4062 17 64.4062 L14.2031 64.4062 Z"
/></font
></defs
><g style="fill:white; stroke:white;"
><rect x="0" y="0" width="600" style="clip-path:url(#clipPath1); stroke:none;" height="150"
/></g
><g style="fill:white; text-rendering:optimizeSpeed; color-rendering:optimizeSpeed; image-rendering:optimizeSpeed; shape-rendering:crispEdges; stroke:white; color-interpolation:sRGB;"
><rect x="0" width="600" height="150" y="0" style="stroke:none;"
/><path style="stroke:none;" d="M78 115 L543 115 L543 33 L78 33 Z"
/></g
><g style="stroke-linecap:butt; fill-opacity:0.149; fill:rgb(38,38,38); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; stroke-linejoin:round; stroke:rgb(38,38,38); color-interpolation:linearRGB; stroke-width:0.6667; stroke-opacity:0.149;"
><line y2="33" style="fill:none;" x1="78" x2="78" y1="115"
/><line y2="33" style="fill:none;" x1="194.25" x2="194.25" y1="115"
/><line y2="33" style="fill:none;" x1="310.5" x2="310.5" y1="115"
/><line y2="33" style="fill:none;" x1="426.75" x2="426.75" y1="115"
/><line y2="33" style="fill:none;" x1="543" x2="543" y1="115"
/><line y2="115" style="fill:none;" x1="543" x2="78" y1="115"
/><line y2="74" style="fill:none;" x1="543" x2="78" y1="74"
/><line y2="33" style="fill:none;" x1="543" x2="78" y1="33"
/><line x1="78" x2="543" y1="115" style="stroke-linecap:square; fill-opacity:1; fill:none; stroke-opacity:1;" y2="115"
/><line x1="78" x2="543" y1="33" style="stroke-linecap:square; fill-opacity:1; fill:none; stroke-opacity:1;" y2="33"
/><line x1="78" x2="78" y1="115" style="stroke-linecap:square; fill-opacity:1; fill:none; stroke-opacity:1;" y2="110.35"
/><line x1="194.25" x2="194.25" y1="115" style="stroke-linecap:square; fill-opacity:1; fill:none; stroke-opacity:1;" y2="110.35"
/><line x1="310.5" x2="310.5" y1="115" style="stroke-linecap:square; fill-opacity:1; fill:none; stroke-opacity:1;" y2="110.35"
/><line x1="426.75" x2="426.75" y1="115" style="stroke-linecap:square; fill-opacity:1; fill:none; stroke-opacity:1;" y2="110.35"
/><line x1="543" x2="543" y1="115" style="stroke-linecap:square; fill-opacity:1; fill:none; stroke-opacity:1;" y2="110.35"
/><line x1="78" x2="78" y1="33" style="stroke-linecap:square; fill-opacity:1; fill:none; stroke-opacity:1;" y2="37.65"
/><line x1="194.25" x2="194.25" y1="33" style="stroke-linecap:square; fill-opacity:1; fill:none; stroke-opacity:1;" y2="37.65"
/><line x1="310.5" x2="310.5" y1="33" style="stroke-linecap:square; fill-opacity:1; fill:none; stroke-opacity:1;" y2="37.65"
/><line x1="426.75" x2="426.75" y1="33" style="stroke-linecap:square; fill-opacity:1; fill:none; stroke-opacity:1;" y2="37.65"
/><line x1="543" x2="543" y1="33" style="stroke-linecap:square; fill-opacity:1; fill:none; stroke-opacity:1;" y2="37.65"
/></g
><g transform="translate(310.5002,122.8)" style="font-size:24px; fill:rgb(38,38,38); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; font-family:'Times New Roman'; stroke:rgb(38,38,38); color-interpolation:linearRGB;"
><text x="-21.5" xml:space="preserve" y="22" style="stroke:none;"
>time</text
></g
><g style="fill:rgb(38,38,38); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; stroke-linejoin:round; stroke:rgb(38,38,38); color-interpolation:linearRGB; stroke-width:0.6667;"
><line y2="33" style="fill:none;" x1="78" x2="78" y1="115"
/><line y2="33" style="fill:none;" x1="543" x2="543" y1="115"
/><line y2="115" style="fill:none;" x1="78" x2="82.65" y1="115"
/><line y2="74" style="fill:none;" x1="78" x2="82.65" y1="74"
/><line y2="33" style="fill:none;" x1="78" x2="82.65" y1="33"
/><line y2="115" style="fill:none;" x1="543" x2="538.35" y1="115"
/><line y2="74" style="fill:none;" x1="543" x2="538.35" y1="74"
/><line y2="33" style="fill:none;" x1="543" x2="538.35" y1="33"
/></g
><g transform="translate(70.5333,115)" style="font-size:24px; fill:rgb(38,38,38); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; font-family:'Times New Roman'; stroke:rgb(38,38,38); color-interpolation:linearRGB;"
><text x="-12" xml:space="preserve" y="8.5" style="stroke:none;"
>0</text
></g
><g transform="translate(70.5333,74)" style="font-size:24px; fill:rgb(38,38,38); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; font-family:'Times New Roman'; stroke:rgb(38,38,38); color-interpolation:linearRGB;"
><text x="-30" xml:space="preserve" y="8.5" style="stroke:none;"
>0.5</text
></g
><g transform="translate(70.5333,33)" style="font-size:24px; fill:rgb(38,38,38); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; font-family:'Times New Roman'; stroke:rgb(38,38,38); color-interpolation:linearRGB;"
><text x="-12" xml:space="preserve" y="8.5" style="stroke:none;"
>1</text
></g
><g transform="translate(36.5333,74) rotate(-90)" style="font-size:24px; fill:rgb(38,38,38); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; font-family:'Times New Roman'; stroke:rgb(38,38,38); color-interpolation:linearRGB;"
><text x="-53" xml:space="preserve" y="-5" style="stroke:none;"
>probability</text
></g
><g transform="translate(310.5003,28.4444)" style="font-size:24px; text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; font-family:'Times New Roman'; color-interpolation:linearRGB; font-weight:bold;"
><text x="-138" xml:space="preserve" y="-5" style="stroke:none;"
>J: 12.0 , U: 20.0 , Delta: 0.0</text
></g
><g style="stroke-linecap:butt; fill:rgb(0,114,189); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; stroke-linejoin:bevel; stroke-dasharray:10,6; stroke:rgb(0,114,189); color-interpolation:linearRGB; stroke-width:1.6; stroke-miterlimit:1;"
><path d="M78 115 L80.3367 109.4375 L82.6734 96.2927 L85.01 83.9376 L87.3467 80.2411 L89.6834 87.5576 L92.0201 101.2271 L94.3568 112.5436 L96.6935 114.2995 L99.0302 105.3765 L101.3668 91.4577 L103.7035 81.4079 L106.0402 81.628 L108.3769 91.9777 L110.7136 105.8653 L113.0502 114.4457 L115.3869 112.2542 L117.7236 100.6864 L120.0603 87.1099 L122.397 80.1716 L124.7337 84.2906 L127.0704 96.8433 L129.407 109.8351 L131.7437 114.9913 L134.0804 109.028 L136.4171 95.7433 L138.7538 83.5981 L141.0905 80.3278 L143.4271 88.0152 L145.7638 101.7643 L148.1005 112.8181 L150.4372 114.1365 L152.7739 104.8799 L155.1105 90.9437 L157.4472 81.204 L159.7839 81.8639 L162.1206 92.5033 L164.4573 106.3458 L166.794 114.5751 L169.1306 111.95 L171.4673 100.1424 L173.804 86.6726 L176.1407 80.1195 L178.4774 84.6568 L180.8141 97.3947 L183.1508 110.2204 L185.4874 114.9652 L187.8241 108.6071 L190.1608 95.1956 L192.4975 83.2725 L194.8342 80.4317 L197.1709 88.4824 L199.5075 102.2971 L201.8442 113.0773 L204.1809 113.957 L206.5176 104.376 L208.8543 90.4363 L211.1909 81.0162 L213.5276 82.1155 L215.8643 93.0338 L218.201 106.8175 L220.5377 114.6875 L222.8744 111.6315 L225.2111 99.5959 L227.5477 86.2461 L229.8844 80.0847 L232.2211 85.0358 L234.5578 97.9461 L236.8945 110.593 L239.2312 114.9217 L241.5678 108.1752 L243.9045 94.6503 L246.2412 82.9612 L248.5779 80.5526 L250.9146 88.9585 L253.2513 102.8253 L255.5879 113.3211 L257.9246 113.7612 L260.2613 103.8652 L262.598 89.936 L264.9347 80.845 L267.2714 82.3824 L269.608 93.5689 L271.9447 107.2799 L274.2814 114.7828 L276.6181 111.299 L278.9548 99.0473 L281.2915 85.8309 L283.6281 80.0673 L285.9648 85.4273 L288.3015 98.4972 L290.6382 110.9527 L292.9749 114.8609 L295.3116 107.7326 L297.6482 94.1079 L299.9849 82.6644 L302.3216 80.6904 L304.6583 89.4432 L306.995 103.3481 L309.3317 113.5491 L311.6683 113.5491 L314.005 103.3481 L316.3417 89.4432 L318.6784 80.6904 L321.0151 82.6644 L323.3518 94.1079 L325.6884 107.7326 L328.0251 114.8609 L330.3618 110.9527 L332.6985 98.4972 L335.0352 85.4273 L337.3719 80.0673 L339.7085 85.8309 L342.0452 99.0473 L344.3819 111.299 L346.7186 114.7828 L349.0553 107.2799 L351.3919 93.5689 L353.7286 82.3824 L356.0653 80.845 L358.402 89.936 L360.7387 103.8652 L363.0754 113.7612 L365.412 113.3211 L367.7487 102.8253 L370.0854 88.9585 L372.4221 80.5526 L374.7588 82.9612 L377.0955 94.6503 L379.4322 108.1752 L381.7688 114.9217 L384.1055 110.593 L386.4422 97.9461 L388.7789 85.0358 L391.1156 80.0847 L393.4522 86.2461 L395.7889 99.5959 L398.1256 111.6315 L400.4623 114.6875 L402.799 106.8175 L405.1357 93.0338 L407.4724 82.1155 L409.8091 81.0162 L412.1457 90.4363 L414.4824 104.376 L416.8191 113.957 L419.1558 113.0773 L421.4925 102.2971 L423.8291 88.4824 L426.1658 80.4317 L428.5025 83.2725 L430.8392 95.1956 L433.1759 108.6071 L435.5126 114.9652 L437.8492 110.2204 L440.1859 97.3947 L442.5226 84.6568 L444.8593 80.1195 L447.196 86.6726 L449.5327 100.1424 L451.8694 111.95 L454.2061 114.5751 L456.5427 106.3458 L458.8794 92.5033 L461.2161 81.8639 L463.5528 81.204 L465.8895 90.9437 L468.2261 104.8799 L470.5628 114.1365 L472.8995 112.8181 L475.2362 101.7643 L477.5729 88.0152 L479.9095 80.3278 L482.2462 83.5981 L484.5829 95.7433 L486.9196 109.028 L489.2563 114.9913 L491.593 109.8351 L493.9297 96.8433 L496.2663 84.2906 L498.603 80.1716 L500.9397 87.1099 L503.2764 100.6864 L505.6131 112.2542 L507.9498 114.4457 L510.2864 105.8653 L512.6231 91.9777 L514.9598 81.628 L517.2964 81.4079 L519.6332 91.4577 L521.9698 105.3765 L524.3065 114.2995 L526.6432 112.5436 L528.9799 101.2271 L531.3166 87.5576 L533.6533 80.2411 L535.9899 83.9376 L538.3267 96.2927 L540.6633 109.4375 L543 115" style="fill:none; fill-rule:evenodd;"
/></g
><g style="stroke-linecap:butt; fill:rgb(217,83,25); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; stroke-linejoin:round; stroke:rgb(217,83,25); color-interpolation:linearRGB; stroke-width:1.6;"
><path d="M78 33 L80.3367 44.125 L82.6734 70.4146 L85.01 95.1249 L87.3467 102.5178 L89.6834 87.8848 L92.0201 60.5457 L94.3568 37.9128 L96.6935 34.401 L99.0302 52.247 L101.3668 80.0847 L103.7035 100.1841 L106.0402 99.744 L108.3769 79.0446 L110.7136 51.2695 L113.0502 34.1085 L115.3869 38.4917 L117.7236 61.6273 L120.0603 88.7802 L122.397 102.6567 L124.7337 94.4189 L127.0704 69.3133 L129.407 43.3299 L131.7437 33.0174 L134.0804 44.9439 L136.4171 71.5135 L138.7538 95.8038 L141.0905 102.3444 L143.4271 86.9695 L145.7638 59.4715 L148.1005 37.3638 L150.4372 34.7269 L152.7739 53.2402 L155.1105 81.1126 L157.4472 100.5921 L159.7839 99.2722 L162.1206 77.9935 L164.4573 50.3085 L166.794 33.8498 L169.1306 39.1 L171.4673 62.7151 L173.804 89.6548 L176.1407 102.761 L178.4774 93.6865 L180.8141 68.2107 L183.1508 42.5592 L185.4874 33.0696 L187.8241 45.7858 L190.1608 72.6087 L192.4975 96.4549 L194.8342 102.1367 L197.1709 86.0353 L199.5075 58.4057 L201.8442 36.8453 L204.1809 35.0859 L206.5176 54.2481 L208.8543 82.1274 L211.1909 100.9675 L213.5276 98.7691 L215.8643 76.9323 L218.201 49.3651 L220.5377 33.625 L222.8744 39.7369 L225.2111 63.8082 L227.5477 90.5078 L229.8844 102.8307 L232.2211 92.9284 L234.5578 67.1077 L236.8945 41.8139 L239.2312 33.1566 L241.5678 46.6497 L243.9045 73.6993 L246.2412 97.0776 L248.5779 101.8949 L250.9146 85.083 L253.2513 57.3495 L255.5879 36.3578 L257.9246 35.4777 L260.2613 55.2696 L262.598 83.1281 L264.9347 101.31 L267.2714 98.2353 L269.608 75.8622 L271.9447 48.4402 L274.2814 33.4344 L276.6181 40.4021 L278.9548 64.9054 L281.2915 91.3382 L283.6281 102.8655 L285.9648 92.1454 L288.3015 66.0056 L290.6382 41.0946 L292.9749 33.2782 L295.3116 47.5348 L297.6482 74.7842 L299.9849 97.6713 L302.3216 101.6192 L304.6583 84.1136 L306.995 56.3037 L309.3317 35.9018 L311.6683 35.9018 L314.005 56.3037 L316.3417 84.1136 L318.6784 101.6192 L321.0151 97.6713 L323.3518 74.7842 L325.6884 47.5348 L328.0251 33.2782 L330.3618 41.0946 L332.6985 66.0056 L335.0352 92.1454 L337.3719 102.8655 L339.7085 91.3382 L342.0452 64.9054 L344.3819 40.4021 L346.7186 33.4344 L349.0553 48.4402 L351.3919 75.8622 L353.7286 98.2353 L356.0653 101.31 L358.402 83.1281 L360.7387 55.2696 L363.0754 35.4777 L365.412 36.3578 L367.7487 57.3495 L370.0854 85.083 L372.4221 101.8949 L374.7588 97.0776 L377.0955 73.6993 L379.4322 46.6497 L381.7688 33.1566 L384.1055 41.8139 L386.4422 67.1077 L388.7789 92.9284 L391.1156 102.8307 L393.4522 90.5078 L395.7889 63.8082 L398.1256 39.7369 L400.4623 33.625 L402.799 49.3651 L405.1357 76.9323 L407.4724 98.7691 L409.8091 100.9675 L412.1457 82.1274 L414.4824 54.2481 L416.8191 35.0859 L419.1558 36.8453 L421.4925 58.4057 L423.8291 86.0353 L426.1658 102.1367 L428.5025 96.4549 L430.8392 72.6087 L433.1759 45.7858 L435.5126 33.0696 L437.8492 42.5592 L440.1859 68.2107 L442.5226 93.6865 L444.8593 102.761 L447.196 89.6548 L449.5327 62.7151 L451.8694 39.1 L454.2061 33.8498 L456.5427 50.3085 L458.8794 77.9935 L461.2161 99.2722 L463.5528 100.5921 L465.8895 81.1126 L468.2261 53.2402 L470.5628 34.7269 L472.8995 37.3638 L475.2362 59.4715 L477.5729 86.9695 L479.9095 102.3444 L482.2462 95.8038 L484.5829 71.5135 L486.9196 44.9439 L489.2563 33.0174 L491.593 43.3299 L493.9297 69.3133 L496.2663 94.4189 L498.603 102.6567 L500.9397 88.7802 L503.2764 61.6273 L505.6131 38.4917 L507.9498 34.1085 L510.2864 51.2695 L512.6231 79.0446 L514.9598 99.744 L517.2964 100.1841 L519.6332 80.0847 L521.9698 52.247 L524.3065 34.401 L526.6432 37.9128 L528.9799 60.5457 L531.3166 87.8848 L533.6533 102.5178 L535.9899 95.1249 L538.3267 70.4146 L540.6633 44.125 L543 33" style="fill:none; fill-rule:evenodd;"
/></g
><g style="stroke-linecap:butt; fill:rgb(237,177,32); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; stroke-linejoin:bevel; stroke-dasharray:10,6; stroke:rgb(237,177,32); color-interpolation:linearRGB; stroke-width:1.6; stroke-miterlimit:1;"
><path d="M78 115 L80.3367 109.4375 L82.6734 96.2927 L85.01 83.9376 L87.3467 80.2411 L89.6834 87.5576 L92.0201 101.2271 L94.3568 112.5436 L96.6935 114.2995 L99.0302 105.3765 L101.3668 91.4577 L103.7035 81.4079 L106.0402 81.628 L108.3769 91.9777 L110.7136 105.8653 L113.0502 114.4457 L115.3869 112.2542 L117.7236 100.6864 L120.0603 87.1099 L122.397 80.1716 L124.7337 84.2906 L127.0704 96.8433 L129.407 109.8351 L131.7437 114.9913 L134.0804 109.028 L136.4171 95.7433 L138.7538 83.5981 L141.0905 80.3278 L143.4271 88.0152 L145.7638 101.7643 L148.1005 112.8181 L150.4372 114.1365 L152.7739 104.8799 L155.1105 90.9437 L157.4472 81.204 L159.7839 81.8639 L162.1206 92.5033 L164.4573 106.3458 L166.794 114.5751 L169.1306 111.95 L171.4673 100.1424 L173.804 86.6726 L176.1407 80.1195 L178.4774 84.6568 L180.8141 97.3947 L183.1508 110.2204 L185.4874 114.9652 L187.8241 108.6071 L190.1608 95.1956 L192.4975 83.2725 L194.8342 80.4317 L197.1709 88.4824 L199.5075 102.2971 L201.8442 113.0773 L204.1809 113.957 L206.5176 104.376 L208.8543 90.4363 L211.1909 81.0162 L213.5276 82.1155 L215.8643 93.0338 L218.201 106.8175 L220.5377 114.6875 L222.8744 111.6315 L225.2111 99.5959 L227.5477 86.2461 L229.8844 80.0847 L232.2211 85.0358 L234.5578 97.9461 L236.8945 110.593 L239.2312 114.9217 L241.5678 108.1752 L243.9045 94.6503 L246.2412 82.9612 L248.5779 80.5526 L250.9146 88.9585 L253.2513 102.8253 L255.5879 113.3211 L257.9246 113.7612 L260.2613 103.8652 L262.598 89.936 L264.9347 80.845 L267.2714 82.3824 L269.608 93.5689 L271.9447 107.2799 L274.2814 114.7828 L276.6181 111.299 L278.9548 99.0473 L281.2915 85.8309 L283.6281 80.0673 L285.9648 85.4273 L288.3015 98.4972 L290.6382 110.9527 L292.9749 114.8609 L295.3116 107.7326 L297.6482 94.1079 L299.9849 82.6644 L302.3216 80.6904 L304.6583 89.4432 L306.995 103.3481 L309.3317 113.5491 L311.6683 113.5491 L314.005 103.3481 L316.3417 89.4432 L318.6784 80.6904 L321.0151 82.6644 L323.3518 94.1079 L325.6884 107.7326 L328.0251 114.8609 L330.3618 110.9527 L332.6985 98.4972 L335.0352 85.4273 L337.3719 80.0673 L339.7085 85.8309 L342.0452 99.0473 L344.3819 111.299 L346.7186 114.7828 L349.0553 107.2799 L351.3919 93.5689 L353.7286 82.3824 L356.0653 80.845 L358.402 89.936 L360.7387 103.8652 L363.0754 113.7612 L365.412 113.3211 L367.7487 102.8253 L370.0854 88.9585 L372.4221 80.5526 L374.7588 82.9612 L377.0955 94.6503 L379.4322 108.1752 L381.7688 114.9217 L384.1055 110.593 L386.4422 97.9461 L388.7789 85.0358 L391.1156 80.0847 L393.4522 86.2461 L395.7889 99.5959 L398.1256 111.6315 L400.4623 114.6875 L402.799 106.8175 L405.1357 93.0338 L407.4724 82.1155 L409.8091 81.0162 L412.1457 90.4363 L414.4824 104.376 L416.8191 113.957 L419.1558 113.0773 L421.4925 102.2971 L423.8291 88.4824 L426.1658 80.4317 L428.5025 83.2725 L430.8392 95.1956 L433.1759 108.6071 L435.5126 114.9652 L437.8492 110.2204 L440.1859 97.3947 L442.5226 84.6568 L444.8593 80.1195 L447.196 86.6726 L449.5327 100.1424 L451.8694 111.95 L454.2061 114.5751 L456.5427 106.3458 L458.8794 92.5033 L461.2161 81.8639 L463.5528 81.204 L465.8895 90.9437 L468.2261 104.8799 L470.5628 114.1365 L472.8995 112.8181 L475.2362 101.7643 L477.5729 88.0152 L479.9095 80.3278 L482.2462 83.5981 L484.5829 95.7433 L486.9196 109.028 L489.2563 114.9913 L491.593 109.8351 L493.9297 96.8433 L496.2663 84.2906 L498.603 80.1716 L500.9397 87.1099 L503.2764 100.6864 L505.6131 112.2542 L507.9498 114.4457 L510.2864 105.8653 L512.6231 91.9777 L514.9598 81.628 L517.2964 81.4079 L519.6332 91.4577 L521.9698 105.3765 L524.3065 114.2995 L526.6432 112.5436 L528.9799 101.2271 L531.3166 87.5576 L533.6533 80.2411 L535.9899 83.9376 L538.3267 96.2927 L540.6633 109.4375 L543 115" style="fill:none; fill-rule:evenodd;"
/></g
><g style="fill:white; text-rendering:optimizeSpeed; color-rendering:optimizeSpeed; image-rendering:optimizeSpeed; shape-rendering:crispEdges; stroke:white; color-interpolation:sRGB;"
><path style="stroke:none;" d="M532 126 L532 40 L457 40 L457 126 Z"
/></g
><g transform="translate(504,55.5)" style="font-size:24px; text-rendering:geometricPrecision; color-rendering:optimizeQuality; image-rendering:optimizeQuality; font-family:'Times New Roman'; color-interpolation:linearRGB;"
><text x="0" xml:space="preserve" y="8.5" style="stroke:none;"
>20</text
></g
><g style="stroke-linecap:butt; fill:rgb(0,114,189); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; stroke-linejoin:bevel; stroke-dasharray:10,6; stroke:rgb(0,114,189); color-interpolation:linearRGB; stroke-width:1.6; stroke-miterlimit:1;"
><line y2="55.5" style="fill:none;" x1="461" x2="501" y1="55.5"
/></g
><g transform="translate(504,83)" style="font-size:24px; text-rendering:geometricPrecision; color-rendering:optimizeQuality; image-rendering:optimizeQuality; font-family:'Times New Roman'; color-interpolation:linearRGB;"
><text x="0" xml:space="preserve" y="8.5" style="stroke:none;"
>11</text
></g
><g style="stroke-linecap:butt; fill:rgb(217,83,25); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; stroke-linejoin:round; stroke:rgb(217,83,25); color-interpolation:linearRGB; stroke-width:1.6;"
><line y2="83" style="fill:none;" x1="461" x2="501" y1="83"
/></g
><g transform="translate(504,110.5)" style="font-size:24px; text-rendering:geometricPrecision; color-rendering:optimizeQuality; image-rendering:optimizeQuality; font-family:'Times New Roman'; color-interpolation:linearRGB;"
><text x="0" xml:space="preserve" y="8.5" style="stroke:none;"
>02</text
></g
><g style="stroke-linecap:butt; fill:rgb(237,177,32); text-rendering:geometricPrecision; image-rendering:optimizeQuality; color-rendering:optimizeQuality; stroke-linejoin:bevel; stroke-dasharray:10,6; stroke:rgb(237,177,32); color-interpolation:linearRGB; stroke-width:1.6; stroke-miterlimit:1;"
><line y2="110.5" style="fill:none;" x1="461" x2="501" y1="110.5"
/></g
><g style="stroke-linecap:butt; fill:rgb(38,38,38); text-rendering:geometricPrecision; color-rendering:optimizeQuality; image-rendering:optimizeQuality; stroke:rgb(38,38,38); color-interpolation:linearRGB; stroke-width:0.6667;"
><path d="M457 126 L457 40 L532 40 L532 126 Z" style="fill:none; fill-rule:evenodd;"
/></g
></g
></svg
>

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -0,0 +1,63 @@
function cellPart = intpartitions(varargin)
%INTPARTITION performs integer partition, i.e. the partition of of a set
%containing homogenous elements. The function generates a cell array
%containing a list of vectors representing all possible ways
%of partitioning a set containing intIn number of identical elements
%without order awareness. The numerical representation in the
%output describes the partitions as: {[3 1]} = [1 1 1 | 1].
%
% Syntax:
% intpartition(n)
% intpartition(n,s)
%
% The resulting partions can also be seen as writing the input n as a sum
% of positive integers. An optional argument s can be supplied to output
% a subset of partitions with number of parts less than or equal to s.
%
% Number of ways of partitioning is according to sequence:
% http://oeis.org/A000041 - (1), 1, 2, 3, 5, 7, 11, 15, 22, 30, 42, ...
%
% Example 1: intpartition(4) gives {[1 1 1 1],[1 1 2],[1 3],[2 2],4}
% Example 2: intpartition(10,2) gives {[3,7],[4,6],[5,5],10}
%
n=varargin{1};
if nargin >= 2
s=varargin{2};
else
s=inf;
end
n = round(n);
if ~isscalar(n)
error('Invalid input. Input must be a scalar integer')
end
cellPart={};
a = zeros(n,1);
k = 1;
y = n - 1;
while k ~= 0
x = a(k) + 1;
k = k-1;
while 2*x <= y
a(k+1) = x;
y = y - x;
k = k + 1;
end
l = k + 1;
while x <= y
a(k+1) = x;
a(l+1) = y;
if k+2<=s
cellPart(end+1) = {a(1:k + 2)};
end
x = x + 1;
y = y - 1;
end
a(k+1) = x + y;
y = x + y - 1;
if k+1<=s
cellPart(end+1) = {a(1:k + 1)};
end
end
end

View File

@ -0,0 +1,62 @@
function P = perms(V)
%PERMS All possible permutations.
% PERMS(1:N), or PERMS(V) where V is a vector of length N, creates a
% matrix with N! rows and N columns containing all possible
% permutations of the N elements.
%
% This function is only practical for situations where N is less
% than about 10 (for N=11, the output takes over 3 gigabytes).
%
% Class support for input V:
% float: double, single
% integer: uint8, int8, uint16, int16, uint32, int32, uint64, int64
% logical, char
%
% See also NCHOOSEK, RANDPERM, PERMUTE.
% Copyright 1984-2015 The MathWorks, Inc.
[~,maxsize] = computer;
n = numel(V);
% Error if output dimensions are too large
if n*factorial(n) > maxsize
error(message('MATLAB:pmaxsize'))
end
V = V(:).'; % Make sure V is a row vector
n = length(V);
if n <= 1
P = V;
else
P = permsr(n);
if isequal(V, 1:n)
P = cast(P, 'like', V);
else
P = V(P);
end
end
%----------------------------------------
function P = permsr(n)
% subfunction to help with recursion
P = 1;
for nn=2:n
Psmall = P;
m = size(Psmall,1);
% P = zeros();
P = sparse(nn*m,nn);
P(1:m, 1) = nn;
P(1:m, 2:end) = Psmall;
for i = nn-1:-1:1
reorder = [1:i-1, i+1:nn];
% assign the next m rows in P.
P((nn-i)*m+1:(nn-i+1)*m,1) = i;
P((nn-i)*m+1:(nn-i+1)*m,2:end) = reorder(Psmall);
end
end

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,617 @@
import matplotlib.pyplot as plt
import numpy as np
import sympy as sp
from IPython.display import Math, display
from matplotlib.axes import Axes
from scipy import constants as const
from scipy.integrate import quad
from scipy.optimize import root_scalar
from scipy.signal import argrelmax,argrelmin
from tqdm import tqdm
import fewfermions.analysis.units as si
from fewfermions.simulate.traps.twod.trap import PancakeTrap
from fewfermions.style import FIGS_PATH, setup
colors, colors_alpha = setup()
def plot_solutions(trap,n_levels,left_cutoff,right_cutoff,n_pot_steps=1000,display_plot=-1,state_mult=1e4):
"""Plot the potential and solutions for a given trap object
(diplay_plot=-1 for all, 0,...,n for individual ones and -2 for none)
"""
x, y, z = trap.x, trap.y, trap.z
axial_width = trap.get_tweezer_rayleigh()
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(axial_width)),
fprime=pot_diff2_ax_numpy,
xtol=1e-18,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(axial_width))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
barrier_interval = np.logical_and(
coords[z] > intersect_start, coords[z] < intersect_end
)
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
lifetime = (
const.h / (transmission_probability * np.abs(energies - potential(minimum)))
)
print(f"{lifetime[true_bound_states]} s")
z_np = np.linspace(left_cutoff, right_cutoff, num=n_pot_steps)
ax: plt.Axes
fig, ax = plt.subplots(figsize=(2.5, 2.5))
# ax.set_title("Axial")
abs_min = np.min(potential(z_np))
print(abs_min)
ax.fill_between(
z_np / si.um,
potential(z_np) / const.h / si.kHz,
abs_min / const.h / si.kHz,
fc=colors_alpha["red"],
alpha=0.5,
)
# ax2 = ax.twinx()
count = 0
for i, bound in enumerate(true_bound_states):
if not bound:
continue
energy = energies[i]
state = states[i]
ax.plot(
z_np / si.um,
np.where(
(energy > potential(z_np)) & (z_np < barrier),
energy / const.h / si.kHz,
np.nan,
),
c="k",
lw=0.5,
marker="None",
)
if display_plot == -1 or display_plot == count:
ax.plot(z_np/si.um, state**2 *state_mult, marker="None", c="k")
count += 1
ax.plot(z_np / si.um, potential(z_np) / const.h / si.kHz, marker="None")
ax.vlines(barrier/ si.um,np.min(potential(z_np) / const.h/ si.kHz),np.max(potential(z_np) / const.h/ si.kHz))
ax.vlines(minimum/ si.um,np.min(potential(z_np) / const.h/ si.kHz),np.max(potential(z_np) / const.h/ si.kHz))
ax.set_title(f"{np.sum(true_bound_states)} bound solutions, power: {trap.subs(trap.power_tweezer)/si.mW}mW")
ax.set_xlabel(r"z ($\mathrm{\mu m}$)")
ax.set_ylabel(r"E / h (kHz)")
def plot_solutions_ax(trap,n_levels,left_cutoff,right_cutoff,n_pot_steps=1000,display_plot=-1,state_mult=1e4):
"""Plot the potential and solutions in the z-direction for a given trap object with no spilling
(diplay_plot=-1 for all, 0,...,n for individual ones and -2 for none)
"""
#turn off magnetic gradient and gravity
trap[trap.g] = 0
initial_gradient = trap.grad_z
trap[trap.grad_z] = 0
trap
x, y, z = trap.x, trap.y, trap.z
axial_width = trap.get_tweezer_rayleigh()
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
"""
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(axial_width)),
fprime=pot_diff2_ax_numpy,
xtol=1e-18,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
"""
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# States that are below the potential barrier
#bound_states = energies < potential(barrier)
bound_states = energies < potential(left_cutoff)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
"""true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[x] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[x] > barrier] ** 2, axis=1),
)"""
true_bound_states = bound_states
"""
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(axial_width))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
barrier_interval = np.logical_and(
coords[x] > intersect_start, coords[x] < intersect_end
)
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
lifetime = (
const.h / (transmission_probability * np.abs(energies - potential(minimum)))
)
print(f"{lifetime[true_bound_states]} s")"""
z_np = np.linspace(left_cutoff, right_cutoff, num=n_pot_steps)
ax: plt.Axes
fig, ax = plt.subplots(figsize=(2.5, 2.5))
# ax.set_title("Axial")
abs_min = np.min(potential(z_np))
print(abs_min)
ax.fill_between(
z_np / si.um,
potential(z_np) / const.h / si.kHz,
abs_min / const.h / si.kHz,
fc=colors_alpha["red"],
alpha=0.5,
)
# ax2 = ax.twinx()
count = 0
for i, bound in enumerate(true_bound_states):
if not bound:
continue
energy = energies[i]
print((np.max(potential(z_np))-energy)/ const.h / si.kHz)
state = states[i]
ax.plot(
z_np / si.um,
np.where(
(energy > potential(z_np)), #& (z_np < barrier),
energy / const.h / si.kHz,
np.nan,
),
c="k",
lw=0.5,
marker="None",
)
if display_plot == -1 or display_plot == count:
ax.plot(z_np/si.um, state**2 *state_mult, marker="None", c="k")
count += 1
ax.plot(z_np / si.um, potential(z_np) / const.h / si.kHz, marker="None")
#ax.vlines(barrier/ si.um,np.min(potential(z_np) / const.h/ si.kHz),np.max(potential(z_np) / const.h/ si.kHz))
#ax.vlines(minimum/ si.um,np.min(potential(z_np) / const.h/ si.kHz),np.max(potential(z_np) / const.h/ si.kHz))
#ax.set_title(f"{np.sum(true_bound_states)} bound solutions, power: {trap.subs(trap.power_tweezer)/si.mW}mW")
ax.set_xlabel(r"z ($\mathrm{\mu m}$)")
ax.set_ylabel(r"E / h (kHz)")
#turn on magnetic gradient and gravity
trap[trap.g] = const.g
trap[trap.grad_z] = initial_gradient
def plot_solutions_rad(trap,n_levels,left_cutoff,right_cutoff,n_pot_steps=1000,display_plot=-1,state_mult=1e4):
"""Plot the potential and solutions in the x-direction for a given trap object
(diplay_plot=-1 for all, 0,...,n for individual ones and -2 for none)
"""
x, y, z = trap.x, trap.y, trap.z
axial_width = trap.get_tweezer_rayleigh()
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.x)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.x)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.x)
pot_diff_ax_numpy = sp.lambdify(trap.x, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.x, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.x, pot_diff3_ax.subs({x: 0, y: 0}))
"""
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(axial_width)),
fprime=pot_diff2_ax_numpy,
xtol=1e-18,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
"""
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.x, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# States that are below the potential barrier
#bound_states = energies < potential(barrier)
bound_states = energies < potential(left_cutoff)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
"""true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[x] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[x] > barrier] ** 2, axis=1),
)"""
true_bound_states = bound_states
"""
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(axial_width))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
barrier_interval = np.logical_and(
coords[x] > intersect_start, coords[x] < intersect_end
)
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
lifetime = (
const.h / (transmission_probability * np.abs(energies - potential(minimum)))
)
print(f"{lifetime[true_bound_states]} s")"""
z_np = np.linspace(left_cutoff, right_cutoff, num=n_pot_steps)
ax: plt.Axes
fig, ax = plt.subplots(figsize=(2.5, 2.5))
# ax.set_title("Axial")
abs_min = np.min(potential(z_np))
print(abs_min)
ax.fill_between(
z_np / si.um,
potential(z_np) / const.h / si.kHz,
abs_min / const.h / si.kHz,
fc=colors_alpha["red"],
alpha=0.5,
)
# ax2 = ax.twinx()
count = 0
for i, bound in enumerate(true_bound_states):
if not bound:
continue
energy = energies[i]
print((np.max(potential(z_np))-energy)/ const.h / si.kHz)
state = states[i]
ax.plot(
z_np / si.um,
np.where(
(energy > potential(z_np)), #& (z_np < barrier),
energy / const.h / si.kHz,
np.nan,
),
c="k",
lw=0.5,
marker="None",
)
if display_plot == -1 or display_plot == count:
ax.plot(z_np/si.um, state**2 *state_mult, marker="None", c="k")
count += 1
ax.plot(z_np / si.um, potential(z_np) / const.h / si.kHz, marker="None")
#ax.vlines(barrier/ si.um,np.min(potential(z_np) / const.h/ si.kHz),np.max(potential(z_np) / const.h/ si.kHz))
#ax.vlines(minimum/ si.um,np.min(potential(z_np) / const.h/ si.kHz),np.max(potential(z_np) / const.h/ si.kHz))
#ax.set_title(f"{np.sum(true_bound_states)} bound solutions, power: {trap.subs(trap.power_tweezer)/si.mW}mW")
ax.set_xlabel(r"z ($\mathrm{\mu m}$)")
ax.set_ylabel(r"E / h (kHz)")
def plot_occupation(trap,n_levels,left_cutoff,right_cutoff,t_spill=25*si.ms,n_spill_steps=100,power_fac_down=0.4,power_fac_up=0.7,n_pot_steps=1000, results=False):
"""
Plotting the occupation of the tweezer when varying the tweezer power.
"""
x, y, z = trap.x, trap.y, trap.z
axial_width = trap.get_tweezer_rayleigh()
initial_power = trap[trap.power_tweezer]
spill_power_factor = np.linspace(power_fac_up, power_fac_down, num=n_spill_steps)
powers = trap[trap.power_tweezer] * spill_power_factor
atom_number = np.zeros_like(powers,dtype=float)
# Resolution of the potential when solving numerically
#n_pot_steps = 1000
for i, power in enumerate(tqdm(powers)):
trap[trap.power_tweezer] = power
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# Determine the potential and its derivatives
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(axial_width)),
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
if t_spill == 0:
atom_number[i] = np.sum(true_bound_states)
continue
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(axial_width))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
barrier_interval = np.logical_and(
coords[z] > intersect_start, coords[z] < intersect_end
)
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
tunneling_rate = (
transmission_probability * np.abs(energies - potential(minimum)) / const.h
)
atom_number[i] = np.sum(np.exp(-t_spill * tunneling_rate[true_bound_states]))
ax: plt.Axes
fig: plt.Figure
fig, ax = plt.subplots(figsize=(2.5, 2.5))
ax.set_title(f"initial power:{initial_power/si.mW}mW, B':{trap[trap.grad_z]/si.G*si.cm}G/cm, w_0:{trap[trap.waist_tweezer]/si.um}um, wvl:{trap[trap.wvl]/si.nm}nm")
ax.set_xlabel("rel. tweezer power (a.u.)")
ax.set_ylabel("atom number")
ax.plot(spill_power_factor, atom_number, marker="None")
#ax.fill_between(spill_power_factor, atom_number, fc=colors_alpha["red"], alpha=0.5)
if results:
return spill_power_factor, atom_number
def plot_occupation_grad(trap,n_levels,left_cutoff,right_cutoff,t_spill=25*si.ms,n_spill_steps=100,grad_fac_down=0.4,grad_fac_up=0.7,n_pot_steps=1000):
"""
Plotting the occupation of the tweezer when varying the magnetic gradient.
"""
x, y, z = trap.x, trap.y, trap.z
axial_width = trap.get_tweezer_rayleigh()
initial_power = trap[trap.power_tweezer]
initial_grad = trap[trap.grad_z]
spill_grad_factor = np.linspace(grad_fac_up, grad_fac_down, num=n_spill_steps)
grads = trap[trap.grad_z] * spill_grad_factor
atom_number = np.zeros_like(grads)
# Resolution of the potential when solving numerically
#n_pot_steps = 1000
for i, grad in enumerate(tqdm(grads)):
trap[trap.grad_z] = grad
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# Determine the potential and its derivatives
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(axial_width)),
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
if t_spill == 0:
atom_number[i] = np.sum(true_bound_states)
continue
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(axial_width))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
barrier_interval = np.logical_and(
coords[z] > intersect_start, coords[z] < intersect_end
)
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
tunneling_rate = (
transmission_probability * np.abs(energies - potential(minimum)) / const.h
)
atom_number[i] = np.sum(np.exp(-t_spill * tunneling_rate[true_bound_states]))
ax: plt.Axes
fig: plt.Figure
fig, ax = plt.subplots(figsize=(2.5, 2.5))
ax.plot(spill_grad_factor, atom_number, marker="None")
ax.fill_between(spill_grad_factor, atom_number, fc=colors_alpha["red"], alpha=0.5)
ax.set_title(f"power:{initial_power/si.mW}mW, initial B':{initial_grad/si.G*si.cm}G/cm, w_0:{trap[trap.waist_tweezer]/si.um}um, wvl:{trap[trap.wvl]/si.nm}nm")
ax.set_xlabel("rel. gradient (a.u.)")
ax.set_ylabel("atom number")

View File

@ -0,0 +1,556 @@
import matplotlib.pyplot as plt
import numpy as np
import sympy as sp
from IPython.display import Math, display
from matplotlib.axes import Axes
from scipy import constants as const
from scipy.integrate import quad
from scipy.optimize import root_scalar
from scipy.signal import argrelmax,argrelmin
from tqdm import tqdm
import fewfermions.analysis.units as si
from fewfermions.simulate.traps.twod.trap import PancakeTrap
from fewfermions.style import FIGS_PATH, setup
colors, colors_alpha = setup()
def plot_solutions(trap,n_levels,left_cutoff,right_cutoff,n_pot_steps=1000,display_plot=-1,state_mult=1e4,plot=True,ret_num=False):
"""Plot the potential and solutions for a given trap object
(diplay_plot=-1 for all, 0,...,n for individual ones and -2 for none)
"""
x, y, z = trap.x, trap.y, trap.z
axial_width = trap.get_tweezer_rayleigh()
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(axial_width)),
fprime=pot_diff2_ax_numpy,
xtol=1e-18,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
if plot:
z_np = np.linspace(left_cutoff, right_cutoff, num=n_pot_steps)
ax: plt.Axes
fig, ax = plt.subplots(figsize=(2.5, 2.5))
# ax.set_title("Axial")
abs_min = np.min(potential(z_np))
ax.fill_between(
z_np / si.um,
potential(z_np) / const.h / si.kHz,
abs_min / const.h / si.kHz,
fc=colors_alpha["red"],
alpha=0.5,
)
count = 0
for i, bound in enumerate(true_bound_states):
if not bound:
continue
energy = energies[i]
state = states[i]
ax.plot(
z_np / si.um,
np.where(
(energy > potential(z_np)) & (z_np < barrier),
energy / const.h / si.kHz,
np.nan,
),
c="k",
lw=0.5,
marker="None",
)
if display_plot == -1 or display_plot == count:
ax.plot(z_np/si.um, state**2 *state_mult, marker="None", c="k")
count += 1
ax.plot(z_np / si.um, potential(z_np) / const.h / si.kHz, marker="None")
ax.vlines(barrier/ si.um,np.min(potential(z_np) / const.h/ si.kHz),np.max(potential(z_np) / const.h/ si.kHz))
ax.vlines(minimum/ si.um,np.min(potential(z_np) / const.h/ si.kHz),np.max(potential(z_np) / const.h/ si.kHz))
ax.set_title(f"{np.sum(true_bound_states)} bound solutions, power: {trap.subs(trap.power_tweezer)/si.mW}mW")
ax.set_xlabel(r"z ($\mathrm{\mu m}$)")
ax.set_ylabel(r"E / h (kHz)")
if ret_num:
return np.sum(true_bound_states)
def solutions_power(trap,power,n_levels,left_cutoff,right_cutoff):
trap[trap.power_tweezer] = power
return plot_solutions(trap,n_levels,left_cutoff,right_cutoff,plot=False, ret_num=True)
def root_power(trap,num_atoms,bracket,n_levels,left_cutoff,right_cutoff):
f = lambda x: solutions_power(trap,x,n_levels,left_cutoff,right_cutoff) - num_atoms
return root_scalar(f,bracket=bracket).root
def sweep_power(trap, gradient,n_levels,left_cutoff,right_cutoff,t_spill=25*si.ms,n_pot_steps=1000,max_atoms=10,n_plotpoints=100):
"""
Sweeps over power and gets the power for 0 atom boundary and the precision level
"""
x, y, z = trap.x, trap.y, trap.z
zr = trap.get_tweezer_rayleigh()
trap[trap.grad_z] = gradient
initial_power = float(4/3/np.sqrt(3)*trap.subs(zr) * np.pi* trap.subs(trap.m * trap.g + trap.mu_b * trap.grad_z) * trap.subs(trap.waist_tweezer**2/trap.a))
final_power = root_power(trap,max_atoms,[initial_power,initial_power*5],n_levels,left_cutoff,right_cutoff)
powers = np.linspace(initial_power,final_power,n_plotpoints)
atom_number = np.zeros_like(powers,dtype=float)
for i, power in enumerate(tqdm(powers)):
trap[trap.power_tweezer] = power
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# Determine the potential and its derivatives
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(zr)),
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
if t_spill == 0:
atom_num = np.sum(true_bound_states)
atom_number = np.append(atom_number,atom_num)
continue
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(zr))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
tunneling_rate = (
transmission_probability * np.abs(energies - potential(minimum)) / const.h
)
atom_number[i] = np.sum(np.exp(-t_spill * tunneling_rate[true_bound_states]))
return powers, atom_number
def sweep_power_old(trap, gradient,dp,n_levels,left_cutoff,right_cutoff,t_spill=25*si.ms,max_spill_steps=200,n_pot_steps=1000,max_atoms=10):
"""
Sweeps over power and gets the power for 0 atom boundary and the precision level
"""
x, y, z = trap.x, trap.y, trap.z
zr = trap.get_tweezer_rayleigh()
trap[trap.grad_z] = gradient
initial_power = 4/3/np.sqrt(3)*trap.subs(zr) * np.pi* trap.subs(trap.m * trap.g + trap.mu_b * trap.grad_z) * trap.subs(trap.waist_tweezer**2/trap.a)
trap[trap.power_tweezer] = initial_power
powers = np.array([initial_power],dtype=float)
atom_number = np.array([0.0],dtype=float)
i = 0
pbar = tqdm(disable=True)
while atom_number[i] <max_atoms and i<max_spill_steps:
#print(i)
trap[trap.power_tweezer] = initial_power + (i+1)*dp
powers = np.append(powers, initial_power + (i+1)*dp)
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# Determine the potential and its derivatives
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(zr)),
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
if t_spill == 0:
atom_num = np.sum(true_bound_states)
atom_number = np.append(atom_number,atom_num)
continue
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(zr))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
tunneling_rate = (
transmission_probability * np.abs(energies - potential(minimum)) / const.h
)
atom_num = np.sum(np.exp(-t_spill * tunneling_rate[true_bound_states]))
atom_number = np.append(atom_number,atom_num)
i += 1
pbar.update(1)
pbar.close()
if i == max_spill_steps:
print(f"{max_atoms} atoms not reached")
return powers,atom_number
raise Exception(f"{max_atoms} atoms not reached")
return powers, atom_number
def solutions_gradient(trap,gradient,n_levels,left_cutoff,right_cutoff):
trap[trap.grad_z] = gradient
return plot_solutions(trap,n_levels,left_cutoff,right_cutoff,plot=False, ret_num=True)
def root_gradient(trap,num_atoms,bracket,n_levels,left_cutoff,right_cutoff):
f = lambda x: solutions_gradient(trap,x,n_levels,left_cutoff,right_cutoff) - num_atoms
return root_scalar(f,bracket=bracket).root
def sweep_gradient(trap, power,n_levels,left_cutoff,right_cutoff,t_spill=25*si.ms,n_pot_steps=1000,max_atoms=10,n_plotpoints=100):
"""
Sweeps over gradient and gets the gradient for 0 atom boundary and the precision level
"""
x, y, z = trap.x, trap.y, trap.z
zr = trap.get_tweezer_rayleigh()
trap[trap.power_tweezer] = power
initial_grad = float(trap.subs(1/trap.mu_b * (3*np.sqrt(3)/4 * power * trap.a/np.pi/trap.waist_tweezer**2/zr - trap.m * trap.g)))
final_grad = root_gradient(trap,max_atoms,[-2.89*si.G/si.cm,initial_grad],n_levels,left_cutoff,right_cutoff)
gradients = np.linspace(initial_grad,final_grad,n_plotpoints,dtype=float)
atom_number = np.zeros_like(gradients,dtype=float)
for i, gradient in enumerate(tqdm(gradients)):
#print(i)
trap[trap.grad_z] = gradient
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# Determine the potential and its derivatives
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(zr)),
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
if t_spill == 0:
atom_num = np.sum(true_bound_states)
atom_number = np.append(atom_number,atom_num)
continue
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(zr))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
tunneling_rate = (
transmission_probability * np.abs(energies - potential(minimum)) / const.h
)
atom_number[i] = np.sum(np.exp(-t_spill * tunneling_rate[true_bound_states]))
return gradients, atom_number
def sweep_gradient_old(trap, power,dgrad,n_levels,left_cutoff,right_cutoff,t_spill=25*si.ms,max_spill_steps=200,n_pot_steps=1000,max_atoms=10):
"""
Sweeps over gradient and gets the gradient for 0 atom boundary and the precision level
"""
x, y, z = trap.x, trap.y, trap.z
zr = trap.get_tweezer_rayleigh()
trap[trap.power_tweezer] = power
initial_grad = float(trap.subs(1/trap.mu_b * (3*np.sqrt(3)/4 * power * trap.a/np.pi/trap.waist_tweezer**2/zr - trap.m * trap.g)))
trap[trap.grad_z] = initial_grad
gradients = np.array([initial_grad],dtype=float)
atom_number = np.array([0.0],dtype=float)
i = 0
pbar = tqdm(disable=True)
while atom_number[i] <max_atoms and i<max_spill_steps:
#print(i)
trap[trap.grad_z] = initial_grad - (i+1)*dgrad
gradients = np.append(gradients, initial_grad - (i+1)*dgrad)
# Solve the hamiltonian numerically in axial direction
energies, states, potential, coords = trap.nstationary_solution(
trap.z, (left_cutoff, right_cutoff), n_pot_steps, k=n_levels
)
# Determine the potential and its derivatives
pot_ax = trap.subs(trap.get_potential())
pot_diff_ax = sp.diff(pot_ax, trap.z)
pot_diff2_ax = sp.diff(pot_diff_ax, trap.z)
pot_diff3_ax = sp.diff(pot_diff2_ax, trap.z)
pot_diff_ax_numpy = sp.lambdify(trap.z, pot_diff_ax.subs({x: 0, y: 0}))
pot_diff2_ax_numpy = sp.lambdify(trap.z, pot_diff2_ax.subs({x: 0, y: 0}))
pot_diff3_ax_numpy = sp.lambdify(trap.z, pot_diff3_ax.subs({x: 0, y: 0}))
barrier = root_scalar(
pot_diff_ax_numpy,
x0=1.5 * float(trap.subs(zr)),
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
minimum = root_scalar(
pot_diff_ax_numpy,
x0=0,
fprime=pot_diff2_ax_numpy,
xtol=1e-28,
fprime2=pot_diff3_ax_numpy,
).root
# States that are below the potential barrier
bound_states = energies < potential(barrier)
# Density of states is larger on the left than on the right
# Likely that the state in question is a true bound state
true_bound_states = np.logical_and(
bound_states,
np.sum(states[:, coords[z] < barrier] ** 2, axis=1)
> np.sum(states[:, coords[z] > barrier] ** 2, axis=1),
)
if t_spill == 0:
atom_num = np.sum(true_bound_states)
atom_number = np.append(atom_number,atom_num)
continue
transmission_probability = np.full_like(energies, np.nan, dtype=float)
for j, energy in enumerate(energies):
if not true_bound_states[j]:
continue
intersect_end = root_scalar(
lambda x: potential(x) - energy,
bracket=(barrier, 3 * float(trap.subs(zr))),
).root
intersect_start = root_scalar(
lambda x: potential(x) - energy,
bracket=(minimum, barrier),
).root
s = quad(
lambda x: np.sqrt(
2
* float(trap.subs(trap.m))
* np.clip(potential(x) - energy, a_min=0, a_max=None)
)
/ const.hbar,
intersect_start,
intersect_end,
)
transmission_probability[j] = sp.exp(-2 * s[0])
tunneling_rate = (
transmission_probability * np.abs(energies - potential(minimum)) / const.h
)
atom_num = np.sum(np.exp(-t_spill * tunneling_rate[true_bound_states]))
atom_number = np.append(atom_number,atom_num)
i += 1
pbar.update(1)
pbar.close()
if i == max_spill_steps:
print(f"{max_atoms} atoms not reached")
return gradients,atom_number
raise Exception(f"{max_atoms} atoms not reached")
return gradients, atom_number
def calculate_stepsize(powers, atom_number,plot=False,max_atoms=10,cutoff=0.1):
"""
Given an array of powers (or gradients) and atom_numbers, returns the average length of steps.
The step length is the area where atom number is within cutoff percent of an integer.
"""
bool_array = np.logical_and(np.abs(atom_number - np.round(atom_number,0)) < cutoff, atom_number > cutoff) #find points where atomnumber is close to integer
bool_array = np.logical_and(bool_array, atom_number < (max_atoms-0.5))
if plot:
plt.plot(powers,atom_number)
plt.plot(powers[bool_array],atom_number[bool_array],"b.")
diff = np.diff(bool_array.astype(int)) # Convert to int to use np.diff
start_indices = np.where(diff == 1)[0] + 1 # Indices where blobs start
end_indices = np.where(diff == -1)[0] + 1 # Indices where blobs end
# Special case: handle if the array starts or ends with a True
if bool_array[0]:
start_indices = np.insert(start_indices, 0, 0) # Add the start of the array
if bool_array[-1]:
end_indices = np.append(end_indices, len(bool_array)) # Add the end of the array
#step length
step_len = np.abs(np.mean(np.diff(powers)))
# Blob lengths
blob_lengths = float(np.mean((end_indices - start_indices)*step_len))
# Results
return blob_lengths

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
wavelength,waist,power,gradient,aspect ratio,trap frequency,trap depth,step size
1.064e-06,1e-06,4.9e-05,0.0,4.175641859171396,3127.6403017866023,0.8410653821540325,0.0386
1.064e-06,1e-06,3.0e-05,-0.012,4.175641859171396,2447.2559215011247,0.5149379890738975,0.0429
1.064e-06,1e-06,1.5e-05,-0.022000000000000002,4.175641859171396,1439.839119056507,0.1782477654486568,0.0529
1.064e-06,1e-06,8.0e-05,0.022999999999999996,4.175641859171396,4292.77086249835,1.5844245817658382,0.03
1.064e-06,1e-06,0.00011999999999999999,0.053,4.175641859171396,6070.8947739052155,3.1688491635316764,0.0271

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More