Source code for MDAnalysis.coordinates.FHIAIMS

# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
#
# MDAnalysis --- https://www.mdanalysis.org
# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors
# (see the file AUTHORS for the full list of names)
#
# Released under the GNU Public Licence, v2 or any higher version
#
# Please cite your use of MDAnalysis in published work:
#
# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler,
# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein.
# MDAnalysis: A Python package for the rapid analysis of molecular dynamics
# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th
# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy.
# doi: 10.25080/majora-629e541a-00e
#
# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein.
# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations.
# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787
#

"""
FHI-AIMS file format --- :mod:`MDAnalysis.coordinates.FHIAIMS`
==============================================================

Classes to read and write `FHI-AIMS`_ coordinate files.

The cell vectors are specified by the (optional) lines with the
``lattice_vector`` tag::

    lattice_vector x  y  z

where x, y, and z are expressed in ångström (Å).

.. Note::

   In the original `FHI-AIMS format`_, up to three lines with
   ``lattice_vector`` are allowed (order matters) where the absent line implies
   no periodicity in that direction.  In MDAnalysis, only the case of no
   ``lattice_vector`` or three ``lattice_vector`` lines are allowed.

Atomic positions and names are specified either by the ``atom`` or by the
``atom_frac`` tags::

    atom           x  y  z  name
    atom_frac      nx ny nz name

where x, y, and z are expressed in ångström, and nx, ny and nz are real numbers
in [0, 1] and are used to compute the atomic positions in units of the basic
cell.

Atomic velocities can be added on the line right after the corresponding
``atom`` in units of Å/ps using the ``velocity`` tag::

    velocity      vx vy vz


The field name is a string identifying the atomic species.  See also the
specifications in the official `FHI-AIMS format`_.

Classes
-------

.. autoclass:: FHIAIMSReader
   :members:
   :inherited-members:

.. autoclass:: FHIAIMSWriter
   :members:
   :inherited-members:

Developer notes: ``FHIAIMSWriter`` format strings
-------------------------------------------------

The :class:`FHIAIMSWriter` class has a :attr:`FHIAIMSWriter.fmt`
attribute, which is a dictionary of different strings for writing
lines in ``.in`` files.  These are as follows:

``xyz``
  An atom line without velocities.  Requires that the `name` and
  `pos` keys be supplied.  E.g.::

     fmt['xyz'].format(pos=(0.0, 1.0, 2.0), name='O')

``vel``
  An line that specifies velocities::

     fmt['xyz'].format(vel=(0.1, 0.2, 0.3))

``box_triclinic``
  The (optional) initial lines of the file which gives box dimensions.
  Requires the `box` keyword, as a length 9 vector.  This is a flattened
  version of the (3, 3) triclinic vector representation of the unit
  cell.


.. Links

.. _FHI-AIMS: https://aimsclub.fhi-berlin.mpg.de/
.. _FHI-AIMS format: https://doi.org/10.6084/m9.figshare.12413477.v1

"""
import re

import itertools
import warnings

import numpy as np

from . import base
from .core import triclinic_box, triclinic_vectors
from ..exceptions import NoDataError
from ..lib import util
from ..lib import mdamath


from .base import Timestep


[docs]class FHIAIMSReader(base.SingleFrameReaderBase): """Reader for the FHIAIMS geometry format. Single frame reader for the `FHI-AIMS`_ input file format. Reads geometry (3D and molecules only), positions (absolut or fractional), velocities if given, all according to the `FHI-AIMS format`_ specifications """ format = ['IN', 'FHIAIMS'] units = {'time': 'ps', 'length': 'Angstrom', 'velocity': 'Angstrom/ps'} def _read_first_frame(self): with util.openany(self.filename, 'rt') as fhiaimsfile: relative, positions, velocities, lattice_vectors = [], [], [], [] skip_tags = ["#", "initial_moment"] oldline = '' for line in fhiaimsfile: line = line.strip() if line.startswith("atom"): positions.append(line.split()[1:-1]) relative.append('atom_frac' in line) oldline = line continue if line.startswith("velocity"): if not 'atom' in oldline: raise ValueError( 'Non-conforming line (velocity must follow atom): ({0})in FHI-AIMS input file {0}'.format(line, self.filename)) velocities.append(line.split()[1:]) oldline = line continue if line.startswith("lattice"): lattice_vectors.append(line.split()[1:]) oldline = line continue if any([line.startswith(tag) for tag in skip_tags]): oldline = line continue raise ValueError( 'Non-conforming line: ({0})in FHI-AIMS input file {0}'.format(line, self.filename)) # positions and velocities are lists of lists of strings; they will be # cast to np.arrays(..., dtype=float32) during assignment to ts.positions/ts.velocities lattice_vectors = np.asarray(lattice_vectors, dtype=np.float32) if len(velocities) not in (0, len(positions)): raise ValueError( 'Found incorrect number of velocity tags ({0}) in the FHI-AIMS file, should be {1}.'.format( len(velocities), len(positions))) if len(lattice_vectors) not in (0, 3): raise ValueError( 'Found partial periodicity in FHI-AIMS file. This cannot be handled by MDAnalysis.') if len(lattice_vectors) == 0 and any(relative): raise ValueError( 'Found relative coordinates in FHI-AIMS file without lattice info.') # create Timestep self.n_atoms = n_atoms = len(positions) self.ts = ts = self._Timestep(n_atoms, **self._ts_kwargs) ts.positions = positions if len(lattice_vectors) > 0: ts.dimensions = triclinic_box(*lattice_vectors) ts.positions[relative] = np.matmul(ts.positions[relative], lattice_vectors) if len(velocities) > 0: ts.velocities = velocities self.ts.frame = 0 # 0-based frame number
[docs] def Writer(self, filename, n_atoms=None, **kwargs): """Returns a FHIAIMSWriter for *filename*. Parameters ---------- filename: str filename of the output FHI-AIMS file Returns ------- :class:`FHIAIMSWriter` """ if n_atoms is None: n_atoms = self.n_atoms return FHIAIMSWriter(filename, n_atoms=n_atoms, **kwargs)
[docs]class FHIAIMSWriter(base.WriterBase): """FHI-AIMS Writer. Single frame writer for the `FHI-AIMS`_ format. Writes geometry (3D and molecules only), positions (absolut only), velocities if given, all according to the `FHI-AIMS format`_ specifications. If no atom names are given, it will set each atom name to "X". """ format = ['IN', 'FHIAIMS'] units = {'time': None, 'length': 'Angstrom', 'velocity': 'Angstrom/ps'} #: format strings for the FHI-AIMS file (all include newline) fmt = { # coordinates output format, see https://doi.org/10.6084/m9.figshare.12413477.v1 'xyz': "atom {pos[0]:12.8f} {pos[1]:12.8f} {pos[2]:12.8f} {name:<3s}\n", 'vel': "velocity {vel[0]:12.8f} {vel[1]:12.8f} {vel[2]:12.8f}\n", # unitcell 'box_triclinic': "lattice_vector {box[0]:12.8f} {box[1]:12.8f} {box[2]:12.8f}\nlattice_vector {box[3]:12.8f} {box[4]:12.8f} {box[5]:12.8f}\nlattice_vector {box[6]:12.8f} {box[7]:12.8f} {box[8]:12.8f}\n" } def __init__(self, filename, convert_units=True, n_atoms=None, **kwargs): """Set up the FHI-AIMS Writer Parameters ----------- filename : str output filename n_atoms : int (optional) number of atoms """ self.filename = util.filename(filename, ext='.in', keep=True) self.n_atoms = n_atoms def _write_next_frame(self, obj): """Write selection at current trajectory frame to file. Parameters ----------- obj : AtomGroup or Universe .. versionchanged:: 2.0.0 Support for deprecated Timestep argument has now been removed. Use AtomGroup or Universe as an input instead. """ # write() method that complies with the Trajectory API try: # make sure to use atoms (Issue 46) ag = obj.atoms # can write from selection == Universe (Issue 49) except AttributeError: errmsg = "Input obj is neither an AtomGroup or Universe" raise TypeError(errmsg) from None # Check for topology information missing_topology = [] try: names = ag.names except (AttributeError, NoDataError): names = itertools.cycle(('X',)) missing_topology.append('names') try: atom_indices = ag.ids except (AttributeError, NoDataError): atom_indices = range(1, ag.n_atoms+1) missing_topology.append('ids') if missing_topology: warnings.warn( "Supplied AtomGroup was missing the following attributes: " "{miss}. These will be written with default values. " "".format(miss=', '.join(missing_topology))) positions = ag.positions try: velocities = ag.velocities has_velocities = True except (AttributeError, NoDataError): has_velocities = False with util.openany(self.filename, 'wt') as output_fhiaims: # Lattice try: # for AtomGroup/Universe tri_dims = obj.universe.trajectory.ts.triclinic_dimensions except AttributeError: # for Timestep tri_dims = obj.triclinic_dimensions # full output if tri_dims is not None: output_fhiaims.write( self.fmt['box_triclinic'].format(box=tri_dims.flatten())) # Atom descriptions and coords # Dont use enumerate here, # all attributes could be infinite cycles! for atom_index, name in zip( range(ag.n_atoms), names): output_fhiaims.write(self.fmt['xyz'].format( pos=positions[atom_index], name=name)) if has_velocities: output_fhiaims.write(self.fmt['vel'].format( vel=velocities[atom_index]))