github.com/rmera/gochem@v0.7.1/qm/orca.go (about) 1 /* 2 * qm.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 */ 23 24 package qm 25 26 import ( 27 "fmt" 28 "log" 29 "os" 30 "os/exec" 31 "runtime" 32 "strconv" 33 "strings" 34 35 chem "github.com/rmera/gochem" 36 v3 "github.com/rmera/gochem/v3" 37 ) 38 39 //OrcaHandle represents an Orca calculation. 40 //Note that the default methods and basis vary with each program, and even 41 //for a given program they are NOT considered part of the API, so they can always change. 42 type OrcaHandle struct { 43 defmethod string 44 defbasis string 45 defauxbasis string 46 previousMO string 47 bsse string 48 command string 49 inputname string 50 wrkdir string 51 nCPU int 52 orca3 bool 53 } 54 55 //NewOrcaHandle initializes and returns a new OrcaHandle. 56 func NewOrcaHandle() *OrcaHandle { 57 run := new(OrcaHandle) 58 run.SetDefaults() 59 return run 60 } 61 62 //OrcaHandle methods 63 64 //SetnCPU sets the number of CPU to be used. 65 func (O *OrcaHandle) SetnCPU(cpu int) { 66 O.nCPU = cpu 67 } 68 69 //SetName sets the name of the job, which will reflect in the 70 //name fo the input and output files. 71 func (O *OrcaHandle) SetName(name string) { 72 O.inputname = name 73 } 74 75 //SetCommand sets the name and path of the Orca excecutable 76 func (O *OrcaHandle) SetCommand(name string) { 77 O.command = name 78 } 79 80 //SetMOName sets the name of the file containing molecular 81 //orbitales (in the corresponding Orca format) to be 82 //used as initial guess. 83 func (O *OrcaHandle) SetMOName(name string) { 84 O.previousMO = name 85 } 86 87 //SetWorkDir sets the name of the working directory for the calculation 88 func (O *OrcaHandle) SetWorkDir(d string) { 89 O.wrkdir = d 90 } 91 92 //SetOrca3 sets the use of Orca3 to true or false. The default state 93 //is false, meaning that Orca4 is used. 94 func (O *OrcaHandle) SetOrca3(b bool) { 95 O.orca3 = b 96 } 97 98 //SetDefaults Sets defaults for ORCA calculation. The default is 99 //currently a single-point at 100 //revPBE/def2-SVP with RI, and all the available CPU with a max of 101 //8. The ORCA command is set to $ORCA_PATH/orca, at least in 102 //unix. 103 //The default is _not_ part of the API, it can change as new methods appear. 104 func (O *OrcaHandle) SetDefaults() { 105 O.defmethod = "BLYP" 106 O.defbasis = "def2-SVP" 107 O.defauxbasis = "def2/J" 108 if O.orca3 { 109 O.defauxbasis = "def2-SVP/J" 110 } 111 O.command = os.ExpandEnv("${ORCA_PATH}/orca") 112 if O.command == "/orca" { //if ORCA_PATH was not defined 113 O.command = "./orca" 114 } 115 cpu := runtime.NumCPU() / 2 116 O.nCPU = cpu 117 118 } 119 120 //BuildInput builds an input for ORCA based int the data in atoms, coords and C. 121 //returns only error. 122 func (O *OrcaHandle) BuildInput(coords *v3.Matrix, atoms chem.AtomMultiCharger, Q *Calc) error { 123 if O.wrkdir != "" { 124 O.wrkdir = O.wrkdir + "/" 125 } 126 //Only error so far 127 if atoms == nil || coords == nil { 128 return Error{ErrMissingCharges, Orca, O.inputname, "", []string{"BuildInput"}, true} 129 } 130 if Q.Basis == "" && !strings.Contains(Q.Method, "3c") { 131 log.Printf("no basis set assigned for ORCA calculation, will used the default %s, \n", O.defbasis) //NOTE: This could be changed for a non-critical error 132 Q.Basis = O.defbasis 133 } 134 if Q.Method == "" { 135 log.Printf("no method assigned for ORCA calculation, will used the default %s, \n", O.defmethod) 136 Q.Method = O.defmethod 137 Q.auxColBasis = "" //makes no sense for pure functional 138 Q.auxBasis = fmt.Sprintf("%s/J", Q.Basis) 139 } 140 141 //Set RI or RIJCOSX if needed 142 ri := "" 143 if Q.RI && Q.RIJ { 144 return Error{"goChem/QM: RI and RIJ cannot be activated at the same time", Orca, O.inputname, "", []string{"BuildInput"}, true} 145 } 146 if Q.RI || Q.RIJ { 147 Q.auxBasis = "def2/J" //Of course, this will only work with Karlsruhe basis. 148 if O.orca3 { 149 Q.auxBasis = Q.Basis + "/J" 150 } 151 // if !strings.Contains(Q.Others," RI "){ 152 ri = "RI" 153 } 154 if Q.RIJ { 155 ri = "RIJCOSX" 156 } 157 158 disp := "D3" 159 if Q.Dispersion != "" { 160 disp = orcaDisp[Q.Dispersion] 161 } 162 opt := "" 163 trustradius := "" 164 jc := jobChoose{} 165 jc.opti = func() { 166 opt = "Opt" 167 trustradius = "%geom trust 0.3\nend\n\n" //Orca uses a fixed trust radius by default. This goChem makes an input that activates variable trust radius. 168 } 169 Q.Job.Do(jc) 170 //If this flag is set we'll look for a suitable MO file. 171 //If not found, we'll just use the default ORCA guess 172 hfuhf := "RHF" 173 if atoms.Multi() != 1 { 174 hfuhf = "UHF" 175 } 176 moinp := "" 177 if Q.OldMO == true { 178 dir, _ := os.Open("./") //This should always work, hence ignoring the error 179 files, _ := dir.Readdir(-1) //Get all the files. 180 for _, val := range files { 181 if O.previousMO != "" { 182 break 183 } 184 if val.IsDir() == true { 185 continue 186 } 187 name := val.Name() 188 if strings.Contains(name, ".gbw") { 189 O.previousMO = name 190 break 191 } 192 } 193 if O.previousMO != "" { 194 // Q.Guess = "MORead" 195 moinp = fmt.Sprintf("%%scf\n Guess MORead\n MOInp \"%s\"\nend\n\n", O.previousMO) 196 } else { 197 moinp = "" 198 // Q.Guess = "" //The default guess 199 } 200 } 201 tight := "TightSCF" 202 if Q.SCFTightness != 0 { 203 tight = orcaSCFTight[Q.SCFTightness] 204 } 205 conv := "" 206 if Q.SCFConvHelp == 0 { 207 //The default for this is nothing for RHF and SlowConv for UHF 208 if atoms.Multi() > 1 { 209 conv = "SlowConv" 210 } 211 } else { 212 conv = orcaSCFConv[Q.SCFConvHelp] 213 } 214 pal := "" 215 if O.nCPU > 1 { 216 pal = fmt.Sprintf("%%pal nprocs %d\n end\n\n", O.nCPU) //fmt.Fprintf(os.Stderr, "CPU number of %d for ORCA calculations currently not supported, maximun 8", O.nCPU) 217 } 218 grid := "" 219 if Q.Grid > 0 && Q.Grid <= 9 { 220 final := "" 221 if Q.Grid > 3 { 222 final = "NoFinalGrid" 223 } 224 grid = fmt.Sprintf("Grid%d %s", Q.Grid, final) 225 } 226 var err error 227 var bsse string 228 if bsse, err = O.buildgCP(Q); err != nil { 229 fmt.Fprintln(os.Stderr, err.Error()) 230 } 231 HF3cAdditional := "" // additional settings for HF-3c. 232 if Q.Method == "HF-3c" { //This method includes its own basis sets and corrections, so previous choices are overwritten. NOTE: there are some defaults that should be changed to get HF-3c to work better. 233 Q.Basis = "" 234 Q.auxBasis = "" 235 Q.auxColBasis = "" 236 Q.Guess = "" 237 bsse = "" 238 disp = "" 239 HF3cAdditional = "%scf\n MaxIter 200\n MaxIntMem 2000\nend\n\n" 240 241 } 242 MainOptions := []string{"!", hfuhf, Q.Method, Q.Basis, Q.auxBasis, Q.auxColBasis, tight, disp, conv, Q.Guess, opt, Q.Others, grid, ri, bsse, "\n\n"} 243 mainline := strings.Join(MainOptions, " ") 244 constraints := O.buildCConstraints(Q.CConstraints) 245 iconstraints, err := O.buildIConstraints(Q.IConstraints) 246 if err != nil { 247 return errDecorate(err, "BuildInput") 248 } 249 cosmo := "" 250 if Q.Dielectric > 0 { 251 method := "cpcm" 252 if O.orca3 { 253 method = "cosmo" 254 } 255 cosmo = fmt.Sprintf("%%%s epsilon %1.0f\n refrac 1.30\n end\n\n", method, Q.Dielectric) 256 } 257 mem := "" 258 if Q.Memory != 0 { 259 mem = fmt.Sprintf("%%MaxCore %d\n\n", Q.Memory) 260 } 261 ElementBasis := "" 262 if Q.HBElements != nil || Q.LBElements != nil { 263 elementbasis := make([]string, 0, len(Q.HBElements)+len(Q.LBElements)+2) 264 elementbasis = append(elementbasis, "%basis \n") 265 for _, val := range Q.HBElements { 266 elementbasis = append(elementbasis, fmt.Sprintf(" newgto %s \"%s\" end\n", val, Q.HighBasis)) 267 } 268 for _, val := range Q.LBElements { 269 elementbasis = append(elementbasis, fmt.Sprintf(" newgto %s \"%s\" end\n", val, Q.LowBasis)) 270 } 271 elementbasis = append(elementbasis, " end\n\n") 272 ElementBasis = strings.Join(elementbasis, "") 273 } 274 //Now lets write the thing 275 if O.inputname == "" { 276 O.inputname = "gochem" 277 } 278 file, err := os.Create(fmt.Sprintf("%s.inp", O.wrkdir+O.inputname)) 279 if err != nil { 280 return Error{ErrCantInput, Orca, O.inputname, err.Error(), []string{"os,Open", "BuildInput"}, true} 281 282 } 283 defer file.Close() 284 _, err = fmt.Fprint(file, mainline) 285 //With this check its assumed that the file is ok.https://www.reddit.com/ 286 if err != nil { 287 return Error{ErrCantInput, Orca, O.inputname, err.Error(), []string{"fmt.Printf", "BuildInput"}, true} 288 289 } 290 // fmt.Println("Ta wena la wea... chupa la callampa, uh uh uuuh", moinp) /////////////// 291 fmt.Fprint(file, HF3cAdditional) 292 fmt.Fprint(file, pal) 293 fmt.Fprint(file, moinp) 294 fmt.Fprint(file, mem) 295 fmt.Fprint(file, constraints) 296 fmt.Fprint(file, iconstraints) 297 fmt.Fprint(file, trustradius) 298 fmt.Fprint(file, ElementBasis) 299 fmt.Fprint(file, cosmo) 300 fmt.Fprint(file, "\n") 301 //Now the type of coords, charge and multiplicity 302 fmt.Fprintf(file, "* xyz %d %d\n", atoms.Charge(), atoms.Multi()) 303 //now the coordinates 304 // fmt.Println(atoms.Len(), coords.Rows()) /////////////// 305 for i := 0; i < atoms.Len(); i++ { 306 newbasis := "" 307 if isInInt(Q.HBAtoms, i) == true { 308 newbasis = fmt.Sprintf("newgto \"%s\" end", Q.HighBasis) 309 } else if isInInt(Q.LBAtoms, i) == true { 310 newbasis = fmt.Sprintf("newgto \"%s\" end", Q.LowBasis) 311 } 312 // fmt.Println(atoms.Atom(i).Symbol) 313 fmt.Fprintf(file, "%-2s %8.3f%8.3f%8.3f %s\n", atoms.Atom(i).Symbol, coords.At(i, 0), coords.At(i, 1), coords.At(i, 2), newbasis) 314 } 315 fmt.Fprintf(file, "*\n") 316 return nil 317 } 318 319 //Run runs the command given by the string O.command 320 //it waits or not for the result depending on wait. 321 //Not waiting for results works 322 //only for unix-compatible systems, as it uses bash and nohup. 323 func (O *OrcaHandle) Run(wait bool) (err error) { 324 if wait == true { 325 out, err := os.Create(fmt.Sprintf("%s.out", O.inputname)) 326 if err != nil { 327 return Error{ErrNotRunning, Orca, O.inputname, "", []string{"Run"}, true} 328 } 329 defer out.Close() 330 command := exec.Command(O.command, fmt.Sprintf("%s.inp", O.inputname)) 331 command.Stdout = out 332 command.Dir = O.wrkdir 333 err = command.Run() 334 335 } else { 336 command := exec.Command("sh", "-c", "nohup "+O.command+fmt.Sprintf(" %s.inp > %s.out &", O.inputname, O.inputname)) 337 command.Dir = O.wrkdir 338 err = command.Start() 339 } 340 if err != nil { 341 err = Error{ErrNotRunning, Orca, O.inputname, err.Error(), []string{"exec.Start", "Run"}, true} 342 } 343 return err 344 } 345 346 //buildIConstraints transforms the list of cartesian constrains in the QMCalc structre 347 //into a string with ORCA-formatted internal constraints. 348 func (O *OrcaHandle) buildIConstraints(C []*IConstraint) (string, error) { 349 if C == nil { 350 return "\n", nil //no constraints 351 } 352 constraints := make([]string, len(C)+3) 353 constraints[0] = "%geom Constraints\n" 354 for key, val := range C { 355 356 if iConstraintOrder[val.Class] != len(val.CAtoms) { 357 return "", Error{"Internal constraint ill-formated", Orca, O.inputname, "", []string{"buildConstraints"}, true} 358 } 359 360 var temp string 361 var value string 362 //if UseVal is false, we don't add any value to the contraint. Orca will constraint the coordinate to its value in the starting structure. 363 if val.UseVal { 364 value = fmt.Sprintf("%2.3f", val.Val) 365 } else { 366 value = "" 367 } 368 if val.Class == 'B' { 369 temp = fmt.Sprintf(" {B %d %d %s C}\n", val.CAtoms[0], val.CAtoms[1], value) 370 } else if val.Class == 'A' { 371 temp = fmt.Sprintf(" {A %d %d %d %s C}\n", val.CAtoms[0], val.CAtoms[1], val.CAtoms[2], value) 372 } else if val.Class == 'D' { 373 temp = fmt.Sprintf(" {D %d %d %d %d %s C}\n", val.CAtoms[0], val.CAtoms[1], val.CAtoms[2], val.CAtoms[3], value) 374 } 375 constraints[key+1] = temp 376 } 377 last := len(constraints) - 1 378 constraints[last-1] = " end\n" 379 constraints[last] = " end\n" 380 final := strings.Join(constraints, "") 381 return final, nil 382 } 383 384 var iConstraintOrder = map[byte]int{ 385 'B': 2, 386 'A': 3, 387 'D': 4, 388 } 389 390 //buildCConstraints transforms the list of cartesian constrains in the QMCalc structre 391 //into a string with ORCA-formatted cartesian constraints 392 func (O *OrcaHandle) buildCConstraints(C []int) string { 393 if C == nil { 394 return "\n" //no constraints 395 } 396 constraints := make([]string, len(C)+3) 397 constraints[0] = "%geom Constraints\n" 398 for key, val := range C { 399 constraints[key+1] = fmt.Sprintf(" {C %d C}\n", val) 400 } 401 last := len(constraints) - 1 402 constraints[last-1] = " end\n" 403 constraints[last] = " end\n" 404 final := strings.Join(constraints, "") 405 return final 406 } 407 408 //Only DFT is supported. Also, only Karlsruhe's basis sets. If you are using Pople's, 409 //let us know so we can send a mission to rescue you from the sixties :-) 410 func (O *OrcaHandle) buildgCP(Q *Calc) (string, error) { 411 ret := "" 412 var err error 413 if strings.ToLower(Q.BSSE) == "gcp" { 414 switch strings.ToLower(Q.Basis) { 415 case "def2-svp": 416 ret = "GCP(DFT/SVP)" 417 case "def2-tzvp": 418 ret = "GCP(DFT/TZ)" 419 case "def2-sv(p)": 420 ret = "GCP(DFT/SV(P))" 421 default: 422 err = Error{Orca, O.inputname, "Method/basis combination for gCP unavailable, will skip the correction", "", []string{"buildCP"}, false} 423 } 424 } 425 return ret, err 426 } 427 428 var orcaSCFTight = map[int]string{ 429 0: "", 430 1: "TightSCF", 431 2: "VeryTightSCF", 432 } 433 434 var orcaSCFConv = map[int]string{ 435 0: "", 436 1: "SlowConv", 437 2: "VerySlowConv", 438 } 439 440 var orcaDisp = map[string]string{ 441 "nodisp": "", 442 "D3OLD": "VDW10", //compatibility with ORCA 2.9 443 "D2": "D2", 444 "D3BJ": "D3BJ", 445 "D3bj": "D3BJ", 446 "D3": "D3ZERO", 447 "D3ZERO": "D3ZERO", 448 "D3Zero": "D3ZERO", 449 "D3zero": "D3ZERO", 450 "VV10": "NL", //for these methods only the default integration grid is supported. 451 "SCVV10": "SCNL", 452 "NL": "NL", 453 "SCNL": "SCNL", 454 } 455 456 //OptimizedGeometry reads the latest geometry from an ORCA optimization. Returns the 457 // geometry or error. Returns the geometry AND error if the geometry read 458 // is not the product of a correctly ended ORCA calculation. In this case 459 // the error is "probable problem in calculation" 460 func (O *OrcaHandle) OptimizedGeometry(atoms chem.Atomer) (*v3.Matrix, error) { 461 var err error 462 geofile := fmt.Sprintf("%s.xyz", O.wrkdir+O.inputname) 463 //Here any error of orcaNormal... or false means the same, so the error can be ignored. 464 if trust := O.orcaNormalTermination(); !trust { 465 err = Error{ErrProbableProblem, Orca, O.inputname, "", []string{"OptimizedGeometry"}, false} 466 } 467 //This might not be super efficient but oh well. 468 mol, err1 := chem.XYZFileRead(geofile) 469 if err1 != nil { 470 return nil, errDecorate(err1, "qm.OptimizedGeometry "+Orca+" "+O.inputname+" "+ErrNoGeometry) // Error{ErrNoEnergy, Orca, O.inputname, err1.Error(),[]string{"OptimizedGeometry"}, true} 471 } 472 return mol.Coords[0], err //returns the coords, the error indicates whether the structure is trusty (normal calculation) or not 473 } 474 475 //Energy returns the energy of a previous Orca calculations. 476 //Returns error if problem, and also if the energy returned that is product of an 477 //abnormally-terminated ORCA calculation. (in this case error is "Probable problem 478 //in calculation") 479 func (O *OrcaHandle) Energy() (float64, error) { 480 var err error 481 err = Error{ErrProbableProblem, Orca, O.inputname, "", []string{"Energy"}, false} 482 f, err1 := os.Open(fmt.Sprintf("%s.out", O.wrkdir+O.inputname)) 483 if err1 != nil { 484 return 0, Error{ErrNoEnergy, Orca, O.inputname, err.Error(), []string{"os.Open", "qm.Energy"}, true} 485 } 486 defer f.Close() 487 f.Seek(-1, 2) //We start at the end of the file 488 energy := 0.0 489 var found bool 490 for i := 0; ; i++ { 491 line, err1 := getTailLine(f) 492 if err1 != nil { 493 return 0.0, errDecorate(err, "Energy "+O.inputname) 494 } 495 if strings.Contains(line, "**ORCA TERMINATED NORMALLY**") { 496 err = nil 497 } 498 if strings.Contains(line, "FINAL SINGLE POINT ENERGY") { 499 splitted := strings.Fields(line) 500 energy, err1 = strconv.ParseFloat(splitted[4], 64) 501 if err1 != nil { 502 return 0.0, Error{ErrNoEnergy, Orca, O.inputname, err1.Error(), []string{"strconv.Parsefloat", "Energy"}, true} 503 504 } 505 found = true 506 break 507 } 508 } 509 if !found { 510 return 0.0, Error{ErrNoEnergy, Orca, O.inputname, "", []string{"Energy"}, false} 511 } 512 return energy * chem.H2Kcal, err 513 } 514 515 //Gets previous line of the file f 516 func getTailLine(f *os.File) (line string, err error) { 517 var i int64 = 1 518 buf := make([]byte, 1) 519 var ini int64 = -1 520 for ; ; i++ { 521 //move the pointer back one byte per cycle 522 if _, err := f.Seek(-2, 1); err != nil { 523 return "", Error{err.Error(), Orca, "Unknown", "", []string{"os.File.Seek", "getTailLine"}, true} 524 } 525 if _, err := f.Read(buf); err != nil { 526 return "", Error{err.Error(), Orca, "Unknown", "", []string{"os.File.Read", "getTailLine"}, true} 527 } 528 if buf[0] == byte('\n') && ini == -1 { 529 ini = i 530 break 531 } 532 } 533 bufF := make([]byte, ini) 534 f.Read(bufF) 535 536 if _, err := f.Seek(int64(-1*(len(bufF))), 1); err != nil { //making up for the read 537 return "", Error{err.Error(), Orca, "Unknown", "", []string{"os.File.Seek", "getTailLine"}, true} 538 539 } 540 return string(bufF), nil 541 } 542 543 //This checks that an ORCA calculation has terminated normally 544 //I know this duplicates code, I wrote this one first and then the other one. 545 func (O *OrcaHandle) orcaNormalTermination() bool { 546 var ini int64 = 0 547 var end int64 = 0 548 var first bool 549 buf := make([]byte, 1) 550 f, err := os.Open(fmt.Sprintf("%s.out", O.wrkdir+O.inputname)) 551 if err != nil { 552 return false 553 } 554 defer f.Close() 555 var i int64 = 1 556 for ; ; i++ { 557 if _, err := f.Seek(-1*i, 2); err != nil { 558 return false 559 } 560 if _, err := f.Read(buf); err != nil { 561 return false 562 } 563 if buf[0] == byte('\n') && first == false { 564 first = true 565 } else if buf[0] == byte('\n') && end == 0 { 566 end = i 567 } else if buf[0] == byte('\n') && ini == 0 { 568 ini = i 569 break 570 } 571 572 } 573 f.Seek(-1*ini, 2) 574 bufF := make([]byte, ini-end) 575 f.Read(bufF) 576 if strings.Contains(string(bufF), "**ORCA TERMINATED NORMALLY**") { 577 return true 578 } 579 return false 580 }