Getting Started#
IOData can be used to read and write different quantum chemistry file formats.
Script usage#
The simplest way to use IOData, without writing any code is to use the iodata-convert
script.
iodata-convert in.fchk out.molden
See the --help
option for more details on usage.
Code usage#
More complex use cases can be implemented in Python, using IOData as a library. IOData stores an object containing the data read from the file.
Reading#
To read a file, use something like this:
from iodata import load_one
mol = load_one('water.xyz') # XYZ files contain atomic coordinates in Angstrom
print(mol.atcoords) # print coordinates in Bohr.
IOData also has basic support for loading databases of molecules. For example, the following will iterate over all frames in an XYZ file:
from iodata import load_many
# print the title line from each frame in the trajectory.
for mol in load_many('trajectory.xyz'):
print(mol.title)
Writing#
IOData can also be used to write different file formats:
from iodata import load_one, dump_one
mol = load_one('water.fchk')
# Here you may put some code to manipulate mol before writing it the data
# to a different file.
dump_one(mol, 'water.molden')
One could also convert (and manipulate) an entire trajectory. The following example converts a geometry optimization trajectory from a Gaussian FCHK file to an XYZ file:
from iodata import load_many, dump_many
# Conversion without manipulation.
dump_many((mol for mol in load_many('water_opt.fchk')), 'water_opt.xyz')
If you wish to perform some manipulations before writing the trajectory, the simplest way is to load the entire trajectory in a list of IOData objects and dump it later:
from iodata import load_many, dump_many
# Read the trajectory
trj = list(load_many('water_opt.fchk'))
# Manipulate if desired
# ...
# Write the trajectory
dump_many(trj, 'water_opt.xyz')
For very large trajectories, you may want to avoid loading it as a whole in
memory. For this, one should avoid making the list
object in the above
example. The following approach would be more memory efficient.
from iodata import load_many, dump_many
def itermols():
for mol in load_many("traj1.xyz"):
# Do some manipulations
yield modified_mol
dump_many(itermols(), "traj2.xyz")
Input files#
IOData can be used to write input files for quantum-chemistry software. By default minimal settings are used, which can be changed if needed. For example, the following will prepare a Gaussian input for a HF/STO-3G calculation from a PDB file:
from iodata import load_one, write_input
write_input(load_one("water.pdb"), "water.com", fmt="gaussian")
The level of theory and other settings can be modified by setting corresponding attributes in the IOData object:
from iodata import load_one, write_input
mol = load_one("water.pdb")
mol.lot = "B3LYP"
mol.obasis_name = "6-31g*"
mol.run_type = "opt"
write_input(mol, "water.com", fmt="gaussian")
The run types can be any of the following: energy
, energy_force
,
opt
, scan
or freq
. These are translated into program-specific
keywords when the file is written.
It is possible to define a custom input file template to allow for specialized
commands. This is done by passing a template string using the optional template
keyword,
placing each IOData attribute (or additional keyword, as shown below) in curly brackets:
from iodata import load_one, write_input
mol = load_one("water.pdb")
mol.lot = "B3LYP"
mol.obasis_name = "Def2QZVP"
mol.run_type = "opt"
custom_template = """\
%NProcShared=4
%mem=16GB
%chk=B3LYP_def2qzvp_H2O
#n {lot}/{obasis_name} scf=(maxcycle=900,verytightlineq,xqc) integral=(grid=ultrafinegrid) pop=(cm5, hlygat, mbs, npa, esp)
{title}
{charge} {spinmult}
{geometry}
"""
write_input(mol, "water.com", fmt="gaussian", template=custom_template)
The input file template may also include keywords that are not part of the IOData object:
from iodata import load_one, write_input
mol = load_one("water.pdb")
mol.lot = "B3LYP"
mol.obasis_name = "Def2QZVP"
mol.run_type = "opt"
custom_template = """\
%chk={chk_name}
#n {lot}/{obasis_name} {run_type}
{title}
{charge} {spinmult}
{geometry}
"""
# Custom keywords as arguments (best for few extra arguments)
write_input(mol, "water.com", fmt="gaussian", template=custom_template, chk_name="B3LYP_def2qzvp_water")
# Custom keywords from a dict (in cases with many extra arguments)
custom_keywords = {"chk_name": "B3LYP_def2qzvp_waters"}
write_input(mol, "water.com", fmt="gaussian", template=custom_template, **custom_keywords)
In some cases, it may be preferable to load the template from file, instead of defining it in the script:
from iodata import load_one, write_input
mol = load_one("water.pdb")
mol.lot = "B3LYP"
mol.obasis_name = "6-31g*"
mol.run_type = "opt"
write_input(mol, "water.com", fmt="gaussian", template=open("my_template.com", "r").read())
Data storage#
IOData can be used to store data in a consistent format for writing at a future point.
import numpy as np
from iodata import IOData
mol = IOData(title="water")
mol.atnums = np.array([8, 1, 1])
mol.atcoords = np.array([[0, 0, 0,], [0, 1, 0,], [0, -1, 0,]]) # in Bohr
Unit conversion#
IOData always represents all quantities in atomic units and unit conversion
constants are defined in iodata.utils
. Conversion to atomic units is done
by multiplication with a unit constant. This convention can be easily
remembered with the following examples:
When you say “this bond length is 1.5 Å”, the IOData equivalent is
bond_length = 1.5 * angstrom
.The conversion from atomic units is similar to axes labels in old papers. For example. a bond length in angstrom is printed as “Bond length / Å”. Expressing this with IOData’s conventions gives
print("Bond length in Angstrom:", bond_length / angstrom)
(This is rather different from the ASE conventions.)