def get_ase_cell(self): """ cell used for visualization """ h = max(self.atoms.get_positions()[:,2]) phi1 = phival(self.n1[0],self.n1[1]) phi2 = phival(self.n2[0],self.n2[1]) a1,l1 = phi1-np.pi/2, h*self.angle1 a2,l2 = phi2-np.pi/2, h*self.angle2 return np.array( [[l1*cos(a1),l1*sin(a1),0],[l2*cos(a2),l2*sin(a2),0],[0,0,h]] )
def get_ase_cell(self): """ cell used for visualization """ h = max(self.atoms.get_positions()[:, 2]) phi1 = phival(self.n1[0], self.n1[1]) phi2 = phival(self.n2[0], self.n2[1]) a1, l1 = phi1 - np.pi / 2, h * self.angle1 a2, l2 = phi2 - np.pi / 2, h * self.angle2 return np.array([[l1 * cos(a1), l1 * sin(a1), 0], [l2 * cos(a2), l2 * sin(a2), 0], [0, 0, h]])
def rotation(self,n,angles=False): """ Rotate around two axes: first around 'unit cell axis' for chirality and then around 'torus axis' for twisting.. Returns the rotation matrix. If angles==True, return (theta,phi,angle), where (theta,phi) gives the rotation axis and 'angle' the rotation angle. Approximate tangential vector by a mean tangential vector (-sin(angle/2),cos(angle/2),0) """ if n[2]==0: return np.eye(3) bend,twist = [self.get(k) for k in ['bend_angle','twist_angle']] rot1 = rotation_matrix(self.baxis,n[2]*bend) a = self.get('bend_angle') taxis = np.array([-sin(a/2),cos(a/2),0]) rot2 = rotation_matrix(taxis,n[2]*twist) R = np.dot(rot2,rot1) if angles: angle, dir = rotation_from_matrix(R) theta = np.arccos(dir[2]) if np.abs(theta)<1E-12 or np.abs(theta-np.pi)<1E-12: phi = 0. else: cosp, sinp = dir[0]/np.sin(theta), dir[1]/np.sin(theta) phi = phival(cosp,sinp) return (theta,phi,angle) else: return R
def angular(r,wf): """ Return angular part of wave function. The real combinations of Y_lm's parameters: ----------- r: (not normalized) position vector wf: index or symbol for state (look below) """ R=sqrt(sum(r**2)) if R<1E-14: return 0.0 theta=acos(r[2]/R) phi=phival(r[0],r[1]) if type(wf)!=type(1): wf=states.index(wf) if wf==0: return 1/sqrt(4*pi) elif wf==1: return sqrt(3/(4*pi))*sin(theta)*cos(phi) elif wf==2: return sqrt(3/(4*pi))*sin(theta)*sin(phi) elif wf==3: return sqrt(3/(4*pi))*cos(theta) elif wf==4: return sqrt(15/(4*pi))*sin(theta)**2*cos(phi)*sin(phi) elif wf==5: return sqrt(15/(4*pi))*sin(theta)*cos(theta)*sin(phi) elif wf==6: return sqrt(15/(4*pi))*sin(theta)*cos(theta)*cos(phi) elif wf==7: return 0.5*sqrt(15/(4*pi))*sin(theta)**2*cos(2*phi) elif wf==8: return 0.5*sqrt(5/(4*pi))*(3*cos(theta)**2-1)
def rotation(self, n, angles=False): """ Rotate around two axes: first around 'unit cell axis' for chirality and then around 'torus axis' for twisting.. Returns the rotation matrix. If angles==True, return (theta,phi,angle), where (theta,phi) gives the rotation axis and 'angle' the rotation angle. Approximate tangential vector by a mean tangential vector (-sin(angle/2),cos(angle/2),0) """ if n[2] == 0: return np.eye(3) bend, twist = [self.get(k) for k in ['bend_angle', 'twist_angle']] rot1 = rotation_matrix(self.baxis, n[2] * bend) a = self.get('bend_angle') taxis = np.array([-sin(a / 2), cos(a / 2), 0]) rot2 = rotation_matrix(taxis, n[2] * twist) R = np.dot(rot2, rot1) if angles: angle, dir = rotation_from_matrix(R) theta = np.arccos(dir[2]) if np.abs(theta) < 1E-12 or np.abs(theta - np.pi) < 1E-12: phi = 0. else: cosp, sinp = dir[0] / np.sin(theta), dir[1] / np.sin(theta) phi = phival(cosp, sinp) return (theta, phi, angle) else: return R
def transform(self,r,n): """ Symmetry transformation n for position r. """ if n[2]==0: return r.copy() a1, a2, R, mode = [self.get(k) for k in ['bend_angle','twist_angle','R','mode']] n1 = self.n1 if mode == 2: orig_chir = np.cross(n2,n1) orig_chir = orig_chir/np.linalg.norm(orig_chir) A = np.dot(R,orig_chir) rot1 = rotation_matrix(n1,n[2]*a1) rot2 = rotation_matrix(n2,-n[2]*a2) Rot = np.dot( rot2,rot1 ) rp = r.copy() rp = np.dot( Rot,rp-A ) + np.dot( rot2,A ) return rp elif mode == 1: x, y, z = r x,y,z = x,z,-y alpha1 = phival(x,y) Rr = np.sqrt(x**2+y**2) R1 = R * np.array([np.cos(alpha1),np.sin(alpha1),np.zeros_like(alpha1)]) rho = np.linalg.norm(r-R1) beta1 = phival(Rr-R,z) alpha2 = alpha1 + n[2]*a1 beta2 = beta1 + n[2]*a2 Rrp = R + rho*np.cos(beta2) return np.array( [Rrp*np.cos(alpha2),Rrp*np.sin(alpha2),-rho*np.sin(beta2)] ) else: raise AssertionError('TwistAndTurn mode is not set')
def to_spherical_coordinates(vec): """ Transforms the given cartesien vector to spherical coordinates. """ x, y, z = vec r = float(np.linalg.norm(vec)) if r < 1e-6: r = 0.0 theta = 0.0 phi = 0.0 else: theta = float(acos(z/r)) phi = float(phival(x,y)) assert 0 <= theta <= np.pi assert 0 <= phi <= 2*np.pi return (r, theta, phi)
def to_spherical_coordinates(vec): """ Transforms the given cartesien vector to spherical coordinates. """ x, y, z = vec r = float(np.linalg.norm(vec)) if r < 1e-6: r = 0.0 theta = 0.0 phi = 0.0 else: theta = float(acos(z / r)) phi = float(phival(x, y)) assert 0 <= theta <= np.pi assert 0 <= phi <= 2 * np.pi return (r, theta, phi)
def set(self, angle=None, height=None, x=None, scale_atoms=False, container=None): """ Reset angle, height, or x, and maybe scale atoms. parameters: =========== height: Height of the primitive cell in z-direction angle: angle (in radians) of rotation x: fractional translation offset related to 180 rotation Only integers and half-integers allowed. """ if container != None: # copy container assert angle == None and height == None and x == None and scale_atoms == False self.set(angle=container.get("angle"), height=container.get("height"), x=container.get("x")) else: if x != None: assert abs(np.round(2 * x) - 2 * x) < 1e-15 if not scale_atoms: if angle != None: self._set(angle=angle) if height != None: self._set(height=height) if x != None: self._set(x=x) else: if x != None: raise AssertionError("It is probably illegal to change x. This changes the symmetry, right?") if angle is None: da = 0.0 else: da = angle - self.get("angle") self._set(angle=angle) old_height = self.get("height") if height != None: self._set(height=height) newr = [] for r in self.atoms.get_positions(): x, y = r[0], r[1] rad = np.sqrt(x ** 2 + y ** 2) frac = r[2] / old_height # twist atoms z/h * da (more) newphi = phival(x, y) + frac * da newr.append([rad * np.cos(newphi), rad * np.sin(newphi), frac * self.get("height")]) self.atoms.set_positions(newr)
def set(self, angle=None, height=None, scale_atoms=False, container=None): """ Reset angle or height, and maybe scale atoms. @param: height Height of the primitive cell in z-direction @param: angle angle (in radians) of rotation """ if container != None: # copy container assert angle == None and height == None and scale_atoms == False self.set(angle=container.get('angle'), height=container.get('height')) else: if not scale_atoms: if angle != None: self._set(angle=angle) if height != None: self._set(height=height) else: if angle is None: da = 0.0 else: da = angle - self.get('angle') self._set(angle=angle) old_height = self.get('height') if height != None: self._set(height=height) newr = [] for r in self.atoms.get_positions(): x, y = r[0], r[1] rad = np.sqrt(x**2 + y**2) frac = r[2] / old_height # twist atoms z/h * da (more) newphi = phival(x, y) + frac * da newr.append([ rad * np.cos(newphi), rad * np.sin(newphi), frac * self.get('height') ]) self.atoms.set_positions(newr)
def angular(r, wf): """ Return angular part of wave function. The real combinations of Y_lm's parameters: ----------- r: (not normalized) position vector wf: index or symbol for state (look below) """ R = sqrt(sum(r ** 2)) if R < 1e-14: return 0.0 theta = acos(r[2] / R) phi = phival(r[0], r[1]) if type(wf) != type(1): wf = states.index(wf) if wf == 0: return 1 / sqrt(4 * pi) elif wf == 1: return sqrt(3 / (4 * pi)) * sin(theta) * cos(phi) elif wf == 2: return sqrt(3 / (4 * pi)) * sin(theta) * sin(phi) elif wf == 3: return sqrt(3 / (4 * pi)) * cos(theta) elif wf == 4: return sqrt(15 / (4 * pi)) * sin(theta) ** 2 * cos(phi) * sin(phi) elif wf == 5: return sqrt(15 / (4 * pi)) * sin(theta) * cos(theta) * sin(phi) elif wf == 6: return sqrt(15 / (4 * pi)) * sin(theta) * cos(theta) * cos(phi) elif wf == 7: return 0.5 * sqrt(15 / (4 * pi)) * sin(theta) ** 2 * cos(2 * phi) elif wf == 8: return 0.5 * sqrt(5 / (4 * pi)) * (3 * cos(theta) ** 2 - 1)
def set(self, angle=None, height=None, M=None, physical=True, pbcz=None, scale_atoms=False, container=None): """ Only height can be reset, not angle. parameters: =========== angle angle (in radians) of the wedge (and M=None) height Height of the primitive cell in z-direction M set angle to 2*pi/M (and angle=None) physical (only if M=None) if angle is small, it does not be exactly 2*pi/integer, i.e. situation has no physical meaning (use for calculating stuff continuously) pbcz True if wedge is periodic in z-direction scale_atoms Scale atoms according to changes in parameters. When changing angle, scale also radial distances. (Radii are inversely proportional to angle.) """ if container != None: assert angle == None and height == None and M == None and pbcz == None self.set(angle=container.get('angle'),height=container.get('height'),\ physical=container.get('physical'), pbcz=container.atoms.get_pbc()[2]) if angle != None or M != None: #assert not scale_atoms assert not (angle != None and M != None) old_angle = self.get('angle') if M != None: assert isinstance(M, int) self._set(angle=2 * np.pi / M) elif angle != None: M = int(round(2 * np.pi / angle)) self._set(angle=angle) # check parameters self._set(physical=float(physical)) if self.get('angle') < 1E-6: raise Warning( 'Too small angle (%f) may bring numerical problems.' % self.get('angle')) if self.get('angle') > np.pi: raise AssertionError('angle>pi') if np.abs(M - 2 * np.pi / self.get('angle')) > 1E-12 and self.get( 'physical'): raise AssertionError('angle not physical: angle != 2*pi/M') if not self.get('physical') and M < 20: warnings.warn('Quite large, non-physical angle 2*pi/%.4f.' % (2 * np.pi / self.get('angle'))) if scale_atoms: if abs(old_angle) < 1E-10: raise ValueError( 'Atoms cannot be scaled; old wedge angle too small.') newr = [] for r in self.atoms.get_positions(): x, y = r[0], r[1] rad = np.sqrt(x**2 + y**2) newphi = phival(x, y) * (self.get('angle') / old_angle) rad2 = rad * old_angle / self.get('angle') newr.append( [rad2 * np.cos(newphi), rad2 * np.sin(newphi), r[2]]) self.atoms.set_positions(newr) if height != None: if scale_atoms: r = self.atoms.get_positions() r[:, 2] = r[:, 2] * height / self.get('height') self.atoms.set_positions(r) self._set(height=height) if pbcz != None: self._set(pbcz=float(pbcz))
def set(self,bend_angle=None,twist_angle=None,R=None,mode=None,container=None,scale_atoms=False): """ Reset angles, R or axes @param: bend_angle bending angle @param: twist_angle twisting angle @param: R radius of curvature for neutral line @param: mode mode for transformation (different modes of approximation) @param: scale_atoms scale atoms according to container modifications """ if container!=None: # copy container assert bend_angle==None assert twist_angle==None assert R==None assert mode == None self.set( angle1=container.bend_angle, angle2=container.twist_angle, R=container.R, mode=container.mode) if bend_angle!=None: if scale_atoms: ratio = bend_angle/self.get('bend_angle') r = self.atoms.get_positions() R = np.sqrt( sum(r[:,0]**2+r[:,1]**2) ) alpha1 = np.array([phival(x1,y1) for x1,y1 in zip(r[:,0],r[:,1])] ) alpha = ratio*alpha1 r2 = np.array( [R*np.cos(alpha),R*np.sin(alpha),r[:,2]] ).transpose() self.atoms.set_positions(r2) self._set(bend_angle = bend_angle) if twist_angle!=None: if scale_atoms: da = twist_angle - self.get('twist_angle') R = self.get('R') r = self.atoms.get_positions() alpha1 = np.array([phival(x1,y1) for x1,y1 in zip(r[:,0],r[:,1])] ) Rr = np.sqrt(r[:,0]**2+r[:,1]**2) R1 = R * np.array( [np.cos(alpha1),np.sin(alpha1),np.zeros_like(alpha1)] ).transpose() rho = np.sqrt( np.sum((r-R1)**2,axis=1) ) beta1 = np.array( [phival(a,b) for a,b in zip(Rr-R,-r[:,2])] ) # scale twisting angle depending on angle between x and z (alpha1) x = alpha1/self.get('bend_angle') beta2 = beta1 + x*da Rrp = R + rho*np.cos(beta2) r2 = np.array( [Rrp*np.cos(alpha1),Rrp*np.sin(alpha1),-rho*np.sin(beta2)] ).transpose() self.atoms.set_positions(r2) self._set(twist_angle = twist_angle) if R!=None: if scale_atoms: dR = R-self.get('R') r = self.atoms.get_positions() rad = np.sqrt( r[:,0]**2+r[:,1]**2 ) phi = np.array([phival(x,y) for x,y in zip(r[:,0],r[:,1])] ) r[:,0] = (rad+dR)*np.cos(phi) r[:,1] = (rad+dR)*np.sin(phi) self.atoms.set_positions(r) self._set(R = R) if mode!=None: self._set(mode = mode) self.atoms.set_pbc((False,False,True))
def set(self, angle=None, height=None, M=None, physical=True, twist=None, scale_atoms=False, container=None): """ parameters: =========== angle angle (in radians) of the wedge (and M=None) height Height of the primitive cell in z-direction M set angle to 2*pi/M (and angle=None) physical (only if M=None) if angle is small, it does not be exactly 2*pi/integer, i.e. situation has no physical meaning (use for calculating stuff continuously) twist The twist angle for z-translation scale_atoms Scale atoms according to changes in parameters """ if container!=None: assert angle==None and height==None and M==None and twist==None self.set(angle=container.get('angle'),height=container.get('height'),\ physical=container.get('physical'), twist=container.get('twist')) if angle!=None or M!=None: #assert not scale_atoms assert not (angle!=None and M!=None) old_angle = self.get('angle') if M != None: assert isinstance(M,int) self._set(angle=2*np.pi/M) elif angle != None: M = np.abs(int( round(2*np.pi/angle) )) self._set(angle=angle) # check parameters self._set( physical=float(physical) ) if np.abs(self.get('angle'))<1E-6: raise Warning('Too small angle (%f) may bring numerical problems.' %self.get('angle')) if self.get('angle')>np.pi: raise AssertionError('angle>pi') if np.abs(M-2*np.pi/np.abs(self.get('angle')))>1E-12 and self.get('physical'): raise AssertionError('angle not physical: angle != 2*pi/M') if not self.get('physical') and M<20: warnings.warn('Quite large, non-physical angle 2*pi/%.4f.' %(2*np.pi/self.get('angle')) ) if scale_atoms: if abs(old_angle)<1E-10: raise ValueError('Atoms cannot be scaled; old wedge angle too small.') newr = [] for r in self.atoms.get_positions(): x,y = r[0],r[1] rad = np.sqrt( x**2+y**2 ) newphi = phival(x,y)*(self.get('angle')/old_angle) newr.append( [rad*np.cos(newphi),rad*np.sin(newphi),r[2]] ) self.atoms.set_positions(newr) if height!=None: if scale_atoms: r = self.atoms.get_positions() r[:,2] = r[:,2] * height/self.get('height') self.atoms.set_positions(r) self._set(height=height) if twist!=None: if scale_atoms: raise NotImplementedError('Atom rescale with twist not implemented.') self._set(twist=twist)
def set(self, angle=None, height=None, M=None, physical=True, twist=None, scale_atoms=False, container=None): """ parameters: =========== angle angle (in radians) of the wedge (and M=None) height Height of the primitive cell in z-direction M set angle to 2*pi/M (and angle=None) physical (only if M=None) if angle is small, it does not be exactly 2*pi/integer, i.e. situation has no physical meaning (use for calculating stuff continuously) twist The twist angle for z-translation scale_atoms Scale atoms according to changes in parameters """ if container != None: assert angle == None and height == None and M == None and twist == None self.set(angle=container.get('angle'),height=container.get('height'),\ physical=container.get('physical'), twist=container.get('twist')) if angle != None or M != None: #assert not scale_atoms assert not (angle != None and M != None) old_angle = self.get('angle') if M != None: assert isinstance(M, int) self._set(angle=2 * np.pi / M) elif angle != None: M = np.abs(int(round(2 * np.pi / angle))) self._set(angle=angle) # check parameters self._set(physical=float(physical)) if np.abs(self.get('angle')) < 1E-6: raise Warning( 'Too small angle (%f) may bring numerical problems.' % self.get('angle')) if self.get('angle') > np.pi: raise AssertionError('angle>pi') if np.abs(M - 2 * np.pi / np.abs(self.get('angle')) ) > 1E-12 and self.get('physical'): raise AssertionError('angle not physical: angle != 2*pi/M') if not self.get('physical') and M < 20: warnings.warn('Quite large, non-physical angle 2*pi/%.4f.' % (2 * np.pi / self.get('angle'))) if scale_atoms: if abs(old_angle) < 1E-10: raise ValueError( 'Atoms cannot be scaled; old wedge angle too small.') newr = [] for r in self.atoms.get_positions(): x, y = r[0], r[1] rad = np.sqrt(x**2 + y**2) newphi = phival(x, y) * (self.get('angle') / old_angle) newr.append( [rad * np.cos(newphi), rad * np.sin(newphi), r[2]]) self.atoms.set_positions(newr) if height != None: if scale_atoms: r = self.atoms.get_positions() r[:, 2] = r[:, 2] * height / self.get('height') self.atoms.set_positions(r) self._set(height=height) if twist != None: if scale_atoms: raise NotImplementedError( 'Atom rescale with twist not implemented.') self._set(twist=twist)
def set(self, angle=None, height=None, M=None, physical=True, pbcz=None, scale_atoms=False, container=None): """ Only height can be reset, not angle. parameters: =========== angle angle (in radians) of the wedge (and M=None) height Height of the primitive cell in z-direction M set angle to 2*pi/M (and angle=None) physical (only if M=None) if angle is small, it does not be exactly 2*pi/integer, i.e. situation has no physical meaning (use for calculating stuff continuously) pbcz True if wedge is periodic in z-direction scale_atoms Scale atoms according to changes in parameters. When changing angle, scale also radial distances. (Radii are inversely proportional to angle.) """ if container!=None: assert angle==None and height==None and M==None and pbcz==None self.set(angle=container.get('angle'),height=container.get('height'),\ physical=container.get('physical'), pbcz=container.atoms.get_pbc()[2]) if angle!=None or M!=None: #assert not scale_atoms assert not (angle!=None and M!=None) old_angle = self.get('angle') if M != None: assert isinstance(M,int) self._set(angle=2*np.pi/M) elif angle != None: M = int( round(2*np.pi/angle) ) self._set(angle=angle) # check parameters self._set( physical=float(physical) ) if self.get('angle')<1E-6: raise Warning('Too small angle (%f) may bring numerical problems.' %self.get('angle')) if self.get('angle')>np.pi: raise AssertionError('angle>pi') if np.abs(M-2*np.pi/self.get('angle'))>1E-12 and self.get('physical'): raise AssertionError('angle not physical: angle != 2*pi/M') if not self.get('physical') and M<20: warnings.warn('Quite large, non-physical angle 2*pi/%.4f.' %(2*np.pi/self.get('angle')) ) if scale_atoms: if abs(old_angle)<1E-10: raise ValueError('Atoms cannot be scaled; old wedge angle too small.') newr = [] for r in self.atoms.get_positions(): x,y = r[0],r[1] rad = np.sqrt( x**2+y**2 ) newphi = phival(x,y)*(self.get('angle')/old_angle) rad2 = rad * old_angle/self.get('angle') newr.append( [rad2*np.cos(newphi),rad2*np.sin(newphi),r[2]] ) self.atoms.set_positions(newr) if height!=None: if scale_atoms: r = self.atoms.get_positions() r[:,2] = r[:,2] * height/self.get('height') self.atoms.set_positions(r) self._set(height=height) if pbcz!=None: self._set(pbcz=float(pbcz))