Marcello-Sega/pytim

View on GitHub
pytim/cube.py

Summary

Maintainability
A
1 hr
Test Coverage
# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding: utf-8 -*-
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
from __future__ import print_function
from . import utilities
import numpy as np

Bohr = .52917721067


def consecutive_filename(universe, basename):
    if basename.endswith('.cube'):
        basename = basename[:-4]
    return utilities.consecutive_filename(universe, basename, 'cube')


def _write_atomgroup(f, group, atomic_numbers):
    """save the particles in a cube file named consecutively using the frame
    number. If the atom type string does not correspond to any symbol in the
    periodic table, zero is assigned. Note that this method does not guarantee

    It assumes that the header has already been written.

    :param file f: the file
    :param AtomGroup group: the atom group to be written to the cube file
    :param array atomic_numbers: the atomic number identifying the element of each atom, or None for automatic choice

    """

    types = group.types
    if atomic_numbers is None:
        n0 = {'number': 0}
        atomic_numbers = [utilities.atoms_maps.get(t, n0)['number'] for t in types]

    for i, p in enumerate(group.positions):
        f.write(_format_atom(p / Bohr, atomic_numbers[i]))


def _format_atom(pos, number, format_str="{:d} {:g} {:g} {:g} {:g}"):
    return format_str.format(int(number), float(number), pos[0], pos[1], pos[2]) + '\n'


def _write_scalar_grid(f, scalars):
    """write in a cube file a scalar field on a rectangular grid.
       Assumes that the header has been already written.

       :param file f: the file
       :param array scalars: a (grid_size,) array with the scalar field values
    """
    for count, val in enumerate(scalars):
        f.write('{:E}'.format(val) + ' ')
        if count % 6 == 5:
            f.write('\n')
    if count % 6 != 5:
        f.write('\n')


def write_file(filename,
               group,
               grid_size,
               spacing,
               scalars,
               atomic_numbers=None,
               normalize=True):
    """write in a cube file a scalar field on a rectangular grid.
       Assumes that the header has been already written.

       :param string filename: the filename
       :param array grid_size: number of points in the grid along each
                               direction
       :param array spacing  : a (3,) array with the point spacing along the 3
                               directions
       :param array scalars  : a (grid_size,) array with the scalar field
                               values
    """
    if normalize:
        maxval = np.max(scalars)
    else:
        maxval = 1.0
    spacing = np.array(spacing) / Bohr  # CUBE units are Bohr
    with open(filename, "w") as f:
        if group is not None:
            natoms = len(group.atoms)
        else:
            natoms = 0
        grid_size = np.array(grid_size, dtype=int)
        shift = -np.array([0, 0, 11]) * spacing * Bohr
        #spacing =  spacing*(grid_size+1.)/grid_size
        f.write('CPMD CUBE FILE\n')
        f.write('GENERATED BY PYTIM\n')
        f.write('{:5d}{:12.6f}{:12.6f}{:12.6f}\n'.format(
            natoms, shift[0], shift[1], shift[2]))
        # NOTE: only rectangular boxes so far
        f.write('{:5d}{:12.6f}{:12.6f}{:12.6f}\n'.format(
            grid_size[0], spacing[0], 0., 0.))
        f.write('{:5d}{:12.6f}{:12.6f}{:12.6f}\n'.format(
            grid_size[1], 0., spacing[1], 0.))
        f.write('{:5d}{:12.6f}{:12.6f}{:12.6f}\n'.format(
            grid_size[2], 0., 0., spacing[2]))
        if group is not None:
            _write_atomgroup(f, group, atomic_numbers)
        # NOTE the 6-fold roll has been determined empirically.
        field = np.roll(
            np.swapaxes(
                scalars.reshape((grid_size[2], grid_size[1], grid_size[0])), 2,
                0),
            6,
            axis=-1).flatten()  # xyz <-> zyx
        _write_scalar_grid(f, field / maxval)