Exemple #1
0
def show_path(directory, filename):
    """
    Show the final migration for a NEB calculation.

    Returns:

    """
    initial_structure = Cathode.from_file(
        os.path.join(directory, "initial", "final_cathode.json")).as_ordered_structure()
    final_structure = Cathode.from_file(
        os.path.join(directory, "final", "final_cathode.json")).as_ordered_structure()

    image_dirs = [d for d in os.listdir(directory) if "0" in d and os.path.isdir(d)]
    image_dirs.sort()
    image_structures = [Structure.from_file(os.path.join(directory, idir, "CONTCAR"))
                        for idir in image_dirs[1:-1]]

    image_structures.append(final_structure)

    transition_structure = initial_structure.copy()
    for structure in image_structures:
        for site in structure:
            transition_structure.append(site.specie, site.frac_coords)

    transition_structure.to("cif", filename)
Exemple #2
0
def find_configuration_dict(path):
    path = os.path.abspath(path)
    return {
        Cathode.from_file(file).__hash__(): {
            "structure": Cathode.from_file(file).as_dict(),
            "directory": file.replace(path,
                                      "").replace("configuration.json", "")
        }
        for file in find_all("configuration.json", path)
    }
Exemple #3
0
def find_transition_cathodes(directory,
                             initial_contains="init.json",
                             final_contains="final.json"):
    """
    Find the initial and final Cathodes for a transition from the files in a
    directory.

    The function demands .json type files, in order to include the magnetic
    moments, as well as the vacant Sites in the Cathode.

    Args:
        directory (str): Path to the directory in which the initial and final
            cathode structure files should be present.
        initial_contains (str): String that is present in the initial Cathode structure
            file.
        final_contains (str): String that is present in the final Cathode structure
            file.

    Returns:
        tuple: Tuple of the initial and final pybat.core.Cathode's

    """
    directory = os.path.abspath(directory)

    initial_structure_file = None
    final_structure_file = None

    for item in os.listdir(directory):

        if initial_contains in item \
                and os.path.isfile(os.path.join(directory, item)):
            initial_structure_file = os.path.join(directory, item)

        if final_contains in item \
                and os.path.isfile(os.path.join(directory, item)):
            final_structure_file = os.path.join(directory, item)

    if initial_structure_file:
        initial_cathode = Cathode.from_file(initial_structure_file)
    else:
        raise FileNotFoundError("No suitably named initial structure file in "
                                "directory.")

    if final_structure_file:
        final_cathode = Cathode.from_file(final_structure_file)
    else:
        raise FileNotFoundError("No suitably named final structure file in "
                                "directory.")

    return initial_cathode, final_cathode
Exemple #4
0
def find_hash_dict(path):
    path = os.path.abspath(path)
    return {
        Cathode.from_file(file).__hash__():
        file.replace(path, "").replace("cathode.json", "")
        for file in find_all("cathode.json", path)
    }
Exemple #5
0
def static(structure_file, functional, directory, write_chgcar, in_custodian,
           number_nodes, launchpad_file, lpad_name):
    """
    Set up an static calculation workflow.
    """
    from pybat.workflow.workflows import get_wf_static

    # Process the input options
    if number_nodes == 0:
        number_nodes = None
    functional = string_to_functional(functional)

    # Set up the calculation directory
    directory = set_up_directory(directory, functional, "static")

    cat = Cathode.from_file(structure_file)

    if launchpad_file:
        lpad = LaunchPad.from_file(launchpad_file)
    else:
        lpad = _load_launchpad(lpad_name)

    lpad.add_wf(
        get_wf_static(structure=cat,
                      functional=functional,
                      directory=directory,
                      write_chgcar=write_chgcar,
                      in_custodian=in_custodian,
                      number_nodes=number_nodes))
Exemple #6
0
def make_supercell(structure_file, supercell, fmt="json"):
    """
    Make a supercell of the Structure in the structure file.

    #TODO Assumes the new structure must be of the same
    class as the original, i.e. a Cathode is transformed into another cathode.

    Args:
        structure_file:
        supercell:
        fmt:

    Returns:

    """

    # Turn into list TODO add checks
    supercell_list = [int(number) for number in supercell]

    # Load the structure as a Cathode
    cathode = Cathode.from_file(structure_file)
    cathode.make_supercell(supercell_list)

    super_structure_file = structure_file.split(".")[0] + "_" + supercell \
                           + "." + fmt

    cathode.to(fmt, super_structure_file)
Exemple #7
0
def optimize(structure_file, functional, directory, is_metal, in_custodian,
             number_nodes, launchpad_file, lpad_name):
    """
    Set up a geometry optimization workflow.
    """
    from pybat.workflow.workflows import get_wf_optimize

    cat = Cathode.from_file(structure_file)

    # Process the input options
    if number_nodes == 0:
        number_nodes = None
    functional = string_to_functional(functional)

    # Set up the calculation directory
    directory = set_up_directory(directory, functional, "optimize")

    if launchpad_file:
        lpad = LaunchPad.from_file(launchpad_file)
    else:
        lpad = _load_launchpad(lpad_name)

    lpad.add_wf(
        get_wf_optimize(structure=cat,
                        functional=functional,
                        directory=directory,
                        is_metal=is_metal,
                        in_custodian=in_custodian,
                        number_nodes=number_nodes))
Exemple #8
0
def get_wf_neb(directory,
               nimages=7,
               functional=("pbe", {}),
               is_metal=False,
               in_custodian=False,
               number_nodes=None):
    """
    Set up a workflow that calculates the kinetic barrier between two geometries.

    # TODO
    TEMPORARY? Should NEB be integrated in other workflows? If so, should we still
    have a separate NEB workflow?

    Args:
        directory (str): Directory in which the NEB calculation should be performed.
        nimages (int): Number of images to use for the NEB calculation.
        functional (tuple): Tuple with the functional details. The first element
            contains a string that indicates the functional used ("pbe", "hse", ...),
            whereas the second element contains a dictionary that allows the user
            to specify the various functional tags.
        is_metal (bool): Flag that indicates the material being studied is a
            metal, which changes the smearing from Gaussian to second order
            Methfessel-Paxton of 0.2 eV. Defaults to False.
        in_custodian (bool): Flag that indicates that the calculations
            should be run within a Custodian. Defaults to False.
        number_nodes (int): Number of nodes that should be used for the calculations.
            Is required to add the proper `_category` to the Firework generated, so
            it is picked up by the right Fireworker. Defaults to the number of images.

    """
    directory = os.path.abspath(directory)

    # If no number of nodes is specified, take the number of images
    if number_nodes is None:
        number_nodes = nimages

    # Create the Firework that sets up and runs the NEB
    neb_firework = NebFirework(directory=directory,
                               nimages=nimages,
                               functional=functional,
                               is_metal=is_metal,
                               in_custodian=in_custodian,
                               number_nodes=number_nodes)

    # Add number of nodes to spec, or "none"
    firework_spec = {}
    if number_nodes is None:
        firework_spec.update({"_category": "none"})
    else:
        firework_spec.update({"_category": str(number_nodes) + "nodes"})

    cathode = Cathode.from_file(
        os.path.join(directory, "final", "initial_cathode.json"))
    dir_name = os.path.abspath(directory).split("/")[-1]
    workflow_name = str(cathode.composition).replace(" ", "") + " " + dir_name

    return Workflow(fireworks=[
        neb_firework,
    ], name=workflow_name)
Exemple #9
0
def static(structure_file, functional, directory, write_chgcar):
    """
    Set up a static calculation for a structure.
    """
    from pybat.cli.commands.setup import static

    cat = Cathode.from_file(structure_file)

    static(structure=cat,
           functional=string_to_functional(functional),
           directory=directory,
           write_chgcar=write_chgcar)
Exemple #10
0
    def run_task(self, fw_spec):
        directory = os.path.abspath(self["directory"])
        cathode = Cathode.from_file(
            os.path.join(directory, "initial_cathode.json"))
        cathode.update_sites(directory,
                             ignore_magmom=self.get("ignore_magmom", False))

        cathode.to("json", os.path.join(directory, "final_cathode.json"))

        FWAction()

        return FWAction(update_spec={"structure": cathode.as_dict()})
Exemple #11
0
def optimize(structure_file, functional, directory, is_metal):
    """
    Set up a geometry optimization for a structure.
    """
    from pybat.cli.commands.setup import optimize

    cat = Cathode.from_file(structure_file)

    optimize(structure=cat,
             functional=string_to_functional(functional),
             directory=directory,
             is_metal=is_metal)
Exemple #12
0
def primitive_structure(structure_file, fmt="json"):
    """

    Args:
        structure_file:
        fmt:

    Returns:

    """
    cathode = Cathode.from_file(structure_file)
    spg = SpacegroupAnalyzer(cathode)

    prim_structure_file = structure_file.split(".")[0] + "_conv" + "." + fmt
    spg.get_primitive_standard_structure().to(fmt, prim_structure_file)
Exemple #13
0
def migration(structure_file, migration_indices, write_cif):
    """
    Define a migration of an ion in a structure.

    """
    from pybat.cli.commands.define import define_migration

    cat = Cathode.from_file(structure_file)

    if migration_indices == (0, 0):
        raise NotImplementedError(
            "Please provide the migration indices using the '-i' "
            "option.")

    define_migration(structure=cat,
                     site=migration_indices[0],
                     final_site=migration_indices[1],
                     write_cif=write_cif)
Exemple #14
0
def get_cathode(directory,
                to_current_dir=False,
                write_cif=False,
                ignore_magmom=False):
    """
    Construct a .json file of the updated Cathode from a geometry
    optimization, based on the initial_cathode.json file and the output of a
    VASP calculation, i.e. the CONTCAR and OUTCAR files. All these files must
    be present in the directory.

    Args:
        directory (str): Directory in which the geometry optimization
            calculation was performed. Must contain the initial_cathode.json,
            OUTCAR and CONTCAR file.
        to_current_dir (bool): Write the output final_cathode files to the
            current working directory.
        write_cif (bool): Flag that determines whether a .cif file of the
            cathode structure is written to the directory.
        ignore_magmom (bool): Flag that indicates that the final magnetic
            moments of the optimized structure should be ignored. This means
            that the magnetic moments of the initial structure will be used.

    Returns:
        None

    """
    directory = os.path.abspath(directory)
    cathode = Cathode.from_file(os.path.join(directory,
                                             "initial_cathode.json"))
    cathode.update_sites(directory, ignore_magmom=ignore_magmom)

    if to_current_dir:
        filename = os.path.join(os.getcwd(), "final_cathode")
    else:
        filename = os.path.join(directory, "final_cathode")

    cathode.to("json", filename + ".json")

    if write_cif:
        cathode.to("cif", filename + ".cif")
Exemple #15
0
def neb(directory,
        nimages=7,
        functional=("pbe", {}),
        is_metal=False,
        is_migration=False):
    """
    Set up the NEB calculation from the initial and final structures.

    Args:
        directory (str): Directory in which the transition calculations should be
            set up.
        functional (tuple): Tuple with the functional choices. The first element
            contains a string that indicates the functional used ("pbe", "hse", ...),
            whereas the second element contains a dictionary that allows the user
            to specify the various functional tags.
        nimages (int): Number of images to use in the NEB calculation.
        is_metal (bool): Flag that indicates the material being studied is a
            metal, which changes the smearing from Gaussian to second order
            Methfessel-Paxton of 0.2 eV.
        is_migration (bool): Flag that indicates that the transition is a migration
            of an atom in the structure.

    Returns:
        None

    """
    directory = os.path.abspath(directory)

    # Extract the optimized initial and final geometries
    initial_dir = os.path.join(directory, "initial")
    final_dir = os.path.join(directory, "final")

    try:
        # Check to see if the initial final_cathode structure is present
        initial_structure = Cathode.from_file(
            os.path.join(initial_dir,
                         "final_cathode.json")).as_ordered_structure()

    except FileNotFoundError:
        # In case the required json file is not present, check to see if
        # there is VASP output which can be used
        initial_structure = Structure.from_file(
            os.path.join(initial_dir, "CONTCAR"))

        # Add the magnetic configuration to the initial structure
        initial_out = Outcar(os.path.join(initial_dir, "OUTCAR"))
        initial_magmom = [site["tot"] for site in initial_out.magnetization]

        try:
            initial_structure.add_site_property("magmom", initial_magmom)
        except ValueError:
            if len(initial_magmom) == 0:
                print("No magnetic moments found in OUTCAR file. Setting "
                      "magnetic moments to zero.")
                initial_magmom = [0] * len(initial_structure)
                initial_structure.add_site_property("magmom", initial_magmom)
            else:
                raise ValueError("Number of magnetic moments in OUTCAR file "
                                 "do not match the number of sites!")
    except BaseException:
        raise FileNotFoundError("Could not find required structure "
                                "information in " + initial_dir + ".")

    try:
        final_structure = Structure.from_file(
            os.path.join(final_dir, "CONTCAR"))
    except FileNotFoundError:
        final_structure = Cathode.from_file(
            os.path.join(final_dir,
                         "final_cathode.json")).as_ordered_structure()

    # In case the transition is a migration
    if is_migration:
        # Set up the static potential for the Pathfinder from the host charge
        # density
        host_charge_density = Chgcar.from_file(os.path.join(directory, "host"))
        host_potential = ChgcarPotential(host_charge_density)

        migration_site_index = find_migrating_ion(initial_structure,
                                                  final_structure)

        neb_path = NEBPathfinder(start_struct=initial_structure,
                                 end_struct=final_structure,
                                 relax_sites=migration_site_index,
                                 v=host_potential)

        images = neb_path.images
        neb_path.plot_images("neb.vasp")

    # In case an "middle image" has been provided via which to interpolate
    elif os.path.exists(os.path.join(directory, "middle")):
        print("Found a 'middle' directory in the NEB directory. Interpolating "
              "via middle geometry.")
        # Load the middle image
        middle_structure = Structure.from_file(
            os.path.join(directory, "middle", "CONTCAR"))
        # Perform an interpolation via this image
        images_1 = initial_structure.interpolate(
            end_structure=middle_structure,
            nimages=int((nimages + 1) / 2),
            interpolate_lattices=True)
        images_2 = middle_structure.interpolate(end_structure=final_structure,
                                                nimages=int((nimages) / 2 + 1),
                                                interpolate_lattices=True)

        images = images_1[:-1] + images_2

    else:
        # Linearly interpolate the initial and final structures
        images = initial_structure.interpolate(end_structure=final_structure,
                                               nimages=nimages + 1,
                                               interpolate_lattices=True)

    # TODO Add functionality for NEB calculations with changing lattices

    user_incar_settings = {}

    # Set up the functional
    if functional[0] != "pbe":
        functional_config = _load_yaml_config(functional[0] + "Set")
        functional_config["INCAR"].update(functional[1])
        user_incar_settings.update(functional_config["INCAR"])

    # Add the standard Methfessel-Paxton smearing for metals
    if is_metal:
        user_incar_settings.update({"ISMEAR": 2, "SIGMA": 0.2})

    neb_calculation = PybatNEBSet(images,
                                  potcar_functional=DFT_FUNCTIONAL,
                                  user_incar_settings=user_incar_settings)

    # Set up the NEB calculation
    neb_calculation.write_input(directory)

    # Make a file to visualize the transition
    neb_calculation.visualize_transition(
        os.path.join(directory, "transition.cif"))
Exemple #16
0
def relax(structure_file,
          functional=("pbe", {}),
          calculation_dir="",
          is_metal=False):
    """
    Set up a standard geometry optimization calculation of a Cathode
    structure. Optimizes both the atomic positions as well as the unit cell.

    Args:
        structure_file (str): Path to the Cathode structure file, either
            relative or absolute.
        functional (tuple): Tuple with the functional choices. The first element
            contains a string that indicates the functional used ("pbe", "hse", ...),
            whereas the second element contains a dictionary that allows the user
            to specify the various functional tags.
        calculation_dir (str): Path to the directory in which to set up the
            VASP calculation.
        is_metal (bool): Flag that indicates the material being studied is a
            metal, which changes the smearing from Gaussian to second order
            Methfessel-Paxton of 0.2 eV.

    Returns:
        str: Path to the directory in which the calculation is set up.

    """

    # Import the structure as a Cathode instance from the structure file
    structure_file = os.path.abspath(structure_file)
    structure = Cathode.from_file(structure_file).as_ordered_structure()

    # Check if a magnetic moment was not provided for the sites. If not, make
    # sure it is zero for the calculations._
    if "magmom" not in structure.site_properties.keys():
        structure.add_site_property("magmom", [0] * len(structure.sites))

    # Set up the calculation
    user_incar_settings = {}

    # Set up the functional
    if functional[0] != "pbe":
        functional_config = _load_yaml_config(functional[0] + "Set")
        functional_config["INCAR"].update(functional[1])
        user_incar_settings.update(functional_config["INCAR"])

    # Set up the calculation directory
    if calculation_dir == "":
        calculation_dir = os.path.join(os.getcwd(), functional[0])
        if functional[0] == "pbeu":
            calculation_dir += "_" + "".join(
                k + str(functional[1]["LDAUU"][k])
                for k in functional[1]["LDAUU"].keys())
        calculation_dir += "_relax"

    # For metals, add some Methfessel Paxton smearing
    if is_metal:
        user_incar_settings.update({"ISMEAR": 2, "SIGMA": 0.2})

    # Set up the geometry optimization
    geo_optimization = BulkRelaxSet(structure=structure,
                                    user_incar_settings=user_incar_settings,
                                    potcar_functional=DFT_FUNCTIONAL)

    # Write the input files to the geometry optimization directory
    geo_optimization.write_input(calculation_dir)
    shutil.copy(structure_file,
                os.path.join(calculation_dir, "initial_cathode.json"))

    return calculation_dir
Exemple #17
0
def find_all_cathode_hashes(path):
    return [Cathode.from_file(file).__hash__() for file in find_all("cathode.json", path)]
Exemple #18
0
def configuration_workflow(structure_file, substitution_sites=None, element_list=None,
                           sizes=None, concentration_restrictions=None,
                           max_configurations=None, functional=("pbe", {}),
                           directory=None, in_custodian=False, number_nodes=None):
    """
    Set up a workflow for a set of atomic configurations, which includes a geometric
    optimization as well as a SCF calculation based on the final geometry.

    Args:
        structure_file (str): Structure file of the cathode material. Note
            that the structure file should be a json format file that is
            derived from the Cathode class, i.e. it should contain the cation
            configuration of the structure.
        substitution_sites (list): List of site indices or pymatgen.Sites to be
            substituted.
        element_list (list): List of string representations of the cation elements
            which have to be substituted on the substitution sites. Can also
            include "Vac" to introduce vacancy sites.
            E.g. ["Li", "Vac"]; ["Mn", "Co", "Ni"]; ...
        sizes (list): List of unit supercell sizes to be considered for the
            enumeration of the configurations.
            E.g. [1, 2]; range(1, 4); ...
        concentration_restrictions (dict): Dictionary of allowed concentration
            ranges for each element. Note that the concentration is defined
            versus the total amount of atoms in the unit cell.
            E.g. {"Li": (0.2, 0.3)}; {"Ni": (0.1, 0.2, "Mn": (0.05, 0.1)}; ...
        max_configurations (int): Maximum number of new configurations to generate.
            Note that the function detects all the cathode.json files present
            in the directory tree and ignores the corresponding configurations.
            max_configurations is the maximum number of new configurations that need
            to be generated, i.e. on top of the configurations already present in the
            directory tree in the form of cathode.json files.
        functional (tuple): Tuple with the functional choices. The first element
            contains a string that indicates the functional used ("pbe", "hse", ...),
            whereas the second element contains a dictionary that allows the user
            to specify the various functional tags.
        directory (str): Path to the directory in which the configurations and
            calculations should be set up.
        in_custodian (bool): Flag that indicates that the calculations
            should be run within a Custodian. Defaults to False.
        number_nodes (int): Number of nodes that should be used for the calculations.
            Is required to add the proper `_category` to the Firework generated, so
            it is picked up by the right Fireworker.

    Returns:
        None

    """

    # Load the cathode from the structure file
    cathode = Cathode.from_file(structure_file)

    # Check for the required input, and request if necessary
    if not substitution_sites or not element_list or not sizes:
        print(cathode)
        print()
    if not substitution_sites:
        substitution_sites = [int(i) for i in input(
            "Please provide the substitution site indices, separated by a space: "
        ).split(" ")]
    if not element_list:
        element_list = [i for i in input(
            "Please provide the substitution elements, separated by a space: "
        ).split(" ")]
    if not sizes:
        sizes = [int(i) for i in input(
            "Please provide the possible unit cell sizes, separated by a space: "
        ).split(" ")]

    # Set up the directory
    if directory == "":
        directory = os.getcwd()
    directory = os.path.abspath(directory)

    configuration_task = ConfigurationTask(
        structure=cathode,
        directory=directory,
        substitution_sites=list(substitution_sites),
        element_list=element_list,
        sizes=list(sizes),
        concentration_restrictions=concentration_restrictions,
        max_configurations=max_configurations
    )

    energy_task = EnergyConfTask(
        functional=functional,
        in_custodian=in_custodian,
        number_nodes=number_nodes
    )

    # Set up a (sort of) clear name for the workflow
    workflow_name = str(cathode.composition.reduced_formula).replace(" ", "")
    workflow_name += " " + str(element_list)
    workflow_name += " " + str(functional)

    configuration_fw = Firework(tasks=[configuration_task, energy_task],
                                name="Configuration Setup",
                                spec={"_category": "none"})

    # Create the workflow
    workflow = Workflow(
        fireworks=[configuration_fw],
        name=workflow_name
    )

    LAUNCHPAD.add_wf(workflow)
Exemple #19
0
def configuration(structure_file, functional, sub_sites, element_list, sizes,
                  directory, include_existing, conc_restrict, max_conf,
                  in_custodian, number_nodes, launchpad_file, lpad_name):
    """
    Set up a workflow for a set of configurations.

    Calculate the geometry and total energy of a set of configurations. The
    configurations can be specified using the various options. In case some required
    input is not specified during command execution, it will be requested from the user.

    """
    from pybat.workflow.workflows import get_wf_configurations

    cat = Cathode.from_file(structure_file)

    # Set up the directory
    if directory == "":
        directory = os.getcwd()
    directory = os.path.abspath(directory)

    # Process the option input
    try:
        substitution_sites = eval(sub_sites)
    except SyntaxError:
        substitution_sites = [int(site) for site in sub_sites.split(" ")]
    try:
        element_list = eval(element_list)
    except SyntaxError:
        element_list = [el for el in element_list.split(" ")]
    try:
        sizes = [int(i) for i in sizes.strip("[]").split(",")]
    except ValueError:
        sizes = eval(sizes)
    try:
        conc_restrict = eval(conc_restrict)
    except TypeError:
        conc_restrict = None

    # In case some required settings are missing, request them
    if not substitution_sites:
        print(cat)
        print()
        substitution_sites = [
            int(i) for i in input(
                "Please provide the substitution site indices, separated by a space: "
            ).split(" ")
        ]
    if not element_list:
        element_list = [
            i for i in input(
                "Please provide the substitution elements, separated by a space: "
            ).split(" ")
        ]
    if not sizes:
        sizes = [
            int(i) for i in input(
                "Please provide the possible unit cell sizes, separated by a space: "
            ).split(" ")
        ]

    if launchpad_file:
        lpad = LaunchPad.from_file(launchpad_file)
    else:
        lpad = _load_launchpad(lpad_name)

    lpad.add_wf(
        get_wf_configurations(structure=cat,
                              substitution_sites=substitution_sites,
                              element_list=element_list,
                              sizes=sizes,
                              concentration_restrictions=conc_restrict,
                              max_configurations=max_conf,
                              functional=string_to_functional(functional),
                              directory=directory,
                              include_existing=include_existing,
                              in_custodian=in_custodian,
                              number_nodes=number_nodes))
Exemple #20
0
    def run_task(self, fw_spec):

        # If requested, check for existing configurations in the directory tree
        current_conf_dict = find_configuration_dict(self["directory"])

        # Adjust max_configurations based on existing configurations
        if self.get("max_configurations", None):
            if self.get("include_existing", True):
                max_conf_to_generate = self.get("max_configurations")
            else:
                max_conf_to_generate = self.get("max_configurations") \
                                       + len(current_conf_dict)
        else:
            max_conf_to_generate = None

        if not self.get("configuration_list", None):

            structure = self["structure"]

            # In case the input geometry is a Structure, convert it to a Cathode
            if isinstance(structure, Structure):
                structure = Cathode.from_structure(structure)

            configurations = structure.get_cation_configurations(
                substitution_sites=self["substitution_sites"],
                cation_list=self["element_list"],
                sizes=self["sizes"],
                concentration_restrictions=self.get(
                    "configuration_restrictions", None),
                max_configurations=max_conf_to_generate)
        else:
            configurations = self.get("configuration_list", None)

        configuration_dict = {}
        conf_number = 0
        for configuration in configurations:

            conf_hash = configuration.__hash__()

            # If the configuration is not found in the directory tree
            if conf_hash not in current_conf_dict.keys():

                # Make sure the configuration directory number is new
                while "conf_" + str(conf_number) in [
                        e for l in [
                            v["directory"].split("/")
                            for v in current_conf_dict.values()
                        ] for e in l if "conf" in e
                ]:
                    conf_number += 1

                configuration_dir = os.path.join(self["directory"],
                                                 "conf_" + str(conf_number),
                                                 "prim")

                # Create the configuration directory and write the structure to a file
                os.makedirs(configuration_dir)
                configuration.to(
                    "json",
                    os.path.join(configuration_dir, "configuration.json"))
                configuration_dict[str(conf_hash)] = {
                    "structure": configuration.as_dict(),
                    "directory": configuration_dir
                }
                conf_number += 1

            elif self.get("include_existing", True):

                configuration_dict[str(conf_hash)] = {
                    "structure":
                    configuration.as_dict(),
                    "directory":
                    os.path.join(self["directory"],
                                 current_conf_dict[conf_hash]["directory"])
                }

            # Break out of the loop if we have enough configurations
            if len(configuration_dict) == self.get("max_configurations", None):
                break

        return FWAction(update_spec={"configuration_dict": configuration_dict})
Exemple #21
0
def static(structure,
           directory="",
           functional=("pbe", {}),
           write_chgcar=False):
    """
    Set up a standard static calculation using the tetrahedron method to
    obtain accurate total energies.

    Args:
        structure: pymatgen.Structure OR path to structure file for which to set up
            the static calculation.
        directory (str): Path to the directory in which to set up the
            static calculation.
        functional (tuple): Tuple with the functional choices. The first element
            contains a string that indicates the functional used ("pbe", "hse", ...),
            whereas the second element contains a dictionary that allows the user
            to specify the various functional tags.
        write_chgcar (bool): Write out the charge density for Bader charge analysis.

    Returns:
        str: Path to the directory in which the calculation is set up.

    """
    # Set up the calculation directory
    directory = _set_up_directory(directory, functional, "static")
    try:
        os.makedirs(directory)
    except FileExistsError:
        pass

    # In case the structure is given as a string, load it from the specified path
    if isinstance(structure, str):
        structure = Cathode.from_file(structure)

    # If the structure is a cathode object
    if isinstance(structure, Cathode):
        structure.to("json", os.path.join(directory, "initial_cathode.json"))
        structure = structure.as_ordered_structure()

    # Set up the calculation
    user_incar_settings = {}

    # Set up the functional
    user_incar_settings.update(_load_functional(functional))

    # Check if a magnetic moment was provided for the sites. If so, perform a
    # spin-polarized calculation
    if "magmom" in structure.site_properties.keys():
        user_incar_settings.update({"ISPIN": 2, "MAGMOM": True})

    # Set charge density to be written if requested
    if write_chgcar:
        user_incar_settings.update({"LCHARG": True, "LAECHG": True})

        # For the HSE06 calculation, also increase the FFT grids, etc...
        if functional[0] == "hse":
            user_incar_settings.update({"PRECFOCK": "Accurate"})

    # Set up the BulkSCFSet
    static_calculation = BulkSCFSet(structure=structure,
                                    user_incar_settings=user_incar_settings,
                                    potcar_functional=DFT_FUNCTIONAL)

    # Write the input files to the SCF calculation directory
    static_calculation.write_input(directory)

    return directory
Exemple #22
0
def optimize(structure, directory="", functional=("pbe", {}), is_metal=False):
    """
    Set up a standard geometry optimization calculation for a structure. Optimizes
    both the atomic positions as well as the unit cell (ISIF=3).

    Args:
        structure: pymatgen.Structure OR path to structure file for which to set up the
            geometry optimization calculation.
        directory (str): Path to the directory in which to set up the
            geometry optimization.
        functional (tuple): Tuple with the functional choices. The first element
            contains a string that indicates the functional used ("pbe", "hse", ...),
            whereas the second element contains a dictionary that allows the user
            to specify the various functional tags. E.g. ("hse", {"LAEXX": 0.2}).
        is_metal (bool): Flag that indicates the material being studied is a
            metal, which changes the smearing from Gaussian to second order
            Methfessel-Paxton of 0.2 eV.

    Returns:
        str: Path to the directory in which the calculation is set up.

    """
    # Set up the calculation directory
    directory = _set_up_directory(directory, functional, "optimize")
    try:
        os.makedirs(directory)
    except FileExistsError:
        pass

    # In case the structure is given as a string, load it from the specified path
    if isinstance(structure, str):
        structure = Cathode.from_file(structure)

    # If the structure is a cathode object, convert it to a structure for VASP IO
    if isinstance(structure, Cathode):
        structure.to("json", os.path.join(directory, "initial_cathode.json"))
        structure = structure.as_ordered_structure()

    # Set up the calculation
    user_incar_settings = {}

    # Set up the functional
    user_incar_settings.update(_load_functional(functional))

    # Check if a magnetic moment was provided for the sites. If so, perform a
    # spin-polarized calculation
    if "magmom" in structure.site_properties.keys():
        user_incar_settings.update({"ISPIN": 2, "MAGMOM": True})

    # For metals, use Methfessel Paxton smearing
    if is_metal:
        user_incar_settings.update({"ISMEAR": 2, "SIGMA": 0.2})

    # Set up the geometry optimization
    geo_optimization = BulkRelaxSet(structure=structure,
                                    user_incar_settings=user_incar_settings,
                                    potcar_functional=DFT_FUNCTIONAL)

    # Write the input files to the geometry optimization directory
    geo_optimization.write_input(directory)

    return directory
Exemple #23
0
def define_migration(structure_file,
                     migration_indices=(0, 0),
                     write_cif=False):
    """
    This script allows the user to define a migration of a cation in a
    Cathode structure.

    The user has to identify the site that is migrating, as well as provide the
    fractional coordinates to which the site will migrate, or a vacant site
    index.

    Args:
        structure_file (str): Path to the structure file.
        migration_indices (tuple): Tuple of the indices which designate the
        migrating site and the vacant site to which the cation will migrate.
        write_cif (bool): Flag that determines if the initial and final
        structures should also be written in a cif format.

    Returns:
        migration_dir (str): The absolute path to the migration directory.

    """
    cathode = Cathode.from_file(structure_file)
    final_structure = cathode.copy()

    if migration_indices == (0, 0):
        # Prompt the user for the migration site
        print("")
        print(cathode)
        print("")
        migration_site_index = int(
            input("Please provide the index of the "
                  "migrating cation (Note: Not the "
                  "VESTA index!): "))
        print("")

        migration_site = cathode.sites[migration_site_index]
        migration_species = migration_site.species_and_occu

        # Check if the site to which the ion should migrate is actually
        # occupied
        if migration_species == Composition():
            raise ValueError("Chosen site is vacant.")

        # Ask the user for the final coordinates of the migrating ion
        final_coords = input("Please provide the index of the site the cation "
                             "is migrating to, or the final fractional "
                             "coordinates of the migration site: ")
        print("")
        final_coords = [
            float(number) for number in list(final_coords.split(" "))
        ]
    else:
        migration_site_index = migration_indices[0]
        migration_site = cathode.sites[migration_site_index]
        migration_species = migration_site.species_and_occu

        # Check if the site to which the ion should migrate is actually
        # occupied
        if migration_species == Composition():
            raise ValueError("Chosen site is vacant.")
        final_coords = [migration_indices[1]]

    # In case of a site index as input
    if len(final_coords) == 1:

        # Grab the required information about the final site
        final_site_index = int(final_coords[0])
        final_site = cathode.sites[final_site_index]
        final_coords = final_site.frac_coords
        final_species = final_site.species_and_occu

        # Check if site is occupied
        if final_species != Composition():
            raise ValueError("Chosen final site is not vacant.")

        # Change the coordinates of the migration site with the ones of
        # the final site.
        final_structure.replace(i=migration_site_index,
                                species=migration_species,
                                coords=final_coords,
                                properties=migration_site.properties)

        # Do the opposite for the final site
        final_structure.replace(i=final_site_index,
                                species=final_species,
                                coords=migration_site.frac_coords,
                                properties=final_site.properties)

        migration_id = str(migration_site_index) + "_" + str(final_site_index)

    # In case of a set of fractional coordinates as input
    elif len(final_coords) == 3:

        # Replace the site with the site of the new coordinates
        final_structure.replace(
            i=migration_site_index,
            species=migration_species,
            coords=final_coords,
            properties=final_structure.sites[migration_site_index].properties)

        migration_id = str(migration_site_index) + "_a"

        letter_index = 0

        while "migration_" + migration_id in os.listdir(os.getcwd()) and \
                        letter_index < len(ascii_letters):
            letter_index += 1
            migration_id = str(migration_site_index) + "_" + \
                           ascii_letters[letter_index]

    else:
        raise IOError("Provided input is incorrect.")

    # Replace the
    final_structure.remove_sites([migration_site_index])

    # Add the final position of the migrating ion
    final_structure.insert(
        i=migration_site_index,
        species=migration_species,
        coords=final_coords,
        properties=final_structure.sites[migration_site_index].properties)

    # Set up the migration directory
    migration_dir = os.path.join(os.getcwd(), "migration_" + migration_id)
    try:
        os.mkdir(migration_dir)
    except FileExistsError:
        print("WARNING: Migration directory already exists.")

    # Set up the filenames
    initial_structure_file = ".".join(
        structure_file.split("/")[-1].split(".")
        [0:-1]) + "_m_" + migration_id + "_init"
    final_structure_file = ".".join(
        structure_file.split("/")[-1].split(".")
        [0:-1]) + "_m_" + migration_id + "_final"

    # Write out the initial and final structures
    cathode.to("json", migration_dir + "/" + initial_structure_file + ".json")
    final_structure.to("json",
                       migration_dir + "/" + final_structure_file + ".json")

    # Write the structures to a cif format if requested
    if write_cif:
        cathode.to("cif",
                   migration_dir + "/" + initial_structure_file + ".cif")
        final_structure.to("cif",
                           migration_dir + "/" + final_structure_file + ".cif")

    return migration_dir
Exemple #24
0
def print_structure(structure_file):
    print(Cathode.from_file(structure_file))
Exemple #25
0
def scf(structure_file,
        functional=("pbe", {}),
        calculation_dir="",
        write_chgcar=False):
    """
    Set up a standard scf calculation. Always uses the tetrahedron method to
    calculate accurate total energies.

    Args:
        structure_file (str): Path to the Cathode structure file, either
            relative or absolute.
        functional (tuple): Tuple with the functional choices. The first element
            contains a string that indicates the functional used ("pbe", "hse", ...),
            whereas the second element contains a dictionary that allows the user
            to specify the various functional tags.
        calculation_dir (str): Path to the directory in which to set up the
            VASP calculation.
        write_chgcar (bool): Write out the charge

    Returns:
        str: Path to the directory in which the calculation is set up.

    """

    # Import the structure as a Cathode instance from the structure file
    structure_file = os.path.abspath(structure_file)
    structure = Cathode.from_file(structure_file).as_ordered_structure()

    # Check if a magnetic moment was not provided for the sites. If not, make
    # sure it is zero for the calculations.
    if "magmom" not in structure.site_properties.keys():
        structure.add_site_property("magmom", [0] * len(structure.sites))

    # Set up the calculation
    user_incar_settings = {}

    # Set up the functional
    if functional[0] != "pbe":
        functional_config = _load_yaml_config(functional[0] + "Set")
        functional_config["INCAR"].update(functional[1])
        user_incar_settings.update(functional_config["INCAR"])

    # Set up the calculation directory
    if calculation_dir == "":
        calculation_dir = os.path.join(os.getcwd(), functional[0])
        if functional[0] == "pbeu":
            calculation_dir += "_" + "".join(
                k + str(functional[1]["LDAUU"][k])
                for k in functional[1]["LDAUU"].keys())
        calculation_dir += "_scf"

    # Set charge density to be written if requested
    if write_chgcar:
        user_incar_settings.update({"LCHARG": True, "LAECHG": True})

        # For the HSE06 calculation, also increase the FFT grids, etc...
        if functional[0] == "hse":
            user_incar_settings.update({"PRECFOCK": "Accurate"})

    # Set up the BulkSCFSet
    scf_calculation = BulkSCFSet(structure=structure,
                                 user_incar_settings=user_incar_settings,
                                 potcar_functional=DFT_FUNCTIONAL)

    # Write the input files to the SCF calculation directory
    scf_calculation.write_input(calculation_dir)
    shutil.copy(structure_file,
                os.path.join(calculation_dir, "initial_cathode.json"))

    return calculation_dir
Exemple #26
0
def define_dimer(structure_file,
                 dimer_indices=(0, 0),
                 distance=0,
                 remove_cations=False,
                 write_cif=False):
    """
    Define the formation of an oxygen dimer in a Cathode structure.

    The user has to provide the indices of the oxygen pair that will form
    the dimer, as well as the final distance between the oxygen atoms.

    Args:
        structure_file (str): Path to the Cathode structure file.
        dimer_indices (tuple): Indices of the oxygen sites which are to form a
        dimer.
        distance (float): Final distance between the oxygen atoms.
        remove_cations (bool): Flag that allows the user to remove the
        cations (Li, Na, ...) around the chosen oxygen pair.
        write_cif (bool): Flag that indicates that the initial and final
        structure files should also be written in a cif format.

    Returns:
        dimer_dir (str): Absolute path to the dimer directory.

    """

    cathode = Cathode.from_file(structure_file)

    if dimer_indices == (0, 0):
        print("")
        print("No site indices were given for structure:")
        print("")
        print(cathode)
        print("")
        dimer_indices = input("Please provide the two indices of the elements "
                              "that need to form a dimer, separated by a "
                              "space (Note: Not the VESTA indices!): ")

        dimer_indices = tuple(
            [int(number) for number in list(dimer_indices.split(" "))])

    if distance == 0:
        distance = input("Please provide the final distance between the atoms "
                         "in the dimer: ")
        print("")
        distance = float(distance)

    if remove_cations:
        # Remove the cations around the oxygen dimer
        cathode.remove_dimer_cations(dimer_indices)

    # Change the distance between the oxygen atoms for the dimer structure
    dimer_structure = cathode.copy()
    dimer_structure.change_site_distance(dimer_indices, distance)

    # Create the dimer directory
    dimer_dir = os.path.join(
        os.getcwd(), "dimer_" + "_".join([str(el) for el in dimer_indices]))
    try:
        os.mkdir(dimer_dir)
    except FileExistsError:
        warnings.warn("Warning: " + dimer_dir + " already exists, "
                      "overwriting...")

    # Set up the filenames
    initial_structure_file = ".".join(
        structure_file.split("/")[-1].split(".")[0:-1]) + "_d_" \
                             + "_".join([str(el) for el in dimer_indices]) \
                             + "_init"
    dimer_structure_file = ".".join(
        structure_file.split("/")[-1].split(".")[0:-1]) + "_d_" \
                           + "_".join([str(el) for el in dimer_indices]) \
                           + "_final"

    # Write out the initial and final structures
    cathode.to("json", dimer_dir + "/" + initial_structure_file + ".json")
    dimer_structure.to("json",
                       dimer_dir + "/" + dimer_structure_file + ".json")

    # Write the structures to a cif format if requested
    if write_cif:
        cathode.to("cif", dimer_dir + "/" + initial_structure_file + ".cif")
        dimer_structure.to("cif",
                           dimer_dir + "/" + dimer_structure_file + ".cif")

    return dimer_dir
Exemple #27
0
def neb(directory, nimages=7, functional=("pbe", {}), is_metal=False):
    """
    Set up the NEB calculation from the initial and final structures.

    Args:
        directory (str): Directory in which the transition calculations should be
            set up.
        functional (tuple): Tuple with the functional choices. The first element
            contains a string that indicates the functional used ("pbe", "hse", ...),
            whereas the second element contains a dictionary that allows the user
            to specify the various functional tags.
        nimages (int): Number of images to use in the NEB calculation.
        is_metal (bool): Flag that indicates the material being studied is a
            metal, which changes the smearing from Gaussian to second order
            Methfessel-Paxton of 0.2 eV.

    Returns:
        None

    """
    directory = os.path.abspath(directory)

    # Extract the optimized initial and final geometries
    initial_dir = os.path.join(directory, "initial")
    final_dir = os.path.join(directory, "final")

    try:
        # Check to see if the initial final_cathode.json structure is present
        initial_structure = Cathode.from_file(
            os.path.join(initial_dir,
                         "final_cathode.json")).as_ordered_structure()

    except FileNotFoundError:
        # In case the required json file is not present, check to see if
        # there is VASP output which can be used
        initial_structure = Structure.from_file(
            os.path.join(initial_dir, "CONTCAR"))

        # Add the magnetic configuration to the initial structure, if present in the
        # OUTCAR
        initial_out = Outcar(os.path.join(initial_dir, "OUTCAR"))
        initial_magmom = [site["tot"] for site in initial_out.magnetization]

        try:
            initial_structure.add_site_property("magmom", initial_magmom)
        except ValueError:
            if len(initial_magmom) == 0:
                print(
                    "No magnetic moments found in OUTCAR file of initial geometry. "
                    "Setting up non-spin-polarized calculation.")
            else:
                raise ValueError("Number of magnetic moments in OUTCAR file "
                                 "do not match the number of sites!")
    except BaseException:
        raise FileNotFoundError("Could not find required structure "
                                "information in " + initial_dir + ".")

    try:
        final_structure = Structure.from_file(
            os.path.join(final_dir, "CONTCAR"))
    except FileNotFoundError:
        final_structure = Cathode.from_file(
            os.path.join(final_dir,
                         "final_cathode.json")).as_ordered_structure()

    # In case an "middle image" has been provided via which to interpolate
    if os.path.exists(os.path.join(directory, "middle")):
        print("Found a 'middle' directory in the NEB directory. Interpolating "
              "via middle geometry.")
        # Load the middle image
        middle_structure = Structure.from_file(
            os.path.join(directory, "middle", "CONTCAR"))
        # Perform an interpolation via this image
        images_1 = initial_structure.interpolate(
            end_structure=middle_structure,
            nimages=int((nimages + 1) / 2),
            interpolate_lattices=True)
        images_2 = middle_structure.interpolate(end_structure=final_structure,
                                                nimages=int((nimages) / 2 + 1),
                                                interpolate_lattices=True)

        images = images_1[:-1] + images_2

    else:
        try:
            # Linearly interpolate the initial and final structures
            images = initial_structure.interpolate(
                end_structure=final_structure,
                nimages=nimages + 1,
                interpolate_lattices=True)
        except ValueError:
            warnings.warn(
                "Found a ValueError while interpolating the initial and final "
                "neb structures. Attempting to sort the structures and "
                "interpolating again. Make sure to check if the transition is "
                "correct.")
            images = initial_structure.get_sorted_structure().interpolate(
                end_structure=final_structure.get_sorted_structure(),
                nimages=nimages + 1,
                interpolate_lattices=True)

    # TODO Add functionality for NEB calculations with changing lattices

    user_incar_settings = {}

    # Set up the functional
    user_incar_settings.update(_load_functional(functional))

    # Check if a magnetic moment was provided for the sites of the initial structure.
    # If so, perform a spin-polarized calculation
    if "magmom" in initial_structure.site_properties.keys():
        user_incar_settings.update({"ISPIN": 2, "MAGMOM": True})

    # Add the standard Methfessel-Paxton smearing for metals
    if is_metal:
        user_incar_settings.update({"ISMEAR": 2, "SIGMA": 0.2})

    neb_calculation = PybatNEBSet(images,
                                  potcar_functional=DFT_FUNCTIONAL,
                                  user_incar_settings=user_incar_settings)

    # Set up the NEB calculation
    neb_calculation.write_input(directory)

    # Make a file to visualize the transition
    neb_calculation.visualize_transition(
        os.path.join(directory, "transition.cif"))