def get_fracs(metals=None, shape=None, num_shells=None, return_ee=False, x_metal1=None, **kwargs): res = db_inter.get_bimet_result(metals=metals, shape=shape, num_shells=num_shells, **kwargs) # limit by composition if x_metal1 is not None: res = filter(lambda r: abs(r.n_metal1 / r.num_atoms - x_metal1) <= 0.05, res) res = sorted(res, key=lambda r: [r.shape, r.num_atoms]) fracs = np.zeros((len(res), 3)) ee = np.zeros(len(res)) # track shape and size so if they don't change, you don't reload atomgraph prevsize = res[0].num_atoms prevshape = res[0].shape # load the first atomgraph bonds = res[0].nanoparticle.load_bonds_list() ag = atomgraph.AtomGraph(bonds, res[0].metal1, res[0].metal2) for i, a in enumerate(res): # only create new atomgraph for new size and/or shape if a.num_atoms != prevsize or a.shape != prevshape: bonds = a.nanoparticle.load_bonds_list() ag = atomgraph.AtomGraph(bonds, a.metal1, a.metal2) # update previous size and shape prevsize = a.num_atoms prevshape = a.shape # get bond type counts counts = ag.countMixing(np.array([int(z) for z in a.ordering])) # normalize bond counts to get bond fractions fracs[i] = counts / counts.sum() ee[i] = a.EE # return bond fraction for metal1-metal1 (aa) and metal2-metal2 (bb) aa = fracs[:, 0] bb = fracs[:, 1] # option to return excess energy with bond fractions if return_ee: return aa, bb, ee return aa, bb
def test_get_bimet_result__return_metals_matches(): # get 3 random metal pairs found in BimetallicResults table metals = random.sample(set(db_inter.build_metal_pairs_list()), 3) for m in metals: res = db_inter.get_bimet_result(metals=m, lim=5, return_list=True) assert all(r.metal1 == m[0] and r.metal2 == m[1] for r in res)
def test_get_bimet_result__return_n_metal1_matches(): for n1 in [30, 40]: res = db_inter.get_bimet_result(n_metal1=n1, lim=5, return_list=True) assert all(r.n_metal1 == n1 for r in res)
def test_get_bimet_result__return_only_bimet_results(): res = db_inter.get_bimet_result(only_bimet=True, lim=20, return_list=True) assert all(0 < r.n_metal1 < r.num_atoms for r in res)
def test_get_bimet_result__return_num_atoms_matches(): for n in [13, 55]: res = db_inter.get_bimet_result(num_atoms=n, lim=5, return_list=True) assert all(r.num_atoms == n for r in res)
def test_get_bimet_result__return_num_shells_matches(): for s in [1, 2]: res = db_inter.get_bimet_result(num_shells=s, lim=5, return_list=True) assert all(r.nanoparticle.num_shells == s for r in res)
from ce_expansion.npdb import db_inter from pprint import pprint from ce_expansion.atomgraph.bcm import BCModel import numpy as np import os from pprint import pprint import ase.units as units this_dir = os.path.dirname(os.path.abspath(__file__)) res = db_inter.get_bimet_result('AuCu', 'icosahedron', 55, n_metal1=22) atoms = res.atoms_obj bond_list = res.nanoparticle.bonds_list metal_types = ['Cu', 'Au', "Ag"] # STEP 1 # create a BCModel object #TODO: account for this case in bcm #metal_types = ['Cu', 'Au', 'au', 'ag'] bcm = BCModel(atoms, bond_list, metal_types) assert bcm.metal_types == ['Ag', 'Au', 'Cu'] print("STEP 1") # STEP 2 # make sure everything passed in becomes an attribute print(bcm.atoms)
def test_get_bimet_result__return_shape_matches(): for shape in ['icosahedron', 'cuboctahedron']: res = db_inter.get_bimet_result(shape=shape, lim=5, return_list=True) assert all(r.shape == shape for r in res)
n_ag = n x_ag = n / num_atoms n_au = num_atoms - n x_au = n_au / num_atoms if n == 0: bcenergy = au_en laenergy = lars_au_en elif n == 309: bcenergy = ag_en laenergy = lars_ag_en else: # our bimet NP res = db_inter.get_bimet_result('agau', 'icosahedron', num_atoms=num_atoms, n_metal1=n, lim=1) atom = res.build_atoms_obj() atom = ase.Atoms(atom) atom.set_calculator(emt.EMT()) opt = ase.optimize.BFGS(atom) opt.run() bcenergy = atom.get_total_energy() save_bc(atom) # Larson's minimum bimet NP larson = ase.io.read(os.path.join(np_ga, 'larson_orig_structures', '%s.xyz' % atom.get_chemical_formula())) larson = ase.Atoms(larson) larson.set_calculator(emt.EMT()) opt = ase.optimize.BFGS(larson)
ax.set_yticklabels(['{:,.0%}'.format(x) for x in ax.get_yticks()[:-1]] + ['']) else: ax.set_ylabel('Number of Atoms') ax.set_ylim(0, max(cn_dist['tot_counts']) * 1.1) ax.bar(x, cn_dist['m1_counts'], color=m1_color, edgecolor='k', label=m1) ax.bar(x, cn_dist['m2_counts'], bottom=cn_dist['m1_counts'], color=m2_color, edgecolor='k', label=m2) ax.legend() fig.tight_layout() if show: plt.show() return fig, ax if __name__ == '__main__': metals = 'agau' shape = 'icosahedron' num_shells = 9 bonds = np.array(structure_gen.build_structure_sql(shape, num_shells) .bonds_list) res = db_inter.get_bimet_result(metals, shape=shape, num_shells=num_shells, only_bimet=True)[4] fig, ax = cn_dist_plot(res, pcty=True) plt.show()
minn = 1 if shape.startswith('cub') else 2 # number of shells on inside and outside to ignore in calculation buffer = 3 for s in range(2 * buffer + 1, 11): if shape.startswith('cub'): s -= 1 # get number of atoms n = db_inter.get_shell2num(shape, s) # get half such that metal1 has the extra atom (if natoms is odd) n = (n + n % 2) // 2 res = db_inter.get_bimet_result(metals, shape=shape, num_shells=s, n_metal1=n) # get ordering array ordering = np.array([int(i) for i in res.ordering]) # load bonds list bonds = res.nanoparticle.load_bonds_list() # build atomgraph object ag = atomgraph.AtomGraph(bonds, 'Au', 'Cu') # get atom indices for each shell shells = db_inter.build_atoms_in_shell_dict(shape, s) # create a 'Test Atom' to ensure shells are being correctly counted test_atom = res.build_atoms_obj()
def check_db_values(update_db=False, metal_opts=None): """ Checks CE values in database to ensure CE and EE match their ordering KArgs: - update_db (bool): if True and mismatch is found, the database will be updated to the correct CE and EE (Default: False) - metal_opts (list): can pass in list of metal combination options to check (Default: all metal pairs from results in DB) Returns: - (np.ndarray): CE's and EE's of mismatches found - each row contains: [CE-db, CE-actual, EE-db, EE-actual] """ # if None, get all metal pairs found in BimetallicResults table in DB if metal_opts is None: metal_opts = db_inter.build_metal_pairs_list() # track systems that failed test fails = [] # get all nanoparticle (shape, num_shell) pairs in database nanoparticles = set([(r.shape, r.num_shells) for r in db_inter.get_nanoparticle()]) # tracked number of results checked num_checked = 0 print('Checking results...') for shape, num_shells in nanoparticles: nanop = ga.structure_gen.build_structure_sql(shape, num_shells) for metals in metal_opts: # ensure metal types are sorted metals = sorted(metals) # find all bimetallic results matching shape, size, and metals results = db_inter.get_bimet_result(metals, shape=shape, num_shells=num_shells) # if no results found, continue to next metal combination if not results: continue # else create bcmraph object bcm = atomgraph.AtomGraph(nanop.bonds_list, metals[0], metals[1]) # iterate over results to compare CE in databse vs. # CE calculated with ordering for res in results: ordering = np.array(list(map(int, res.ordering))) n_metal2 = ordering.sum() actual_ce = bcm.getTotalCE(ordering) actual_ee = bcm.getEE(ordering) # increment number of results checkered num_checked += 1 # create output string outp = (f'{res.shape[:3].upper():>4}', f'{res.num_atoms:<7,}', f'{res.build_chem_formula():<15}') # if deviation, add info to fails list if abs(actual_ce - res.CE) > 1E-10: # print system with problem print(*outp, f'WRONG VALUE! ({actual_ce - res.CE:.3e} eV)') fails.append([ metals, shape, num_shells, n_metal2, res.CE, actual_ce, res.EE, actual_ee ]) # if update_db, correct the CE value # NOTE: this most likely means that CE is not optimized if update_db: db_inter.update_bimet_result(metals=metals, shape=res.shape, num_atoms=res.num_atoms, diameter=res.diameter, n_metal1=res.n_metal1, CE=actual_ce, ordering=res.ordering, EE=actual_ee, nanop=res.nanoparticle, allow_insert=False, ensure_ce_min=False) else: print(*outp, end='\r') fails = np.array(fails) nfail = len(fails) issue_str = 'issue' if nfail == 1 else 'issues' print(' ' * 50, end='\r') print(f'{nfail:,} {issue_str} found.') print(f'{num_checked:,} results checked.') return fails
def build_nmet2_nmet2shell_plot(metals, shape, num_shells, show_ee=True, show=True, save=False, pctx=False, pcty=False): """Plots number of metal2 in shell_i vs number of metal2 in NP Args: - metals (str): two metals (element names) in NP - shape (str): shape of NP CURRENTLY SUPPORTS: - cuboctahedron - elongated-pentagonal-bipyramid - icosahedron - num_shells (int): number of shells the NP is made of (excluding core atom) KArgs: show_ee (bool): plots excess energy (EE) on same plot with secondary axis (default: {True}) - show (bool): shows figure is True (default: True) - save (bool): saves figure is True (default: False) - pctx (bool): x-axis plotted as concentration of metal2 if True (default: {False}) - pcty (bool): y-axis plotted as percentage of shell_i that is metal2 (default: {False}) Returns: - (plt.Figure): matplotlib plot of results - (np.ndarray): matrix of results: [n_metal2, (n|x)metal2-shell_i, ..., EE] - (dict): minimum CE and minimum EE NP as datatable.BimetallicResults objs Raises: ValueError: num_shells must be greater than 0 """ if num_shells < 1: raise ValueError("num_shells is too small") # build dictionary of atom shell indices shell_dict = db_inter.build_atoms_in_shell_dict(shape, num_shells) # if pct use X else N xtype = 'X' if pctx else 'N' ytype = 'X' if pcty else 'N' # all results run with specific size, shape, and metals nanoparticles = db_inter.get_bimet_result(metals=metals, shape=shape, num_shells=num_shells) # get NP with lowest Cohesive Energy (CE) and Excess Energy (EE) min_atoms = { 'CE': min(nanoparticles, key=lambda i: i.CE), 'EE': min(nanoparticles, key=lambda i: i.EE) } # initialize results matrix results = np.zeros((len(nanoparticles), len(shell_dict) + 2)) # record counts in results matrix for i, nanop in enumerate(nanoparticles): results[i, 0] = nanop.n_metal2 for s in shell_dict: col = s + 1 # add number of metal2 in shell <s> to results results[i, col] = (nanop.build_atoms_obj()[shell_dict[s]].symbols == nanop.metal2).sum() # calculate as percentage of atoms that are "metal2" in given shell if pcty: results[i, col] = results[i, col] / len(shell_dict[s]) # record EE of nanoparticle results[i, -1] = nanop.EE # sort results by n_metal2 results = results[results[:, 0].argsort()] # convert x axis to metal 2 concentration if pctx if pctx: results[:, 0] = results[:, 0] / nanop.num_atoms fig, ax = plt.subplots(figsize=(9, 7)) plt.rcParams['axes.spines.right'] = show_ee if show_ee: # excess energy secondary axis ee_color = 'dodgerblue' ax2 = ax.twinx() ax2.tick_params(axis='y', labelcolor=ee_color) ax.scatter([], [], marker='o', label='EE', color=ee_color, s=50, edgecolor='k') ax2.scatter(results[:, 0], results[:, -1], marker='o', color=ee_color, edgecolor='k', s=50) ax2.set_ylabel('Excess Energy (eV / atom)', color=ee_color) # set ylim mag = (results[:, -1].max() - results[:, -1].min()) buffmin = mag * 0.1 buffmax = mag * 0.3 ax2.set_ylim(results[:, -1].min() - buffmin, results[:, -1].max() + buffmax) # plot (n or x)i vs (n or x)i-shell for i, col in enumerate(range(results.shape[1] - 2, 0, -1)): # outer shell = Surface if not i: lab = 'Surface' # DO NOT PLOT CORE ATOM (ignore it) elif col == 1: lab = 'Core' continue else: lab = 'Shell %i' % (col - 1) ax.plot(results[:, 0], results[:, col], 'o-', label=lab, zorder=col) if pctx: ax.set_xlim(-0.1, 1.1) if pcty: ax.set_ylim(0, 1.19) ax.set_yticklabels(['{:,.0%}'.format(x) for x in ax.get_yticks()]) ax.set_xlabel('$\\rm %s_{%s}$ in NP' % (xtype, nanop.metal2)) ax.set_ylabel('$\\rm %s_{%s}$ in Shell$\\rm _i$' % (ytype, nanop.metal2)) title = '%s atom %s%s %s NP' % (nanop.num_atoms, nanop.metal1, nanop.metal2, nanop.shape) ax.set_title(title) ax.legend(loc='upper center', ncol=4, handletextpad=0.2, columnspacing=0.6) fig.tight_layout() user = os.path.expanduser('~') path = os.path.join( box, 'Michael_Cowan_PhD_research', 'np_ga', 'FIGURES', '%smet_vs_%smetshell-LARSON\\%s%s-%s' % (xtype, ytype, nanop.metal1, nanop.metal2, nanop.shape[:3])) if save: if not os.path.isdir(path): pathlib.Path(path).mkdir(parents=True, exist_ok=True) if show_ee: path = os.path.join(path, 'ee') if not os.path.isdir(path): os.mkdir(path) fig.savefig(os.path.join(path, title.replace(' ', '_') + '.png'), dpi=50) fig.savefig(os.path.join(path, title.replace(' ', '_') + '.svg')) res = min_atoms['EE'] res.save_np( os.path.join( path, 'EE-%s-%s.xyz' % (res.build_chem_formula(), res.shape[:3]))) if show: plt.show() return fig, results, min_atoms
def batch_run_ga_from_ga_module__needs_fixing(metals, shape, save_data=True, batch_runinfo=None, shells=None, max_generations=5000, min_generations=-1, max_nochange=2000, add_coreshell=True, **kwargs): """ Submission function to run GAs of a given metal combination and shape, sweeping over different sizes (measured in number of shells) - capable of saving minimum structures as XYZs, logging GA stats, saving all run info as excel, and creating a 3D surface plot of results Args: - metals (iterator): list of two metals used in the bimetallic NP - shape (str): shape of NP that is being studied NOTE: currently supports - icosahedron - cuboctahedron - fcc-cube - elongated-trigonal-pyramic KArgs: - plotit (bool): if true, a 3D surface plot is made of GA sims dope concentration vs. size vs. excess energy (Default: False) - save_data (bool): - if true, GA sim data is saved to BimetallicResults table in database (Default: True) - batch_runinfo (str): if str, add to BimetallicLog entry (Default: None) - shells (int || list): if int, only that shell size is simulated elif list of ints, nshell_range = shells (Default: None) - max_generations (int): if not -1, use specified value as max generations for each GA sim (Default: 5000) - min_generations (int): if not -1, use specified value as min generations that run before max_nochange criteria is applied (Default: -1) - max_nochange (int): maximum generations GA will go without a change in minimum CE (Default: 2000) - add_coreshell (bool): if True, core shell structures will be included in GA simulations (Default: True) Returns: None """ # track start of run start_time = dt.now() # clear previous plots and define desktop and data paths plt.close('all') desk = os.path.join(os.path.expanduser('~'), 'desktop') # number of shells range to sim ga for each shape default_shell_range = [1, 11] shape2shell = { 'cuboctahedron': default_shell_range, 'elongated-pentagonal-bipyramid': default_shell_range, 'fcc-cube': default_shell_range, 'icosahedron': default_shell_range } nshell_range = shape2shell[shape] if shells: if isinstance(shells, int): nshell_range = [shells, shells + 1] else: nshell_range = shells nstructs = len(range(*nshell_range)) if not nstructs: print(nshell_range) return # print run info print('') print('RUN INFO'.center(CENTER, '-')) print(' Metals: %s' % (' '.join(metals_types))) print(' Shape: %s' % shape) print(' Save GA Results: %s' % bool(save_data)) print(' Shell Range: %s' % str(nshell_range)) print('-' * CENTER) # keep track of total new minimum CE structs (based on saved structs) tot_new_structs = 0 # count total structs tot_structs = 0 for struct_i, nshells in enumerate(range(*nshell_range)): # build atom, adjacency list, and AtomGraph nanop = structure_gen.build_structure_sql(shape, nshells, build_bonds_arr=True) num_atoms = len(nanop) diameter = nanop.get_diameter() BCModel(nanop.atoms, nanop.bonds_list, metal_types) ag = BCModel(nanop.bonds_list, metal1, metal2) # check to see if monometallic results exist # if not, calculate them mono1 = db_inter.get_bimet_result(metals=metals, shape=shape, num_atoms=num_atoms, n_metal1=num_atoms, lim=1) if not mono1: mono_ord = np.zeros(num_atoms) mono_ce1 = ag.getTotalCE(mono_ord) mono1 = db_inter.update_bimet_result( metals=metals, shape=shape, num_atoms=num_atoms, diameter=diameter, n_metal1=num_atoms, CE=mono_ce1, ordering=''.join(str(int(i)) for i in mono_ord), EE=0, nanop=nanop, allow_insert=True) mono2 = db_inter.get_bimet_result(metals=metals, shape=shape, num_atoms=num_atoms, n_metal1=0, lim=1) if not mono2: mono_ord = np.ones(num_atoms) mono_ce2 = ag.getTotalCE(mono_ord) mono2 = db_inter.update_bimet_result( metals=metals, shape=shape, num_atoms=num_atoms, diameter=diameter, n_metal1=0, CE=mono_ce2, ordering=''.join(str(int(i)) for i in mono_ord), EE=0, nanop=nanop, allow_insert=True) # USE THIS TO TEST EVERY CONCENTRATION if nanop.num_atoms < 366: n = np.arange(0, num_atoms + 1) x = n / float(num_atoms) else: # x = metal2 concentration [0, 1] x = np.linspace(0, 1, 11) n = (x * num_atoms).astype(int) # recalc concentration to match n x = n / float(num_atoms) # add core-shell structures list of comps to run if add_coreshell: srfatoms = db_inter.build_atoms_in_shell_dict(shape, nshells) nsrf = len(srfatoms) ncore = num_atoms - nsrf n = np.unique(n.tolist() + [ncore, nsrf]) x = n / float(num_atoms) # total structures checked ( - 2 to exclude monometallics) tot_structs += float(len(n) - 2) starting_outp = '%s%s in %i atom %s' % (metal1, metal2, num_atoms, shape) print(starting_outp.center(CENTER)) # track min structures for each size new_min_structs = 0 # sweep over different compositions for i, nmet2 in enumerate(n): # INITIALIZE POP object pop = Pop(nanop.get_atoms_obj_skel().copy(), nanop.bonds_list, metals, shape=shape, n_metal2=nmet2, **kwargs) # run GA simulation pop.run(max_generations, min_generations, max_nochange) # if new minimum CE found and <save_data> # store result in DB if pop.is_new_min() and save_data: new_min_structs += 1 tot_new_structs += 1 pop.save_to_db() outp = 'Completed Size %i of %i (%i new mins)' % ( struct_i + 1, nstructs, new_min_structs) print('-' * CENTER) print(outp.center(CENTER)) print('-' * CENTER) # insert log results into DB if save_data: db_inter.insert_bimetallic_log(start_time=start_time, metal1=metal1, metal2=metal2, shape=shape, ga_generations=max_generations, shell_range='%i - %i' % tuple(nshell_range), new_min_structs=tot_new_structs, tot_structs=tot_structs, batch_run_num=batch_runinfo)
from ce_expansion.npdb import db_inter, datatables """ Test all results in BimetallicResults to ensure that the ordering compression algo is working correctly """ res = db_inter.get_bimet_result() for i, r in enumerate(res): print(f' testing {i:06d} {r.num_atoms:05d} atoms', end='\r') test = datatables.BimetallicResults(r.metal1, r.metal2, r.shape, r.num_atoms, r.diameter, r.n_metal1, r.n_metal2, r.CE, r.EE, r.ordering) assert (test.ordering == r.ordering).all() assert test.n_metal2 == r.n_metal2 == sum(map(int, test.ordering)) assert len(r.ordering) == r.num_atoms