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 input.fchk output.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:

1from iodata import load_one
2
3mol = load_one("water.xyz")  # XYZ files contain atomic coordinates in Angstrom
4print(mol.atcoords)  # print coordinates in Bohr.

Note that IOData will automatically convert units from the file format’s official specification to atomic units (which is the format used throughout HORTON3).

The file format is inferred from the extension, but one can override the detection mechanism by manually specifying the format:

1from iodata import load_one
2
3mol = load_one("water.foo", "xyz")  # XYZ file with unusual extension
4print(mol.atcoords)

IOData also has basic support for loading databases of molecules. For example, the following will iterate over all frames in an XYZ file:

1from iodata import load_many
2
3# print the title line from each frame in the trajectory.
4for mol in load_many("trajectory.xyz"):
5    print(mol.title)
6    print(mol.atcoords)

More details can be found in the API documentation of iodata.api.load_one() and iodata.api.load_many().

Writing

IOData can also be used to write different file formats:

1from iodata import dump_one, load_one
2
3mol = load_one("water.fchk")
4# Here you may put some code to manipulate mol before writing it the data
5# to a different file.
6dump_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:

1from iodata import dump_many, load_many
2
3# Load all optimization steps and write as XYZ.
4dump_many(load_many("peroxide_opt.fchk"), "peroxide_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:

1from iodata import dump_many, load_many
2
3# Read the trajectory
4trj = list(load_many("peroxide_opt.fchk"))
5# Manipulate if desired
6for i, data in enumerate(trj):
7    data.title = f"Frame {i}"
8# Write the trajectory
9dump_many(trj, "peroxide_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.

 1from iodata import dump_many, load_many
 2
 3
 4def iter_data():
 5    """Read and modify the trajectory."""
 6    for i, data in enumerate(load_many("peroxide_opt.fchk")):
 7        data.title = f"Frame {i}"
 8        yield data
 9
10
11# Write the trajectory
12dump_many(iter_data(), "peroxide_opt.xyz")

More details can be found in the API documentation of iodata.api.dump_one() and iodata.api.dump_many().

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:

1from iodata import load_one, write_input
2
3write_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:

1from iodata import load_one, write_input
2
3mol = load_one("water.pdb")
4mol.lot = "B3LYP"
5mol.obasis_name = "6-31g*"
6mol.run_type = "opt"
7write_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:

 1from iodata import load_one, write_input
 2
 3mol = load_one("water.pdb")
 4mol.lot = "B3LYP"
 5mol.obasis_name = "Def2QZVP"
 6mol.run_type = "opt"
 7custom_template = """\
 8%NProcShared=4
 9%mem=16GB
10%chk=B3LYP_def2qzvp_H2O
11#n {lot}/{obasis_name} scf=(maxcycle=900,verytightlineq,xqc)
12integral=(grid=ultrafinegrid) pop=(cm5, hlygat, mbs, npa, esp)
13
14{title}
15
16{charge} {spinmult}
17{geometry}
18
19"""
20write_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:

 1from iodata import load_one, write_input
 2
 3mol = load_one("water.pdb")
 4mol.lot = "B3LYP"
 5mol.obasis_name = "Def2QZVP"
 6mol.run_type = "opt"
 7custom_template = """\
 8%chk={chk_name}
 9#n {lot}/{obasis_name} {run_type}
10
11{title}
12
13{charge} {spinmult}
14{geometry}
15
16"""
17# Custom keywords as arguments (best for few extra arguments)
18write_input(
19    mol, "water.com", fmt="gaussian", template=custom_template, chk_name="B3LYP_def2qzvp_water"
20)
21
22# Custom keywords from a dict (in cases with many extra arguments)
23custom_keywords = {"chk_name": "B3LYP_def2qzvp_waters"}
24write_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:

1from iodata import load_one, write_input
2
3mol = load_one("water.pdb")
4mol.lot = "B3LYP"
5mol.obasis_name = "6-31g*"
6mol.run_type = "opt"
7with open("my_template.com") as fh:
8    write_input(mol, "water.com", fmt="gaussian", template=fh.read())

More details can be found in the API documentation of iodata.api.write_input().

Data representation

IOData can be used to represent data in a consistent format for writing at a future point.

1import numpy as np
2
3from iodata import IOData
4
5mol = IOData(title="water")
6mol.atnums = np.array([8, 1, 1])
7mol.atcoords = np.array([[0, 0, 0], [0, 1, 0], [0, -1, 0]])  # in Bohr

All supported attributes can be found in the API documentation of the iodata.iodata.IOData class.

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)