diff --git a/src/extern/CMakeLists.txt b/src/extern/CMakeLists.txt
index 6972fd132..debee173a 100644
--- a/src/extern/CMakeLists.txt
+++ b/src/extern/CMakeLists.txt
@@ -18,6 +18,7 @@
set(dir "${CMAKE_CURRENT_SOURCE_DIR}")
list(APPEND srcs
+ "${dir}/driver.f90"
"${dir}/mopac.f90"
"${dir}/orca.f90"
"${dir}/turbomole.f90"
diff --git a/src/extern/driver.f90 b/src/extern/driver.f90
new file mode 100644
index 000000000..914a18326
--- /dev/null
+++ b/src/extern/driver.f90
@@ -0,0 +1,199 @@
+! This file is part of xtb.
+! SPDX-Identifier: LGPL-3.0-or-later
+!
+! xtb is free software: you can redistribute it and/or modify it under
+! the terms of the GNU Lesser General Public License as published by
+! the Free Software Foundation, either version 3 of the License, or
+! (at your option) any later version.
+!
+! xtb is distributed in the hope that it will be useful,
+! but WITHOUT ANY WARRANTY; without even the implied warranty of
+! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+! GNU Lesser General Public License for more details.
+!
+! You should have received a copy of the GNU Lesser General Public License
+! 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
+ 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
+
+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
+
+ call mol%update
+
+ 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
+ call rdtm(mol%n, .true., energy, gradient, xyz_cached)
+ cache = all(abs(xyz_cached - mol%xyz) < 1.e-10_wp)
+ end if
+ if (.not. cache) then
+ call wrtm(mol%n, mol%at, mol%xyz)
+
+ write (env%unit, '(72("="))')
+ write (env%unit, '(1x,"*",1x,a)') &
+ "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)
+ else
+ write (env%unit, '(1x,"*",1x,a)') &
+ "successful driver run, taking over control again..."
+ end if
+ write (env%unit, '(72("="))')
+
+ call rdtm(mol%n, .true., energy, gradient, xyz_cached)
+ 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
+ do i = 1, fixset%n
+ !print*,i,fixset%atoms(i)
+ gradient(1:3, fixset%atoms(i)) = 0
+ end do
+ end if
+
+ 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"
+ 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"
+ 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
+
+ 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
+
+end module xtb_extern_driver
diff --git a/src/extern/meson.build b/src/extern/meson.build
index c2eda73b5..4f2c2283d 100644
--- a/src/extern/meson.build
+++ b/src/extern/meson.build
@@ -16,6 +16,7 @@
# along with xtb. If not, see .
srcs += files(
+ 'driver.f90',
'mopac.f90',
'orca.f90',
'turbomole.f90',
diff --git a/src/extern/turbomole.f90 b/src/extern/turbomole.f90
index 5a4f8cd81..08b19087e 100644
--- a/src/extern/turbomole.f90
+++ b/src/extern/turbomole.f90
@@ -43,7 +43,7 @@ module xtb_extern_turbomole
implicit none
private
- public :: wrtm
+ public :: wrtm, rdtm
public :: TTMCalculator, newTMCalculator
type, extends(TCalculator) :: TTMCalculator
diff --git a/src/main/setup.f90 b/src/main/setup.f90
index 1e3a0d2b1..e1ba40237 100644
--- a/src/main/setup.f90
+++ b/src/main/setup.f90
@@ -23,6 +23,7 @@ module xtb_main_setup
use xtb_extern_orca, only : TOrcaCalculator, newOrcaCalculator
use xtb_extern_mopac, only : TMopacCalculator, newMopacCalculator
use xtb_extern_turbomole, only : TTMCalculator, newTMCalculator
+ use xtb_extern_driver, only : TDriverCalculator, newDriverCalculator
use xtb_type_calculator, only : TCalculator
use xtb_type_environment, only : TEnvironment
use xtb_type_molecule, only : TMolecule
@@ -66,6 +67,7 @@ subroutine newCalculator(env, mol, calc, fname, restart, accuracy, input)
type(TMopacCalculator), allocatable :: mopac
type(TTMCalculator), allocatable :: turbo
type(TOniomCalculator), allocatable :: oniom
+ type(TDriverCalculator), allocatable :: driver
logical :: exitRun
@@ -108,7 +110,10 @@ subroutine newCalculator(env, mol, calc, fname, restart, accuracy, input)
allocate(turbo)
call newTMCalculator(turbo, set%extcode, set%extmode)
call move_alloc(turbo, calc)
-
+ case(p_ext_driver)
+ allocate(driver)
+ call newDriverCalculator(driver, env, set%ext_driver)
+ call move_alloc(driver, calc)
case(p_ext_oniom)
allocate(oniom)
call newOniomCalculator(oniom, env, mol, input)
diff --git a/src/prog/main.F90 b/src/prog/main.F90
index 5d8d7498d..4ddfaff48 100644
--- a/src/prog/main.F90
+++ b/src/prog/main.F90
@@ -1347,6 +1347,13 @@ subroutine parseArguments(env, args, inputFile, paramFile, accuracy, lgrad, &
case('--orca')
call set_exttyp('orca')
+ case('--driver')
+ call set_exttyp('driver')
+ call args%nextArg(sec)
+ if (allocated(sec)) then
+ set%ext_driver%executable = sec
+ end if
+
case('--mopac')
call set_exttyp('mopac')
diff --git a/src/set_module.f90 b/src/set_module.f90
index e1762cdaf..e2117f708 100644
--- a/src/set_module.f90
+++ b/src/set_module.f90
@@ -912,6 +912,8 @@ subroutine set_exttyp(typ)
set%mode_extrun = p_ext_xtb
case('orca')
set%mode_extrun = p_ext_orca
+ case('driver')
+ set%mode_extrun = p_ext_driver
case('oniom')
set%mode_extrun = p_ext_oniom
case('turbomole')
diff --git a/src/setparam.f90 b/src/setparam.f90
index c9d2ba8f4..d4f5d2f99 100644
--- a/src/setparam.f90
+++ b/src/setparam.f90
@@ -112,6 +112,7 @@ module xtb_setparam
integer, parameter :: p_ext_vtb = -1
integer, parameter :: p_ext_eht = 0
integer, parameter :: p_ext_xtb = 1
+ integer, parameter :: p_ext_driver = 3
integer, parameter :: p_ext_turbomole = 4
integer, parameter :: p_ext_orca = 5
integer, parameter :: p_ext_mopac = 12
@@ -386,6 +387,7 @@ module xtb_setparam
!! ------------------------------------------------------------------------
+ type(qm_external) :: ext_driver
type(qm_external) :: ext_orca
type(qm_external) :: ext_turbo
type(qm_external) :: ext_mopac