Exemplo n.º 1
0
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
Exemplo n.º 2
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
Exemplo n.º 3
0
    def process_item(self, item):
        """
        Process the list of entries into a phase diagram

        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
        """
        entries = self.compatibility.process_entries(item)

        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": {
                        "formation_energy_per_atom":
                        pd.get_form_energy_per_atom(e),
                        "e_above_hull": ehull,
                        "is_stable": e in pd.stable_entries
                    }
                }

                # Logic for if stable or 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)

                docs.append(d)
        except PhaseDiagramError as p:
            print(e.as_dict())
            self.logger.warning("Phase diagram error: {}".format(p))
            return []

        return docs
Exemplo n.º 4
0
def convexHull(entries, tolerance=0, printing=False):
    # This initializes the REST adaptor. You may need to put your own API key in as an arg. If we need MP DB
    #with open("key.txt", "r") as myfile:
    #    data = myfile.readlines()
    #a = MPRester(data[0].rstrip())
    #    entries = a.get_entries_in_chemsys(elems)
    pd2 = PhaseDiagram(entries)
    data = collections.defaultdict(list)
    for e in pd2.stable_entries:
        ehull = pd2.get_equilibrium_reaction_energy(e)
        data["Composition"].append(e.composition.reduced_formula)
        data['NElements'].append(e.attribute)
        data["Ehull"].append(ehull)
        #data['Entropy'].append(e.data)
        #data['Gibbs'].append(ehull)
        data["Decomposition"].append("self")
    for e in pd2.unstable_entries:
        decomp, ehull = pd2.get_decomp_and_e_above_hull(e, allow_negative=True)
        #        decomp, ehull = pd2.get_decomp_and_e_above_hull(e)
        data["Composition"].append(e.composition.reduced_formula)
        data['NElements'].append(e.attribute)
        data["Ehull"].append(ehull)
        #data['Entropy'].append(e.data)
        #data['Gibbs'].append(ehull)
        data["Decomposition"].append(" + ".join([
            "%.2f %s" % (v, k.composition.formula) for k, v in decomp.items()
        ])[0:70])
    df = pd.DataFrame(
        data, columns=["NElements", "Composition", "Ehull", "Decomposition"])
    if printing == True:
        #print(df[df.Ehull<=tolerance])
        print(df)
    return (df)
Exemplo n.º 5
0
def get_ehull(structure_type,
              tot_e,
              species,
              unmix_entries=None,
              all_entries=None,
              debug=False,
              from_mp=False):
    """
    Get Ehull predicted under given total energy and species. The composition
    can be either given by the species dict(for garnet only) or a formula.

    Args:
        structure_type(str): "garnet" or "perovskite"
        tot_e (float): total energy, the unit is in accordance with given
            composition.
        species (dict): species in dictionary.
        unmix_entries (list): additional list of unmix entries.
        all_entries(list): Manually supply the entries whithin the chemical space
        debug(bool): Whether or not to run it in debug mode. (For test only)
        from_mp(bool): Whether or not to query entries from MP (would take long)

    Returns:
        ehull (float): energy above hull.
    """

    formula = spe2form(structure_type, species)
    composition = Composition(formula)
    elements = [i.name for i in composition.elements]
    unmix_entries = [] if unmix_entries is None else unmix_entries
    if not all_entries:
        if from_mp:
            all_entries = m.get_entries_in_chemsys(
                [el.name for el in composition], inc_structure=True)
        else:
            entries_dict = EHULL_ENTRIES[structure_type]
            all_entries = get_entries_in_chemsy(entries_dict, elements)

    all_entries = filter_entries(structure_type, all_entries, species)

    # For unmix: no need to find calc entries, for mix,
    # calc entries were provided through unmix_entries
    # calc_entries_dict = CALC_ENTRIES[structure_type]
    # all_calc_entries = get_entries_in_chemsy(calc_entries_dict, elements)
    # compat = MaterialsProjectCompatibility()
    # all_calc_entries = compat.process_entries(all_calc_entries)
    # if all_calc_entries:
    #     all_entries = all_entries + all_calc_entries

    if not all_entries:
        raise ValueError("Incomplete")
    entry = prepare_entry(structure_type, tot_e, species)
    if debug:
        return entry, all_entries

    phase_diagram = PhaseDiagram(all_entries + [entry] + unmix_entries)

    return phase_diagram.get_decomp_and_e_above_hull(entry)
Exemplo n.º 6
0
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]
Exemplo n.º 7
0
 def get_decomp_entries_and_e_above_hull(self,
                                         entries=None,
                                         exclusions=None,
                                         trypreload=None):
     if not entries:
         entries = self.get_PD_entries(exclusions=exclusions,
                                       trypreload=trypreload)
     pd = PhaseDiagram(entries)
     decomp_entries, hull_energy = pd.get_decomp_and_e_above_hull(self)
     return decomp_entries, hull_energy
Exemplo n.º 8
0
    def from_entries(cls, entries: List[ComputedEntry], sandboxes=None):

        pd = PhaseDiagram(entries)
        sandboxes = sandboxes or ["core"]

        docs = []

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

            d = {
                "material_id":
                e.entry_id,
                "uncorrected_energy_per_atom":
                e.uncorrected_energy / e.composition.num_atoms,
                "energy_per_atom":
                e.uncorrected_energy / e.composition.num_atoms,
                "formation_energy_per_atom":
                pd.get_form_energy_per_atom(e),
                "energy_above_hull":
                ehull,
                "is_stable":
                e in pd.stable_entries,
                "sandboxes":
                sandboxes,
            }

            if "last_updated" in e.data:
                d["last_updated"] = e.data["last_updated"]

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

            d["energy_type"] = e.parameters.get("run_type", "Unknown")
            d["entry_types"] = [e.parameters.get("run_type", "Unknown")]
            d["entries"] = {e.parameters.get("run_type", ""): e}

            for k in ["last_updated"]:
                if k in e.parameters:
                    d[k] = e.parameters[k]
                elif k in e.data:
                    d[k] = e.data[k]

            docs.append(
                ThermoDoc.from_composition(composition=e.composition, **d))

        return docs
Exemplo n.º 9
0
    def test_dim1(self):
        # Ensure that dim 1 PDs can eb 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:
                decomp, ehull = pd.get_decomp_and_e_above_hull(e)
                self.assertGreaterEqual(ehull, 0)
            plotter = PDPlotter(pd)
            lines, stable_entries, unstable_entries = plotter.pd_plot_data
            self.assertEqual(lines[0][1], [0, 0])
Exemplo n.º 10
0
def get_ehull(structure_type,
              tot_e,
              species,
              unmix_entries=None,
              all_entries=None,
              debug=False):
    """
    Get Ehull predicted under given total energy and species. The composition
    can be either given by the species dict(for garnet only) or a formula.

    Args:
        tot_e (float): total energy, the unit is in accordance with given
            composition.
        species (dict): species in dictionary.
        unmix_entries (list): additional list of unmix entries.

    Returns:
        ehull (float): energy above hull.
    """
    formula = spe2form(structure_type, species)
    composition = Composition(formula)
    unmix_entries = [] if unmix_entries is None else unmix_entries

    if not all_entries:
        all_entries = m.get_entries_in_chemsys([el.name for el in composition],
                                               inc_structure=True)
    all_entries = filter_entries(structure_type, all_entries, species)

    all_calc_entries = [e for e in CALC_ENTRIES[structure_type]
                        if set(e.composition).issubset(set(composition)) \
                        and e.name != composition.reduced_formula]

    if all_calc_entries:
        all_entries = all_entries + all_calc_entries

    compat = MaterialsProjectCompatibility()
    all_entries = compat.process_entries(all_entries)

    if not all_entries:
        raise ValueError("Incomplete")
    entry = prepare_entry(structure_type, tot_e, species)

    if debug:
        return entry, all_entries

    phase_diagram = PhaseDiagram(all_entries + [entry] + unmix_entries)

    return phase_diagram.get_decomp_and_e_above_hull(entry)
Exemplo n.º 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)
Exemplo n.º 12
0
    def present(self,
                df=None,
                new_result_ids=None,
                all_result_ids=None,
                filename=None,
                save_hull_distance=False,
                finalize=False):
        """
        Generate plots of convex hulls for each of the runs

        Args:
            df (DataFrame): dataframe with formation energies, compositions, ids
            new_result_ids ([]): list of new result ids (i. e. indexes
                in the updated dataframe)
            all_result_ids ([]): list of all result ids associated
                with the current run
            filename (str): filename to output, if None, no file output
                is produced

        Returns:
            (pyplot): plotter instance
        """
        df = df if df is not None else self.df
        new_result_ids = new_result_ids if new_result_ids is not None \
            else self.new_result_ids
        all_result_ids = all_result_ids if all_result_ids is not None \
            else self.all_result_ids

        # TODO: consolidate duplicated code here
        # Generate all entries
        comps = df.loc[all_result_ids]['Composition'].dropna()
        system_elements = []
        for comp in comps:
            system_elements += list(Composition(comp).as_dict().keys())
        elems = set(system_elements)
        if len(elems) > 4:
            warnings.warn(
                "Number of elements too high for phase diagram plotting")
            return None
        ind_to_include = []
        for ind in df.index:
            if set(Composition(
                    df.loc[ind]['Composition']).as_dict().keys()).issubset(
                        elems):
                ind_to_include.append(ind)
        _df = df.loc[ind_to_include]

        # Create computed entry column
        _df['entry'] = [
            ComputedEntry(
                Composition(row['Composition']),
                row['delta_e'] * Composition(
                    row['Composition']).num_atoms,  # un-normalize the energy
                entry_id=index) for index, row in _df.iterrows()
        ]
        # Partition ids into sets of prior to CAMD run, from CAMD but prior to
        # current iteration, and new ids
        ids_prior_to_camd = list(set(_df.index) - set(all_result_ids))
        ids_prior_to_run = list(set(all_result_ids) - set(new_result_ids))

        # Create phase diagram based on everything prior to current run
        entries = list(_df.loc[ids_prior_to_run + ids_prior_to_camd]['entry'])
        # Filter for nans by checking if it's a computed entry
        entries = [
            entry for entry in entries if isinstance(entry, ComputedEntry)
        ]
        pg_elements = [Element(el) for el in sorted(elems)]
        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, **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 _df.index
        ]

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

        for entry in _df['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()

        if filename is not None and save_hull_distance:
            if self.stabilities is None:
                print("ERROR: No stability information in analyzer.")
                return None
            with open(filename.split(".")[0] + '.json', 'w') as f:
                json.dump(self.stabilities, f)
Exemplo n.º 13
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

        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)

    def test_read_json(self):
        with ScratchDir("."):
            dumpfn(self.pd, "pd.json")
            loadfn("pd.json")
Exemplo n.º 14
0
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)
Exemplo n.º 15
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
Exemplo n.º 16
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
Exemplo n.º 17
0
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)