def test_standardize_cell_from_primitive(self): for fname in self._filenames: spgnum = int(fname.split('-')[1]) cell = read_vasp("./data/%s" % fname) if 'distorted' in fname: prim_cell = standardize_cell(cell, to_primitive=True, no_idealize=True, symprec=1e-1) std_cell = standardize_cell(prim_cell, to_primitive=False, no_idealize=True, symprec=1e-1) dataset = get_symmetry_dataset(std_cell, symprec=1e-1) else: prim_cell = standardize_cell(cell, to_primitive=True, no_idealize=True, symprec=1e-5) std_cell = standardize_cell(prim_cell, to_primitive=False, no_idealize=True, symprec=1e-5) dataset = get_symmetry_dataset(std_cell, symprec=1e-5) self.assertEqual(dataset['number'], spgnum, msg=("%s" % fname))
def test_standardize_cell_from_primitive(self): for fname, spgnum in zip(self._filenames, self._spgnum_ref): cell = read_vasp(fname) if 'distorted' in fname: prim_cell = standardize_cell(cell, to_primitive=True, no_idealize=True, symprec=1e-1) std_cell = standardize_cell(prim_cell, to_primitive=False, no_idealize=True, symprec=1e-1) dataset = get_symmetry_dataset(std_cell, symprec=1e-1) else: prim_cell = standardize_cell(cell, to_primitive=True, no_idealize=True, symprec=1e-5) std_cell = standardize_cell(prim_cell, to_primitive=False, no_idealize=True, symprec=1e-5) dataset = get_symmetry_dataset(std_cell, symprec=1e-5) self.assertEqual(dataset['number'], spgnum, msg=("%s" % fname))
def test_spglib_standardize(): for i in [ "../tests/MoS2_2H_1l.xyz", "../tests/WS2_2H_1l.xyz", "../tests/graphene.xyz", ]: atoms = ase.io.read(PROJECT_ROOT_DIR.joinpath(i)) N = [[3, 1, 0], [-1, 2, 0], [0, 0, 1]] sc = make_supercell(atoms, N) cppatoms = ase_atoms_to_cpp_atoms(sc) cppatoms.standardize(1, 0, 1e-5, 5) cell = (sc.cell, sc.get_scaled_positions(), sc.numbers) spgcell = spglib.standardize_cell(cell, to_primitive=True, no_idealize=False, symprec=1e-5, angle_tolerance=5) atoms = Atoms( cell=spgcell[0], scaled_positions=spgcell[1], numbers=spgcell[2], pbc=[True, True, True], ) cppatoms = cpp_atoms_to_ase_atoms(cppatoms) comp = SymmetryEquivalenceCheck() is_equal = comp.compare(atoms, cppatoms) assert ( is_equal ), "Standardization in backend and from spglib do not yield same result."
def run_task(self, fw_spec): wd = os.getcwd() struct = Structure.from_file(wd + "/POSCAR") numbers = [site.specie.number for site in struct] lattice = struct.lattice.matrix positions = struct.frac_coords if "magmom" in struct.site_properties: magmoms = struct.site_properties["magmom"] cell = (lattice, positions, numbers, magmoms) else: magmoms = None cell = (lattice, positions, numbers) lat, pos, nums = standardize_cell(cell, to_primitive=False, symprec=1e-2) structure = Structure(lat, nums, pos) if magmoms is not None: structure.add_site_property("magmom", magmoms) structure.to(fmt="poscar", filename="CONTCAR") return FWAction(update_spec={"structure": structure})
def get_primitive_cell(self, standardize=False, use_elements=None, use_magmoms=None): """ Get primitive cell of a given structure. Args: standardize (bool): Get orthogonal box use_magmoms (bool): Whether to consider magnetic moments (cf. get_initial_magnetic_moments()) use_elements (bool): If False, chemical elements will be ignored Returns: (pyiron_atomistics.atomistics.structure.atoms.Atoms): Primitive cell Example (assume `basis` is a primitive cell): >>> structure = basis.repeat(2) >>> symmetry = Symmetry(structure) >>> len(symmetry.get_primitive_cell()) == len(basis) True """ cell, positions, indices = spglib.standardize_cell( self._get_spglib_cell(use_elements=use_elements, use_magmoms=use_magmoms), to_primitive=not standardize, ) positions = (cell.T @ positions.T).T new_structure = self._structure.copy() new_structure.cell = cell new_structure.indices[:len(indices)] = indices new_structure = new_structure[:len(indices)] new_structure.positions = positions return new_structure
def main(fxyz, prefix, verbose, precision): # read frames if fxyz != 'none': frames = read(fxyz, ':') nframes = len(frames) print("read xyz file:", fxyz, ", a total of", nframes, "frames") standardized_frames = [] for frame in frames: space_now = spglib.get_spacegroup( frame, symprec=precision) # spglib.get_symmetry(frame, symprec=1e-1)) print(space_now) lattice, scaled_positions, numbers = spglib.standardize_cell( frame, to_primitive=1, no_idealize=1, symprec=precision) if verbose: show_cell(lattice, scaled_positions, numbers) # output frtemp = atom(numbers=numbers, cell=lattice, scaled_positions=scaled_positions, pbc=frame.get_pbc()) frtemp.info['space_group'] = space_now standardized_frames.append(frtemp) write(prefix + '-standardized.xyz', standardized_frames)
def conventional(self, symprec=1e-5): """ conventional structure Arugments: symprec (symmetry tolerance): distance tolerance in Cartesian coordinates to find crystal symmetry """ cell = self.structure.formatting('cell') cell = (cell['lattice'], cell['positions'], cell['numbers']) # method 1 lattice, scaled_positions, numbers = spglib.standardize_cell( cell, symprec=symprec) newcell = (lattice, scaled_positions, numbers) # method 2 #newcell=spglib.standardize_cell(cell, symprec=symprec) if newcell == None: raise StructureFactoryError('The search is failed') else: poscar = self.__toPOSCAR(newcell) self.structure.pop() # delete old object self.structure = Structure().append(poscar) return self.structure
def standardize_cell(structure): import spglib from phonopy.structure.atoms import Atoms as PhonopyAtoms from phonopy.structure.atoms import atom_data bulk = PhonopyAtoms(symbols=[site.kind_name for site in structure.sites], positions=[site.position for site in structure.sites], cell=structure.cell) structure_data = (structure.cell, bulk.get_scaled_positions(), bulk.get_atomic_numbers()) #lattice, refined_positions, numbers = spglib.refine_cell(structure_data, symprec=1e-5) lattice, standardized_positions, numbers = spglib.standardize_cell(structure_data, symprec=1e-5, to_primitive=False, no_idealize=False) symbols = [atom_data[i][1] for i in numbers] # print lattice, standardized_positions, numbers # print [site.kind_name for site in structure.sites] standardized_bulk = PhonopyAtoms(symbols=symbols, scaled_positions=standardized_positions, cell=lattice) # create new aiida structure object standarized = StructureData(cell=standardized_bulk.get_cell()) for position, symbol in zip(standardized_bulk.get_positions(), standardized_bulk.get_chemical_symbols()): standarized.append_atom(position=position, symbols=symbol) return {'standardized_structure': standarized}
def cell_to_primitive(cell_or_dataset, symprec=1e-3, angle_tolerance=-1.0, hall_number=0, message=True): '''Convert a cell to a primitive cell''' if isinstance(cell_or_dataset, dict): dataset = cell_or_dataset else: cell = tuple(cell_or_dataset) dataset = spglib.get_symmetry_dataset(cell, symprec=symprec, angle_tolerance=angle_tolerance, hall_number=hall_number) # Construct a primitive cell object std_lattice = dataset['std_lattice'] std_positions = dataset['std_positions'] std_types = dataset['std_types'] std_cell = (std_lattice, std_positions, std_types) lattice, scaled_positions, numbers = spglib.standardize_cell( std_cell, to_primitive=True, no_idealize=True, symprec=symprec) primitive_cell = (lattice, scaled_positions, numbers) if message: if compare_cells(cell, primitive_cell): print( 'The unit cell was a primitive cell. However, the primitive cell computed by spglib is returned, it is maybe not the same as the provided unit cell' ) else: print('The unit cell was transformed to a primitive cell') return primitive_cell
def primitive(self, symprec=1e-2): """ Returns a Crystal object in the primitive unit cell. Parameters ---------- symprec : float, optional Symmetry-search distance tolerance in Cartesian coordinates [Angstroms]. Returns ------- primitive : Crystal Crystal with primitive cell. Even if the crystal already has a primitive cell, a new crystal is returned. Raises ------ RuntimeError : If primitive cell could not be found. Notes ----- Optional atomic properties (e.g magnetic moment) might be lost in the reduction. """ search = standardize_cell(self._spglib_cell(), to_primitive=True, no_idealize=True, symprec=symprec) if search is None: raise RuntimeError("Primitive cell could not be found.") return self._from_spglib_cell(*search, source=self.source)
def make_conventional_cell(file_name_read): if file_name_read.split('.')[-1] == 'vasp': input_file_format = 'vasp' elif file_name_read.split('.')[-1] == 'cif': input_file_format = 'cif' else: print('Please provide correct file name: .vasp or .cif') exit(1) file_read = open(file_name_read, 'r') if input_file_format == 'vasp': pos = vasp.read_vasp(file_read) elif input_file_format == 'cif': pos = io.read(file_read, format='cif') if indict['dimensional'][0] == '3D': if indict['if_conventional_cell'][0] == 'yes': to_primitive = False elif indict['if_conventional_cell'][0] == 'no': to_primitive = True pos_std = standardize_cell(pos, to_primitive=to_primitive) pos_conv = Atoms(pos_std[2], scaled_positions=pos_std[1], cell=pos_std[0]) elif indict['dimensional'][0] == '2D': pos_conv = pos return pos_conv
def standardize_cell(structure, to_primitive=True, no_idealize=False, symprec=1e-3, verbosity=0): """ Standardizes the input crystal structure using `spglib`. Args: structure: :class:`simulation.Structure` object with a crystal structure. to_primitive: Boolean specifying whether to convert the input structure to a primitive unit cell. no_idealize: Boolean specifying whether to "idealize" lattice vectors, angles according to the ITC. symprec: Float with the Cartesian distance tolerance. verbosity: Integer with the level of standard output verbosity. Returns: :class:`simulation.Structure` object with the standardized unit cell if successful, the input structure as is, otherwise. """ _check_spglib_install() rev_lookup = dict( zip(structure.site_comp_indices, structure.site_compositions)) cell = spglib.standardize_cell(_structure_to_cell(structure), to_primitive=to_primitive, no_idealize=no_idealize, symprec=symprec) if not _check_spglib_success(cell, verbosity=verbosity): return structure _cell_to_structure(cell, structure, rev_lookup) return structure
def ideal(self, symprec=1e-2): """ Returns a Crystal object with an idealized unit cell. Parameters ---------- symprec : float, optional Symmetry-search distance tolerance in Cartesian coordinates [Angstroms]. Returns ------- ideal : Crystal Crystal with idealized cell. Raises ------ RuntimeError : If an ideal cell could not be found. Notes ----- Optional atomic properties (e.g magnetic moment) might be lost in the symmetrization. """ search = standardize_cell(self._spglib_cell(), to_primitive=True, no_idealize=False, symprec=symprec) if search is None: raise RuntimeError("Ideal cell could not be found.") return self._from_spglib_cell(*search, source=self.source)
def standardize_crystal(crystal, method="spglib", **kwargs): if method != "spglib": raise NotImplementedError("Only spglib is currently supported") lattice = crystal.unit_cell.direct uc_dict = crystal.unit_cell_atoms() positions = uc_dict["frac_pos"] elements = uc_dict["element"] asym_atoms = uc_dict["asym_atom"] asym_labels = uc_dict["label"] cell = lattice, positions, elements reduced_cell = standardize_cell(cell, **kwargs) if reduced_cell is None: LOG.warn("Could not find reduced cell for crystal %s", crystal) return None dataset = get_symmetry_dataset(reduced_cell) asym_idx = np.unique(dataset["equivalent_atoms"]) asym_idx = asym_idx[np.argsort(asym_atoms[asym_idx])] sg = SpaceGroup(dataset["number"], choice=dataset["choice"]) reduced_lattice, positions, elements = reduced_cell unit_cell = UnitCell(reduced_lattice) asym = AsymmetricUnit( [Element[x] for x in elements[asym_idx]], positions[asym_idx], labels=asym_labels[asym_idx], ) return Crystal(unit_cell, sg, asym)
def calc_check(cell, spgnum, tol=1.0e-5): print('\n[primitive cell (calc)]') print_cell(cell) dataset = spglib.get_symmetry_dataset(cell, tol) spgnum_pcell = dataset['number'] hallnum_pcell = dataset['hall_number'] print_dataset(dataset) print('\n[conventional cell (calc pcell+standardize)]') scell = spglib.standardize_cell(cell) print_cell(scell) dataset = spglib.get_symmetry_dataset(scell, tol) spgnum_ccell = dataset['number'] hallnum_ccell = dataset['hall_number'] print_dataset(dataset) print('\n# check crystal structure...') print('%-25s %10s %10s' % ('', 'spg num', 'hall num')) print('%-25s %10d %10s' % ('cell orginal cif', spgnum, '-')) print('%-25s %10d %10d' % ('pcell calc', spgnum_pcell, hallnum_pcell)) print('%-25s %10d %10d' % ('ccell calc pcell+std', spgnum_ccell, hallnum_ccell)) b = True if (spgnum != spgnum_pcell): b = False print('warning: primitive cell... NG') if (spgnum != spgnum_ccell): b = False print('warning: conventional cell... NG') if (b == True): print('calc... OK') return b, spgnum_pcell
def __standardize(atoms): atoms = atoms.copy() cell = (atoms.get_cell()).tolist() pos = atoms.get_scaled_positions().tolist() numbers = atoms.get_atomic_numbers() cell, scaled_pos, numbers = spglib.standardize_cell( (cell, pos, numbers), to_primitive=True, symprec=1e-4, no_idealize=False, ) atoms = ase.atoms.Atoms(scaled_positions=scaled_pos, numbers=numbers, cell=cell, pbc=True) axes = [0, 1, 2] lengths = atoms.cell.lengths() order = [ x for x, y in sorted(zip(axes, lengths), key=lambda pair: pair[1]) ] if order != [0, 1, 2]: atoms = ase.geometry.permute_axes(atoms, order) self.current_stack = atoms self.__plot_stack(atoms, fig, ax[2], self.current_scdata)
def get_primitive_cell(self, symprec=1e-5): spg_cell = (self.lattice, self.positions, self.atoms) # 用下面这个原胞会改变坐标轴,no_idealize=True保持了坐标的方向。 # lattice, positions, atoms = spglib.find_primitive(spg_cell, symprec) lattice, positions, atoms = spglib.standardize_cell(spg_cell, to_primitive=True, no_idealize=True, symprec=symprec) return self.__class__(lattice, positions, atoms)
def test_standardize_cell_from_primitive(self): for fname, spgnum in zip(self._filenames, self._spgnum_ref): cell = read_vasp(fname) if 'distorted' in fname: symprec = 1e-1 else: symprec = 1e-5 prim_cell = standardize_cell(cell, to_primitive=True, no_idealize=True, symprec=symprec) std_cell = standardize_cell(prim_cell, to_primitive=False, no_idealize=True, symprec=symprec) dataset = get_symmetry_dataset(std_cell, symprec=symprec) self.assertEqual(dataset['number'], spgnum, msg=("%s" % fname))
def standardize_cell(structure, symprec, angle_tolerance, to_primitive=False, no_idealize=False): """compute the standardised cell for an AiiDA structure When computing symmetry, atomic sites with the same **Kind** are treated as symmetrically equivalent (rather than just the atomic elements). Parameters ---------- structure: aiida.StructureData to_primitive: bool If True, the standardized primitive cell is created. no_idealize: bool If True, it is disabled to idealize lengths and angles of basis vectors and positions of atoms according to crystal symmetry. symprec: float Symmetry search tolerance in the unit of length. angle_tolerance: float or None Symmetry search tolerance in the unit of angle degrees. If the value is negative or None, an internally optimized routine is used to judge symmetry. Returns ------- aiida.StructureData """ from aiida.orm.nodes.data.structure import Site structure = convert_structure(structure, "aiida") cell, int2kind_map = prepare_for_spglib(structure) new_cell = spglib.standardize_cell( cell, to_primitive=to_primitive, no_idealize=no_idealize, symprec=symprec, angle_tolerance=-1 if angle_tolerance is None else angle_tolerance, ) if new_cell is None: raise ValueError("standardization of cell failed") new_structure = structure.clone() new_structure.clear_sites() new_structure.cell = new_cell[0].tolist() positions = frac_to_cartesian(new_structure.cell, new_cell[1]) for position, eid in zip(positions, new_cell[2].tolist()): new_structure.append_site( Site(kind_name=int2kind_map[eid], position=position)) return new_structure
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 standardize(self, to_primitive=True, symprec=1e-4, angle_tolerance=5) -> None: """Wrapper of the spglib standardize() function with extra features. For 2D systems, the non-periodic axis is enforced as the z-axis. Args: to_primitive (bool): If True, primitive cell is obtained. If False, conventional cell is obtained. symprec (float): Precision to determine new cell. Note: The combination of to_primitive=True and a larger value of symprec (1e-2) can be used to refine a structure. """ atoms = self.copy() pbc1 = find_periodic_axes(atoms) lattice, positions, numbers = ( atoms.get_cell(), atoms.get_scaled_positions(), atoms.numbers, ) cell = (lattice, positions, numbers) newcell = spglib.standardize_cell( cell, to_primitive=to_primitive, no_idealize=False, symprec=symprec, angle_tolerance=angle_tolerance, ) if newcell == None: logger.error("Cell could not be standardized.") return None else: atoms = ase.Atoms( scaled_positions=newcell[1], numbers=newcell[2], cell=newcell[0], pbc=atoms.pbc, ) pbc2 = find_periodic_axes(atoms) logger.debug("new pbc: {} {} {}".format(*pbc2)) if pbc1 != pbc2: old = [k for k, v in pbc1.items() if v] new = [k for k, v in pbc2.items() if v] assert len(old) == len( new), "Periodicity changed due to standardization." if len(new) == 2: npbcax = list(set([0, 1, 2]) - set(new))[0] atoms = ase.geometry.permute_axes(atoms, new + [npbcax]) self.__init__(atoms)
def get_refined_pcell(self): """ Using spglib's standardize_cell method to refine the cell of giving. If self is a non-primitive cell, the number of atoms will reduced. else will return a refined primitive cell. """ rcell = (self.lattice, self.positions, self.numbers) lattice, positions, numbers = spglib.standardize_cell( rcell, to_primitive=True, no_idealize=False, symprec=self.symprec) return self.__class__(lattice, positions, numbers)
def __str__(self): # check for ECPs in basis family has_ecp = [] if self.basis and not self.basis.predefined: composition = set(self.atomic_numbers) has_ecp = [num for num in composition if not self.basis.get_basis(chemical_symbols[num]).all_electron] # convert geometry to primitive and find inequivalent atoms cell = self.abc, self.positions, self.atomic_numbers cell = spglib.standardize_cell(cell, to_primitive=True, no_idealize=False) abc, positions, atomic_numbers = cell # symmetries related stuff dataset = spglib.get_symmetry_dataset(cell) # leave only symmetrically inequivalent atoms inequiv_atoms = np.unique(dataset['equivalent_atoms']) positions = positions[inequiv_atoms] atomic_numbers = atomic_numbers[inequiv_atoms] # convert positions from fractional to cartesian positions = np.dot(abc.T, positions.T).T # convert symmetry operations from fractional to cartesian rotations = np.dot(abc.T, np.dot(dataset["rotations"], np.linalg.inv(abc.T))) rotations = np.swapaxes(rotations, 0, 1) translations = np.dot(dataset["translations"], abc) n_symops = len(dataset["translations"]) symops = np.zeros((n_symops * 4, 3), dtype=float) symops[0::4] = rotations[:, 0] symops[1::4] = rotations[:, 1] symops[2::4] = rotations[:, 2] symops[3::4] = translations # make a list of lines f34_lines = ["{0} {1} {2}".format(self.dimensionality, self.centring, self.crystal_type)] f34_lines += ["{0[0]:17.9E} {0[1]:17.9E} {0[2]:17.9E}".format( np.round(vec, 9) + 0.) for vec in abc] # symmetry operation part f34_lines.append(str(n_symops)) f34_lines += ["{0[0]:17.9E} {0[1]:17.9E} {0[2]:17.9E}".format( np.round(line, 9) + 0.) for line in symops] # atoms part f34_lines.append(str(len(atomic_numbers))) f34_lines += ["{0:3} {1[0]:17.9E} {1[1]:17.9E} {1[2]:17.9E}".format( anum + 200 if anum in has_ecp else anum, pos) for anum, pos in zip(atomic_numbers, positions)] return "\n".join(f34_lines)
def get_shaped_cell(self): """ The numbers of atoms is not changed, but the lattice shape is optimized to be fulled. """ n = self.numbers.size numbers = self.numbers.copy() index = np.array([i for i in range(n)]) rcell = (self.lattice, self.positions, index) lattice, positions, new_index = spglib.standardize_cell( rcell, to_primitive=True, no_idealize=False, symprec=self.symprec) numbers = numbers[new_index] return self.__class__(lattice, positions, numbers)
def standardize(self, sbs=[], symprec=1e-2): """ reduce to primitive cell """ import spglib if len(sbs) == 0: sbs = range(self.nframes) for i in sbs: frame = self.frames[i] lattice, scaled_positions, numbers = spglib.standardize_cell( frame, to_primitive=1, no_idealize=1, symprec=symprec) self.frames[i] = Atoms(numbers=numbers, cell=lattice, scaled_positions=scaled_positions, pbc=frame.get_pbc())
def gulp_opt(stru): gulp_inp = 'opt.gin' tp_inp = 'tp_inp' out = 'out' cif_out = 'final.cif' symprec = 0.01 ccell = standardize_cell((stru.cell, stru.get_scaled_positions(), stru.numbers), symprec=symprec) if ccell: symmetry = get_symmetry(ccell, symprec=symprec) spacegroup = get_spacegroup(ccell, symprec=symprec) else: symmetry = get_symmetry((stru.cell, stru.get_scaled_positions(), stru.numbers), symprec=symprec) spacegroup = get_spacegroup((stru.cell, stru.get_scaled_positions(), stru.numbers), symprec=symprec) asymmetric_atoms = np.unique(symmetry.equivalent_atoms) pass
def get_standardized_cell(self, to_primitive: bool, no_idealize: bool, symprec: float = 1e-5, no_sort: bool = False, get_sort_list: list = False): """ Get stadandardized cell. Args: to_primitive (bool): True => primitive, False => conventional. no_idealize (bool): True => not rotate crystal body, False => rotate crystal body symprec (float): symmetry tolerance, for more detail see spglib documentation no_sort (bool): does not change atoms order get_sort_list (bool): When no_sort=True, return sort list Returns: tuple: If get_sort_list=False, return cell. If get_sort_list=True, return (cell, sort_list). """ spg_cell = spglib.standardize_cell(self._cell_for_spglib, to_primitive=to_primitive, no_idealize=no_idealize, symprec=symprec) symbols = get_symbols_from_numbers(spg_cell[2]) std_cell = (spg_cell[0], spg_cell[1], symbols) if no_sort: return std_cell num_atoms, unique_symbols, scaled_positions, sort_list = \ sort_positions_by_symbols( symbols=std_cell[2], positions=std_cell[1]) symbols = [] for num, symbol in zip(num_atoms, unique_symbols): symbols.extend([symbol] * num) sort_std_cell = (std_cell[0], scaled_positions, symbols) if get_sort_list: return (sort_std_cell, sort_list) return sort_std_cell
def get_primitive_atoms(self, atoms): """Transform from primitive to conventional cell""" lattice, scaled_positions, numbers = standardize_cell(atoms, to_primitive=True, no_idealize=False, symprec=1e-5) atoms = Atoms(numbers=numbers, cell=lattice, pbc=True) atoms.set_scaled_positions(scaled_positions) atoms.wrap() return atoms
def standardize(self, to_primitive=True, symprec=1e-4): """ Wrapper of the spglib standardize() function with extra features. For 2D systems, the non-periodic axis is enforced as the z-axis. Args: to_primitive (bool): Reduces to primitive cell or not. symprec (float): Precision to determine new cell. Note: The combination of to_primitive=True and a larger value of symprec (1e-3) can be used to symmetrize a structure. """ atoms = self.atoms.copy() pbc1 = self.find_nonperiodic_axes() lattice, positions, numbers = ( atoms.get_cell(), atoms.get_scaled_positions(), atoms.numbers, ) cell = (lattice, positions, numbers) newcell = spglib.standardize_cell(cell, to_primitive=to_primitive, no_idealize=False, symprec=symprec) if newcell == None: logging.error("Cell could not be standardized.") return None else: atoms = ase.Atoms( scaled_positions=newcell[1], numbers=newcell[2], cell=newcell[0], pbc=self.atoms.pbc, ) pbc2 = self.find_nonperiodic_axes() if pbc1 != pbc2: old = [k for k, v in pbc1.items() if v] new = [k for k, v in pbc2.items() if v] assert len(old) == len( new), "Periodicity changed due to standardization." if len(new) == 2: npbcax = list(set([0, 1, 2]) - set(new))[0] atoms = ase.geometry.permute_axes(atoms, new + [npbcax]) assert self.is_2d(atoms), "Permutation to 2D not working." self.atoms = atoms
def read(self, file): """Read and parse fort.34 file""" if isinstance(file, str): with open(file) as f: data = f.read() else: data = file.read() parsed_data = _parse_string(f34_parser(), data) self.dimensionality, self.centring, self.crystal_type = parsed_data['header'] if self.dimensionality != 3: raise NotImplementedError('Structure with dimensionality < 3 currently not supported') # primitive cell vectors and basis positions in cartesian coordinates abc = np.array(parsed_data['abc'].asList()).reshape((3, 3)) positions = np.array([d[1:] for d in parsed_data['geometry']]) # convert positions to fractional positions = np.dot(np.linalg.inv(abc).T, positions.T).T atomic_numbers = [d[0] for d in parsed_data['geometry']] # convert to conventional cell cell = (abc, positions, atomic_numbers) cell = spglib.standardize_cell(cell, to_primitive=False, no_idealize=False) self.abc, self.positions, atomic_numbers = cell # ECPs self.atomic_numbers = [num if num < 201 else num - 200 for num in atomic_numbers] # get symmetry operations self.n_symops = parsed_data['n_symops'] self.symops = np.array(parsed_data['symops'].asList()).reshape(self.n_symops * 4, 3) rotations = np.zeros((self.n_symops, 3, 3)) for i in range(3): rotations[:, i] = self.symops[i::4] # convert symmetry operations from cartesian to fractional rotations = np.dot(np.dot(np.linalg.inv(abc.T), rotations), abc.T) # have to round rotations matrix as it is used to find symmetry group rotations = np.round(np.swapaxes(rotations, 0, 1), 9) translations = np.dot(self.symops[3::4], np.linalg.inv(abc)) hall = spglib.get_hall_number_from_symmetry(rotations, translations) self.space_group = int(spglib.get_spacegroup_type(hall)['number']) # we have conventional cell now return self
def _reduce_to_primitive(self, structure): """Reduce the two structure to their primitive type""" try: import spglib except ImportError: raise SpgLibNotFoundError( "SpgLib is required if to_primitive=True") cell = (structure.get_cell()).tolist() pos = structure.get_scaled_positions().tolist() numbers = structure.get_atomic_numbers() cell, scaled_pos, numbers = spglib.standardize_cell( (cell, pos, numbers), to_primitive=True) atoms = Atoms(scaled_positions=scaled_pos, numbers=numbers, cell=cell, pbc=True) return atoms
def test_standardize_cell_and_pointgroup(self): for fname, spgnum in zip(self._filenames, self._spgnum_ref): cell = read_vasp(fname) if 'distorted' in fname: symprec = 1e-1 else: symprec = 1e-5 std_cell = standardize_cell(cell, to_primitive=False, no_idealize=True, symprec=symprec) dataset = get_symmetry_dataset(std_cell, symprec=symprec) self.assertEqual(dataset['number'], spgnum, msg=("%s" % fname)) # The test for point group has to be done after standarization. ptg_symbol, _, _ = get_pointgroup(dataset['rotations']) self.assertEqual(dataset['pointgroup'], ptg_symbol, msg=("%s" % fname))
print(" Refine distorted rutile structure") lattice, positions, numbers = spglib.refine_cell(rutile_dist, symprec=1e-1) show_cell(lattice, positions, numbers) print('') print("[find_primitive]") print(" Fine primitive distorted silicon structure") lattice, positions, numbers = spglib.find_primitive(silicon_dist, symprec=1e-1) show_cell(lattice, positions, numbers) print('') print("[standardize_cell]") print(" Standardize distorted rutile structure:") print(" (to_primitive=0 and no_idealize=0)") lattice, positions, numbers = spglib.standardize_cell(rutile_dist, to_primitive=0, no_idealize=0, symprec=1e-1) show_cell(lattice, positions, numbers) print('') print("[standardize_cell]") print(" Standardize distorted rutile structure:") print(" (to_primitive=0 and no_idealize=1)") lattice, positions, numbers = spglib.standardize_cell(rutile_dist, to_primitive=0, no_idealize=1, symprec=1e-1) show_cell(lattice, positions, numbers) print('') print("[standardize_cell]")
def crystal_space_group(system, symprec=1e-5, to_primitive=False, no_idealize=False): """ Uses spglib to evaluate space group information for a given system. Parameters ---------- system : atomman.System The system to analyze. symprec : float Absolute length tolerance to use in identifying symmetry of atomic sites and system boundaries. to_primitive : bool Indicates if the returned unit cell is conventional (False) or primitive (True). Default value is False. no_idealize : bool Indicates if the atom positions in the returned unit cell are averaged (True) or idealized based on the structure (False). Default value is False. Returns ------- dict Results dictionary containing space group information and an associated unit cell system. """ # Identify the standardized unit cell representation ucell = spglib.standardize_cell(system.dump('spglib_cell'), to_primitive=to_primitive, no_idealize=no_idealize, symprec=symprec) # Convert back to atomman systems and normalize ucell = am.load('spglib_cell', ucell, symbols=system.symbols) ucell.atoms.pos -= ucell.atoms.pos[0] ucell = ucell.normalize() # Get space group metadata sym_data = spglib.get_symmetry_dataset(ucell.dump('spglib_cell')) spg_type = spglib.get_spacegroup_type(sym_data['hall_number']) # Generate Pearson symbol if spg_type['number'] <= 2: crystalclass = 'a' elif spg_type['number'] <= 15: crystalclass = 'm' elif spg_type['number'] <= 74: crystalclass = 'o' elif spg_type['number'] <= 142: crystalclass = 't' elif spg_type['number'] <= 194: crystalclass = 'h' else: crystalclass = 'c' latticetype = spg_type['international'][0] if latticetype in ['A', 'B']: latticetype = 'C' natoms = str(ucell.natoms) pearson = crystalclass + latticetype + natoms # Return results results_dict = spg_type results_dict['ucell'] = ucell results_dict['hall_number'] = sym_data['hall_number'] results_dict['wyckoffs'] = sym_data['wyckoffs'] results_dict['pearson'] = pearson return results_dict