From 21a4c70bb7886cb0f3565157880fcfbf35473757 Mon Sep 17 00:00:00 2001 From: albert <92109627+Albkat@users.noreply.github.com> Date: Tue, 5 Dec 2023 19:02:16 +0100 Subject: [PATCH] format geometry_optimization Signed-off-by: albert <92109627+Albkat@users.noreply.github.com> --- optts.f90 | 126 +++++++++++++ src/axis_trafo.f90 | 198 ++++++++++++--------- src/geoopt_driver.f90 | 99 +++++++---- src/main/property.F90 | 2 +- src/model_hessian.f90 | 27 +-- src/optimizer.f90 | 363 +++++++++++++++++++++++++------------- src/setparam.f90 | 11 +- src/splitparam.f90 | 4 +- src/type/anc.f90 | 6 +- src/type/setvar.f90 | 46 +++-- test/unit/test_thermo.f90 | 4 +- 11 files changed, 596 insertions(+), 290 deletions(-) create mode 100644 optts.f90 diff --git a/optts.f90 b/optts.f90 new file mode 100644 index 000000000..a5cb69207 --- /dev/null +++ b/optts.f90 @@ -0,0 +1,126 @@ +subroutine optts(n,at,xyz,q,qsh,nshell,z,cn,nel,nopen,nbf,nao,P,wb, & + & kspd,gscal,kcn,xbrad,xbdamp,alphaj,d3a1,d3a2,d3s8, & + & d3atm,egap,et,maxiter,epot,grd) + + use iso_fortran_env, only : wp => real64 + use setcommon + use splitcommon + use atmass + implicit none + + integer :: n,at(n),nel,nopen,nbf,nao,icall,nshell,maxiter + real(wp) :: xyz(3,n),epot,wb(n,n),q(n),z(n),kspd(6),et,egap + real(wp) :: cn(n),grd(3,n),qsh(nshell),P(nao,nao) + real(wp) :: gscal,kcn(4),xbrad,xbdamp,alphaj,d3a1,d3a2,d3s8,d3atm + + real(wp),allocatable:: coord(:,:), freq(:), u(:,:), uu(:,:) + real(wp),allocatable:: xyza (:,:,:), rmass(:), e(:), e2(:), geo(:,:) + real(wp),allocatable:: coord0(:,:), displ(:), xyzmin(:,:), bmat(:,:) + real(wp),allocatable:: x(:),b(:),c(:),d(:),xx(:),yy(:),yy1(:),yy2(:) + + real(wp) :: norm,thr + integer :: i,j,k,m,n3,ii,ia,ic,isave,nprj,mode,maxoptiter + character(len=2) :: asym + character(len=30) :: fname + logical :: ex,fail + + thr=-10.0_wp ! modes below this value are projected out ! + mode=tsroot+6 + fname='xtb_normalmodes' + + write(*,*) + write(*,'(7x,''======================================='')') + write(*,'(7x,''| T S O P T I M I Z E R |'')') + write(*,'(7x,''======================================='')') + write(*,*) + + + write(*,*) 'normal mode file : ',trim(fname) + write(*,*) 'root for TS : ',mode + + ! error section + inquire(file=fname,exist=ex) + if (.not.ex) then + write(*,*) 'run -hess first!' + return + endif + + !> DoF + n3=3*n + allocate(freq(n3),u(n3,n3),rmass(n3)) + + ! read normal modes ! + ! normal case ! + open(newunit=112,file=fname,form='unformatted') + read (112)k + if(k.ne.n3) stop 'severe read error on modes' + read (112)freq + read (112)rmass + read (112)u + close(112) + + ! remove masses from normal modes and re-normalize ! + nprj=0 + do m=7,n3 + norm=0 + + ! loop through the atoms + do ia=1,n + ! loop through compnents + do ic=1,3 + + + ii = (ia-1)*3+ic + u(ii,m)=u(ii,m)*sqrt(ams(at(ia))) ! remove atomic masses + norm=norm+u(ii,m)**2 ! renormalize + + enddo + enddo + + u(1:n3,m)=u(1:n3,m)/(sqrt(norm)+1.d-12) + if(freq(m).lt.thr) nprj=nprj+1 + enddo + + allocate(uu(n3,nprj)) + k=0 + do i=7,n3 + if(freq(i).lt.thr) then + k=k+1 + uu(1:n3,k)=u(1:n3,i) + endif + enddo + + maxoptiter=25 + isave=micro_opt + micro_opt=1000 ! no coordinate update in ancopt + + write(*,'(''frequency /cm-1 :'',F12.2)')freq(mode) + write(*,'(''maxiter (opt) :'',i5 )')maxoptiter + write(*,'(''optlevel :'',i5 )')optlev + write(*,'(''# of projected modes :'',i5 )')nprj + + open(unit=112,file='.xtbtmpmode',form='unformatted') + write(112) nprj + write(112) uu + close(112) + + tsopt=.false. + call ancopt(n,at,xyz,q,qsh,nshell,z,cn,nel,nopen,nbf,nao, + . P,wb,kspd,gscal,kcn,xbrad,xbdamp,alphaj,d3a1,d3a2,d3s8, + . d3atm,egap,et,maxiter,maxoptiter,epot,grd,optlev, + . .true.,fail) + + call system('rm .xtbtmpmode 2>/dev/null') ! normal operation + micro_opt =isave ! back to default + tsopt=.true. + + ! call system('mv hessian hessian.xtbtmp 2>/dev/null') ! don't use hessian in ancopt + + call ancopt(n,at,xyz,q,qsh,nshell,z,cn,nel,nopen,nbf,nao, + . P,wb,kspd,gscal,kcn,xbrad,xbdamp,alphaj,d3a1,d3a2,d3s8, + . d3atm,egap,et,maxiter,maxoptcycle,epot,grd,optlev, + . .true.,fail) + + ! call system('mv hessian.xtbtmp hessian 2>/dev/null') + +end subroutine optts diff --git a/src/axis_trafo.f90 b/src/axis_trafo.f90 index 32dd280ac..4d084e939 100644 --- a/src/axis_trafo.f90 +++ b/src/axis_trafo.f90 @@ -112,96 +112,128 @@ subroutine axis(numat,nat,xyz,aa,bb,cc) return end subroutine axis - subroutine axis2(numat,nat,xyz,aa,bb,cc,avmom,sumw) - use xtb_splitparam - implicit double precision (a-h,o-z) - dimension xyz(3,numat) - integer nat(numat) - PARAMETER (BOHR=0.52917726) - dimension t(6), rot(3), xyzmom(3), eig(3), evec(3,3) - dimension x(numat),y(numat),z(numat),coord(3,numat) - data t /6*0.d0/ - !************************************************************************ - !* const1 = 10**40/(n*a*a) - !* n = avergadro's number - !* a = cm in an angstrom - !* 10**40 is to allow units to be 10**(-40)gram-cm**2 - !* - !************************************************************************ - const1 = 1.66053d0 - !************************************************************************ - !* - !* const2 = conversion factor from angstrom-amu to cm**(-1) - !* - !* = (planck's constant*n*10**16)/(8*pi*pi*c) - !* = 6.62618*10**(-27)[erg-sec]*6.02205*10**23*10**16/ - !* (8*(3.1415926535)**2*2.997925*10**10[cm/sec]) - !* - !************************************************************************ - const2=16.8576522d0 - - sumw=1.d-20 - sumwx=0.d0 - sumwy=0.d0 - sumwz=0.d0 - coord(1:3,1:numat)=xyz(1:3,1:numat)*bohr +subroutine axis2(n,xyz,aa,bb,cc,avmom,sumw) + + use xtb_splitparam, only : atmass + use xtb_mctc_convert, only : autoaa + + implicit double precision (a-h,o-z) + + !> number of atoms + integer, intent(in) :: n + + !> cartesian coordinates + real(wp), intent(in) :: xyz(:,:) + + !> rotational constants + real(wp), intent(out) :: aa,bb,cc + + !> average moment of inertia + real(wp), intent(out) :: avmom + + !> sum of atomic masses + real(wp), intent(out) :: sumw + + !> const1 = 10**40/(n*a*a) + !> n = avergadro's number + !> a = cm in an angstrom + !> 10**40 is to allow units to be 10**(-40)gram-cm**2 + real(wp), parameter :: const1 = 1.66053_wp + + !> const2 = conversion factor from angstrom-amu to cm**(-1) + !> = (planck's constant*n*10**16)/(8*pi*pi*c) + !> = 6.62618*10**(-27)[erg-sec]*6.02205*10**23*10**16/ + !> (8*(3.1415926535)**2*2.997925*10**10[cm/sec]) + real(wp), parameter :: const2 = 16.8576522_wp + + !> temporary variables + real(wp) :: xsum,eps,ams + + !> weighted sum of coordinates + real(wp) :: sumwx,sumwy,sumwz + + !> loop variables + integer :: i + + !> temporary arrays + real(wp) :: t(6), rot(3), xyzmom(3), eig(3), evec(3,3) + real(wp) :: x(n),y(n),z(n) + + !> Cartesian coordinates in Bohr + real(wp) :: coord(3,n) + + t(:) = 0.0_wp + sumw = 0.0_wp + sumwx = 0.0_wp + sumwy = 0.0_wp + sumwz = 0.0_wp + + ! convert Bohr to Angstrom ! + coord(:,:) = xyz(:,:) * autoaa + + ! cma ! + do i=1,n + sumw=sumw+atmass(i) + sumwx=sumwx+atmass(i)*coord(1,i) + sumwy=sumwy+atmass(i)*coord(2,i) + sumwz=sumwz+atmass(i)*coord(3,i) + enddo + + sumwx=sumwx/sumw + sumwy=sumwy/sumw + sumwz=sumwz/sumw + + do i=1,n + x(i)=coord(1,i)-sumwx + y(i)=coord(2,i)-sumwy + z(i)=coord(3,i)-sumwz + enddo + + !************************************************************************ + !* matrix for moments of inertia is of form + !* + !* | y**2+z**2 | + !* | -y*x z**2+x**2 | -i =0 + !* | -z*x -z*y x**2+y**2 | + !* + !************************************************************************ + do i=1,6 + t(i)=dble(i)*1.0d-10 + enddo + + do i=1,n + t(1)=t(1)+atmass(i)*(y(i)**2+z(i)**2) + t(2)=t(2)-atmass(i)*x(i)*y(i) + t(3)=t(3)+atmass(i)*(z(i)**2+x(i)**2) + t(4)=t(4)-atmass(i)*z(i)*x(i) + t(5)=t(5)-atmass(i)*y(i)*z(i) + t(6)=t(6)+atmass(i)*(x(i)**2+y(i)**2) + enddo + + call rsp(t,3,3,eig,evec) + + do i=1,3 + + if(eig(i).lt.3.d-4) then + eig(i)=0.d0 + rot(i)=0.d0 + else + rot(i)=2.9979245d+4*const2/eig(i) + endif - do i=1,numat - sumw=sumw+atmass(i) - sumwx=sumwx+atmass(i)*coord(1,i) - sumwy=sumwy+atmass(i)*coord(2,i) - sumwz=sumwz+atmass(i)*coord(3,i) - enddo + xyzmom(i)=eig(i)*const1 - sumwx=sumwx/sumw - sumwy=sumwy/sumw - sumwz=sumwz/sumw - f=1.0d0/bohr - do i=1,numat - x(i)=coord(1,i)-sumwx - y(i)=coord(2,i)-sumwy - z(i)=coord(3,i)-sumwz - enddo + enddo - !************************************************************************ - !* matrix for moments of inertia is of form - !* - !* | y**2+z**2 | - !* | -y*x z**2+x**2 | -i =0 - !* | -z*x -z*y x**2+y**2 | - !* - !************************************************************************ - do i=1,6 - t(i)=dble(i)*1.0d-10 - enddo - do i=1,numat - t(1)=t(1)+atmass(i)*(y(i)**2+z(i)**2) - t(2)=t(2)-atmass(i)*x(i)*y(i) - t(3)=t(3)+atmass(i)*(z(i)**2+x(i)**2) - t(4)=t(4)-atmass(i)*z(i)*x(i) - t(5)=t(5)-atmass(i)*y(i)*z(i) - t(6)=t(6)+atmass(i)*(x(i)**2+y(i)**2) - enddo - call rsp(t,3,3,eig,evec) - do i=1,3 - if(eig(i).lt.3.d-4) then - eig(i)=0.d0 - rot(i)=0.d0 - else - rot(i)=2.9979245d+4*const2/eig(i) - endif - xyzmom(i)=eig(i)*const1 - enddo + aa=rot(3)/2.9979245d+4 + bb=rot(2)/2.9979245d+4 + cc=rot(1)/2.9979245d+4 - aa=rot(3)/2.9979245d+4 - bb=rot(2)/2.9979245d+4 - cc=rot(1)/2.9979245d+4 - avmom=1.d-47*(xyzmom(1)+xyzmom(2)+xyzmom(3))/3. + avmom=1.d-47*(xyzmom(1)+xyzmom(2)+xyzmom(3))/3. - return - end subroutine axis2 +end subroutine axis2 subroutine axisvec(numat,nat,xyz,aa,bb,cc,evec) use xtb_splitparam diff --git a/src/geoopt_driver.f90 b/src/geoopt_driver.f90 index 55985c4e0..b401bfed4 100644 --- a/src/geoopt_driver.f90 +++ b/src/geoopt_driver.f90 @@ -18,66 +18,81 @@ module xtb_geoopt contains +!> wrapper for relaxation engines +!> more info: https://xtb-docs.readthedocs.io/en/latest/optimization.html subroutine geometry_optimization & & (env,mol,wfn,calc,egap,et,maxiter,maxcycle_in,etot,g,sigma, & & tight,pr,initial_sp,fail) + use xtb_mctc_accuracy, only : wp use xtb_mctc_io, only : stdout - use xtb_type_molecule use xtb_type_restart use xtb_type_calculator use xtb_type_data - use xtb_optimizer use xtb_relaxation_engine use xtb_single - use xtb_setparam - ! Declaration section implicit none character(len=*), parameter :: source = 'xtb_geoopt' - !> Dummy-argument list + !> instace of polymorphic calculatior class(TCalculator), intent(inout) :: calc - !! instance of calculatior + + !> calculation environment type(TEnvironment), intent(inout) :: env - !! calculation environment + + !> molecular data information type(TMolecule), intent(inout) :: mol - !! molecular information + + !> wavefunction type(TRestart),intent(inout) :: wfn - !! wavefunction + + !> optimization level integer, intent(in) :: tight - !! optimization level + + !> max number of SCC cycles integer, intent(in) :: maxiter - !! max number of SCC cycles + + !> max number of optimization cycles integer, intent(in) :: maxcycle_in - !! max number of optimization cycles + + !> total energy real(wp),intent(inout) :: etot - !! total energy + + !> electronic temperature real(wp),intent(in) :: et - !! electronic temprature + + !> HOMO-LUMO gap real(wp),intent(inout) :: egap - !! HOMO-LUMO gap + + !> gradients real(wp),intent(inout) :: g(3,mol%n) - !! gradients + + !> strain derivatives real(wp),intent(inout) :: sigma(3,3) - !! strain derivatives + + !> internal printlevel logical, intent(in) :: pr - !! printlevel + + !> optimization convergence logical, intent(out) :: fail - !! true-> optimization not converged + + !> perform initial single point logical, intent(in) :: initial_sp - !! true-> perform initial single point - !> Local variables type(scc_results) :: res logical :: final_sp, exitRun integer :: printlevel integer :: ilog +!----------------! +! Initialization ! +!----------------! + final_sp = pr if (pr) then @@ -88,10 +103,9 @@ subroutine geometry_optimization & printlevel = 0 endif - !> create optimization log file + ! create/open optimization log ! if (.not.allocated(set%opt_logfile)) then call open_file(ilog,'xtbopt.log','w') - !! default else if (set%opt_logfile == '-') then ilog = stdout @@ -102,36 +116,46 @@ subroutine geometry_optimization & endif endif + ! create ONIOM optimization logs for low & high methods ! if (set%oniom_settings%logs) then call open_file(set%oniom_settings%ilog1,"low.inner_region.log",'w') call open_file(set%oniom_settings%ilog2,"high.inner_region.log",'w') endif - call mol%update - !! update interatomic and minimum image(periodic) distances + call mol%update ! update interatomic and minimum image (periodic) distances + +!-------------! +! Calculation ! +!-------------! if (initial_sp) call singlepoint & &(env,mol,wfn,calc, & & egap,et,maxiter,printlevel-1,.false.,.false.,1.0_wp,etot,g,sigma,res) - !> actual calculation select case(set%opt_engine) - case(p_engine_rf) + case(p_engine_rf) ! ANCopt ! call ancopt & &(env,ilog,mol,wfn,calc, & & egap,et,maxiter,maxcycle_in,etot,g,sigma,tight,pr,fail) - final_sp = .true. - !! required since ANCopt might perform an untracked displacement + final_sp = .true. ! required since ANCopt might perform an untracked displacement + case(p_engine_lbfgs) - call l_ancopt & + call l_ancopt & ! L-ANCopt ! &(env,ilog,mol,wfn,calc, & & tight,maxcycle_in,etot,egap,g,sigma,printlevel,fail) + case(p_engine_inertial) - call fire & + call fire & ! FIRE ! &(env,ilog,mol,wfn,calc, & & tight,maxcycle_in,etot,egap,g,sigma,printlevel,fail) + end select - + +!-----------------! +! Post-processing ! +!-----------------! + + ! check convergence ! if (pr) then if (fail) then call touch_file('NOT_CONVERGED') @@ -141,14 +165,14 @@ subroutine geometry_optimization & endif end if - call env%check(exitRun) - !! check if any errors occur + call env%check(exitRun) ! error check if (exitRun) then call env%rescue("Trying to recover from failed geometry optimization", source) fail = .true. end if + ! print fine details of optimized geometry ! if (pr.and.set%pr_finalstruct) then write(env%unit,'(''================'')') write(env%unit,*) 'final structure:' @@ -157,7 +181,7 @@ subroutine geometry_optimization & endif if (pr.and.set%pr_geosum) call geosum(mol%n,mol%at,mol%xyz) - !> usually final SP is performed for all three engines + ! final SP (usually for all engines) ! if (final_sp) then if (pr) call generic_header(env%unit,'Final Singlepoint',49,10) call singlepoint & @@ -165,9 +189,10 @@ subroutine geometry_optimization & & egap,et,maxiter,printlevel,.false.,.false.,1.0_wp,etot,g,sigma,res) endif - if (ilog .ne.stdout) call close_file(ilog) - !! close log file if it is not printed out on stdout + ! close log file ! + if (ilog.ne.stdout) call close_file(ilog) + ! close ONIOM optlogs ! if (set%oniom_settings%logs) then call close_file(set%oniom_settings%ilog1) call close_file(set%oniom_settings%ilog2) diff --git a/src/main/property.F90 b/src/main/property.F90 index d7806d8fc..578c71453 100644 --- a/src/main/property.F90 +++ b/src/main/property.F90 @@ -1059,7 +1059,7 @@ subroutine print_thermo(iunit,nat,nvib_in,at,xyz,freq,etot,htot,gtot,nimag,pr,zp nvib=0 nimag=0 - call axis2(nat,at,xyz,aa,bb,cc,avmom,wt) + call axis2(nat,xyz,aa,bb,cc,avmom,wt) nvib_theo=3*nat-6 if(cc.lt.1.d-10) linear=.true. diff --git a/src/model_hessian.f90 b/src/model_hessian.f90 index bf09d82b3..af87dffc3 100644 --- a/src/model_hessian.f90 +++ b/src/model_hessian.f90 @@ -2081,14 +2081,15 @@ end subroutine mh_eeq end module xtb_modelhessian - subroutine ddvopt(Cart,nAtoms,Hess,iANr,s6) - Implicit Integer(i-n) - Implicit Real*8 (a-h, o-z) +subroutine ddvopt(Cart,nAtoms,Hess,iANr,s6) + + implicit integer(i-n) + implicit real*8 (a-h, o-z) ! include "common/real.inc" (molpro 2002.6) - Real*8 Zero, One, Two, Three, Four, Five, Six, Seven, & + real*8 :: Zero, One, Two, Three, Four, Five, Six, Seven, & & Eight, RNine, Ten, Half, Pi, SqrtP2, TwoP34, & & TwoP54, One2C2 - Parameter(Zero =0.0D0, One =1.0D0, Two=2.0D0, Three=3.0D0, & + parameter :: (Zero =0.0D0, One =1.0D0, Two=2.0D0, Three=3.0D0, & & Four =4.0D0, Five =5.0D0, Six=6.0D0, Seven=7.0D0, & & Eight=8.0D0, rNine=9.0D0, Ten=1.0D1, Half=0.5D0, & & Pi =3.141592653589793D0, & @@ -2098,16 +2099,16 @@ subroutine ddvopt(Cart,nAtoms,Hess,iANr,s6) & One2C2=0.2662567690426443D-04) ! end: common/real.inc - Real*8 Cart(3,nAtoms),rij(3),rjk(3),rkl(3), & + real*8 :: Cart(3,nAtoms),rij(3),rjk(3),rkl(3), & & Hess((3*nAtoms)*(3*nAtoms+1)/2),si(3),sj(3),sk(3), & & sl(3),sm(3),x(2),y(2),z(2), & & xyz(3,4), C(3,4), Dum(3,4,3,4) - Integer iANr(nAtoms) + integer :: iANr(nAtoms) - logical rcutoff + logical :: rcutoff ! include "common/ddvdt.inc" (molpro 2002.6) - Real*8 rAV(3,3), aAV(3,3), & + real*8 :: rAV(3,3), aAV(3,3), & & B_Str(6), A_Bend(2), A_Trsn(2), A_StrH(2), & & rkr, rkf, A_Str, RF_Const, & & wthr @@ -2130,10 +2131,10 @@ subroutine ddvopt(Cart,nAtoms,Hess,iANr,s6) ! end: "common/ddvdt.inc" !cc VDWx-Parameters (Grimme) used for vdw-correction of model hessian - real*8 alphavdw, damp, c6(100), c6k, c6l, c66, vander(100), & + real*8 :: alphavdw, damp, c6(100), c6k, c6l, c66, vander(100), & & vdw(3,3), dr(3) - integer kxyz, lxyz - data vander & + integer kxyz, lxyz + data vander & ! H, He & /0.91d0,0.92d0, & ! Li-Ne @@ -2650,7 +2651,7 @@ subroutine ddvopt(Cart,nAtoms,Hess,iANr,s6) 444 Continue End Do ! jAtom Return - End + end subroutine subroutine gff_ddvopt(Cart,nAtoms,Hess,at,s6,param,topo) use xtb_gfnff_data, only : TGFFData diff --git a/src/optimizer.f90 b/src/optimizer.f90 index 691f3d033..aa3e5ac2a 100644 --- a/src/optimizer.f90 +++ b/src/optimizer.f90 @@ -25,6 +25,7 @@ module xtb_optimizer use xtb_david2 implicit none + !> time profiling logical,private,parameter :: profile = .true. type :: convergence_log @@ -42,6 +43,7 @@ module xtb_optimizer contains +!> construct optimizer settings from optimization level subroutine get_optthr(n,olev,ethr,gthr,maxcycle,acc) use xtb_setparam implicit none @@ -51,66 +53,74 @@ subroutine get_optthr(n,olev,ethr,gthr,maxcycle,acc) real(wp),intent(out) :: gthr integer, intent(out) :: maxcycle real(wp),intent(out) :: acc + select case(olev) -! very approximate = crude + ! very approximate = crude ! case(p_olev_crude) ethr = 5.d-4 gthr = 1.d-2 maxcycle=n acc=3.00d0 -! approximate = sloopy + + ! approximate = sloopy ! case(p_olev_sloppy) ethr = 1.d-4 gthr = 6.d-3 maxcycle=n acc=3.00d0 -! loose + + ! loose ! case(p_olev_loose) ethr = 5.d-5 gthr = 4.d-3 maxcycle=n*2 acc=2.00d0 -! for DCOSMO-RS opts with TM i.e. between loose and normal, keyword "lax" + + ! for DCOSMO-RS opts with TM i.e. between loose and normal ! case(p_olev_lax) ethr = 2.d-5 gthr = 2.5d-3 maxcycle=n*2 acc=2.00d0 -! normal + + ! normal ! case default ethr = 5.d-6 gthr = 1.d-3 maxcycle=n*3 acc=1.0d0 -! tight + + ! tight ! case(p_olev_tight) ethr = 1.d-6 gthr = 8.d-4 maxcycle=n*5 acc=0.20d0 -! very tight + + ! very tight ! case(p_olev_vtight) ethr = 1.d-7 gthr = 2.d-4 maxcycle=n*20 acc=0.05d0 -! extreme + + ! extreme ! case(p_olev_extreme) ethr = 5.d-8 gthr = 5.d-5 maxcycle=n*20 acc=0.01d0 end select + maxcycle=min(maxcycle,10000) maxcycle=max(maxcycle,200) end subroutine get_optthr -!---------------------------------------- -! Approximate Normal Coordinate Optimizer -!---------------------------------------- +!> Approximate Normal Coordinate rational function optimizer subroutine ancopt(env,ilog,mol,chk,calc, & & egap,et,maxiter,maxcycle_in,etot,g,sigma,tight,pr,fail) + use xtb_mctc_convert use xtb_mctc_la @@ -132,67 +142,143 @@ subroutine ancopt(env,ilog,mol,chk,calc, & use xtb_lsrmsd implicit none - + + !> traceback for error handling character(len=*), parameter :: source = "optimizer_ancopt" - !! source of errors in the main program unit + + !> calculation environment type(TEnvironment), intent(inout) :: env - !! calculation environment + + !> molecular structure data type(TMolecule), intent(inout) :: mol - !! molecular structure data + + !> optimization level integer, intent(in) :: tight - !! optimization level + + !> max number of SCC cycles integer, intent(in) :: maxiter - !! max SCC cycles + + !> max number of optimization cycles integer, intent(in) :: maxcycle_in - !! max GEOOPT cycles + + !> wrapper for changing info during SCC type(TRestart),intent(inout) :: chk - !! wrapper for changing info during SCC + + !> polymorphic calculator instance class(TCalculator), intent(inout) :: calc - !! calculator instance + + !> electronic energy real(wp) :: eel - !! electronic energy + + !> total energy real(wp),intent(inout) :: etot - !! total energy + + !> electronic temperature real(wp),intent(in) :: et - !! electronic temperature + + !> HOMO-LUMO gap real(wp),intent(inout) :: egap - !! HOMO-LUMO gap + + !> gradients real(wp),intent(inout) :: g(3,mol%n) - !! gradients + + !> strain derivatives real(wp),intent(inout) :: sigma(3,3) - !! strain derivatives + + !> printlevel logical, intent(in) :: pr - !! if printed + + !> optimization failure logical, intent(out) :: fail - !! if failed + +!-----------------! +! local variables ! +!-----------------! - !> local variables type(TMolecule) :: molopt type(scc_results) :: res type(tb_anc) :: anc type(tb_timer) :: timer - real(wp) :: step,amu2au,au2cm,dumi,dumj,damp,hlow,edum,s6,thr,aaa,bbb - real(wp) :: maxdispl,gthr,ethr,hmax,energy,acc,rij(3),t1,t0,w1,w0,ccc - integer :: n3,i,j,k,l,jjj,ic,jc,ia,ja,ii,jj,info,lwork,nat3,liwork - integer :: nvar,iter,nread,maxcycle,maxmicro,itry,maxopt,iupdat,iii + + !> maximum coordinate displacement + real(wp) :: maxdispl + + !> lowest value for force constant + real(wp) :: hlow + + !> highest value for force constant + real(wp) :: hmax + + !> dispersion scaling + real(wp) :: s6 + + !> total energy from initial single point callculation + real(wp) :: estart + + !> energy & gradient convergence thresholds + real(wp) :: ethr, gthr + + + !> number of modes followed + integer :: modef + + !> max number of opt cycles + integer :: maxopt + + !> max number of opt cycles before generation of new ANC + integer :: maxmicro + + !> algorithm for updating Hessian + integer :: iupdat ! 0 = BFGS, 1 = Powell + + !> current iteration + integer :: iter + + !> number of atomic coordinates + integer :: nat3 + + !> deegrees of freedom + integer :: nvar + + !> work array size + integer :: lwork, liwork + + real(wp) :: step,amu2au,au2cm,dumi,dumj,damp,edum,thr,aaa,bbb + real(wp) :: energy,acc,rij(3),t1,t0,w1,w0,ccc + + integer :: n3,i,j,k,l,jjj,ic,jc,ia,ja,ii,jj,info + integer :: nread,itry,iii integer :: id,ihess,error integer, intent(in) :: ilog integer, external :: lin + + !> Hessian matrix real(wp),allocatable :: h (:,:) + real(wp),allocatable :: b (:,:) + real(wp),allocatable :: pmode(:,:) + real(wp),allocatable :: grmsd(:,:) + + !> force constants real(wp),allocatable :: fc(:) + + !> eigenvalues real(wp),allocatable :: eig(:) + real(wp),allocatable :: aux(:) real(wp),allocatable :: hess(:) integer, allocatable :: iwork(:) integer, allocatable :: totsym(:) - real(wp),allocatable :: pmode(:,:) - real(wp),allocatable :: grmsd(:,:) type(convergence_log), allocatable :: avconv + real(wp) :: U(3,3), x_center(3), y_center(3), rmsdval - integer :: modef - logical :: restart,ex,converged,linear - real(wp) :: estart,esave + real(wp) :: esave + logical :: restart,ex,converged + + !> if linear molecule + logical :: linear + + !> formatting strings for output character(len=*),parameter :: scifmt = & '(10x,":",3x,a,e22.7,1x,a,1x,":")' character(len=*),parameter :: dblfmt = & @@ -202,42 +288,43 @@ subroutine ancopt(env,ilog,mol,chk,calc, & character(len=*),parameter :: chrfmt = & '(10x,":",3x,a,a18, 10x,":")' - ! Print ANCopt header ! + ! print ANCopt header ! call ancopt_header(env%unit,set%veryverbose) - if(mol%n.eq.1) return - !! do not optimize for 1 molecule + if(mol%n.eq.1) return ! skip optimization for 1 atom + + ! performance profiling timer ! if (profile) call timer%new(8,.false.) if (profile) call timer%measure(1,'optimizer setup') - -! defaults - fail =.false. - modef=0 - hmax = 5.0_wp - maxdispl=set%optset%maxdispl_opt - hlow = set%optset%hlow_opt!0.01 in ancopt, 0.002 too small - s6 = set%mhset%s6 !slightly better than 30 for various proteins -! initial number of steps before new ANC are made by model Hessian -! increased during opt. - maxmicro=set%optset%micro_opt + + ! defaults ! + iter = 0 + fail = .false. + modef = 0 + iupdat= 0 + hmax = 5.0_wp + nat3 = 3 * mol%n + maxdispl = set%optset%maxdispl_opt ! def: 1.0 + hlow = set%optset%hlow_opt ! def: 0.01 + s6 = set%mhset%s6 ! def: 20.0 + maxmicro=set%optset%micro_opt ! def: 20 estart = etot - - iupdat=0 !0=BFGS, 1=Powell - + call get_optthr(mol%n,tight,ethr,gthr,maxopt,acc) + + ! if provided by user ! + if(maxcycle_in.gt.0)then + maxopt=maxcycle_in + endif + if(maxopt.lt.maxmicro) maxmicro=maxopt + + ! use Powell update if TS optimization (-> not implemeted) ! if(set%tsopt)then hlow=max(hlow,0.250d0) iupdat=1 endif - - call get_optthr(mol%n,tight,ethr,gthr,maxcycle,acc) - - if(maxcycle_in.le.0)then - maxopt=maxcycle - else - maxopt=maxcycle_in - endif - if(maxopt.lt.maxmicro) maxmicro=maxopt - if (set%optset%average_conv) then + + ! energy and gradient averaging ! + if (set%optset%average_conv) then ! default: .false. select type(calc) class is(TTMCalculator) avconv = load_turbomole_log(maxopt) @@ -250,22 +337,23 @@ subroutine ancopt(env,ilog,mol,chk,calc, & end select end if - call axis2(mol%n,mol%at,mol%xyz,aaa,bbb,ccc,dumi,dumj) - - !call open_file(ilog,'xtbopt.log','w') - iter = 0 - nat3 = 3 * mol%n - nvar = nat3 - 6 - linear = .false. - if(ccc.lt.1.d-10) then + ! determine if linear molecule ! + call axis2(mol%n,mol%xyz,aaa,bbb,ccc,dumi,dumj) + if (ccc.lt.1.d-10) then linear = .true. nvar = nat3 - 5 + else + linear = .false. + nvar = nat3 - 6 endif - if(fixset%n.gt.0) then ! exact fixing - nvar=nat3-3*fixset%n-3 + + ! exact fixing case ! + if(fixset%n.gt.0) then + nvar = nat3 - 3*fixset%n - 3 if(nvar.le.0) nvar=1 endif + ! adjust if restrated ! call open_binary(id,'.xtbtmpmode','r') if(id.ne.-1)then read (id) modef @@ -277,43 +365,52 @@ subroutine ancopt(env,ilog,mol,chk,calc, & allocate(pmode(nat3,1)) ! dummy allocated endif + ! print ANCopt settings ! if(pr)then write(env%unit,'(/,10x,51("."))') write(env%unit,'(10x,":",22x,a,22x,":")') "SETUP" write(env%unit,'(10x,":",49("."),":")') - write(env%unit,chrfmt) "optimization level",int2optlevel(tight) - write(env%unit,intfmt) "max. optcycles ",maxopt - write(env%unit,intfmt) "ANC micro-cycles ",maxmicro - write(env%unit,intfmt) "degrees of freedom",nvar + write(env%unit,chrfmt) "optimization level", int2optlevel(tight) + write(env%unit,intfmt) "max. optcycles ", maxopt + write(env%unit,intfmt) "ANC micro-cycles ", maxmicro + write(env%unit,intfmt) "degrees of freedom", nvar + if (modef>0) then - write(env%unit,intfmt) "# mode follow ",modef + write(env%unit,intfmt) "# mode follow ", modef endif + write(env%unit,'(10x,":",49("."),":")') + if (set%optset%exact_rf) then - write(env%unit,chrfmt) "RF solver ","spevx" + write(env%unit,chrfmt) "RF solver ", "spevx" else - write(env%unit,chrfmt) "RF solver ","davidson" + write(env%unit,chrfmt) "RF solver ", "davidson" endif - write(env%unit,chrfmt) "write xtbopt.log ",bool2string(ilog.ne.-1) + + write(env%unit,chrfmt) "write xtbopt.log ", bool2string(ilog.ne.-1) + if (linear) then - write(env%unit,chrfmt) "linear (good luck)",bool2string(linear) + write(env%unit,chrfmt) "linear (good luck)", bool2string(linear) else - write(env%unit,chrfmt) "linear? ",bool2string(linear) + write(env%unit,chrfmt) "linear? ", bool2string(linear) endif - write(env%unit,scifmt) "energy convergence",ethr, "Eh " - write(env%unit,scifmt) "grad. convergence ",gthr, "Eh/α" - write(env%unit,dblfmt) "maxmium RF displ. ",maxdispl," " - write(env%unit,scifmt) "Hlow (freq-cutoff)",hlow, " " - write(env%unit,dblfmt) "Hmax (freq-cutoff)",hmax, " " - write(env%unit,dblfmt) "S6 in model hess. ",s6, " " + + write(env%unit,scifmt) "energy convergence", ethr, "Eh " + write(env%unit,scifmt) "grad. convergence ", gthr, "Eh/α" + write(env%unit,dblfmt) "maxmium RF displ. ", maxdispl," " + write(env%unit,scifmt) "Hlow (freq-cutoff)", hlow, " " + write(env%unit,dblfmt) "Hmax (freq-cutoff)", hmax, " " + write(env%unit,dblfmt) "S6 in model hess. ", s6, " " write(env%unit,'(10x,51("."))') endif + ! work arrays ! lwork = 1 + 6*nat3 + 2*nat3**2 liwork = 8 * nat3 allocate(h(nat3,nat3),fc(nat3*(nat3+1)/2),eig(nat3)) + ! read in Hessian from file ! if (set%mhset%model == p_modh_read) then call open_file(ihess, 'hessian', 'r') if (ihess == -1) then @@ -332,39 +429,46 @@ subroutine ancopt(env,ilog,mol,chk,calc, & ex = .false. endif - call anc%allocate(mol%n,nvar,hlow,hmax) - - molopt = mol - - if (profile) call timer%measure(1) + call anc%allocate(mol%n,nvar,hlow,hmax) ! allocate ANC + molopt = mol ! copy molecular information + if (profile) call timer%measure(1) ! start opt timer ! ====================================================================== ANC_microiter: do ! ====================================================================== - if (profile) call timer%measure(2,'model hessian') + if (profile) call timer%measure(2,'model hessian') ! start timer for Hessian + + ! initial guess for Hessian ! if (.not.ex)then ! normal case - if(pr)write(env%unit,'(/,''generating ANC from model Hessian ...'')') - call modhes(env, calc, set%mhset, molopt%n, molopt%xyz, molopt%at, fc, pr) ! WBO (array wb) not used in present version - call env%check(fail) - if (fail) then - call env%error("Calculation of model hessian failed", source) - return - end if - !call qpothess(molopt%n,fc,molopt%xyz) - thr=1.d-11 - else - if(pr)write(env%unit,'(/,''generating ANC from read Hessian ...'')') - k=0 - do i=1,nat3 - do j=1,i - k=k+1 - fc(k)=h(j,i) - enddo - enddo - thr=1.d-10 + if (pr) & + & write(env%unit,'(/,''generating ANC from model Hessian ...'')') + + call modhes(env, calc, set%mhset, molopt%n, molopt%xyz, molopt%at, fc, pr) ! WBO (array wb) not used in present version + + call env%check(fail) + + if (fail) then + call env%error("Calculation of model hessian failed", source) + return + end if + !call qpothess(molopt%n,fc,molopt%xyz) + thr=1.d-11 + + else ! read in Hessian from file + if(pr)write(env%unit,'(/,''generating ANC from read Hessian ...'')') + k=0 + do i=1,nat3 + do j=1,i + k=k+1 + fc(k)=h(j,i) + enddo + enddo + thr=1.d-10 endif + print*,fc + stop if(modef.eq.0)then if(fixset%n.gt.0)then call trproj(molopt%n,nat3,molopt%xyz,fc,.false., -1 ,pmode,1) ! exact fixing @@ -375,8 +479,10 @@ subroutine ancopt(env,ilog,mol,chk,calc, & else call trproj(molopt%n,nat3,molopt%xyz,fc,.false.,modef,pmode,modef) ! NMF endif + if (profile) call timer%measure(2) + stop if (profile) call timer%measure(3,'ANC generation') ! this is completely useless, we blow up the Hessian, just to pack it again... k=0 @@ -388,7 +494,7 @@ subroutine ancopt(env,ilog,mol,chk,calc, & enddo enddo -! initialize hessian for opt. + ! initialize hessian for opt. ! call anc%new(env%unit,molopt%xyz,h,pr,linear) if (profile) call timer%measure(3) @@ -665,9 +771,9 @@ subroutine relax(env,iter,mol,anc,restart,maxcycle,maxdispl,ethr,gthr, & if(ii.gt.1)then ! hessian update if(iupdat.eq.0)then - call bfgs (anc%nvar,gnorm,gint,gold,displ,anc%hess) + call bfgs (anc%nvar,gnorm,gint,gold,displ,anc%hess) else - call powell(anc%nvar,gnorm,gint,gold,displ,anc%hess) + call powell(anc%nvar,gnorm,gint,gold,displ,anc%hess) endif endif if (profile) call timer%measure(7) @@ -758,6 +864,7 @@ subroutine relax(env,iter,mol,anc,restart,maxcycle,maxdispl,ethr,gthr, & restart=.true. etot=energy call anc%get_cartesian(mol%xyz) + end subroutine relax pure subroutine solver_ssyevx(n,thr,A,U,e,fail) @@ -991,6 +1098,7 @@ subroutine prdispl(nvar,displ) end subroutine prdispl +!> generate model Hessian subroutine modhes(env, calc, modh, natoms, xyz, chg, Hess, pr) use xtb_type_setvar use xtb_modelhessian @@ -1016,21 +1124,22 @@ subroutine modhes(env, calc, modh, natoms, xyz, chg, Hess, pr) type(modhess_setvar),intent(in) :: modh logical, intent(in) :: pr -! Other variables + !> other variables integer :: i integer :: nhess integer, intent(in) :: natoms real(wp),intent(in) :: xyz(3,natoms) - real(wp),intent(out) :: hess((natoms*3)*((natoms*3)+1)/2) + + !> model hessian + real(wp),intent(out) :: Hess((natoms*3)*((natoms*3)+1)/2) integer, intent(in) :: chg(natoms) -! initialize - nhess=3*natoms - Hess=0.d0 + ! initialize ! + Hess=0.0_wp select type(calc) - class default - select case(modh%model) + class default ! all calculator cases except GFN-FF + select case(modh%model) case default call env%error("internal error in model hessian!", source) return @@ -1047,7 +1156,7 @@ subroutine modhes(env, calc, modh, natoms, xyz, chg, Hess, pr) if (pr) write(env%unit,'(a)') "Using Swart-Hessian" call mh_swart(xyz, natoms, Hess, chg, modh) end select - type is(TGFFCalculator) + type is(TGFFCalculator) ! GFN-FF case select case(modh%model) case default call env%error("internal error in model hessian!", source) diff --git a/src/setparam.f90 b/src/setparam.f90 index 993cc2c65..0c5b245a3 100644 --- a/src/setparam.f90 +++ b/src/setparam.f90 @@ -223,17 +223,16 @@ module xtb_setparam character(len=:), allocatable :: opt_outfile character(len=:), allocatable :: opt_logfile integer, allocatable :: opt_engine + + !> ANCopt settings type(ancopt_setvar) :: optset = ancopt_setvar (& optlev = p_olev_normal, & -! number of opt. cycles before new ANC are made - micro_opt = 20, & -! total number of opt. cycles, 0 means automatically determined + micro_opt = 20, & ! increased during opt. maxoptcycle = 0, & ! det. in ancopt routine if not read in -! maximum coordinate displacement in ancopt maxdispl_opt = 1.000_wp, & -! lowest force constant in ANC generation (should be > 0.005) - hlow_opt = 0.010_wp, & + hlow_opt = 0.010_wp, & ! 0.002 is too small average_conv = .false.) + type(modhess_setvar) :: mhset = modhess_setvar (& model = p_modh_old, & ! force constants for stretch, bend and torsion diff --git a/src/splitparam.f90 b/src/splitparam.f90 index de34d1752..b515421fc 100644 --- a/src/splitparam.f90 +++ b/src/splitparam.f90 @@ -33,7 +33,9 @@ module xtb_splitparam real(wp) :: rcma = 0.0_wp real(wp) :: massf1 = 0.0_wp real(wp) :: massf2 = 0.0_wp - real(wp),allocatable :: atmass(:) + + !> atomic masses + real(wp), allocatable :: atmass(:) integer :: maxfrag = 0 diff --git a/src/type/anc.f90 b/src/type/anc.f90 index e50652a85..ffc1ba05d 100644 --- a/src/type/anc.f90 +++ b/src/type/anc.f90 @@ -25,9 +25,9 @@ module xtb_type_anc private type tb_anc - integer :: n !< number of atoms - integer :: n3 !< dimension of hessian - integer :: nvar !< actual dimension + integer :: n ! number of atoms + integer :: n3 ! dimension of hessian + integer :: nvar ! actual dimension real(wp) :: hlow real(wp) :: hmax real(wp),allocatable :: hess(:) diff --git a/src/type/setvar.f90 b/src/type/setvar.f90 index 4b8501847..385192cef 100644 --- a/src/type/setvar.f90 +++ b/src/type/setvar.f90 @@ -70,39 +70,51 @@ module xtb_type_setvar real(wp) :: broydamp = 0.40_wp end type scc_setvar -!! ------------------------------------------------------------------------ -! approximate normal coordinate rational function optimizer -!! ------------------------------------------------------------------------ + !> approximate normal coordinate rational function optimizer type :: ancopt_setvar -! default optimization level -! crude = -3, sloppy = -2, loose = -1, normal = 0, -! tight = 1, verytight = 2, extreme = 3 + + !> default optimization level + !> crude = -3, sloppy = -2, loose = -1, normal = 0, + !> tight = 1, verytight = 2, extreme = 3 integer :: optlev = 0 -! number of opt. cycles before new ANC are made + + !> number of opt. cycles before new ANC are made by model Hessian integer :: micro_opt = 0 -! total number of opt. cycles, 0 means automatically determined - integer :: maxoptcycle = 0 ! det. in ancopt routine if not read in -! maximum coordinate displacement in ancopt + + !> total number of opt. cycles, 0 means automatically determined + integer :: maxoptcycle = 0 + + !> maximum coordinate displacement in ancopt real(wp) :: maxdispl_opt = 0.0_wp -! lowest force constant in ANC generation (should be > 0.005) + + !> lowest force constant in ANC generation (should be > 0.005) real(wp) :: hlow_opt = 0.0_wp - logical :: exact_rf = .false. + + logical :: exact_rf = .false. + + !> average energy and gradient before checking for convergence + !> to accelerate numerically noisy potential energy surfaces logical :: average_conv = .false. + end type ancopt_setvar type modhess_setvar integer :: model = 0 -! force constants for stretch, bend and torsion + + !> cutoff for constructing internal coordinates + real(wp) :: rcut = 0.0_wp + + !> dispersion scaling in ANC generation + real(wp) :: s6 = 0.0_wp + + !> force constants for stretch, bend and torsion real(wp) :: kr = 0.0_wp real(wp) :: kf = 0.0_wp real(wp) :: kt = 0.0_wp real(wp) :: ko = 0.0_wp real(wp) :: kd = 0.0_wp real(wp) :: kq = 0.0_wp -! cutoff for constructing internal coordinates - real(wp) :: rcut = 0.0_wp -! dispersion scaling in ANC generation - real(wp) :: s6 = 0.0_wp + end type modhess_setvar !! ------------------------------------------------------------------------ diff --git a/test/unit/test_thermo.f90 b/test/unit/test_thermo.f90 index f1b676f5f..8da8499af 100644 --- a/test/unit/test_thermo.f90 +++ b/test/unit/test_thermo.f90 @@ -94,7 +94,7 @@ subroutine test_axis(error) call check(error, rot1(2), .226266337664493_wp, thr=thr2) call check(error, rot1(3), 7.01216792608729_wp, thr=thr2) - call axis2(mol%n, mol%at, mol%xyz, rot2(1), rot2(2), rot2(3), avmom2, mass2) + call axis2(mol%n, mol%xyz, rot2(1), rot2(2), rot2(3), avmom2, mass2) call check(error, rot2(1), .226251131473004_wp, thr=thr2) call check(error, rot2(2), .226266337664493_wp, thr=thr2) @@ -128,7 +128,7 @@ subroutine test_axis(error) call check(error, rot1(2), .19263614502269_wp, thr=thr2) call check(error, rot1(3), 4.7581644454539_wp, thr=thr2) - call axis2(mol%n, mol%at, mol%xyz, rot2(1), rot2(2), rot2(3), avmom2, mass2) + call axis2(mol%n, mol%xyz, rot2(1), rot2(2), rot2(3), avmom2, mass2) call check(error, rot2(1), .19017218374861_wp, thr=thr2) call check(error, rot2(2), .19263614502269_wp, thr=thr2)