def parse_composition(structure_type, s, ctype): toks = s.strip().split() if len(toks) == 1: c = Composition({toks[0].split(":")[0]: 1}) else: c = Composition( {t.split(":")[0]: float(t.split(":")[1]) for t in toks}) c = Composition({k2: v2 / sum(c.values()) for k2, v2 in c.items()}) if len(c) != 2: raise ValueError("Bad composition on %s." % ctype) frac = [c.get_atomic_fraction(k) for k in c.keys()] if structure_type == 'garnet': if ctype == "A": if abs(frac[0] - 0.5) > 0.01: raise ValueError("Bad composition on %s. " "Only 1:1 mixing allowed!" % ctype) elif ctype in ["C", "D"]: if not (abs(frac[0] - 1.0 / 3) < 0.01 or abs(frac[1] - 1.0 / 3) < 0.01): raise ValueError("Bad composition on %s. " "Only 2:1 mixing allowed!" % ctype) elif structure_type == 'perovskite': if abs(frac[0] - 0.5) > 0.01: raise ValueError("Bad composition on %s. " "Only 1:1 mixing allowed!" % ctype) try: for k in c.keys(): k.oxi_state if k not in ELS[structure_type][ctype]: raise ValueError("%s is not a valid species for %s site." % (k, ctype)) except AttributeError: raise ValueError("Oxidation states must be specified for all species!") return c
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()
def __getitem__(self, idx): """ Returns ------- atom_weights: torch.Tensor shape (M, 1) weights of atoms in the material atom_fea: torch.Tensor shape (M, n_fea) features of atoms in the material self_fea_idx: torch.Tensor shape (M*M, 1) list of self indices nbr_fea_idx: torch.Tensor shape (M*M, 1) list of neighbor indices target: torch.Tensor shape (1,) target value for material cry_id: torch.Tensor shape (1,) input id for the material """ cry_id, composition, target = self.df.iloc[idx] comp_dict = Composition(composition).get_el_amt_dict() elements = list(comp_dict.keys()) weights = list(comp_dict.values()) weights = np.atleast_2d(weights).T / np.sum(weights) assert len(elements) != 1, f"cry-id {cry_id} [{composition}] is a pure system" try: atom_fea = np.vstack( [self.elem_features.get_fea(element) for element in elements] ) except AssertionError: raise AssertionError( f"cry-id {cry_id} [{composition}] contains element types not in embedding" ) except ValueError: raise ValueError( f"cry-id {cry_id} [{composition}] composition cannot be parsed into elements" ) env_idx = list(range(len(elements))) self_fea_idx = [] nbr_fea_idx = [] nbrs = len(elements) - 1 for i, _ in enumerate(elements): self_fea_idx += [i] * nbrs nbr_fea_idx += env_idx[:i] + env_idx[i + 1 :] # convert all data to tensors atom_weights = torch.Tensor(weights) atom_fea = torch.Tensor(atom_fea) self_fea_idx = torch.LongTensor(self_fea_idx) nbr_fea_idx = torch.LongTensor(nbr_fea_idx) if self.task == "regression": targets = torch.Tensor([float(target)]) elif self.task == "classification": targets = torch.LongTensor([target]) return ( (atom_weights, atom_fea, self_fea_idx, nbr_fea_idx), targets, composition, cry_id, )