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 17 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
62 changes: 61 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,40 @@ 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(fmt: str) -> ModuleType:
"""Find an input module with the requested attribute name.

Parameters
----------
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], 'write_input'):
raise ValueError(f'{fmt} input module does not have write_input!')
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 +222,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
----------
iodata
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(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)
Empty file added iodata/inputs/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions iodata/inputs/gaussian.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 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."""


import attr

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):
"""Do not edit this docstring. It will be overwritten."""
tovrstra marked this conversation as resolved.
Show resolved Hide resolved
# load IOData dict using attr.asdict because the IOData class uses __slots__
fields = attr.asdict(data, recurse=False)

fields["atcoords"] = data.atcoords / angstrom
# set defaults
fields["title"] = data.title if data.title is not None else 'Input Generated by IOData'
fields["run_type"] = data.run_type if data.run_type is not None else 'energy'
fields["lot"] = data.lot if data.lot is not None else 'hf'
fields["obasis_name"] = data.obasis_name if data.obasis_name is not None else 'sto-3g'
# convert spin polarization to multiplicity
fields["spinmult"] = int(data.spinpol) + 1 if data.spinpol is not None else 1
fields["charge"] = int(data.charge) if data.charge is not None else 0

# 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)
76 changes: 76 additions & 0 deletions iodata/inputs/orca.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 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/>
# --
"""Orca Input Module."""

from typing import TextIO
from string import Template
from attr import asdict

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

__all__ = []


default_template = """\
! ${lot} ${obasis_name} ${run_type}
# ${title}
*xyz ${charge} ${spinmult}
${geometry}
*"""


def write_input(f: TextIO, data: IOData, template: str = None):
"""Do not edit this docstring. It will be overwritten."""
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as above regarding documentation decorator.

# load IOData dict using attr.asdict because the IOData class uses __slots__
fields = asdict(data, recurse=False)

# store atomic coordinates in angstrom
fields["atcoords"] = data.atcoords / angstrom

# replace None for default values
fields["title"] = data.title if data.title is not None else 'Input Generated by IOData'
fields["run_type"] = data.run_type if data.run_type is not None else 'energy'
# convert spin polarization to multiplicity
fields["spinmult"] = int(data.spinpol) + 1 if data.spinpol is not None else 1
tovrstra marked this conversation as resolved.
Show resolved Hide resolved
fields["charge"] = int(data.charge) if data.nelec is not None else 0
FarnazH marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
fields["charge"] = int(data.charge) if data.nelec is not None else 0
fields["charge"] = int(data.charge) if data.charge is not None else 0

fields["lot"] = data.lot if data.lot is not None else 'HF'
fields["obasis_name"] = data.obasis_name if data.obasis_name is not None else 'STO-3G'

# convert run type to orca keywords
run_types = {"energy": "Energy", "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.

Same as gaussian.py, we can update this dict.

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

# generate geometry (in angstrom)
geometry = []
for num, coord in zip(fields["atnums"], fields["atcoords"]):
sym = f"{num2sym[num]:3}"
# check if template has a %coords block
if template is not None and "%coords" in template:
sym = f"{sym:>11}" # adding an appropiate indentation
geometry.append(f"{sym} {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)
22 changes: 22 additions & 0 deletions iodata/test/data/input_gaussian_h2o_opt_ub3lyp.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
%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



9 changes: 9 additions & 0 deletions iodata/test/data/input_gaussian_hcl_anion_opt_hf.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#n hf/sto-3g opt

Input Generated by IOData

-1 2
H 0.000000 0.000000 0.000000
Cl 1.000000 0.000000 0.000000


10 changes: 10 additions & 0 deletions iodata/test/data/input_gaussian_hcl_sp_rhf.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#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


16 changes: 16 additions & 0 deletions iodata/test/data/input_orca_h2o_sp_b3lyp.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
! B3LYP def2-SVP Grid4 TightSCF NOFINALGRID KeepDens
# Water
%output PrintLevel Mini Print[ P_Mulliken ] 1 Print[P_AtCharges_M] 1 end
%pal nprocs 4 end
%coords
CTyp xyz
Charge 0
Mult 1
Units Angs
coords
H 0.783837 -0.492236 -0.000000
O -0.000000 0.062020 -0.000000
H -0.783837 -0.492236 -0.000000
end
end

6 changes: 6 additions & 0 deletions iodata/test/data/input_orca_hcl_anion_opt_hf.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
! HF STO-3G Opt
# Input Generated by IOData
*xyz -1 2
H 0.000000 0.000000 0.000000
Cl 1.000000 0.000000 0.000000
*
8 changes: 8 additions & 0 deletions iodata/test/data/input_orca_nh3_sp_hf.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
! HF STO-3G Energy
# Molden file created by orca_2mkl for BaseName=test
*xyz 0 1
N -0.007455 0.044763 0.054913
H 0.791232 0.011334 0.023580
H -0.313244 -0.879581 0.283126
H -0.374402 0.246289 -1.069692
*
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


13 changes: 13 additions & 0 deletions iodata/test/data/template_orca.com
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
! ${lot} ${obasis_name} Grid4 TightSCF NOFINALGRID KeepDens
# ${title}
%output PrintLevel Mini Print[ P_Mulliken ] 1 Print[P_AtCharges_M] 1 end
%pal nprocs 4 end
%coords
CTyp xyz
Charge ${charge}
Mult ${spinmult}
Units Angs
coords
${geometry}
end
end
Loading