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

Input Writer #188

Merged
merged 26 commits into from
Mar 12, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3c30091
Add Gaussian input writer
wilhadams Aug 19, 2020
75e0b53
Add write_input to api.py module
FarnazH Aug 19, 2020
41aa3eb
Test gaussian input writer using default template
FarnazH Aug 20, 2020
0ea6726
Test gaussian input writer using user template
FarnazH Aug 20, 2020
9e5b2e6
Add __all__ to inputs/gaussian.py module
FarnazH Aug 20, 2020
17d5c88
Fix pylint no-else-return and wrong-import-order
FarnazH Aug 20, 2020
2709a3e
Fix docstring & update default values
FarnazH Aug 20, 2020
7cef0c9
Add write_input function for orca
lmacaya Aug 22, 2020
c4d733e
Add tests for orca write_input
lmacaya Aug 22, 2020
27a2a61
Add template for orca write_input testing
lmacaya Aug 22, 2020
eb988c3
Add __init__.py for relative import
lmacaya Aug 22, 2020
2a9961a
Add default docstring to gaussian write_input for future decorator
lmacaya Aug 22, 2020
e4eb9b2
Use attr.asdict in Gaussian input writer
FarnazH Aug 24, 2020
713e410
Add expected test inputs to test/data/input_*.txt
FarnazH Aug 24, 2020
8319cf0
Remove empty lines & unsed import in test_inputs
FarnazH Aug 24, 2020
a87c0f2
Add recurse=False in orca module & remove test fix
FarnazH Aug 24, 2020
23442ef
Remove attrname argument of _select_input_module
FarnazH Aug 24, 2020
328f38b
Fix pylint wrong-import-order
FarnazH Aug 24, 2020
0891a63
Add header to __init__
lmacaya Sep 2, 2020
6027980
Add write_input decorator and minor fixes
lmacaya Sep 4, 2020
22233a4
Update docstrings in api.py
lmacaya Sep 4, 2020
6a97b0d
Add populate_fields func to common
lmacaya Sep 4, 2020
28c167c
Reformat gaussian write_input
lmacaya Sep 4, 2020
0f628f8
Reformat orca write_input
lmacaya Sep 4, 2020
2d32f4e
Merge branch 'master' into write_input
tovrstra Mar 12, 2021
5c68134
Fix minor issues and include input writer in docs
tovrstra Mar 12, 2021
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
64 changes: 63 additions & 1 deletion iodata/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from .utils import LineIterator


__all__ = ['load_one', 'load_many', 'dump_one', 'dump_many']
__all__ = ['load_one', 'load_many', 'dump_one', 'dump_many', 'write_input']


def _find_format_modules():
Expand Down Expand Up @@ -78,6 +78,42 @@ def _select_format_module(filename: str, attrname: str, fmt: str = None) -> Modu
attrname, filename))


def _find_input_modules():
"""Return all input modules found with importlib."""
result = {}
for module_info in iter_modules(import_module('iodata.inputs').__path__):
if not module_info.ispkg:
format_module = import_module('iodata.inputs.' + module_info.name)
result[module_info.name] = format_module
return result


INPUT_MODULES = _find_input_modules()


def _select_input_module(attrname: str, fmt: str) -> ModuleType:
"""Find an input module with the requested attribute name.

Parameters
----------
attrname
tovrstra marked this conversation as resolved.
Show resolved Hide resolved
The required attribute of the input module.
fmt
The name of the input module to use.

Returns
-------
format_module
The module implementing the required input format.

"""
if fmt in INPUT_MODULES:
if not hasattr(INPUT_MODULES[fmt], attrname):
raise ValueError(f'{fmt} input module does not have {attrname}!')
return INPUT_MODULES[fmt]
raise ValueError(f"Could not find input format {fmt}!")


def load_one(filename: str, fmt: str = None, **kwargs) -> IOData:
"""Load data from a file.

Expand Down Expand Up @@ -188,3 +224,29 @@ def dump_many(iodatas: Iterator[IOData], filename: str, fmt: str = None, **kwarg
format_module = _select_format_module(filename, 'dump_many', fmt)
with open(filename, 'w') as f:
format_module.dump_many(f, iodatas, **kwargs)


def write_input(iodata: IOData, filename: str, fmt: str, template: str = None, **kwargs):
"""Write input file using an instance of IOData for the specified software format.

Parameters
----------
iodatas
FarnazH marked this conversation as resolved.
Show resolved Hide resolved
An IOData instance containing the information needed to write input.
filename : str
The input file name.
fmt : str
The name of the software for which input file is generated.
template : str, optional
The template input file.
**kwargs
Keyword arguments are passed on to the input-specific write_input function.

"""
input_module = _select_input_module('write_input', fmt)
# load template as a string
if template is not None:
with open(template, 'r') as t:
template = t.read()
with open(filename, 'w') as f:
input_module.write_input(f, iodata, template=template, **kwargs)
75 changes: 75 additions & 0 deletions iodata/inputs/gaussian.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# IODATA is an input and output module for quantum chemistry.
# Copyright (C) 2011-2019 The IODATA Development Team
#
# This file is part of IODATA.
#
# IODATA is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# IODATA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>
# --
"""Gaussian Input Module."""


# from attr import asdict
tovrstra marked this conversation as resolved.
Show resolved Hide resolved
from typing import TextIO
from string import Template

from ..iodata import IOData
from ..periodic import num2sym
from ..utils import angstrom


__all__ = []


default_template = """\
#n ${lot}/${obasis_name} ${run_type}

${title}

${charge} ${spinmult}
${geometry}

"""


def write_input(f: TextIO, data: IOData, template: str = None):

# load IOData dict using attr.asdict because the IOData class uses __slots__
# fields = asdict(data)
tovrstra marked this conversation as resolved.
Show resolved Hide resolved
fields = {"atnums": data.atnums,
"atcoords": data.atcoords / angstrom,
"title": data.title if not None else 'Input Generated by IOData',
"run_type": data.run_type if data.run_type is not None else 'energy',
# convert spin polarization to multiplicity
"spinmult": int(data.spinpol) + 1,
FarnazH marked this conversation as resolved.
Show resolved Hide resolved
"charge": int(data.charge),
FarnazH marked this conversation as resolved.
Show resolved Hide resolved
"lot": data.lot if data.lot is not None else 'hf',
"obasis_name": data.obasis_name if data.obasis_name is not None else 'sto-3g',
}

# convert run type to Gaussian keywords
run_types = {"energy": "sp", "freq": "freq", "opt": "opt"}
Copy link
Contributor

Choose a reason for hiding this comment

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

Based in #202, we can update this dict to all the run_types supported in IOData.

fields["run_type"] = run_types[fields["run_type"].lower()]

# generate geometry (in angstrom)
geometry = []
for num, coord in zip(fields["atnums"], fields["atcoords"]):
geometry.append(f"{num2sym[num]:3} {coord[0]:10.6f} {coord[1]:10.6f} {coord[2]:10.6f}")
fields["geometry"] = "\n".join(geometry)

# get template
if template is None:
template = default_template

# populate files & write input
print(Template(template).substitute(fields), file=f)
19 changes: 19 additions & 0 deletions iodata/test/data/template_gaussian.com
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
%chk=gaussian.chk
%mem=3500MB
%nprocs=4
#p ${lot}/${obasis_name} opt scf(tight,xqc,fermi) integral(grid=ultrafine) nosymmetry

${title} ${lot}/${obasis_name} opt-force

0 1
${geometry}

--Link1--
%chk=gaussian.chk
%mem=3500MB
%nprocs=4
#p ${lot}/${obasis_name} force guess=read geom=allcheck integral(grid=ultrafine) output=wfn

gaussian.wfn


111 changes: 111 additions & 0 deletions iodata/test/test_inputs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# IODATA is an input and output module for quantum chemistry.
# Copyright (C) 2011-2019 The IODATA Development Team
#
# This file is part of IODATA.
#
# IODATA is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# IODATA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>
# --
"""Test iodata.inputs module."""


import os

from typing import List

import numpy as np

from ..iodata import IOData
from ..utils import angstrom
from ..api import load_one, write_input

try:
from importlib_resources import path
except ImportError:
from importlib.resources import path


def check_load_input_and_compare(fname: str, expected_lines: List[str]):
"""Load saved input file and compare to expected input lines.

Parameters
----------
fname : str
Input file name to load.
expected_lines : sequence of str
Expected lines in the input file.

"""
with open(fname, 'r') as ifn:
content = "".join(ifn.readlines())
expected = "\n".join(expected_lines)
assert content == expected


def test_input_gaussian_from_xyz(tmpdir):
# load geometry from xyz file & add level of theory & basis set
with path('iodata.test.data', 'water_number.xyz') as fn:
mol = load_one(fn)
mol.nelec = 10
mol.spinpol = 0
mol.lot = 'ub3lyp'
mol.obasis_name = '6-31g*'
# write input in a temporary folder using an input template
fname = os.path.join(tmpdir, 'input_from_xyz.com')
with path('iodata.test.data', 'template_gaussian.com') as tname:
write_input(mol, fname, fmt='gaussian', template=tname)
# load input & compare
lines = ["%chk=gaussian.chk", "%mem=3500MB", "%nprocs=4",
"#p ub3lyp/6-31g* opt scf(tight,xqc,fermi) integral(grid=ultrafine) nosymmetry",
"", "Water ub3lyp/6-31g* opt-force", "", "0 1", ""
"H 0.783837 -0.492236 -0.000000",
"O -0.000000 0.062020 -0.000000",
"H -0.783837 -0.492236 -0.000000",
"", "--Link1--", "%chk=gaussian.chk", "%mem=3500MB", "%nprocs=4",
"#p ub3lyp/6-31g* force guess=read geom=allcheck integral(grid=ultrafine) output=wfn",
"", "gaussian.wfn", "", "", "", ""]
tovrstra marked this conversation as resolved.
Show resolved Hide resolved
check_load_input_and_compare(fname, lines)


def test_input_gaussian_from_iodata(tmpdir):

FarnazH marked this conversation as resolved.
Show resolved Hide resolved
# make an instance of IOData for HCl anion
data = {"atcoords": np.array([[0.0, 0.0, 0.0], [angstrom, 0.0, 0.0]]),
"atnums": np.array([1, 17]), "nelec": 19, "run_type": 'opt',
"title": " hydrogen chloride anion", "spinpol": 1}
mol = IOData(**data)
# write input in a temporary file
fname = os.path.join(tmpdir, 'input_from_iodata.com')
write_input(mol, fname, fmt='gaussian')
# load input & compare
expected_lines = ["#n hf/sto-3g opt", "", " hydrogen chloride anion", "", "-1 2",
"H 0.000000 0.000000 0.000000",
"Cl 1.000000 0.000000 0.000000",
"", "", ""]
check_load_input_and_compare(fname, expected_lines)


def test_input_gaussian_from_fchk(tmpdir):
# load fchk
with path('iodata.test.data', 'water_hfs_321g.fchk') as fn:
mol = load_one(fn)
# write input in a temporary file
fname = os.path.join(tmpdir, 'input_from_fchk.in')
write_input(mol, fname, fmt='gaussian')
# compare saved input to expected
expected_lines = ["#n rhfs/3-21g sp", "", "water", "", "0 1",
"H 0.000000 0.783837 -0.443405",
"O 0.000000 0.000000 0.110851",
"H -0.000000 -0.783837 -0.443405",
"", "", ""]
check_load_input_and_compare(fname, expected_lines)