Beispiel #1
0
    def get_phase_diagram_plot(self):
        """
        Returns a phase diagram plot, as a matplotlib plot object.
        """

        # set the font to Times, rendered with Latex
        plt.rc('font', **{'family': 'serif', 'serif': ['Times']})
        plt.rc('text', usetex=True)

        # parse the composition space endpoints
        endpoints_line = self.lines[0].split()
        endpoints = []
        for word in endpoints_line[::-1]:
            if word == 'endpoints:':
                break
            else:
                endpoints.append(Composition(word))

        if len(endpoints) < 2:
            print('There must be at least 2 endpoint compositions to make a '
                  'phase diagram.')
            quit()

        # parse the compositions and total energies of all the structures
        compositions = []
        total_energies = []
        for i in range(4, len(self.lines)):
            line = self.lines[i].split()
            compositions.append(Composition(line[1]))
            total_energies.append(float(line[2]))

        # make a list of PDEntries
        pdentries = []
        for i in range(len(compositions)):
            pdentries.append(PDEntry(compositions[i], total_energies[i]))

        # make a CompoundPhaseDiagram
        compound_pd = CompoundPhaseDiagram(pdentries, endpoints)

        # make a PhaseDiagramPlotter
        pd_plotter = PDPlotter(compound_pd, show_unstable=50)
        return pd_plotter.get_plot(label_unstable=False)
Beispiel #2
0
def get_plot(
    self,
    show_unstable=0.2,
    label_stable=True,
    label_unstable=True,
    ordering=None,
    energy_colormap=None,
    process_attributes=False,
    label_uncertainties=False,
):
    """
    Plot a PhaseDiagram.

    :param show_unstable: Whether unstable (above the hull) phases will be
        plotted. If a number > 0 is entered, all phases with
        e_hull < show_unstable (eV/atom) will be shown.
    :param label_stable: Whether to label stable compounds.
    :param label_unstable: Whether to label unstable compounds.
    :param ordering: Ordering of vertices (matplotlib backend only).
    :param energy_colormap: Colormap for coloring energy (matplotlib backend only).
    :param process_attributes: Whether to process the attributes (matplotlib
        backend only).
    :param plt: Existing plt object if plotting multiple phase diagrams (
        matplotlib backend only).
    :param label_uncertainties: Whether to add error bars to the hull (plotly
        backend only). For binaries, this also shades the hull with the
        uncertainty window
    """

    plotter = PDPlotter(self, backend="plotly", show_unstable=show_unstable)

    return plotter.get_plot(
        label_stable=label_stable,
        label_unstable=label_unstable,
        ordering=ordering,
        energy_colormap=energy_colormap,
        process_attributes=process_attributes,
        label_uncertainties=label_uncertainties,
    )
Beispiel #3
0
class PDPlotterTest(unittest.TestCase):
    def setUp(self):
        entries = list(
            EntrySet.from_csv(os.path.join(module_dir, "pdentries_test.csv")))

        self.pd_ternary = PhaseDiagram(entries)
        self.plotter_ternary_mpl = PDPlotter(self.pd_ternary,
                                             backend="matplotlib")
        self.plotter_ternary_plotly = PDPlotter(self.pd_ternary,
                                                backend="plotly")

        entrieslio = [e for e in entries if "Fe" not in e.composition]
        self.pd_binary = PhaseDiagram(entrieslio)
        self.plotter_binary_mpl = PDPlotter(self.pd_binary,
                                            backend="matplotlib")
        self.plotter_binary_plotly = PDPlotter(self.pd_binary,
                                               backend="plotly")

        entries.append(PDEntry("C", 0))
        self.pd_quaternary = PhaseDiagram(entries)
        self.plotter_quaternary_mpl = PDPlotter(self.pd_quaternary,
                                                backend="matplotlib")
        self.plotter_quaternary_plotly = PDPlotter(self.pd_quaternary,
                                                   backend="plotly")

    def test_pd_plot_data(self):
        (lines, labels,
         unstable_entries) = self.plotter_ternary_mpl.pd_plot_data
        self.assertEqual(len(lines), 22)
        self.assertEqual(
            len(labels),
            len(self.pd_ternary.stable_entries),
            "Incorrect number of lines generated!",
        )
        self.assertEqual(
            len(unstable_entries),
            len(self.pd_ternary.all_entries) -
            len(self.pd_ternary.stable_entries),
            "Incorrect number of lines generated!",
        )
        (lines, labels,
         unstable_entries) = self.plotter_quaternary_mpl.pd_plot_data
        self.assertEqual(len(lines), 33)
        self.assertEqual(len(labels), len(self.pd_quaternary.stable_entries))
        self.assertEqual(
            len(unstable_entries),
            len(self.pd_quaternary.all_entries) -
            len(self.pd_quaternary.stable_entries),
        )
        (lines, labels,
         unstable_entries) = self.plotter_binary_mpl.pd_plot_data
        self.assertEqual(len(lines), 3)
        self.assertEqual(len(labels), len(self.pd_binary.stable_entries))

    def test_mpl_plots(self):
        # Some very basic ("non")-tests. Just to make sure the methods are callable.
        self.plotter_binary_mpl.get_plot().close()
        self.plotter_ternary_mpl.get_plot().close()
        self.plotter_quaternary_mpl.get_plot().close()
        self.plotter_ternary_mpl.get_contour_pd_plot().close()
        self.plotter_ternary_mpl.get_chempot_range_map_plot(
            [Element("Li"), Element("O")]).close()
        self.plotter_ternary_mpl.plot_element_profile(
            Element("O"), Composition("Li2O")).close()

    def test_plotly_plots(self):
        # Also very basic tests. Ensures callability and 2D vs 3D properties.
        self.plotter_binary_plotly.get_plot()
        self.plotter_ternary_plotly.get_plot()
        self.plotter_quaternary_plotly.get_plot()
Beispiel #4
0
    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()
    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(
    [Element("Na"), Element("Nb")], fontsize=14, plotsize=1.5)
#plt.write_image("chem_pot_{}.png".format('-'.join(system)), "png")
chem_pot_plot.savefig(f'chem_pot_{system_name}.png')  # save figure
# getting plot for PD - variables 'fontsize' for labels size and plotsize for fig size have been added (not present in original pymatgen) to get_plot function
pd_plot = plt.get_plot(label_stable=True, fontsize=24, plotsize=3)

pd_plot.savefig(f'PD_{system_name}.png')
Beispiel #6
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)