Source code for gyroid.basis

# -*- coding: utf-8 -*-
"""
gyroid.basis
===============

:copyright: (c) 2012 by Yi-Xin Liu
:license: BSD, see LICENSE for more details.

"""

import numpy as np
from numpy.linalg import inv

from .common import BRAVAIS,CARTESIAN,EPS

__all__ = ["Basis","Star"]

[docs]class Basis(object): """ """ def __init__(self,group,grid): self.dim = group.dim self.shape = group.shape self.stars = [] self.N = 0 G2_pre = grid.Gsq[0] # Previous G2 for G2 in grid.Gsq: if np.abs(G2-G2_pre) > EPS: s = Star(group,grid,G2_pre) self.stars.append(s) if s.iwaves is None: self.N += 1 else: self.N += 2 G2_pre = G2
[docs] def generate_structure(self,real_grid,c): """ real_grid is a tuple contains the discrete numbers for a, b, and c unit vectors in real space. c is an array contains the coefficients for each basis. """ if np.size(real_grid) != self.dim: raise ValueError("Dimension not match when generating struct.") if np.size(c) == 1: cc = c c = np.zeros(self.N) c = cc elif np.size(c) != self.N: raise ValueError("No. of Bases and coefficients not match when generating structure.") struct = np.zeros(real_grid) for ind,v in np.ndenumerate(struct): # numpy.ndarray can divide tuple x = ind * self.shape.l / real_grid i = 0 for s in self.stars: f1,f2 = s.f(x,self.shape) struct[ind] += f1 i += 1 if f2 is not None: struct[ind] += f2 i += 1 return struct
[docs]class Star(object): """ A StarSet is a collection of stars with all waves have same magnitude. """ def __init__(self,group,grid,Gsq): if self.__check_cancel(): raise ValueError("Check cancel failed when creating a Star.") self.dim = group.dim self.Gsq = Gsq waves = self.__select_waves(grid,Gsq) sorted_waves,phases = self.__sort_waves(waves) (self.waves,phases,self.iwaves,iphases) = self.__find_waves_in_star( group,grid,sorted_waves) self.N = np.size(self.waves,1) self.c, self.ic = self.__find_coeff(phases,iphases) if self.iwaves is None: self.sign = self.__set_coeff_for_closed_star(grid)
[docs] def f(self,x,shape): """ the value of basis function f(r) at position r. r is a Bravais type real space vector. """ if self.iwaves is None: f1 = self.__f(self.waves,self.c,x,shape) return (f1.real,None) else: v1 = self.__f(self.waves,self.c,x,shape) v2 = self.__f(self.iwaves,self.ic,x,shape) f1 = (v1 + v2) / np.sqrt(2.0) f2 = complex(0.0,1.0) * (v1 - v2) / np.sqrt(2.0) return (f1.real,f2.real)
def __f(self,waves,c,x,shape): f = 0 i = 0 for G in waves.T: gr = np.dot(np.dot(G,shape.g),np.dot(x,shape.h)) f += c[i] * np.exp(complex(0.0,1.0) * gr) i += 1 return f def __select_waves(self,grid,G2): (ind,) = np.where(np.abs(grid.Gsq-G2)<EPS) if np.max(ind) - np.min(ind) + 1 != np.size(ind): raise ValueError("Waves in Grid not sorted according to G^2.") return grid.waves[:,ind] def __check_cancel(self): """ Not implemented yet. Since we have excluded the cancel waves in creating Grid.waves. Currently, we do not support canceled stars. """ return False def __sort_waves(self,waves,phases=None): if self.dim == 1: if phases is None: if np.size(waves,1) == 1: return waves,None return (np.fliplr(np.sort(waves)),None) else: pw = np.vstack([phases,waves]) ind = np.lexsort(pw) pw_sorted = np.fliplr(pw.take(ind,axis=-1)) return (np.array([pw_sorted[1]]),pw_sorted[0]) if self.dim == 2: if phases is None: rw = np.vstack([waves[1],waves[0]]) ind = np.lexsort(rw) return (np.fliplr(waves.take(ind,axis=-1)),None) else: prw = np.vstack([phases,waves[1],waves[0]]) ind = np.lexsort(prw) prw_sorted = np.fliplr(prw.take(ind,axis=-1)) return (np.vstack([prw_sorted[2],prw_sorted[1]]), prw_sorted[0]) if self.dim == 3: if phases is None: rw = np.vstack([waves[2],waves[1],waves[0]]) ind = np.lexsort(rw) return (np.fliplr(waves.take(ind,axis=-1)),None) else: prw = np.vstack([phases,waves[2],waves[1],waves[0]]) ind = np.lexsort(prw) prw_sorted = np.fliplr(prw.take(ind,axis=-1)) return (np.vstack([ prw_sorted[3],prw_sorted[2],prw_sorted[1]]), prw_sorted[0]) # Following code is a shortcut but hard to read # ind = np.lexsort(waves.T) # return np.fliplr(np.fliplr(waves.T.take(ind,axis=-1)).T) def __calc_phase(self,G,t,basis_type): twopi = 2.0 * np.pi if basis_type == BRAVAIS: return twopi * np.round(np.dot(G,t)).astype(type(G[0])) else: return twopi * np.dot(G,t) def __calc_wave(self,G,R,basis_type): if basis_type == BRAVAIS: return np.round(np.dot(G,R)).astype(type(G[0])) else: return np.dot(G,R) def __form_star(self,G,group,grid,waves): star_waves = None phases = None for i in np.arange(group.order): Gn = self.__calc_wave(G,group.symm[i].R,group.type) # Pseudo-Spectral method Gn = grid.to_BZ(Gn) if index_waves(Gn,waves.T) is not None: if star_waves is None: star_waves = np.array([Gn]) ph = self.__calc_phase(G,group.symm[i].t,group.type) phases = np.array([ph]) else: if index_waves(Gn,star_waves) is None: star_waves = np.append(star_waves,[Gn],axis=0) ph = self.__calc_phase(G,group.symm[i].t,group.type) phases = np.append(phases,[ph],axis=0) else: raise ValueError("Waves does not contain entire star.") return star_waves.T,phases def __find_waves_in_star(self,g,grid,waves): """ For waves with a same |G|^2, they may form a closed star, two open stars, or several closed stars. """ G1 = waves[:,0] star_waves, phases = self.__form_star(G1,g,grid,waves) star_waves, phases = self.__sort_waves(star_waves,phases) Gi = -1.0 * G1 Gi = grid.to_BZ(Gi) if index_waves(Gi,star_waves.T) is not None: if np.size(waves,1) != np.size(star_waves,1): raise ValueError("Closed star expected.") return star_waves,phases,None,None else: invert_waves, invert_phases = self.__form_star(Gi,g,grid,waves) invert_waves, invert_phases = self.__sort_waves( invert_waves,invert_phases) l1 = np.size(star_waves,1) l2 = np.size(invert_waves,1) if np.size(waves,1) != (l1 + l2) or l1 != l2: raise ValueError("Open star pair expected.") return star_waves,phases,invert_waves,invert_phases def __find_coeff(self,phases,iphases): if iphases is None: # c_norm = exp(i*phi) c_norm = np.exp(complex(0.0,phases[0])) * np.sqrt(self.N) return ([np.exp(complex(0.0,phases[i]))/c_norm for i in np.arange(self.N)], None) else: c_norm = np.exp(complex(0.0,phases[0])) * np.sqrt(self.N) ic_norm = np.exp(complex(0.0,phases[self.N-1]))*np.sqrt(self.N) return ([np.exp(complex(0.0,phases[i]))/c_norm for i in np.arange(self.N)], [np.exp(complex(0.0,iphases[i]))/ic_norm for i in np.arange(self.N)] ) def __set_coeff_for_closed_star(self,grid): """ For an ordered closed star, if we denote the first wave in the star G1, then its inversion (-G1) must be the last wave in the star. """ G = self.waves[:,0] Gi = -1.0 * G Gi = grid.to_BZ(Gi) # find index of Gi in the star i = index_waves(Gi,self.waves.T) c, ci = self.c[0], self.c[i] if np.abs(c.imag) < EPS: c1 = c.real else: raise ValueError("""First coefficient in closed star has imaginary part.""") if np.abs(ci.imag) < EPS: c2 = ci.real else: raise ValueError("""Last coefficient in closed star has imaginary part.""") if np.abs(c1 - c2) < EPS: return 1 elif np.abs(c1 + c2) < EPS: self.c = self.c * complex(0.0,-1.0) return -1 else: raise ValueError("""Closed star is neither cosine-like nor sine-like.""") # for inversion star pairs, the first star's sign is +1 # the second is -1
def index_waves(w,waves): """ w is a row vector. waves is a 2D array, each row is a row vector. """ if np.size(w) != np.size(waves,1): return None i = 0 for ww in waves: if np.all(np.abs(ww-w) < EPS): return i i += 1 return None