github.com/rmera/gochem@v0.7.1/qm/fermions.go (about) 1 /* 2 * fermions.go, part of gochem. 3 * 4 * 5 * Copyright 2014 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 //This functionality hasn't been tested for a while, so 24 //I can not ensure that it will work with current FermiONs++ 25 //versions. 26 27 package qm 28 29 import ( 30 "fmt" 31 "log" 32 "os" 33 "os/exec" 34 "strconv" 35 "strings" 36 37 chem "github.com/rmera/gochem" 38 v3 "github.com/rmera/gochem/v3" 39 ) 40 41 //A structure for handing FermiONs calculations 42 //Note that the default methods and basis vary with each program, and even 43 //for a given program they are NOT considered part of the API, so they can always change. 44 type FermionsHandle struct { 45 defmethod string 46 defbasis string 47 defauxbasis string 48 // previousMO string 49 // restart bool 50 // smartCosmo bool 51 command string 52 inputname string 53 nCPU int 54 gpu string 55 wrkdir string 56 } 57 58 //NewFermionsHandle initializes and returns a FermionsHandle 59 func NewFermionsHandle() *FermionsHandle { 60 run := new(FermionsHandle) 61 run.SetDefaults() 62 return run 63 } 64 65 //FermionsHandle methods 66 67 //SetGPU sets GPU usage. Alternatives are "cuda" or "opencl" (alias "ocl"). Anything else is ignored. GPU is off by default. 68 func (O *FermionsHandle) SetGPU(rawname string) { 69 name := strings.ToLower(rawname) 70 if name == "cuda" { 71 O.gpu = "USE_CUDA YES\nCUDA_LINALG_MIN 500" 72 } else if name == "opencl" || name == "ocl" { 73 O.gpu = "USE_OCL YES\nOCL_LINALG_MIN 500" //OpenCL is only for SCF energies!!!!! 74 } 75 } 76 77 //SetName sets the name of the job, that will translate into the input and output files. 78 func (O *FermionsHandle) SetName(name string) { 79 O.inputname = name 80 } 81 82 //SetCommand sets the name and/or path of the FermiONs++ excecutable 83 func (O *FermionsHandle) SetCommand(name string) { 84 O.command = name 85 } 86 87 //SetWorkDir sets the name of the working directory for the Fermions calculations 88 func (O *FermionsHandle) SetWorkDir(d string) { 89 O.wrkdir = d 90 } 91 92 //Sets defaults for Fermions++ calculation. Default is a single-point at 93 //revPBE/def2-SVP 94 func (O *FermionsHandle) SetDefaults() { 95 O.defmethod = "revpbe" 96 O.defbasis = "def2-svp" 97 O.command = "qccalc" 98 O.inputname = "gochem" 99 O.gpu = "" 100 101 } 102 103 //BuildInput builds an input for Fermions++ based int the data in atoms, coords and C. 104 //returns only error. 105 func (O *FermionsHandle) BuildInput(coords *v3.Matrix, atoms chem.AtomMultiCharger, Q *Calc) error { 106 //Only error so far 107 if O.wrkdir != "" { 108 O.wrkdir = O.wrkdir + "/" 109 } 110 if atoms == nil || coords == nil { 111 return Error{ErrMissingCharges, Fermions, O.inputname, "", []string{"BuildInput"}, true} 112 } 113 if Q.Basis == "" { 114 log.Printf("no basis set assigned for Fermions++ calculation, will used the default %s, \n", O.defbasis) 115 Q.Basis = O.defbasis 116 } 117 if Q.Method == "" { 118 log.Printf("no method assigned for Fermions++ calculation, will used the default %s, \n", O.defmethod) 119 Q.Method = O.defmethod 120 } 121 122 disp, ok := fermionsDisp[strings.ToLower(Q.Dispersion)] 123 if !ok { 124 disp = "disp_corr D3" 125 } 126 127 grid, ok := fermionsGrid[Q.Grid] 128 if !ok { 129 grid = "M3" 130 } 131 grid = fmt.Sprintf("GRID_RAD_TYPE %s", grid) 132 var err error 133 134 m := strings.ToLower(Q.Method) 135 method, ok := fermionsMethods[m] 136 if !ok { 137 method = "EXC XC_GGA_X_PBE_R\n ECORR XC_GGA_C_PBE" 138 } 139 task := "SinglePoint" 140 dloptions := "" 141 jc := jobChoose{} 142 jc.opti = func() { 143 task = "DLF_OPTIMIZE" 144 dloptions = fmt.Sprintf("*start::dlfind\n JOB std\n method l-bfgs\n trust_radius energy\n dcd %s.dcd\n maxcycle 300\n maxene 200\n coord_type cartesian\n*end\n", O.inputname) 145 //Only cartesian constraints supported by now. 146 if len(Q.CConstraints) > 0 { 147 dloptions = fmt.Sprintf("%s\n*start::dlf_constraints\n", dloptions) 148 for _, v := range Q.CConstraints { 149 dloptions = fmt.Sprintf("%s cart %d\n", dloptions, v+1) //fortran numbering, starts with 1. 150 } 151 dloptions = fmt.Sprintf("%s*end\n", dloptions) 152 } 153 } 154 Q.Job.Do(jc) 155 cosmo := "" 156 if Q.Dielectric > 0 { 157 cosmo = fmt.Sprintf("*start::solvate\n pcm_model cpcm\n epsilon %f\n cavity_model bondi\n*end\n", Q.Dielectric) 158 } 159 160 ////////////////////////////////////////////////////////////// 161 //Now lets write the thing. 162 ////////////////////////////////////////////////////////////// 163 file, err := os.Create(fmt.Sprintf("%s%s.in", O.wrkdir, O.inputname)) 164 if err != nil { 165 return Error{ErrCantInput, Fermions, O.inputname, err.Error(), []string{"os.Create", "BuildInput"}, true} 166 } 167 defer file.Close() 168 //Start with the geometry part (coords, charge and multiplicity) 169 fmt.Fprintf(file, "*start::geo\n") 170 fmt.Fprintf(file, "%d %d\n", atoms.Charge(), atoms.Multi()) 171 for i := 0; i < atoms.Len(); i++ { 172 fmt.Fprintf(file, "%-2s %8.3f%8.3f%8.3f\n", atoms.Atom(i).Symbol, coords.At(i, 0), coords.At(i, 1), coords.At(i, 2)) 173 } 174 fmt.Fprintf(file, "*end\n\n") 175 fmt.Fprintf(file, "*start::sys\n") 176 fmt.Fprintf(file, " TODO %s\n", task) 177 fmt.Fprintf(file, " BASIS %s\n PC pure\n", strings.ToLower(Q.Basis)) 178 fmt.Fprintf(file, " %s\n", method) 179 fmt.Fprintf(file, " %s\n", grid) 180 fmt.Fprintf(file, " %s\n", disp) 181 fmt.Fprintf(file, " %s\n", O.gpu) 182 if !Q.Job.Opti { 183 fmt.Fprintf(file, " INFO 2\n") 184 } 185 fmt.Fprintf(file, "*end\n\n") 186 fmt.Fprintf(file, "%s\n", cosmo) 187 fmt.Fprintf(file, "%s\n", dloptions) 188 return nil 189 } 190 191 //Run runs the command given by the string O.command 192 //it waits or not for the result depending on wait. 193 //Not waiting for results works 194 //only for unix-compatible systems, as it uses bash and nohup. 195 func (O *FermionsHandle) Run(wait bool) (err error) { 196 if wait == true { 197 command := exec.Command(O.command, fmt.Sprintf("%s.in", O.inputname), fmt.Sprintf("%s.out", O.inputname)) 198 command.Dir = O.wrkdir 199 err = command.Run() 200 201 } else { 202 //This will not work in windows. 203 command := exec.Command("sh", "-c", "nohup "+O.command+fmt.Sprintf(" %s.in > %s.out &", O.inputname, O.inputname)) 204 command.Dir = O.wrkdir 205 err = command.Start() 206 } 207 if err != nil { 208 err = Error{ErrNotRunning, Fermions, O.inputname, err.Error(), []string{"exec.Start/Run", "Run"}, true} 209 210 } 211 return err 212 } 213 214 var fermionsDisp = map[string]string{ 215 "": "", 216 "nodisp": "", 217 "d2": "disp_corr D2", 218 "d3bj": "disp_corr BJ", 219 "bj": "disp_corr BJ", 220 "d3": "disp_corr D3", 221 } 222 223 //M5 etc are not supported 224 var fermionsGrid = map[int]string{ 225 1: "M3", 226 2: "M3", 227 3: "M3", 228 4: "M4", 229 5: "M4", 230 } 231 232 //All meta-gga functionals here are buggy in the libxc library, and thus not reliable. I leave them hoping this will get fixed eventually. 233 var fermionsMethods = map[string]string{ 234 "b3lyp": "EXC XC_HYB_GGA_XC_B3LYP\n ECORR XC_HYB_GGA_XC_B3LYP", 235 "b3-lyp": "EXC XC_HYB_GGA_XC_B3LYP\n ECORR XC_HYB_GGA_XC_B3LYP", 236 "pbe": "EXC XC_GGA_X_PBE\n ECORR XC_GGA_C_PBE", 237 "revpbe": "EXC XC_GGA_X_PBE_R\n ECORR XC_GGA_C_PBE", 238 "pbe0": "EXC XC_HYB_GGA_XC_PBEH\n ECORR XC_HYB_GGA_XC_PBEH", 239 "mpw1b95": "EXC XC_HYB_MGGA_XC_MPW1B95\n ECORR XC_HYB_MGGA_XC_MPW1B95", //probably also buggy 240 "tpss": "EXC XC_MGGA_X_TPSS\n ECORR XC_MGGA_C_TPSS", //Buggy in current libxc, avoid. 241 "bp86": "EXC XC_GGA_X_B88\n ECORR XC_GGA_C_P86", 242 "b-p": "EXC XC_GGA_X_B88\n ECORR XC_GGA_C_P86", 243 "blyp": "EXC XC_GGA_X_B88\n ECORR XC_GGA_C_LYP", 244 "b-lyp": "EXC XC_GGA_X_B88\n ECORR XC_GGA_C_LYP", 245 } 246 247 /* 248 //Reads the latest geometry from an Fermions++ optimization. Returns the 249 //geometry or error. 250 func (O *FermionsHandle) OptimizedGeometry(atoms chem.Ref) (*v3.Matrix, error) { 251 var err2 error 252 if !O.fermionsNormalTermination() { 253 return nil, fmt.Errorf("Probable problem in calculation") 254 } 255 //The following is kinda clumsy and should be replaced for a better thing which looks for 256 //the convergency signal instead of the not-convergency one. Right now I dont know 257 //what is that signal for FermiONs++. 258 if searchFromEnd("NOT CONVERGED",fmt.Sprintf("%s.out",O.inputname)){ 259 err2=fmt.Errorf("Probable problem in calculation") 260 } 261 trj,err:=dcd.New(fmt.Sprintf("%s.dcd",O.inputname)) 262 if err!=nil{ 263 return nil, fmt.Errorf("Probable problem in calculation %", err) 264 } 265 ret := chem.v3.Zeros(trj.Len()) 266 for { 267 err := trj.Next(ret) 268 if err != nil && err.Error() != "No more frames" { 269 return nil, fmt.Errorf("Probable problem in calculation %", err) 270 } else { 271 break 272 } 273 274 } 275 return ret, err2 276 277 278 } 279 280 */ 281 282 //Energy the energy of a previously completed Fermions++ calculation. 283 //Returns error if problem, and also if the energy returned that is product of an 284 //abnormally-terminated Fermions++ calculation. (in this case error is "Probable problem 285 //in calculation") 286 func (O *FermionsHandle) Energy() (float64, error) { 287 var err error 288 inp := O.wrkdir + O.inputname 289 err = Error{ErrProbableProblem, Fermions, inp, "", []string{"Energy"}, false} 290 f, err1 := os.Open(fmt.Sprintf("%s.out", inp)) 291 if err1 != nil { 292 return 0, Error{ErrNoEnergy, Fermions, inp, err1.Error(), []string{"os.Open", "Energy"}, true} 293 } 294 defer f.Close() 295 f.Seek(-1, 2) //We start at the end of the file 296 energy := 0.0 297 var found bool 298 for i := 0; ; i++ { 299 line, err1 := getTailLine(f) 300 if err1 != nil { 301 return 0.0, Error{ErrNoEnergy, Fermions, inp, err1.Error(), []string{"os.File.Seek", "Energy"}, true} 302 } 303 if strings.Contains(line, "Timing report") { 304 err = nil 305 } 306 if strings.Contains(line, " * Free energy: ") { 307 splitted := strings.Fields(line) 308 energy, err1 = strconv.ParseFloat(splitted[len(splitted)-3], 64) 309 if err1 != nil { 310 return 0.0, Error{ErrNoEnergy, Fermions, inp, err1.Error(), []string{"strconv.ParseFloat", "Energy"}, true} 311 } 312 found = true 313 break 314 } 315 } 316 if !found { 317 return 0.0, Error{ErrNoEnergy, Fermions, inp, "", []string{"Energy"}, true} 318 } 319 return energy * chem.H2Kcal, err 320 } 321 322 //This checks that an Fermions++ calculation has terminated normally 323 //Notice that this function will succeed whenever FermiONs++ exists 324 //correctly. In the case of a geometry optimization, this DOES NOT 325 //mean that the optimization converged (as opposed to, the NWChem 326 //interface, whose the equivalent function will return true ONLY 327 //if the optimization converged) 328 func (O *FermionsHandle) fermionsNormalTermination() bool { 329 return searchFromEnd("Timing report", fmt.Sprintf("%s.out", O.wrkdir+O.inputname)) 330 } 331 332 //This will return true if the templa string is present in the file filename 333 //which is seached from the end. 334 func searchFromEnd(templa, filename string) bool { 335 ret := false 336 f, err1 := os.Open(filename) 337 if err1 != nil { 338 return false 339 } 340 defer f.Close() 341 f.Seek(-1, 2) //We start at the end of the file 342 for i := 0; ; i++ { 343 line, err1 := getTailLine(f) 344 if err1 != nil { 345 return false 346 } 347 if strings.Contains(line, templa) { 348 ret = true 349 break 350 } 351 } 352 return ret 353 } 354 355 //OptimizedGeometry does nothing and returns nil for both values. 356 // It is there for compatibility but it represents unimplemented functionality. 357 func (O *FermionsHandle) OptimizedGeometry(atoms chem.Atomer) (*v3.Matrix, error) { 358 return nil, nil 359 }