Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Gaussian harness #442

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions qcengine/programs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from .torchani import TorchANIHarness
from .turbomole import TurbomoleHarness
from .xtb import XTBHarness
from .gaussian import GaussianHarness

__all__ = ["register_program", "get_program", "list_all_programs", "list_available_programs"]

Expand Down Expand Up @@ -118,6 +119,7 @@ def list_available_programs() -> Set[str]:
register_program(TurbomoleHarness())
register_program(TeraChemFrontEndHarness())
register_program(TeraChemPBSHarness())
register_program(GaussianHarness())

# Semi-empirical
register_program(MopacHarness())
Expand Down
245 changes: 245 additions & 0 deletions qcengine/programs/gaussian.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
"""
Calls the GAUSSIAN executable
"""

import os
import re

Check notice

Code scanning / CodeQL

Unused import Note

Import of 're' is not used.
import tempfile

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'tempfile' is not used.
import warnings

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'warnings' is not used.
from collections import defaultdict

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'defaultdict' is not used.
from typing import Any, Dict, List, Optional, Tuple

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'Tuple' is not used.
import cclib
from cclib.method import Nuclear

import numpy as np

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'np' is not used.
from qcelemental import constants
from qcelemental.models import AtomicInput, AtomicResult, Molecule, Provenance

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'Molecule' is not used.
from qcelemental.molparse import regex

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'regex' is not used.
from qcelemental.util import parse_version, safe_version, which

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'parse_version' is not used.
Import of 'safe_version' is not used.

from qcengine.config import TaskConfig, get_config

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'get_config' is not used.

from ..exceptions import InputError, UnknownError
from ..util import disk_files, execute, temporary_directory

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'disk_files' is not used.
Import of 'temporary_directory' is not used.
from .model import ProgramHarness

class GaussianHarness(ProgramHarness):

_defaults = {
"name": "gaussian",
"scratch": True,
"thread_safe": False,
"thread_parallel": True,
"node_parallel": False,
"managed_memory": True,
}
version_cache: Dict[str, str] = {}

class Config(ProgramHarness.Config):
pass

def found(self, raise_error: bool = False) -> bool:
return which(
"g09",
QuChem marked this conversation as resolved.
Show resolved Hide resolved
return_bool=True,
raise_error=raise_error,
raise_msg="Please install Gaussian. Check it's in your PATH with `which g09`."
)
QuChem marked this conversation as resolved.
Show resolved Hide resolved

def get_version(self) -> str:
self.found(raise_error=True)

which_prog = which("g09")

v_input = '''%mem=20MW
#P HF/sto-3g

#test HF/sto-3g for H atom

0 2
H

'''
if which_prog not in self.version_cache:
success, output = execute([which_prog, 'v.inp', 'v.log'],
{'v.inp': v_input},
['v.log']
)
if success:
outtext = output['outfiles']['v.log']
outtext = outtext.splitlines()
for line in outtext:
if 'Gaussian 09' in line:
#version_line = line.split('Gaussian 09:')[-1]
#version_line = version_line.split()[0]
#self.version_cache[which_prog] = safe_version(version_line)
self.version_cache[which_prog] = '2009'

return self.version_cache[which_prog]

def compute(self, input_model: "AtomicInput", config: TaskConfig) -> "AtomicResult":
"""
Run Gaussian
"""
# Check if Gaussian executable is found
self.found(raise_error=True)

# Setup the job
job_inputs = self.build_input(input_model, config)

# Run Gaussian
exe_success, proc = self.execute(job_inputs)

# Determine whether the calculation succeeded
if exe_success:
# If execution succeeded, collect results
result = self.parse_output(proc, input_model)

return result

else:
proc['outfiles']['stderr'] = proc['outfiles']['output.log']
outfile = proc['outfiles']['output.log']

if 'Error termination via ' in outfile:
raise InputError(proc['outfiles']['output.log'])

else:
# Return UnknownError for error propagation
raise UnknownError(proc['outfiles']['output.log'])

def build_input(
self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None
) -> Dict[str, Any]:

# Build keywords
keywords = {k.upper(): v for k, v in input_model.keywords.items()}

gaussian_kw = []

if input_model.driver == "energy":
gaussian_kw.append("sp")
elif input_model.driver == "gradient":
gaussian_kw.append("force")
elif input_model.driver == "hessian":
gaussian_kw.append("freq")
else:
raise InputError(f"Driver {input_model.driver} not implemented for Gaussian.")

#if input_model.molecule.fix_com or input_model.molecule.fix_orientation:
# keywords["SYM_IGNORE"] = "TRUE"
Comment on lines +166 to +167

Check notice

Code scanning / CodeQL

Commented-out code Note

This comment appears to contain commented-out code.
if 'SCF_CONVERGENCE' in keywords:
gaussian_kw.append('SCF=' + keywords["SCF_CONVERGENCE"])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the name "SCF_CONVERGENCE" coming from? QCNG tends to want the same keyword names in AtIn.keywords as the experience user would use in the program natively. Is Conver=N on https://gaussian.com/scf/ ("Options" tab) what you're targeting?

if 'POPULATION' in keywords:
gaussian_kw.append('Pop=' + keywords['POPULATION'])

keywords = {'scf_damp': 'true',

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable keywords is not used.
'scf_diis': 'false'}
# Begin input file
input_file = []
input_file.append('%mem={}MB'.format(int(config.memory * 1024)))
input_file.append("#P {}/{}".format(input_model.model.method, input_model.model.basis) + ' ' + ' '.join(gaussian_kw) + '\n')
input_file.append("write your comment here\n")

# Create a mol object
mol = input_model.molecule
input_file.append(f'{int(mol.molecular_charge)} {mol.molecular_multiplicity}')

# Write the geometry
for real, sym, geom in zip(mol.real, mol.symbols, mol.geometry):
if real is False:
raise InputError('Cannot handle ghost atoms yet.')
input_file.append(f'{sym} {geom[0]:14.8f} {geom[1]:14.8f} {geom[2]:14.8f}')
input_file.append("\n")

gaussian_ret = {
'infiles': {'input.inp': '\n'.join(input_file)},
'commands': [which("g09"), 'input.inp', 'output.log']
#'scratch_directory': config.scratch_directory
}

return gaussian_ret

def execute(self,
inputs,
*,
extra_outfiles: Optional[Dict[str, str]] = None,
extra_commands: Optional[List[str]] = None,
scratch_name = None,
timeout: Optional[int] = None
):

success, dexe = execute(
inputs['commands'],
inputs['infiles'],
outfiles = ['output.log'],
scratch_messy = True
)

if (dexe['outfiles']['output.log'] is None) or (
'Error termination via' in dexe['outfiles']['output.log']):
print ('THERE IS AN ERROR!')

success = False

return success, dexe

def parse_output(self, outfiles: Dict[str, str], input_model: AtomicInput) -> AtomicResult:

output_data = {}
properties = {}
cclib_vars = {}

tmp_output_path = outfiles['scratch_directory']
tmp_output_file = os.path.join(tmp_output_path, 'output.log')
data = cclib.io.ccread(tmp_output_file)
cclib_vars = data.getattributes(True)

last_occupied_energy = data.moenergies[0][data.homos[0]]

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable last_occupied_energy is not used.
#output_data['HOMO ENERGY'] = last_occupied_energy

scf_energy = data.scfenergies[0] / constants.conversion_factor("hartree", "eV") # Change from the eV unit to the Hartree unit
#output_data['SCF ENERGY'] = scf_energy

if input_model.driver == 'energy':
output_data['return_result'] = scf_energy
QuChem marked this conversation as resolved.
Show resolved Hide resolved
#print (os.system('ccget --list ' + tmp_output_file)) #data available in the output for parsing

#if input_model.driver == 'energy':
# print (cclib.__version__)
# print (output_data)
Comment on lines +261 to +263

Check notice

Code scanning / CodeQL

Commented-out code Note

This comment appears to contain commented-out code.
#print (input_model)

properties = {
'nuclear_repulsion_energy': Nuclear(data).repulsion_energy(),
'scf_total_energy': scf_energy,
'return_energy': scf_energy
}

output_data['properties'] = properties
output_data['stdout'] = outfiles['outfiles']['output.log']
output_data['success'] = True
#print ('output_data: ', output_data)

provenance = Provenance(creator="gaussian", version=self.get_version(), routine='g09').dict()

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable provenance is not used.

stdout = outfiles.pop('stdout')

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable stdout is not used.
stderr = outfiles.pop('stderr')

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable stderr is not used.
#print("\nPRINT STDOUT: \n", stdout)


method = input_model.model.method.lower()

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable method is not used.
#method = method[4:] if method.startswith("") else method

# filter unwanted data
to_remove = ['atomnos', 'atomcoords', 'natom']
output_data['extras'] = {'cclib': {k:v for k, v in cclib_vars.items() if k not in to_remove}}

# HACK - scf_values can have NaN
# remove for now
output_data['extras']['cclib'].pop('scfvalues')

merged_data = {**input_model.dict(), **output_data}

return AtomicResult(**merged_data)

Loading