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 }