def chemical_spaces_and_subspaces(self): """ Args: Returns: list of unique chemical spaces (tuple) """ compounds = self.compounds return list(set([tuple(CompAnalyzer(c).els) for c in compounds]))
def A_for_decomp_solver(self, compound, competing_compounds): """ Args: compound (str) - the compound (str) to analyze competing_compounds (list) - list of compounds (str) that may participate in the decomposition reaction for the input compound Returns: matrix (2D array) of elemental amounts (float) used for implementing molar conservation during decomposition solution """ chemical_space = tuple(CompAnalyzer(compound).els) atoms_per_fu = [ CompAnalyzer(c).num_atoms_in_formula() for c in competing_compounds ] A = self.amts_matrix(competing_compounds, chemical_space) for row in range(len(competing_compounds)): for col in range(len(chemical_space)): A[row, col] *= atoms_per_fu[row] return A.T
def __init__(self, hull_data, chemical_space): """ Args: hull_data (dict) - dictionary generated in GetHullInputData().hull_data chemical_space (str) - chemical space to analyze in 'el1_el2_...' (alphabetized) format Returns: grabs only the relevant sub-dict from hull_data changes chemical space to tuple (el1, el2, ...) """ hull_data = hull_data[chemical_space] # below is unnecessary except for this one case where I dont want to regenerate a hullin.json keys_to_remove = [ k for k in hull_data if CompAnalyzer(k).num_els_in_formula == 1 ] hull_data = { k: hull_data[k] for k in hull_data if k not in keys_to_remove } # keys_to_remove = [k for k in keys_to_remove # if ('1' in k) or # ('2' in k) or # ('3' in k) or # ('4' in k) or # ('5' in k) or # ('6' in k) or # ('7' in k) or # ('8' in k) or # ('9' in k) or # ('0' in k)] # self.hull_data = {k : tmp_hull_data[k] for k in tmp_hull_data if k not in keys_to_remove} els = chemical_space.split('_') for el in els: hull_data[el] = { 'E': 0, 'amts': { els[i]: CompAnalyzer(el).fractional_amt_of_el(els[i]) for i in range(len(els)) } } self.hull_data = hull_data #print(self.hull_data) self.chemical_space = tuple(els)
def decomp_energy(self, compound): """ Args: compound (str) - the compound (str) to analyze Returns: decomposition energy (float) """ hull_data = self.hull_data decomp_products = self.decomp_products(compound) if isinstance(decomp_products, float): return np.nan decomp_enthalpy = 0 for k in decomp_products: decomp_enthalpy += decomp_products[k]['amt'] * decomp_products[k][ 'E'] * CompAnalyzer(k).num_atoms_in_formula() return (hull_data[compound]['E'] * CompAnalyzer(compound).num_atoms_in_formula() - decomp_enthalpy ) / CompAnalyzer(compound).num_atoms_in_formula()
def _kJmol_eVat(E, formula): """ Args: E (float) - energy in kJ/mol formula (str) Returns: E (float) in eV/atom """ natoms = CompAnalyzer(formula).num_atoms_in_formula() return E / natoms / 96.485
def A(self): input_data = self.input_data relevant_els = self._relevant_els sorted_formulas = self._sorted_formulas A = np.zeros((len(relevant_els), len(input_data))) for row in range(len(relevant_els)): el = relevant_els[row] for col in range(len(input_data)): formula = sorted_formulas[col] A[row, col] = CompAnalyzer(formula).amt_of_el(el) return A
def cmpd_to_pt(cmpd, els): """ Args: cmpd (str) - chemical formula els (list) - ordered list of elements (str) in triangle (right, top, left) Returns: (x, y) for compound """ tri = [CompAnalyzer(cmpd).fractional_amt_of_el(el) for el in els] return triangle_to_square(tri)
def _eVat_kJmol(E, formula): """ Args: E (float) - energy in eV/atom formula (str) Returns: E (float) in kJ/mol """ natoms = CompAnalyzer(formula).num_atoms_in_formula() return E * natoms * 96.485
def __init__(self, compound_to_energy, formation_energy_key): """ Args: compound_to_energy (dict) - {formula (str) : {formation_energy_key (str) : formation energy (float)}} formation_energy_key (str) - key within compound_to_energy to use for formation energy Returns: dictionary of {formula (str) : formation energy (float)} """ self.compound_to_energy = {k : compound_to_energy[k][formation_energy_key] for k in compound_to_energy if CompAnalyzer(k).num_els_in_formula > 1}
def A(self): input_data = self.input_data relevant_els = self._relevant_els sorted_formulas = self._sorted_formulas formulas = [f for f in sorted_formulas if f not in self.excluded] A = np.zeros((len(relevant_els), len(formulas))) for row in range(len(relevant_els)): el = relevant_els[row] for col in range(len(formulas)): formula = formulas[col] A[row, col] = CompAnalyzer(formula).amt_of_el(el) return A
def Es_for_decomp_solver(self, compound, competing_compounds): """ Args: compound (str) - the compound (str) to analyze competing_compounds (list) - list of compounds (str) that may participate in the decomposition reaction for the input compound Returns: array of formation energies per formula unit (float) used for minimization problem during decomposition solution """ atoms_per_fu = [CompAnalyzer(c).num_atoms_in_formula() for c in competing_compounds] Es_per_atom = self.formation_energy_array(competing_compounds) return [Es_per_atom[i]*atoms_per_fu[i] for i in range(len(competing_compounds))]
def hull_data(self, fjson=False, remake=False): """ Args: fjson (str) - file name to write hull data to remake (bool) - if True, write json; if False, read json Returns: dict of {chemical space (str) : {formula (str) : {'E' : formation energy (float), 'amts' : {el (str) : fractional amt of el in formula (float) for el in space}} for all relevant formulas including elements} elements are automatically given formation energy = 0 chemical space is now in 'el1_el2_...' format to be jsonable """ if not fjson: fjson = 'hull_input_data.json' if (remake == True) or not os.path.exists(fjson): hull_data = {} hull_spaces = self.hull_spaces() compounds = self.compounds compound_to_energy = self.compound_to_energy for space in hull_spaces: for el in space: compound_to_energy[el] = 0 relevant_compounds = [ c for c in compounds if set(CompAnalyzer(c).els).issubset(set(space)) ] + list(space) hull_data['_'.join(list(space))] = { c: { 'E': compound_to_energy[c], 'amts': { el: CompAnalyzer(c).fractional_amt_of_el(el=el) for el in space } } for c in relevant_compounds } return write_json(hull_data, fjson) else: return read_json(fjson)
def competing_compounds(self, compound): """ Args: compound (str) - the compound (str) to analyze Returns: list of compounds (str) that may participate in the decomposition reaction for the input compound """ compounds = self.sorted_compounds if compound in self.unstable_compounds: compounds = self.stable_compounds competing_compounds = [c for c in compounds if c != compound if set(CompAnalyzer(c).els).issubset(CompAnalyzer(compound).els)] return competing_compounds
def hull_output_data(self): """ Args: compound (str) - formula to get data for Returns: hull_output_data but only for single compound """ gppd = self._gppd entries = gppd.all_entries stable_entries = list(gppd.stable_entries) el_refs = list(gppd.el_refs.values()) data = {} for e in entries + el_refs: print(e) stability = True if ((e in stable_entries) or (e in el_refs)) else False original_cmpd = CompAnalyzer(e.original_comp.formula).std_formula() print(original_cmpd) cmpd = CompAnalyzer(e.composition.formula).std_formula() print(cmpd) if CompAnalyzer(cmpd).num_els_in_formula == 1: phid = None decomp = None rxn = None else: phid = gppd.get_equilibrium_reaction_energy( e) if stability else gppd.get_e_above_hull(e) decomp = gppd.get_decomposition(e.composition) rxn = _convert_decomp_to_rxn(decomp) data[original_cmpd] = { 'stability': stability, 'phid': phid, 'rxn': rxn, 'entry': e.as_dict() } return data
def b(self): input_data = self.input_data relevant_els = self._relevant_els b = np.zeros((len(relevant_els))) sorted_formulas = self._sorted_formulas for i in range(len(relevant_els)): el = relevant_els[i] amt = 0 for formula in sorted_formulas: amt += input_data[formula]['amt'] * CompAnalyzer( formula).amt_of_el(el) b[i] = amt return b
def A(self): r, p = self.reactants, self.products balance_els = self.balance_els A = np.zeros(shape=(len(r) + len(p), len(balance_els) + len(p))) A = A.T for i in range(len(balance_els)): count = 0 for j in range(len(r) + len(p)): count += 1 if count <= len(r): sign = 1 cmpd = r[j] else: sign = -1 cmpd = p[j - len(r)] A[i, j] = sign * CompAnalyzer(cmpd).amt_of_el(balance_els[i]) line = [0 for i in range(len(r))] for j in range(len(p)): line.append( np.sum( [CompAnalyzer(p[j]).amt_of_el(el) for el in balance_els])) A[-1] = line return np.array(A)
def test_hull_analysis_against_MP(self): """ Uses an MP query of the Al-Ca-Mg-O-Si space Compares my decomp energies to theirs Changes mine to 0 for stable compounds to force agreement with theirs """ d = read_json(os.path.join(test_data_dir, 'MP_Ca-Mg-Al-Si-O.json')) data_for_hulls = {CompAnalyzer(d[k]['pretty_formula']).std_formula() : {'E' : d[k]['formation_energy_per_atom']} for k in d if d[k]['is_min_ID'] == True} obj = GetHullInputData(data_for_hulls, 'E') fjson = os.path.join(test_data_dir, '_'.join(['hulls', 'Ca-Mg-Al-Si-O.json'])) hull_data = obj.hull_data(fjson, True) for chemical_space in hull_data: obj = AnalyzeHull(hull_data, chemical_space) hull_results = obj.hull_output_data for ID in d: if d[ID]['is_min_ID'] == 1: cmpd = CompAnalyzer(d[ID]['pretty_formula']).std_formula() Ed_MP = d[ID]['e_above_hull'] Ed_me = hull_results[cmpd]['Ed'] # MP shows all stables as Hd = 0 if Ed_me < 0: Ed_me = 0 self.assertAlmostEqual(Ed_me, Ed_MP, places=3)
def feed(self): """ Returns: {formula (str) : # moles in feed (float)} """ interface = self.interface n_species = interface.count('|') + 1 items = interface.split('|') return { CompAnalyzer(item.split('_')[1]).std_formula(): float(item.split('_')[0]) for item in items }
def get_label(cmpd, els): """ Args: cmpd (str) - chemical formula els (list) - ordered list of elements (str) as you want them to appear in label Returns: neatly formatted chemical formula label """ label = r'$' for el in els: amt = CompAnalyzer(cmpd).amt_of_el(el) if amt == 0: continue label += el if amt == 1: continue label += '_{%s}' % amt label += '$' return label
def rxn(self): order = self.el_order_for_rxns T = self.temp species = self.species rxn = r'' reactants = [s for s in species if species[s]['side'] == 'left'] products = [s for s in species if species[s]['side'] == 'right'] if not order: order = [CompAnalyzer(c).els for c in reactants + products] order = [j for i in order for j in i] order = sorted(list(set(order))) count = 0 for r in reactants: amt = species[r]['amt'] if amt == 0: continue if amt != 1: rxn += str(np.round(amt, 2)) rxn += get_label(r, order) count += 1 if count < len(reactants): rxn += '+' rxn += ' --> ' count = 0 for p in products: amt = species[p]['amt'] if amt == 0: continue if amt != 1: rxn += str(np.round(amt, 2)) rxn += get_label(p, order) count += 1 if count < len(products): rxn += '+' rxn += r'' rxn = rxn.replace('$', '').replace('{', '').replace('}', '').replace( '_', '').replace('+', ' + ') return rxn
def make_data(remake=False): fjson = '_data_for_RxnEngr.json' if not remake and os.path.exists(fjson): return read_json(fjson) data_file = '/Users/chrisbartel/Dropbox/postdoc/projects/synthesis/paperdb/data/mp/MP_stability.json' data = read_json(data_file) my_els = ['Li', 'Co', 'Ba', 'Ti', 'Y', 'Ba', 'Cu', 'C', 'O'] cmpds = sorted(list(data['0'].keys())) relevant_cmpds = [ c for c in cmpds if set(CompAnalyzer(c).els).issubset(set(sorted(my_els))) ] d = {T: {} for T in data} for c in relevant_cmpds: for T in d: if c in data[T]: d[T][c] = data[T][c] return write_json(d, fjson)
def decomp_solution(self, compound): """ Args: compound (str) - the compound (str) to analyze Returns: scipy.optimize.minimize result for finding the linear combination of competing compounds that minimizes the competing formation energy """ competing_compounds = self.competing_compounds(compound) A = self.A_for_decomp_solver(compound, competing_compounds) b = self.b_for_decomp_solver(compound, competing_compounds) Es = self.Es_for_decomp_solver(compound, competing_compounds) n0 = [0.01 for c in competing_compounds] max_bound = CompAnalyzer(compound).num_atoms_in_formula() bounds = [(0, max_bound) for c in competing_compounds] def competing_formation_energy(nj): nj = np.array(nj) return np.dot(nj, Es) constraints = [{'type': 'eq', 'fun': lambda x: np.dot(A, x) - b}] tol, maxiter, disp = 1e-4, 1000, False for tol in [1e-4, 1e-3, 5e-3, 1e-2]: solution = minimize(competing_formation_energy, n0, method='SLSQP', bounds=bounds, constraints=constraints, tol=tol, options={ 'maxiter': maxiter, 'disp': disp }) if solution.success: return solution return solution
def test_fractional_amts(self): formula = 'O2 Ti O' answer = [3 / 4, 1 / 4] self.assertEqual(CompAnalyzer(formula).fractional_amts, answer)
def all_els(self): r, p = self.reactants, self.products els = [CompAnalyzer(c).els for c in r + p] els = [j for i in els for j in i] return sorted(list(set(els)))
def test_frac_amt_of_el_when_el_is_there(self): formula = 'Al2O3' el = 'Al' answer = 0.4 self.assertEqual( CompAnalyzer(formula).fractional_amt_of_el(el=el), answer)
def _label_pts(self, el_order_for_label, pt_label_size=16, label_unstable=False, specify_labels={}, only_certain_labels=[], skip_certain_labels=[]): """ Args: el_order_for_label (list) - ordered list of elements for labels pt_label_size (int) - font size for cmpd labels label_unstable (bool) - whether or not to label unstable points specify_labels (dict) - specific positions for certain compounds {cmpd : {'xpos' : , 'ypos' : , 'xalign' : , 'yalign' : }} only_certain_labels (list) - enforce only these compounds are labeled skip_certain_labels (list) - skip these labels Returns: labels the compounds """ input_data = self.input_data stable_cmpds = [ c for c in input_data if input_data[c]['stability'] if len(CompAnalyzer(c).els) > 1 ] unstable_cmpds = [ c for c in input_data if not input_data[c]['stability'] ] if label_unstable: cmpds_to_label = stable_cmpds + unstable_cmpds else: cmpds_to_label = stable_cmpds if len(only_certain_labels) > 0: cmpds_to_label = only_certain_labels cmpds_to_label = [ c for c in cmpds_to_label if c not in skip_certain_labels ] count = 0 for cmpd in cmpds_to_label: pt = cmpd_to_pt(cmpd, self.els) tri = [ CompAnalyzer(cmpd).fractional_amt_of_el(el) for el in self.els ] label = get_label(cmpd, el_order_for_label) if cmpd in specify_labels: xpos, xalign, ypos, yalign = [ specify_labels[cmpd][k] for k in ['xpos', 'xalign', 'ypos', 'yalign'] ] elif tri[1] in (0, 1): xpos = pt[0] xalign = 'center' ypos = -0.02 yalign = 'top' elif tri[0] in (0, 1): xpos = pt[0] - 0.02 xalign = 'right' ypos = pt[1] yalign = 'center' elif tri[2] in (0, 1): xpos = pt[0] + 0.02 xalign = 'left' ypos = pt[1] yalign = 'center' else: if count % 2: x_sign = 1 y_sign = -1 xalign = 'left' yalign = 'top' else: x_sign, y_sign, xalign, yalign = -1, 1, 'right', 'bottom' xpos = pt[0] + x_sign * 0.01 ypos = pt[1] + y_sign * 0.01 count += 1 if cmpd in stable_cmpds: color = params()['stable']['c'] else: color = params()['unstable']['c'] ax = plt.text(xpos, ypos, label, horizontalalignment=xalign, verticalalignment=yalign, fontsize=pt_label_size, color=color, zorder=100)
def _entries(self): d = self.compound_to_total_energy_per_atom return [ PDEntry(c, d[c] * CompAnalyzer(c).num_atoms_in_formula()) for c in d ]
def _relevant_els(self): cmpds = self.input_data.keys() els = [CompAnalyzer(c).els for c in cmpds] return sorted(list(set([j for i in els for j in i])))
def _eV_to_kJ(formula, eV_at): return 96.485 * eV_at * CompAnalyzer(formula).num_atoms_in_formula()
def test_amt_of_el_when_el_is_there(self): formula = 'Al2O3' el = 'Al' answer = 2 self.assertEqual(CompAnalyzer(formula).amt_of_el(el=el), answer)