Application Center - Maplesoft

App Preview:

Density functional theory program for atoms utilizing s-function basis sets

You can switch back to the summary page by clicking here.

Learn about Maple
Download Application


 

DFT (density functional theory) program for atoms utilizing

basis-sets consisting of s-functions (spherical Gaussians) only

copyright: P. Vogt, H. Huber, Jan. 2000

All properties in atomic units!

In this program we kept the names and structure as close as possible to the corresponding MAPLE-sheet for SCF-calculations, to show the analogies and differences

Input (nuclear data, basis-set data, occupation matrix)

> restart:

> with(linalg):

Warning, the protected names norm and trace have been redefined and unprotected

> Digits:=10:

> NumberofNuclei := 1: Example: helium atom i.e. 1 nucleus in the origin

> Dim := 3: 3 basis functions on the nucleus

define nuclear data:

atomic number

X Y Z

> AtomicNumber := array([2]);

AtomicNumber := vector([2])

> NuclearCoordinate := array([[0,0,0]]);

NuclearCoordinate := matrix([[0, 0, 0]])

define basis-set data:

Exponent

X Y Z

> Exponent := array([3.6,1.2,0.4]);

Exponent := vector([3.6, 1.2, .4])

> BasisCoordinate := array([[0,0,0],
[0,0,0],
[0,0,0]]);

BasisCoordinate := matrix([[0, 0, 0], [0, 0, 0], [0...

Occupation-Matrix in the molecular orbital representation (MO-basis)

> Occupation := array([[2,0,0],
[0,0,0],
[0,0,0]]);
lowest MO occupied with 2 electrons

Occupation := matrix([[2, 0, 0], [0, 0, 0], [0, 0, ...

Parameters and auxiliary functions

> X:=1:Y:=2:Z:=3: makes the program more readable, as x, y ans z-ccordinates are stored in arrays

sqrDistance - Function to calculate the square of the distance between two basis functions

> sqrDistance := proc (func1, func2)

> (BasisCoordinate[func1,X]-BasisCoordinate[func2,X])^2 +
(BasisCoordinate[func1,Y]-BasisCoordinate[func2,Y])^2 +
(BasisCoordinate[func1,Z]-BasisCoordinate[func2,Z])^2;

> end:

ProductGaussian - Calculates the product of two Gaussian functions

The product of two Gaussian fucntions is again a Gaussian with a new exponent cnew,

which is the sum of the original exponents, and new coordinates Rxnew, Rynew, Rznew,

which are found from the old ones by weighting with the exponents;

> ProductGaussian := proc(func1,func2,cnew::evaln,Rxnew::evaln,Rynew::evaln,Rznew::evaln)

> local a,b;

> cnew := Exponent[func1] + Exponent[func2];

> a := Exponent[func1] / eval(cnew);

> b := Exponent[func2] / eval(cnew);

> Rxnew := a * BasisCoordinate[func1,X] + b * BasisCoordinate[func2,X];

> Rynew := a * BasisCoordinate[func1,Y] + b * BasisCoordinate[func2,Y];

> Rznew := a * BasisCoordinate[func1,Z] + b * BasisCoordinate[func2,Z];

> end:

AuxInt - Auxiliary function for the calculation of the potential and the two-electron-integrals

> AuxInt := proc(X)

> if X=0 then 1 else evalf(1/2*sqrt(Pi/X)*erf(sqrt(X))) fi;

> end:

Diagonalisation of a matrix by the Jacobi method

a: Matrix to be diagonalised

d: Eigenvalues

v: Eigenvectors

includes symmetrisation of the matrix at the beginning and

sorting of the eigenvalues and -vectors at the end

> Diagonalisation := proc(a,d,v)
local i,ip,iq,j,c,g,h,s,sm,t,tau,theta,tresh,b,z,Maxiter;
Symmetrisation(a);
Maxiter:=10**Digits;
d:=array(1..Dim);
v:=array(1..Dim,1..Dim);
b:=array(1..Dim);
z:=array(1..Dim);
for ip from 1 to Dim do
for iq from 1 to Dim do
v[ip,iq]:=0;
od:
v[ip,ip]:=1;
od:
for ip from 1 to Dim do
b[ip]:=a[ip,ip];
d[ip]:=b[ip];
z[ip]:=0;
od:
for i from 1 to Maxiter do
sm:=0;
for ip from 1 to Dim-1 do
for iq from ip+1 to Dim do
sm:=sm+abs(a[ip,iq]);
od:
od:
if(sm<10**(-Digits-2)) then break fi;
if(i<4)then
tresh:=0.2*sm/Dim**2;
else
tresh:=0;
fi;
for ip from 1 to Dim-1 do
for iq from ip+1 to Dim do
g:=100*abs(a[ip,iq]);
if ((i>4) and (abs(d[ip])+g=abs(d[ip]))and(abs(d[iq])+g=abs(d[iq]))) then
a[ip,iq]:=0;
elif(abs(a[ip,iq])>tresh) then
h:=evalm(d[iq]-d[ip]);
if(abs(h)+g = abs(h))then
t:=a[ip,iq]/h;
else
theta:=0.5*h/a[ip,iq];
t:=1/(abs(theta)+sqrt(1.+theta**2));
if(theta<0) then t:=-t; fi;
fi;
c:=1/sqrt(1+t**2);
s:=t*c;
tau:=s/(1+c);
h:=t*a[ip,iq];
z[ip]:=z[ip]-h;
z[iq]:=z[iq]+h;
d[ip]:=d[ip]-h;
d[iq]:=d[iq]+h;
a[ip,iq]:=0;
for j from 1 to ip-1 do
g:=a[j,ip];
h:=a[j,iq];
a[j,ip]:=g-s*(h+g*tau);
a[j,iq]:=h+s*(g-h*tau);
od;
for j from ip+1 to iq-1 do
g:=a[ip,j];
h:=a[j,iq];
a[ip,j]:=g-s*(h+g*tau);
a[j,iq]:=h+s*(g-h*tau);
od:
for j from iq+1 to Dim do
g:=a[ip,j]:
h:=a[iq,j]:
a[ip,j]:=g-s*(h+g*tau):
a[iq,j]:=h+s*(g-h*tau):
od:
for j from 1 to Dim do
g:=v[j,ip]:
h:=v[j,iq]:
v[j,ip]:=g-s*(h+g*tau):
v[j,iq]:=h+s*(g-h*tau):
od:
fi:
od:
od:
for ip from 1 to Dim do
b[ip]:=b[ip]+z[ip]:
d[ip]:=b[ip]:
z[ip]:=0:
od:od:
Sort(d,v);

> print(d);

> end:

Sort - Sort the eigenvalues und eigenvectors

To sort the eigenvalues and corresponding eigenvectors after a diagonalisation according to the

size of the eigenvalues.

> Sort:=proc(Eigenvalues,Eigenvect)

> local MaxValue,MaxIndex,column1,column2, buffer;

> for column1 to Dim-1 do column1;

> MaxValue:=Eigenvalues[column1];

> MaxIndex:=Dim+1;

> for column2 from column1+1 to Dim do

> if MaxValue > Eigenvalues[column2] then

> MaxIndex:=column2;

> MaxValue:=Eigenvalues[MaxIndex];

> fi;

> od;

> if MaxIndex < Dim+1 then

> Eigenvect:=swapcol(Eigenvect,column1,MaxIndex);

> Eigenvalues[MaxIndex]:=Eigenvalues[column1];

> Eigenvalues[column1]:=MaxValue;

> fi;

> od;

> evalm(Eigenvalues);

> end:

Symmetrisation - Symmetrizes a matrix A

> Symmetrisation:=proc(A)

> A:=scalarmul(matadd(A,transpose(A)),0.5);

> end:

Integral functions (S, T, V, 2e-integral, M- and Vx-Operator)

SCF like functions

Overlapintegral - Function to calculate the overlapinteg rals <j|j>

> Overlapintegral := proc (func1, func2)

> local alpha, beta, cinv,Q ,aux;

> alpha := Exponent[func1];

> beta := Exponent[func2];

> cinv := 1/(alpha+beta);

> Q := exp(-alpha * beta * cinv * sqrDistance(func1,func2));

> aux := (4*alpha*beta*cinv^2);

> Q*sqrt(sqrt(aux^3));

> end:

KineticIntegral - Function to calculate the kinetic integral <j| T |j>

> KineticIntegral := proc (func1, func2)

> local alpha, beta, E;

> alpha := Exponent[func1];

> beta := Exponent[func2];

> E := alpha * beta /(alpha+beta);

> Overlapintegral(func1,func2) * E *(3 - 2*E * sqrDistance(func1,func2));

> end:

PotentialIntegral - Function to calculate the potential integral <j| V |j>

> PotentialIntegral := proc(func1, func2)

> local c,Rx,Ry,Rz,V,argument, nuc;

> ProductGaussian(func1, func2,c,Rx,Ry,Rz);

> V := 0;

> for nuc to NumberofNuclei do

> argument := c*((Rx-NuclearCoordinate[nuc,X])^2 + (Ry-NuclearCoordinate[nuc,Y])^2 + (Rz-NuclearCoordinate[nuc,Z])^2);

> V := V + AtomicNumber[nuc] * AuxInt(argument);

> od;

> evalf(-Overlapintegral(func1,func2) * 2/sqrt(Pi) * sqrt(c) * V);

> end:

TwoElectronIntegral - Function to calculate the 2-e-integral <jj|1/ r |jj>

> TwoElectronIntegral := proc(i,j,k,l)

> local Argument, cnew, c1, c2, Rx1, Rx2, Ry1, Ry2, Rz1, Rz2;

> ProductGaussian(i,j,c1,Rx1,Ry1,Rz1);

> ProductGaussian(k,l,c2,Rx2,Ry2,Rz2);

> cnew := c1 * c2 / (c1 + c2);

> Argument := cnew * ((Rx1 - Rx2)^2 + (Ry1 - Ry2)^2 + (Rz1 - Rz2)^2);

> evalf(2/sqrt(Pi)) * Overlapintegral(i, j) * Overlapintegral(k, l) * sqrt(cnew) * AuxInt(Argument);

> end:

MOperator - 2-e-Operator for closed shells

as in SCF, but without exchange integral!

> MOperator := proc(i,j)

> local Sum, k, l;

> Sum := 0;

> for k to Dim do

> for l to Dim do

> Sum := Sum + DensityMatrix[k, l] * TwoElectronIntegral(i, j, k, l);

> od;

> od;

> Sum;

> end:

Exchange functional

rho - calculates the density (called by ValueOfVx)

> rho := proc(r)

> local row, column, sum, mu, nu, factor, prefactor;

prefactor = (2/Pi)**(3/2)

> prefactor := .50794908747392775826;

> sum := 0;

> for row from 1 to Dim do

> for column from 1 to row do

> mu := Exponent[row];

> nu := Exponent[column];

> factor := 2*(mu * nu)**0.75;

> if row=column then

> factor:=0.5*factor;

> fi;

> sum := sum + factor * DensityMatrix[row,column] * exp(-(mu + nu) * r**2);

> od:od:

> prefactor*sum:

> end:

ValueOfVx - calculates the value of the exchange functional

(uses rho and is called by the numerical integration trapzd, which in turn is called by DensityIntegral)

> ValueOfVx := proc(i,j,x)

> local rhotmp,result;

> rhotmp := rho(x);

> if rhotmp = 0 then

> result := 0;

> else

> result := x**2 * exp(-(Exponent[i]+Exponent[j]) * x**2+ln(rhotmp) / 3);

> fi;

> result;

> end:

DensityIntegral - numerical integration over exchange density (for a spherical density only!)

This is the trapezoidal integration routine used in DensityIntegral

(this routine from Numerical Recipe uses ValueofX (see above))

> trapzd := proc(i,j,A,B,st::evaln,K,it::evaln)

> local result, tnm, loop, del, x, sum;

> if eval(K) = 1 then

> result := 0.5 * (B-A) * (ValueOfVx(i,j,A) + ValueOfVx(i,j,B));

> it := 1;

> else

> tnm := eval(it);

> del := (B-A) / tnm;

> x := A + 0.5 * del;

> sum := 0;

> for loop from 1 to eval(it) do

> sum := sum + ValueOfVx(i,j,x);

> x := x + del;

> od;

> result := 0.5 * (eval(st) + (B-A) * sum / tnm);

> it := eval(it) * 2;

> fi;

> result;

> end:

DensityIntegral - calculates the integral used in VxOperator

(this is procedure QSIMP from Numerical Recipes; it calles trapzd)

> DensityIntegral := proc(i,j)

> local A,B,EPS, s, st, ost, os, K, it, loop;

> A:=0;B:=10;

> EPS:=1e-6;

> K:=1;it := 0;loop:=true;

> st:=-1e30;s:=-1e30;

> os:=s;ost:=st;

> while (loop) do

> os := s;

> ost := st;

> st := evalf(trapzd(i,j,A,B,ost,K,it));

> K := K + 1;

> s := (4 * st - ost) / 3;

> loop := evalb(abs(s-os) > EPS * abs(os));

> od;

> s;

> end:

VxOperator - Exchange Operator

> VxOperator := proc(row,column)

> local prefactor;

prefactor = -(2/Pi)**(3/2) * 4*Pi * (3/Pi)**(1/3)

> prefactor := -6.2857027940461438505;

> evalf(prefactor * sqrt(sqrt((Exponent[row] * Exponent[column])**3))*DensityIntegral(row,column));

> end:

Construction of integral matrices

The same as in the SCF program

Overlap Matrix S

> SMatrix := array(1..Dim,1..Dim):

> for row to Dim do

> for column to row do

> SMatrix[row,column] := Overlapintegral(row,column);

> SMatrix[column,row] := SMatrix[row,column];

> od:

> od:

> print (SMatrix):

matrix([[1.000000000, .8059274485, .4647580015], [....

Kinetic Energy Matrix T

> TMatrix := array(1..Dim,1..Dim):

> for row to Dim do

> for column to row do

> TMatrix[row,column] := KineticIntegral(row,column);

> TMatrix[column,row] := TMatrix[row,column];

> od:

> od:

> print(TMatrix);

matrix([[5.400000000, 2.176004111, .5019386415], [2...

Potential Energy Matrix V

> VMatrix := array(1..Dim,1..Dim):

> for row to Dim do

> for column to Dim do

> VMatrix[row,column] := PotentialIntegral(row,column);

> VMatrix[column,row] := VMatrix[row,column];

> od;

> od;

> print(VMatrix);

matrix([[-6.055518051, -3.984754970, -2.097692986],...

1. DFT-Step (Solution of the general eigenvalue problem)

The same as in the SCF program

Orthogonalisation of the basis-set

The general eigenvalue problem can be simplified to the special eigenvalue problem by a transformation

to an orthonormal basis set, This is solved by diagonalisation. Othonormalisation means that the

overlap-matrix has to be transformed to the unity-matrix, i.e. a transformation matrix A has to

be found such that A T SA = I. To this purpose we first diagonalise : B T SB = D, where

B = "EigenVectors" is the transformation matrix, and D = "SSpur" is diagonal.

> SSpur := diag(eigenvals(SMatrix)):

> evalf(Diagonalisation(SMatrix,EigenValues,EigenVectors));

vector([.6917733905e-1, .5352419987, 2.395580662])

> print(EigenVectors);

matrix([[.4472763863, -.7071067818, .5476712825], [...

Then we form the square-root of the diagonal matrix = "RootSMatrix"

> RootSMatrix := SSpur:

> for i to Dim do

> RootSMatrix[i,i] := sqrt(EigenValues[i]);

> od:

> print(RootSMatrix);

matrix([[.2630158532, 0, 0], [0, .7316023501, 0], [...

Multiplying the "inverse RootSMatrix" W with the EigenVectors B from the left yields the

desired transformation matrix: A = BW, as (BW) T SBW = W(B T SB)W =

WDW = I

> TransMatrix := evalm(EigenVectors &* inverse(RootSMatrix));

TransMatrix := matrix([[1.700568163, -.9665179203, ...

Fock Operator, transformation to the orthogonal basis-set and diagonalisation

In the first SCF-step, the density matrix is put to zero, i.e. the problem is treated as one-electron problem. The Fock operator in a one electron problem is F = Hcore = T + V = TVMatrix.

Transformation to the orthonormal basis-set yields F' = A T FA = A T TVMatrix A

> TVMatrix := evalm( TMatrix &+ VMatrix); Hcore = T + V

TVMatrix := matrix([[-.655518051, -1.808750859, -1....

> FockOp := evalm(transpose(TransMatrix) &* TVMatrix &* TransMatrix);

FockOp := matrix([[3.956781872, -1.918614281, .4708...

The Fock operator F' is diagonalised and yields as eigenvalues the orbital energies and as

eigenvectors the coefficients c' representing the molecular orbitals in the orthonormal basis-set

> evalf(Diagonalisation(FockOp,EigenValues,OBEigenVec));

vector([-1.931689269, .9992234504e-1, 4.911149797])...

> print(OBEigenVec);

matrix([[.1323469014e-1, .4450994840, .8953833215],...

Backtransformation of the coefficients and formation of the density matrix

Backtransformation of the coefficients to the original AO-Basis: c = Ac'

> MOEigenVectors := evalm(TransMatrix &* OBEigenVec);

MOEigenVectors := matrix([[.3126432866, -.129062093...

Transformation der Besetzungs-Matrix in der MO-Basis (s. Eingabe) in die ursprngliche

AO-Basis -> Dichtematrix P

> DensityMatrix := evalm(MOEigenVectors &* Occupation &* transpose(MOEigenVectors));

DensityMatrix := matrix([[.1954916493, .2306093145,...

Further DFT-Steps

Two-Electron-Coulomb-Operator Matrix

M mn = Element mn of the M-Matrix = Element mn of the 2J-Matrix

= S l S r P lr ( mn / lr )

> MMatrix := array(1..Dim,1..Dim):

> for row to Dim do

> for column to row do

> MMatrix[row,column] := MOperator(row,column);

> MMatrix[column,row] := MMatrix[row,column];

> od;

> od:

> print(MMatrix);

matrix([[3.044590070, 2.289587182, 1.273020140], [2...

Two-Electron-Exchange-Operator Matrix

> VxMatrix := array(1..Dim,1..Dim):

> for row to Dim do

> for column to row do

> VxMatrix[row,column] := VxOperator(row,column);

> VxMatrix[column,row] := VxMatrix[row,column];

> od;

> od;

> print(VxMatrix);

matrix([[-.9703748454, -.7056097271, -.3852262193],...

Fock Operator:

F = Hcore + M + Vx

> FMatrix := evalm(TVMatrix &+ MMatrix &+ VxMatrix);

FMatrix := matrix([[1.418697174, -.2247734041, -.70...

F' = A T FA, Transformation of the Fock Operator to the orthonormal basis-set

> FockOp := evalm(transpose(TransMatrix) &* FMatrix &* TransMatrix);

FockOp := matrix([[5.294317035, -2.296609181, .7037...

> evalf(Diagonalisation(FockOp,EigenValues,OBEigenVec));

vector([-.3003245905, 1.373549917, 6.613849587])

> print(OBEigenVec);

The Fock operator F' is diagonalised and yields as eigenvalues the orbital energies and as

eigenvectors the coefficients c' representing the molecular orbitals in the orthonormal basis-set

matrix([[.9560554191e-1, .4896373201, .8666688381],...

Calculate density matrix

Backtransformation of the coefficients to the original AO-Basis: c = Ac'

> MOEigenVectors := evalm(TransMatrix &* OBEigenVec);

MOEigenVectors := matrix([[.2488184658, -.643598547...

Transformation of the Occupation-Matrix from the MO-Basis (see input) to the original

AO-Basis -> Densitymatrix P

> DensityMatrix := evalm(MOEigenVectors &* Occupation &* transpose(MOEigenVectors));

DensityMatrix := matrix([[.1238212578, .5518412163e...

Convergence

Energy = trace of "EMatrix" = trace [P ( H + 1/2 M)]

> EMatrix := evalm(.5 * DensityMatrix &* (TVMatrix &+ VxMatrix &+ FMatrix &+ (-.5 * VxMatrix) ));

EMatrix := matrix([[-.5181460315, -.5990276650, -.5...

> Etot_old := Etot; The energy is saved in Etot_old , for a comparison in the next step

> Etot := trace(EMatrix); form trace

> Dif := Etot - Etot_old; Energy lowering in the last SCF-Step -> Convergence ?

Etot_old := Etot

Etot := -2.415306005

Dif := 0.

>

Next DFT-Step

>