def transform(self, symop, **kwargs): """Returns new model with new basis: b_new = symop*b_old and new coordinates: xyz_new = symop.inv()*xyz_old and the THE SAME SPACE GROUP ! WARNING! Take care: symmetry operations are not transformed (at the moment) so this function in its current state is to be used for expanding or contracting the unit cell! Symop should be either in symbolic form or be an augmented matrix of type sympy.Matrix""" if isinstance(symop, sympy.Matrix): if symop.shape != (4,4): raise IndexError("Wrong shape of the matrix. Correct: 4 x 4 !") symop_sympy = symop elif isinstance(symop, str): symop_sympy = core.xyzt2augmat(symop) else: raise TypeError("Wrong type of the symmetry operation. See the docstring !") scatterers = copy.deepcopy(self._scatterers) fragment = Fragment(scatterers) fragment.transform(symop_sympy.inv()) #print self._symmetry.unit_cell().parameters() #unit_cell().__dict__ lattice = self._symmetry.unit_cell().parameters() pars = [] pars.extend(lattice[0:3]) angles = lattice[3:] pars.append(0) latt_old = sympy.Matrix(pars) latt_new = list(latt_old.T*symop_sympy)[0:3] latt_new.extend(angles) symmetry_new = copy.deepcopy(self._symmetry) setattr(symmetry_new, "unit_cell", tuple(latt_new)) return self.__class__(symmetry_new, [fragment])
def leftmult(Cls, self, symop): """Returns instance of Fragment obtained from the current instance by left multiplication with symop. The initialization of the new instance is done according to case 2 described in __init__""" if str == type(symop): sym_mat = core.xyzt2augmat(symop) else: sym_mat = symop #I am not sure how the triple product modulo should be executed!!!!! #new_stab = map( # core.modulo_n, core.lstab((self.stab, sym_mat)) # ) new_stab = [] sym_mat_inv = core.modulo_n(sym_mat.inv(), 1) for item in self.stab: new_elt = core.modulo_n(core.modulo_n(sym_mat*item,1)*sym_mat_inv,1) new_stab.append(new_elt) #new_stab = [core.modulo_n(core.modulo_n(sym_mat*item,1)*sym_mat.inv(),1) for item in self.stab] new_coset = map(core.modulo_n, core.lcoset((self.coset, sym_mat))) return Cls(new_stab, new_coset)
def coset(self, coset = []): coset_list = [] for symop in coset: if str == type(symop): coset_list.append(core.xyzt2augmat(symop)) else: coset_list.append(symop) self._coset = coset_list
def stab(self, stab): """See doc string for __init__""" stab_list = [] for item in stab: if str == type(item): stab_list.append(core.xyzt2augmat(item)) else: stab_list.append(item) self._stab = stab_list
def shift_stab_origin(self, origin_shift): """Shifts origin of the stabilizer of the instance IMPORTANT!!! origin_shift is not a modulo operation!!!""" if str == type(origin_shift): originShift = core.xyzt2augmat(origin_shift) else: originShift = origin_shift items = [] for item in self.stab: items.append(originShift*item*originShift.inv()) self.stab = items
def stab(self): """Computing stabilizer of a bilayer(or any 2 fragments). self.layers must contain 2 Layers L_0 and L_1. IMPORTANT: L_1 = g*L_0 so that if S_0 = stab(L_0) => L_1 = g*S_0*L_0. The latter means tha coset g*S_O contains all symops to generate L_1 from L_0. The stabilizer of the bilayer is then a union of 2 sets: 1. set1 contains all common symmetry elements of S_0 and S_1. El-ts of this set simply transform each of the layers into itself 2. set2 contains symmetry elements that transform L_0 into L_1 and at the same time L_1 into L_0. El-ts that transform L_0 into L_1 are in the coset: g*S_0 (that's why it is important that L_1 = g*L_O, that the corresponding coset attributes of the layers are set correctly) so g' from g*S_0 applied to L_1 should yield L_0: g'*L_1=L_0 and since g'L_0 = L_1 =>g'*g'L_0 = L_0 => g'*g' or g'^2 should belong to S_0. Hence set2 contains elements of coset g*S_0 (or L_1.coset) which squares belong to S_0. """ if self._stab: return self._stab #finding overlap between stabilizers of the constituent layers stab_overlap = \ [item for item in self.layers[1].stab if item in self.layers[0].stab] #for a layer that is generated from the other one by a stacking oper: #finding coset members whose 2 orders belong to stabilizer of the basic #layer unit_element = core.xyzt2augmat('x,y,z') sec_ord_elts = [] for nr,layer in enumerate(self.layers): if unit_element not in layer.coset: for symop in layer.coset: if core.modulo_n(symop*symop,1) in self.layers[1-nr].stab \ and symop not in stab_overlap: sec_ord_elts.append(symop) self._stab = stab_overlap + sec_ord_elts #performing some basic checks coset_union = self.layers[0].coset + self.layers[1].coset #print len(self.layers[0].coset) assert core.cosets_are_disjoint([self.layers[0].coset, self.layers[1].coset]), "Cosets are not disjoint!" assert len(self.layers[1].coset) == len(self.layers[0].coset),\ "Coset attributes for the constituent layers are of different order!" return self._stab
def build_shift_classes(self): """ IMPORTANT: THIS METHOD IS PARTLY HARDCODED, FOR IT ONLY ACCOUNTS FOR SHIFTS WITHIN XY PLANE REQUIRED FOR MODELLING DISORDER IN ZEOLITE-BETA. IT CAN HOWEVER BE EASILY MODIFIED!!! -------------------------------------------------------------- This method will group fragments into shift classes(which will be stored in self.__shift_classes) in the following way: 1. Find basic fragment within the layer 2. Find fragments that are obtained from the basic one without applying shifts and put them into one class, shift_class_0. This is achieved by sear- ching for symops in self.stab that have zero shift component, applying them to basic_fragment.stab and locating indices of the corresponding fragements within self. 3.Shift_class_i is produced by applying one of all the possible shift vectors (symops of self.stab that have unit rotation matrix as their rotation part) to the fragments in zero_group_0, locating so obtained fragments within the layer and storing them within as a single group. 4.The total number of shift_classes so obtained will be equal to the number of the possible pure shifts. 5.Those shift_classes are later employed for the computation of shift vector between two fragments """ self.__shift_classes = [] shift_class_0 = set() basic_fragment = self.basicFragment basic_stab = basic_fragment.stab all_shifts = set() unitAugMat = core.xyzt2augmat('x,y,z') zero_shift_xy = unitAugMat[0:2,-1] unit_rot_part = unitAugMat[0:3,0:3] #Constructing shift_class_0 and filling all_shfits with indices #of pure shift symops in self.symop for nr,symop in enumerate(self.stab): if symop[0:2,-1] == zero_shift_xy: coset = map(core.modulo_n, core.lcoset((basic_stab, symop))) index = self.fragment_index(coset) shift_class_0.add(index) elif symop[0:3, 0:3] == unit_rot_part: all_shifts.add(nr) self.__shift_classes.append(shift_class_0)
def leftmult(Cls, self, symop): """to be filled""" if str == type(symop): sym_mat = core.xyzt2augmat(symop) else: sym_mat = symop temp = super(Layer,Cls).leftmult(self, symop) fragments = [] if self.fragments: for fragment in self.fragments: fragments.append(Fragment.leftmult(fragment, sym_mat)) kwargs = { "stab": temp.stab, "coset": temp.coset, "fragments": fragments } return Cls(**kwargs)
def is_basic(self): """Basic fragment is the one whose coset contains 'x,y,z' operation for it is used to generate other fragments""" return core.xyzt2augmat("x,y,z") in self.coset