Пример #1
0
    def _read_castep(self, folder, sname=None):
        try:
            if sname is not None:
                cfile = os.path.join(folder, sname + ".castep")
            else:
                cfile = glob.glob(os.path.join(folder, "*.castep"))[0]
                sname = seedname(cfile)
            with silence_stdio():
                atoms = io.read(cfile)
            atoms.info["name"] = sname
            return atoms

        except IndexError:
            raise IOError("ERROR: No .castep files found in {}.".format(
                os.path.abspath(folder)))
        except OSError as e:
            raise IOError("ERROR: {}".format(e))
        except (
                io.formats.UnknownFileTypeError,
                ValueError,
                TypeError,
                Exception,
        ) as e:
            raise IOError(
                "ERROR: Invalid file: {file}, due to error: {error}".format(
                    file=sname + ".castep", error=e))
Пример #2
0
def read_output_castep(folder, avgprop='hyperfine'):

    # Read a castep file in the given folder, and then the required property
    cfile = glob.glob(os.path.join(folder, '*.castep'))[0]
    sname = seedname(cfile)
    a = io.read(cfile)
    a.info['name'] = sname

    # Now read properties depending on what's being measured
    if avgprop == 'hyperfine':
        m = parse_hyperfine_magres(os.path.join(folder, sname + '.magres'))
        a.arrays.update(m.arrays)

    return a
Пример #3
0
    def _read_castep_gamma_phonons(self,
                                   atoms,
                                   folder,
                                   sname=None,
                                   QpointPhononModes=None):
        """Parse CASTEP phonon data into a casteppy object,
        and return eigenvalues and eigenvectors at the gamma point.
        """

        # Parse CASTEP phonon data into casteppy object
        try:
            if sname is not None:
                pd = QpointPhononModes.from_castep(
                    os.path.join(folder, sname + '.phonon'))
            else:
                pd = QpointPhononModes.from_castep(
                    glob.glob(os.path.join(folder, '*.phonon'))[0])
                sname = seedname(
                    glob.glob(os.path.join(folder, '*.phonon'))[0])
            # Convert frequencies back to cm-1
            pd.frequencies_unit = '1/cm'
            # Get phonon frequencies+modes
            evals = np.array(pd.frequencies.magnitude)
            evecs = np.array(pd.eigenvectors)

            # Only grab the gamma point!
            gamma_i = None
            for i, q in enumerate(pd.qpts):
                if np.isclose(q, [0, 0, 0]).all():
                    gamma_i = i
                    break

            if gamma_i is None:
                raise CastepError('Could not find gamma point phonons in'
                                  ' CASTEP phonon file')

            atoms.info['ph_evals'] = evals[gamma_i]
            atoms.info['ph_evecs'] = evecs[gamma_i]

        except IndexError:
            raise IOError("No .phonon files found in {}.".format(
                os.path.abspath(folder)))
        except (OSError, IOError) as e:
            raise IOError("ERROR: {}".format(e))
        except Exception as e:
            raise IOError(
                "ERROR: Could not read {file} due to error: {e}".format(
                    file=sname + '.phonon', e=e))
Пример #4
0
    def next_job(self):
        """Grab the next job from folder_in"""

        cfile_list = glob.glob(os.path.join(self.folder_in, '*.cell'))
        if len(cfile_list) == 0:
            return None

        cfile = cfile_list[0]
        name = seedname(cfile)
        self.log('Starting job {0}\n'.format(name))
        files = [cfile]
        # Check if .param file is available too
        if os.path.isfile(os.path.join(self.folder_in, name + '.param')):
            files += [os.path.join(self.folder_in, name + '.param')]
        job = {
            'name': name,
            'args': {
                'files': files
            }
        }

        return job
Пример #5
0
    def __init__(self, structures=[],
                 info={},
                 cell_reduce=False,
                 progress=False, suppress_ase_warnings=True):
        """
        Initialize the AtomsCollection

        | Args:
        |    structures (list[str] or list[ase.Atoms]): list of file names or
        |                                               Atoms that will form
        |                                               the collection
        |    info (dict): dictionary of general information to attach
        |                 to this collection
        |    cell_reduce (bool): if True, perform a Niggli cell reduction on
        |                        all loaded structures
        |    progress (bool): visualize a progress bar for the loading process
        |    suppress_ase_warnings (bool): suppress annoying ASE warnings when
        |                                  loading files (default is True)
        """

        # Start by parsing out the structures
        self.structures = []

        if isinstance(structures, ase.Atoms):
            # Well, it's just one...
            structures = [structures]
        elif inspect.isgenerator(structures):
            # Let's unravel it
            iter_structs = structures
            structures = []
            for s in iter_structs:
                structures.append(s)

        if progress:
            sys.stdout.write("Loading collection...\n")
        s_n = len(structures)
        for s_i, struct in enumerate(structures):
            if progress:
                # Progress bar
                sys.stdout.write("\rLoading: {0}".format(utils.progbar(s_i+1,
                                                                       s_n)))
            # Is it an Atoms object?
            if type(struct) is ase.Atoms:
                self.structures.append(ase.Atoms(struct))
                # Copy all arrays
                for k in struct.arrays.keys():
                    if not self.structures[-1].has(k):
                        self.structures[-1].new_array(k, struct.get_array(k))
                if struct.calc is not None:
                    # Prevents pointless attempts at re-calculating
                    self.structures[-1].calc._old_atoms = self.structures[-1]
            # Or is it a string?
            elif utils.is_string(struct):
                with utils.silence_stdio(suppress_ase_warnings,
                                         suppress_ase_warnings):
                    self.structures.append(ase_io.read(str(struct)))
                # If there's no name, give it the filename
                if 'name' not in self.structures[-1].info:
                    self.structures[-1].info['name'] = utils.seedname(struct)
            else:
                raise TypeError('Structures must be Atoms objects or valid '
                                'file names,'
                                ' not {0}'.format(type(struct).__name__))
            if cell_reduce:
                # Here we must keep the energy if it was present
                # We do this by hand because ASE has its good reasons
                # for severing the atoms-calculator connection when changing
                # the unit cell.
                try:
                    _E = self.structures[-1].calc.results['energy']
                except (KeyError, AttributeError):
                    _E = None
                niggli_reduce(self.structures[-1])
                if _E is not None:
                    _calc = SinglePointCalculator(self.structures[-1],
                                                  energy=_E)
                    self.structures[-1].set_calculator(_calc)

        if progress:
            sys.stdout.write('\nLoaded {0} structures\n'.format(s_n))

        self._all = _AllCaller(self.structures, ase.Atoms)

        self._arrays = {}

        # Now assign the info
        if type(info) is not dict:
            raise TypeError('Info must be dict,'
                            ' not {0}'.format(type(info).__name__))
        else:
            self.info = info.copy()
Пример #6
0
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
        newcell = ase_io.read(StringIO(stdout), format='castep-cell')
        if clone_calc:
            newcell.set_calculator(copy.deepcopy(calc))
        # Generate it a name, function of its properties
        postfix = hashlib.md5(str(newcell.get_positions()
                                  ).encode()).hexdigest()
        newcell.info['name'] = '{0}_{1}'.format(basename, postfix)
        yield newcell
Пример #7
0
def muon_vibrational_average_write(structure,
                                   method="independent",
                                   mu_index=-1,
                                   mu_symbol="H:mu",
                                   grid_n=20,
                                   sigma_n=3,
                                   avgprop="hyperfine",
                                   calculator="castep",
                                   displace_T=0,
                                   phonon_source_file=None,
                                   phonon_source_type="castep",
                                   **kwargs):
    """
    Write input files to compute a vibrational average for a quantity on a muon
    in a given system.

    | Pars:
    |   structure (str):    Filename for input structure file
    |   method (str):       Method to use for the average. Options are
    |                       'independent', 'montecarlo'.
    |                       Default is 'independent'.
    |   mu_index (int):     Position of the muon in the given cell file.
    |                       Default is -1.
    |   mu_symbol (str):    Use this symbol to look for the muon among
    |                       CASTEP custom species. Overrides muon_index if
    |                       present in cell.
    |   grid_n (int):       Number of configurations used for sampling.
    |                       Applies slightly
    |                       differently to different schemes.
    |   sigma_n (int):      Number of sigmas of the harmonic wavefunction used
    |                       for sampling.
    |   avgprop (str):      Property to calculate and average. Default is
    |                       'hyperfine'.
    |   calculator (str):   Source of the property to calculate and average.
    |                       Can be 'castep' or 'dftb+'. Default is 'castep'.
    |   phonon_source (str):Source of the phonon data. Can be 'castep' or
    |                       'asedftbp'. Default is 'castep'.
    |   **kwargs:           Other arguments (such as specific arguments for
    |                       the given phonon method)
    """

    # Open the structure file
    with silence_stdio():
        cell = io.read(structure)
    path = os.path.split(structure)[0]
    sname = seedname(structure)

    cell.info["name"] = sname

    # Fetch species
    try:
        species = cell.get_array("castep_custom_species")
    except KeyError:
        species = np.array(cell.get_chemical_symbols())

    mu_indices = np.where(species == mu_symbol)[0]
    if len(mu_indices) > 1:
        raise MuonAverageError("More than one muon found in the system")
    elif len(mu_indices) == 1:
        mu_index = mu_indices[0]
    else:
        species = list(species)
        species[mu_index] = mu_symbol
        species = np.array(species)

    cell.set_array("castep_custom_species", species)

    io_formats = {"castep": ReadWriteCastep, "dftb+": ReadWriteDFTB}

    # Load the phonons
    if phonon_source_file is not None:
        phpath, phfile = os.path.split(phonon_source_file)
        phfile = seedname(seedname(phfile))  # have to do twice for dftb case
    else:
        phpath = path
        phfile = sname

    try:
        rw = io_formats[phonon_source_type]()
        atoms = rw.read(phpath, phfile, read_phonons=True)
        ph_evals = atoms.info["ph_evals"]
        ph_evecs = atoms.info["ph_evecs"]
    except IOError:
        raise
        return
    except KeyError:
        phonon_source_file = os.path.join(phpath, phfile + ".phonon")
        if phonon_source_type == "dftb+":
            phonon_source_file = phonon_source_file + "s.pkl"
        raise (IOError(
            "Phonon file {0} could not be read.".format(phonon_source_file)))
        return

    # Fetch masses
    try:
        masses = parse_castep_masses(cell)
    except AttributeError:
        # Just fall back on ASE standard masses if not available
        masses = cell.get_masses()
    masses[mu_index] = constants.m_mu_amu
    cell.set_masses(masses)

    # Now create the distribution scheme
    if method == "independent":
        displsch = IndependentDisplacements(ph_evals, ph_evecs, masses,
                                            mu_index, sigma_n)
    elif method == "montecarlo":
        # Set seed
        np.random.seed(kwargs["random_seed"])
        displsch = MonteCarloDisplacements(ph_evals, ph_evecs, masses)

    displsch.recalc_displacements(n=grid_n, T=displace_T)

    # Make it a collection
    pos = cell.get_positions()
    displaced_cells = []
    for i, d in enumerate(displsch.displacements):
        dcell = cell.copy()
        dcell.set_positions(pos + d)
        if calculator == "dftb" and not kwargs["dftb_pbc"]:
            dcell.set_pbc(False)
        dcell.info["name"] = sname + "_displaced_{0}".format(i)
        displaced_cells.append(dcell)

    if kwargs["write_allconf"]:
        # Write a global configuration structure
        allconf = sum(displaced_cells, cell.copy())
        with silence_stdio():
            if all(allconf.get_pbc()):
                io.write(sname + "_allconf.cell", allconf)
            else:
                io.write(sname + "_allconf.xyz", allconf)

    # Get a calculator
    if calculator == "castep":
        params = {
            "castep_param": kwargs["castep_param"],
            "k_points_grid": kwargs["k_points_grid"],
            "mu_symbol": mu_symbol,
        }
        io_format = ReadWriteCastep(params=params,
                                    calc=cell.calc,
                                    script=kwargs["script_file"])
        opt_args = {"calc_type": "MAGRES"}

    elif calculator == "dftb+":
        params = {
            "dftb_set":
            kwargs["dftb_set"],
            "dftb_pbc":
            kwargs["dftb_pbc"],
            "k_points_grid":
            kwargs["k_points_grid"] if kwargs["dftb_pbc"] else None,
        }
        io_format = ReadWriteDFTB(params=params,
                                  calc=cell.calc,
                                  script=kwargs["script_file"])
        opt_args = {"calc_type": "SPINPOL"}

    displaced_coll = AtomsCollection(displaced_cells)
    displaced_coll.info["displacement_scheme"] = displsch
    displaced_coll.info["muon_index"] = mu_index
    displaced_coll.save_tree(sname + "_displaced",
                             io_format.write,
                             opt_args=opt_args)
Пример #8
0
def muon_vibrational_average_read(structure,
                                  calculator="castep",
                                  avgprop="hyperfine",
                                  average_T=0,
                                  average_file="averages.dat",
                                  **kwargs):
    # Open the structure file
    sname = seedname(structure)

    io_formats = {"castep": ReadWriteCastep, "dftb+": ReadWriteDFTB}

    try:
        displaced_coll = AtomsCollection.load_tree(
            sname + "_displaced",
            io_formats[calculator]().read,
            opt_args={"read_magres": True},
            safety_check=2,
        )
    except Exception:
        raise
        return

    mu_i = displaced_coll.info["muon_index"]
    displsch = displaced_coll.info["displacement_scheme"]

    to_avg = []

    for a in displaced_coll:
        if avgprop == "hyperfine":
            to_avg.append(a.get_array("hyperfine")[mu_i])
        elif avgprop == "charge":
            # Used mostly as test
            try:
                to_avg.append(a.get_charges()[mu_i])
            except RuntimeError:
                raise (IOError("Could not read charges."))

    to_avg = np.array(to_avg)
    displsch.recalc_weights(T=average_T)
    # New shape
    N = len(displaced_coll)
    shape = tuple([slice(N)] + [None] * (len(to_avg.shape) - 1))
    weights = displsch.weights[shape]
    avg = np.sum(weights * to_avg, axis=0)

    # Print output report
    with open(average_file, "w") as f:
        avgname = {
            "hyperfine": "hyperfine tensor",
            "charge": "charge"
        }[avgprop]
        f.write("""
Quantum average of {property} calculated on {cell}.
Scheme details:

{scheme}

Averaged value:

{avg}

All values, by configuration:

{vals}

        """.format(
            property=avgname,
            cell=structure,
            scheme=displsch,
            avg=avg,
            vals="\n".join([
                "Conf: {0} (Weight = {1})\n{2}\n".format(
                    i, displsch.weights[i], v) for i, v in enumerate(to_avg)
            ]),
        ))
Пример #9
0
def muon_vibrational_average_read(cell_file,
                                  calculator='castep',
                                  avgprop='hyperfine',
                                  average_T=0,
                                  average_file='averages.dat',
                                  **kwargs):
    # Open the structure file
    cell = io.read(cell_file)
    path = os.path.split(cell_file)[0]
    sname = seedname(cell_file)
    num_atoms = len(cell)

    reader_function = {
        'castep': read_output_castep,
        'dftb+': read_output_dftbp
    }[calculator]

    displaced_coll = AtomsCollection.load_tree(sname + '_displaced',
                                               reader_function,
                                               safety_check=2,
                                               opt_args={'avgprop': avgprop})
    mu_i = displaced_coll.info['muon_index']
    displsch = displaced_coll.info['displacement_scheme']

    to_avg = []

    for a in displaced_coll:
        if avgprop == 'hyperfine':
            to_avg.append(a.get_array('hyperfine')[mu_i])
        elif avgprop == 'charge':
            # Used mostly as test
            to_avg.append(a.get_charges()[mu_i])

    to_avg = np.array(to_avg)
    displsch.recalc_weights(T=average_T)
    # New shape
    N = len(displaced_coll)
    shape = tuple([slice(N)] + [None] * (len(to_avg.shape) - 1))
    avg = np.sum(displsch.weights[shape] * to_avg, axis=0)

    # Print output report
    with open(average_file, 'w') as f:
        avgname = {
            'hyperfine': 'hyperfine tensor',
            'charge': 'charge'
        }[avgprop]
        f.write("""
Quantum average of {property} calculated on {cell}.
Scheme details:

{scheme}

Averaged value:

{avg}

All values, by configuration:

{vals}

        """.format(property=avgname,
                   cell=cell_file,
                   scheme=displsch,
                   avg=avg,
                   vals='\n'.join([
                       '{0}\n{1}\n'.format(i, v) for i, v in enumerate(to_avg)
                   ])))
Пример #10
0
def muon_vibrational_average_write(cell_file,
                                   method='independent',
                                   mu_index=-1,
                                   mu_symbol='H:mu',
                                   grid_n=20,
                                   sigma_n=3,
                                   avgprop='hyperfine',
                                   calculator='castep',
                                   displace_T=0,
                                   phonon_source_file=None,
                                   phonon_source_type='castep',
                                   **kwargs):
    """
    Write input files to compute a vibrational average for a quantity on a muon
    in a given system.

    | Pars:
    |   cell_file (str):    Filename for input structure file
    |   method (str):       Method to use for the average. Options are 'independent',
    |                       'montecarlo'. Default is 'independent'.
    |   mu_index (int):     Position of the muon in the given cell file.
    |                       Default is -1.
    |   mu_symbol (str):    Use this symbol to look for the muon among
    |                       CASTEP custom species. Overrides muon_index if
    |                       present in cell.
    |   grid_n (int):       Number of configurations used for sampling. Applies slightly
    |                       differently to different schemes.
    |   sigma_n (int):      Number of sigmas of the harmonic wavefunction used
    |                       for sampling.
    |   avgprop (str):      Property to calculate and average. Default is 'hyperfine'.
    |   calculator (str):   Source of the property to calculate and average.
    |                       Can be 'castep' or 'dftb+'. Default is 'castep'.
    |   phonon_source (str):Source of the phonon data. Can be 'castep' or 'asedftbp'.
    |                       Default is 'castep'.
    |   **kwargs:           Other arguments (such as specific arguments for the given
    |                       phonon method)
    """

    # Open the structure file
    cell = io.read(cell_file)
    path = os.path.split(cell_file)[0]
    sname = seedname(cell_file)
    num_atoms = len(cell)

    cell.info['name'] = sname

    # Fetch species
    try:
        species = cell.get_array('castep_custom_species')
    except KeyError:
        species = np.array(cell.get_chemical_symbols())

    mu_indices = np.where(species == mu_symbol)[0]
    if len(mu_indices) > 1:
        raise MuonAverageError('More than one muon found in the system')
    elif len(mu_indices) == 1:
        mu_index = mu_indices[0]
    else:
        species = list(species)
        species[mu_index] = mu_symbol
        species = np.array(species)

    cell.set_array('castep_custom_species', species)

    # Load the phonons
    if phonon_source_type == 'castep':
        if phonon_source_file is not None:
            phpath, phfile = os.path.split(phonon_source_file)
            phfile = seedname(phfile)
        else:
            phpath = path
            phfile = sname
        ph_evals, ph_evecs = read_castep_gamma_phonons(phfile, phpath)
    elif phonon_source_type == 'dftb+':
        if phonon_source_file is None:
            phonon_source_file = os.path.join(path, sname + '.phonons.pkl')

        with open(phonon_source_file, 'rb') as f:
            phdata = pickle.load(f)
            # Find the gamma point
            gamma_i = None
            for i, p in enumerate(phdata.path):
                if (p == 0).all():
                    gamma_i = i
                    break
            try:
                ph_evals = phdata.frequencies[gamma_i]
                ph_evecs = phdata.modes[gamma_i]
            except TypeError:
                raise RuntimeError(('Phonon file {0} does not contain gamma '
                                    'point data').format(phonon_source_file))

    # Fetch masses
    try:
        masses = parse_castep_masses(cell)
    except AttributeError:
        # Just fall back on ASE standard masses if not available
        masses = cell.get_masses()
    masses[mu_index] = constants.m_mu_amu
    cell.set_masses(masses)

    # Now create the distribution scheme
    if method == 'independent':
        displsch = IndependentDisplacements(ph_evals, ph_evecs, masses,
                                            mu_index, sigma_n)
    elif method == 'montecarlo':
        displsch = MonteCarloDisplacements(ph_evals, ph_evecs, masses)

    displsch.recalc_displacements(n=grid_n, T=displace_T)

    # Make it a collection
    pos = cell.get_positions()
    displaced_cells = []
    for i, d in enumerate(displsch.displacements):
        dcell = cell.copy()
        dcell.set_positions(pos + d)
        if calculator == 'dftb' and not kwargs['dftb_pbc']:
            dcell.set_pbc(False)
        dcell.info['name'] = sname + '_displaced_{0}'.format(i)
        displaced_cells.append(dcell)

    if kwargs['write_allconf']:
        # Write a global configuration structure
        allconf = sum(displaced_cells, cell.copy())
        if all(allconf.get_pbc()):
            io.write(sname + '_allconf.cell', allconf)
        else:
            io.write(sname + '_allconf.xyz', allconf)

    # Get a calculator
    if calculator == 'castep':
        writer_function = castep_write_input
        calc = create_hfine_castep_calculator(
            mu_symbol=mu_symbol,
            calc=cell.calc,
            param_file=kwargs['castep_param'],
            kpts=kwargs['k_points_grid'])
    elif calculator == 'dftb+':
        writer_function = dftb_write_input
        kpts = kwargs['k_points_grid'] if kwargs['dftb_pbc'] else None
        calc = create_spinpol_dftbp_calculator(param_set=kwargs['dftb_set'],
                                               kpts=kpts)

    displaced_coll = AtomsCollection(displaced_cells)
    displaced_coll.info['displacement_scheme'] = displsch
    displaced_coll.info['muon_index'] = mu_index
    displaced_coll.save_tree(sname + '_displaced',
                             writer_function,
                             opt_args={
                                 'calc': calc,
                                 'script': kwargs['script_file']
                             })