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)
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()
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})
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)
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
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))
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'))
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()
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)
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()
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()
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
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 _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
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
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
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
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()
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
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
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()
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
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()
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)