From c5e74500772067712f4077264a46f18797081f19 Mon Sep 17 00:00:00 2001 From: albert <92109627+Albkat@users.noreply.github.com> Date: Sat, 25 Nov 2023 17:47:48 +0100 Subject: [PATCH 1/2] refactor ORCA calculator Signed-off-by: albert <92109627+Albkat@users.noreply.github.com> --- src/extern/driver.f90 | 388 +++++++++++++++++------------ src/extern/orca.f90 | 563 ++++++++++++++++++++++++++++-------------- src/main/setup.f90 | 3 +- src/prog/main.F90 | 4 +- src/setparam.f90 | 18 +- 5 files changed, 629 insertions(+), 347 deletions(-) diff --git a/src/extern/driver.f90 b/src/extern/driver.f90 index 36c2a4b16..fa55b8758 100644 --- a/src/extern/driver.f90 +++ b/src/extern/driver.f90 @@ -15,111 +15,130 @@ ! along with xtb. If not, see . module xtb_extern_driver - use xtb_mctc_accuracy, only: wp - use xtb_mctc_io, only: stdout - use xtb_mctc_filetypes, only: fileType, generateFileName - use xtb_mctc_symbols, only: toSymbol - use xtb_type_calculator, only: TCalculator - use xtb_type_data, only: scc_results - use xtb_type_environment, only: TEnvironment - use xtb_type_molecule, only: TMolecule, len - use xtb_type_param, only: scc_parameter - use xtb_type_restart, only: TRestart - use xtb_io_writer, only: writeMolecule - use xtb_mctc_systools - use xtb_mctc_strings - use xtb_setparam - use xtb_readin - use xtb_mctc_convert - use xtb_fixparam - use xtb_scanparam - use xtb_sphereparam - use xtb_metadynamic - use xtb_constrainpot - use xtb_extern_turbomole, only: wrtm, rdtm - implicit none - private - - public :: TDriverCalculator, newDriverCalculator - - type, extends(TCalculator) :: TDriverCalculator - type(qm_external) :: ext - contains - !> Perform single point calculation - procedure :: singlepoint - !> Write informative printout - procedure :: writeInfo - end type TDriverCalculator + use xtb_mctc_accuracy, only: wp + use xtb_mctc_io, only: stdout + use xtb_mctc_filetypes, only: fileType, generateFileName + use xtb_mctc_symbols, only: toSymbol + use xtb_type_calculator, only: TCalculator + use xtb_type_data, only: scc_results + use xtb_type_environment, only: TEnvironment + use xtb_type_molecule, only: TMolecule, len + use xtb_type_param, only: scc_parameter + use xtb_type_restart, only: TRestart + use xtb_io_writer, only: writeMolecule + use xtb_mctc_systools + use xtb_mctc_strings + use xtb_setparam + use xtb_readin + use xtb_mctc_convert + use xtb_fixparam + use xtb_scanparam + use xtb_sphereparam + use xtb_metadynamic + use xtb_constrainpot + use xtb_extern_turbomole, only: wrtm, rdtm + implicit none + private + + public :: TDriverCalculator, newDriverCalculator, checkExe + + type, extends(TCalculator) :: TDriverCalculator + type(qm_external) :: ext + contains + !> Perform single point calculation + procedure :: singlepoint + !> Write informative printout + procedure :: writeInfo + end type TDriverCalculator + + character,parameter :: tilde = '~' contains !> Create a new calculator for driving the external driver program - subroutine newDriverCalculator(self, env, ext) - !> Instance of the external driver calculator - type(TDriverCalculator), intent(out) :: self - !> Calculation environment - type(TEnvironment), intent(inout) :: env - !> Settings for the external driver calculator - type(qm_external), intent(in) :: ext - - self%threadsafe = .false. - self%ext = ext - end subroutine newDriverCalculator - - subroutine singlepoint(self, env, mol, chk, printlevel, restart, & - & energy, gradient, sigma, hlgap, results) - !> Source of the generated errors - character(len=*), parameter :: source = 'extern_driver_singlepoint' - !> Calculator instance - class(TDriverCalculator), intent(inout) :: self - !> Computational environment - type(TEnvironment), intent(inout) :: env - !> Molecular structure data - type(TMolecule), intent(inout) :: mol - !> Wavefunction data - type(TRestart), intent(inout) :: chk - !> Print level for IO - integer, intent(in) :: printlevel - !> Restart from previous results - logical, intent(in) :: restart - !> Total energy - real(wp), intent(out) :: energy - !> Molecular gradient - real(wp), intent(out) :: gradient(:, :) - !> Strain derivatives - real(wp), intent(out) :: sigma(:, :) - !> HOMO-LUMO gap - real(wp), intent(out) :: hlgap - !> Detailed results - type(scc_results), intent(out) :: results - - integer :: i, ich - integer :: mode_sp_run = 1 - real(wp) :: efix - real(wp) :: dipole(3) - logical :: cache, exist - logical, parameter :: ccm = .true. - logical :: exitRun - character(len=*), parameter :: outfmt = & - '(9x,"::",1x,a,f23.12,1x,a,1x,"::")' - real(wp) :: xyz_cached(3, mol%n) - integer :: err - character(len=:),allocatable :: extension - character(len=:),allocatable :: tmpname - - call mol%update - - cache = .false. - energy = 0.0_wp - gradient(:, :) = 0.0_wp - sigma(:, :) = 0.0_wp - hlgap = 0.0_wp - efix = 0.0_wp - dipole(:) = 0.0_wp - - !$omp critical (turbo_lock) - inquire (file='gradient', exist=exist) - if (exist) then +subroutine newDriverCalculator(self, env, ext) + + !> Instance of the external driver calculator + type(TDriverCalculator), intent(out) :: self + + !> Calculation environment + type(TEnvironment), intent(inout) :: env + + !> Settings for the external driver calculator + type(qm_external), intent(in) :: ext + + self%threadsafe = .false. + self%ext = ext + +end subroutine newDriverCalculator + +subroutine singlepoint(self, env, mol, chk, printlevel, restart, & + & energy, gradient, sigma, hlgap, results) + + !> Source of the generated errors + character(len=*), parameter :: source = 'extern_driver_singlepoint' + + !> Calculator instance + class(TDriverCalculator), intent(inout) :: self + + !> Computational environment + type(TEnvironment), intent(inout) :: env + + !> Molecular structure data + type(TMolecule), intent(inout) :: mol + + !> Wavefunction data + type(TRestart), intent(inout) :: chk + + !> Print level for IO + integer, intent(in) :: printlevel + + !> Restart from previous results + logical, intent(in) :: restart + + !> Total energy + real(wp), intent(out) :: energy + + !> Molecular gradient + real(wp), intent(out) :: gradient(:, :) + + !> Strain derivatives + real(wp), intent(out) :: sigma(:, :) + + !> HOMO-LUMO gap + real(wp), intent(out) :: hlgap + + !> Detailed results + type(scc_results), intent(out) :: results + + integer :: i, ich + integer :: mode_sp_run = 1 + real(wp) :: efix + real(wp) :: dipole(3) + logical :: cache, exist + logical, parameter :: ccm = .true. + logical :: exitRun + character(len=*), parameter :: outfmt = & + '(9x,"::",1x,a,f23.12,1x,a,1x,"::")' + real(wp) :: xyz_cached(3, mol%n) + integer :: err + character(len=:),allocatable :: extension + character(len=:),allocatable :: tmpname + + call mol%update + + cache = .false. + energy = 0.0_wp + gradient(:, :) = 0.0_wp + sigma(:, :) = 0.0_wp + hlgap = 0.0_wp + efix = 0.0_wp + dipole(:) = 0.0_wp + + !$omp critical (turbo_lock) + inquire (file='gradient', exist=exist) + + if (exist) then ! ### only TM output is supported for now ### call rdtm(env,mol%n, .true., energy, gradient, xyz_cached) cache = all(abs(xyz_cached - mol%xyz) < 1.e-10_wp) @@ -128,72 +147,79 @@ subroutine singlepoint(self, env, mol, chk, printlevel, restart, & & "Geometry is equivalent to the one in & & 'gradient'. Reading gradient from: 'gradient'." endif - end if - if (.not. cache) then + end if + + if (.not. cache) then + call generateFileName(tmpname, 'xtbdriver', extension, mol%ftype) + if (printlevel > 0) then - write(env%unit,'(/,a,1x,a,/)') & - "updated geometry written to:",tmpname + write(env%unit,'(/,a,1x,a,/)') & + "updated geometry written to:",tmpname endif + call open_file(ich,tmpname,'w') + if (exist) then - call writeMolecule(mol, ich, format=mol%ftype, energy=energy, & - & gnorm=norm2(gradient)) + call writeMolecule(mol, ich, format=mol%ftype, energy=energy, & + & gnorm=norm2(gradient)) else - call writeMolecule(mol, ich, format=mol%ftype) + call writeMolecule(mol, ich, format=mol%ftype) end if + call close_file(ich) write (env%unit, '(72("="))') write (env%unit, '(1x,"*",1x,a)') & - "letting driver take over the control..." + "letting driver take over the control..." + call execute_command_line('exec 2>&1 '//self%ext%executable, exitstat=err) + if (err /= 0) then - call env%error('driver returned with non-zero exit status, doing the same', source) + call env%error('driver returned with non-zero exit status, doing the same', source) else - write (env%unit, '(1x,"*",1x,a)') & - "successful driver run, taking over control again..." + write (env%unit, '(1x,"*",1x,a)') & + "successful driver run, taking over control again..." end if + write (env%unit, '(72("="))') - ! ### only TM output is supported for now ### + ! only TM output is supported for now ! call rdtm(env,mol%n, .true., energy, gradient, xyz_cached) - end if - !$omp end critical (turbo_lock) - call env%check(exitRun) - if (exitRun) then + end if + + !$omp end critical (turbo_lock) + + call env%check(exitRun) + if (exitRun) then call env%error("Electronic structure method terminated", source) return - end if - - ! ------------------------------------------------------------------------ - ! various external potentials - call constrain_pot(potset, mol%n, mol%at, mol%xyz, gradient, efix) - call constrpot(mol%n, mol%at, mol%xyz, gradient, efix) - call cavity_egrad(mol%n, mol%at, mol%xyz, efix, gradient) - call metadynamic(metaset, mol%n, mol%at, mol%xyz, efix, gradient) - call metadynamic(rmsdset, mol%n, mol%at, mol%xyz, efix, gradient) - - ! ------------------------------------------------------------------------ - ! fixing of certain atoms - ! print*,abs(efix/etot) - energy = energy + efix - results%e_total = energy - results%gnorm = norm2(gradient) - results%dipole = dipole - if (fixset%n .gt. 0) then + end if + + ! various external potentials ! + call constrain_pot(potset, mol%n, mol%at, mol%xyz, gradient, efix) + call constrpot(mol%n, mol%at, mol%xyz, gradient, efix) + call cavity_egrad(mol%n, mol%at, mol%xyz, efix, gradient) + call metadynamic(metaset, mol%n, mol%at, mol%xyz, efix, gradient) + call metadynamic(rmsdset, mol%n, mol%at, mol%xyz, efix, gradient) + + ! fixing of certain atoms ! + energy = energy + efix + results%e_total = energy + results%gnorm = norm2(gradient) + results%dipole = dipole + if (fixset%n .gt. 0) then do i = 1, fixset%n - !print*,i,fixset%atoms(i) - gradient(1:3, fixset%atoms(i)) = 0 + gradient(1:3, fixset%atoms(i)) = 0 end do - end if + end if - if (printlevel .ge. 2) then + if (printlevel .ge. 2) then ! start with summary header if (.not. set%silent) then - write (env%unit, '(9x,53(":"))') - write (env%unit, '(9x,"::",21x,a,21x,"::")') "SUMMARY" + write (env%unit, '(9x,53(":"))') + write (env%unit, '(9x,"::",21x,a,21x,"::")') "SUMMARY" end if write (env%unit, '(9x,53(":"))') write (env%unit, outfmt) "total energy ", results%e_total, "Eh " @@ -201,21 +227,79 @@ subroutine singlepoint(self, env, mol, chk, printlevel, restart, & write (env%unit, outfmt) "HOMO-LUMO gap ", results%hl_gap, "eV " write (env%unit, '(9x,53(":"))') write (env%unit, '(a)') - end if - end subroutine singlepoint + end if + +end subroutine singlepoint + +subroutine writeInfo(self, unit, mol) + + !> Calculator instance + class(TDriverCalculator), intent(in) :: self + + !> Unit for I/O + integer, intent(in) :: unit + + !> Molecular structure data + type(TMolecule), intent(in) :: mol + + call generic_header(unit, "Driver", 49, 10) + +end subroutine writeInfo + +!> find executable in the system +subroutine checkExe(env,name,prog) - subroutine writeInfo(self, unit, mol) + !> traceback + character(len=*), parameter :: source = "extern_driver_checkExe" - !> Calculator instance - class(TDriverCalculator), intent(in) :: self + !> calculation environment + type(TEnvironment), intent(inout) :: env - !> Unit for I/O - integer, intent(in) :: unit + !> exe + character(len=:), allocatable, intent(inout) :: name - !> Molecular structure data - type(TMolecule), intent(in) :: mol + !> exe name to search for + character(len=*), intent(in) :: prog + + !> absolute path to home dir + character(len=:),allocatable :: homedir + + !> $PATH + character(len=:),allocatable :: syspath + + !> file existence bool + logical :: exists + + ! user-provided executable name ! + if (allocated(name)) then + + if (name(1:1).eq.tilde) then ! relative -> absolute path convertion ! + call rdvar('HOME',homedir) + name = homedir // name(2:) + endif + + ! system search ! + inquire(file=name,exist=exists) + + if (.not.exists) then + call env%error("'"//name//"' was not found, please check",source) + return + endif + + ! no executable provided ! + else + + call rdvar('PATH',syspath) + + ! search for ORCA executable in $PATH ! + call rdpath(syspath,'orca',name,exists) + if (.not.exists) then + call env%error('Could not locate ORCA executable',source) + return + endif - call generic_header(unit, "Driver", 49, 10) - end subroutine writeInfo + endif + +end subroutine checkExe end module xtb_extern_driver diff --git a/src/extern/orca.f90 b/src/extern/orca.f90 index f600d9f88..6e73ca0c1 100644 --- a/src/extern/orca.f90 +++ b/src/extern/orca.f90 @@ -44,238 +44,358 @@ module xtb_extern_orca type, extends(TCalculator) :: TOrcaCalculator + type(qm_external) :: ext + contains - !> Perform single point calculation + + !> perform single-point calculation procedure :: singlepoint - !> Calculate hessian + + !> calculate hessian procedure :: hessian - !> Write informative printout + + !> write informative printout procedure :: writeInfo + end type TOrcaCalculator contains -!----------------------------------------------------- -! Check the ORCA execution settings -!----------------------------------------------------- +!> create a new calculator for driving ORCA +subroutine newOrcaCalculator(self, env, ext, oniom) + + !> ORCA calculator instance + type(TOrcaCalculator), intent(out) :: self + + !> calculation environment + type(TEnvironment), intent(inout) :: env + + !> ORCA settings + type(qm_external), intent(in) :: ext + + !> if ONIOM calculation + logical, intent(in), optional :: oniom + + ! settings ! + self%ext = ext + if (present(oniom)) self%ext%oniom=oniom + self%threadsafe = .false. ! parallelization ! + + ! setup ORCA ! + call checkOrca(env, self%ext) + +end subroutine newOrcaCalculator + +!> setup environment for ORCA run subroutine checkOrca(env, ext) + + use xtb_extern_driver, only : checkExe + + !> traceback character(len=*), parameter :: source = 'extern_orca_checkOrca' - !! the name of the error producer routine + !> calculation environment type(TEnvironment), intent(inout) :: env - !! Calculation environment to handle I/O stream and error log + + !> ORCA settings type(qm_external), intent(inout) :: ext - !! Settings for the Orca calculation - character(len=:),allocatable :: homedir,syspath - character(len=:),allocatable :: line - character(len=5) :: chdum + + !> file existence logical :: exist + + !> XMol file logical :: chk_xyzfile + + !> ENGRAD keyword logical :: chk_engrad + + !> ORCA input file unit integer :: iorca - !! file handle - integer :: err - integer :: idx_bang,idx_star,idx_engr,idx_file,idx_xmol,idx_spce -!$ integer,external :: omp_get_num_threads - - !> check if the ORCA executable exist - if (allocated(ext%executable)) then - !! user input - - if (ext%executable(1:1).eq.'~') then - !! this is relative to the users home, expand it - call rdvar('HOME',homedir) - !! read environment variable - ext%executable = homedir // ext%executable(2:) - endif - inquire(file=ext%executable,exist=exist) - if (.not.exist) then - call env%error("'"//ext%executable//"' was not found, please check",source) - return - endif - else - !! no executable provided, lets find it - call rdvar('PATH',syspath) - call rdpath(syspath,'orca',ext%executable,exist) - !! find ORCA excutable in PATH variable - if (.not.exist) then - call env%error('Could not locate orca executable',source) - return - endif - endif + ! find ORCA ! + call checkExe(env,ext%executable,"orca") - !> check for wrong executable with the same name + ! check for wrong executable [GNOME screen reader] ! if (index(ext%executable,'/usr') == 1) then inquire(file=ext%executable//'_scf',exist=exist) if (.not. exist) then call env%error("Executable '"//ext%executable//"' is in the system path, "//& - & "is this the GNOME screen reader?") + & "is this the GNOME screen reader?", source) return endif endif - - !> see if there is a preference for an input file + + ! preference for an input file ! if (allocated(ext%input_file)) then inquire(file=ext%input_file,exist=exist) ext%exist = exist - else + + else ! assign input file name ! ext%input_file = "orca-"//get_random_name()//'.inp' - !! Create random file for the ORCA input file - inquire(file=ext%input_file,exist=exist) + inquire(file=ext%input_file,exist=exist) + ext%exist = exist endif - !> sanity check + ! check existing input ! if (ext%exist) then call open_file(iorca,ext%input_file,'r') - !> if exists, but empty - if (iorca.eq.-1) then + + ! sanity check ! + if (iorca.eq.-1) then call env%error("ORCA input file '"//ext%input_file//"' just vanished!",source) return endif - chk_engrad = .false. - chk_xyzfile = .false. - do - call strip_line(iorca,line,err) - !! to read line from iorca unit - if (err.ne.0) exit - idx_bang = index(line,'!') - idx_star = index(line,'*') - idx_engr = index(lowercase(line),'engrad') - idx_file = index(lowercase(line),'xyzfile') - idx_xmol = index(line,'.xyz') - idx_spce = index(trim(line),' ',back=.true.) - if (idx_bang.gt.0 .and. idx_engr.gt.0 .and. idx_engr.gt.idx_bang) & - chk_engrad = .true. - - if (idx_star.gt.0 .and. idx_file.gt.0 .and. idx_xmol.gt.0 .and. & - &idx_file.gt.idx_star .and. idx_xmol.gt.idx_file) then - chk_xyzfile = .true. - ext%input_string = trim(adjustl(line(idx_spce:))) - !! calculation method - endif - enddo + ! find engrad & xyzfile keys ! + call chkInput(iorca,chk_engrad,chk_xyzfile,ext%str_file) + call close_file(iorca) + if (.not.(chk_engrad.and.chk_xyzfile)) then call env%error("Please add '! ENGRAD' and/or '* xyzfile' to '"//& & ext%input_file //"'!",source) return endif + + else ! input string: find || create ! - else - !! if not exist, check for the input line if (allocated(ext%input_string)) then if (index(ext%input_string,'!') == 1) then ext%input_string = ext%input_string(2:) endif else - ! general input ext%input_string = 'b97-3c' endif + endif end subroutine checkOrca -!----------------------------------------------------- -! Create a new calculator for driving the Orca program -!----------------------------------------------------- -subroutine newOrcaCalculator(self, env, ext,oniom) +!> check ORCA input file for certain keywords +subroutine chkInput(iunit,grad,xyz,geo) + + !> ORCA file unit + integer, intent(in) :: iunit - implicit none - type(TOrcaCalculator), intent(out) :: self - !! Instance of the Orca calculator - type(TEnvironment), intent(inout) :: env - !! Calculation environment - type(qm_external), intent(in) :: ext - !! Settings for the Orca calculator - logical, intent(in),optional :: oniom + !> if certain keywords are present + logical, intent(out) :: grad, xyz - self%ext = ext - !! to save external settings from TSet%ext_orca to the new caluclator - self%threadsafe = .false. - !! not to call orca in parallel - if (present(oniom)) self%ext%oniom=oniom - !! if oniom calc - call checkOrca(env, self%ext) + !> buffer string + character(len=:), allocatable :: line + + !> molecular structure file name + character(len=:), allocatable :: geo + !> error handling + integer :: err -end subroutine newOrcaCalculator + !> position holders + integer :: startPos, endPos + + ! Intilialization ! + grad = .false. + xyz = .false. + + ! read file ! + do + + call strip_line(iunit,line,err) + if (err.ne.0) exit + + line = lowercase(line) + + if (firstArgComesFirst(line,'!','engrad')) & + grad = .true. + + if(firstArgComesFirst(line,'*','xyzfile')) then + if (firstArgComesFirst(line,'xyzfile','.xyz')) then + + ! get the name of geometry file ! + endPos = index(line,'.xyz') + startPos = index(line(:endPos),' ',back=.true.) + geo = line(startPos+1:endPos+3) + + xyz = .true. + endif + endif + + enddo + +end subroutine chkInput -!----------------------------------------------------- -! Create *.inp for the ORCA run -!----------------------------------------------------- -subroutine writeOrcaInp(io,mol,input,mode) +!> check argument positions wrt. each other +function firstArgComesFirst(str,arg1,arg2) + + !> raw line + character(len=*), intent(in) :: str + + !> arguments + character(len=*), intent(in) :: arg1, arg2 + + !> return value + logical :: firstArgComesFirst + + !> arg positions + integer :: pos1, pos2 + + ! find arg positions ! + pos1 = index(str,arg1) + pos2 = index(str,arg2) + + if (pos1 > 0 .and. pos2 > 0) then + firstArgComesFirst = pos1 < pos2 + else + firstArgComesFirst = .false. + endif + +end function + +!> create *.inp for ORCA run +subroutine writeOrcaInp(env,io,mol,input,mode) + + !> calculation enironment + type(TEnvironment), intent(inout) :: env + + !> ORCA input file unit integer, intent(in) :: io + + !> molecular structure data type(TMolecule), intent(in) :: mol + + !> input string character(len=*), intent(in) :: input + + !> calculation type character(len=*), intent(in) :: mode - integer :: i, nel, mult + + integer :: i, nel, mult, etc + integer :: num_threads write(io,'("#",1x,a)') & - "orca input generated by xtb, this is not the right way of using orca!" + "ORCA input is generated automatically. Not correct way." + + ! number of cores ! !$omp parallel !$omp master - !$ write(io,'("%",a,/,3x,a,1x,i0,/,a)') & - !$ "pal","nprocs",omp_get_num_threads()-1,"end" + num_threads = omp_get_num_threads() !$omp end master - !$omp end parallel + !$omp end parallel + + write(io,'("%",a,/,3x,a,1x,i0,/,a)') & + "pal","nprocs", num_threads ,"end" + + ! memory usage ! write(io,'("%",a,1x,i0)') & - "MaxCore",3000 ! hard coded, might be replaced at some point + "MaxCore", get_mem(env,num_threads) + + ! orca keywords ! write(io,'("!",1x,a)') & "nopop", "miniprint", input, mode - ! if(index(solv,'h2o').ne.0) solv='water' - ! if(index(solv,'chcl3').ne.0) solv='chloroform' - ! if(index(solv,'ether').ne.0) solv='diethyl ether' - ! if(smd)then - ! write(ich,'(''! cpcm('',a,'')'')')trim(solv) - ! write(ich,'(''%cpcm'')') - ! write(ich,'('' smd true'')') - ! write(ich,'('' solvent '',''"'',a,''"'')')trim(solv) - ! write(ich,'(''end'')') - ! endif + ! calculate number of electrons and multiplicity ! nel = sum(mol%at) - nint(mol%chrg) mult = mod(nel, 2) + 1 if (mod(nel, 2) == mod(mol%uhf, 2)) mult = mol%uhf + 1 + ! print molecular geometry ! write(io,'("*",1x,a,1x,i0,1x,i0)') & "xyz",nint(mol%chrg),mult + do i = 1, mol%n write(io,'(3x,a2,3(2x,F20.14))') toSymbol(mol%at(i)), mol%xyz(:,i)*autoaa end do + write(io,'("*",/)') end subroutine writeOrcaInp +!> get available memory from /proc/meminfo +function get_mem(env,nThread) + !> calculation environment + type(TEnvironment), intent(inout) :: env + + !> number of threads + integer, intent(in) :: nThread + + !> available memory + integer :: get_mem + + !> IO for + integer :: meminfo + + !> buffer string + character(len=:), allocatable :: line + + !> error handling + integer :: err, mem + + !> array for readl + real(wp) :: xx + + call open_file(meminfo, '/proc/meminfo','r') + + ! default case ! + get_mem = 3000 + + ! read from /proc/meminfo ! + if (meminfo.ne.-1) then + + call strip_line(meminfo,line,err) + call strip_line(meminfo,line,err) + call strip_line(meminfo,line,err) + if (index(line,"MemAvailable").ne.0) then + call readl(line,xx,mem) + get_mem = (int(xx)/1024)/nThread ! memory per core ! + get_mem = get_mem - 100 ! 100 MB that maybe needed for add jobs ! + endif + + endif + +endfunction get_mem + +!> read energy and gradients from ORCA calculation subroutine readOrcaEngrad(io, energy, gradient) + + !> orca unit number integer, intent(in) :: io + + !> electronic energy real(wp), intent(out) :: energy + + !> gradients real(wp), intent(out) :: gradient(:, :) - integer :: iat, nat + !> number of atoms + integer :: nat + !> loop index + integer :: iat + read(io,'(a)') read(io,'(a)') read(io,'(a)') read(io,*) nat + read(io,'(a)') read(io,'(a)') read(io,'(a)') read(io,*) energy + read(io,'(a)') read(io,'(a)') read(io,'(a)') + do iat = 1, nat read(io,*)gradient(1,iat) read(io,*)gradient(2,iat) read(io,*)gradient(3,iat) end do + end subroutine readOrcaEngrad subroutine readOrcaHess(io, hess, dipgrad) @@ -319,67 +439,85 @@ subroutine readOrcaHess(io, hess, dipgrad) end do end subroutine readOrcaHess -!----------------------------------------------------- -! To create or modify input file and execute the ORCA -!----------------------------------------------------- +!> wrapper for ORCA system call subroutine runOrca(env,ext,mol,energy,gradient) + !> traceback character(len=*), parameter :: source = 'extern_orca_runOrca' - !! the name of the error producer routine + + !> calculation environment type(TEnvironment), intent(inout) :: env - !! Calculation environment to handle I/O stream and error log + + !> ORCA settings type(qm_external), intent(in) :: ext - !! Settings for the Orca calculation + + !> molecular structure data type(TMolecule), intent(in) :: mol - !! Molecular structure data + + !> ORCA SP energy real(wp),intent(out) :: energy + + !> ORCA gradients real(wp),intent(out) :: gradient(:, :) - integer :: i,j,err - integer :: iorca - !! file IO unit + integer :: i,j,err, err2 + !> file IO unit + integer :: iorca, eunit logical :: exist character(len=:),allocatable :: line character(len=:),allocatable :: outfile character(len=:),allocatable :: tmpfile + character(Len=:), allocatable :: message + !$omp critical (orca_lock) - !> To decide whether to create or modify the input_file + ! input file handling ! if (ext%exist) then - !! we dump the name of the external xyz file to input_string... not cool - call open_file(iorca,ext%input_string,'w') + + ! rewrite *.xyz file from ORCA input ! + call open_file(iorca,ext%str_file,'w') call writeMolecule(mol, iorca, format=fileType%xyz) call close_file(iorca) + else - !! create new.inp file + + ! create ORCA input ! call open_file(iorca,ext%input_file,'w') - call writeOrcaInp(iorca,mol,ext%input_string, "engrad") + call writeOrcaInp(env,iorca,mol,ext%input_string, "engrad") call close_file(iorca) + endif - !> Actual orca cml run - write(env%unit,'(72("="))') - write(env%unit,'(1x,"*",1x,a)') & - "letting orca take over the control..." - if (set%oniom_settings%silent) then - call execute_command_line('exec 2>&1 '//ext%executable//' '// & - ext%input_file//'>orca.out',exitstat=err) + + ! shift responsibility from xtb ! + write(env%unit,'(25x,"*",1x,a)') & + "ORCA takes the control..." + write(env%unit,'(2x,72("-"))') + + ! ORCA system call ! + if (set%oniom_settings%silent) then ! silent mode ! + call execute_command_line('exec '//ext%executable//' '// & + ext%input_file//'>orca.out 2> err',exitstat=err) else - call execute_command_line('exec 2>&1 '//ext%executable//' '// & - ext%input_file,exitstat=err) + call execute_command_line('exec '//ext%executable//' '// & + ext%input_file//' 2> err',exitstat=err) endif - - if (err.ne.0) then - call env%error('orca returned with non-zero exit status, doing the same',source) + + ! ORCA dumps mpirun errors into stderr ! + call open_file(eunit,'err','r') + call getline(eunit,message,err2) + + ! shift responsibility back ! + write(env%unit,'(2x,72("-"))') + if (err.ne.0 .or. .not. is_iostat_end(err2)) then + call raise('E','ORCA returned with non-zero exit status, doing the same') else - write(env%unit,'(1x,"*",1x,a)') & - "successful orca run, taking over control again..." + write(env%unit,'(10x,"*",1x,a)') & + "successful ORCA run, taking over control again..." endif - write(env%unit,'(72("="))') - !> find output .engrad file + ! find output .engrad file ! i = index(ext%input_file,'.inp') - if (i > 0) then outfile = ext%input_file(:i-1)//'.engrad' tmpfile = ext%input_file(:i-1) @@ -387,12 +525,18 @@ subroutine runOrca(env,ext,mol,energy,gradient) outfile = ext%input_file//'.engrad' tmpfile = ext%input_file endif + inquire(file=outfile,exist=exist) if (.not.exist) then - call env%error("Could not find '"//outfile//"', aborting driver run",source) + call raise('E',"Could not find '"//outfile//"', aborting driver run") + else + + ! read .engrad ! call open_file(iorca,outfile,'r') call readOrcaEngrad(iorca, energy, gradient) + + ! clean calculation directory ! if (set%ceasefiles) then call remove_file(iorca) call env%io%deleteFile(tmpfile//".gbw") @@ -403,38 +547,51 @@ subroutine runOrca(env,ext,mol,energy,gradient) else call close_file(iorca) endif + endif !$omp end critical (orca_lock) end subroutine runOrca - +!> wrapper for ORCA single-point run subroutine singlepoint(self, env, mol, chk, printlevel, restart, & & energy, gradient, sigma, hlgap, results) - !> Source of the generated errors + + !> traceback character(len=*), parameter :: source = 'extern_orca_singlepoint' - !> Calculator instance + + !> calculator instance class(TOrcaCalculator), intent(inout) :: self - !> Computational environment + + !> computational environment type(TEnvironment), intent(inout) :: env - !> Molecular structure data + + !> molecular structure data type(TMolecule), intent(inout) :: mol - !> Wavefunction data + + !> wavefunction data type(TRestart), intent(inout) :: chk - !> Print level for IO + + !> print level for I/O integer, intent(in) :: printlevel - !> Restart from previous results + + !> restart from previous results logical, intent(in) :: restart - !> Total energy + + !> total energy real(wp), intent(out) :: energy - !> Molecular gradient + + !> molecular gradient real(wp), intent(out) :: gradient(:, :) - !> Strain derivatives + + !> strain derivatives real(wp), intent(out) :: sigma(:, :) + !> HOMO-LUMO gap real(wp), intent(out) :: hlgap - !> Detailed results + + !> detailed results type(scc_results), intent(out) :: results integer :: i,ich @@ -447,58 +604,63 @@ subroutine singlepoint(self, env, mol, chk, printlevel, restart, & character(len=*),parameter :: outfmt = & '(9x,"::",1x,a,f23.12,1x,a,1x,"::")' - call mol%update - + ! Intialization ! energy = 0.0_wp gradient(:, :) = 0.0_wp sigma(:, :) = 0.0_wp hlgap = 0.0_wp efix = 0.0_wp dipole(:) = 0.0_wp + call mol%update + ! system call wrapper ! call runOrca(env,self%ext,mol,energy,gradient) + ! error handling ! call env%check(exitRun) if (exitRun) then call env%error("Electronic structure method terminated", source) return end if - ! ------------------------------------------------------------------------ - ! various external potentials + ! various external potentials ! call constrain_pot(potset,mol%n,mol%at,mol%xyz,gradient,efix) call constrpot (mol%n,mol%at,mol%xyz,gradient,efix) call cavity_egrad(mol%n,mol%at,mol%xyz,efix,gradient) call metadynamic (metaset,mol%n,mol%at,mol%xyz,efix,gradient) call metadynamic (rmsdset,mol%n,mol%at,mol%xyz,efix,gradient) - - ! ------------------------------------------------------------------------ - ! fixing of certain atoms - ! print*,abs(efix/etot) energy = energy + efix + + ! save results ! results%e_total = energy results%gnorm = norm2(gradient) results%dipole = dipole + + ! exact fixing ! if (fixset%n.gt.0) then do i=1, fixset%n - !print*,i,fixset%atoms(i) gradient(1:3,fixset%atoms(i))=0 enddo endif + ! print calculation results ! if (printlevel.ge.2) then - ! start with summary header + + ! start with summary header ! if (.not.set%silent) then - write(env%unit,'(9x,53(":"))') - write(env%unit,'(9x,"::",21x,a,21x,"::")') "SUMMARY" + write(env%unit,'(/,9x,53(":"))') + write(env%unit,'(9x,"::",18x,a,19x,"::")') "ORCA SUMMARY" endif + write(env%unit,'(9x,53(":"))') write(env%unit,outfmt) "total energy ", results%e_total,"Eh " write(env%unit,outfmt) "gradient norm ", results%gnorm, "Eh/a0" write(env%unit,outfmt) "HOMO-LUMO gap ", results%hl_gap, "eV " write(env%unit,'(9x,53(":"))') write(env%unit,'(a)') + endif + end subroutine singlepoint !> Evaluate hessian by finite difference for all atoms @@ -536,7 +698,7 @@ subroutine hessian(self, env, mol0, chk0, list, step, hess, dipgrad) call close_file(iorca) else call open_file(iorca,self%ext%input_file,'w') - call writeOrcaInp(iorca,mol0,self%ext%input_string, "anfreq") + call writeOrcaInp(env,iorca,mol0,self%ext%input_string, "anfreq") call close_file(iorca) endif @@ -597,48 +759,73 @@ subroutine writeInfo(self, unit, mol) !$omp end parallel end subroutine writeInfo +!> random name generator function get_random_name() result(str) + + !> random name character(len=:), allocatable :: str + + !> random number real :: rnd + + !> reinterpretation of real number as an integer integer :: irnd call random_number(rnd) irnd = transfer(rnd, irnd) str = to_hex(abs(irnd)) + end function -pure function to_hex(val, width) result(string) +!> transform integer value into hexadecimal +pure function to_hex(val) result(string) + + !> integer input integer, intent(in) :: val - integer, intent(in), optional :: width + + !> resulting name character(len=:), allocatable :: string - integer, parameter :: buffer_len = range(val)+2 + + !> length of the buffer + integer, parameter :: buffer_len = range(val) + 2 + + !> intermediate value buffer character(len=buffer_len) :: buffer + + !> position in the buffer integer :: pos + + !> current integer value integer :: n + + !> hexadecimal base integer, parameter :: base = 16 + + !> hexadecimal digits mapping character(len=1), parameter :: numbers(0:base-1) = & ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"] + + ! initialization ! + n = val + buffer = "" + pos = buffer_len + 1 + + ! return 0 ! if (val == 0) then string = numbers(0) return end if - - n = abs(val) - buffer = "" - - pos = buffer_len + 1 + + ! decimal -> hexadecimal ! do while (n > 0) pos = pos - 1 buffer(pos:pos) = numbers(mod(n, base)) n = n/base end do - if (val < 0) then - pos = pos - 1 - buffer(pos:pos) = '-' - end if string = buffer(pos:) + end function to_hex end module xtb_extern_orca diff --git a/src/main/setup.f90 b/src/main/setup.f90 index dca319dcf..28491c29f 100644 --- a/src/main/setup.f90 +++ b/src/main/setup.f90 @@ -154,7 +154,8 @@ subroutine newCalculator(env, mol, calc, fname, restart, accuracy, input, iff_da end if call move_alloc(iff, calc) - + + ! ORCA => https://orcaforum.kofo.mpg.de/app.php/portal ! case(p_ext_orca) allocate(orca) call newOrcaCalculator(orca, env, set%ext_orca) diff --git a/src/prog/main.F90 b/src/prog/main.F90 index 7d17a1016..18abfe80f 100644 --- a/src/prog/main.F90 +++ b/src/prog/main.F90 @@ -1281,6 +1281,7 @@ subroutine parseArguments(env, args, inputFile, paramFile, lgrad, & #endif !$ endif !$ endif + case('--restart') restart = .true. @@ -1444,8 +1445,7 @@ subroutine parseArguments(env, args, inputFile, paramFile, lgrad, & call set_exttyp('oniom') call args%nextArg(sec) - !> To handle no argument case - if (.not.allocated(sec)) then + if (.not.allocated(sec)) then ! handle no argument case ! call env%error("No inner region is provided for ONIOM", source) return end if diff --git a/src/setparam.f90 b/src/setparam.f90 index 2ff0ffe0b..993cc2c65 100644 --- a/src/setparam.f90 +++ b/src/setparam.f90 @@ -134,10 +134,19 @@ module xtb_setparam type qm_external - character(len=:),allocatable :: path - character(len=:),allocatable :: executable - character(len=:),allocatable :: input_file - character(len=:),allocatable :: input_string + character(len=:), allocatable :: path + + !> absolute path to executable + character(len=:), allocatable :: executable + + !> external input + character(len=:), allocatable :: input_file + + !> alternative for input_file + character(len=:), allocatable :: input_string + + !> molecular structure file + character(len=:), allocatable :: str_file !> if input_file exist logical :: exist @@ -145,6 +154,7 @@ module xtb_setparam !> special case of the oniom embedding logical :: oniom=.false. + end type qm_external integer, parameter :: p_ext_vtb = -1 From 726d7d29e6f4a10f06686fda0292ba1343c91043 Mon Sep 17 00:00:00 2001 From: albert <92109627+Albkat@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:49:02 +0100 Subject: [PATCH 2/2] update driver.f90 Signed-off-by: albert <92109627+Albkat@users.noreply.github.com> --- src/extern/driver.f90 | 59 +++++++++++++++++++++++++++++++------------ src/extern/orca.f90 | 1 + 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/extern/driver.f90 b/src/extern/driver.f90 index fa55b8758..4320c81bf 100644 --- a/src/extern/driver.f90 +++ b/src/extern/driver.f90 @@ -125,6 +125,13 @@ subroutine singlepoint(self, env, mol, chk, printlevel, restart, & character(len=:),allocatable :: extension character(len=:),allocatable :: tmpname + !> executable + character(len=50), allocatable :: exe(:) + character(len=:), allocatable :: buff + + !> number of arguments + integer :: nargs + call mol%update cache = .false. @@ -172,11 +179,23 @@ subroutine singlepoint(self, env, mol, chk, printlevel, restart, & write (env%unit, '(72("="))') write (env%unit, '(1x,"*",1x,a)') & "letting driver take over the control..." + + ! check driver executable ! + allocate(exe(1)) + call parse(self%ext%executable,' ',exe,nargs) + ! if absolute path is given ! + if (index(exe(1),"/").ne.0) then + buff = exe(1) + call checkExe(env,buff) + else + call checkExe(env,buff,exe(1)) + endif + call execute_command_line('exec 2>&1 '//self%ext%executable, exitstat=err) if (err /= 0) then - call env%error('driver returned with non-zero exit status, doing the same', source) + call raise('E','driver returned with non-zero exit status, doing the same') else write (env%unit, '(1x,"*",1x,a)') & "successful driver run, taking over control again..." @@ -216,11 +235,13 @@ subroutine singlepoint(self, env, mol, chk, printlevel, restart, & end if if (printlevel .ge. 2) then - ! start with summary header + + ! start with summary header ! if (.not. set%silent) then write (env%unit, '(9x,53(":"))') write (env%unit, '(9x,"::",21x,a,21x,"::")') "SUMMARY" end if + write (env%unit, '(9x,53(":"))') write (env%unit, outfmt) "total energy ", results%e_total, "Eh " write (env%unit, outfmt) "gradient norm ", results%gnorm, "Eh/a0" @@ -259,7 +280,7 @@ subroutine checkExe(env,name,prog) character(len=:), allocatable, intent(inout) :: name !> exe name to search for - character(len=*), intent(in) :: prog + character(len=*), intent(in), optional :: prog !> absolute path to home dir character(len=:),allocatable :: homedir @@ -270,33 +291,39 @@ subroutine checkExe(env,name,prog) !> file existence bool logical :: exists + + ! user-provided executable name ! if (allocated(name)) then - + if (name(1:1).eq.tilde) then ! relative -> absolute path convertion ! call rdvar('HOME',homedir) name = homedir // name(2:) - endif - + endif + ! system search ! inquire(file=name,exist=exists) if (.not.exists) then - call env%error("'"//name//"' was not found, please check",source) - return + call raise('E',"'"//trim(name)//"' was not found, please check") endif - ! no executable provided ! + ! no exe ! else + + ! search for prog executable in $PATH ! + if (present(prog)) then - call rdvar('PATH',syspath) + call rdvar('PATH',syspath) + call rdpath(syspath,prog,name,exists) - ! search for ORCA executable in $PATH ! - call rdpath(syspath,'orca',name,exists) - if (.not.exists) then - call env%error('Could not locate ORCA executable',source) - return - endif + if (.not.exists) then + call raise('E','Could not locate '//trim(prog)//' executable') + endif + + else + call raise('E','No executable specified') + endif endif diff --git a/src/extern/orca.f90 b/src/extern/orca.f90 index 6e73ca0c1..77ca4958e 100644 --- a/src/extern/orca.f90 +++ b/src/extern/orca.f90 @@ -514,6 +514,7 @@ subroutine runOrca(env,ext,mol,energy,gradient) else write(env%unit,'(10x,"*",1x,a)') & "successful ORCA run, taking over control again..." + call env%io%deleteFile("err") endif ! find output .engrad file !