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  }