#coding: utf-8
"""
This is a python module for calculation orca jobs conviently.
Import it and enjoy!
Lincense: Whatever, use it, copy it, or modify it as you like.
Author: T. H. Graphite
"""
import json
import os
import re
import subprocess
import sys
import cclib
class Calculation():
"""Calculate orca jobs.
Keyword Arguments:
name {str}-- the instance name, for creating new files and dirs.
work_dir {str} -- work directory, default: the path of module.
Once a instance is created, a new directory work_dir/name will be made.
Thus, all the calcultion will be done at this directory.
Methods:
load_settings(path)
set_input(keyword_excl, keyword_prct, ncharge, nmulti, coordinates)
set_input_from_xyz(keyword_excl, keyword_prct, ncharge, nmulti, path)
Attributes:
parse_json
parse_result
"""
def __init__(self, name, work_dir=sys.path[0]):
self.name = name
self.work_dir = work_dir
os.chdir(self.work_dir)
self.dir = os.path.join(self.work_dir, self.name)
self.load_settings()
if not os.path.exists(self.dir):
os.mkdir(self.dir)
self.input_path = os.path.join(self.dir, self.name + '.inp')
self.output_path = os.path.join(self.dir, self.name + '.out')
def load_settings(self, path=None):
"""Load settings from path.
Keyword Arguments:
path {str} -- the path of json settings file (default: work_dir/settings.json)
"""
if path is None:
path = os.path.join(self.work_dir, 'settings.json')
try:
settings_file = open(path, 'r')
settings_dict = json.load(settings_file)
self.orca_path = settings_dict['orca_path']
self.template_keyword_excls = settings_dict['template_keyword_excls']
self.template_keyword_prcts = settings_dict['template_keyword_prcts']
except Exception:
print('No settings file or invalid format. Exit.')
exit()
def set_input(self, keyword_excl, keyword_prct, ncharge, nmulti, coordinates):
"""Set input contents for calculation
Arguments:
keyword_excl {str} -- keywords start with a !
keyword_prct {str} -- keyword start with a %, a.k.a. the 'block' in orca manual.
ncharge {str or int} -- charge of molecule
nmulti {str or int} -- multiplicity of molecule
coordinates {str} -- coordinates in format like "C 1.000 1.000 1.000\nC 2.000 2.000 2.000..." .
"""
self.input_content = "{kw_excl}\n{kw_prct}\n*xyz {nchg} {nmtp}\n{coord}\n*".format(
kw_excl=keyword_excl,
kw_prct=keyword_prct,
nchg=str(ncharge),
nmtp=str(nmulti),
coord=coordinates)
def set_input_from_xyz(self, keyword_excl, keyword_prct, ncharge, nmulti, path):
"""Set input contents for calculation, coordinates from a xyz-like file
(.xyz, .gjf, or any file can be filter by the regex below)
Arguments:
keyword_excl {str} -- keywords start with a !
keyword_prct {str} -- keyword start with a %
ncharge {str or int} -- charge of molecule
nmulti {str or int} -- multiplicity of molecule
path {str} -- the path of the xyz-like file
"""
regex = r'\ {0,2}[A-Z][a-z]?(\ *-?[0-9]*\.[0-9]*){3,}'
pattern = re.compile(regex)
file_object = open(path, 'r')
coordinates = str()
for line in file_object:
if pattern.match(line):
coordinates += line
file_object.close()
self.set_input(keyword_excl, keyword_prct, ncharge, nmulti, coordinates)
def set_input_from_template(self, temp_excl, temp_prct, ncharge, nmulti, path):
"""Like set_input_from_xyz, but use template names (in the settings file, see: load_settings)
Arguments:
temp_excl {str} -- a key of template_keyword_excls
temp_prct {str} -- a key of template_keyword_prcts
ncharge {str or int} -- charge of molecule
nmulti {str or int} -- multiplicity of molecule
path {str} -- the path of the xyz-like file
"""
keyword_excl = self.template_keyword_excls[temp_excl]
keyword_prct = self.template_keyword_prcts[temp_prct]
self.set_input_from_xyz(keyword_excl, keyword_prct, ncharge, nmulti, path)
def run_orca(self):
"""Running orca
Returns:
Boolean -- whether the calculation is terminated normally or not.
"""
if not os.path.exists(self.orca_path):
raise(EnvironmentError)
input_file = open(self.input_path, 'w')
input_file.write(self.input_content)
input_file.close()
run_command = self.orca_path + ' ' + self.input_path + ' > ' + self.output_path
try:
subprocess.check_call(run_command, shell=True)
return True
except subprocess.CalledProcessError:
return False
@property
def parse_json(self):
"""Parse the output file using cclib.
Returns:
str -- a json string, including lots of interesting infos
"""
parse_target = cclib.ccopen(self.output_path)
if parse_target is not None:
try:
print(parse_target)
parse_result = parse_target.parse()
json = cclib.ccwrite(
parse_result, outputtype='json', returnstr=True)
return json
except Exception:
return None
@property
def parse_result(self):
"""The dictionary version of parse_json.
Returns:
dict -- a flattened dict
"""
def walk(dictionary):
for key, value in dictionary.items():
if isinstance(value, dict):
for tup in walk(value):
yield (key, ) + tup
else:
yield key, value
result = dict()
for tup in walk(json.loads(self.parse_json)):
result.setdefault('/'.join(tup[:-1]), tup[-1])
return result
if __name__ == '__main__':
print(__doc__)