mirror of https://github.com/QMCPACK/qmcpack.git
4232 lines
137 KiB
Python
Executable File
4232 lines
137 KiB
Python
Executable File
##################################################################
|
|
## (c) Copyright 2015- by Jaron T. Krogel ##
|
|
##################################################################
|
|
|
|
#! /usr/bin/env python
|
|
|
|
import os
|
|
from copy import deepcopy
|
|
from numpy import array,floor,empty,dot,diag,sqrt,pi,mgrid,exp,append,arange,ceil,cross,cos,sin,identity,ndarray,atleast_2d,around,ones,zeros,logical_not,flipud
|
|
from numpy.linalg import inv,det,norm
|
|
from types import NoneType
|
|
from unit_converter import convert
|
|
from extended_numpy import nearest_neighbors,convex_hull,voronoi_neighbors
|
|
from periodic_table import pt
|
|
from generic import obj
|
|
from developer import DevBase,unavailable,error,warn
|
|
from debug import ci,ls,gs
|
|
|
|
|
|
try:
|
|
from scipy.special import erfc
|
|
except ImportError:
|
|
erfc = unavailable('scipy.special','erfc')
|
|
#end try
|
|
try:
|
|
import matplotlib.pyplot as plt
|
|
from matplotlib.pyplot import plot,subplot,title,xlabel,ylabel
|
|
except (ImportError,RuntimeError):
|
|
plot,subplot,title,xlabel,ylabel,plt = unavailable('matplotlib.pyplot','plot','subplot','title','xlabel','ylabel','plt')
|
|
#end try
|
|
|
|
|
|
|
|
def equate(expr):
|
|
return expr
|
|
#end def equate
|
|
|
|
def negate(expr):
|
|
return not expr
|
|
#end def negate
|
|
|
|
|
|
|
|
def kmesh(kaxes,dim,shift=None):
|
|
'''
|
|
Create a Monkhorst-Pack k-point mesh
|
|
'''
|
|
if shift is None:
|
|
shift = (0.,0,0)
|
|
#end if
|
|
ndim = len(dim)
|
|
d = array(dim)
|
|
s = array(shift)
|
|
s.shape = 1,ndim
|
|
d.shape = 1,ndim
|
|
kp = empty((1,ndim),dtype=float)
|
|
kgrid = empty((d.prod(),ndim))
|
|
n=0
|
|
for k in range(dim[2]):
|
|
for j in range(dim[1]):
|
|
for i in range(dim[0]):
|
|
kp[:] = i,j,k
|
|
kp = dot((kp+s)/d,kaxes)
|
|
#kp = (kp+s)/d
|
|
kgrid[n] = kp
|
|
n+=1
|
|
#end for
|
|
#end for
|
|
#end for
|
|
return kgrid
|
|
#end def kmesh
|
|
|
|
|
|
def tile_points(points,tilevec,axin):
|
|
if not isinstance(points,ndarray):
|
|
points = array(points)
|
|
#end if
|
|
if not isinstance(tilevec,ndarray):
|
|
tilevec = array(tilevec)
|
|
#end if
|
|
if not isinstance(axin,ndarray):
|
|
axin = array(axin)
|
|
#end if
|
|
t = tilevec
|
|
if len(points.shape)==1:
|
|
npoints,dim = len(points),1
|
|
else:
|
|
npoints,dim = points.shape
|
|
#end if
|
|
ntpoints = npoints*t.prod()
|
|
if ntpoints==0:
|
|
tpoints = array([])
|
|
else:
|
|
tpoints = empty((ntpoints,dim))
|
|
ns=0
|
|
ne=npoints
|
|
for k in range(t[2]):
|
|
for j in range(t[1]):
|
|
for i in range(t[0]):
|
|
v = dot(array([[i,j,k]]),axin)
|
|
for d in range(dim):
|
|
tpoints[ns:ne,d] = points[:,d]+v[0,d]
|
|
#end for
|
|
ns+=npoints
|
|
ne+=npoints
|
|
#end for
|
|
#end for
|
|
#end for
|
|
#end if
|
|
axes = dot(diag(t),axin)
|
|
return tpoints,axes
|
|
#end def tile_points
|
|
|
|
|
|
def reduce_tilematrix(tiling):
|
|
tiling = array(tiling)
|
|
t = array(tiling,dtype=int)
|
|
if abs(tiling-t).sum()>1e-6:
|
|
Structure.class_error('requested tiling is non-integer\n tiling requested: '+str(tiling))
|
|
#end if
|
|
|
|
dim = len(t)
|
|
matrix_tiling = t.shape == (dim,dim)
|
|
if matrix_tiling:
|
|
#find a tiling tuple from the tiling matrix
|
|
# do this by shearing the tiling matrix (or equivalently the tiled cell)
|
|
# until it is orthogonal (in the untiled cell axes)
|
|
# this is just rearranging triangular tiles of space to reshape the cell
|
|
# so that t1*t2*t3 = det(T) = det(A_tiled)/det(A_untiled)
|
|
#this way the atoms in the (perhaps oddly shaped) supercell can be
|
|
# obtained from simple translations of the untiled cell positions
|
|
T = t #tiling matrix
|
|
tilematrix = T.copy()
|
|
del t
|
|
Tnew = array(T,dtype=float) #sheared/orthogonal tiling matrix
|
|
tbar = identity(dim) #basis for shearing
|
|
dr = range(dim)
|
|
other = [] # other[d] = dimensions other than d
|
|
for d in dr:
|
|
other.append(set(dr)-set([d]))
|
|
#end for
|
|
#move each axis to be parallel to barred directions
|
|
# these are volume preserving shears of the supercell
|
|
# each shear keeps two cell face planes fixed while moving the others
|
|
for d in dr:
|
|
tb = tbar[d]
|
|
t = T[d]
|
|
d2,d3 = other[d]
|
|
n = cross(Tnew[d2],Tnew[d3]) #vector normal to 2 cell faces
|
|
tn = dot(n,t)*1./dot(n,tb)*tb #new axis vector
|
|
Tnew[d] = tn
|
|
#end for
|
|
#the resulting tiling matrix should be diagonal and integer
|
|
tr = diag(Tnew)
|
|
t = array(around(tr),dtype=int)
|
|
nondiagonal = abs(Tnew-diag(tr)).sum()>1e-6
|
|
noninteger = abs(tr-t).sum()>1e-6
|
|
if nondiagonal:
|
|
Structure.class_error('could not find a diagonal tiling matrix for generating tiled coordinates')
|
|
#end if
|
|
if noninteger:
|
|
Structure.class_error('calculated diagonal tiling matrix is non-integer\n tiled coordinates cannot be determined')
|
|
#end if
|
|
tilevector = abs(t)
|
|
else:
|
|
tilevector = t
|
|
tilematrix = diag(t)
|
|
#end if
|
|
|
|
return tilematrix,tilevector
|
|
#end def reduce_tilematrix
|
|
|
|
|
|
def tile_magnetization(mag,tilevec,mag_order,mag_prim):
|
|
# jtk mark current
|
|
# implement true magnetic tiling based on the magnetic order
|
|
# Structure needs a function magnetic_period which takes magnetic order
|
|
# and translates it into a magnetic period tuple
|
|
# magnetic_period should divide evenly into the tiling vector
|
|
# the magnetic unit cell is self.tile(magnetic_period)
|
|
# re-representing the folded cell as the magnetic primitive cell
|
|
# requires different axes, often with a non-diagonal tiling matrix
|
|
# if magnetic primitive is requested, a small tiling vector should first be used
|
|
# (ie 221,212,122,222 periods all involve a 211 prim tiling w/ a simple reshaping/reassignment of the cell axes
|
|
# Structure should have a function providing labels to each magnetic species
|
|
mag = array(tilevec.prod()*list(mag),dtype=object)
|
|
return mag
|
|
#end def tile_magnetization
|
|
|
|
|
|
|
|
|
|
class Sobj(DevBase):
|
|
None
|
|
#end class Sobj
|
|
|
|
|
|
|
|
class Structure(Sobj):
|
|
|
|
operations = obj()
|
|
|
|
@classmethod
|
|
def set_operations(cls):
|
|
cls.operations.set(
|
|
remove_folded_structure = cls.remove_folded_structure,
|
|
recenter = cls.recenter,
|
|
)
|
|
#end def set_operations
|
|
|
|
|
|
def __init__(self,axes=None,scale=1.,elem=None,pos=None,mag=None,
|
|
center=None,kpoints=None,kweights=None,kgrid=None,kshift=None,
|
|
permute=None,units=None,tiling=None,rescale=True,dim=3,
|
|
magnetization=None,magnetic_order=None,magnetic_prim=True,
|
|
operations=None,background_charge=0,frozen=None,bconds=None,
|
|
posu=None):
|
|
if center is None:
|
|
if axes !=None:
|
|
center = array(axes).sum(0)/2
|
|
else:
|
|
center = dim*[0]
|
|
#end if
|
|
#end if
|
|
if bconds is None:
|
|
bconds = dim*['p']
|
|
#end if
|
|
if axes is None:
|
|
axes = []
|
|
bconds = []
|
|
#end if
|
|
if elem is None:
|
|
elem = []
|
|
#end if
|
|
if posu!=None:
|
|
pos = posu
|
|
#end if
|
|
if pos is None:
|
|
pos = empty((0,dim))
|
|
#end if
|
|
if kshift is None:
|
|
kshift = 0,0,0
|
|
#end if
|
|
if mag is None:
|
|
mag = len(elem)*[None]
|
|
#end if
|
|
self.scale = 1.
|
|
self.units = units
|
|
self.dim = dim
|
|
self.center = array(center)
|
|
self.axes = array(axes)
|
|
self.bconds = array(bconds,dtype=str)
|
|
self.set_elem(elem)
|
|
self.pos = array(pos)
|
|
self.frozen = None
|
|
self.mag = array(mag,dtype=object)
|
|
self.kpoints = empty((0,dim))
|
|
self.kweights = empty((0,))
|
|
self.background_charge = background_charge
|
|
self.remove_folded_structure()
|
|
if len(axes)==0:
|
|
self.kaxes=array([])
|
|
else:
|
|
self.kaxes=2*pi*inv(self.axes).T
|
|
#end if
|
|
if posu!=None:
|
|
self.pos_to_cartesian()
|
|
#end if
|
|
if frozen!=None:
|
|
self.frozen = array(frozen,dtype=bool)
|
|
if self.frozen.shape!=self.pos.shape:
|
|
self.error('frozen directions must have the same shape as positions\n positions shape: {0}\n frozen directions shape: {1}'.format(self.pos.shape,self.frozen.shape))
|
|
#end if
|
|
#end if
|
|
self.magnetize(magnetization)
|
|
if tiling!=None:
|
|
self.tile(tiling,
|
|
in_place = True,
|
|
magnetic_order = magnetic_order,
|
|
magnetic_prim = magnetic_prim
|
|
)
|
|
#end if
|
|
if kpoints!=None:
|
|
self.add_kpoints(kpoints,kweights)
|
|
#end if
|
|
if kgrid!=None:
|
|
self.add_kmesh(kgrid,kshift)
|
|
#end if
|
|
if rescale:
|
|
self.rescale(scale)
|
|
else:
|
|
self.scale = scale
|
|
#end if
|
|
if permute!=None:
|
|
self.permute(permute)
|
|
#end if
|
|
if operations!=None:
|
|
self.operate(operations)
|
|
#end if
|
|
#end def __init__
|
|
|
|
|
|
def set_elem(self,elem):
|
|
self.elem = array(elem,dtype=object)
|
|
#end def set_elem
|
|
|
|
|
|
def size(self):
|
|
return len(self.elem)
|
|
#end def size
|
|
|
|
|
|
def operate(self,operations):
|
|
for op in operations:
|
|
if not op in self.operations:
|
|
self.error('{0} is not a known operation\n valid options are:\n {1}'.format(op,list(self.operations.keys())))
|
|
else:
|
|
self.operations[op](self)
|
|
#end if
|
|
#end for
|
|
#end def operate
|
|
|
|
|
|
def set_folded(self,folded):
|
|
self.set_folded_structure(folded)
|
|
#end def set_folded
|
|
|
|
|
|
def remove_folded(self):
|
|
self.remove_folded_structure()
|
|
#end def remove_folded
|
|
|
|
|
|
def set_folded_structure(self,folded):
|
|
self.folded_structure = folded
|
|
self.tmatrix = self.tilematrix(folded)
|
|
#end def set_folded_structure
|
|
|
|
|
|
def remove_folded_structure(self):
|
|
self.folded_structure = None
|
|
self.tmatrix = None
|
|
#end def remove_folded_structure
|
|
|
|
|
|
def group_atoms(self,folded=True):
|
|
if len(self.elem)>0:
|
|
order = self.elem.argsort()
|
|
if (self.elem!=self.elem[order]).any():
|
|
self.elem = self.elem[order]
|
|
self.pos = self.pos[order]
|
|
#end if
|
|
#end if
|
|
if self.folded_structure!=None and folded:
|
|
self.folded_structure.group_atoms(folded)
|
|
#end if
|
|
#end def group_atoms
|
|
|
|
|
|
def rename(self,folded=True,**name_pairs):
|
|
elem = self.elem
|
|
for old,new in name_pairs.iteritems():
|
|
for i in xrange(len(self.elem)):
|
|
if old==elem[i]:
|
|
elem[i] = new
|
|
#end if
|
|
#end for
|
|
#end for
|
|
if self.folded_structure!=None and folded:
|
|
self.folded_structure.rename(folded=folded,**name_pairs)
|
|
#end if
|
|
#end def rename
|
|
|
|
|
|
def reset_axes(self,axes=None):
|
|
if axes is None:
|
|
axes = self.axes
|
|
else:
|
|
axes = array(axes)
|
|
self.remove_folded_structure()
|
|
#end if
|
|
self.axes = axes
|
|
self.kaxes = 2*pi*inv(axes).T
|
|
self.center = axes.sum(0)/2
|
|
#end def reset_axes
|
|
|
|
|
|
def adjust_axes(self,axes):
|
|
self.skew(dot(inv(self.axes),axes))
|
|
#end def adjust_axes
|
|
|
|
|
|
def reshape_axes(self,reshaping):
|
|
R = array(reshaping)
|
|
if abs(abs(det(R))-1)<1e-6:
|
|
self.axes = dot(self.axes,R)
|
|
else:
|
|
R = dot(inv(self.axes),R)
|
|
if abs(abs(det(R))-1)<1e-6:
|
|
self.axes = dot(self.axes,R)
|
|
else:
|
|
self.error('reshaping matrix must not change the volume\n reshaping matrix:\n {0}\n volume change ratio: {1}'.format(R,abs(det(R))))
|
|
#end if
|
|
#end if
|
|
#end def reshape_axes
|
|
|
|
|
|
def bounding_box(self,scale=1.0,box='tight',recenter=False):
|
|
pmin = self.pos.min(0)
|
|
pmax = self.pos.max(0)
|
|
pcenter = (pmax+pmin)/2
|
|
prange = pmax-pmin
|
|
if box=='tight':
|
|
axes = diag(prange)
|
|
elif box=='cubic' or box=='cube':
|
|
prmax = prange.max()
|
|
axes = diag((prmax,prmax,prmax))
|
|
elif isinstance(box,ndarray) or isinstance(box,list):
|
|
box = array(box)
|
|
if box.shape!=(3,3):
|
|
self.error('requested box must be 3-dimensional (3x3 axes)\n you provided: '+str(box)+'\n shape: '+str(box.shape))
|
|
#end if
|
|
binv = inv(box)
|
|
pu = dot(self.pos,binv)
|
|
pmin = pu.min(0)
|
|
pmax = pu.max(0)
|
|
pcenter = (pmax+pmin)/2
|
|
prange = pmax-pmin
|
|
axes = dot(diag(prange),box)
|
|
else:
|
|
self.error("invalid request for box\n valid options are 'tight', 'cubic', or axes array (3x3)\n you provided: "+str(box))
|
|
#end if
|
|
self.reset_axes(scale*axes)
|
|
self.slide(self.center-pcenter,recenter)
|
|
#end def bounding_box
|
|
|
|
|
|
def center_molecule(self):
|
|
self.slide(self.center-self.pos.mean(0),recenter=False)
|
|
#end def center_molecule
|
|
|
|
|
|
def center_solid(self):
|
|
u = self.pos_unit()
|
|
du = (1-u.min(0)-u.max(0))/2
|
|
self.slide(dot(du,self.axes),recenter=False)
|
|
#end def center_solid
|
|
|
|
|
|
def permute(self,permutation):
|
|
dim = self.dim
|
|
P = empty((dim,dim),dtype=int)
|
|
if len(permutation)!=dim:
|
|
self.error(' permutation vector must have {0} elements\n you provided {1}'.format(dim,permutation))
|
|
#end if
|
|
for i in range(dim):
|
|
p = permutation[i]
|
|
pv = zeros((dim,),dtype=int)
|
|
if p=='x' or p=='0':
|
|
pv[0] = 1
|
|
elif p=='y' or p=='1':
|
|
pv[1] = 1
|
|
elif p=='z' or p=='2':
|
|
pv[2] = 1
|
|
#end if
|
|
P[:,i] = pv[:]
|
|
#end for
|
|
self.center = dot(self.center,P)
|
|
if len(self.axes)>0:
|
|
self.axes = dot(self.axes,P)
|
|
#end if
|
|
if len(self.pos)>0:
|
|
self.pos = dot(self.pos,P)
|
|
#end if
|
|
if len(self.kaxes)>0:
|
|
self.kaxes = dot(self.kaxes,P)
|
|
#end if
|
|
if len(self.kpoints)>0:
|
|
self.kpoints = dot(self.kpoints,P)
|
|
#end if
|
|
if self.folded_structure!=None:
|
|
self.folded_structure.permute(permutation)
|
|
#end if
|
|
#end def permute
|
|
|
|
|
|
def upcast(self,DerivedStructure):
|
|
if not issubclass(DerivedStructure,Structure):
|
|
self.error(DerivedStructure.__name__,'is not derived from Structure')
|
|
#end if
|
|
ds = DerivedStructure()
|
|
for name,value in self.iteritems():
|
|
ds[name] = deepcopy(value)
|
|
#end for
|
|
return ds
|
|
#end def upcast
|
|
|
|
|
|
def incorporate(self,other):
|
|
self.set_elem(list(self.elem)+list(other.elem))
|
|
self.pos = array(list(self.pos )+list(other.pos ))
|
|
#end def incorporate
|
|
|
|
|
|
def distances(self,pos1=None,pos2=None):
|
|
if isinstance(pos1,Structure):
|
|
pos1 = pos1.pos
|
|
#end if
|
|
if pos2==None:
|
|
if pos1==None:
|
|
return sqrt((self.pos**2).sum(1))
|
|
else:
|
|
pos2 = self.pos
|
|
#end if
|
|
#end if
|
|
if len(pos1)!=len(pos2):
|
|
self.error('positions arrays are not the same length')
|
|
#end if
|
|
return sqrt(((pos1-pos2)**2).sum(1))
|
|
#end def distances
|
|
|
|
|
|
def volume(self):
|
|
if len(self.axes)==0:
|
|
return None
|
|
else:
|
|
return abs(det(self.axes))
|
|
#end if
|
|
#end def volume
|
|
|
|
|
|
def rwigner(self):
|
|
if self.dim!=3:
|
|
self.error('rwigner is currently only implemented for 3 dimensions')
|
|
#end if
|
|
rmin = 1e90
|
|
n=empty((1,3))
|
|
for k in range(-1,2):
|
|
for j in range(-1,2):
|
|
for i in range(-1,2):
|
|
if i!=0 or j!=0 or k!=0:
|
|
n[:] = i,j,k
|
|
rmin = min(rmin,.5*norm(dot(n,self.axes)))
|
|
#end if
|
|
#end for
|
|
#end for
|
|
#end for
|
|
return rmin
|
|
#end def rwigner
|
|
|
|
|
|
def rinscribe(self):
|
|
if self.dim!=3:
|
|
self.error('rinscribe is currently only implemented for 3 dimensions')
|
|
#end if
|
|
radius = 1e99
|
|
dim=3
|
|
axes=self.axes
|
|
for d in range(dim):
|
|
i = d
|
|
j = (d+1)%dim
|
|
rc = cross(axes[i,:],axes[j,:])
|
|
radius = min(radius,.5*abs(det(axes))/norm(rc))
|
|
#end for
|
|
return radius
|
|
#end def rinscribe
|
|
|
|
|
|
def rmin(self):
|
|
return self.rwigner()
|
|
#end def rmin
|
|
|
|
|
|
def rcell(self):
|
|
return self.rinscribe()
|
|
#end def rcell
|
|
|
|
|
|
# apply volume preserving shear-removing transformations to cell axes
|
|
# resulting unsheared cell has orthogonal axes
|
|
# while remaining periodically correct
|
|
# note that the unshearing procedure is not unique
|
|
# it depends on the order of unshearing operations
|
|
def unsheared_axes(self,distances=False):
|
|
if self.dim!=3:
|
|
self.error('rinscribe is currently only implemented for 3 dimensions')
|
|
#end if
|
|
dim=3
|
|
axbar = identity(dim)
|
|
axnew = array(axes,dtype=float)
|
|
dists = empty((dim,))
|
|
for d in range(dim):
|
|
d2 = (d+1)%dim
|
|
d3 = (d+2)%dim
|
|
n = cross(axnew[d2],axnew[d3]) #vector normal to 2 cell faces
|
|
axdist = dot(n,axes[d])/dot(n,axbar[d])
|
|
axnew[d] = axdist*axbar[d]
|
|
dists[d] = axdist
|
|
#end for
|
|
if not distances:
|
|
return axnew
|
|
else:
|
|
return axnew,dists
|
|
#end if
|
|
#end def unsheared_axes
|
|
|
|
|
|
# vectors parallel to cell faces
|
|
# length of vectors is distance between parallel face planes
|
|
# note that the product of distances is not the cell volume in general
|
|
# see "unsheared_axes" function
|
|
# (e.g. a volume preserving shear may bring two face planes arbitrarily close)
|
|
def face_vectors(self,axes=None,distances=False):
|
|
if axes is None:
|
|
axes = self.axes
|
|
#end if
|
|
fv = inv(axes).T
|
|
for d in range(len(fv)):
|
|
fv[d] /= norm(fv[d]) # face normals
|
|
#end for
|
|
dv = dot(axes,fv.T) # axis projections onto face normals
|
|
fv = dot(dv,fv) # face normals lengthened by plane separation
|
|
if not distances:
|
|
return fv
|
|
else:
|
|
return fv,diag(dv)
|
|
#end if
|
|
#end def face_vectors
|
|
|
|
|
|
def face_distances(self):
|
|
return self.face_vectors(distances=True)[1]
|
|
#end def face_distances
|
|
|
|
|
|
def set_orig(self):
|
|
self.orig_pos = pos.copy()
|
|
#end def set_orig
|
|
|
|
|
|
def rescale(self,scale):
|
|
self.scale *= scale
|
|
self.axes *= scale
|
|
self.pos *= scale
|
|
self.center *= scale
|
|
self.kaxes /= scale
|
|
self.kpoints/= scale
|
|
if self.folded_structure!=None:
|
|
self.folded_structure.rescale(scale)
|
|
#end if
|
|
#end def rescale
|
|
|
|
|
|
def stretch(self,s1,s2,s3):
|
|
if self.dim!=3:
|
|
self.error('stretch is currently only implemented for 3 dimensions')
|
|
#end if
|
|
d = diag((s1,s2,s3))
|
|
self.skew(d)
|
|
#end def stretch
|
|
|
|
|
|
def skew(self,skew):
|
|
axinv = inv(self.axes)
|
|
axnew = dot(self.axes,skew)
|
|
kaxinv = inv(self.kaxes)
|
|
kaxnew = dot(inv(skew),self.kaxes)
|
|
self.pos = dot(dot(self.pos,axinv),axnew)
|
|
self.center = dot(dot(self.center,axinv),axnew)
|
|
self.kpoints = dot(dot(self.kpoints,kaxinv),kaxnew)
|
|
self.axes = axnew
|
|
self.kaxes = kaxnew
|
|
if self.folded_structure!=None:
|
|
self.folded_structure.skew(skew)
|
|
#end if
|
|
#end def skew
|
|
|
|
|
|
def change_units(self,units,folded=True):
|
|
if units!=self.units:
|
|
scale = convert(1,self.units,units)
|
|
self.scale *= scale
|
|
self.axes *= scale
|
|
self.pos *= scale
|
|
self.center *= scale
|
|
self.kaxes /= scale
|
|
self.kpoints/= scale
|
|
self.units = units
|
|
#end if
|
|
if self.folded_structure!=None and folded:
|
|
self.folded_structure.change_units(units,folded=folded)
|
|
#end if
|
|
#end def change_units
|
|
|
|
|
|
# insert sep space at loc along axis
|
|
# if sep<0, space is removed instead
|
|
def cleave(self,axis,loc,sep=None,remove=False,tol=1e-6):
|
|
self.remove_folded_structure()
|
|
if isinstance(axis,int):
|
|
if sep is None:
|
|
self.error('separation induced by cleave must be provided')
|
|
#end if
|
|
v = self.face_vectors()[axis]
|
|
if isinstance(loc,float):
|
|
c = loc*v/norm(v)
|
|
#end if
|
|
else:
|
|
v = axis
|
|
#end if
|
|
c = array(c) # point on cleave plane
|
|
v = array(v) # normal vector to cleave plane, norm is cleave separation
|
|
if sep!=None:
|
|
v = abs(sep)*v/norm(v)
|
|
#end if
|
|
if norm(v)<tol:
|
|
return
|
|
#end if
|
|
vn = array(v/norm(v))
|
|
if sep!=None and sep<0:
|
|
v = -v # preserve the normal direction for atom identification, but reverse the shift direction
|
|
#end if
|
|
self.recorner() # want box contents to be static
|
|
if len(self.axes)>0:
|
|
components = 0
|
|
dim = self.dim
|
|
axes = self.axes
|
|
for i in xrange(dim):
|
|
i2 = (i+1)%dim
|
|
i3 = (i+2)%dim
|
|
a2 = axes[i2]/norm(axes[i2])
|
|
a3 = axes[i3]/norm(axes[i3])
|
|
comp = abs(dot(a2,vn))+abs(dot(a3,vn))
|
|
if comp < 1e-6:
|
|
components+=1
|
|
iaxis = i
|
|
#end if
|
|
#end for
|
|
commensurate = components==1
|
|
if not commensurate:
|
|
self.error('cannot insert vacuum because cleave is incommensurate with the cell\n cleave plane must be parallel to a cell face')
|
|
#end if
|
|
a = self.axes[iaxis]
|
|
#self.axes[iaxis] = (1.+dot(v,a)/dot(a,a))*a
|
|
self.axes[iaxis] = (1.+dot(v,v)/dot(v,a))*a
|
|
#end if
|
|
indices = []
|
|
pos = self.pos
|
|
for i in xrange(len(pos)):
|
|
p = pos[i]
|
|
comp = dot(p-c,vn)
|
|
if comp>0 or abs(comp)<tol:
|
|
pos[i] += v
|
|
indices.append(i)
|
|
#end if
|
|
#end for
|
|
if remove:
|
|
self.remove(indices)
|
|
#end if
|
|
#end def cleave
|
|
|
|
|
|
def translate(self,v):
|
|
v = array(v)
|
|
pos = self.pos
|
|
for i in range(len(pos)):
|
|
pos[i]+=v
|
|
#end for
|
|
self.center+=v
|
|
if self.folded_structure!=None:
|
|
self.folded_structure.translate(v)
|
|
#end if
|
|
#end def translate
|
|
|
|
|
|
def slide(self,v,recenter=True):
|
|
v = array(v)
|
|
pos = self.pos
|
|
for i in range(len(pos)):
|
|
pos[i]+=v
|
|
#end for
|
|
if recenter:
|
|
self.recenter()
|
|
#end if
|
|
if self.folded_structure!=None:
|
|
self.folded_structure.slide(v,recenter)
|
|
#end if
|
|
#end def slide
|
|
|
|
|
|
def zero_corner(self):
|
|
corner = self.center-self.axes.sum(0)/2
|
|
self.translate(-corner)
|
|
#end def zero_corner
|
|
|
|
|
|
def locate_simple(self,pos):
|
|
pos = array(pos)
|
|
if pos.shape==(self.dim,):
|
|
pos = [pos]
|
|
#end if
|
|
nn = nearest_neighbors(1,self.pos,pos)
|
|
return nn.ravel()
|
|
#end def
|
|
|
|
|
|
def locate(self,identifiers,radii=None,exterior=False):
|
|
indices = None
|
|
if isinstance(identifiers,Structure):
|
|
cell = identifiers
|
|
indices = cell.inside(self.pos)
|
|
elif isinstance(identifiers,ndarray) and identifiers.dtype==bool:
|
|
indices = arange(len(self.pos))[identifiers]
|
|
elif isinstance(identifiers,int):
|
|
indices = [identifiers]
|
|
elif len(identifiers)>0 and isinstance(identifiers[0],int):
|
|
indices = identifiers
|
|
elif isinstance(identifiers,str):
|
|
atom = identifiers
|
|
indices = []
|
|
for i in xrange(len(self.elem)):
|
|
if self.elem[i]==atom:
|
|
indices.append(i)
|
|
#end if
|
|
#end for
|
|
elif len(identifiers)>0 and isinstance(identifiers[0],str):
|
|
indices = []
|
|
for atom in identifiers:
|
|
for i in xrange(len(self.elem)):
|
|
if self.elem[i]==atom:
|
|
indices.append(i)
|
|
#end if
|
|
#end for
|
|
#end for
|
|
#end if
|
|
if radii!=None or indices==None:
|
|
if indices is None:
|
|
pos = identifiers
|
|
else:
|
|
pos = self.pos[indices]
|
|
#end if
|
|
if isinstance(radii,float) or isinstance(radii,int):
|
|
radii = len(pos)*[radii]
|
|
elif radii!=None and len(radii)!=len(pos):
|
|
self.error('lengths of input radii and positions do not match\n len(radii)={0}\n len(pos)={1}'.format(len(radii),len(pos)))
|
|
#end if
|
|
dtable = self.min_image_distances(pos)
|
|
indices = []
|
|
if radii is None:
|
|
for i in range(len(pos)):
|
|
indices.append(dtable[i].argmin())
|
|
#end for
|
|
else:
|
|
ipos = arange(len(self.pos))
|
|
for i in range(len(pos)):
|
|
indices.extend(ipos[dtable[i]<radii[i]])
|
|
#end for
|
|
#end if
|
|
#end if
|
|
if exterior:
|
|
indices = list(set(range(len(self.pos)))-set(indices))
|
|
#end if
|
|
return indices
|
|
#end def locate
|
|
|
|
|
|
def freeze(self,identifiers,radii=None,exterior=False,negate=False,directions='xyz'):
|
|
if isinstance(identifiers,ndarray) and identifiers.shape==self.pos.shape and identifiers.dtype==bool:
|
|
if negate:
|
|
self.frozen = ~identifiers
|
|
else:
|
|
self.frozen = identifiers.copy()
|
|
#end if
|
|
return
|
|
#end if
|
|
|
|
indices = self.locate(identifiers,radii,exterior)
|
|
if len(indices)==0:
|
|
self.error('failed to select any atoms to freeze')
|
|
#end if
|
|
if isinstance(directions,str):
|
|
d = empty((3,),dtype=bool)
|
|
d[0] = 'x' in directions
|
|
d[1] = 'y' in directions
|
|
d[2] = 'z' in directions
|
|
directions = len(indices)*[d]
|
|
else:
|
|
directions = array(directions,dtype=bool)
|
|
#end if
|
|
if self.frozen is None:
|
|
self.frozen = zeros(self.pos.shape,dtype=bool)
|
|
#end if
|
|
frozen = self.frozen
|
|
i=0
|
|
if not negate:
|
|
for index in indices:
|
|
frozen[index] = directions[i]
|
|
i+=1
|
|
#end for
|
|
else:
|
|
for index in indices:
|
|
frozen[index] = directions[i]==False
|
|
i+=1
|
|
#end for
|
|
#end if
|
|
#end def freeze
|
|
|
|
|
|
def magnetize(self,identifiers=None,magnetization='',**mags):
|
|
magsin = None
|
|
if isinstance(identifiers,obj):
|
|
magsin = identifiers.copy()
|
|
elif isinstance(magnetization,obj):
|
|
magsin = magnetization.copy()
|
|
#endif
|
|
if magsin!=None:
|
|
magsin.transfer_from(mags)
|
|
mags = magsin
|
|
identifiers = None
|
|
magnetization = ''
|
|
#end if
|
|
for e,m in mags.iteritems():
|
|
if not e in self.elem:
|
|
self.error('cannot magnetize non-existent element {0}'.format(e))
|
|
elif not isinstance(m,(NoneType,int)):
|
|
self.error('magnetizations provided must be either None or integer\n you provided: {0}\n full magnetization request provided:\n {1}'.format(m,mags))
|
|
#end if
|
|
self.mag[self.elem==e] = m
|
|
#end for
|
|
if identifiers is None and magnetization=='':
|
|
return
|
|
elif magnetization=='':
|
|
magnetization = identifiers
|
|
indices = range(len(self.elem))
|
|
else:
|
|
indices = self.locate(identifiers)
|
|
#end if
|
|
if not isinstance(magnetization,(list,tuple,ndarray)):
|
|
magnetization = [magnetization]
|
|
#end if
|
|
for m in magnetization:
|
|
if not isinstance(m,(NoneType,int)):
|
|
self.error('magnetizations provided must be either None or integer\n you provided: {0}\n full magnetization list provided: {1}'.format(m,magnetization))
|
|
#end if
|
|
#end for
|
|
if len(magnetization)==1:
|
|
m = magnetization[0]
|
|
for i in indices:
|
|
self.mag[i] = m
|
|
#end for
|
|
elif len(magnetization)==len(indices):
|
|
for i in range(len(indices)):
|
|
self.mag[indices[i]] = magnetization[i]
|
|
#end for
|
|
else:
|
|
self.error('magnetization list and list selected atoms differ in length\n length of magnetization list: {0}\n number of atoms selected: {1}\n magnetization list: {2}\n atom indices selected: {3}\n atoms selected: {4}'.format(len(magnetization),len(indices),magnetization,indices,self.elem[indices]))
|
|
#end if
|
|
#end def magnetize
|
|
|
|
|
|
def carve(self,identifiers):
|
|
indices = self.locate(identifiers)
|
|
if isinstance(identifiers,Structure):
|
|
sub = identifiers
|
|
sub.elem = self.elem[indices].copy()
|
|
sub.pos = self.pos[indices].copy()
|
|
else:
|
|
sub = self.copy()
|
|
sub.elem = self.elem[indices]
|
|
sub.pos = self.pos[indices]
|
|
#end if
|
|
sub.host_indices = array(indices)
|
|
return sub
|
|
#end def carve
|
|
|
|
|
|
def remove(self,identifiers):
|
|
indices = self.locate(identifiers)
|
|
keep = list(set(range(len(self.pos)))-set(indices))
|
|
erem = self.elem[indices]
|
|
prem = self.pos[indices]
|
|
self.elem = self.elem[keep]
|
|
self.pos = self.pos[keep]
|
|
self.remove_folded_structure()
|
|
return erem,prem
|
|
#end def remove
|
|
|
|
|
|
def replace(self,identifiers,elem=None,pos=None,radii=None,exterior=False):
|
|
indices = self.locate(identifiers,radii,exterior)
|
|
if isinstance(elem,Structure):
|
|
cell = elem
|
|
elem = cell.elem
|
|
pos = cell.pos
|
|
elif elem==None:
|
|
elem = self.elem
|
|
#end if
|
|
indices=array(indices)
|
|
elem=array(elem,dtype=object)
|
|
pos =array(pos)
|
|
nrem = len(indices)
|
|
nadd = len(pos)
|
|
if nadd<nrem:
|
|
ar = array(range(0,nadd))
|
|
rr = array(range(nadd,nrem))
|
|
self.elem[indices[ar]] = elem[:]
|
|
self.pos[indices[ar]] = pos[:]
|
|
self.remove(indices[rr])
|
|
elif nadd>nrem:
|
|
ar = array(range(0,nrem))
|
|
er = array(range(nrem,nadd))
|
|
self.elem[indices[ar]] = elem[ar]
|
|
self.pos[indices[ar]] = pos[ar]
|
|
ii = indices[ar[-1]]
|
|
self.set_elem( list(self.elem[0:ii])+list(elem[er])+list(self.elem[ii:]) )
|
|
self.pos = array( list(self.pos[0:ii])+list(pos[er])+list(self.pos[ii:]) )
|
|
else:
|
|
self.elem[indices] = elem[:]
|
|
self.pos[indices] = pos[:]
|
|
#end if
|
|
self.remove_folded_structure()
|
|
#end def replace
|
|
|
|
|
|
def replace_nearest(self,elem,pos=None):
|
|
if isinstance(elem,Structure):
|
|
cell = elem
|
|
elem = cell.elem
|
|
pos = cell.pos
|
|
#end if
|
|
nn = nearest_neighbors(1,self.pos,pos)
|
|
np = len(pos)
|
|
nps= len(self.pos)
|
|
d = empty((np,))
|
|
ip = array(range(np))
|
|
ips= nn.ravel()
|
|
for i in ip:
|
|
j = ips[i]
|
|
d[i]=sqrt(((pos[i]-self.pos[j])**2).sum())
|
|
#end for
|
|
order = d.argsort()
|
|
ip = ip[order]
|
|
ips=ips[order]
|
|
replacable = empty((nps,))
|
|
replacable[:] = False
|
|
replacable[ips]=True
|
|
insert = []
|
|
last_replaced=nps-1
|
|
for n in range(np):
|
|
i = ip[n]
|
|
j = ips[n]
|
|
if replacable[j]:
|
|
self.pos[j] = pos[i]
|
|
self.elem[j]=elem[i]
|
|
replacable[j]=False
|
|
last_replaced = j
|
|
else:
|
|
insert.append(i)
|
|
#end if
|
|
#end for
|
|
insert=array(insert)
|
|
ii = last_replaced
|
|
if len(insert)>0:
|
|
self.set_elem( list(self.elem[0:ii])+list(elem[insert])+list(self.elem[ii:]) )
|
|
self.pos = array( list(self.pos[0:ii])+list(pos[insert])+list(self.pos[ii:]) )
|
|
#end if
|
|
self.remove_folded_structure()
|
|
#end def replace_nearest
|
|
|
|
|
|
def point_defect(self,identifiers=None,elem=None,dr=None):
|
|
if isinstance(elem,str):
|
|
elem = [elem]
|
|
if dr!=None:
|
|
dr = [dr]
|
|
#end if
|
|
#end if
|
|
if not 'point_defects' in self:
|
|
self.point_defects = obj()
|
|
#end if
|
|
point_defects = self.point_defects
|
|
ncenters = len(point_defects)
|
|
if identifiers is None:
|
|
index = ncenters
|
|
if index>=len(self.pos):
|
|
self.error('attempted to add a point defect at index {0}, which does not exist\n for reference there are {1} atoms in the structure'.format(index,len(self.pos)))
|
|
#end if
|
|
else:
|
|
indices = self.locate(identifiers)
|
|
if len(indices)>1:
|
|
self.error('{0} atoms were located by identifiers provided\n a point defect replaces only a single atom\n atom indices located: {1}'.format(len(indices),indices))
|
|
#end if
|
|
index = indices[0]
|
|
#end if
|
|
if elem is None:
|
|
self.error('must supply substitutional elements comprising the point defect\n expected a list or similar for input argument elem')
|
|
elif len(elem)>1 and dr is None:
|
|
self.error('must supply displacements (dr) since many atoms comprise the point defect')
|
|
elif dr!=None and len(elem)!=len(dr):
|
|
self.error('elem and dr must have the same length')
|
|
#end if
|
|
r = self.pos[index]
|
|
e = self.elem[index]
|
|
elem = array(elem)
|
|
pos = zeros((len(elem),len(r)))
|
|
if dr is None:
|
|
rc = r
|
|
for i in range(len(elem)):
|
|
pos[i] = r
|
|
#end for
|
|
else:
|
|
nrc = 0
|
|
rc = 0*r
|
|
dr = array(dr)
|
|
for i in range(len(elem)):
|
|
pos[i] = r + dr[i]
|
|
if norm(dr[i])>1e-5:
|
|
rc+=dr[i]
|
|
nrc+=1
|
|
#end if
|
|
#end for
|
|
if nrc==0:
|
|
rc = r
|
|
else:
|
|
rc = r + rc/nrc
|
|
#end if
|
|
#end if
|
|
point_defect = obj(
|
|
center = rc,
|
|
elem_replaced = e,
|
|
elem = elem,
|
|
pos = pos
|
|
)
|
|
point_defects.append(point_defect)
|
|
elist = list(self.elem)
|
|
plist = list(self.pos)
|
|
if len(elem)==0 or len(elem)==1 and elem[0]=='':
|
|
elist.pop(index)
|
|
plist.pop(index)
|
|
else:
|
|
elist[index] = elem[0]
|
|
plist[index] = pos[0]
|
|
for i in range(1,len(elem)):
|
|
elist.append(elem[i])
|
|
plist.append(pos[i])
|
|
#end for
|
|
#end if
|
|
self.set_elem(elist)
|
|
self.pos = array(plist)
|
|
self.remove_folded_structure()
|
|
#end def point_defect
|
|
|
|
|
|
def order_by_species(self,folded=False):
|
|
species = []
|
|
species_counts = []
|
|
elem_indices = []
|
|
|
|
spec_set = set()
|
|
for i in xrange(len(self.elem)):
|
|
e = self.elem[i]
|
|
if not e in spec_set:
|
|
spec_set.add(e)
|
|
species.append(e)
|
|
species_counts.append(0)
|
|
elem_indices.append([])
|
|
#end if
|
|
sindex = species.index(e)
|
|
species_counts[sindex] += 1
|
|
elem_indices[sindex].append(i)
|
|
#end for
|
|
|
|
elem_order = []
|
|
for elem_inds in elem_indices:
|
|
elem_order.extend(elem_inds)
|
|
#end for
|
|
self.reorder(elem_order)
|
|
|
|
if folded and self.folded_structure!=None:
|
|
self.folded_structure.order_by_species(folded)
|
|
#end if
|
|
|
|
return species,species_counts
|
|
#end def order_by_species
|
|
|
|
|
|
def reorder(self,order):
|
|
order = array(order)
|
|
self.elem = self.elem[order]
|
|
self.pos = self.pos[order]
|
|
#end def reorder
|
|
|
|
|
|
# find layers parallel to a particular cell face
|
|
# layers are found by scanning a window of width dtol along the axis and counting
|
|
# the number of atoms within the window. window position w/ max number of atoms
|
|
# defines the layer. layer distance is the window position.
|
|
# the resolution of the scan is determined by dbin
|
|
# (axis length)/dbin is the number of fine bins
|
|
# dtol/dbin is the number of fine bins in the moving (boxcar) window
|
|
# plot=True: plot the layer histogram (fine hist and moving average)
|
|
# composition=True: return the composition of each layer (count of each species)
|
|
# returns an object containing indices of atoms in each layer by distance along axis
|
|
# example: structure w/ 3 layers of 4 atoms each at distances 3.0, 6.0, and 9.0 Angs.
|
|
# layers
|
|
# 3.0 = [ 0, 1, 2, 3 ]
|
|
# 6.0 = [ 4, 5, 6, 7 ]
|
|
# 9.0 = [ 8, 9,10,11 ]
|
|
def layers(self,axis=0,dtol=0.03,dbin=0.01,plot=False,composition=False):
|
|
nbox = int(dtol/dbin)
|
|
if nbox%2==0:
|
|
nbox+=1
|
|
#end if
|
|
nwind = (nbox-1)/2
|
|
s = self.copy()
|
|
s.recenter()
|
|
vaxis = s.axes[axis]
|
|
daxis = norm(vaxis)
|
|
naxis = vaxis/daxis
|
|
dbin = dtol/nbox
|
|
nbins = int(ceil(daxis/dbin))
|
|
dbin = daxis/nbins
|
|
dbins = daxis*(arange(nbins)+.5)/nbins
|
|
dists = daxis*s.pos_unit()[:,axis]
|
|
hist = zeros((nbins,),dtype=int)
|
|
boxhist = zeros((nbins,),dtype=int)
|
|
ihist = obj()
|
|
iboxhist = obj()
|
|
index = 0
|
|
for d in dists:
|
|
ibin = int(floor(d/dbin))
|
|
hist[ibin]+=1
|
|
if not ibin in ihist:
|
|
ihist[ibin] = []
|
|
#end if
|
|
ihist[ibin].append(index)
|
|
index+=1
|
|
#end for
|
|
for ib in xrange(nbins):
|
|
for i in xrange(ib-nwind,ib+nwind+1):
|
|
n = hist[i%nbins]
|
|
if n>0:
|
|
boxhist[ib]+=n
|
|
if not ib in iboxhist:
|
|
iboxhist[ib] = []
|
|
#end if
|
|
iboxhist[ib].extend(ihist[i%nbins])
|
|
#end if
|
|
#end for
|
|
#end for
|
|
peaks = []
|
|
nlast=0
|
|
for ib in xrange(nbins):
|
|
n = boxhist[ib]
|
|
if nlast==0 and n>0:
|
|
pcur = []
|
|
peaks.append(pcur)
|
|
#end if
|
|
if n>0:
|
|
pcur.append(ib)
|
|
#end if
|
|
nlast = n
|
|
#end for
|
|
if boxhist[0]>0 and boxhist[-1]>0:
|
|
peaks[0].extend(peaks[-1])
|
|
peaks.pop()
|
|
#end if
|
|
layers = obj()
|
|
ip = []
|
|
for peak in peaks:
|
|
ib = peak[boxhist[peak].argmax()]
|
|
ip.append(ib)
|
|
pindices = iboxhist[ib]
|
|
ldist = dbins[ib] # distance is along an axis vector
|
|
faxis = self.face_vectors()[axis]
|
|
ldist = dot(ldist*naxis,faxis/norm(faxis))
|
|
layers[ldist] = array(pindices,dtype=int)
|
|
#end for
|
|
if plot:
|
|
plt.plot(dbins,boxhist,'b.-',label='boxcar histogram')
|
|
plt.plot(dbins,hist,'r.-',label='fine histogram')
|
|
plt.plot(dbins[ip],boxhist[ip],'rv',markersize=20)
|
|
plt.show()
|
|
plt.legend()
|
|
#end if
|
|
if not composition:
|
|
return layers
|
|
else:
|
|
return layers,self.layer_composition(layers)
|
|
#end if
|
|
#end def layers
|
|
|
|
|
|
def layer_composition(self,layers):
|
|
lcomp = obj()
|
|
for d,ind in layers.iteritems():
|
|
comp = obj()
|
|
elem = self.elem[ind]
|
|
for e in elem:
|
|
if e not in comp:
|
|
comp[e] = 1
|
|
else:
|
|
comp[e] += 1
|
|
#end if
|
|
#end for
|
|
lcomp[d]=comp
|
|
#end for
|
|
return lcomp
|
|
#end def layer_composition
|
|
|
|
|
|
def shells(self,identifiers,radii=None,exterior=False,cumshells=False,distances=False,dtol=1e-6):
|
|
# get indices for 'core' and 'bulk'
|
|
# core is selected by identifiers, forms core for shells to be built around
|
|
# bulk is all atoms except for core
|
|
if identifiers=='point_defects':
|
|
if not 'point_defects' in self:
|
|
self.error('requested shells around point defects, but structure has no point defects')
|
|
#end if
|
|
core = []
|
|
for pd in self.point_defects:
|
|
core.append(pd.center)
|
|
#end for
|
|
core = array(core)
|
|
bulk_ind = self.locate(core,radii=dtol,exterior=True)
|
|
core_ind = self.locate(bulk_ind,exterior=True)
|
|
bulk = self.pos[bulk_ind]
|
|
else:
|
|
core_ind = self.locate(identifiers,radii,exterior)
|
|
bulk_ind = self.locate(core_ind,exterior=True)
|
|
core = self.pos[core_ind]
|
|
bulk = self.pos[bulk_ind]
|
|
#end if
|
|
bulk_ind = array(bulk_ind,dtype=int)
|
|
# build distance table between bulk and core
|
|
dtable = self.distance_table(bulk,core)
|
|
# find shortest distance for each bulk atom to any core atom and order by distance
|
|
dist = dtable.min(1)
|
|
ind = arange(len(bulk))
|
|
order = dist.argsort()
|
|
dist = dist[order]
|
|
ind = bulk_ind[ind[order]]
|
|
# find shells around the core
|
|
# the closest atom to the core starts the first shell and defines a shell distance
|
|
# other atoms are in the shell if within dtol distance of the first atom
|
|
# otherwise a new shell is started
|
|
ns = 0
|
|
ds = -1
|
|
shells = obj()
|
|
shells[ns] = list(core_ind) # first shell is all core atoms
|
|
dshells = [0.]
|
|
for n in xrange(len(dist)):
|
|
if abs(dist[n]-ds)>dtol:
|
|
shell = [ind[n]] # new shell starts with single atom
|
|
ns+=1
|
|
shells[ns] = shell
|
|
ds = dist[n] # shell distance is distance of this atom from core
|
|
dshells.append(ds)
|
|
else:
|
|
shell.append(ind[n])
|
|
#end if
|
|
#end for
|
|
dshells = array(dshells,dtype=float)
|
|
results = [shells]
|
|
if cumshells:
|
|
# assemble cumulative shells, ie cumshell[ns] = sum(shells[n],n=0 to ns)
|
|
cumshells = obj()
|
|
cumshells[0] = list(shells[0])
|
|
for ns in xrange(1,len(shells)):
|
|
cumshells[ns] = cumshells[ns-1]+shells[ns]
|
|
#end for
|
|
for ns,cshell in cumshells.iteritems():
|
|
cumshells[ns] = array(cshell,dtype=int)
|
|
#end for
|
|
results.append(cumshells)
|
|
#end if
|
|
for ns,shell in shells.iteritems():
|
|
shells[ns] = array(shell,dtype=int)
|
|
if distances:
|
|
results.append(dshells)
|
|
#end if
|
|
if len(results)==1:
|
|
results = results[0]
|
|
#end if
|
|
return results
|
|
#end def shells
|
|
|
|
|
|
# find connected sets of atoms.
|
|
# indices is a list of atomic indices to consider (self.pos[indices] are their positions)
|
|
# atoms are considered connected if they are within rmax of each other
|
|
# order sets the maximum number of atoms in any connected graph
|
|
# order = 1 returns single atoms
|
|
# order = 2 returns dimers + order=1 results
|
|
# order = 3 returns trimers + order=2 results
|
|
# ...
|
|
# degree is explained w/ an example: a triangle of atoms 0,1,2 and a line of atoms 3,4,5 (3 & 5 are not neighbors)
|
|
# degree = False : returned object (cgraphs) has following structure:
|
|
# cgraphs[1] = [ (0,), (1,), (2,), (3,), (4,), (5,) ] # first order connected graphs (atoms)
|
|
# cgraphs[2] = [ (0,1), (0,2), (1,2), (3,4), (4,5) ] # second order connected graphs (dimers)
|
|
# cgraphs[3] = [ (0,1,2), (3,4,5) ] # third order connected graphs (trimers)
|
|
# degree = True : returned object (cgraphs) has following structure:
|
|
# cgraphs
|
|
# 1 # first order connected graphs (atoms)
|
|
# 0 # sum of vertex degrees is 0 (a single atom has no neighbors)
|
|
# (0,) = [ (0,), (1,), (2,), (3,), (4,), (5,) ] # graphs with vertex degree (0,)
|
|
# 2 # second order connected graphs (dimers)
|
|
# 2 # sum of vertex degrees is 2 (each atom is connected to 1 neighbor)
|
|
# (1,1) = [ (0,1), (0,2), (1,2), (3,4), (4,5) ] # graphs with vertex degree (1,1)
|
|
# 3 # third order connected graphs (trimers)
|
|
# 4 # sum of vertex degrees is 4 (2 atoms have 1 neighbor and 1 atom has 2)
|
|
# (1,1,2) = [ (3,5,4) ]
|
|
# 6 # sum of vertex degrees is 6 (each atom is connected to 2 others)
|
|
# (2,2,2) = [ (0,1,2) ] # graphs with vertex degree (2,2,2)
|
|
def connected_graphs(self,order,indices=None,rmax=None,nmax=None,degree=False,site_maps=False,**spec_max):
|
|
if indices is None:
|
|
indices = arange(len(self.pos),dtype=int)
|
|
pos = self.pos
|
|
else:
|
|
pos = self.pos[indices]
|
|
#end if
|
|
elem = set(self.elem[indices])
|
|
spec = set(spec_max.keys())
|
|
if spec==elem or rmax!=None:
|
|
None
|
|
elif spec<elem and nmax!=None:
|
|
for e in elem:
|
|
if e not in spec:
|
|
spec_max[e] = nmax
|
|
#end if
|
|
#end for
|
|
#end if
|
|
np = len(indices)
|
|
# get neighbor table for subset of atoms specified by indices
|
|
nt,dt = self.neighbor_table(pos,pos,distances=True)
|
|
# determine how many neighbors to consider based on rmax (all are neighbors if rmax is None)
|
|
nneigh = zeros((np,),dtype=int)
|
|
if len(spec_max)>0:
|
|
for n in xrange(np):
|
|
nneigh[n] = min(spec_max[self.elem[n]],len(nt[n]))
|
|
#end for
|
|
elif rmax is None:
|
|
nneigh[:] = np
|
|
else:
|
|
nneigh = (dt<rmax).sum(1)
|
|
#end if
|
|
# record which atoms are neighbors to each other
|
|
neigh_pairs = set()
|
|
for i in xrange(np):
|
|
for ni in nt[i,1:nneigh[i]]:
|
|
ii = indices[i]
|
|
jj = indices[ni]
|
|
neigh_pairs.add((ii,jj))
|
|
neigh_pairs.add((jj,ii))
|
|
#end for
|
|
#end for
|
|
# find the connected graphs
|
|
graphs_found = set() # map to contain tuples of connected atom's indices
|
|
cgraphs = obj()
|
|
for o in range(1,order+1): # organize by order
|
|
cgraphs[o] = []
|
|
#end for
|
|
if order>0:
|
|
cg = cgraphs[1]
|
|
for i in xrange(np): # list of single atoms
|
|
gi = (i,)
|
|
cg.append(gi)
|
|
graphs_found.add(gi)
|
|
#end for
|
|
for o in range(2,order+1): # graphs of order o are found by adding all
|
|
cglast = cgraphs[o-1] # possible single neighbors to each graph of order o-1
|
|
cg = cgraphs[o]
|
|
for gilast in cglast:
|
|
for i in gilast:
|
|
for ni in nt[i,1:nneigh[i]]:
|
|
gi = tuple(sorted(gilast+(ni,)))
|
|
if gi not in graphs_found and len(set(gi))==o:
|
|
graphs_found.add(gi)
|
|
cg.append(gi)
|
|
#end if
|
|
#end for
|
|
#end for
|
|
#end for
|
|
#end for
|
|
#end if
|
|
# map indices back to actual atomic indices
|
|
for o,cg in cgraphs.iteritems():
|
|
cgmap = []
|
|
for gi in cg:
|
|
gi = array(gi)
|
|
gimap = tuple(sorted(indices[array(gi)]))
|
|
cgmap.append(gimap)
|
|
#end for
|
|
cgraphs[o] = array(sorted(cgmap),dtype=int)
|
|
#end for
|
|
# reorganize the graph listing by cluster and vertex degree, if desired
|
|
if degree:
|
|
#degree_map = obj()
|
|
cgraphs_deg = obj()
|
|
for o,cg in cgraphs.iteritems():
|
|
dgo = obj()
|
|
cgraphs_deg[o] = dgo
|
|
for gi in cg:
|
|
di = zeros((o,),dtype=int)
|
|
for m in xrange(o):
|
|
i = gi[m]
|
|
for n in xrange(m+1,o):
|
|
j = gi[n]
|
|
if (i,j) in neigh_pairs:
|
|
di[m]+=1
|
|
di[n]+=1
|
|
#end if
|
|
#end for
|
|
#end for
|
|
d = int(di.sum())
|
|
dorder = di.argsort()
|
|
di = tuple(di[dorder])
|
|
gi = tuple(array(gi)[dorder])
|
|
if not d in dgo:
|
|
dgo[d]=obj()
|
|
#end if
|
|
dgd = dgo[d]
|
|
if not di in dgd:
|
|
dgd[di] = []
|
|
#end if
|
|
dgd[di].append(gi)
|
|
#degree_map[gi] = d,di
|
|
#end for
|
|
for dgd in dgo:
|
|
for di,dgi in dgd.iteritems():
|
|
dgd[di]=array(dgi,dtype=int)
|
|
#end for
|
|
#end for
|
|
#end for
|
|
cgraphs = cgraphs_deg
|
|
#end if
|
|
|
|
if not site_maps:
|
|
return cgraphs
|
|
else:
|
|
cmaps = obj()
|
|
if not degree:
|
|
for order,og in cgraphs.iteritems():
|
|
cmap = obj()
|
|
for slist in og:
|
|
for s in slist:
|
|
if not s in cmap:
|
|
cmap[s] = obj()
|
|
#end if
|
|
cmap[s].append(slist)
|
|
#end for
|
|
#end for
|
|
cmaps[order] = cmap
|
|
#end for
|
|
else:
|
|
for order,og in cgraphs.iteritems():
|
|
for total_degree,tg in og.iteritems():
|
|
for local_degree,lg in tg.iteritems():
|
|
cmap = obj()
|
|
for slist in lg:
|
|
n=0
|
|
for s in slist:
|
|
d = local_degree[n]
|
|
if not s in cmap:
|
|
cmap[s] = obj()
|
|
#end if
|
|
if not d in cmap[s]:
|
|
cmap[s][d] = obj()
|
|
#end if
|
|
cmap[s][d].append(slist)
|
|
n+=1
|
|
#end for
|
|
#end for
|
|
cmaps.add_attribute_path((order,total_degree,local_degree),cmap)
|
|
#end for
|
|
#end for
|
|
#end for
|
|
#end if
|
|
return cgraphs,cmaps
|
|
#end if
|
|
#end def connected_graphs
|
|
|
|
|
|
def min_image_vectors(self,points=None,points2=None,axes=None,pairs=True):
|
|
if points is None:
|
|
points = self.pos
|
|
#end if
|
|
if axes is None:
|
|
axes = self.axes
|
|
#end if
|
|
axinv = inv(axes)
|
|
points = array(points)
|
|
single = points.shape==(self.dim,)
|
|
if single:
|
|
points = [points]
|
|
#end if
|
|
if points2 is None:
|
|
points2 = self.pos
|
|
elif points2.shape==(self.dim,):
|
|
points2 = [points2]
|
|
#end if
|
|
npoints = len(points)
|
|
npoints2 = len(points2)
|
|
if pairs:
|
|
vtable = empty((npoints,npoints2,self.dim),dtype=float)
|
|
i=-1
|
|
for p in points:
|
|
i+=1
|
|
j=-1
|
|
for pp in points2:
|
|
j+=1
|
|
u = dot(pp-p,axinv)
|
|
vtable[i,j] = dot(u-floor(u+.5),axes)
|
|
#end for
|
|
#end for
|
|
result = vtable
|
|
else:
|
|
if npoints!=npoints2:
|
|
self.error('cannot create one to one minimum image vectors, point sets differ in length\n npoints1 = {0}\n npoints2 = {1}'.format(npoints,npoints2))
|
|
#end if
|
|
vectors = empty((npoints,self.dim),dtype=float)
|
|
n = 0
|
|
for p in points:
|
|
pp = points2[n]
|
|
u = dot(pp-p,axinv)
|
|
vectors[n] = dot(u-floor(u+.5),axes)
|
|
n+=1
|
|
#end for
|
|
result = vectors
|
|
#end if
|
|
|
|
return result
|
|
#end def min_image_vectors
|
|
|
|
|
|
def min_image_distances(self,points=None,points2=None,axes=None,vectors=False,pairs=True):
|
|
vtable = self.min_image_vectors(points,points2,axes,pairs=pairs)
|
|
rdim = len(vtable.shape)-1
|
|
dtable = sqrt((vtable**2).sum(rdim))
|
|
if not vectors:
|
|
return dtable
|
|
else:
|
|
return dtable,vtable
|
|
#end if
|
|
#end def min_image_distances
|
|
|
|
|
|
def distance_table(self,points=None,points2=None,axes=None,vectors=False):
|
|
return self.min_image_distances(points,points2,axes,vectors)
|
|
#end def distance_table
|
|
|
|
|
|
def vector_table(self,points=None,points2=None,axes=None):
|
|
return self.min_image_vectors(points,points2,axes)
|
|
#end def vector_table
|
|
|
|
|
|
def neighbor_table(self,points=None,points2=None,axes=None,distances=False,vectors=False):
|
|
dtable,vtable = self.min_image_distances(points,points2,axes,vectors=True)
|
|
ntable = empty(dtable.shape,dtype=int)
|
|
for i in range(len(dtable)):
|
|
ntable[i] = dtable[i].argsort()
|
|
#end for
|
|
results = [ntable]
|
|
if distances:
|
|
for i in range(len(dtable)):
|
|
dtable[i] = dtable[i][ntable[i]]
|
|
#end for
|
|
results.append(dtable)
|
|
#end if
|
|
if vectors:
|
|
for i in range(len(vtable)):
|
|
vtable[i] = vtable[i][ntable[i]]
|
|
#end for
|
|
results.append(vtable)
|
|
#end if
|
|
if len(results)==1:
|
|
results = results[0]
|
|
#end if
|
|
return results
|
|
#end def neighbor_table
|
|
|
|
|
|
def min_image_norms(self,points,norms):
|
|
if isinstance(norms,int) or isinstance(norms,float):
|
|
norms = [norms]
|
|
#end if
|
|
vtable = self.min_image_vectors(points)
|
|
rdim = len(vtable.shape)-1
|
|
nout = []
|
|
for p in norms:
|
|
nout.append( ((abs(vtable)**p).sum(rdim))**(1./p) )
|
|
#end for
|
|
if len(norms)==1:
|
|
nout = nout[0]
|
|
#end if
|
|
return nout
|
|
#end def min_image_norms
|
|
|
|
# get all neighbors according to contacting voronoi polyhedra in PBC
|
|
def voronoi_neighbors(self,indices=None,restrict=False):
|
|
if indices is None:
|
|
indices = arange(len(self.pos))
|
|
#end if
|
|
# make a new version of this (small cell)
|
|
sn = self.copy()
|
|
sn.recenter()
|
|
# tile a large cell periodically
|
|
d = 3
|
|
t = tuple(zeros((d,),dtype=int)+3)
|
|
ss = sn.tile(t)
|
|
ss.recenter(sn.center)
|
|
# get nearest neighbor index pairs in the large cell
|
|
neigh_pairs = voronoi_neighbors(ss.pos)
|
|
# create a mapping from large to small indices
|
|
large_to_small = 3**d*range(len(self.pos))
|
|
# find the neighbor pairs in the small cell
|
|
neighbors = obj()
|
|
small_inds = set(ss.locate(sn.pos))
|
|
for n in xrange(len(neigh_pairs)):
|
|
i,j = neigh_pairs[n,:]
|
|
if i in small_inds or j in small_inds: # pairs w/ at least one in cell image
|
|
i = large_to_small[i] # mapping to small cell indices
|
|
j = large_to_small[j]
|
|
if not restrict or (i in indices and j in indices): # restrict to orig index set
|
|
if not i in neighbors:
|
|
neighbors[i] = [j]
|
|
else:
|
|
neighbors[i].append(j)
|
|
#ned if
|
|
if not j in neighbors:
|
|
neighbors[j] = [i]
|
|
else:
|
|
neighbors[j].append(i)
|
|
#end if
|
|
#end if
|
|
#end if
|
|
#end for
|
|
# remove any duplicates and order by distance
|
|
dt = self.distance_table()
|
|
for i,ni in neighbors.iteritems():
|
|
ni = array(list(set(ni)),dtype=int)
|
|
di = dt[i,ni]
|
|
order = di.argsort()
|
|
neighbors[i] = ni[order]
|
|
#end for
|
|
return neighbors
|
|
#end def voronoi_neighbors
|
|
|
|
|
|
# get nearest neighbors according to constrants (voronoi, max distance, coord. number)
|
|
def nearest_neighbors(self,indices=None,rmax=None,nmax=None,restrict=False,voronoi=False,distances=False,**spec_max):
|
|
if indices is None:
|
|
indices = arange(len(self.pos))
|
|
#end if
|
|
elem = set(self.elem[indices])
|
|
spec = set(spec_max.keys())
|
|
if spec==elem or rmax!=None or voronoi:
|
|
None
|
|
elif spec<elem and nmax!=None:
|
|
for e in elem:
|
|
if e not in spec:
|
|
spec_max[e] = nmax
|
|
#end if
|
|
#end for
|
|
else:
|
|
self.error('must specify nmax for all species\n species present: {0}\n you only provided nmax for these species: {1}'.format(sorted(elem),sorted(spec)))
|
|
#end if
|
|
pos = self.pos[indices]
|
|
if not restrict:
|
|
pos2 = self.pos
|
|
else:
|
|
pos2 = pos
|
|
#end if
|
|
if voronoi:
|
|
neighbors = self.voronoi_neighbors(indices=indices,restrict=restrict)
|
|
dt = self.distance_table(pos,pos2)[:,1:]
|
|
else:
|
|
nt,dt = self.neighbor_table(pos,pos2,distances=True)
|
|
dt=dt[:,1:]
|
|
nt=nt[:,1:]
|
|
neighbors = list(nt)
|
|
#end if
|
|
for i in xrange(len(indices)):
|
|
neighbors[i] = indices[neighbors[i]]
|
|
#end for
|
|
dist = list(dt)
|
|
if rmax is None:
|
|
for i in xrange(len(indices)):
|
|
nn = neighbors[i]
|
|
dn = dist[i]
|
|
smax = spec_max[self.elem[indices[i]]]
|
|
if len(nn)>smax:
|
|
neighbors[i] = nn[:smax]
|
|
dist[i] = dn[:smax]
|
|
#end if
|
|
#end for
|
|
else:
|
|
for i in xrange(len(indices)):
|
|
neighbors[i] = neighbors[i][dt[i]<rmax]
|
|
#end for
|
|
#end if
|
|
if not distances:
|
|
return neighbors
|
|
else:
|
|
return neighbors,dist
|
|
#end if
|
|
#end def nearest_neighbors
|
|
|
|
|
|
# determine local chemical coordination limited by constraints
|
|
def chemical_coordination(self,indices=None,nmax=None,rmax=None,restrict=False,voronoi=False,neighbors=False,distances=False,**spec_max):
|
|
if indices is None:
|
|
indices = arange(len(self.pos))
|
|
#end if
|
|
if not distances:
|
|
neigh = self.nearest_neighbors(indices=indices,nmax=nmax,rmax=rmax,restrict=restrict,voronoi=voronoi,**spec_max)
|
|
else:
|
|
neigh,dist = self.nearest_neighbors(indices=indices,nmax=nmax,rmax=rmax,restrict=restrict,voronoi=voronoi,distances=True,**spec_max)
|
|
#end if
|
|
neigh_elem = []
|
|
for i in xrange(len(indices)):
|
|
neigh_elem.extend(self.elem[neigh[i]])
|
|
#end for
|
|
chem_key = tuple(sorted(set(neigh_elem)))
|
|
chem_coord = zeros((len(indices),len(chem_key)),dtype=int)
|
|
for i in xrange(len(indices)):
|
|
counts = zeros((len(chem_key),),dtype=int)
|
|
nn = list(self.elem[neigh[i]])
|
|
for n in xrange(len(counts)):
|
|
chem_coord[i,n] = nn.count(chem_key[n])
|
|
#end for
|
|
#end for
|
|
chem_map = obj()
|
|
i=0
|
|
for coord in chem_coord:
|
|
coord = tuple(coord)
|
|
if not coord in chem_map:
|
|
chem_map[coord] = [indices[i]]
|
|
else:
|
|
chem_map[coord].append(indices[i])
|
|
#end if
|
|
i+=1
|
|
#end for
|
|
for coord,ind in chem_map.iteritems():
|
|
chem_map[coord] = array(ind,dtype=int)
|
|
#end for
|
|
results = [chem_key,chem_coord,chem_map]
|
|
if neighbors:
|
|
results.append(neigh)
|
|
#end if
|
|
if distances:
|
|
results.append(dist)
|
|
#end if
|
|
return results
|
|
#end def chemical_coordination
|
|
|
|
|
|
def rcore_max(self,units=None):
|
|
nt,dt = self.neighbor_table(self.pos,distances=True)
|
|
d = dt[:,1]
|
|
rcm = d.min()/2
|
|
if units!=None:
|
|
rcm = convert(rcm,self.units,units)
|
|
#end if
|
|
return rcm
|
|
#end def rcore_max
|
|
|
|
|
|
def cell_image(self,p):
|
|
if self.dim!=3:
|
|
self.error('cell_image is currently only implemented for 3 dimensions')
|
|
#end if
|
|
pos = atleast_2d(p)
|
|
c = empty((1,3))
|
|
c[:] = self.center[:]
|
|
axes = self.axes
|
|
axinv = inv(axes)
|
|
for i in range(len(pos)):
|
|
u = dot(pos[i]-c,axinv)
|
|
pos[i] = dot(u-floor(u+.5),axes)+c
|
|
#end for
|
|
if isinstance(p,ndarray):
|
|
p[:] = pos[:]
|
|
elif len(pos)==1 and len(p)==3:
|
|
p[0] = pos[0,0]
|
|
p[1] = pos[0,1]
|
|
p[2] = pos[0,2]
|
|
else:
|
|
for i in range(len(p)):
|
|
p[i][0] = pos[i,0]
|
|
p[i][1] = pos[i,1]
|
|
p[i][2] = pos[i,2]
|
|
#end for
|
|
#end if
|
|
#end def cell_image
|
|
|
|
|
|
def recenter(self,center=None):
|
|
if center!=None:
|
|
self.center=array(center)
|
|
#end if
|
|
pos = self.pos
|
|
c = empty((1,self.dim))
|
|
c[:] = self.center[:]
|
|
axes = self.axes
|
|
axinv = inv(axes)
|
|
for i in range(len(pos)):
|
|
u = dot(pos[i]-c,axinv)
|
|
pos[i] = dot(u-floor(u+.5),axes)+c
|
|
#end for
|
|
self.recenter_k()
|
|
#end def recenter
|
|
|
|
|
|
def recorner(self):
|
|
pos = self.pos
|
|
axes = self.axes
|
|
axinv = inv(axes)
|
|
for i in range(len(pos)):
|
|
u = dot(pos[i],axinv)
|
|
pos[i] = dot(u-floor(u),axes)
|
|
#end for
|
|
#end def recorner
|
|
|
|
|
|
def recenter_k(self,kpoints=None,kaxes=None,kcenter=None,remove_duplicates=False):
|
|
use_self = kpoints==None
|
|
if use_self:
|
|
kpoints=self.kpoints
|
|
#end if
|
|
if kaxes==None:
|
|
kaxes=self.kaxes
|
|
#end if
|
|
if len(kpoints)>0:
|
|
axes = kaxes
|
|
axinv = inv(axes)
|
|
if kcenter is None:
|
|
c = axes.sum(0)/2
|
|
else:
|
|
c = array(kcenter)
|
|
#end if
|
|
for i in range(len(kpoints)):
|
|
u = dot(kpoints[i]-c,axinv)
|
|
u -= floor(u+.5)
|
|
u[abs(u-.5)<1e-12] -= 1.0
|
|
u[abs(u )<1e-12] = 0.0
|
|
kpoints[i] = dot(u,axes)+c
|
|
#end for
|
|
if remove_duplicates:
|
|
inside = self.inside(kpoints,axes,c)
|
|
kpoints = kpoints[inside]
|
|
nkpoints = len(kpoints)
|
|
unique = empty((nkpoints,),dtype=bool)
|
|
unique[:] = True
|
|
nn = nearest_neighbors(1,kpoints)
|
|
if nkpoints>1:
|
|
nn.shape = nkpoints,
|
|
dist = self.distances(kpoints,kpoints[nn])
|
|
tol = 1e-8
|
|
duplicates = arange(nkpoints)[dist<tol]
|
|
for i in duplicates:
|
|
if unique[i]:
|
|
for j in duplicates:
|
|
if sqrt(((kpoints[i]-kpoints[j])**2).sum(1))<tol:
|
|
unique[j] = False
|
|
#end if
|
|
#end for
|
|
#end if
|
|
#end for
|
|
#end if
|
|
kpoints = kpoints[unique]
|
|
#end if
|
|
#end if
|
|
if use_self:
|
|
self.kpoints = kpoints
|
|
else:
|
|
return kpoints
|
|
#end if
|
|
#end def recenter_k
|
|
|
|
|
|
def inside(self,pos,axes=None,center=None,tol=1e-8,separate=False):
|
|
if axes==None:
|
|
axes=self.axes
|
|
#end if
|
|
if center==None:
|
|
center=self.center
|
|
#end if
|
|
axes = array(axes)
|
|
center = array(center)
|
|
inside = []
|
|
surface = []
|
|
su = []
|
|
axinv = inv(axes)
|
|
for i in xrange(len(pos)):
|
|
u = dot(pos[i]-center,axinv)
|
|
umax = abs(u).max()
|
|
if abs(umax-.5)<tol:
|
|
surface.append(i)
|
|
su.append(u)
|
|
elif umax<.5:
|
|
inside.append(i)
|
|
#end if
|
|
#end for
|
|
npos,dim = pos.shape
|
|
drange = range(dim)
|
|
n = len(surface)
|
|
i=0
|
|
while i<n:
|
|
j=i+1
|
|
while j<n:
|
|
du = abs(su[i]-su[j])
|
|
match = False
|
|
for d in drange:
|
|
match = match or abs(du[d]-1.)<tol
|
|
#end for
|
|
if match:
|
|
surface[j]=surface[-1]
|
|
surface.pop()
|
|
su[j]=su[-1]
|
|
su.pop()
|
|
n-=1
|
|
else:
|
|
j+=1
|
|
#end if
|
|
#end while
|
|
i+=1
|
|
#end while
|
|
if not separate:
|
|
inside+=surface
|
|
return inside
|
|
else:
|
|
return inside,surface
|
|
#end if
|
|
#end def inside
|
|
|
|
|
|
def tile(self,*td,**kwargs):
|
|
in_place = False
|
|
magnetic_order = None
|
|
magnetic_primitive = True
|
|
if 'in_place' in kwargs:
|
|
in_place = kwargs['in_place']
|
|
#end if
|
|
if 'magnetic_order' in kwargs:
|
|
magnetic_order = kwargs['magnetic_order']
|
|
#end if
|
|
if 'magnetic_primitive' in kwargs:
|
|
magnetic_primitive = kwargs['magnetic_primitive']
|
|
#end if
|
|
|
|
dim = self.dim
|
|
if len(td)==1:
|
|
if isinstance(td[0],int):
|
|
tiling = dim*[td[0]]
|
|
else:
|
|
tiling = td[0]
|
|
#end if
|
|
else:
|
|
tiling = td
|
|
#end if
|
|
tiling = array(tiling)
|
|
|
|
matrix_tiling = tiling.shape == (dim,dim)
|
|
|
|
tilematrix,tilevector = reduce_tilematrix(tiling)
|
|
|
|
ncells = tilevector.prod()
|
|
|
|
if ncells==1 and abs(tilematrix-identity(self.dim)).sum()<1e-1:
|
|
if in_place:
|
|
return self
|
|
else:
|
|
return self.copy()
|
|
#end if
|
|
#end if
|
|
|
|
self.recenter()
|
|
|
|
elem = array(ncells*list(self.elem))
|
|
pos,axes = tile_points(self.pos,tilevector,self.axes)
|
|
if matrix_tiling:
|
|
#axes = dot(self.axes,tilematrix)
|
|
axes = dot(tilematrix,self.axes)
|
|
#end if
|
|
center = axes.sum(0)/2
|
|
mag = tile_magnetization(self.mag,tilevector,magnetic_order,magnetic_primitive)
|
|
#kaxes = dot(inv(tilematrix),self.kaxes)
|
|
kaxes = dot(inv(tilematrix.T),self.kaxes)
|
|
kpoints = array(self.kpoints)
|
|
kweights = array(self.kweights)
|
|
|
|
ts = self.copy()
|
|
ts.center = center
|
|
ts.set_elem(elem)
|
|
ts.axes = axes
|
|
ts.pos = pos
|
|
ts.mag = mag
|
|
ts.kaxes = kaxes
|
|
ts.kpoints = kpoints
|
|
ts.kweights= kweights
|
|
ts.background_charge = ncells*self.background_charge
|
|
|
|
ts.recenter()
|
|
ts.unique_kpoints()
|
|
if self.folded_structure!=None:
|
|
ts.tmatrix = dot(tilematrix,self.tmatrix)
|
|
ts.folded_structure = self.folded_structure.copy()
|
|
else:
|
|
ts.tmatrix = tilematrix
|
|
ts.folded_structure = self.copy()
|
|
#end if
|
|
|
|
if in_place:
|
|
self.clear()
|
|
self.transfer_from(ts)
|
|
ts = self
|
|
#end if
|
|
|
|
return ts
|
|
#end def tile
|
|
|
|
|
|
def kfold(self,tiling,kpoints,kweights):
|
|
if isinstance(tiling,int):
|
|
tiling = self.dim*[tiling]
|
|
#end if
|
|
tiling = array(tiling)
|
|
if tiling.shape==(self.dim,self.dim):
|
|
tiling = tiling.T
|
|
#end if
|
|
tilematrix,tilevector = reduce_tilematrix(tiling)
|
|
ncells = tilevector.prod()
|
|
kp,kaxnew = tile_points(kpoints,tilevector,self.kaxes)
|
|
kw = array(ncells*list(kweights),dtype=float)/ncells
|
|
return kp,kw
|
|
#end def kfold
|
|
|
|
|
|
def get_primitive(self):
|
|
if self.folded_structure is None:
|
|
fs = self
|
|
else:
|
|
fs = self.folded_structure
|
|
while fs.folded_structure!=None:
|
|
fs = fs.folded_structure
|
|
#end while
|
|
#end if
|
|
return fs
|
|
#end def get_primitive
|
|
|
|
|
|
def fold(self,small,*requests):
|
|
self.error('fold needs a developers attention to make it equivalent with tile')
|
|
if self.dim!=3:
|
|
self.error('fold is currently only implemented for 3 dimensions')
|
|
#end if
|
|
self.recenter_k()
|
|
corners = []
|
|
ndim = len(small.axes)
|
|
imin = empty((ndim,),dtype=int)
|
|
imax = empty((ndim,),dtype=int)
|
|
imin[:] = 1000000
|
|
imax[:] = -1000000
|
|
axinv = inv(self.kaxes)
|
|
center = self.kaxes.sum(0)/2
|
|
c = empty((1,3))
|
|
for k in -1,2:
|
|
for j in -1,2:
|
|
for i in -1,2:
|
|
c[:] = i,j,k
|
|
c = dot(c,small.kaxes)
|
|
u = dot(c-center,axinv)
|
|
for d in range(ndim):
|
|
imin[d] = min(int(floor(u[0,d])),imin[d])
|
|
imax[d] = max(int(ceil(u[0,d])),imax[d])
|
|
#end for
|
|
#end for
|
|
#end for
|
|
#end for
|
|
|
|
axes = small.kaxes
|
|
axinv = inv(small.kaxes)
|
|
|
|
center = small.kaxes.sum(0)/2
|
|
nkpoints = len(self.kpoints)
|
|
kindices = []
|
|
kpoints = []
|
|
shift = empty((ndim,))
|
|
kr = range(nkpoints)
|
|
for k in range(imin[2],imax[2]+1):
|
|
for j in range(imin[1],imax[1]+1):
|
|
for i in range(imin[0],imax[0]+1):
|
|
for n in kr:
|
|
shift[:] = i,j,k
|
|
shift = dot(shift,self.kaxes)
|
|
kp = self.kpoints[n]+shift
|
|
u = dot(kp-center,axinv)
|
|
if abs(u).max()<.5+1e-10:
|
|
kindices.append(n)
|
|
kpoints.append(kp)
|
|
#end if
|
|
#end for
|
|
#end for
|
|
#end for
|
|
#end for
|
|
kindices = array(kindices)
|
|
kpoints = array(kpoints)
|
|
inside = self.inside(kpoints,axes,center)
|
|
kindices = kindices[inside]
|
|
kpoints = kpoints[inside]
|
|
|
|
small.kpoints = kpoints
|
|
small.recenter_k()
|
|
kpoints = array(small.kpoints)
|
|
if len(requests)>0:
|
|
results = []
|
|
for request in requests:
|
|
if request=='kmap':
|
|
kmap = obj()
|
|
for k in self.kpoints:
|
|
kmap[tuple(k)] = []
|
|
#end for
|
|
for i in range(len(kpoints)):
|
|
kp = tuple(self.kpoints[kindices[i]])
|
|
kmap[kp].append(array(kpoints[i]))
|
|
#end for
|
|
for kl,ks in kmap.iteritems():
|
|
kmap[kl] = array(ks)
|
|
#end for
|
|
res = kmap
|
|
elif request=='tilematrix':
|
|
res = self.tilematrix(small)
|
|
else:
|
|
self.error(request+' is not a recognized input to fold')
|
|
#end if
|
|
results.append(res)
|
|
#end if
|
|
return results
|
|
#end if
|
|
#end def fold
|
|
|
|
|
|
def tilematrix(self,small=None,tol=1e-6):
|
|
if small==None:
|
|
if self.folded_structure!=None:
|
|
small = self.folded_structure
|
|
else:
|
|
return identity(self.dim,dtype=int)
|
|
#end if
|
|
#end if
|
|
tm = dot(self.axes,inv(small.axes))
|
|
tilemat = array(around(tm),dtype=int)
|
|
error = abs(tilemat-tm).sum()
|
|
non_integer_elements = error > tol
|
|
if non_integer_elements:
|
|
self.error('large cell cannot be constructed as an integer tiling of the small cell\n large cell axes: \n'+str(self.axes)+'\n small cell axes: \n'+str(small.axes)+'\n large/small: \n'+str(self.axes/small.axes)+'\n tiling matrix: \n'+str(tm)+'\n integerized tiling matrix: \n'+str(tilemat)+'\n error: '+str(error)+'\n tolerance: '+str(tol))
|
|
#end if
|
|
return tilemat
|
|
#end def tilematrix
|
|
|
|
|
|
def add_kpoints(self,kpoints,kweights=None,unique=False):
|
|
if kweights is None:
|
|
kweights = ones((len(kpoints),))
|
|
#end if
|
|
self.kpoints = append(self.kpoints,kpoints,axis=0)
|
|
self.kweights = append(self.kweights,kweights)
|
|
if unique:
|
|
self.unique_kpoints()
|
|
#end if
|
|
self.recenter_k() #added because qmcpack cannot handle kpoints outside the box
|
|
if self.folded_structure!=None:
|
|
kp,kw = self.kfold(self.tmatrix,kpoints,kweights)
|
|
self.folded_structure.add_kpoints(kp,kw,unique=unique)
|
|
#end if
|
|
#end def add_kpoints
|
|
|
|
|
|
def clear_kpoints(self):
|
|
self.kpoints = empty((0,self.dim))
|
|
self.kweights = empty((0,))
|
|
if self.folded_structure!=None:
|
|
self.folded_structure.clear_kpoints()
|
|
#end if
|
|
#end def clear_kpoints
|
|
|
|
|
|
def add_kmesh(self,kgrid,kshift=None,unique=False):
|
|
self.add_kpoints(kmesh(self.kaxes,kgrid,kshift),unique=unique)
|
|
#end def add_kmesh
|
|
|
|
|
|
def kpoints_unit(self):
|
|
return dot(self.kpoints,inv(self.kaxes))
|
|
#end def kpoints_unit
|
|
|
|
|
|
def kpoints_reduced(self):
|
|
return self.kpoints*self.scale/(2*pi)
|
|
#end def kpoints_reduced
|
|
|
|
|
|
def inversion_symmetrize_kpoints(self,tol=1e-10,folded=False):
|
|
kp = self.kpoints
|
|
kaxes = self.kaxes
|
|
ntable,dtable = self.neighbor_table(kp,-kp,kaxes,distances=True)
|
|
pairs = set()
|
|
keep = empty((len(kp),),dtype=bool)
|
|
keep[:] = True
|
|
for i in range(len(dtable)):
|
|
if keep[i] and dtable[i,0]<tol:
|
|
j = ntable[i,0]
|
|
if j!=i and keep[j]:
|
|
keep[j] = False
|
|
self.kweights[i] += self.kweights[j]
|
|
#end if
|
|
#end if
|
|
#end for
|
|
self.kpoints = self.kpoints[keep]
|
|
self.kweights = self.kweights[keep]
|
|
if folded and self.folded_structure!=None:
|
|
self.folded_structure.inversion_symmetrize_kpoints(tol)
|
|
#end if
|
|
#end def inversion_symmetrize_kpoints
|
|
|
|
|
|
def unique_kpoints(self,tol=1e-10,folded=False):
|
|
kmap = obj()
|
|
kp = self.kpoints
|
|
if len(kp)>0:
|
|
kaxes = self.kaxes
|
|
ntable,dtable = self.neighbor_table(kp,kp,kaxes,distances=True)
|
|
npoints = len(kp)
|
|
keep = empty((len(kp),),dtype=bool)
|
|
keep[:] = True
|
|
kmo = obj()
|
|
for i in range(npoints):
|
|
if keep[i]:
|
|
km = []
|
|
jn=0
|
|
while jn<npoints and dtable[i,jn]<tol:
|
|
j = ntable[i,jn]
|
|
km.append(j)
|
|
if j!=i and keep[j]:
|
|
keep[j] = False
|
|
self.kweights[i] += self.kweights[j]
|
|
#end if
|
|
jn+=1
|
|
#end while
|
|
kmo[i] = set(km)
|
|
#end if
|
|
#end for
|
|
self.kpoints = self.kpoints[keep]
|
|
self.kweights = self.kweights[keep]
|
|
j=0
|
|
for i in range(len(keep)):
|
|
if keep[i]:
|
|
kmap[j] = kmo[i]
|
|
j+=1
|
|
#end if
|
|
#end for
|
|
#end if
|
|
if folded and self.folded_structure!=None:
|
|
self.folded_structure.unique_kpoints(tol)
|
|
#end if
|
|
return kmap
|
|
#end def unique_kpoints
|
|
|
|
|
|
def kmap(self):
|
|
kmap = None
|
|
if self.folded_structure!=None:
|
|
fs = self.folded_structure
|
|
self.kpoints = array(fs.kpoints)
|
|
self.kweights = array(fs.kweights)
|
|
kmap = self.unique_kpoints()
|
|
#end if
|
|
return kmap
|
|
#end def kmap
|
|
|
|
|
|
def pos_unit(self,pos=None):
|
|
if pos is None:
|
|
pos = self.pos
|
|
#end if
|
|
return dot(pos,inv(self.axes))
|
|
#end def pos_unit
|
|
|
|
|
|
def pos_to_cartesian(self):
|
|
self.pos = dot(self.pos,self.axes)
|
|
#end def pos_to_cartesian
|
|
|
|
|
|
def at_Gpoint(self):
|
|
kpu = self.kpoints_unit()
|
|
kg = array([0,0,0])
|
|
return len(kpu)==1 and norm(kg-kpu[0])<1e-6
|
|
#end def at_Gpoint
|
|
|
|
|
|
def at_Lpoint(self):
|
|
kpu = self.kpoints_unit()
|
|
kg = array([.5,.5,.5])
|
|
return len(kpu)==1 and norm(kg-kpu[0])<1e-6
|
|
#end def at_Lpoint
|
|
|
|
|
|
def at_real_kpoint(self):
|
|
kpu = 2*self.kpoints_unit()
|
|
return len(kpu)==1 and abs(kpu-around(kpu)).sum()<1e-6
|
|
#end def at_real_kpoint
|
|
|
|
|
|
def bonds(self,neighbors,vectors=False):
|
|
if self.dim!=3:
|
|
self.error('bonds is currently only implemented for 3 dimensions')
|
|
#end if
|
|
natoms,dim = self.pos.shape
|
|
centers = empty((natoms,neighbors,dim))
|
|
distances = empty((natoms,neighbors))
|
|
vect = empty((natoms,neighbors,dim))
|
|
t = self.tile((3,3,3))
|
|
t.recenter(self.center)
|
|
nn = nearest_neighbors(neighbors+1,t.pos,self.pos)
|
|
for i in range(natoms):
|
|
ii = nn[i,0]
|
|
n=0
|
|
for jj in nn[i,1:]:
|
|
p1 = t.pos[ii]
|
|
p2 = t.pos[jj]
|
|
centers[i,n,:] = (p1+p2)/2
|
|
distances[i,n]= sqrt(((p1-p2)**2).sum())
|
|
vect[i,n,:] = p2-p1
|
|
n+=1
|
|
#end for
|
|
#end for
|
|
sn = self.copy()
|
|
nnr = nn[:,1:].ravel()
|
|
sn.set_elem(t.elem[nnr])
|
|
sn.pos = t.pos[nnr]
|
|
sn.recenter()
|
|
indices = self.locate(sn.pos)
|
|
indices = indices.reshape(natoms,neighbors)
|
|
if not vectors:
|
|
return indices,centers,distances
|
|
else:
|
|
return indices,centers,distances,vect
|
|
#end if
|
|
#end def bonds
|
|
|
|
|
|
def displacement(self,reference,map=False):
|
|
if self.dim!=3:
|
|
self.error('displacement is currently only implemented for 3 dimensions')
|
|
#end if
|
|
ref = reference.tile((3,3,3))
|
|
ref.recenter(reference.center)
|
|
rmap = array(3**3*range(len(reference.pos)),dtype=int)
|
|
nn = nearest_neighbors(1,ref.pos,self.pos).ravel()
|
|
displacement = self.pos - ref.pos[nn]
|
|
if not map:
|
|
return displacement
|
|
else:
|
|
return displacement,rmap[nn]
|
|
#end if
|
|
#end def displacement
|
|
|
|
|
|
def scalar_displacement(self,reference):
|
|
return sqrt((self.displacement(reference)**2).sum(1))
|
|
#end def scalar_displacement
|
|
|
|
|
|
def distortion(self,reference,neighbors):
|
|
if self.dim!=3:
|
|
self.error('distortion is currently only implemented for 3 dimensions')
|
|
#end if
|
|
if reference.volume()/self.volume() < 1.1:
|
|
ref = reference.tile((3,3,3))
|
|
ref.recenter(reference.center)
|
|
else:
|
|
ref = reference
|
|
#end if
|
|
rbi,rbc,rbl,rbv = ref.bonds(neighbors,vectors=True)
|
|
sbi,sbc,sbl,sbv = self.bonds(neighbors,vectors=True)
|
|
nn = nearest_neighbors(1,reference.pos,self.pos).ravel()
|
|
distortion = empty(sbv.shape)
|
|
magnitude = empty((len(self.pos),))
|
|
for i in range(len(self.pos)):
|
|
ir = nn[i]
|
|
bonds = sbv[i]
|
|
rbonds = rbv[ir]
|
|
ib = empty((neighbors,),dtype=int)
|
|
ibr = empty((neighbors,),dtype=int)
|
|
r = range(neighbors)
|
|
rr = range(neighbors)
|
|
for n in range(neighbors):
|
|
mindist = 1e99
|
|
ibmin = -1
|
|
ibrmin = -1
|
|
for nb in r:
|
|
for nbr in rr:
|
|
d = norm(bonds[nb]-rbonds[nbr])
|
|
if d<mindist:
|
|
mindist=d
|
|
ibmin=nb
|
|
ibrmin=nbr
|
|
#end if
|
|
#end for
|
|
#end for
|
|
ib[n]=ibmin
|
|
ibr[n]=ibrmin
|
|
r.remove(ibmin)
|
|
rr.remove(ibrmin)
|
|
#end for
|
|
#end for
|
|
d = bonds[ib]-rbonds[ibr]
|
|
distortion[i] = d
|
|
magnitude[i] = (sqrt((d**2).sum(axis=1))).sum()
|
|
#end for
|
|
return distortion,magnitude
|
|
#end def distortion
|
|
|
|
|
|
def bond_compression(self,reference,neighbors):
|
|
ref = reference
|
|
rbi,rbc,rbl = ref.bonds(neighbors)
|
|
sbi,sbc,sbl = self.bonds(neighbors)
|
|
bondlen = rbl.mean()
|
|
return abs(1.-sbl/bondlen).max(axis=1)
|
|
#end def bond_compression
|
|
|
|
|
|
def boundary(self,dims=(0,1,2),dtol=1e-6):
|
|
dim_eff = len(dims)
|
|
natoms,dim = self.pos.shape
|
|
bdims = array(dim*[False])
|
|
for d in dims:
|
|
bdims[d] = True
|
|
#end for
|
|
p = self.pos[:,bdims]
|
|
indices = convex_hull(p,dim_eff,dtol)
|
|
return indices
|
|
#end def boundary
|
|
|
|
|
|
def embed(self,small,dims=(0,1,2),dtol=1e-6,utol=1e-6):
|
|
small = small.copy()
|
|
small.recenter()
|
|
center = array(self.center)
|
|
self.recenter(small.center)
|
|
bind = small.boundary(dims,dtol)
|
|
bpos = small.pos[bind]
|
|
belem= small.elem[bind]
|
|
nn = nearest_neighbors(1,self.pos,bpos).ravel()
|
|
mpos = self.pos[nn]
|
|
dr = (mpos-bpos).mean(0)
|
|
for i in xrange(len(bpos)):
|
|
bpos[i]+=dr
|
|
#end for
|
|
dmax = sqrt(((mpos-bpos)**2).sum(1)).max()
|
|
for i in xrange(len(small.pos)):
|
|
small.pos[i]+=dr
|
|
#end for
|
|
ins,surface = small.inside(self.pos,tol=utol,separate=True)
|
|
replaced = empty((len(self.pos),),dtype=bool)
|
|
replaced[:] = False
|
|
inside = replaced.copy()
|
|
inside[ins] = True
|
|
nn = nearest_neighbors(1,self.pos,small.pos).ravel()
|
|
elist = list(self.elem)
|
|
plist = list(self.pos)
|
|
pos = small.pos
|
|
elem = small.elem
|
|
for i in xrange(len(pos)):
|
|
n = nn[i]
|
|
if not replaced[n]:
|
|
elist[n] = elem[i]
|
|
plist[n] = pos[i]
|
|
replaced[n] = True
|
|
else:
|
|
elist.append(elem[i])
|
|
plist.append(pos[i])
|
|
#end if
|
|
#end for
|
|
remove = arange(len(self.pos))[inside & logical_not(replaced)]
|
|
remove.sort()
|
|
remove = flipud(remove)
|
|
for i in remove:
|
|
elist.pop(i)
|
|
plist.pop(i)
|
|
#end for
|
|
self.set_elem(elist)
|
|
self.pos = array(plist)
|
|
self.recenter(center)
|
|
return dmax
|
|
#end def embed
|
|
|
|
|
|
def shell(self,cell,neighbors,direction='in'):
|
|
if self.dim!=3:
|
|
self.error('shell is currently only implemented for 3 dimensions')
|
|
#end if
|
|
dd = {'in':equate,'out':negate}
|
|
dir = dd[direction]
|
|
natoms,dim=self.pos.shape
|
|
ncells=3**3
|
|
ntile = ncells*natoms
|
|
pos = empty((ntile,dim))
|
|
ind = empty((ntile,),dtype=int)
|
|
oind = range(natoms)
|
|
for nt in range(ncells):
|
|
n=nt*natoms
|
|
ind[n:n+natoms]=oind[:]
|
|
pos[n:n+natoms]=self.pos[:]
|
|
#end for
|
|
nt=0
|
|
for k in -1,0,1:
|
|
for j in -1,0,1:
|
|
for i in -1,0,1:
|
|
iv = array([[i,j,k]])
|
|
v = dot(iv,self.axes)
|
|
for d in range(dim):
|
|
ns = nt*natoms
|
|
ne = ns+natoms
|
|
pos[ns:ne,d] += v[0,d]
|
|
#end for
|
|
nt+=1
|
|
#end for
|
|
#end for
|
|
#end for
|
|
|
|
inside = empty(ntile,)
|
|
inside[:]=False
|
|
ins = cell.inside(pos)
|
|
inside[ins]=True
|
|
|
|
iishell = set()
|
|
nn = nearest_neighbors(neighbors,pos)
|
|
for ii in range(len(nn)):
|
|
for jj in nn[ii]:
|
|
in1 = inside[ii]
|
|
in2 = inside[jj]
|
|
if dir(in1 and not in2):
|
|
iishell.add(ii)
|
|
#end if
|
|
if dir(in2 and not in1):
|
|
iishell.add(jj)
|
|
#end if
|
|
#end if
|
|
#end if
|
|
ishell = ind[list(iishell)]
|
|
return ishell
|
|
#end def shell
|
|
|
|
|
|
def interpolate(self,other,images,min_image=True,recenter=True,match_com=False,chained=False):
|
|
s1 = self.copy()
|
|
s2 = other.copy()
|
|
s1.remove_folded()
|
|
s2.remove_folded()
|
|
if s2.units!=s1.units:
|
|
s2.change_units(s1.units)
|
|
#end if
|
|
if (s1.elem!=s2.elem).any():
|
|
self.error('cannot interpolate structures, atoms do not match\n atoms1: {0}\n atoms2: {1}'.format(s1.elem,s2.elem))
|
|
#end if
|
|
structures = []
|
|
npath = images+2
|
|
c1 = s1.center
|
|
c2 = s2.center
|
|
ax1 = s1.axes
|
|
ax2 = s2.axes
|
|
pos1 = s1.pos
|
|
pos2 = s2.pos
|
|
min_image &= abs(ax1-ax2).max()<1e-6
|
|
if min_image:
|
|
dp = self.min_image_vectors(pos1,pos2,ax1,pairs=False)
|
|
pos2 = pos1 + dp
|
|
#end if
|
|
if match_com:
|
|
com1 = pos1.mean(axis=0)
|
|
com2 = pos2.mean(axis=1)
|
|
dcom = com1-com2
|
|
for n in xrange(len(pos2)):
|
|
pos2[n] += dcom
|
|
#end for
|
|
if chained:
|
|
other.pos = pos2
|
|
#end if
|
|
#end if
|
|
for n in xrange(npath):
|
|
f1 = 1.-float(n)/(npath-1)
|
|
f2 = 1.-f1
|
|
center = f1*c1 + f2*c2
|
|
axes = f1*ax1 + f2*ax2
|
|
pos = f1*pos1 + f2*pos2
|
|
s = s1.copy()
|
|
s.reset_axes(axes)
|
|
s.center = center
|
|
s.pos = pos
|
|
if recenter:
|
|
s.recenter()
|
|
#end if
|
|
structures.append(s)
|
|
#end for
|
|
return structures
|
|
#end def interpolate
|
|
|
|
|
|
def madelung(self,axes=None,tol=1e-10):
|
|
if self.dim!=3:
|
|
self.error('madelung is currently only implemented for 3 dimensions')
|
|
#end if
|
|
if axes==None:
|
|
a = self.axes.T
|
|
else:
|
|
a = axes.T
|
|
#end if
|
|
volume = abs(det(a))
|
|
b = 2*pi*inv(a).T
|
|
rconv = 8*(3.*volume/(4*pi))**(1./3)
|
|
kconv = 2*pi/rconv
|
|
gconst = -1./(4*kconv**2)
|
|
vmc = -pi/(kconv**2*volume)-2*kconv/sqrt(pi)
|
|
|
|
nshells = 20
|
|
vshell = [0.]
|
|
p = Sobj()
|
|
m = Sobj()
|
|
for n in range(1,nshells+1):
|
|
i = mgrid[-n:n+1,-n:n+1,-n:n+1]
|
|
i = i.reshape(3,(2*n+1)**3)
|
|
R = sqrt((dot(a,i)**2).sum(0))
|
|
G2 = (dot(b,i)**2).sum(0)
|
|
|
|
izero = n + n*(2*n+1) + n*(2*n+1)**2
|
|
|
|
p.R = R[0:izero]
|
|
p.G2 = G2[0:izero]
|
|
m.R = R[izero+1:]
|
|
m.G2 = G2[izero+1:]
|
|
domains = [p,m]
|
|
vshell.append(0.)
|
|
for d in domains:
|
|
vshell[n] += (erfc(kconv*d.R)/d.R).sum() + 4*pi/volume*(exp(gconst*d.G2)/d.G2).sum()
|
|
#end for
|
|
if abs(vshell[n]-vshell[n-1])<tol:
|
|
break
|
|
#end if
|
|
#end for
|
|
vm = vmc + vshell[-1]
|
|
|
|
if axes==None:
|
|
self.Vmadelung = vm
|
|
#end if
|
|
return vm
|
|
#end def madelung
|
|
|
|
|
|
def read_xyz(self,filepath):
|
|
elem = []
|
|
pos = []
|
|
if os.path.exists(filepath):
|
|
lines = open(filepath,'r').read().splitlines()
|
|
else:
|
|
lines = filepath.splitlines()
|
|
#end if
|
|
ntot = 1000000
|
|
natoms = 0
|
|
for l in lines:
|
|
ls = l.strip()
|
|
if ls.isdigit():
|
|
ntot = int(ls)
|
|
#end if
|
|
tokens = ls.split()
|
|
if len(tokens)==4:
|
|
elem.append(tokens[0])
|
|
pos.append(array(tokens[1:],float))
|
|
natoms+=1
|
|
if natoms==ntot:
|
|
break
|
|
#end if
|
|
#end if
|
|
#end for
|
|
self.dim = 3
|
|
self.set_elem(elem)
|
|
self.pos = array(pos)
|
|
self.units = 'A'
|
|
#end def read_xyz
|
|
|
|
|
|
def write(self,filepath=None,format=None):
|
|
if filepath is None and format is None:
|
|
self.error('please specify either the filepath or format arguments to write()')
|
|
elif format is None:
|
|
if '.' in filepath:
|
|
format = filepath.split('.')[-1]
|
|
else:
|
|
self.error('file format could not be determined\neither request the format directly with the format keyword or add a file format extension to the file name')
|
|
#end if
|
|
#end if
|
|
format = format.lower()
|
|
if format=='xyz':
|
|
c = self.write_xyz(filepath)
|
|
elif format=='xsf':
|
|
c = self.write_xsf(filepath)
|
|
else:
|
|
self.error('file format {0} is unrecognized'.format(format))
|
|
#end if
|
|
return c
|
|
#end def write
|
|
|
|
|
|
def write_xyz(self,filepath=None,header=True,units='A'):
|
|
if self.dim!=3:
|
|
self.error('write_xyz is currently only implemented for 3 dimensions')
|
|
#end if
|
|
s = self.copy()
|
|
s.change_units(units)
|
|
c=''
|
|
if header:
|
|
c += str(len(s.elem))+'\n\n'
|
|
#end if
|
|
for i in range(len(s.elem)):
|
|
e = s.elem[i]
|
|
p = s.pos[i]
|
|
c+=' {0:2} {1:12.8f} {2:12.8f} {3:12.8f}\n'.format(e,p[0],p[1],p[2])
|
|
#end for
|
|
if filepath!=None:
|
|
open(filepath,'w').write(c)
|
|
#end if
|
|
return c
|
|
#end def write_xyz
|
|
|
|
|
|
def write_xsf(self,filepath=None):
|
|
if self.dim!=3:
|
|
self.error('write_xsf is currently only implemented for 3 dimensions')
|
|
#end if
|
|
s = self.copy()
|
|
s.change_units('A')
|
|
c = ' CRYSTAL\n'
|
|
c += ' PRIMVEC\n'
|
|
for a in s.axes:
|
|
c += ' {0:12.8f} {1:12.8f} {2:12.8f}\n'.format(*a)
|
|
#end for
|
|
c += ' PRIMCOORD\n'
|
|
c += ' {0} 1\n'.format(len(s.elem))
|
|
for i in range(len(s.elem)):
|
|
e = s.elem[i]
|
|
identified = e in pt.elements
|
|
if not identified:
|
|
if len(e)>2:
|
|
e = e[0:2]
|
|
elif len(e)==2:
|
|
e = e[0:1]
|
|
#end if
|
|
identified = e in pt.elements
|
|
#end if
|
|
if not identified:
|
|
self.error('{0} is not an element\nxsf file cannot be written'.format(e))
|
|
#end if
|
|
enum = pt.elements[e].atomic_number
|
|
r = s.pos[i]
|
|
c += ' {0:>3} {1:12.8f} {2:12.8f} {3:12.8f}\n'.format(enum,r[0],r[1],r[2])
|
|
#end for
|
|
if filepath!=None:
|
|
open(filepath,'w').write(c)
|
|
#end if
|
|
return c
|
|
#end def write_xsf
|
|
|
|
|
|
def read(self,filepath,format=None,elem=None):
|
|
if not os.path.exists(filepath):
|
|
self.error('file {0} does not exist'.format(filepath))
|
|
#end if
|
|
path,file = os.path.split(filepath)
|
|
if format is None:
|
|
if '.' in file:
|
|
name,format = file.rsplit('.',1)
|
|
else:
|
|
format = file
|
|
#else:
|
|
# self.error('file does not have a format extension: {0}'.format(filepath))
|
|
#end if
|
|
#end if
|
|
c = open(filepath,'r').read()
|
|
self.read_contents(c,format,elem=elem)
|
|
return c
|
|
#end def read
|
|
|
|
|
|
def read_contents(self,contents,format,elem=None):
|
|
format = format.lower()
|
|
if format=='poscar':
|
|
self.read_poscar(contents,elem=elem,contents=True)
|
|
else:
|
|
self.error('unrecognized file format: {0}'.format(format))
|
|
#end if
|
|
#end def read_contents
|
|
|
|
|
|
def read_poscar(self,filepath,elem=None,contents=False):
|
|
if not contents:
|
|
lines = open(filepath,'r').read().splitlines()
|
|
else:
|
|
lines = filepath.splitlines()
|
|
#end if
|
|
nlines = len(lines)
|
|
min_lines = 8
|
|
if nlines<min_lines:
|
|
self.error('POSCAR file must have at least {0} lines\n only {1} lines found'.format(min_lines,nlines))
|
|
#end if
|
|
dim = 3
|
|
scale = float(lines[1].strip())
|
|
axes = empty((dim,dim))
|
|
axes[0] = array(lines[2].split(),dtype=float)
|
|
axes[1] = array(lines[3].split(),dtype=float)
|
|
axes[2] = array(lines[4].split(),dtype=float)
|
|
if scale<0.0:
|
|
scale = abs(scale)/det(axes)
|
|
#end if
|
|
axes = scale*axes
|
|
tokens = lines[5].split()
|
|
if tokens[0].isdigit():
|
|
counts = array(tokens,dtype=int)
|
|
if elem is None:
|
|
self.error('variable elem must be provided to read_poscar() to assign atomic species to positions for POSCAR format')
|
|
elif len(elem)!=len(counts):
|
|
self.error('one elem must be given for each element count in the POSCAR file\n number of elem counts: {0}\n number of elem given: {1}'.format(len(counts),len(elem)))
|
|
#end if
|
|
lcur = 6
|
|
else:
|
|
elem = tokens
|
|
counts = array(lines[6].split(),dtype=int)
|
|
lcur = 7
|
|
#end if
|
|
species = elem
|
|
elem = []
|
|
for i in range(len(counts)):
|
|
elem.extend(counts[i]*[species[i]])
|
|
#end for
|
|
self.dim = dim
|
|
self.units = 'A'
|
|
self.reset_axes(axes)
|
|
|
|
if lcur<len(lines) and len(lines[lcur])>0:
|
|
c = lines[lcur].lower().strip()[0]
|
|
lcur+=1
|
|
else:
|
|
return
|
|
#end if
|
|
selective_dynamics = c=='s'
|
|
if selective_dynamics: # Selective dynamics
|
|
if lcur<len(lines) and len(lines[lcur])>0:
|
|
c = lines[lcur].lower().strip()[0]
|
|
lcur+=1
|
|
else:
|
|
return
|
|
#end if
|
|
#end if
|
|
cartesian = c=='c' or c=='k'
|
|
npos = counts.sum()
|
|
if lcur+npos>len(lines):
|
|
return
|
|
#end if
|
|
spos = []
|
|
for i in range(npos):
|
|
spos.append(lines[lcur+i].split())
|
|
#end for
|
|
spos = array(spos)
|
|
pos = array(spos[:,0:3],dtype=float)
|
|
if cartesian:
|
|
pos = scale*pos
|
|
else:
|
|
pos = dot(pos,axes)
|
|
#end if
|
|
self.set_elem(elem)
|
|
self.pos = pos
|
|
if selective_dynamics or spos.shape[1]>3:
|
|
move = array(spos[:,3:6],dtype=str)
|
|
self.freeze(range(self.size()),directions=move=='F')
|
|
#end if
|
|
#end def read_poscar
|
|
|
|
|
|
def plot2d_ax(self,ix,iy,*args,**kwargs):
|
|
if self.dim!=3:
|
|
self.error('plot2d_ax is currently only implemented for 3 dimensions')
|
|
#end if
|
|
iz = list(set([0,1,2])-set([ix,iy]))[0]
|
|
ax = self.axes.copy()
|
|
a = self.axes[iz]
|
|
dc = self.center-ax.sum(0)/2
|
|
pp = array([0*a,ax[ix],ax[ix]+ax[iy],ax[iy],0*a])
|
|
for i in range(len(pp)):
|
|
pp[i]+=dc
|
|
pp[i]-=dot(a,pp[i])/dot(a,a)*a
|
|
#end for
|
|
plot(pp[:,ix],pp[:,iy],*args,**kwargs)
|
|
#end def plot2d_ax
|
|
|
|
|
|
def plot2d_pos(self,ix,iy,*args,**kwargs):
|
|
if self.dim!=3:
|
|
self.error('plot2d_pos is currently only implemented for 3 dimensions')
|
|
#end if
|
|
iz = list(set([0,1,2])-set([ix,iy]))[0]
|
|
pp = self.pos.copy()
|
|
a = self.axes[iz]
|
|
for i in range(len(pp)):
|
|
pp[i] -= dot(a,pp[i])/dot(a,a)*a
|
|
#end for
|
|
plot(pp[:,ix],pp[:,iy],*args,**kwargs)
|
|
#end def plot2d
|
|
|
|
|
|
def plot2d(self,pos_style='b.',ax_style='k-'):
|
|
if self.dim!=3:
|
|
self.error('plot2d is currently only implemented for 3 dimensions')
|
|
#end if
|
|
subplot(1,3,1)
|
|
self.plot2d_ax(0,1,ax_style,lw=2)
|
|
self.plot2d_pos(0,1,pos_style)
|
|
title('a1,a2')
|
|
subplot(1,3,2)
|
|
self.plot2d_ax(1,2,ax_style,lw=2)
|
|
self.plot2d_pos(1,2,pos_style)
|
|
title('a2,a3')
|
|
subplot(1,3,3)
|
|
self.plot2d_ax(2,0,ax_style,lw=2)
|
|
self.plot2d_pos(2,0,pos_style)
|
|
title('a3,a1')
|
|
#end def plot2d
|
|
|
|
def show(self,viewer='vmd',filepath='/tmp/tmp.xyz'):
|
|
if self.dim!=3:
|
|
self.error('show is currently only implemented for 3 dimensions')
|
|
#end if
|
|
self.write_xyz(filepath)
|
|
os.system(viewer+' '+filepath)
|
|
#end def show
|
|
#end class Structure
|
|
Structure.set_operations()
|
|
|
|
|
|
def interpolate_structures(struct1,struct2=None,images=None,min_image=True,recenter=True,match_com=False,repackage=False,chained=False):
|
|
if images is None:
|
|
Structure.class_error('images must be provided','interpolate_structures')
|
|
#end if
|
|
|
|
# if a list of structures is provided,
|
|
# interpolate between pairs in the chain of structures
|
|
if isinstance(struct1,(list,tuple)):
|
|
structures_in = struct1
|
|
structures = []
|
|
for n in xrange(len(structures_in)-1):
|
|
struct1 = structures_in[n]
|
|
struct2 = structures_in[n+1]
|
|
structs = interpolate_structures(struct1,struct2,images,min_image,recenter,match_com,repackage,chained=True)
|
|
if n==0:
|
|
structures.append(structs[0])
|
|
#end if
|
|
structures.extend(structs[1:-1])
|
|
if n==len(structures_in)-2:
|
|
structures.append(structs[-1])
|
|
#end if
|
|
#end for
|
|
return structures
|
|
#end if
|
|
|
|
# handle PhysicalSystem objects indirectly
|
|
system1 = None
|
|
system2 = None
|
|
if not isinstance(struct1,Structure):
|
|
system1 = struct1.copy()
|
|
system1.remove_folded()
|
|
struct1 = system1.structure
|
|
#end if
|
|
if not isinstance(struct2,Structure):
|
|
system2 = struct2.copy()
|
|
system2.remove_folded()
|
|
struct2 = system2.structure
|
|
#end if
|
|
|
|
# perform the interpolation
|
|
structures = struct1.interpolate(struct2,images,min_image,recenter,match_com)
|
|
|
|
# repackage into physical system objects if requested
|
|
if repackage:
|
|
if system1!=None:
|
|
system = system1
|
|
elif system2!=None:
|
|
system = system2
|
|
else:
|
|
Structure.class_error('cannot repackage into physical systems since no system object was provided in place of a structure','interpolate_structures')
|
|
#end if
|
|
systems = []
|
|
for s in structures:
|
|
ps = system.copy()
|
|
ps.structure = s
|
|
systems.append(ps)
|
|
#end for
|
|
result = systems
|
|
else:
|
|
result = structures
|
|
#end if
|
|
|
|
return result
|
|
#end def interpolate_structures
|
|
|
|
|
|
def structure_animation(filepath,structures,tiling=None):
|
|
path,file = os.path.split(filepath)
|
|
if not file.endswith('xyz'):
|
|
Structure.class_error('only xyz files are supported for now','structure_animation')
|
|
#end if
|
|
anim = ''
|
|
for s in structures:
|
|
if tiling is None:
|
|
anim += s.write_xyz()
|
|
else:
|
|
anim += s.tile(tiling).write_xyz()
|
|
#end if
|
|
#end for
|
|
open(filepath,'w').write(anim)
|
|
#end def structure_animation
|
|
|
|
|
|
|
|
class DefectStructure(Structure):
|
|
def __init__(self,*args,**kwargs):
|
|
if len(args)>0 and isinstance(args[0],Structure):
|
|
self.transfer_from(args[0],copy=True)
|
|
else:
|
|
Structure.__init__(self,*args,**kwargs)
|
|
#end if
|
|
#end def __init__
|
|
|
|
|
|
def defect_from_bond_compression(self,compression_cutoff,bond_eq,neighbors):
|
|
bind,bcent,blens = self.bonds(neighbors)
|
|
ind = bind[ abs(blens/bond_eq - 1.) > compression_cutoff ]
|
|
idefect = array(list(set(ind.ravel())))
|
|
defect = self.carve(idefect)
|
|
return defect
|
|
#end def defect_from_bond_compression
|
|
|
|
|
|
def defect_from_displacement(self,displacement_cutoff,reference):
|
|
displacement = self.scalar_displacement(reference)
|
|
idefect = displacement > displacement_cutoff
|
|
defect = self.carve(idefect)
|
|
return defect
|
|
#end def defect_from_displacement
|
|
|
|
|
|
def compare(self,dist_cutoff,d1,d2=None):
|
|
if d2==None:
|
|
d2 = d1
|
|
d1 = self
|
|
#end if
|
|
res = Sobj()
|
|
natoms1 = len(d1.pos)
|
|
natoms2 = len(d2.pos)
|
|
if natoms1<natoms2:
|
|
dsmall,dlarge = d1,d2
|
|
else:
|
|
dsmall,dlarge = d2,d1
|
|
#end if
|
|
nn = nearest_neighbors(1,dlarge,dsmall)
|
|
dist = dsmall.distances(dlarge[nn.ravel()])
|
|
dmatch = dist<dist_cutoff
|
|
ismall = array(range(len(dsmall.pos)))
|
|
ismall = ismall[dmatch]
|
|
ilarge = nn[ismall]
|
|
if natoms1<natoms2:
|
|
i1,i2 = ismall,ilarge
|
|
else:
|
|
i2,i1 = ismall,ilarge
|
|
#end if
|
|
natoms_match = dmatch.sum()
|
|
res.all_match = natoms1==natoms2 and natoms1==natoms_match
|
|
res.natoms_match = natoms_match
|
|
res.imatch1 = i1
|
|
res.imatch2 = i2
|
|
return res
|
|
#end def compare
|
|
#end class DefectStructure
|
|
|
|
|
|
|
|
class Crystal(Structure):
|
|
lattice_constants = obj(
|
|
triclinic = ['a','b','c','alpha','beta','gamma'],
|
|
monoclinic = ['a','b','c','beta'],
|
|
orthorhombic = ['a','b','c'],
|
|
tetragonal = ['a','c'],
|
|
hexagonal = ['a','c'],
|
|
cubic = ['a'],
|
|
rhombohedral = ['a','alpha']
|
|
)
|
|
|
|
lattices = list(lattice_constants.keys())
|
|
|
|
centering_types = obj(
|
|
primitive = 'P',
|
|
base_centered = ('A','B','C'),
|
|
face_centered = 'F',
|
|
body_centered = 'I',
|
|
rhombohedral_centered = 'R'
|
|
)
|
|
|
|
lattice_centerings = obj(
|
|
triclinic = ['P'],
|
|
monoclinic = ['P','A','B','C'],
|
|
orthorhombic = ['P','C','I','F'],
|
|
tetragonal = ['P','I'],
|
|
hexagonal = ['P','R'],
|
|
cubic = ['P','I','F'],
|
|
rhombohedral = ['P']
|
|
)
|
|
|
|
centerings = obj(
|
|
P = [],
|
|
A = [[0,.5,.5]],
|
|
B = [[.5,0,.5]],
|
|
C = [[.5,.5,0]],
|
|
F = [[0,.5,.5],[.5,0,.5],[.5,.5,0]],
|
|
I = [[.5,.5,.5]],
|
|
R = [[2./3, 1./3, 1./3],[1./3, 2./3, 2./3]]
|
|
)
|
|
|
|
cell_types = set(['primitive','conventional'])
|
|
|
|
cell_aliases = obj(
|
|
prim = 'primitive',
|
|
conv = 'conventional'
|
|
)
|
|
cell_classes = obj(
|
|
sc = 'cubic',
|
|
bcc = 'cubic',
|
|
fcc = 'cubic',
|
|
hex = 'hexagonal'
|
|
)
|
|
for lattice in lattices:
|
|
cell_classes[lattice]=lattice
|
|
#end for
|
|
|
|
|
|
#helpful websites for structures
|
|
# wikipedia.org
|
|
# webelements.com
|
|
# webmineral.com
|
|
# springermaterials.com
|
|
|
|
|
|
known_crystals = {
|
|
('diamond','fcc'):obj(
|
|
lattice = 'cubic',
|
|
cell = 'primitive',
|
|
centering = 'F',
|
|
constants = 3.57,
|
|
units = 'A',
|
|
atoms = 'C',
|
|
basis = [[0,0,0],[.25,.25,.25]]
|
|
),
|
|
('diamond','sc'):obj(
|
|
lattice = 'cubic',
|
|
cell = 'conventional',
|
|
centering = 'F',
|
|
constants = 3.57,
|
|
units = 'A',
|
|
atoms = 'C',
|
|
basis = [[0,0,0],[.25,.25,.25]]
|
|
),
|
|
('diamond','prim'):obj(
|
|
lattice = 'cubic',
|
|
cell = 'primitive',
|
|
centering = 'F',
|
|
constants = 3.57,
|
|
units = 'A',
|
|
atoms = 'C',
|
|
basis = [[0,0,0],[.25,.25,.25]]
|
|
),
|
|
('diamond','conv'):obj(
|
|
lattice = 'cubic',
|
|
cell = 'conventional',
|
|
centering = 'F',
|
|
constants = 3.57,
|
|
units = 'A',
|
|
atoms = 'C',
|
|
basis = [[0,0,0],[.25,.25,.25]]
|
|
),
|
|
('wurtzite','prim'):obj(
|
|
lattice = 'hexagonal',
|
|
cell = 'primitive',
|
|
centering = 'P',
|
|
constants = (3.35,5.22),
|
|
units = 'A',
|
|
#atoms = ('Zn','O'),
|
|
#basis = [[1./3, 2./3, 3./8],[1./3, 2./3, 0]]
|
|
atoms = ('Zn','O','Zn','O'),
|
|
basis = [[0,0,5./8],[0,0,0],[2./3,1./3,1./8],[2./3,1./3,1./2]]
|
|
),
|
|
('ZnO','prim'):obj(
|
|
lattice = 'wurtzite',
|
|
cell = 'prim',
|
|
constants = (3.35,5.22),
|
|
units = 'A',
|
|
atoms = ('Zn','O','Zn','O')
|
|
),
|
|
('NaCl','prim'):obj(
|
|
lattice = 'cubic',
|
|
cell = 'primitive',
|
|
centering = 'F',
|
|
constants = 5.64,
|
|
units = 'A',
|
|
atoms = ('Na','Cl'),
|
|
basis = [[0,0,0],[.5,0,0]],
|
|
basis_vectors = 'conventional'
|
|
),
|
|
('rocksalt','prim'):obj(
|
|
lattice = 'cubic',
|
|
cell = 'primitive',
|
|
centering = 'F',
|
|
constants = 5.64,
|
|
units = 'A',
|
|
atoms = ('Na','Cl'),
|
|
basis = [[0,0,0],[.5,0,0]],
|
|
basis_vectors = 'conventional'
|
|
),
|
|
('copper','prim'):obj(
|
|
lattice = 'cubic',
|
|
cell = 'primitive',
|
|
centering = 'F',
|
|
constants = 3.615,
|
|
units = 'A',
|
|
atoms = 'Cu'
|
|
),
|
|
('calcium','prim'):obj(
|
|
lattice = 'cubic',
|
|
cell = 'primitive',
|
|
centering = 'F',
|
|
constants = 5.588,
|
|
units = 'A',
|
|
atoms = 'Ca'
|
|
),
|
|
# http://www.webelements.com/oxygen/crystal_structure.html
|
|
# Phys Rev 160 694
|
|
('oxygen','prim'):obj(
|
|
lattice = 'monoclinic',
|
|
cell = 'primitive',
|
|
centering = 'C',
|
|
constants = (5.403,3.429,5.086,132.53),
|
|
units = 'A',
|
|
angular_units = 'degrees',
|
|
atoms = ('O','O'),
|
|
basis = [[0,0,1.15/2],[0,0,-1.15/2]],
|
|
basis_vectors = identity(3)
|
|
),
|
|
# http://en.wikipedia.org/wiki/Calcium_oxide
|
|
# http://www.springermaterials.com/docs/info/10681719_224.html
|
|
('CaO','prim'):obj(
|
|
lattice = 'NaCl',
|
|
cell = 'prim',
|
|
constants = 4.81,
|
|
atoms = ('Ca','O')
|
|
),
|
|
('CaO','conv'):obj(
|
|
lattice = 'NaCl',
|
|
cell = 'conv',
|
|
constants = 4.81,
|
|
atoms = ('Ca','O')
|
|
),
|
|
# http://en.wikipedia.org/wiki/Copper%28II%29_oxide
|
|
# http://iopscience.iop.org/0953-8984/3/28/001/
|
|
# http://www.webelements.com/compounds/copper/copper_oxide.html
|
|
# http://www.webmineral.com/data/Tenorite.shtml
|
|
('CuO','prim'):obj(
|
|
lattice = 'monoclinic',
|
|
cell = 'primitive',
|
|
centering = 'C',
|
|
constants = (4.683,3.422,5.128,99.54),
|
|
units = 'A',
|
|
angular_units = 'degrees',
|
|
atoms = ('Cu','O','Cu','O'),
|
|
basis = [[.25,.25,0],[0,.418,.25],
|
|
[.25,.75,.5],[.5,.5-.418,.75]],
|
|
basis_vectors = 'conventional'
|
|
),
|
|
('Ca2CuO3','prim'):obj(# kateryna foyevtsova
|
|
lattice = 'orthorhombic',
|
|
cell = 'primitive',
|
|
centering = 'I',
|
|
constants = (3.77,3.25,12.23),
|
|
units = 'A',
|
|
atoms = ('Cu','O','O','O','Ca','Ca'),
|
|
basis = [[ 0, 0, 0 ],
|
|
[ .50, 0, 0 ],
|
|
[ 0, 0, .16026165],
|
|
[ 0, 0, .83973835],
|
|
[ 0, 0, .35077678],
|
|
[ 0, 0, .64922322]],
|
|
basis_vectors = 'conventional'
|
|
),
|
|
('La2CuO4','prim'):obj( #tetragonal structure
|
|
lattice = 'tetragonal',
|
|
cell = 'primitive',
|
|
centering = 'I',
|
|
constants = (3.809,13.169),
|
|
units = 'A',
|
|
atoms = ('Cu','O','O','O','O','La','La'),
|
|
basis = [[ 0, 0, 0],
|
|
[ .5, 0, 0],
|
|
[ 0, .5, 0],
|
|
[ 0, 0, .182],
|
|
[ 0, 0, -.182],
|
|
[ 0, 0, .362],
|
|
[ 0, 0, -.362]]
|
|
),
|
|
('Cl2Ca2CuO2','prim'):obj(
|
|
lattice = 'tetragonal',
|
|
cell = 'primitive',
|
|
centering = 'I',
|
|
constants = (3.869,15.05),
|
|
units = 'A',
|
|
atoms = ('Cu','O','O','Ca','Ca','Cl','Cl'),
|
|
basis = [[ 0, 0, 0 ],
|
|
[ .5, 0, 0 ],
|
|
[ 0, .5, 0 ],
|
|
[ .5, .5, .104],
|
|
[ 0, 0, .396],
|
|
[ 0, 0, .183],
|
|
[ .5, .5, .317]],
|
|
basis_vectors = 'conventional'
|
|
),
|
|
('Cl2Ca2CuO2','afm'):obj(
|
|
lattice = 'tetragonal',
|
|
cell = 'conventional',
|
|
centering = 'P',
|
|
axes = [[.5,-.5,0],[.5,.5,0],[0,0,1]],
|
|
constants = (2*3.869,15.05),
|
|
units = 'A',
|
|
atoms = 4*['Cu','O','O','Ca','Ca','Cl','Cl'],
|
|
basis = [[ 0, 0, 0 ], #Cu
|
|
[ .25, 0, 0 ],
|
|
[ 0, .25, 0 ],
|
|
[ .25, .25, .104],
|
|
[ 0, 0, .396],
|
|
[ 0, 0, .183],
|
|
[ .25, .25, .317],
|
|
[ .25, .25, .5 ], #Cu
|
|
[ .5, .25, .5 ],
|
|
[ .25, .5, .5 ],
|
|
[ .5, .5, .604],
|
|
[ .25, .25, .896],
|
|
[ .25, .25, .683],
|
|
[ .5, .5, .817],
|
|
[ .5, 0, 0 ], #Cu2
|
|
[ .75, 0, 0 ],
|
|
[ .5, .25, 0 ],
|
|
[ .75, .25, .104],
|
|
[ .5, 0, .396],
|
|
[ .5, 0, .183],
|
|
[ .75, .25, .317],
|
|
[ .75, .25, .5 ], #Cu2
|
|
[ 0, .25, .5 ],
|
|
[ .75, .5, .5 ],
|
|
[ 0, .5, .604],
|
|
[ .75, .25, .896],
|
|
[ .75, .25, .683],
|
|
[ 0, .5, .817]],
|
|
basis_vectors = 'conventional'
|
|
),
|
|
('CuO2_plane','prim'):obj(
|
|
lattice = 'tetragonal',
|
|
cell = 'primitive',
|
|
centering = 'P',
|
|
constants = (3.809,13.169),
|
|
units = 'A',
|
|
atoms = ('Cu','O','O'),
|
|
basis = [[ 0, 0, 0],
|
|
[ .5, 0, 0],
|
|
[ 0, .5, 0]]
|
|
),
|
|
('graphite_aa','hex'):obj(
|
|
axes = [[1./2,-sqrt(3.)/2,0],[1./2,sqrt(3.)/2,0],[0,0,1]],
|
|
constants = (2.462,3.525),
|
|
units = 'A',
|
|
atoms = ('C','C'),
|
|
basis = [[0,0,0],[2./3,1./3,0]]
|
|
),
|
|
('graphite_ab','hex'):obj(
|
|
axes = [[1./2,-sqrt(3.)/2,0],[1./2,sqrt(3.)/2,0],[0,0,1]],
|
|
constants = (2.462,3.525),
|
|
units = 'A',
|
|
cscale = (1,2),
|
|
atoms = ('C','C','C','C'),
|
|
basis = [[0,0,0],[2./3,1./3,0],[0,0,1./2],[1./3,2./3,1./2]]
|
|
),
|
|
('graphene','prim'):obj(
|
|
lattice = 'hexagonal',
|
|
cell = 'primitive',
|
|
centering = 'P',
|
|
constants = (2.462,15.0),
|
|
units = 'A',
|
|
atoms = ('C','C'),
|
|
basis = [[0,0,0],[2./3,1./3,0]]
|
|
),
|
|
('graphene','rect'):obj(
|
|
lattice = 'orthorhombic',
|
|
cell = 'conventional',
|
|
centering = 'C',
|
|
constants = (2.462,sqrt(3.)*2.462,15.0),
|
|
units = 'A',
|
|
atoms = ('C','C'),
|
|
basis = [[0,0,0],[1./2,1./6,0]]
|
|
)
|
|
}
|
|
|
|
kc_keys = list(known_crystals.keys())
|
|
for (name,cell) in kc_keys:
|
|
desc = known_crystals[name,cell]
|
|
if cell=='prim' and not (name,'conv') in known_crystals:
|
|
cdesc = desc.copy()
|
|
if cdesc.cell=='primitive':
|
|
cdesc.cell = 'conventional'
|
|
known_crystals[name,'conv'] = cdesc
|
|
elif cdesc.cell=='prim':
|
|
cdesc.cell = 'conv'
|
|
known_crystals[name,'conv'] = cdesc
|
|
#end if
|
|
#end if
|
|
#end if
|
|
del kc_keys
|
|
|
|
|
|
def __init__(self,
|
|
lattice = None,
|
|
cell = None,
|
|
centering = None,
|
|
constants = None,
|
|
atoms = None,
|
|
basis = None,
|
|
basis_vectors = None,
|
|
tiling = None,
|
|
cscale = None,
|
|
axes = None,
|
|
units = None,
|
|
angular_units = 'degrees',
|
|
kpoints = None,
|
|
kgrid = None,
|
|
frozen = None,
|
|
magnetization = None,
|
|
magnetic_order = None,
|
|
magnetic_prim = True,
|
|
kshift = (0,0,0),
|
|
permute = None,
|
|
operations = None,
|
|
elem = None,
|
|
pos = None):
|
|
|
|
if lattice is None and cell is None and atoms is None and units is None:
|
|
return
|
|
#end if
|
|
|
|
gi = obj(
|
|
lattice = lattice ,
|
|
cell = cell ,
|
|
centering = centering ,
|
|
constants = constants ,
|
|
atoms = atoms ,
|
|
basis = basis ,
|
|
basis_vectors = basis_vectors ,
|
|
tiling = tiling ,
|
|
cscale = cscale ,
|
|
axes = axes ,
|
|
units = units ,
|
|
angular_units = angular_units ,
|
|
frozen = frozen ,
|
|
magnetization = magnetization ,
|
|
magnetic_order = magnetic_order,
|
|
magnetic_prim = magnetic_prim ,
|
|
kpoints = kpoints ,
|
|
kgrid = kgrid ,
|
|
kshift = kshift ,
|
|
permute = permute ,
|
|
operations = operations ,
|
|
)
|
|
generation_info = gi.copy()
|
|
|
|
lattice_in = lattice
|
|
if isinstance(lattice,str):
|
|
lattice=lattice.lower()
|
|
#end if
|
|
if isinstance(cell,str):
|
|
cell=cell.lower()
|
|
#end if
|
|
|
|
known_crystal = False
|
|
if (lattice_in,cell) in self.known_crystals:
|
|
known_crystal = True
|
|
lattice_info = self.known_crystals[lattice_in,cell].copy()
|
|
elif (lattice,cell) in self.known_crystals:
|
|
known_crystal = True
|
|
lattice_info = self.known_crystals[lattice,cell].copy()
|
|
#end if
|
|
|
|
if known_crystal:
|
|
while 'lattice' in lattice_info and 'cell' in lattice_info and (lattice_info.lattice,lattice_info.cell) in self.known_crystals:
|
|
li_old = lattice_info
|
|
lattice_info = self.known_crystals[li_old.lattice,li_old.cell].copy()
|
|
del li_old.lattice
|
|
del li_old.cell
|
|
lattice_info.transfer_from(li_old,copy=False)
|
|
#end while
|
|
if 'cell' in lattice_info:
|
|
cell = lattice_info.cell
|
|
elif cell in self.cell_aliases:
|
|
cell = self.cell_aliases[cell]
|
|
elif cell in self.cell_classes:
|
|
lattice = self.cell_classes[cell]
|
|
else:
|
|
self.error('cell shape '+cell+' is not recognized\n the variable cell_classes or cell_aliases must be updated to include '+cell)
|
|
#end if
|
|
if 'lattice' in lattice_info:
|
|
lattice = lattice_info.lattice
|
|
#end if
|
|
if 'angular_units' in lattice_info:
|
|
angular_units = lattice_info.angular_units
|
|
#end if
|
|
inputs = obj(
|
|
centering = centering,
|
|
constants = constants,
|
|
atoms = atoms,
|
|
basis = basis,
|
|
basis_vectors = basis_vectors,
|
|
tiling = tiling,
|
|
cscale = cscale,
|
|
axes = axes,
|
|
units = units
|
|
)
|
|
for var,val in inputs.iteritems():
|
|
if val is None and var in lattice_info:
|
|
inputs[var] = lattice_info[var]
|
|
#end if
|
|
#end for
|
|
centering,constants,atoms,basis,basis_vectors,tiling,cscale,axes,units=inputs.list('centering','constants','atoms','basis','basis_vectors','tiling','cscale','axes','units')
|
|
#end if
|
|
|
|
if constants is None:
|
|
self.error('the variable constants must be provided')
|
|
#end if
|
|
if atoms is None:
|
|
self.error('the variable atoms must be provided')
|
|
#end if
|
|
|
|
if lattice not in self.lattices:
|
|
self.error('lattice type '+str(lattice)+' is not recognized\n valid lattice types are: '+str(list(self.lattices)))
|
|
#end if
|
|
if cell=='conventional':
|
|
if centering is None:
|
|
self.error('centering must be provided for a conventional cell\n options for a '+lattice+' lattice are: '+str(self.lattice_centerings[lattice]))
|
|
elif centering not in self.centerings:
|
|
self.error('centering type '+str(centering)+' is not recognized\n options for a '+lattice+' lattice are: '+str(self.lattice_centerings[lattice]))
|
|
#end if
|
|
#end if
|
|
if isinstance(constants,int) or isinstance(constants,float):
|
|
constants=[constants]
|
|
#end if
|
|
if len(constants)!=len(self.lattice_constants[lattice]):
|
|
self.error('the '+lattice+' lattice depends on the constants '+str(self.lattice_constants[lattice])+'\n you provided '+str(len(constants))+': '+str(constants))
|
|
#end if
|
|
if isinstance(atoms,str):
|
|
if basis!=None:
|
|
atoms = len(basis)*[atoms]
|
|
else:
|
|
atoms=[atoms]
|
|
#end if
|
|
#end if
|
|
if basis is None:
|
|
if len(atoms)==1:
|
|
basis = [(0,0,0)]
|
|
else:
|
|
self.error('must provide as many basis coordinates as basis atoms\n atoms provided: '+str(atoms)+'\n basis provided: '+str(basis))
|
|
#end if
|
|
#end if
|
|
if basis_vectors!=None and not isinstance(basis_vectors,str) and len(basis_vectors)!=3:
|
|
self.error('3 basis vectors must be given, you provided '+str(len(basis))+':\n '+str(basis_vectors))
|
|
#end if
|
|
|
|
if tiling is None:
|
|
tiling = (1,1,1)
|
|
#end if
|
|
if cscale is None:
|
|
cscale = len(constants)*[1]
|
|
#end if
|
|
if len(cscale)!=len(constants):
|
|
self.error('cscale and constants must be the same length')
|
|
#end if
|
|
basis = array(basis)
|
|
tiling = array(tiling,dtype=int)
|
|
cscale = array(cscale)
|
|
constants = cscale*array(constants)
|
|
|
|
a,b,c,alpha,beta,gamma = None,None,None,None,None,None
|
|
if angular_units=='radians':
|
|
pi_1o2 = pi/2
|
|
pi_2o3 = 2*pi/3
|
|
elif angular_units=='degrees':
|
|
pi_1o2 = 90.
|
|
pi_2o3 = 120.
|
|
else:
|
|
self.error('angular units must be radians or degrees\n you provided '+str(angular_units))
|
|
#end if
|
|
if lattice=='triclinic':
|
|
a,b,c,alpha,beta,gamma = constants
|
|
elif lattice=='monoclinic':
|
|
a,b,c,beta = constants
|
|
alpha = gamma = pi_1o2
|
|
elif lattice=='orthorhombic':
|
|
a,b,c = constants
|
|
alpha=beta=gamma=pi_1o2
|
|
elif lattice=='tetragonal':
|
|
a,c = constants
|
|
b=a
|
|
alpha=beta=gamma=pi_1o2
|
|
elif lattice=='hexagonal':
|
|
a,c = constants
|
|
b=a
|
|
alpha=beta=pi_1o2
|
|
gamma=pi_2o3
|
|
elif lattice=='cubic':
|
|
a=constants[0]
|
|
b=c=a
|
|
alpha=beta=gamma=pi_1o2
|
|
elif lattice=='rhombohedral':
|
|
a,alpha = constants
|
|
b=c=a
|
|
beta=gamma=alpha
|
|
#end if
|
|
if angular_units=='degrees':
|
|
alpha *= pi/180
|
|
beta *= pi/180
|
|
gamma *= pi/180
|
|
#end if
|
|
|
|
points = [[0,0,0]]
|
|
#get the conventional axes
|
|
sa,ca = sin(alpha),cos(alpha)
|
|
sb,cb = sin(beta) ,cos(beta)
|
|
sg,cg = sin(gamma),cos(gamma)
|
|
y = (ca-cg*cb)/sg
|
|
a1c = a*array([1,0,0])
|
|
a2c = b*array([cg,sg,0])
|
|
a3c = c*array([cb,y,sqrt(sb**2-y**2)])
|
|
#a1c = array([a,0,0])
|
|
#a2c = array([b*cos(gamma),b*sin(gamma),0])
|
|
#a3c = array([c*cos(beta),c*cos(alpha)*sin(beta),c*sin(alpha)*sin(beta)])
|
|
axes_conv = array([a1c,a2c,a3c]).copy()
|
|
|
|
|
|
#from numpy import dot,arccos
|
|
#from numpy.linalg import norm
|
|
#print a,b,c
|
|
#print alpha,beta,gamma
|
|
#print alpha,arccos(dot(a2c,a3c)/(norm(a2c)*norm(a3c)))
|
|
#print beta, arccos(dot(a3c,a1c)/(norm(a3c)*norm(a1c)))
|
|
#print gamma,arccos(dot(a1c,a2c)/(norm(a1c)*norm(a2c)))
|
|
#exit()
|
|
|
|
|
|
if axes is None:
|
|
if cell not in self.cell_types:
|
|
self.error('cell must be primitive or conventional\n You provided: '+str(cell))
|
|
#end if
|
|
if cell=='primitive' and centering=='P':
|
|
cell='conventional'
|
|
#end if
|
|
#get the primitive axes
|
|
if centering=='P':
|
|
a1 = a1c
|
|
a2 = a2c
|
|
a3 = a3c
|
|
elif centering=='A':
|
|
a1 = a1c
|
|
a2 = (a2c+a3c)/2
|
|
a3 = (-a2c+a3c)/2
|
|
elif centering=='B':
|
|
a1 = (a1c+a3c)/2
|
|
a2 = a2c
|
|
a3 = (-a1c+a3c)/2
|
|
elif centering=='C':
|
|
a1 = (a1c-a2c)/2
|
|
a2 = (a1c+a2c)/2
|
|
a3 = a3c
|
|
elif centering=='I':
|
|
a1=[ a/2, b/2,-c/2]
|
|
a2=[-a/2, b/2, c/2]
|
|
a3=[ a/2,-b/2, c/2]
|
|
elif centering=='F':
|
|
a1=[a/2, b/2, 0]
|
|
a2=[ 0, b/2, c/2]
|
|
a3=[a/2, 0, c/2]
|
|
elif centering=='R':
|
|
a1=[ a, 0, 0]
|
|
a2=[ a/2, a*sqrt(3.)/2, 0]
|
|
a3=[-a/6, a/(2*sqrt(3.)), c/3]
|
|
else:
|
|
self.error('the variable centering must be specified\n valid options are: P,A,B,C,I,F,R')
|
|
#end if
|
|
axes_prim = array([a1,a2,a3])
|
|
if cell=='primitive':
|
|
axes = axes_prim
|
|
elif cell=='conventional':
|
|
axes = axes_conv
|
|
points.extend(self.centerings[centering])
|
|
#end if
|
|
elif known_crystal:
|
|
axes = dot(diag([a,b,c]),array(axes))
|
|
#end if
|
|
points = array(points,dtype=float)
|
|
|
|
elem = []
|
|
pos = []
|
|
if basis_vectors is None:
|
|
basis_vectors = axes
|
|
elif basis_vectors=='primitive':
|
|
basis_vectors = axes_prim
|
|
elif basis_vectors=='conventional':
|
|
basis_vectors = axes_conv
|
|
#end if
|
|
nbasis = len(atoms)
|
|
for point in points:
|
|
for i in range(nbasis):
|
|
atom = atoms[i]
|
|
bpoint = basis[i]
|
|
p = dot(point,axes) + dot(bpoint,basis_vectors)
|
|
elem.append(atom)
|
|
pos.append(p)
|
|
#end for
|
|
#end for
|
|
pos = array(pos)
|
|
|
|
self.set(
|
|
constants = array([a,b,c]),
|
|
angles = array([alpha,beta,gamma]),
|
|
generation_info = generation_info
|
|
)
|
|
|
|
Structure.__init__(
|
|
self,
|
|
axes = axes,
|
|
scale = a,
|
|
elem = elem,
|
|
pos = pos,
|
|
center = axes.sum(0)/2,
|
|
units = units,
|
|
frozen = frozen,
|
|
magnetization = magnetization,
|
|
magnetic_order = magnetic_order,
|
|
magnetic_prim = magnetic_prim,
|
|
tiling = tiling,
|
|
kpoints = kpoints,
|
|
kgrid = kgrid,
|
|
kshift = kshift,
|
|
permute = permute,
|
|
rescale = False,
|
|
operations = operations)
|
|
|
|
#end def __init__
|
|
#end class Crystal
|
|
|
|
|
|
class Jellium(Structure):
|
|
prefactors = obj()
|
|
prefactors.transfer_from({1:2*pi,2:4*pi,3:4./3*pi})
|
|
|
|
def __init__(self,charge=None,background_charge=None,cell=None,volume=None,density=None,rs=None,dim=3,
|
|
axes=None,kpoints=None,kweights=None,kgrid=None,kshift=None,units=None):
|
|
if rs!=None:
|
|
if not dim in self.prefactors:
|
|
self.error('only 1,2, or 3 dimensional jellium is currently supported\n you requested one with dimension {0}'.format(dim))
|
|
#end if
|
|
density = 1.0/(self.prefactors[dim]*rs**dim)
|
|
#end if
|
|
if axes!=None:
|
|
cell = axes
|
|
#end if
|
|
if background_charge!=None:
|
|
charge = background_charge
|
|
#end if
|
|
if cell!=None:
|
|
cell = array(cell)
|
|
dim = len(cell)
|
|
volume = det(cell)
|
|
elif volume!=None:
|
|
volume = float(volume)
|
|
cell = volume**(1./dim)*identity(dim)
|
|
#end if
|
|
if density!=None:
|
|
density = float(density)
|
|
if charge is None and volume!=None:
|
|
charge = density*volume
|
|
elif volume is None and charge!=None:
|
|
volume = charge/density
|
|
cell = volume**(1./dim)*identity(dim)
|
|
#end if
|
|
#end if
|
|
if charge is None or cell is None:
|
|
self.error('not enough information to form jellium structure\n information provided:\n charge: {0}\n cell: {1}\n volume: {2}\n density: {3}\n rs: {4}\n dim: {5}'.format(charge,cell,volume,density,rs,dim))
|
|
#end if
|
|
Structure.__init__(self,background_charge=charge,axes=cell,dim=dim,kpoints=kpoints,kweights=kweights,kgrid=kgrid,kshift=kshift,units=units)
|
|
#end def __init__
|
|
|
|
def density(self):
|
|
return self.background_charge/self.volume()
|
|
#end def density
|
|
|
|
def rs(self):
|
|
return 1.0/(self.density()*self.prefactors[self.dim])**(1./self.dim)
|
|
#end def rs
|
|
|
|
def tile(self):
|
|
self.not_implemented()
|
|
#end def tile
|
|
#end class Jellium
|
|
|
|
|
|
|
|
|
|
def generate_cell(shape,tiling=None,scale=1.,units=None,struct_type=Structure):
|
|
if tiling is None:
|
|
tiling = (1,1,1)
|
|
#end if
|
|
axes = Sobj()
|
|
axes.sc = 1.*array([[ 1,0,0],[0, 1,0],[0,0, 1]])
|
|
axes.bcc = .5*array([[-1,1,1],[1,-1,1],[1,1,-1]])
|
|
axes.fcc = .5*array([[ 1,1,0],[1, 0,1],[0,1, 1]])
|
|
ax = dot(diag(tiling),axes[shape])
|
|
center = ax.sum(0)/2
|
|
c = Structure(axes=ax,scale=scale,center=center,units=units)
|
|
if struct_type!=Structure:
|
|
c=c.upcast(struct_type)
|
|
#end if
|
|
return c
|
|
#end def generate_cell
|
|
|
|
|
|
|
|
def generate_structure(type='crystal',*args,**kwargs):
|
|
if type=='crystal':
|
|
s = generate_crystal_structure(*args,**kwargs)
|
|
elif type=='defect':
|
|
s = generate_defect_structure(*args,**kwargs)
|
|
elif type=='atom':
|
|
s = generate_atom_structure(*args,**kwargs)
|
|
elif type=='dimer':
|
|
s = generate_dimer_structure(*args,**kwargs)
|
|
elif type=='jellium':
|
|
s = generate_jellium_structure(*args,**kwargs)
|
|
elif type=='empty':
|
|
s = Structure()
|
|
elif type=='basic':
|
|
s = Structure(*args,**kwargs)
|
|
else:
|
|
Structure.class_error(str(type)+' is not a valid structure type\n options are crystal, defect, or atom')
|
|
#end if
|
|
return s
|
|
#end def generate_structure
|
|
|
|
|
|
|
|
|
|
def generate_atom_structure(atom=None,units='A',Lbox=None,skew=0,axes=None,kgrid=(1,1,1),kshift=(0,0,0),struct_type=Structure):
|
|
if Lbox!=None:
|
|
axes = [[Lbox*(1-skew),0,0],[0,Lbox,0],[0,0,Lbox*(1+skew)]]
|
|
#end if
|
|
if axes is None:
|
|
s = Structure(elem=[atom],pos=[[0,0,0]],units=units)
|
|
else:
|
|
s = Structure(elem=[atom],pos=[[0,0,0]],axes=axes,kgrid=kgrid,kshift=kshift,units=units)
|
|
s.center_molecule()
|
|
#end if
|
|
return s
|
|
#end def generate_atom_structure
|
|
|
|
|
|
def generate_dimer_structure(dimer=None,units='A',separation=None,Lbox=None,skew=0,axes=None,kgrid=(1,1,1),kshift=(0,0,0),struct_type=Structure,axis='x'):
|
|
if separation is None:
|
|
Structure.class_error('separation must be provided to construct dimer','generate_dimer_structure')
|
|
#end if
|
|
if Lbox!=None:
|
|
axes = [[Lbox*(1-skew),0,0],[0,Lbox,0],[0,0,Lbox*(1+skew)]]
|
|
#end if
|
|
if axis=='x':
|
|
p2 = [separation,0,0]
|
|
elif axis=='y':
|
|
p2 = [0,separation,0]
|
|
elif axis=='z':
|
|
p2 = [0,0,separation]
|
|
else:
|
|
Structure.class_error('dimer orientation axis must be x,y,z\n you provided: {0}'.format(axis),'generate_dimer_structure')
|
|
#end if
|
|
if axes is None:
|
|
s = Structure(elem=dimer,pos=[[0,0,0],p2],units=units)
|
|
else:
|
|
s = Structure(elem=dimer,pos=[[0,0,0],p2],axes=axes,kgrid=kgrid,kshift=kshift,units=units)
|
|
s.center_molecule()
|
|
#end if
|
|
return s
|
|
#end def generate_dimer_structure
|
|
|
|
|
|
def generate_jellium_structure(*args,**kwargs):
|
|
return Jellium(*args,**kwargs)
|
|
#end def generate_jellium_structure
|
|
|
|
|
|
|
|
|
|
def generate_crystal_structure(lattice=None,cell=None,centering=None,
|
|
constants=None,atoms=None,basis=None,
|
|
basis_vectors=None,tiling=None,cscale=None,
|
|
axes=None,units=None,angular_units='degrees',
|
|
magnetization=None,magnetic_order=None,magnetic_prim=True,
|
|
kpoints=None,kgrid=None,kshift=(0,0,0),permute=None,
|
|
structure=None,shape=None,element=None,scale=None, #legacy inputs
|
|
operations=None,
|
|
struct_type=Crystal,elem=None,pos=None,frozen=None,
|
|
posu=None):
|
|
|
|
if structure!=None:
|
|
lattice = structure
|
|
#end if
|
|
if shape!=None:
|
|
cell = shape
|
|
#end if
|
|
if element!=None:
|
|
atoms = element
|
|
#end if
|
|
if scale!=None:
|
|
constants = scale
|
|
#end if
|
|
|
|
#interface for total manual specification
|
|
# this is only here because 'crystal' is default and must handle other cases
|
|
if elem!=None and (pos!=None or posu!=None):
|
|
return Structure(
|
|
axes = axes,
|
|
elem = elem,
|
|
pos = pos,
|
|
units = units,
|
|
frozen = frozen,
|
|
magnetization = magnetization,
|
|
magnetic_order = magnetic_order,
|
|
magnetic_prim = magnetic_prim,
|
|
tiling = tiling,
|
|
kpoints = kpoints,
|
|
kgrid = kgrid,
|
|
kshift = kshift,
|
|
permute = permute,
|
|
rescale = False,
|
|
operations = operations,
|
|
posu = posu)
|
|
elif isinstance(structure,Structure):
|
|
if kpoints!=None:
|
|
structure.add_kpoints(kpoints,kweights)
|
|
#end if
|
|
if kgrid!=None:
|
|
structure.add_kmesh(kgrid,kshift)
|
|
#end if
|
|
return structure
|
|
#end if
|
|
|
|
|
|
s=Crystal(
|
|
lattice = lattice ,
|
|
cell = cell ,
|
|
centering = centering ,
|
|
constants = constants ,
|
|
atoms = atoms ,
|
|
basis = basis ,
|
|
basis_vectors = basis_vectors ,
|
|
tiling = tiling ,
|
|
cscale = cscale ,
|
|
axes = axes ,
|
|
units = units ,
|
|
angular_units = angular_units ,
|
|
frozen = frozen ,
|
|
magnetization = magnetization ,
|
|
magnetic_order = magnetic_order,
|
|
magnetic_prim = magnetic_prim ,
|
|
kpoints = kpoints ,
|
|
kgrid = kgrid ,
|
|
kshift = kshift ,
|
|
permute = permute ,
|
|
operations = operations ,
|
|
elem = elem ,
|
|
pos = pos
|
|
)
|
|
|
|
if struct_type!=Crystal:
|
|
s=s.upcast(struct_type)
|
|
#end if
|
|
|
|
return s
|
|
#end def generate_crystal_structure
|
|
|
|
|
|
|
|
defects = obj(
|
|
diamond = obj(
|
|
H = obj(
|
|
pristine = [[0,0,0]],
|
|
defect = [[0,0,0],[.625,.375,.375]]
|
|
),
|
|
T = obj(
|
|
pristine = [[0,0,0]],
|
|
defect = [[0,0,0],[.5,.5,.5]]
|
|
),
|
|
X = obj(
|
|
pristine = [[.25,.25,.25]],
|
|
defect = [[.39,.11,.15],[.11,.39,.15]]
|
|
),
|
|
FFC = obj(
|
|
#pristine = [[ 0, 0, 0],[.25,.25,.25]],
|
|
#defect = [[.151,.151,-.08],[.10,.10,.33]]
|
|
pristine = [[ 0, 0, 0],[.25 ,.25 ,.25 ],[.5 ,.5 , 0],[.75 ,.75 ,.25 ],[1.5 ,1.5 ,0 ],[1.75 ,1.75 ,.25 ]],
|
|
defect = [[.151,.151,-.081],[.099,.099,.331],[.473,.473,-.059],[.722,.722,.230],[1.528,1.528,.020],[1.777,1.777,.309]]
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
def generate_defect_structure(defect,structure,shape=None,element=None,
|
|
tiling=None,scale=1.,kgrid=None,kshift=(0,0,0),
|
|
units=None,struct_type=DefectStructure):
|
|
if structure in defects:
|
|
dstruct = defects[structure]
|
|
else:
|
|
DefectStructure.class_error('defects for '+structure+' structure have not yet been implemented')
|
|
#end if
|
|
if defect in dstruct:
|
|
drep = dstruct[defect]
|
|
else:
|
|
DefectStructure.class_error(defect+' defect not found for '+structure+' structure')
|
|
#end if
|
|
|
|
ds = generate_crystal_structure(
|
|
structure = structure,
|
|
shape = shape,
|
|
element = element,
|
|
tiling = tiling,
|
|
scale = 1.0,
|
|
kgrid = kgrid,
|
|
kshift = kshift,
|
|
units = units,
|
|
struct_type = struct_type
|
|
)
|
|
|
|
ds.replace(drep.pristine,pos=drep.defect)
|
|
|
|
ds.rescale(scale)
|
|
|
|
return ds
|
|
#end def generate_defect_structure
|
|
|
|
|
|
def read_structure(filepath,elem=None):
|
|
s = generate_structure('empty')
|
|
s.read(filepath,elem=elem)
|
|
return s
|
|
#end def read_structure
|
|
|
|
|
|
#if __name__=='__main__':
|
|
# from numpy.random import rand
|
|
# from matplotlib.pyplot import figure,plot,show
|
|
#
|
|
# ax = array([[1.0,.3,.1],[.2,1.2,-.1],[.2,.1,1.]])
|
|
# #ax = array([[1.0,0,0],[0,1.,0],[0,0,1.]])
|
|
# pos = 4*(rand(50,3)-.5)
|
|
# c = (ax[0]+ax[1])/2
|
|
# elem = []
|
|
# for i in range(len(pos)):
|
|
# elem.append('Ge')
|
|
# #end for
|
|
# s = Structure(axes=ax,pos=pos,elem=elem)
|
|
#
|
|
# #figure()
|
|
# #plot(s.pos[:,0],s.pos[:,1],'bo')
|
|
# #plot([0,x1,x1+x2,x2,0],[0,y1,y1+y2,y2,0],'k-',lw=2)
|
|
# #s.recenter(c)
|
|
# #plot(s.pos[:,0],s.pos[:,1],'r.')
|
|
#
|
|
# #figure()
|
|
# #s.plot2d('bo')
|
|
# #s.recenter(c)
|
|
# #s.plot2d('r.')
|
|
# #show()
|
|
#
|
|
# figure()
|
|
# s.recenter(c)
|
|
# s.plot2d('bo')
|
|
# cs=s.carve(s.axes/2,s.center)
|
|
# cs.plot2d('r.')
|
|
# show()
|
|
##end if
|
|
|
|
|
|
|
|
if __name__=='__main__':
|
|
a = convert(5.639,'A','B')
|
|
|
|
large = generate_structure('diamond','fcc','Ge',(2,2,2),a)
|
|
small = generate_structure('diamond','fcc','Ge',(1,1,1),a)
|
|
|
|
large.add_kmesh((1,1,1))
|
|
|
|
kmap = large.fold(small)
|
|
|
|
print small.kpoints_unit()
|
|
|
|
#end if
|