def elPolarization(self, berryPhase, calcValues, ELEC_BY_VOL_CONST): ''' Calculate electronic component of polarization Input: berryPhase[direction 1, 2, 3][spin] Calculation: Pel_x = electron charge / unit volume (m) * \ berry phase mean value/2pi * lattice_matrices (diagonal x); ''' # [OLEG]: check which lattice vectors to use br1 or br2 nspins = numpy.shape(berryPhase)[1] elP = numpy.zeros((nspins, 3)) for spinIndex in range(0, nspins): for coordIndex in range(0, 3): # loop over 3 lattice vector direct. latVec = calcValues['Real primitive lat vectors in bohr'][ coordIndex] normLatVec = numpy.linalg.norm(latVec) # length of lat. vector elP[spinIndex,coordIndex] = \ (berryPhase[coordIndex,spinIndex]/(2*numpy.pi)) * \ ELEC_BY_VOL_CONST * \ bohrToMeters(normLatVec) print("Electronic polarization (C/m2) " +\ "sp(%1i) " % (spinIndex+1), \ "[% e, % e, % e]" % tuple(elP[spinIndex,:])) print("=" * 87) return elP
def elPolarization(self, berryPhase, calcValues, ELEC_BY_VOL_CONST): ''' Calculate electronic component of polarization Input: berryPhase[direction 1, 2, 3][spin] Calculation: Pel_x = electron charge / unit volume (m) * \ berry phase mean value/2pi * lattice_matrices (diagonal x); ''' latticeConstants = calcValues['Lattice Constants in bohr'] latticeMatrix_x = calcValues['Lattice Matrix in bohr'][0] latticeMatrix_y = calcValues['Lattice Matrix in bohr'][1] latticeMatrix_z = calcValues['Lattice Matrix in bohr'][2] # return the absolute value of the vector sqrt(x^2 + y^2 + z^2) absVector = lambda vec: math.sqrt(sum([i**2 for i in vec])) # length of lattice vectors lattice_x = absVector(latticeMatrix_x) lattice_y = absVector(latticeMatrix_y) lattice_z = absVector(latticeMatrix_z) # Electronic Polarization [OLEG]: check which lattice constants to use nspins = numpy.shape(berryPhase)[1] elP = numpy.zeros((nspins, 3)) for spinIndex in range(0, nspins): for coordIndex in range(0, 3): elP[spinIndex,coordIndex] = \ (berryPhase[coordIndex,spinIndex]/(2*numpy.pi)) * \ ELEC_BY_VOL_CONST * \ bohrToMeters(latticeConstants[coordIndex]) print "Electronic polarization (C/m2) " +\ "sp(%1i) " % (spinIndex+1), \ "[% e, % e, % e]" % tuple(elP[spinIndex,:]) print "=" * 87 return elP
def elPolarization(self, berryPhase, calcValues, ELEC_BY_VOL_CONST): ''' Calculate electronic component of polarization Input: berryPhase[direction 1, 2, 3][spin] Calculation: Pel_x = electron charge / unit volume (m) * \ berry phase mean value/2pi * lattice_matrices (diagonal x); ''' latticeConstants = calcValues['Lattice Constants in bohr'] latticeMatrix_x = calcValues['Lattice Matrix in bohr'][0] latticeMatrix_y = calcValues['Lattice Matrix in bohr'][1] latticeMatrix_z = calcValues['Lattice Matrix in bohr'][2] # return the absolute value of the vector sqrt(x^2 + y^2 + z^2) absVector = lambda vec: math.sqrt(sum([i**2 for i in vec])) # length of lattice vectors lattice_x = absVector(latticeMatrix_x) lattice_y = absVector(latticeMatrix_y) lattice_z = absVector(latticeMatrix_z) # Electronic Polarization [OLEG]: check which lattice constants to use nspins = numpy.shape(berryPhase)[1] elP = numpy.zeros((nspins,3)) for spinIndex in range(0,nspins): for coordIndex in range(0,3): elP[spinIndex,coordIndex] = \ (berryPhase[coordIndex,spinIndex]/(2*numpy.pi)) * \ ELEC_BY_VOL_CONST * \ bohrToMeters(latticeConstants[coordIndex]); print "Electronic polarization (C/m2) " +\ "sp(%1i) " % (spinIndex+1), \ "[% e, % e, % e]" % tuple(elP[spinIndex,:]); print "="*87 return elP; # END elPolarization
def determineIonPolarization(self, fnWrpMethod, args): ''' INPUT: fnWrpMethod - function that determines the method for wrapping the phase args - contains logical variables regarding the calculation setup args['so'] - spin-orbit coupling args['sp'] - spin-polarized calculation args['orb'] - additional orbital potential (LDA+U) Calculation: Pion_x = electron charge / unit volume (m) * lattice_x * ( sum of ( atom valence charge * position(x) ) ) where atom valence charge = ( core value - spin val 1 - spin val 2 ) ''' print "\n", "CALCULATION OF IONIC POLARIZATION" ionP = [] calcValues = self.calculationValues() ELEC_BY_VOL_CONST = self.ELEC_BY_VOL_CONST latticeConstants = calcValues['Lattice Constants in bohr'] atomListing = calcValues['Atom Listing'] #produce a tuple pair which includes the valence electrons and #the coordinates for each element calcIonValues = [] # (coordinates(x,y,z), valence value) #TODO: include good exception handling for this stage #construct the calcIonValues for the calculation if args['sp'] and not args['so']: nspins = 2 else: nspins = 1 for atom in atomListing: for i in range(atom['MULT']): theElementName = atom['Element Name'] if calcValues['Element Listing'].has_key(theElementName): theElement = calcValues['Element Listing'][theElementName] if nspins == 2: theValence = [] for spin in [1, 2]: theValence.append( \ atom['Znucl']/2 - theElement['Core Value']/2 \ ) else: theValence = atom['Znucl'] - theElement['Core Value'] xCoordinate = atom['X-Coord'][i] yCoordinate = atom['Y-Coord'][i] zCoordinate = atom['Z-Coord'][i] #produce tuple from coordinates coordinates = (xCoordinate, yCoordinate, zCoordinate) calcIonValues.append( (theElementName, coordinates, theValence)) else: print DEFAULT_PREFIX + 'ERROR: Missing element in element list' print DEFAULT_PREFIX + theElementName print DEFAULT_PREFIX + calcValues['Element List'] print DEFAULT_PREFIX + 'Exiting....' sys.exit(1) self._calcIonValues = calcIonValues #### CALCULATION #### xPolarIon, yPolarIon, zPolarIon = (0., 0., 0.) print "=" * 87 print "Elem.| Fractional coord. | spin | Zion |", \ " dir(1) ", \ "| ", "dir(2) ", "| ", "dir(3)" print "-" * 87 print " "*41, "+"+"-"*12, "Ionic phase (rad)", \ "-"*12+"+" totIonPhase = numpy.zeros((nspins, 3)) for element, iCoord, iValence in calcIonValues: spinIndex = -1 if isinstance(iValence, collections.Iterable): pass else: iValence = [ iValence, ] for spinValence in iValence: spinIndex += 1 ionPhase = numpy.zeros((nspins, 3)) coordIndex = -1 for fcoord in iCoord: coordIndex += 1 # fractional coordinates used psi = fcoord * spinValence * 2 * numpy.pi ionPhase[spinIndex, coordIndex] = psi if spinIndex == 0: print "%2s " % element, \ "(%6.4f, %6.4f, %6.4f) " % iCoord, \ "sp(%1i)" % (spinIndex+1), \ "%5.2f" % spinValence, \ "[% e, % e, % e]" % tuple(ionPhase[spinIndex,:]) else: print " "*29, \ "sp(%1i)" % (spinIndex+1), \ "%5.2f" % spinValence, \ "[% e, % e, % e]" % tuple(ionPhase[spinIndex,:]) totIonPhase[:, :] += ionPhase print "-" * 87 for spinIndex in range(0, nspins): print "Total ionic phase (rad)", " "*5, \ "sp(%1i)" % (spinIndex+1), " "*5, \ "[% e, % e, % e]" % tuple(totIonPhase[spinIndex,:]) # warap phases totIonPhase = fnWrpMethod(totIonPhase) for spinIndex in range(0, nspins): print "Total ionic phase wrap. (rad)", \ "sp(%1i)" % (spinIndex+1), " "*5, \ "[% e, % e, % e]" % tuple(totIonPhase[spinIndex,:]) #IONIC Polarization ionPol = numpy.zeros((nspins, 3)) for spinIndex in range(0, nspins): for coordIndex in range(0, 3): psi = totIonPhase[spinIndex, coordIndex] a = latticeConstants[coordIndex] ionPol[spinIndex,coordIndex] = \ (psi/(2*numpy.pi)) * ELEC_BY_VOL_CONST * \ bohrToMeters(a) print "Ionic polarization (C/m2) ", \ "sp(%1i)" % (spinIndex+1), " "*5, \ "[% e, % e, % e]" % tuple(ionPol[spinIndex,:]) print "=" * 87 return ionPol # END determineIonPolarization
def __init__(self, **args): # spin polarization: yes/no spCalc = args['sp'] ############################ ######### PARSING ########## ############################ ###Rest of the Files### #parse all the things! #### *.struct file parser parser_struct_handle = open(args['file_struct'], 'r').readlines() parser_struct_handle = b_PyParse.MainStructParser(parser_struct_handle) parser_struct_handle.parse() #### *.scf file parser parser_scf_handle = open(args['file_scf'], 'r').readlines() parser_scf_handle = b_PyParse.MainSCFParser(parser_scf_handle) parser_scf_handle.parse() #### *.outputd parser parser_outputd_handle = open(args['file_outputd'], 'r').readlines() parser_outputd_handle = b_PyParse.MainOutputDParser( parser_outputd_handle) parser_outputd_handle.parse() #### *.outputst parser parser_outputst_handle = open(args['file_outputst'], 'r').readlines() parser_outputst_handle = b_PyParse.MainOutputstParser( parser_outputst_handle) parser_outputst_handle.parse() ##################################### ############ END Parsing ############ ##################################### ############################# ###### Getting Values ####### ############################# self._calculationValues = orderedDict() #### *.struct handle # - determine name of atoms # - determine MULT for each atom self._calculationValues['Atom Listing'] = \ parser_struct_handle['Atom Listing'] #### *.scf handle # - Cell Volume self._calculationValues['Cell Volume in bohr^3'] = \ parser_scf_handle['Cell Volume'] self._calculationValues['Cell Volume in m^3'] = \ bohrToMeters(self._calculationValues['Cell Volume in bohr^3'],3) #### *.outputd handle # - BR2_DIR matrix (v_x, v_y, v_z) # - number of atoms in cell # - Lattice Constants (x,y,z) laticematrix = parser_outputd_handle['BR2_DIR Matrix'] latticematrixa1 = laticematrix[0] self._calculationValues['Lattice Matrix a1 in bohr'] = \ latticematrixa1 latticematrixa2 = laticematrix[1] self._calculationValues['Lattice Matrix a2 in bohr'] = \ latticematrixa2 latticematrixa3 = laticematrix[2] self._calculationValues['Lattice Matrix a3 in bohr'] = \ latticematrixa3 self._calculationValues['Lattice Matrix in bohr'] = \ parser_outputd_handle['BR2_DIR Matrix'] self._calculationValues['Lattice Matrix in m'] = \ [[ bohrToMeters(i) for i in j ] for j in \ self._calculationValues['Lattice Matrix in bohr']] self._calculationValues['Number of Atoms in Unit Cell'] = \ parser_outputd_handle['Number of Atoms in Unit Cell'] self._calculationValues['Lattice Constants in bohr'] = \ parser_outputd_handle['Lattice Constants'] self._calculationValues['Lattice Constants in m'] = \ [ bohrToMeters(i) for i in \ self._calculationValues['Lattice Constants in bohr']] #### *.outputst handle # for each element: # - Core Value # - Spin Value 1 # - Spin Value 2 self._calculationValues['Element Listing'] = \ parser_outputst_handle['Element List'] #### ######################## # get electronic phase # ######################## # get raw list [k-points, phase] phaseDirSpinPathRaw = args['phases'] # wrap phases in the range [-pi ... +pi] phaseDirSpinPathWrp11 = self.wrpPhase(phaseDirSpinPathRaw, \ self.wrp11) # print nice print "\n","Initial Berry phases and their", \ "wrapped values in the range [-pi ... +pi]" print "=" * 87 print " "*30, "| init k-point", "| phase raw (rad)", \ "| phase wrap. (rad)" icoord = -1 for coord in phaseDirSpinPathRaw: icoord += 1 print "-" * 87 print "direction(%u)" % int(icoord + 1) ispin = -1 for spin in coord: ispin += 1 print " " * 12, "spin(%u)" % int(ispin + 1) ipath = -1 for path in spin: ipath += 1 # perform wraping using the method privided in input kpt = phaseDirSpinPathRaw[icoord][ispin][ipath][0] ph = phaseDirSpinPathRaw[icoord][ispin][ipath][1] phwrp = phaseDirSpinPathWrp11[icoord][ispin][ipath][1] print " "*20, "path(%4d) %4d % e % e" \ % (ipath+1, kpt, ph, phwrp) print "=" * 87 print "\n", "CALCULATION OF ELECTRONIC POLARIZATION" print "=" * 87 print "Value", " "*25, "| spin ", "| ", "dir(1) ", \ "| ", "dir(2) ", "| ", "dir(3)" print "-" * 87 # find path-average phase phaseDirSpinWrp11 = self.pathAvrgPhase(phaseDirSpinPathWrp11) # wrap the average phase again as it can go out of bounds [-pi..+pi] phaseDirSpinWrp11 = self.wrp11(phaseDirSpinWrp11) nspins = numpy.shape(phaseDirSpinWrp11)[1] for spinIndex in range(0, nspins): print "Berry phase (rad) [-pi ... +pi] sp(%1i)" \ % (spinIndex+1), \ " [% e, % e, % e]" % tuple(phaseDirSpinWrp11[:,spinIndex]) if not spCalc and not args[ 'so']: # in case of non-SP or non-SO calculation... phaseDirSpinWrp11 = 2 * phaseDirSpinWrp11 # account for the spin degeneracy nspins = numpy.shape(phaseDirSpinWrp11)[1] if nspins != 1: # double check print "Inconsistency detected in the number of spins" print "Is it spin-polarized calculation? spCalc =", spCalc print "Number of spins in the electronic phase array", \ nspins print "Expected 1 spin" print "Decision is taken to EXIT" sys.exit(2) print "Berry phase (rad) up+dn "+ \ "[% e, % e, % e]" % tuple(phaseDirSpinWrp11) # wrap phases again [-pi ... +pi] phaseDirSpinWrp11 = self.wrp11(phaseDirSpinWrp11) print "Berry phase (rad) [-pi ... +pi] " +\ " up+dn [% e, % e, % e]" \ % tuple(phaseDirSpinWrp11) #electron charge / cell volume self.ELEC_BY_VOL_CONST = ELECTRON_CHARGE / \ bohrToMeters(self._calculationValues['Cell Volume in bohr^3'], \ dimension = 3.) # electronic polarization (C/m2) elP = self.elPolarization(phaseDirSpinWrp11,self._calculationValues, \ self.ELEC_BY_VOL_CONST) # ionic polarization (C/m2) ionP = self.determineIonPolarization(self.wrp11, args) # Total polarization (C/m2) will be returned # when calling mainCalculation() self._totalPolarizationVal = self.totalPolarization(elP, ionP)
def determineIonPolarization(self, fnWrpMethod, args): ''' INPUT: fnWrpMethod - function that determines the method for wrapping the phase args - contains logical variables regarding the calculation setup args['so'] - spin-orbit coupling args['sp'] - spin-polarized calculation args['orb'] - additional orbital potential (LDA+U) Calculation: Pion_x = electron charge / unit volume (m) * lattice_x * ( sum of ( atom valence charge * position(x) ) ) where atom valence charge = ( core value - spin val 1 - spin val 2 ) ''' print("\n\nCALCULATION OF IONIC POLARIZATION",\ "(conventional lattice coordinates)") ionP = [] calcValues = self.calculationValues() ELEC_BY_VOL_CONST = self.ELEC_BY_VOL_CONST latticeConstants = calcValues['Lattice Constants in bohr'] atomListing = calcValues['Atom Listing'] #produce a tuple pair which includes the valence electrons and #the coordinates for each element calcIonValues = [] # (coordinates(x,y,z), valence value) #TODO: include good exception handling for this stage #construct the calcIonValues for the calculation if args['sp'] and not args['so']: nspins = 2 else: nspins = 1 iatom = -1 # atom index Zcore = self.calcVal['Atom core charges'] # non-equiv. atoms for atom in atomListing: # loop over non-equivalent atoms iatom = iatom + 1 theElementName = atom['Element Name'] for i in range(atom['MULT']): if nspins == 2: theValence = [] for spin in [1, 2]: theValence.append( \ atom['Znucl']/2 - Zcore[iatom]/2 \ ) else: theValence = atom['Znucl'] - Zcore[iatom] xCoordinate = atom['X-Coord'][i] yCoordinate = atom['Y-Coord'][i] zCoordinate = atom['Z-Coord'][i] #produce tuple from coordinates coordinates = (xCoordinate, yCoordinate, zCoordinate) calcIonValues.append((theElementName, coordinates, theValence)) self._calcIonValues = calcIonValues #### CALCULATION #### xPolarIon, yPolarIon, zPolarIon = (0., 0., 0.) print("=" * 87) print("Elem.| Fractional coord. | spin | Zion |", \ " dir(1) ", \ "| ", "dir(2) ", "| ", "dir(3)") print("-" * 87) print(" "*41, "+"+"-"*12, "Ionic phase (rad)", \ "-"*12+"+") totIonPhase = numpy.zeros((nspins, 3)) for element, iCoord, iValence in calcIonValues: spinIndex = -1 if isinstance(iValence, collections.Iterable): pass else: iValence = [ iValence, ] for spinValence in iValence: spinIndex += 1 ionPhase = numpy.zeros((nspins, 3)) coordIndex = -1 for fcoord in iCoord: coordIndex += 1 # fractional coordinates used psi = fcoord * spinValence * 2 * numpy.pi ionPhase[spinIndex, coordIndex] = psi if spinIndex == 0: print("%2s " % element, \ "(%6.4f, %6.4f, %6.4f) " % iCoord, \ "sp(%1i)" % (spinIndex+1), \ "%5.2f" % spinValence, \ "[% e, % e, % e]" % tuple(ionPhase[spinIndex,:])) else: print(" "*29, \ "sp(%1i)" % (spinIndex+1), \ "%5.2f" % spinValence, \ "[% e, % e, % e]" % tuple(ionPhase[spinIndex,:])) totIonPhase[:, :] += ionPhase print("-" * 87) for spinIndex in range(0, nspins): print("Total ionic phase (rad)", " "*5, \ "sp(%1i)" % (spinIndex+1), " "*5, \ "[% e, % e, % e]" % tuple(totIonPhase[spinIndex,:])) # warap phases totIonPhase = fnWrpMethod(totIonPhase) for spinIndex in range(0, nspins): print("Total ionic phase wrap. (rad)", \ "sp(%1i)" % (spinIndex+1), " "*5, \ "[% e, % e, % e]" % tuple(totIonPhase[spinIndex,:])) #IONIC Polarization ionPol = numpy.zeros((nspins, 3)) for spinIndex in range(0, nspins): for coordIndex in range(0, 3): # loop over 3 lattice vector direct. psi = totIonPhase[spinIndex, coordIndex] # WIEN2k always uses conventional latt. vec. for ionic positions latVec = calcValues['Real conventional lat vectors in bohr'][ coordIndex] normLatVec = numpy.linalg.norm(latVec) # length of lat. vector ionPol[spinIndex,coordIndex] = \ (psi/(2*numpy.pi)) * ELEC_BY_VOL_CONST * \ bohrToMeters(normLatVec) print("Ionic polarization (C/m2) ", \ "sp(%1i)" % (spinIndex+1), " "*5, \ "[% e, % e, % e]" % tuple(ionPol[spinIndex,:])) print("=" * 87) return ionPol # END determineIonPolarization
def __init__(self, **args): # spin polarization: yes/no spCalc = args['sp'] ############################ ######### PARSING ########## ############################ ###Rest of the Files### #parse all the things! #### *.struct file parser parser_struct_handle = open(args['file_struct'], 'r').readlines() parser_struct_handle = b_PyParse.MainStructParser(parser_struct_handle) parser_struct_handle.parse() #### *.inc parser parser_inc_handle = open(args['file_inc'], 'r').readlines() parser_inc_handle = b_PyParse.MainIncParser(parser_inc_handle) parser_inc_handle.parse() ##################################### ############ END Parsing ############ ##################################### ############################# ###### Getting Values ####### ############################# self.calcVal = orderedDict() #### *.struct handle # - name of atoms # - MULT for each atom # - coordinates # - nuclear charge self.calcVal['Atom Listing'] = \ parser_struct_handle['Atom Listing'] # - lattice type (P,F,B,CXY,CYZ,CXZ,R,H) self.calcVal['lattice type'] = \ parser_struct_handle['lattice type'] # - lattice orthogonal (T/F) self.calcVal['lattice ortho'] = \ parser_struct_handle['lattice ortho'] # - Cell Volume self.calcVal['Cell Volume in bohr^3'] = \ parser_struct_handle['cell volume'] # - Lattice Constants (a,b,c) self.calcVal['Lattice Constants in bohr'] = \ parser_struct_handle['lattice constants'] # - lattice vectors BR1_DIR matrix (v_x, v_y, v_z) self.calcVal['Real conventional lat vectors in bohr'] = \ parser_struct_handle['real space conventional lattice vectors'] # - lattice vectors BR2_DIR matrix (v_x, v_y, v_z) self.calcVal['Real primitive lat vectors in bohr'] = \ parser_struct_handle['real space primitive lattice vectors'] ### *.inc handle # - core charge for each non-eqivalent atom self.calcVal['Atom core charges'] = \ parser_inc_handle['core charges'] # check consistency of case.struct and case.inc files if len(self.calcVal['Atom core charges']) != \ len(self.calcVal['Atom Listing']): print("Number of non-equivalent atoms in case.struct:", \ len(self.calcVal['Atom Listing'])) print("Number of non-equivalent atoms in case.inc:", \ len(self.calcVal['Atom core charges'])) raise Exception("Inconsistent number of non-equivalent atoms") # list of pi-wrapping functions that will be applied to wrap the phase wrpFnList = [self.wrp02, self.wrp11] for wrpFn in wrpFnList: # iterate over various wrapping functions if wrpFn == self.wrp11: print("\n\n~~~~~~~~~~~~~~~~~~~~ " +\ "phases are wrapped in the range [-pi .. +pi]" +\ " ~~~~~~~~~~~~~~~~~~~~~~") elif wrpFn == self.wrp02: print("\n\n~~~~~~~~~~~~~~~~~~~~ " +\ "phases are wrapped in the range [0 .. +2pi] " +\ " ~~~~~~~~~~~~~~~~~~~~~") ######################## # get electronic phase # ######################## # get raw list [k-points, phase] phaseDirSpinPathRaw = args['phases'] # wrap phases in the range [-pi ... +pi] phaseDirSpinPathWrp = self.wrpPhase(phaseDirSpinPathRaw, \ wrpFn) # print nice print("\n","Initial Berry phases and their", \ "wrapped values") print("=" * 87) print(" "*30, "| init k-point", "| phase raw (rad)", \ "| phase wrap. (rad)") icoord = -1 for coord in phaseDirSpinPathRaw: icoord += 1 print("-" * 87) print("direction(%u)" % int(icoord + 1)) ispin = -1 for spin in coord: ispin += 1 print(" " * 12, "spin(%u)" % int(ispin + 1)) ipath = -1 for path in spin: ipath += 1 # perform wraping using the method privided in input kpt = phaseDirSpinPathRaw[icoord][ispin][ipath][0] ph = phaseDirSpinPathRaw[icoord][ispin][ipath][1] phwrp = phaseDirSpinPathWrp[icoord][ispin][ipath][1] print(" "*20, "path(%4d) %4d % e % e" \ % (ipath+1, kpt, ph, phwrp)) print("=" * 87) print("\n\nCALCULATION OF ELECTRONIC POLARIZATION",\ "(primitive lattice coordinates)") print("=" * 87) print("Value", " "*25, "| spin ", "| ", "dir(1) ", \ "| ", "dir(2) ", "| ", "dir(3)") print("-" * 87) # find path-average phase phaseDirSpinWrp = self.pathAvrgPhase(phaseDirSpinPathWrp) # wrap the average phase again as it can go out of bounds [-pi..+pi] phaseDirSpinWrp = wrpFn(phaseDirSpinWrp) nspins = numpy.shape(phaseDirSpinWrp)[1] for spinIndex in range(0, nspins): print("Berry phase wrapped (rad) sp(%1i)" \ % (spinIndex+1), \ " [% e, % e, % e]" % tuple(phaseDirSpinWrp[:,spinIndex])) if not spCalc and not args[ 'so']: # in case of non-SP or non-SO calculation... phaseDirSpinWrp = 2 * phaseDirSpinWrp # account for the spin degeneracy nspins = numpy.shape(phaseDirSpinWrp)[1] if nspins != 1: # double check print("Inconsistency detected in the number of spins") print("Is it spin-polarized calculation? spCalc =", spCalc) print("Number of spins in the electronic phase array", \ nspins) print("Expected 1 spin") print("Decision is taken to EXIT") sys.exit(2) print("Berry phase (rad) up+dn "+ \ "[% e, % e, % e]" % tuple(phaseDirSpinWrp)) # wrap phases again [-pi ... +pi] phaseDirSpinWrp = wrpFn(phaseDirSpinWrp) print("Berry phase wrapped (rad)" +\ " up+dn [% e, % e, % e]" \ % tuple(phaseDirSpinWrp)) #electron charge / cell volume self.ELEC_BY_VOL_CONST = ELECTRON_CHARGE / \ bohrToMeters(self.calcVal['Cell Volume in bohr^3'], \ dimension = 3.) # electronic polarization (C/m2) elP = self.elPolarization(phaseDirSpinWrp,self.calcVal, \ self.ELEC_BY_VOL_CONST) # convert elP from primitive basis to Cart. coordinates # WIEN2k always uses primitive latt. vec. in constructing Brillouin zone print("\nThe electronic polarization vector is presented in",\ "this coord. system:") latVec = numpy.zeros((3, 3)) for i in range(3): # gather lattice vectors into a (3x3) array latVec[i, :] = self.calcVal[ 'Real primitive lat vectors in bohr'][i] print(" "*4, "dir(%1i) =" % (i+1), \ "[% e, % e, % e] bohr" % tuple(latVec[i,:])) print("and will be transformed into Cartesian coordinates.") for i in range(elP.shape[0]): # loop over spin channels elP[i, :] = vec2cart(elP[i, :], latVec) # prim -> Cartesian ############################# # ionic polarization (C/m2) # ############################# ionP = self.determineIonPolarization(wrpFn, args) # convert ionP from conventional to Cart. coordinates # WIEN2k always uses conventional latt. vec. for ionic positions print("\nThe ionic positions and associated polarization vector is",\ "presented in this coord. system:") latVec = numpy.zeros((3, 3)) for i in range(3): # gather lattice vectors into a (3x3) array latVec[i, :] = self.calcVal[ 'Real conventional lat vectors in bohr'][i] print(" "*4, "dir(%1i) =" % (i+1), \ "[% e, % e, % e] bohr" % tuple(latVec[i,:])) for i in range(ionP.shape[0]): # loop over spin channels ionP[i, :] = vec2cart(ionP[i, :], latVec) # conventional -> Cartesian ############################# # total polarization (C/m2) # ############################# # Total polarization (C/m2) will be returned # when calling mainCalculation() self._totalPolarizationVal = self.totalPolarization(elP, ionP) # Prepare transform polarization from lattice vectors to Cartesian # coordinates (totP -> totPcart latVec = numpy.zeros((3, 3)) # END iterate over various wrapping functions print(''' Notes: (1) When lattice vectors are _not_ aligned with Cartesian coordinates, an additional transformation is required to present the polarization vector in Cartesian coordinates. This can be important when calculating Born effective charges Z*_i,j = (Omega/e) * dP_i/dr_j, where i, j are Cartesian components, Omega is the cell volume (see output above), and e is the elementary charge. The total polarization vector is presented in Cartesian coordinates and should be used to determine dP_i. It is, however, up to the user to transform the atom displacement vector components dr_j into Cartesian coordinates. The change in fractional coordinated should be converted into dr_j using lattice vectors dir(1,2,3) used in calculation of the ionic polarization (see above). An Octave/Matlab sctipt can be found in https://github.com/spichardo/BerryPI/wiki/Tutorial-3:-Non-orthogonal-lattice-vectors (2) Results are presented for two pi-wrapping options [-pi .. +pi] and [0 .. +2pi] separated by ~~~~~~. It is designed to assist in situations when a sudden change in the Berry phase (and associated polarization) occurs due to a pi-wrapping artefact (see discussion pertaining Fig. 2 in Ref. [1]). It is up to the user to select the most relevant option. This decision should be made based on comparing results for _two_ different structures. The goal is to ensure a smooth change in the phase in response to a small perturbation.''')
def determineIonPolarization(self, fnWrpMethod, args): ''' INPUT: fnWrpMethod - function that determines the method for wrapping the phase args - contains logical variables regarding the calculation setup args['so'] - spin-orbit coupling args['sp'] - spin-polarized calculation args['orb'] - additional orbital potential (LDA+U) Calculation: Pion_x = electron charge / unit volume (m) * lattice_x * ( sum of ( atom valence charge * position(x) ) ) where atom valence charge = ( core value - spin val 1 - spin val 2 ) ''' print "\n", "CALCULATION OF IONIC POLARIZATION" ionP = [] calcValues = self.calculationValues() ELEC_BY_VOL_CONST = self.ELEC_BY_VOL_CONST latticeConstants = calcValues['Lattice Constants in bohr'] atomListing = calcValues['Atom Listing'] #produce a tuple pair which includes the valence electrons and #the coordinates for each element calcIonValues = [] # (coordinates(x,y,z), valence value) #TODO: include good exception handling for this stage #construct the calcIonValues for the calculation if args['sp'] and not args['so']: nspins = 2 else: nspins = 1 for atom in atomListing: for i in range(atom['MULT']): theElementName = atom['Element Name'] if calcValues['Element Listing'].has_key(theElementName): theElement = calcValues['Element Listing'][theElementName] if nspins == 2: theValence = [] for spin in [1, 2]: theValence.append( \ atom['Znucl']/2 - theElement['Core Value']/2 \ ); else: theValence = atom['Znucl'] - theElement['Core Value']; xCoordinate = atom['X-Coord'][i] yCoordinate = atom['Y-Coord'][i] zCoordinate = atom['Z-Coord'][i] #produce tuple from coordinates coordinates = (xCoordinate, yCoordinate, zCoordinate) calcIonValues.append((theElementName,coordinates, theValence)) else: print DEFAULT_PREFIX + 'ERROR: Missing element in element list' print DEFAULT_PREFIX + theElementName print DEFAULT_PREFIX + calcValues['Element List'] print DEFAULT_PREFIX + 'Exiting....' sys.exit(1) self._calcIonValues = calcIonValues #### CALCULATION #### xPolarIon, yPolarIon, zPolarIon = (0., 0., 0.) print "="*87 print "Elem.| Fractional coord. | spin | Zion |", \ " dir(1) ", \ "| ", "dir(2) ", "| ", "dir(3)" print "-"*87 print " "*41, "+"+"-"*12, "Ionic phase (rad)", \ "-"*12+"+" totIonPhase = numpy.zeros((nspins,3)) for element, iCoord, iValence in calcIonValues: spinIndex = -1 if isinstance(iValence, collections.Iterable): pass else: iValence = [iValence, ] for spinValence in iValence: spinIndex += 1 ionPhase = numpy.zeros((nspins,3)) coordIndex = -1 for fcoord in iCoord: coordIndex += 1 # fractional coordinates used psi = fcoord * spinValence * 2*numpy.pi ionPhase[ spinIndex , coordIndex ] = psi if spinIndex == 0: print "%2s " % element, \ "(%6.4f, %6.4f, %6.4f) " % iCoord, \ "sp(%1i)" % (spinIndex+1), \ "%5.2f" % spinValence, \ "[% e, % e, % e]" % tuple(ionPhase[spinIndex,:]); else: print " "*29, \ "sp(%1i)" % (spinIndex+1), \ "%5.2f" % spinValence, \ "[% e, % e, % e]" % tuple(ionPhase[spinIndex,:]); totIonPhase[:,:] += ionPhase print "-"*87 for spinIndex in range(0,nspins): print "Total ionic phase (rad)", " "*5, \ "sp(%1i)" % (spinIndex+1), " "*5, \ "[% e, % e, % e]" % tuple(totIonPhase[spinIndex,:]); # warap phases totIonPhase = fnWrpMethod( totIonPhase ); for spinIndex in range(0,nspins): print "Total ionic phase wrap. (rad)", \ "sp(%1i)" % (spinIndex+1), " "*5, \ "[% e, % e, % e]" % tuple(totIonPhase[spinIndex,:]); #IONIC Polarization ionPol = numpy.zeros((nspins,3)) for spinIndex in range(0,nspins): for coordIndex in range(0,3): psi = totIonPhase[spinIndex,coordIndex] a = latticeConstants[coordIndex] ionPol[spinIndex,coordIndex] = \ (psi/(2*numpy.pi)) * ELEC_BY_VOL_CONST * \ bohrToMeters(a); print "Ionic polarization (C/m2) ", \ "sp(%1i)" % (spinIndex+1), " "*5, \ "[% e, % e, % e]" % tuple(ionPol[spinIndex,:]); print "="*87 return ionPol # END determineIonPolarization
def __init__(self, **args): # spin polarization: yes/no spCalc = args['sp'] ############################ ######### PARSING ########## ############################ ###Rest of the Files### #parse all the things! #### *.struct file parser parser_struct_handle = open(args['file_struct'], 'r').readlines() parser_struct_handle = b_PyParse.MainStructParser(parser_struct_handle) parser_struct_handle.parse() #### *.scf file parser parser_scf_handle = open(args['file_scf'], 'r').readlines() parser_scf_handle = b_PyParse.MainSCFParser(parser_scf_handle) parser_scf_handle.parse() #### *.outputd parser parser_outputd_handle = open(args['file_outputd'], 'r').readlines() parser_outputd_handle = b_PyParse.MainOutputDParser(parser_outputd_handle) parser_outputd_handle.parse() #### *.outputst parser parser_outputst_handle = open(args['file_outputst'], 'r').readlines() parser_outputst_handle = b_PyParse.MainOutputstParser(parser_outputst_handle) parser_outputst_handle.parse() ##################################### ############ END Parsing ############ ##################################### ############################# ###### Getting Values ####### ############################# self._calculationValues = orderedDict(); #### *.struct handle # - determine name of atoms # - determine MULT for each atom self._calculationValues['Atom Listing'] = \ parser_struct_handle['Atom Listing']; #### *.scf handle # - Cell Volume self._calculationValues['Cell Volume in bohr^3'] = \ parser_scf_handle['Cell Volume']; self._calculationValues['Cell Volume in m^3'] = \ bohrToMeters(self._calculationValues['Cell Volume in bohr^3'],3); #### *.outputd handle # - BR2_DIR matrix (v_x, v_y, v_z) # - number of atoms in cell # - Lattice Constants (x,y,z) laticematrix=parser_outputd_handle['BR2_DIR Matrix'] latticematrixa1=laticematrix[0] self._calculationValues['Lattice Matrix a1 in bohr'] = \ latticematrixa1; latticematrixa2=laticematrix[1] self._calculationValues['Lattice Matrix a2 in bohr'] = \ latticematrixa2; latticematrixa3=laticematrix[2] self._calculationValues['Lattice Matrix a3 in bohr'] = \ latticematrixa3; self._calculationValues['Lattice Matrix in bohr'] = \ parser_outputd_handle['BR2_DIR Matrix']; self._calculationValues['Lattice Matrix in m'] = \ [[ bohrToMeters(i) for i in j ] for j in \ self._calculationValues['Lattice Matrix in bohr']]; self._calculationValues['Number of Atoms in Unit Cell'] = \ parser_outputd_handle['Number of Atoms in Unit Cell']; self._calculationValues['Lattice Constants in bohr'] = \ parser_outputd_handle['Lattice Constants']; self._calculationValues['Lattice Constants in m'] = \ [ bohrToMeters(i) for i in \ self._calculationValues['Lattice Constants in bohr']]; #### *.outputst handle # for each element: # - Core Value # - Spin Value 1 # - Spin Value 2 self._calculationValues['Element Listing'] = \ parser_outputst_handle['Element List']; #### ######################## # get electronic phase # ######################## # get raw list [k-points, phase] phaseDirSpinPathRaw = args['phases'] # wrap phases in the range [-pi ... +pi] phaseDirSpinPathWrp11 = self.wrpPhase(phaseDirSpinPathRaw, \ self.wrp11); # print nice print "\n","Initial Berry phases and their", \ "wrapped values in the range [-pi ... +pi]"; print "="*87 print " "*30, "| init k-point", "| phase raw (rad)", \ "| phase wrap. (rad)" icoord = -1 for coord in phaseDirSpinPathRaw: icoord += 1 print "-"*87 print "direction(%u)" % int(icoord + 1) ispin = -1 for spin in coord: ispin += 1 print " "*12, "spin(%u)" % int(ispin + 1) ipath = -1 for path in spin: ipath += 1 # perform wraping using the method privided in input kpt = phaseDirSpinPathRaw[icoord][ispin][ipath][0] ph = phaseDirSpinPathRaw[icoord][ispin][ipath][1] phwrp = phaseDirSpinPathWrp11[icoord][ispin][ipath][1] print " "*20, "path(%4d) %4d % e % e" \ % (ipath+1, kpt, ph, phwrp) print "="*87 print "\n","CALCULATION OF ELECTRONIC POLARIZATION" print "="*87 print "Value", " "*25, "| spin ", "| ", "dir(1) ", \ "| ", "dir(2) ", "| ", "dir(3)" print "-"*87 # find path-average phase phaseDirSpinWrp11 = self.pathAvrgPhase(phaseDirSpinPathWrp11); # wrap the average phase again as it can go out of bounds [-pi..+pi] phaseDirSpinWrp11 = self.wrp11(phaseDirSpinWrp11); nspins = numpy.shape(phaseDirSpinWrp11)[1] for spinIndex in range(0,nspins): print "Berry phase (rad) [-pi ... +pi] sp(%1i)" \ % (spinIndex+1), \ " [% e, % e, % e]" % tuple(phaseDirSpinWrp11[:,spinIndex]); if not spCalc and not args['so']: # in case of non-SP or non-SO calculation... phaseDirSpinWrp11 = 2*phaseDirSpinWrp11 # account for the spin degeneracy nspins = numpy.shape(phaseDirSpinWrp11)[1] if nspins != 1: # double check print "Inconsistency detected in the number of spins" print "Is it spin-polarized calculation? spCalc =", spCalc print "Number of spins in the electronic phase array", \ nspins; print "Expected 1 spin" print "Decision is taken to EXIT" sys.exit(2) print "Berry phase (rad) up+dn "+ \ "[% e, % e, % e]" % tuple(phaseDirSpinWrp11); # wrap phases again [-pi ... +pi] phaseDirSpinWrp11 = self.wrp11(phaseDirSpinWrp11) print "Berry phase (rad) [-pi ... +pi] " +\ " up+dn [% e, % e, % e]" \ % tuple(phaseDirSpinWrp11); #electron charge / cell volume self.ELEC_BY_VOL_CONST = ELECTRON_CHARGE / \ bohrToMeters(self._calculationValues['Cell Volume in bohr^3'], \ dimension = 3.); # electronic polarization (C/m2) elP = self.elPolarization(phaseDirSpinWrp11,self._calculationValues, \ self.ELEC_BY_VOL_CONST); # ionic polarization (C/m2) ionP = self.determineIonPolarization(self.wrp11,args) # Total polarization (C/m2) will be returned # when calling mainCalculation() self._totalPolarizationVal = self.totalPolarization(elP, ionP)
def __init__(self, **args): # spin polarization: yes/no spCalc = args['sp'] ############################ ######### PARSING ########## ############################ ###Rest of the Files### #parse all the things! #### *.struct file parser parser_struct_handle = open(args['file_struct'], 'r').readlines() parser_struct_handle = b_PyParse.MainStructParser(parser_struct_handle) parser_struct_handle.parse() #### *.inc parser parser_inc_handle = open(args['file_inc'], 'r').readlines() parser_inc_handle = b_PyParse.MainIncParser(parser_inc_handle) parser_inc_handle.parse() ##################################### ############ END Parsing ############ ##################################### ############################# ###### Getting Values ####### ############################# self._calculationValues = orderedDict() #### *.struct handle # - name of atoms # - MULT for each atom # - coordinates # - nuclear charge self._calculationValues['Atom Listing'] = \ parser_struct_handle['Atom Listing'] # - Cell Volume self._calculationValues['Cell Volume in bohr^3'] = \ parser_struct_handle['cell volume'] # - Lattice Constants (a,b,c) self._calculationValues['Lattice Constants in bohr'] = \ parser_struct_handle['lattice constants'] # - lattice vectors BR2_DIR matrix (v_x, v_y, v_z) self._calculationValues['Lattice Matrix in bohr'] = \ parser_struct_handle['real space lattice vectors'] ### *.inc handle # - core charge for each non-eqivalent atom self._calculationValues['Atom core charges'] = \ parser_inc_handle['core charges'] # check consistency of case.struct and case.inc files if len(self._calculationValues['Atom core charges']) != \ len(self._calculationValues['Atom Listing']): print("Number of non-equivalent atoms in case.struct:", \ len(self._calculationValues['Atom Listing'])) print("Number of non-equivalent atoms in case.inc:", \ len(self._calculationValues['Atom core charges'])) raise Exception("Inconsistent number of non-equivalent atoms") ######################## # get electronic phase # ######################## # get raw list [k-points, phase] phaseDirSpinPathRaw = args['phases'] # wrap phases in the range [-pi ... +pi] phaseDirSpinPathWrp11 = self.wrpPhase(phaseDirSpinPathRaw, \ self.wrp11) # print nice print("\n","Initial Berry phases and their", \ "wrapped values in the range [-pi ... +pi]") print("=" * 87) print(" "*30, "| init k-point", "| phase raw (rad)", \ "| phase wrap. (rad)") icoord = -1 for coord in phaseDirSpinPathRaw: icoord += 1 print("-" * 87) print("direction(%u)" % int(icoord + 1)) ispin = -1 for spin in coord: ispin += 1 print(" " * 12, "spin(%u)" % int(ispin + 1)) ipath = -1 for path in spin: ipath += 1 # perform wraping using the method privided in input kpt = phaseDirSpinPathRaw[icoord][ispin][ipath][0] ph = phaseDirSpinPathRaw[icoord][ispin][ipath][1] phwrp = phaseDirSpinPathWrp11[icoord][ispin][ipath][1] print(" "*20, "path(%4d) %4d % e % e" \ % (ipath+1, kpt, ph, phwrp)) print("=" * 87) print("\n", "CALCULATION OF ELECTRONIC POLARIZATION") print("=" * 87) print("Value", " "*25, "| spin ", "| ", "dir(1) ", \ "| ", "dir(2) ", "| ", "dir(3)") print("-" * 87) # find path-average phase phaseDirSpinWrp11 = self.pathAvrgPhase(phaseDirSpinPathWrp11) # wrap the average phase again as it can go out of bounds [-pi..+pi] phaseDirSpinWrp11 = self.wrp11(phaseDirSpinWrp11) nspins = numpy.shape(phaseDirSpinWrp11)[1] for spinIndex in range(0, nspins): print("Berry phase (rad) [-pi ... +pi] sp(%1i)" \ % (spinIndex+1), \ " [% e, % e, % e]" % tuple(phaseDirSpinWrp11[:,spinIndex])) if not spCalc and not args[ 'so']: # in case of non-SP or non-SO calculation... phaseDirSpinWrp11 = 2 * phaseDirSpinWrp11 # account for the spin degeneracy nspins = numpy.shape(phaseDirSpinWrp11)[1] if nspins != 1: # double check print("Inconsistency detected in the number of spins") print("Is it spin-polarized calculation? spCalc =", spCalc) print("Number of spins in the electronic phase array", \ nspins) print("Expected 1 spin") print("Decision is taken to EXIT") sys.exit(2) print("Berry phase (rad) up+dn "+ \ "[% e, % e, % e]" % tuple(phaseDirSpinWrp11)) # wrap phases again [-pi ... +pi] phaseDirSpinWrp11 = self.wrp11(phaseDirSpinWrp11) print("Berry phase (rad) [-pi ... +pi] " +\ " up+dn [% e, % e, % e]" \ % tuple(phaseDirSpinWrp11)) #electron charge / cell volume self.ELEC_BY_VOL_CONST = ELECTRON_CHARGE / \ bohrToMeters(self._calculationValues['Cell Volume in bohr^3'], \ dimension = 3.) # electronic polarization (C/m2) elP = self.elPolarization(phaseDirSpinWrp11,self._calculationValues, \ self.ELEC_BY_VOL_CONST) # ionic polarization (C/m2) ionP = self.determineIonPolarization(self.wrp11, args) # Total polarization (C/m2) will be returned # when calling mainCalculation() self._totalPolarizationVal = self.totalPolarization(elP, ionP)