Source code for metatrain.utils.data.writers

import os
from pathlib import Path
from typing import Dict, Optional, Protocol, Type, Union

from metatomic.torch import ModelCapabilities

from .ase import ASEWriter
from .diskdataset import DiskDatasetWriter
from .memmap import MemmapWriter
from .metatensor import MetatensorWriter
from .writers import (
    Writer,
)
from .writers import (
    _split_tensormaps as _split_tensormaps,
)


class WriterFactory(Protocol):
    def __call__(
        self,
        filename: Union[str, Path],
        capabilities: Optional[ModelCapabilities] = None,
        append: Optional[bool] = None,
    ) -> Writer: ...


def _make_factory(
    cls: Type[Writer],
) -> WriterFactory:
    def factory(
        filename: Union[str, Path],
        capabilities: Optional[ModelCapabilities] = None,
        append: Optional[bool] = None,
    ) -> Writer:
        return cls(filename, capabilities, append)

    return factory


PREDICTIONS_WRITERS: Dict[str, WriterFactory] = {
    ".xyz": _make_factory(ASEWriter),
    ".mts": _make_factory(MetatensorWriter),
    ".zip": _make_factory(DiskDatasetWriter),
}
""":py:class:`dict`: dictionary mapping file suffixes to a prediction writer"""

DEFAULT_WRITER: WriterFactory = _make_factory(ASEWriter)

MEMMAP_WRITER: WriterFactory = _make_factory(MemmapWriter)


[docs] def get_writer( filename: Union[str, Path], capabilities: Optional[ModelCapabilities] = None, append: Optional[bool] = None, fileformat: Optional[str] = None, ) -> Writer: """Selects the appropriate writer based on the file extension. For certain file suffixes, the systems will also be written (i.e ``xyz``). A path ending in a path separator (e.g. ``predictions/``) is treated as a memory-mapped ``MemmapDataset`` directory rather than a single file. Since nothing exists on disk yet when a writer is selected, the trailing separator is the write-side equivalent of that check. The capabilities of the model are used to infer the type (physical quantity) of the predictions. In this way, for example, position gradients of energies can be saved as forces. For the moment, strain gradients of the energy are saved as stresses (and not as virials). :param filename: name of the file to write, or a directory path ending in a path separator for a memmap dataset :param capabilities: capabilities of the model :param append: if :py:obj:`True`, the data will be appended to the file, if it exists. If :py:obj:`False`, the file will be overwritten. If :py:obj:`None`, the default behavior of the writer is used. :param fileformat: format of the target value file. If :py:obj:`None` the format is determined from the file extension. :return: a :py:class:`Writer` instance. :raises ValueError: if ``filename`` both ends in a path separator and has a recognized file suffix (e.g. ``"predictions.zip/"``), since it is then ambiguous whether a memmap directory or a file of that format was intended. """ filename_str = str(filename) if filename_str.endswith(("/", os.sep)): stripped_suffix = Path(filename_str.rstrip("/" + os.sep)).suffix if stripped_suffix in PREDICTIONS_WRITERS: raise ValueError( f"'{filename_str}' is ambiguous: it looks like both a " f"'{stripped_suffix}' file and a directory path (it ends with a " "path separator). Remove the trailing separator to write a " f"'{stripped_suffix}' file, or remove the '{stripped_suffix}' suffix " "to write a memmap dataset directory." ) return MEMMAP_WRITER(filename, capabilities, append) if fileformat is None: fileformat = Path(filename).suffix try: writer_factory = PREDICTIONS_WRITERS[fileformat] except KeyError: raise ValueError( f"fileformat '{fileformat}' is not supported. Use a trailing path " "separator (e.g. 'predictions/') to write a memmap dataset instead." ) return writer_factory(Path(filename).stem + fileformat, capabilities, append)