コード例 #1
0
 def test_get_stability(self):
     entries = self.rester.get_entries_in_chemsys(["Fe", "O"])
     modified_entries = []
     for entry in entries:
         # Create modified entries with energies that are 0.01eV higher
         # than the corresponding entries.
         if entry.composition.reduced_formula == "Fe2O3":
             modified_entries.append(
                 ComputedEntry(entry.composition,
                               entry.uncorrected_energy + 0.01,
                               parameters=entry.parameters,
                               entry_id="mod_{}".format(entry.entry_id)))
     rest_ehulls = self.rester.get_stability(modified_entries)
     all_entries = entries + modified_entries
     compat = MaterialsProjectCompatibility()
     all_entries = compat.process_entries(all_entries)
     pd = PhaseDiagram(all_entries)
     for e in all_entries:
         if str(e.entry_id).startswith("mod"):
             for d in rest_ehulls:
                 if d["entry_id"] == e.entry_id:
                     data = d
                     break
             self.assertAlmostEqual(pd.get_e_above_hull(e),
                                    data["e_above_hull"])
コード例 #2
0
ファイル: analysis.py プロジェクト: henniggroup/MPInterfaces
def get_competing_phases():
    """
    Collect the species to which the material might decompose to.

    Returns:
        A list of phases as tuples formatted as
        [(formula_1, Materials_Project_ID_1),
        (formula_2, Materials_Project_ID_2), ...]
    """

    composition = Structure.from_file('POSCAR').composition
    try:
        energy = Vasprun('vasprun.xml').final_energy
    except:
        energy = 100  # The function can work without a vasprun.xml
    entries = MPR.get_entries_in_chemsys([elt.symbol for elt in composition])
    my_entry = ComputedEntry(composition, energy)
    entries.append(my_entry)

    #pda = PDAnalyzer(PhaseDiagram(entries))
    pda = PhaseDiagram(entries)
    decomp = pda.get_decomp_and_e_above_hull(my_entry, allow_negative=True)
    competing_phases = [(entry.composition.reduced_formula, entry.entry_id)
                        for entry in decomp[0]]

    return competing_phases
コード例 #3
0
ファイル: analysis.py プロジェクト: henniggroup/MPInterfaces
def get_hull_distance(competing_phase_directory='../competing_phases'):
    """
    Calculate the material's distance to the thermodynamic hull,
    based on species in the Materials Project database.

    Args:
        competing_phase_directory (str): absolute or relative path
            to the location where your competing phases have been
            relaxed. The default expectation is that they are stored
            in a directory named 'competing_phases' at the same level
            as your material's relaxation directory.
    Returns:
        float: distance (eV/atom) between the material and the
            hull.
    """

    finished_competitors = {}
    original_directory = os.getcwd()
    # Determine which competing phases have been relaxed in the current
    # framework and store them in a dictionary ({formula: entry}).
    if os.path.isdir(competing_phase_directory):
        os.chdir(competing_phase_directory)
        for comp_dir in [dir for dir in os.listdir(os.getcwd())
                         if os.path.isdir(dir) and is_converged(dir)]:
            vasprun = Vasprun('{}/vasprun.xml'.format(comp_dir))
            composition = vasprun.final_structure.composition
            energy = vasprun.final_energy
            finished_competitors[comp_dir] = ComputedEntry(composition, energy)
        os.chdir(original_directory)
    else:
        raise ValueError('Competing phase directory does not exist.')

    composition = Structure.from_file('POSCAR').composition
    try:
        energy = Vasprun('vasprun.xml').final_energy
    except:
        raise ValueError('This directory does not have a converged vasprun.xml')
    my_entry = ComputedEntry(composition, energy)  # 2D material
    entries = MPR.get_entries_in_chemsys([elt.symbol for elt in composition])

    # If the energies of competing phases have been calculated in
    # the current framework, put them in the phase diagram instead
    # of the MP energies.
    for i in range(len(entries)):
        formula = entries[i].composition.reduced_formula
        if formula in finished_competitors:
            entries[i] = finished_competitors[formula]
        else:
            entries[i] = ComputedEntry(entries[i].composition, 100)

    entries.append(my_entry)  # 2D material

    #pda = PDAnalyzer(PhaseDiagram(entries))
    pda = PhaseDiagram(entries)
    decomp = pda.get_decomp_and_e_above_hull(my_entry, allow_negative=True)

    return decomp[1]
コード例 #4
0
ファイル: matproj.py プロジェクト: czhengsci/pymatgen
    def get_pourbaix_entries(self, chemsys):
        """
        A helper function to get all entries necessary to generate
        a pourbaix diagram from the rest interface.

        Args:
            chemsys ([str]): A list of elements comprising the chemical
                system, e.g. ['Li', 'Fe']
        """
        from pymatgen.analysis.pourbaix.entry import PourbaixEntry, IonEntry
        from pymatgen.analysis.phase_diagram import PhaseDiagram
        from pymatgen.core.ion import Ion
        from pymatgen.entries.compatibility import\
            MaterialsProjectAqueousCompatibility

        chemsys = list(set(chemsys + ['O', 'H']))
        entries = self.get_entries_in_chemsys(
            chemsys, property_data=['e_above_hull'], compatible_only=False)
        compat = MaterialsProjectAqueousCompatibility("Advanced")
        entries = compat.process_entries(entries)
        solid_pd = PhaseDiagram(entries) # Need this to get ion formation energy
        url = '/pourbaix_diagram/reference_data/' + '-'.join(chemsys)
        ion_data = self._make_request(url)

        pbx_entries = []
        for entry in entries:
            if not set(entry.composition.elements)\
                    <= {Element('H'), Element('O')}:
                pbx_entry = PourbaixEntry(entry)
                pbx_entry.g0_replace(solid_pd.get_form_energy(entry))
                pbx_entry.reduced_entry()
                pbx_entries.append(pbx_entry)

        # position the ion energies relative to most stable reference state
        for n, i_d in enumerate(ion_data):
            ion_entry = IonEntry(Ion.from_formula(i_d['Name']), i_d['Energy'])
            refs = [e for e in entries
                    if e.composition.reduced_formula == i_d['Reference Solid']]
            if not refs:
                raise ValueError("Reference solid not contained in entry list")
            stable_ref = sorted(refs, key=lambda x: x.data['e_above_hull'])[0]
            rf = stable_ref.composition.get_reduced_composition_and_factor()[1]
            solid_diff = solid_pd.get_form_energy(stable_ref)\
                         - i_d['Reference solid energy'] * rf
            elt = i_d['Major_Elements'][0]
            correction_factor = ion_entry.ion.composition[elt]\
                                / stable_ref.composition[elt]
            correction = solid_diff * correction_factor
            pbx_entries.append(PourbaixEntry(ion_entry, correction,
                                             'ion-{}'.format(n)))
        return pbx_entries
コード例 #5
0
ファイル: restapi.py プロジェクト: gmatteo/abipy
    def __init__(self, entries):
        self.entries = entries
        from abipy.core.structure import Structure
        for e in entries:
            e.structure = Structure.as_structure(e.structure)

        self.structures = [e.structure for e in entries]
        self.mpids = [e.entry_id for e in entries]

        # Create phase diagram.
        from pymatgen.analysis.phase_diagram import PhaseDiagram
        self.phasediagram = PhaseDiagram(self.entries)
コード例 #6
0
ファイル: cost.py プロジェクト: blondegeek/pymatgen
    def get_lowest_decomposition(self, composition):
        """
        Get the decomposition leading to lowest cost

        Args:
            composition:
                Composition as a pymatgen.core.structure.Composition
        Returns:
            Decomposition as a dict of {Entry: amount}
        """

        entries_list = []
        elements = [e.symbol for e in composition.elements]
        for i in range(len(elements)):
            for combi in itertools.combinations(elements, i + 1):
                chemsys = [Element(e) for e in combi]
                x = self.costdb.get_entries(chemsys)
                entries_list.extend(x)
        try:
            pd = PhaseDiagram(entries_list)
            return pd.get_decomposition(composition)
        except IndexError:
            raise ValueError("Error during PD building; most likely, "
                             "cost data does not exist!")
コード例 #7
0
class ReactionNetwork:
    """
    This class creates and stores a weighted, directed graph in graph-tool
    that is a dense network of all possible chemical reactions (edges)
    between phase combinations (vertices) in a chemical system. Reaction
    pathway hypotheses are generated using pathfinding methods.
    """
    def __init__(
        self,
        entries,
        n=2,
        temp=300,
        interpolate_comps=None,
        extend_entries=None,
        include_metastable=False,
        include_polymorphs=False,
        filter_rxn_energies=0.5,
    ):
        """Initializes ReactionNetwork object with necessary preprocessing
        steps. This does not yet compute the graph.

        Args:
            entries ([ComputedStructureEntry]): list of ComputedStructureEntry-
                like objects to consider in network. These can be acquired
                from Materials Project (using MPRester) or created manually in
                pymatgen. Entries should have same compatability (e.g.
                MPCompability) for phase diagram generation.
            n (int): maximum number of phases allowed on each side of the
                reaction (default 2). Note that n > 2 leads to significant (
                and often intractable) combinatorial explosion.
            temp (int): Temperature (in Kelvin) used for estimating Gibbs
                free energy of formation, as well as scaling the cost function
                later during network generation. Must select from [300, 400,
                500, ... 2000] K.
            extend_entries ([ComputedStructureEntry]): list of
                ComputedStructureEntry-like objects which will be included in
                the network even after filtering for thermodynamic stability.
                Helpful if target phase has a significantly high energy above
                the hull.
            include_metastable (float or bool): either a) the specified cutoff
                for energy per atom (eV/atom) above hull, or b) True/False
                if considering only stable vs. all entries. An energy cutoff of
                0.1 eV/atom is a reasonable starting threshold for thermodynamic
                stability. Defaults to False.
            include_polymorphs (bool): Whether or not to consider non-ground
                state polymorphs. Defaults to False. Note this is not useful
                unless structural metrics are considered in the cost function
                (to be added!)
            filter_rxn_energies (float): Energy filter. Reactions with
                energy_per_atom > filter will be excluded from network.
        """
        self.logger = logging.getLogger("ReactionNetwork")
        self.logger.setLevel("INFO")

        # Chemical system / phase diagram variables
        self._all_entries = entries
        self._max_num_phases = n
        self._temp = temp
        self._e_above_hull = include_metastable
        self._include_polymorphs = include_polymorphs
        self._elements = {
            elem
            for entry in self.all_entries
            for elem in entry.composition.elements
        }
        self._pd_dict, self._filtered_entries = self._filter_entries(
            entries, include_metastable, temp, include_polymorphs)
        self._entry_mu_ranges = {}
        self._pd = None
        self._rxn_e_filter = filter_rxn_energies

        if (len(self._elements) <= 10
            ):  # phase diagrams take considerable time to build with 10+ elems
            self._pd = PhaseDiagram(self._filtered_entries)

        if interpolate_comps:
            interpolated_entries = []
            for comp in interpolate_comps:
                energy = self._pd.get_hull_energy(Composition(comp))
                interpolated_entries.append(
                    PDEntry(comp, energy, attribute={"interpolated": True}))
            print("Interpolated entries:", "\n")
            print(interpolated_entries)
            self._filtered_entries.extend(interpolated_entries)

        if extend_entries:
            self._filtered_entries.extend(extend_entries)

        for idx, e in enumerate(self._filtered_entries):
            e.entry_idx = idx

        self.num_entries = len(self._filtered_entries)

        self._all_entry_combos = [
            set(combo) for combo in generate_all_combos(
                self._filtered_entries, self._max_num_phases)
        ]

        self.entry_set = EntrySet(self._filtered_entries)
        self.entry_indices = {
            e: idx
            for idx, e in enumerate(self._filtered_entries)
        }

        # Graph variables used during graph creation
        self._precursors = None
        self._all_targets = None
        self._current_target = None
        self._cost_function = None
        self._complex_loopback = None
        self._precursors_entries = None
        self._most_negative_rxn = None  # used in piecewise cost function
        self._g = None  # Graph object in graph-tool

        filtered_entries_str = ", ".join([
            entry.composition.reduced_formula
            for entry in self._filtered_entries
        ])
        self.logger.info(
            f"Initializing network with {len(self._filtered_entries)} "
            f"entries: \n{filtered_entries_str}")

    def generate_rxn_network(
        self,
        precursors=None,
        targets=None,
        cost_function="softplus",
        complex_loopback=True,
    ):
        """
        Generates and stores the reaction network (weighted, directed graph)
        using graph-tool.

        Args:
            precursors ([ComputedEntry]): entries for all phases which serve as the
                main reactants; if None, a "dummy" node is used to represent any
                possible set of precursors.
            targets ([ComputedEntry]): entries for all phases which are the final
                products; if None, a "dummy" node is used to represent any possible
                set of targets.
            cost_function (str): name of cost function to use for entire network
                (e.g. "softplus").
            complex_loopback (bool): if True, adds zero-weight edges which "loop back"
                to allow for multi-step or autocatalytic-like reactions, i.e. original
                precursors can reappear many times and in different steps.
        """
        self._precursors = set(precursors) if precursors else None
        self._all_targets = set(targets) if targets else None
        self._current_target = (
            {targets[0]} if targets else None
        )  # take first entry to be first designated target
        self._cost_function = cost_function
        self._complex_loopback = complex_loopback

        if not self._precursors:
            precursors_entries = RxnEntries(None,
                                            "d")  # use dummy precursors node

            if self._complex_loopback:
                raise ValueError(
                    "Complex loopback can't be enabled when using a dummy precursors "
                    "node!")
        else:
            precursors_entries = RxnEntries(precursors, "s")

        self._precursors_entries = precursors_entries

        g = gt.Graph()  # initialization of graph obj in graph-tool

        g.vp["entries"] = g.new_vertex_property("object")
        g.vp["type"] = g.new_vertex_property(
            "int")  # Type 0: precursors, 1: reactants, 2: products, 3: target
        g.vp["bool"] = g.new_vertex_property("bool")
        g.vp["path"] = g.new_vertex_property(
            "bool")  # whether node is part of path
        g.vp["chemsys"] = g.new_vertex_property("string")

        g.ep["weight"] = g.new_edge_property("double")
        g.ep["rxn"] = g.new_edge_property("object")
        g.ep["bool"] = g.new_edge_property("bool")
        g.ep["path"] = g.new_edge_property(
            "bool")  # whether edge is part of path

        precursors_v = g.add_vertex()
        self._update_vertex_properties(
            g,
            precursors_v,
            {
                "entries": precursors_entries,
                "type": 0,
                "bool": True,
                "path": True,
                "chemsys": precursors_entries.chemsys,
            },
        )

        target_chemsys = set(
            list(self._current_target)[0].composition.chemical_system.split(
                "-"))
        entries_dict = {}
        idx = 1
        for entries in self._all_entry_combos:
            reactants = RxnEntries(entries, "R")
            products = RxnEntries(entries, "P")
            chemsys = reactants.chemsys
            if (self._precursors_entries.description == "D"
                    and not target_chemsys.issubset(chemsys.split("-"))):
                continue
            if chemsys not in entries_dict:
                entries_dict[chemsys] = dict({"R": {}, "P": {}})

            entries_dict[chemsys]["R"][reactants] = idx
            self._update_vertex_properties(
                g,
                idx,
                {
                    "entries": reactants,
                    "type": 1,
                    "bool": True,
                    "path": False,
                    "chemsys": chemsys,
                },
            )

            if (self._precursors_entries.description == "D"
                    and not self._all_targets.issubset(entries)):
                idx = idx + 1
                continue

            entries_dict[chemsys]["P"][products] = idx + 1
            self._update_vertex_properties(
                g,
                idx + 1,
                {
                    "entries": products,
                    "type": 2,
                    "bool": True,
                    "path": False,
                    "chemsys": chemsys,
                },
            )
            idx = idx + 2

        g.add_vertex(idx)  # add ALL precursors, reactant, and product vertices
        target_v = g.add_vertex()  # add target vertex
        target_entries = RxnEntries(self._current_target, "t")
        self._update_vertex_properties(
            g,
            target_v,
            {
                "entries": target_entries,
                "type": 3,
                "bool": True,
                "path": True,
                "chemsys": target_entries.chemsys,
            },
        )

        self.logger.info("Generating reactions by chemical subsystem...")

        all_edges = []

        all_rxn_combos = []
        start_time = time()

        for chemsys, vertices in entries_dict.items():
            precursor_edges = [[precursors_v, v, 0, None, True, False]
                               for entry, v in vertices["R"].items()
                               if self._precursors_entries.description == "D"
                               or entry.entries.issubset(self._precursors)]

            target_edges = [
                edge for edge_list in map(
                    partial(
                        self._get_target_edges,
                        current_target=self._current_target,
                        target_v=int(target_v),
                        precursors=self._precursors,
                        max_num_phases=self._max_num_phases,
                        entries_dict=entries_dict,
                        complex_loopback=complex_loopback,
                    ),
                    vertices["P"].items(),
                ) for edge in edge_list if edge
            ]

            rxn_combos = product(vertices["R"].items(), vertices["P"].items())
            all_rxn_combos.append(rxn_combos)

            all_edges.extend(precursor_edges)
            all_edges.extend(target_edges)

        db = bag.from_sequence(chain.from_iterable(all_rxn_combos),
                               partition_size=100000)
        reaction_edges = db.map_partitions(
            find_rxn_edges,
            cost_function=cost_function,
            rxn_e_filter=self._rxn_e_filter,
            temp=self.temp,
            num_entries=self.num_entries,
        ).compute()

        all_edges.extend(reaction_edges)

        g.add_edge_list(
            all_edges,
            eprops=[g.ep["weight"], g.ep["rxn"], g.ep["bool"], g.ep["path"]])

        end_time = time()
        self.logger.info(
            f"Graph creation took {round(end_time - start_time, 1)} seconds.")
        self.logger.info(
            f"Created graph with {g.num_vertices()} nodes and {g.num_edges()} edges."
        )
        self._g = g

    def find_k_shortest_paths(self, k, verbose=True):
        """
        Finds k shortest paths to current target using Yen's Algorithm.

        Args:
            k (int): desired number of shortest pathways (ranked by cost)
            verbose (bool): whether to print all identified pathways to the console.

        Returns:
            [RxnPathway]: list of RxnPathway objects containing reactions traversed on
                each path.
        """
        g = self._g
        paths = []

        precursors_v = gt.find_vertex(g, g.vp["type"], 0)[0]
        target_v = gt.find_vertex(g, g.vp["type"], 3)[0]

        for num, path in enumerate(self._yens_ksp(g, k, precursors_v,
                                                  target_v)):
            rxns = []
            weights = []

            for step, v in enumerate(path):
                g.vp["path"][v] = True

                if (g.vp["type"][v] == 2
                    ):  # add rxn step if current node in path is a product
                    e = g.edge(path[step - 1], v)
                    g.ep["path"][
                        e] = True  # mark this edge as occurring on a path
                    rxns.append(g.ep["rxn"][e])
                    weights.append(g.ep["weight"][e])

            rxn_pathway = RxnPathway(rxns, weights)
            paths.append(rxn_pathway)

        if verbose:
            for path in paths:
                print(path, "\n")

        return paths

    def find_all_rxn_pathways(
        self,
        k=15,
        precursors=None,
        targets=None,
        max_num_combos=4,
        chempots=None,
        consider_crossover_rxns=10,
        filter_interdependent=True,
    ):
        """
        Builds the k shortest paths to provided targets and then seeks to combine
        them to achieve a "net reaction" with balanced stoichiometry. In other
        words, the full conversion of all intermediates to final products.
        Warning: this method can take a significant amount of time depending on the
        size of the network and the max_num_combos parameter. General
        recommendations are k = 15 and max_num_combos = 4, although a higher
        max_num_combos may be required to capture the full pathway.

        Args:
            k (int): Number of shortest paths to calculate to each target (i.e. if
                there are 3 targets and k=15, then 3x15 = 45 paths will be generated
                and the reactions from these will be combined.
            precursors ([ComputedEntry]): list of all precursor ComputedEntry
                objects; defaults to precursors provided when network was created.
            targets ([ComputedEntry]): list of all target ComputedEntry objects;
                defaults to targets provided when network was created.
            max_num_combos (int): upper limit on how many reactions to consider at a
                time (default 4).
            chempots ({Element: float}): dictionary of chemical potentials, used for
                identifying intermediate reactions open to a specific element
            consider_crossover_rxns (bool): Whether to consider "crossover" reactions
                between intermediates in other pathways. This can be crucial for
                generating realistic predictions and it is highly recommended;
                generally the added computational cost is extremely low.
            filter_interdependent (bool):

        Returns:
            ([CombinedPathway], PathwayAnalysis): Tuple containing list of
                CombinedPathway objects (sorted by total cost) and a PathwayAnalysis
                object with helpful analysis methods for hypothesized pathways.
        """
        paths_to_all_targets = dict()

        if not targets:
            targets = self._all_targets
        else:
            targets = set(targets)

        if not precursors or set(precursors) == self._precursors:
            precursors = self._precursors
        else:
            self.set_precursors(precursors, self._complex_loopback)

        try:
            net_rxn = ComputedReaction(list(precursors),
                                       list(targets),
                                       num_entries=self.num_entries)
        except ReactionError:
            raise ReactionError(
                "Net reaction must be balanceable to find all reaction pathways."
            )

        self.logger.info(f"NET RXN: {net_rxn} \n")

        for target in targets:
            print(f"PATHS to {target.composition.reduced_formula} \n")
            print("--------------------------------------- \n")
            self.set_target(target)
            paths = self.find_k_shortest_paths(k)
            paths = {
                rxn: cost
                for path in paths
                for (rxn, cost) in zip(path.rxns, path.costs)
            }
            paths_to_all_targets.update(paths)

        print("Finding crossover reactions paths...")

        if consider_crossover_rxns:
            intermediates = {
                entry
                for rxn in paths_to_all_targets for entry in rxn.all_entries
            } - targets
            intermediate_rxns = self.find_intermediate_rxns(
                intermediates, targets, chempots)
            paths = {
                rxn: get_rxn_cost(rxn, self._cost_function, self.temp)
                for rxn in intermediate_rxns
            }
            paths_to_all_targets.update(paths)
            paths_to_all_targets.update(
                self.find_crossover_rxns(intermediates, targets))

        paths_to_all_targets.pop(net_rxn, None)

        paths_to_all_targets = {
            k: v
            for k, v in paths_to_all_targets.items()
            if not (self._precursors.intersection(k._product_entries)
                    or self._all_targets.intersection(k._reactant_entries)
                    or len(k._product_entries) > 3)
        }

        rxn_list = [r for r in paths_to_all_targets.keys()]
        pprint(rxn_list)
        normalized_rxns = [
            Reaction.from_string(r.normalized_repr) for r in rxn_list
        ]

        num_rxns = len(rxn_list)

        self.logger.info(f"Considering {num_rxns} reactions...")
        batch_size = 500000
        total_paths = []
        for n in range(1, max_num_combos + 1):
            if n >= 4:
                self.logger.info(
                    f"Generating and filtering size {n} pathways...")
            all_c_mats, all_m_mats = [], []
            for combos in tqdm(
                    grouper(combinations(range(num_rxns), n), batch_size),
                    total=int(comb(num_rxns, n) / batch_size),
            ):
                comp_matrices = np.stack([
                    np.vstack([rxn_list[r].vector for r in combo])
                    for combo in combos if combo
                ])
                c_mats, m_mats = self._balance_path_arrays(
                    comp_matrices, net_rxn.vector)
                all_c_mats.extend(c_mats)
                all_m_mats.extend(m_mats)

            for c_mat, m_mat in zip(all_c_mats, all_m_mats):
                rxn_dict = {}
                for rxn_mat in c_mat:
                    reactant_entries = [
                        self._filtered_entries[i] for i in range(len(rxn_mat))
                        if rxn_mat[i] < 0
                    ]
                    product_entries = [
                        self._filtered_entries[i] for i in range(len(rxn_mat))
                        if rxn_mat[i] > 0
                    ]
                    rxn = ComputedReaction(reactant_entries,
                                           product_entries,
                                           entries=self.num_entries)
                    cost = paths_to_all_targets[rxn_list[normalized_rxns.index(
                        Reaction.from_string(rxn.normalized_repr))]]
                    rxn_dict[rxn] = cost
                p = BalancedPathway(rxn_dict, net_rxn, balance=False)
                p.set_multiplicities(m_mat.flatten())
                total_paths.append(p)

        if filter_interdependent:
            final_paths = set()
            for p in total_paths:
                interdependent, combined_rxn = find_interdependent_rxns(
                    p, [c.composition for c in precursors])
                if interdependent:
                    continue
                final_paths.add(p)
        else:
            final_paths = total_paths

        return sorted(list(final_paths), key=lambda x: x.total_cost)

    def find_crossover_rxns(self, intermediates, targets):
        """
        Identifies possible "crossover" reactions (i.e., reactions where the
            predicted intermediate phases result in one or more targets phases.

        Args:
            intermediates ([ComputedEntry]): List of intermediate entries
            targets ([ComputedEntry]): List of target entries

        Returns:
            [ComputedReaction]: List of crossover reactions
        """
        all_crossover_rxns = dict()
        for reactants_combo in generate_all_combos(intermediates,
                                                   self._max_num_phases):
            for products_combo in generate_all_combos(targets,
                                                      self._max_num_phases):
                try:
                    rxn = ComputedReaction(
                        list(reactants_combo),
                        list(products_combo),
                        num_entries=self.num_entries,
                    )
                except ReactionError:
                    continue
                if rxn._lowest_num_errors > 0:
                    continue
                path = {
                    rxn: get_rxn_cost(rxn, self._cost_function, self._temp)
                }
                all_crossover_rxns.update(path)
        return all_crossover_rxns

    def find_intermediate_rxns(self, intermediates, targets, chempots=None):
        """
        Identifies thermodynamically predicted reactions from intermediate to one or
        more targets using interfacial reaction method. This method has the unique benefit (
        compared to the find_crossover_rxns method) of identifying reactions open to a
        specfic element or producing 3 or more products.

        Args:
            intermediates ([ComputedEntry]): List of intermediate entries
            targets ([ComputedEntry]): List of target entries
            chempots ({Element: float}): Dictionary of chemical potentials used to
                create grand potential phase diagram by which interfacial reactions are
                predicted.

        Returns:
            [ComputedReaction]: List of intermediate reactions
        """
        all_rxns = set()
        combos = list(generate_all_combos(intermediates, 2))
        for entries in tqdm(combos):
            n = len(entries)
            r1 = entries[0].composition.reduced_composition
            chemsys = {
                str(el)
                for entry in entries for el in entry.composition.elements
            }
            elem = None
            if chempots:
                elem = str(list(chempots.keys())[0])
                chemsys.update(elem)
                if chemsys == {elem}:
                    continue

            if n == 1:
                r2 = entries[0].composition.reduced_composition
            elif n == 2:
                r2 = entries[1].composition.reduced_composition
            else:
                raise ValueError(
                    "Can't have an interface that is not 1 to 2 entries!")

            if chempots:
                elem_comp = Composition(elem).reduced_composition
                if r1 == elem_comp or r2 == elem_comp:
                    continue

            entry_subset = self.entry_set.get_subset_in_chemsys(list(chemsys))
            pd = PhaseDiagram(entry_subset)
            grand_pd = None
            if chempots:
                grand_pd = GrandPotentialPhaseDiagram(entry_subset, chempots)

            rxns = react_interface(r1, r2, pd, self.num_entries, grand_pd)
            rxns_filtered = {
                r
                for r in rxns if set(r._product_entries) & targets
            }
            if rxns_filtered:
                most_favorable_rxn = min(
                    rxns_filtered,
                    key=lambda x: (x.calculated_reaction_energy / sum(
                        [x.get_el_amount(elem) for elem in x.elements])),
                )
                all_rxns.add(most_favorable_rxn)

        return all_rxns

    def set_precursors(self, precursors=None, complex_loopback=True):
        """
        Replaces network's previous precursor node with provided new precursors.
        Finds new edges that link products back to reactants as dependent on the
        complex_loopback parameter.

        Args:
            precursors ([ComputedEntry]): list of new precursor entries
            complex_loopback (bool): if True, adds zero-weight edges which "loop back"
                to allow for multi-step or autocatalytic-like reactions, i.e. original
                precursors can reappear many times and in different steps.

        Returns:
            None
        """
        g = self._g
        self._precursors = set(precursors) if precursors else None

        if not self._precursors:
            precursors_entries = RxnEntries(None,
                                            "d")  # use dummy precursors node
            if complex_loopback:
                raise ValueError(
                    "Complex loopback can't be enabled when using a dummy precursors "
                    "node!")
        else:
            precursors_entries = RxnEntries(precursors, "s")

        g.remove_vertex(gt.find_vertex(g, g.vp["type"], 0))
        new_precursors_v = g.add_vertex()

        self._update_vertex_properties(
            g,
            new_precursors_v,
            {
                "entries": precursors_entries,
                "type": 0,
                "bool": True,
                "path": True,
                "chemsys": precursors_entries.chemsys,
            },
        )

        new_edges = []
        remove_edges = []

        for v in gt.find_vertex(g, g.vp["type"],
                                1):  # iterate over all reactants
            phases = g.vp["entries"][v].entries

            remove_edges.extend(list(v.in_edges()))

            if precursors_entries.description == "D" or phases.issubset(
                    self._precursors):
                new_edges.append([new_precursors_v, v, 0, None, True, False])

        for v in gt.find_vertex(g, g.vp["type"],
                                2):  # iterate over all products
            phases = g.vp["entries"][v].entries

            if complex_loopback:
                combos = generate_all_combos(phases.union(self._precursors),
                                             self._max_num_phases)
            else:
                combos = generate_all_combos(phases, self._max_num_phases)

            for c in combos:
                combo_phases = set(c)
                if complex_loopback and combo_phases.issubset(
                        self._precursors):
                    continue
                combo_entry = RxnEntries(combo_phases, "R")
                loopback_v = gt.find_vertex(g, g.vp["entries"], combo_entry)[0]
                new_edges.append([v, loopback_v, 0, None, True, False])

        for e in remove_edges:
            g.remove_edge(e)

        g.add_edge_list(
            new_edges,
            eprops=[g.ep["weight"], g.ep["rxn"], g.ep["bool"], g.ep["path"]])

    def set_target(self, target):
        """
        Replaces network's current target phase with new target phase.

        Args:
            target (ComputedEntry): ComputedEntry-like object for new target phase.

        Returns:
            None
        """
        g = self._g

        if target in self._current_target:
            return
        else:
            self._current_target = {target}

        g.remove_vertex(gt.find_vertex(g, g.vp["type"], 3))
        new_target_entry = RxnEntries(self._current_target, "t")
        new_target_v = g.add_vertex()
        self._update_vertex_properties(
            g,
            new_target_v,
            {
                "entries": new_target_entry,
                "type": 3,
                "bool": True,
                "path": True,
                "chemsys": new_target_entry.chemsys,
            },
        )

        new_edges = []

        for v in gt.find_vertex(g, g.vp["type"], 2):  # search for all products
            if self._current_target.issubset(g.vp["entries"][v].entries):
                new_edges.append([v, new_target_v, 0, None, True,
                                  False])  # link all products to new target

        g.add_edge_list(
            new_edges,
            eprops=[g.ep["weight"], g.ep["rxn"], g.ep["bool"], g.ep["path"]])

    def set_cost_function(self, cost_function):
        """
        Replaces network's current cost function with new function by recomputing
        edge weights.

        Args:
            cost_function (str): name of cost function. Current options are
                ["softplus", "relu", "piecewise"].

        Returns:
            None
        """
        g = self._g
        self._cost_function = cost_function

        for e in gt.find_edge_range(g, g.ep["weight"], (1e-8, 1e8)):
            g.ep["weight"][e] = get_rxn_cost(g.ep["rxn"][e], )

    @staticmethod
    def _get_target_edges(
        vertex,
        current_target,
        target_v,
        precursors,
        max_num_phases,
        entries_dict,
        complex_loopback,
    ):
        entry = vertex[0]
        v = vertex[1]

        edge_list = []
        phases = entry.entries
        if current_target.issubset(phases):
            edge_list.append([v, target_v, 0, None, True, False])

        if complex_loopback:
            combos = generate_all_combos(phases.union(precursors),
                                         max_num_phases)
        else:
            combos = generate_all_combos(phases, max_num_phases)

        if complex_loopback:
            for c in combos:
                combo_phases = set(c)
                if combo_phases.issubset(precursors):
                    continue
                combo_entry = RxnEntries(combo_phases, "R")
                loopback_v = entries_dict[
                    combo_entry.chemsys]["R"][combo_entry]

                edge_list.append([v, loopback_v, 0, None, True, False])

        return edge_list

    @staticmethod
    def _update_vertex_properties(g, v, prop_dict):
        """
        Helper method for updating several vertex properties at once in a graph-tool
        graph.

        Args:
            g (gt.Graph): a graph-tool Graph object.
            v (gt.Vertex or int): a graph-tool Vertex object (or its index) for a vertex
                in the provided graph.
            prop_dict (dict): a dictionary of the form {"prop": val}, where prop is the
                name of a VertexPropertyMap of the graph and val is the new updated
                value for that vertex's property.

        Returns:
            None
        """
        for prop, val in prop_dict.items():
            g.vp[prop][v] = val
        return None

    @staticmethod
    def _yens_ksp(g,
                  num_k,
                  precursors_v,
                  target_v,
                  edge_prop="bool",
                  weight_prop="weight"):
        """
        Yen's Algorithm for k-shortest paths. Inspired by igraph implementation by
        Antonin Lenfant. Ref: Jin Y. Yen, "Finding the K Shortest Loopless Paths
        in a Network", Management Science, Vol. 17, No. 11, Theory Series (Jul.,
        1971), pp. 712-716.

        Args:
            g (gt.Graph): the graph-tool graph object.
            num_k (int): number of k shortest paths that should be found.
            precursors_v (gt.Vertex): graph-tool vertex object containing precursors.
            target_v (gt.Vertex): graph-tool vertex object containing target.
            edge_prop (str): name of edge property map which allows for filtering edges.
                Defaults to the word "bool".
            weight_prop (str): name of edge property map that stores edge weights/costs.
                Defaults to the word "weight".

        Returns:
            List of lists of graph vertices corresponding to each shortest path
                (sorted in increasing order by cost).
        """
        def path_cost(vertices):
            """Calculates path cost given a list of vertices."""
            cost = 0
            for j in range(len(vertices) - 1):
                cost += g.ep[weight_prop][g.edge(vertices[j], vertices[j + 1])]
            return cost

        path = gt.shortest_path(g,
                                precursors_v,
                                target_v,
                                weights=g.ep[weight_prop])[0]

        if not path:
            return []
        a = [path]
        a_costs = [path_cost(path)]

        b = queue.PriorityQueue(
        )  # automatically sorts by path cost (priority)

        for k in range(1, num_k):
            try:
                prev_path = a[k - 1]
            except IndexError:
                print(f"Identified only k={k} paths before exiting. \n")
                break

            for i in range(len(prev_path) - 1):
                spur_v = prev_path[i]
                root_path = prev_path[:i]

                filtered_edges = []

                for path in a:
                    if len(path) - 1 > i and root_path == path[:i]:
                        e = g.edge(path[i], path[i + 1])
                        if not e:
                            continue
                        g.ep[edge_prop][e] = False
                        filtered_edges.append(e)

                gv = gt.GraphView(g, efilt=g.ep[edge_prop])
                spur_path = gt.shortest_path(gv,
                                             spur_v,
                                             target_v,
                                             weights=g.ep[weight_prop])[0]

                for e in filtered_edges:
                    g.ep[edge_prop][e] = True

                if spur_path:
                    total_path = root_path + spur_path
                    total_path_cost = path_cost(total_path)
                    b.put((total_path_cost, total_path))

            while True:
                try:
                    cost_, path_ = b.get(block=False)
                except queue.Empty:
                    break
                if path_ not in a:
                    a.append(path_)
                    a_costs.append(cost_)
                    break

        return a

    @staticmethod
    @njit(parallel=True)
    def _balance_path_arrays(
        comp_matrices,
        net_coeffs,
        tol=1e-6,
    ):
        """
        Fast solution for reaction multiplicities via mass balance stochiometric
        constraints. Parallelized using Numba.

        Args:
            comp_matrices ([np.array]): list of numpy arrays containing stoichiometric
                coefficients of all compositions in all reactions, for each trial
                combination.
            net_coeffs ([np.array]): list of numpy arrays containing stoichiometric
                coefficients of net reaction.
            tol (float): numerical tolerance for determining if a multiplicity is zero
                (reaction was removed).

        Returns:
            ([bool],[np.array]): Tuple containing bool identifying which trial
                BalancedPathway objects were successfully balanced, and a list of all
                multiplicities arrays.
        """
        shape = comp_matrices.shape
        net_coeff_filter = np.argwhere(net_coeffs != 0).flatten()
        len_net_coeff_filter = len(net_coeff_filter)
        all_multiplicities = np.zeros((shape[0], shape[1]), np.float64)
        indices = np.full(shape[0], False)

        for i in prange(shape[0]):
            correct = True
            for j in range(len_net_coeff_filter):
                idx = net_coeff_filter[j]
                if not comp_matrices[i][:, idx].any():
                    correct = False
                    break
            if not correct:
                continue

            comp_pinv = np.linalg.pinv(comp_matrices[i]).T
            multiplicities = comp_pinv @ net_coeffs
            solved_coeffs = comp_matrices[i].T @ multiplicities

            if (multiplicities < tol).any():
                continue
            elif not (np.abs(solved_coeffs - net_coeffs) <=
                      (1e-08 + 1e-05 * np.abs(net_coeffs))).all():
                continue
            all_multiplicities[i] = multiplicities
            indices[i] = True

        filtered_indices = np.argwhere(indices != 0).flatten()
        length = filtered_indices.shape[0]
        filtered_comp_matrices = np.empty((length, shape[1], shape[2]),
                                          np.float64)
        filtered_multiplicities = np.empty((length, shape[1]), np.float64)

        for i in range(length):
            idx = filtered_indices[i]
            filtered_comp_matrices[i] = comp_matrices[idx]
            filtered_multiplicities[i] = all_multiplicities[idx]

        return filtered_comp_matrices, filtered_multiplicities

    @staticmethod
    def _filter_entries(all_entries,
                        e_above_hull,
                        temp,
                        include_polymorphs=False):
        """
        Helper method for filtering entries by specified energy above hull

        Args:
            all_entries ([ComputedEntry]): List of ComputedEntry-like objects to be
                filtered
            e_above_hull (float): Thermodynamic stability threshold (energy above hull)
                [eV/atom]
            include_polymorphs (bool): whether to include higher energy polymorphs of
                existing structures

        Returns:
            [ComputedEntry]: list of all entries with energies above hull equal to or
                less than the specified e_above_hull.
        """
        pd_dict = expand_pd(all_entries)
        pd_dict = {
            chemsys:
            PhaseDiagram(GibbsComputedStructureEntry.from_pd(pd, temp))
            for chemsys, pd in pd_dict.items()
        }

        filtered_entries = set()
        all_comps = dict()
        for chemsys, pd in pd_dict.items():
            for entry in pd.all_entries:
                if (entry in filtered_entries
                        or pd.get_e_above_hull(entry) > e_above_hull):
                    continue
                formula = entry.composition.reduced_formula
                if not include_polymorphs and (formula in all_comps):
                    if all_comps[
                            formula].energy_per_atom < entry.energy_per_atom:
                        continue
                    filtered_entries.remove(all_comps[formula])
                all_comps[formula] = entry
                filtered_entries.add(entry)

        return pd_dict, list(filtered_entries)

    @property
    def g(self):
        return self._g

    @property
    def pd(self):
        return self._pd

    @property
    def all_entries(self):
        return self._all_entries

    @property
    def filtered_entries(self):
        return self._filtered_entries

    @property
    def precursors(self):
        return self._precursors

    @property
    def all_targets(self):
        return self._all_targets

    @property
    def temp(self):
        return self._temp

    def __repr__(self):
        return (f"ReactionNetwork for chemical system: "
                f"{'-'.join(sorted([str(e) for e in self._pd.elements]))}, "
                f"with Graph: {str(self._g)}")
コード例 #8
0
ファイル: restapi.py プロジェクト: gmatteo/abipy
class PhaseDiagramResults(object):
    """
    Simplified interface to phase-diagram pymatgen API.

    Inspired to:

        https://anaconda.org/matsci/plotting-and-analyzing-a-phase-diagram-using-the-materials-api/notebook

    See also: :cite:`Ong2008,Ong2010`
    """
    def __init__(self, entries):
        self.entries = entries
        from abipy.core.structure import Structure
        for e in entries:
            e.structure = Structure.as_structure(e.structure)

        self.structures = [e.structure for e in entries]
        self.mpids = [e.entry_id for e in entries]

        # Create phase diagram.
        from pymatgen.analysis.phase_diagram import PhaseDiagram
        self.phasediagram = PhaseDiagram(self.entries)

    def plot(self, show_unstable=True, show=True):
        """
        Plot phase diagram.

        Args:
            show_unstable (float): Whether unstable phases will be plotted as
                well as red crosses. If a number > 0 is entered, all phases with
                ehull < show_unstable will be shown.
            show: True to show plot.

        Return:
            plotter object.
        """
        from pymatgen.analysis.phase_diagram import PDPlotter
        plotter = PDPlotter(self.phasediagram, show_unstable=show_unstable)
        if show:
            plotter.show()
        return plotter

    @lazy_property
    def dataframe(self):
        """Pandas dataframe with the most important results."""
        rows = []
        for e in self.entries:
            d = e.structure.get_dict4pandas(with_spglib=True)
            decomp, ehull = self.phasediagram.get_decomp_and_e_above_hull(e)

            rows.append(OrderedDict([
                ("Materials ID", e.entry_id),
                ("spglib_symb", d["spglib_symb"]), ("spglib_num", d["spglib_num"]),
                ("Composition", e.composition.reduced_formula),
                ("Ehull", ehull), # ("Equilibrium_reaction_energy", pda.get_equilibrium_reaction_energy(e)),
                ("Decomposition", " + ".join(["%.2f %s" % (v, k.composition.formula) for k, v in decomp.items()])),
            ]))

        import pandas as pd
        return pd.DataFrame(rows, columns=list(rows[0].keys()) if rows else None)

    def print_dataframes(self, with_spglib=False, file=sys.stdout, verbose=0):
        """
        Print pandas dataframe to file `file`.

        Args:
            with_spglib: True to compute spacegroup with spglib.
            file: Output stream.
            verbose: Verbosity level.
        """
        print_dataframe(self.dataframe, file=file)
        if verbose:
            from abipy.core.structure import dataframes_from_structures
            dfs = dataframes_from_structures(self.structures, index=self.mpids, with_spglib=with_spglib)
            print_dataframe(dfs.lattice, title="Lattice parameters:", file=file)
            if verbose > 1:
                print_dataframe(dfs.coords, title="Atomic positions (columns give the site index):", file=file)
コード例 #9
0
from pymatgen.apps.borg.hive import VaspToComputedEntryDrone
from pymatgen.apps.borg.queen import BorgQueen
from pymatgen.entries.compatibility import MaterialsProjectCompatibility

#pd = pd2.get_phase_diagram_data()

# ler o tamanho da lista

# cria uma lista só com os potenciais químicos
# chempot_list = [all_phase_diagrams[pd_index][1] for pd_index in range(number_of_phase_diagrams)]

drone = VaspToComputedEntryDrone()
queen = BorgQueen(drone, rootpath=".")
entries = queen.get_data()

pd2 = PhaseDiagram(entries)

all_phase_diagrams = pd.get_phase_diagram_data()

number_of_phase_diagrams = len(all_phase_diagrams)

open_elements_specific = None
open_element_all = Element("O")
mpr = MPRester("######")

mp_entries = mpr.get_entries_in_chemsys(['Li', 'Ca', 'O'],
                                        compatible_only=True)

pd = PhaseDiagramOpenAnalyzer(mp_entries[0], open_element_all)

entries.extend(mp_entries)
コード例 #10
0
from pymatgen.ext.matproj import MPRester
from pymatgen.apps.borg.hive import VaspToComputedEntryDrone
from pymatgen.apps.borg.queen import BorgQueen
from pymatgen.entries.compatibility import MaterialsProjectCompatibility
from pymatgen.analysis.phase_diagram import PhaseDiagram
from pymatgen.analysis.phase_diagram import PDPlotter

# Assimilate VASP calculations into ComputedEntry object. Let's assume that
# the calculations are for a series of new LixFeyOz phases that we want to
# know the phase stability.
drone = VaspToComputedEntryDrone()
queen = BorgQueen(drone, rootpath=".")
entries = queen.get_data()

# Obtain all existing Li-Fe-O phases using the Materials Project REST API
with MPRester("key") as m:
    mp_entries = m.get_entries_in_chemsys(["Li", "Sn", "S"])

# Combined entry from calculated run with Materials Project entries
entries.extend(mp_entries)

# Process entries using the MaterialsProjectCompatibility
compat = MaterialsProjectCompatibility()
entries = compat.process_entries(entries)

# Generate and plot Li-Fe-O phase diagram
pd = PhaseDiagram(entries)
plotter = PDPlotter(pd)
plotter.show()
コード例 #11
0
 def test_1d_pd(self):
     entry = PDEntry("H", 0)
     pd = PhaseDiagram([entry])
     decomp, e = pd.get_decomp_and_e_above_hull(PDEntry("H", 1))
     self.assertAlmostEqual(e, 1)
     self.assertAlmostEqual(decomp[entry], 1.0)
コード例 #12
0
ファイル: mixing_scheme.py プロジェクト: mturiansky/pymatgen
    def get_mixing_state_data(self, entries: list[ComputedStructureEntry], verbose: bool = False):
        """
        Generate internal state data to be passed to get_adjustments.

        Args:
            entries: The list of ComputedStructureEntry to process. It is assumed that the entries have
                already been filtered using _filter_and_sort_entries() to remove any irrelevant run types,
                apply compat_1 and compat_2, and confirm that all have unique entry_id.

        Returns:
            DataFrame: A pandas DataFrame that contains information associating structures from
                different functionals with specific materials and establishing how many run_type_1
                ground states have been computed with run_type_2. The DataFrame contains one row
                for each distinct material (Structure), with the following columns:
                    formula: str the reduced_formula
                    spacegroup: int the spacegroup
                    num_sites: int the number of sites in the Structure
                    entry_id_1: the entry_id of the run_type_1 entry
                    entry_id_2: the entry_id of the run_type_2 entry
                    run_type_1: Optional[str] the run_type_1 value
                    run_type_2: Optional[str] the run_type_2 value
                    energy_1: float or nan the ground state energy in run_type_1 in eV/atom
                    energy_2: float or nan the ground state energy in run_type_2 in eV/atom
                    is_stable_1: bool whether this material is stable on the run_type_1 PhaseDiagram
                    hull_energy_1: float or nan the energy of the run_type_1 hull at this composition in eV/atom
                    hull_energy_2: float or nan the energy of the run_type_1 hull at this composition in eV/atom
            None: Returns None if the supplied ComputedStructureEntry are insufficient for applying
                the mixing scheme.
        """
        filtered_entries = []

        for entry in entries:
            if not isinstance(entry, ComputedStructureEntry):
                warnings.warn(
                    "Entry {} is not a ComputedStructureEntry and will be"
                    "ignored. The DFT mixing scheme requires structures for"
                    " all entries".format(entry.entry_id)
                )
                continue

            filtered_entries.append(entry)

        # separate by run_type
        entries_type_1 = [e for e in filtered_entries if e.parameters["run_type"] in self.valid_rtypes_1]
        entries_type_2 = [e for e in filtered_entries if e.parameters["run_type"] in self.valid_rtypes_2]

        # construct PhaseDiagram for each run_type, if possible
        pd_type_1, pd_type_2 = None, None
        try:
            pd_type_1 = PhaseDiagram(entries_type_1)
        except ValueError:
            warnings.warn(f"{self.run_type_1} entries do not form a complete PhaseDiagram.")

        try:
            pd_type_2 = PhaseDiagram(entries_type_2)
        except ValueError:
            warnings.warn(f"{self.run_type_2} entries do not form a complete PhaseDiagram.")

        # Objective: loop through all the entries, group them by structure matching (or fuzzy structure matching
        # where relevant). For each group, put a row in a pandas DataFrame with the composition of the run_type_1 entry,
        # the run_type_2 entry, whether or not that entry is a ground state (not necessarily on the hull), its energy,
        # and the energy of the hull at that composition
        all_entries = list(entries_type_1) + list(entries_type_2)
        row_list = []
        columns = [
            "formula",
            "spacegroup",
            "num_sites",
            "is_stable_1",
            "entry_id_1",
            "entry_id_2",
            "run_type_1",
            "run_type_2",
            "energy_1",
            "energy_2",
            "hull_energy_1",
            "hull_energy_2",
        ]

        def _get_sg(struc) -> int:
            """helper function to get spacegroup with a loose tolerance"""
            try:
                return struc.get_space_group_info(symprec=0.1)[1]
            except Exception:
                return -1

        # loop through all structures
        # this logic follows emmet.builders.vasp.materials.MaterialsBuilder.filter_and_group_tasks
        structures = []
        for entry in all_entries:
            s = entry.structure
            s.entry_id = entry.entry_id
            structures.append(s)

        # First group by composition, then by spacegroup number, then by structure matching
        for comp, compgroup in groupby(sorted(structures, key=lambda s: s.composition), key=lambda s: s.composition):
            l_compgroup = list(compgroup)
            # group by spacegroup, then by number of sites (for diatmics) or by structure matching
            for sg, pregroup in groupby(sorted(l_compgroup, key=_get_sg), key=_get_sg):
                l_pregroup = list(pregroup)
                if comp.reduced_formula in ["O2", "H2", "Cl2", "F2", "N2", "I", "Br", "H2O"] and self.fuzzy_matching:
                    # group by number of sites
                    for n, sitegroup in groupby(
                        sorted(l_pregroup, key=lambda s: s.num_sites), key=lambda s: s.num_sites
                    ):
                        l_sitegroup = list(sitegroup)
                        row_list.append(
                            self._populate_df_row(l_sitegroup, comp, sg, n, pd_type_1, pd_type_2, all_entries)
                        )
                else:
                    for group in self.structure_matcher.group_structures(l_pregroup):
                        grp = list(group)
                        n = group[0].num_sites
                        # StructureMatcher.group_structures returns a list of lists,
                        # so each group should be a list containing matched structures
                        row_list.append(self._populate_df_row(grp, comp, sg, n, pd_type_1, pd_type_2, all_entries))

        mixing_state_data = pd.DataFrame(row_list, columns=columns)
        mixing_state_data.sort_values(
            ["formula", "energy_1", "spacegroup", "num_sites"], inplace=True, ignore_index=True
        )

        return mixing_state_data
コード例 #13
0
    def __init__(self,
                 entries,
                 comp_dict=None,
                 conc_dict=None,
                 filter_solids=False,
                 nproc=None):
        """
        Args:
            entries ([PourbaixEntry] or [MultiEntry]): Entries list
                containing Solids and Ions or a list of MultiEntries
            comp_dict ({str: float}): Dictionary of compositions,
                defaults to equal parts of each elements
            conc_dict ({str: float}): Dictionary of ion concentrations,
                defaults to 1e-6 for each element
            filter_solids (bool): applying this filter to a pourbaix
                diagram ensures all included phases are filtered by
                stability on the compositional phase diagram.  This
                breaks some of the functionality of the analysis,
                though, so use with caution.
            nproc (int): number of processes to generate multientries with
                in parallel.  Defaults to None (serial processing)
        """
        entries = deepcopy(entries)

        # Get non-OH elements
        self.pbx_elts = set(
            itertools.chain.from_iterable(
                [entry.composition.elements for entry in entries]))
        self.pbx_elts = list(self.pbx_elts - ELEMENTS_HO)
        self.dim = len(self.pbx_elts) - 1

        # Process multientry inputs
        if isinstance(entries[0], MultiEntry):
            self._processed_entries = entries
            # Extract individual entries
            single_entries = list(
                set(
                    itertools.chain.from_iterable(
                        [e.entry_list for e in entries])))
            self._unprocessed_entries = single_entries
            self._filtered_entries = single_entries
            self._conc_dict = None
            self._elt_comp = {
                k: v
                for k, v in entries[0].composition.items()
                if k not in ELEMENTS_HO
            }
            self._multielement = True

        # Process single entry inputs
        else:
            # Set default conc/comp dicts
            if not comp_dict:
                comp_dict = {
                    elt.symbol: 1. / len(self.pbx_elts)
                    for elt in self.pbx_elts
                }
            if not conc_dict:
                conc_dict = {elt.symbol: 1e-6 for elt in self.pbx_elts}
            self._conc_dict = conc_dict

            self._elt_comp = comp_dict
            self.pourbaix_elements = self.pbx_elts

            solid_entries = [
                entry for entry in entries if entry.phase_type == "Solid"
            ]
            ion_entries = [
                entry for entry in entries if entry.phase_type == "Ion"
            ]

            # If a conc_dict is specified, override individual entry concentrations
            for entry in ion_entries:
                ion_elts = list(set(entry.composition.elements) - ELEMENTS_HO)
                # TODO: the logic here for ion concentration setting is in two
                #       places, in PourbaixEntry and here, should be consolidated
                if len(ion_elts) == 1:
                    entry.concentration = conc_dict[ion_elts[0].symbol] \
                                          * entry.normalization_factor
                elif len(ion_elts) > 1 and not entry.concentration:
                    raise ValueError("Elemental concentration not compatible "
                                     "with multi-element ions")

            self._unprocessed_entries = solid_entries + ion_entries

            if not len(solid_entries + ion_entries) == len(entries):
                raise ValueError(
                    "All supplied entries must have a phase type of "
                    "either \"Solid\" or \"Ion\"")

            if filter_solids:
                # O is 2.46 b/c pbx entry finds energies referenced to H2O
                entries_HO = [ComputedEntry('H', 0), ComputedEntry('O', 2.46)]
                solid_pd = PhaseDiagram(solid_entries + entries_HO)
                solid_entries = list(
                    set(solid_pd.stable_entries) - set(entries_HO))

            self._filtered_entries = solid_entries + ion_entries

            if len(comp_dict) > 1:
                self._multielement = True
                self._processed_entries = self._preprocess_pourbaix_entries(
                    self._filtered_entries, nproc=nproc)
            else:
                self._processed_entries = self._filtered_entries
                self._multielement = False

        self._stable_domains, self._stable_domain_vertices = \
            self.get_pourbaix_domains(self._processed_entries)
コード例 #14
0
 def setUp(self):
     self.entries = EntrySet.from_csv(str(module_dir / "pdentries_test.csv"))
     self.pd = PhaseDiagram(self.entries)
     warnings.simplefilter("ignore")
コード例 #15
0
app.config["suppress_callback_exceptions"] = True

# tell Crystal Toolkit about the app
ctc.register_app(app)

# first, retrieve entries from Materials Project
with MPRester() as mpr:
    # li_entries = mpr.get_entries_in_chemsys(["Li"])
    # li_o_entries = mpr.get_entries_in_chemsys(["Li", "O"])
    li_co_o_entries = mpr.get_entries_in_chemsys(["Li", "O", "Co"])
    # li_co_o_fe_entries = mpr.get_entries_in_chemsys(["Li", "O", "Co", "Fe"])

# and then create the phase diagrams
# li_phase_diagram = PhaseDiagram(li_entries)
# li_o_phase_diagram = PhaseDiagram(li_o_entries)
li_co_o_phase_diagram = PhaseDiagram(li_co_o_entries)
# li_co_o_fe_phase_diagram = PhaseDiagram(li_co_o_fe_entries)

# and the corresponding Crystal Toolkit components
# we're creating four components here to demonstrate
# visualizing 1-D, 2-D, 3-D and 4-D phase diagrams
# li_phase_diagram_component = ctc.PhaseDiagramComponent(li_phase_diagram)
# li_o_phase_diagram_component = ctc.PhaseDiagramComponent(li_o_phase_diagram)
li_co_o_phase_diagram_component = ctc.PhaseDiagramComponent(
    li_co_o_phase_diagram)
# li_co_o_fe_phase_diagram_component = ctc.PhaseDiagramComponent(li_co_o_fe_phase_diagram)

print(li_co_o_entries)

# example layout to demonstrate capabilities of component
my_layout = html.Div([
コード例 #16
0
from pymatgen.ext.matproj import MPRester
from pymatgen.apps.borg.hive import VaspToComputedEntryDrone
from pymatgen.apps.borg.queen import BorgQueen
from pymatgen.entries.compatibility import MaterialsProjectCompatibility
from pymatgen.analysis.phase_diagram import PhaseDiagram
from pymatgen.analysis.phase_diagram import PDPlotter
from pymatgen.entries.computed_entries import ComputedEntry

entrada2 = [ComputedEntry("Li16Sn4S16", -150.73334384, -9.5)]

# Obtain all existing Li-Fe-O phases using the Materials Project REST API
with MPRester("Fvlb5EsNq71JxDy3") as m:
    mp_entries = m.get_entries_in_chemsys(["Li", "Sn", "S"])

# Process entries using the MaterialsProjectCompatibility
compat = MaterialsProjectCompatibility()
mp_entries = compat.process_entries(mp_entries)

entrada2.extend(mp_entries)

# Generate and plot Li-Fe-O phase diagram
pd = PhaseDiagram(entrada2)
plotter = PDPlotter(pd)
plotter.show()
コード例 #17
0
    def setUp(self):
        self.entries = [
            ComputedEntry(Composition("Li"), 0),
            ComputedEntry(Composition("Mn"), 0),
            ComputedEntry(Composition("O2"), 0),
            ComputedEntry(Composition("MnO2"), -10),
            ComputedEntry(Composition("Mn2O4"), -60),
            ComputedEntry(Composition("MnO3"), 20),
            ComputedEntry(Composition("Li2O"), -10),
            ComputedEntry(Composition("Li2O2"), -8),
            ComputedEntry(Composition("LiMnO2"), -30),
        ]
        self.pd = PhaseDiagram(self.entries)
        chempots = {"Li": -3}
        self.gpd = GrandPotentialPhaseDiagram(self.entries, chempots)
        self.ir = []
        # ir[0]
        self.ir.append(
            InterfacialReactivity(
                Composition("O2"),
                Composition("Mn"),
                self.pd,
                norm=0,
                include_no_mixing_energy=0,
                pd_non_grand=None,
                use_hull_energy=False,
            )
        )
        # ir[1]
        self.ir.append(
            InterfacialReactivity(
                Composition("MnO2"),
                Composition("Mn"),
                self.gpd,
                norm=0,
                include_no_mixing_energy=1,
                pd_non_grand=self.pd,
                use_hull_energy=False,
            )
        )
        # ir[2]
        self.ir.append(
            InterfacialReactivity(
                Composition("Mn"),
                Composition("O2"),
                self.gpd,
                norm=1,
                include_no_mixing_energy=1,
                pd_non_grand=self.pd,
                use_hull_energy=False,
            )
        )
        # ir[3]
        self.ir.append(
            InterfacialReactivity(
                Composition("Li2O"),
                Composition("Mn"),
                self.gpd,
                norm=0,
                include_no_mixing_energy=1,
                pd_non_grand=self.pd,
                use_hull_energy=False,
            )
        )
        # ir[4]
        self.ir.append(
            InterfacialReactivity(
                Composition("Mn"),
                Composition("O2"),
                self.gpd,
                norm=1,
                include_no_mixing_energy=0,
                pd_non_grand=self.pd,
                use_hull_energy=False,
            )
        )
        # ir[5]
        self.ir.append(
            InterfacialReactivity(
                Composition("Mn"),
                Composition("Li2O"),
                self.gpd,
                norm=1,
                include_no_mixing_energy=1,
                pd_non_grand=self.pd,
                use_hull_energy=False,
            )
        )
        # ir[6]
        self.ir.append(
            InterfacialReactivity(
                Composition("Li2O2"),
                Composition("Li"),
                self.pd,
                norm=0,
                include_no_mixing_energy=0,
                pd_non_grand=None,
                use_hull_energy=True,
            )
        )
        # ir[7]
        self.ir.append(
            InterfacialReactivity(
                Composition("Li2O2"),
                Composition("Li"),
                self.pd,
                norm=0,
                include_no_mixing_energy=0,
                pd_non_grand=None,
                use_hull_energy=False,
            )
        )
        # ir[8]
        self.ir.append(
            InterfacialReactivity(
                Composition("Li2O2"),
                Composition("MnO2"),
                self.gpd,
                norm=0,
                include_no_mixing_energy=0,
                pd_non_grand=self.pd,
                use_hull_energy=True,
            )
        )
        # ir[9]
        self.ir.append(
            InterfacialReactivity(
                Composition("Li2O2"),
                Composition("MnO2"),
                self.gpd,
                norm=0,
                include_no_mixing_energy=0,
                pd_non_grand=self.pd,
                use_hull_energy=False,
            )
        )
        # ir[10]
        self.ir.append(
            InterfacialReactivity(
                Composition("O2"),
                Composition("Mn"),
                self.pd,
                norm=1,
                include_no_mixing_energy=0,
                pd_non_grand=None,
                use_hull_energy=False,
            )
        )
        # ir[11]
        self.ir.append(
            InterfacialReactivity(
                Composition("Li2O2"),
                Composition("Li2O2"),
                self.gpd,
                norm=1,
                include_no_mixing_energy=1,
                pd_non_grand=self.pd,
                use_hull_energy=False,
            )
        )
        # ir[12]
        self.ir.append(
            InterfacialReactivity(
                Composition("Li2O2"),
                Composition("Li2O2"),
                self.pd,
                norm=1,
                include_no_mixing_energy=0,
                pd_non_grand=None,
                use_hull_energy=False,
            )
        )

        with self.assertRaises(Exception) as context1:
            self.ir.append(
                InterfacialReactivity(
                    Composition("Li2O2"),
                    Composition("Li"),
                    self.pd,
                    norm=1,
                    include_no_mixing_energy=1,
                    pd_non_grand=None,
                )
            )
        self.assertTrue("Please provide grand phase diagram to compute no_mixing_energy!" == str(context1.exception))

        with self.assertRaises(Exception) as context2:
            self.ir.append(
                InterfacialReactivity(
                    Composition("O2"),
                    Composition("Mn"),
                    self.gpd,
                    norm=0,
                    include_no_mixing_energy=1,
                    pd_non_grand=None,
                )
            )
        self.assertTrue(
            "Please provide non-grand phase diagram to compute no_mixing_energy!" == str(context2.exception)
        )
コード例 #18
0
    def find_intermediate_rxns(self, intermediates, targets, chempots=None):
        """
        Identifies thermodynamically predicted reactions from intermediate to one or
        more targets using interfacial reaction method. This method has the unique benefit (
        compared to the find_crossover_rxns method) of identifying reactions open to a
        specfic element or producing 3 or more products.

        Args:
            intermediates ([ComputedEntry]): List of intermediate entries
            targets ([ComputedEntry]): List of target entries
            chempots ({Element: float}): Dictionary of chemical potentials used to
                create grand potential phase diagram by which interfacial reactions are
                predicted.

        Returns:
            [ComputedReaction]: List of intermediate reactions
        """
        all_rxns = set()
        combos = list(generate_all_combos(intermediates, 2))
        for entries in tqdm(combos):
            n = len(entries)
            r1 = entries[0].composition.reduced_composition
            chemsys = {
                str(el)
                for entry in entries for el in entry.composition.elements
            }
            elem = None
            if chempots:
                elem = str(list(chempots.keys())[0])
                chemsys.update(elem)
                if chemsys == {elem}:
                    continue

            if n == 1:
                r2 = entries[0].composition.reduced_composition
            elif n == 2:
                r2 = entries[1].composition.reduced_composition
            else:
                raise ValueError(
                    "Can't have an interface that is not 1 to 2 entries!")

            if chempots:
                elem_comp = Composition(elem).reduced_composition
                if r1 == elem_comp or r2 == elem_comp:
                    continue

            entry_subset = self.entry_set.get_subset_in_chemsys(list(chemsys))
            pd = PhaseDiagram(entry_subset)
            grand_pd = None
            if chempots:
                grand_pd = GrandPotentialPhaseDiagram(entry_subset, chempots)

            rxns = react_interface(r1, r2, pd, self.num_entries, grand_pd)
            rxns_filtered = {
                r
                for r in rxns if set(r._product_entries) & targets
            }
            if rxns_filtered:
                most_favorable_rxn = min(
                    rxns_filtered,
                    key=lambda x: (x.calculated_reaction_energy / sum(
                        [x.get_el_amount(elem) for elem in x.elements])),
                )
                all_rxns.add(most_favorable_rxn)

        return all_rxns
コード例 #19
0
    def __init__(
        self,
        entries,
        n=2,
        temp=300,
        interpolate_comps=None,
        extend_entries=None,
        include_metastable=False,
        include_polymorphs=False,
        filter_rxn_energies=0.5,
    ):
        """Initializes ReactionNetwork object with necessary preprocessing
        steps. This does not yet compute the graph.

        Args:
            entries ([ComputedStructureEntry]): list of ComputedStructureEntry-
                like objects to consider in network. These can be acquired
                from Materials Project (using MPRester) or created manually in
                pymatgen. Entries should have same compatability (e.g.
                MPCompability) for phase diagram generation.
            n (int): maximum number of phases allowed on each side of the
                reaction (default 2). Note that n > 2 leads to significant (
                and often intractable) combinatorial explosion.
            temp (int): Temperature (in Kelvin) used for estimating Gibbs
                free energy of formation, as well as scaling the cost function
                later during network generation. Must select from [300, 400,
                500, ... 2000] K.
            extend_entries ([ComputedStructureEntry]): list of
                ComputedStructureEntry-like objects which will be included in
                the network even after filtering for thermodynamic stability.
                Helpful if target phase has a significantly high energy above
                the hull.
            include_metastable (float or bool): either a) the specified cutoff
                for energy per atom (eV/atom) above hull, or b) True/False
                if considering only stable vs. all entries. An energy cutoff of
                0.1 eV/atom is a reasonable starting threshold for thermodynamic
                stability. Defaults to False.
            include_polymorphs (bool): Whether or not to consider non-ground
                state polymorphs. Defaults to False. Note this is not useful
                unless structural metrics are considered in the cost function
                (to be added!)
            filter_rxn_energies (float): Energy filter. Reactions with
                energy_per_atom > filter will be excluded from network.
        """
        self.logger = logging.getLogger("ReactionNetwork")
        self.logger.setLevel("INFO")

        # Chemical system / phase diagram variables
        self._all_entries = entries
        self._max_num_phases = n
        self._temp = temp
        self._e_above_hull = include_metastable
        self._include_polymorphs = include_polymorphs
        self._elements = {
            elem
            for entry in self.all_entries
            for elem in entry.composition.elements
        }
        self._pd_dict, self._filtered_entries = self._filter_entries(
            entries, include_metastable, temp, include_polymorphs)
        self._entry_mu_ranges = {}
        self._pd = None
        self._rxn_e_filter = filter_rxn_energies

        if (len(self._elements) <= 10
            ):  # phase diagrams take considerable time to build with 10+ elems
            self._pd = PhaseDiagram(self._filtered_entries)

        if interpolate_comps:
            interpolated_entries = []
            for comp in interpolate_comps:
                energy = self._pd.get_hull_energy(Composition(comp))
                interpolated_entries.append(
                    PDEntry(comp, energy, attribute={"interpolated": True}))
            print("Interpolated entries:", "\n")
            print(interpolated_entries)
            self._filtered_entries.extend(interpolated_entries)

        if extend_entries:
            self._filtered_entries.extend(extend_entries)

        for idx, e in enumerate(self._filtered_entries):
            e.entry_idx = idx

        self.num_entries = len(self._filtered_entries)

        self._all_entry_combos = [
            set(combo) for combo in generate_all_combos(
                self._filtered_entries, self._max_num_phases)
        ]

        self.entry_set = EntrySet(self._filtered_entries)
        self.entry_indices = {
            e: idx
            for idx, e in enumerate(self._filtered_entries)
        }

        # Graph variables used during graph creation
        self._precursors = None
        self._all_targets = None
        self._current_target = None
        self._cost_function = None
        self._complex_loopback = None
        self._precursors_entries = None
        self._most_negative_rxn = None  # used in piecewise cost function
        self._g = None  # Graph object in graph-tool

        filtered_entries_str = ", ".join([
            entry.composition.reduced_formula
            for entry in self._filtered_entries
        ])
        self.logger.info(
            f"Initializing network with {len(self._filtered_entries)} "
            f"entries: \n{filtered_entries_str}")
コード例 #20
0
import json
from pymatgen import MPRester
from pymatgen.entries.compatibility import MaterialsProjectCompatibility
from pymatgen.analysis.phase_diagram import PhaseDiagram, PDPlotter, PDEntry
from pymatgen.core.periodic_table import Element
from pymatgen.core.composition import Composition
from pymatgen.io.vasp.outputs import Vasprun

#if __name__ == "__main__":
MAPI_KEY = 'DSR45TfHVuyuB1WvP1'  # You must change this to your Materials API key! (or set MAPI_KEY env variable)
system = ['Na', 'C', 'O']  # system we want to get PD for
system_name = '-'.join(system)

mpr = MPRester(MAPI_KEY)  # object for connecting to MP Rest interface
compat = MaterialsProjectCompatibility(
)  # sets energy corrections and +U/pseudopotential choice

unprocessed_entries = mpr.get_entries_in_chemsys(system)
processed_entries = compat.process_entries(
    unprocessed_entries)  # filter and add energy corrections

# vasprun = Vasprun('vasprun.xml')
# comp = vasprun.initial_structure.composition
# energy = vasprun.final_energy
# NBTentry = PDEntry(comp, energy)
# processed_entries.append(NBTentry)

pd = PhaseDiagram(processed_entries)
コード例 #21
0
    def analyze_GGA_chempots(self, full_sub_approach=False):
        """
        For calculating GGA-PBE atomic chemical potentials by using
            Materials Project pre-computed data

        Args:
            full_sub_approach: generate chemical potentials by looking at
                full phase diagram (setting to True is NOT recommended
                if subs_species set has more than one element in it...)

        This code retrieves atomic chempots from Materials
        Project (MP) entries by making use of the pymatgen
        phase diagram (PD) object and computed entries from the MP
        database. There are debug notes that are made based on the stability of
        the structure of interest with respect to the phase diagram generated from MP

        NOTE on 'full_sub_approach':
            The default approach for substitutional elements (full_sub_approach = False)
            is to only consider facets which have a maximum of 1 composition with
            the extrinsic species present
            (see PyCDT paper for chemical potential methodology DOI: 10.1016/j.cpc.2018.01.004).

            This default approach speeds up analysis when analyzing several substitutional
            species at the same time.

            If you prefer to consider the full phase diagram (not recommended
            when you have more than 2 substitutional defects), then set
            full_sub_approach to True.
        """
        logger = logging.getLogger(__name__)

        #gather entries
        self.get_mp_entries(full_sub_approach=full_sub_approach)

        # figure out how system should be treated for chemical potentials
        # based on phase diagram
        entry_list = self.entries['bulk_derived']
        pd = PhaseDiagram(entry_list)

        decomp_en = round(
            pd.get_decomp_and_e_above_hull(self.bulk_ce,
                                           allow_negative=True)[1], 4)

        stable_composition_exists = False
        for i in pd.stable_entries:
            if i.composition.reduced_composition == self.redcomp:
                stable_composition_exists = True

        if (decomp_en <= 0) and stable_composition_exists:
            logger.debug(
                "Bulk Computed Entry found to be stable with respect "
                "to MP Phase Diagram (e_above_hull = {} eV/atom).".format(
                    decomp_en))
        elif (decomp_en <= 0) and not stable_composition_exists:
            logger.info(
                "Bulk Computed Entry found to be stable with respect "
                "to MP Phase Diagram (e_above_hull = {} eV/atom).\n"
                "However, no stable entry with this composition exists "
                "in the MP database!\nPlease consider submitting the "
                "POSCAR to the MP xtaltoolkit, so future users will "
                "know about this structure:"
                " https://materialsproject.org/#apps/xtaltoolkit\n"
                "Manually inserting structure into phase diagram and "
                "proceeding as normal.".format(decomp_en))
            entry_list.append(self.bulk_ce)
        elif stable_composition_exists:
            logger.warning(
                "Bulk Computed Entry not stable with respect to MP "
                "Phase Diagram (e_above_hull = {} eV/atom), but found "
                "stable MP composition to exist.\nProducing chemical "
                "potentials with respect to stable phase.".format(decomp_en))
        else:
            logger.warning(
                "Bulk Computed Entry not stable with respect to MP "
                "Phase Diagram (e_above_hull = {} eV/atom) and no "
                "stable structure with this composition exists in the "
                "MP database.\nProceeding with atomic chemical "
                "potentials according to composition position within "
                "phase diagram.".format(decomp_en))

        pd = PhaseDiagram(entry_list)
        chem_lims = self.get_chempots_from_pd(pd)
        logger.debug("Bulk Chemical potential facets: {}".format(
            chem_lims.keys()))

        if not full_sub_approach:
            # NOTE if full_sub_approach was True, then all the sub_entries
            # would be ported into the bulk_derived list
            finchem_lims = {}  # this will be final chem_lims dictionary
            for key in chem_lims.keys():
                face_list = key.split('-')
                blk, blknom, subnom = self.diff_bulk_sub_phases(face_list)
                finchem_lims[blknom] = {}
                finchem_lims[blknom] = chem_lims[key]

            # Now add single elements to extend the phase diagram,
            # adding new additions to chemical potentials ONLY for the cases
            # where the phases in equilibria are those from the bulk phase
            # diagram. This is essentially the assumption that the majority of
            # the elements in the total composition will be from the native
            # species present rather than the sub species (a good approximation)
            for sub_el in self.sub_species:
                sub_specie_entries = entry_list[:]
                for entry in self.entries['subs_set'][sub_el]:
                    sub_specie_entries.append(entry)

                pd = PhaseDiagram(sub_specie_entries)
                chem_lims = self.get_chempots_from_pd(pd)

                for key in chem_lims.keys():
                    face_list = key.split('-')
                    blk, blknom, subnom = self.diff_bulk_sub_phases(
                        face_list, sub_el=sub_el)
                    # if number of facets from bulk phase diagram is
                    # equal to bulk species then full_sub_approach says this
                    # can be grouped with rest of structures
                    if len(blk) == len(self.bulk_species_symbol):
                        if blknom not in finchem_lims.keys():
                            finchem_lims[blknom] = chem_lims[key]
                        else:
                            finchem_lims[blknom][Element(sub_el)] = \
                                chem_lims[key][Element(sub_el)]
                        if 'name-append' not in finchem_lims[blknom].keys():
                            finchem_lims[blknom]['name-append'] = subnom
                        else:
                            finchem_lims[blknom]['name-append'] += '-' + subnom
                    else:
                        # if chem pots determined by two (or more) sub-specie
                        # containing phases, skip this facet!
                        continue
            #run a check to make sure all facets dominantly defined by bulk species
            overdependent_chempot = False
            facets_to_delete = []
            for facet_name, cps in finchem_lims.items():
                cp_key_num = (len(cps.keys()) -
                              1) if 'name-append' in cps else len(cps.keys())
                if cp_key_num != (len(self.bulk_species_symbol) +
                                  len(self.sub_species)):
                    facets_to_delete.append(facet_name)
                    logger.info(
                        "Not using facet {} because insufficient number of bulk facets for "
                        "bulk set {} with sub_species set {}. (only dependent on {})."
                        "".format(facet_name, self.bulk_species_symbol,
                                  self.sub_species, cps.get('name-append')))
            if len(facets_to_delete) == len(finchem_lims):
                overdependent_chempot = True
                logger.warning(
                    "Determined chemical potentials to be over dependent"
                    " on a substitutional specie. Needing to revert to full_sub_approach. If "
                    "multiple sub species exist this could take a while/break the code..."
                )
            else:
                finchem_lims = {
                    k: v
                    for k, v in finchem_lims.items()
                    if k not in facets_to_delete
                }

            if not overdependent_chempot:
                chem_lims = {}
                for orig_facet, fc_cp_dict in finchem_lims.items():
                    if 'name-append' not in fc_cp_dict:
                        facet_nom = orig_facet
                    else:
                        full_facet_list = orig_facet.split('-')
                        full_facet_list.extend(
                            fc_cp_dict['name-append'].split('-'))
                        full_facet_list.sort()
                        facet_nom = '-'.join(full_facet_list)
                    chem_lims[facet_nom] = {
                        k: v
                        for k, v in fc_cp_dict.items() if k != 'name-append'
                    }
            else:
                #This is for when overdetermined chempots occur, forcing the full_sub_approach to happen
                for sub, subentries in self.entries['subs_set'].items():
                    for subentry in subentries:
                        entry_list.append(subentry)
                pd = PhaseDiagram(entry_list)
                chem_lims = self.get_chempots_from_pd(pd)

        return chem_lims
コード例 #22
0
class PhaseDiagramTest(unittest.TestCase):
    def setUp(self):
        self.entries = EntrySet.from_csv(str(module_dir / "pdentries_test.csv"))
        self.pd = PhaseDiagram(self.entries)
        warnings.simplefilter("ignore")

    def tearDown(self):
        warnings.simplefilter("default")

    def test_init(self):
        # Ensure that a bad set of entries raises a PD error. Remove all Li
        # from self.entries.
        entries = filter(
            lambda e: (not e.composition.is_element) or e.composition.elements[0] != Element("Li"),
            self.entries,
        )
        self.assertRaises(ValueError, PhaseDiagram, entries)

    def test_dim1(self):
        # Ensure that dim 1 PDs can be generated.
        for el in ["Li", "Fe", "O2"]:
            entries = [e for e in self.entries if e.composition.reduced_formula == el]
            pd = PhaseDiagram(entries)
            self.assertEqual(len(pd.stable_entries), 1)

            for e in entries:
                ehull = pd.get_e_above_hull(e)
                self.assertGreaterEqual(ehull, 0)

            plotter = PDPlotter(pd)
            lines, *_ = plotter.pd_plot_data
            self.assertEqual(lines[0][1], [0, 0])

    def test_ordering(self):
        # Test sorting of elements
        entries = [ComputedEntry(Composition(formula), 0) for formula in ["O", "N", "Fe"]]
        pd = PhaseDiagram(entries)
        sorted_elements = (Element("Fe"), Element("N"), Element("O"))
        self.assertEqual(tuple(pd.elements), sorted_elements)

        entries.reverse()
        pd = PhaseDiagram(entries)
        self.assertEqual(tuple(pd.elements), sorted_elements)

        # Test manual specification of order
        ordering = [Element(elt_string) for elt_string in ["O", "N", "Fe"]]
        pd = PhaseDiagram(entries, elements=ordering)
        self.assertEqual(tuple(pd.elements), tuple(ordering))

    def test_stable_entries(self):
        stable_formulas = [ent.composition.reduced_formula for ent in self.pd.stable_entries]
        expected_stable = [
            "Fe2O3",
            "Li5FeO4",
            "LiFeO2",
            "Fe3O4",
            "Li",
            "Fe",
            "Li2O",
            "O2",
            "FeO",
        ]
        for formula in expected_stable:
            self.assertTrue(formula in stable_formulas, formula + " not in stable entries!")

    def test_get_formation_energy(self):
        stable_formation_energies = {
            ent.composition.reduced_formula: self.pd.get_form_energy(ent) for ent in self.pd.stable_entries
        }
        expected_formation_energies = {
            "Li5FeO4": -164.8117344866667,
            "Li2O2": -14.119232793333332,
            "Fe2O3": -16.574164339999996,
            "FeO": -5.7141519966666685,
            "Li": 0.0,
            "LiFeO2": -7.732752316666666,
            "Li2O": -6.229303868333332,
            "Fe": 0.0,
            "Fe3O4": -22.565714456666683,
            "Li2FeO3": -45.67166036000002,
            "O2": 0.0,
        }
        for formula, energy in expected_formation_energies.items():
            self.assertAlmostEqual(energy, stable_formation_energies[formula], 7)

    def test_all_entries_hulldata(self):
        self.assertEqual(len(self.pd.all_entries_hulldata), 490)

    def test_planar_inputs(self):
        e1 = PDEntry("H", 0)
        e2 = PDEntry("He", 0)
        e3 = PDEntry("Li", 0)
        e4 = PDEntry("Be", 0)
        e5 = PDEntry("B", 0)
        e6 = PDEntry("Rb", 0)

        pd = PhaseDiagram([e1, e2, e3, e4, e5, e6], map(Element, ["Rb", "He", "B", "Be", "Li", "H"]))

        self.assertEqual(len(pd.facets), 1)

    def test_str(self):
        self.assertIsNotNone(str(self.pd))

    def test_get_e_above_hull(self):
        for entry in self.pd.all_entries:
            for entry in self.pd.stable_entries:
                decomp, e_hull = self.pd.get_decomp_and_e_above_hull(entry)
                self.assertLess(
                    e_hull,
                    1e-11,
                    "Stable entries should have e above hull of zero!",
                )
                self.assertEqual(decomp[entry], 1, "Decomposition of stable entry should be itself.")
            else:
                e_ah = self.pd.get_e_above_hull(entry)
                self.assertTrue(isinstance(e_ah, Number))
                self.assertGreaterEqual(e_ah, 0)

    def test_get_equilibrium_reaction_energy(self):
        for entry in self.pd.stable_entries:
            self.assertLessEqual(
                self.pd.get_equilibrium_reaction_energy(entry),
                0,
                "Stable entries should have negative equilibrium reaction energy!",
            )

    def test_get_phase_separation_energy(self):
        for entry in self.pd.unstable_entries:
            if entry.composition.fractional_composition not in [
                e.composition.fractional_composition for e in self.pd.stable_entries
            ]:
                self.assertGreaterEqual(
                    self.pd.get_phase_separation_energy(entry),
                    0,
                    "Unstable entries should have positive decomposition energy!",
                )
            else:
                if entry.is_element:
                    el_ref = self.pd.el_refs[entry.composition.elements[0]]
                    e_d = entry.energy_per_atom - el_ref.energy_per_atom
                    self.assertAlmostEqual(self.pd.get_phase_separation_energy(entry), e_d, 7)
                # NOTE the remaining materials would require explicit tests as they
                # could be either positive or negative
                pass

        for entry in self.pd.stable_entries:
            if entry.composition.is_element:
                self.assertEqual(
                    self.pd.get_phase_separation_energy(entry),
                    0,
                    "Stable elemental entries should have decomposition energy of zero!",
                )
            else:
                self.assertLessEqual(
                    self.pd.get_phase_separation_energy(entry),
                    0,
                    "Stable entries should have negative decomposition energy!",
                )
                self.assertAlmostEqual(
                    self.pd.get_phase_separation_energy(entry, stable_only=True),
                    self.pd.get_equilibrium_reaction_energy(entry),
                    7,
                    (
                        "Using `stable_only=True` should give decomposition energy equal to "
                        "equilibrium reaction energy!"
                    ),
                )

        # Test that we get correct behaviour with a polymorph
        toy_entries = {
            "Li": 0.0,
            "Li2O": -5,
            "LiO2": -4,
            "O2": 0.0,
        }

        toy_pd = PhaseDiagram([PDEntry(c, e) for c, e in toy_entries.items()])

        # stable entry
        self.assertAlmostEqual(
            toy_pd.get_phase_separation_energy(PDEntry("Li2O", -5)),
            -1.0,
            7,
        )
        # polymorph
        self.assertAlmostEqual(
            toy_pd.get_phase_separation_energy(PDEntry("Li2O", -4)),
            -2.0 / 3.0,
            7,
        )

        # Test that the method works for novel entries
        novel_stable_entry = PDEntry("Li5FeO4", -999)
        self.assertLess(
            self.pd.get_phase_separation_energy(novel_stable_entry),
            0,
            "Novel stable entries should have negative decomposition energy!",
        )

        novel_unstable_entry = PDEntry("Li5FeO4", 999)
        self.assertGreater(
            self.pd.get_phase_separation_energy(novel_unstable_entry),
            0,
            "Novel unstable entries should have positive decomposition energy!",
        )

        duplicate_entry = PDEntry("Li2O", -14.31361175)
        scaled_dup_entry = PDEntry("Li4O2", -14.31361175 * 2)
        stable_entry = [e for e in self.pd.stable_entries if e.name == "Li2O"][0]

        self.assertEqual(
            self.pd.get_phase_separation_energy(duplicate_entry),
            self.pd.get_phase_separation_energy(stable_entry),
            "Novel duplicates of stable entries should have same decomposition energy!",
        )

        self.assertEqual(
            self.pd.get_phase_separation_energy(scaled_dup_entry),
            self.pd.get_phase_separation_energy(stable_entry),
            "Novel scaled duplicates of stable entries should have same decomposition energy!",
        )

    def test_get_decomposition(self):
        for entry in self.pd.stable_entries:
            self.assertEqual(
                len(self.pd.get_decomposition(entry.composition)),
                1,
                "Stable composition should have only 1 decomposition!",
            )
        dim = len(self.pd.elements)
        for entry in self.pd.all_entries:
            ndecomp = len(self.pd.get_decomposition(entry.composition))
            self.assertTrue(
                ndecomp > 0 and ndecomp <= dim,
                "The number of decomposition phases can at most be equal to the number of components.",
            )

        # Just to test decomp for a ficitious composition
        ansdict = {
            entry.composition.formula: amt for entry, amt in self.pd.get_decomposition(Composition("Li3Fe7O11")).items()
        }
        expected_ans = {
            "Fe2 O2": 0.0952380952380949,
            "Li1 Fe1 O2": 0.5714285714285714,
            "Fe6 O8": 0.33333333333333393,
        }
        for k, v in expected_ans.items():
            self.assertAlmostEqual(ansdict[k], v, 7)

    def test_get_transition_chempots(self):
        for el in self.pd.elements:
            self.assertLessEqual(len(self.pd.get_transition_chempots(el)), len(self.pd.facets))

    def test_get_element_profile(self):
        for el in self.pd.elements:
            for entry in self.pd.stable_entries:
                if not (entry.composition.is_element):
                    self.assertLessEqual(
                        len(self.pd.get_element_profile(el, entry.composition)),
                        len(self.pd.facets),
                    )

        expected = [
            {
                "evolution": 1.0,
                "chempot": -4.2582781416666666,
                "reaction": "Li2O + 0.5 O2 -> Li2O2",
            },
            {
                "evolution": 0,
                "chempot": -5.0885906699999968,
                "reaction": "Li2O -> Li2O",
            },
            {
                "evolution": -1.0,
                "chempot": -10.487582010000001,
                "reaction": "Li2O -> 2 Li + 0.5 O2",
            },
        ]
        result = self.pd.get_element_profile(Element("O"), Composition("Li2O"))
        for d1, d2 in zip(expected, result):
            self.assertAlmostEqual(d1["evolution"], d2["evolution"])
            self.assertAlmostEqual(d1["chempot"], d2["chempot"])
            self.assertEqual(d1["reaction"], str(d2["reaction"]))

    def test_get_get_chempot_range_map(self):
        elements = [el for el in self.pd.elements if el.symbol != "Fe"]
        self.assertEqual(len(self.pd.get_chempot_range_map(elements)), 10)

    def test_getmu_vertices_stability_phase(self):
        results = self.pd.getmu_vertices_stability_phase(Composition("LiFeO2"), Element("O"))
        self.assertAlmostEqual(len(results), 6)
        test_equality = False
        for c in results:
            if (
                abs(c[Element("O")] + 7.115) < 1e-2
                and abs(c[Element("Fe")] + 6.596) < 1e-2
                and abs(c[Element("Li")] + 3.931) < 1e-2
            ):
                test_equality = True
        self.assertTrue(test_equality, "there is an expected vertex missing in the list")

    def test_getmu_range_stability_phase(self):
        results = self.pd.get_chempot_range_stability_phase(Composition("LiFeO2"), Element("O"))
        self.assertAlmostEqual(results[Element("O")][1], -4.4501812249999997)
        self.assertAlmostEqual(results[Element("Fe")][0], -6.5961470999999996)
        self.assertAlmostEqual(results[Element("Li")][0], -3.6250022625000007)

    def test_get_hull_energy(self):
        for entry in self.pd.stable_entries:
            h_e = self.pd.get_hull_energy(entry.composition)
            self.assertAlmostEqual(h_e, entry.energy)
            n_h_e = self.pd.get_hull_energy(entry.composition.fractional_composition)
            self.assertAlmostEqual(n_h_e, entry.energy_per_atom)

    def test_get_hull_energy_per_atom(self):
        for entry in self.pd.stable_entries:
            h_e = self.pd.get_hull_energy_per_atom(entry.composition)
            self.assertAlmostEqual(h_e, entry.energy_per_atom)

    def test_1d_pd(self):
        entry = PDEntry("H", 0)
        pd = PhaseDiagram([entry])
        decomp, e = pd.get_decomp_and_e_above_hull(PDEntry("H", 1))
        self.assertAlmostEqual(e, 1)
        self.assertAlmostEqual(decomp[entry], 1.0)

    def test_get_critical_compositions_fractional(self):
        c1 = Composition("Fe2O3").fractional_composition
        c2 = Composition("Li3FeO4").fractional_composition
        c3 = Composition("Li2O").fractional_composition

        comps = self.pd.get_critical_compositions(c1, c2)
        expected = [
            Composition("Fe2O3").fractional_composition,
            Composition("Li0.3243244Fe0.1621621O0.51351349"),
            Composition("Li3FeO4").fractional_composition,
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

        comps = self.pd.get_critical_compositions(c1, c3)
        expected = [
            Composition("Fe0.4O0.6"),
            Composition("LiFeO2").fractional_composition,
            Composition("Li5FeO4").fractional_composition,
            Composition("Li2O").fractional_composition,
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

    def test_get_critical_compositions(self):
        c1 = Composition("Fe2O3")
        c2 = Composition("Li3FeO4")
        c3 = Composition("Li2O")

        comps = self.pd.get_critical_compositions(c1, c2)
        expected = [
            Composition("Fe2O3"),
            Composition("Li0.3243244Fe0.1621621O0.51351349") * 7.4,
            Composition("Li3FeO4"),
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

        comps = self.pd.get_critical_compositions(c1, c3)
        expected = [
            Composition("Fe2O3"),
            Composition("LiFeO2"),
            Composition("Li5FeO4") / 3,
            Composition("Li2O"),
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

        # Don't fail silently if input compositions aren't in phase diagram
        # Can be very confusing if you're working with a GrandPotentialPD
        self.assertRaises(
            ValueError,
            self.pd.get_critical_compositions,
            Composition("Xe"),
            Composition("Mn"),
        )

        # For the moment, should also fail even if compositions are in the gppd
        # because it isn't handled properly
        gppd = GrandPotentialPhaseDiagram(self.pd.all_entries, {"Xe": 1}, self.pd.elements + [Element("Xe")])
        self.assertRaises(
            ValueError,
            gppd.get_critical_compositions,
            Composition("Fe2O3"),
            Composition("Li3FeO4Xe"),
        )

        # check that the function still works though
        comps = gppd.get_critical_compositions(c1, c2)
        expected = [
            Composition("Fe2O3"),
            Composition("Li0.3243244Fe0.1621621O0.51351349") * 7.4,
            Composition("Li3FeO4"),
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

        # case where the endpoints are identical
        self.assertEqual(self.pd.get_critical_compositions(c1, c1 * 2), [c1, c1 * 2])

    def test_get_composition_chempots(self):
        c1 = Composition("Fe3.1O4")
        c2 = Composition("Fe3.2O4.1Li0.01")

        e1 = self.pd.get_hull_energy(c1)
        e2 = self.pd.get_hull_energy(c2)

        cp = self.pd.get_composition_chempots(c1)
        calc_e2 = e1 + sum(cp[k] * v for k, v in (c2 - c1).items())
        self.assertAlmostEqual(e2, calc_e2)

    def test_get_all_chempots(self):
        c1 = Composition("Fe3.1O4")
        c2 = Composition("FeO")

        cp1 = self.pd.get_all_chempots(c1)
        cpresult = {
            Element("Li"): -4.077061954999998,
            Element("Fe"): -6.741593864999999,
            Element("O"): -6.969907375000003,
        }

        for elem, energy in cpresult.items():
            self.assertAlmostEqual(cp1["Fe3O4-FeO-LiFeO2"][elem], energy)

        cp2 = self.pd.get_all_chempots(c2)
        cpresult = {
            Element("O"): -7.115354140000001,
            Element("Fe"): -6.5961471,
            Element("Li"): -3.9316151899999987,
        }

        for elem, energy in cpresult.items():
            self.assertAlmostEqual(cp2["FeO-LiFeO2-Fe"][elem], energy)

    def test_to_from_dict(self):
        # test round-trip for other entry types such as ComputedEntry
        entry = ComputedEntry("H", 0.0, 0.0, entry_id="test")
        pd = PhaseDiagram([entry])
        d = pd.as_dict()
        pd_roundtrip = PhaseDiagram.from_dict(d)
        self.assertEqual(pd.all_entries[0].entry_id, pd_roundtrip.all_entries[0].entry_id)
        dd = self.pd.as_dict()
        new_pd = PhaseDiagram.from_dict(dd)
        new_dd = new_pd.as_dict()
        self.assertEqual(new_dd, dd)
        self.assertIsInstance(pd.to_json(), str)
コード例 #23
0
    def read_phase_diagram_and_chempots(self,
                                        full_sub_approach=False,
                                        include_mp_entries=True):
        """
        Once phase diagram has been set up and run by user (in a folder 
        called "PhaseDiagram"), this method parses and prints the chemical 
        potentials based on the computed entries. The methodology is 
        basically identical to that in the analyze_GGA_chempots method.

        Will supplement unfinished entries with MP database entries 
        unless no_mp_entries is set to False

        Args:
            full_sub_approach: same attribute as described at length in 
                the analyze_GGA_chempots method. Basically, the user can 
                set this to True if they want to mix extrinsic species 
                in the phase diagram

            include_mp_entries: if set to True, extra entries from 
                Materials Project will be added to phase diagram
                according to phases that are stable in the Materials 
                Project database

        """
        pdfile = os.path.join(self.path_base, 'PhaseDiagram')
        if not os.path.exists(pdfile):
            print('Phase diagram file does not exist at ', pdfile)
            return

        # this is where we read computed entries into a list for parsing...
        # NOTE TO USER: If not running with VASP need to use another
        # pymatgen functionality for importing computed entries below...
        personal_entry_list = []
        for structfile in os.listdir(pdfile):
            if os.path.exists(os.path.join(pdfile, structfile, 'vasprun.xml')):
                try:
                    print('loading ', structfile)
                    vr = Vasprun(os.path.join(pdfile, structfile,
                                              'vasprun.xml'),
                                 parse_potcar_file=False)
                    personal_entry_list.append(vr.get_computed_entry())
                except:
                    print('Could not load ', structfile)

        #add bulk computed entry to phase diagram, and see if it is stable
        if not self.bulk_ce:
            vr_path = os.path.join(self.path_base, 'bulk', 'vasprun.xml')
            if os.path.exists(vr_path):
                print('loading bulk computed entry')
                bulkvr = Vasprun(vr_path)
                self.bulk_ce = bulkvr.get_computed_entry()
            else:
                print ('No bulk entry given locally. Phase diagram ' + \
                       'calculations cannot be set up without this')
                return

        self.bulk_composition = self.bulk_ce.composition
        self.redcomp = self.bulk_composition.reduced_composition

        # Supplement entries to phase diagram with those from MP database
        if include_mp_entries:
            mpcpa = MPChemPotAnalyzer(bulk_ce=self.bulk_ce,
                                      sub_species=self.sub_species,
                                      mapi_key=self.mapi_key)
            tempcl = mpcpa.analyze_GGA_chempots(
                full_sub_approach=full_sub_approach)  # Use MPentries

            curr_pd = PhaseDiagram(
                list(set().union(mpcpa.entries['bulk_derived'],
                                 mpcpa.entries['subs_set'])))
            stable_idlist = {
                i.composition.reduced_composition:
                [i.energy_per_atom, i.entry_id, i]
                for i in curr_pd.stable_entries
            }
            for mpcomp, mplist in stable_idlist.items():
                matched = False
                for pe in personal_entry_list:
                    if (pe.composition.reduced_composition == mpcomp):
                        # #USER: uncomment this if you want additional stable phases of identical composition included in your phase diagram
                        # if personalentry.energy_per_atom > mplist[0]:
                        #     print('Adding entry from MP-database:',mpcomp,'(entry-id:',mplist[1])
                        #     personal_entry_list.append(mplist[2])
                        matched = True
                if not matched:
                    print('Adding entry from MP-database:', mpcomp,
                          '(entry-id:', mplist[1])
                    personal_entry_list.append(mplist[2])
        else:
            personal_entry_list.append(self.bulk_ce)
            #if you dont have entries for elemental corners of phase diagram then code breaks
            #manually inserting entries with energies of zero for competeness...USER DO NOT USE THIS
            eltcount = {
                elt: 0
                for elt in set(self.bulk_ce.composition.elements)
            }
            for pentry in personal_entry_list:
                if pentry.is_element:
                    eltcount[pentry.composition.elements[0]] += 1
            for elt, eltnum in eltcount.items():
                if not eltnum:
                    s = Structure([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]],
                                  [elt], [[0, 0, 0]])
                    eltentry = ComputedStructureEntry(s, 0.)
                    print(
                        'USER! Note that you have added a fake ' + str(elt) +
                        ' structure to prevent from breaking the '
                        'Phase Diagram Analyzer.\n As a result DO NOT trust the chemical potential results for regions '
                        'of phase diagram that involve the element ' +
                        str(elt))
                    personal_entry_list.append(eltentry)

        personal_entry_list.append(self.bulk_ce)

        #compute chemical potentials
        if full_sub_approach:
            pd = PhaseDiagram(personal_entry_list)
            chem_lims = self.get_chempots_from_pd(pd)
        else:
            #first seperate out the bulk associated elements from those of substitutional elements
            entry_list = []
            sub_associated_entry_list = []
            for localentry in personal_entry_list:
                bulk_associated = True
                for elt in localentry.composition.elements:
                    if elt not in self.bulk_composition.elements:
                        bulk_associated = False

                if bulk_associated:
                    entry_list.append(localentry)
                else:
                    sub_associated_entry_list(localentry)

            #now iterate through and collect chemical potentials
            pd = PhaseDiagram(entry_list)
            chem_lims = self.get_chempots_from_pd(pd)

            finchem_lims = {}  # this will be final chem_lims dictionary
            for key in chem_lims.keys():
                face_list = key.split('-')
                blk, blknom, subnom = self.diff_bulk_sub_phases(face_list)
                finchem_lims[blknom] = {}
                finchem_lims[blknom] = chem_lims[key]

            # Now consider adding single elements to extend the phase diagram,
            # adding new additions to chemical potentials ONLY for the cases
            # where the phases in equilibria are those from the bulk phase
            # diagram. This is essentially the assumption that the majority of
            # the elements in the total composition will be from the native
            # species present rather than the sub species (a good approximation)
            for sub_el in self.sub_species:
                sub_specie_entries = entry_list[:]
                for entry in sub_associated_entry_list:
                    if sub_el in entry.composition.elements:
                        sub_specie_entries.append(entry)

                pd = PhaseDiagram(sub_specie_entries)
                chem_lims = self.get_chempots_from_pd(pd)

                for key in chem_lims.keys():
                    face_list = key.split('-')
                    blk, blknom, subnom = self.diff_bulk_sub_phases(
                        face_list, sub_el=sub_el)
                    # if one less than number of bulk species then can be
                    # grouped with rest of structures
                    if len(blk) == len(self.bulk_species_symbol):
                        if blknom not in finchem_lims.keys():
                            finchem_lims[blknom] = chem_lims[key]
                        else:
                            finchem_lims[blknom][sub_el] = \
                                chem_lims[key][sub_el]
                        if 'name-append' not in finchem_lims[blknom].keys():
                            finchem_lims[blknom]['name-append'] = subnom
                        else:
                            finchem_lims[blknom]['name-append'] += '-' + subnom
                    else:
                        # if chem pots determined by two (or more) sub-specie
                        # containing phases, skip this facet!
                        continue

            # run a check to make sure all facets dominantly defined by bulk species
            overdependent_chempot = False
            facets_to_delete = []
            for facet_name, cps in finchem_lims.items():
                cp_key_num = (len(cps.keys()) -
                              1) if 'name-append' in cps else len(cps.keys())
                bulk_species_symbol = [
                    s.symbol for s in self.bulk_composition.elements
                ]
                if cp_key_num != (len(bulk_species_symbol) +
                                  len(self.sub_species)):
                    facets_to_delete.append(facet_name)
                    print(
                        "Not using facet {} because insufficient number of bulk facets for "
                        "bulk set {} with sub_species set {}. (only dependent on {})."
                        "".format(facet_name, self.bulk_species_symbol,
                                  self.sub_species, cps.get('name-append')))
            if len(facets_to_delete) == len(finchem_lims):
                overdependent_chempot = True
                print(
                    "Determined chemical potentials to be over dependent"
                    " on a substitutional specie. Needing to revert to full_sub_approach. If "
                    "multiple sub species exist this could take a while/break the code..."
                )
            else:
                finchem_lims = {
                    k: v
                    for k, v in finchem_lims.items()
                    if k not in facets_to_delete
                }

            if not overdependent_chempot:
                chem_lims = {}
                for orig_facet, fc_cp_dict in finchem_lims.items():
                    if 'name-append' not in fc_cp_dict:
                        facet_nom = orig_facet
                    else:
                        full_facet_list = orig_facet.split('-')
                        full_facet_list.extend(
                            fc_cp_dict['name-append'].split('-'))
                        full_facet_list.sort()
                        facet_nom = '-'.join(full_facet_list)
                    chem_lims[facet_nom] = {
                        k: v
                        for k, v in fc_cp_dict.items() if k != 'name-append'
                    }
            else:
                # This is for when overdetermined chempots occur, forcing the full_sub_approach to happen
                for sub, subentries in self.entries['subs_set'].items():
                    for subentry in subentries:
                        entry_list.append(subentry)
                pd = PhaseDiagram(entry_list)
                chem_lims = self.get_chempots_from_pd(pd)

        return chem_lims
コード例 #24
0
    def test_get_phase_separation_energy(self):
        for entry in self.pd.unstable_entries:
            if entry.composition.fractional_composition not in [
                e.composition.fractional_composition for e in self.pd.stable_entries
            ]:
                self.assertGreaterEqual(
                    self.pd.get_phase_separation_energy(entry),
                    0,
                    "Unstable entries should have positive decomposition energy!",
                )
            else:
                if entry.is_element:
                    el_ref = self.pd.el_refs[entry.composition.elements[0]]
                    e_d = entry.energy_per_atom - el_ref.energy_per_atom
                    self.assertAlmostEqual(self.pd.get_phase_separation_energy(entry), e_d, 7)
                # NOTE the remaining materials would require explicit tests as they
                # could be either positive or negative
                pass

        for entry in self.pd.stable_entries:
            if entry.composition.is_element:
                self.assertEqual(
                    self.pd.get_phase_separation_energy(entry),
                    0,
                    "Stable elemental entries should have decomposition energy of zero!",
                )
            else:
                self.assertLessEqual(
                    self.pd.get_phase_separation_energy(entry),
                    0,
                    "Stable entries should have negative decomposition energy!",
                )
                self.assertAlmostEqual(
                    self.pd.get_phase_separation_energy(entry, stable_only=True),
                    self.pd.get_equilibrium_reaction_energy(entry),
                    7,
                    (
                        "Using `stable_only=True` should give decomposition energy equal to "
                        "equilibrium reaction energy!"
                    ),
                )

        # Test that we get correct behaviour with a polymorph
        toy_entries = {
            "Li": 0.0,
            "Li2O": -5,
            "LiO2": -4,
            "O2": 0.0,
        }

        toy_pd = PhaseDiagram([PDEntry(c, e) for c, e in toy_entries.items()])

        # stable entry
        self.assertAlmostEqual(
            toy_pd.get_phase_separation_energy(PDEntry("Li2O", -5)),
            -1.0,
            7,
        )
        # polymorph
        self.assertAlmostEqual(
            toy_pd.get_phase_separation_energy(PDEntry("Li2O", -4)),
            -2.0 / 3.0,
            7,
        )

        # Test that the method works for novel entries
        novel_stable_entry = PDEntry("Li5FeO4", -999)
        self.assertLess(
            self.pd.get_phase_separation_energy(novel_stable_entry),
            0,
            "Novel stable entries should have negative decomposition energy!",
        )

        novel_unstable_entry = PDEntry("Li5FeO4", 999)
        self.assertGreater(
            self.pd.get_phase_separation_energy(novel_unstable_entry),
            0,
            "Novel unstable entries should have positive decomposition energy!",
        )

        duplicate_entry = PDEntry("Li2O", -14.31361175)
        scaled_dup_entry = PDEntry("Li4O2", -14.31361175 * 2)
        stable_entry = [e for e in self.pd.stable_entries if e.name == "Li2O"][0]

        self.assertEqual(
            self.pd.get_phase_separation_energy(duplicate_entry),
            self.pd.get_phase_separation_energy(stable_entry),
            "Novel duplicates of stable entries should have same decomposition energy!",
        )

        self.assertEqual(
            self.pd.get_phase_separation_energy(scaled_dup_entry),
            self.pd.get_phase_separation_energy(stable_entry),
            "Novel scaled duplicates of stable entries should have same decomposition energy!",
        )
コード例 #25
0
    def setup_phase_diagram_calculations(self,
                                         full_phase_diagram=False,
                                         energy_above_hull=0,
                                         struct_fmt='poscar'):
        """
        This method allows for setting up local phase diagram calculations so a user can calculate
        chemical potentials on a level of interest beyond PBE-GGA/GGA+U
        Method is to pull the MP phase diagram and use PBE-GGA level data to decide which phases need to be computed

        full_phase_diagram flag has two options:
            False: set up the structures/phases which are stable in GGA phase diagram and are relevant for defining
                    the chemical potentials (exist to define the facets adjacent to composition of interest)
            True:  set up the full phase diagram according to all the entries in the MP database with elements of interest

        entry_above_hull: allows for a range of energies above hull for each composition being set up
                default is 0, meaning just the PBE-GGA ground state phases are set up. If you set value to 0.5 then all
                phases within 0.5 eV/atom of PBE-GGA ground state hull will be set up etc.

        struct_fmt: is file format you want structure to be written as. Options are “cif”, “poscar”, “cssr”, and “json”

        """

        #while GGA chem pots won't be used here; use this method for quickly gathering phase diagram object entries
        #   AND to find phases of interest if you just want to re-calculate local facets
        MPgga_muvals = self.MPC.get_chempots_from_composition(
            self.bulk_composition)

        if full_phase_diagram:
            setupphases = set([
                localentry.name for entrykey in self.MPC.entries.keys()
                for localentry in self.MPC.entries[entrykey]
            ])  #all elements in phase diagram
        else:
            if len(
                    self.bulk_composition
            ) == 2:  #neccessary because binary species have chempots written as "A-rich, B-rich"
                setupphases = set([
                    phase.split('_')[0] for facet in MPgga_muvals.keys()
                    for phase in facet.split('-')
                ])
            else:
                setupphases = set([
                    phase for facet in MPgga_muvals.keys()
                    for phase in facet.split('-')
                ])  #just local facets

        structures_to_setup = {
        }  #this will be a list of structure objects which need to be setup locally

        #create phase diagram object for analyzing PBE-GGA energetics of structures computed in MP database
        full_structure_entries = [
            struct for entrykey in self.MPC.entries.keys()
            for struct in self.MPC.entries[entrykey]
        ]
        pd = PhaseDiagram(full_structure_entries)

        for entry in full_structure_entries:
            if (entry.name in setupphases) and (pd.get_decomp_and_e_above_hull(
                    entry, allow_negative=True)[1] <= energy_above_hull):
                with MPRester(api_key=self.mapi_key) as mp:
                    localstruct = mp.get_structure_by_material_id(
                        entry.entry_id)
                structures_to_setup[str(entry.entry_id) + '_' +
                                    str(entry.name)] = localstruct

        #Set up structure files locally if desired
        if os.path.exists(os.path.join(self.path_base, 'PhaseDiagram')):
            print('phase diagram already exists! Dont overwrite...')
        else:
            os.makedirs(os.path.join(self.path_base, 'PhaseDiagram'))
            for localname, localstruct in structures_to_setup.items():
                filename = os.path.join(self.path_base, 'PhaseDiagram',
                                        localname)
                os.makedirs(filename)
                if struct_fmt == 'poscar':
                    outputname = 'POSCAR'
                else:
                    outputname = 'structfile'
                localstruct.to(fmt=struct_fmt,
                               filename=os.path.join(filename, outputname))
                #NOTE TO USER. Can use pymatgen here to setup additional calculation files if interested...

        return structures_to_setup
コード例 #26
0
    def process_item(self, item):
        """
        Process the list of entries into thermo docs for each sandbox
        Args:
            item (set(entry)): a list of entries to process into a phase diagram

        Returns:
            [dict]: a list of thermo dictionaries to update thermo with
        """

        docs = []

        sandboxes, entries = item
        entries = self.compatibility.process_entries(entries)

        # determine chemsys
        chemsys = "-".join(
            sorted(
                set([
                    el.symbol for e in entries for el in e.composition.elements
                ])))

        self.logger.debug(
            f"Procesing {len(entries)} entries for {chemsys} - {sandboxes}")

        try:
            pd = PhaseDiagram(entries)

            docs = []

            for e in entries:
                (decomp, ehull) = pd.get_decomp_and_e_above_hull(e)

                d = {
                    self.thermo.key: e.entry_id,
                    "thermo": {
                        "energy": e.uncorrected_energy,
                        "energy_per_atom":
                        e.uncorrected_energy / e.composition.num_atoms,
                        "formation_energy_per_atom":
                        pd.get_form_energy_per_atom(e),
                        "e_above_hull": ehull,
                        "is_stable": e in pd.stable_entries,
                    },
                }

                # Store different info if stable vs decomposes
                if d["thermo"]["is_stable"]:
                    d["thermo"][
                        "eq_reaction_e"] = pd.get_equilibrium_reaction_energy(
                            e)
                else:
                    d["thermo"]["decomposes_to"] = [{
                        "task_id": de.entry_id,
                        "formula": de.composition.formula,
                        "amount": amt,
                    } for de, amt in decomp.items()]

                d["thermo"]["entry"] = e.as_dict()
                d["thermo"][
                    "explanation"] = self.compatibility.get_explanation_dict(e)

                elsyms = sorted(
                    set([el.symbol for el in e.composition.elements]))
                d["chemsys"] = "-".join(elsyms)
                d["nelements"] = len(elsyms)
                d["elements"] = list(elsyms)
                d["_sbxn"] = list(sandboxes)

                docs.append(d)
        except PhaseDiagramError as p:
            elsyms = []
            for e in entries:
                elsyms.extend([el.symbol for el in e.composition.elements])

            self.logger.warning("Phase diagram errorin chemsys {}: {}".format(
                "-".join(sorted(set(elsyms))), p))
            return []

        return docs
コード例 #27
0
ファイル: analysis.py プロジェクト: sailfish009/CAMD
    def plot_hull(self, df, new_result_ids, filename=None, finalize=False):
        """
        Generate plots of convex hulls for each of the runs

        Args:
            df (DataFrame): dataframe with formation energies and formulas
            new_result_ids ([]): list of new result ids (i. e. indexes
                in the updated dataframe)
            filename (str): filename to output, if None, no file output
                is produced
            finalize (bool): flag indicating whether to include all new results

        Returns:
            (pyplot): plotter instance
        """
        # Generate all entries
        total_comp = Composition(df['Composition'].sum())
        if len(total_comp) > 4:
            warnings.warn(
                "Number of elements too high for phase diagram plotting")
            return None
        filtered = filter_dataframe_by_composition(df, total_comp)
        filtered = filtered[['delta_e', 'Composition']]
        filtered = filtered.dropna()

        # Create computed entry column with un-normalized energies
        filtered["entry"] = [
            ComputedEntry(
                Composition(row["Composition"]),
                row["delta_e"] * Composition(row["Composition"]).num_atoms,
                entry_id=index,
            ) for index, row in filtered.iterrows()
        ]

        ids_prior_to_run = list(set(filtered.index) - set(new_result_ids))
        if not ids_prior_to_run:
            warnings.warn(
                "No prior data, prior phase diagram cannot be constructed")
            return None

        # Create phase diagram based on everything prior to current run
        entries = filtered.loc[ids_prior_to_run]["entry"].dropna()

        # Filter for nans by checking if it's a computed entry
        pg_elements = sorted(total_comp.keys())
        pd = PhaseDiagram(entries, elements=pg_elements)
        plotkwargs = {
            "markerfacecolor": "white",
            "markersize": 7,
            "linewidth": 2,
        }
        if finalize:
            plotkwargs.update({"linestyle": "--"})
        else:
            plotkwargs.update({"linestyle": "-"})
        plotter = PDPlotter(pd, backend='matplotlib', **plotkwargs)

        getplotkwargs = {"label_stable": False} if finalize else {}
        plot = plotter.get_plot(**getplotkwargs)

        # Get valid results
        valid_results = [
            new_result_id for new_result_id in new_result_ids
            if new_result_id in filtered.index
        ]

        if finalize:
            # If finalize, we'll reset pd to all entries at this point to
            # measure stabilities wrt. the ultimate hull.
            pd = PhaseDiagram(filtered["entry"].values, elements=pg_elements)
            plotter = PDPlotter(pd,
                                backend="matplotlib",
                                **{
                                    "markersize": 0,
                                    "linestyle": "-",
                                    "linewidth": 2
                                })
            plot = plotter.get_plot(plt=plot)

        for entry in filtered["entry"][valid_results]:
            decomp, e_hull = pd.get_decomp_and_e_above_hull(
                entry, allow_negative=True)
            if e_hull < self.hull_distance:
                color = "g"
                marker = "o"
                markeredgewidth = 1
            else:
                color = "r"
                marker = "x"
                markeredgewidth = 1

            # Get coords
            coords = [
                entry.composition.get_atomic_fraction(el) for el in pd.elements
            ][1:]
            if pd.dim == 2:
                coords = coords + [pd.get_form_energy_per_atom(entry)]
            if pd.dim == 3:
                coords = triangular_coord(coords)
            elif pd.dim == 4:
                coords = tet_coord(coords)
            plot.plot(*coords,
                      marker=marker,
                      markeredgecolor=color,
                      markerfacecolor="None",
                      markersize=11,
                      markeredgewidth=markeredgewidth)

        if filename is not None:
            plot.savefig(filename, dpi=70)
        plot.close()
コード例 #28
0
    def __init__(self, entries, comp_dict=None, conc_dict=None,
                 filter_solids=False, nproc=None):
        entries = deepcopy(entries)

        # Get non-OH elements
        pbx_elts = set(itertools.chain.from_iterable(
            [entry.composition.elements for entry in entries]))
        pbx_elts = list(pbx_elts - elements_HO)

        # Process multientry inputs
        if isinstance(entries[0], MultiEntry):
            self._processed_entries = entries
            # Extract individual entries
            single_entries = list(set(itertools.chain.from_iterable(
                [e.entry_list for e in entries])))
            self._unprocessed_entries = single_entries
            self._filtered_entries = single_entries
            self._conc_dict = None
            self._elt_comp = {k: v for k, v in entries[0].composition.items()
                              if not k in elements_HO}
            self._multielement = True

        # Process single entry inputs
        else:
            # Set default conc/comp dicts
            if not comp_dict:
                comp_dict = {elt.symbol: 1. / len(pbx_elts) for elt in pbx_elts}
            if not conc_dict:
                conc_dict = {elt.symbol: 1e-6 for elt in pbx_elts}
            self._conc_dict = conc_dict

            self._elt_comp = comp_dict
            self.pourbaix_elements = pbx_elts

            solid_entries = [entry for entry in entries
                             if entry.phase_type == "Solid"]
            ion_entries = [entry for entry in entries
                           if entry.phase_type == "Ion"]

            # If a conc_dict is specified, override individual entry concentrations
            for entry in ion_entries:
                ion_elts = list(set(entry.composition.elements) - elements_HO)
                # TODO: the logic here for ion concentration setting is in two
                #       places, in PourbaixEntry and here, should be consolidated
                if len(ion_elts) == 1:
                    entry.concentration = conc_dict[ion_elts[0].symbol] \
                                          * entry.normalization_factor
                elif len(ion_elts) > 1 and not entry.concentration:
                    raise ValueError("Elemental concentration not compatible "
                                     "with multi-element ions")

            self._unprocessed_entries = solid_entries + ion_entries

            if not len(solid_entries + ion_entries) == len(entries):
                raise ValueError("All supplied entries must have a phase type of "
                                 "either \"Solid\" or \"Ion\"")

            if filter_solids:
                # O is 2.46 b/c pbx entry finds energies referenced to H2O
                entries_HO = [ComputedEntry('H', 0), ComputedEntry('O', 2.46)]
                solid_pd = PhaseDiagram(solid_entries + entries_HO)
                solid_entries = list(set(solid_pd.stable_entries) - set(entries_HO))

            self._filtered_entries = solid_entries + ion_entries

            if len(comp_dict) > 1:
                self._multielement = True
                self._processed_entries = self._generate_multielement_entries(
                    self._filtered_entries, nproc=nproc)
            else:
                self._processed_entries = self._filtered_entries
                self._multielement = False

        self._stable_domains, self._stable_domain_vertices = \
            self.get_pourbaix_domains(self._processed_entries)
コード例 #29
0
 def test_from_pd(self):
     pd = PhaseDiagram(self.mp_entries)
     gibbs_entries = GibbsComputedStructureEntry.from_pd(pd)
     self.assertIsNotNone(gibbs_entries)
コード例 #30
0
ファイル: matproj.py プロジェクト: navnidhirajput/pymatgen
    def get_pourbaix_entries(self, chemsys):
        """
        A helper function to get all entries necessary to generate
        a pourbaix diagram from the rest interface.

        Args:
            chemsys ([str]): A list of elements comprising the chemical
                system, e.g. ['Li', 'Fe']
        """
        from pymatgen.analysis.pourbaix_diagram import PourbaixEntry, IonEntry
        from pymatgen.analysis.phase_diagram import PhaseDiagram
        from pymatgen.core.ion import Ion
        from pymatgen.entries.compatibility import\
            MaterialsProjectAqueousCompatibility

        pbx_entries = []

        # Get ion entries first, because certain ions have reference
        # solids that aren't necessarily in the chemsys (Na2SO4)
        url = '/pourbaix_diagram/reference_data/' + '-'.join(chemsys)
        ion_data = self._make_request(url)
        ion_ref_comps = [Composition(d['Reference Solid']) for d in ion_data]
        ion_ref_elts = list(itertools.chain.from_iterable(
            i.elements for i in ion_ref_comps))
        ion_ref_entries = self.get_entries_in_chemsys(
            list(set([str(e) for e in ion_ref_elts] + ['O', 'H'])),
            property_data=['e_above_hull'], compatible_only=False)
        compat = MaterialsProjectAqueousCompatibility("Advanced")
        ion_ref_entries = compat.process_entries(ion_ref_entries)
        ion_ref_pd = PhaseDiagram(ion_ref_entries)

        # position the ion energies relative to most stable reference state
        for n, i_d in enumerate(ion_data):
            ion_entry = IonEntry(Ion.from_formula(i_d['Name']), i_d['Energy'])
            refs = [e for e in ion_ref_entries
                    if e.composition.reduced_formula == i_d['Reference Solid']]
            if not refs:
                raise ValueError("Reference solid not contained in entry list")
            stable_ref = sorted(refs, key=lambda x: x.data['e_above_hull'])[0]
            rf = stable_ref.composition.get_reduced_composition_and_factor()[1]
            solid_diff = ion_ref_pd.get_form_energy(stable_ref)\
                         - i_d['Reference solid energy'] * rf
            elt = i_d['Major_Elements'][0]
            correction_factor = ion_entry.ion.composition[elt]\
                                / stable_ref.composition[elt]
            ion_entry.energy += solid_diff * correction_factor
            pbx_entries.append(PourbaixEntry(ion_entry, 'ion-{}'.format(n)))
            # import nose; nose.tools.set_trace()

        # Construct the solid pourbaix entries from filtered ion_ref entries
        extra_elts = set(ion_ref_elts) - {Element(s) for s in chemsys}\
            - {Element('H'), Element('O')}
        for entry in ion_ref_entries:
            entry_elts = set(entry.composition.elements)
            # Ensure no OH chemsys or extraneous elements from ion references
            if not (entry_elts <= {Element('H'), Element('O')} or \
                    extra_elts.intersection(entry_elts)):
                # replace energy with formation energy, use dict to
                # avoid messing with the ion_ref_pd and to keep all old params
                form_e = ion_ref_pd.get_form_energy(entry)
                new_entry = deepcopy(entry)
                new_entry.uncorrected_energy = form_e
                new_entry.correction = 0.0
                pbx_entry = PourbaixEntry(new_entry)
                if entry.entry_id == "mp-697146":
                    pass
                    # import nose; nose.tools.set_trace()
                # pbx_entry.reduced_entry()
                pbx_entries.append(pbx_entry)

        return pbx_entries
コード例 #31
0
    def from_entries(cls, entries, working_ion_entry, strip_structures=False):
        """
        Create a new InsertionElectrode.

        Args:
            entries: A list of ComputedStructureEntries (or subclasses)
                representing the different topotactic states of the battery,
                e.g. TiO2 and LiTiO2.
            working_ion_entry: A single ComputedEntry or PDEntry
                representing the element that carries charge across the
                battery, e.g. Li.
            strip_structures: Since the electrode document only uses volume we can make the
                electrode object significantly leaner by dropping the structure data.
                If this parameter is set to True, the ComputedStructureEntry will be replaced
                with ComputedEntry and the volume will be stored in ComputedEntry.data['volume']
        """

        if strip_structures:
            ents = []
            for ient in entries:
                dd = ient.as_dict()
                ent = ComputedEntry.from_dict(dd)
                ent.data["volume"] = ient.structure.volume
                ents.append(ent)
            entries = ents

        _working_ion = working_ion_entry.composition.elements[0]
        _working_ion_entry = working_ion_entry

        # Prepare to make phase diagram: determine elements and set their energy
        # to be very high
        elements = set()
        for entry in entries:
            elements.update(entry.composition.elements)

        # Set an artificial energy for each element for convex hull generation
        element_energy = max([entry.energy_per_atom for entry in entries]) + 10

        pdentries = []
        pdentries.extend(entries)
        pdentries.extend(
            [PDEntry(Composition({el: 1}), element_energy) for el in elements])

        # Make phase diagram to determine which entries are stable vs. unstable
        pd = PhaseDiagram(pdentries)

        def lifrac(e):
            return e.composition.get_atomic_fraction(_working_ion)

        # stable entries ordered by amount of Li asc
        _stable_entries = tuple(
            sorted([e for e in pd.stable_entries if e in entries], key=lifrac))

        # unstable entries ordered by amount of Li asc
        _unstable_entries = tuple(
            sorted([e for e in pd.unstable_entries if e in entries],
                   key=lifrac))

        # create voltage pairs
        _vpairs = tuple(
            InsertionVoltagePair.from_entries(
                _stable_entries[i],
                _stable_entries[i + 1],
                working_ion_entry,
            ) for i in range(len(_stable_entries) - 1))
        framework = _vpairs[0].framework
        return cls(
            voltage_pairs=_vpairs,
            working_ion_entry=_working_ion_entry,
            _stable_entries=_stable_entries,
            _unstable_entries=_unstable_entries,
            _framework_formula=framework.reduced_formula,
        )
コード例 #32
0
    def setUp(self):
        self.entries = [ComputedEntry(Composition('Li'), 0),
                        ComputedEntry(Composition('Mn'), 0),
                        ComputedEntry(Composition('O2'), 0),
                        ComputedEntry(Composition('MnO2'), -10),
                        ComputedEntry(Composition('Mn2O4'), -60),
                        ComputedEntry(Composition('MnO3'), 20),
                        ComputedEntry(Composition('Li2O'), -10),
                        ComputedEntry(Composition('Li2O2'), -8),
                        ComputedEntry(Composition('LiMnO2'), -30)
                        ]
        self.pd = PhaseDiagram(self.entries)
        chempots = {'Li': -3}
        self.gpd = GrandPotentialPhaseDiagram(self.entries, chempots)
        self.ir = []
        self.ir.append(
            InterfacialReactivity(Composition('O2'), Composition('Mn'),
                                  self.pd, norm=0, include_no_mixing_energy=0,
                                  pd_non_grand=None, use_hull_energy=False))
        self.ir.append(
            InterfacialReactivity(Composition('MnO2'), Composition('Mn'),
                                  self.gpd, norm=0, include_no_mixing_energy=1,
                                  pd_non_grand=self.pd, use_hull_energy=False))
        self.ir.append(
            InterfacialReactivity(Composition('Mn'), Composition('O2'),
                                  self.gpd, norm=1, include_no_mixing_energy=1,
                                  pd_non_grand=self.pd, use_hull_energy=False))
        self.ir.append(
            InterfacialReactivity(Composition('Li2O'), Composition('Mn'),
                                  self.gpd, norm=0, include_no_mixing_energy=1,
                                  pd_non_grand=self.pd, use_hull_energy=False))
        self.ir.append(
            InterfacialReactivity(Composition('Mn'), Composition('O2'),
                                  self.gpd, norm=1, include_no_mixing_energy=0,
                                  pd_non_grand=self.pd, use_hull_energy=False))
        self.ir.append(
            InterfacialReactivity(Composition('Mn'), Composition('Li2O'),
                                  self.gpd, norm=1, include_no_mixing_energy=1,
                                  pd_non_grand=self.pd, use_hull_energy=False))
        self.ir.append(
            InterfacialReactivity(Composition('Li2O2'), Composition('Li'),
                                  self.pd, norm=0, include_no_mixing_energy=0,
                                  pd_non_grand=None, use_hull_energy=True))
        self.ir.append(
            InterfacialReactivity(Composition('Li2O2'), Composition('Li'),
                                  self.pd, norm=0, include_no_mixing_energy=0,
                                  pd_non_grand=None, use_hull_energy=False))

        self.ir.append(
            InterfacialReactivity(Composition('Li2O2'), Composition('MnO2'),
                                  self.gpd, norm=0, include_no_mixing_energy=0,
                                  pd_non_grand=self.pd, use_hull_energy=True))

        self.ir.append(
            InterfacialReactivity(Composition('Li2O2'), Composition('MnO2'),
                                  self.gpd, norm=0, include_no_mixing_energy=0,
                                  pd_non_grand=self.pd, use_hull_energy=False))

        self.ir.append(
            InterfacialReactivity(Composition('O2'), Composition('Mn'),
                                  self.pd, norm=1, include_no_mixing_energy=0,
                                  pd_non_grand=None, use_hull_energy=False))

        self.ir.append(
            InterfacialReactivity(Composition('Li2O2'), Composition('Li2O2'),
                                  self.gpd, norm=1, include_no_mixing_energy=1,
                                  pd_non_grand=self.pd, use_hull_energy=False))

        self.ir.append(
            InterfacialReactivity(Composition('Li2O2'), Composition('Li2O2'),
                                  self.pd, norm=1, include_no_mixing_energy=0,
                                  pd_non_grand=None, use_hull_energy=False))

        with self.assertRaises(Exception) as context1:
            self.ir.append(
                InterfacialReactivity(Composition('Li2O2'), Composition('Li'),
                                      self.pd, norm=1,
                                      include_no_mixing_energy=1,
                                      pd_non_grand=None))
        self.assertTrue(
            'Please provide grand phase diagram '
            'to compute no_mixing_energy!' == str(context1.exception))

        with self.assertRaises(Exception) as context2:
            self.ir.append(
                InterfacialReactivity(Composition('O2'), Composition('Mn'),
                                      self.gpd, norm=0,
                                      include_no_mixing_energy=1,
                                      pd_non_grand=None))
        self.assertTrue(
            'Please provide non-grand phase diagram '
            'to compute no_mixing_energy!' == str(context2.exception))
コード例 #33
0
###############################################################################
print('')
print('Calculating Phase Diagram...\n')

for phase in computed_phases:

    # getting Composition Object
    comp = Composition(phase)
    # getting entry for PD Object
    entry = PDEntry(comp, computed_phases[phase])
    # building list of entries
    entries.append(entry)

# getting PD from list of entries
pd = PhaseDiagram(entries)

# get distance from convex hull for cubic phase
comp = Composition('NaNbO3')
energy = -38.26346361
entry = PDEntry(comp, energy)
cubic_instability = pd.get_e_above_hull(entry)

pd_dict = pd.as_dict()

# Getting Plot
plt = PDPlotter(pd, show_unstable=False)  # you can also try show_unstable=True

#plt_data = plt.pd_plot_data
# getting plot for chem potential - variables 'fontsize' for labels size and 'plotsize' for fig size have been added (not present in original pymatgen) to get_chempot_range_map_plot function
chem_pot_plot = plt.get_chempot_range_map_plot(
コード例 #34
0
from pymatgen.analysis.phase_diagram import PhaseDiagram, PDPlotter, PDEntry
from pymatgen.core.periodic_table import Element
from pymatgen.core.composition import Composition
from pymatgen.io.vasp.outputs import Vasprun
import sys

print("Usage: get_phase_diagram_from_MP.py 'Element1,Element2,Element3,...'")

system = [el for el in sys.argv[1].split(',')]  # system we want to get PD for

MAPI_KEY = 'DSR45TfHVuyuB1WvP1'  # You must change this to your Materials API key! (or set MAPI_KEY env variable)
system_name = '-'.join(system)

mpr = MPRester(MAPI_KEY)  # object for connecting to MP Rest interface
compat = MaterialsProjectCompatibility(
)  # sets energy corrections and +U/pseudopotential choice

unprocessed_entries = mpr.get_entries_in_chemsys(system, inc_structure=True)
processed_entries = compat.process_entries(
    unprocessed_entries)  # filter and add energy corrections

pd = PhaseDiagram(processed_entries)
pd_dict = pd.as_dict()

filename = f'PD_{system_name}.json'

with open(filename, 'w') as f:
    json.dump(pd_dict, f)

print(f"PhaseDiagram object saved as dict in {filename}")
コード例 #35
0
    def __init__(
        self,
        entries: Union[List[PourbaixEntry], List[MultiEntry]],
        comp_dict: Optional[Dict[str, float]] = None,
        conc_dict: Optional[Dict[str, float]] = None,
        filter_solids: bool = True,
        nproc: Optional[int] = None,
    ):
        """
        Args:
            entries ([PourbaixEntry] or [MultiEntry]): Entries list
                containing Solids and Ions or a list of MultiEntries
            comp_dict ({str: float}): Dictionary of compositions,
                defaults to equal parts of each elements
            conc_dict ({str: float}): Dictionary of ion concentrations,
                defaults to 1e-6 for each element
            filter_solids (bool): applying this filter to a Pourbaix
                diagram ensures all included solid phases are filtered by
                stability on the compositional phase diagram. Defaults to True.
                The practical consequence of this is that highly oxidized or reduced
                phases that might show up in experiments due to kinetic limitations
                on oxygen/hydrogen evolution won't appear in the diagram, but they are
                not actually "stable" (and are frequently overstabilized from DFT errors).
                Hence, including only the stable solid phases generally leads to the
                most accurate Pourbaix diagrams.
            nproc (int): number of processes to generate multientries with
                in parallel.  Defaults to None (serial processing)
        """
        entries = deepcopy(entries)
        self.filter_solids = filter_solids

        # Get non-OH elements
        self.pbx_elts = list(
            set(
                itertools.chain.from_iterable(
                    [entry.composition.elements
                     for entry in entries])) - ELEMENTS_HO)
        self.dim = len(self.pbx_elts) - 1

        # Process multientry inputs
        if isinstance(entries[0], MultiEntry):
            self._processed_entries = entries
            # Extract individual entries
            single_entries = list(
                set(
                    itertools.chain.from_iterable(
                        [e.entry_list for e in entries])))
            self._unprocessed_entries = single_entries
            self._filtered_entries = single_entries
            self._conc_dict = None
            self._elt_comp = {
                k: v
                for k, v in entries[0].composition.items()
                if k not in ELEMENTS_HO
            }
            self._multielement = True

        # Process single entry inputs
        else:
            # Set default conc/comp dicts
            if not comp_dict:
                comp_dict = {
                    elt.symbol: 1.0 / len(self.pbx_elts)
                    for elt in self.pbx_elts
                }
            if not conc_dict:
                conc_dict = {elt.symbol: 1e-6 for elt in self.pbx_elts}
            self._conc_dict = conc_dict

            self._elt_comp = comp_dict
            self.pourbaix_elements = self.pbx_elts

            solid_entries = [
                entry for entry in entries if entry.phase_type == "Solid"
            ]
            ion_entries = [
                entry for entry in entries if entry.phase_type == "Ion"
            ]

            # If a conc_dict is specified, override individual entry concentrations
            for entry in ion_entries:
                ion_elts = list(set(entry.composition.elements) - ELEMENTS_HO)
                # TODO: the logic here for ion concentration setting is in two
                #       places, in PourbaixEntry and here, should be consolidated
                if len(ion_elts) == 1:
                    entry.concentration = conc_dict[
                        ion_elts[0].symbol] * entry.normalization_factor
                elif len(ion_elts) > 1 and not entry.concentration:
                    raise ValueError(
                        "Elemental concentration not compatible with multi-element ions"
                    )

            self._unprocessed_entries = solid_entries + ion_entries

            if not len(solid_entries + ion_entries) == len(entries):
                raise ValueError(
                    "All supplied entries must have a phase type of "
                    'either "Solid" or "Ion"')

            if self.filter_solids:
                # O is 2.46 b/c pbx entry finds energies referenced to H2O
                entries_HO = [ComputedEntry("H", 0), ComputedEntry("O", 2.46)]
                solid_pd = PhaseDiagram(solid_entries + entries_HO)
                solid_entries = list(
                    set(solid_pd.stable_entries) - set(entries_HO))

            self._filtered_entries = solid_entries + ion_entries
            if len(comp_dict) > 1:
                self._multielement = True
                self._processed_entries = self._preprocess_pourbaix_entries(
                    self._filtered_entries, nproc=nproc)
            else:
                self._processed_entries = self._filtered_entries
                self._multielement = False

        self._stable_domains, self._stable_domain_vertices = self.get_pourbaix_domains(
            self._processed_entries)
コード例 #36
0
def biased_hull(atomate_db, comp_list, anions=['N', 'O'], bias=[0]):
    with MPRester() as mpr:
        for pretty in comp_list:
            composition = Composition(pretty)
            composition = [str(i) for i in composition.elements]
            #           anion_num = composition[2]
            #           composition.pop()
            #           composition.append(anions[0])
            #           composition.append(anions[1])
            #First, build the phase diagram and hull
            orig_entries = mpr.get_entries_in_chemsys(composition)
            #orig_entries = mpr.get_entries_in_chemsys(chemsys_list[k])
            entries = []
            for i in range(len(bias)):
                entries.append(copy.deepcopy(orig_entries))
                for j in range(0, len(entries[i])):
                    temp = entries[i][j].parameters['potcar_symbols']
                    if temp in [['PBE ' + anions[0]], ['PBE ' + anions[1]],
                                ['PBE ' + anions[0], 'PBE ' + anions[1]],
                                ['PBE ' + anions[1], 'PBE ' + anions[0]]]:
                        new_entry = ComputedEntry(
                            entries[i][j].composition, entries[i][j].energy +
                            bias[i])  #add arbitrary energy to gas phase
                        entries[i][j] = copy.deepcopy(new_entry)

    #Then, find each entry in atomate_db which has this composition and get its hull energy
            print(pretty)
            structures = []
            cursor = atomate_db.collection.find({
                'task_label': 'static',
                'formula_pretty': pretty
            })
            for structure in cursor:
                structures.append(structure)
            struct_entries = []
            for structure in structures:
                temp = structure['calcs_reversed'][0]
                struct_entry = ComputedEntry(
                    temp['composition_unit_cell'],
                    temp['output']['energy'],
                    parameters={
                        'run_type':
                        temp['run_type'],
                        'is_hubbard':
                        structure['input']['is_hubbard'],
                        'pseudo_potential':
                        structure['input']['pseudo_potential'],
                        'hubbards':
                        structure['input']['hubbards'],
                        'potcar_symbols':
                        structure['orig_inputs']['potcar']['symbols'],
                        'oxide_type':
                        'oxide'
                    },
                    data={'oxide_type': 'oxide'})
                for i in range(0, 4):
                    struct_entry.parameters['potcar_symbols'][
                        i] = 'PBE ' + struct_entry.parameters[
                            'potcar_symbols'][i]
                struct_entry = MaterialsProjectCompatibility().process_entries(
                    [struct_entry
                     ])[0]  #takes list as argument and returns list
                struct_entries.append(struct_entry)
            bias_strings = []
            stable_polymorph = {'id': 0, 'tilt_order': ''}
            for i in range(len(bias)):
                entries[i].extend(struct_entries)
                pd = PhaseDiagram(entries[i])
                bias_string = 'ehull_' + str(bias[i]) + 'eV'
                bias_strings.append(bias_string)
                stable_polymorph[bias_strings[i]] = 1000
                print(bias_strings)
                for j in range(0, len(struct_entries)):
                    stability = pd.get_decomp_and_e_above_hull(
                        struct_entries[j])
                    print(structures[j]['formula_pretty'],
                          structures[j]['task_id'],
                          [phase.composition
                           for phase in stability[0]], stability[1])
                    if stability[1] < stable_polymorph[bias_strings[i]]:
                        stable_polymorph['id'] = structures[j]['task_id']
                        stable_polymorph[bias_strings[i]] = stability[1]
                    if 'tags' in structures[j]:
                        if structures[j]['tags'][1] == 'tetra':
                            stable_polymorph['tilt_order'] = structures[j][
                                'tags'][2]
                        else:
                            stable_polymorph['tilt_order'] = structures[j][
                                'tags'][1]
                    output_dict[structures[j]
                                ['formula_pretty']] = stable_polymorph
        return output_dict
コード例 #37
0
    def _solve_gs_preserve(self, A, f, mu, subsample_mapping, skip_gs=False):
        """
        Code notes from Daniil Kitchaev ([email protected]) - 2018-09-10

        This is a WORK IN PROGRESS based on Wenxuan's ground-state preservation fitting code.
        A, f, and mu as as in the other routines
        subsample mapping deals with the fact that weights change when fitting on a partial set (when figuring out mu)
        skin_gs gives the option of ignoring the constrained fitting part, which is helpful when figuring out mu

        In general, this code is really not production ready - the algorithm that serious numerical issues, and getting
        around them involved lots of fiddling with eigenvalue roundoffs, etc, as is commented below.

        There are also issues with the fact that constraints can be very difficult to satisfy, causing the solver to
        diverge (or just quit silently giving absurd results) - ths solution here appears to be to use MOSEK instead
        of cvxopt, and to iteratively remove constraints when they cause problems. Usually after cleaning up the data,
        everything can be fit though without removing constraints.

        At the end of the day, this algorithm seems to only be useful for niche applications because enforcing ground
        state preservation causes a giant bias in the fit and makes the error in E-above-hull highly correlated with the
        value of E-above-hull. The result is entropies are completely wrong, which is what you usually want out of a
        cluster expansion.

        So, use the code at your own risk. AFAIK, it works as described in Wenxuans paper, with various additions from
        me for numerical stability. It has not been extensively tested though or used in real projects due to the bias
        issue I described above. I think that unless the bias problem is resolved, this fitting scheme will not be
        of much practical use.
        """
        if not subsample_mapping:
            assert A.shape[0] == self.feature_matrix.shape[0]
            subsample_mapping = {}
            for i in range(self.feature_matrix.shape[0]):
                subsample_mapping[i] = i

        from cvxopt import matrix
        from cvxopt import solvers
        from pymatgen.core.periodic_table import get_el_sp
        try:
            import mosek
        except:
            raise ValueError("GS preservation fitting is finicky and MOSEK solvers are typically required for numerical stability.")
        solvers.options['show_progress'] = False
        solvers.options['MOSEK'] = {mosek.dparam.check_convexity_rel_tol: 1e-6}

        ehull = list(self.e_above_hull_input)
        structure_index_at_hull = [i for (i,e) in enumerate(ehull) if e < 1e-5]

        reduce_composition_at_hull = [
            self.structures[i].composition.element_composition.reduced_composition.element_composition for
            i in structure_index_at_hull]

        all_corr_in = np.array(self.feature_matrix)
        all_engr_in = np.array(self.normalized_energies)

        # Some structures can be degenerate in correlation space, even if they are distinct in reality. We can't
        # constrain their energies since as far as the CE is concerned, same correlation = same structure
        duplicated_correlation_set = []
        for i in range(len(all_corr_in)):
            if i not in structure_index_at_hull:
                for j in structure_index_at_hull:
                    if np.max(np.abs(all_corr_in[i] - all_corr_in[j])) < 1e-6:
                        logging.info("Structure {} ({} - {}) has the same correlation as hull structure {} ({} {})".format(i,
                                                                    self.structures[i].composition.element_composition.reduced_formula,
                                                                    self.spacegroups[i],
                                                                    j,
                                                                    self.structures[j].composition.element_composition.reduced_formula,
                                                                    self.spacegroups[j]))
                        duplicated_correlation_set.append(i)

        all_engr_in.shape = (len(all_engr_in), 1)
        f.shape = (f.shape[0], 1)

        # Adjust weights if subsample changed whats included and whats not
        weights_tmp = []
        for i in range(A.shape[0]):
            weights_tmp.append(self.weights[subsample_mapping[i]])

        subsample_mapping_inv = {}
        for i, j in subsample_mapping.items():
            subsample_mapping_inv[j] = i
        for i in duplicated_correlation_set:
            if i in subsample_mapping_inv.keys():
                weights_tmp[subsample_mapping_inv[i]] = 0


        weight_vec = np.array(weights_tmp)

        weight_matrix = np.diag(weight_vec.transpose())

        N_corr = A.shape[1]

        # Deal with roundoff error making P not positive semidefinite by using the SVD of A
        # At = USV*
        # At A = U S St Ut -> any negatives in S get squared
        # Unfortunately, this is usually not enough, so the next step is to explicitly add something small (1e-10)
        # to all eigenvalues so that eigenvalues close to zero are instead very slightly positive.
        # Otherwise, random numerical error makes the matrix not positive semidefinite, and the convex optimization
        # gets confused
        Aw = weight_matrix.dot(A)
        u, s, v = la.svd(Aw.transpose())
        Ss = np.pad(np.diag(s), ((0, u.shape[0] - len(s)),(0,0)), mode='constant', constant_values=0)
        P_corr_part = 2 * u.dot((Ss.dot(Ss.transpose()))).dot(u.transpose())
        P = np.lib.pad(P_corr_part, ((0, N_corr), (0, N_corr)), mode='constant', constant_values=0)
        P = 0.5 * (P + P.transpose())
        ev, Q = la.eigh(P)
        Qi = la.inv(Q)
        P = Q.dot(np.diag(np.abs(ev)+1e-10)).dot(Qi)

        q_corr_part = -2 * ((weight_matrix.dot(A)).transpose()).dot(f)
        q_z_part = np.ones((N_corr, 1)) / mu
        q = np.concatenate((q_corr_part, q_z_part), axis=0)

        G_1 = np.concatenate((np.identity(N_corr), -np.identity(N_corr)), axis=1)
        G_2 = np.concatenate((-np.identity(N_corr), -np.identity(N_corr)), axis=1)
        G_3 = np.concatenate((G_1, G_2), axis=0)
        h_3 = np.zeros((2 * N_corr, 1))

        # formulation is min 1/2 x'Px+ q'x s.t.: Gx<=h, Ax=b

        # P = 2 * A^T A
        # q = -2 * E^T A = q^T -> q = -2 * A^T E

        # See Wenxuan npjCompMat paper for derivation. All of the above mess is implementing this formula, plus dealing
        # with numerical issues with zero eigenvalues getting rounded off to something slightly negative

        init_vals = matrix(np.linalg.lstsq(self.feature_matrix, self.normalized_energies)[0])

        input_entries = []
        for s, e in zip(self.structures, self.energies):
            input_entries.append(PDEntry(s.composition.element_composition, e))
        max_e = max(input_entries, key=lambda e: e.energy_per_atom).energy_per_atom + 1000
        for el in self.ce.structure.composition.keys():
            input_entries.append(PDEntry(Composition({el: 1}).element_composition, max_e))
        pd_input = PhaseDiagram(input_entries)

        constraint_strings = []

        # Uncomment to save various matrices for debugging purposes
        #np.save("A.npy", A)
        #np.save("f.npy", f)
        #np.save("w.npy", weight_vec)
        #np.save("P.npy", P)
        #np.save("q.npy", q)
        #np.save("G_noC.npy", G_3)
        #np.save("h_noC.npy", h_3)

        # The next part deals with adding constraints based on on-hull/off-hull compositions
        # Once again, there are numerical errors that arise when some structures are very close in correlation space
        # or in energy, such that the solver runs into either numerical issues or something else. The solution seems
        # to be to add constraints in batches, and try the increasingly constrained fit every once in a while.
        # When the fitting fails, roll back to find the problematic constraint and remove it. Usually there isnt more
        # than one or two bad constrains, and looking at them by hand is enough to figure out why they are causing
        # problems.
        BATCH_SIZE = int(np.sqrt(len(all_corr_in)))
        tot_constraints = 0
        removed_constraints = 0
        if not skip_gs:
            for i in range(len(all_corr_in)):
                if i not in structure_index_at_hull and i not in duplicated_correlation_set:

                    reduced_comp = self.structures[i].composition.element_composition.reduced_composition.element_composition
                    if reduced_comp in reduce_composition_at_hull:  ## in hull composition

                        hull_idx = reduce_composition_at_hull.index(reduced_comp)
                        global_index = structure_index_at_hull[hull_idx]

                        G_3_new_line = np.concatenate((all_corr_in[global_index] - all_corr_in[i], np.zeros((N_corr))))

                        G_3_new_line.shape = (1, 2 * N_corr)
                        G_3 = np.concatenate((G_3, G_3_new_line), axis=0)
                        small_error = np.array(-1e-3) # TODO: This tolerance is actually quite big, but it can be reduced as needed
                        small_error.shape = (1, 1)
                        h_3 = np.concatenate((h_3, small_error), axis=0)
                        tot_constraints += 1
                        string = "{}|Added constraint from {}({} - {}) structure at hull comp".format(h_3.shape[0], reduced_comp, self.spacegroups[i], i)
                        print(string)
                        constraint_strings.append(string)

                    else:  # out of hull composition

                        comp_now = self.structures[i].composition.element_composition.reduced_composition.element_composition
                        decomposition_now = pd_input.get_decomposition(comp_now)
                        new_vector = -1.0 * all_corr_in[i]
                        for decompo_keys, decompo_values in decomposition_now.items():
                            reduced_decompo_keys = decompo_keys.composition.element_composition.reduced_composition.element_composition
                            index_1 = reduce_composition_at_hull.index(reduced_decompo_keys)
                            vertex_index_global = structure_index_at_hull[index_1]
                            new_vector = new_vector + decompo_values * all_corr_in[vertex_index_global]

                        G_3_new_line = np.concatenate((new_vector, np.zeros(N_corr)))

                        G_3_new_line.shape = (1, 2 * N_corr)
                        G_3 = np.concatenate((G_3, G_3_new_line), axis=0)

                        small_error = np.array(-1e-3)
                        small_error.shape = (1, 1)
                        h_3 = np.concatenate((h_3, small_error), axis=0)
                        tot_constraints += 1
                        string = "{}|Added constraint from {}({}) structure not at hull comp".format(h_3.shape[0], reduced_comp, i)
                        print(string)
                        constraint_strings.append(string)

                elif i in structure_index_at_hull:
                    if self.structures[i].composition.element_composition.is_element:
                        continue

                    entries_new = []
                    for j in structure_index_at_hull:
                        if not j == i:
                            entries_new.append(
                                PDEntry(self.structures[j].composition.element_composition, self.energies[j]))

                    for el in self.ce.structure.composition.keys():
                        entries_new.append(PDEntry(Composition({el: 1}).element_composition,
                                                   max(self.normalized_energies) + 1000))

                    pd_new = PhaseDiagram(entries_new)

                    comp_now = self.structures[i].composition.element_composition.reduced_composition.element_composition
                    decomposition_now = pd_new.get_decomposition(comp_now)

                    new_vector = all_corr_in[i]

                    abandon = False
                    print("Constraining gs of {}({})".format(self.structures[i].composition, self.structures[i].composition))
                    for decompo_keys, decompo_values in decomposition_now.items():
                        reduced_decompo_keys = decompo_keys.composition.element_composition.reduced_composition.element_composition
                        if not reduced_decompo_keys in reduce_composition_at_hull:
                            abandon = True
                            break

                        index = reduce_composition_at_hull.index(reduced_decompo_keys)
                        vertex_index_global = structure_index_at_hull[index]
                        new_vector = new_vector - decompo_values * all_corr_in[vertex_index_global]
                    if abandon:
                        continue

                    G_3_new_line = np.concatenate((new_vector, np.zeros(N_corr)))

                    G_3_new_line.shape = (1, 2 * N_corr)
                    G_3 = np.concatenate((G_3, G_3_new_line), axis=0)
                    small_error = np.array(-1e-3) # TODO: Same tolerance as above
                    small_error.shape = (1, 1)
                    h_3 = np.concatenate((h_3, small_error), axis=0)
                    tot_constraints += 1
                    string = "{}|Added constraint from {}({}) structure on hull, decomp".format(h_3.shape[0], comp_now, i)
                    print(string)
                    constraint_strings.append(string)

                if i % BATCH_SIZE == 0 or i == len(all_corr_in)-1:
                    valid = False
                    const_remove = 0
                    G_t = deepcopy(G_3)
                    h_t = deepcopy(h_3)
                    # Remove constraints until fit works
                    while not valid:
                        sol = solvers.qp(matrix(P), matrix(q), matrix(G_3), matrix(h_3), initvals=init_vals, solver='mosek')
                        if sol['status'] == 'optimal':
                            valid = True
                        else:
                            const_remove += 1
                            G_3 = G_t[:-1 * (const_remove),:]
                            h_3 = h_t[:-1 * (const_remove)]
                            removed_constraints += 1

                    if const_remove > 0:
                        constraint_strings.append("{}|Removed".format(G_t.shape[0] - const_remove + 1))

                    # Add constraints back in one by one and remove if they cause problems
                    for num_new in range(1, const_remove):
                        G_new_line = G_t[-1 * (const_remove - num_new),:]
                        h_new_line = h_t[-1 * (const_remove - num_new)]
                        G_new_line.shape = (1, 2 * N_corr)
                        h_new_line.shape = (1,1)
                        G_3 = np.concatenate((G_3, G_new_line), axis=0)
                        h_3 = np.concatenate((h_3, h_new_line), axis=0)
                        sol = solvers.qp(matrix(P), matrix(q), matrix(G_3), matrix(h_3), initvals=init_vals, solver='mosek')
                        removed_constraints -= 1
                        if sol['status'] != 'optimal':
                            G_3 = G_3[:-1, :]
                            h_3 = h_3[:-1]
                            removed_constraints += 1
                            constraint_strings.append("{}|Removed".format(G_t.shape[0] - const_remove + num_new + 1))
            # Uncomment for iterative saving matricex
            #np.save("G.npy", G_3)
            #np.save("h.npy", h_3)



        # Uncomment for debugging
        #np.save("G.npy", G_3)
        #np.save("h.npy", h_3)

        sol = solvers.qp(matrix(P), matrix(q), matrix(G_3), matrix(h_3), initvals=init_vals, solver='mosek')
        print("Final status: {}".format(sol['status']))
        print("Mu: {}".format(mu))
        print("Constrants: {}/{}".format(tot_constraints - removed_constraints, tot_constraints))
        ecis = np.array(sol['x'])[:N_corr, 0]

        # Uncomment for some debugging info
        #print(ecis)
        #for string in constraint_strings:
        #    print(string)
        return ecis
コード例 #38
0
    def get_phase_diagram_data(self):
        """
        Returns grand potential phase diagram data to external plot
        Assumes openelement specific element equals None
        :return: Data to external plot
        """
        open_elements_specific = None
        open_element_all = Element(self.open_element)
        mpr = MPRester("key")

        # import do dados dos arquivos tipo vasp
        drone = VaspToComputedEntryDrone()
        queen = BorgQueen(drone, rootpath=".")
        entries = queen.get_data()

        # Get data to make phase diagram
        mp_entries = mpr.get_entries_in_chemsys(self.system,
                                                compatible_only=True)

        entries.extend(mp_entries)

        compat = MaterialsProjectCompatibility()
        entries = compat.process_entries(entries)
        #explanation_output = open("explain.txt",'w')
        entries_output = open("entries.txt", 'w')
        compat.explain(entries[0])
        print(entries, file=entries_output)
        #print(entries)

        if open_elements_specific:
            gcpd = GrandPotentialPhaseDiagram(entries, open_elements_specific)
            self.plot_phase_diagram(gcpd, False)
            self.analyze_phase_diagram(gcpd)

        if open_element_all:
            pd = PhaseDiagram(entries)
            chempots = pd.get_transition_chempots(open_element_all)
            #print(chempots)
            #all_gcpds = list()
            toplot = []
            # dic = {}
            for idx in range(len(chempots)):
                if idx == len(chempots) - 1:
                    avgchempot = chempots[idx] - 0.1
                else:
                    avgchempot = 0.5 * (chempots[idx] + chempots[idx + 1])
                gcpd = GrandPotentialPhaseDiagram(
                    entries, {open_element_all: avgchempot}, pd.elements)
                # toplot.append(self.get_grand_potential_phase_diagram(gcpd))

                min_chempot = None if idx == len(chempots) - 1 else chempots[
                    idx + 1]
                max_chempot = chempots[idx]
                #gcpd = GrandPotentialPhaseDiagram(entries, {open_element_all: max_chempot}, pd.elements)

                toplot.append(self.get_grand_potential_phase_diagram(gcpd))
                #toplot.append(max_chempot)

                #self.plot_phase_diagram(gcpd, False)
                #print({open_element_all: max_chempot})

            # Data to plot phase diagram
            return toplot
コード例 #39
0
ファイル: surface_analysis.py プロジェクト: matk86/pymatgen
    def __init__(self, material_id, vasprun_dict, ref_element,
                 exclude_ids=[], custom_entries=[], mapi_key=None):
        """
        Analyzes surface energies and Wulff shape of a particular
            material using the chemical potential.
        Args:
            material_id (str): Materials Project material_id (a string,
                e.g., mp-1234).
            vasprun_dict (dict): Dictionary containing a list of Vaspruns
                for slab calculations as items and the corresponding Miller
                index of the slab as the key.
                eg. vasprun_dict = {(1,1,1): [vasprun_111_1, vasprun_111_2,
                vasprun_111_3], (1,1,0): [vasprun_111_1, vasprun_111_2], ...}
            element: element to be considered as independent
                variables. E.g., if you want to show the stability
                ranges of all Li-Co-O phases wrt to uLi
            exclude_ids (list of material_ids): List of material_ids
                to exclude when obtaining the decomposition components
                to calculate the chemical potential
            custom_entries (list of pymatgen-db type entries): List of
                user specified pymatgen-db type entries to use in finding
                decomposition components for the chemical potential
            mapi_key (str): Materials Project API key for accessing the
                MP database via MPRester
        """

        self.ref_element = ref_element
        self.mprester = MPRester(mapi_key) if mapi_key else MPRester()
        self.ucell_entry = \
            self.mprester.get_entry_by_material_id(material_id,
                                                   inc_structure=True,
                                                   property_data=
                                                   ["formation_energy_per_atom"])
        ucell = self.ucell_entry.structure

        # Get x and y, the number of species in a formula unit of the bulk
        reduced_comp = ucell.composition.reduced_composition.as_dict()
        if len(reduced_comp.keys()) == 1:
            x = y = reduced_comp[ucell[0].species_string]
        else:
            for el in reduced_comp.keys():
                if self.ref_element == el:
                    y = reduced_comp[el]
                else:
                    x = reduced_comp[el]

        # Calculate Gibbs free energy of the bulk per unit formula
        gbulk = self.ucell_entry.energy /\
                (len([site for site in ucell
                      if site.species_string == self.ref_element]) / y)

        entries = [entry for entry in
                   self.mprester.get_entries_in_chemsys(list(reduced_comp.keys()),
                                                        property_data=["e_above_hull",
                                                                       "material_id"])
                   if entry.data["e_above_hull"] == 0 and
                   entry.data["material_id"] not in exclude_ids] \
            if not custom_entries else custom_entries

        pd = PhaseDiagram(entries)
        chempot_ranges = pd.get_chempot_range_map([Element(self.ref_element)])
        # If no chemical potential is found, we return u=0, eg.
        # for a elemental system, the relative u of Cu for Cu is 0
        chempot_range = [chempot_ranges[entry] for entry in chempot_ranges.keys()
                         if entry.composition ==
                         self.ucell_entry.composition][0][0]._coords if \
            chempot_ranges else [[0,0], [0,0]]

        e_of_element = [entry.energy_per_atom for entry in
                        entries if str(entry.composition.reduced_composition)
                        == self.ref_element + "1"][0]

        self.x = x
        self.y = y
        self.gbulk = gbulk
        chempot_range = list(chempot_range)
        self.chempot_range = sorted([chempot_range[0][0], chempot_range[1][0]])
        self.e_of_element = e_of_element
        self.vasprun_dict = vasprun_dict