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 }