Source code for schrodinger.application.phase.packages.shape_binary_utils

import contextlib
import os

from schrodinger import shape

# Shape data treatment

COPY_BINARY = "copy"
REMOTE_BINARY = "remote"

# -----------------------------------------------------------------------------


def _raise_error(errno, message):
    '''
    Raises `RuntimeError` using the `message` and error string for `errno`.

    :param errno: Error code.
    :type errno: int

    :param message: Error message prefix.
    :type message: str
    '''

    reason = os.strerror(errno)
    if reason:
        message += f': {reason}'
    raise RuntimeError(message)


# -----------------------------------------------------------------------------


def _close_reader_or_writer(instance, filename):
    '''
    Closes shape binary file reader/writer.

    :param instance: Shape binary file reader or writer.
    :type instance: `schrodinger.shape.ShapeBinaryFileReader` or
         `schrodinger.shape.ShapeBinaryFileWriter`

    :param filename: File name (for error message).
    :type filename: str

    :raises RuntimeError: If `instance.close()` fails.
    '''

    if not instance.close():
        _raise_error(instance.getErrno(), f"could not close '{filename}'")


# -----------------------------------------------------------------------------


[docs]class ShapeFileWriter(contextlib.AbstractContextManager):
[docs] def __init__(self, filename, metadata, compress): ''' :param filename: File name (expected to exist if metadata is empty). :type filename: str :param metadata: Text to be stored as file-scope "metadata". If empty, "append" opening mode is assumed. If not empty, existing file with the same name will be truncated. :type metadata: str :param compress: Compress the file using zlib. :type compress: bool ''' self._writer = shape.ShapeBinaryFileWriter(filename, metadata, compress) self._filename = filename self._check('could not ' + ('open' if metadata else 'create') + f" '{filename}'")
def __exit__(self, exc_type, exc_value, traceback): _close_reader_or_writer(self._writer, self._filename) def _check(self, message=None): ''' Raises `RuntimeError` if `self._writer` is not valid. :param message: Error message prefix. :type message: str ''' if not self._writer.valid(): if message is None: message = f"'{self._filename}': invalid writer" _raise_error(self._writer.getErrno(), message)
[docs] def append(self, title, shapes): ''' Appends `shapes` to the associated binary file. :param title: Title of the shapes. :type title: str :param shapes: List of `shape.ShapeStructure` instances (may have different number of spheres). :type shapes: list(shape.ShapeStructure) ''' self._writer.write(title, shapes) self._check(f"'{self._filename}': write failed")
# -----------------------------------------------------------------------------
[docs]class ShapeFileReader(contextlib.AbstractContextManager):
[docs] def __init__(self, filename, block_size=10000): ''' :param filename: File name. :type filename: str :param block_size: Number of (title, structures) entries to read in one scoop. :type block_size: int ''' self._reader = shape.ShapeBinaryFileReader(filename) self._filename = filename self._block_size = block_size if self._reader.closed(): _raise_error(self._reader.getErrno(), f"could not open '{filename}'")
def __exit__(self, exc_type, exc_value, traceback): _close_reader_or_writer(self._reader, self._filename) @property def metadata(self): ''' File-scope metadata from the associated "shape binary" file. ''' return self._reader.metadata def __iter__(self): ''' Yields blocks of named blocks of shape structures. :return: Blocks of shapes and their titles. The `titles` is a vector of (name, number-of-shape-structures) pairs, and `shapes` is a vector of shape.ShapeStructure instances. len(shapes) == titles[0][1] + titles[1][1] + ... + titles[N][1] :rtype: (shape.ShapeStructureVector, shape.ShapeBlockTitleVector) ''' shapes = shape.ShapeStructureVector() titles = shape.ShapeBlockTitleVector() while not self._reader.closed(): yield self.getBlock(shapes, titles)
[docs] def getBlock(self, shapes=None, titles=None): ''' Reads next block of shapes and their titles. Returns (None, None) on EOF, raises RuntimeError on errors. Uses memory associated with `shapes` and/or `titles` unless the parameter is None. :param shapes: SWIG-wrapped c++ std::vector<shape::ShapeStructure>. :type shapes: shape.ShapeStructureVector :param titles: SWIG-wrapped c++ std::vector<std::pair<std::string, size_t>>. :type titles: shape.ShapeBlockTitleVector :return: Blocks of shapes and their titles. The `titles` is a vector of (name, number-of-shape-structures) pairs, and `shapes` is a vector of shape.ShapeStructure instances. len(shapes) == titles[0][1] + titles[1][1] + ... + titles[N][1] :rtype: (shape.ShapeStructureVector, shape.ShapeBlockTitleVector) ''' if self._reader.closed(): return (None, None) if shapes is None: shapes = shape.ShapeStructureVector() if titles is None: titles = shape.ShapeBlockTitleVector() if not self._reader.readMultiple(self._block_size, titles, shapes): errno = self._reader.getErrno() self._reader.close() if errno: _raise_error(errno, f"{self._filename}: read error") return (shapes, titles)
# -----------------------------------------------------------------------------
[docs]class SingularShapeFileReader(ShapeFileReader):
[docs] def __init__(self, filename): super().__init__(filename)
def __iter__(self): ''' Yields named blocks of shape structures. :return: Title and shapes. :rtype: (str, shape.ShapeStructureVector) ''' shapes = shape.ShapeStructureVector() ok, title = self._reader.read(shapes) while ok: yield (title, shapes) ok, title = self._reader.read(shapes) errno = self._reader.getErrno() self._reader.close() if errno: _raise_error(errno, f"{self._filename}: read error")
# -----------------------------------------------------------------------------