Continued from previous post that can be found here

So, mid way through the project, I realized that I had written the expression for PBC tiny bit wrong and that lead to disastrous calculation of U value. So in this post, let us rectify that and add Dipole terms to the system.

PBC for a given index, is actually given by (after hours of breaking the nerves in my fried brain)

function pbc_1(system,index)
    eval_1(i,n)= ((abs(i)-1) ÷ n)*((sign(i)+1) ÷ 2) - ((sign(i-1)-1) ÷ 2)*((i-n) ÷ n)
    index_1(i,n)=((abs(i+n-1) % n)+1)
    unit_size=size(system)[1]
    a=(-system[1,1,1].ge+system[1,1,2].ge)[3]*unit_size
    uc=eval_1.(index,unit_size)*a
    index_mod=index_1.(index,unit_size)#index .% (unit_size+1)
    return index_mod,uc
    end;

pbc_1 takes the system (which stores the tetra object in 3D space in a $nxnxn$ matrix) and index to which it needs to calculate the corresponding PBC tetra unit inside the unit cell and also the value to which it needs to be moved by. It turns out because of the dispatch mechanism in Julia, it makes more sense to write a dispatchable function to get the system at a given index with PBC. Using the above pbc_1 one can use something like

#----get the tetra of a system at given index with PBC
function system_at_index(system,index)
    index_mod,uc=pbc_1(system,index)
    tetra_tmp=system[index_mod[1],index_mod[2],index_mod[3]]
    ge=tetra_tmp.ge+uc
    cl1=tetra_tmp.cl1+uc
    cl2=tetra_tmp.cl2+uc
    cl3=tetra_tmp.cl3+uc
    return tetra(ge,cl1,cl2,cl3)
    end;

And thus our NN function previously written, reduces to

# NN algorithm
function get_nn_2(system,pos)
    tmp=Array{Float64, 1}[]
    for i in -1:1
        for j in -1:1
            for k in -1:1
                index_tmp=pos+[i,j,k]
                temp=system_at_index(system,index_tmp)
                push!(tmp,temp.cl1)
                push!(tmp,temp.cl2)
                push!(tmp,temp.cl3)
            end
        end
    end
    return tmp
end

Having corrected this, we can now start writing the dipole equation. For a given set of dipoles as shown bellow,

dipole-dipole interaction

Dipole interaction energy can be derived and is given by

\[\begin{equation}U_{\mathrm{dd}}=\frac{1}{4 \pi \epsilon_{0} r_{i j}^{3}}\left[\vec{\mu}_{i} \cdot \vec{\mu}_{j}-3 \frac{\left(\vec{\mu}_{i} \cdot \mathbf{r}_{i j}\right)\left(\mathbf{r}_{i j} \cdot \vec{\mu}_{j}\right)}{r_{i j}^{2}}\right]\end{equation} \label{eq:1}\]

Where $\mu_{i}$ is the dipole vector and $r_{i j}$ is the vector connecting the dipoles. Here, for the sake of simplicity, we will ignore the pre-factors and code just the form of it. This doesn’t take out any physics as for our case, the strength is arbitrary and infact controlled by the coupling between dipole energy and “bonding” energy.

# Function to get dipole orientation of a tetrahedron
function get_dipol_vec(tetra::tetra)
    return (tetra.ge - (tetra.cl1+tetra.cl2+tetra.cl3)/3)/norm(tetra.ge - (tetra.cl1+tetra.cl2+tetra.cl3)/3)
end


# Get the distance  vector between two tetrahedrons supplying pl will plot it in pl
function get_dipole_dist(tetra1::tetra,tetra2::tetra,pl=nothing)
    r1=(tetra1.ge+tetra1.cl1+tetra1.cl2+tetra1.cl3)/4
    r2=(tetra2.ge+tetra2.cl1+tetra2.cl2+tetra2.cl3)/4
    if pl == nothing
        return r1-r2
    else
        plot_tetra(tetra1,pl)
        plot_tetra(tetra2,pl)
        plot!(pl,[i[1] for i in [r1,r2]],[i[2] for i in [r1,r2]],[i[3] for i in [r1,r2]],color="red",linewidth=3,label="connecting vector r12")
        end;
    end;

Above code get_dipol_vec calculates the dipole vector orientation for a given tetrahedron which is nothing but the distance of $B$ atom to the center of the triangle formed by 3 $X$ atoms. get_dipole_dist calculates the vector connecting the two centers of the tetrahedrons.

function get_dipole_energy_between_tetra(tetra1::tetra,tetra2::tetra)
    r12=get_dipole_dist(tetra1,tetra2)
    r12=r12
    d1=get_dipol_vec(tetra1)
    d2=get_dipol_vec(tetra2)
    four_pe0= 9 * 10^9 #1/4πe0 in N⋅m^2⋅C^−2
    return -dot(d1,d2)+3*(dot(d1,r12)*dot(d2,r12))
    end;

get_dipole_energy_between_tetra implements the \ref{eq:1} formula without the scaling which can be a parameter to be tuned. Now we can actually calculate the change total energy of a single twist of a tetrahedron, which is actually the quantity that determines the acceptance of Monte carlo. For this, we write a function that takes the system, position of the index to make flip and finally axis and theta to rotate the position about. We also need the scaling factor for calculating the two competing energy which is given by

\[\begin{equation} H=H_{d i p o l e}+ \mathcal{g} \cdot H_{b o n d} \end{equation}\label{eq:2}\]

Just to make it easy, we will re-write \ref{eq:2} to $H=\mathcal{g_1} \cdot H_{d i p o l e}+ \mathcal{g_2} \cdot H_{b o n d}$ so that we can check the individual effect while debugging. So now we can calculate the above by

function get_rotation_energy_1(system,g1,g2,pos,axis,theta)
    system_test=copy(system)
    system_test[pos[1],pos[2],pos[3]]=
            rot_tetra_test_1(system_at_index(system,pos),axis,theta);
    nn=[[1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]]
    u=0
    for i in nn
        ind,_=pbc_1(system,pos+i)
        u+=(get_mean_var(system_test,ind,"var")-get_mean_var(system,ind,"var"))
    end
    d=get_dipole_energy(system_test,pos)-get_dipole_energy(system,pos)
    return g1*u+g2*d
    end;

And thus, we have written the entire set of code to calculate the necessary equations. The beauty of Julia is that it is both a lisp language as well as a JIT one, Thus we can plot beautiful objects that are as fast as fortran. So, before leaving lets plot the Dipole energy and U while rotating. Here is the code.

dipole=[]
function rot_draw(i,pl2,pl3)
    pl=plot()#plot(xlim=(-2,10),ylim=(-2,10))
    theta=i
    axis=[1,0,1.]
    system_test=copy(system)
    pos_1=[2,2,2]
    nn=[[1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]]
    system_test[pos_1[1],pos_1[2],pos_1[3]]=
            rot_tetra_test_1(system_at_index(system,pos_1),axis,theta);
    tetra1=system_test[pos_1[1],pos_1[2],pos_1[3]]
    plot_tetra(tetra1,pl)
    d=0
    for i in nn
        ge=(tetra1.ge+tetra1.cl1+tetra1.cl2+tetra1.cl3)/4
        pos_new=pos_1+i
        temp=system_at_index(system,pos_new)
        tetra2=(temp.ge+temp.cl1+temp.cl2+temp.cl3)/4
        plot!(pl,[ge[1],tetra2[1]],[ge[2],tetra2[2]],[ge[3],tetra2[3]],color="red",linewidth=2,label="")
        plot_tetra(temp,pl)
        d+=get_dipole_energy_between_tetra(tetra1,temp)
        end;
    scatter!(pl2,[i/(π)],[d],markersize=3,label="",color="red")
    plot(pl,pl2,size = (800, 400),aspect=1)
    u=0
    for i in nn
        ind,_=pbc_1(system,pos_1+i)
        u+=(get_mean_var(system_test,ind,"var")-get_mean_var(system,ind,"var"))
        end;
    l =  @layout([a [b; c]])
    scatter!(pl3,[i/(π)],[u],markersize=3,label="",color="yellow")
    plot(pl,pl2,pl3,size = (800, 400),aspect=1,layout=l)
    end;
Plots.gr()
pl2=plot(size = (800, 800),aspect=1,ylim=(-150,250),xlim=(0,2),xlabel="Angle (π)",ylabel="Dipole")
pl3=plot(size = (800, 300),aspect=1,ylim=(-.5,2),xlim=(0,2),xlabel="Angle (π)",ylabel="U")
anim = @animate for i  LinRange(0,2*pi,100)
    rot_draw(i,pl2,pl3)
end
gif(anim, "animations/complete1_111.gif", fps = 20)

We will do it for various rotation axis

along [110] direction
along [100] direction

Just as a check, rotating the tetrahedron along the $[111]$ direction, (in which it is oriented at) should not change the dipole, but because of the $C_3$ symmetry of the tetrahedron, one should see a 3 fold symmetry in NN or Bonding energy which is seen bellow !

along [111] direction

Next stop, MC…

Github repo of the project can be found here

References

[1] Radha, Santosh Kumar, and Walter R. L. Lambrecht. “Understanding the Crystallographic Phase Relations in Alkali‐Trihalogeno‐Germanates in Terms of Ferroelectric or Antiferroelectric Arrangements of the Tetrahedral GeX$_3$ Units.” Advanced Electronic Materials 6.2 (2019): 1900887. Crossref. Web.
[2] Radha, Santosh Kumar, Churna Bhandari, and Walter RL Lambrecht. "Distortion modes in halide perovskites: To twist or to stretch, a matter of tolerance and lone pairs." Physical Review Materials 2.6 (2018): 063605.