Source code for scm.plams.trajectories.xyzhistoryfile

#!/usr/bin/env python

from scm.plams.core.settings import Settings
import numpy
from scm.plams.mol.atom import Atom
from scm.plams.mol.molecule import Molecule
from scm.plams.tools.periodic_table import PT
from scm.plams.trajectories.xyzfile import XYZTrajectoryFile, data_from_xyzcomment

__all__ = ["XYZHistoryFile"]


[docs]class XYZHistoryFile(XYZTrajectoryFile): """ Class representing an XYZ file containing a molecular simulation history with varying numbers of atoms An instance of this class has the following attributes: * ``file_object`` -- A Python :py:class:`file` object, referring to the actual XYZ file * ``position`` -- The frame to which the cursor is currently pointing in the XYZ file * ``mode`` -- Designates whether the file is in read or write mode ('r' or 'w') * ``elements`` -- The elements of the atoms in the system at the current frame An |XYZHistoryFile| object behaves very similar to a regular file object. It has read and write methods (:meth:`read_next` and :meth:`write_next`) that read and write from/to the position of the cursor in the ``file_object`` attribute. If the file is in read mode, an additional method :meth:`read_frame` can be used that moves the cursor to any frame in the file and reads from there. The amount of information stored in memory is kept to a minimum, as only information from the current frame is ever stored. Reading and writing to and from the files can be done as follows:: >>> from scm.plams import XYZHistoryFile >>> xyz = XYZHistoryFile('old.xyz') >>> mol = xyz.get_plamsmol() >>> xyzout = XYZHistoryFile('new.xyz',mode='w') >>> for i in range(xyz.get_length()) : >>> crd,cell = xyz.read_frame(i,molecule=mol) >>> xyzout.write_next(molecule=mol) The above script reads information from the XYZ file ``old.xyz`` into the |Molecule| object ``mol`` in a step-by-step manner. The |Molecule| object is then passed to the :meth:`write_next` method of the new |XYZHistoryFile| object corresponding to the new xyz file ``new.xyz``. The exact same result can also be achieved by iterating over the instance as a callable >>> xyz = XYZHistoryFile('old.xyz') >>> mol = xyz.get_plamsmol() >>> xyzout = XYZHistoryFile('new.xyz',mode='w') >>> for crd,cell in xyz(mol) : >>> xyzout.write_next(molecule=mol) This procedure requires all coordinate information to be passed to and from the |Molecule| object for each frame, which can be time-consuming. It is therefore also possible to bypass the |Molecule| object when reading through the frames:: >>> xyz = XYZHistoryFile('old.xyz') >>> xyzout = XYZHistoryFile('new.xyz',mode='w') >>> for crd,cell in xyz : >>> xyzout.write_next(coords=crd,elements=xyz.elements) By default the write mode will create a minimal version of the XYZ file, containing only elements and coordinates. Additional information can be written to the file by supplying additional arguments to the :meth:`write_next` method. The additional keywords `step` and `energy` trigger the writing of a remark containing the molecule name, the step number, the energy, and the lattice vectors. >>> mol = Molecule('singleframe.xyz') >>> xyzout = XYZHistoryFile('new.xyz',mode='w') >>> xyzout.set_name('MyMol') >>> xyzout.write_next(molecule=mol, step=0, energy=5.) """
[docs] def __init__(self, filename, mode="r", fileobject=None, ntap=None): """ Initiates an XYZHistoryFile object * ``filename`` -- The path to the XYZ file * ``mode`` -- The mode in which to open the XYZ file ('r' or 'w') * ``fileobject`` -- Optionally, a file object can be passed instead (filename needs to be set to None) * ``ntap`` -- If the file is in write mode, the number of atoms can be passed here """ XYZTrajectoryFile.__init__(self, filename, mode, fileobject, ntap) self.input_elements = self.elements[:]
def _is_endoffile(self): """ If the end of file is reached, return coords and cell as None """ end = False line = self.file_object.readline() if len(line) == 0: end = True return end nats = int(line.split()[0]) for i in range(nats + 1): line = self.file_object.readline() if len(line) == 0: end = True break for i in range(self.nveclines): line = self.file_object.readline() if len(line) == 0: end = True break return end def _read_coordinates(self, molecule): """ Read the coordinates at current step """ # Find the number of atoms line = self.file_object.readline() if len(line) == 0: return None, None # End of file is reached nats = int(line.split()[0]) line = self.file_object.readline() # Handle the comment line cell = None historydata = data_from_xyzcomment(line) if "Lattice" in historydata: cell = historydata["Lattice"] del historydata["Lattice"] if self.include_historydata: self.historydata = historydata # Read coordinates and elements coords = [] elements = [] for i in range(nats): line = self.file_object.readline() words = line.split() coords.append([float(w) for w in words[1:4]]) elements.append(words[0]) # If the elements changed, update the molecule if elements != self.elements: self.elements = elements self.coords = numpy.array(coords) # Rebuild the molecule (bonds will disappear for now) if isinstance(molecule, Molecule): for at in reversed(molecule.atoms): molecule.delete_atom(at) molecule.properties = Settings() for el in elements: atom = Atom(PT.get_atomic_number(el)) molecule.add_atom(atom) else: self.coords[:] = coords # Possibly read lattice lattice = [] for i in range(self.nveclines): line = self.file_object.readline() words = line.split() lattice.append([float(w) for w in words[1:]]) if cell is None and len(lattice) > 0: cell = lattice # Assign the data to the molecule object if isinstance(molecule, Molecule): self._set_plamsmol(self.coords, cell, molecule) return coords, cell
[docs] def write_next( self, coords=None, molecule=None, elements=None, cell=[0.0, 0.0, 0.0], conect=None, historydata=None ): """ Write frame to next position in trajectory file * ``coords`` -- A list or numpy array of (``ntap``,3) containing the system coordinates * ``molecule`` -- A molecule object to read the molecular data from * ``elements`` -- The element symbols of the atoms in the system * ``cell`` -- A set of lattice vectors or cell diameters * ``energy`` -- An energy value to be written to the remark line * ``conect`` -- A dictionary containing connectivity info (not used) * ``historydata`` -- A dictionary containing additional variables to be written to the comment line The ``historydata`` dictionary can contain for example: ('Step','Energy'), the frame number and the energy respectively .. note:: Either ``coords`` and ``elements`` or ``molecule`` are mandatory arguments """ if isinstance(molecule, Molecule): coords, cell, elements = self._read_plamsmol(molecule)[:3] self.elements = elements cell = self._convert_cell(cell) self._write_moldata(coords, cell, historydata) self.position += 1