Source code for pychemia.crystal.symmetry

from __future__ import unicode_literals
import spglib as spg
from pychemia.utils.serializer import generic_serializer
from pychemia.utils.computing import deep_unicode


[docs]def spglib_version(): from pychemia import Structure from . import CrystalSymmetry # Testing version of spglib st = Structure(symbols=['H']) symm = CrystalSymmetry(st) ret = spg.spglib.spg.dataset(symm.transposed, symm.reduced, symm.numbers, 1e-5, -1.0) if type(ret[3]) is list: HAS_SPGLIB = False version = "%d.%d.%d" % spg.get_version() print('SPGLIB current version is %s, please install spglib > 1.9' % version) else: HAS_SPGLIB = True return HAS_SPGLIB
[docs]class CrystalSymmetry(object): """ Takes a pychemia.Structure object and creates an object with methods to identify symmetry groups and other symmetry related operations. Uses spglib to perform various symmetry finding operations. """ def __init__(self, structure): """ Creates a new StructureSymmetry object for a given structure This class allows interaction with spglib for several operations related to symmetry :param structure: Example: >>> import pychemia >>> a = 4.05 >>> b = a/2 >>> fcc = pychemia.Structure(symbols=['Au'], cell=[[0, b, b], [b, 0, b], [b, b, 0]], periodicity=True) >>> symm = pychemia.crystal.CrystalSymmetry(fcc) >>> symm.number() 225 >>> symm.symbol() == u'Fm-3m' True """ assert structure.is_crystal assert structure.is_perfect self.structure = structure # Spglib"s convention for the lattice definition is the transpose of cell # self._transposed_cell = structure.cell.transpose().copy() self.spglib_lattice = generic_serializer(structure.cell.copy()) # Spglib requires numpy floats. # self._transposed_cell = np.array(self._transposed_cell, dtype='double', order='C') # self._reduced = np.array(structure.reduced, dtype='double', order='C') self.spglib_positions = generic_serializer(structure.reduced) # Get a list of indices for each atom in structure # Indices starting in 1 # self._numbers = np.array([structure.species.index(x) + 1 for x in structure.symbols], dtype='intc') self.spglib_numbers = [structure.species.index(x) + 1 for x in structure.symbols] # @property # def transposed(self): # return self._transposed_cell # # @property # def reduced(self): # return self._reduced # # @property # def numbers(self): # return self._numbers @property def spglib_cell(self): return self.spglib_lattice, self.spglib_positions, self.spglib_numbers
[docs] def get_spacegroup(self, symprec=1e-5): return spg.get_spacegroup(cell=self.spglib_cell, symprec=symprec)
[docs] def get_symmetry(self, symprec=1e-5): return spg.get_symmetry(cell=self.spglib_cell, symprec=symprec)
[docs] def get_symmetry_dataset(self, symprec=1e-5): return spg.get_symmetry_dataset(cell=self.spglib_cell, symprec=symprec)
[docs] def number(self, symprec=1e-5): return int(self.get_symmetry_dataset(symprec)['number'])
[docs] def symbol(self, symprec=1e-5): return deep_unicode(self.get_symmetry_dataset(symprec)['international'])
[docs] def hall_number(self, symprec=1e-5): return self.get_symmetry_dataset(symprec)['hall_number']
[docs] def get_new_structure(self, spglib_cell): from pychemia import Structure cell = spglib_cell[0] reduced = spglib_cell[1] symbols = [self.structure.species[x - 1] for x in spglib_cell[2]] return Structure(cell=cell, reduced=reduced, symbols=symbols)
[docs] def refine_cell(self, symprec=1e-5): """ Refine a pychemia Structure using the tolerances and return a new structure in a Bravais lattice :param symprec: (float) Tolerance of distance between atomic positions and between lengths of lattice vectors to be tolerated in the symmetry finding. :return: A new pychemia Structure in a Bravais lattice :rtype : (pychemia.Structure) Example: >>> import pychemia >>> a = 4.05 >>> b = a/2 >>> fcc = pychemia.Structure(symbols=['Au'], ... cell=[[0, b+1E-5, b-1E-5], [b+1E-5, 0, b-1E-5], [b+1E-5, b-1E-5, 0]], periodicity=True) >>> symm = pychemia.crystal.CrystalSymmetry(fcc) >>> symm.number() 2 >>> symm.symbol() == u'P-1' True >>> fcc2 = symm.refine_cell(symprec=1E-3) >>> symm2 = pychemia.crystal.CrystalSymmetry(fcc2) >>> symm2.number() 225 >>> symm2.symbol() == u'Fm-3m' True """ new_spglib_cell = spg.refine_cell(self.spglib_cell, symprec=symprec) return self.get_new_structure(new_spglib_cell)
[docs] def find_primitive(self, symprec=1e-5): new_spglib_cell = spg.find_primitive(self.spglib_cell, symprec=symprec) return self.get_new_structure(new_spglib_cell)
[docs] def standarize_cell(self, symprec=1e-5): new_spglib_cell = spg.standarize_cell(self.spglib_cell, symprec=symprec) return self.get_new_structure(new_spglib_cell)
[docs] def get_space_group_type(self, symprec=1e-5): return spg.get_spacegroup_type(self.hall_number(symprec=symprec))
[docs] def crystal_system(self, symprec=1e-5): num = self.number(symprec) if num < 3: return u'Triclinic' elif num < 16: return u'Monoclinic' elif num < 75: return u'Orthorhombic' elif num < 143: return u'Tetragonal' elif num < 168: return u'Trigonal' elif num < 195: return u'Hexagonal' else: return u'Cubic'
[docs] def symmetrize(self, initial_symprec=0.01, final_symprec=0.1, delta_symprec=0.01): if self.structure.natom == 1: return self.structure.copy() prec = initial_symprec while prec < final_symprec: if self.number(symprec=prec) > self.number(symprec=initial_symprec): break else: prec += delta_symprec if prec > final_symprec: prec = final_symprec new_bravais = self.refine_cell(symprec=prec) sym2 = CrystalSymmetry(new_bravais) return sym2.find_primitive()
# def get_symmetry_dataset(self, symprec=1e-5, angle_tolerance=-1.0): # # keys = ('number', # 'hall_number', # 'international', # 'hall', # 'transformation_matrix', # 'origin_shift', # 'rotations', # 'translations', # 'wyckoffs', # 'equivalent_atoms', # 'brv_lattice', # 'brv_types', # 'brv_positions') # dataset = {} # for key, data in zip(keys, spg.spglib.spg.dataset(self._transposed_cell, # self._reduced, # self._numbers, # symprec, # angle_tolerance)): # dataset[key] = data # # dataset['international'] = dataset['international'].strip() # dataset['hall'] = dataset['hall'].strip() # dataset['transformation_matrix'] = np.array( # dataset['transformation_matrix'], dtype='double', order='C') # dataset['origin_shift'] = np.array(dataset['origin_shift'], dtype='double') # dataset['rotations'] = np.array(dataset['rotations'], # dtype='intc', order='C') # dataset['translations'] = np.array(dataset['translations'], # dtype='double', order='C') # letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" # dataset['wyckoffs'] = [letters[x] for x in dataset['wyckoffs']] # dataset['equivalent_atoms'] = np.array(dataset['equivalent_atoms'], # dtype='intc') # dataset['brv_lattice'] = np.array(np.transpose(dataset['brv_lattice']), # dtype='double', order='C') # dataset['brv_types'] = np.array(dataset['brv_types'], dtype='intc') # dataset['brv_positions'] = np.array(dataset['brv_positions'], # dtype='double', order='C') # # return dataset # # def get_spacegroup(self, symprec=1e-5, angle_tolerance=-1.0): # """ # Return space group in international table symbol and number # as a string. # # :param symprec: (float) Symmetry precision # :param angle_tolerance: (float) angle tolerance for spglib internal routine # :return: # """ # # dataset = self.get_symmetry_dataset(symprec=symprec, angle_tolerance=angle_tolerance) # return "%s (%d)" % (dataset['international'], dataset['number']) # # def spacegroup(self, symprec=1e-5, angle_tolerance=-1.0): # """ # Computes the space group for the structure with a given # precision in distances (symprec) and angle tolerance in degrees (angle_tolerance) # # :param symprec: (float) Tolerance of distance between atomic positions and between lengths of # lattice vectors # to be tolerated in the symmetry finding. # :param angle_tolerance: (float) Tolerance of angle between lattice vectors in degrees to be # tolerated in the symmetry finding. # # :return: The space group with the symbol and number as a string # :rtype : (str) # """ # # return self.get_spacegroup(symprec, angle_tolerance) # # def symbol(self, symprec=1e-5, angle_tolerance=-1.0): # """ # Computes the space group symbol for the structure with a given # precision in distances (symprec) and angle tolerance in degrees (angle_tolerance) # # :param symprec: (float) Tolerance of distance between atomic positions and between lengths of # lattice vectors to be tolerated in the symmetry finding. # :param angle_tolerance: (float) Tolerance of angle between lattice vectors in degrees to be # tolerated in the symmetry finding. # # :return: The space group symbol as a string # :rtype : (str) # """ # return self.spacegroup(symprec, angle_tolerance).split()[0] # # def number(self, symprec=1e-5, angle_tolerance=-1.0): # """ # Computes the space group number for the structure with a given # precision in distances (symprec) and angle tolerance in degrees (angle_tolerance) # # :param symprec: (float) Tolerance of distance between atomic positions and between lengths # of lattice vectors to be tolerated in the symmetry finding. # :param angle_tolerance: (float) Tolerance of angle between lattice vectors in degrees to be # tolerated in the symmetry finding. # # :return: The space group number # :rtype : (int) # """ # return int(self.spacegroup(symprec, angle_tolerance).split()[-1].strip()[1:-1]) # def refine_cell(self, symprec=1e-5, angle_tolerance=-1.0): # from pychemia import Structure # natom = self.structure.natom # cell = np.array(self.structure.cell.transpose(), dtype='double', order='C') # # pos = np.zeros((natom * 4, 3), dtype='double') # pos[:natom] = self._reduced # # numbers = np.zeros(natom * 4, dtype='intc') # numbers[:natom] = np.array(self._numbers, dtype='intc') # # natom_bravais = spg.spglib.spg.refine_cell(cell, pos, numbers, natom, symprec, angle_tolerance) # if natom_bravais == 0: # return self.structure.copy() # # cell = np.array(cell.T, dtype='double', order='C') # reduced = np.array(pos[:natom_bravais], dtype='double', order='C') # symbols = [self.structure.species[x] for x in (numbers[:natom_bravais] - 1)] # # return Structure(cell=cell, symbols=symbols, reduced=reduced) # # def find_primitive(self, symprec=1e-5, angle_tolerance=1.0): # """ # Search the primitive pychemia Structure using the tolerances. # If no primitive cell is found a copy of the original structure is returned # # :param symprec: (float) Tolerance of distance between atomic positions and between lengths of # lattice vectors to be tolerated in the symmetry finding. # :param angle_tolerance: (float) Tolerance of angle between lattice vectors in degrees to # be tolerated in the symmetry finding. # # :return: A new pychemia Structure in a Bravais lattice # :rtype : (pychemia.Structure) # """ # from pychemia import Structure # # Create copies of the arguments # cell = np.array(self._transposed_cell, dtype='double', order='C') # reduced = np.array(self._reduced, dtype='double', order='C') # numbers = np.array(self._numbers, dtype='intc') # # natom_prim = spg.spglib.spg.primitive(cell, reduced, numbers, symprec, angle_tolerance) # symbols = [self.structure.species[x] for x in (numbers[:natom_prim] - 1)] # reduced = reduced[:natom_prim] # # if natom_prim > 0: # return Structure(cell=cell.T, reduced=reduced, symbols=symbols) # else: # return self.structure.copy() #