Ejemplo n.º 1
0
    def _setup_configs(self, rerun):
        """Displaces the seed configuration preparatory to calculating the force
        sets for phonon spectra.

        .. note:: This method *appears* to be VASP-specific. However, the
          configurations that are generated by `phonopy` as VASP `POSCAR` files
          are turned into :class:`~matdb.atoms.Atoms` objects before they are
          passed to the super class that sets up the actual calculations. So, it
          is quite general.

        Args:
            rerun (int): when > 1, recreate the folders even if they
              already exist. If > 0, recreate the jobfile.
        """
        #We also don't want to setup again if we have the results already.
        if self.ready() and rerun == 0:
            return

        if not self.is_setup() or rerun > 1:
            from ase.io import write
            write(path.join(self.phonodir, "POSCAR"), self.atoms, "vasp")
            scell = ' '.join(map(str, self.supercell))
            sargs = ["phonopy", "-d", '--dim="{}"'.format(scell)]
            pres = execute(sargs, self.phonodir, venv=True)

            #Make sure that phonopy produced the supercell. If it didn't, it
            #should have printed an error to *stdout* because it doesn't know
            #about stderr...
            if not path.isfile(path.join(self.phonodir, "SPOSCAR")):
                msg.err('\n'.join(pres["output"]))

            from matdb.atoms import Atoms
            if not self.dfpt:
                #Frozen phonons, create a config execution folder for each of
                #the displacements.
                from glob import glob
                with chdir(self.phonodir):
                    for dposcar in glob("POSCAR-*"):
                        dind = int(dposcar.split('-')[1])
                        datoms = Atoms(dposcar, format="vasp")
                        self.create(datoms)
            else:
                #Pull the perfect supercell and set it up for executing with
                #DFPT parameters.
                with chdir(self.phonodir):
                    datoms = Atoms("SPOSCAR", format="vasp")
                    self.create(datoms)

        # Last of all, create the job file to execute the job array.
        self.jobfile(rerun)
Ejemplo n.º 2
0
    def _setup_configs(self, rerun=False):
        """Sets up the database structure for the enumeration code and creates
        the 'lattice.in' file. Also loops over the enumeration routine
        until the desired number of configurations have been reached
        (this is important for enumerations over small systems where
        the number of systems that are superperiodic is sinificant and
        so the number reported by polya is significantly larger than
        the actual number of unique configs).
        
        Args:
            group (:class:`~matdb.database.basic.Group`): An instance of 
                the group class.

        """
        # We need to construct a lattice.in file then run phenum so that
        # each system gets the correct number of configurations.
        self._build_lattice_file(self.root)
        dind = 0
        # Perform the enumeration, we allow for multiple attempts since the
        # number of configs returned the first time could be to small for
        # enumerations over small systems.
        current = getcwd()
        with chdir(self.root):
            recurse = 0
            while dind < self.nconfigs and recurse < 10:
                dind = self._enumerate(dind, recurse, current)
                recurse += 1

        # Last of all, create the job file to execute the job array.
        self.jobfile(rerun)
        self.save_index()
        self.save_pkl(self.euids, self.euid_file)
Ejemplo n.º 3
0
    def H(self):
        """Returns the Hessian matrix extracted from the frozen phonon calculations.
        """
        if self._H is not None:
            return self._H

        #Otherwise, we need to calculate it from scratch. This depends on
        #whether we are using DFPT or frozen phonons.
        if not self.dfpt:
            self.calc_forcesets()
            dim = ' '.join(map(str, self.supercell))
            xargs = ["phonopy", '--dim="{}"'.format(dim), "--writefc"]
            execute(xargs, self.phonodir, venv=True)

            if not path.isfile(path.join(self.phonodir, "FORCE_CONSTANTS")):
                msg.err("Cannot convert FORCE_SETS to FORCE_CONSTANTS")
                msg.err(''.join(xargs["output"]))
        else:
            self.calc_fc()

        with chdir(self.phonodir):
            result = file_IO.parse_FORCE_CONSTANTS()

        self._H = unroll_fc(result)
        self.save_pkl(self._H, self.H_file)
        return self._H
Ejemplo n.º 4
0
def _mimic_vasp(folder, xroot, prefix="W.1"):
    """Copies a `vasprun.xml` and `OUTCAR ` output files from the given folder into
    the execution directory to mimic what VASP would have done.

    Args:
        folder (str): path to the folder where the model files are stored.
        xroot (str): path to the root folder where the config steps are stored.
    """
    from matdb.utility import chdir
    from glob import glob
    from os import path
    from matdb.utility import symlink

    files = ["vasprun.xml", "OUTCAR"]

    with chdir(folder):
        for vaspfile in files:
            pattern = vaspfile + "__*"
            for dft in glob(pattern):
                name, config = dft.split("__")
                xpath = path.join(xroot, path.join(*config.split("_")), prefix)
                #We want to make some testing assertions to ensure that the
                #stubs ran correctly.
                #Bypass checking the DynMatrix subfolder
                if "DynMatrix" in xpath:
                    continue
                assert path.isfile(path.join(xpath, "CONTCAR"))
                assert path.isfile(path.join(xpath, ".matdb.module"))
                target = path.join(xpath, name)
                symlink(target, path.join(folder, dft))
Ejemplo n.º 5
0
 def write_input(self, atoms):
     """Overload of the ASE input writer.
     """
     if not path.isdir(self.folder):
         mkdir(self.folder)
     with chdir(self.folder):
         super(AsyncQe, self).write_input(atoms)
Ejemplo n.º 6
0
 def _get_dynmatrix(self):
     """Extracts the force constants from `FORCE_SETS` and constructs the
     dynamical matrix for the calculation.
     """
     with chdir(self.folder):
         force_sets = file_IO.parse_FORCE_SETS()
         self.phonopy.set_displacement_dataset(force_sets)
         self.phonopy.produce_force_constants(
             calculate_full_force_constants=True,
             computation_algorithm="svd")
         self.phonopy._set_dynamical_matrix()
Ejemplo n.º 7
0
def test_chdir(tmpdir):
    """Tests the chdir context manager.
    """

    from matdb.utility import chdir
    from os import getcwd, mkdir
    
    target = str(tmpdir.join("chdir"))
    if not path.isdir(target):
        mkdir(target)
    
    with chdir(target):
        assert getcwd() == target

    cur_dir = getcwd()
    try:
        with chdir(str(tmpdir.join('not_chdir'))):
            l = 1
    except:
        l = 1
    assert getcwd() == cur_dir
Ejemplo n.º 8
0
def test_prot_to_cfg():
    """Tests the conversion of a prototype structure to a cfg file.
    """
    from matdb.fitting.mtp import _prot_to_cfg
    from matdb.utility import _get_reporoot, touch, chdir
    from os import path, remove
    import tarfile

    template_root = path.join(_get_reporoot(), "matdb", "templates")
    if not path.isdir(path.join(template_root, "uniqueUnaries")):
        with chdir(template_root):
            tarf = "prototypes.tar.gz"
            tar = tarfile.open(tarf, "r:gz")
            tar.extractall()
            tar.close()
    source = path.join(template_root, "uniqueUnaries", "A10_POSCAR.orig")
    species = ["Al"]
    relax_file = "relax.cfg"
    type_map = {0:0}
    root = "."
    min_atoms = 1
    max_atoms = None

    touch(relax_file)
    _prot_to_cfg(source, species, relax_file, type_map, root, min_atoms, max_atoms)

    assert path.isfile(relax_file)

    lc = 0
    with open(relax_file, "r") as f:
        for line in f:
            lc += 1

    assert lc == 12

    source = path.join(template_root, "uniqueBinaries", "b31_POSCAR.orig")
    species = ["Al", "Cu"]
    relax_file = "relax.cfg"
    type_map = {0:0, 1:1}
    root = "."
    min_atoms = 1
    max_atoms = 4
    _prot_to_cfg(source, species, relax_file, type_map, root, min_atoms, max_atoms)
    
    lc = 0
    with open(relax_file, "r") as f:
        for line in f:
            lc += 1

    assert lc == 12

    remove(relax_file)
Ejemplo n.º 9
0
def run(args):
    """Runs the matdb setup and cleanup to produce database files.
    """
    print("matdb  Copyright (C) 2019  HALL LABS")
    print("This program comes with ABSOLUTELY NO WARRANTY.")
    print(
        "This is free software, and you are welcome to redistribute it under "
        "certain conditions.")
    if args is None:
        return

    targets = {}
    with chdir("seed"):
        for pattern in args["seeds"]:
            #Handle the default file type, which is vasp.
            if ':' in pattern:
                fmt, pat = pattern.split(':')
            else:
                fmt, pat = "vasp", pattern
            for filename in glob(pat):
                targets[filename] = Atoms(filename, format=fmt)

    result = {}
    for filename, at in tqdm(list(targets.items())):
        result[filename] = _get_supers(at, args["sizes"])

    items = [("Filename", 20, "cokay"), ("Supercell", 40, "cstds"),
             ("Req.", 6, "cinfo"), ("Act.", 6, "cgens"), ("rmin", 8, "cerrs"),
             ("pg", 6, "cwarn")]

    msg.blank(2)
    heading = '|'.join([
        "{{0: ^{0}}}".format(size).format(name) for name, size, color in items
    ])
    msg.arb(heading, [msg.cenum[i[2]] for i in items], '|')
    msg.std(''.join('-' for i in range(len(heading) + 1)))
    for filename, hs in result.items():
        for size, hnf in hs.items():
            names = (filename, hnf.hnf.flatten().tolist(), size, hnf.size,
                     hnf.rmin, hnf.pg)
            text = '|'.join([
                "{{0: <{0}}}".format(item[1]).format(name)
                for name, item in zip(names, items)
            ])
            msg.arb(text, [msg.cenum[i[2]] for i in items], '|')
        msg.blank(2)

    return result
Ejemplo n.º 10
0
def read(context, yfile):
    """Reads in the specified YAML file, following any additional file
    directives to compile a full representation of the template hierarchy for
    the root file.

    Args:
        context (str): path to the root folder where the yaml file is located. Needed for relative paths of file links.

        yfile (str): name of the template YAML file *relative* to `context`. Should *not* include the `.yaml` or `.yml` extension.

    """
    with chdir(context):
        if yfile[0] == ":":
            root = path.abspath(yfile[1:])
        else:
            root = path.abspath(yfile)

    if path.isfile(root + ".yml"):
        target = root + ".yml"
    else:
        emsg = ("The specified template file '{}' was not found relative "
                "to the given context directory ('{}'). Note that all files"
                " should use the `.yml` extension, *not* `.yaml`.")
        raise ValueError(emsg.format(yfile, context))

    with open(target, 'r') as stream:
        result = yaml.load(stream, Loader=yaml.FullLoader)

    #Determine the new context for recursive file links within the values of
    #this file.
    ncontext = path.dirname(target)

    #The specification allows for a "local" context that describes folder
    #locations for specific items within the template.
    lcontext = None
    if isinstance(result, dict) and "context" in result:
        lcontext = result["context"]
        del result["context"]

    #The unpacking command will mutate the values in result so that file links
    #are expanded to be full-fledged python objects from their YAML files.
    _unpack_obj(ncontext, result, lcontext)
    return result
Ejemplo n.º 11
0
def build_calc(name, relpath, *args, **kwargs):
    """Builds a calculator instance using sensible defaults for *interatomic potentials*
    that do *not* require a temporary directory to dump files.

    .. note:: The `Vasp`, `Aflow` and `Qe` calculators are not buildable using this
      function.

    .. warning:: The calculator produced by this function *cannot* be hashed or serialized
      as part of a full `matdb`; it is intended for _local_ use only.

    Args:
        name (str): name of the calculator in this package; one of ['Quip'].
        relpath (str): path to the directory in which to instantiate the calculator.

    Notes:
        atoms (matdb.atoms.Atoms): 
            default atoms object for the calculator. An empty `Atoms` object is created. 
            This shouldn't impact calculations since the calculator does not require a folder to run.

        workdir (str): 
            path to a working directory. Calculators that don't need this folder are the only ones 
            supported by this function, so we default to local directory.

        contr_dir (str): 
            path to the `matdb` controller's root directory. That folder is used only when 
            the calculator is hashed or serialized, for local use it doesn't matter so we default to the local directory.

        ran_seed (int): 
            random seed used to initialized the calculator.

    Raises:
        ValueError: if the `name` is not a folder-independent interatomic potential.
    """
    #This import is purposefully here to avoid recursive import loops.
    from matdb.atoms import Atoms
    target = matdb.calculators.get_calc_class(name)
    atoms = Atoms()
    if relpath is not None:
        with chdir(relpath):
            result = target(atoms, '.', '.', 0, *args, **kwargs)
    else:
        result = target(atoms, '.', '.', 0, *args, **kwargs)
    return result
Ejemplo n.º 12
0
    def extract(self, folder, cleanup="default", asis=False):
        """Extracts results from completed calculations and sets them on the
        :class:`ase.Atoms` object.

        Args:
            folder (str): path to the folder in which the executable was run.
            cleanup (str): the level of cleanup to perfor after extraction.
        """
        # If the folder can not be extracted, return False
        if not self.can_extract(folder):
            return False

        # Read output
        out_file = path.join(folder, '{0}.xml'.format(self.out_file))
        output = self._read(out_file)

        if (self.parameters['input_data']['control']['calculation'] == 'relax'
                or self.parameters['input_data']['control']['calculation']
                == 'md'):
            self.atoms.positions = output["atoms"]
            self.atoms.cell = output["cell"]

        # we need to move into the folder being extracted in order to
        # let ase check the convergence
        with chdir(folder):
            lattice = self.atoms.cell
            vol = np.linalg.det(lattice)
            rl = (vol**(1. / 3.)) / 0.529177208
            self.converged = output["convergence"]
            E = np.array(output["etot"])
            F = np.array(output["forces"])
            S = np.array(output["stress"]) * rl**3
            self.atoms.add_property(self.force_name, F)
            self.atoms.add_param(self.stress_name, S)
            self.atoms.add_param(self.virial_name, S * self.atoms.get_volume())
            self.atoms.add_param(self.energy_name, E)

        self.cleanup(folder, clean_level=cleanup)

        # At this time, always return True. Might need to determine if there is a change
        # to return a False.
        return True
Ejemplo n.º 13
0
def _unpack_obj(context, obj, lcontext=None):
    """Unpacks each item of the specified object recursively so that all
    dictionary values are visited and all list items are also visited.
    .. warning:: `obj` will be mutated if any value it considers turns out to be
      a link (according to :func:`is_link`). In that case, the file descriptor
      will be placed by the actual contents of the YAML file that the link
      points to.
    Args:
        context (str): path to the root folder where the yaml file is
          located. Needed for relative paths of file links.
        lcontext (dict): local context for the items in `obj`. Keys are the
          names of keys in `obj`; values are relative folder paths that should
          be used as the context for reads within that item.
    """
    if isinstance(obj, dict):
        result = obj
        for k, o in obj.items():
            ncontext = context
            #If the template specifies a relative context for this item,
            #then switch out the context for all of its children.
            if lcontext is not None and k in lcontext:
                with chdir(context):
                    ncontext = path.abspath(lcontext[k])

            if is_link(o):
                result[k] = read(ncontext, o)
            else:
                result[k] = _unpack_obj(ncontext, o)
    elif isinstance(obj, (list, set, tuple)):
        result = []
        for o in obj:
            if is_link(o):
                result.append(read(context, o))
            else:
                result.append(_unpack_obj(context, o))
    else:
        result = obj

    return result
Ejemplo n.º 14
0
def test_recovery(Pd):
    """Tests the rerun on unfinshed jobs
    """
    from os import path
    from matdb.utility import symlink, chdir
    from glob import glob

    Pd.setup()
    Pd.execute(env_vars={"SLURM_ARRAY_TASK_ID": "1"})

    files = ["vasprun.xml", "OUTCAR"]
    folder = path.join(reporoot, "tests", "data", "Pd", "manual_recover")
    with chdir(folder):
        for vaspfile in files:
            pattern = vaspfile + "__*"
            for dft in glob(pattern):
                name, config = dft.split("__")
                xpath = path.join(Pd.root, path.join(*config.split("_")),
                                  "S1.1")
                target = path.join(xpath, name)
                symlink(target, path.join(folder, dft))

    Pd.extract()

    Pd.recover(True)
    assert path.isfile(
        path.join(Pd.root, "Manual", "phonon.manual", "Pd", "recovery.sh"))
    assert path.isfile(
        path.join(Pd.root, "Manual", "phonon.manual", "Pd", "failures"))

    folder = path.join(reporoot, "tests", "data", "Pd", "manual")
    _mimic_vasp(folder, Pd.root, "S1.1")
    Pd.recover(True)
    assert not path.isfile(
        path.join(Pd.root, "Manual", "phonon.manual", "Pd", "recovery.sh"))
    assert not path.isfile(
        path.join(Pd.root, "Manual", "phonon.manual", "Pd", "failures"))
Ejemplo n.º 15
0
    def BZ_sample(self):
        """Returns a full sampling of the BZ by calculating frequencies at every
        unique k-point as sampled on the :attr:`dosmesh` grid.

        Returns:
            tuple: `(q-points, weights, eigenvalues)` where the `q-points` are
            the unique points after symmetry reduction; `weights` are the
            corresponding weights for each point; `eigenvalues` is a
            :class:`numpy.ndarray` of frequencies (in THz) for each point.
        """
        #This is a little convoluted because of how the phonopy API works. We
        #want to get a full sampling of the BZ, but with symmetry, and then
        #compare the eigenvalues at every point.
        if self._bzsample is None:
            with chdir(self.phonodir):
                atoms = matdb_to_phonopy(self.atoms)
                phonpy = Phonopy(atoms, np.array(self.supercell).reshape(3, 3))
                phonpy.set_force_constants(roll_fc(self.H))
                phonpy._set_dynamical_matrix()

                #Phonopy requires full settings to compute the unique grid and
                #eigenvalues. We spoof the command-line parser.
                parser = get_parser()
                (options, args) = parser.parse_args()
                option_list = parser.option_list
                options.mesh_numbers = ' '.join(map(str, self.dosmesh))
                phonopy_conf = PhonopyConfParser(options=options,
                                                 option_list=option_list)
                settings = phonopy_conf.get_settings()

                #Next, set the mesh on the phonopy object and ask it to reduce and
                #calculate frequencies.
                mesh = settings.get_mesh()
                phonpy.set_mesh(*mesh)
                self._bzsample = phonpy.get_mesh()[0:3]

        return self._bzsample
Ejemplo n.º 16
0
    def _create_dbfull(self, folder, pattern, energy, force, virial, config_type):
        """Creates the full combined database.
        """
        from matdb.utility import chdir, dbcat
        from glob import glob
        from tqdm import tqdm
        from os import path

        #NB! There is a subtle bug here: if you try and open a matdb.atoms.Atoms
        #within the context manager of `chdir`, something messes up with the
        #memory sharing in fortran and it dies. This has to be separate.
        with chdir(folder):
            self.dbfiles = glob(pattern)
        rewrites = []

        for dbfile in self.dbfiles:
            #Look at the first configuration in the atoms list to
            #determine if it matches the energy, force, virial and
            #config type parameter names.
            dbpath = path.join(folder, dbfile)
            params, doforce = _atoms_conform(dbpath, energy, force, virial)
            if len(params) > 0 or doforce:
                msg.std("Conforming database file {}.".format(dbpath))
                al = AtomsList(dbpath)
                outpath = path.join(self.root, dbfile.replace(".xyz",".h5"))
                for ai in tqdm(al):
                    for target, source in params.items():
                        if (target == "config_type" and
                            config_type is not None):
                            ai.params[target] = config_type
                        else:
                            ai.add_param(target,ai.params[source])
                            del ai.params[source]
                            if source in ai.info: #pragma: no cover
                                                  #(if things were
                                                  #dane correctly by
                                                  #the atoms object
                                                  #this should never
                                                  #be used. It exists
                                                  #mainly as a
                                                  #safegaurd.
                                msg.warn("The atoms object didn't properly "
                                         "update the parameters of the legacy "
                                         "atoms object.")
                                del ai.info[source]

                    if doforce:
                        ai.add_property("ref_force",ai.properties[force])
                        del ai.properties[force]

                al.write(outpath)

                #Mark this db as non-conforming so that we created a new
                #version of it.
                rewrites.append(dbfile)

                dbcat([dbpath], outpath, docat=False, renames=params,
                      doforce=doforce)

        # We want a single file to hold all of the data for all the atoms in the database.
        all_atoms = AtomsList()
        for dbfile in self.dbfiles:
            if dbfile in rewrites:
                infile = dbfile.replace(".xyz",".h5")
                all_atoms.extend(AtomsList(path.join(self.root, infile)))
            else:
                dbpath = path.join(folder, dbfile)
                all_atoms.extend(AtomsList(dbpath))

        all_atoms.write(self._dbfull)

        #Finally, create the config file.
        from matdb.utility import dbcat
        with chdir(folder):
            dbcat(self.dbfiles, self._dbfull, config_type=self.config_type, docat=False)
Ejemplo n.º 17
0
    def command(self):
        """Returns the command that is needed to train the GAP
        potentials specified by this object.
        .. note:: This method also configures the directory that the command
          will run in so that it has the relevant files.
        """

        self._set_root()

        if not path.isfile(path.join(self.root, "status.txt")):
            self.iter_status = "train"
            self.iter_count = 1
            self.cell_iter = 0
        else:
            with open(path.join(self.root, "status.txt"), "r") as f:
                for line in f:
                    old_status = line.strip().split()

            if old_status[0] == "done":
                self.iter_status = "train"
                # iter_count is the total iteration number for the whole process, not limited to one cell
                self.iter_count = int(old_status[1]) + 1
                self.cell_iter = int(old_status[2])

                if int(old_status[3]) <= self.next_cell_threshold:
                    self.cell_iter = int(old_status[2]) + 1
                    if len(self.cell_sizes) > self.cell_iter:
                        self.relax_max_atoms = self.cell_sizes[self.cell_iter]
                    else:  #pragma: no cover
                        msg.err(
                            "The MTP process has finished for the cell sizes "
                            "specified in the YML file.")
                    target1 = path.join(self.root, "unrelaxed.cfg")
                    target2 = path.join(self.root, "to-relax.cfg")
                    if path.isfile(target1):
                        remove(target1)
                    if path.isfile(target2):
                        remove(target2)

                if self.iter_count >= self.iter_threshold:  #pragma: no cover
                    msg.err(
                        "The number of iterations has exceeded "
                        "the allowed number of {0}. To increase this limit "
                        "use the iteration_threshold aption in the YML "
                        "file.".format(self.iter_threshold))
            else:
                self.iter_status = old_status[0]
                self.iter_count = int(old_status[1])
                self.cell_iter = int(old_status[2])

        #if we're at the start of a training iteration use the command to train the potential
        if self.iter_status == "train":
            self._make_train_cfg(self.iter_count)

            #remove the selected.cfg and rexaed.cfg from the last iteration if they exist
            if path.isfile(path.join(self.root, "new_training.cfg")):
                remove(path.join(self.root, "new_training.cfg"))
            if path.isfile(path.join(self.root, "relaxed.cfg")):
                remove(path.join(self.root, "relaxed.cfg"))

            if not path.isfile(path.join(self.root, "relax.ini")):
                self._make_relax_ini()
            if not path.isfile(path.join(self.root, "pot.mtp")):
                self._make_pot_initial()
            template = self._train_template()
            with open(path.join(self.root, "status.txt"), "w+") as f:
                f.write("relax_setup {0} {1}".format(self.iter_count,
                                                     self.cell_iter))

        if self.iter_status == "relax_setup":
            # If pot has been trained
            if path.isfile(path.join(self.root, "Trained.mtp_")):
                rename(path.join(self.root, "Trained.mtp_"),
                       path.join(self.root, "pot.mtp"))

            # Calculate the grad of the training configurations.
            calc_grade = self._calc_grade_template()
            execute(calc_grade.split(), self.root)
            if path.isfile(path.join(self.root, "temp1.cfg")):
                remove(path.join(self.root, "temp1.cfg"))
            if not path.isfile(path.join(self.root,
                                         "state.mvs")):  #pragma: no cover
                raise MlpError(
                    "mlp failed to produce the 'state.mvs` file with command "
                    "'mlp calc-grade pot.mtp train.cfg train.cfg temp1.cfg'")

            # if the unrelaxed.cfg file exists we need to move it to
            # replace the existing 'to-relax.cfg' otherwise we need to
            # create the 'to-relax.cfg' file.
            if path.isfile(path.join(self.root,
                                     "unrelaxed.cfg")) and self.use_unrelaxed:
                rename(path.join(self.root, "unrelaxed.cfg"),
                       path.join(self.root, "to-relax.cfg"))
                self.iter_status = "relax"
            elif not path.isfile(path.join(self.root, "to-relax.cfg")):
                self._setup_to_relax_cfg()
                template = "matdb_mtp_to_relax.py > create-to-relax.txt"
                with open(path.join(self.root, "status.txt"), "w+") as f:
                    f.write("relax {0} {1}".format(self.iter_count,
                                                   self.cell_iter))
            else:
                self.iter_status = "relax"

        if self.iter_status == "relax":
            # command to relax structures
            if path.isfile(path.join(self.root, "candidate.cfg")):
                remove(path.join(self.root, "candidate.cfg"))

            template = self._relax_template()
            with open(path.join(self.root, "status.txt"), "w+") as f:
                f.write("select {0} {1}".format(self.iter_count,
                                                self.cell_iter))

        # if relaxation is done
        if self.iter_status == "select":
            cand_files = glob(path.join(self.root, "candidate.cfg_*"))
            cat(cand_files, path.join(self.root, "candidate.cfg"))
            for cfile in cand_files:
                remove(cfile)

            # command to select next training set.
            template = self._select_template()
            with open(path.join(self.root, "status.txt"), "w+") as f:
                f.write("add {0} {1}".format(self.iter_count, self.cell_iter))

        if self.iter_status == "add":
            with chdir(self.root):
                # Now add the selected atoms to the Active database.
                # input filename sould always be entered before output filename
                execute([
                    "mlp", "convert-cfg", "new_training.cfg", "POSCAR",
                    "--output-format=vasp-poscar"
                ], self.root)
                if path.isfile("new_training.cfg"):
                    rename("new_training.cfg",
                           "new_training.cfg_iter_{}".format(self.iter_count))
                if path.isfile("relaxed.cfg"):
                    rename("relaxed.cfg",
                           "relaxed.cfg_iter_{}".format(self.iter_count))

                new_configs = []
                new_POSCARS = glob("POSCAR*")
                # Here We need to re-write the POSCARs so that they
                # have the correct species.
                for POSCAR in new_POSCARS:
                    # We need to put the correct species into the
                    # title of the POSCAR so that ASE can read it
                    pos_file = []
                    with open(POSCAR, 'r') as f:
                        for line in f:
                            pos_file.append(line)
                    pos_file[0] = "{0} : {1}".format(" ".join(self.species),
                                                     pos_file[0])
                    with open(POSCAR, 'w+') as f:
                        for line in pos_file:
                            f.write(line)

                    new_configs.append(Atoms(POSCAR, format="vasp"))
                    remove(POSCAR)

            self.active.add_configs(new_configs, self.iter_count)
            self.active.setup()
            if len(self.active.configs
                   ) != self.active.nconfigs:  #pragma: no cover
                raise LogicError(
                    "The active database failed to setup the calculations "
                    "for iteration {0}".format(self.iter_count))
            self.active.execute()
            with open(path.join(self.root, "status.txt"), "w+") as f:
                f.write("done {0} {1} {2}".format(self.iter_count,
                                                  self.cell_iter,
                                                  len(new_configs)))
            template = ''

        return template
Ejemplo n.º 18
0
def create_to_relax(setup_args):
    """Creates the to-relax.cfg file from the passed in args dictionary.
    
    Args:
        setup_args (dict): A dictionary containing the arguments needed to 
          construct the potential.
    """

    args = setup_args["phenum_args"]
    species = np.unique(setup_args["species"])
    crystals = setup_args["crystals"]
    min_atoms = setup_args["min_atoms"]
    max_atoms = setup_args["max_atoms"]
    root = setup_args["root"]

    touch(args["outfile"])

    for crystal in crystals:
        prot_map = {
            0: "uniqueUnaries",
            1: "uniqueBinaries",
            2: "uniqueTernaries"
        }
        if crystal == "prototypes":

            # The prototypes are saved into the file prototypes.tar.gz, if
            # this is the first time prototypes has been run we need to unpack it.
            template_root = path.join(_get_reporoot(), "matdb", "templates")
            if not path.isdir(path.join(template_root, "uniqueUnaries")):
                with chdir(template_root):
                    tarf = "prototypes.tar.gz"
                    tar = tarfile.open(tarf, "r:gz")
                    tar.extractall()
                    tar.close()

            for size in range(len(species)):
                cand_path = path.join(template_root, prot_map[size])
                structures = glob("{0}/*".format(cand_path))
                perms = [list(i) for i in permutations(species, r=size + 1)]
                for fpath, perm in product(structures, perms):
                    type_map = {}
                    for i, s in enumerate(species):
                        if s in perm:
                            type_map[perm.index(s)] = i
                    _prot_to_cfg(fpath, perm, args["outfile"], type_map, root,
                                 min_atoms, max_atoms)

        else:
            # eventually we'll want to replace this with an actual
            # enumeration over the options the user specifies.
            infile = path.join(
                _get_reporoot(), "matdb", "templates",
                "struct_enum.out_{0}_{1}".format(len(species), crystal))
            expected_min_atoms = 1
            if crystal == "hcp":
                min_atoms = 2
                expected_min_atoms = 2
            if min_atoms != expected_min_atoms or max_atoms is not None:
                with open(infile, "r") as f:
                    min_num = None
                    max_num = None
                    last_num = 1
                    past_start = False
                    for line in f:
                        if line.split()[0] == "start":
                            past_start = True
                            continue
                        if past_start:
                            data = line.strip().split()
                            lab = data[-2]
                            last_num = int(data[0])
                            if len(lab) == min_atoms and min_num is None:
                                min_num = int(data[0])
                            if len(lab) > max_atoms and max_num is None:
                                max_num = int(data[0])
                                break
                # In case in the input file, there is no configuration
                # that has the number of atoms bigger than max_atoms,
                # the max_num will be set to the number in the last
                # line.
                if max_num is None:
                    max_num = last_num
                args["structures"] = range(min_num, max_num)

            args["input"] = infile
            _make_structures(args)
Ejemplo n.º 19
0
def _calc_bands(atoms, hessian, supercell=(1, 1, 1), outfile=None, grid=None):
    """Calculates the band structure for the given Hessian matrix.

    Args:
        atoms (matdb.atoms.Atoms): atoms object corresponding to the *primitive*
          cell. The specified supercell matrix should  result in a number
          of atoms that matches the dimensionality of the Hessian.
        supercell (tuple): tuple of `int` supercell matrix components; can have either
          3 or 9 components.
        hessian (numpy.ndarray): with shape `(natoms*3, natoms*3)`.
        grid (list): list of `int` specifying the number of divisions in k-space
          along each reciprocal unit vector.
        outfile (str): path to the output `band.yaml` file that should be
          created by this function.

    Returns:
        If `outfile` is None, then this method returns a dictionary that has the
        same format as :func:`from_yaml`.
    """
    #Create a temporary directory in which to work.
    target = mkdtemp()
    bandfile = path.join(target, "band.yaml")

    if grid is None:
        grid = [13, 13, 13]
    if isinstance(supercell, np.ndarray):
        supercell = supercell.flatten()

    #First, roll up the Hessian and write it as a FORCE_CONSTANTS file.
    with chdir(target):
        HR = roll(hessian)
        write_FORCE_CONSTANTS(HR)
        atoms.write("POSCAR", format="vasp")

        #We need to create the band.conf file and write the special
        #paths in k-space at which the phonons should be calculated.
        atom_types = _ordered_unique(atoms.get_chemical_symbols())
        settings = [("FORCE_CONSTANTS", "READ"),
                    ("ATOM_NAME", ' '.join(atom_types)),
                    ("DIM", ' '.join(map(str, supercell))),
                    ("MP", ' '.join(map(str, grid)))]

        labels, bands = parsed_kpath(atoms)
        bandfmt = "{0:.3f} {1:.3f} {2:.3f}"
        sband = []
        for Q in bands:
            sband.append(bandfmt.format(*Q))

        settings.append(("BAND", "  ".join(sband)))
        settings.append(("BAND_LABELS", ' '.join(labels)))

        with open("band.conf", 'w') as f:
            for k, v in settings:
                f.write("{} = {}\n".format(k, v))

    sargs = ["phonopy", "band.conf"]
    xres = execute(sargs, target, venv=True)

    if not path.isfile(bandfile):  #pragma: no cover
        msg.err("could not calculate phonon bands; see errors.")
        msg.std(''.join(xres["output"]))

    result = None
    if outfile is not None:
        #Move the band.yaml file to the new target location.
        from shutil import move
        move(bandfile, outfile)
    else:
        result = from_yaml(bandfile)

    #Remove the temporary directory that we created and return the result.
    rmtree(target)
    return result
Ejemplo n.º 20
0
def calc(primitive,
         cachedir=None,
         supercell=(1, 1, 1),
         delta=0.01,
         quick=True):
    """Calculates the Hessian for a given atoms object (which *must* have an
    attached calculator).

    .. note:: We choose to use the Hessian as the fundamental quantity in
      vibrational analysis in `matdb`.

    .. note:: `atoms` will be relaxed before calculating the Hessian.

    Args:
        primitive (matdb.atoms.Atoms): atomic structure of the *primitive*.
        cachedir (str): path to the directory where phonon calculations are
          cached. If not specified, a temporary directory will be used.
        supercell (tuple): number of times to duplicate the cell when
          forming the supercell.
        delta (float): displacement in Angstroms of each atom when computing the
          phonons. 
        quick (bool): when True, use symmetry to speed up the Hessian
          calculation. See :func:`_calc_quick`.

    Returns:
        numpy.ndarray: Hessian matrix that has dimension `(natoms*3, natoms*3)`,
        where `natoms` is the number of atoms in the *supercell*.
    """
    if quick:
        return _calc_quick(primitive, supercell, delta)
    else:
        atoms = primitive.make_supercell(supercell)
        atoms.set_calculator(primitive.get_calculator())

    from ase.vibrations import Vibrations

    #The phonon calculator caches the displacements and force sets for each
    #atomic displacement using pickle. This generates three files for each
    #atomic degree of freedom (one for each cartesian direction). We want to
    #save these in a special directory.
    tempcache = False
    if cachedir is None:
        cachedir = mkdtemp()
        tempcache = True
    else:
        cachedir = path.abspath(path.expanduser(cachedir))
    if not path.isdir(cachedir):
        mkdir(cachedir)

    result = None
    precon = Exp(A=3)
    aphash = None

    #Calculate a hash of the calculator and atoms object that we are calculating
    #for. If the potential doesn't have a `to_dict` method, then we ignore the
    #hashing.
    if not tempcache and hasattr(atoms, "to_dict") and hasattr(
            atoms._calc, "to_dict"):
        atoms_pot = {"atoms": atoms.to_dict(), "pot": atoms._calc.to_dict()}
        #This UUID will probably be different, even if the positions and species
        #are identical.
        del atoms_pot["atoms"]["uuid"]
        hash_str = convert_dict_to_str(atoms_pot)
        aphash = str(sha1(hash_str).hexdigest())

    if not tempcache:
        #Check whether we should clobber the cache or not.
        extras = ["vibsummary.log", "vib.log", "phonons.log"]

        with chdir(cachedir):
            hash_match = False
            if path.isfile("atomspot.hash"):
                with open("atomspot.hash") as f:
                    xhash = f.read()
                hash_match = xhash == aphash

            hascache = False
            if not hash_match:
                for vibfile in glob("vib.*.pckl"):
                    remove(vibfile)
                    hascache = True

                for xfile in extras:
                    if path.isfile(xfile):
                        remove(xfile)
                        hascache = True

            if hascache:
                msg.warn(
                    "Using hard-coded cache directory. We were unable to "
                    "verify that the atoms-potential combination matches "
                    "the one for which existing cache files exist. So, we "
                    "clobbered the existing files to get the science "
                    "right. You can fix this by using `matdb.atoms.Atoms` "
                    "and `matdb.calculators.*Calculator` objects.")

    with chdir(cachedir):
        #Relax the cell before we calculate the Hessian; this gets the forces
        #close to zero before we make harmonic approximation.
        try:
            with open("phonons.log") as f:
                with redirect_stdout(f):
                    minim = PreconLBFGS(atoms, precon=precon, use_armijo=True)
                    minim.run(fmax=1e-5)
        except:
            #The potential is unstable probably. Issue a warning.
            msg.warn(
                "Couldn't optimize the atoms object. Potential may be unstable."
            )

        vib = Vibrations(atoms, delta=delta)
        with open("vib.log", 'a') as f:
            with redirect_stdout(f):
                vib.run()

        vib.summary(log="vibsummary.log")
        result = vib.H

        #Cache the hash of the atoms object and potential that we were using so
        #that we can check next time whether we should clobber the cache or not.
        if aphash is not None and not tempcache:
            with open(path.join(cachedir, "atomspot.hash"), 'w') as f:
                f.write(aphash)

    return result
Ejemplo n.º 21
0
    def _enumerate(self, dind, recurse, home):
        """Performs the enumeration using phenum and creates the files in the
        correct folder for each system enumerated.

        Args:
            dind (int): The number of configs found so far.
            recurse (int): The number of times we've attempted to find
                a unique set of enumerations over the same range.
            home (str): The home directory.
        """
        _enum_out({
            "input":
            "enum.in",
            "outfile":
            "enum.out",
            "seed":
            self.ran_seed if self.ran_seed is None else self.ran_seed + dind +
            recurse,
            "lattice":
            "lattice.in",
            "distribution": ["all", str(self.nconfigs)],
            "super":
            self.keep_supers,
            "sizes":
            None,
            "savedist":
            None,
            "filter":
            None,
            "acceptrate":
            None
        })

        remove("enum.in")
        [remove(f) for f in listdir('.') if f.startswith("polya.")]
        # extract the POSCARS
        euids = _make_structures(
            {
                "structures": None,
                "input": "enum.out",
                "species": self.species,
                "rattle": self.rattle,
                "mink": "t",
                "outfile": "vasp.{}",
                "displace": self.displace,
                "config": "f",
                "remove_zeros": "f"
            },
            return_euids=True)

        # Now we need to create the folder for each system we've enumerated
        if self.euids is None:
            self.euids = []

        for count, dposcar in enumerate(glob("vasp.*")):
            if dind == self.nconfigs:
                break
            elif euids[count].hexdigest() not in self.euids:
                dind += 1
                datoms = Atoms(dposcar, format="vasp")
                with chdir(home):
                    self.create(datoms, cid=dind)

                copyonce(dposcar, path.join(self.configs[dind], "POSCAR_orig"))
                self.index[str(euids[count].hexdigest())] = self.configs[dind]
                self.euids.append(str(euids[count].hexdigest()))
        [remove(f) for f in listdir('.') if f.startswith("vasp.")]

        return dind