def get_integer_formula_and_factor(self, max_denominator=10000): """ Calculates an integer formula and factor. Args: max_denominator (int): all amounts in the el:amt dict are first converted to a Fraction with this maximum denominator Returns: A pretty normalized formula and a multiplicative factor, i.e., Li0.5O0.25 returns (Li2O, 0.25). O0.25 returns (O2, 0.125) """ vals = [ Fraction(v).limit_denominator(max_denominator) for v in self.values() ] denom = lcm(*(f.denominator for f in vals)) mul = gcd(*[int(v * denom) for v in vals]) d = { k: round(v / mul * denom) for k, v in self.get_el_amt_dict().items() } (formula, factor) = reduce_formula(d) if formula in Composition.special_formulas: formula = Composition.special_formulas[formula] factor /= 2 return formula, factor * mul / denom
def reduce_formula(sym_amt): syms = sorted(sym_amt.keys(), key=lambda s: Element(s).X) syms = list(filter( lambda s: abs(sym_amt[s]) > Composition.amount_tolerance, syms )) num_el = len(syms) contains_polyanion = ( num_el >= 3 and Element(syms[num_el-1]).X - Element(syms[num_el-2]).X < 1.65 ) factor = abs(gcd(*sym_amt.values())) reduced_form = [] n = num_el-2 if contains_polyanion else num_el for i in range(0, n): s = syms[i] normamt = sym_amt[s] * 1.0 / factor reduced_form.append(s) reduced_form.append(formula_double_format(normamt)) if contains_polyanion: poly_sym_amt = { syms[i]: sym_amt[syms[i]] / factor for i in range(n, num_el) } (poly_form, poly_factor) = reduce_formula(poly_sym_amt) if poly_factor != 1: reduced_form.append("({}){}".format(poly_form, int(poly_factor))) else: reduced_form.append(poly_form) reduced_form = "".join(reduced_form) return reduced_form, factor
def reduce_formula(sym_amt): syms = sorted(sym_amt.keys(), key=lambda s: Element(s).X) syms = list( filter(lambda s: abs(sym_amt[s]) > Composition.amount_tolerance, syms)) num_el = len(syms) contains_polyanion = ( num_el >= 3 and Element(syms[num_el - 1]).X - Element(syms[num_el - 2]).X < 1.65) factor = abs(gcd(*sym_amt.values())) reduced_form = [] n = num_el - 2 if contains_polyanion else num_el for i in range(0, n): s = syms[i] normamt = sym_amt[s] * 1.0 / factor reduced_form.append(s) reduced_form.append(formula_double_format(normamt)) if contains_polyanion: poly_sym_amt = { syms[i]: sym_amt[syms[i]] / factor for i in range(n, num_el) } (poly_form, poly_factor) = reduce_formula(poly_sym_amt) if poly_factor != 1: reduced_form.append("({}){}".format(poly_form, int(poly_factor))) else: reduced_form.append(poly_form) reduced_form = "".join(reduced_form) return reduced_form, factor
def get_integer_formula_and_factor(self, max_denominator=10000): mul = gcd(*[ Fraction(v).limit_denominator(max_denominator) for v in self._elmap.values() ]) sym_amt = self.get_el_amt_dict() d = { k: round(v/mul) for k,v in sym_amt.items() } (formula, factor) = reduce_formula(d) if formula in Composition.special_formulas: formula = Composition.special_formulas[formula] factor /= 2 return formula, factor * mul
def reduce_formula(sym_amt, iupac_ordering=False): """ Helper method to reduce a sym_amt dict to a reduced formula and factor. Args: sym_amt (dict): {symbol: amount}. iupac_ordering (bool, optional): Whether to order the formula by the iupac "electronegativity" series, defined in Table VI of "Nomenclature of Inorganic Chemistry (IUPAC Recommendations 2005)". This ordering effectively follows the groups and rows of the periodic table, except the Lanthanides, Actanides and hydrogen. Note that polyanions will still be determined based on the true electronegativity of the elements. Returns: (reduced_formula, factor). """ syms = sorted(sym_amt.keys(), key=lambda x: [get_el_sp(x).X, x]) syms = list(filter( lambda x: abs(sym_amt[x]) > Composition.amount_tolerance, syms)) factor = 1 # Enforce integers for doing gcd. if all((int(i) == i for i in sym_amt.values())): factor = abs(gcd(*(int(i) for i in sym_amt.values()))) polyanion = [] # if the composition contains a poly anion if len(syms) >= 3 and get_el_sp(syms[-1]).X - get_el_sp(syms[-2]).X < 1.65: poly_sym_amt = {syms[i]: sym_amt[syms[i]] / factor for i in [-2, -1]} (poly_form, poly_factor) = reduce_formula( poly_sym_amt, iupac_ordering=iupac_ordering) if poly_factor != 1: polyanion.append("({}){}".format(poly_form, int(poly_factor))) syms = syms[:len(syms) - 2 if polyanion else len(syms)] if iupac_ordering: syms = sorted(syms, key=lambda x: [get_el_sp(x).iupac_ordering, x]) reduced_form = [] for s in syms: normamt = sym_amt[s] * 1.0 / factor reduced_form.append(s) reduced_form.append(formula_double_format(normamt)) reduced_form = "".join(reduced_form + polyanion) return reduced_form, factor
def get_integer_formula_and_factor(self, max_denominator=10000): mul = gcd(*[ Fraction(v).limit_denominator(max_denominator) for v in self._elmap.values() ]) sym_amt = self.get_el_amt_dict() d = {k: round(v / mul) for k, v in sym_amt.items()} (formula, factor) = reduce_formula(d) if formula in Composition.special_formulas: formula = Composition.special_formulas[formula] factor /= 2 return formula, factor * mul
def reduce_formula(sym_amt): """ Helper method to reduce a sym_amt dict to a reduced formula and factor. Args: sym_amt (dict): {symbol: amount}. Returns: (reduced_formula, factor). """ syms = sorted(sym_amt.keys(), key=lambda s: get_el_sp(s).X) syms = list( filter(lambda s: abs(sym_amt[s]) > Composition.amount_tolerance, syms)) num_el = len(syms) contains_polyanion = ( num_el >= 3 and get_el_sp(syms[num_el - 1]).X - get_el_sp(syms[num_el - 2]).X < 1.65) factor = 1 # Enforce integers for doing gcd. if all((int(i) == i for i in sym_amt.values())): factor = abs(gcd(*(int(i) for i in sym_amt.values()))) reduced_form = [] n = num_el - 2 if contains_polyanion else num_el for i in range(0, n): s = syms[i] normamt = sym_amt[s] * 1.0 / factor reduced_form.append(s) reduced_form.append(formula_double_format(normamt)) if contains_polyanion: poly_sym_amt = { syms[i]: sym_amt[syms[i]] / factor for i in range(n, num_el) } (poly_form, poly_factor) = reduce_formula(poly_sym_amt) if poly_factor != 1: reduced_form.append("({}){}".format(poly_form, int(poly_factor))) else: reduced_form.append(poly_form) reduced_form = "".join(reduced_form) return reduced_form, factor
def reduce_formula(sym_amt): """ Helper method to reduce a sym_amt dict to a reduced formula and factor. Args: sym_amt (dict): {symbol: amount}. Returns: (reduced_formula, factor). """ syms = sorted(sym_amt.keys(), key=lambda s: get_el_sp(s).X) syms = list(filter(lambda s: abs(sym_amt[s]) > Composition.amount_tolerance, syms)) num_el = len(syms) contains_polyanion = (num_el >= 3 and get_el_sp(syms[num_el - 1]).X - get_el_sp(syms[num_el - 2]).X < 1.65) factor = 1 # Enforce integers for doing gcd. if all((int(i) == i for i in sym_amt.values())): factor = abs(gcd(*(int(i) for i in sym_amt.values()))) reduced_form = [] n = num_el - 2 if contains_polyanion else num_el for i in range(0, n): s = syms[i] normamt = sym_amt[s] * 1.0 / factor reduced_form.append(s) reduced_form.append(formula_double_format(normamt)) if contains_polyanion: poly_sym_amt = {syms[i]: sym_amt[syms[i]] / factor for i in range(n, num_el)} (poly_form, poly_factor) = reduce_formula(poly_sym_amt) if poly_factor != 1: reduced_form.append("({}){}".format(poly_form, int(poly_factor))) else: reduced_form.append(poly_form) reduced_form = "".join(reduced_form) return reduced_form, factor
def get_integer_formula_and_factor(self, max_denominator=10000): """ Calculates an integer formula and factor. Args: max_denominator (int): all amounts in the el:amt dict are first converted to a Fraction with this maximum denominator Returns: A pretty normalized formula and a multiplicative factor, i.e., Li0.5O0.25 returns (Li2O, 0.25). O0.25 returns (O2, 0.125) """ mul = gcd(*[Fraction(v).limit_denominator(max_denominator) for v in self.values()]) d = {k: round(v / mul) for k, v in self.get_el_amt_dict().items()} (formula, factor) = reduce_formula(d) if formula in Composition.special_formulas: formula = Composition.special_formulas[formula] factor /= 2 return formula, factor * mul
def anonymized_formula(self) -> str: """ An anonymized formula. Unique species are arranged in ordering of increasing amounts and assigned ascending alphabets. Useful for prototyping formulas. For example, all stoichiometric perovskites have anonymized_formula ABC3. """ reduced = self.element_composition if all(x == int(x) for x in self.values()): reduced /= gcd(*(int(i) for i in self.values())) anon = "" for e, amt in zip(string.ascii_uppercase, sorted(reduced.values())): if amt == 1: amt_str = "" elif abs(amt % 1) < 1e-8: amt_str = str(int(amt)) else: amt_str = str(amt) anon += f"{e}{amt_str}" return anon
def anonymized_formula(self): """ An anonymized formula. Unique species are arranged in ordering of increasing amounts and assigned ascending alphabets. Useful for prototyping formulas. For example, all stoichiometric perovskites have anonymized_formula ABC3. """ reduced = self.element_composition if all(x == int(x) for x in self._elmap.values()): reduced /= gcd(*self._elmap.values()) anon = "" for e, amt in zip(string.ascii_uppercase, sorted(reduced.values())): if amt == 1: amt_str = "" elif abs(amt % 1) < 1e-8: amt_str = str(int(amt)) else: amt_str = str(amt) anon += ("{}{}".format(e, amt_str)) return anon
def test_gcd(self): self.assertEqual(gcd(7, 14, 63), 7)
def solute_site_preference_finder( structure, e0, T, vac_defs, antisite_defs, solute_defs, solute_concen=0.01, trial_chem_pot = None): """ Compute the solute defect densities using dilute solution model. Args: structure: pymatgen.core.structure.Structure object representing the primitive or unitcell of the crystal. e0: The total energy of the undefected system. This is E0 from VASP calculation. T: Temperature in Kelvin vac_defs: List of vacancy defect parameters in the dictionary format. The keys of the dict associated with each vacancy defect are 1) site_index, 2) site_specie, 3) site_multiplicity, and 4) energy. 1-3 can be obtained from pymatgen.analysis.defects.point_defects.Vacancy class. Site index is expected to start with 1 (fortran index). antisite_defs: List of antisite defect parameters in the dictionary format. The keys of the dict associated with each antisite defect are 1) site_index, 2) site_specie, 3) site_multiplicity, 4) substitution_specie, and 5) energy. 1-3 can be obtained from pymatgen.analysis.defects.point_defects.Vacancy class. solute_defs: List of solute defect parameters in the dictionary format. Similary to that of antisite defs, wtih solute specie specified in substitution_specie solute_concen: Solute concentration (in fractional value) trial_chem_pot: Trial chemical potentials to speedup the plot generation. Format is {el1:mu1,...} Returns: plot_data: The data for plotting the solute defect concentration. """ if not check_input(vac_defs): raise ValueError('Vacancy energy is not defined') if not check_input(antisite_defs): raise ValueError('Antisite energy is not defined') formation_energies = {} formation_energies['vacancies'] = copy.deepcopy(vac_defs) formation_energies['antisites'] = copy.deepcopy(antisite_defs) formation_energies['solute'] = copy.deepcopy(solute_defs) for vac in formation_energies['vacancies']: del vac['energy'] for asite in formation_energies['antisites']: del asite['energy'] for solute in formation_energies['solute']: del solute['energy'] # Setup the system site_species = [vac_def['site_specie'] for vac_def in vac_defs] solute_specie = solute_defs[0]['substitution_specie'] site_species.append(solute_specie) multiplicity = [vac_def['site_multiplicity'] for vac_def in vac_defs] m = len(set(site_species)) # distinct species n = len(vac_defs) # inequivalent sites # Reduce the system and associated parameters such that only distinctive # atoms are retained comm_div = gcd(*tuple(multiplicity)) multiplicity = [val/comm_div for val in multiplicity] multiplicity.append(0) e0 = e0/comm_div T = Integer(T) c0 = np.diag(multiplicity) #print(('c0', c0)) mu = [Symbol('mu'+str(i)) for i in range(m)] # Generate maps for hashing # Generate specie->mu map and use it for site->mu map specie_order = [] # Contains hash for site->mu map Eg: [Al, Ni] site_specie_set = set() # Eg: {Ni, Al} for i in range(len(site_species)): site_specie = site_species[i] if site_specie not in site_specie_set: site_specie_set.add(site_specie) specie_order.append(site_specie) site_mu_map = [] # Eg: [mu0,mu0,mu0,mu1] where mu0->Al, and mu1->Ni for i in range(len(site_species)): site_specie = site_species[i] j = specie_order.index(site_specie) site_mu_map.append(j) specie_site_index_map = [] # Eg: [(0,3),(3,4)] for Al & Ni for i in range(m): low_ind = site_species.index(specie_order[i]) if i < m-1: hgh_ind = site_species.index(specie_order[i+1]) else: hgh_ind = len(site_species) specie_site_index_map.append((low_ind,hgh_ind)) """ dC: delta concentration matrix: dC[i,j,k]: Concentration change of atom i, due to presence of atom j on lattice site k Special case is [i,i,i] which is considered as vacancy Few cases: dC[i,i,i] = -1 due to being vacancy special case dC[k,k,i] = +1 due to increment in k at i lattice if i lattice type is of different element dC[i,k,i] = -1 due to decrement of ith type atom due to presence of kth type atom on ith sublattice and kth type atom specie is different from ith sublattice atom specie dC[i,k,k] = 0 due to no effect on ith type atom dC[i,j,k] = 0 if i!=j!=k """ dC = np.zeros((n+1,n+1,n), dtype=np.int) for i in range(n): for j in range(n): for k in range(n): if i == j and site_species[j] != site_species[k] and \ site_species[i] != site_species: dC[i,j,k] = 1 for j in range(n+1): for k in range(n): if i == k: #if j == k or site_species[j] != site_species[k]: dC[i,j,k] = -1 for k in range(n): dC[n,n,k] = 1 for k in range(n): for j in range(n): if i != j: if site_species[i] == site_species[k]: dC[i,j,k] = 0 for ind_map in specie_site_index_map: if ind_map[1]-ind_map[0] > 1: for index1 in range(ind_map[0]+1,ind_map[1]): for index2 in range(ind_map[0]): for i in range(n): print (i, index1, index2) dC[i,index1,index2] = 0 for index2 in range(ind_map[1],n): for i in range(n): print (i, index1, index2) dC[i,index1,index2] = 0 print ('dC', dC) # dE matrix: Flip energies (or raw defect energies) els = [vac_def['site_specie'] for vac_def in vac_defs] dE = [] for i in range(n+1): dE.append([]) for i in range(n+1): for j in range(n): dE[i].append(0) for j in range(n): for i in range(n): if i == j: dE[i][j] = vac_defs[i]['energy'] else: sub_specie = vac_defs[i]['site_specie'] site_specie = vac_defs[j]['site_specie'] if site_specie == sub_specie: dE[i][j] = 0 else: for as_def in antisite_defs: if int(as_def['site_index']) == j+1 and \ sub_specie == as_def['substitution_specie']: dE[i][j] = as_def['energy'] break # Solute site_specie = vac_defs[j]['site_specie'] for solute_def in solute_defs: def_site_ind = int(solute_def['site_index']) def_site_specie = solute_def['site_specie'] #print((def_site_specie, site_specie)) #print((def_site_ind, j+1)) if def_site_specie == site_specie and def_site_ind == j+1: #print(('se', solute_def['energy'])) dE[n][j] = solute_def['energy'] break dE = np.array(dE) #np.where(dE == np.array(None), 0, dE) # Initialization for concentrations # c(i,p) == presence of ith type atom on pth type site c = Matrix(n+1,n,[0]*n*(n+1)) for i in range(n+1): for p in range(n): c[i,p] = Integer(c0[i,p]) #print((c[i,p])) for epi in range(n+1): sum_mu = sum([mu[site_mu_map[j]]*Integer( dC[j,epi,p]) for j in range(n+1)]) #print((sum_mu)) #print((multiplicity[p], dC[i,epi,p], dE[epi,p])) c[i,p] += Integer(multiplicity[p]*dC[i,epi,p]) * \ exp(-(dE[epi,p]-sum_mu)/(k_B*T)) #print(("--------c---------")) #for i in range(n+1): # print((c[i,:])) #print(("--------c---------")) #specie_concen = [sum(mult[ind[0]:ind[1]]) for ind in specie_site_index_map] #total_c = [sum(c[ind[0]:ind[1]]) for ind in specie_site_index_map] total_c = [] for ind in specie_site_index_map: total_c.append(sum([sum(c[i,:]) for i in range(*ind)])) #total_c = [sum(c[i,:]) for i in range(n)] c_ratio = [total_c[i]/sum(total_c) for i in range(m)] print(('-------c_ratio-------------')) for i in range(m): print((c_ratio[i])) # Expression for Omega, the Grand Potential omega = e0 - sum([mu[site_mu_map[i]]*sum(c0[i,:]) for i in range(n+1)]) for p_r in range(n): for epi in range(n): sum_mu = sum([mu[site_mu_map[j]]*Integer( dC[j,epi,p_r]) for j in range(n+1)]) omega -= k_B*T*multiplicity[p_r]*exp(-(dE[epi,p_r]-sum_mu)/(k_B*T)) print ('omega') print (omega) def compute_mus(): def reduce_mu(): host_concen = 1-solute_concen new_c0 = c0.astype(float) for i in range(n): new_c0[i,i] = host_concen*c0[i,i] new_c0[n,n] = 2*solute_concen omega = [ e0-sum([mu[site_mu_map[i]]*sum(new_c0[i,:]) for i in range(n+1)])] x = solve(omega) return x # Compute trial mu mu_red = reduce_mu() #print(('mu_red', mu_red)) mult = multiplicity #for ind in specie_site_index_map: # print(ind[0], ind[1]) specie_concen = [ sum(mult[ind[0]:ind[1]]) for ind in specie_site_index_map] #print('specie_concen', specie_concen) max_host_specie_concen = 1-solute_concen host_specie_concen_ratio = [specie_concen[i]/sum(specie_concen)* \ max_host_specie_concen for i in range(m)] host_specie_concen_ratio[-1] = solute_concen #print('hostspecie_concen_rat', host_specie_concen_ratio) y_vect = host_specie_concen_ratio #print(('y_vect', y_vect)) vector_func = [y_vect[i]-c_ratio[i] for i in range(m)] vector_func.append(omega) #print((vector_func)) mu_vals = None c_val = None m_min = -15.0 if e0 > 0: m_max = 10 # Search space needs to be modified else: m_max = 0 for m1 in np.arange(m_min,m_max,0.3): for m2 in np.arange(m_min,m_max,0.3): m0 = mu_red[mu[0]].subs([(mu[1],m1),(mu[2],m2)]) try: #print(m1,m2) mu_vals = nsolve(vector_func,mu,[m0,m1,m2],module="numpy") #mu_vals = nsolve(vector_func,mu,[m0,m1,m2]) # Line needs to be modified to include all mus when n > 2 except: continue break if mu_vals: mu_vals = [float(mu_val) for mu_val in mu_vals] break else: raise ValueError("Couldn't find mus") #print (('mu_vals', mu_vals)) return mu_vals if not trial_chem_pot: mu_vals = compute_mus() else: try: mu_vals = [trial_chem_pot[element] for element in specie_order] except: mu_vals = compute_mus() #print(('mu_vals', mu_vals)) # Compute ymax max_host_specie_concen = 1-solute_concen mult = multiplicity specie_concen = [ sum(mult[ind[0]:ind[1]]) for ind in specie_site_index_map] host_specie_concen_ratio = [specie_concen[i]/sum(specie_concen)* \ max_host_specie_concen for i in range(m)] host_specie_concen_ratio[-1] = solute_concen #print('hostspecie_concen_rat', host_specie_concen_ratio) li = specie_site_index_map[0][0] hi = specie_site_index_map[0][1] comp1_min = sum(multiplicity[li:hi])/sum(multiplicity)* \ max_host_specie_concen - 0.01 comp1_max = sum(multiplicity[li:hi])/sum(multiplicity)* \ max_host_specie_concen + 0.01 #print(ymin, ymax) delta = (comp1_max - comp1_min)/50.0 #for i in range(len(mu)): # print(mu[i], mu_vals[i]) # Compile mu's for all composition ratios in the range #+/- 1% from the stoichiometry result = {} for y in np.arange(comp1_min,comp1_max+delta,delta): result[y] = [] y_vect = [] y_vect.append(y) y2 = max_host_specie_concen - y y_vect.append(y2) y_vect.append(solute_concen) #print ('y_vect', y_vect) vector_func = [y_vect[i]-c_ratio[i] for i in range(1,m)] vector_func.append(omega) #try: #print (vector_func) #print (mu) #print (mu_vals) x = nsolve(vector_func,mu,mu_vals,module="numpy") #x = nsolve(vector_func,mu,mu_vals) #except: # del result[y] # continue result[y].append(x[0]) result[y].append(x[1]) result[y].append(x[2]) res = [] #print ('result', result.keys()) # Compute the concentrations for all the compositions for key in sorted(result.keys()): mu_val = result[key] total_c_val = [total_c[i].subs(dict(zip(mu,mu_val))) \ for i in range(len(total_c))] c_val = c.subs(dict(zip(mu,mu_val))) #print ('c_val', c_val) # Concentration of first element/over total concen res1 = [] res1.append(float(total_c_val[0]/sum(total_c_val))) sum_c0 = sum([c0[i,i] for i in range(n)]) #print ('c_val_n_0', c_val[n,0]) #print ('c_val_n_1', c_val[n,1]) for i in range(n+1): for j in range(n): if i == j: # Vacancy #res1.append(float((c0[i,i]-sum(c_val[:,i]))/c0[i,i])) vac_conc = float(exp(-(mu_val[site_mu_map[i]]+dE[i,i])/(k_B*T))) res1.append(vac_conc) else: # Antisite res1.append(float(c_val[i,j]/c0[j,j])) res.append(res1) #print ('c0', c0) #print ('res', res) res = np.array(res) #print ('res', res) dtype = [(str('x'),np.float64)]+[(str('y%d%d' % (i, j)), np.float64) \ for i in range(n+1) for j in range(n)] res1 = np.sort(res.view(dtype),order=[str('x')],axis=0) conc = [] for i in range(n+1): conc.append([]) for j in range(n): conc[i].append([]) #print(conc) for i in range(n+1): # Append vacancies for j in range(n): y1 = [dat[0][i*n+j+1] for dat in res1] conc[i][j] = y1 #print(type(conc[i][j])) plot_data = {} """Because all the plots have identical x-points storing it in a single array""" #for dat in res1: # print dat plot_data['x'] = [dat[0][0] for dat in res1] # x-axis data # Element whose composition is varied. For x-label plot_data['x_label'] = els[0]+ "_mole_fraction" plot_data['y_label'] = "Fraction of {} at {} sites".format( solute_specie,els[0]) y_data = [] #for i in range(n): # Vacancy plots inds = specie_site_index_map[m-1] #print ('inds', inds) data1 = np.sum([conc[ind][0] for ind in range(*inds)],axis=0) #print ('data1', data1) data2 = np.sum([conc[ind][1] for ind in range(*inds)],axis=0) #print ('data2', data2) frac_data = data1/(data1+data2) #print ('frac_data', frac_data) frac_data = frac_data.tolist() y_data.append({'data':frac_data}) plot_data['y'] = y_data return plot_data
def dilute_solution_model(structure, e0, vac_defs, antisite_defs, T, trial_chem_pot = None, generate='plot'): """ Compute the defect densities using dilute solution model. Args: structure: pymatgen.core.structure.Structure object representing the primitive or unitcell of the crystal. e0: The total energy of the undefected system. This is E0 from VASP calculation. vac_defs: List of vacancy defect parameters in the dictionary format. The keys of the dict associated with each vacancy defect are 1) site_index, 2) site_specie, 3) site_multiplicity, and 4) energy. 1-3 can be obtained from pymatgen.analysis.defects.point_defects.Vacancy class. Site index is expected to start with 1 (fortran index). antisite_defs: List of antisite defect parameters in the dictionary format. The keys of the dict associated with each antisite defect are 1) site_index, 2) site_specie, 3) site_multiplicity, 4) substitution_specie, and 5) energy. 1-3 can be obtained from pymatgen.analysis.defects.point_defects.Vacancy class. T: Temperature in Kelvin trial_chem_pot (optional): Trial chemical potentials to speedup the plot generation. Format is {el1:mu1,...} generate (string): Options are plot or energy Chemical potentials are also returned with energy option. If energy option is not chosen, plot is generated. Returns: If generate=plot, the plot data is generated and returned in HighCharts format. If generate=energy, defect formation enthalpies and chemical potentials are returned. """ if not check_input(vac_defs): raise ValueError('Vacancy energy is not defined') if not check_input(antisite_defs): raise ValueError('Antisite energy is not defined') formation_energies = {} formation_energies['vacancies'] = copy.deepcopy(vac_defs) formation_energies['antisites'] = copy.deepcopy(antisite_defs) for vac in formation_energies['vacancies']: del vac['energy'] for asite in formation_energies['antisites']: del asite['energy'] # Setup the system site_species = [vac_def['site_specie'] for vac_def in vac_defs] multiplicity = [vac_def['site_multiplicity'] for vac_def in vac_defs] m = len(set(site_species)) # distinct species n = len(vac_defs) # inequivalent sites # Reduce the system and associated parameters such that only distinctive # atoms are retained comm_div = gcd(*tuple(multiplicity)) multiplicity = [val/comm_div for val in multiplicity] e0 = e0/comm_div T = Integer(T) c0 = np.diag(multiplicity) #print ('c0',c0) mu = [Symbol('mu'+i.__str__()) for i in range(m)] # Generate maps for hashing # Generate specie->mu map and use it for site->mu map specie_order = [] # Contains hash for site->mu map Eg: [Al, Ni] site_specie_set = set() # Eg: {Ni, Al} for i in range(n): site_specie = site_species[i] if site_specie not in site_specie_set: site_specie_set.add(site_specie) specie_order.append(site_specie) site_mu_map = [] # Eg: [mu0,mu0,mu0,mu1] where mu0->Al, and mu1->Ni for i in range(n): site_specie = site_species[i] j = specie_order.index(site_specie) site_mu_map.append(j) specie_site_index_map = [] # Eg: [(0,3),(3,4)] for Al & Ni for i in range(m): low_ind = site_species.index(specie_order[i]) if i < m-1: hgh_ind = site_species.index(specie_order[i+1]) else: hgh_ind = n specie_site_index_map.append((low_ind,hgh_ind)) """ dC: delta concentration matrix: dC[i,j,k]: Concentration change of atom i, due to presence of atom j on lattice site k Special case is [i,i,i] which is considered as vacancy Few cases: dC[i,i,i] = -1 due to being vacancy special case dC[k,k,i] = +1 due to increment in k at i lattice if i lattice type is of different element dC[i,k,i] = -1 due to decrement of ith type atom due to presence of kth type atom on ith sublattice and kth type atom specie is different from ith sublattice atom specie dC[i,k,k] = 0 due to no effect on ith type atom dC[i,j,k] = 0 if i!=j!=k """ dC = np.zeros((n,n,n), dtype=np.int) for i in range(n): for j in range(n): for k in range(n): if i == j and site_species[j] != site_species[k] and \ site_species[i] != site_species[k]: dC[i,j,k] = 1 for j in range(n): for k in range(n): if i == k: #if j == k or site_species[j] != site_species[k]: dC[i,j,k] = -1 for k in range(n): for j in range(n): for i in range(n): if i != j: if site_species[j] == site_species[k]: dC[i,j,k] = 0 #for j in range(n): # if k != j: # for i in range(n): # if i == j: # if abs(i-j) <= 1 and abs(j-k) <= 1 and abs(i-k) <= 1: # dC[i,j,k] = 0 for ind_map in specie_site_index_map: if ind_map[1]-ind_map[0] > 1: for index1 in range(ind_map[0]+1,ind_map[1]): for index2 in range(ind_map[0]): for i in range(n): #print (i, index1, index2) dC[i,index1,index2] = 0 for index2 in range(ind_map[1],n): for i in range(n): #print (i, index1, index2) dC[i,index1,index2] = 0 #print ('dC', dC) # dE matrix: Flip energies (or raw defect energies) els = [vac_def['site_specie'] for vac_def in vac_defs] dE = [] for i in range(n): dE.append([]) for i in range(n): for j in range(n): dE[i].append(0) for j in range(n): for i in range(n): if i == j: dE[i][j] = vac_defs[i]['energy'] else: sub_specie = vac_defs[i]['site_specie'] site_specie = vac_defs[j]['site_specie'] if site_specie == sub_specie: dE[i][j] = 0 else: for as_def in antisite_defs: if int(as_def['site_index']) == j+1 and \ sub_specie == as_def['substitution_specie']: dE[i][j] = as_def['energy'] break dE = np.array(dE) #np.where(dE is None, dE, 0) #print ('dE', dE) # Initialization for concentrations # c(i,p) == presence of ith type atom on pth type site c = Matrix(n,n,[0]*n**2) for i in range(n): for p in range(n): c[i,p] = Integer(c0[i,p]) site_flip_contribs = [] for epi in range(n): sum_mu = sum([mu[site_mu_map[j]]*Integer(dC[j,epi,p]) \ for j in range(n)]) #print (i, epi, p, dC[i,epi, p], sum_mu) #c[i,p] += Integer(multiplicity[p]*dC[i,epi,p]) * \ # exp(-(dE[epi,p]-sum_mu)/(k_B*T)) flip = Integer(multiplicity[p]*dC[i,epi,p]) * \ exp(-(dE[epi,p]-sum_mu)/(k_B*T)) if flip not in site_flip_contribs: site_flip_contribs.append(flip) c[i,p] += flip #else: #print (i, epi, p) #print ('flip already present in site_flips') #print ('c', c) total_c = [] for ind in specie_site_index_map: total_c.append(sum([sum(c[i,:]) for i in range(*ind)])) c_ratio = [total_c[-1]/total_c[i] for i in range(m)] #print ('c_ratio') #for i in range(len(c_ratio)): # print(c_ratio[i]) # Expression for Omega, the Grand Potential omega = e0 - sum([mu[site_mu_map[i]]*sum(c0[i,:]) for i in range(n)]) for p_r in range(n): for epi in range(n): sum_mu = sum([mu[site_mu_map[j]]*Float( dC[j,epi,p_r]) for j in range(n)]) omega -= k_B*T*multiplicity[p_r]*exp(-(dE[epi,p_r]-sum_mu)/(k_B*T)) def compute_mus(): def reduce_mu(): omega = [e0 - sum([mu[site_mu_map[i]]*sum(c0[i,:]) for i in range(n)])] x = solve(omega) return x # Compute trial mu mu_red = reduce_mu() mult = multiplicity specie_concen = [sum(mult[ind[0]:ind[1]]) for ind in specie_site_index_map] y_vect = [specie_concen[-1]/specie_concen[i] for i in range(m)] vector_func = [y_vect[i]-c_ratio[i] for i in range(m-1)] vector_func.append(omega) min_diff = 1e10 mu_vals = None c_val = None m1_min = -20.0 if e0 > 0: m1_max = 10 # Search space needs to be modified else: m1_max = 0 for m1 in np.arange(m1_min,m1_max,0.1): m0 = mu_red[mu[0]].subs(mu[-1],m1) try: x = nsolve(vector_func,mu,[m0,m1],module="numpy") except: continue c_val = c.subs(dict(zip(mu,x))) #if all(x >= 0 for x in c_val): specie_concen = [] for ind in specie_site_index_map: specie_concen.append(sum([sum(c_val[i,:]) for i in range(*ind)])) y_comp = [specie_concen[-1]/specie_concen[i] for i in range(m)] diff = math.sqrt(sum([pow(abs(y_comp[i]-y_vect[i]),2) for i in range(m)])) if diff < min_diff: min_diff = diff mu_vals = x if mu_vals: mu_vals = [float(mu_val) for mu_val in mu_vals] else: raise ValueError() return mu_vals def compute_def_formation_energies(): i = 0 for vac_def in vac_defs: site_specie = vac_def['site_specie'] ind = specie_order.index(site_specie) uncor_energy = vac_def['energy'] formation_energy = uncor_energy + mu_vals[ind] formation_energies['vacancies'][i]['formation_energy'] = formation_energy specie_ind = site_mu_map[i] indices = specie_site_index_map[specie_ind] specie_ind_del = indices[1]-indices[0] cur_ind = i - indices[0] + 1 if not specie_ind_del-1: label = '$V_{'+site_specie+'}$' else: label = '$V_{'+site_specie+'_'+str(cur_ind)+'}$' formation_energies['vacancies'][i]['label'] = label i += 1 i = 0 for as_def in antisite_defs: site_specie = as_def['site_specie'] sub_specie = as_def['substitution_specie'] ind1 = specie_order.index(site_specie) ind2 = specie_order.index(sub_specie) uncor_energy = as_def['energy'] formation_energy = uncor_energy + mu_vals[ind1] - mu_vals[ind2] formation_energies['antisites'][i]['formation_energy'] = formation_energy specie_ind = site_mu_map[i] indices = specie_site_index_map[specie_ind] specie_ind_del = indices[1]-indices[0] cur_ind = i - indices[0] + 1 if not specie_ind_del-1: label = '$'+sub_specie+'_{'+site_specie+'}$' else: label = '$'+sub_specie+'_{'+site_specie+'_'+str(cur_ind)+'}$' formation_energies['antisites'][i]['label'] = label i += 1 return formation_energies if not trial_chem_pot: mu_vals = compute_mus() else: try: mu_vals = [trial_chem_pot[element] for element in specie_order] except: mu_vals = compute_mus() if generate == 'energy': formation_energies = compute_def_formation_energies() mu_dict = dict(zip(specie_order,mu_vals)) return formation_energies, mu_dict #sys.exit() # Compute ymax li = specie_site_index_map[0][0] hi = specie_site_index_map[0][1] comp1_min = int(sum(multiplicity[li:hi])/sum(multiplicity)*100)-1 comp1_max = int(sum(multiplicity[li:hi])/sum(multiplicity)*100)+1 delta = float(comp1_max-comp1_min)/120.0 yvals = [] for comp1 in np.arange(comp1_min,comp1_max+delta,delta): comp2 = 100-comp1 y = comp2/comp1 yvals.append(y) #ymin = comp2_min/comp1_max #print comp1_min, comp1_max #print comp2_min, comp2_max #print ymax, ymin # Compile mu's for all composition ratios in the range #+/- 1% from the stoichiometry result = {} #for y in np.arange(ymin,ymax+delta,delta): for y in yvals: result[y] = [] vector_func = [y-c_ratio[0]] vector_func.append(omega) x = nsolve(vector_func,mu,mu_vals,module="numpy") result[y].append(x[0]) result[y].append(x[1]) res = [] new_mu_dict = {} # Compute the concentrations for all the compositions for key in sorted(result.keys()): mu_val = result[key] total_c_val = [total_c[i].subs(dict(zip(mu,mu_val))) \ for i in range(len(total_c))] c_val = c.subs(dict(zip(mu,mu_val))) res1 = [] # Concentration of first element/over total concen res1.append(float(total_c_val[0]/sum(total_c_val))) new_mu_dict[res1[0]] = mu_val sum_c0 = sum([c0[i,i] for i in range(n)]) #print res1[0] for i in range(n): for j in range(n): if i == j: # Vacancy #print (i, c_val[:,i]) # Consider numerical accuracy #res1.append(float((c0[i,i]-sum(c_val[:,i]))/c0[i,i])) #print ((mu_val[site_mu_map[i]]-dE[i,i])/(k_B*T)) vac_conc = float(exp(-(mu_val[site_mu_map[i]]+dE[i,i])/(k_B*T))) #print vac_conc res1.append(vac_conc) else: # Antisite res1.append(float(c_val[i,j]/c0[j,j])) res.append(res1) res = np.array(res) dtype = [(str('x'),np.float64)]+[(str('y%d%d' % (i, j)), np.float64) \ for i in range(n) for j in range(n)] res1 = np.sort(res.view(dtype), order=[str('x')],axis=0) conc_data = {} """Because all the plots have identical x-points storing it in a single array""" conc_data['x'] = [dat[0][0] for dat in res1] # x-axis data # Element whose composition is varied. For x-label conc_data['x_label'] = els[0]+ " mole fraction" conc_data['y_label'] = "Point defect concentration" conc = [] for i in range(n): conc.append([]) for j in range(n): conc[i].append([]) for i in range(n): for j in range(n): y1 = [dat[0][i*n+j+1] for dat in res1] conc[i][j] = y1 y_data = [] for i in range(n): data = conc[i][i] specie = els[i] specie_ind = site_mu_map[i] indices = specie_site_index_map[specie_ind] specie_ind_del = indices[1]-indices[0] cur_ind = i - indices[0] + 1 #if 'V' not in els: # vac_string = "$V_{" #else: vac_string = "$Vac_{" if not specie_ind_del-1: label = vac_string+specie+'}$' else: label = vac_string+specie+'_'+str(cur_ind)+'}$' # Plot data and legend info y_data.append({'data':data,'name':label}) for i in range(n): site_specie = els[i] specie_ind = site_mu_map[i] indices = specie_site_index_map[specie_ind] specie_ind_del = indices[1]-indices[0] cur_ind = i - indices[0] + 1 for j in range(m): # Antisite plot dat sub_specie = specie_order[j] if sub_specie == site_specie: continue if not specie_ind_del-1: label = '$'+sub_specie+'_{'+site_specie+'}$' else: label = '$'+sub_specie+'_{'+site_specie+'_'+str(cur_ind)+'}$' inds = specie_site_index_map[j] data = np.sum([conc[ind][i] for ind in range(*inds)],axis=0) data = data.tolist() y_data.append({'data':data,'name':label}) conc_data['y'] = y_data # Compute the formation energies def compute_vac_formation_energies(mu_vals): en = [] for vac_def in vac_defs: site_specie = vac_def['site_specie'] ind = specie_order.index(site_specie) uncor_energy = vac_def['energy'] formation_energy = uncor_energy + mu_vals[ind] en.append(float(formation_energy)) return en en_res = [] for key in sorted(new_mu_dict.keys()): mu_val = new_mu_dict[key] en_res.append(compute_vac_formation_energies(mu_val)) en_data = {'x_label':els[0]+' mole fraction', 'x':[]} en_data['x'] = [dat[0][0] for dat in res1] # x-axis data i = 0 y_data = [] for vac_def in vac_defs: data = [data[i] for data in en_res] site_specie = vac_def['site_specie'] ind = specie_order.index(site_specie) specie_ind = site_mu_map[i] indices = specie_site_index_map[specie_ind] specie_ind_del = indices[1]-indices[0] cur_ind = i - indices[0] + 1 vac_string = "$Vac_{" if not specie_ind_del-1: label = vac_string+site_specie+'}$' else: label = vac_string+site_specie+'_'+str(cur_ind)+'}$' y_data.append({'data':data,'name':label}) i += 1 def compute_as_formation_energies(mu_vals): en = [] for as_def in antisite_defs: site_specie = as_def['site_specie'] sub_specie = as_def['substitution_specie'] ind1 = specie_order.index(site_specie) ind2 = specie_order.index(sub_specie) uncor_energy = as_def['energy'] form_en = uncor_energy + mu_vals[ind1] - mu_vals[ind2] en.append(form_en) return en en_res = [] for key in sorted(new_mu_dict.keys()): mu_val = new_mu_dict[key] en_res.append(compute_as_formation_energies(mu_val)) i = 0 for as_def in antisite_defs: data = [data[i] for data in en_res] site_specie = as_def['site_specie'] sub_specie = as_def['substitution_specie'] ind1 = specie_order.index(site_specie) ind2 = specie_order.index(sub_specie) specie_ind = site_mu_map[i] indices = specie_site_index_map[specie_ind] specie_ind_del = indices[1]-indices[0] cur_ind = i - indices[0] + 1 if not specie_ind_del-1: label = '$'+sub_specie+'_{'+site_specie+'}$' else: label = '$'+sub_specie+'_{'+site_specie+'_'+str(cur_ind)+'}$' y_data.append({'data':data,'name':label}) i += 1 en_data['y'] = y_data # Return chem potential as well mu_data = {'x_label':els[0]+' mole fraction', 'x':[]} mu_data['x'] = [dat[0][0] for dat in res1] # x-axis data y_data = [] for j in range(m): specie = specie_order[j] mus = [new_mu_dict[key][j] for key in sorted(new_mu_dict.keys())] y_data.append({'data':mus, 'name':specie}) mu_data['y'] = y_data return conc_data, en_data, mu_data
def get_formula_from_components(components: List[Component], molecules_first: bool = False, use_iupac_formula: bool = True, use_common_formulas: bool = True) -> str: """Reconstructs a chemical formula from structure components. The chemical formulas for the individual components will be grouped together. If two components share the same composition, they will be treated as equivalent. Args: components: A list of structure components, generated using :obj:`pymatgen.analysis.dimensionality.get_structure_components`. molecules_first: Whether to put any molecules (zero-dimensional components) at the beginning of the formula. use_iupac_formula (bool, optional): Whether to order formulas by the iupac "electronegativity" series, defined in Table VI of "Nomenclature of Inorganic Chemistry (IUPAC Recommendations 2005)". This ordering effectively follows the groups and rows of the periodic table, except the Lanthanides, Actanides and hydrogen. If set to ``False``, the elements will be ordered according to the electronegativity values. use_common_formulas: Whether to use the database of common formulas. The common formula will be used preferentially to the iupac or reduced formula. Returns: The chemical formula. """ def order(comp_formula): composition = Composition(comp_formula) if use_iupac_formula: return (sum( [get_el_sp(s).iupac_ordering for s in composition.elements]) / len(composition.elements)) else: return composition.average_electroneg components = get_formula_inequiv_components( components, use_iupac_formula=use_iupac_formula, use_common_formulas=use_common_formulas) if molecules_first: mol_comps, other_comps = filter_molecular_components(components) else: mol_comps = [] other_comps = components formulas = (sorted([c['formula'] for c in mol_comps], key=order) + sorted([c['formula'] for c in other_comps], key=order)) # if components include special formulas, then the count can be 0.5 # therefore if any non integer amounts we can just use a factor of 2 all_int = all(v['count'] % 1 == 0 for v in components) prefactor = 1 if all_int else 2 form_count_dict = { c['formula']: int(c['count'] * prefactor) for c in components } # the following is based on ``pymatgen.core.composition.reduce_formula`` num_comps = len(formulas) factor = abs(gcd(*(form_count_dict.values()))) reduced_form = [] for i in range(0, num_comps): formula = formulas[i] normamt = form_count_dict[formula] * 1.0 / factor formatted_formula = formula if normamt == 1 else "({})".format(formula) reduced_form.append(formatted_formula) reduced_form.append(formula_double_format(normamt)) reduced_form = "".join(reduced_form) return reduced_form
def dilute_solution_model(structure, e0, vac_defs, antisite_defs, T, trial_chem_pot=None, generate='plot'): """ Compute the defect densities for a structure based on the input parameters using dilute solution model. Args: structure: pymatgen.core.structure.Structure object representing the primitive or unitcell of the crystal. e0: The total energy of the undefected system. This is E0 from VASP calculation. vac_defs: List of vacancy defect parameters in the dictionary format. The keys of the dict associated with each vacancy defect are 1) site_index, 2) site_specie, 3) site_multiplicity, and 4) energy. 1-3 can be obtained from pymatgen.analysis.defects.point_defects.Vacancy class. Site index is expected to start with 1 (fortran index). antisite_defs: List of antisite defect parameters in the dictionary format. The keys of the dict associated with each antisite defect are 1) site_index, 2) site_specie, 3) site_multiplicity, 4) substitution_specie, and 5) energy. 1-3 can be obtained from pymatgen.analysis.defects.point_defects.Vacancy class. T: Temperature in Kelvin trial_chem_pot: Trial chemical potentials to speedup the plot generation Format is {el1:mu1,...} generate (string): Options are plot or energy Chemical potentials are also returned with energy option. If energy option is not chosen, plot is generated. """ if not check_input(vac_defs): raise ValueError('Vacancy energy is not defined') if not check_input(antisite_defs): raise ValueError('Antisite energy is not defined') formation_energies = {} formation_energies['vacancies'] = copy.deepcopy(vac_defs) formation_energies['antisites'] = copy.deepcopy(antisite_defs) for vac in formation_energies['vacancies']: del vac['energy'] for asite in formation_energies['antisites']: del asite['energy'] # Setup the system site_species = [vac_def['site_specie'] for vac_def in vac_defs] multiplicity = [vac_def['site_multiplicity'] for vac_def in vac_defs] #print multiplicity m = len(set(site_species)) # distinct species n = len(vac_defs) # inequivalent sites # Reduce the system and associated parameters such that only distinctive # atoms are retained comm_div = gcd(*tuple(multiplicity)) multiplicity = [val / comm_div for val in multiplicity] e0 = e0 / comm_div T = Integer(T) c0 = np.diag(multiplicity) #print 'c0', c0 mu = [Symbol('mu' + str(i)) for i in range(m)] # Generate maps for hashing # Generate specie->mu map and use it for site->mu map specie_order = [] # Contains hash for site->mu map Eg: [Al, Ni] site_specie_set = set() # Eg: {Ni, Al} for i in range(n): site_specie = site_species[i] if site_specie not in site_specie_set: site_specie_set.add(site_specie) specie_order.append(site_specie) site_mu_map = [] # Eg: [mu0,mu0,mu0,mu1] where mu0->Al, and mu1->Ni for i in range(n): site_specie = site_species[i] j = specie_order.index(site_specie) site_mu_map.append(j) specie_site_index_map = [] # Eg: [(0,3),(3,4)] for Al & Ni for i in range(m): low_ind = site_species.index(specie_order[i]) if i < m - 1: hgh_ind = site_species.index(specie_order[i + 1]) else: hgh_ind = n specie_site_index_map.append((low_ind, hgh_ind)) #print 'specie_site_index_map', specie_site_index_map #for el in specie_site_index_map: # print range(*el) #print site_species #print 'site_mu_map', site_mu_map #print specie_order """ dC: delta concentration matrix: dC[i,j,k]: Concentration change of atom i, due to presence of atom j on lattice site k Special case is [i,i,i] which is considered as vacancy Few cases: dC[i,i,i] = -1 due to being vacancy special case dC[k,k,i] = +1 due to increment in k at i lattice if i lattice type is of different element dC[i,k,i] = -1 due to decrement of ith type atom due to presence of kth type atom on ith sublattice and kth type atom specie is different from ith sublattice atom specie dC[i,k,k] = 0 due to no effect on ith type atom dC[i,j,k] = 0 if i!=j!=k """ dC = np.zeros((n, n, n), dtype=np.int) for i in range(n): for j in range(n): for k in range(n): if i == j: # and site_species[j] != site_species[k]: dC[i, j, k] = 1 for j in range(n): for k in range(n): if i == k: #if j == k or site_species[j] != site_species[k]: dC[i, j, k] = -1 #print dC # dE matrix: Flip energies (or raw defect energies) els = [vac_def['site_specie'] for vac_def in vac_defs] dE = [] for i in range(n): dE.append([]) for i in range(n): for j in range(n): dE[i].append(None) for j in range(n): for i in range(n): if i == j: dE[i][j] = vac_defs[i]['energy'] else: sub_specie = vac_defs[i]['site_specie'] site_specie = vac_defs[j]['site_specie'] if site_specie == sub_specie: dE[i][j] = 0 else: for as_def in antisite_defs: if as_def['site_index'] == j+1 and \ sub_specie == as_def['substitution_specie']: dE[i][j] = as_def['energy'] break dE = np.array(dE) np.where(dE == None, dE, 0) #print dE # Initialization for concentrations # c(i,p) == presence of ith type atom on pth type site c = Matrix(n, n, [0] * n**2) for i in range(n): for p in range(n): c[i, p] = Integer(c0[i, p]) for epi in range(n): sum_mu = sum([ mu[site_mu_map[j]] * Integer(dC[j, epi, p]) for j in range(n) ]) c[i,p] += Integer(multiplicity[p]*dC[i,epi,p]) * \ exp(-(dE[epi,p]-sum_mu)/(k_B*T)) #specie_concen = [sum(mult[ind[0]:ind[1]]) for ind in specie_site_index_map] #total_c = [sum(c[ind[0]:ind[1]]) for ind in specie_site_index_map] total_c = [] for ind in specie_site_index_map: total_c.append(sum([sum(c[i, :]) for i in range(*ind)])) #total_c = [sum(c[i,:]) for i in range(n)] c_ratio = [total_c[-1] / total_c[i] for i in range(m)] #print 'c_ratio' #for i in range(len(c_ratio)): #print c_ratio[i] # Expression for Omega, the Grand Potential omega = e0 - sum([mu[site_mu_map[i]] * sum(c0[i, :]) for i in range(n)]) for p_r in range(n): for epi in range(n): sum_mu = sum([ mu[site_mu_map[j]] * Integer(dC[j, epi, p_r]) for j in range(n) ]) omega -= k_B * T * multiplicity[p_r] * exp( -(dE[epi, p_r] - sum_mu) / (k_B * T)) def compute_mus(): def reduce_mu(): omega = [ e0 - sum([mu[site_mu_map[i]] * sum(c0[i, :]) for i in range(n)]) ] x = solve(omega) return x # Compute trial mu mu_red = reduce_mu() #print mu_red mult = multiplicity #for ind in specie_site_index_map: # print ind[0], ind[1] specie_concen = [ sum(mult[ind[0]:ind[1]]) for ind in specie_site_index_map ] #print 'specie_concent', specie_concen y_vect = [specie_concen[-1] / specie_concen[i] for i in range(m)] #print 'y_vect', y_vect vector_func = [y_vect[i] - c_ratio[i] for i in range(m - 1)] vector_func.append(omega) #print vector_func #vector_func.append(mu_equalities) #print 'y0', y0 min_diff = 1e10 mu_vals = None c_val = None m1_min = -20.0 if e0 > 0: m1_max = 10 # Search space needs to be modified else: m1_max = 0 for m1 in np.arange(m1_min, m1_max, 0.1): m0 = mu_red[mu[0]].subs(mu[-1], m1) try: x = nsolve(vector_func, mu, [m0, m1], module="numpy") # Line needs to be modified to include all mus when n > 2 except: continue c_val = c.subs(dict(zip(mu, x))) #print c_val #if all(x >= 0 for x in c_val): specie_concen = [] for ind in specie_site_index_map: specie_concen.append( sum([sum(c_val[i, :]) for i in range(*ind)])) y_comp = [specie_concen[-1] / specie_concen[i] for i in range(m)] diff = math.sqrt( sum([pow(abs(y_comp[i] - y_vect[i]), 2) for i in range(m)])) if diff < min_diff: min_diff = diff mu_vals = x if mu_vals: mu_vals = [float(mu_val) for mu_val in mu_vals] else: raise ValueError() print mu_vals return mu_vals #print els def compute_def_formation_energies(): i = 0 for vac_def in vac_defs: site_specie = vac_def['site_specie'] ind = specie_order.index(site_specie) uncor_energy = vac_def['energy'] formation_energy = uncor_energy + mu_vals[ind] print site_specie, 'vancancy formation_energy', formation_energy formation_energies['vacancies'][i][ 'formation_energy'] = formation_energy specie_ind = site_mu_map[i] indices = specie_site_index_map[specie_ind] specie_ind_del = indices[1] - indices[0] cur_ind = i - indices[0] + 1 if not specie_ind_del - 1: label = '$V_{' + site_specie + '}$' else: label = '$V_{' + site_specie + '_' + str(cur_ind) + '}$' formation_energies['vacancies'][i]['label'] = label i += 1 i = 0 for as_def in antisite_defs: site_specie = as_def['site_specie'] sub_specie = as_def['substitution_specie'] ind1 = specie_order.index(site_specie) ind2 = specie_order.index(sub_specie) uncor_energy = as_def['energy'] formation_energy = uncor_energy + mu_vals[ind1] - mu_vals[ind2] print site_specie, sub_specie, 'antisite ', formation_energy formation_energies['antisites'][i][ 'formation_energy'] = formation_energy specie_ind = site_mu_map[i] indices = specie_site_index_map[specie_ind] specie_ind_del = indices[1] - indices[0] cur_ind = i - indices[0] + 1 if not specie_ind_del - 1: label = '$' + sub_specie + '_{' + site_specie + '}$' else: label = '$' + sub_specie + '_{' + site_specie + '_' + str( cur_ind) + '}$' formation_energies['antisites'][i]['label'] = label i += 1 return formation_energies if not trial_chem_pot: mu_vals = compute_mus() else: try: mu_vals = [trail_chem_pot[element] for element in specie_ordger] except: mu_vals = compute_mus() if generate == 'energy': formation_energies = compute_def_formation_energies() mu_dict = dict(zip(specie_order, mu_vals)) return formation_energies, mu_dict # Compute ymax li = specie_site_index_map[0][0] hi = specie_site_index_map[0][1] comp1_min = int(sum(multiplicity[li:hi]) / sum(multiplicity) * 100) - 1 comp2_max = 100 - comp1_min ymax = comp2_max / comp1_min comp1_max = int(sum(multiplicity[li:hi]) / sum(multiplicity) * 100) + 1 comp2_min = 100 - comp1_max ymin = comp2_min / comp1_max #print ymin, ymax delta = (ymax - ymin) / 40.0 #for i in range(len(mu)): # print mu[i], mu_vals[i] # Compile mu's for all composition ratios in the range #+/- 1% from the stoichiometry result = {} for y in np.arange(ymin, ymax, delta): result[y] = [] vector_func = [y - c_ratio[0]] vector_func.append(omega) x = nsolve(vector_func, mu, mu_vals, module="numpy") result[y].append(x[0]) result[y].append(x[1]) res = [] # Compute the concentrations for all the compositions for key in result: mu_val = result[key] total_c_val = [total_c[i].subs(dict(zip(mu,mu_val))) \ for i in range(len(total_c))] c_val = c.subs(dict(zip(mu, mu_val))) res1 = [] # Concentration of first element/over total concen res1.append(float(total_c_val[0] / sum(total_c_val))) sum_c0 = sum([c0[i, i] for i in range(n)]) for i in range(n): for j in range(n): if i == j: # Vacancy res1.append(float( (c0[i, i] - sum(c_val[:, i])) / c0[i, i])) else: # Antisite res1.append(float(c_val[i, j] / c0[i, i])) res.append(res1) res = np.array(res) dtype = [('x',np.float64)]+[('y'+str(i)+str(j),np.float64) \ for i in range(n) for j in range(n)] res1 = np.sort(res.view(dtype), order=['x'], axis=0) plot_data = {} """Because all the plots have identical x-points storing it in a single array""" plot_data['x'] = [dat[0][0] for dat in res1] # x-axis data # Element whose composition is varied. For x-label plot_data['x_label'] = els[0] + " mole fraction" plot_data['y_label'] = "Point defect concentration" conc = [] for i in range(n): conc.append([]) for j in range(n): conc[i].append([]) #print conc for i in range(n): # Append vacancies for j in range(n): y1 = [dat[0][i * n + j + 1] for dat in res1] conc[i][j] = y1 #print type(conc[i][j]) y_data = [] for i in range(n): # Vacancy plots data = conc[i][i] specie = els[i] specie_ind = site_mu_map[i] indices = specie_site_index_map[specie_ind] specie_ind_del = indices[1] - indices[0] cur_ind = i - indices[0] + 1 if 'V' not in els: vac_string = "$V_{" else: vac_string = "$Vac_{" if not specie_ind_del - 1: label = vac_string + specie + '}$' else: label = vac_string + specie + '_' + str(cur_ind) + '}$' # Plot data and legend info y_data.append({'data': data, 'name': label}) site_specie = els[i] for j in range(m): # Antisite plot dat sub_specie = specie_order[j] if sub_specie == site_specie: continue if not specie_ind_del - 1: label = '$' + sub_specie + '_{' + specie + '}$' else: label = '$' + sub_specie + '_{' + specie + '_' + str( cur_ind) + '}$' inds = specie_site_index_map[j] data = np.sum([conc[ind][i] for ind in range(*inds)], axis=0) data = data.tolist() y_data.append({'data': data, 'name': label}) plot_data['y'] = y_data return plot_data