예제 #1
0
 def test_chemicalSRO(self):
     df_sc = pd.DataFrame({'struct': [self.sc], 'site': [0]})
     df_cscl = pd.DataFrame({'struct': [self.cscl], 'site': [0]})
     vnn = ChemicalSRO.from_preset("VoronoiNN", cutoff=6.0)
     vnn.fit(df_sc[['struct', 'site']])
     vnn_csros = vnn.featurize_dataframe(df_sc, ['struct', 'site'])
     self.assertAlmostEqual(vnn_csros['CSRO_Al_VoronoiNN'][0], 0.0)
     vnn = ChemicalSRO(VoronoiNN(), includes="Cs")
     vnn.fit(df_cscl[['struct', 'site']])
     vnn_csros = vnn.featurize_dataframe(df_cscl, ['struct', 'site'])
     self.assertAlmostEqual(vnn_csros['CSRO_Cs_VoronoiNN'][0], 0.0714285714)
     vnn = ChemicalSRO(VoronoiNN(), excludes="Cs")
     vnn.fit(df_cscl[['struct', 'site']])
     vnn_csros = vnn.featurize_dataframe(df_cscl, ['struct', 'site'])
     self.assertAlmostEqual(vnn_csros['CSRO_Cl_VoronoiNN'][0], -0.0714285714)
     jmnn = ChemicalSRO.from_preset("JMolNN", el_radius_updates={"Al": 1.55})
     jmnn.fit(df_sc[['struct', 'site']])
     jmnn_csros = jmnn.featurize_dataframe(df_sc, ['struct', 'site'])
     self.assertAlmostEqual(jmnn_csros['CSRO_Al_JMolNN'][0], 0.0)
     jmnn = ChemicalSRO.from_preset("JMolNN")
     jmnn.fit(df_cscl[['struct', 'site']])
     jmnn_csros = jmnn.featurize_dataframe(df_cscl, ['struct', 'site'])
     self.assertAlmostEqual(jmnn_csros['CSRO_Cs_JMolNN'][0], -0.5)
     self.assertAlmostEqual(jmnn_csros['CSRO_Cl_JMolNN'][0], -0.5)
     mdnn = ChemicalSRO.from_preset("MinimumDistanceNN")
     mdnn.fit(df_cscl[['struct', 'site']])
     mdnn_csros = mdnn.featurize_dataframe(df_cscl, ['struct', 'site'])
     self.assertAlmostEqual(mdnn_csros['CSRO_Cs_MinimumDistanceNN'][0], 0.5)
     self.assertAlmostEqual(mdnn_csros['CSRO_Cl_MinimumDistanceNN'][0], -0.5)
     monn = ChemicalSRO.from_preset("MinimumOKeeffeNN")
     monn.fit(df_cscl[['struct', 'site']])
     monn_csros = monn.featurize_dataframe(df_cscl, ['struct', 'site'])
     self.assertAlmostEqual(monn_csros['CSRO_Cs_MinimumOKeeffeNN'][0], 0.5)
     self.assertAlmostEqual(monn_csros['CSRO_Cl_MinimumOKeeffeNN'][0], -0.5)
     mvnn = ChemicalSRO.from_preset("MinimumVIRENN")
     mvnn.fit(df_cscl[['struct', 'site']])
     mvnn_csros = mvnn.featurize_dataframe(df_cscl, ['struct', 'site'])
     self.assertAlmostEqual(mvnn_csros['CSRO_Cs_MinimumVIRENN'][0], 0.5)
     self.assertAlmostEqual(mvnn_csros['CSRO_Cl_MinimumVIRENN'][0], -0.5)
     # test fit + transform
     vnn = ChemicalSRO.from_preset("VoronoiNN")
     vnn.fit(df_cscl[['struct', 'site']])  # dataframe
     vnn_csros = vnn.transform(df_cscl[['struct', 'site']].values)
     self.assertAlmostEqual(vnn_csros[0][0], 0.071428571428571286)
     self.assertAlmostEqual(vnn_csros[0][1], -0.071428571428571286)
     vnn = ChemicalSRO.from_preset("VoronoiNN")
     vnn.fit(df_cscl[['struct', 'site']].values)  # np.array
     vnn_csros = vnn.transform(df_cscl[['struct', 'site']].values)
     self.assertAlmostEqual(vnn_csros[0][0], 0.071428571428571286)
     self.assertAlmostEqual(vnn_csros[0][1], -0.071428571428571286)
     vnn = ChemicalSRO.from_preset("VoronoiNN")
     vnn.fit([[self.cscl, 0]])  # list
     vnn_csros = vnn.transform([[self.cscl, 0]])
     self.assertAlmostEqual(vnn_csros[0][0], 0.071428571428571286)
     self.assertAlmostEqual(vnn_csros[0][1], -0.071428571428571286)
     # test fit_transform
     vnn = ChemicalSRO.from_preset("VoronoiNN")
     vnn_csros = vnn.fit_transform(df_cscl[['struct', 'site']].values)
     self.assertAlmostEqual(vnn_csros[0][0], 0.071428571428571286)
     self.assertAlmostEqual(vnn_csros[0][1], -0.071428571428571286)
def get_adj_matrix(structure, tol=0, cutoff=10):
    n_atoms = len(structure)
    adj_matrix_no_weight = np.zeros((n_atoms, n_atoms))
    adj_matrix_multi_edge = np.zeros((n_atoms, n_atoms))
    adj_matrix_sol_angle = np.zeros((n_atoms, n_atoms))

    # compute adjacency matrix
    voro = VoronoiNN(tol=tol,
                     cutoff=cutoff,
                     allow_pathological=True,
                     extra_nn_info=False,
                     compute_adj_neighbors=False)
    all_nn_info = voro.get_all_nn_info(structure)
    # we force a graph not to have self-loops
    for center_index in range(n_atoms):
        for nn_index in range(len(all_nn_info[center_index])):
            nn_site_index = all_nn_info[center_index][nn_index]['site_index']
            if nn_site_index > center_index:
                # weight_type: None
                adj_matrix_no_weight[center_index, nn_site_index] = 1
                # weight_type: 'multi_edge'
                adj_matrix_multi_edge[center_index, nn_site_index] += 1
                # weight_type: 'sol_angle':
                sol_angle = all_nn_info[center_index][nn_index]['weight']
                adj_matrix_sol_angle[center_index, nn_site_index] += sol_angle
            else:
                adj_matrix_no_weight[center_index, nn_site_index] = \
                        adj_matrix_no_weight[nn_site_index, center_index]
                adj_matrix_multi_edge[center_index, nn_site_index] = \
                        adj_matrix_multi_edge[nn_site_index, center_index]
                adj_matrix_sol_angle[center_index, nn_site_index] = \
                        adj_matrix_sol_angle[nn_site_index, center_index]

    return adj_matrix_no_weight, adj_matrix_multi_edge, adj_matrix_sol_angle
예제 #3
0
def average_coordination_number(structures, freq=10):
    """
    Calculates the ensemble averaged Voronoi coordination numbers
    of a list of Structures using VoronoiNN.
    Typically used for analyzing the output of a Molecular Dynamics run.
    Args:
        structures (list): list of Structures.
        freq (int): sampling frequency of coordination number [every freq steps].
    Returns:
        Dictionary of elements as keys and average coordination numbers as values.
    """
    coordination_numbers = {}
    for spec in structures[0].composition.as_dict().keys():
        coordination_numbers[spec] = 0.0
    count = 0
    for t in range(len(structures)):
        if t % freq != 0:
            continue
        count += 1
        vnn = VoronoiNN()
        for atom in range(len(structures[0])):
            cn = vnn.get_cn(structures[t], atom, use_weights=True)
            coordination_numbers[structures[t][atom].species_string] += cn
    elements = structures[0].composition.as_dict()
    for el in coordination_numbers:
        coordination_numbers[el] = coordination_numbers[el] / elements[
            el] / count
    return coordination_numbers
예제 #4
0
    def calculate_coordination_of_bulk_atoms(self, bulk_atoms):
        '''
        Finds all unique atoms in a bulk structure and then determines their
        coordination number. Then parses these coordination numbers into a
        dictionary whose keys are the elements of the atoms and whose values are
        their possible coordination numbers.
        For example: `bulk_cns = {'Pt': {3., 12.}, 'Pd': {12.}}`

        Arg:
            bulk_atoms  An `ase.Atoms` object of the bulk structure.
        Returns:
            bulk_cn_dict    A defaultdict whose keys are the elements within
                            `bulk_atoms` and whose values are a set of integers of the
                            coordination numbers of that element.
        '''
        voronoi_nn = VoronoiNN(tol=0.1)  # 0.1 chosen for better detection

        # Object type conversion so we can use Voronoi
        bulk_struct = AseAtomsAdaptor.get_structure(bulk_atoms)
        sga = SpacegroupAnalyzer(bulk_struct)
        sym_struct = sga.get_symmetrized_structure()

        # We'll only loop over the symmetrically distinct sites for speed's sake
        bulk_cn_dict = defaultdict(set)
        for idx in sym_struct.equivalent_indices:
            site = sym_struct[idx[0]]
            cn = voronoi_nn.get_cn(sym_struct, idx[0], use_weights=True)
            cn = round(cn, 5)
            bulk_cn_dict[site.species_string].add(cn)
        return bulk_cn_dict
예제 #5
0
    def test_all_nn_classes(self):
        self.assertEqual(MinimumDistanceNN(cutoff=5, get_all_sites=True).get_cn(
            self.cscl, 0), 14)
        self.assertEqual(MinimumDistanceNN().get_cn(self.diamond, 0), 4)
        self.assertEqual(MinimumDistanceNN().get_cn(self.nacl, 0), 6)
        self.assertEqual(MinimumDistanceNN().get_cn(self.lifepo4, 0), 6)
        self.assertEqual(MinimumDistanceNN(tol=0.01).get_cn(self.cscl, 0), 8)
        self.assertEqual(MinimumDistanceNN(tol=0.1).get_cn(self.mos2, 0), 6)

        for image in MinimumDistanceNN(tol=0.1).get_nn_images(self.mos2, 0):
            self.assertTrue(image in [(0, 0, 0), (0, 1, 0), (-1, 0, 0),
                                      (0, 0, 0), (0, 1, 0), (-1, 0, 0)])

        okeeffe = MinimumOKeeffeNN(tol=0.01)
        self.assertEqual(okeeffe.get_cn(self.diamond, 0), 4)
        self.assertEqual(okeeffe.get_cn(self.nacl, 0), 6)
        self.assertEqual(okeeffe.get_cn(self.cscl, 0), 8)
        self.assertEqual(okeeffe.get_cn(self.lifepo4, 0), 2)

        virenn = MinimumVIRENN(tol=0.01)
        self.assertEqual(virenn.get_cn(self.diamond, 0), 4)
        self.assertEqual(virenn.get_cn(self.nacl, 0), 6)
        self.assertEqual(virenn.get_cn(self.cscl, 0), 8)
        self.assertEqual(virenn.get_cn(self.lifepo4, 0), 2)

        brunner_recip = BrunnerNN_reciprocal(tol=0.01)
        self.assertEqual(brunner_recip.get_cn(self.diamond, 0), 4)
        self.assertEqual(brunner_recip.get_cn(self.nacl, 0), 6)
        self.assertEqual(brunner_recip.get_cn(self.cscl, 0), 14)
        self.assertEqual(brunner_recip.get_cn(self.lifepo4, 0), 6)

        brunner_rel = BrunnerNN_relative(tol=0.01)
        self.assertEqual(brunner_rel.get_cn(self.diamond, 0), 4)
        self.assertEqual(brunner_rel.get_cn(self.nacl, 0), 6)
        self.assertEqual(brunner_rel.get_cn(self.cscl, 0), 14)
        self.assertEqual(brunner_rel.get_cn(self.lifepo4, 0), 6)

        brunner_real = BrunnerNN_real(tol=0.01)
        self.assertEqual(brunner_real.get_cn(self.diamond, 0), 4)
        self.assertEqual(brunner_real.get_cn(self.nacl, 0), 6)
        self.assertEqual(brunner_real.get_cn(self.cscl, 0), 14)
        self.assertEqual(brunner_real.get_cn(self.lifepo4, 0), 30)

        econn = EconNN()
        self.assertEqual(econn.get_cn(self.diamond, 0), 4)
        self.assertEqual(econn.get_cn(self.nacl, 0), 6)
        self.assertEqual(econn.get_cn(self.cscl, 0), 14)
        self.assertEqual(econn.get_cn(self.lifepo4, 0), 6)

        voroinn = VoronoiNN(tol=0.5)
        self.assertEqual(voroinn.get_cn(self.diamond, 0), 4)
        self.assertEqual(voroinn.get_cn(self.nacl, 0), 6)
        self.assertEqual(voroinn.get_cn(self.cscl, 0), 8)
        self.assertEqual(voroinn.get_cn(self.lifepo4, 0), 6)

        crystalnn = CrystalNN()
        self.assertEqual(crystalnn.get_cn(self.diamond, 0), 4)
        self.assertEqual(crystalnn.get_cn(self.nacl, 0), 6)
        self.assertEqual(crystalnn.get_cn(self.cscl, 0), 8)
        self.assertEqual(crystalnn.get_cn(self.lifepo4, 0), 6)
예제 #6
0
def average_coordination_number(structures, freq=10):
    """
    Calculates the ensemble averaged Voronoi coordination numbers
    of a list of Structures using VoronoiNN.
    Typically used for analyzing the output of a Molecular Dynamics run.
    Args:
        structures (list): list of Structures.
        freq (int): sampling frequency of coordination number [every freq steps].
    Returns:
        Dictionary of elements as keys and average coordination numbers as values.
    """
    coordination_numbers = {}
    for spec in structures[0].composition.as_dict().keys():
        coordination_numbers[spec] = 0.0
    count = 0
    for t in range(len(structures)):
        if t % freq != 0:
            continue
        count += 1
        vnn = VoronoiNN()
        for atom in range(len(structures[0])):
            cn = vnn.get_cn(structures[t], atom, use_weights=True)
            coordination_numbers[structures[t][atom].species_string] += cn
    elements = structures[0].composition.as_dict()
    for el in coordination_numbers:
        coordination_numbers[
            el] = coordination_numbers[el] / elements[el] / count
    return coordination_numbers
예제 #7
0
def _count_voronoinn(s1, mess='face_dist'):
    """

    :type s1: pymatgen structre type
    """
    vo = VoronoiNN(tol=0.01,
                   allow_pathological=False,
                   weight='solid_angle',
                   extra_nn_info=True,
                   compute_adj_neighbors=True)
    vo_data = vo.get_all_nn_info(s1)
    site_ele = [i.specie.name for i in s1.sites]
    nei_all_ele = _count_i(vo_data, site_ele, mess=mess)
    a_dict = {}
    for i in s1.types_of_specie:
        a_list = []
        for j in nei_all_ele:
            if j[0] == i.name:
                a_list.extend(j[1])
        a_list = np.array(a_list)
        a_dict[i] = a_list
    result_0 = a_dict[s1.types_of_specie[0]]
    number = []
    for ele in s1.types_of_specie:
        b_index = np.where(result_0[:, 0] == ele.name)[0]
        int_ = result_0[b_index, 1:].astype(float)
        number.append(np.sum(int_) / s1.num_sites)
    return number
예제 #8
0
def find_bulk_cn_dict(bulk_atoms):
    '''
    Get a dictionary of coordination numbers
    for each distinct site in the bulk structure.

    Taken from pymatgen.core.surface Class Slab
    `get_surface_sites`.
    https://pymatgen.org/pymatgen.core.surface.html
    '''
    struct = AseAtomsAdaptor.get_structure(bulk_atoms)
    sga = SpacegroupAnalyzer(struct)
    sym_struct = sga.get_symmetrized_structure()
    unique_indices = [equ[0] for equ in sym_struct.equivalent_indices]
    # Get a dictionary of unique coordination numbers
    # for atoms in each structure.
    # for example, Pt[1,1,1] would have cn=3 and cn=12
    # depends on the Pt atom.
    voronoi_nn = VoronoiNN()
    cn_dict = {}
    for idx in unique_indices:
        elem = sym_struct[idx].species_string
        if elem not in cn_dict.keys():
            cn_dict[elem] = []
        cn = voronoi_nn.get_cn(sym_struct, idx, use_weights=True)
        cn = float('%.5f' % (round(cn, 5)))
        if cn not in cn_dict[elem]:
            cn_dict[elem].append(cn)
    return cn_dict
예제 #9
0
def find_adsorption_vector(bulk_cn_dict, slab_atoms, surface_indices,
                           adsorption_site):
    """
    Returns the vector of an adsorption site representing the
    furthest distance from the neighboring atoms.
    The vector is a (1,3) numpy array.
    The idea comes from CatKit.
    https://catkit.readthedocs.io/en/latest/?badge=latest

    Arg:
        bulk_cn_dict         A dictionary of coordination numbers
                             for each distinct site in the respective bulk structure
        slab_atoms           The `ase.Atoms` format of a supercell slab.
        surface_indices      The index of the surface atoms in a list.
        adsorption_site      A `numpy.ndarray` object that contains the x-y-z coordinates
                             of the adsorptions sites.

    Output:
        vector            numpy.ndarray. Adsorption vector for an adsorption site.
    """
    vnn = VoronoiNN(allow_pathological=True)

    slab_atoms += Atoms('U', [adsorption_site])
    U_index = slab_atoms.get_chemical_symbols().index('U')
    struct_with_U = AseAtomsAdaptor.get_structure(slab_atoms)
    nn_info = vnn.get_nn_info(struct_with_U, n=U_index)
    nn_indices = [neighbor['site_index'] for neighbor in nn_info]
    surface_nn_indices = [idx for idx in nn_indices if idx in surface_indices]

    # get the index of the closest 4 atom to the site to form a plane
    # chose 4 because it will gaurantee a more accurate plane for edge cases
    nn_dists_from_U = {
        idx:
        np.linalg.norm(slab_atoms[idx].position - slab_atoms[U_index].position)
        for idx in surface_nn_indices
    }
    sorted_dists = {
        idx: distance
        for idx, distance in sorted(nn_dists_from_U.items(),
                                    key=lambda item: item[1])
    }
    closest_4_nn_indices = np.array(list(sorted_dists.keys())[:4], dtype=int)
    plane_coords = struct_with_U.cart_coords[closest_4_nn_indices]
    vector = _plane_normal(plane_coords)

    # Check to see if the vector is reasonable.
    # set an arbitay threshold where the vector and [0, 0, 1]
    # should be less than 60 degrees.
    # If someone has a better way to detect in the future, go for it
    if _ang_between_vectors(np.array([0., 0., 1.]), vector) > 60.:
        message = ('Warning: this might be an edge case where the '
                   'adsorption vector is not appropriate.'
                   ' We will place adsorbates using default [0, 0, 1] vector.')
        warnings.warn(message)
        vector = np.array([0., 0., 1.])

    del slab_atoms[[U_index]]
    return vector
예제 #10
0
    def test_all_nn_classes(self):
        self.assertAlmostEqual(MinimumDistanceNN().get_cn(self.diamond, 0), 4)
        self.assertAlmostEqual(MinimumDistanceNN().get_cn(self.nacl, 0), 6)
        self.assertAlmostEqual(
            MinimumDistanceNN(tol=0.01).get_cn(self.cscl, 0), 8)
        self.assertAlmostEqual(
            MinimumDistanceNN(tol=0.1).get_cn(self.mos2, 0), 6)
        for image in MinimumDistanceNN(tol=0.1).get_nn_images(self.mos2, 0):
            self.assertTrue(image in [(0, 0, 0), (0, 1,
                                                  0), (-1, 0,
                                                       0), (0, 0,
                                                            0), (0, 1,
                                                                 0), (-1, 0,
                                                                      0)])

        self.assertAlmostEqual(
            MinimumOKeeffeNN(tol=0.01).get_cn(self.diamond, 0), 4)
        self.assertAlmostEqual(
            MinimumOKeeffeNN(tol=0.01).get_cn(self.nacl, 0), 6)
        self.assertAlmostEqual(
            MinimumOKeeffeNN(tol=0.01).get_cn(self.cscl, 0), 8)

        self.assertAlmostEqual(
            MinimumVIRENN(tol=0.01).get_cn(self.diamond, 0), 4)
        self.assertAlmostEqual(MinimumVIRENN(tol=0.01).get_cn(self.nacl, 0), 6)
        self.assertAlmostEqual(MinimumVIRENN(tol=0.01).get_cn(self.cscl, 0), 8)

        self.assertAlmostEqual(
            BrunnerNN_reciprocal(tol=0.01).get_cn(self.diamond, 0), 4)
        self.assertAlmostEqual(
            BrunnerNN_reciprocal(tol=0.01).get_cn(self.nacl, 0), 6)
        self.assertAlmostEqual(
            BrunnerNN_reciprocal(tol=0.01).get_cn(self.cscl, 0), 14)

        self.assertAlmostEqual(
            BrunnerNN_relative(tol=0.01).get_cn(self.diamond, 0), 16)
        self.assertAlmostEqual(
            BrunnerNN_relative(tol=0.01).get_cn(self.nacl, 0), 18)
        self.assertAlmostEqual(
            BrunnerNN_relative(tol=0.01).get_cn(self.cscl, 0), 8)

        self.assertAlmostEqual(
            BrunnerNN_real(tol=0.01).get_cn(self.diamond, 0), 16)
        self.assertAlmostEqual(
            BrunnerNN_real(tol=0.01).get_cn(self.nacl, 0), 18)
        self.assertAlmostEqual(
            BrunnerNN_real(tol=0.01).get_cn(self.cscl, 0), 8)

        self.assertAlmostEqual(EconNN(tol=0.01).get_cn(self.diamond, 0), 4)
        self.assertAlmostEqual(EconNN(tol=0.01).get_cn(self.nacl, 0), 6)
        self.assertAlmostEqual(EconNN(tol=0.01).get_cn(self.cscl, 0), 14)

        self.assertAlmostEqual(VoronoiNN(tol=0.5).get_cn(self.diamond, 0), 4)
        self.assertAlmostEqual(VoronoiNN(tol=0.5).get_cn(self.nacl, 0), 6)
        self.assertAlmostEqual(VoronoiNN(tol=0.5).get_cn(self.cscl, 0), 8)
예제 #11
0
    def _find_surface_atoms_with_voronoi(self, bulk_atoms, surface_atoms):
        '''
        Labels atoms as surface or bulk atoms according to their coordination
        relative to their bulk structure. If an atom's coordination is less than it
        normally is in a bulk, then we consider it a surface atom. We calculate the
        coordination using pymatgen's Voronoi algorithms.

        Note that if a single element has different sites within a bulk and these
        sites have different coordinations, then we consider slab atoms
        "under-coordinated" only if they are less coordinated than the most under
        undercoordinated bulk atom. For example:  Say we have a bulk with two Cu
        sites. One site has a coordination of 12 and another a coordination of 9.
        If a slab atom has a coordination of 10, we will consider it a bulk atom.

        Args:
            bulk_atoms      `ase.Atoms` of the bulk structure the surface was cut
                            from.
            surface_atoms   `ase.Atoms` of the surface
        Returns:
            tags    A list of 0's and 1's whose indices align with the atoms in
                    `surface_atoms`. 0's indicate a bulk atom and 1 indicates a
                    surface atom.
        '''
        # Initializations
        surface_struct = AseAtomsAdaptor.get_structure(surface_atoms)
        center_of_mass = self.calculate_center_of_mass(surface_struct)
        bulk_cn_dict = self.calculate_coordination_of_bulk_atoms(bulk_atoms)
        voronoi_nn = VoronoiNN(tol=0.1)  # 0.1 chosen for better detection

        tags = []
        for idx, site in enumerate(surface_struct):

            # Tag as surface atom only if it's above the center of mass
            if site.frac_coords[2] > center_of_mass[2]:
                try:

                    # Tag as surface if atom is under-coordinated
                    cn = voronoi_nn.get_cn(surface_struct,
                                           idx,
                                           use_weights=True)
                    cn = round(cn, 5)
                    if cn < min(bulk_cn_dict[site.species_string]):
                        tags.append(1)
                    else:
                        tags.append(0)

                # Tag as surface if we get a pathological error
                except RuntimeError:
                    tags.append(1)

            # Tag as bulk otherwise
            else:
                tags.append(0)
        return tags
예제 #12
0
    def fxn(self, unit, base, start, final):
        material = unit['material'][1]  #type: str
        elements = unit['elements'].copy()
        elements.pop(elements.index('O'))

        base_s = Poscar.from_dict(base['poscar']).structure
        start_s = Poscar.from_dict(start['poscar']).structure
        run_s = Poscar.from_dict(
            self.get_run(unit, base, start, final)['poscar']).structure
        # print(len(base_s))
        # print(len(start_s))
        start_i = get_coords(base_s.copy(), run_s.copy())
        print(start_i)
        vnn = VoronoiNN(targets=[Element(x) for x in elements])
        start_vnn = vnn.get_nn_info(base_s, start_i)

        weight = {}
        total_length = {}
        for pt in start_vnn:
            # print('{}: {:3.2f}'.format(pt['site_index'], pt['weight']))
            temp_weight = round(pt['weight'] + 0.45)
            if pt['site'].species_string in weight:
                weight[pt['site'].species_string] = weight[
                    pt['site'].species_string] + temp_weight
            else:
                weight[pt['site'].species_string] = temp_weight
            if temp_weight >= 0.99:
                bond_length = base_s.get_distance(start_i, pt['site_index'])
                if pt['site'].species_string in total_length:
                    total_length[pt['site'].species_string] = total_length[
                        pt['site'].species_string] + bond_length
                else:
                    total_length[pt['site'].species_string] = bond_length

        if material.find(elements[0].lower()) == 0 and material.find(
                elements[1].lower()) == 0:
            raise Exception('Both Could be Element A')
        elif material.find(elements[0].lower()) == 0:
            bonds = {
                'A_Bonds': weight[elements[0]],
                'A_length': total_length[elements[0]] / weight[elements[0]],
                'B_Bonds': weight[elements[1]],
                'B_length': total_length[elements[1]] / weight[elements[1]],
            }
        elif material.find(elements[1].lower()) == 0:
            bonds = {
                'A_Bonds': weight[elements[1]],
                'A_length': total_length[elements[1]] / weight[elements[1]],
                'B_Bonds': weight[elements[0]],
                'B_length': total_length[elements[0]] / weight[elements[0]],
            }
        else:
            raise Exception('Couldn\'t find A')
        return bonds
예제 #13
0
    def featurize(self, strc):
        # Get the Voronoi tessellation of each site
        voro = VoronoiNN()
        nns = [voro.get_voronoi_polyhedra(strc, i) for i in range(len(strc))]

        # Compute the radius of largest possible atom for each site
        #  The largest radius is equal to the distance from the center of the
        #   cell to the closest Voronoi face
        max_r = [min(x['face_dist'] for x in nn.values()) for nn in nns]

        # Compute the packing efficiency
        return [4. / 3. * np.pi * np.power(max_r, 3).sum() / strc.volume]
예제 #14
0
 def get_ave_cn(self):
     coordination_numbers = {}
     vnn = VoronoiNN()
     for spec in self.poscar.structure.composition.as_dict().keys():
         coordination_numbers[spec] = 0.0
     for j, atom in enumerate(self.poscar.structure):
         cn = vnn.get_cn(self.poscar.structure, j, use_weights=True)
         coordination_numbers[atom.species_string] += cn
     elements = self.poscar.structure.composition.as_dict()
     for el in coordination_numbers:
         coordination_numbers[el] = coordination_numbers[el] / elements[el]
     return coordination_numbers
예제 #15
0
 def test_cns(self):
     cnv = CoordinationNumber.from_preset('VoronoiNN')
     self.assertEqual(len(cnv.feature_labels()), 1)
     self.assertEqual(cnv.feature_labels()[0], 'CN_VoronoiNN')
     self.assertAlmostEqual(cnv.featurize(self.sc, 0)[0], 6)
     self.assertAlmostEqual(cnv.featurize(self.cscl, 0)[0], 14)
     self.assertAlmostEqual(cnv.featurize(self.cscl, 1)[0], 14)
     self.assertEqual(len(cnv.citations()), 2)
     cnv = CoordinationNumber(VoronoiNN(), use_weights='sum')
     self.assertEqual(cnv.feature_labels()[0], 'CN_VoronoiNN')
     self.assertAlmostEqual(cnv.featurize(self.cscl, 0)[0], 9.2584516)
     self.assertAlmostEqual(cnv.featurize(self.cscl, 1)[0], 9.2584516)
     self.assertEqual(len(cnv.citations()), 2)
     cnv = CoordinationNumber(VoronoiNN(), use_weights='effective')
     self.assertEqual(cnv.feature_labels()[0], 'CN_VoronoiNN')
     self.assertAlmostEqual(cnv.featurize(self.cscl, 0)[0], 11.648923254)
     self.assertAlmostEqual(cnv.featurize(self.cscl, 1)[0], 11.648923254)
     self.assertEqual(len(cnv.citations()), 2)
     cnj = CoordinationNumber.from_preset('JmolNN')
     self.assertEqual(cnj.feature_labels()[0], 'CN_JmolNN')
     self.assertAlmostEqual(cnj.featurize(self.sc, 0)[0], 0)
     self.assertAlmostEqual(cnj.featurize(self.cscl, 0)[0], 0)
     self.assertAlmostEqual(cnj.featurize(self.cscl, 1)[0], 0)
     self.assertEqual(len(cnj.citations()), 1)
     jmnn = JmolNN(el_radius_updates={"Al": 1.55, "Cl": 1.7, "Cs": 1.7})
     cnj = CoordinationNumber(jmnn)
     self.assertEqual(cnj.feature_labels()[0], 'CN_JmolNN')
     self.assertAlmostEqual(cnj.featurize(self.sc, 0)[0], 6)
     self.assertAlmostEqual(cnj.featurize(self.cscl, 0)[0], 8)
     self.assertAlmostEqual(cnj.featurize(self.cscl, 1)[0], 8)
     self.assertEqual(len(cnj.citations()), 1)
     cnmd = CoordinationNumber.from_preset('MinimumDistanceNN')
     self.assertEqual(cnmd.feature_labels()[0], 'CN_MinimumDistanceNN')
     self.assertAlmostEqual(cnmd.featurize(self.sc, 0)[0], 6)
     self.assertAlmostEqual(cnmd.featurize(self.cscl, 0)[0], 8)
     self.assertAlmostEqual(cnmd.featurize(self.cscl, 1)[0], 8)
     self.assertEqual(len(cnmd.citations()), 1)
     cnmok = CoordinationNumber.from_preset('MinimumOKeeffeNN')
     self.assertEqual(cnmok.feature_labels()[0], 'CN_MinimumOKeeffeNN')
     self.assertAlmostEqual(cnmok.featurize(self.sc, 0)[0], 6)
     self.assertAlmostEqual(cnmok.featurize(self.cscl, 0)[0], 8)
     self.assertAlmostEqual(cnmok.featurize(self.cscl, 1)[0], 6)
     self.assertEqual(len(cnmok.citations()), 2)
     cnmvire = CoordinationNumber.from_preset('MinimumVIRENN')
     self.assertEqual(cnmvire.feature_labels()[0], 'CN_MinimumVIRENN')
     self.assertAlmostEqual(cnmvire.featurize(self.sc, 0)[0], 6)
     self.assertAlmostEqual(cnmvire.featurize(self.cscl, 0)[0], 8)
     self.assertAlmostEqual(cnmvire.featurize(self.cscl, 1)[0], 14)
     self.assertEqual(len(cnmvire.citations()), 2)
     self.assertEqual(len(cnmvire.implementors()), 2)
     self.assertEqual(cnmvire.implementors()[0], 'Nils E. R. Zimmermann')
예제 #16
0
    def get_chemical_ordering_parameters(self):
        '''
        Computes the mean chemical ordering parameter and chemical ordering
        parameter variance for all elements in the crystal using
        Voronoi polyhedra

        returns:
            [mean coordination number, coordination number variance]
        '''
        if len(self._crystal.composition) == 1:
            return [0] * len(self._shells)

        # Get a list of types
        elems, fracs = zip(*self._crystal.composition.element_composition.
                           fractional_composition.items())

        # Precompute the list of NNs in the structure
        weight = 'area'
        voro = VoronoiNN(weight=weight)
        all_nn = self._get_all_nearest_neighbors(voro, self._crystal)

        # Evaluate each shell
        output = []
        for shell in self._shells:
            # Initialize an array to store the ordering parameters
            ordering = np.zeros((len(self._crystal), len(elems)))

            # Get the ordering of each type of each atom
            for site_idx in range(len(self._crystal)):
                nns = voro._get_nn_shell_info(self._crystal, all_nn, site_idx,
                                              shell)

                # Sum up the weights
                total_weight = sum(x['weight'] for x in nns)

                # Get weight by type
                for nn in nns:
                    site_elem = nn['site'].specie.element \
                        if isinstance(nn['site'].specie, Specie) else \
                        nn['site'].specie
                    elem_idx = elems.index(site_elem)
                    ordering[site_idx, elem_idx] += nn['weight']

                # Compute the ordering parameter
                ordering[site_idx, :] = 1 - ordering[site_idx, :] / \
                    total_weight / np.array(fracs)

            # Compute the average ordering for the entire structure
            output.append(np.abs(ordering).mean())

        return output
예제 #17
0
def get_local_env_method(method):  # pylint:disable=too-many-return-statements
    """get a local environment method based on its name"""
    method = method.lower()

    if method.lower() == "crystalnn":
        # see eq. 15 and 16 in
        # https://pubs.acs.org/doi/full/10.1021/acs.inorgchem.0c02996
        # for the x_diff_weight parameter.
        # in the paper it is called δen and it is set to 3
        # we found better results by lowering this weight
        return CrystalNN(porous_adjustment=True,
                         x_diff_weight=1.5,
                         search_cutoff=4.5)
    if method.lower() == "econnn":
        return EconNN()
    if method.lower() == "brunnernn":
        return BrunnerNN_relative()
    if method.lower() == "minimumdistance":
        return MinimumDistanceNN()
    if method.lower() == "vesta":
        return VESTA_NN
    if method.lower() == "voronoinn":
        return VoronoiNN()
    if method.lower() == "atr":
        return ATR_NN
    if method.lower() == "li":
        return LI_NN

    return JmolNN()
예제 #18
0
    def _compute_polyhedra(self):
        '''
        Compute the voronoi polyhedron and specie of each atomic site,
        format as a tuple (polyhedron, specie), and store those tuples in
        a list as an attribute
        '''

        # call the voronoi object
        voronoi = VoronoiNN()
        self._polyhedra = []  # declare polyhedra attribute
        # populate the attribute with the polyhedron associated with each
        # atomic site
        for i, _ in enumerate(self._crystal):
            self._polyhedra.append(
                (voronoi.get_voronoi_polyhedra(self._crystal,
                                               i), self._crystal[i].specie))
    def featurize(self, strc, idx):
        # Get the targeted site
        my_site = strc[idx]

        # Get the tessellation of a site
        nn = get_nearest_neighbors(
            VoronoiNN(weight=self.weight, cutoff=8, compute_adj_neighbors=False),
            strc,
            idx,
        )

        # Get the element and weight of each site
        elems = [n['site'].specie for n in nn]
        weights = [n['weight'] for n in nn]

        # Compute the difference for each property
        output = np.zeros((len(self.properties),))
        output_signed = np.zeros((len(self.properties),))
        output_max = np.zeros((len(self.properties),))
        output_min = np.zeros((len(self.properties),))

        total_weight = np.sum(weights)
        for i, p in enumerate(self.properties):
            my_prop = self.data_source.get_elemental_property(my_site.specie, p)
            n_props = self.data_source.get_elemental_properties(elems, p)
            output[i] = (np.dot(weights, np.abs(np.subtract(n_props, my_prop))) / total_weight)
            output_signed[i] = (np.dot(weights, np.subtract(n_props, my_prop)) / total_weight)
            output_max[i] = np.max(np.subtract(n_props, my_prop))
            output_min[i] = np.min(np.subtract(n_props, my_prop))
        return np.hstack([output, output_signed, output_max, output_min])
예제 #20
0
    def test_filtered(self):
        nn = VoronoiNN(weight='area')

        # Make a bcc crystal
        bcc = Structure([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ['Cu', 'Cu'],
                        [[0, 0, 0], [0.5, 0.5, 0.5]], coords_are_cartesian=False)

        # Compute the weight of the little face
        big_face_area = np.sqrt(3) * 3 / 2 * (2 / 4 / 4)
        small_face_area = 0.125
        little_weight = small_face_area / big_face_area

        # Run one test where you get the small neighbors
        nn.tol = little_weight * 0.99
        nns = nn.get_nn_info(bcc, 0)
        self.assertEqual(14, len(nns))

        # Run a second test where we screen out little faces
        nn.tol = little_weight * 1.01
        nns = nn.get_nn_info(bcc, 0)
        self.assertEqual(8, len(nns))

        # Make sure it works for the `get_all` operation
        all_nns = nn.get_all_nn_info(bcc * [2, 2, 2])
        self.assertEqual([8, ] * 16, [len(x) for x in all_nns])
예제 #21
0
class VoronoiNNTest(PymatgenTest):
    def setUp(self):
        self.s = self.get_structure('LiFePO4')
        self.nn = VoronoiNN(targets=[Element("O")])

    def test_get_voronoi_polyhedra(self):
        self.assertEqual(len(self.nn.get_voronoi_polyhedra(self.s, 0).items()), 8)

    def test_get_cn(self):
        self.assertAlmostEqual(self.nn.get_cn(
                self.s, 0, use_weights=True), 5.809265748999465, 7)

    def test_get_coordinated_sites(self):
        self.assertEqual(len(self.nn.get_nn(self.s, 0)), 8)

    def tearDown(self):
        del self.s
        del self.nn
예제 #22
0
class VoronoiNNTest(PymatgenTest):
    def setUp(self):
        self.s = self.get_structure('LiFePO4')
        self.nn = VoronoiNN(targets=[Element("O")])

    def test_get_voronoi_polyhedra(self):
        self.assertEqual(len(self.nn.get_voronoi_polyhedra(self.s, 0).items()),
                         8)

    def test_get_cn(self):
        self.assertAlmostEqual(self.nn.get_cn(self.s, 0, use_weights=True),
                               5.809265748999465, 7)

    def test_get_coordinated_sites(self):
        self.assertEqual(len(self.nn.get_nn(self.s, 0)), 8)

    def tearDown(self):
        del self.s
        del self.nn
def get_polyhedras_for_all_sites(structure):
    """get polyhedras for each of the sites in the structure
    return a list"""
    # index of list = index of the structure site
    # given an index returns a list of dictionaries
    # index correspods to the site of the central atom and dictionary has all neighbors as key and corresponding angle ratios as values
    v_polyhedra_sites = []
    for i in range(structure.num_sites):
        v_polyhedra_sites.append(VoronoiNN(allow_pathological=True).get_voronoi_polyhedra(structure, i))
    return v_polyhedra_sites
예제 #24
0
    def featurize(self, strc):
        # Shortcut: Return 0 if there is only 1 type of atom
        if len(strc.composition) == 1:
            return [0] * len(self.shells)

        # Get a list of types
        elems, fracs = zip(*strc.composition.element_composition.
                           fractional_composition.items())

        # Precompute the list of NNs in the structure
        voro = VoronoiNN(weight=self.weight)
        all_nn = get_all_nearest_neighbors(voro, strc)

        # Evaluate each shell
        output = []
        for shell in self.shells:
            # Initialize an array to store the ordering parameters
            ordering = np.zeros((len(strc), len(elems)))

            # Get the ordering of each type of each atom
            for site_idx in range(len(strc)):
                nns = voro._get_nn_shell_info(strc, all_nn, site_idx, shell)

                # Sum up the weights
                total_weight = sum(x['weight'] for x in nns)

                # Get weight by type
                for nn in nns:
                    site_elem = nn['site'].specie.element \
                        if isinstance(nn['site'].specie, Specie) else \
                        nn['site'].specie
                    elem_idx = elems.index(site_elem)
                    ordering[site_idx, elem_idx] += nn['weight']

                # Compute the ordering parameter
                ordering[site_idx, :] = 1 - ordering[site_idx, :] / \
                                        total_weight / np.array(fracs)

            # Compute the average ordering for the entire structure
            output.append(np.abs(ordering).mean())

        return output
예제 #25
0
def is_config_reasonable(adslab):
    """
    Function that check weather the adsorbate placement
    is reasonable. For any atom in the adsorbate, if the distance
    between the atom and slab atoms are closer than 80% of
    their expected covalent bond, we reject that placement.

    Args:
        adslab          An `ase.Atoms` object of the adsorbate+slab complex.

    Returns:
        A boolean indicating whether or not the adsorbate placement is
        reasonable.
    """
    vnn = VoronoiNN(allow_pathological=True, tol=0.2, cutoff=10)
    adsorbate_indices = [atom.index for atom in adslab if atom.tag == 2]
    structure = AseAtomsAdaptor.get_structure(adslab)
    slab_lattice = structure.lattice

    # Check to see if adsorpton site is within the unit cell
    for idx in adsorbate_indices:
        coord = slab_lattice.get_fractional_coords(structure[idx].coords)
        if np.any((coord < 0) | (coord > 1)):
            return False

        # Then, check the covalent radius between each adsorbate atoms
        # and its nearest neighbors that are slab atoms
        # to make sure adsorbate is not buried into the surface
        nearneighbors = vnn.get_nn_info(structure, n=idx)
        slab_nn = [
            nn for nn in nearneighbors
            if nn['site_index'] not in adsorbate_indices
        ]
        for nn in slab_nn:
            ads_elem = structure[idx].species_string
            nn_elem = structure[nn['site_index']].species_string
            cov_bond_thres = 0.8 * (covalent_radius[ads_elem] +
                                    covalent_radius[nn_elem]) / 100
            actual_dist = adslab.get_distance(idx, nn['site_index'], mic=True)
            if actual_dist < cov_bond_thres:
                return False
    return True
예제 #26
0
    def test_Cs2O(self):
        """A problematic structure in the Materials Project"""
        strc = Structure([[4.358219, 0.192833, 6.406960], [2.114414, 3.815824, 6.406960],
                          [0.311360, 0.192833, 7.742498]],
                         ['O', 'Cs', 'Cs'],
                         [[0, 0, 0], [0.264318, 0.264318, 0.264318], [0.735682, 0.735682, 0.735682]],
                         coords_are_cartesian=False)

        # Compute the voronoi tessellation
        result = VoronoiNN().get_all_voronoi_polyhedra(strc)
        self.assertEqual(3, len(result))
예제 #27
0
    def test_cache(self):
        x = Structure(Lattice([[2.189, 0, 1.264], [0.73, 2.064, 1.264],
                               [0, 0, 2.528]]), ["C0+", "C0+"],
                      [[2.554, 1.806, 4.423], [0.365, 0.258, 0.632]],
                      validate_proximity=False,
                      to_unit_cell=False,
                      coords_are_cartesian=True)

        # Reset the cache
        _get_all_nearest_neighbors.cache_clear()

        # Compute the nearest neighbors
        method = VoronoiNN()
        nn_1 = get_nearest_neighbors(method, x, 0)

        # Compute it again and make sure the cache hits
        nn_2 = get_nearest_neighbors(method, x, 0)
        self.assertAlmostEqual(nn_1[0]['weight'], nn_2[0]['weight'])
        self.assertEqual(1, _get_all_nearest_neighbors.cache_info().misses)
        self.assertEqual(1, _get_all_nearest_neighbors.cache_info().hits)

        # Reinstantiate the VoronoiNN class, should not cause a miss
        method = VoronoiNN()
        nn_2 = get_nearest_neighbors(method, x, 0)
        self.assertAlmostEqual(nn_1[0]['weight'], nn_2[0]['weight'])
        self.assertEqual(1, _get_all_nearest_neighbors.cache_info().misses)
        self.assertEqual(2, _get_all_nearest_neighbors.cache_info().hits)

        # Change the NN method, should induce a miss
        method = VoronoiNN(weight='volume')
        get_nearest_neighbors(method, x, 0)
        self.assertEqual(2, _get_all_nearest_neighbors.cache_info().misses)
        self.assertEqual(2, _get_all_nearest_neighbors.cache_info().hits)

        # Perturb the structure, make sure it induces a miss and
        #  a change in the NN weights
        x.perturb(0.1)
        nn_2 = get_nearest_neighbors(method, x, 0)
        self.assertNotAlmostEqual(nn_1[0]['weight'], nn_2[0]['weight'])
        self.assertEqual(3, _get_all_nearest_neighbors.cache_info().misses)
        self.assertEqual(2, _get_all_nearest_neighbors.cache_info().hits)
예제 #28
0
    def fxn(self, unit, base, start, final):
        elements = unit['elements'].copy()
        elements.pop(elements.index('O'))

        base_s = Poscar.from_dict(base['poscar']).structure
        run_s = Poscar.from_dict(
            self.get_run(unit, base, start, final)['poscar']).structure
        start_i = get_coords(base_s.copy(), run_s.copy())
        print(start_i)
        vnn = VoronoiNN(targets=[Element(x) for x in elements])
        start_vnn = vnn.get_nn_info(base_s, start_i)

        pauling_total = 0
        weight = 0
        for pt in start_vnn:
            # print('{}: {:3.2f}'.format(pt['site_index'], pt['weight']))
            pauling_total += self.pauling[
                pt['site'].species_string] * pt['weight']
            weight += pt['weight']
        pauling_diff = self.pauling['O'] - pauling_total / weight
        return pauling_diff
예제 #29
0
def find_surface_atoms_indices(bulk_cn_dict, atoms):
    '''
    A helper function referencing codes from pymatgen to
    get a list of surface atoms indices of a slab's
    top surface. Due to how our workflow is setup, the
    pymatgen method cannot be directly applied.

    Taken from pymatgen.core.surface Class Slab,
    `get_surface_sites`.
    https://pymatgen.org/pymatgen.core.surface.html

    Arg:
        bulk_cn_dict    A dictionary of coordination numbers
                        for each distinct site in the respective bulk structure
        atoms           The slab where you are trying to find surface sites in
                        `ase.Atoms` format
    Output:
        indices_list    A list that contains the indices of
                        the surface atoms
    '''
    struct = AseAtomsAdaptor.get_structure(atoms)
    voronoi_nn = VoronoiNN()
    # Identify index of the surface atoms
    indices_list = []
    weights = [site.species.weight for site in struct]
    center_of_mass = np.average(struct.frac_coords, weights=weights, axis=0)

    for idx, site in enumerate(struct):
        if site.frac_coords[2] > center_of_mass[2]:
            try:
                cn = voronoi_nn.get_cn(struct, idx, use_weights=True)
                cn = float('%.5f' % (round(cn, 5)))
                # surface atoms are undercoordinated
                if cn < min(bulk_cn_dict[site.species_string]):
                    indices_list.append(idx)
            except RuntimeError:
                # or if pathological error is returned,
                # indicating a surface site
                indices_list.append(idx)
    return indices_list
예제 #30
0
def VoronoiInfo(poscar, vasprun, elements):
    base_s = poscar.structure
    start_i = get_center_i(
        base_s,
        Element('O'),
    )
    print(start_i)
    vnn = VoronoiNN(targets=[Element(x) for x in elements])
    start_vnn = vnn.get_nn_info(base_s, start_i)

    weight = {}
    total_length = {}
    for pt in start_vnn:
        # print('{}: {:3.2f}'.format(pt['site_index'], pt['weight']))
        temp_weight = round(pt['weight'] + 0.45)
        if pt['site'].species_string in weight:
            weight[pt['site'].species_string] = weight[
                pt['site'].species_string] + temp_weight
        else:
            weight[pt['site'].species_string] = temp_weight
        if temp_weight >= 0.99:
            bond_length = base_s.get_distance(start_i, pt['site_index'])
            if pt['site'].species_string in total_length:
                total_length[pt['site'].species_string] = total_length[
                    pt['site'].species_string] + bond_length
            else:
                total_length[pt['site'].species_string] = bond_length
    bonds = {
        'A_Bonds':
        weight[elements[0]],
        'A_length':
        total_length[elements[0]] / weight[elements[0]],
        'B_Bonds':
        weight[elements[1]],
        'B_length':
        total_length[elements[1]] / weight[elements[1]],
        'avg_length': (total_length[elements[0]] + total_length[elements[1]]) /
        (weight[elements[0]] + weight[elements[1]]),
    }
    return bonds
예제 #31
0
    def test_filtered(self):
        nn = VoronoiNN(weight='area')

        # Make a bcc crystal
        bcc = Structure([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ['Cu', 'Cu'],
                        [[0, 0, 0], [0.5, 0.5, 0.5]], coords_are_cartesian=False)

        # Compute the weight of the little face
        big_face_area = np.sqrt(3) * 3 / 2 * (2 / 4 / 4)
        small_face_area = 0.125
        little_weight = small_face_area / big_face_area

        # Run one test where you get the small neighbors
        nn.tol = little_weight * 0.99
        nns = nn.get_nn_info(bcc, 0)
        self.assertEqual(14, len(nns))

        # Run a second test where we screen out little faces
        nn.tol = little_weight * 1.01
        nns = nn.get_nn_info(bcc, 0)
        self.assertEqual(8, len(nns))

        # Make sure it works for the `get_all` operation
        all_nns = nn.get_all_nn_info(bcc * [2, 2, 2])
        self.assertEqual([8,]*16, [len(x) for x in all_nns])
예제 #32
0
    def __init__(self, max_l=10, compute_w=False, compute_w_hat=False):
        """
        Initialize the featurizer

        Args:
            max_l (int) - Maximum spherical harmonic to consider
            compute_w (bool) - Whether to compute Ws as well
            compute_w_hat (bool) - Whether to compute What
        """
        self._nn = VoronoiNN(weight='solid_angle')
        self.max_l = max_l
        self.compute_W = compute_w
        self.compute_What = compute_w_hat
예제 #33
0
    def test_multiple_same_methods(self):
        # test that if running the benchmark on multiple NN methods of the same class,
        # that each NN method is named differently and appears in the benchmark and
        # score results
        bm = Benchmark(self.structures)
        nn_methods = [VoronoiNN(), VoronoiNN(tol=0.5)]

        results = bm.benchmark(nn_methods)
        expected_results = {
            "VoronoiNN(0)0": {"test_structure": {"Cl": 6}},
            "VoronoiNN(0)1": {"test_structure": {"Na": 6}},
            "VoronoiNN(1)0": {"test_structure": {"Cl": 6}},
            "VoronoiNN(1)1": {"test_structure": {"Na": 6}},
        }
        self.assertEqual(results.to_dict(), expected_results)

        scores = bm.score(nn_methods)
        expected_scores = {
            "VoronoiNN(0)": {"Total": 4.0, "test_structure": 4.0},
            "VoronoiNN(1)": {"Total": 4.0, "test_structure": 4.0},
        }
        self.assertEqual(scores.to_dict(), expected_scores)
예제 #34
0
def get_coordination_numbers(d):
    """
    Helper method to get the coordination number of all sites in the final
    structure from a run.

    Args:
        d:
            Run dict generated by VaspToDbTaskDrone.

    Returns:
        Coordination numbers as a list of dict of [{"site": site_dict,
        "coordination": number}, ...].
    """
    structure = Structure.from_dict(d["output"]["crystal"])
    f = VoronoiNN()
    cn = []
    for i, s in enumerate(structure.sites):
        try:
            n = f.get_cn(structure, i)
            number = int(round(n))
            cn.append({"site": s.as_dict(), "coordination": number})
        except Exception:
            logger.error("Unable to parse coordination errors")
    return cn
예제 #35
0
class VoronoiNNTest(PymatgenTest):
    def setUp(self):
        self.s = self.get_structure('LiFePO4')
        self.nn = VoronoiNN(targets=[Element("O")])
        self.s_sic = self.get_structure('Si')
        self.s_sic["Si"] =  {'Si': 0.5, 'C': 0.5}
        self.nn_sic = VoronoiNN()

    def test_get_voronoi_polyhedra(self):
        self.assertEqual(len(self.nn.get_voronoi_polyhedra(self.s, 0).items()), 8)

    def test_get_cn(self):
        self.assertAlmostEqual(self.nn.get_cn(
                self.s, 0, use_weights=True), 5.809265748999465, 7)
        self.assertAlmostEqual(self.nn_sic.get_cn(
                self.s_sic, 0, use_weights=True), 4.5381161643940668, 7)

    def test_get_coordinated_sites(self):
        self.assertEqual(len(self.nn.get_nn(self.s, 0)), 8)

    def test_volume(self):
        self.nn.targets = None
        volume = 0
        for n in range(len(self.s)):
            for nn in self.nn.get_voronoi_polyhedra(self.s, n).values():
                volume += nn['volume']
        self.assertAlmostEqual(self.s.volume, volume)

    def test_solid_angle(self):
        self.nn.targets = None
        for n in range(len(self.s)):
            angle = 0
            for nn in self.nn.get_voronoi_polyhedra(self.s, n).values():
                angle += nn['solid_angle']
            self.assertAlmostEqual(4 * np.pi, angle)
        self.assertEqual(solid_angle([0,0,0], [[1,0,0],[-1,0,0],[0,1,0]]), pi)

    def test_nn_shell(self):
        # First, make a SC lattice. Make my math easier
        s = Structure([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ['Cu'], [[0, 0, 0]])

        # Get the 1NN shell
        self.nn.targets = None
        nns = self.nn.get_nn_shell_info(s, 0, 1)
        self.assertEqual(6, len(nns))

        # Test the 2nd NN shell
        nns = self.nn.get_nn_shell_info(s, 0, 2)
        self.assertEqual(18, len(nns))
        self.assertArrayAlmostEqual([1] * 6,
                                    [x['weight'] for x in nns if
                                     max(np.abs(x['image'])) == 2])
        self.assertArrayAlmostEqual([2] * 12,
                                    [x['weight'] for x in nns if
                                     max(np.abs(x['image'])) == 1])

        # Test the 3rd NN shell
        nns = self.nn.get_nn_shell_info(s, 0, 3)
        for nn in nns:
            #  Check that the coordinates were set correctly
            self.assertArrayAlmostEqual(nn['site'].frac_coords, nn['image'])

        # Test with a structure that has unequal faces
        cscl = Structure(Lattice([[4.209, 0, 0], [0, 4.209, 0], [0, 0, 4.209]]),
            ["Cl1-", "Cs1+"], [[2.1045, 2.1045, 2.1045], [0, 0, 0]],
            validate_proximity=False, to_unit_cell=False,
            coords_are_cartesian=True, site_properties=None)
        self.nn.weight = 'area'
        nns = self.nn.get_nn_shell_info(cscl, 0, 1)
        self.assertEqual(14, len(nns))
        self.assertEqual(6, np.isclose([x['weight'] for x in nns],
                                       0.125/0.32476).sum())  # Square faces
        self.assertEqual(8, np.isclose([x['weight'] for x in nns], 1).sum())

        nns = self.nn.get_nn_shell_info(cscl, 0, 2)
        # Weight of getting back on to own site
        #  Square-square hop: 6*5 options times (0.125/0.32476)^2 weight each
        #  Hex-hex hop: 8*7 options times 1 weight each
        self.assertAlmostEqual(60.4444,
                               np.sum([x['weight'] for x in nns if x['site_index'] == 0]),
                               places=3)

    def test_adj_neighbors(self):
        # Make a simple cubic structure
        s = Structure([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ['Cu'], [[0, 0, 0]])

        # Compute the NNs with adjacency
        self.nn.targets = None
        neighbors = self.nn.get_voronoi_polyhedra(s, 0)

        # Each neighbor has 4 adjacent neighbors, all orthogonal
        for nn_key, nn_info in neighbors.items():
            self.assertEqual(4, len(nn_info['adj_neighbors']))

            for adj_key in nn_info['adj_neighbors']:
                self.assertEqual(0, np.dot(nn_info['normal'], neighbors[adj_key]['normal']))

    def test_all_at_once(self):
        # Get all of the sites for LiFePO4
        all_sites = self.nn.get_all_voronoi_polyhedra(self.s)

        # Make sure they are the same as the single-atom ones
        for i, site in enumerate(all_sites):
            # Compute the tessellation using only one site
            by_one = self.nn.get_voronoi_polyhedra(self.s, i)

            # Match the coordinates the of the neighbors, as site matching does not seem to work?
            all_coords = np.sort([x['site'].coords for x in site.values()], axis=0)
            by_one_coords = np.sort([x['site'].coords for x in by_one.values()], axis=0)

            self.assertArrayAlmostEqual(all_coords, by_one_coords)

        # Test the nn_info operation
        all_nn_info = self.nn.get_all_nn_info(self.s)
        for i, info in enumerate(all_nn_info):
            # Compute using the by-one method
            by_one = self.nn.get_nn_info(self.s, i)

            # Get the weights
            all_weights = sorted([x['weight'] for x in info])
            by_one_weights = sorted([x['weight'] for x in by_one])

            self.assertArrayAlmostEqual(all_weights, by_one_weights)

    def test_Cs2O(self):
        """A problematic structure in the Materials Project"""
        strc = Structure([[4.358219, 0.192833, 6.406960], [2.114414, 3.815824, 6.406960],
                          [0.311360, 0.192833, 7.742498]],
                         ['O', 'Cs', 'Cs'],
                         [[0, 0, 0], [0.264318, 0.264318, 0.264318], [0.735682, 0.735682, 0.735682]],
                         coords_are_cartesian=False)

        # Compute the voronoi tessellation
        result = VoronoiNN().get_all_voronoi_polyhedra(strc)
        self.assertEqual(3, len(result))

    def test_filtered(self):
        nn = VoronoiNN(weight='area')

        # Make a bcc crystal
        bcc = Structure([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ['Cu', 'Cu'],
                        [[0, 0, 0], [0.5, 0.5, 0.5]], coords_are_cartesian=False)

        # Compute the weight of the little face
        big_face_area = np.sqrt(3) * 3 / 2 * (2 / 4 / 4)
        small_face_area = 0.125
        little_weight = small_face_area / big_face_area

        # Run one test where you get the small neighbors
        nn.tol = little_weight * 0.99
        nns = nn.get_nn_info(bcc, 0)
        self.assertEqual(14, len(nns))

        # Run a second test where we screen out little faces
        nn.tol = little_weight * 1.01
        nns = nn.get_nn_info(bcc, 0)
        self.assertEqual(8, len(nns))

        # Make sure it works for the `get_all` operation
        all_nns = nn.get_all_nn_info(bcc * [2, 2, 2])
        self.assertEqual([8,]*16, [len(x) for x in all_nns])

    def tearDown(self):
        del self.s
        del self.nn
예제 #36
0
 def setUp(self):
     self.s = self.get_structure('LiFePO4')
     self.nn = VoronoiNN(targets=[Element("O")])
예제 #37
0
 def setUp(self):
     self.s = self.get_structure('LiFePO4')
     self.nn = VoronoiNN(targets=[Element("O")])
     self.s_sic = self.get_structure('Si')
     self.s_sic["Si"] =  {'Si': 0.5, 'C': 0.5}
     self.nn_sic = VoronoiNN()
예제 #38
0
class VoronoiNNTest(PymatgenTest):
    def setUp(self):
        self.s = self.get_structure('LiFePO4')
        self.nn = VoronoiNN(targets=[Element("O")])

    def test_get_voronoi_polyhedra(self):
        self.assertEqual(len(self.nn.get_voronoi_polyhedra(self.s, 0).items()), 8)

    def test_get_cn(self):
        self.assertAlmostEqual(self.nn.get_cn(
                self.s, 0, use_weights=True), 5.809265748999465, 7)

    def test_get_coordinated_sites(self):
        self.assertEqual(len(self.nn.get_nn(self.s, 0)), 8)

    def test_volume(self):
        self.nn.targets = None
        volume = 0
        for n in range(len(self.s)):
            for nn in self.nn.get_voronoi_polyhedra(self.s, n).values():
                volume += nn['volume']
        self.assertAlmostEqual(self.s.volume, volume)

    def test_solid_angle(self):
        self.nn.targets = None
        for n in range(len(self.s)):
            angle = 0
            for nn in self.nn.get_voronoi_polyhedra(self.s, n).values():
                angle += nn['solid_angle']
            self.assertAlmostEqual(4 * np.pi, angle)
        self.assertEqual(solid_angle([0,0,0], [[1,0,0],[-1,0,0],[0,1,0]]), pi)

    def test_nn_shell(self):
        # First, make a SC lattice. Make my math easier
        s = Structure([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ['Cu'], [[0, 0, 0]])

        # Get the 1NN shell
        self.nn.targets = None
        nns = self.nn.get_nn_shell_info(s, 0, 1)
        self.assertEqual(6, len(nns))

        # Test the 2nd NN shell
        nns = self.nn.get_nn_shell_info(s, 0, 2)
        self.assertEqual(18, len(nns))
        self.assertArrayAlmostEqual([1] * 6,
                                    [x['weight'] for x in nns if
                                     max(np.abs(x['image'])) == 2])
        self.assertArrayAlmostEqual([2] * 12,
                                    [x['weight'] for x in nns if
                                     max(np.abs(x['image'])) == 1])

        # Test the 3rd NN shell
        nns = self.nn.get_nn_shell_info(s, 0, 3)
        for nn in nns:
            #  Check that the coordinates were set correctly
            self.assertArrayAlmostEqual(nn['site'].frac_coords, nn['image'])

        # Test with a structure that has unequal faces
        cscl = Structure(Lattice([[4.209, 0, 0], [0, 4.209, 0], [0, 0, 4.209]]),
            ["Cl1-", "Cs1+"], [[2.1045, 2.1045, 2.1045], [0, 0, 0]],
            validate_proximity=False, to_unit_cell=False,
            coords_are_cartesian=True, site_properties=None)
        self.nn.weight = 'area'
        nns = self.nn.get_nn_shell_info(cscl, 0, 1)
        self.assertEqual(14, len(nns))
        self.assertEqual(6, np.isclose([x['weight'] for x in nns],
                                       0.125/0.32476).sum())  # Square faces
        self.assertEqual(8, np.isclose([x['weight'] for x in nns], 1).sum())

        nns = self.nn.get_nn_shell_info(cscl, 0, 2)
        # Weight of getting back on to own site
        #  Square-square hop: 6*5 options times (0.125/0.32476)^2 weight each
        #  Hex-hex hop: 8*7 options times 1 weight each
        self.assertAlmostEqual(60.4444,
                               np.sum([x['weight'] for x in nns if x['site_index'] == 0]),
                               places=3)

    def tearDown(self):
        del self.s
        del self.nn
예제 #39
0
def get_interstitial_diffusion_pathways_from_cell(structure : Structure, interstitial_atom : str, vis=False,
                                                  get_midpoints=False, dummy='He', min_dist=0.5, weight_cutoff=0.0001,
                                                  is_interstitial_structure=False):
    """

    Find Vacancy Strucutres for diffusion into and out of the specified atom_i site.

    :param structure: Structure
        Structure to calculate diffusion pathways
    :param atom_i: int
        Atom to get diffion path from
    :return: [ Structure ]
    """

    vnn = VoronoiNN(targets=[interstitial_atom])
    # To Find Pathway, look for voronoi edges
    if not is_interstitial_structure:
        orig_structure = structure.copy()
        structure = structure.copy() # type: Structure
        interstitial_structure = structure.copy()
        interstitial_structure.DISTANCE_TOLERANCE = 0.01

        if vis:
            Poscar(structure).write_file(vis)
            open_in_VESTA(vis)
        inter_gen = list(VoronoiInterstitialGenerator(orig_structure, interstitial_atom))
        if vis:
            print(len(inter_gen))
        for interstitial in inter_gen:
            sat_structure = None
            for dist_tol in [0.2, 0.15, 0.1, 0.05, 0.01, 0.001]:
                try:
                    sat_structure = create_saturated_interstitial_structure(interstitial, dist_tol=dist_tol) # type: Structure
                    break
                except ValueError:
                    continue
                except TypeError:
                    continue
            if not sat_structure:
                continue
            sat_structure.remove_site_property('velocities')
            if vis:
                Poscar(sat_structure).write_file(vis)
                open_in_VESTA(vis)
                time.sleep(0.5)
            for site in sat_structure: # type: PeriodicSite
                if site.specie == interstitial_atom:
                    try:
                        interstitial_structure.append(site.specie, site.coords, coords_are_cartesian=True, validate_proximity=True)
                    except StructureError:
                        pass

        # combined_structure.merge_sites(mode='delete')
        interstitial_structure.remove_site_property('velocities')
        if vis:
            Poscar(interstitial_structure).write_file(vis)
            open_in_VESTA(vis)
    else:
        interstitial_structure = structure.copy()

    # edges = vnn.get_nn_info(structure, atom_i)
    # base_coords = structure[atom_i].coords
    pathway_structure = interstitial_structure.copy() # type: Structure
    pathway_structure.DISTANCE_TOLERANCE = 0.01
    # Add H for all other diffusion atoms, so symmetry is preserved
    for i in get_atom_i(interstitial_structure, interstitial_atom):
        sym_edges = vnn.get_nn_info(interstitial_structure, i)
        base = pathway_structure[i] # type: PeriodicSite
        for edge in sym_edges:
            dest = edge['site']
            if base.distance(dest, jimage=edge['image']) > min_dist and edge['weight'] > weight_cutoff:
                coords = (base.coords + dest.coords) / 2
                try:
                    neighbors = [i, edge['site_index']]
                    # neighbors.sort()
                    pathway_structure.append(dummy, coords, True, validate_proximity=True, properties={'neighbors': neighbors, 'image' : edge['image']})
                except StructureError:
                    pass
                except ValueError:
                    pass
    if vis:
        Poscar(pathway_structure).write_file(vis)
        open_in_VESTA(vis)



    # Remove symmetrically equivalent pathways:
    # sga = SpacegroupAnalyzer(pathway_structure, 0.1, angle_tolerance=10)
    # ss = sga.get_symmetrized_structure()
    return interstitial_structure, pathway_structure
예제 #40
0
    def from_bulk_and_miller(cls, structure, miller_index, min_slab_size=8.0,
                             min_vacuum_size=10.0, max_normal_search=None,
                             center_slab=True, selective_dynamics=False,
                             undercoord_threshold=0.09):
        """
        This method constructs the adsorbate site finder from a bulk
        structure and a miller index, which allows the surface sites
        to be determined from the difference in bulk and slab coordination,
        as opposed to the height threshold.

        Args:
            structure (Structure): structure from which slab
                input to the ASF is constructed
            miller_index (3-tuple or list): miller index to be used
            min_slab_size (float): min slab size for slab generation
            min_vacuum_size (float): min vacuum size for slab generation
            max_normal_search (int): max normal search for slab generation
            center_slab (bool): whether to center slab in slab generation
            selective dynamics (bool): whether to assign surface sites
                to selective dynamics
            undercoord_threshold (float): threshold of "undercoordation"
                to use for the assignment of surface sites.  Default is
                0.1, for which surface sites will be designated if they
                are 10% less coordinated than their bulk counterpart
        """
        # TODO: for some reason this works poorly with primitive cells
        #       may want to switch the coordination algorithm eventually
        vnn_bulk = VoronoiNN(tol=0.05)
        bulk_coords = [len(vnn_bulk.get_nn(structure, n))
                       for n in range(len(structure))]
        struct = structure.copy(site_properties={'bulk_coordinations': bulk_coords})
        slabs = generate_all_slabs(struct, max_index=max(miller_index),
                                   min_slab_size=min_slab_size,
                                   min_vacuum_size=min_vacuum_size,
                                   max_normal_search=max_normal_search,
                                   center_slab=center_slab)

        slab_dict = {slab.miller_index: slab for slab in slabs}

        if miller_index not in slab_dict:
            raise ValueError("Miller index not in slab dict")

        this_slab = slab_dict[miller_index]

        vnn_surface = VoronoiNN(tol=0.05, allow_pathological=True)

        surf_props, undercoords = [], []
        this_mi_vec = get_mi_vec(this_slab)
        mi_mags = [np.dot(this_mi_vec, site.coords) for site in this_slab]
        average_mi_mag = np.average(mi_mags)
        for n, site in enumerate(this_slab):
            bulk_coord = this_slab.site_properties['bulk_coordinations'][n]
            slab_coord = len(vnn_surface.get_nn(this_slab, n))
            mi_mag = np.dot(this_mi_vec, site.coords)
            undercoord = (bulk_coord - slab_coord) / bulk_coord
            undercoords += [undercoord]
            if undercoord > undercoord_threshold and mi_mag > average_mi_mag:
                surf_props += ['surface']
            else:
                surf_props += ['subsurface']
        new_site_properties = {'surface_properties': surf_props,
                               'undercoords': undercoords}
        new_slab = this_slab.copy(site_properties=new_site_properties)
        return cls(new_slab, selective_dynamics)
예제 #41
0
def get_vacancy_diffusion_pathways_from_cell(structure : Structure, atom_i : int, vis=False, get_midpoints=False):
    """

    Find Vacancy Strucutres for diffusion into and out of the specified atom_i site.

    :param structure: Structure
        Structure to calculate diffusion pathways
    :param atom_i: int
        Atom to get diffion path from
    :return: [ Structure ]
    """

    # To Find Pathway, look for voronoi edges
    orig_structure = structure.copy()
    structure = structure.copy() # type: Structure
    target_atom = structure[atom_i].specie
    vnn = VoronoiNN(targets=[target_atom])
    edges = vnn.get_nn_info(structure, atom_i)
    base_coords = structure[atom_i].coords

    # Add H in middle of the discovered pathways.  Use symmetry analysis to elminate equivlent H and therfore
    # equivalent pathways
    site_dir = {}
    for edge in edges:
        coords = np.round((base_coords + edge['site'].coords)/2,3)
        structure.append('H', coords, True)
       # site_dir[tuple(np.round(coords))] = structure.index(edge['site']) # Use Tuple for indexing dict, need to round
        site_dir[tuple(np.round(coords))] =  [list(x) for x in np.round(structure.frac_coords % 1,2) ].index(list(np.round(edge['site'].frac_coords % 1, 2))) # Use Tuple for indexing dict, need to round
    # Add H for all other diffusion atoms, so symmetry is preserved
    for i in get_atom_i(orig_structure, target_atom):
        sym_edges = vnn.get_nn_info(orig_structure, i)
        base_coords = structure[i].coords
        for edge in sym_edges:
            coords = (base_coords + edge['site'].coords) / 2
            try:
                structure.append('H', coords, True, True)
            except:
                pass

    # Remove symmetrically equivalent pathways:
    sga = SpacegroupAnalyzer(structure, 0.5, angle_tolerance=20)
    ss = sga.get_symmetrized_structure()

    final_structure = structure.copy()
    indices = []
    for i in range(len(orig_structure), len(orig_structure)+len(edges)): # get all 'original' edge sites
        sites = ss.find_equivalent_sites(ss[i])
        new_indices = [ss.index(site) for site in sites if ss.index(site) < len(orig_structure) + len(edges)] # Check if symmetrically equivalent to other original edge sites
        new_indices.remove(i)
        if i not in indices: # Don't duplicate effort
            indices = indices + new_indices
            indices.sort()
    indices = indices + list(range(len(orig_structure)+len(edges), len(final_structure)))
    final_structure.remove_sites(indices)
    diffusion_elements = [ site_dir[tuple(np.round(h.coords))] for h in final_structure[len(orig_structure):] ]
    if vis:
        view(final_structure, 'VESTA')
        print(diffusion_elements)

    if get_midpoints:
        centers = [h.frac_coords for h in final_structure[len(orig_structure):]]
        return (diffusion_elements, centers)


    return diffusion_elements