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
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
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
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)