Source code for soprano.collection.generate.airss
# Soprano - a library to crack crystals! by Simone Sturniolo
# Copyright (C) 2016 - Science and Technology Facility Council
# Soprano is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# Soprano is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Bindings for AIRSS Buildcell program for random structure generation"""
import copy
import hashlib
import os
import subprocess as sp
from ase import io as ase_io
# Internal imports
from soprano.utils import safe_communicate, seedname
# Python 2-to-3 compatibility
try:
    from cStringIO import StringIO
except ImportError:
    from io import StringIO
[docs]
def airssGen(
    input_file,
    n=100,
    buildcell_command="buildcell",
    buildcell_path=None,
    clone_calc=True,
):
    """Generator function binding to AIRSS' Buildcell.
    This function searches for a buildcell executable and uses it to
    generate multiple new Atoms structures for a collection.
    | Args:
    |   input_file (str or file): the .cell file with appropriate comments
    |                             specifying the details of buildcell's
    |                             construction work.
    |   n (int): number of structures to generate. If set to None the
    |            generator goes on indefinitely.
    |   buildcell_command (str): command required to call the buildcell
    |                            executable.
    |   buildcell_path (str): path where the buildcell executable can be
    |                         found. If not present, the buildcell command
    |                         will be invoked directly (assuming the
    |                         executable is in the system PATH).
    |   clone_calc (bool): if True, the CASTEP calculator in the input file
    |                      will be copied and attached to the new structures.
    |                      This means that for example any additional CASTEP
    |                      keywords/blocks in the input file will be carried
    |                      on to the new structures. Default is True.
    | Returns:
    |   airssGenerator (generator): an iterable object that yields structures
    |                               created by buildcell.
    """
    # First: check that AIRSS is even installed
    if buildcell_path is None:
        buildcell_path = ""
    airss_cmd = [os.path.join(buildcell_path, buildcell_command)]
    try:
        stdout, stderr = sp.Popen(
            airss_cmd + ["-h"], stdout=sp.PIPE, stderr=sp.PIPE
        ).communicate()
    except OSError:
        # Not even installed!
        raise RuntimeError("No instance of Buildcell found on this system")
    # Now open the given input file
    try:
        input_file = open(input_file)  # If it's a string
    except TypeError:
        pass  # If it's already a file
    template = input_file.read()
    # Now get the file name
    basename = seedname(input_file.name)
    input_file.close()
    # Calculator (if needed)
    calc = None
    if clone_calc:
        calc = ase_io.read(input_file.name).calc
    # And keep track of the count!
    # (at least if it's not infinite)
    i = 0
    while True:
        if n is not None:
            if i >= n:
                return
            i += 1
        # Generate a structure
        subproc = sp.Popen(
            airss_cmd,
            universal_newlines=True,
            stdin=sp.PIPE,
            stdout=sp.PIPE,
            stderr=sp.PIPE,
        )
        stdout, stderr = safe_communicate(subproc, template)
        # Now turn it into a proper Atoms object
        # To do this we need to make it look like a file to ASE's io.read
        try:
            newcell = ase_io.read(StringIO(stdout), format="castep-cell")
        except Exception:
            # If ANYTHING happens, let's consider that stdout might be wrong
            raise RuntimeError(
                    f"Invalid output from buildcell:\nstdout:\n{stdout}" f"\nstderr:\n{stderr}"
            )
        if clone_calc:
            newcell.calc = copy.deepcopy(calc)
        # Generate it a name, function of its properties
        postfix = hashlib.md5(str(newcell.get_positions()).encode()).hexdigest()
        newcell.info["name"] = f"{basename}_{postfix}"
        yield newcell