예제 #1
0
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
예제 #2
0
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)
예제 #3
0
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)
예제 #4
0
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)
예제 #5
0
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)
예제 #6
0
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)
예제 #7
0
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)
예제 #8
0
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)
예제 #9
0
    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)
예제 #10
0
        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()
예제 #11
0
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()
예제 #12
0
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
예제 #13
0
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
예제 #14
0
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