github.com/rmera/gochem@v0.7.1/qm/turbomole.go (about)

     1  /*
     2   * turbomole.go, part of gochem.
     3   *
     4   *
     5   * Copyright 2012 Raul Mera <rmera{at}chemDOThelsinkiDOTfi>
     6   *
     7   * This program is free software; you can redistribute it and/or modify
     8   * it under the terms of the GNU Lesser General Public License as
     9   * published by the Free Software Foundation; either version 2.1 of the
    10   * License, or (at your option) any later version.
    11   *
    12   * This program is distributed in the hope that it will be useful,
    13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15   * GNU General Public License for more details.
    16   *
    17   * You should have received a copy of the GNU Lesser General
    18   * Public License along with this program.  If not, see
    19   * <http://www.gnu.org/licenses/>.
    20   *
    21   *
    22   * Gochem is developed at the laboratory for instruction in Swedish, Department of Chemistry,
    23   * University of Helsinki, Finland.
    24   *
    25   *
    26   */
    27  
    28  //The TM handler implementation differs from the rest in that it uses several TM programs
    29  // (define, x2t, t2x, cosmoprep) in order to prepare the input and retrieve results.
    30  //Because of this, the programs using this handler will not work if TM is not installed.
    31  //The handler has been made to work with TM7.
    32  
    33  package qm
    34  
    35  import (
    36  	"bufio"
    37  	"fmt"
    38  	"io"
    39  	"log"
    40  	"os"
    41  	"os/exec"
    42  	"strconv"
    43  	"strings"
    44  
    45  	chem "github.com/rmera/gochem"
    46  	v3 "github.com/rmera/gochem/v3"
    47  )
    48  
    49  // TMHandle is the representation of a Turbomole (TM) calculation
    50  // This imlpementation supports only singlets and doublets.
    51  type TMHandle struct {
    52  	defmethod    string
    53  	defbasis     string
    54  	defauxbasis  string
    55  	defgrid      int
    56  	previousMO   string
    57  	command      string
    58  	cosmoprepcom string
    59  	inputname    string
    60  	gimic        bool
    61  	marij        bool
    62  	dryrun       bool
    63  }
    64  
    65  // Creates and initializes a new instance of TMRuner, with values set
    66  // to its defaults.
    67  func NewTMHandle() *TMHandle {
    68  	run := new(TMHandle)
    69  	run.SetDefaults()
    70  	return run
    71  }
    72  
    73  const noCosmoPrep = "goChem/QM: Unable to run cosmoprep"
    74  
    75  //TMHandle methods
    76  
    77  // SetName sets the name of the subdirectory, in the current directory
    78  // where the calculation will be ran
    79  func (O *TMHandle) Name(name ...string) string {
    80  	ret := O.inputname
    81  	if len(name) > 0 && name[0] != "" {
    82  		O.inputname = name[0]
    83  		ret = name[0]
    84  	}
    85  	return ret
    86  
    87  }
    88  
    89  // SetMARIJ sets the multipole acceleration
    90  func (O *TMHandle) SetMARIJ(state bool) {
    91  	O.marij = state
    92  }
    93  
    94  // SetDryRun sets the flag to see this is a dry run or
    95  // if define will actually be run.
    96  func (O *TMHandle) SetDryRun(dry bool) {
    97  	O.dryrun = dry
    98  }
    99  
   100  // SetCommand doesn't do anything, and it is here only for compatibility.
   101  // In TM the command is set according to the method. goChem assumes a normal TM installation.
   102  func (O *TMHandle) SetCommand(name string) {
   103  	//Does nothing again
   104  }
   105  
   106  // SetCommand doesn't do anything, and it is here only for compatibility.
   107  // In TM the command is set according to the method. goChem assumes a normal TM installation.
   108  func (O *TMHandle) SetCosmoPrepCommand(name string) {
   109  	O.cosmoprepcom = name
   110  }
   111  
   112  // SetDefaults sets default values for TMHandle. default is an optimization at
   113  //
   114  //	TPSS-D3 / def2-SVP
   115  //
   116  // Defaults are not part of the API, they might change as new methods appear.
   117  func (O *TMHandle) SetDefaults() {
   118  	O.defmethod = "B97-3c"
   119  	O.defbasis = "def2-mTZVP"
   120  	O.defgrid = 4
   121  	O.defauxbasis = "def2-mTZVP"
   122  	O.command = "ridft"
   123  	O.marij = false  //Apparently marij can cause convergence problems
   124  	O.dryrun = false //define IS run by default.
   125  	O.inputname = "gochemturbo"
   126  	O.cosmoprepcom = "cosmoprep"
   127  }
   128  
   129  // addMARIJ adds the multipole acceleration if certain conditions are fullfilled:
   130  // O.marij must be true
   131  // The RI approximation must be in use
   132  // The system must have more than 20 atoms
   133  // The basis set cannot be very large (i.e. it can NOT be quadruple-zeta, tzvpp, or basis with diffuse functions)
   134  func (O *TMHandle) addMARIJ(defstring string, atoms chem.AtomMultiCharger, Q *Calc) string {
   135  	if !O.marij {
   136  		return defstring
   137  	}
   138  	if strings.Contains(strings.ToLower(Q.Basis), "def2") && strings.HasSuffix(Q.Basis, "d") { //Rappoport basis
   139  		return defstring
   140  	}
   141  	if strings.Contains(Q.Basis, "cc") && strings.Contains(Q.Basis, "aug") { //correlation consistent with diffuse funcs.
   142  		return defstring
   143  	}
   144  	if strings.Contains(strings.ToLower(Q.Basis), "qz") { //both cc-pVQZ and def2-QZVP and QZVPP
   145  		return defstring
   146  	}
   147  
   148  	if strings.Contains(strings.ToLower(Q.Basis), "def2-tzvpp") { //This is less clear but just in case I won't add MARIJ for tzvpp
   149  		return defstring
   150  	}
   151  	//I Have no idea what the MARIJ string was supposed to be. For now the method doesn't work. Remove this if you fix the code
   152  	//below, and uncoment it.
   153  	//	if Q.RI && atoms.Len() >= 20 {
   154  	//		defstring = fmt.Sprintf("%s%s\n\n")
   155  	//	}
   156  	return defstring
   157  }
   158  
   159  func ReasonableSetting(k string, Q *Calc) string {
   160  	if strings.Contains(k, "$scfiterlimit") {
   161  		k = "$scfiterlimit   100\n"
   162  	}
   163  
   164  	if strings.Contains(k, "$maxcor    500 MiB  per_core") {
   165  		k = "$maxcor    3000 MiB  per_core\n"
   166  	}
   167  	if strings.Contains(k, "$ricore      500") {
   168  		k = "$ricore      1000\n"
   169  	}
   170  	if Q.SCFConvHelp > 1 && strings.Contains(k, "$scfdamp") {
   171  		k = "$scfdamp start=10 step=0.005 min=0.5\n"
   172  
   173  	}
   174  	if Q.SCFTightness > 1 && strings.Contains(k, "$scfconv") {
   175  		k = "$scfconv  8\n"
   176  	}
   177  
   178  	return k
   179  }
   180  
   181  /*
   182  //Replaces the regular expression regex in the TM control file by replacement
   183  func (O *TMHandle) ReplaceInControl(regex, replacement string) error {
   184      	//NOTE: This has repeated code. Refactor
   185  	re, err := regexp.Compile(regex)
   186  	if err != nil {
   187  		return fmt.Errorf("ReplaceInControl: Replacement failed: %s", err.Error())
   188  	}
   189  
   190  	path, err := os.Getwd()
   191  	if err != nil {
   192  		return err
   193  	}
   194  	dir := strings.Split(path, "/")
   195  	if dir[len(dir)-1] != O.inputname {
   196  		defer os.Chdir("../")
   197  		os.Chdir(O.inputname)
   198  	}
   199  
   200  	f, err := os.Open("control")
   201  	if err != nil {
   202  		return Error{ErrCantInput, Turbomole, O.inputname, "", []string{"os.Open", "addtoControl"}, true}
   203  	}
   204  	lines := make([]string, 0, 200) //200 is just a guess for the number of lines in the control file
   205  	c := bufio.NewReader(f)
   206  	for err == nil {
   207  		var line string
   208  		line, err = c.ReadString('\n')
   209  		lines = append(lines, line)
   210  	}
   211  	f.Close() //I cant defer it because I need it closed now.
   212  	out, err := os.Create("control")
   213  	if err != nil {
   214  		return Error{ErrCantInput, Turbomole, O.inputname, "", []string{"os.Create", "addtoControl"}, true}
   215  	}
   216  	defer out.Close()
   217  	for _, i := range lines {
   218  		k := i
   219  		if re.MatchString(i) {
   220  			k = replacement
   221  		}
   222  
   223  		if _, err := fmt.Fprintf(out, k); err != nil {
   224  			return Error{ErrCantInput, Turbomole, O.inputname, "", []string{"fmt.Fprintf", "ReplaceInControl"}, true}
   225  		}
   226  	}
   227  	return nil
   228  }
   229  */
   230  
   231  //Adds the text strings given before or after the first line containing the where[0] string. By default the "target" is "$symmetry"
   232  func (O *TMHandle) AddToControl(toappend []string, before bool, where ...string) error {
   233  	c, err := os.Getwd()
   234  	if err != nil {
   235  		return err
   236  	}
   237  	dir := strings.Split(c, "/")
   238  	if dir[len(dir)-1] != O.inputname {
   239  		defer os.Chdir("../")
   240  		os.Chdir(O.inputname)
   241  	}
   242  	return O.addToControl(toappend, nil, before, where...)
   243  }
   244  
   245  // Adds all the strings in toapend to the control file, just before the $symmetry keyword
   246  // or just before the keyword specified in where.
   247  func (O *TMHandle) addToControl(toappend []string, Q *Calc, before bool, where ...string) error {
   248  	f, err := os.Open("control")
   249  	if err != nil {
   250  		return Error{ErrCantInput, Turbomole, O.inputname, "", []string{"os.Open", "addtoControl"}, true}
   251  	}
   252  	lines := make([]string, 0, 200) //200 is just a guess for the number of lines in the control file
   253  	c := bufio.NewReader(f)
   254  	for err == nil {
   255  		var line string
   256  		line, err = c.ReadString('\n')
   257  		lines = append(lines, line)
   258  	}
   259  	f.Close() //I cant defer it because I need it closed now.
   260  	out, err := os.Create("control")
   261  	if err != nil {
   262  		return Error{ErrCantInput, Turbomole, O.inputname, "", []string{"os.Create", "addtoControl"}, true}
   263  	}
   264  	defer out.Close()
   265  	var k string
   266  	target := "$symmetry"
   267  	if len(where) > 0 {
   268  		target = where[0]
   269  	}
   270  	for _, i := range lines {
   271  		k = i //May not be too efficient
   272  		if Q != nil {
   273  			k = ReasonableSetting(k, Q)
   274  		}
   275  		if strings.Contains(i, target) {
   276  			if !before {
   277  				if _, err := fmt.Fprintf(out, k); err != nil {
   278  					return Error{ErrCantInput, Turbomole, O.inputname, "", []string{"fmt.Fprintf", "addtoControl"}, true}
   279  
   280  				}
   281  			}
   282  			for _, j := range toappend {
   283  				if _, err := fmt.Fprintf(out, j+"\n"); err != nil {
   284  					return Error{ErrCantInput, Turbomole, O.inputname, "", []string{"fmt.Fprintf", "addtoControl"}, true}
   285  				}
   286  			}
   287  		}
   288  		if !strings.Contains(i, target) || before {
   289  			if _, err := fmt.Fprintf(out, k); err != nil {
   290  				return Error{ErrCantInput, Turbomole, O.inputname, "", []string{"fmt.Fprintf", "addtoControl"}, true}
   291  
   292  			}
   293  		}
   294  	}
   295  	return nil
   296  }
   297  
   298  func (O *TMHandle) addCosmo(epsilon float64) error {
   299  	//The ammount of newlines is wrong, must fix
   300  	cosmostring := "" //a few newlines before the epsilon
   301  	if epsilon == 0 {
   302  		return nil
   303  	}
   304  	cosmostring = fmt.Sprintf("%s%3.1f\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nr all b\n*\n\n\n\n\n\n", cosmostring, epsilon)
   305  	def := exec.Command(O.cosmoprepcom)
   306  	pipe, err := def.StdinPipe()
   307  	if err != nil {
   308  		return Error{noCosmoPrep, Turbomole, O.inputname, err.Error(), []string{"exec.StdinPipe", "addCosmo"}, true}
   309  	}
   310  	defer pipe.Close()
   311  	pipe.Write([]byte(cosmostring))
   312  	if err := def.Run(); err != nil {
   313  		return Error{noCosmoPrep, Turbomole, O.inputname, err.Error(), []string{"exec.Run", "addCosmo"}, true}
   314  
   315  	}
   316  	return nil
   317  
   318  }
   319  
   320  func (O *TMHandle) addBasis(basisOrEcp string, basiselems []string, basis, defstring string) string {
   321  	if basiselems == nil { //no atoms to add basis to, do nothing
   322  		return defstring
   323  	}
   324  	for _, elem := range basiselems {
   325  		defstring = fmt.Sprintf("%s%s \"%s\" %s\n", defstring, basisOrEcp, strings.ToLower(elem), basis)
   326  	}
   327  	return defstring
   328  }
   329  
   330  // modifies the coord file such as to freeze the atoms in the slice frozen.
   331  func (O *TMHandle) addFrozen(frozen []int) error {
   332  	f, err := os.Open("coord")
   333  	if err != nil {
   334  		return Error{noCosmoPrep, Turbomole, O.inputname, err.Error(), []string{"os.Open", "addFrozen"}, true}
   335  
   336  	}
   337  	lines := make([]string, 0, 200) //200 is just a guess for the number of lines in the coord file
   338  	c := bufio.NewReader(f)
   339  	for err == nil {
   340  		var line string
   341  		line, err = c.ReadString('\n')
   342  		lines = append(lines, line)
   343  	}
   344  	f.Close() //I cant defer it because I need it closed now.
   345  	out, err := os.Create("coord")
   346  	defer out.Close()
   347  	for key, i := range lines {
   348  		if isInInt(frozen, key-1) {
   349  			j := strings.Replace(i, "\n", " f\n", -1)
   350  			if _, err := fmt.Fprintf(out, j); err != nil {
   351  				return Error{noCosmoPrep, Turbomole, O.inputname, err.Error(), []string{"fmt.Fprintf", "addFrozen"}, true}
   352  
   353  			}
   354  		} else {
   355  			if _, err := fmt.Fprintf(out, i); err != nil {
   356  				return Error{noCosmoPrep, Turbomole, O.inputname, err.Error(), []string{"fmt.Fprintf", "addFrozen"}, true}
   357  
   358  			}
   359  		}
   360  	}
   361  	return nil
   362  }
   363  
   364  func copy2pipe(pipe io.ReadCloser, file *os.File, end chan bool) {
   365  	io.Copy(file, pipe)
   366  	end <- true
   367  }
   368  
   369  // BuildInput builds an input for TM based int the data in atoms, coords and C.
   370  // returns only error.
   371  // Note that at this point the interface does not support multiplicities different from 1 and 2.
   372  // The number in atoms is simply ignored.
   373  func (O *TMHandle) BuildInput(coords *v3.Matrix, atoms chem.AtomMultiCharger, Q *Calc) error {
   374  	const noDefine = "goChem/QM: Unable to run define"
   375  	const nox2t = "goChem/QM: Unable to run x2t"
   376  	err := os.Mkdir(O.inputname, os.FileMode(0755))
   377  	for i := 0; err != nil; i++ {
   378  		if strings.Contains(err.Error(), "file exists") {
   379  			O.inputname = fmt.Sprintf("%s%d", O.inputname, i)
   380  			err = os.Mkdir(O.inputname, os.FileMode(0755))
   381  		} else {
   382  			return Error{"goChem/QM: Unable to build input", Turbomole, O.inputname, err.Error(), []string{"os.Mkdir", "BuildInput"}, true}
   383  		}
   384  	}
   385  	_ = os.Chdir(O.inputname)
   386  	defer os.Chdir("..")
   387  	//Set the coordinates in a slightly stupid way.
   388  	chem.XYZFileWrite("file.xyz", coords, atoms)
   389  	x2t := exec.Command("x2t", "file.xyz")
   390  	stdout, err := x2t.StdoutPipe()
   391  	if err != nil {
   392  		return Error{nox2t, Turbomole, O.inputname, err.Error(), []string{"exec.StdoutPipe", "BuildInput"}, true}
   393  	}
   394  	coord, err := os.Create("coord")
   395  	if err != nil {
   396  		return Error{nox2t, Turbomole, O.inputname, err.Error(), []string{"os.Create", "BuildInput"}, true}
   397  
   398  	}
   399  	if err := x2t.Start(); err != nil {
   400  		return Error{nox2t, Turbomole, O.inputname, err.Error(), []string{"exec.Start", "BuildInput"}, true}
   401  
   402  	}
   403  	//	var end chan bool
   404  	//	go copy2pipe(stdout, coord, end)
   405  	//	<-end
   406  	io.Copy(coord, stdout)
   407  	coord.Close()                           //not defearable
   408  	defstring := "\n\n\na coord\nired\n*\n" //reduntant internals
   409  	if Q.CartesianOpt {
   410  		defstring = "\n\n\na coord\n*\nno\n"
   411  	}
   412  	if atoms == nil || coords == nil {
   413  		return Error{ErrMissingCharges, Turbomole, O.inputname, "", []string{"BuildInput"}, true}
   414  	}
   415  	if Q.Basis == "" {
   416  		log.Printf("no basis set assigned for TM calculation, will used the default %s, \n", O.defbasis)
   417  		Q.Basis = O.defbasis
   418  	}
   419  	//there is another check for these methods below. I should try to make it that it only checks for this
   420  	//once.
   421  	if Q.Method == "r2scan-3c" {
   422  		Q.Basis = "def2-mTZVPP"
   423  		Q.RI = true
   424  	} else if Q.Method == "b97-3c" {
   425  		Q.Basis = "def2-mTZVP"
   426  		Q.RI = true
   427  	} else if Q.Method == "hf-3c" {
   428  		Q.Basis = "minix"
   429  		Q.RI = false
   430  	}
   431  	defstring = defstring + "b all " + Q.Basis + "\n"
   432  	if Q.LowBasis != "" && len(Q.LBElements) > 0 {
   433  		defstring = O.addBasis("b", Q.LBElements, Q.LowBasis, defstring)
   434  	}
   435  	if Q.HighBasis != "" && len(Q.HBElements) > 0 {
   436  		defstring = O.addBasis("b", Q.HBElements, Q.HighBasis, defstring)
   437  	}
   438  	//Manually adding ECPs seem to be problematic, so I don't advise to do so.
   439  	if Q.ECP != "" && len(Q.ECPElements) > 0 {
   440  		defstring = O.addBasis("ecp", Q.ECPElements, Q.ECP, defstring)
   441  	}
   442  	defstring = defstring + "\n*\n"
   443  	//The following needs to be added because some atoms (I haven't tried so many, but
   444  	//so far only copper) causes define to ask an additional question. If one doesn't add "y\n"
   445  	//for each of those questions, the whole input for define will be wrong.
   446  	stupid := ""
   447  	stupidatoms := "Zn Cu" //if you want to add more stupid atoms just add then to the string: "Cu Zn"
   448  	for i := 0; i < atoms.Len(); i++ {
   449  		if stupidatoms == "" {
   450  			break
   451  		}
   452  		if strings.Contains(stupidatoms, atoms.Atom(i).Symbol) {
   453  			stupidatoms = strings.Replace(stupidatoms, atoms.Atom(i).Symbol, "", -1)
   454  			stupid = stupid + "y\n"
   455  		}
   456  	}
   457  	//Here we only produce singlet and doublet states (sorry). I will most certainly *not* deal with the "joys"
   458  	//of setting other multiplicities in define.
   459  	defstring = fmt.Sprintf("%seht\n%sy\ny\n%d\n\n\n\n\n", defstring, stupid, atoms.Charge()) //I add one additional "y\n"
   460  	method, ok := tMMethods[strings.ToLower(Q.Method)]
   461  	if !ok {
   462  		fmt.Fprintf(os.Stderr, "no method assigned for TM calculation, will used the default %s, \n", O.defmethod)
   463  		Q.Method = O.defmethod
   464  		Q.RI = true
   465  	} else {
   466  		Q.Method = method
   467  	}
   468  	//We only support HF and DFT
   469  	O.command = "dscf"
   470  	if Q.Method != "hf" && Q.Method != "hf-3c" {
   471  		grid := ""
   472  		if Q.Grid != 0 && Q.Grid <= 7 {
   473  			grid = fmt.Sprintf("grid\n m%d\n", Q.Grid)
   474  		} else {
   475  
   476  			grid = fmt.Sprintf("grid\n m%d\n", O.defgrid)
   477  		}
   478  		defstring = defstring + "dft\non\nfunc " + Q.Method + "\n" + grid + "*\n"
   479  		if Q.RI {
   480  			mem := 500
   481  			if Q.Memory != 0 {
   482  				mem = Q.Memory
   483  			}
   484  			defstring = fmt.Sprintf("%sri\non\nm %d\n*\n", defstring, mem)
   485  			O.command = "ridft"
   486  		}
   487  	}
   488  	defstring = O.addMARIJ(defstring, atoms, Q)
   489  	defstring = defstring + "*\n"
   490  	log.Println(defstring)
   491  
   492  	//set the frozen atoms (only cartesian constraints are supported)
   493  	if err := O.addFrozen(Q.CConstraints); err != nil {
   494  		return errDecorate(err, "BuildInput")
   495  	}
   496  	if O.dryrun {
   497  		return nil
   498  	}
   499  	def := exec.Command("define")
   500  	pipe, err := def.StdinPipe()
   501  	if err != nil {
   502  		return Error{noDefine, Turbomole, O.inputname, err.Error(), []string{"exec.StdinPipe", "BuildInput"}, true}
   503  	}
   504  	defer pipe.Close()
   505  	pipe.Write([]byte(defstring))
   506  	if err := def.Run(); err != nil {
   507  		return Error{noDefine, Turbomole, O.inputname, err.Error(), []string{"exec.Run", "BuildInput"}, true}
   508  	}
   509  	jc := jobChoose{}
   510  	jc.opti = func() {
   511  		O.command = "jobex -c 200"
   512  		if Q.RI {
   513  			O.command = O.command + " -ri"
   514  			if Q.OptTightness >= 2 {
   515  				O.command = O.command + "-gcart 4"
   516  
   517  			}
   518  		}
   519  	}
   520  	jc.forces = func() {
   521  		O.command = "NumForce -central"
   522  		if Q.RI {
   523  			O.command = O.command + " -ri"
   524  		}
   525  		O.addToControl([]string{" weight derivatives"}, nil, false, "$dft")
   526  	}
   527  	Q.Job.Do(jc)
   528  
   529  	//Now modify control
   530  	args := make([]string, 1, 2)
   531  	args[0], ok = tMDisp[Q.Dispersion]
   532  	if !ok {
   533  		fmt.Fprintf(os.Stderr, "Dispersion correction requested %s not supported, will used the default: D3, \n", Q.Dispersion)
   534  		args[0] = "$disp3"
   535  	}
   536  
   537  	if Q.Method == "hf-3c" {
   538  		args[0] = "$disp3 -bj -func hf-3c"
   539  	}
   540  	if Q.Method == "b97-3c" {
   541  		args[0] = "$disp3 -bj -abc"
   542  	}
   543  
   544  	if strings.Contains(Q.Method, "r2scan") { // both r2scan and r2scan-3c get the extra grid
   545  		if err := O.addToControl([]string{"    radsize   8"}, nil, false, "gridsize"); err != nil {
   546  			return errDecorate(err, "BuildInput")
   547  		}
   548  		if Q.Method == "r2scan-3c" {
   549  			args[0] = "$disp4"
   550  		}
   551  
   552  	}
   553  	if Q.Gimic {
   554  		O.command = "mpshift"
   555  		args = append(args, "$gimic")
   556  	}
   557  	if err := O.addToControl(args, Q, true); err != nil {
   558  		return errDecorate(err, "BuildInput")
   559  	}
   560  
   561  	//Finally the cosmo business.
   562  	err = O.addCosmo(Q.Dielectric)
   563  	if err != nil {
   564  		return errDecorate(err, "BuildInput")
   565  	}
   566  	return nil
   567  
   568  }
   569  
   570  var tMMethods = map[string]string{
   571  	"hf":        "hf",
   572  	"hf-3c":     "hf-3c",
   573  	"b3lyp":     "b3-lyp",
   574  	"b3-lyp":    "b3-lyp",
   575  	"pbe":       "pbe",
   576  	"tpss":      "tpss",
   577  	"tpssh":     "tpssh",
   578  	"bp86":      "b-p",
   579  	"b-p":       "b-p",
   580  	"blyp":      "b-lyp",
   581  	"b-lyp":     "b-lyp",
   582  	"b97-3c":    "b97-3c",
   583  	"r2scan-3c": "r2scan-3c",
   584  	"r2scan":    "r2scan",
   585  	"pw6b95":    "pw6b95",
   586  }
   587  
   588  var tMDisp = map[string]string{
   589  	"":       "",
   590  	"nodisp": "",
   591  	"D":      "$olddisp",
   592  	"D2":     "$disp2",
   593  	"D3":     "$disp3",
   594  	"D3BJ":   "$disp3 -bj",
   595  	"D4":     "$disp4",
   596  	"D3abc":  "$disp3 -bj -abc",
   597  }
   598  
   599  func (O *TMHandle) PreOpt(wait bool) error {
   600  	command := exec.Command("sh", "-c", "jobex -level xtb -c 1000 > jobexpreopt.out")
   601  	var err error
   602  	os.Chdir(O.inputname)
   603  	defer os.Chdir("..")
   604  	if wait == true {
   605  		err = command.Run()
   606  	} else {
   607  		err = command.Start()
   608  	}
   609  	if err != nil {
   610  		err = Error{ErrNotRunning, Turbomole, O.inputname, err.Error(), []string{"exec.Run/Start", "Run"}, true}
   611  		return err
   612  
   613  	}
   614  	return nil
   615  }
   616  
   617  // Run runs the command given by the string O.command
   618  // it waits or not for the result depending on wait.
   619  // This is a Unix-only function.
   620  func (O *TMHandle) Run(wait bool) error {
   621  	os.Chdir(O.inputname)
   622  	defer os.Chdir("..")
   623  	var err error
   624  	filename := strings.Fields(O.command)
   625  	//fmt.Println("nohup " + O.command + " > " + filename[0] + ".out")
   626  	command := exec.Command("sh", "-c", "nohup "+O.command+" >"+filename[0]+".out")
   627  	if wait == true {
   628  		err = command.Run()
   629  	} else {
   630  		err = command.Start()
   631  	}
   632  	if err != nil {
   633  		err = Error{ErrNotRunning, Turbomole, O.inputname, err.Error(), []string{"exec.Run/Start", "Run"}, true}
   634  
   635  	}
   636  	return err
   637  }
   638  
   639  // Energy returns the energy from the corresponding calculation, in kcal/mol.
   640  func (O *TMHandle) Energy() (float64, error) {
   641  	os.Chdir(O.inputname)
   642  	defer os.Chdir("..")
   643  	f, err := os.Open("energy")
   644  	if err != nil {
   645  		return 0, Error{ErrNoEnergy, Turbomole, O.inputname, err.Error(), []string{"os.Open", "Energy"}, true}
   646  	}
   647  	defer f.Close()
   648  	fio := bufio.NewReader(f)
   649  	line, err := getSecondToLastLine(fio)
   650  	if err != nil {
   651  		return 0, errDecorate(err, "Energy "+O.inputname)
   652  	}
   653  	en := strings.Fields(line)[1]
   654  	energy, err := strconv.ParseFloat(en, 64)
   655  	if err != nil {
   656  		err = Error{ErrNoEnergy, Turbomole, O.inputname, err.Error(), []string{"strconv.ParseFloat", "Energy"}, true}
   657  	}
   658  	return energy * chem.H2Kcal, err
   659  }
   660  
   661  // OptimizedGeometry returns the coordinates for the optimized structure.
   662  func (O *TMHandle) OptimizedGeometry(atoms chem.Atomer) (*v3.Matrix, error) {
   663  	const not2x = "unable to run t2x "
   664  	os.Chdir(O.inputname)
   665  	defer os.Chdir("..")
   666  	x2t := exec.Command("t2x")
   667  	stdout, err := x2t.StdoutPipe()
   668  	if err != nil {
   669  		return nil, Error{ErrNoGeometry, Turbomole, O.inputname, not2x + err.Error(), []string{"exec.StdoutPipe", "OptimizedGeometry"}, true}
   670  	}
   671  	if err := x2t.Start(); err != nil {
   672  		return nil, Error{ErrNoGeometry, Turbomole, O.inputname, not2x + err.Error(), []string{"exec.Start", "OptimizedGeometry"}, true}
   673  
   674  	}
   675  	mol, err := chem.XYZRead(stdout)
   676  	if err != nil {
   677  		return nil, errDecorate(err, "qm.OptimizedGeometry "+Turbomole+" "+O.inputname)
   678  	}
   679  	return mol.Coords[len(mol.Coords)-1], nil
   680  
   681  }
   682  
   683  // Gets the second to last line in a turbomole energy file given as a bufio.Reader.
   684  // expensive on the CPU but rather easy on the memory, as the file is read line by line.
   685  func getSecondToLastLine(f *bufio.Reader) (string, error) {
   686  	prevline := ""
   687  	line := ""
   688  	var err error
   689  	for {
   690  		line, err = f.ReadString('\n')
   691  		if err != nil {
   692  			break
   693  		}
   694  		if !strings.Contains(line, "$end") {
   695  			prevline = line
   696  		} else {
   697  			break
   698  		}
   699  	}
   700  	return prevline, Error{err.Error(), Turbomole, "", "Unknown", []string{"getSecondToLastLine"}, true}
   701  }