Exemple #1
0
    def __init__(self, modelfilename=None, comment=None, xsize=None, ysize=None, zsize=None, atoms=None):
        """ sets:
                self.comment
                self.xsize
                self.ysize
                self.zsize
                self.atoms
                self.natoms
                self.atomtypes
                self.xx, self.yy, self.zz
                self.filename
            Extra routines can set:
                self.coord_numbers """

        self.filename = modelfilename
        if self.filename is not None:
            self._load()
        else:
            self.comment = comment
            self.xsize = xsize
            self.ysize = ysize
            self.zsize = zsize
            self.atoms = [atom.copy() for atom in atoms]
            self.natoms = len(self.atoms)

        if(self.xsize and self.ysize and self.zsize):
            self.hutch = Hutch(self)

        if(not hasattr(self,'atomtypes')):
            self.atomtypes = Counter(atom.z for atom in self.atoms)
Exemple #2
0
    def __init__(self, *args, **kwargs):
        """ sets:
                self.comment
                self.lx
                self.ly
                self.lz
                self.atoms
                self.natoms
                self.atomtypes
            Extra routines can set:
                self.coord_numbers """

        super(Model,self).__init__()
        if(len(args) == 1):
            modelfile = args[0]
            if modelfile[-4:] == '.xyz':
                self.read_xyz(modelfile)
            elif modelfile[-4:] == '.dat':
                self.read_dat(modelfile)
            else:
                raise Exception("Unknown input model type!")
        elif(len(args) == 5):
            self.comment = args[0]
            self.lx = args[1]
            self.ly = args[2]
            self.lz = args[3]
            self.atoms = args[4]
            self.natoms = len(args[4])
        else:
            raise Exception("Unknown input parameters to Model()!")
        self.hutch = Hutch(self)

        self.atomtypes = {}
        for atom in self.atoms:
            self.atomtypes[atom.z] = self.atomtypes.get(atom.z, 0) + 1
Exemple #3
0
class Model(object):
    """
        Holds an atomic model and defines a set of helper functions for it.
        functions:
            write(outfile=stdout):  Supported extensions are xyz, dat, and a simple version of cif
            generate_neighbors(cutoff):  Generates neighbors for every atom using the supplied cutoff (which can be a float or a dictionary)
            get_atoms_in_cutoff(atom,cutoff)
            nearest_neigh(atom)
    """
    
    def __init__(self, modelfilename=None, comment=None, xsize=None, ysize=None, zsize=None, atoms=None):
        """ sets:
                self.comment
                self.xsize
                self.ysize
                self.zsize
                self.atoms
                self.natoms
                self.atomtypes
                self.xx, self.yy, self.zz
                self.filename
            Extra routines can set:
                self.coord_numbers """

        self.filename = modelfilename
        if self.filename is not None:
            self._load()
        else:
            self.comment = comment
            self.xsize = xsize
            self.ysize = ysize
            self.zsize = zsize
            self.atoms = [atom.copy() for atom in atoms]
            self.natoms = len(self.atoms)

        if(self.xsize and self.ysize and self.zsize):
            self.hutch = Hutch(self)

        if(not hasattr(self,'atomtypes')):
            self.atomtypes = Counter(atom.z for atom in self.atoms)

    def __contains__(self, key):
        return key in self.atoms

    def __getitem__(self, atomid):
        try:
            if(self.atoms[atomid].id == atomid):
                return self.atoms[atomid]
            else:
                raise Exception("Atom positions have been shuffled or you gave an out of bounds index.")
        except:
            for atom in self.atoms:
                if(atom.id == atomid):
                    return atom
        return None

    def __len__(self):
        assert self.natoms == len(self.atoms)
        return self.natoms
        
    def add(self, atom, reset_id=False):
        """ Adds atom 'atom' to the model. Note that there is no error checking to prevent
        the user from adding an atom twice. Be careful not to do that. """
        if reset_id:
            atom.id = self.natoms
        self.atoms.append(atom)
        self.natoms += 1
        self.atomtypes[atom.z] += 1
        try:
            self.hutch.add_atom(atom)
        except AttributeError:
            pass

    def remove(self, atom):
        """ Removes atom 'atom' from the model """
        try:
            self.hutch.remove_atom(atom)
        except:# AttributeError or ValueError:
            pass
        self.atoms.remove(atom)
        self.natoms -= 1
        self.atomtypes[atom.z] -= 1

    def _load(self):
        filename, ext = os.path.splitext(self.filename)
        if(ext == 'xyz' or ext == '.xyz'):
            self._load_xyz()
        elif(ext == 'dat' or ext == '.dat'):
            self._load_dat()
        else:
            raise Exception("Unknown input model type! File {0} has extension {1}".format(self.filename,ext))

    def _load_xyz(self):
        modelfile = self.filename
        self.atoms = []
        self.atomtypes = defaultdict(int)
        self.natoms = 0
        with open(modelfile) as f:
            natoms = int(f.readline().strip())
            self.comment = f.readline().strip()
            try:
                self.xsize,self.ysize,self.zsize = tuple([float(x) for x in self.comment.split()[:3]])
            except:
                self.xsize,self.ysize,self.zsize = (None,None,None)
            for i in range(natoms):
                znum,x,y,z = tuple(f.readline().strip().split())
                x = float(x); y = float(y); z = float(z)
                atom = Atom(i,znum,x,y,z)
                self.add(atom)

    def _load_dat(self):
        """ Still need to refactor """
        modelfile = self.filename
        with open(modelfile) as f:
            content = f.readlines()

        self.comment = content.pop(0) # Comment line
        content = [x for x in content if not x.startswith('#')]

        for line in content:
            if('atoms' in line): self.natoms = int(line.split()[0])
            if('xlo' in line and 'xhi' in line):
                self.xsize = abs(float(line.split()[0])) + abs(float(line.split()[1]))
            if('ylo' in line and 'yhi' in line):
                self.ysize = abs(float(line.split()[0])) + abs(float(line.split()[1]))
            if('zlo' in line and 'zhi' in line):
                self.zsize = abs(float(line.split()[0])) + abs(float(line.split()[1]))
            if('atom types' in line): nelems = int(line.split()[0])
            if('Masses' in line): mflag = content.index(line) + 1
            if('Atoms' in line): aflag = content.index(line) + 1
        try:
            mflag
        except NameError:
            raise Exception("ERROR! You need to define the masses in the .dat file.")
        atomtypes = {}
        while(nelems > 0):
            if(len(content[mflag].split()) == 2):
                atomtypes[int(content[mflag].split()[0])] = masses.get_znum(float(content[mflag].split()[1]))
                nelems -= 1
            mflag += 1
        self.atoms = []
        natoms = self.natoms
        while(natoms > 0):
            sline = content[aflag].split()
            if(len(sline) >= 5):
                # We found an atom
                id = int(sline[0])
                type = int(sline[1])
                x = float(sline[2])
                y = float(sline[3])
                z = float(sline[4])
                znum = atomtypes[type]
                # Add it to the model
                self.atoms.append(Atom(id,znum,x,y,z))
                natoms -= 1
            aflag += 1

    def write(self, outfile=None, ext=None, reverse=True):
        if(outfile is not None and ext is None): _,ext = os.path.splitext(outfile)
        elif(ext is None): ext = '.xyz'
        if(ext == '.xyz' or ext == 'xyz'):
            self._write_xyz(outfile)
        elif(ext == '.dat' or ext == 'dat'):
            self._write_dat(outfile, reverse)
        elif(ext == '.cif' or ext == 'cif'):
            self._write_cif(outfile)
        return ''

    def _write_dat(self, outfile=None, reverse=True):
        if outfile is None: of = sys.stdout
        else:               of = open(outfile,'w')
        of.write(self.comment+'\n')
        of.write('{0} atoms\n\n'.format(self.natoms))
        of.write('{0} atom types\n\n'.format(len(self.atomtypes)))
        of.write('{0} {1} xlo xhi\n'.format(-self.xsize/2,self.xsize/2))
        of.write('{0} {1} ylo yhi\n'.format(-self.ysize/2,self.ysize/2))
        of.write('{0} {1} zlo zhi\n\n'.format(-self.zsize/2,self.zsize/2))
        of.write('Masses\n\n')
        atomtypes = list(self.atomtypes)
        atomtypes.sort()
        if reverse:
            atomtypes.reverse()
        for i,z in enumerate(atomtypes):
            of.write('{0} {1}\n'.format(i+1,round(masses.get_mass(z),2)))
        of.write('\n')
        of.write('Atoms\n\n')
        for i,atom in enumerate(self.atoms):
            of.write('{0} {1} {2} {3} {4}\n'.format(atom.id+1, atomtypes.index(atom.z)+1, atom.coord[0], atom.coord[1], atom.coord[2]))

    def _write_xyz(self, outfile=None):
        if outfile is None: of = sys.stdout
        else:               of = open(outfile,'w')
        of.write(str(self.natoms)+'\n')
        of.write("{1} {2} {3} {0}\n".format(self.comment.strip(),self.xsize, self.xsize, self.zsize))
        for i,atom in enumerate(self.atoms):
            of.write(atom.realxyz()+'\n')

    def _write_cif(self, outfile=None):
        if outfile is None: of = sys.stdout
        else:               of = open(outfile,'w')
        of.write('_pd_phase_name\t'+self.comment+'\n')
        of.write('_cell_length_a '+str(self.xsize)+'\n')
        of.write('_cell_length_b '+str(self.ysize)+'\n')
        of.write('_cell_length_c '+str(self.zsize)+'\n')
        of.write('_cell_angle_alpha 90\n_cell_angle_beta 90\n_cell_angle_gamma 90\n')
        of.write('_symmetry_space_group_name_H-M         \'P 1\'\n_symmetry_Int_Tables_number            1\n\n')
        of.write('loop_\n_symmetry_equiv_pos_as_xyz\n   \'x, y, z\'\n\n')
        of.write('loop_\n   _atom_site_label\n   _atom_site_occupancy\n   _atom_site_fract_x\n   _atom_site_fract_y\n   _atom_site_fract_z\n   _atom_site_adp_type\n   _atom_site_B_iso_or_equiv\n   _atom_site_type_symbol\n')
        atomtypes = {}
        for atom in self.atoms:
            atomtypes[atom.z] = atomtypes.get(atom.z, 0) + 1
            of.write('   '+znum2sym.z2sym(atom.z)+str(atomtypes[atom.z])+'\t1.0\t'+str(atom.coord[0]/self.xsize+0.5)+'\t'+str(atom.coord[1]/self.ysize+0.5)+'\t'+str(atom.coord[2]/self.zsize+0.5)+'\tBiso\t1.000000\t'+znum2sym.z2sym(atom.z)+'\n')
        of.write('\n')
        of.close()

    def __str__(self):
        return self.write()

    @property
    def composition(self):
        d = {}
        for key in self.atomtypes:
            d[znum2sym.z2sym(key)] = self.atomtypes[key]/float(self.natoms)
        return d

    @property
    def coordinates(self):
        xx = np.fromfunction(lambda i: self.atoms[i].coord[0], self.natoms, dtype=np.float)
        yy = np.fromfunction(lambda i: self.atoms[i].coord[1], self.natoms, dtype=np.float)
        zz = np.fromfunction(lambda i: self.atoms[i].coord[2], self.natoms, dtype=np.float)
        return xx,yy,zz

    def generate_neighbors(self, cutoff):
        for atom in self.atoms:
            atom.neighs = self.get_atoms_in_cutoff(atom,cutoff)
            if(atom in atom.neighs): atom.neighs.remove(atom)
            atom.cn = len(atom.neighs)

    def check_neighbors(self):
        for atom in self.atoms:
            for n in atom.neighs:
                if atom not in n.neighs:
                    print("You're neighbors are screwed up! Atom IDs are {0}, {1}.".format(atom.id,n.id))
                    print("Neighbors of: {0}".format(atom))
                    print(atom.neighs)
                    print("Neighbors of: {0}".format(n))
                    print(n.neighs)
                    print("Dist = {0}".format(self.dist(atom,n)))
                    print("Dist = {0}".format(self.dist(n,atom)))

    def generate_average_coord_numbers(self):
        """ atom.neighs must be defined first for all atoms
            Form will be:
                {'Cu-Al': 4.5, ... }
        """
        coord_numbers = {}
        for typea in self.atomtypes:
            coord_numbers[znum2sym.z2sym(typea)] = 0
            for typeb in self.atomtypes:
                coord_numbers[znum2sym.z2sym(typea)+'-'+znum2sym.z2sym(typeb)] = 0
        for atom in self.atoms:
            for n in atom.neighs:
                coord_numbers[znum2sym.z2sym(atom.z)] += 1
                coord_numbers[znum2sym.z2sym(atom.z)+'-'+znum2sym.z2sym(n.z)] += 1
        for key in coord_numbers:
            elem = znum2sym.sym2z(key.split('-')[0])
            coord_numbers[key] /= float(self.atomtypes[elem])
        return coord_numbers

    def get_atoms_in_cutoff(self,atom,cutoff):
        return self.hutch.get_atoms_in_radius(atom,cutoff)

    def dist(self, atom1, atom2, pbc=True):
        x = (atom1.coord[0] - atom2.coord[0])
        y = (atom1.coord[1] - atom2.coord[1])
        z = (atom1.coord[2] - atom2.coord[2])
        if pbc:
            x = x - self.xsize*round(x/self.xsize)
            y = y - self.ysize*round(y/self.ysize)
            z = z - self.zsize*round(z/self.zsize)
        return math.sqrt(x**2+y**2+z**2)

    def dist2(self, atom1, atom2, pbc=True):
        x = (atom1.coord[0] - atom2.coord[0])
        y = (atom1.coord[1] - atom2.coord[1])
        z = (atom1.coord[2] - atom2.coord[2])
        if pbc:
            x = x - self.xsize*round(x/self.xsize)
            y = y - self.ysize*round(y/self.ysize)
            z = z - self.zsize*round(z/self.zsize)
        return x**2+y**2+z**2

    def get_all_neighbor_distances(self):
        dists = []
        for atomi in self.atoms:
            for atomj in atomi.neighs: #self.atoms[self.atoms.index(atomi)+1:]:
                if atomi is atomj: continue
                dists.append( (atomi, atomj, self.dist(atomi,atomj)) )
        return dists

    def nearest_neigh_of_same_type(self, atom, cutoff=3.5):
        """ returns the nearest atom of the same species as 'atom' """
        atoms = []
        while(len(atoms) == 0):
            atoms = self.get_atoms_in_cutoff(atom, cutoff)
            atoms = [x for x in atoms if x.z == atom.z]
            #if atom in atoms: atoms.remove(atom)
            cutoff *= 2
        cutoff /= 2 # set back to the value used in case I want it later
        d = float("inf")
        for atomi in atoms:
            dt = self.dist(atom, atomi)
            if dt < d:
                d = dt
                a = atomi
        if(a.z != atom.z): raise Exception("Error! Function 'nearest_neigh_of_same_type' didn't work!")
        return a

    def nearest_neigh(self, atom):
        """ returns an atoms nearest neighbor """
        atoms = self.hutch.get_atoms_in_same_hutch(atom)[:]
        if atom in atoms: atoms.remove(atom)

        # This generation of nearby hutches isn't perfect but it will work
        rots = [(1,0,0),(0,1,0),(0,0,1)]
        i = 0
        while len(atoms) == 0:
            hutch = ((hutch[0]+rots[i][0])%self.hutch.nhutchs,(hutch[1]+rots[i][1])%self.hutch.nhutchs,(hutch[2]+rots[i][2])%self.hutch.nhutchs)
            i = (i+1) % 3
            atoms = self.hutch.hutchs[hutch]
            if atom in atoms: atoms.remove(atom)
        start = atoms[0]

        atoms = self.get_atoms_in_cutoff(atom,self.dist(atom,start))
        #if atom in atoms: atoms.remove(atom)
        d = float("inf")
        for atomi in atoms:
            dt = self.dist(atom,atomi)
            if dt < d:
                d = dt
                a = atomi
        return a

    def print_bond_stats(self):
        # TODO Rewrite this function if I ever need it again. There was a "self.bonds = composition" line before the last for loop in "generate_average_coord_numbers" to define self.bonds, but I deleted that line.
        nbonds = 0.0
        for key in self.bonds:
            if '-' not in key:
                nbonds += self.bonds[key]
        bond_stats = self.bonds.copy()
        for key in bond_stats.keys():
            if '-' in key:
                del bond_stats[key]
            else:
                bond_stats[key] *= 1.0/(nbonds*self.composition[key])
        print('Bond statistics:')
        pprint(self.bonds)
        print('Ratio of actual/expected bonds:')
        pprint(bond_stats)

    def radial_composition(self, outfile):
        """ Creates 1D waves stored in outfile for each element in the model. Each 
        wave is a histogram of the number of atoms between two radial positions
        starting at the center of model and radiating outward. """
        # TODO Rewrite if I ever need this again
        npix = 16
        keys = self.atomtypes.keys()
        #histo = [[0.0 for x in range(npix)] for key in keys]
        histo = [{} for x in range(npix)]
        dx = (self.xsize/2.0)/npix # Cube assumed
        for i,r in enumerate(drange(dx, npix*dx-dx, dx)):
            atoms = self.get_atoms_in_cutoff( (0.0,0.0,0.0), r)
            print(r, len(atoms))
            comp = {}
            for atom in atoms:
                comp[str(atom.z)] = comp.get(str(atom.z),0) + 1.0
            for type in self.atomtypes:
                if( str(type) not in comp.keys()):
                    comp[str(type)] = 0.0
            comp['Total'] = len(atoms)
            histo[i] = comp
        of = open(outfile,'w')
        of.write('IGOR\n')
        for atomtype in keys:
            of.write('\nWAVES/N=({0})\t {1}\nBEGIN\n'.format(npix,'partial_radial_comp_'+znum2sym.z2sym(atomtype)))
            for i in range(npix-2):
                if(i != 0):
                    of.write("{0} ".format((histo[i][str(atomtype)] - histo[i-1][str(atomtype)])/( 4.0/3.0*np.pi*( (i*dx)**3 - ((i-1)*dx)**3 ))))
                    #print("{1}  {0} ".format(histo[i][str(atomtype)],i*dx))
                    #print("  {1}  {0} ".format(histo[i][str(atomtype)] - histo[i-1][str(atomtype)],i*dx))
                else:
                    of.write("{0} ".format(histo[i][str(atomtype)]))
                    #print("{1}  {0} ".format(histo[i][str(atomtype)],i*dx))
            of.write("\n")
            of.write('END\n')
            of.write('X SetScale x 0,{1}, {0};\n'.format('partial_comp_'+znum2sym.z2sym(atomtype),npix*dx-dx))
        of.close()

    def local_composition(self, outfile):
        """ Variable radius sliding average Goes pixel by pixel and calculates the
        composition around that pixel (within some radius) and assigns the center
        pixel that composition. Use a 256 x 256 x 256 matrix. """
        # TODO Rewrite if I ever need this again
        radius = 3.6 * 2
        npix = 64
        #mat = np.zeros((npix,npix,npix),dtype=np.float)
        #mat = np.zeros((npix,npix,npix),dtype={'names':['col1', 'col2', 'col3'], 'formats':['f4','f4','f4']})
        #mat = np.zeros((npix,npix,npix),dtype={'names':['40', '13', '29'], 'formats':['f4','f4','f4']})
        #mat = np.zeros((npix,npix,npix),dtype={'names':['id','data'], 'formats':['f4','f4']})
        #names = ['id','data']
        #formats = ['i4',('f4','f4','f4')]
        #mat = np.zeros((npix,npix,npix),dtype=dict(names = names, formats=formats))
        #mat = np.zeros((npix,npix,npix),dtype={'40':('i4',0), '29':('f4',0), '13':('f4',0)})
        print("Creating matrix...")
        mat = [[[{} for i in range(npix)] for j in range(npix)] for k in range(npix)]
        print("Finished creating matrix.")
        #print(repr(mat))
        dx = self.xsize/npix
        dy = self.ysize/npix
        dz = self.zsize/npix
        for ii,i in enumerate(drange(-npix/2*dx,npix/2*dx-dx,dx)):
            print("On ii = {0}".format(ii))
            for jj,j in enumerate(drange(-npix/2*dy,npix/2*dy-dy,dy)):
                for kk,k in enumerate(drange(-npix/2*dz,npix/2*dz-dz,dz)):
                    atoms = self.get_atoms_in_cutoff( (i,j,k), radius )
                    comp = {}
                    for atom in atoms:
                        comp[str(atom.z)] = comp.get(str(atom.z),0) + 1.0
                    for key in comp:
                        comp[key] /= len(atoms)
                    #print(comp)
                    #mat[ii][jj][kk] = copy.copy(comp)
                    mat[ii][jj][kk] = comp
        of = open(outfile,'w')
        of.write('IGOR\n')
        for atomtype in self.atomtypes:
            of.write('\nWAVES/N=({0},{1},{2})\t {3}\nBEGIN\n'.format(npix,npix,npix,'partial_comp_'+znum2sym.z2sym(atomtype)))
            for layer in mat:
                for column in layer:
                    for value in column:
                        try:
                            of.write("{0} ".format(value[str(atomtype)]))
                        except KeyError:
                            of.write("{0} ".format(0.0))
                of.write("\n")
            of.write('END\n')
            of.write('X SetScale/P x 0,1,"", {0}; SetScale/P y 0,1,"", {0}; SetScale/P z 0,1,"", {0}; SetScale d 0,0,"", {0}\n'.format('partial_comp_'+znum2sym.z2sym(atomtype)))
        of.close()
        return mat

    def compare(self, m2):
        """ Compares self and m2. An exception is raised if every atom in self is not found in m2 (but not vice versa). """
        assert self.natoms == m2.natoms
        for atom1 in self.atoms:
            found = False
            for atom2 in m2.atoms:
                if(atom1 == atom2):
                    found = True
                    break
            else:
                raise Exception("Atom not found! {0}".format(atom1))
        else:
            pass

    def recenter(self, debug=False):
        xmin = min(atom.coord[0] for atom in self.atoms)
        xmax = max(atom.coord[0] for atom in self.atoms)
        ymin = min(atom.coord[1] for atom in self.atoms)
        ymax = max(atom.coord[1] for atom in self.atoms)
        zmin = min(atom.coord[2] for atom in self.atoms)
        zmax = max(atom.coord[2] for atom in self.atoms)
        if(debug):
            print("Original x-min/max = ({0},{1}".format(xmin,xmax))
            print("Original y-min/max = ({0},{1}".format(ymin,ymax))
            print("Original z-min/max = ({0},{1}".format(zmin,zmax))
        xcenter = xmin + (xmax - xmin)/2.0
        ycenter = ymin + (ymax - ymin)/2.0
        zcenter = zmin + (zmax - zmin)/2.0
        if(debug):
            print("Center was at ({0},{1},{2})".format(xcenter,ycenter,zcenter))
        for i in range(self.natoms):
            self.atoms[i].coord = ( self.atoms[i].coord[0] - xcenter, self.atoms[i].coord[1] - ycenter, self.atoms[i].coord[2] - zcenter )
        if(debug):
            xmin = min(atom.coord[0] for atom in self.atoms)
            xmax = max(atom.coord[0] for atom in self.atoms)
            print("Original x-min/max = ({0},{1}".format(xmin,xmax))
            ymin = min(atom.coord[1] for atom in self.atoms)
            ymax = max(atom.coord[1] for atom in self.atoms)
            print("Original y-min/max = ({0},{1}".format(ymin,ymax))
            zmin = min(atom.coord[2] for atom in self.atoms)
            zmax = max(atom.coord[2] for atom in self.atoms)
            print("Original z-min/max = ({0},{1}".format(zmin,zmax))
            xcenter = round(xmin + (xmax - xmin)/2.0,15)
            ycenter = round(ymin + (ymax - ymin)/2.0,15)
            zcenter = round(zmin + (zmax - zmin)/2.0,15)
            print("Center is at ({0},{1},{2})".format(xcenter,ycenter,zcenter))

    def crop(self, xstart, xend, ystart, yend, zstart, zend):
        atoms = []
        for atom in self.atoms:
            if( xstart < atom.coord[0] < xend and
                ystart < atom.coord[1] < yend and
                zstart < atom.coord[2] < zend):
                atoms.append(atom)
        newm = Model('cropped model',2*abs(xend)+2*abs(xstart),2*abs(yend)+2*abs(ystart),2*abs(zend)+2*abs(zstart),atoms)
        newm.recenter()
        return newm

    def min_dist(self):
        # TODO Can use the function "find_nearest_neighbor" and that should make this much faster
        m = float("inf")
        for i,atomi in enumerate(self.atoms):
            for atomj in self.atoms[self.atoms.index(atomi)+1:]:
                d = self.dist(atomi, atomj)
                if(d < m):
                    m = d
                    t = (atomi,atomj)
        print("\nMinimimum atomic spacing: {0} from atoms {1}\n".format(m,t))
        return m, t

    def atoms_in_box(self):
        for atom in self.atoms:
            if( atom.coord[0] < -self.xsize/2 or
                atom.coord[0] >  self.xsize/2 or
                atom.coord[1] < -self.ysize/2 or
                atom.coord[1] >  self.ysize/2 or
                atom.coord[2] < -self.zsize/2 or
                atom.coord[2] >  self.zsize/2):
                print('ERROR! Atom {0} is out of your box! Coord: {1} Box: {2}'.format(atom, atom.coord, (self.xsize/2, self.ysize/2, self.zsize/2)))
        print("\nYour atom coords are all inside the box.\n")

    def atom_density(self):
        ad = self.natoms / (self.xsize * self.ysize * self.zsize)
        print("\nAtom density: {0}".format(ad))
        return ad

    def min_max_positions(self):
        print('\nBox sizes: {0} x {1} x {2}'.format(self.xsize, self.ysize, self.zsize))
        print('Half box sizes: {0}, {1}, {2}'.format(self.xsize/2, self.ysize/2, self.zsize/2))
        xx = [atom.coord[0] for atom in self.atoms]
        yy = [atom.coord[1] for atom in self.atoms]
        zz = [atom.coord[2] for atom in self.atoms]
        print('x-min: {0}\t x-max: {1}'.format(min(xx),max(xx)))

    def combine(self, *models):
        m0 = self.copy()
        for m in models:
            for atom in m.atoms:
                if atom not in m0.atoms:
                    m0.add(atom)
        return m0

    def generate_larger_model(self, mult):
        # This is slow because I am adding atoms one at a time
        model = Model(xsize=self.xsize*mult, ysize=self.ysize*mult, zsize=self.zsize*mult, atoms=[])
        natoms = 0
        for atom in self.atoms:
            for i in range(mult):
                for j in range(mult):
                    for k in range(mult):
                        if(i == j == k == 0): continue
                        natoms += 1
                        x = atom.coord[0] + i*model.xsize
                        y = atom.coord[1] + j*model.ysize
                        z = atom.coord[2] + k*model.zsize
                        model.add_atom(Atom(natoms, atom.z, x, y, z))

        # Shift right 1/2 world and left mult/2 worlds
        # which is equivalent to left (mult-1)/2 worlds
        for i,atom in enumerate(model.atoms):
            model.atoms[i].set_coord(model.atoms[i].coord[0] - (mult-1)/2.0*model.xsize, model.atoms[i].coord[1] - (mult-1)/2.0*model.ysize, model.atoms[i].coord[2] - (mult-1)/2.0*model.zsize)
        return model

    def icofrac(self):
        """ Sets the atom.z for all atoms in self to be a number between 0 and nbins based on the fraction of pentagonal VP faces. """
        nbins = 6
        del_bin = 100.0/nbins
        fracs = []
        for atom in m.atoms:
            fracs.append((float(atom.vp.index[2])/float(sum(atom.vp.index))*100.0))
            bin = int( (float(atom.vp.index[2])/float(sum(atom.vp.index))*100.0) /(100.0/(nbins-1)))
            atom.z = bin+1
        fracs.sort()
        print('Min %: {0}. Max %: {1}'.format(min(fracs),max(fracs)))

    def bond_angle_distribution(m, nbins=None, dtheta=None):
        if nbins is None:
            nbins = np.pi/dtheta
        elif dtheta is None:
            dtheta = np.pi/nbins
        else:
            raise Exception("Either nbins or dtheta must be specified.")
        divisor = 0
        hist = np.zeros(nbins+1, np.int)
        for atomi in m.atoms:
            for j in range(0,atomi.cn):
                rij = m.dist(atomi,atomi.neighs[j])
                for k in range(j+1,atomi.cn):
                    rik = m.dist(atomi,atomi.neighs[k])
                    rjk = m.dist(atomi.neighs[j],atomi.neighs[k])
                    temp = round( (rij**2 + rik**2 - rjk**2)/(2.0*rij*rik) , 10 )
                    tijk = acos(temp)
                    bin = int(tijk/dtheta)
                    hist[bin] += 1
            divisor += atomi.cn*(atomi.cn-1)
        #if(divisor > 0):
        #    hist = [float(x)/float(divisor)*200 for x in hist]

        dtheta = np.arange(0.0,180.0+dtheta*180.0/np.pi,dtheta*180.0/np.pi)
        return dtheta,hist

    def voronoi(self, atom=None, atoms=None, cutoff=None, atol=0.03, tol=0.03, tltol=0.03):
        if atoms is not None and isinstance(atoms, list):
            for atom in atoms:
                if atom.neighs is None:
                    raise Exception("Atom {0} does not have neighbors.".format(atom))
                voronoi_3d.calculate_atom(self, atom, cutoff, atol=0.03, tol=0.03, tltol=0.03)
        elif atom is not None:
            if atom.neighs is None:
                raise Exception("Atom {0} does not have neighbors.".format(atom))
            voronoi_3d.calculate_atom(self, atom, cutoff, atol=0.03, tol=0.03, tltol=0.03)
        else:
            self.voronoi(atoms=self.atoms, atol=atol, tol=tol, tltol=tltol)
        return None

    def rotate(self, array=None, alpha=None, beta=None, gamma=None, degree=True, invert=True):
        rotate_3d.rotate(self, array, alpha, beta, gamma, degree, invert)

    def translate(self, vector):
        for i,atom in enumerate(self.atoms):
            old_coord = [atom.coord[0], atom.coord[1], atom.coord[2]]
            new_coord = (old_coord[0] + vector[0], old_coord[1] + vector[1], old_coord[2] + vector[2])
            atom.coord = (new_coord[0], new_coord[1], new_coord[2])
Exemple #4
0
class Model(object):
    """ xyz model file class 
    functions:
        write_our_xyz(outfile)
        write_cif(outfile)
        generate_neighbors(cutoff)
        get_atoms_in_cutoff(atom,cutoff)
        nearest_neigh(atom)
        save_vp_dict(vp_dict) """
    
    def __init__(self, *args, **kwargs):
        """ sets:
                self.comment
                self.lx
                self.ly
                self.lz
                self.atoms
                self.natoms
                self.atomtypes
            Extra routines can set:
                self.coord_numbers """

        super(Model,self).__init__()
        if(len(args) == 1):
            modelfile = args[0]
            if modelfile[-4:] == '.xyz':
                self.read_xyz(modelfile)
            elif modelfile[-4:] == '.dat':
                self.read_dat(modelfile)
            else:
                raise Exception("Unknown input model type!")
        elif(len(args) == 5):
            self.comment = args[0]
            self.lx = args[1]
            self.ly = args[2]
            self.lz = args[3]
            self.atoms = args[4]
            self.natoms = len(args[4])
        else:
            raise Exception("Unknown input parameters to Model()!")
        self.hutch = Hutch(self)

        self.atomtypes = {}
        for atom in self.atoms:
            self.atomtypes[atom.z] = self.atomtypes.get(atom.z, 0) + 1


    def read_xyz(self,modelfile):
        with open(modelfile) as f:
            content = f.readlines()

        self.comment = content.pop(0) # Comment line
        if('-1' in content[-1] and '.' not in content[-1]): # '-1' line
            content.pop(-1)
        self.lx,self.ly,self.lz = tuple([float(x) for x in content.pop(0).strip().split()])

        self.natoms = len(content)
        content = [x.strip().split() for x in content]
        for i in range(0,len(content)):
            for j in range(0,len(content[i])):
                try:
                    content[i][j] = int(content[i][j])
                except:
                    try:
                        content[i][j] = float(content[i][j])
                    except:
                        pass
        self.atoms = []
        for i,atom in enumerate(content):
            self.atoms.append(Atom(i,atom[0],atom[1],atom[2],atom[3]))
            if type(self.atoms[i].z) == type('hi'):
                self.atoms[i].z = znum2sym.sym2z(self.atoms[i].z)

    def read_dat(self,modelfile):
        with open(modelfile) as f:
            content = f.readlines()
        self.comment = content.pop(0) # Comment line
        content = [x for x in content if not x.startswith('#')]

        for line in content:
            if('atoms' in line): self.natoms = int(line.split()[0])
            if('xlo' in line and 'xhi' in line):
                self.lx = abs(float(line.split()[0])) + abs(float(line.split()[1]))
            if('ylo' in line and 'yhi' in line):
                self.ly = abs(float(line.split()[0])) + abs(float(line.split()[1]))
            if('zlo' in line and 'zhi' in line):
                self.lz = abs(float(line.split()[0])) + abs(float(line.split()[1]))
            if('atom types' in line): nelems = int(line.split()[0])
            if('Masses' in line): mflag = content.index(line) + 1
            if('Atoms' in line): aflag = content.index(line) + 1
        try:
            mflag
        except NameError:
            raise Exception("ERROR! You need to define the masses in the .dat file.")
        atomtypes = {}
        while(nelems > 0):
            if(len(content[mflag].split()) == 2):
                atomtypes[int(content[mflag].split()[0])] = masses.get_znum(float(content[mflag].split()[1]))
                nelems -= 1
            mflag += 1
        self.atoms = []
        natoms = self.natoms
        while(natoms > 0):
            sline = content[aflag].split()
            if(len(sline) >= 5):
                # We found an atom
                id = int(sline[0])
                type = int(sline[1])
                x = float(sline[2])
                y = float(sline[3])
                z = float(sline[4])
                znum = atomtypes[type]
                # Add it to the model
                self.atoms.append(Atom(id,znum,x,y,z))
                natoms -= 1
            aflag += 1

    def write_dat(self,outfile=None):
        if outfile is None:
            of = sys.stdout
        else:
            of = open(outfile,'w')
        of.write(self.comment+'\n')
        of.write('{0} atoms\n\n'.format(self.natoms))
        of.write('{0} atom types\n\n'.format(len(self.atomtypes)))
        of.write('{0} {1} xlo xhi\n'.format(-self.lx/2,self.lx/2))
        of.write('{0} {1} ylo yhi\n'.format(-self.ly/2,self.ly/2))
        of.write('{0} {1} zlo zhi\n\n'.format(-self.lz/2,self.lz/2))
        of.write('Masses\n\n')
        atomtypes = list(self.atomtypes)
        atomtypes.sort()
        atomtypes.reverse()
        for i,z in enumerate(atomtypes):
            of.write('{0} {1}\n'.format(i+1,round(masses.get_mass(z),2)))
        of.write('\n')
        of.write('Atoms\n\n')
        for i,atom in enumerate(self.atoms):
            of.write('{0} {1} {2} {3} {4}\n'.format(atom.id+1, atomtypes.index(atom.z)+1, atom.coord[0], atom.coord[1], atom.coord[2]))


    def write_our_xyz(self,outfile=None):
        if outfile is None:
            of = sys.stdout
        else:
            of = open(outfile,'w')
        of.write(self.comment.strip()+'\n')
        of.write("{0} {1} {2}\n".format(self.lx, self.lx, self.lz))
        for atom in self.atoms:
            of.write(atom.ourxyz()+'\n')
        of.write('-1')
        of.close()

    def write_real_xyz(self,outfile=None):
        if outfile is None:
            of = sys.stdout
        else:
            of = open(outfile,'w')
        of.write(str(self.natoms)+'\n')
        of.write("{0} {1} {2} {3}\n".format(self.comment.strip(),self.lx, self.lx, self.lz))
        for i,atom in enumerate(self.atoms):
            of.write(atom.realxyz()+'\n')

    def write_cif(self,outfile=None):
        if outfile is None:
            of = sys.stdout
        else:
            of = open(outfile,'w')
        of.write('_pd_phase_name\t'+self.comment+'\n')
        of.write('_cell_length_a '+str(self.lx)+'\n')
        of.write('_cell_length_b '+str(self.ly)+'\n')
        of.write('_cell_length_c '+str(self.lz)+'\n')
        of.write('_cell_angle_alpha 90\n_cell_angle_beta 90\n_cell_angle_gamma 90\n')
        of.write('_symmetry_space_group_name_H-M         \'P 1\'\n_symmetry_Int_Tables_number            1\n\n')
        of.write('loop_\n_symmetry_equiv_pos_as_xyz\n   \'x, y, z\'\n\n')
        of.write('loop_\n   _atom_site_label\n   _atom_site_occupancy\n   _atom_site_fract_x\n   _atom_site_fract_y\n   _atom_site_fract_z\n   _atom_site_adp_type\n   _atom_site_B_iso_or_equiv\n   _atom_site_type_symbol\n')

        atomtypes = {}
        for atom in self.atoms:
            atomtypes[atom.z] = atomtypes.get(atom.z, 0) + 1
            of.write('   '+znum2sym.z2sym(atom.z)+str(atomtypes[atom.z])+'\t1.0\t'+str(atom.coord[0]/self.lx+0.5)+'\t'+str(atom.coord[1]/self.ly+0.5)+'\t'+str(atom.coord[2]/self.lz+0.5)+'\tBiso\t1.000000\t'+znum2sym.z2sym(atom.z)+'\n')
        of.write('\n')
        of.close()

    
        
    def add_atom(self,atom):
        self.atoms.append(atom)
        self.hutch.add_atom(atom)
        self.natoms += 1

    def generate_neighbors(self,cutoff):
        for atom in self.atoms:
            atom.neighs = self.get_atoms_in_cutoff(atom,cutoff)
            atom.cn = len(atom.neighs)

    def check_neighbors(self):
        for atom in self.atoms:
            for n in atom.neighs:
                if atom not in n.neighs:
                    print("You're neighbors are screwed up! Atom IDs are {0}, {1}.".format(atom.id,n.id))
                    print("Neighbors of: {0}".format(atom))
                    print(atom.neighs)
                    print("Neighbors of: {0}".format(n))
                    print(n.neighs)
                    print("Dist = {0}".format(self.dist(atom,n)))
                    print("Dist = {0}".format(self.dist(n,atom)))


    def generate_coord_numbers(self):
        """ atom.neighs must be defined first for all atoms """
        self.coord_numbers = {}
        # Form will be:
        #   {'Cu-Al': 4.5}, etc.
        for typea in self.atomtypes:
            self.coord_numbers[znum2sym.z2sym(typea)] = 0
            for typeb in self.atomtypes:
                self.coord_numbers[znum2sym.z2sym(typea)+'-'+znum2sym.z2sym(typeb)] = 0
        for atom in self.atoms:
            for n in atom.neighs:
                self.coord_numbers[znum2sym.z2sym(atom.z)] += 1
                self.coord_numbers[znum2sym.z2sym(atom.z)+'-'+znum2sym.z2sym(n.z)] += 1
        self.bonds = self.coord_numbers.copy()
        for key in self.coord_numbers:
            elem = znum2sym.sym2z(key.split('-')[0])
            self.coord_numbers[key] /= float(self.atomtypes[elem])


    def get_atoms(self):
        return self.atoms
    def get_natoms(self):
        return self.natoms
    def get_box_size(self):
        return (self.lx,self.ly,self.lz)

    def get_atoms_in_cutoff(self,atom,cutoff):
        x_start = atom.coord[0] - cutoff
        y_start = atom.coord[1] - cutoff
        z_start = atom.coord[2] - cutoff
        x_end   = atom.coord[0] + cutoff
        y_end   = atom.coord[1] + cutoff
        z_end   = atom.coord[2] + cutoff
        if(x_start < -self.lx/2.0): x_start = x_start + self.lx #PBC
        if(y_start < -self.ly/2.0): y_start = y_start + self.ly #PBC
        if(z_start < -self.lz/2.0): z_start = z_start + self.lz #PBC
        if(x_end > self.lx/2.0): x_end = x_end - self.lx #PBC
        if(y_end > self.ly/2.0): y_end = y_end - self.ly #PBC
        if(z_end > self.lz/2.0): z_end = z_end - self.lz #PBC
        atom_s = Atom(0,0,x_start,y_start,z_start)
        hutch_s = self.hutch.hutch_position(atom_s)
        i_start = hutch_s[0]
        j_start = hutch_s[1]
        k_start = hutch_s[2]
        atom_e = Atom(0,0,x_end,y_end,z_end)
        hutch_e = self.hutch.hutch_position(atom_e)
        i_end = hutch_e[0]
        j_end = hutch_e[1]
        k_end = hutch_e[2]
        list = []

        for i in range(0,self.hutch.nhutchs):
            if(i_start <= i_end):
                if(i < i_start or i > i_end): continue
            else:
                if(i < i_start and i > i_end): continue
            for j in range(0,self.hutch.nhutchs):
                if(j_start <= j_end):
                    if(j < j_start or j > j_end): continue
                else:
                    if(j < j_start and j > j_end): continue
                for k in range(0,self.hutch.nhutchs):
                    if(k_start <= k_end):
                        if(k < k_start or k > k_end): continue
                    else:
                        if(k < k_start and k > k_end): continue
                    list = list + self.hutch.get_atoms_in_hutch((i,j,k))
        list.remove(atom)
        list = [atomi for atomi in list if self.dist(atom,atomi) <= cutoff]
        return list

    def dist(self,atom1,atom2):
        x = (atom1.coord[0] - atom2.coord[0])
        y = (atom1.coord[1] - atom2.coord[1])
        z = (atom1.coord[2] - atom2.coord[2])
        while(x > self.lx/2): x = self.lx - x
        while(y > self.ly/2): y = self.ly - y
        while(z > self.lz/2): z = self.lz - z
        while(x < -self.lx/2): x = self.lx + x
        while(y < -self.ly/2): y = self.ly + y
        while(z < -self.lz/2): z = self.lz + z
        x2 = x**2
        y2 = y**2
        z2 = z**2
        return math.sqrt(x2+y2+z2)
    def get_all_dists(self):
        dists = []
        for atomi in self.atoms:
            for atomj in self.atoms[self.atoms.index(atomi)+1:]:
                dists.append([atomi,atomj,self.dist(atomi,atomj)])
        return dists
    def nearest_neigh(self,atom):
        """ returns an atoms nearest neighbor """
        hutch = self.hutch.hutch_position(atom)
        atoms = self.hutch.get_atoms_in_hutch(hutch)[:]
        if atom in atoms: atoms.remove(atom)

        # This generation isn't perfect but it will work
        rots = [(1,0,0),(0,1,0),(0,0,1)]
        i = 0
        while len(atoms) == 0:
            hutch = ((hutch[0]+rots[i][0])%self.hutch.nhutchs,(hutch[1]+rots[i][1])%self.hutch.nhutchs,(hutch[2]+rots[i][2])%self.hutch.nhutchs)
            i = (i+1) % 3
            atoms = self.hutch.get_atoms_in_hutch(hutch)
            if atom in atoms: atoms.remove(atom)
        start = atoms[0]

        atoms = self.get_atoms_in_cutoff(atom,self.dist(atom,start))
        d = float("inf")
        for atomi in atoms:
            dt = self.dist(atom,atomi)
            if dt < d:
                d = dt
                a = atomi
        return a

    def save_vp_dict(self,vp_dict):
        self.vp_dict = vp_dict

    def composition(self):
        d = {}
        for key in self.atomtypes:
            d[znum2sym.z2sym(key)] = self.atomtypes[key]/float(self.natoms)
        return d

    def print_bond_stats(self):
        comp = self.composition()
        nbonds = 0.0
        for key in self.bonds:
            if '-' not in key:
                nbonds += self.bonds[key]
        bond_stats = self.bonds.copy()
        for key in bond_stats.keys():
            if '-' in key:
                del bond_stats[key]
            else:
                #bond_stats[key] /= nbonds/comp[znum2sym.sym2z(key)]
                bond_stats[key] *= 1.0/(nbonds*comp[key])
        print('Bond statistics:')
        pprint(self.bonds)
        print('Ratio of actual/expected bonds:')
        pprint(bond_stats)

    def atom_coords(self):
        coords = []
        for atom in self.atoms:
            coords.append(atom.coord)
        return coords

    def nn_vor(self):
        coords = self.atom_coords()
        vor = Voronoi(coords)
        print(vor.vertices)
        print(vor.regions)
        voronoi_plot_2d(vor)
        plt.show()