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

     1  /*
     2   * nwchem.go, part of gochem.
     3   *
     4   *
     5   * Copyright 2013 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   */
    23  
    24  package qm
    25  
    26  import (
    27  	"bufio"
    28  	"fmt"
    29  	"log"
    30  	"os"
    31  	"os/exec"
    32  	"runtime"
    33  	"strconv"
    34  	"strings"
    35  
    36  	chem "github.com/rmera/gochem"
    37  	v3 "github.com/rmera/gochem/v3"
    38  )
    39  
    40  //NWChemHandle represents an NWChem calculation.
    41  //Note that the default methods and basis vary with each program, and even
    42  //for a given program they are NOT considered part of the API, so they can always change.
    43  type NWChemHandle struct {
    44  	defmethod   string
    45  	defbasis    string
    46  	defauxbasis string
    47  	previousMO  string
    48  	restart     bool
    49  	smartCosmo  bool
    50  	command     string
    51  	inputname   string
    52  	nCPU        int
    53  	wrkdir      string
    54  }
    55  
    56  //Initializes and returns a NWChem handle
    57  func NewNWChemHandle() *NWChemHandle {
    58  	run := new(NWChemHandle)
    59  	run.SetDefaults()
    60  	return run
    61  }
    62  
    63  //NWChemHandle methods
    64  
    65  //SetnCPU sets the number of CPU to be used
    66  func (O *NWChemHandle) SetnCPU(cpu int) {
    67  	O.nCPU = cpu
    68  }
    69  
    70  //SetRestart sets whether the calculation is a restart
    71  //of a previous one.
    72  func (O *NWChemHandle) SetRestart(r bool) {
    73  	O.restart = r
    74  }
    75  
    76  //SetName sets the name for the job, reflected in the input and
    77  //output files.
    78  func (O *NWChemHandle) SetName(name string) {
    79  	O.inputname = name
    80  }
    81  
    82  //SetCommand sets the name/path of the MOPAC excecutable
    83  func (O *NWChemHandle) SetCommand(name string) {
    84  	O.command = name
    85  }
    86  
    87  //SetWorkDir sets the working directory for the calculation
    88  func (O *NWChemHandle) SetWorkDir(d string) {
    89  	O.wrkdir = d
    90  }
    91  
    92  //SetMOName sets the name of a file containing orbitals which will be used as a guess for this calculations
    93  func (O *NWChemHandle) SetMOName(name string) {
    94  	O.previousMO = name
    95  }
    96  
    97  //SetsSmartCosmo sets the behaviour of NWChem regarding COSMO.
    98  //For an optimization, a true value causes NBWChem to first calculate an SCF with do_gasphase True and use THAT density guess
    99  //for the first optimization step. The optimization is done with do_gasphase False.
   100  //for a SP, smartCosmo simply means do_gasphase False.
   101  //Notice that SmartCosmo is not reallty too smart, for optimizations. In my tests, it doesn't really
   102  //make things better. I keep it mostly just in case.
   103  func (O *NWChemHandle) SetSmartCosmo(set bool) {
   104  	O.smartCosmo = set
   105  }
   106  
   107  //SetDefaults sets the NWChem calculation to goChem's default values (_not_ considered part of the API!)
   108  //As of now, default is a single-point at
   109  //TPSS/def2-SVP with RI, and half the logical CPUs available (to account for the multithreading common on Intel CPUs)
   110  func (O *NWChemHandle) SetDefaults() {
   111  	O.defmethod = "tpss"
   112  	O.defbasis = "def2-svp"
   113  	O.command = "nwchem"
   114  	O.smartCosmo = false
   115  	cpu := runtime.NumCPU() / 2 //we divide by 2 because of the hyperthreading that is so frequent nowadays.
   116  	O.nCPU = cpu
   117  
   118  }
   119  
   120  //BuildInput builds an input for NWChem based int the data in atoms, coords and C.
   121  //returns only error.
   122  func (O *NWChemHandle) BuildInput(coords *v3.Matrix, atoms chem.AtomMultiCharger, Q *Calc) error {
   123  	//Only error so far
   124  	if atoms == nil || coords == nil {
   125  		return Error{ErrMissingCharges, NWChem, O.inputname, "", []string{"BuildInput"}, true}
   126  	}
   127  	if Q.Basis == "" {
   128  		log.Printf("no basis set assigned for NWChem calculation, will use the default %s, \n", O.defbasis)
   129  		Q.Basis = O.defbasis
   130  	}
   131  	if Q.Method == "" {
   132  		log.Printf("no method assigned for NWChem calculation, will use the default %s, \n", O.defmethod)
   133  		Q.Method = O.defmethod
   134  		Q.RI = true
   135  	}
   136  	if O.inputname == "" {
   137  		O.inputname = "gochem"
   138  	}
   139  	if O.wrkdir != "" {
   140  		O.wrkdir = O.wrkdir + "/"
   141  	}
   142  	//The initial guess
   143  	vectors := fmt.Sprintf("output  %s.movecs", O.inputname) //The initial guess
   144  	switch Q.Guess {
   145  	case "":
   146  	case "hcore": //This option is not a great idea, apparently.
   147  		vectors = fmt.Sprintf("input hcore %s", vectors)
   148  	default:
   149  		if !Q.OldMO {
   150  			//If the user gives something in Q.Guess but DOES NOT want an old MO to be used, I assume he/she wants to put whatever
   151  			//is in Q.Guess directly  in the vector keyword. If you want the default put an empty string in Q.Guess.
   152  			vectors = fmt.Sprintf("%s %s", Q.Guess, vectors)
   153  			break
   154  		}
   155  		//I assume the user gave a basis set name in Q.Guess which I can use to project vectors from a previous run.
   156  		moname := getOldMO(O.previousMO)
   157  		if moname == "" {
   158  			break
   159  		}
   160  		if strings.ToLower(Q.Guess) == strings.ToLower(Q.Basis) {
   161  			//Useful if you only change functionals.
   162  			vectors = fmt.Sprintf("input %s %s", moname, vectors)
   163  		} else {
   164  			//This will NOT work if one assigns different basis sets to different atoms.
   165  			vectors = fmt.Sprintf("input project %s %s %s", strings.ToLower(Q.Guess), moname, vectors)
   166  		}
   167  	}
   168  	vectors = "vectors " + vectors
   169  
   170  	disp, ok := nwchemDisp[Q.Dispersion]
   171  	if !ok {
   172  		disp = "vdw 3"
   173  	}
   174  	tightness := ""
   175  	switch Q.SCFTightness {
   176  	case 1:
   177  		tightness = "convergence energy 5.000000E-08\n convergence density 5.000000E-09\n convergence gradient 1E-05"
   178  	case 2:
   179  		//NO idea if this will work, or the criteria will be stronger than the criteria for the intergral evaluation
   180  		//and thus the SCF will never converge. Delete when properly tested.
   181  		tightness = "convergence energy 1.000000E-10\n convergence density 5.000000E-11\n convergence gradient 1E-07"
   182  	}
   183  
   184  	//For  now, the only convergence help I trust is to run a little HF calculation before and use the orbitals as a guess.
   185  	//It works quite nicely. When the NWChem people get their shit together and fix the bugs with cgmin and RI and cgmin and
   186  	//COSMO, cgmin will be a great option also.
   187  	scfiters := "iterations 60"
   188  	prevscf := ""
   189  	if Q.SCFConvHelp > 0 {
   190  		scfiters = "iterations 200"
   191  		if Q.Guess == "" {
   192  			prevscf = fmt.Sprintf("\nbasis \"3-21g\"\n * library 3-21g\nend\nset \"ao basis\" 3-21g\nscf\n maxiter 200\n vectors output hf.movecs\n %s\nend\ntask scf energy\n\n", strings.ToLower(mopacMultiplicity[atoms.Multi()]))
   193  			vectors = fmt.Sprintf("vectors input project \"3-21g\" hf.movecs output %s.movecs", O.inputname)
   194  		}
   195  	}
   196  	grid, ok := nwchemGrid[Q.Grid]
   197  	if !ok {
   198  		grid = "medium"
   199  	}
   200  	if Q.SCFTightness > 0 { //We need this if we want the SCF to converge.
   201  		grid = "xfine"
   202  	}
   203  	grid = fmt.Sprintf("grid %s", grid)
   204  	var err error
   205  
   206  	//Only cartesian constraints supported by now.
   207  	constraints := ""
   208  	if len(Q.CConstraints) > 0 {
   209  		constraints = "constraints\n fix atom"
   210  		for _, v := range Q.CConstraints {
   211  			constraints = fmt.Sprintf("%s %d", constraints, v+1) //goChem numbering starts from 0, apparently NWChem starts from 1, hence the v+1
   212  		}
   213  		constraints = constraints + "\nend"
   214  	}
   215  
   216  	cosmo := ""
   217  	if Q.Dielectric > 0 {
   218  		//SmartCosmo in a single-point means that do_gasphase False is used, nothing fancy.
   219  		if Q.Job.Opti || O.smartCosmo {
   220  			cosmo = fmt.Sprintf("cosmo\n dielec %4.1f\n do_gasphase False\nend", Q.Dielectric)
   221  		} else {
   222  			cosmo = fmt.Sprintf("cosmo\n dielec %4.1f\n do_gasphase True\nend", Q.Dielectric)
   223  		}
   224  	}
   225  	memory := ""
   226  	if Q.Memory != 0 {
   227  		memory = fmt.Sprintf("memory total %d mb", Q.Memory)
   228  	}
   229  	m := strings.ToLower(Q.Method)
   230  	method, ok := nwchemMethods[m]
   231  	if !ok {
   232  		method = "xtpss03 ctpss03"
   233  	}
   234  	method = fmt.Sprintf("xc %s", method)
   235  
   236  	task := "dft energy"
   237  	driver := ""
   238  	preopt := ""
   239  	esp := ""
   240  	jc := jobChoose{}
   241  	jc.charges = func() {
   242  		esp = "esp\n restrain\nend\n"
   243  		task = task + "\ntask esp"
   244  	}
   245  	jc.opti = func() {
   246  		eprec := "" //The available presition is set to default except if tighter SCF convergene criteria are being used.
   247  		if Q.SCFTightness > 0 {
   248  			eprec = " eprec 1E-7\n"
   249  		}
   250  		if Q.Dielectric > 0 && O.smartCosmo {
   251  			//If COSMO is used, and O.SmartCosmo is enabled, we start the optimization with a rather loose SCF (the default).
   252  			//and use that denisty as a starting point for the next calculation. The idea is to
   253  			//avoid the gas phase calculation in COSMO.
   254  			//This procedure doesn't seem to help at all, and just using do_gasphase False appears to be good enough in my tests.
   255  			preopt = fmt.Sprintf("cosmo\n dielec %4.1f\n do_gasphase True\nend\n", Q.Dielectric)
   256  			preopt = fmt.Sprintf("%sdft\n iterations 100\n %s\n %s\n print low\nend\ntask dft energy\n", preopt, vectors, method)
   257  			vectors = fmt.Sprintf("vectors input %s.movecs output  %s.movecs", O.inputname, O.inputname) //We must modify the initial guess so we use the vectors we have just generated
   258  		}
   259  		//We use this 3-step optimization scheme where we try to compensate for the lack of
   260  		//variable trust radius in nwchem (at least, back when I wrote this!)
   261  		task = "dft optimize"
   262  		//First an optimization with very loose convergency and a small trust radius.
   263  		driver = fmt.Sprintf("driver\n maxiter 200\n%s trust 0.05\n gmax 0.0500\n grms 0.0300\n xmax 0.1800\n xrms 0.1200\n xyz %s_prev\nend\ntask dft optimize", eprec, O.inputname)
   264  		//Then a second optimization with a less loose convergency and a 0.1 trust radius
   265  		driver = fmt.Sprintf("%s\ndriver\n maxiter 200\n%s trust 0.1\n gmax 0.009\n grms 0.001\n xmax 0.04 \n xrms 0.02\n xyz %s_prev2\nend\ntask dft optimize", driver, eprec, O.inputname)
   266  		//Then the final optimization with the default trust radius and convergence criteria.
   267  		driver = fmt.Sprintf("%s\ndriver\n maxiter 200\n%s trust 0.3\n xyz %s\nend\n", driver, eprec, O.inputname)
   268  		//Old criteria (ORCA): gmax 0.003\n grms 0.0001\n xmax 0.004 \n xrms 0.002\n
   269  	}
   270  	Q.Job.Do(jc)
   271  	//////////////////////////////////////////////////////////////
   272  	//Now lets write the thing. Ill process/write the basis later
   273  	//////////////////////////////////////////////////////////////
   274  	file, err := os.Create(fmt.Sprintf("%s.nw", O.wrkdir+O.inputname))
   275  	if err != nil {
   276  		return Error{err.Error(), NWChem, O.wrkdir + O.inputname, "", []string{"os.Create", "BuildInput"}, true}
   277  
   278  	}
   279  	defer file.Close()
   280  	start := "start"
   281  	if O.restart {
   282  		start = "restart"
   283  	}
   284  
   285  	_, err = fmt.Fprintf(file, "%s %s\n", start, O.inputname)
   286  	//after this check its assumed that the file is ok.
   287  	if err != nil {
   288  		return Error{err.Error(), NWChem, O.inputname, "", []string{"fmt.Fprintf", "BuildInput"}, true}
   289  	}
   290  	fmt.Fprint(file, "echo\n") //echo input in the output.
   291  	fmt.Fprintf(file, "charge %d\n", atoms.Charge())
   292  	if memory != "" {
   293  		fmt.Fprintf(file, "%s\n", memory) //the memory
   294  	}
   295  	//Now the geometry:
   296  	//If we have cartesian constraints we give the directive noautoz to optimize in cartesian coordinates.
   297  	autoz := ""
   298  	if len(Q.CConstraints) > 0 {
   299  		autoz = "noautoz"
   300  	}
   301  	fmt.Fprintf(file, "geometry units angstroms noautosym %s\n", autoz)
   302  	elements := make([]string, 0, 5) //I will collect the different elements that are in the molecule using the same loop as the geometry.
   303  	for i := 0; i < atoms.Len(); i++ {
   304  		symbol := atoms.Atom(i).Symbol
   305  		//In the following if/else I try to set up basis for specific atoms. Not SO sure it works.
   306  		if isInInt(Q.HBAtoms, i) {
   307  			symbol = symbol + "1"
   308  		} else if isInInt(Q.LBAtoms, i) {
   309  			symbol = symbol + "2"
   310  		}
   311  		fmt.Fprintf(file, " %-2s  %8.3f%8.3f%8.3f \n", symbol, coords.At(i, 0), coords.At(i, 1), coords.At(i, 2))
   312  
   313  		if !isInString(elements, symbol) {
   314  			elements = append(elements, symbol)
   315  		}
   316  	}
   317  	fmt.Fprintf(file, "end\n")
   318  	fmt.Fprintf(file, prevscf) //The preeliminar SCF if exists.
   319  	//The basis. First the ao basis (required)
   320  	decap := strings.ToLower //hoping to make the next for loop less ugly
   321  	basis := make([]string, 1, 2)
   322  	basis[0] = "\"ao basis\""
   323  	fmt.Fprintf(file, "basis \"large\" spherical\n") //According to the manual this fails with COSMO. The calculations dont crash. Need to compare energies and geometries with Turbomole in order to be sure.
   324  	for _, el := range elements {
   325  		if isInString(Q.HBElements, el) || strings.HasSuffix(el, "1") {
   326  			fmt.Fprintf(file, " %-2s library %s\n", el, decap(Q.HighBasis))
   327  		} else if isInString(Q.LBElements, el) || strings.HasSuffix(el, "2") {
   328  			fmt.Fprintf(file, " %-2s library %s\n", el, decap(Q.LowBasis))
   329  		} else {
   330  			fmt.Fprintf(file, " %-2s library %s\n", el, decap(Q.Basis))
   331  		}
   332  	}
   333  	fmt.Fprintf(file, "end\n")
   334  	fmt.Fprintf(file, "set \"ao basis\" large\n")
   335  	//Only Ahlrichs basis are supported for RI. USE AHLRICHS BASIS, PERKELE! :-)
   336  	//The only Ahlrichs J basis in NWchem appear to be equivalent to def2-TZVPP/J (orca nomenclature). I suppose that they are still faster
   337  	//than not using RI if the main basis is SVP. One can also hope that they are good enough if the main basis is QZVPP or something.
   338  	//(about the last point, it appears that in Turbomole, the aux basis also go up to TZVPP).
   339  	//This comment is based on the H, Be and C basis.
   340  	if Q.RI {
   341  		fmt.Fprint(file, "basis \"cd basis\"\n * library \"Ahlrichs Coulomb Fitting\"\nend\n")
   342  	}
   343  	//Now the geometry constraints. I kind of assume they are
   344  	if constraints != "" {
   345  		fmt.Fprintf(file, "%s\n", constraints)
   346  	}
   347  	fmt.Fprintf(file, preopt)
   348  	if cosmo != "" {
   349  		fmt.Fprintf(file, "%s\n", cosmo)
   350  	}
   351  	//The DFT block
   352  	fmt.Fprint(file, "dft\n")
   353  	fmt.Fprintf(file, " %s\n", vectors)
   354  	fmt.Fprintf(file, " %s\n", scfiters)
   355  	if tightness != "" {
   356  		fmt.Fprintf(file, " %s\n", tightness)
   357  	}
   358  	fmt.Fprintf(file, " %s\n", grid)
   359  	fmt.Fprintf(file, " %s\n", method)
   360  	if disp != "" {
   361  		fmt.Fprintf(file, " disp %s\n", disp)
   362  	}
   363  	if Q.Job.Opti {
   364  		fmt.Fprintf(file, " print convergence\n")
   365  	}
   366  	//task part
   367  	fmt.Fprintf(file, " mult %d\n", atoms.Multi())
   368  	fmt.Fprint(file, "end\n")
   369  	if Q.Job.Charges {
   370  		fmt.Fprintf(file, esp)
   371  	}
   372  	fmt.Fprintf(file, "%s", driver)
   373  	fmt.Fprintf(file, "task %s\n", task)
   374  
   375  	return nil
   376  }
   377  
   378  //Run runs the command given by the string O.command
   379  //it waits or not for the result depending on wait.
   380  //Not waiting for results works
   381  //only for unix-compatible systems, as it uses bash and nohup.
   382  func (O *NWChemHandle) Run(wait bool) (err error) {
   383  	if wait == true {
   384  		out, err := os.Create(fmt.Sprintf("%s.out", O.wrkdir+O.inputname))
   385  		if err != nil {
   386  			return Error{ErrNotRunning, NWChem, O.wrkdir + O.inputname, err.Error(), []string{"os.Create", "Run"}, true}
   387  
   388  		}
   389  		defer out.Close()
   390  		command := exec.Command(O.command, fmt.Sprintf("%s.nw", O.inputname))
   391  		if O.nCPU > 1 {
   392  			//	fmt.Println("mpirun", "-np", fmt.Sprintf("%d", O.nCPU), O.command, fmt.Sprintf("%s.nw", O.inputname)) ////////////////////////////
   393  			command = exec.Command("mpirun", "-np", fmt.Sprintf("%d", O.nCPU), O.command, fmt.Sprintf("%s.nw", O.inputname))
   394  		}
   395  		command.Dir = O.wrkdir
   396  		command.Stdout = out
   397  		command.Stderr = out
   398  		err = command.Run()
   399  
   400  	} else {
   401  		//This will not work in windows.
   402  		command := exec.Command("sh", "-c", "nohup "+O.command+fmt.Sprintf(" %s.nw > %s.out &", O.inputname, O.inputname))
   403  		command.Dir = O.wrkdir
   404  		err = command.Start()
   405  	}
   406  	if err != nil {
   407  		err = Error{ErrNotRunning, Mopac, O.inputname, err.Error(), []string{"exec.command.Start/Run", "Run"}, true}
   408  	}
   409  	return err
   410  }
   411  
   412  func getOldMO(prevMO string) string {
   413  	dir, _ := os.Open("./")     //This should always work, hence ignoring the error
   414  	files, _ := dir.Readdir(-1) //Get all the files.
   415  	for _, val := range files {
   416  		if prevMO != "" {
   417  			break
   418  		}
   419  		if val.IsDir() == true {
   420  			continue
   421  		}
   422  		name := val.Name()
   423  		if strings.Contains(".movecs", name) {
   424  			prevMO = name
   425  			break
   426  		}
   427  	}
   428  	if prevMO != "" {
   429  		return prevMO
   430  	} else {
   431  		return ""
   432  
   433  	}
   434  }
   435  
   436  var nwchemDisp = map[string]string{
   437  	"nodisp": "",
   438  	"D2":     "vdw 2",
   439  	"D3":     "vdw 3",
   440  	"D3ZERO": "vdw 3",
   441  	"D3Zero": "vdw 3",
   442  	"D3zero": "vdw 3",
   443  }
   444  
   445  var nwchemGrid = map[int]string{
   446  	1: "xcoarse",
   447  	2: "coarse",
   448  	3: "medium",
   449  	4: "fine",
   450  	5: "xfine",
   451  }
   452  
   453  var nwchemMethods = map[string]string{
   454  	"b3lyp":   "b3lyp",
   455  	"b3-lyp":  "b3lyp",
   456  	"pbe0":    "pbe0",
   457  	"mpw1b95": "mpw1b95",
   458  	"revpbe":  "revpbe cpbe96",
   459  	"TPSS":    "xtpss03 ctpss03",
   460  	"tpss":    "xtpss03 ctpss03",
   461  	"TPSSh":   "xctpssh",
   462  	"tpssh":   "xctpssh",
   463  	"bp86":    "becke88 perdew86",
   464  	"b-p":     "becke88 perdew86",
   465  	"blyp":    "becke88 lyp",
   466  }
   467  
   468  //OptimizedGeometry returns the latest geometry from an NWChem optimization. Returns the
   469  //geometry or error. Returns the geometry AND error if the geometry read
   470  //is not the product of a correctly ended NWChem calculation. In this case
   471  //the error is "probable problem in calculation".
   472  func (O *NWChemHandle) OptimizedGeometry(atoms chem.Atomer) (*v3.Matrix, error) {
   473  	var err2 error
   474  	lastnumber := 0
   475  	lastname := ""
   476  	if !O.nwchemNormalTermination() {
   477  		err2 = Error{ErrProbableProblem, NWChem, O.inputname, "", []string{"OptimizedGeometry"}, false}
   478  	}
   479  	dir, err := os.Open(O.wrkdir)
   480  	if err != nil {
   481  		return nil, Error{ErrNoGeometry, NWChem, O.inputname, err.Error(), []string{"os.Open", "OptimizedGeometry"}, true}
   482  	}
   483  	files, err := dir.Readdirnames(-1)
   484  	if err != nil {
   485  		return nil, Error{ErrNoGeometry, NWChem, O.inputname, err.Error(), []string{"os.Readdirnames", "OptimizedGeometry"}, true}
   486  	}
   487  	//This is a crappy sort/filter, but really, it will never be the bottleneck.
   488  	//We go over the dir content and look for xyz files with the name of the input and without
   489  	// the susbstring _prev. Among these, we choose the file with the largest number in the filename
   490  	//(which will be the latest geometry written) and return that geometry.
   491  	for _, v := range files {
   492  		//quite ugly, feel free to fix.
   493  		if !(strings.Contains(v, O.inputname) && strings.Contains(v, ".xyz") && !strings.Contains(v, "_prev")) {
   494  			continue
   495  		}
   496  		numbers := strings.Split(strings.Replace(v, ".xyz", "", 1), "-")
   497  		ndx, err := strconv.Atoi(numbers[len(numbers)-1])
   498  		if err != nil {
   499  			continue
   500  		}
   501  		if ndx >= lastnumber {
   502  			lastnumber = ndx
   503  			lastname = v
   504  		}
   505  	}
   506  	if lastname == "" {
   507  		return nil, Error{ErrNoGeometry, NWChem, O.inputname, "", []string{"OptimizedGeometry"}, true}
   508  	}
   509  	mol, err := chem.XYZFileRead(O.wrkdir + lastname)
   510  	if err != nil {
   511  		return nil, errDecorate(err, "qm.OptimizedGeometry "+" "+NWChem+" "+O.inputname+" "+ErrNoGeometry)
   512  	}
   513  	return mol.Coords[0], err2
   514  }
   515  
   516  //Energy returns the energy of a previous NWChem calculation.
   517  //Returns error if problem, and also if the energy returned that is product of an
   518  //abnormally-terminated NWChem calculation. (in this case error is "Probable problem
   519  //in calculation")
   520  func (O *NWChemHandle) Energy() (float64, error) {
   521  	var err error
   522  	err = Error{ErrProbableProblem, NWChem, O.inputname, "", []string{"Energy"}, false}
   523  	f, err1 := os.Open(fmt.Sprintf("%s.out", O.wrkdir+O.inputname))
   524  	if err1 != nil {
   525  		return 0, Error{ErrNoEnergy, NWChem, O.inputname, err1.Error(), []string{"os.Open", "Energy"}, true}
   526  	}
   527  	defer f.Close()
   528  	f.Seek(-1, 2) //We start at the end of the file
   529  	energy := 0.0
   530  	var found bool
   531  	for i := 0; ; i++ {
   532  		line, err1 := getTailLine(f)
   533  		if err1 != nil {
   534  			return 0.0, errDecorate(err1, "qm.Energy "+NWChem)
   535  		}
   536  		if strings.Contains(line, "CITATION") {
   537  			err = nil
   538  		}
   539  		if strings.Contains(line, "Total DFT energy") {
   540  			splitted := strings.Fields(line)
   541  			energy, err1 = strconv.ParseFloat(splitted[len(splitted)-1], 64)
   542  			if err1 != nil {
   543  				return 0.0, Error{ErrNoEnergy, NWChem, O.inputname, err1.Error(), []string{"strconv.ParseFloat", "Energy"}, true}
   544  
   545  			}
   546  			found = true
   547  			break
   548  		}
   549  	}
   550  	if !found {
   551  		return 0.0, Error{ErrNoEnergy, NWChem, O.inputname, "", []string{"Energy"}, true}
   552  
   553  	}
   554  	return energy * chem.H2Kcal, err
   555  }
   556  
   557  func (O *NWChemHandle) move2lines(fin *bufio.Reader) error {
   558  	_, err := fin.ReadString('\n')
   559  	if err != nil {
   560  		return Error{ErrNoEnergy, NWChem, O.inputname, err.Error(), []string{"os.Open", "Charges"}, true}
   561  	}
   562  	_, err = fin.ReadString('\n')
   563  	if err != nil {
   564  		return Error{ErrNoEnergy, NWChem, O.inputname, err.Error(), []string{"os.Open", "Charges"}, true}
   565  	}
   566  	return nil
   567  }
   568  
   569  //Charges returns the RESP charges from a previous NWChem calculation.
   570  func (O *NWChemHandle) Charges() ([]float64, error) {
   571  	f, err1 := os.Open(fmt.Sprintf("%s.out", O.wrkdir+O.inputname))
   572  	if err1 != nil {
   573  		return nil, Error{ErrNoCharges, NWChem, O.inputname, err1.Error(), []string{"os.Open", "Charges"}, true}
   574  	}
   575  	charges := make([]float64, 0, 30)
   576  	var reading bool = false
   577  	defer f.Close()
   578  	fin := bufio.NewReader(f)
   579  	for {
   580  
   581  		line, err := fin.ReadString('\n')
   582  		if err != nil {
   583  			if strings.Contains(err.Error(), "EOF") { //This allows that an XYZ ends without a newline
   584  				break
   585  			} else {
   586  				return nil, Error{ErrNoCharges, NWChem, O.inputname, err.Error(), []string{"os.Open", "Charges"}, true}
   587  			}
   588  		}
   589  		if strings.Contains(line, "ESP         RESP        RESP2") {
   590  			reading = true
   591  			err := O.move2lines(fin)
   592  			if err != nil {
   593  				return nil, err
   594  			}
   595  			continue
   596  		}
   597  		if !reading {
   598  			continue
   599  		}
   600  		if reading && strings.Contains(line, "------------------------------------") {
   601  			break
   602  		}
   603  		fields := strings.Fields(line)
   604  		if len(fields) < 7 {
   605  			return nil, Error{ErrNoCharges, NWChem, O.inputname, "", []string{"os.Open", "Charges"}, true}
   606  		}
   607  		charge, err := strconv.ParseFloat(fields[len(fields)-2], 64)
   608  		if err != nil {
   609  			return nil, Error{ErrNoCharges, NWChem, O.inputname, err.Error(), []string{"os.Open", "Charges"}, true}
   610  		}
   611  		charges = append(charges, charge)
   612  
   613  	}
   614  
   615  	return charges, nil
   616  }
   617  
   618  //This checks that an NWChem calculation has terminated normally
   619  //I know this duplicates code, I wrote this one first and then the other one.
   620  func (O *NWChemHandle) nwchemNormalTermination() bool {
   621  	ret := false
   622  	f, err1 := os.Open(fmt.Sprintf("%s.out", O.wrkdir+O.inputname))
   623  	if err1 != nil {
   624  		return false
   625  	}
   626  	defer f.Close()
   627  	f.Seek(-1, 2) //We start at the end of the file
   628  	for i := 0; ; i++ {
   629  		line, err1 := getTailLine(f)
   630  		if err1 != nil {
   631  			return false
   632  		}
   633  		if strings.Contains(line, "CITATION") {
   634  			ret = true
   635  			break
   636  		}
   637  	}
   638  	return ret
   639  }