def build_operators(spos, trans, check_mapping=False, eps=1e-6): '''Given a list of fractional positions, and a list of fractional translations, produce a set of matrices, describing how fractional translations permute atomic positions within the unit cell. Two fractional positions si and sj are considered to be identical when |si-sj|<eps. ''' ntrans = len(trans) natoms = len(spos) ops = np.zeros((ntrans, natoms, natoms), int) for i, ti in enumerate(trans): for j, sj in enumerate(spos): for k, sk in enumerate(spos): # If displacement between two atomic positions # differs from the fractional translations by # by a lattice translation+-eps we consider the # that two atomic positions to be map onto each # other by the fractional translation disp = sk - sj - ti ops[i, j, k] = np.linalg.norm(disp - np.rint(disp)) < eps if check_mapping: # Every row and every column of every operator must # contain exactly one unity, otherwise translations # are not maping atoms one-to-one. if np.any(np.sum(ops, axis=1) != 1) or np.any( np.sum(ops, axis=2) != 1): post_error('Translations are not one-to-one. ' 'Try changing the matching tolerance, or try using ' 'the POSCAR file with more regular positions.') return ops
def build_operators(spos, trans, check_mapping=False, eps=1e-6): """Given a list of fractional positions, and a list of fractional translations, produce a set of matrices, describing how fractional translations permute atomic positions within the unit cell. Two fractional positions si and sj are considered to be identical when |si-sj|<eps. """ ntrans = len(trans) natoms = len(spos) ops = np.zeros((ntrans, natoms, natoms), int) for i, ti in enumerate(trans): for j, sj in enumerate(spos): for k, sk in enumerate(spos): # If displacement between two atomic positions # differs from the fractional translations by # by a lattice translation+-eps we consider the # that two atomic positions to be map onto each # other by the fractional translation disp = sk - sj - ti ops[i, j, k] = np.linalg.norm(disp - np.rint(disp)) < eps if check_mapping: # Every row and every column of every operator must # contain exactly one unity, otherwise translations # are not maping atoms one-to-one. if np.any(np.sum(ops, axis=1) != 1) or np.any(np.sum(ops, axis=2) != 1): post_error( "Translations are not one-to-one. " "Try changing the matching tolerance, or try using " "the POSCAR file with more regular positions." ) return ops
def parse_poscar(filename): '''Parses POSCAR file. Returns 3x3 float array containing unit cell vectors as rows, Nx3 array containing fractional positions of atoms, N being number of atoms and a list of chemical symbols. ''' try: gl = Getlines(filename) except: post_error('Unable to open "{0}" for reading.'.format(filename)) # Skip the comment line gl.get_next() # Read the scaling factor f = float(gl.get_next()) # Read the unit cell vectors cell = np.zeros((3, 3), float) cell[0] = f * np.array(gl.get_next().split(), float) cell[1] = f * np.array(gl.get_next().split(), float) cell[2] = f * np.array(gl.get_next().split(), float) # Read the chemical symbols syms = gl.get_next().split() # Read the atom counts counts = np.array(gl.get_next().split(), int) # Get the number of atoms natoms = np.sum(counts) # Rearrange into the long list of chemical symbols symbols = [] for s, c in zip(syms, counts): symbols += [s] * c # Cartesian or fractional coordinates? ctype = gl.get_next()[0].lower() if ctype == 'c': mult = np.linalg.inv(cell) elif ctype == 'd': mult = np.eye(3) else: post_error('"{0}" is unknown POSCAR option'.format(plines[7].strip())) # Allocate storage for positions spos = np.zeros((len(symbols), 3)) # Read the positions for i in xrange(len(symbols)): spos[i] = np.array(gl.get_next().split()[:3], float) return cell, spos, symbols
def main(): desc_str = 'Unfold bands calculated by VASP. For this, phase '\ 'information needs to be present in the PROCAR file '\ 'which means that bands need to be calculated with '\ 'the option LORBIT=12 specified in the INCAR file. '\ 'The required input consists of POSCAR file, PROCAR '\ 'file and a set of up to three fractional translations. '\ 'There is no need to specify all translations, just the '\ 'generators. The programs will generate all distinct '\ 'translations from the generators and generate all irreps. '\ 'NOTE: In cas of non-collinear spin-polarized calculations ' \ 'mx, my and mz components of orbital weights are not ' \ 'unfolded, only spin up and spin down totals.' parser = argparse.ArgumentParser(prog='vasp_unfold', description = desc_str) parser.add_argument('poscar', type=str, help='POSCAR file') parser.add_argument('procar', type=str, help='PROCAR file') parser.add_argument('--tgen', type=translation, action='append', metavar='SX,SY,SZ', help='Fractional translation ' 'generator. No whitespaces are allowed between the ' 'components! SX, SY and SZ can be either 0 or 1/n, ' 'where n is an integer. Up to three linearly ' 'independant generators can be specified.') parser.add_argument('--out', type=str, help='Output filename. If left ' 'unspecified output is writen to PROCAR.irrep.n ' 'where PROCAR is location of the input PROCAR file. ' 'If specified, output is written to OUT.irrep.n.') parser.add_argument('--eps', type=float, default=1e-6, help='Numerical ' 'precision. When building permutation representation ' 'of the fractional translations this parameter is used ' 'to determine whether two fractional positions are ' 'identical. For irregular structures, it may need to ' 'be increased. If this does not help, try to tweak ' 'the atomic positions in POSCAR to make the structure ' 'more regular. Default is 1e-6.') parser.add_argument('--all-irreps', default=False, action='store_true', help='Flag specifying that all irreps from the unfolding ' 'will be written to the output. By default, only irrep 0 ' '(unit irrep) is written out.') parser.add_argument('--check-mapping', action='store_true', default=False, help='Specifies to check if supplied translation ' 'generators generate fractional translations which are ' 'one-to-one, ie. map every atom on exactly one other atom ' 'in the unit cell. This MUST not be enabled for the cases ' 'where vacancies or excess atoms are present.') args = parser.parse_args() tgens = args.tgen trans, irreps = build_translations(tgens) try: cell, spos, symbols = parse_poscar(args.poscar) except: post_error('Unable to parse the input POSCAR file. Please ' 'check if the file exists and is formatted properly') ops = build_operators(spos, trans, args.check_mapping, args.eps) try: data = parse_procar(args.procar) except: post_error(errors.poscar_parse_error) if data[-1] is None: post_error('Phase information has to be present in the PROCAR ' 'file. Please repeat the calculation with LORBIT=12.') phases = np.copy(data[-1]) norbs = phases.shape[1]/len(spos) projs = build_projectors(irreps, ops, norbs) if args.out is None: output = args.procar else: output = args.out if args.all_irreps: nirrep = len(projs) else: nirrep = 1 for i, p in enumerate(projs[:nirrep]): for s in xrange(data[-1].shape[-1]): try: data[-1][:,:,:,s] = np.dot(p, phases[:,:,:,s]).swapaxes(0, 1) except: post_error('Unable to apply projectors. Are you sure ' 'that specified POSCAR and PROCAR file belong to ' 'the same crystal structure?') # We update total absolute weights to correspond to # unfolded phases (by multiplying them by the magnitude # ratio of unfolded and folded phases phase_ratio = np.abs(data[-1])/(np.abs(phases)+1e-4) for idim in xrange(data[-2].shape[3]): data[-2][:,:,:,idim,:] *= phase_ratio write_procar('{0}.irrep.{1}'.format(output, i), *data)
def write_procar(fname, orbitals, kpoints, kweights, bands, occupations, weights, phases): '''Write PROCAR file based on supplied data ''' # Labels for orbitals orblabels = ['s', 'py', 'pz', 'px', 'dxy', 'dyz', 'dz2', 'dxz', 'dx2', 'f-3', 'f-2', 'f-1', 'f0', 'f1', 'f2', 'f3'] norb = len(orbitals) npoints = len(kpoints) nbands = bands.shape[1] nions = weights.shape[1]/norb nspin = weights.shape[-1] ndim = weights.shape[-2] try: out = open(fname, 'w') except: post_error('Unable to open "{0}" for writing'.format(fname)) # Write the first line of the PROCAR file if phases is not None: out.write('PROCAR lm decomposed + phase\n') else: out.write('PROCAR lm decomposed\n') # Format out the column title line for orbital weights orb_ttl_1 = 'ion ' orb_ttl_2 = 'ion ' for i in xrange(norb): orb_ttl_1 += '{0: >6} '.format(orblabels[i]) orb_ttl_2 += '{0: >6} '.format(orblabels[i]) orb_ttl_1 += '{0: >6}\n'.format('tot') orb_ttl_2 += '\n' for s in xrange(nspin): # Write the second line containing the sizes out.write('# of k-points: {0} # of bands: {1}' ' # of ions: {2}\n\n'.format(npoints, nbands, nions)) for i, k in enumerate(kpoints): # Write the k-point info out.write(' k-point {0: >4} : '.format(i+1)) out.write('{0:.8f} {1:.8f} {2:.8f} '.format(*k)) out.write('weight = {0:.8f}\n\n'.format(kweights[i])) for j, b in enumerate(bands[i,:,s]): # Write the band info out.write('band {0: >4} # '.format(j+1)) out.write('energy {0: >13.8f} # '.format(b)) out.write('occ. {0: >11.8f}\n\n'.format(occupations[i, j, s])) # Write absolute weight blocks. In case of # non-collinear calculation, there is four # such blocks for d in xrange(ndim): if d == 0: # Write names of orbitals (s, px, py etc...) out.write(orb_ttl_1) # Allocate storage for accumulation of orbital totals tot_orb = np.zeros(norb, float) # Loop over individual atoms for k in xrange(nions): # Write atom's index out.write('{0: >3} '.format(k+1)) # Extract corresponding weights w = weights[i, k*norb:(k+1)*norb, j,d,s] # Add to totals tot_orb += w # Write out row of weights for l in xrange(norb): out.write('{0: >6.3f} '.format(w[l])) # Write atom total out.write('{0: >6.3f}\n'.format(np.sum(w))) # We will now write line with orbital totals out.write('tot ') for l in xrange(norb): out.write('{0: >6.3f} '.format(tot_orb[l])) # Finally, atom+orbital total out.write('{0: >6.3f}\n'.format(np.sum(tot_orb))) # If we have phases we write them now if phases is not None: # Write again names of orbitals out.write(orb_ttl_2) # Loop over atoms for k in xrange(nions): # Write atom index out.write('{0: >3} '.format(k+1)) # Extract corresponding phase phs = phases[i, k*norb:(k+1)*norb, j, s] # Write real part for l in xrange(norb): out.write('{0: >6.3f} '.format(phs[l].real)) # Write atom index out.write('\n{0: >3} '.format(k+1)) # Write imaginary part for l in xrange(norb): out.write('{0: >6.3f} '.format(phs[l].imag)) out.write('\n') out.write('\n') out.write('\n') out.close()
def parse_procar(filename): '''This function parses a PROCAR file. It returns a tuple consisting of following elements: orbitals - (norbs) string array of orbital labels (s, px, py etc...) kpoints - (npoints,3) float array of k-point coordinates kweights - (npoints) float array of k-point weights bands - (npoints,nbands,nspin) float array of band energies occupancies - (npoints,nbands,nspin) float array of band occupancies weights - (npoints,nions*norbs,nbands,ndim,nspin) float array of orbital weights phases - (npoints,nions*norbs,nbands,nspin) complex array of phases of orbital weights if LORBIT=12, otherwise None Where: norbs - number of orbitals (It can be 9 or 16 with f orbitals) npoints - number of k-points nbands - number of bands nspin - number of spins (1 for non spin-polarized, 2 otherwise) nions - number of atoms ndim - orbital weight dimensionality (1 for collinear, 4 otherwise) ''' try: gl = Getlines(filename) except: post_error('Unable to open "{0}" for reading.'.format(filename)) header_1 = gl.get_next() header_2 = gl.get_next().split() npoints = int(header_2[3]) nbands = int(header_2[7]) nions = int(header_2[-1]) # Remember the position in file start = gl.tell() # Skip two lines containing first k-point and band gl.get_next() gl.get_next() # Determine the number of orbitals orbitals = gl.get_next().split()[1:-1] norbs = len(orbitals) # Allocate maximal storage. In case calculation was # non spin-polarized or collinear we can just trim # the excess components at the end kpoints = np.zeros((npoints, 3), float) kweights = np.zeros(npoints, float) bands = np.zeros((npoints, nbands, 2), float) occupancies = np.zeros((npoints, nbands, 2), float) weights = np.zeros((npoints, nions * norbs, nbands, 4, 2), float) dim = 0 # Determine if the calculation was non-collinear # by counting how many lines in the first band # block begin with tot. That number will be equal # to the number of sub-blocks for orbital weights # (1 in case of collinear and 4 otherwise) while True: line = gl.get_next() if line.startswith('tot'): dim += 1 elif line.startswith('band'): break # This function will read block of absolute weights # for i-th k-point, j-th band and s-th spin component def get_absweights(i, j, s): # Skip line with orbital names gl.get_next() # k loops over total, mx, my and mz for k in xrange(dim): # l goes over ions for l in xrange(nions): weights[i,l*norbs:(l+1)*norbs,j,k,s] = \ np.array(gl.get_next().split()[1:-1], float) # Skip line with totals gl.get_next() # Check whether phase information is included if '+ phase' in header_1: # Allocate storage for phases phases = np.zeros((npoints, nions * norbs, nbands, 2), complex) # Declare nested function that handles # parsing of complex weights def get_weights(i, j, s): # Read abs values of weights get_absweights(i, j, s) # Skip line with orbital names gl.get_next() for k in xrange(nions): # Get real part phases[i,k*norbs:(k+1)*norbs,j,s] = \ np.array(gl.get_next().split()[1:], float) # Get imaginary part phases[i,k*norbs:(k+1)*norbs,j,s] += \ 1j*np.array(gl.get_next().split()[1:], float) else: # Phases are None in this case phases = None # In this case we just have absolutes of weights # No need for a new function get_weights = get_absweights # Go back to the beginning of the first k-point gl.seek(start) for i in xrange(npoints): # Parse k-point coordinates temp = gl.get_next() problem_index = [] for ic in range(len(temp) - 1): if temp[ic] <= '9' and temp[ic] >= '0' and temp[ic + 1] == '-': problem_index.append(ic) if len(problem_index): addition = 0 for ic in problem_index: temp = temp[:ic + 1 + addition] + ' ' + temp[ic + 1 + addition:] addition += 1 print temp k_line = temp.split() kpoints[i] = np.array(k_line[3:6], float) kweights[i] = float(k_line[-1]) for j in xrange(nbands): # Parse band energy band_line = gl.get_next().split() bands[i, j, 0] = float(band_line[4]) occupancies[i, j, 0] = float(band_line[-1]) # Parse orbital weights get_weights(i, j, 0) # Seek now for the second spin component res = gl.get_next(False) # If there is no second spin component, finish by # returning just the first component if res is None: # Trim the second spin component bands = bands[:, :, :1] occupancies = occupancies[:, :, :1] weights = weights[:, :, :, :dim, :1] if phases is not None: phases = phases[:, :, :, :1] return orbitals, kpoints, kweights, bands, occupancies, \ weights, phases # Otherwise, read bands and weights for the second component for i in xrange(npoints): # Skip k-point coordinates gl.get_next() for j in xrange(nbands): # Parse band energy band_line = gl.get_next().split() bands[i, j, 1] = float(band_line[4]) occupancies[i, j, 1] = float(band_line[-1]) # Parse orbital weights get_weights(i, j, 1) return orbitals, kpoints, kweights, bands, occupancies, \ weights[:,:,:,:dim,:], phases
def write_procar(fname, orbitals, kpoints, kweights, bands, occupations, weights, phases): '''Write PROCAR file based on supplied data ''' # Labels for orbitals orblabels = [ 's', 'py', 'pz', 'px', 'dxy', 'dyz', 'dz2', 'dxz', 'dx2', 'f-3', 'f-2', 'f-1', 'f0', 'f1', 'f2', 'f3' ] norb = len(orbitals) npoints = len(kpoints) nbands = bands.shape[1] nions = weights.shape[1] / norb nspin = weights.shape[-1] ndim = weights.shape[-2] try: out = open(fname, 'w') except: post_error('Unable to open "{0}" for writing'.format(fname)) # Write the first line of the PROCAR file if phases is not None: out.write('PROCAR lm decomposed + phase\n') else: out.write('PROCAR lm decomposed\n') # Format out the column title line for orbital weights orb_ttl_1 = 'ion ' orb_ttl_2 = 'ion ' for i in xrange(norb): orb_ttl_1 += '{0: >6} '.format(orblabels[i]) orb_ttl_2 += '{0: >6} '.format(orblabels[i]) orb_ttl_1 += '{0: >6}\n'.format('tot') orb_ttl_2 += '\n' for s in xrange(nspin): # Write the second line containing the sizes out.write('# of k-points: {0} # of bands: {1}' ' # of ions: {2}\n\n'.format( npoints, nbands, nions)) for i, k in enumerate(kpoints): # Write the k-point info out.write(' k-point {0: >4} : '.format(i + 1)) out.write('{0:.8f} {1:.8f} {2:.8f} '.format(*k)) out.write('weight = {0:.8f}\n\n'.format(kweights[i])) for j, b in enumerate(bands[i, :, s]): # Write the band info out.write('band {0: >4} # '.format(j + 1)) out.write('energy {0: >13.8f} # '.format(b)) out.write('occ. {0: >11.8f}\n\n'.format(occupations[i, j, s])) # Write absolute weight blocks. In case of # non-collinear calculation, there is four # such blocks for d in xrange(ndim): # Write names of orbitals (s, px, py etc...) out.write(orb_ttl_1) # Allocate storage for accumulation of orbital totals tot_orb = np.zeros(norb, float) # Loop over individual atoms for k in xrange(nions): # Write atom's index out.write('{0: >3} '.format(k + 1)) # Extract corresponding weights w = weights[i, k * norb:(k + 1) * norb, j, d, s] # Add to totals tot_orb += w # Write out row of weights for l in xrange(norb): out.write('{0: >6.3f} '.format(w[l])) # Write atom total out.write('{0: >6.3f}\n'.format(np.sum(w))) # We will now write line with orbital totals out.write('tot ') for l in xrange(norb): out.write('{0: >6.3f} '.format(tot_orb[l])) # Finally, atom+orbital total out.write('{0: >6.3f}\n'.format(np.sum(tot_orb))) # If we have phases we write them now if phases is not None: # Write again names of orbitals out.write(orb_ttl_2) # Loop over atoms for k in xrange(nions): # Write atom index out.write('{0: >3} '.format(k + 1)) # Extract corresponding phase phs = phases[i, k * norb:(k + 1) * norb, j, s] # Write real part for l in xrange(norb): out.write('{0: >6.3f} '.format(phs[l].real)) # Write atom index out.write('\n{0: >3} '.format(k + 1)) # Write imaginary part for l in xrange(norb): out.write('{0: >6.3f} '.format(phs[l].imag)) out.write('\n') out.write('\n') out.write('\n') out.close()
def build_translations(tgens): '''Build a list of translations and irreps from at most three linearly independent generators specified as lists of three fractions. ''' if len(tgens) > 3: post_error('There can be at most three generators ' 'of fractional translations.') # Use folded generators as floats to perform linear independece tests tgensf = np.array(tgens, dtype=float) % 1 # Tolerance for linear independenec per dimension eps = 1e-2 # Check if generators are linearly independent if len(tgens) == 2 and np.all(np.cross(tgensf[0], tgensf[1]) < eps**2): post_error('Generators are not linearly independant.') elif len(tgens) == 3 and np.linalg.det(np.array(tgensf)) < eps**3: post_error('Generators are not linearly independant.') # Expand the generator list to be a 3x3 matrix tgens = np.append(tgens, np.ones((3 - len(tgens), 3)), axis=0) # Get the order of every generator order = np.array([frac_translation_order(g) for g in tgens], int) # Get the corresponding roots of unity which will be # used to construct irreps deltag = np.exp(-2 * np.pi * 1j / order) # Fold the translation vectors into the unit cell tgens = tgens % 1 # Total number of translations ntrans = np.prod(order) # Storage for translations trans = np.zeros((ntrans, 3), float) # Calculate irreps of every generator irrepg = np.zeros((ntrans, 3), complex) # Loop over all possible products of powers of generators # and store the irreps for i in xrange(order[0]): for j in xrange(order[1]): for k in xrange(order[2]): ii = i * order[1] * order[2] + j * order[2] + k irrepg[ii] = deltag**[i, j, k] # Irrep table for all translations irreps = np.zeros((ntrans, ntrans), complex) # Loop over all posible products of powers of generators for i in xrange(order[0]): for j in xrange(order[1]): for k in xrange(order[2]): ti = i * order[1] * order[2] + j * order[2] + k # Get the translation vector trans[ti] = np.dot([i, j, k], tgens) % 1 # Get all irreps of that translations for l in xrange(ntrans): irreps[l, ti] = np.prod(irrepg[l]**[i, j, k]) return trans, irreps
def build_translations(tgens): """Build a list of translations and irreps from at most three linearly independent generators specified in the form [nx,ny,nz], representing translation [1/nx,1/ny,1/nz]. """ if len(tgens) > 3: post_error("There can be at most three generators " "of fractional translations.") # Check if generators are linearly independent if len(tgens) == 2 and np.all(np.cross(tgens[0], tgens[1]) == 0): post_error("Generators are not linearly independant.") elif len(tgens) == 3 and np.linalg.det(tgens) == 0: post_error("Generators are not linearly independant.") # Expand the generator list to have a 3x3 matrix tgens = np.append(tgens, np.ones((3 - len(tgens), 3)), axis=0) # Get the order of every generator order = np.array([lcmm(*g) for g in tgens], int) # Get the corresponding roots of unity which will be # used to construct irreps deltag = np.exp(-2 * np.pi * 1j / order) # Calculate translation vectors tgens = (1.0 / tgens) % 1 # Total number of translations ntrans = np.prod(order) # Storage for translations trans = np.zeros((ntrans, 3), float) # Calculate irreps of every generator irrepg = np.zeros((ntrans, 3), complex) # Loop over all possible products of powers of generators # and store the irreps for i in xrange(order[0]): for j in xrange(order[1]): for k in xrange(order[2]): ii = i * order[1] * order[2] + j * order[2] + k irrepg[ii] = deltag ** [i, j, k] # Irrep table for all translations irreps = np.zeros((ntrans, ntrans), complex) # Loop over all posible products of powers of generators for i in xrange(order[0]): for j in xrange(order[1]): for k in xrange(order[2]): ti = i * order[1] * order[2] + j * order[2] + k # Get the translation vector trans[ti] = np.dot([i, j, k], tgens) % 1 # Get all irreps of that translations for l in xrange(ntrans): irreps[l, ti] = np.prod(irrepg[l] ** [i, j, k]) return trans, irreps
def parse_procar(filename): '''This function parses a PROCAR file. It returns a tuple consisting of following elements: orbitals - (norbs) string array of orbital labels (s, px, py etc...) kpoints - (npoints,3) float array of k-point coordinates kweights - (npoints) float array of k-point weights bands - (npoints,nbands,nspin) float array of band energies occupancies - (npoints,nbands,nspin) float array of band occupancies weights - (npoints,nions*norbs,nbands,ndim,nspin) float array of orbital weights phases - (npoints,nions*norbs,nbands,nspin) complex array of phases of orbital weights if LORBIT=12, otherwise None Where: norbs - number of orbitals (It can be 9 or 16 with f orbitals) npoints - number of k-points nbands - number of bands nspin - number of spins (1 for non spin-polarized, 2 otherwise) nions - number of atoms ndim - orbital weight dimensionality (1 for collinear, 4 otherwise) ''' try: gl = Getlines(filename) except: post_error('Unable to open "{0}" for reading.'.format(filename)) header_1 = gl.readline() header_2 = gl.readline().split() npoints = int(header_2[3]) nbands = int(header_2[7]) nions = int(header_2[-1]) # Remember the position in file start = gl.tell() # Skip two lines containing first k-point and band gl.readline() gl.readline() # Determine the number of orbitals orbitals = gl.readline().split()[1:-1] norbs = len(orbitals) # Allocate maximal storage. In case calculation was # non spin-polarized or collinear we can just trim # the excess components at the end kpoints = np.zeros((npoints, 3), float) kweights = np.zeros(npoints, float) bands = np.zeros((npoints, nbands, 2), float) occupancies = np.zeros((npoints, nbands, 2), float) weights = np.zeros((npoints, nions*norbs, nbands, 4, 2), float) dim = 0 # Determine if the calculation was non-collinear # by counting how many lines in the first band # block begin with tot. That number will be equal # to the number of sub-blocks for orbital weights # (1 in case of collinear and 4 otherwise) while True: line = gl.readline() if line.startswith('tot'): dim += 1 elif line.startswith('band'): break # This function will read block of absolute weights # for i-th k-point, j-th band and s-th spin component def get_absweights(i, j, s): # Skip line with orbital names gl.readline() for k in xrange(dim): # Fetch entire orbital weight block data = np.fromfile(gl, sep=" ", count=nions*(norbs+2)) # Cast it into tabular shape data = data.reshape((nions, norbs+2)) # Discard first and last columns and store weights weights[i,:,j,k,s] = data[:,1:-1].flatten() # Skip line with the totals gl.readline() # Check whether phase information is included if '+ phase' in header_1: # Allocate storage for phases phases = np.zeros((npoints, nions*norbs, nbands, 2), complex) # Declare nested function that handles # parsing of complex weights def get_weights(i, j, s): # Read abs values of weights get_absweights(i, j, s) # Skip line with orbital names gl.readline() # Fetch entire phase block data = np.fromfile(gl, sep=" ", count=2*nions*(norbs+1)) # Cast it into tabular shape data = data.reshape((2*nions, norbs+1)) # Discard first column and store real and imaginary # parts respectively phases[i,:,j,s] = data[::2,1:].flatten() phases[i,:,j,s] += 1j*data[1::2,1:].flatten() else: # Phases are None in this case phases = None # In this case we just have absolutes of weights # No need for a new function get_weights = get_absweights # Go back to the beginning of the first k-point gl.seek(start) for i in xrange(npoints): # Parse k-point coordinates k_line = gl.readline().split() kpoints[i] = [float(k_line[c]) for c in [3, 4, 5]] kweights[i] = float(k_line[-1]) for j in xrange(nbands): # Parse band energy band_line = gl.readline().split() bands[i, j, 0] = float(band_line[4]) occupancies[i, j, 0] = float(band_line[-1]) # Parse orbital weights get_weights(i, j, 0) # Seek now for the second spin component res = gl.readline(False) # If there is no second spin component, finish by # returning just the first component if res is None: # Trim the second spin component bands = bands[:,:,:1] occupancies = occupancies[:,:,:1] weights = weights[:,:,:,:dim,:1] if phases is not None: phases = phases[:,:,:,:1] return [orbitals, kpoints, kweights, bands, occupancies, \ weights, phases] # Otherwise, read bands and weights for the second component for i in xrange(npoints): # Skip k-point coordinates gl.readline() for j in xrange(nbands): # Parse band energy band_line = gl.readline().split() bands[i, j, 1] = float(band_line[4]) occupancies[i, j, 1] = float(band_line[-1]) # Parse orbital weights get_weights(i, j, 1) return [orbitals, kpoints, kweights, bands, occupancies, \ weights[:,:,:,:dim,:], phases]
def parse_poscar(filename): '''Parses POSCAR file. Returns 3x3 float array containing unit cell vectors as rows, Nx3 array containing fractional positions of atoms, N being number of atoms and a list of chemical symbols. ''' try: gl = Getlines(filename) except: post_error('Unable to open "{0}" for reading.'.format(filename)) # Skip the comment line gl.readline() # Read the scaling factor f = float(gl.readline()) # Read the unit cell vectors cell = np.zeros((3, 3), float) cell[0] = f*np.array(gl.readline().split(), float) cell[1] = f*np.array(gl.readline().split(), float) cell[2] = f*np.array(gl.readline().split(), float) # Read the chemical symbols syms = gl.readline().split() # Read the atom counts counts = np.array(gl.readline().split(), int) # Get the number of atoms natoms = np.sum(counts) # Rearrange into the long list of chemical symbols symbols = [] for s, c in zip(syms, counts): symbols += [s]*c # Cartesian or fractional coordinates? ctype = gl.readline()[0].lower() if ctype == 'c': mult = np.linalg.inv(cell) elif ctype == 'd': mult = np.eye(3) else: post_error('"{0}" is unknown POSCAR option'.format(plines[7].strip())) # Allocate storage for positions spos = np.zeros((len(symbols), 3)) # Read the positions for i in xrange(len(symbols)): spos[i] = np.array(gl.readline().split()[:3], float) # If necessary, this will convert from # Cartesian to fractional coordinates spos = np.dot(spos, mult) return cell, spos, symbols
def build_translations(tgens): '''Build a list of translations and irreps from at most three linearly independent generators specified as lists of three fractions. ''' if len(tgens) > 3: post_error('There can be at most three generators ' 'of fractional translations.') # Use folded generators as floats to perform linear independece tests tgensf = np.array(tgens, dtype=float)%1 # Tolerance for linear independenec per dimension eps = 1e-2 # Check if generators are linearly independent if len(tgens) == 2 and np.all(np.cross(tgensf[0], tgensf[1]) < eps**2): post_error('Generators are not linearly independant.') elif len(tgens) == 3 and np.linalg.det(np.array(tgensf)) < eps**3: post_error('Generators are not linearly independant.') # Expand the generator list to be a 3x3 matrix tgens = np.append(tgens, np.ones((3-len(tgens), 3)), axis=0) # Get the order of every generator order = np.array([frac_translation_order(g) for g in tgens], int) # Get the corresponding roots of unity which will be # used to construct irreps deltag = np.exp(-2*np.pi*1j/order) # Fold the translation vectors into the unit cell tgens = tgens % 1 # Total number of translations ntrans = np.prod(order) # Storage for translations trans = np.zeros((ntrans, 3), float) # Calculate irreps of every generator irrepg = np.zeros((ntrans, 3), complex) # Loop over all possible products of powers of generators # and store the irreps for i in xrange(order[0]): for j in xrange(order[1]): for k in xrange(order[2]): ii = i*order[1]*order[2]+j*order[2]+k irrepg[ii] = deltag**[i, j, k] # Irrep table for all translations irreps = np.zeros((ntrans, ntrans), complex) # Loop over all posible products of powers of generators for i in xrange(order[0]): for j in xrange(order[1]): for k in xrange(order[2]): ti = i*order[1]*order[2]+j*order[2]+k # Get the translation vector trans[ti] = np.dot([i, j, k], tgens)%1 # Get all irreps of that translations for l in xrange(ntrans): irreps[l, ti] = np.prod(irrepg[l]**[i, j, k]) return trans, irreps
def build_translations(tgens): '''Build a list of translations and irreps from at most three linearly independent generators specified in the form [nx,ny,nz], representing translation [1/nx,1/ny,1/nz]. ''' if len(tgens) > 3: post_error('There can be at most three generators ' 'of fractional translations.') # Check if generators are linearly independent if len(tgens) == 2 and np.all(np.cross(tgens[0], tgens[1]) == 0): post_error('Generators are not linearly independant.') elif len(tgens) == 3 and np.linalg.det(tgens) == 0: post_error('Generators are not linearly independant.') # Expand the generator list to have a 3x3 matrix tgens = np.append(tgens, np.ones((3 - len(tgens), 3)), axis=0) # Get the order of every generator order = np.array([lcmm(*g) for g in tgens], int) # Get the corresponding roots of unity which will be # used to construct irreps deltag = np.exp(-2 * np.pi * 1j / order) # Calculate translation vectors tgens = (1.0 / tgens) % 1 # Total number of translations ntrans = np.prod(order) # Storage for translations trans = np.zeros((ntrans, 3), float) # Calculate irreps of every generator irrepg = np.zeros((ntrans, 3), complex) # Loop over all possible products of powers of generators # and store the irreps for i in xrange(order[0]): for j in xrange(order[1]): for k in xrange(order[2]): ii = i * order[1] * order[2] + j * order[2] + k irrepg[ii] = deltag**[i, j, k] # Irrep table for all translations irreps = np.zeros((ntrans, ntrans), complex) # Loop over all posible products of powers of generators for i in xrange(order[0]): for j in xrange(order[1]): for k in xrange(order[2]): ti = i * order[1] * order[2] + j * order[2] + k # Get the translation vector trans[ti] = np.dot([i, j, k], tgens) % 1 # Get all irreps of that translations for l in xrange(ntrans): irreps[l, ti] = np.prod(irrepg[l]**[i, j, k]) return trans, irreps
def parse_procar(filename): '''This function parses a PROCAR file. It returns a tuple consisting of following elements: orbitals - (norbs) string array of orbital labels (s, px, py etc...) kpoints - (npoints,3) float array of k-point coordinates kweights - (npoints) float array of k-point weights bands - (npoints,nbands,nspin) float array of band energies occupancies - (npoints,nbands,nspin) float array of band occupancies weights - (npoints,nions*norbs,nbands,ndim,nspin) float array of orbital weights phases - (npoints,nions*norbs,nbands,nspin) complex array of phases of orbital weights if LORBIT=12, otherwise None Where: norbs - number of orbitals (It can be 9 or 16 with f orbitals) npoints - number of k-points nbands - number of bands nspin - number of spins (1 for non spin-polarized, 2 otherwise) nions - number of atoms ndim - orbital weight dimensionality (1 for collinear, 4 otherwise) ''' try: gl = Getlines(filename) except: post_error('Unable to open "{0}" for reading.'.format(filename)) header_1 = gl.readline() header_2 = gl.readline().split() npoints = int(header_2[3]) nbands = int(header_2[7]) nions = int(header_2[-1]) # Remember the position in file start = gl.tell() # Skip two lines containing first k-point and band gl.readline() gl.readline() # Determine the number of orbitals orbitals = gl.readline().split()[1:-1] norbs = len(orbitals) # Allocate maximal storage. In case calculation was # non spin-polarized or collinear we can just trim # the excess components at the end kpoints = np.zeros((npoints, 3), float) kweights = np.zeros(npoints, float) bands = np.zeros((npoints, nbands, 2), float) occupancies = np.zeros((npoints, nbands, 2), float) weights = np.zeros((npoints, nions * norbs, nbands, 4, 2), float) dim = 0 # Determine if the calculation was non-collinear # by counting how many lines in the first band # block begin with tot. That number will be equal # to the number of sub-blocks for orbital weights # (1 in case of collinear and 4 otherwise) while True: line = gl.readline() if line.startswith('tot'): dim += 1 elif line.startswith('band'): break # This function will read block of absolute weights # for i-th k-point, j-th band and s-th spin component def get_absweights(i, j, s): # Skip line with orbital names gl.readline() for k in xrange(dim): # Fetch entire orbital weight block data = np.fromfile(gl, sep=" ", count=nions * (norbs + 2)) # Cast it into tabular shape data = data.reshape((nions, norbs + 2)) # Discard first and last columns and store weights weights[i, :, j, k, s] = data[:, 1:-1].flatten() # Skip line with the totals gl.readline() # Check whether phase information is included if '+ phase' in header_1: # Allocate storage for phases phases = np.zeros((npoints, nions * norbs, nbands, 2), complex) # Declare nested function that handles # parsing of complex weights def get_weights(i, j, s): # Read abs values of weights get_absweights(i, j, s) # Skip line with orbital names gl.readline() # Fetch entire phase block data = np.fromfile(gl, sep=" ", count=2 * nions * (norbs + 1)) # Cast it into tabular shape data = data.reshape((2 * nions, norbs + 1)) # Discard first column and store real and imaginary # parts respectively phases[i, :, j, s] = data[::2, 1:].flatten() phases[i, :, j, s] += 1j * data[1::2, 1:].flatten() else: # Phases are None in this case phases = None # In this case we just have absolutes of weights # No need for a new function get_weights = get_absweights # Go back to the beginning of the first k-point gl.seek(start) for i in xrange(npoints): # Parse k-point coordinates k_line = gl.readline().split() kpoints[i] = [float(k_line[c]) for c in [3, 4, 5]] kweights[i] = float(k_line[-1]) for j in xrange(nbands): # Parse band energy band_line = gl.readline().split() bands[i, j, 0] = float(band_line[4]) occupancies[i, j, 0] = float(band_line[-1]) # Parse orbital weights get_weights(i, j, 0) # Seek now for the second spin component res = gl.readline(False) # If there is no second spin component, finish by # returning just the first component if res is None: # Trim the second spin component bands = bands[:, :, :1] occupancies = occupancies[:, :, :1] weights = weights[:, :, :, :dim, :1] if phases is not None: phases = phases[:, :, :, :1] return [orbitals, kpoints, kweights, bands, occupancies, \ weights, phases] # Otherwise, read bands and weights for the second component for i in xrange(npoints): # Skip k-point coordinates gl.readline() for j in xrange(nbands): # Parse band energy band_line = gl.readline().split() bands[i, j, 1] = float(band_line[4]) occupancies[i, j, 1] = float(band_line[-1]) # Parse orbital weights get_weights(i, j, 1) return [orbitals, kpoints, kweights, bands, occupancies, \ weights[:,:,:,:dim,:], phases]