Пример #1
0
def make_InAs001_surface(inas_layers = 4, licras_layers = 4, vacuum_layers = 8):
    total_layers = inas_layers + licras_layers + vacuum_layers
    err = 1./total_layers/10
    a = Length(6.0583, "ang")
    fcc_lattice = np.array([[.0,.5,.5],[.5,.0,.5],[.5,.5,.0]])
    lattice = Lattice(fcc_lattice * a)
    surface = Structure(lattice, ['Li', 'Cr', 'As'], [[0.50,0.50,0.50],[0.00,0.00,0.00],[0.25,0.25,0.25]])
    surface.make_supercell([[0,0,1],[1,-1,0],[1,1,-1]])
    surface.make_supercell([[1,0,0],[0,1,0],[0,0,total_layers]])
    to_remove = []
    print (1.*inas_layers / total_layers)
    print 1.*(inas_layers+licras_layers)/total_layers
    print 1. - 1. / vacuum_layers
    print err
    for idx,site in enumerate(surface):
        if (site.frac_coords[2] - 1.*inas_layers / total_layers) < -err:
            if site.specie.symbol == 'Li': to_remove.append(idx)
            if site.specie.symbol == 'Cr': surface.replace(idx,Element('In'))
        if (site.frac_coords[2] - 1. + 1./vacuum_layers/8.) > -err:
            surface.replace(idx,Element('H'))
        elif (1.*(inas_layers+licras_layers)/total_layers) - site.frac_coords[2] < err:
            to_remove.append(idx)
    surface.remove_sites(to_remove)
    selective_dynamics = len(surface) * [[True,True,True]]
    for idx,site in enumerate(surface):
        if (np.linalg.norm(site.frac_coords) < err):
            selective_dynamics[idx] = [False,False,False]
    return surface.get_sorted_structure(),np.array(selective_dynamics)
Пример #2
0
    def get_refined_structure(self):
        """
        Get the refined structure based on detected symmetry. The refined
        structure is a *conventional* cell setting with atoms moved to the
        expected symmetry positions.

        Returns:
            Refined structure.
        """
        # Atomic positions have to be specified by scaled positions for spglib.
        num_atom = self._structure.num_sites
        lattice = self._transposed_latt.copy()
        pos = np.zeros((num_atom * 4, 3), dtype='double')
        pos[:num_atom] = self._positions.copy()

        zs = np.zeros(num_atom * 4, dtype='intc')
        zs[:num_atom] = np.array(self._numbers, dtype='intc')
        num_atom_bravais = spg.refine_cell(
            lattice, pos, zs, num_atom, self._symprec, self._angle_tol)

        zs = zs[:num_atom_bravais]
        species = [self._unique_species[i - 1] for i in zs]
        s = Structure(lattice.T.copy(),
                      species,
                      pos[:num_atom_bravais])
        return s.get_sorted_structure()
Пример #3
0
 def test_get_sorted_structure(self):
     coords = list()
     coords.append([0, 0, 0])
     coords.append([0.75, 0.5, 0.75])
     s = Structure(self.lattice, ["O", "Li"] , coords)
     sorted_s = s.get_sorted_structure()
     self.assertEqual(sorted_s[0].species_and_occu, {Element("Li"):1})
     self.assertEqual(sorted_s[1].species_and_occu, {Element("O"):1})
Пример #4
0
 def test_get_sorted_structure(self):
     coords = list()
     coords.append([0, 0, 0])
     coords.append([0.75, 0.5, 0.75])
     s = Structure(self.lattice, ["O", "Li"], coords,
                   site_properties={'charge': [-2, 1]})
     sorted_s = s.get_sorted_structure()
     self.assertEqual(sorted_s[0].species_and_occu, Composition("Li"))
     self.assertEqual(sorted_s[1].species_and_occu, Composition("O"))
     self.assertEqual(sorted_s[0].charge, 1)
     self.assertEqual(sorted_s[1].charge, -2)
Пример #5
0
def convert(bulk, slab, index, output, generate=True, print_M=True):
    primitiveCell = mg.Structure.from_file(bulk)
    refSlab = mg.Structure.from_file(slab)
    sa = SpacegroupAnalyzer(primitiveCell)
    conventionalCell = sa.get_conventional_standard_structure()
    conventionalCell.to(filename='POSCAR.conventional')
    bulk = read('POSCAR.conventional')
    os.remove('POSCAR.conventional')
    slab = surface(bulk, index, layers=2, vacuum=10)
    lattice, _, _ = spglib.standardize_cell(cell=(slab.get_cell(),
                                                  slab.get_scaled_positions(),
                                                  slab.get_atomic_numbers()),
                                            no_idealize=True)
    lattice_params = np.sort(np.linalg.norm(lattice, axis=1))[:2]
    scales = np.round(
        np.array([refSlab.lattice.a, refSlab.lattice.b] / lattice_params), 2)
    newLattice = []
    oldLattice = refSlab.lattice
    for length, scale in zip([oldLattice.a, oldLattice.b], scales):
        for j in range(len(lattice)):
            if abs((np.linalg.norm(lattice[j]) * scale) - length) < 1e-1:
                newLattice.append(copy.copy(scale * lattice[j][:]))
                lattice[j] = [0, 0, 0]
                break
    for i in range(len(lattice)):
        norm = np.linalg.norm(lattice[i])
        if norm > 1e-1:
            newLattice.append(lattice[i] / norm * oldLattice.c)
            break
    newLattice = Lattice(np.array(newLattice))
    for x, y in zip(oldLattice.angles, newLattice.angles):
        assert abs(
            x - y
        ) < 1e-2, "The converted lattice has incorrect angles: {} compared with reference slab: {}".format(
            " ".join(str(x) for x in newLattice.angles),
            " ".join(str(x) for x in oldLattice.angles))
    newSlab = Structure(newLattice, [], [])
    for atom in refSlab:
        newSlab.append(atom.specie, atom.frac_coords[:])

    if generate:
        Poscar(newSlab.get_sorted_structure()).write_file(output, direct=True)

    transformMat = newSlab.lattice.matrix.dot(
        np.linalg.inv(primitiveCell.lattice.matrix))
    transformMat = transformMat.round().astype(int)
    if print_M:
        print('-------------------------------------------')
        print('Your Transformtaion Matrix is:')
        print(' ')
        print(transformMat)
        print('-------------------------------------------')

    return transformMat
Пример #6
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-f',
                        '--file',
                        default='POSCAR',
                        type=str,
                        help='path to input file')
    parser.add_argument('-t',
                        '--tol',
                        default=1e-3,
                        type=float,
                        help='symmetry tolerance (default 1e-3)')
    parser.add_argument('-o',
                        '--output',
                        default='poscar',
                        help='output file format')
    args = parser.parse_args()

    struct = Structure.from_file(args.file)
    sym = SpacegroupAnalyzer(struct, symprec=args.tol)
    data = sym.get_symmetry_dataset()

    print("Initial structure has {} atoms".format(struct.num_sites))
    print("\tSpace group number: {}".format(data['number']))
    print("\tInternational symbol: {}".format(data['international']))
    print("\tLattice type: {}".format(sym.get_lattice_type()))

    # seekpath conventional cell definition different from spglib
    std = spglib.refine_cell(sym._cell, symprec=args.tol)
    seek_data = seekpath.get_path(std)

    # now remake the structure
    lattice = seek_data['conv_lattice']
    scaled_positions = seek_data['conv_positions']
    numbers = seek_data['conv_types']
    species = [sym._unique_species[i - 1] for i in numbers]
    conv = Structure(lattice, species, scaled_positions)
    conv.get_sorted_structure().to(filename="{}_conv".format(args.file),
                                   fmt=args.output)

    print("Final structure has {} atoms".format(conv.num_sites))
Пример #7
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-f', '--file', default='POSCAR', type=str,
                        help='path to input file')
    parser.add_argument('-t', '--tol', default=1e-3, type=float,
                        help='symmetry tolerance (default 1e-3)')
    parser.add_argument('-o', '--output', default='poscar',
                        help='output file format')
    args = parser.parse_args()

    struct = Structure.from_file(args.file)
    sym = SpacegroupAnalyzer(struct, symprec=args.tol)
    data = sym.get_symmetry_dataset()

    print('Initial structure has {} atoms'.format(struct.num_sites))
    print('\tSpace group number: {}'.format(data['number']))
    print('\tInternational symbol: {}'.format(data['international']))
    print('\tLattice type: {}'.format(sym.get_lattice_type()))

    # first standardise the cell using the tolerance we want (seekpath has no
    # tolerance setting)
    std = spglib.refine_cell(sym._cell, symprec=args.tol)
    seek_data = seekpath.get_path(std)

    transform = seek_data['primitive_transformation_matrix']

    # now remake the structure
    lattice = seek_data['primitive_lattice']
    scaled_positions = seek_data['primitive_positions']
    numbers = seek_data['primitive_types']
    species = [sym._unique_species[i - 1] for i in numbers]
    prim = Structure(lattice, species, scaled_positions)
    prim.get_sorted_structure().to(filename='{}_prim'.format(args.file),
                                   fmt=args.output)

    print('Final structure has {} atoms'.format(prim.num_sites))
    print('Conv -> Prim transformation matrix:')
    print('\t' + str(transform).replace('\n', '\n\t'))
Пример #8
0
    def get_refined_structure(self):
        """
        Get the refined structure based on detected symmetry. The refined
        structure is a *conventional* cell setting with atoms moved to the
        expected symmetry positions.

        Returns:
            Refined structure.
        """
        # Atomic positions have to be specified by scaled positions for spglib.
        lattice, scaled_positions, numbers \
            = spglib.refine_cell(self._cell, self._symprec, self._angle_tol)

        species = [self._unique_species[i - 1] for i in numbers]
        s = Structure(lattice, species, scaled_positions)
        return s.get_sorted_structure()
Пример #9
0
    def get_refined_structure(self):
        """
        Get the refined structure based on detected symmetry. The refined
        structure is a *conventional* cell setting with atoms moved to the
        expected symmetry positions.

        Returns:
            Refined structure.
        """
        # Atomic positions have to be specified by scaled positions for spglib.
        lattice, scaled_positions, numbers \
            = spglib.refine_cell(self._cell, self._symprec, self._angle_tol)

        species = [self._unique_species[i - 1] for i in numbers]
        s = Structure(lattice, species, scaled_positions)
        return s.get_sorted_structure()
Пример #10
0
def make_Si001_surface(Si_layers = 4, vacuum_layers = 4):
    total_layers = Si_layers + vacuum_layers
    a = Length(5.431, "ang")
    fcc_lattice = np.array([[.0,.5,.5],[.5,.0,.5],[.5,.5,.0]])
    lattice = Lattice(fcc_lattice * a)
    surface = Structure(lattice, ['Si','Si'], [[0.00,0.00,0.00],[0.25,0.25,0.25]])
    surface.make_supercell([[0,0,1],[1,-1,0],[1,1,-1]])
    surface.make_supercell([[2,0,0],[0,2,0],[0,0,total_layers]])
    to_remove = []
    for idx,site in enumerate(surface):
        if (site.frac_coords[2] > 1.0*Si_layers/total_layers):
            to_remove.append(idx)
    surface.remove_sites(to_remove)
    print to_remove
    selective_dynamics = len(surface) * [[True,True,True]]
    for idx,site in enumerate(surface):
        if (site.frac_coords[2] < 0.001):
            selective_dynamics[idx] = [False,False,False]
            surface.replace(idx,Element('H'))
    return surface.get_sorted_structure(),np.array(selective_dynamics)
Пример #11
0
def make_trilayer(xyscale = 2, zscale = 4):
    a = Length(5.431,"ang")
    fcc_lattice = np.array([[.0,.5,.5],[.5,.0,.5],[.5,.5,.0]])
    lattice = Lattice(fcc_lattice * a)
    trilayer = Structure(lattice, ['Si','Si'], [[0.00,0.00,0.00],[0.25,0.25,0.25]])
    trilayer.make_supercell([[0,0,1],[1,-1,0],[1,1,-1]])
    trilayer.make_supercell([[xyscale,0,0],[0,xyscale,0],[0,0,zscale]])
    # Dope the Ga sites
    for idx,site in enumerate(trilayer):
        if np.linalg.norm(site.frac_coords - np.array([+.0,+.5,.0])) < 1e-10:
            trilayer.replace(idx,Element('Ga'))
        if np.linalg.norm(site.frac_coords - np.array([+.5,+.0,.0])) < 1e-10:
            trilayer.replace(idx,Element('Ga'))
    # Insert the Mn sites
    trilayer.append('Mn', [0.0,0.0,zscale*a-a/2], coords_are_cartesian=True)
    trilayer.append('Mn', [0,a,zscale*a-a/2], coords_are_cartesian=True)
    trilayer.append('Mn', [0.0,0.0,a/2], coords_are_cartesian=True)
    trilayer.append('Mn', [0,a,a/2], coords_are_cartesian=True)
    
    return trilayer.get_sorted_structure()
Пример #12
0
    def get_refined_structure(self):
        """
        Get the refined structure based on detected symmetry. The refined
        structure is a *conventional* cell setting with atoms moved to the
        expected symmetry positions.

        Returns:
            Refined structure.
        """
        # Atomic positions have to be specified by scaled positions for spglib.
        num_atom = self._structure.num_sites
        lattice = self._transposed_latt.copy()
        pos = np.zeros((num_atom * 4, 3), dtype=float)
        pos[:num_atom] = self._positions.copy()

        numbers = np.zeros(num_atom * 4, dtype=int)
        numbers[:num_atom] = self._numbers.copy()
        num_atom_bravais = spg.refine_cell(lattice, pos, numbers, num_atom,
                                           self._symprec, self._angle_tol)
        zs = numbers[:num_atom_bravais]
        species = [self._unique_species[i - 1] for i in zs]
        s = Structure(lattice.T.copy(), species, pos[:num_atom_bravais])
        return s.get_sorted_structure()
Пример #13
0
    def _get_structure(self, data, primitive, substitution_dictionary=None):
        """
        Generate structure from part of the cif.
        """
        # Symbols often representing
        # common representations for elements/water in cif files
        special_symbols = {
            "D": "D",
            "Hw": "H",
            "Ow": "O",
            "Wat": "O",
            "wat": "O"
        }
        elements = [el.symbol for el in Element]

        lattice = self.get_lattice(data)
        self.symmetry_operations = self.get_symops(data)
        oxi_states = self.parse_oxi_states(data)

        coord_to_species = OrderedDict()

        def parse_symbol(sym):

            if substitution_dictionary:
                return substitution_dictionary.get(sym)
            else:
                m = re.findall(r"w?[A-Z][a-z]*", sym)
                if m and m != "?":
                    return m[0]
                return ""

        def get_matching_coord(coord):
            for op in self.symmetry_operations:
                c = op.operate(coord)
                for k in coord_to_species.keys():
                    if np.allclose(pbc_diff(c, k), (0, 0, 0),
                                   atol=self._site_tolerance):
                        return tuple(k)
            return False

        for i in range(len(data["_atom_site_label"])):
            symbol = parse_symbol(data["_atom_site_label"][i])

            if symbol:
                if symbol not in elements and symbol not in special_symbols:
                    symbol = symbol[:2]
            else:
                continue
            # make sure symbol was properly parsed from _atom_site_label
            # otherwise get it from _atom_site_type_symbol
            try:
                if symbol in special_symbols:
                    get_el_sp(special_symbols.get(symbol))
                else:
                    Element(symbol)
            except (KeyError, ValueError):
                # sometimes the site doesn't have the type_symbol.
                # we then hope the type_symbol can be parsed from the label
                if "_atom_site_type_symbol" in data.data.keys():
                    symbol = data["_atom_site_type_symbol"][i]

            if oxi_states is not None:
                if symbol in special_symbols:
                    el = get_el_sp(
                        special_symbols.get(symbol) + str(oxi_states[symbol]))
                else:
                    el = Specie(symbol, oxi_states.get(symbol, 0))
            else:

                el = get_el_sp(special_symbols.get(symbol, symbol))

            x = str2float(data["_atom_site_fract_x"][i])
            y = str2float(data["_atom_site_fract_y"][i])
            z = str2float(data["_atom_site_fract_z"][i])
            try:
                occu = str2float(data["_atom_site_occupancy"][i])
            except (KeyError, ValueError):
                occu = 1

            if occu > 0:
                coord = (x, y, z)
                match = get_matching_coord(coord)
                if not match:
                    coord_to_species[coord] = Composition({el: occu})
                else:
                    coord_to_species[match] += {el: occu}

        if any([sum(c.values()) > 1 for c in coord_to_species.values()]):
            warnings.warn("Some occupancies sum to > 1! If they are within "
                          "the tolerance, they will be rescaled.")

        allspecies = []
        allcoords = []

        if coord_to_species.items():
            for species, group in groupby(sorted(list(
                    coord_to_species.items()),
                                                 key=lambda x: x[1]),
                                          key=lambda x: x[1]):

                tmp_coords = [site[0] for site in group]

                coords = self._unique_coords(tmp_coords)

                allcoords.extend(coords)
                allspecies.extend(len(coords) * [species])

            # rescale occupancies if necessary
            for i, species in enumerate(allspecies):
                totaloccu = sum(species.values())
                if 1 < totaloccu <= self._occupancy_tolerance:
                    allspecies[i] = species / totaloccu

        if allspecies and len(allspecies) == len(allcoords):
            struct = Structure(lattice, allspecies, allcoords)
            struct = struct.get_sorted_structure()

            if primitive:
                struct = struct.get_primitive_structure()
                struct = struct.get_reduced_structure()
            return struct
Пример #14
0
    def get_conventional_standard_structure(self,
                                            international_monoclinic=True):
        """
        Gives a structure with a conventional cell according to certain
        standards. The standards are defined in Setyawan, W., & Curtarolo,
        S. (2010). High-throughput electronic band structure calculations:
        Challenges and tools. Computational Materials Science,
        49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010
        They basically enforce as much as possible
        norm(a1)<norm(a2)<norm(a3)

        Returns:
            The structure in a conventional standardized cell
        """
        tol = 1e-5
        struct = self.get_refined_structure()
        latt = struct.lattice
        latt_type = self.get_lattice_type()
        sorted_lengths = sorted(latt.abc)
        sorted_dic = sorted([{
            'vec': latt.matrix[i],
            'length': latt.abc[i],
            'orig_index': i
        } for i in [0, 1, 2]],
                            key=lambda k: k['length'])

        if latt_type in ("orthorhombic", "cubic"):
            #you want to keep the c axis where it is
            #to keep the C- settings
            transf = np.zeros(shape=(3, 3))
            if self.get_spacegroup_symbol().startswith("C"):
                transf[2] = [0, 0, 1]
                a, b = sorted(latt.abc[:2])
                sorted_dic = sorted([{
                    'vec': latt.matrix[i],
                    'length': latt.abc[i],
                    'orig_index': i
                } for i in [0, 1]],
                                    key=lambda k: k['length'])
                for i in range(2):
                    transf[i][sorted_dic[i]['orig_index']] = 1
                c = latt.abc[2]
            else:
                for i in range(len(sorted_dic)):
                    transf[i][sorted_dic[i]['orig_index']] = 1
                a, b, c = sorted_lengths
            latt = Lattice.orthorhombic(a, b, c)

        elif latt_type == "tetragonal":
            #find the "a" vectors
            #it is basically the vector repeated two times
            transf = np.zeros(shape=(3, 3))
            a, b, c = sorted_lengths
            for d in range(len(sorted_dic)):
                transf[d][sorted_dic[d]['orig_index']] = 1

            if abs(b - c) < tol:
                a, c = c, a
                transf = np.dot([[0, 0, 1], [0, 1, 0], [1, 0, 0]], transf)
            latt = Lattice.tetragonal(a, c)
        elif latt_type in ("hexagonal", "rhombohedral"):
            #for the conventional cell representation,
            #we allways show the rhombohedral lattices as hexagonal

            #check first if we have the refined structure shows a rhombohedral
            #cell
            #if so, make a supercell
            a, b, c = latt.abc
            if np.all(np.abs([a - b, c - b, a - c]) < 0.001):
                struct.make_supercell(((1, -1, 0), (0, 1, -1), (1, 1, 1)))
                a, b, c = sorted(struct.lattice.abc)

            if abs(b - c) < 0.001:
                a, c = c, a
            new_matrix = [[a / 2, -a * math.sqrt(3) / 2, 0],
                          [a / 2, a * math.sqrt(3) / 2, 0], [0, 0, c]]
            latt = Lattice(new_matrix)
            transf = np.eye(3, 3)

        elif latt_type == "monoclinic":
            #you want to keep the c axis where it is
            #to keep the C- settings

            if self.get_spacegroup().int_symbol.startswith("C"):
                transf = np.zeros(shape=(3, 3))
                transf[2] = [0, 0, 1]
                sorted_dic = sorted([{
                    'vec': latt.matrix[i],
                    'length': latt.abc[i],
                    'orig_index': i
                } for i in [0, 1]],
                                    key=lambda k: k['length'])
                a = sorted_dic[0]['length']
                b = sorted_dic[1]['length']
                c = latt.abc[2]
                new_matrix = None
                for t in itertools.permutations(list(range(2)), 2):
                    m = latt.matrix
                    landang = Lattice([m[t[0]], m[t[1]],
                                       m[2]]).lengths_and_angles
                    if landang[1][0] > 90:
                        #if the angle is > 90 we invert a and b to get
                        #an angle < 90
                        landang = Lattice([-m[t[0]], -m[t[1]],
                                           m[2]]).lengths_and_angles
                        transf = np.zeros(shape=(3, 3))
                        transf[0][t[0]] = -1
                        transf[1][t[1]] = -1
                        transf[2][2] = 1
                        a, b, c = landang[0]
                        alpha = math.pi * landang[1][0] / 180
                        new_matrix = [[a, 0, 0], [0, b, 0],
                                      [0, c * cos(alpha), c * sin(alpha)]]
                        continue

                    elif landang[1][0] < 90:
                        transf = np.zeros(shape=(3, 3))
                        transf[0][t[0]] = 1
                        transf[1][t[1]] = 1
                        transf[2][2] = 1
                        a, b, c = landang[0]
                        alpha = math.pi * landang[1][0] / 180
                        new_matrix = [[a, 0, 0], [0, b, 0],
                                      [0, c * cos(alpha), c * sin(alpha)]]

                if new_matrix is None:
                    #this if is to treat the case
                    #where alpha==90 (but we still have a monoclinic sg
                    new_matrix = [[a, 0, 0], [0, b, 0], [0, 0, c]]
                    transf = np.zeros(shape=(3, 3))
                    for c in range(len(sorted_dic)):
                        transf[c][sorted_dic[c]['orig_index']] = 1
            #if not C-setting
            else:
                #try all permutations of the axis
                #keep the ones with the non-90 angle=alpha
                #and b<c
                new_matrix = None
                for t in itertools.permutations(list(range(3)), 3):
                    m = latt.matrix
                    landang = Lattice([m[t[0]], m[t[1]],
                                       m[t[2]]]).lengths_and_angles
                    if landang[1][0] > 90 and landang[0][1] < landang[0][2]:
                        landang = Lattice([-m[t[0]], -m[t[1]],
                                           m[t[2]]]).lengths_and_angles
                        transf = np.zeros(shape=(3, 3))
                        transf[0][t[0]] = -1
                        transf[1][t[1]] = -1
                        transf[2][t[2]] = 1
                        a, b, c = landang[0]
                        alpha = math.pi * landang[1][0] / 180
                        new_matrix = [[a, 0, 0], [0, b, 0],
                                      [0, c * cos(alpha), c * sin(alpha)]]
                        continue
                    elif landang[1][0] < 90 and landang[0][1] < landang[0][2]:
                        transf = np.zeros(shape=(3, 3))
                        transf[0][t[0]] = 1
                        transf[1][t[1]] = 1
                        transf[2][t[2]] = 1
                        a, b, c = landang[0]
                        alpha = math.pi * landang[1][0] / 180
                        new_matrix = [[a, 0, 0], [0, b, 0],
                                      [0, c * cos(alpha), c * sin(alpha)]]
                if new_matrix is None:
                    #this if is to treat the case
                    #where alpha==90 (but we still have a monoclinic sg
                    new_matrix = [[sorted_lengths[0], 0, 0],
                                  [0, sorted_lengths[1], 0],
                                  [0, 0, sorted_lengths[2]]]
                    transf = np.zeros(shape=(3, 3))
                    for c in range(len(sorted_dic)):
                        transf[c][sorted_dic[c]['orig_index']] = 1

            if international_monoclinic:
                # The above code makes alpha the non-right angle.
                # The following will convert to proper international convention
                # that beta is the non-right angle.
                op = [[0, 1, 0], [1, 0, 0], [0, 0, -1]]
                transf = np.dot(op, transf)
                new_matrix = np.dot(op, new_matrix)
                beta = Lattice(new_matrix).beta
                if beta < 90:
                    op = [[-1, 0, 0], [0, -1, 0], [0, 0, 1]]
                    transf = np.dot(op, transf)
                    new_matrix = np.dot(op, new_matrix)

            latt = Lattice(new_matrix)

        elif latt_type == "triclinic":
            #we use a LLL Minkowski-like reduction for the triclinic cells
            struct = struct.get_reduced_structure("LLL")

            a, b, c = latt.lengths_and_angles[0]
            alpha, beta, gamma = [
                math.pi * i / 180 for i in latt.lengths_and_angles[1]
            ]
            new_matrix = None
            test_matrix = [
                [a, 0, 0], [b * cos(gamma), b * sin(gamma), 0.0],
                [
                    c * cos(beta),
                    c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma),
                    c * math.sqrt(
                        sin(gamma)**2 - cos(alpha)**2 - cos(beta)**2 +
                        2 * cos(alpha) * cos(beta) * cos(gamma)) / sin(gamma)
                ]
            ]

            def is_all_acute_or_obtuse(m):
                recp_angles = np.array(Lattice(m).reciprocal_lattice.angles)
                return np.all(recp_angles <= 90) or np.all(recp_angles > 90)

            if is_all_acute_or_obtuse(test_matrix):
                transf = np.eye(3)
                new_matrix = test_matrix

            test_matrix = [
                [-a, 0, 0], [b * cos(gamma), b * sin(gamma), 0.0],
                [
                    -c * cos(beta),
                    -c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma),
                    -c * math.sqrt(
                        sin(gamma)**2 - cos(alpha)**2 - cos(beta)**2 +
                        2 * cos(alpha) * cos(beta) * cos(gamma)) / sin(gamma)
                ]
            ]

            if is_all_acute_or_obtuse(test_matrix):
                transf = [[-1, 0, 0], [0, 1, 0], [0, 0, -1]]
                new_matrix = test_matrix

            test_matrix = [
                [-a, 0, 0], [-b * cos(gamma), -b * sin(gamma), 0.0],
                [
                    c * cos(beta),
                    c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma),
                    c * math.sqrt(
                        sin(gamma)**2 - cos(alpha)**2 - cos(beta)**2 +
                        2 * cos(alpha) * cos(beta) * cos(gamma)) / sin(gamma)
                ]
            ]

            if is_all_acute_or_obtuse(test_matrix):
                transf = [[-1, 0, 0], [0, -1, 0], [0, 0, 1]]
                new_matrix = test_matrix

            test_matrix = [
                [a, 0, 0], [-b * cos(gamma), -b * sin(gamma), 0.0],
                [
                    -c * cos(beta),
                    -c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma),
                    -c * math.sqrt(
                        sin(gamma)**2 - cos(alpha)**2 - cos(beta)**2 +
                        2 * cos(alpha) * cos(beta) * cos(gamma)) / sin(gamma)
                ]
            ]
            if is_all_acute_or_obtuse(test_matrix):
                transf = [[1, 0, 0], [0, -1, 0], [0, 0, -1]]
                new_matrix = test_matrix

            latt = Lattice(new_matrix)

        new_coords = np.dot(transf, np.transpose(struct.frac_coords)).T
        new_struct = Structure(latt,
                               struct.species_and_occu,
                               new_coords,
                               site_properties=struct.site_properties,
                               to_unit_cell=True)
        return new_struct.get_sorted_structure()
Пример #15
0
    def _get_structure(self, data, primitive, substitution_dictionary=None):
        """
        Generate structure from part of the cif.
        """
        # Symbols often representing
        #common representations for elements/water in cif files
        special_symbols = {"D":"D", "Hw":"H", "Ow":"O", "Wat":"O", "wat": "O"}
        elements = map(str, ptable.all_elements)

        lattice = self.get_lattice(data)
        self.symmetry_operations = self.get_symops(data)
        oxi_states = self.parse_oxi_states(data)

        coord_to_species = OrderedDict()

        def parse_symbol(sym):

            if substitution_dictionary:
                return substitution_dictionary.get(sym)
            else:
                m = re.findall(r"w?[A-Z][a-z]*", sym)
                if m and m != "?":
                    return m[0]
                return ""

        for i in range(len(data["_atom_site_label"])):
            symbol = parse_symbol(data["_atom_site_label"][i])

            if symbol:
                if symbol not in elements and symbol not in special_symbols:
                    symbol = symbol[:2]
            else:
                continue
            # make sure symbol was properly parsed from _atom_site_label
            # otherwise get it from _atom_site_type_symbol
            try:
                if symbol in special_symbols:
                    get_el_sp(special_symbols.get(symbol))
                else:
                    Element(symbol)
            except KeyError:
                # sometimes the site doesn't have the type_symbol.
                # we then hope the type_symbol can be parsed from the label
                if "_atom_site_type_symbol" in data.data.keys():
                    symbol = data["_atom_site_type_symbol"][i]

            if oxi_states is not None:
                if symbol in special_symbols:
                    el = get_el_sp(special_symbols.get(symbol) +
                                   str(oxi_states[symbol]))
                else:
                    el = Specie(symbol, oxi_states.get(symbol, 0))
            else:

                el = get_el_sp(special_symbols.get(symbol) if \
                            symbol in special_symbols else symbol)

            x = str2float(data["_atom_site_fract_x"][i])
            y = str2float(data["_atom_site_fract_y"][i])
            z = str2float(data["_atom_site_fract_z"][i])
            try:
                occu = str2float(data["_atom_site_occupancy"][i])
            except (KeyError, ValueError):
                occu = 1
            if occu > 0:
                coord = (x, y, z)
                if coord not in coord_to_species:
                    coord_to_species[coord] = {el: occu}
                else:
                    coord_to_species[coord][el] = occu


        coord_to_species = {k: Composition(v)
                            for k, v in coord_to_species.items()}
        allspecies = []
        allcoords = []

        if coord_to_species.items():
            for species, group in groupby(
                    sorted(list(coord_to_species.items()), key=lambda x: x[1]),
                    key=lambda x: x[1]):

                tmp_coords = [site[0] for site in group]

                coords = self._unique_coords(tmp_coords)

                allcoords.extend(coords)
                allspecies.extend(len(coords) * [species])

            #rescale occupancies if necessary
            for species in allspecies:
                totaloccu = sum(species.values())
                if 1 < totaloccu <= self._occupancy_tolerance:
                    for key, value in six.iteritems(species):
                        species[key] = value / totaloccu

        if allspecies and len(allspecies) == len(allcoords):

            struct = Structure(lattice, allspecies, allcoords)
            struct = struct.get_sorted_structure()

            if primitive:
                struct = struct.get_primitive_structure()
                struct = struct.get_reduced_structure()
            return struct
Пример #16
0
    def _get_structure(self, data, primitive):
        """
        Generate structure from part of the cif.
        """
        def parse_symbol(sym):
            # Common representations for elements/water in cif files
            # TODO: fix inconsistent handling of water
            special = {
                "D": "D",
                "Hw": "H",
                "Ow": "O",
                "Wat": "O",
                "wat": "O",
                "OH": "",
                "OH2": ""
            }
            m = re.findall(r"w?[A-Z][a-z]*", sym)
            if m and m != "?":
                if sym in special:
                    v = special[sym]
                else:
                    v = special.get(m[0], m[0])
                if len(m) > 1 or (m[0] in special):
                    warnings.warn("{} parsed as {}".format(sym, v))
                return v

        lattice = self.get_lattice(data)

        # if magCIF, get magnetic symmetry moments and magmoms
        # else standard CIF, and use empty magmom dict
        if self.feature_flags["magcif_incommensurate"]:
            raise NotImplementedError(
                "Incommensurate structures not currently supported.")
        elif self.feature_flags["magcif"]:
            self.symmetry_operations = self.get_magsymops(data)
            magmoms = self.parse_magmoms(data, lattice=lattice)
        else:
            self.symmetry_operations = self.get_symops(data)
            magmoms = {}

        oxi_states = self.parse_oxi_states(data)

        coord_to_species = OrderedDict()
        coord_to_magmoms = OrderedDict()

        def get_matching_coord(coord):
            keys = list(coord_to_species.keys())
            coords = np.array(keys)
            for op in self.symmetry_operations:
                c = op.operate(coord)
                inds = find_in_coord_list_pbc(coords,
                                              c,
                                              atol=self._site_tolerance)
                # cant use if inds, because python is dumb and np.array([0]) evaluates
                # to False
                if len(inds):
                    return keys[inds[0]]
            return False

        for i in range(len(data["_atom_site_label"])):
            try:
                # If site type symbol exists, use it. Otherwise, we use the
                # label.
                symbol = parse_symbol(data["_atom_site_type_symbol"][i])
            except KeyError:
                symbol = parse_symbol(data["_atom_site_label"][i])
            if not symbol:
                continue

            if oxi_states is not None:
                o_s = oxi_states.get(symbol, 0)
                # use _atom_site_type_symbol if possible for oxidation state
                if "_atom_site_type_symbol" in data.data.keys():
                    oxi_symbol = data["_atom_site_type_symbol"][i]
                    o_s = oxi_states.get(oxi_symbol, o_s)
                try:
                    el = Specie(symbol, o_s)
                except:
                    el = DummySpecie(symbol, o_s)
            else:
                el = get_el_sp(symbol)

            x = str2float(data["_atom_site_fract_x"][i])
            y = str2float(data["_atom_site_fract_y"][i])
            z = str2float(data["_atom_site_fract_z"][i])
            magmom = magmoms.get(data["_atom_site_label"][i], Magmom(0))

            try:
                occu = str2float(data["_atom_site_occupancy"][i])
            except (KeyError, ValueError):
                occu = 1

            if occu > 0:
                coord = (x, y, z)
                match = get_matching_coord(coord)
                if not match:
                    coord_to_species[coord] = Composition({el: occu})
                    coord_to_magmoms[coord] = magmom
                else:
                    coord_to_species[match] += {el: occu}
                    coord_to_magmoms[
                        match] = None  # disordered magnetic not currently supported

        sum_occu = [sum(c.values()) for c in coord_to_species.values()]
        if any([o > 1 for o in sum_occu]):
            warnings.warn(
                "Some occupancies (%s) sum to > 1! If they are within "
                "the tolerance, they will be rescaled." % str(sum_occu))

        allspecies = []
        allcoords = []
        allmagmoms = []

        # check to see if magCIF file is disordered
        if self.feature_flags["magcif"]:
            for k, v in coord_to_magmoms.items():
                if v is None:
                    # Proposed solution to this is to instead store magnetic moments
                    # as Specie 'spin' property, instead of site property, but this
                    # introduces ambiguities for end user (such as unintended use of
                    # `spin` and Specie will have fictious oxidation state).
                    raise NotImplementedError(
                        'Disordered magnetic structures not currently supported.'
                    )

        if coord_to_species.items():
            for species, group in groupby(sorted(list(
                    coord_to_species.items()),
                                                 key=lambda x: x[1]),
                                          key=lambda x: x[1]):
                tmp_coords = [site[0] for site in group]
                tmp_magmom = [
                    coord_to_magmoms[tmp_coord] for tmp_coord in tmp_coords
                ]

                if self.feature_flags["magcif"]:
                    coords, magmoms = self._unique_coords(
                        tmp_coords, tmp_magmom)
                else:
                    coords, magmoms = self._unique_coords(tmp_coords)

                allcoords.extend(coords)
                allspecies.extend(len(coords) * [species])
                allmagmoms.extend(magmoms)

            # rescale occupancies if necessary
            for i, species in enumerate(allspecies):
                totaloccu = sum(species.values())
                if 1 < totaloccu <= self._occupancy_tolerance:
                    allspecies[i] = species / totaloccu

        if allspecies and len(allspecies) == len(allcoords) and len(
                allspecies) == len(allmagmoms):

            if self.feature_flags["magcif"]:
                struct = Structure(lattice,
                                   allspecies,
                                   allcoords,
                                   site_properties={"magmom": allmagmoms})
            else:
                struct = Structure(lattice, allspecies, allcoords)

            struct = struct.get_sorted_structure()

            if primitive:
                struct = struct.get_primitive_structure()
                struct = struct.get_reduced_structure()
            return struct
Пример #17
0
    def _get_structure(self, data, primitive):
        """
        Generate structure from part of the cif.
        """
        def parse_symbol(sym):
            # Common representations for elements/water in cif files
            # TODO: fix inconsistent handling of water
            special = {"D": "D", "Hw": "H", "Ow": "O", "Wat": "O",
                       "wat": "O", "OH": "", "OH2": ""}
            m = re.findall(r"w?[A-Z][a-z]*", sym)
            if m and m != "?":
                if sym in special:
                    v = special[sym]
                else:
                    v = special.get(m[0], m[0])
                if len(m) > 1 or (m[0] in special):
                    warnings.warn("{} parsed as {}".format(sym, v))
                return v

        lattice = self.get_lattice(data)
        self.symmetry_operations = self.get_symops(data)
        oxi_states = self.parse_oxi_states(data)

        coord_to_species = OrderedDict()

        def get_matching_coord(coord):
            keys = list(coord_to_species.keys())
            coords = np.array(keys)
            for op in self.symmetry_operations:
                c = op.operate(coord)
                inds = find_in_coord_list_pbc(coords, c, atol=self._site_tolerance)
                # cant use if inds, because python is dumb and np.array([0]) evaluates
                # to False
                if len(inds):
                    return keys[inds[0]]
            return False

        ############################################################
        """
        This part of the code deals with handling formats of data as found in
        CIF files extracted from the Springer Materials/Pauling File
        databases, and that are different from standard ICSD formats.
        """

        # Check to see if "_atom_site_type_symbol" exists, as some test CIFs do
        # not contain this key.
        if "_atom_site_type_symbol" in data.data.keys():

            # Keep a track of which data row needs to be removed.
            # Example of a row: Nb,Zr '0.8Nb + 0.2Zr' .2a .m-3m 0 0 0 1 14
            # 'rhombic dodecahedron, Nb<sub>14</sub>'
            # Without this code, the above row in a structure would be parsed
            # as an ordered site with only Nb (since
            # CifParser would try to parse the first two characters of the
            # label "Nb,Zr") and occupancy=1.
            # However, this site is meant to be a disordered site with 0.8 of
            # Nb and 0.2 of Zr.
            idxs_to_remove = []

            for idx, el_row in enumerate(data["_atom_site_label"]):

                # CIF files from the Springer Materials/Pauling File have
                # switched the label and symbol. Thus, in the
                # above shown example row, '0.8Nb + 0.2Zr' is the symbol.
                # Below, we split the strings on ' + ' to
                # check if the length (or number of elements) in the label and
                # symbol are equal.
                if len(data["_atom_site_type_symbol"][idx].split(' + ')) > \
                        len(data["_atom_site_label"][idx].split(' + ')):

                    # Dictionary to hold extracted elements and occupancies
                    els_occu = {}

                    # parse symbol to get element names and occupancy and store
                    # in "els_occu"
                    symbol_str = data["_atom_site_type_symbol"][idx]
                    symbol_str_lst = symbol_str.split(' + ')
                    for elocc_idx in range(len(symbol_str_lst)):
                        # Remove any bracketed items in the string
                        symbol_str_lst[elocc_idx] = re.sub(r'\([0-9]*\)', '',
                            symbol_str_lst[elocc_idx].strip())

                        # Extract element name and its occupancy from the
                        # string, and store it as a
                        # key-value pair in "els_occ".
                        els_occu[str(re.findall(r'\D+', symbol_str_lst[
                            elocc_idx].strip())[1]).replace('<sup>', '')] = \
                            float('0' + re.findall(r'\.?\d+', symbol_str_lst[
                                elocc_idx].strip())[1])

                    x = str2float(data["_atom_site_fract_x"][idx])
                    y = str2float(data["_atom_site_fract_y"][idx])
                    z = str2float(data["_atom_site_fract_z"][idx])

                    coord = (x, y, z)
                    # Add each partially occupied element on the site coordinate
                    for et in els_occu:
                        match = get_matching_coord(coord)
                        if not match:
                            coord_to_species[coord] = Composition(
                                {parse_symbol(et): els_occu[parse_symbol(et)]})
                        else:
                            coord_to_species[match] += {
                                parse_symbol(et): els_occu[parse_symbol(et)]}
                    idxs_to_remove.append(idx)

            # Remove the original row by iterating over all keys in the CIF
            # data looking for lists, which indicates
            # multiple data items, one for each row, and remove items from the
            # list that corresponds to the removed row,
            # so that it's not processed by the rest of this function (which
            # would result in an error).
            for cif_key in data.data:
                if type(data.data[cif_key]) == list:
                    for id in sorted(idxs_to_remove, reverse=True):
                        del data.data[cif_key][id]

        ############################################################
        for i in range(len(data["_atom_site_label"])):
            try:
                # If site type symbol exists, use it. Otherwise, we use the
                # label.
                symbol = parse_symbol(data["_atom_site_type_symbol"][i])
            except KeyError:
                symbol = parse_symbol(data["_atom_site_label"][i])
            if not symbol:
                continue

            if oxi_states is not None:
                o_s = oxi_states.get(symbol, 0)
                # use _atom_site_type_symbol if possible for oxidation state
                if "_atom_site_type_symbol" in data.data.keys():
                    oxi_symbol = data["_atom_site_type_symbol"][i]
                    o_s = oxi_states.get(oxi_symbol, o_s)
                try:
                    el = Specie(symbol, o_s)
                except:
                    el = DummySpecie(symbol, o_s)
            else:
                el = get_el_sp(symbol)

            x = str2float(data["_atom_site_fract_x"][i])
            y = str2float(data["_atom_site_fract_y"][i])
            z = str2float(data["_atom_site_fract_z"][i])

            try:
                occu = str2float(data["_atom_site_occupancy"][i])
            except (KeyError, ValueError):
                occu = 1

            if occu > 0:
                coord = (x, y, z)
                match = get_matching_coord(coord)
                if not match:
                    coord_to_species[coord] = Composition({el: occu})
                else:
                    coord_to_species[match] += {el: occu}

        sum_occu = [sum(c.values()) for c in coord_to_species.values()]
        if any([o > 1 for o in sum_occu]):
            warnings.warn("Some occupancies (%s) sum to > 1! If they are within "
                          "the tolerance, they will be rescaled." % str(sum_occu))

        allspecies = []
        allcoords = []

        if coord_to_species.items():
            for species, group in groupby(
                    sorted(list(coord_to_species.items()), key=lambda x: x[1]),
                    key=lambda x: x[1]):
                tmp_coords = [site[0] for site in group]

                coords = self._unique_coords(tmp_coords)

                allcoords.extend(coords)
                allspecies.extend(len(coords) * [species])

            # rescale occupancies if necessary
            for i, species in enumerate(allspecies):
                totaloccu = sum(species.values())
                if 1 < totaloccu <= self._occupancy_tolerance:
                    allspecies[i] = species / totaloccu

        if allspecies and len(allspecies) == len(allcoords):
            struct = Structure(lattice, allspecies, allcoords)
            struct = struct.get_sorted_structure()

            if primitive:
                struct = struct.get_primitive_structure()
                struct = struct.get_reduced_structure()
            return struct
    def condense_structure(self, structure: Structure) -> Dict[str, Any]:
        """Condenses the structure into an intermediate dict representation.

        Args:
            structure: A pymatgen structure object.

        Returns:
            The condensed structure information. The data is formatted as a
            :obj:`dict` with a fixed set of keys. An up-to-date example of the,
            the condensed representation of MoS2 given in the documentation.
            See: ``robocrystallographer/docs_rst/source/format.rst`` or
            https://hackingmaterials.lbl.gov/robocrystallographer/format.html
        """
        # sort so we can give proper symmetry labels
        structure = structure.get_sorted_structure()

        # wrap all site coords into unit cell
        structure.translate_sites(range(structure.num_sites), [1, 1, 1])

        sga = SpacegroupAnalyzer(structure, symprec=self.symprec)
        if self.use_conventional_cell:
            structure = sga.get_conventional_standard_structure()
        else:
            structure = sga.get_symmetrized_structure()

        bonded_structure = self.near_neighbors.get_bonded_structure(structure)

        components = get_structure_components(
            bonded_structure,
            inc_orientation=True,
            inc_site_ids=True,
            inc_molecule_graph=True,
        )

        dimensionality = max(c["dimensionality"] for c in components)
        mineral = self._condense_mineral(structure, components)
        formula = self._condense_formula(structure, components)

        structure_data = {
            "formula": formula,
            "spg_symbol": sga.get_space_group_symbol(),
            "crystal_system": sga.get_crystal_system(),
            "mineral": mineral,
            "dimensionality": dimensionality,
        }

        site_analyzer = SiteAnalyzer(
            bonded_structure,
            symprec=self.symprec,
            use_symmetry_equivalent_sites=self.use_symmetry_equivalent_sites,
        )
        structure_data["sites"] = site_analyzer.get_all_site_summaries()
        structure_data[
            "distances"] = site_analyzer.get_all_bond_distance_summaries()
        structure_data[
            "angles"] = site_analyzer.get_all_connectivity_angle_summaries()
        structure_data[
            "nnn_distances"] = site_analyzer.get_all_nnn_distance_summaries()

        component_summary, component_makeup = self._condense_components(
            components, sga, site_analyzer)
        structure_data["components"] = component_summary
        structure_data["component_makeup"] = component_makeup

        if components_are_vdw_heterostructure(components):
            hs_info = get_vdw_heterostructure_information(
                components,
                use_iupac_formula=self.use_iupac_formula,
                use_common_formulas=self.use_common_formulas,
            )
        else:
            hs_info = None

        structure_data["vdw_heterostructure_info"] = hs_info

        return structure_data
Пример #19
0
    def _get_structure(self, data, primitive, substitution_dictionary=None):
        """
        Generate structure from part of the cif.
        """
        # Symbols often representing
        #common representations for elements/water in cif files
        special_symbols = {
            "D": "D",
            "Hw": "H",
            "Ow": "O",
            "Wat": "O",
            "wat": "O"
        }
        elements = [el.symbol for el in Element]

        lattice = self.get_lattice(data)
        self.symmetry_operations = self.get_symops(data)
        oxi_states = self.parse_oxi_states(data)

        coord_to_species = OrderedDict()

        def parse_symbol(sym):

            if substitution_dictionary:
                return substitution_dictionary.get(sym)
            else:
                m = re.findall(r"w?[A-Z][a-z]*", sym)
                if m and m != "?":
                    return m[0]
                return ""

        for i in range(len(data["_atom_site_label"])):
            symbol = parse_symbol(data["_atom_site_label"][i])

            if symbol:
                if symbol not in elements and symbol not in special_symbols:
                    symbol = symbol[:2]
            else:
                continue
            # make sure symbol was properly parsed from _atom_site_label
            # otherwise get it from _atom_site_type_symbol
            try:
                if symbol in special_symbols:
                    get_el_sp(special_symbols.get(symbol))
                else:
                    Element(symbol)
            except (KeyError, ValueError):
                # sometimes the site doesn't have the type_symbol.
                # we then hope the type_symbol can be parsed from the label
                if "_atom_site_type_symbol" in data.data.keys():
                    symbol = data["_atom_site_type_symbol"][i]

            if oxi_states is not None:
                if symbol in special_symbols:
                    el = get_el_sp(
                        special_symbols.get(symbol) + str(oxi_states[symbol]))
                else:
                    el = Specie(symbol, oxi_states.get(symbol, 0))
            else:

                el = get_el_sp(special_symbols.get(symbol) if \
                            symbol in special_symbols else symbol)

            x = str2float(data["_atom_site_fract_x"][i])
            y = str2float(data["_atom_site_fract_y"][i])
            z = str2float(data["_atom_site_fract_z"][i])
            try:
                occu = str2float(data["_atom_site_occupancy"][i])
            except (KeyError, ValueError):
                occu = 1
            if occu > 0:
                coord = (x, y, z)
                if coord not in coord_to_species:
                    coord_to_species[coord] = {el: occu}
                else:
                    coord_to_species[coord][el] = occu

        coord_to_species = {
            k: Composition(v)
            for k, v in coord_to_species.items()
        }
        allspecies = []
        allcoords = []

        if coord_to_species.items():
            for species, group in groupby(sorted(list(
                    coord_to_species.items()),
                                                 key=lambda x: x[1]),
                                          key=lambda x: x[1]):

                tmp_coords = [site[0] for site in group]

                coords = self._unique_coords(tmp_coords)

                allcoords.extend(coords)
                allspecies.extend(len(coords) * [species])

            #rescale occupancies if necessary
            for species in allspecies:
                totaloccu = sum(species.values())
                if 1 < totaloccu <= self._occupancy_tolerance:
                    for key, value in six.iteritems(species):
                        species[key] = value / totaloccu

        if allspecies and len(allspecies) == len(allcoords):

            struct = Structure(lattice, allspecies, allcoords)
            struct = struct.get_sorted_structure()

            if primitive:
                struct = struct.get_primitive_structure()
                struct = struct.get_reduced_structure()
            return struct
Пример #20
0
    def _get_structure(self, data, primitive):
        """
        Generate structure from part of the cif.
        """
        lengths = [float_from_str(data["_cell_length_" + i])
                   for i in ["a", "b", "c"]]
        angles = [float_from_str(data["_cell_angle_" + i])
                  for i in ["alpha", "beta", "gamma"]]
        lattice = Lattice.from_lengths_and_angles(lengths, angles)
        try:
            sympos = data["_symmetry_equiv_pos_as_xyz"]
        except KeyError:
            try:
                sympos = data["_symmetry_equiv_pos_as_xyz_"]
            except KeyError:
                warnings.warn("No _symmetry_equiv_pos_as_xyz type key found. "
                              "Defaulting to P1.")
                sympos = ['x, y, z']
        self.symmetry_operations = parse_symmetry_operations(sympos)

        def parse_symbol(sym):
            m = re.search("([A-Z][a-z]*)", sym)
            if m:
                return m.group(1)
            return ""

        try:
            oxi_states = {data["_atom_type_symbol"][i]:
                          float_from_str(data["_atom_type_oxidation_number"][i])
                          for i in xrange(len(data["_atom_type_symbol"]))}
        except (ValueError, KeyError):
            oxi_states = None

        coord_to_species = OrderedDict()

        for i in xrange(len(data["_atom_site_type_symbol"])):
            symbol = parse_symbol(data["_atom_site_type_symbol"][i])
            if oxi_states is not None:
                el = Specie(symbol,
                            oxi_states[data["_atom_site_type_symbol"][i]])
            else:
                el = Element(symbol)
            x = float_from_str(data["_atom_site_fract_x"][i])
            y = float_from_str(data["_atom_site_fract_y"][i])
            z = float_from_str(data["_atom_site_fract_z"][i])
            try:
                occu = float_from_str(data["_atom_site_occupancy"][i])
            except (KeyError, ValueError):
                occu = 1
            if occu > 0:
                coord = (x, y, z)
                if coord not in coord_to_species:
                    coord_to_species[coord] = {el: occu}
                else:
                    coord_to_species[coord][el] = occu

        allspecies = []
        allcoords = []

        for coord, species in coord_to_species.items():
            coords = self._unique_coords(coord)
            allcoords.extend(coords)
            allspecies.extend(len(coords) * [species])

        #rescale occupancies if necessary
        for species in allspecies:
            totaloccu = sum(species.values())
            if  1 < totaloccu <= self._occupancy_tolerance:
                for key, value in species.iteritems():
                    species[key] = value / totaloccu

        struct = Structure(lattice, allspecies, allcoords)
        if primitive:
            struct = struct.get_primitive_structure()
        return struct.get_sorted_structure()
Пример #21
0
    def _get_structure(self, data, primitive, substitution_dictionary=None):
        """
        Generate structure from part of the cif.
        """
        # Symbols often representing
        # common representations for elements/water in cif files
        special_symbols = {
            "D": "D",
            "Hw": "H",
            "Ow": "O",
            "Wat": "O",
            "wat": "O"
        }
        elements = [el.symbol for el in Element]

        lattice = self.get_lattice(data)
        self.symmetry_operations = self.get_symops(data)
        oxi_states = self.parse_oxi_states(data)

        coord_to_species = OrderedDict()

        def parse_symbol(sym):

            if substitution_dictionary:
                return substitution_dictionary.get(sym)
            elif sym in ['OH', 'OH2']:
                warnings.warn("Symbol '{}' not recognized".format(sym))
                return ""
            else:
                m = re.findall(r"w?[A-Z][a-z]*", sym)
                if m and m != "?":
                    return m[0]
                return ""

        def get_matching_coord(coord):
            for op in self.symmetry_operations:
                c = op.operate(coord)
                for k in coord_to_species.keys():
                    if np.allclose(pbc_diff(c, k), (0, 0, 0),
                                   atol=self._site_tolerance):
                        return tuple(k)
            return False

        ############################################################
        """
        This part of the code deals with handling formats of data as found in CIF files extracted from the
        Springer Materials/Pauling File databases, and that are different from standard ICSD formats.
        """

        # Check to see if "_atom_site_type_symbol" exists, as some test CIFs do not contain this key.
        if "_atom_site_type_symbol" in data.data.keys():

            # Keep a track of which data row needs to be removed.
            # Example of a row: Nb,Zr '0.8Nb + 0.2Zr' .2a .m-3m 0 0 0 1 14 'rhombic dodecahedron, Nb<sub>14</sub>'
            # Without this code, the above row in a structure would be parsed as an ordered site with only Nb (since
            # CifParser would try to parse the first two characters of the label "Nb,Zr") and occupancy=1.
            # However, this site is meant to be a disordered site with 0.8 of Nb and 0.2 of Zr.
            idxs_to_remove = []

            for idx, el_row in enumerate(data["_atom_site_label"]):

                # CIF files from the Springer Materials/Pauling File have switched the label and symbol. Thus, in the
                # above shown example row, '0.8Nb + 0.2Zr' is the symbol. Below, we split the strings on ' + ' to
                # check if the length (or number of elements) in the label and symbol are equal.
                if len(data["_atom_site_type_symbol"][idx].split(' + ')) > \
                        len(data["_atom_site_label"][idx].split(' + ')):

                    # Dictionary to hold extracted elements and occupancies
                    els_occu = {}

                    # parse symbol to get element names and occupancy and store in "els_occu"
                    symbol_str = data["_atom_site_type_symbol"][idx]
                    symbol_str_lst = symbol_str.split(' + ')
                    for elocc_idx in range(len(symbol_str_lst)):
                        # Remove any bracketed items in the string
                        symbol_str_lst[elocc_idx] = re.sub(
                            '\([0-9]*\)', '',
                            symbol_str_lst[elocc_idx].strip())

                        # Extract element name and its occupancy from the string, and store it as a
                        # key-value pair in "els_occ".
                        els_occu[str(re.findall('\D+', symbol_str_lst[elocc_idx].strip())[1]).replace('<sup>', '')] = \
                            float('0' + re.findall('\.?\d+', symbol_str_lst[elocc_idx].strip())[1])

                    x = str2float(data["_atom_site_fract_x"][idx])
                    y = str2float(data["_atom_site_fract_y"][idx])
                    z = str2float(data["_atom_site_fract_z"][idx])

                    coord = (x, y, z)
                    # Add each partially occupied element on the site coordinate
                    for et in els_occu:
                        match = get_matching_coord(coord)
                        if not match:
                            coord_to_species[coord] = Composition(
                                {parse_symbol(et): els_occu[parse_symbol(et)]})
                        else:
                            coord_to_species[match] += {
                                parse_symbol(et): els_occu[parse_symbol(et)]
                            }
                    idxs_to_remove.append(idx)

            # Remove the original row by iterating over all keys in the CIF data looking for lists, which indicates
            # multiple data items, one for each row, and remove items from the list that corresponds to the removed row,
            # so that it's not processed by the rest of this function (which would result in an error).
            for cif_key in data.data:
                if type(data.data[cif_key]) == list:
                    for id in sorted(idxs_to_remove, reverse=True):
                        del data.data[cif_key][id]

        ############################################################

        for i in range(len(data["_atom_site_label"])):
            symbol = parse_symbol(data["_atom_site_label"][i])

            if symbol:
                if symbol not in elements and symbol not in special_symbols:
                    symbol = symbol[:2]
            else:
                continue
            # make sure symbol was properly parsed from _atom_site_label
            # otherwise get it from _atom_site_type_symbol
            try:
                if symbol in special_symbols:
                    get_el_sp(special_symbols.get(symbol))
                else:
                    Element(symbol)
            except (KeyError, ValueError):
                # sometimes the site doesn't have the type_symbol.
                # we then hope the type_symbol can be parsed from the label
                if "_atom_site_type_symbol" in data.data.keys():
                    symbol = data["_atom_site_type_symbol"][i]

            if oxi_states is not None:
                if symbol in special_symbols:
                    el = get_el_sp(
                        special_symbols.get(symbol) + str(oxi_states[symbol]))
                else:
                    el = Specie(symbol, oxi_states.get(symbol, 0))
            else:

                el = get_el_sp(special_symbols.get(symbol, symbol))

            x = str2float(data["_atom_site_fract_x"][i])
            y = str2float(data["_atom_site_fract_y"][i])
            z = str2float(data["_atom_site_fract_z"][i])
            try:
                occu = str2float(data["_atom_site_occupancy"][i])
            except (KeyError, ValueError):
                occu = 1

            if occu > 0:
                coord = (x, y, z)
                match = get_matching_coord(coord)
                if not match:
                    coord_to_species[coord] = Composition({el: occu})
                else:
                    coord_to_species[match] += {el: occu}

        if any([sum(c.values()) > 1 for c in coord_to_species.values()]):
            warnings.warn("Some occupancies sum to > 1! If they are within "
                          "the tolerance, they will be rescaled.")

        allspecies = []
        allcoords = []

        if coord_to_species.items():
            for species, group in groupby(sorted(list(
                    coord_to_species.items()),
                                                 key=lambda x: x[1]),
                                          key=lambda x: x[1]):

                tmp_coords = [site[0] for site in group]

                coords = self._unique_coords(tmp_coords)

                allcoords.extend(coords)
                allspecies.extend(len(coords) * [species])

            # rescale occupancies if necessary
            for i, species in enumerate(allspecies):
                totaloccu = sum(species.values())
                if 1 < totaloccu <= self._occupancy_tolerance:
                    allspecies[i] = species / totaloccu

        if allspecies and len(allspecies) == len(allcoords):
            struct = Structure(lattice, allspecies, allcoords)
            struct = struct.get_sorted_structure()

            if primitive:
                struct = struct.get_primitive_structure()
                struct = struct.get_reduced_structure()
            return struct
Пример #22
0
    def _get_structure(self, data, primitive, substitution_dictionary=None):
        """
        Generate structure from part of the cif.
        """
        # Symbols often representing
        # common representations for elements/water in cif files
        special_symbols = {"D": "D", "Hw": "H", "Ow": "O", "Wat": "O",
                           "wat": "O"}
        elements = [el.symbol for el in Element]

        lattice = self.get_lattice(data)
        self.symmetry_operations = self.get_symops(data)
        oxi_states = self.parse_oxi_states(data)

        coord_to_species = OrderedDict()

        def parse_symbol(sym):

            if substitution_dictionary:
                return substitution_dictionary.get(sym)
            else:
                m = re.findall(r"w?[A-Z][a-z]*", sym)
                if m and m != "?":
                    return m[0]
                return ""

        def get_matching_coord(coord):
            for op in self.symmetry_operations:
                c = op.operate(coord)
                for k in coord_to_species.keys():
                    if np.allclose(pbc_diff(c, k), (0, 0, 0),
                                   atol=self._site_tolerance):
                        return tuple(k)
            return False

        for i in range(len(data["_atom_site_label"])):
            symbol = parse_symbol(data["_atom_site_label"][i])

            if symbol:
                if symbol not in elements and symbol not in special_symbols:
                    symbol = symbol[:2]
            else:
                continue
            # make sure symbol was properly parsed from _atom_site_label
            # otherwise get it from _atom_site_type_symbol
            try:
                if symbol in special_symbols:
                    get_el_sp(special_symbols.get(symbol))
                else:
                    Element(symbol)
            except (KeyError, ValueError):
                # sometimes the site doesn't have the type_symbol.
                # we then hope the type_symbol can be parsed from the label
                if "_atom_site_type_symbol" in data.data.keys():
                    symbol = data["_atom_site_type_symbol"][i]

            if oxi_states is not None:
                if symbol in special_symbols:
                    el = get_el_sp(special_symbols.get(symbol) +
                                   str(oxi_states[symbol]))
                else:
                    el = Specie(symbol, oxi_states.get(symbol, 0))
            else:

                el = get_el_sp(special_symbols.get(symbol, symbol))

            x = str2float(data["_atom_site_fract_x"][i])
            y = str2float(data["_atom_site_fract_y"][i])
            z = str2float(data["_atom_site_fract_z"][i])
            try:
                occu = str2float(data["_atom_site_occupancy"][i])
            except (KeyError, ValueError):
                occu = 1

            if occu > 0:
                coord = (x, y, z)
                match = get_matching_coord(coord)
                if not match:
                    coord_to_species[coord] = Composition({el: occu})
                else:
                    coord_to_species[match] += {el: occu}

        if any([sum(c.values()) > 1 for c in coord_to_species.values()]):
            warnings.warn("Some occupancies sum to > 1! If they are within "
                          "the tolerance, they will be rescaled.")

        allspecies = []
        allcoords = []

        if coord_to_species.items():
            for species, group in groupby(
                    sorted(list(coord_to_species.items()), key=lambda x: x[1]),
                    key=lambda x: x[1]):

                tmp_coords = [site[0] for site in group]

                coords = self._unique_coords(tmp_coords)

                allcoords.extend(coords)
                allspecies.extend(len(coords) * [species])

            # rescale occupancies if necessary
            for i, species in enumerate(allspecies):
                totaloccu = sum(species.values())
                if 1 < totaloccu <= self._occupancy_tolerance:
                    allspecies[i] = species / totaloccu

        if allspecies and len(allspecies) == len(allcoords):
            struct = Structure(lattice, allspecies, allcoords)
            struct = struct.get_sorted_structure()

            if primitive:
                struct = struct.get_primitive_structure()
                struct = struct.get_reduced_structure()
            return struct
Пример #23
0
    def _get_structure(self, data, primitive):
        """
        Generate structure from part of the cif.
        """
        lengths = [
            str2float(data["_cell_length_" + i]) for i in ["a", "b", "c"]
        ]
        angles = [
            str2float(data["_cell_angle_" + i])
            for i in ["alpha", "beta", "gamma"]
        ]
        lattice = Lattice.from_lengths_and_angles(lengths, angles)
        try:
            sympos = data["_symmetry_equiv_pos_as_xyz"]
        except KeyError:
            try:
                sympos = data["_symmetry_equiv_pos_as_xyz_"]
            except KeyError:
                warnings.warn("No _symmetry_equiv_pos_as_xyz type key found. "
                              "Defaulting to P1.")
                sympos = ['x, y, z']
        self.symmetry_operations = [SymmOp.from_xyz_string(s) for s in sympos]

        def parse_symbol(sym):
            # capitalization conventions are not strictly followed, eg Cu will be CU
            m = re.search("([A-Za-z]*)", sym)
            if m:
                return m.group(1)[:2].capitalize()
            return ""

        try:
            oxi_states = {
                data["_atom_type_symbol"][i]:
                str2float(data["_atom_type_oxidation_number"][i])
                for i in range(len(data["_atom_type_symbol"]))
            }
        except (ValueError, KeyError):
            oxi_states = None

        coord_to_species = OrderedDict()
        for i in range(len(data["_atom_site_label"])):
            symbol = parse_symbol(data["_atom_site_label"][i])

            # make sure symbol was properly parsed from _atom_site_label
            # otherwise get it from _atom_site_type_symbol
            try:
                Element(symbol)
            except KeyError:
                symbol = parse_symbol(data["_atom_site_type_symbol"][i])

            if oxi_states is not None:
                # sometimes the site doesn't have the type_symbol.
                # we then hope the type_symbol can be parsed from the label
                if "_atom_site_type_symbol" in data.data.keys():
                    k = data["_atom_site_type_symbol"][i]
                else:
                    k = symbol
                el = Specie(symbol, oxi_states[k])
            else:
                el = Element(symbol)
            x = str2float(data["_atom_site_fract_x"][i])
            y = str2float(data["_atom_site_fract_y"][i])
            z = str2float(data["_atom_site_fract_z"][i])
            try:
                occu = str2float(data["_atom_site_occupancy"][i])
            except (KeyError, ValueError):
                occu = 1
            if occu > 0:
                coord = (x, y, z)
                if coord not in coord_to_species:
                    coord_to_species[coord] = {el: occu}
                else:
                    coord_to_species[coord][el] = occu

        allspecies = []
        allcoords = []

        for coord, species in coord_to_species.items():
            coords = self._unique_coords(coord)
            allcoords.extend(coords)
            allspecies.extend(len(coords) * [species])

        #rescale occupancies if necessary
        for species in allspecies:
            totaloccu = sum(species.values())
            if 1 < totaloccu <= self._occupancy_tolerance:
                for key, value in six.iteritems(species):
                    species[key] = value / totaloccu

        struct = Structure(lattice, allspecies, allcoords)
        if primitive:
            struct = struct.get_primitive_structure().get_reduced_structure()
        return struct.get_sorted_structure()
Пример #24
0
    def _get_structure(self, data, primitive, substitution_dictionary=None):
        """
        Generate structure from part of the cif.
        """
        # Symbols often representing
        # common representations for elements/water in cif files
        special_symbols = {"D": "D", "Hw": "H", "Ow": "O", "Wat": "O",
                           "wat": "O"}
        elements = [el.symbol for el in Element]

        lattice = self.get_lattice(data)
        self.symmetry_operations = self.get_symops(data)
        oxi_states = self.parse_oxi_states(data)

        coord_to_species = OrderedDict()

        def parse_symbol(sym):

            if substitution_dictionary:
                return substitution_dictionary.get(sym)
            elif sym in ['OH', 'OH2']:
                warnings.warn("Symbol '{}' not recognized".format(sym))
                return ""
            else:
                m = re.findall(r"w?[A-Z][a-z]*", sym)
                if m and m != "?":
                    return m[0]
                return ""

        def get_matching_coord(coord):
            for op in self.symmetry_operations:
                c = op.operate(coord)
                for k in coord_to_species.keys():
                    if np.allclose(pbc_diff(c, k), (0, 0, 0),
                                   atol=self._site_tolerance):
                        return tuple(k)
            return False

        ############################################################
        """
        This part of the code deals with handling formats of data as found in CIF files extracted from the
        Springer Materials/Pauling File databases, and that are different from standard ICSD formats.
        """

        # Check to see if "_atom_site_type_symbol" exists, as some test CIFs do not contain this key.
        if "_atom_site_type_symbol" in data.data.keys():

            # Keep a track of which data row needs to be removed.
            # Example of a row: Nb,Zr '0.8Nb + 0.2Zr' .2a .m-3m 0 0 0 1 14 'rhombic dodecahedron, Nb<sub>14</sub>'
            # Without this code, the above row in a structure would be parsed as an ordered site with only Nb (since
            # CifParser would try to parse the first two characters of the label "Nb,Zr") and occupancy=1.
            # However, this site is meant to be a disordered site with 0.8 of Nb and 0.2 of Zr.
            idxs_to_remove = []

            for idx, el_row in enumerate(data["_atom_site_label"]):

                # CIF files from the Springer Materials/Pauling File have switched the label and symbol. Thus, in the
                # above shown example row, '0.8Nb + 0.2Zr' is the symbol. Below, we split the strings on ' + ' to
                # check if the length (or number of elements) in the label and symbol are equal.
                if len(data["_atom_site_type_symbol"][idx].split(' + ')) > \
                        len(data["_atom_site_label"][idx].split(' + ')):

                    # Dictionary to hold extracted elements and occupancies
                    els_occu = {}

                    # parse symbol to get element names and occupancy and store in "els_occu"
                    symbol_str = data["_atom_site_type_symbol"][idx]
                    symbol_str_lst = symbol_str.split(' + ')
                    for elocc_idx in range(len(symbol_str_lst)):
                        # Remove any bracketed items in the string
                        symbol_str_lst[elocc_idx] = re.sub('\([0-9]*\)', '', symbol_str_lst[elocc_idx].strip())

                        # Extract element name and its occupancy from the string, and store it as a
                        # key-value pair in "els_occ".
                        els_occu[str(re.findall('\D+', symbol_str_lst[elocc_idx].strip())[1]).replace('<sup>', '')] = \
                            float('0' + re.findall('\.?\d+', symbol_str_lst[elocc_idx].strip())[1])

                    x = str2float(data["_atom_site_fract_x"][idx])
                    y = str2float(data["_atom_site_fract_y"][idx])
                    z = str2float(data["_atom_site_fract_z"][idx])

                    coord = (x, y, z)
                    # Add each partially occupied element on the site coordinate
                    for et in els_occu:
                        match = get_matching_coord(coord)
                        if not match:
                            coord_to_species[coord] = Composition({parse_symbol(et): els_occu[parse_symbol(et)]})
                        else:
                            coord_to_species[match] += {parse_symbol(et): els_occu[parse_symbol(et)]}
                    idxs_to_remove.append(idx)

            # Remove the original row by iterating over all keys in the CIF data looking for lists, which indicates
            # multiple data items, one for each row, and remove items from the list that corresponds to the removed row,
            # so that it's not processed by the rest of this function (which would result in an error).
            for cif_key in data.data:
                if type(data.data[cif_key]) == list:
                    for id in sorted(idxs_to_remove, reverse=True):
                        del data.data[cif_key][id]

        ############################################################

        for i in range(len(data["_atom_site_label"])):
            symbol = parse_symbol(data["_atom_site_label"][i])

            if symbol:
                if symbol not in elements and symbol not in special_symbols:
                    symbol = symbol[:2]
            else:
                continue
            # make sure symbol was properly parsed from _atom_site_label
            # otherwise get it from _atom_site_type_symbol
            try:
                if symbol in special_symbols:
                    get_el_sp(special_symbols.get(symbol))
                else:
                    Element(symbol)
            except (KeyError, ValueError):
                # sometimes the site doesn't have the type_symbol.
                # we then hope the type_symbol can be parsed from the label
                if "_atom_site_type_symbol" in data.data.keys():
                    symbol = data["_atom_site_type_symbol"][i]

            if oxi_states is not None:
                if symbol in special_symbols:
                    el = get_el_sp(special_symbols.get(symbol) +
                                   str(oxi_states[symbol]))
                else:
                    el = Specie(symbol, oxi_states.get(symbol, 0))
            else:

                el = get_el_sp(special_symbols.get(symbol, symbol))

            x = str2float(data["_atom_site_fract_x"][i])
            y = str2float(data["_atom_site_fract_y"][i])
            z = str2float(data["_atom_site_fract_z"][i])
            try:
                occu = str2float(data["_atom_site_occupancy"][i])
            except (KeyError, ValueError):
                occu = 1

            if occu > 0:
                coord = (x, y, z)
                match = get_matching_coord(coord)
                if not match:
                    coord_to_species[coord] = Composition({el: occu})
                else:
                    coord_to_species[match] += {el: occu}

        if any([sum(c.values()) > 1 for c in coord_to_species.values()]):
            warnings.warn("Some occupancies sum to > 1! If they are within "
                          "the tolerance, they will be rescaled.")

        allspecies = []
        allcoords = []

        if coord_to_species.items():
            for species, group in groupby(
                    sorted(list(coord_to_species.items()), key=lambda x: x[1]),
                    key=lambda x: x[1]):

                tmp_coords = [site[0] for site in group]

                coords = self._unique_coords(tmp_coords)

                allcoords.extend(coords)
                allspecies.extend(len(coords) * [species])

            # rescale occupancies if necessary
            for i, species in enumerate(allspecies):
                totaloccu = sum(species.values())
                if 1 < totaloccu <= self._occupancy_tolerance:
                    allspecies[i] = species / totaloccu

        if allspecies and len(allspecies) == len(allcoords):
            struct = Structure(lattice, allspecies, allcoords)
            struct = struct.get_sorted_structure()

            if primitive:
                struct = struct.get_primitive_structure()
                struct = struct.get_reduced_structure()
            return struct
Пример #25
0
    def _get_structure(self, data, primitive):
        """
        Generate structure from part of the cif.
        """
        lengths = [
            str2float(data["_cell_length_" + i]) for i in ["a", "b", "c"]
        ]
        angles = [
            str2float(data["_cell_angle_" + i])
            for i in ["alpha", "beta", "gamma"]
        ]
        lattice = Lattice.from_lengths_and_angles(lengths, angles)
        try:
            sympos = data["_symmetry_equiv_pos_as_xyz"]
        except KeyError:
            try:
                sympos = data["_symmetry_equiv_pos_as_xyz_"]
            except KeyError:
                warnings.warn("No _symmetry_equiv_pos_as_xyz type key found. "
                              "Defaulting to P1.")
                sympos = ['x, y, z']
        self.symmetry_operations = parse_symmetry_operations(sympos)

        def parse_symbol(sym):
            m = re.search("([A-Z][a-z]*)", sym)
            if m:
                return m.group(1)
            return ""

        try:
            oxi_states = {
                data["_atom_type_symbol"][i]:
                str2float(data["_atom_type_oxidation_number"][i])
                for i in xrange(len(data["_atom_type_symbol"]))
            }
        except (ValueError, KeyError):
            oxi_states = None

        coord_to_species = OrderedDict()

        for i in xrange(len(data["_atom_site_type_symbol"])):
            symbol = parse_symbol(data["_atom_site_type_symbol"][i])
            if oxi_states is not None:
                el = Specie(symbol,
                            oxi_states[data["_atom_site_type_symbol"][i]])
            else:
                el = Element(symbol)
            x = str2float(data["_atom_site_fract_x"][i])
            y = str2float(data["_atom_site_fract_y"][i])
            z = str2float(data["_atom_site_fract_z"][i])
            try:
                occu = str2float(data["_atom_site_occupancy"][i])
            except (KeyError, ValueError):
                occu = 1
            if occu > 0:
                coord = (x, y, z)
                if coord not in coord_to_species:
                    coord_to_species[coord] = {el: occu}
                else:
                    coord_to_species[coord][el] = occu

        allspecies = []
        allcoords = []

        for coord, species in coord_to_species.items():
            coords = self._unique_coords(coord)
            allcoords.extend(coords)
            allspecies.extend(len(coords) * [species])

        #rescale occupancies if necessary
        for species in allspecies:
            totaloccu = sum(species.values())
            if 1 < totaloccu <= self._occupancy_tolerance:
                for key, value in species.iteritems():
                    species[key] = value / totaloccu

        struct = Structure(lattice, allspecies, allcoords)
        if primitive:
            struct = struct.get_primitive_structure().get_reduced_structure()
        return struct.get_sorted_structure()
Пример #26
0
    def get_conventional_standard_structure(
            self, international_monoclinic=True):
        """
        Gives a structure with a conventional cell according to certain
        standards. The standards are defined in Setyawan, W., & Curtarolo,
        S. (2010). High-throughput electronic band structure calculations:
        Challenges and tools. Computational Materials Science,
        49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010
        They basically enforce as much as possible
        norm(a1)<norm(a2)<norm(a3)

        Returns:
            The structure in a conventional standardized cell
        """
        tol = 1e-5
        struct = self.get_refined_structure()
        latt = struct.lattice
        latt_type = self.get_lattice_type()
        sorted_lengths = sorted(latt.abc)
        sorted_dic = sorted([{'vec': latt.matrix[i],
                              'length': latt.abc[i],
                              'orig_index': i} for i in [0, 1, 2]],
                            key=lambda k: k['length'])

        if latt_type in ("orthorhombic", "cubic"):
            #you want to keep the c axis where it is
            #to keep the C- settings
            transf = np.zeros(shape=(3, 3))
            if self.get_spacegroup_symbol().startswith("C"):
                transf[2] = [0, 0, 1]
                a, b = sorted(latt.abc[:2])
                sorted_dic = sorted([{'vec': latt.matrix[i],
                                      'length': latt.abc[i],
                                      'orig_index': i} for i in [0, 1]],
                                    key=lambda k: k['length'])
                for i in range(2):
                    transf[i][sorted_dic[i]['orig_index']] = 1
                c = latt.abc[2]
            else:
                for i in range(len(sorted_dic)):
                    transf[i][sorted_dic[i]['orig_index']] = 1
                a, b, c = sorted_lengths
            latt = Lattice.orthorhombic(a, b, c)

        elif latt_type == "tetragonal":
            #find the "a" vectors
            #it is basically the vector repeated two times
            transf = np.zeros(shape=(3, 3))
            a, b, c = sorted_lengths
            for d in range(len(sorted_dic)):
                transf[d][sorted_dic[d]['orig_index']] = 1

            if abs(b - c) < tol:
                a, c = c, a
                transf = np.dot([[0, 0, 1], [0, 1, 0], [1, 0, 0]], transf)
            latt = Lattice.tetragonal(a, c)
        elif latt_type in ("hexagonal", "rhombohedral"):
            #for the conventional cell representation,
            #we allways show the rhombohedral lattices as hexagonal

            #check first if we have the refined structure shows a rhombohedral
            #cell
            #if so, make a supercell
            a, b, c = latt.abc
            if np.all(np.abs([a - b, c - b, a - c]) < 0.001):
                struct.make_supercell(((1, -1, 0), (0, 1, -1), (1, 1, 1)))
                a, b, c = sorted(struct.lattice.abc)

            if abs(b - c) < 0.001:
                a, c = c, a
            new_matrix = [[a / 2, -a * math.sqrt(3) / 2, 0],
                          [a / 2, a * math.sqrt(3) / 2, 0],
                          [0, 0, c]]
            latt = Lattice(new_matrix)
            transf = np.eye(3, 3)

        elif latt_type == "monoclinic":
            #you want to keep the c axis where it is
            #to keep the C- settings

            if self.get_spacegroup().int_symbol.startswith("C"):
                transf = np.zeros(shape=(3, 3))
                transf[2] = [0, 0, 1]
                sorted_dic = sorted([{'vec': latt.matrix[i],
                                      'length': latt.abc[i],
                                      'orig_index': i} for i in [0, 1]],
                                    key=lambda k: k['length'])
                a = sorted_dic[0]['length']
                b = sorted_dic[1]['length']
                c = latt.abc[2]
                new_matrix = None
                for t in itertools.permutations(list(range(2)), 2):
                    m = latt.matrix
                    landang = Lattice(
                        [m[t[0]], m[t[1]], m[2]]).lengths_and_angles
                    if landang[1][0] > 90:
                        #if the angle is > 90 we invert a and b to get
                        #an angle < 90
                        landang = Lattice(
                            [-m[t[0]], -m[t[1]], m[2]]).lengths_and_angles
                        transf = np.zeros(shape=(3, 3))
                        transf[0][t[0]] = -1
                        transf[1][t[1]] = -1
                        transf[2][2] = 1
                        a, b, c = landang[0]
                        alpha = math.pi * landang[1][0] / 180
                        new_matrix = [[a, 0, 0],
                                      [0, b, 0],
                                      [0, c * cos(alpha), c * sin(alpha)]]
                        continue

                    elif landang[1][0] < 90:
                        transf = np.zeros(shape=(3, 3))
                        transf[0][t[0]] = 1
                        transf[1][t[1]] = 1
                        transf[2][2] = 1
                        a, b, c = landang[0]
                        alpha = math.pi * landang[1][0] / 180
                        new_matrix = [[a, 0, 0],
                                      [0, b, 0],
                                      [0, c * cos(alpha), c * sin(alpha)]]

                if new_matrix is None:
                    #this if is to treat the case
                    #where alpha==90 (but we still have a monoclinic sg
                    new_matrix = [[a, 0, 0],
                                  [0, b, 0],
                                  [0, 0, c]]
                    transf = np.zeros(shape=(3, 3))
                    for c in range(len(sorted_dic)):
                        transf[c][sorted_dic[c]['orig_index']] = 1
            #if not C-setting
            else:
                #try all permutations of the axis
                #keep the ones with the non-90 angle=alpha
                #and b<c
                new_matrix = None
                for t in itertools.permutations(list(range(3)), 3):
                    m = latt.matrix
                    landang = Lattice(
                        [m[t[0]], m[t[1]], m[t[2]]]).lengths_and_angles
                    if landang[1][0] > 90 and landang[0][1] < landang[0][2]:
                        landang = Lattice(
                            [-m[t[0]], -m[t[1]], m[t[2]]]).lengths_and_angles
                        transf = np.zeros(shape=(3, 3))
                        transf[0][t[0]] = -1
                        transf[1][t[1]] = -1
                        transf[2][t[2]] = 1
                        a, b, c = landang[0]
                        alpha = math.pi * landang[1][0] / 180
                        new_matrix = [[a, 0, 0],
                                      [0, b, 0],
                                      [0, c * cos(alpha), c * sin(alpha)]]
                        continue
                    elif landang[1][0] < 90 and landang[0][1] < landang[0][2]:
                        transf = np.zeros(shape=(3, 3))
                        transf[0][t[0]] = 1
                        transf[1][t[1]] = 1
                        transf[2][t[2]] = 1
                        a, b, c = landang[0]
                        alpha = math.pi * landang[1][0] / 180
                        new_matrix = [[a, 0, 0],
                                      [0, b, 0],
                                      [0, c * cos(alpha), c * sin(alpha)]]
                if new_matrix is None:
                    #this if is to treat the case
                    #where alpha==90 (but we still have a monoclinic sg
                    new_matrix = [[sorted_lengths[0], 0, 0],
                                  [0, sorted_lengths[1], 0],
                                  [0, 0, sorted_lengths[2]]]
                    transf = np.zeros(shape=(3, 3))
                    for c in range(len(sorted_dic)):
                        transf[c][sorted_dic[c]['orig_index']] = 1

            if international_monoclinic:
                # The above code makes alpha the non-right angle.
                # The following will convert to proper international convention
                # that beta is the non-right angle.
                op = [[0, 1, 0], [1, 0, 0], [0, 0, -1]]
                transf = np.dot(op, transf)
                new_matrix = np.dot(op, new_matrix)
                beta = Lattice(new_matrix).beta
                if beta < 90:
                    op = [[-1, 0, 0], [0, -1, 0], [0, 0, 1]]
                    transf = np.dot(op, transf)
                    new_matrix = np.dot(op, new_matrix)

            latt = Lattice(new_matrix)

        elif latt_type == "triclinic":
            #we use a LLL Minkowski-like reduction for the triclinic cells
            struct = struct.get_reduced_structure("LLL")

            a, b, c = latt.lengths_and_angles[0]
            alpha, beta, gamma = [math.pi * i / 180
                                  for i in latt.lengths_and_angles[1]]
            new_matrix = None
            test_matrix = [[a, 0, 0],
                          [b * cos(gamma), b * sin(gamma), 0.0],
                          [c * cos(beta),
                           c * (cos(alpha) - cos(beta) * cos(gamma)) /
                           sin(gamma),
                           c * math.sqrt(sin(gamma) ** 2 - cos(alpha) ** 2
                                         - cos(beta) ** 2
                                         + 2 * cos(alpha) * cos(beta)
                                         * cos(gamma)) / sin(gamma)]]

            def is_all_acute_or_obtuse(m):
                recp_angles = np.array(Lattice(m).reciprocal_lattice.angles)
                return np.all(recp_angles <= 90) or np.all(recp_angles > 90)

            if is_all_acute_or_obtuse(test_matrix):
                transf = np.eye(3)
                new_matrix = test_matrix

            test_matrix = [[-a, 0, 0],
                           [b * cos(gamma), b * sin(gamma), 0.0],
                           [-c * cos(beta),
                            -c * (cos(alpha) - cos(beta) * cos(gamma)) /
                            sin(gamma),
                            -c * math.sqrt(sin(gamma) ** 2 - cos(alpha) ** 2
                                           - cos(beta) ** 2
                                           + 2 * cos(alpha) * cos(beta)
                                           * cos(gamma)) / sin(gamma)]]

            if is_all_acute_or_obtuse(test_matrix):
                transf = [[-1, 0, 0],
                          [0, 1, 0],
                          [0, 0, -1]]
                new_matrix = test_matrix

            test_matrix = [[-a, 0, 0],
                           [-b * cos(gamma), -b * sin(gamma), 0.0],
                           [c * cos(beta),
                            c * (cos(alpha) - cos(beta) * cos(gamma)) /
                            sin(gamma),
                            c * math.sqrt(sin(gamma) ** 2 - cos(alpha) ** 2
                                          - cos(beta) ** 2
                                          + 2 * cos(alpha) * cos(beta)
                                          * cos(gamma)) / sin(gamma)]]

            if is_all_acute_or_obtuse(test_matrix):
                transf = [[-1, 0, 0],
                          [0, -1, 0],
                          [0, 0, 1]]
                new_matrix = test_matrix

            test_matrix = [[a, 0, 0],
                           [-b * cos(gamma), -b * sin(gamma), 0.0],
                           [-c * cos(beta),
                            -c * (cos(alpha) - cos(beta) * cos(gamma)) /
                            sin(gamma),
                            -c * math.sqrt(sin(gamma) ** 2 - cos(alpha) ** 2
                                           - cos(beta) ** 2
                                           + 2 * cos(alpha) * cos(beta)
                                           * cos(gamma)) / sin(gamma)]]
            if is_all_acute_or_obtuse(test_matrix):
                transf = [[1, 0, 0],
                          [0, -1, 0],
                          [0, 0, -1]]
                new_matrix = test_matrix

            latt = Lattice(new_matrix)

        new_coords = np.dot(transf, np.transpose(struct.frac_coords)).T
        new_struct = Structure(latt, struct.species_and_occu, new_coords,
                               site_properties=struct.site_properties,
                               to_unit_cell=True)
        return new_struct.get_sorted_structure()
    def from_options(cls, task: Task, original_structure: Structure,
                     standardize_structure: bool, sort_structure: bool,
                     is_magnetization: bool, kpt_mode: str, kpt_density: float,
                     kpt_shift: list, only_even: bool, band_ref_dist: float,
                     factor: Optional[int], symprec: float,
                     angle_tolerance: float):
        """ Construct Structure and Kpoints from task and some options.

        Note: When task and kpt_mode are not consistent e.g., task=Task.band,
              kpt_mode="manual", task is prioritized.

        Args: See ViseInputSet docstrings

        Return: TaskStructureKpoints class object
        """
        if sort_structure:
            structure = original_structure.get_sorted_structure()
            symbol_list = get_symbol_list(structure)
            orig_symbol_list = get_symbol_list(original_structure)
            if symbol_list != orig_symbol_list:
                logger.warning(
                    "CAUTION: The sequence of the species is changed."
                    f"Symbol set in the original structure "
                    f"{symbol_list} "
                    f"Symbol set in the generated structure "
                    f"{orig_symbol_list}")
        else:
            structure = original_structure.copy()

        is_structure_changed = False
        if task == Task.defect:
            kpt_mode = "manual_set"
        elif task == Task.cluster_opt:
            kpt_mode = "manual_set"
            kpt_density = 1e-5
            only_even = False
            kpt_shift = [0, 0, 0]
        elif task == Task.band:
            kpt_mode = "band"
        elif task == Task.phonon_force:
            kpt_mode = "manual_set"
            if kpt_shift != [0, 0, 0]:
                logger.warning(
                    "For phonon force calculations, Gamma centering "
                    "is forced for k-point sampling.")
            kpt_shift = [0, 0, 0]
        else:
            primitive_structure, is_structure_changed = \
                find_spglib_primitive(structure, symprec, angle_tolerance)

            if standardize_structure:
                org = original_structure.lattice.matrix
                primitive = primitive_structure.lattice.matrix
                if is_structure_changed:
                    with np.printoptions(precision=3, suppress=True):
                        logger.warning("CAUTION: The structure is changed.\n"
                                       f"Original lattice\n {org} \n"
                                       f"Generated lattice\n {primitive} \n")
                structure = primitive_structure
            else:
                if is_structure_changed and kpt_mode != "manual_set":
                    logger.warning(
                        "Standardizaion is set to False and the given "
                        "structure is not a primitive cell. Thus, the "
                        "kpoint set is switched to manual_set.")
                    kpt_mode = "manual_set"
                else:
                    logger.info("The structure is a standardized primitive.")

        # Gamma-center mesh is a must for GW calculations due to vasp
        # implementation and tetrahedron method, while is a strong recommend for
        # dos and dielectric function to sample the band edges.
        if task in PLOT_TASK and kpt_shift != [0, 0, 0]:
            logger.warning("Gamma centering is forced for k-point sampling.")
            kpt_shift = [0, 0, 0]

        if factor is None:
            if task == Task.dielectric_function:
                factor = 3
            elif task in (Task.dos, Task.dielectric_dfpt,
                          Task.dielectric_finite_field):
                factor = 2
            else:
                factor = 1

        kpoints = MakeKpoints(mode=kpt_mode,
                              structure=structure,
                              kpt_density=kpt_density,
                              only_even=only_even,
                              ref_distance=band_ref_dist,
                              kpt_shift=kpt_shift,
                              factor=factor,
                              symprec=symprec,
                              angle_tolerance=angle_tolerance,
                              is_magnetization=is_magnetization)
        kpoints.make_kpoints()

        return cls(structure=structure,
                   kpoints=kpoints.kpoints,
                   is_structure_changed=kpoints.is_structure_changed,
                   sg=kpoints.sg,
                   num_kpts=kpoints.num_kpts,
                   factor=factor)